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): CapstanConfig

CapstanConfig:

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): string

createCapstanApp(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(): void

getAPIRegistry()

Return all API definitions registered via defineAPI().

function getAPIRegistry(): ReadonlyArray<APIDefinition>

createContext(honoCtx)

Create a CapstanContext from a Hono context.

function createContext(honoCtx: HonoContext): CapstanContext

createCapstanOpsContext(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 | undefined

createCapstanOpsRuntime(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;
}): CapstanOpsRuntime

Approval 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(): void

PendingApproval 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,
): void

verifyCapstanApp(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): string

definePlugin(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>): void

RedisStore

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;
}): void

getAuditLog(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(): void

defineWebSocket(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>>): void

Response 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>): void

csrfProtection

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(): MiddlewareHandler

createRequestLogger

Create structured JSON request logging middleware. Respects LOG_LEVEL env var (debug/info/warn/error).

function createRequestLogger(): MiddlewareHandler

Cache 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"
}): LLMProvider

anthropicProvider(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"
}): LLMProvider

LLM 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): CronJobConfig

createCronRunner()

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(): CronRunner

createAgentCron(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[];
  },
): ModelDefinition

field

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): string

defineEmbedding(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
}): EmbeddingAdapter

generateDrizzleSchema(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>): CrudRuntime
// 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"
): string

verifySession(token, secret)

Verify a JWT signature and expiration. Returns the payload on success, null on failure.

function verifySession(token: string, secret: string): SessionPayload | null

generateApiKey(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): string

createAuthMiddleware(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[],
): boolean

derivePermission(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;
}): OAuthProvider

githubProvider(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;
}): OAuthProvider

createOAuthHandlers(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): RuntimeActionRequest

Execution Identity

function createExecutionIdentity(kind: string, source: string): ExecutionIdentity
function createRequestExecution(request: Request): ExecutionIdentity
function createDelegationLink(from: Identity, to: Identity): DelegationLink

DPoP 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,
): CanonicalizedRouteManifest

createRouteScanCache()

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): string

generateOpenApiSpec(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>(): T

Outlet

Layout outlet component for rendering nested routes.

function Outlet(): JSX.Element

OutletProvider

Context provider for the outlet system.

function OutletProvider(props: { children: React.ReactNode }): JSX.Element

ServerOnly

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 | null

ClientOnly

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.Element

serverOnly()

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(): void

useAuth()

React hook to access auth context in components.

function useAuth(): CapstanAuthContext

useParams()

React hook to access route parameters.

function useParams(): Record<string, string>

hydrateCapstanPage()

Client-side hydration entry point.

function hydrateCapstanPage(): void

PageContext

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;
}

Generate a <link rel="preload"> element for a font.

function fontPreloadLink(config: FontConfig): ReactElement | null

defineMetadata(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): Metadata

ErrorBoundary

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(): ReactElement

RenderMode 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.html

generateStaticParams

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 }): RenderStrategy

renderPartialStream(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";

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 singleton

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 };
}): ReactElement

useRouterState()

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 }) => void

bootstrapClient()

Initialize the client router. Reads window.__CAPSTAN_MANIFEST__, creates the router singleton, and sets up global <a> click delegation.

function bootstrapClient(): void

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): RuntimeAppBuild

CSS 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): ChildProcess

File Watchers

function watchRoutes(routesDir: string, onChange: (event: string, filePath: string) => void): FSWatcher
function watchStyles(stylesDir: string, onChange: (event: string, filePath: string) => void): FSWatcher

Module 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): void

Platform 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): string

Other 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>]
FlagDefaultDescription
--port3000Server port
--hostlocalhostServer host

capstan build

Build for production.

capstan build [--static] [--target <target>]
FlagDescription
--staticPre-render SSG pages
--targetBuild 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>]
FlagDefaultDescription
--from.Directory containing dist/
--port3000Server port
--host0.0.0.0Server 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:push

capstan db:status

Show migration status: applied, pending, and database state.

capstan db:status

Verification

capstan verify

Run 8-step verification cascade or deployment verification.

capstan verify [<path>] [--json] [--deployment] [--target <target>]
FlagDescription
--jsonOutput structured JSON for AI agents
--deploymentVerify deployment mode (requires built dist/)
--targetSpecific 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]
FlagDefaultDescription
--targetdockerDeployment target: docker, vercel-node, vercel-edge, cloudflare, fly
--forcefalseOverwrite 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 mcp

capstan agent:manifest

Print the agent manifest JSON to stdout.

capstan agent:manifest

capstan agent:openapi

Print the OpenAPI 3.1 spec JSON to stdout.

capstan agent:openapi

Operations

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 paths

create-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 --help

Templates

TemplateIncludes
blankHealth check API, home page, root layout, requireAuth policy, AGENTS.md
ticketsEverything 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): string

Template-specific generators (tickets template):

function ticketModel(): string
function ticketsIndexApi(): string
function ticketByIdApi(): string

Deployment config generators:

function flyDockerfile(): string
function flyToml(appName: string): string
function vercelConfig(target: "vercel-node" | "vercel-edge"): string
function wranglerConfig(appName: string): string

Interactive Prompts

function runPrompts(): Promise<{
  projectName: string;
  template: Template;
  deploy: DeployTarget;
}>

function detectPackageManagerRuntime(isBun?: boolean): PackageManagerRuntime

interface PackageManagerRuntime {
  installCommand: string;
  runCommand: string;
  devCommand: string;
}