API Reference
Complete reference for every public export across all Capstan packages. This is the authoritative source for function signatures, types, interfaces, and CLI commands.
@zauso-ai/capstan-core
The core framework package. Provides the server, routing primitives, policy engine, approval workflow, and application verifier.
defineAPI(def)
Define a typed API route handler with input/output validation and agent introspection.
function defineAPI<TInput = unknown, TOutput = unknown>(
def: APIDefinition<TInput, TOutput>,
): APIDefinition<TInput, TOutput>Parameters:
interface APIDefinition<TInput = unknown, TOutput = unknown> {
input?: z.ZodType<TInput>;
output?: z.ZodType<TOutput>;
description?: string;
capability?: "read" | "write" | "external";
resource?: string;
policy?: string;
handler: (args: { input: TInput; ctx: CapstanContext }) => Promise<TOutput>;
}The handler is wrapped to validate input (before) and output (after) against the provided Zod schemas. The definition is registered in a global registry for agent manifest generation.
defineConfig(config)
Identity function that provides type-checking and editor auto-complete for the app configuration.
function defineConfig(config: CapstanConfig): CapstanConfigCapstanConfig:
interface CapstanConfig {
app?: {
name?: string;
title?: string;
description?: string;
};
database?: {
provider?: "sqlite" | "postgres" | "mysql";
url?: string;
};
auth?: {
providers?: Array<{ type: string; [key: string]: unknown }>;
session?: {
strategy?: "jwt" | "database";
secret?: string;
maxAge?: string;
};
};
agent?: {
manifest?: boolean;
mcp?: boolean;
openapi?: boolean;
rateLimit?: {
default?: { requests: number; window: string };
perAgent?: boolean;
};
};
server?: {
port?: number;
host?: string;
};
}defineMiddleware(def)
Define a middleware for the request pipeline. Accepts either a full definition object or a bare handler function.
function defineMiddleware(
def: MiddlewareDefinition | MiddlewareDefinition["handler"],
): MiddlewareDefinition
interface MiddlewareDefinition {
name?: string;
handler: (args: {
request: Request;
ctx: CapstanContext;
next: () => Promise<Response>;
}) => Promise<Response>;
}definePolicy(def)
Define a named permission policy.
function definePolicy(def: PolicyDefinition): PolicyDefinition
interface PolicyDefinition {
key: string;
title: string;
effect: PolicyEffect;
check: (args: {
ctx: CapstanContext;
input?: unknown;
}) => Promise<PolicyCheckResult>;
}
type PolicyEffect = "allow" | "deny" | "approve" | "redact";
interface PolicyCheckResult {
effect: PolicyEffect;
reason?: string;
}defineRateLimit(config)
Define rate limiting rules with per-auth-type windows.
function defineRateLimit(config: RateLimitConfig): RateLimitConfig
interface RateLimitConfig {
default: { requests: number; window: string };
perAuthType?: {
anonymous?: { requests: number; window: string };
human?: { requests: number; window: string };
agent?: { requests: number; window: string };
};
}enforcePolicies(policies, ctx, input?)
Run all provided policies and return the most restrictive result. All policies are evaluated (no short-circuiting). Severity order: allow < redact < approve < deny.
function enforcePolicies(
policies: PolicyDefinition[],
ctx: CapstanContext,
input?: unknown,
): Promise<PolicyCheckResult>env(key)
Read an environment variable, returning an empty string if not set.
function env(key: string): stringcreateCapstanApp(config)
Build a fully-wired Capstan application backed by a Hono server.
function createCapstanApp(config: CapstanConfig): CapstanApp
interface CapstanApp {
app: Hono;
routeRegistry: RouteMetadata[];
registerAPI: (
method: HttpMethod,
path: string,
apiDef: APIDefinition,
policies?: PolicyDefinition[],
) => void;
}The returned registerAPI method mounts an API definition as an HTTP route and records metadata in routeRegistry. The Hono app includes CORS middleware, context injection, approval endpoints, and the agent manifest endpoint at /.well-known/capstan.json.
clearAPIRegistry()
Clear all entries from the global API registry. Called automatically by createCapstanApp().
function clearAPIRegistry(): voidgetAPIRegistry()
Return all API definitions registered via defineAPI().
function getAPIRegistry(): ReadonlyArray<APIDefinition>createContext(honoCtx)
Create a CapstanContext from a Hono context.
function createContext(honoCtx: HonoContext): CapstanContextcreateCapstanOpsContext(config)
Create the semantic ops context used by the runtime request logger, policy engine, approval flow, and health snapshots.
function createCapstanOpsContext(config?: {
enabled?: boolean;
appName?: string;
source?: string;
recentWindowMs?: number;
retentionLimit?: number;
sink?: {
recordEvent(event: CapstanOpsEvent): Promise<void> | void;
close?(): Promise<void> | void;
};
}): CapstanOpsContext | undefinedcreateCapstanOpsRuntime(config)
Create the in-process semantic ops runtime. Records normalized events, derives incidents, emits health snapshots, and can fan out events to sinks.
function createCapstanOpsRuntime(config?: {
enabled?: boolean;
appName?: string;
source?: string;
recentWindowMs?: number;
retentionLimit?: number;
}): CapstanOpsRuntimeApproval Functions
// Create a pending approval
function createApproval(opts: {
method: string;
path: string;
input: unknown;
policy: string;
reason: string;
}): PendingApproval
// Get an approval by ID
function getApproval(id: string): PendingApproval | undefined
// List approvals, optionally filtered by status
function listApprovals(
status?: "pending" | "approved" | "denied",
): PendingApproval[]
// Approve or deny a pending approval
function resolveApproval(
id: string,
decision: "approved" | "denied",
resolvedBy?: string,
): PendingApproval | undefined
// Clear all approvals
function clearApprovals(): voidPendingApproval type:
interface PendingApproval {
id: string;
method: string;
path: string;
input: unknown;
policy: string;
reason: string;
status: "pending" | "approved" | "denied";
createdAt: string;
resolvedAt?: string;
resolvedBy?: string;
result?: unknown;
}mountApprovalRoutes(app, handlerRegistry)
Mount the approval management HTTP endpoints on a Hono app.
function mountApprovalRoutes(
app: Hono,
handlerRegistry: HandlerRegistry,
): voidverifyCapstanApp(appRoot)
Run the 7-step verification cascade against a Capstan application.
function verifyCapstanApp(appRoot: string): Promise<VerifyReport>
interface VerifyReport {
status: "passed" | "failed";
appRoot: string;
timestamp: string;
steps: VerifyStep[];
repairChecklist: Array<{
index: number;
step: string;
message: string;
file?: string;
line?: number;
hint?: string;
fixCategory?: string;
autoFixable?: boolean;
}>;
summary: {
totalSteps: number;
passedSteps: number;
failedSteps: number;
skippedSteps: number;
errorCount: number;
warningCount: number;
};
}renderRuntimeVerifyText(report)
Render a VerifyReport as human-readable text.
function renderRuntimeVerifyText(report: VerifyReport): stringdefinePlugin(def)
Define a reusable plugin that can add routes, policies, and middleware to a Capstan app.
function definePlugin(def: PluginDefinition): PluginDefinition
interface PluginDefinition {
name: string;
version?: string;
setup: (ctx: PluginSetupContext) => void;
}
interface PluginSetupContext {
addRoute: (method: HttpMethod, path: string, handler: APIDefinition) => void;
addPolicy: (policy: PolicyDefinition) => void;
addMiddleware: (path: string, handler: MiddlewareDefinition["handler"]) => void;
config: Readonly<CapstanConfig>;
}Load plugins via the plugins array in defineConfig().
KeyValueStore<T>
Pluggable key-value store interface used by approvals, rate limiting, and DPoP replay detection. Swap the default in-memory store for Redis or any external backend.
interface KeyValueStore<T> {
get(key: string): Promise<T | undefined>;
set(key: string, value: T, ttlMs?: number): Promise<void>;
delete(key: string): Promise<void>;
has(key: string): Promise<boolean>;
values(): Promise<T[]>;
clear(): Promise<void>;
}MemoryStore
Default in-memory implementation of KeyValueStore<T>.
class MemoryStore<T> implements KeyValueStore<T> {
constructor();
}Store Setters
Replace the default in-memory stores with custom KeyValueStore implementations.
function setApprovalStore(store: KeyValueStore<PendingApproval>): void
function setRateLimitStore(store: KeyValueStore<RateLimitEntry>): void
function setDpopReplayStore(store: KeyValueStore<boolean>): void
function setAuditStore(store: KeyValueStore<AuditEntry>): voidRedisStore
Redis-backed implementation of KeyValueStore<T>. Uses ioredis (optional peer dependency). All keys are prefixed with a configurable namespace to avoid collisions.
class RedisStore<T> implements KeyValueStore<T> {
constructor(redis: any, prefix?: string); // default prefix: "capstan:"
}Usage:
import Redis from "ioredis";
import { RedisStore, setApprovalStore, setAuditStore } from "@zauso-ai/capstan-core";
const redis = new Redis();
setApprovalStore(new RedisStore(redis, "myapp:approvals:"));
setAuditStore(new RedisStore(redis, "myapp:audit:"));defineCompliance(config)
Declare EU AI Act compliance metadata and enable audit logging.
function defineCompliance(config: ComplianceConfig): void
interface ComplianceConfig {
riskLevel: "minimal" | "limited" | "high" | "unacceptable";
auditLog?: boolean;
transparency?: {
description?: string;
provider?: string;
contact?: string;
};
}When auditLog is true, every defineAPI() handler invocation is automatically recorded. The audit log is served at GET /capstan/audit.
recordAuditEntry(entry)
Manually record a custom audit log entry.
function recordAuditEntry(entry: {
action: string;
authType?: string;
userId?: string;
resource?: string;
detail?: unknown;
}): voidgetAuditLog(filter?)
Retrieve audit log entries, optionally filtered.
function getAuditLog(filter?: {
action?: string;
authType?: string;
since?: string;
}): AuditEntry[]clearAuditLog()
Clear all audit log entries (useful in tests).
function clearAuditLog(): voiddefineWebSocket(path, handler)
Define a WebSocket route handler for real-time bidirectional communication.
function defineWebSocket(
path: string,
handler: WebSocketHandler,
): WebSocketRoute
interface WebSocketHandler {
onOpen?: (ws: WebSocketClient) => void;
onMessage?: (ws: WebSocketClient, message: string | ArrayBuffer) => void;
onClose?: (ws: WebSocketClient, code: number, reason: string) => void;
onError?: (ws: WebSocketClient, error: Error) => void;
}
interface WebSocketClient {
send(data: string | ArrayBuffer): void;
close(code?: number, reason?: string): void;
readonly readyState: number;
}
interface WebSocketRoute {
path: string;
handler: WebSocketHandler;
}Usage:
import { defineWebSocket } from "@zauso-ai/capstan-core";
export const chat = defineWebSocket("/ws/chat", {
onOpen(ws) { console.log("client connected"); },
onMessage(ws, message) { ws.send(`echo: ${message}`); },
onClose(ws, code, reason) { console.log("disconnected", code); },
});WebSocketRoom
Pub/sub room for broadcasting messages across connected clients.
class WebSocketRoom {
join(client: WebSocketClient): void;
leave(client: WebSocketClient): void;
broadcast(message: string, exclude?: WebSocketClient): void;
get size(): number;
close(): void;
}Usage:
import { defineWebSocket, WebSocketRoom } from "@zauso-ai/capstan-core";
const lobby = new WebSocketRoom();
export const ws = defineWebSocket("/ws/lobby", {
onOpen(ws) { lobby.join(ws); },
onMessage(ws, msg) { lobby.broadcast(String(msg), ws); },
onClose(ws) { lobby.leave(ws); },
});cacheSet(key, data, opts?)
Store a value in the cache with optional TTL, tags, and ISR revalidation.
function cacheSet<T>(key: string, data: T, opts?: CacheOptions): Promise<void>
interface CacheOptions {
ttl?: number; // Time-to-live in seconds
tags?: string[]; // Cache tags for bulk invalidation
revalidate?: number; // Revalidate interval in seconds (ISR)
}cacheGet(key)
Retrieve a cached value. Returns undefined on miss. Supports stale-while-revalidate when revalidate was set.
function cacheGet<T>(key: string): Promise<T | undefined>cacheInvalidateTag(tag)
Invalidate all cache entries associated with a tag. Also invalidates response cache entries with the same tag (cross-invalidation).
function cacheInvalidateTag(tag: string): Promise<void>cached(fn, opts?)
Stale-while-revalidate decorator. Wraps an async function with caching. Subsequent calls return the cached value until TTL expires, then revalidate in the background.
function cached<T>(
fn: () => Promise<T>,
opts?: CacheOptions & { key?: string },
): () => Promise<T>setCacheStore(store)
Replace the default in-memory cache store with a custom KeyValueStore.
function setCacheStore(store: KeyValueStore<CacheEntry<unknown>>): voidResponse Cache
Full-page response cache used by ISR render strategies.
interface ResponseCacheEntry {
html: string;
headers: Record<string, string>;
statusCode: number;
createdAt: number;
revalidateAfter: number | null;
tags: string[];
}
function responseCacheGet(key: string): Promise<{ entry: ResponseCacheEntry; stale: boolean } | undefined>
function responseCacheSet(key: string, entry: ResponseCacheEntry, opts?: { ttlMs?: number }): Promise<void>
function responseCacheInvalidateTag(tag: string): Promise<number>
function responseCacheInvalidate(key: string): Promise<boolean>
function responseCacheClear(): Promise<void>
function setResponseCacheStore(store: KeyValueStore<ResponseCacheEntry>): voidcsrfProtection
Hono middleware that enforces Double Submit Cookie CSRF protection on state-changing requests (POST/PUT/DELETE/PATCH). Issues fresh tokens on safe requests.
function csrfProtection(): MiddlewareHandlercreateRequestLogger
Create structured JSON request logging middleware. Respects LOG_LEVEL env var (debug/info/warn/error).
function createRequestLogger(): MiddlewareHandlerCache Utilities
function cacheInvalidate(key: string): void
function cacheInvalidatePath(urlPath: string): void
function cacheClear(): void
function responseCacheInvalidatePath(urlPath: string): void
function normalizeCacheTag(tag: string): string | undefined
function normalizeCacheTags(tags: string[]): string[]
function normalizeCachePath(urlOrPath: string): string
function createPageCacheKey(urlPath: string): string // prefixed with "page:"@zauso-ai/capstan-ops
Semantic operations kernel used by the runtime and CLI.
createCapstanOpsRuntime(options)
Create the persistent ops runtime that records events, incidents, and health snapshots into an OpsStore.
function createCapstanOpsRuntime(options: {
store: OpsStore;
serviceName?: string;
environment?: string;
}): {
recordEvent(input: OpsRecordEventInput): Promise<OpsEventRecord>;
recordIncident(input: OpsRecordIncidentInput): Promise<OpsIncidentRecord>;
captureSnapshot(input: OpsCaptureSnapshotInput): Promise<OpsSnapshotRecord>;
captureDerivedSnapshot(timestamp?: string): Promise<OpsSnapshotRecord>;
createOverview(): OpsOverview;
}InMemoryOpsStore
class InMemoryOpsStore implements OpsStore {
constructor(options?: {
retention?: OpsRetentionConfig;
eventRetentionMs?: number;
incidentRetentionMs?: number;
snapshotRetentionMs?: number;
});
}SqliteOpsStore
Persists structured ops data at .capstan/ops/ops.db. The CLI inspects it with ops:events, ops:incidents, ops:health, and ops:tail.
class SqliteOpsStore implements OpsStore {
constructor(options: {
path: string;
retention?: OpsRetentionConfig;
});
}createOpsQuery(store)
Create a query interface for filtering events, incidents, and snapshots.
function createOpsQuery(store: OpsStore): {
events(filter?: OpsEventFilter): OpsEventRecord[];
incidents(filter?: OpsIncidentFilter): OpsIncidentRecord[];
snapshots(filter?: OpsSnapshotFilter): OpsSnapshotRecord[];
}createOpsQueryIndex(store)
Build an index of aggregate statistics from store contents.
function createOpsQueryIndex(store: OpsStore): OpsQueryIndex
interface OpsQueryIndex {
totalEvents: number;
totalIncidents: number;
totalSnapshots: number;
eventsBySeverity: Record<string, number>;
eventsByStatus: Record<string, number>;
incidentsBySeverity: Record<string, number>;
incidentsByStatus: Record<string, number>;
snapshotsByHealth: Record<string, number>;
}createOpsOverview(query, index)
Generate a complete operational overview from query results and index.
function createOpsOverview(
query: ReturnType<typeof createOpsQuery>,
index: OpsQueryIndex,
): OpsOverview
interface OpsOverview {
totals: { events: number; incidents: number; snapshots: number };
incidents: { open: number; acknowledged: number; resolved: number };
health: OpsHealthStatus;
recentWindows: { events: OpsEventRecord[]; incidents: OpsIncidentRecord[] };
index: OpsQueryIndex;
}deriveOpsHealthStatus(store, options?)
Derive health status from recent events and incidents.
function deriveOpsHealthStatus(
store: OpsStore,
options?: { windowMs?: number },
): {
status: OpsHealthStatus;
summary: string;
signals: OpsHealthSignal[];
}
type OpsHealthStatus = "healthy" | "degraded" | "unhealthy";Ops Types
type OpsSeverity = "debug" | "info" | "warning" | "error" | "critical";
type OpsIncidentStatus = "open" | "acknowledged" | "suppressed" | "resolved";
type OpsTarget = "runtime" | "release" | "approval" | "policy"
| "capability" | "cron" | "ops" | "cli";
interface OpsEventRecord {
id: string;
kind: string;
timestamp: string;
severity: OpsSeverity;
status?: string;
target: OpsTarget;
scope?: OpsScope;
title: string;
summary?: string;
message?: string;
fingerprint?: string;
tags?: string[];
correlation?: OpsCorrelation;
metadata?: Record<string, unknown>;
}
interface OpsIncidentRecord extends OpsEventRecord {
incidentStatus: OpsIncidentStatus;
acknowledgedAt?: string;
resolvedAt?: string;
}
interface OpsRetentionConfig {
events?: { maxAgeMs: number };
incidents?: { maxAgeMs: number };
snapshots?: { maxAgeMs: number };
}
interface OpsEventFilter {
ids?: string[];
kinds?: string[];
severities?: OpsSeverity[];
statuses?: string[];
targets?: OpsTarget[];
tags?: string[];
scopes?: OpsScopeFilter[];
from?: string;
to?: string;
sort?: "asc" | "desc";
limit?: number;
}
interface OpsStore {
addEvent(record: OpsEventRecord): OpsEventRecord;
getEvent(id: string): OpsEventRecord | undefined;
listEvents(filter?: OpsEventFilter): OpsEventRecord[];
addIncident(record: OpsIncidentRecord): OpsIncidentRecord;
getIncident(id: string): OpsIncidentRecord | undefined;
getIncidentByFingerprint(fingerprint: string): OpsIncidentRecord | undefined;
listIncidents(filter?: OpsIncidentFilter): OpsIncidentRecord[];
addSnapshot(record: OpsSnapshotRecord): OpsSnapshotRecord;
listSnapshots(filter?: OpsSnapshotFilter): OpsSnapshotRecord[];
compact(options?: OpsCompactionOptions): OpsCompactionResult;
close(): void | Promise<void>;
}@zauso-ai/capstan-agent — LLM Providers
Built-in LLM provider adapters for chat completion and streaming.
openaiProvider(config)
Create an OpenAI-compatible LLM provider. Works with any OpenAI-compatible API (OpenAI, Azure OpenAI, Ollama, etc.) by setting baseUrl. Supports both chat() and stream().
function openaiProvider(config: {
apiKey: string;
baseUrl?: string; // default: "https://api.openai.com/v1"
model?: string; // default: "gpt-4o"
}): LLMProvideranthropicProvider(config)
Create an Anthropic LLM provider. Supports chat(). System prompts are extracted from messages and sent via the Anthropic system parameter.
function anthropicProvider(config: {
apiKey: string;
model?: string; // default: "claude-sonnet-4-20250514"
baseUrl?: string; // default: "https://api.anthropic.com/v1"
}): LLMProviderLLM Types
interface LLMMessage {
role: "system" | "user" | "assistant";
content: string;
}
interface LLMResponse {
content: string;
model: string;
usage?: {
promptTokens: number;
completionTokens: number;
totalTokens: number;
};
finishReason?: string;
}
interface LLMStreamChunk {
content: string;
done: boolean;
}
interface LLMOptions {
model?: string;
temperature?: number;
maxTokens?: number;
systemPrompt?: string;
responseFormat?: Record<string, unknown>;
}
interface LLMProvider {
name: string;
chat(messages: LLMMessage[], options?: LLMOptions): Promise<LLMResponse>;
stream?(messages: LLMMessage[], options?: LLMOptions): AsyncIterable<LLMStreamChunk>;
}@zauso-ai/capstan-ai
Standalone AI toolkit. Works independently or with the Capstan framework, including browser/filesystem harness mode.
createAI(config)
Factory function that creates a standalone AI instance with all capabilities. No Capstan framework required.
function createAI(config: AIConfig): AIContext
interface AIConfig {
llm: LLMProvider;
memory?: {
backend?: MemoryBackend;
embedding?: { embed(texts: string[]): Promise<number[][]>; dimensions: number };
autoExtract?: boolean;
};
defaultScope?: MemoryScope;
}
interface AIContext {
think<T = string>(prompt: string, opts?: ThinkOptions<T>): Promise<T>;
generate(prompt: string, opts?: GenerateOptions): Promise<string>;
thinkStream(prompt: string, opts?: Omit<ThinkOptions, "schema">): AsyncIterable<string>;
generateStream(prompt: string, opts?: GenerateOptions): AsyncIterable<string>;
remember(content: string, opts?: RememberOptions): Promise<string>;
recall(query: string, opts?: RecallOptions): Promise<MemoryEntry[]>;
memory: {
about(type: string, id: string): MemoryAccessor;
forget(entryId: string): Promise<boolean>;
assembleContext(opts: AssembleContextOptions): Promise<string>;
};
agent: {
run(config: AgentRunConfig): Promise<AgentRunResult>;
};
}
interface AgentRunConfig {
goal: string;
tools?: AgentTool[];
tasks?: AgentTask[];
maxIterations?: number;
systemPrompt?: string;
}Usage:
import { createAI } from "@zauso-ai/capstan-ai";
import { openaiProvider } from "@zauso-ai/capstan-agent";
const ai = createAI({
llm: openaiProvider({ apiKey: process.env.OPENAI_API_KEY! }),
});
// Structured reasoning with Zod schema
const result = await ai.think("Classify this ticket", {
schema: z.object({ category: z.string(), priority: z.enum(["low", "medium", "high"]) }),
});
// Text generation
const summary = await ai.generate("Summarize this document...");Task helpers are exported directly from @zauso-ai/capstan-ai:
import {
createShellTask,
createWorkflowTask,
createRemoteTask,
createSubagentTask,
} from "@zauso-ai/capstan-ai";createHarness(config)
Durable harness runtime for long-running agents. Adds browser/filesystem sandboxes, verification hooks, persisted runs/events/artifacts/checkpoints, and runtime lifecycle control on top of runAgentLoop().
function createHarness(config: HarnessConfig): Promise<Harness>
interface HarnessConfig {
llm: LLMProvider;
sandbox?: {
browser?: boolean | BrowserSandboxConfig;
fs?: boolean | FsSandboxConfig;
};
verify?: {
enabled?: boolean;
maxRetries?: number;
verifier?: HarnessVerifierFn;
};
observe?: {
logger?: HarnessLogger;
onEvent?: (event: HarnessEvent) => void;
};
context?: {
enabled?: boolean;
maxPromptTokens?: number;
reserveOutputTokens?: number;
maxMemories?: number;
maxArtifacts?: number;
maxRecentMessages?: number;
maxRecentToolResults?: number;
microcompactToolResultChars?: number;
sessionCompactThreshold?: number;
defaultScopes?: MemoryScope[];
autoPromoteObservations?: boolean;
autoPromoteSummaries?: boolean;
};
runtime?: {
rootDir?: string;
maxConcurrentRuns?: number;
driver?: HarnessSandboxDriver;
beforeToolCall?: HarnessToolPolicyFn;
beforeTaskCall?: HarnessTaskPolicyFn;
};
}
interface BrowserSandboxConfig {
engine?: "playwright" | "camoufox";
platform?: string;
accountId?: string;
guardMode?: "vision" | "hybrid";
headless?: boolean;
proxy?: string;
viewport?: { width: number; height: number };
}
interface FsSandboxConfig {
rootDir: string;
allowWrite?: boolean;
allowDelete?: boolean;
maxFileSize?: number;
}Harness instance:
interface Harness {
startRun(config: AgentRunConfig): Promise<HarnessRunHandle>;
run(config: AgentRunConfig): Promise<HarnessRunResult>;
pauseRun(runId: string): Promise<HarnessRunRecord>;
cancelRun(runId: string): Promise<HarnessRunRecord>;
resumeRun(runId: string, options?: HarnessResumeOptions): Promise<HarnessRunResult>;
getRun(runId: string): Promise<HarnessRunRecord | undefined>;
listRuns(): Promise<HarnessRunRecord[]>;
getEvents(runId?: string): Promise<HarnessRunEventRecord[]>;
getTasks(runId: string): Promise<HarnessTaskRecord[]>;
getArtifacts(runId: string): Promise<HarnessArtifactRecord[]>;
getCheckpoint(runId: string): Promise<AgentLoopCheckpoint | undefined>;
getSessionMemory(runId: string): Promise<HarnessSessionMemoryRecord | undefined>;
getLatestSummary(runId: string): Promise<HarnessSummaryRecord | undefined>;
listSummaries(runId?: string): Promise<HarnessSummaryRecord[]>;
rememberMemory(input: HarnessMemoryInput): Promise<HarnessMemoryRecord>;
recallMemory(query: HarnessMemoryQuery): Promise<HarnessMemoryMatch[]>;
assembleContext(runId: string, options?: HarnessContextAssembleOptions): Promise<HarnessContextPackage>;
replayRun(runId: string): Promise<HarnessReplayReport>;
getPaths(): HarnessRuntimePaths;
destroy(): Promise<void>;
}Usage:
import { createHarness } from "@zauso-ai/capstan-ai";
const harness = await createHarness({
llm: openaiProvider({ apiKey: process.env.OPENAI_API_KEY! }),
sandbox: {
browser: { engine: "camoufox", platform: "jd", accountId: "price-monitor-01" },
fs: { rootDir: "./workspace" },
},
runtime: {
rootDir: process.cwd(),
maxConcurrentRuns: 2,
},
verify: { enabled: true },
});
const started = await harness.startRun({
goal: "Research the storefront and save notes to workspace/report.md",
});
const result = await started.result;
await harness.destroy();engine: "playwright" is the lightweight default. engine: "camoufox" enables the kernel adapter with stealth engines, persistent profiles, and platform guards.
runtime.driver defaults to LocalHarnessSandboxDriver, which creates an isolated sandbox directory per run under .capstan/harness/sandboxes/<runId>/. The runtime store persists: runs, events, tasks, artifacts, checkpoints, session-memory, summaries, and long-term memory entries.
Use openHarnessRuntime(rootDir?) when you need an independent control plane that can inspect paused/completed runs without a live harness instance. The control plane also accepts an object form with an authorize callback for runtime supervision with auth.
think(llm, prompt, opts?)
Structured reasoning: sends a prompt to the LLM and optionally parses the response against a schema.
function think<T = string>(
llm: LLMProvider,
prompt: string,
opts?: ThinkOptions<T>,
): Promise<T>
interface ThinkOptions<T = unknown> {
schema?: { parse: (data: unknown) => T };
model?: string;
temperature?: number;
maxTokens?: number;
systemPrompt?: string;
memory?: boolean;
about?: [string, string];
}When schema is provided, the LLM is asked for JSON output and the result is parsed and validated. Without a schema, the raw text is returned.
generate(llm, prompt, opts?)
Text generation: sends a prompt to the LLM and returns the raw text response.
function generate(
llm: LLMProvider,
prompt: string,
opts?: GenerateOptions,
): Promise<string>
interface GenerateOptions {
model?: string;
temperature?: number;
maxTokens?: number;
systemPrompt?: string;
memory?: boolean;
about?: [string, string];
}thinkStream(llm, prompt, opts?)
Streaming text generation. Requires the LLM provider to support stream(). Yields text chunks as tokens are generated. Throws if the LLM provider does not implement stream().
function thinkStream(
llm: LLMProvider,
prompt: string,
opts?: GenerateOptions,
): AsyncIterable<string>generateStream(llm, prompt, opts?)
Alias for thinkStream. Streaming text generation that yields chunks as the LLM generates tokens.
function generateStream(
llm: LLMProvider,
prompt: string,
opts?: GenerateOptions,
): AsyncIterable<string>MemoryAccessor
The developer-facing memory interface, returned by createMemoryAccessor() or ai.memory.about().
interface MemoryAccessor {
remember(content: string, opts?: RememberOptions): Promise<string>;
recall(query: string, opts?: RecallOptions): Promise<MemoryEntry[]>;
forget(entryId: string): Promise<boolean>;
about(type: string, id: string): MemoryAccessor;
assembleContext(opts: AssembleContextOptions): Promise<string>;
}
interface RememberOptions {
scope?: MemoryScope;
type?: "fact" | "event" | "preference" | "instruction";
importance?: "low" | "medium" | "high" | "critical";
metadata?: Record<string, unknown>;
}
interface RecallOptions {
scope?: MemoryScope;
limit?: number; // Max results (default: 10)
minScore?: number; // Minimum relevance score
types?: string[]; // Filter by memory type
}
interface MemoryScope {
type: string;
id: string;
}
interface MemoryEntry {
id: string;
content: string;
scope: MemoryScope;
createdAt: string;
updatedAt: string;
metadata?: Record<string, unknown>;
embedding?: number[];
importance?: "low" | "medium" | "high" | "critical";
type?: "fact" | "event" | "preference" | "instruction";
accessCount: number;
lastAccessedAt: string;
}
interface AssembleContextOptions {
query: string;
maxTokens?: number; // Default: 4000
scopes?: MemoryScope[];
}remember() stores a memory, automatically deduplicating (>0.92 cosine similarity merges with existing) and embedding for vector search. Returns the memory ID.
recall() retrieves relevant memories using hybrid search: vector similarity (0.7 weight) + keyword matching (0.3 weight) + recency decay (30-day half-life).
about() returns a new MemoryAccessor scoped to a specific entity. All subsequent operations are isolated to that scope.
assembleContext() builds an LLM-ready context string from stored memories, sorted by importance and packed within a token budget.
Usage:
const customerMemory = ai.memory.about("customer", "cust_123");
await customerMemory.remember("Prefers email communication", { type: "preference" });
const relevant = await customerMemory.recall("communication preferences");
await ai.memory.forget(relevant[0].id);runAgentLoop(llm, config, tools, opts?)
Self-orchestrating agent loop. The LLM reasons about a goal, selects and executes tools, feeds results back, and repeats until done or the iteration limit is reached.
function runAgentLoop(
llm: LLMProvider,
config: AgentRunConfig,
tools: AgentTool[],
opts?: {
beforeToolCall?: (tool: string, args: unknown) => Promise<{ allowed: boolean; reason?: string }>;
afterToolCall?: (tool: string, args: unknown, result: unknown) => Promise<void>;
callStack?: Set<string>;
onMemoryEvent?: (content: string) => Promise<void>;
},
): Promise<AgentRunResult>
interface AgentTool {
name: string;
description: string;
parameters?: Record<string, unknown>;
execute(args: Record<string, unknown>): Promise<unknown>;
}
interface AgentRunConfig {
goal: string;
about?: [string, string];
maxIterations?: number; // Default: 10
memory?: boolean;
tools?: AgentTool[];
systemPrompt?: string;
excludeRoutes?: string[];
}
interface AgentRunResult {
result: unknown;
iterations: number;
toolCalls: Array<{ tool: string; args: unknown; result: unknown }>;
status: "completed" | "max_iterations" | "approval_required";
pendingApproval?: { tool: string; args: unknown; reason: string };
}The loop uses JSON-based tool calling: the LLM responds with {"tool": "name", "arguments": {...}} to invoke a tool, or plain text to finish. The beforeToolCall hook enables policy enforcement -- returning { allowed: false } stops the loop with "approval_required" status.
BuiltinMemoryBackend
Default in-memory backend with optional vector search support. Suitable for development and testing. No external dependencies.
class BuiltinMemoryBackend implements MemoryBackend {
constructor(opts?: { embedding?: MemoryEmbedder });
}
interface MemoryEmbedder {
embed(texts: string[]): Promise<number[][]>;
dimensions: number;
}Features: keyword-only fallback when no embedder is provided, hybrid search (vector + keyword + recency decay) when embedder is present, auto-dedup at >0.92 cosine similarity.
MemoryBackend (Interface)
Pluggable backend interface for memory storage. Implement for custom backends (Mem0, Hindsight, Redis, etc.).
interface MemoryBackend {
store(entry: Omit<MemoryEntry, "id" | "accessCount" | "lastAccessedAt" | "createdAt" | "updatedAt">): Promise<string>;
query(scope: MemoryScope, text: string, k: number): Promise<MemoryEntry[]>;
remove(id: string): Promise<boolean>;
clear(scope: MemoryScope): Promise<void>;
}@zauso-ai/capstan-cron
Recurring job scheduler for Capstan AI workflows. Works with Bun-native cron when available and falls back to a simple interval runner elsewhere.
defineCron(config)
Declarative helper that returns the cron config unchanged.
function defineCron(config: CronJobConfig): CronJobConfigcreateCronRunner()
Interval-based scheduler for simple cron expressions. Intentionally lightweight -- approximates supported cron patterns as intervals.
function createCronRunner(): CronRunner
interface CronJobConfig {
name: string;
pattern: string;
handler: () => Promise<void>;
timezone?: string;
maxConcurrent?: number;
onError?: (err: Error) => void;
enabled?: boolean;
}
interface CronRunner {
add(config: CronJobConfig): string;
remove(id: string): boolean;
start(): void;
stop(): void;
getJobs(): CronJobInfo[];
}createBunCronRunner()
Use Bun's native cron implementation when running on Bun. Falls back to createCronRunner() when Bun.cron is unavailable.
function createBunCronRunner(): CronRunnercreateAgentCron(config)
Create a cron job that submits scheduled runs into a harness runtime. If you do not provide a runtime, it falls back to bootstrapping createHarness() on demand.
function createAgentCron(config: AgentCronConfig): CronJobConfig
interface AgentCronConfig {
cron: string;
name: string;
goal: string | (() => string);
timezone?: string;
llm?: unknown;
harnessConfig?: Record<string, unknown>;
run?: {
about?: [string, string];
maxIterations?: number;
memory?: boolean;
systemPrompt?: string;
excludeRoutes?: string[];
};
triggerMetadata?: Record<string, unknown>;
runtime?: {
harness?: { startRun(config: unknown, options?: unknown): Promise<{ runId: string; result: Promise<unknown> }> };
createHarness?: () => Promise<{ startRun(...): ... }>;
reuseHarness?: boolean;
};
onQueued?: (meta: { runId: string; trigger: unknown }) => void;
onResult?: (result: unknown, meta: { runId: string; trigger: unknown }) => void;
onError?: (err: Error) => void;
}Usage:
import { createCronRunner, createAgentCron } from "@zauso-ai/capstan-cron";
import { createHarness } from "@zauso-ai/capstan-ai";
const harness = await createHarness({
llm: openaiProvider({ apiKey: process.env.OPENAI_API_KEY! }),
sandbox: {
browser: { engine: "camoufox", platform: "jd", accountId: "price-monitor-01" },
fs: { rootDir: "./workspace" },
},
});
const runner = createCronRunner();
runner.add(createAgentCron({
cron: "0 */2 * * *",
name: "price-monitor",
goal: "Check the storefront and refresh workspace/report.md",
runtime: { harness },
}));
runner.start();Shared Types
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
interface CapstanAuthContext {
isAuthenticated: boolean;
type: "human" | "agent" | "anonymous";
userId?: string;
role?: string;
email?: string;
agentId?: string;
agentName?: string;
permissions?: string[];
}
interface CapstanContext {
auth: CapstanAuthContext;
request: Request;
env: Record<string, string | undefined>;
honoCtx: HonoContext;
}
interface RouteMetadata {
method: HttpMethod;
path: string;
description?: string;
capability?: "read" | "write" | "external";
resource?: string;
policy?: string;
inputSchema?: Record<string, unknown>;
outputSchema?: Record<string, unknown>;
}@zauso-ai/capstan-db
Database layer with model definitions, schema generation, migrations, and CRUD route scaffolding.
defineModel(name, config)
Declare a data model with fields, relations, and indexes.
function defineModel(
name: string,
config: {
fields: Record<string, FieldDefinition>;
relations?: Record<string, RelationDefinition>;
indexes?: IndexDefinition[];
},
): ModelDefinitionfield
Field builder namespace with helpers for each scalar type.
const field: {
id(): FieldDefinition;
string(opts?: FieldOptions): FieldDefinition;
text(opts?: FieldOptions): FieldDefinition;
integer(opts?: FieldOptions): FieldDefinition;
number(opts?: FieldOptions): FieldDefinition;
boolean(opts?: FieldOptions): FieldDefinition;
date(opts?: FieldOptions): FieldDefinition;
datetime(opts?: FieldOptions): FieldDefinition;
json<T = unknown>(opts?: FieldOptions): FieldDefinition;
enum(values: readonly string[], opts?: FieldOptions): FieldDefinition;
vector(dimensions: number): FieldDefinition;
}relation
Relation builder namespace.
const relation: {
belongsTo(model: string, opts?: { foreignKey?: string; through?: string }): RelationDefinition;
hasMany(model: string, opts?: { foreignKey?: string; through?: string }): RelationDefinition;
hasOne(model: string, opts?: { foreignKey?: string; through?: string }): RelationDefinition;
manyToMany(model: string, opts?: { foreignKey?: string; through?: string }): RelationDefinition;
}createDatabase(config)
Create a Drizzle database instance for the specified provider.
function createDatabase(config: DatabaseConfig): Promise<DatabaseInstance>
interface DatabaseConfig {
provider: "sqlite" | "postgres" | "mysql";
url: string;
}
interface DatabaseInstance {
db: unknown; // Drizzle ORM instance
close: () => void; // Close the connection
}Migration Functions
// Generate SQL migration statements from model diffs
function generateMigration(
fromModels: ModelDefinition[],
toModels: ModelDefinition[],
): string[]
// Execute SQL statements in a transaction
function applyMigration(
db: { $client: { exec: (sql: string) => void } },
sql: string[],
): void
// Create the _capstan_migrations tracking table
function ensureTrackingTable(
client: MigrationDbClient,
provider?: DbProvider,
): void
// Get list of applied migration names
function getAppliedMigrations(client: MigrationDbClient): string[]
// Get full migration status (applied + pending)
function getMigrationStatus(
client: MigrationDbClient,
allMigrationNames: string[],
provider?: DbProvider,
): MigrationStatus
// Apply pending migrations with tracking
function applyTrackedMigrations(
client: MigrationDbClient,
migrations: Array<{ name: string; sql: string }>,
provider?: DbProvider,
): string[]generateCrudRoutes(model)
Generate CRUD API route files from a model definition.
function generateCrudRoutes(model: ModelDefinition): CrudRouteFiles[]
interface CrudRouteFiles {
path: string; // Relative to app/routes/
content: string; // File content
}pluralize(word)
Naive English pluralizer for model-to-table name conversion.
function pluralize(word: string): stringdefineEmbedding(modelName, config)
Configure an embedding model for vector generation.
function defineEmbedding(
modelName: string,
config: {
dimensions: number;
adapter: EmbeddingAdapter;
},
): EmbeddingInstance
interface EmbeddingInstance {
embed(text: string): Promise<number[]>;
embedBatch(texts: string[]): Promise<number[][]>;
dimensions: number;
}openaiEmbeddings(opts)
Create an embedding adapter using the OpenAI embeddings API.
function openaiEmbeddings(opts: {
apiKey: string;
model?: string; // default: inferred from defineEmbedding modelName
baseUrl?: string; // for compatible providers
}): EmbeddingAdaptergenerateDrizzleSchema(models, provider)
Generate Drizzle ORM schema from model definitions.
function generateDrizzleSchema(
models: ModelDefinition[],
provider: "sqlite" | "postgres" | "mysql",
): Record<string, DrizzleTable>Database Runtime
function createDatabaseRuntime(db: DrizzleClient, schema: Record<string, DrizzleTable>): DatabaseRuntime
function createCrudRepository(db: DrizzleClient, model: ModelDefinition, table: DrizzleTable): CrudRepository
function createCrudRuntime(db: DrizzleClient, models: ModelDefinition[], schema: Record<string, DrizzleTable>): CrudRuntimeVector Search
// Calculate cosine distance between two vectors
function cosineDistance(a: number[], b: number[]): number
// Find K nearest neighbors by vector similarity
function findNearest(
items: { id: string; vector: number[] }[],
query: number[],
k?: number,
): { id: string; score: number }[]
// Hybrid search combining vector similarity (0.7) + keyword matching (0.3)
function hybridSearch(
items: { id: string; vector: number[]; text: string }[],
query: { vector: number[]; text: string },
k?: number,
): { id: string; score: number }[]Data Preparation
function prepareCreateData(model: ModelDefinition, input: Record<string, unknown>): Record<string, unknown>
function prepareUpdateData(model: ModelDefinition, input: Record<string, unknown>): Record<string, unknown>DB Types
type ScalarType = "string" | "integer" | "number" | "boolean" | "date" | "datetime" | "text" | "json";
type DbProvider = "sqlite" | "postgres" | "mysql";
type RelationKind = "belongsTo" | "hasMany" | "hasOne" | "manyToMany";
interface FieldDefinition {
type: ScalarType;
required?: boolean;
unique?: boolean;
default?: unknown;
min?: number;
max?: number;
enum?: readonly string[];
updatedAt?: boolean;
autoId?: boolean;
references?: string;
}
interface RelationDefinition {
kind: RelationKind;
model: string;
foreignKey?: string;
through?: string;
}
interface IndexDefinition {
fields: string[];
unique?: boolean;
order?: "asc" | "desc";
}
interface ModelDefinition {
name: string;
fields: Record<string, FieldDefinition>;
relations: Record<string, RelationDefinition>;
indexes: IndexDefinition[];
}@zauso-ai/capstan-auth
Authentication and authorization: JWT sessions, API keys, OAuth, grants, execution identity, DPoP, and SPIFFE.
signSession(payload, secret, maxAge?)
Create a signed JWT containing session data.
function signSession(
payload: Omit<SessionPayload, "iat" | "exp">,
secret: string,
maxAge?: string, // default: "7d"
): stringverifySession(token, secret)
Verify a JWT signature and expiration. Returns the payload on success, null on failure.
function verifySession(token: string, secret: string): SessionPayload | nullgenerateApiKey(prefix?)
Generate a new API key with hash and lookup prefix.
function generateApiKey(prefix?: string): {
key: string; // Full plaintext key (show once)
hash: string; // SHA-256 hex digest (store in DB)
prefix: string; // Lookup prefix (store for indexed queries)
}verifyApiKey(key, storedHash)
Verify a plaintext API key against a stored SHA-256 hash. Uses timing-safe comparison.
function verifyApiKey(key: string, storedHash: string): Promise<boolean>extractApiKeyPrefix(key)
Extract the lookup prefix from a full plaintext API key.
function extractApiKeyPrefix(key: string): stringcreateAuthMiddleware(config, deps)
Create a middleware function that resolves auth context from a request.
function createAuthMiddleware(
config: AuthConfig,
deps: AuthResolverDeps,
): (request: Request) => Promise<AuthContext>
interface AuthConfig {
session: { secret: string; maxAge?: string };
apiKeys?: { prefix?: string; headerName?: string };
}
interface AuthResolverDeps {
findAgentByKeyPrefix?: (prefix: string) => Promise<AgentCredential | null>;
}checkPermission(required, granted)
Check whether a required permission is satisfied by the granted set. Supports wildcards: *:read, ticket:*, *:*.
function checkPermission(
required: { resource: string; action: "read" | "write" | "delete" },
granted: string[],
): booleanderivePermission(capability, resource?)
Derive a permission object from a capability mode.
function derivePermission(
capability: "read" | "write" | "external",
resource?: string,
): { resource: string; action: string }googleProvider(opts)
Create a pre-configured Google OAuth provider. Returns an OAuthProvider configured with Google's authorize, token, and user info endpoints and ["openid", "email", "profile"] scopes.
function googleProvider(opts: {
clientId: string;
clientSecret: string;
}): OAuthProvidergithubProvider(opts)
Create a pre-configured GitHub OAuth provider. Returns an OAuthProvider configured with GitHub's endpoints and ["user:email"] scopes.
function githubProvider(opts: {
clientId: string;
clientSecret: string;
}): OAuthProvidercreateOAuthHandlers(config, fetchFn?)
Create OAuth route handlers for the full authorization code flow.
function createOAuthHandlers(
config: OAuthConfig,
fetchFn?: typeof globalThis.fetch,
): OAuthHandlers
interface OAuthConfig {
providers: OAuthProvider[];
callbackPath?: string; // default: "/auth/callback"
sessionSecret: string;
}
interface OAuthHandlers {
login: (request: Request, providerName: string) => Response;
callback: (request: Request) => Promise<Response>;
}The login handler redirects to the OAuth provider with a CSRF state parameter. The callback handler validates state, exchanges the authorization code for an access token, fetches user info, and creates a signed JWT session cookie.
Auth Types
interface OAuthProvider {
name: string;
authorizeUrl: string;
tokenUrl: string;
userInfoUrl: string;
clientId: string;
clientSecret: string;
scopes: string[];
}
interface SessionPayload {
userId: string;
email?: string;
role?: string;
iat: number;
exp: number;
}
interface AgentCredential {
id: string;
name: string;
apiKeyHash: string;
apiKeyPrefix: string;
permissions: string[];
revokedAt?: string;
}
interface AuthContext {
isAuthenticated: boolean;
type: "human" | "agent" | "anonymous";
userId?: string;
role?: string;
email?: string;
agentId?: string;
agentName?: string;
permissions?: string[];
}Grant-Based Authorization
Fine-grained permission system for runtime and harness actions.
function authorizeGrant(required: AuthGrant, granted: AuthGrant[]): AuthorizationDecision
function checkGrant(required: AuthGrant, granted: AuthGrant[]): boolean
function normalizePermissionsToGrants(permissions: (string | AuthGrant)[]): AuthGrant[]
function serializeGrantsToPermissions(grants: AuthGrant[]): string[]
function createGrant(resource: string, action: string, scope?: Record<string, string>): AuthGrant
interface AuthGrant {
resource: string;
action: string;
scope?: Record<string, string>;
}Runtime Grant Helpers
Factory functions for common runtime action grants.
function grantRunActions(actions?: string[], runId?: string): AuthGrant[]
function grantRunCollectionActions(actions?: string[]): AuthGrant[]
function grantApprovalActions(actions?: string[], approvalId?: string): AuthGrant[]
function grantApprovalCollectionActions(actions?: string[]): AuthGrant[]
function grantEventActions(actions?: string[]): AuthGrant[]
function grantEventCollectionActions(actions?: string[]): AuthGrant[]
function grantArtifactActions(actions?: string[]): AuthGrant[]
function grantCheckpointActions(actions?: string[]): AuthGrant[]
function grantTaskActions(actions?: string[]): AuthGrant[]
function grantSummaryActions(actions?: string[]): AuthGrant[]
function grantSummaryCollectionActions(actions?: string[]): AuthGrant[]
function grantMemoryActions(actions?: string[]): AuthGrant[]
function grantContextActions(actions?: string[]): AuthGrant[]
function grantRuntimePathsActions(actions?: string[]): AuthGrant[]Runtime Authorizer
function deriveRuntimeGrantRequirements(request: RuntimeActionRequest): AuthGrant[]
function authorizeRuntimeAction(request: RuntimeActionRequest, granted: AuthGrant[]): AuthorizationResult
function createRuntimeGrantAuthorizer(granted: AuthGrant[]): RuntimeGrantAuthorizer
function createHarnessGrantAuthorizer(granted: AuthGrant[]): HarnessGrantAuthorizer
function toRuntimeGrantRequest(request: HarnessAuthRequest): RuntimeActionRequestExecution Identity
function createExecutionIdentity(kind: string, source: string): ExecutionIdentity
function createRequestExecution(request: Request): ExecutionIdentity
function createDelegationLink(from: Identity, to: Identity): DelegationLinkDPoP and Workload Identity
// Validate a DPoP proof JWT (RFC 9449)
function validateDpopProof(proof: string, options: DpopValidationOptions): Promise<DpopResult>
// Clear DPoP replay cache (for testing)
function clearDpopReplayCache(): void
// Extract SPIFFE workload identity from certificate
function extractWorkloadIdentity(certOrClaim: string): WorkloadIdentity | null
// Validate SPIFFE ID format
function isValidSpiffeId(uri: string): boolean@zauso-ai/capstan-router
File-based routing: directory scanning, URL matching, and manifest generation.
scanRoutes(routesDir)
Scan a directory tree and produce a RouteManifest describing every route file.
function scanRoutes(routesDir: string): Promise<RouteManifest>
interface RouteManifest {
routes: RouteEntry[];
scannedAt: string;
rootDir: string;
}
interface RouteEntry {
filePath: string;
type: RouteType;
urlPattern: string;
methods?: string[];
layouts: string[];
middlewares: string[];
params: string[];
isCatchAll: boolean;
}
type RouteType = "page" | "api" | "layout" | "middleware";matchRoute(manifest, method, urlPath)
Match a URL path and HTTP method against a route manifest. Priority: static segments > dynamic segments > catch-all. For equal specificity, API routes are preferred for non-GET methods, page routes for GET.
function matchRoute(
manifest: RouteManifest,
method: string,
urlPath: string,
): MatchedRoute | null
interface MatchedRoute {
route: RouteEntry;
params: Record<string, string>;
}generateRouteManifest(manifest)
Extract API route information from a RouteManifest for the agent surface layer.
function generateRouteManifest(
manifest: RouteManifest,
): { apiRoutes: AgentApiRoute[] }
interface AgentApiRoute {
method: string;
path: string;
filePath: string;
}canonicalizeRouteManifest(routes, rootDir)
Canonicalize and validate route entries -- detect conflicts, sort by specificity, generate diagnostics.
function canonicalizeRouteManifest(
routes: RouteEntry[],
rootDir: string,
): CanonicalizedRouteManifest
interface CanonicalizedRouteManifest {
routes: RouteEntry[];
diagnostics: RouteDiagnostic[];
}validateRouteManifest(routes, rootDir)
Validate route entries and return canonicalized routes with diagnostics. Wrapper for canonicalizeRouteManifest.
function validateRouteManifest(
routes: RouteEntry[],
rootDir: string,
): CanonicalizedRouteManifestcreateRouteScanCache()
Create a cache instance for storing route scan results to avoid redundant scanning.
function createRouteScanCache(): RouteScanCache
class RouteScanCache {
get(rootDir: string): RouteScanCacheState | undefined;
set(rootDir: string, state: RouteScanCacheState): void;
clear(rootDir?: string): void;
}createRouteConflictError(diagnostics)
Create a structured error from route diagnostics for error handling.
function createRouteConflictError(
diagnostics: RouteDiagnostic[],
): RouteConflictError
class RouteConflictError extends Error {
code: "ROUTE_CONFLICT";
conflicts: RouteConflict[];
diagnostics: RouteDiagnostic[];
}Router Types
type RouteType = "page" | "api" | "layout" | "middleware" | "loading" | "error" | "not-found";
type RouteDiagnosticSeverity = "error" | "warning";
interface RouteDiagnostic {
code: RouteConflictReason;
severity: RouteDiagnosticSeverity;
message: string;
routeType: RouteType;
urlPattern: string;
canonicalPattern: string;
filePaths: string[];
directoryDepth?: number;
}
interface RouteStaticInfo {
exportNames: string[];
hasMetadata?: boolean;
renderMode?: "ssr" | "ssg" | "isr" | "streaming";
revalidate?: number;
hasGenerateStaticParams?: boolean;
}@zauso-ai/capstan-agent
Multi-protocol adapter layer: CapabilityRegistry, MCP server, A2A handler, OpenAPI spec, LangChain integration.
CapabilityRegistry
Unified registry for projecting routes to multiple protocol surfaces.
class CapabilityRegistry {
constructor(config: AgentConfig);
register(route: RouteRegistryEntry): void;
registerAll(routes: RouteRegistryEntry[]): void;
getRoutes(): readonly RouteRegistryEntry[];
getConfig(): Readonly<AgentConfig>;
toManifest(): AgentManifest;
toOpenApi(): Record<string, unknown>;
toMcp(executeRoute: (method: string, path: string, input: unknown) => Promise<unknown>): {
server: McpServer;
getToolDefinitions: () => Array<{ name: string; description: string; inputSchema: unknown }>;
};
toA2A(executeRoute: (method: string, path: string, input: unknown) => Promise<unknown>): {
handleRequest: (body: unknown) => Promise<unknown>;
getAgentCard: () => A2AAgentCard;
};
}createMcpServer(config, routes, executeRoute)
Create an MCP server that exposes API routes as MCP tools. Tool naming convention: GET /tickets becomes get_tickets, GET /tickets/:id becomes get_tickets_by_id.
function createMcpServer(
config: AgentConfig,
routes: RouteRegistryEntry[],
executeRoute: (method: string, path: string, input: unknown) => Promise<unknown>,
): {
server: McpServer;
getToolDefinitions: () => Array<{ name: string; description: string; inputSchema: unknown }>;
}serveMcpStdio(server)
Connect an MCP server to stdio transport for use with Claude Desktop, Cursor, etc.
function serveMcpStdio(server: McpServer): Promise<void>routeToToolName(method, path)
Convert an HTTP method + URL path into a snake_case MCP tool name.
function routeToToolName(method: string, path: string): stringgenerateOpenApiSpec(config, routes)
Generate an OpenAPI 3.1.0 specification from agent config and routes.
function generateOpenApiSpec(
config: AgentConfig,
routes: RouteRegistryEntry[],
): Record<string, unknown>generateA2AAgentCard(config, routes)
Generate an A2A Agent Card from config and routes.
function generateA2AAgentCard(
config: AgentConfig,
routes: RouteRegistryEntry[],
): A2AAgentCard
interface A2AAgentCard {
name: string;
description?: string;
url: string;
version: string;
capabilities: { streaming?: boolean; pushNotifications?: boolean };
skills: Array<{
id: string;
name: string;
description?: string;
inputSchema?: Record<string, unknown>;
outputSchema?: Record<string, unknown>;
}>;
authentication?: { schemes: string[] };
}createMcpClient(options)
Create an MCP client to consume tools from an external MCP server.
function createMcpClient(options: McpClientOptions): McpClient
interface McpClientOptions {
url?: string; // Streamable HTTP endpoint
command?: string; // stdio command (alternative to url)
args?: string[]; // stdio command args
transport?: "streamable-http" | "stdio";
}
interface McpClient {
listTools(): Promise<Array<{ name: string; description?: string; inputSchema: unknown }>>;
callTool(name: string, args?: unknown): Promise<unknown>;
close(): Promise<void>;
}McpTestHarness
Test harness for verifying MCP tool behavior without a live server.
class McpTestHarness {
constructor(registry: CapabilityRegistry);
listTools(): Array<{ name: string; description: string; inputSchema: unknown }>;
callTool(name: string, args?: unknown): Promise<unknown>;
}toLangChainTools(registry, options?)
Convert registered capabilities into LangChain-compatible StructuredTool instances.
function toLangChainTools(
registry: CapabilityRegistry,
options?: {
filter?: (route: RouteRegistryEntry) => boolean;
},
): StructuredTool[]createA2AHandler(config, routes, executeRoute)
Create an A2A JSON-RPC handler supporting tasks/send, tasks/get, and agent/card methods.
function createA2AHandler(
config: AgentConfig,
routes: RouteRegistryEntry[],
executeRoute: (method: string, path: string, input: unknown) => Promise<unknown>,
): {
handleRequest: (body: unknown) => Promise<A2AJsonRpcResponse>;
getAgentCard: () => A2AAgentCard;
}Agent Types
interface AgentManifest {
capstan: string;
name: string;
description?: string;
baseUrl?: string;
authentication: {
schemes: Array<{ type: "bearer"; name: string; header: string; description: string }>;
};
resources: Array<{
key: string;
title: string;
description?: string;
fields: Record<string, { type: string; required?: boolean; enum?: string[] }>;
}>;
capabilities: Array<{
key: string;
title: string;
description?: string;
mode: "read" | "write" | "external";
resource?: string;
endpoint: { method: string; path: string; inputSchema?: Record<string, unknown>; outputSchema?: Record<string, unknown> };
policy?: string;
}>;
mcp?: { endpoint: string; transport: string };
}
interface RouteRegistryEntry {
method: string;
path: string;
description?: string;
capability?: "read" | "write" | "external";
resource?: string;
policy?: string;
inputSchema?: Record<string, unknown>;
outputSchema?: Record<string, unknown>;
}
interface AgentConfig {
name: string;
description?: string;
baseUrl?: string;
resources?: Array<{
key: string;
title: string;
description?: string;
fields: Record<string, { type: string; required?: boolean; enum?: string[] }>;
}>;
}
interface A2ATask {
id: string;
status: "submitted" | "working" | "input-required" | "completed" | "failed" | "canceled";
skill: string;
input?: unknown;
output?: unknown;
error?: string;
}@zauso-ai/capstan-react
React SSR with loaders, layouts, hydration, Image, Font, Metadata, and ErrorBoundary.
renderPage(options)
Server-side render a page component to HTML.
function renderPage(options: RenderPageOptions): Promise<RenderResult>defineLoader(loader)
Define a data loader for a page component.
function defineLoader(loader: LoaderFunction): LoaderFunction
type LoaderFunction = (args: LoaderArgs) => Promise<unknown>;
interface LoaderArgs {
params: Record<string, string>;
request: Request;
}useLoaderData()
React hook to access loader data in a page component.
function useLoaderData<T = unknown>(): TOutlet
Layout outlet component for rendering nested routes.
function Outlet(): JSX.ElementOutletProvider
Context provider for the outlet system.
function OutletProvider(props: { children: React.ReactNode }): JSX.ElementServerOnly
React component that renders its children only during SSR. Children are excluded from the client hydration bundle, enabling selective hydration.
function ServerOnly(props: { children: React.ReactNode }): JSX.Element | nullClientOnly
React component that renders its children only in the browser. During SSR, an optional fallback is rendered instead.
function ClientOnly(props: {
children: React.ReactNode;
fallback?: React.ReactNode;
}): JSX.ElementserverOnly()
Guard function that throws if called in a browser environment. Use at the top of server-only modules to prevent accidental client-side imports.
function serverOnly(): voiduseAuth()
React hook to access auth context in components.
function useAuth(): CapstanAuthContextuseParams()
React hook to access route parameters.
function useParams(): Record<string, string>hydrateCapstanPage()
Client-side hydration entry point.
function hydrateCapstanPage(): voidPageContext
React context for page data.
const PageContext: React.Context<CapstanPageContext>Image
Optimized image component with responsive srcset, lazy loading, and blur-up placeholder.
function Image(props: ImageProps): ReactElement
interface ImageProps {
src: string;
alt: string;
width?: number;
height?: number;
priority?: boolean; // eager loading + fetchpriority="high"
quality?: number; // 1-100, default 80
placeholder?: "blur" | "empty";
blurDataURL?: string;
sizes?: string;
loading?: "lazy" | "eager";
className?: string;
style?: Record<string, string | number>;
}defineFont(config)
Configure a font for optimized loading. Returns a className, style object, and CSS variable name.
function defineFont(config: FontConfig): FontResult
interface FontConfig {
family: string;
src?: string;
weight?: string | number;
style?: string;
display?: "auto" | "block" | "swap" | "fallback" | "optional";
preload?: boolean;
subsets?: string[];
variable?: string;
}
interface FontResult {
className: string;
style: { fontFamily: string };
variable?: string;
}fontPreloadLink(config)
Generate a <link rel="preload"> element for a font.
function fontPreloadLink(config: FontConfig): ReactElement | nulldefineMetadata(metadata)
Define page metadata for SEO, OpenGraph, and Twitter Cards.
function defineMetadata(metadata: Metadata): Metadata
interface Metadata {
title?: string | { default: string; template?: string };
description?: string;
keywords?: string[];
robots?: string | { index?: boolean; follow?: boolean };
openGraph?: {
title?: string;
description?: string;
type?: string;
url?: string;
image?: string;
siteName?: string;
};
twitter?: {
card?: "summary" | "summary_large_image";
title?: string;
description?: string;
image?: string;
};
icons?: { icon?: string; apple?: string };
canonical?: string;
alternates?: Record<string, string>;
}generateMetadataElements(metadata)
Convert a Metadata object into an array of React meta/title/link elements for use in <head>.
function generateMetadataElements(metadata: Metadata): ReactElement[]mergeMetadata(parent, child)
Merge two metadata objects. Child values override parent. Supports title templates: if parent has { template: "%s | Site" } and child has title: "Page", the result is "Page | Site".
function mergeMetadata(parent: Metadata, child: Metadata): MetadataErrorBoundary
React error boundary component with reset functionality.
class ErrorBoundary extends Component<ErrorBoundaryProps> {}
interface ErrorBoundaryProps {
fallback: ReactElement | ((error: Error, reset: () => void) => ReactElement);
children?: ReactNode;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
}NotFound
Pre-built 404 component for use with error boundaries or route handlers.
function NotFound(): ReactElementRenderMode and RenderStrategy
type RenderMode = "ssr" | "ssg" | "isr" | "streaming"
interface RenderStrategy {
render(ctx: RenderStrategyContext): Promise<RenderStrategyResult>
}
interface RenderStrategyContext {
options: RenderPageOptions;
url: string;
revalidate?: number;
cacheTags?: string[];
}
interface RenderStrategyResult extends RenderResult {
cacheStatus?: "HIT" | "MISS" | "STALE";
}Built-in Render Strategies
SSRStrategy -- renders the page on every request (default). ISRStrategy -- incremental static regeneration with stale-while-revalidate. SSGStrategy -- static site generation, serves pre-rendered HTML from the filesystem.
class SSRStrategy implements RenderStrategy {}
class ISRStrategy implements RenderStrategy {}
class SSGStrategy implements RenderStrategy {
constructor(staticDir?: string) // default: join(cwd(), "dist", "static")
}urlToFilePath(url, staticDir)
Maps a URL path to its pre-rendered HTML file path. Strips query strings and hash fragments before mapping.
function urlToFilePath(url: string, staticDir: string): string
// / -> {staticDir}/index.html
// /about -> {staticDir}/about/index.html
// /blog/123 -> {staticDir}/blog/123/index.htmlgenerateStaticParams
Page-level export for SSG pages with dynamic route parameters. Returns the list of param sets to pre-render at build time. Required when an SSG page has dynamic params (e.g. [id].page.tsx).
export const renderMode = "ssg";
export async function generateStaticParams(): Promise<Array<Record<string, string>>> {
return [{ id: "1" }, { id: "2" }, { id: "3" }];
}createStrategy(mode, opts?)
Factory function to create a render strategy instance.
function createStrategy(mode: RenderMode, opts?: { staticDir?: string }): RenderStrategyrenderPartialStream(options)
Render a page and its inner layouts without the document shell. Used for client-side navigation payloads.
function renderPartialStream(options: RenderPageOptions): Promise<RenderStreamResult>@zauso-ai/capstan-react/client
Client-side SPA router, navigation primitives, and prefetching.
import { Link, useNavigate, useRouterState, bootstrapClient } from "@zauso-ai/capstan-react/client";Link
Navigation link component that renders a standard <a> tag with SPA interception.
function Link(props: LinkProps): ReactElement
interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
href: string;
prefetch?: PrefetchStrategy; // default: "hover"
replace?: boolean;
scroll?: boolean; // default: true
}CapstanRouter
Core router class that manages client-side navigation state.
class CapstanRouter {
readonly state: RouterState;
navigate(url: string, opts?: NavigateOptions): Promise<void>;
prefetch(url: string): Promise<void>;
subscribe(listener: (state: RouterState) => void): () => void;
destroy(): void;
}Access via singleton:
import { getRouter, initRouter } from "@zauso-ai/capstan-react/client";
const router = getRouter(); // null if not initialized
const router = initRouter(manifest); // create singletonNavigationProvider
React context provider that bridges the imperative router with React components. Listens for capstan:navigate CustomEvents and updates PageContext.
function NavigationProvider(props: {
children: ReactNode;
initialLoaderData?: unknown;
initialParams?: Record<string, string>;
initialAuth?: { isAuthenticated: boolean; type: string };
}): ReactElementuseRouterState()
React hook that returns the current router state. Re-renders when state changes.
function useRouterState(): RouterState
interface RouterState {
url: string;
status: RouterStatus; // "idle" | "loading" | "error"
error?: Error;
}useNavigate()
React hook that returns a navigation function.
function useNavigate(): (url: string, opts?: { replace?: boolean; scroll?: boolean }) => voidbootstrapClient()
Initialize the client router. Reads window.__CAPSTAN_MANIFEST__, creates the router singleton, and sets up global <a> click delegation.
function bootstrapClient(): voidNavigationCache
LRU cache for navigation payloads.
class NavigationCache {
constructor(maxSize?: number, ttlMs?: number); // defaults: 50, 5min
get(url: string): NavigationPayload | undefined;
set(url: string, payload: NavigationPayload): void;
has(url: string): boolean;
delete(url: string): boolean;
clear(): void;
readonly size: number;
}PrefetchManager
Manages link prefetching via IntersectionObserver and pointer events. Strategies: "viewport" (IntersectionObserver, 200px margin), "hover" (80ms delay), "none".
class PrefetchManager {
observe(element: Element, strategy: PrefetchStrategy): void;
unobserve(element: Element): void;
destroy(): void;
}withViewTransition(fn)
Wrap DOM mutations in the View Transitions API when supported. Falls back to direct execution.
function withViewTransition(fn: () => void | Promise<void>): Promise<void>Client Types
type RouterStatus = "idle" | "loading" | "error";
type PrefetchStrategy = "none" | "hover" | "viewport";
interface ClientMetadata {
title?: string;
description?: string;
keywords?: string[];
robots?: string | { index?: boolean; follow?: boolean };
canonical?: string;
openGraph?: Record<string, unknown>;
twitter?: Record<string, unknown>;
icons?: Record<string, unknown>;
alternates?: Record<string, string>;
}
interface NavigationPayload {
url: string;
layoutKey: string;
html?: string;
loaderData: unknown;
metadata?: ClientMetadata;
componentType: "server" | "client";
}
interface NavigateOptions {
replace?: boolean;
state?: unknown;
scroll?: boolean;
noCache?: boolean;
}
interface ClientRouteEntry {
urlPattern: string;
componentType: "server" | "client";
layouts: string[];
}
interface ClientRouteManifest {
routes: ClientRouteEntry[];
}
interface NavigateEventDetail {
url: string;
loaderData: unknown;
params: Record<string, string>;
metadata?: ClientMetadata;
}Manifest and Scroll Utilities
function getManifest(): ClientRouteManifest | null;
function matchRoute(manifest: ClientRouteManifest, pathname: string): { route: ClientRouteEntry; params: Record<string, string> } | null;
function findSharedLayout(from: string | undefined, to: string): string;
function generateScrollKey(): string;
function setCurrentScrollKey(key: string): void;
function saveScrollPosition(): void;
function restoreScrollPosition(key: string | null): boolean;
function scrollToTop(): void;@zauso-ai/capstan-dev
Development server, Vite build pipeline, and deployment adapters.
createDevServer(config)
Create and start a development server with file watching and live reload.
function createDevServer(config: DevServerConfig): Promise<DevServerInstance>
interface DevServerConfig {
port?: number;
host?: string;
routesDir: string;
publicDir?: string;
stylesDir?: string;
}
interface DevServerInstance {
start(): Promise<void>;
stop(): Promise<void>;
port: number;
}Vite Integration
function createViteConfig(config: CapstanViteConfig): Record<string, unknown>
function createViteDevMiddleware(config: CapstanViteConfig): Promise<{ middleware: unknown; close: () => Promise<void> } | null>
function buildClient(config: CapstanViteConfig): Promise<void>
interface CapstanViteConfig {
rootDir: string;
isDev: boolean;
clientEntry?: string; // default: "app/client.tsx"
}buildStaticPages(options)
Pre-render SSG pages at build time. For each page route with renderMode === "ssg": static routes render once, dynamic routes call generateStaticParams() and render for each param set. Called automatically by capstan build --static.
function buildStaticPages(options: BuildStaticOptions): Promise<BuildStaticResult>
interface BuildStaticOptions {
rootDir: string;
outputDir: string;
manifest: RouteManifest;
}
interface BuildStaticResult {
pages: number;
paths: string[];
errors: string[];
}buildPortableRuntimeApp(config)
Build a portable runtime application without filesystem dependencies.
function buildPortableRuntimeApp(config: PortableRuntimeConfig): RuntimeAppBuildCSS Pipeline
function buildCSS(entryFile: string, outFile: string, isDev?: boolean): Promise<void>
function detectCSSMode(rootDir: string): CSSMode // "tailwind" | "lightningcss" | "none"
function buildTailwind(entryFile: string, outFile: string): Promise<void>
function startTailwindWatch(entryFile: string, outFile: string): ChildProcessFile Watchers
function watchRoutes(routesDir: string, onChange: (event: string, filePath: string) => void): FSWatcher
function watchStyles(stylesDir: string, onChange: (event: string, filePath: string) => void): FSWatcherModule Loaders
function loadRouteModule(filePath: string): Promise<unknown>
function loadApiHandlers(filePath: string): Promise<Record<string, APIDefinition>>
function loadPageModule(filePath: string): Promise<PageModule>Route Middleware
function loadRouteMiddleware(filePath: string): Promise<MiddlewareHandler>
function loadRouteMiddlewares(filePaths: string[]): Promise<MiddlewareHandler[]>
function composeRouteMiddlewares(middlewares: MiddlewareHandler[], handler: RouteHandler): RouteHandler
function runRouteMiddlewares(filePaths: string[], args: RouteHandlerArgs, handler: RouteHandler): Promise<Response>Virtual Route Modules
Register in-memory virtual route modules for testing or dynamic routes.
function registerVirtualRouteModule(filePath: string, mod: unknown): void
function registerVirtualRouteModules(modules: Map<string, unknown>): void
function clearVirtualRouteModules(filePath?: string): voidPlatform Adapters
function createCloudflareHandler(app: { fetch: (req: Request) => Promise<Response> }): { fetch(...): Promise<Response> }
function createVercelHandler(app: { fetch: (req: Request) => Promise<Response> }): (req: Request) => Promise<Response>
function createVercelNodeHandler(app: { fetch: (req: Request) => Promise<Response> }): (req: IncomingMessage, res: ServerResponse) => Promise<void>
function createFlyAdapter(config?: FlyConfig): ServerAdapter
function createNodeAdapter(): ServerAdapter
function createBunAdapter(): ServerAdapter
interface FlyConfig {
primaryRegion?: string;
replayWrites?: boolean;
}Deployment Config Generators
function generateWranglerConfig(name: string): string
function generateVercelConfig(): object
function generateFlyToml(config?: FlyConfig): stringOther Dev Utilities
function printStartupBanner(config: { port: number; routes: number }): void
function createPageFetch(request: Request, options?: PageFetchOptions): PageFetchClient
interface PageFetchClient {
get(path: string, init?: RequestInit): Promise<Response>;
post(path: string, body?: unknown, init?: RequestInit): Promise<Response>;
put(path: string, body?: unknown, init?: RequestInit): Promise<Response>;
delete(path: string, init?: RequestInit): Promise<Response>;
}
class PageFetchError extends Error {
method: string;
url: string;
phase: string;
status?: number;
}
class RouteMiddlewareLoadError extends Error {}
class RouteMiddlewareExportError extends Error {}@zauso-ai/capstan-cli
Command-line interface for development, building, deployment, verification, and operations.
Development
capstan dev
Start development server with live reload.
capstan dev [--port <number>] [--host <string>]| Flag | Default | Description |
|---|---|---|
--port | 3000 | Server port |
--host | localhost | Server host |
capstan build
Build for production.
capstan build [--static] [--target <target>]| Flag | Description |
|---|---|
--static | Pre-render SSG pages |
--target | Build target: node-standalone, docker, vercel-node, vercel-edge, cloudflare, fly |
Output: dist/ with _capstan_server.js, _capstan_manifest.json, openapi.json, deploy-manifest.json, public/.
capstan start
Start production server from built output.
capstan start [--from <dir>] [--port <number>] [--host <string>]| Flag | Default | Description |
|---|---|---|
--from | . | Directory containing dist/ |
--port | 3000 | Server port |
--host | 0.0.0.0 | Server host |
Scaffolding
capstan add
Scaffold new components.
capstan add model <name> # -> app/models/<name>.model.ts
capstan add api <name> # -> app/routes/<name>/index.api.ts
capstan add page <name> # -> app/routes/<name>/index.page.tsx
capstan add policy <name> # -> app/policies/index.ts (appends)Database
capstan db:migrate
Generate migration SQL from model definitions.
capstan db:migrate --name <migration-name>Creates timestamped migration file in app/migrations/.
capstan db:push
Apply all pending migrations to the database.
capstan db:pushcapstan db:status
Show migration status: applied, pending, and database state.
capstan db:statusVerification
capstan verify
Run 8-step verification cascade or deployment verification.
capstan verify [<path>] [--json] [--deployment] [--target <target>]| Flag | Description |
|---|---|
--json | Output structured JSON for AI agents |
--deployment | Verify deployment mode (requires built dist/) |
--target | Specific deployment target to verify |
Runtime mode cascade: structure -> config -> routes -> models -> typecheck -> contracts -> manifest -> protocols.
Deployment mode: validates integrity hashes, target compatibility, database provider, auth config.
Deployment
capstan deploy:init
Generate root deployment files for a target.
capstan deploy:init [--target <target>] [--force]| Flag | Default | Description |
|---|---|---|
--target | docker | Deployment target: docker, vercel-node, vercel-edge, cloudflare, fly |
--force | false | Overwrite existing files |
Generates platform-specific configs: Dockerfile, vercel.json, wrangler.toml, fly.toml, .dockerignore, .env.example.
Agent / Protocol
capstan mcp
Start MCP server via stdio for Claude Desktop / Cursor. Scans routes, extracts defineAPI metadata, converts Zod schemas to JSON Schema, and serves MCP tools via stdio.
capstan mcpcapstan agent:manifest
Print the agent manifest JSON to stdout.
capstan agent:manifestcapstan agent:openapi
Print the OpenAPI 3.1 spec JSON to stdout.
capstan agent:openapiOperations
capstan ops:events
List recent ops events.
capstan ops:events [<path>] [--kind <kind>] [--severity <severity>] [--limit <n>] [--since <timestamp>] [--json]capstan ops:incidents
List incidents from the ops store.
capstan ops:incidents [<path>] [--status <status>] [--severity <severity>] [--limit <n>] [--since <timestamp>] [--json]capstan ops:health
Show derived health snapshot. Reports: status (healthy/degraded/unhealthy), total events, incidents, open incidents, critical/warning counts, top issues.
capstan ops:health [<path>] [--json]capstan ops:tail
Show latest ops feed (merged events + incidents). --follow polls every 1 second for new items.
capstan ops:tail [<path>] [--limit <n>] [--follow] [--json]Harness Runtime
Commands for managing durable AI agent runs. All accept --root <dir>, --grants <json>, --subject <json>, --json.
capstan harness:list # List persisted runs
capstan harness:get <runId> # Read one run record
capstan harness:events [<runId>] # Read runtime events
capstan harness:artifacts <runId> # List artifacts for a run
capstan harness:checkpoint <runId> # Read loop checkpoint
capstan harness:approval <approvalId> # Read one approval record
capstan harness:approvals [<runId>] # List approvals
capstan harness:approve <runId> [--note <text>] # Approve a blocked run
capstan harness:deny <runId> [--note <text>] # Deny and cancel
capstan harness:pause <runId> # Request cooperative pause
capstan harness:cancel <runId> # Request cancellation
capstan harness:replay <runId> # Replay events and verify state
capstan harness:paths # Print harness filesystem pathscreate-capstan-app
Project scaffolder CLI.
CLI Usage
# Interactive mode
npx create-capstan-app@beta
# With project name (prompts for template)
npx create-capstan-app@beta my-app
# Fully non-interactive
npx create-capstan-app@beta my-app --template blank
npx create-capstan-app@beta my-app --template tickets
# With deployment target
npx create-capstan-app my-app --template blank --deploy docker
npx create-capstan-app my-app --template tickets --deploy vercel-node
# Help
npx create-capstan-app@beta --helpTemplates
| Template | Includes |
|---|---|
blank | Health check API, home page, root layout, requireAuth policy, AGENTS.md |
tickets | Everything in blank + Ticket model, CRUD routes, auth config, database config |
scaffoldProject(config)
Programmatic API for the scaffolder.
function scaffoldProject(config: {
projectName: string;
template: "blank" | "tickets";
outputDir: string;
}): Promise<void>Deploy Target Generation
type DeployTarget = "none" | "docker" | "vercel-node" | "vercel-edge" | "cloudflare" | "fly"Template Generators
Programmatic template content generators for scaffolding:
function packageJson(projectName: string, template?: Template): string
function tsconfig(): string
function capstanConfig(projectName: string, title: string, template?: Template): string
function rootLayout(title: string): string
function indexPage(title: string, projectName: string, template?: Template): string
function healthApi(): string
function policiesIndex(): string
function gitignore(): string
function dockerfile(): string
function dockerignore(): string
function envExample(): string
function mainCss(): string
function agentsMd(projectName: string, template: Template): stringTemplate-specific generators (tickets template):
function ticketModel(): string
function ticketsIndexApi(): string
function ticketByIdApi(): stringDeployment config generators:
function flyDockerfile(): string
function flyToml(appName: string): string
function vercelConfig(target: "vercel-node" | "vercel-edge"): string
function wranglerConfig(appName: string): stringInteractive Prompts
function runPrompts(): Promise<{
projectName: string;
template: Template;
deploy: DeployTarget;
}>
function detectPackageManagerRuntime(isBun?: boolean): PackageManagerRuntime
interface PackageManagerRuntime {
installCommand: string;
runCommand: string;
devCommand: string;
}