Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions app/how-it-works/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,13 @@ export default function HowItWorksPage() {
</P>
<P>
<strong className="text-chalk">One system for humans and agents.</strong> The registry
endpoints are machine-readable JSON with full source, so an AI coding assistant
installs and composes these components exactly the way a person does. Agent
interfaces, installable by agents, is not a gimmick; it&apos;s the distribution
model.
endpoints are machine-readable JSON with full source, and the contract an agent
consumes is generated from the component TypeScript at build time: descriptions,
props, and principle tags are parsed from source, never hand-typed, so the feed
cannot drift. A dense, prose-free version sized for a context window lives at{" "}
<code className="rounded bg-asphalt px-1.5 py-0.5 font-mono text-[13px] text-chalk">/r/contract.json</code>
, and it carries something prop schemas can&apos;t: the behavioral guarantees each
component makes, derived from the principles.
</P>
<P>
<strong className="text-chalk">Designed for the pit, not the lap.</strong> The system
Expand Down
202 changes: 146 additions & 56 deletions scripts/build-registry.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
/**
* Generates shadcn-compatible registry JSON for every component in /registry.
* Output: public/r/<name>.json — consumed by:
* npx shadcn@latest add https://agent-pitstop.vercel.app/r/<name>.json
* Generates the machine contract for every component in /registry.
*
* The rule (per the "agent-ready" bar): nothing an agent consumes is
* hand-typed. Descriptions, props, and principle tags are parsed from
* the component source at build time, so the contract cannot drift
* from the truth. Outputs:
*
* public/r/<name>.json shadcn registry item (full source embedded)
* + generated `meta` (description, props, principles)
* public/r/index.json catalog, one fetch discovers everything
* public/r/contract.json dense mode: prose-free, sized for a context window
* public/llms.txt human-shaped overview, generated from the same parse
*
* Runs automatically before `next build` (see "prebuild" in package.json).
*/
import { readdir, readFile, mkdir, writeFile } from "node:fs/promises";
Expand All @@ -10,62 +20,108 @@ import path from "node:path";
const ROOT = process.cwd();
const REGISTRY_DIR = path.join(ROOT, "registry");
const OUT_DIR = path.join(ROOT, "public", "r");
const SITE = "https://agent-pitstop.vercel.app";

/** Parse the component's header comment: title, description, principles applied. */
function parseHeader(src) {
const block = src.match(/\/\*\*([\s\S]*?)\*\//);
if (!block) return { title: null, description: "", principles: [] };
const lines = block[1]
.split("\n")
.map((l) => l.replace(/^\s*\*\s?/, "").trim());

const titleLine = lines.find((l) => l.includes("— Agent Pit Stop"));
const title = titleLine ? titleLine.split("—")[0].trim() : null;

const descLines = [];
let started = false;
for (const l of lines) {
if (l.includes("— Agent Pit Stop")) {
started = true;
continue;
}
if (!started) continue;
if (l.startsWith("Principles applied") || l.startsWith("Contributor notes")) break;
if (l) descLines.push(l);
}
const description = descLines.join(" ").trim();

const principles = [];
const pStart = lines.findIndex((l) => l.startsWith("Principles applied"));
if (pStart !== -1) {
for (let i = pStart + 1; i < lines.length; i++) {
const m = lines[i].match(/^-\s*([A-Za-z ]+?):/);
if (m) principles.push(m[1].trim());
else if (lines[i].startsWith("-") === false && lines[i] !== "") break;
}
}
return { title, description, principles };
}

/** Parse `export interface <Component>Props { ... }` into structured props. */
function parseProps(src, componentName) {
const iface = src.match(
new RegExp(`export interface ${componentName}Props \\{([\\s\\S]*?)\\n\\}`),
);
if (!iface) return [];
const body = iface[1];
const props = [];
const re = /(?:\/\*\*\s*([\s\S]*?)\s*\*\/\s*)?(\w+)(\?)?:\s*([^;]+);/g;
let m;
while ((m = re.exec(body)) !== null) {
props.push({
name: m[2],
type: m[4].trim().replace(/\s+/g, " "),
required: m[3] !== "?",
description: (m[1] ?? "").replace(/\s+/g, " ").trim(),
});
}
return props;
}

const meta = {
"tool-call-card": {
title: "Tool Call Card",
description: "Show an agent's tool use with legible, progressive disclosure.",
},
"approval-gate": {
title: "Approval Gate",
description: "Inline permission request with itemized scope. Nothing moves until the human says so.",
},
"agent-task-list": {
title: "Agent Task List",
description: "The agent's plan, made visible: pending, active, done, and failed.",
},
"interrupt-bar": {
title: "Interrupt Bar",
description: "The steering wheel and the brake for a running agent. Stop keeps partial work; steering redirects without restarting.",
},
"confidence-meter": {
title: "Confidence Meter",
description: "Honest uncertainty as coarse bands, paired with a verification affordance below the threshold.",
},
"agent-roster": {
title: "Agent Roster",
description: "The team sheet for multi-agent runs: role, state, and live activity per agent, blockers named.",
},
"agent-inbox": {
title: "Agent Inbox",
description: "The review queue for background agents. Needs-review items carry the reason, not just a red dot.",
},
"context-budget": {
title: "Context Budget",
description: "A delegation contract's limits made visible: consumption vs caps in honest bands, cap-hit as a rendered state.",
},
};
function pascal(name) {
return name
.split("-")
.map((p) => p[0].toUpperCase() + p.slice(1))
.join("");
}

await mkdir(OUT_DIR, { recursive: true });

const entries = await readdir(REGISTRY_DIR, { withFileTypes: true });
let count = 0;
const index = [];
const dense = [];

for (const entry of entries) {
if (!entry.isDirectory() || entry.name.startsWith("_")) continue;
const name = entry.name;
const dir = path.join(REGISTRY_DIR, name);
const files = (await readdir(dir)).filter((f) => f.endsWith(".tsx") || f.endsWith(".ts"));
const mainSrc = await readFile(path.join(dir, `${name}.tsx`), "utf8");

const header = parseHeader(mainSrc);
const componentName = pascal(name);
const props = parseProps(mainSrc, componentName);
const title = header.title ?? componentName;
// first sentence of the parsed description = the one-liner
const oneLiner = (header.description.match(/^.*?[.!?](\s|$)/)?.[0] ?? header.description).trim();

const item = {
$schema: "https://ui.shadcn.com/schema/registry-item.json",
name,
type: "registry:component",
title: meta[name]?.title ?? name,
description: meta[name]?.description ?? "",
title,
description: oneLiner,
author: "Anton Castro <https://github.com/acaspx>",
dependencies: ["motion"],
meta: {
component: componentName,
longDescription: header.description,
principles: header.principles,
props,
docs: `${SITE}/components/${name}`,
},
files: await Promise.all(
files.map(async (f) => ({
path: `registry/${name}/${f}`,
Expand All @@ -78,49 +134,83 @@ for (const entry of entries) {
await writeFile(path.join(OUT_DIR, `${name}.json`), JSON.stringify(item, null, 2));
index.push({
name,
title: item.title,
description: item.description,
url: `https://agent-pitstop.vercel.app/r/${name}.json`,
docs: `https://agent-pitstop.vercel.app/components/${name}`,
title,
description: oneLiner,
principles: header.principles,
url: `${SITE}/r/${name}.json`,
docs: `${SITE}/components/${name}`,
});
dense.push({
n: name,
c: componentName,
d: oneLiner,
pr: header.principles,
p: props.map((p) => `${p.name}${p.required ? "" : "?"}:${p.type}${p.description ? ` // ${p.description}` : ""}`),
});
count++;
}

// machine-readable catalog: one fetch discovers every component
// full catalog: one fetch discovers every component
await writeFile(path.join(OUT_DIR, "index.json"), JSON.stringify({ items: index }, null, 2));

// llms.txt: the whole system, consumable by an AI assistant in one request
// dense mode: prose-free, sized for an agent's context window
await writeFile(
path.join(OUT_DIR, "contract.json"),
JSON.stringify(
{
system: "agent-pitstop",
install: `npx shadcn@latest add ${SITE}/r/<n>.json`,
deps: ["react", "tailwind", "motion"],
guarantees: [
"state is never communicated by color alone",
"failures keep their inputs visible",
"stopping preserves partial work",
"nothing irreversible without explicit consent",
"confidence renders as bands, never decimals",
],
components: dense,
},
null,
1,
),
);

// llms.txt: human-shaped overview, generated from the same parse
const llms = `# Agent Pit Stop

> An open source design system for agent interfaces: UX principles with live
> demos and production React components for the moments where agents and
> humans sync (approval, inspection, handoff, recovery). MIT licensed.
> Components depend only on React, Tailwind, and Motion, and install via the
> shadcn registry. Site: https://agent-pitstop.vercel.app
> shadcn registry. Site: ${SITE}

## Install

Any component: npx shadcn@latest add https://agent-pitstop.vercel.app/r/<name>.json
Catalog (JSON): https://agent-pitstop.vercel.app/r/index.json
Any component: npx shadcn@latest add ${SITE}/r/<name>.json
Catalog (JSON): ${SITE}/r/index.json
Dense contract for agents (props, principles, guarantees): ${SITE}/r/contract.json
This file, the catalog, and the contract are generated from component source at build time; they cannot drift.

## Components

${index.map((i) => `- [${i.title}](${i.docs}): ${i.description} (source: ${i.url})`).join("\n")}

## Principles

- [Legible Thinking](https://agent-pitstop.vercel.app/principles/legible-thinking): agents lose trust by being wrong invisibly; show intent, state, and evidence
- [Interruptibility](https://agent-pitstop.vercel.app/principles/interruptibility): users delegate more to agents they can stop; steer beats stop
- [Delegation Contracts](https://agent-pitstop.vercel.app/principles/delegation-contracts): itemize scope before consent; boundaries beat permissions
- [Calibrated Trust](https://agent-pitstop.vercel.app/principles/calibrated-trust): bands not decimals; verification cheaper than redoing
- [Graceful Failure](https://agent-pitstop.vercel.app/principles/graceful-failure): failure is a rendered state; partial work is never discarded
- [Legible Thinking](${SITE}/principles/legible-thinking): agents lose trust by being wrong invisibly; show intent, state, and evidence
- [Interruptibility](${SITE}/principles/interruptibility): users delegate more to agents they can stop; steer beats stop
- [Delegation Contracts](${SITE}/principles/delegation-contracts): itemize scope before consent; boundaries beat permissions
- [Calibrated Trust](${SITE}/principles/calibrated-trust): bands not decimals; verification cheaper than redoing
- [Graceful Failure](${SITE}/principles/graceful-failure): failure is a rendered state; partial work is never discarded

## Docs

- [How it works](https://agent-pitstop.vercel.app/how-it-works)
- [Rules for the pit](https://agent-pitstop.vercel.app/principles)
- [How it works](${SITE}/how-it-works)
- [Rules for the pit](${SITE}/principles)
- [Contributing](https://github.com/acaspx/agent-pitstop/blob/main/CONTRIBUTING.md)
`;
await writeFile(path.join(ROOT, "public", "llms.txt"), llms);

console.log(`registry: wrote ${count} item(s) + index.json + llms.txt to public/`);
console.log(
`registry: wrote ${count} item(s) + index.json + contract.json + llms.txt (all generated from source)`,
);
Loading