SDK overview
Drive a crouter daemon from a Node or browser application with the typed SDK.
The ESM-only client an application installs to drive a crouter daemon: create agent runs, watch streamed events, wait for typed results, and reach the rest of the daemon's /v1 API.
One package, one class. new Crouter() in Node talks to the owner's unix socket. new Crouter({ baseURL, token }) in a browser or a remote process talks to the daemon's TCP listener with a bearer token. Resource methods issue /v1 requests; createAndWait, parse, and auth.status compose more than one request. crtrd stays the sole owner of canvas state.
import Crouter from '@crouter/sdk';
import { z } from 'zod';
const client = new Crouter();
const run = await client.nodes.parse({
prompt: 'Summarize the failing tests in this repo.',
cwd: '/path/to/repo',
output_schema: z.object({ failures: z.array(z.string()), root_cause: z.string() }),
});
if (run.kind === 'result') console.log(run.output_parsed.root_cause);Go live with an app key
Once you have installed the SDK and tried the local flow in Getting started, generate the app's key on your server:
npx @crouter/sdk keygen --app <client id>keygen saves the private JWK to local/app-key.json (mode 0600); keep that file out of git. With --app, it offers to add the public key to your registered app through your own crtr, as an additional key that never replaces the current one. Otherwise, click Add key on the app's Sign-in tab and paste the public JWK printed on stdout (or paste it under Register an app for a new app). An app authenticates only with its key; the directory issues no client secret. On your server, load the saved key and pass it as privateKey:
import {readFile} from 'node:fs/promises';
import {OAuth2Client} from '@crouter/sdk';
const privateKey = JSON.parse(await readFile('local/app-key.json', 'utf8'));
const oauth = new OAuth2Client({clientId, issuer, redirectUri, privateKey});
const {url, state, codeVerifier, nonce} = await oauth.authorizeUrl({scopes: ['openid', 'crtr:llm', 'crtr:act'], state: sessionState});
// Save state, codeVerifier, nonce in the user's session, then redirect to url.
const connection = await oauth.exchangeCode({code: callbackCode, state: callbackState, expectedState: state, codeVerifier, nonce});
const client = oauth.client(connection, store); // Store the connection for later refreshes.The directory rejects the key until its public JWK is added to the app. To change keys later, follow Rotating your key: add the new key, switch the app to it, verify a real sign-in, then retire the old key. An app can hold two active keys at once, so there is no downtime.
Pages
| Page | Covers |
|---|---|
| Getting started | Install, new Crouter() on the owner's socket, client.auth.status() and crtr sys connect for a browser or remote app, Chrome's local-network prompt |
| Client construction | Every constructor option and its environment-variable fallback; per-request options |
| Nodes | create parameters, the outcome union, parse() with a zod schema, waitForOutcome, message, cancel, nested resources |
| Streaming | The event table, stream() / events(), the activity helper, resume with after, stream_gap and stream_dropped |
| Files | Absolute-path reads, writes, and one-level lists |
| Bash | One bounded command and its output result |
| Memory | memory.list and memory.get: an app reads the person's public documents with no scope |
| Resource map | Every namespace (including customObjects, runs, memory, shares, providers) with its routes, what is deliberately excluded, and the client.request() escape hatch |
| Errors | The error class table and the retry policy |
| Docker environment | start(), attach(), connection() from @crouter/env-docker |
| Migration | Moving off generate() and local() — a hard cut, with before/after |
| Release notes | Behavior changes an existing app must act on, newest first |
| Apps on crouter cloud | Typed results from app runs with runs.parse, declines, bounding a run, and run_idle |
| Apps with many users | Per-person runtime connections, app-owned user access, privacy, and spending |
Phases
The surface ships in three cuts.
| Phase | What lands | State |
|---|---|---|
| Phase 1 | Client construction and transports; client.auth.status(); client.nodes.create, retrieve, list, outcome, waitForOutcome, createAndWait, parse, message, cancel, and interrupt; nodes.reports.list; profiles.ensure and retrieve; system.status and health; the error hierarchy; per-request options; crtr sys connect; and env-docker.connection() | Shipped |
| Phase 2 namespaces | Node lifecycle, job, worktree, and result; canvas and canvas history; crons; human requests and inbox; models; client.files.read through /v1/files/peek, write, and list; and client.bash.run | Shipped |
| Phase 2 streaming | GET /v1/nodes/{id}/events, nodes.stream(params, options?), nodes.events(id, options?), NodeStream, and the activity helper | Shipped |
| Phase 3 | The review, comment, and chat-inventory namespaces | Not shipped |
The per-run scopes create field ships in phase 1. nodes.update scope support remains phase 2 and is not in the shipped client or wire declarations.
Verification status
The SDK declarations and streaming examples are verified.
Where the agents read this
The same content routed for agents lives in the built-in document crouter-sdk, which ships with the crtr binary. An agent working in an application's repository reaches it with crtr canvas read crouter-sdk and does not need this repository checked out.
Why a daemon
When deciding how an application should host or reconnect to an agent, read this because the daemon keeps the durable canvas and broker lifecycle in one place while terminals and SDK clients come and go.
Getting started
Connect to a local or remote daemon and run your first agent with the SDK.