Skip to content

Commit 5edfb19

Browse files
committed
docs: add claude-code harness, real product logos, top-level auto-discovery
- Add claude-code harness page (Claude Code + Hindsight MCP flow) - Replace placeholder ◉ glyphs with the real product marks (Claude orange-C from Simple Icons, OpenClaw pixel-lobster from github.com/openclaw/openclaw, NVIDIA from Simple Icons) via a reusable HarnessLogo component - Auto-discover any top-level template folder at the repo root that has a bank-template.json, so adding e.g. engineering/ at the root makes it appear in the docs on the next push to main with no code change
1 parent eed83a1 commit 5edfb19

12 files changed

Lines changed: 628 additions & 58 deletions

File tree

website/public/logos/claude.svg

Lines changed: 1 addition & 0 deletions
Loading

website/public/logos/nvidia.svg

Lines changed: 1 addition & 0 deletions
Loading

website/public/logos/openclaw-wordmark.svg

Lines changed: 418 additions & 0 deletions
Loading

website/public/logos/openclaw.svg

Lines changed: 60 additions & 0 deletions
Loading
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
import type { Harness } from '../lib/harnesses';
3+
4+
interface Props {
5+
harness: Harness;
6+
/** css size class, e.g. "h-9 w-9" — default for card thumbnails */
7+
size?: string;
8+
/** "tile" wraps logo in a soft chip; "bare" renders the SVG directly */
9+
variant?: 'tile' | 'bare';
10+
}
11+
12+
const { harness, size = 'h-9 w-9', variant = 'tile' } = Astro.props;
13+
14+
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
15+
const logoUrl = `${base}/${harness.logo.replace(/^\//, '')}`;
16+
17+
const containerCls =
18+
variant === 'tile'
19+
? `inline-flex ${size} items-center justify-center rounded-lg border border-ink-200 bg-white p-1.5 shadow-sm`
20+
: `inline-flex ${size} items-center justify-center`;
21+
---
22+
23+
<span class={containerCls} aria-hidden="true">
24+
<img
25+
src={logoUrl}
26+
alt=""
27+
class="h-full w-full object-contain"
28+
loading="lazy"
29+
decoding="async"
30+
/>
31+
</span>

website/src/lib/agents.ts

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,23 @@ import matter from 'gray-matter';
55

66
const __dirname = path.dirname(fileURLToPath(import.meta.url));
77
const ROOT = path.resolve(__dirname, '../../..');
8-
const TEMPLATES_DIR = path.join(ROOT, 'marketing');
8+
9+
/**
10+
* Top-level repo dirs that are NOT agent templates and should be skipped
11+
* when auto-discovering. Anything else with a bank-template.json shows up.
12+
*/
13+
const SKIP_AT_ROOT = new Set([
14+
'node_modules',
15+
'dist',
16+
'website',
17+
'src',
18+
'public',
19+
'.git',
20+
'.github',
21+
'.astro',
22+
'.vscode',
23+
'.idea',
24+
]);
925

1026
export interface AgentFile {
1127
fileName: string;
@@ -159,31 +175,42 @@ function buildNode(absDir: string, segments: string[]): AgentNode | null {
159175
};
160176
}
161177

162-
let cached: AgentNode | null = null;
178+
let cachedRoots: AgentNode[] | null = null;
163179

164180
/**
165-
* Load the top-level "marketing" agent (and recursively all sub-agents).
181+
* Discover top-level agent templates by scanning the repo root for any
182+
* directory containing a bank-template.json. Each match becomes a top-level
183+
* agent and its subtree is loaded recursively.
184+
*
185+
* Drop a new directory like `engineering/bank-template.json` at the repo root
186+
* and it shows up in the docs on the next build — no code changes needed.
166187
*/
167-
export function loadRootAgent(): AgentNode {
168-
if (cached) return cached;
169-
const node = buildNode(TEMPLATES_DIR, ['marketing']);
170-
if (!node) {
171-
throw new Error(`No bank-template.json found in ${TEMPLATES_DIR}`);
188+
export function loadRoots(): AgentNode[] {
189+
if (cachedRoots) return cachedRoots;
190+
const entries = fs.readdirSync(ROOT, { withFileTypes: true });
191+
const roots: AgentNode[] = [];
192+
for (const e of entries) {
193+
if (!e.isDirectory()) continue;
194+
if (e.name.startsWith('.') || SKIP_AT_ROOT.has(e.name)) continue;
195+
const node = buildNode(path.join(ROOT, e.name), [e.name]);
196+
if (node) roots.push(node);
172197
}
173-
cached = node;
174-
return node;
198+
roots.sort((a, b) => a.key.localeCompare(b.key));
199+
cachedRoots = roots;
200+
return roots;
175201
}
176202

177203
/**
178-
* Flatten the tree into an array of all agent nodes (root + descendants).
204+
* Flatten the tree into an array of every agent node across every top-level
205+
* template (each root + all descendants).
179206
*/
180-
export function flattenAgents(root: AgentNode = loadRootAgent()): AgentNode[] {
207+
export function flattenAgents(roots: AgentNode[] = loadRoots()): AgentNode[] {
181208
const out: AgentNode[] = [];
182209
const walk = (n: AgentNode) => {
183210
out.push(n);
184211
for (const c of n.children) walk(c);
185212
};
186-
walk(root);
213+
for (const r of roots) walk(r);
187214
return out;
188215
}
189216

website/src/lib/harnesses.ts

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,55 @@ export interface Harness {
1414
/** external links */
1515
links: { label: string; href: string }[];
1616
color: string;
17+
/** path under /public, relative (no base prefix). square mark used in cards. */
18+
logo: string;
19+
/** optional wider wordmark for hero contexts. */
20+
wordmark?: string;
1721
}
1822

1923
export const HARNESSES: Harness[] = [
24+
{
25+
slug: 'claude-code',
26+
name: 'Claude Code',
27+
tagline:
28+
'Anthropic\'s Claude Code CLI plus the Hindsight MCP server. No skill upload, no gateway.',
29+
flag: '--harness claude-code',
30+
intro:
31+
'The claude-code harness pairs the Claude Code CLI with the Hindsight MCP server. The self-driving-agents CLI stages the agent files locally, auto-approves the Hindsight tools and create-agent skill in your Claude Code settings, and prints a one-line prompt you paste to finish the install.',
32+
audience:
33+
'Use this harness if you already use Claude Code in your terminal and want a self-driving agent backed by Hindsight memory — no skill zip, no separate gateway.',
34+
steps: [
35+
{
36+
title: 'Install the agent',
37+
body: 'The CLI copies the template content to ~/.self-driving-agents/claude-code/<agent>/ and updates ~/.claude/settings.json to allow the Hindsight MCP tools, the create-agent skill, and read-only Bash on the staged dir.',
38+
code: 'npx @vectorize-io/self-driving-agents install marketing/seo --harness claude-code',
39+
},
40+
{
41+
title: 'Make sure the Hindsight plugin is installed',
42+
body:
43+
'Claude Code reaches Hindsight via the hindsight-memory plugin (MCP server + create-agent skill). Install it once and you\'re set for every agent.',
44+
},
45+
{
46+
title: 'Start Claude Code and paste the printed prompt',
47+
body:
48+
'Open Claude Code in any project. Paste the prompt the CLI printed at the end of install — it tells Claude to run /hindsight-memory:create-agent, ingest the staged files, and create the mental models declared in bank-template.json.',
49+
code:
50+
'Use /hindsight-memory:create-agent to create a "marketing-seo" agent. Then ingest all files from ~/.self-driving-agents/claude-code/marketing-seo/ (skip bank-template.json). Read ~/.self-driving-agents/claude-code/marketing-seo/bank-template.json and create the exact mental models (knowledge pages) defined in its "mental_models" array using agent_knowledge_create_page for each one.',
51+
},
52+
],
53+
howItWorks: [
54+
'The CLI stages every .md/.txt plus bank-template.json under ~/.self-driving-agents/claude-code/<agent>/.',
55+
'Your ~/.claude/settings.json gets allowedTools entries for mcp__hindsight__*, Skill(hindsight-memory:create-agent), and Bash(ls/cat ~/.self-driving-agents/*) so the next steps run without approval prompts.',
56+
'When you run the printed prompt, the create-agent skill provisions the Hindsight bank, ingests the staged content as seed knowledge, and creates one knowledge page per mental_model from bank-template.json.',
57+
'From then on, the agent uses the Hindsight MCP tools to load pages at session start, retain feedback during the chat, and update its own pages after the conversation ends.',
58+
],
59+
links: [
60+
{ label: 'Claude Code', href: 'https://docs.anthropic.com/en/docs/claude-code' },
61+
{ label: 'Hindsight', href: 'https://github.com/vectorize-io/hindsight' },
62+
],
63+
color: '#D97757',
64+
logo: 'logos/claude.svg',
65+
},
2066
{
2167
slug: 'claude',
2268
name: 'Claude Chat & Cowork',
@@ -56,7 +102,8 @@ export const HARNESSES: Harness[] = [
56102
{ label: 'Claude.ai', href: 'https://claude.ai' },
57103
{ label: 'Claude Skills docs', href: 'https://support.anthropic.com/' },
58104
],
59-
color: '#D97706',
105+
color: '#D97757',
106+
logo: 'logos/claude.svg',
60107
},
61108
{
62109
slug: 'openclaw',
@@ -90,13 +137,15 @@ export const HARNESSES: Harness[] = [
90137
'The seed .md files are ingested as initial facts so the agent ships with prior knowledge instead of starting blank.',
91138
],
92139
links: [
93-
{ label: 'OpenClaw', href: 'https://openclaw.dev' },
140+
{ label: 'OpenClaw', href: 'https://github.com/openclaw/openclaw' },
94141
{
95142
label: 'Hindsight plugin',
96143
href: 'https://www.npmjs.com/package/@vectorize-io/hindsight-openclaw-plugin',
97144
},
98145
],
99-
color: '#059669',
146+
color: '#E26B3A',
147+
logo: 'logos/openclaw.svg',
148+
wordmark: 'logos/openclaw-wordmark.svg',
100149
},
101150
{
102151
slug: 'nemoclaw',
@@ -133,6 +182,7 @@ export const HARNESSES: Harness[] = [
133182
{ label: 'NVIDIA NeMo Agent', href: 'https://github.com/NVIDIA/NeMo-Agent' },
134183
],
135184
color: '#76B900',
185+
logo: 'logos/nvidia.svg',
136186
},
137187
];
138188

website/src/pages/agents/[...slug].astro

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
---
22
import Layout from '../../layouts/Layout.astro';
33
import CodeBlock from '../../components/CodeBlock.astro';
4-
import { flattenAgents, loadRootAgent, type AgentNode, type AgentFile } from '../../lib/agents';
4+
import { flattenAgents, type AgentNode, type AgentFile } from '../../lib/agents';
5+
import { HARNESSES } from '../../lib/harnesses';
56
import { marked } from 'marked';
67
78
export async function getStaticPaths() {
89
const agents = flattenAgents();
9-
const root = loadRootAgent();
1010
return agents.map((agent) => ({
1111
params: { slug: agent.slug },
12-
props: { agent, rootSlug: root.slug },
12+
props: { agent },
1313
}));
1414
}
1515
1616
interface Props {
1717
agent: AgentNode;
18-
rootSlug: string;
1918
}
20-
const { agent, rootSlug } = Astro.props;
19+
const { agent } = Astro.props;
2120
2221
const base = import.meta.env.BASE_URL;
2322
const link = (p: string) => `${base.replace(/\/$/, '')}/${p.replace(/^\//, '')}`;
@@ -29,8 +28,8 @@ const crumbs = segments.map((seg, i) => {
2928
return { label: seg, href: link(`/agents/${slug}`) };
3029
});
3130
32-
const installCmd = (h: string) =>
33-
`npx @vectorize-io/self-driving-agents install ${agent.slug} --harness ${h}`;
31+
const installCmd = (slug: string) =>
32+
`npx @vectorize-io/self-driving-agents install ${agent.slug} --harness ${slug}`;
3433
3534
const renderMd = (md: string): string => {
3635
// simple, safe-enough rendering — content is from this repo
@@ -100,12 +99,8 @@ const colorOf = (f: AgentFile) => {
10099
Pick the harness that matches where you'll chat with the agent.
101100
</p>
102101

103-
<div class="mt-5 grid gap-4 md:grid-cols-3">
104-
{[
105-
{ slug: 'claude', name: 'Claude Chat / Cowork' },
106-
{ slug: 'openclaw', name: 'OpenClaw' },
107-
{ slug: 'nemoclaw', name: 'NemoClaw' },
108-
].map((h) => (
102+
<div class="mt-5 grid gap-4 sm:grid-cols-2">
103+
{HARNESSES.map((h) => (
109104
<div class="rounded-xl border border-ink-200 bg-white p-4">
110105
<div class="mb-2 flex items-center justify-between">
111106
<span class="text-sm font-semibold text-ink-900">{h.name}</span>

website/src/pages/agents/index.astro

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
---
22
import Layout from '../../layouts/Layout.astro';
33
import AgentCard from '../../components/AgentCard.astro';
4-
import { loadRootAgent } from '../../lib/agents';
4+
import { loadRoots } from '../../lib/agents';
55
6-
const root = loadRootAgent();
7-
const all = [root, ...root.children];
6+
const roots = loadRoots();
7+
const all = roots.flatMap((r) => [r, ...r.children]);
88
99
const base = import.meta.env.BASE_URL;
1010
const link = (p: string) => `${base.replace(/\/$/, '')}/${p.replace(/^\//, '')}`;

website/src/pages/harnesses/[slug].astro

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
import Layout from '../../layouts/Layout.astro';
33
import CodeBlock from '../../components/CodeBlock.astro';
4+
import HarnessLogo from '../../components/HarnessLogo.astro';
45
import { HARNESSES, type Harness } from '../../lib/harnesses';
56
67
export function getStaticPaths() {
@@ -29,13 +30,7 @@ const link = (p: string) => `${base.replace(/\/$/, '')}/${p.replace(/^\//, '')}`
2930
<span>{harness.slug}</span>
3031
</nav>
3132

32-
<span
33-
class="inline-flex h-12 w-12 items-center justify-center rounded-xl text-white"
34-
style={`background-color: ${harness.color};`}
35-
aria-hidden="true"
36-
>
37-
38-
</span>
33+
<HarnessLogo harness={harness} size="h-14 w-14" />
3934
<h1 class="mt-4 text-3xl font-bold text-ink-900 sm:text-4xl">
4035
{harness.name}
4136
</h1>

0 commit comments

Comments
 (0)