@north-light/crouter 0.3.330 → 0.3.331

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/builtin-memory/crouter-plugin/INDEX.md +12 -0
  2. package/dist/builtin-memory/crouter-plugin/README.md +25 -0
  3. package/dist/builtin-memory/crouter-plugin/bundles-and-memory.md +32 -0
  4. package/dist/builtin-memory/crouter-plugin/commands.md +21 -0
  5. package/dist/builtin-memory/crouter-plugin/deploying.md +33 -0
  6. package/dist/builtin-memory/crouter-plugin/errors.md +42 -0
  7. package/dist/builtin-memory/crouter-plugin/getting-started.md +28 -0
  8. package/dist/builtin-memory/crouter-plugin/output.md +30 -0
  9. package/dist/builtin-memory/crouter-plugin/parameters.md +29 -0
  10. package/dist/builtin-memory/crouter-sdk/INDEX.md +13 -0
  11. package/dist/builtin-memory/crouter-sdk/README.md +65 -0
  12. package/dist/builtin-memory/crouter-sdk/bash.md +41 -0
  13. package/dist/builtin-memory/crouter-sdk/client.md +98 -0
  14. package/dist/builtin-memory/crouter-sdk/docker.md +72 -0
  15. package/dist/builtin-memory/crouter-sdk/errors.md +112 -0
  16. package/dist/builtin-memory/crouter-sdk/files.md +51 -0
  17. package/dist/builtin-memory/crouter-sdk/getting-started.md +169 -0
  18. package/dist/builtin-memory/crouter-sdk/memory.md +88 -0
  19. package/dist/builtin-memory/crouter-sdk/migration.md +118 -0
  20. package/dist/builtin-memory/crouter-sdk/nodes.md +197 -0
  21. package/dist/builtin-memory/crouter-sdk/resources.md +88 -0
  22. package/dist/builtin-memory/crouter-sdk/streaming.md +148 -0
  23. package/package.json +1 -1
  24. package/runtime.lock.json +8 -8
  25. package/dist/builtin-memory/crouter-plugin.md +0 -16
  26. package/dist/builtin-memory/crouter-sdk.md +0 -90
@@ -0,0 +1,12 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When an application needs to expose operations as crtr commands over HTTP, this knowledge should be read because one typed command definition can provide the handler and install archive without a hand-written manifest drifting from the app.
4
+ short-form: Use @north-light/crouter-plugin to expose an application's typed HTTP operations as crtr commands from one TypeScript command tree.
5
+ surfaces:
6
+ - on: boot
7
+ at: preview
8
+ ---
9
+
10
+ # `@north-light/crouter-plugin`
11
+
12
+ Read `crouter-plugin/README` for the authoring overview and the directory pages for the specific plugin surface. The directory listing names every page. Use the directory only to choose a focused page; the reference pages hold the authoring details.
@@ -0,0 +1,25 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When you need Authoring crtr HTTP plugins, this knowledge
4
+ should be read because `@north-light/crouter-plugin` turns one TypeScript
5
+ command tree into both a Fetch handler and the archive accepted by `crtr pkg
6
+ plugin install --endpoint`. Use it when an application should expose typed
7
+ operations as native `crtr` commands without maintaining `commands.json` or a
8
+ separate HTTP route definition.
9
+ ---
10
+
11
+ # Authoring crtr HTTP plugins
12
+
13
+ `@north-light/crouter-plugin` turns one TypeScript command tree into both a Fetch handler and the archive accepted by `crtr pkg plugin install --endpoint`. Use it when an application should expose typed operations as native `crtr` commands without maintaining `commands.json` or a separate HTTP route definition.
14
+
15
+ Start with the `node_modules/@north-light/crouter-plugin/README.md` for a complete plugin that a developer can paste into an empty file. Then read the page that matches the work at hand:
16
+
17
+ - [[crouter-plugin/getting-started]] — the deployment and install sequence.
18
+ - [[crouter-plugin/commands]] — command trees and descriptions that let an agent choose a command.
19
+ - [[crouter-plugin/parameters]] — every `param.*` builder and the inferred handler input type.
20
+ - [[crouter-plugin/output]] — every `field.*` builder and handler result validation.
21
+ - [[crouter-plugin/errors]] — `LeafError`, HTTP errors, and NDJSON streaming leaves.
22
+ - [[crouter-plugin/deploying]] — Fetch frameworks, mount paths, proxies, authentication, and archive compression.
23
+ - [[crouter-plugin/bundles-and-memory]] — generating an archive and shipping memory docs.
24
+
25
+ The package exports only `LeafError`, `ManifestInvalidError`, `PluginDefinitionError`, `buildBundle`, `buildCommandManifest`, `createFetchHandler`, `defineBranch`, `defineLeaf`, `definePlugin`, `defineStreamingLeaf`, `field`, `kebab`, and `param`. Those are the complete authoring surface.
@@ -0,0 +1,32 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When you need Bundles and memory docs, this knowledge
4
+ should be read because `createFetchHandler` builds and serves the install
5
+ archive automatically. Use `buildBundle` when you need to inspect or save the
6
+ generated bytes during an application build. Pass the same mount path where
7
+ the handler is served.
8
+ ---
9
+
10
+ # Bundles and memory docs
11
+
12
+ `createFetchHandler` builds and serves the install archive automatically. Use `buildBundle` when you need to inspect or save the generated bytes during an application build. Pass the same mount path where the handler is served.
13
+
14
+ ```ts
15
+ import { writeFile } from 'node:fs/promises';
16
+ import { buildBundle } from '@north-light/crouter-plugin';
17
+ import { plugin } from './crtr-plugin.js';
18
+
19
+ const bundle = await buildBundle(plugin, { mountPath: '/crtr' });
20
+ await writeFile('commands.json', bundle.commandsJson);
21
+ await writeFile('acme.tar', bundle.tar);
22
+ ```
23
+
24
+ `buildBundle` returns `commandsJson`, `bundleJson`, generated memory members, uncompressed tar bytes, and an ETag. It runs the manifest validator and throws `ManifestInvalidError` when the generated command manifest is invalid. `buildCommandManifest(plugin, { mountPath })` returns the manifest object when archive bytes are not needed.
25
+
26
+ The tar has exactly `bundle.json`, `commands.json`, and `memory/<name>.md` members for the memory docs declared in `definePlugin`. It does not contain `plugin.json`. For an endpoint install, crouter creates `.crouter-plugin/plugin.json` from the endpoint, `--name`, `--auth-env`, and archive hash.
27
+
28
+ ## Memory docs
29
+
30
+ Add a `memory` array to `definePlugin` when the app needs to give agents reusable operating knowledge. Each entry has `name`, `kind`, `whenAndWhyToRead`, `body`, and optional `unlisted`. `name` is a safe relative path without the `.md` suffix. The package writes the frontmatter required by the archive installer, so the author supplies no YAML.
31
+
32
+ `whenAndWhyToRead` is one routing sentence in the form `When <circumstance>, this <kind> should be read because <payoff>.` Keep the body to knowledge the agent needs to use the application. It becomes available under the installed plugin name together with the generated commands.
@@ -0,0 +1,21 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: "When you need Commands, this knowledge should be read
4
+ because `definePlugin` declares the top-level crtr command. `name` must be
5
+ lowercase kebab case. `description`, `whenToUse`, and `summary` are required
6
+ text for the plugin, every branch, and every leaf. Write them for an agent
7
+ choosing a command: describe the concrete object or action, state when the
8
+ command applies, and state the short outcome."
9
+ ---
10
+
11
+ # Commands
12
+
13
+ `definePlugin` declares the top-level crtr command. `name` must be lowercase kebab case. `description`, `whenToUse`, and `summary` are required text for the plugin, every branch, and every leaf. Write them for an agent choosing a command: describe the concrete object or action, state when the command applies, and state the short outcome.
14
+
15
+ `rootEntry` describes the top-level command and requires `concept`, `description`, and `whenToUse`. `commands` is an object whose keys are camelCase or lowercase names. The package converts each key to the lowercase kebab name crtr exposes. A key must round-trip, so use `appId` for `app-id`, not a spelling that converts ambiguously.
16
+
17
+ Use `defineBranch` to group commands and give it `children`. Use `defineLeaf` for a request that returns one object. A leaf requires `params`, `output`, `effects`, and `handler`; `params` may be omitted when the command has no input. `effects` is a non-empty list shown to the agent. Say whether a command mutates an application, creates billable resources, or is read-only.
18
+
19
+ `defineStreamingLeaf` has the same declaration shape, but its handler returns `AsyncIterable<object>`. It marks the generated REST mapping as streaming and is covered in [[crouter-plugin/errors]].
20
+
21
+ All command and parameter keys are derived into the manifest. Do not add a route path, HTTP method, REST mapping, or separate manifest to a command definition. The package always generates `POST` with all parameters in the JSON body.
@@ -0,0 +1,33 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: "When you need Deployment, this knowledge should be read
4
+ because `createFetchHandler(plugin, options)` returns `(request: Request) =>
5
+ Promise<Response>`. Use it directly in Cloudflare Workers, Bun, Deno, and any
6
+ framework route that accepts Fetch `Request` and `Response` objects. A
7
+ framework with different request types needs only an adapter at its boundary;
8
+ the package itself has no framework dependency."
9
+ ---
10
+
11
+ # Deployment
12
+
13
+ `createFetchHandler(plugin, options)` returns `(request: Request) => Promise<Response>`. Use it directly in Cloudflare Workers, Bun, Deno, and any framework route that accepts Fetch `Request` and `Response` objects. A framework with different request types needs only an adapter at its boundary; the package itself has no framework dependency.
14
+
15
+ The handler serves the archive for every `GET` request and runs a command for a matching `POST` request. It routes a command from the longest matching suffix of the request path, so the same handler works at the origin root, under `/crtr`, and behind a proxy that preserves that path.
16
+
17
+ ## Mount paths
18
+
19
+ Install the public URL where the handler is mounted. For `https://acme.example.com/crtr`, run `crtr pkg plugin install --endpoint https://acme.example.com/crtr --name acme`. The archive generated for that request declares `/crtr/acme/...` as every `rest.path`.
20
+
21
+ This prefix is required because crtr stores only `https://acme.example.com` as the command transport endpoint after installation. An archive declaring `/acme/...` for a handler mounted at `/crtr` sends commands to the origin root. Do not use `baseUrl` as a repair; absolute paths replace a base URL path.
22
+
23
+ Set `baseUrl` only when `request.url` is not the public URL, such as a proxy that rewrites the path or terminates TLS on a different host. Pass the complete public URL, including its mount path, to `createFetchHandler`. The generated `rest.path` must be the public path an agent could request directly.
24
+
25
+ ## Authentication
26
+
27
+ Pass `token` to require `Authorization: Bearer <token>` on archive and command requests. `--auth-env NAME` tells crtr which environment variable of the calling process holds that value. It does not configure the server. For a command an agent runs, the calling process is its broker, which reads the profile env store (`crtr profile env set <profile> --name NAME`) rather than your shell's exports. Omitting `token` serves without authentication and logs a warning when the handler is created.
28
+
29
+ ## Archive response
30
+
31
+ The install response is an uncompressed tar with `Content-Type: application/x-tar`. Do not configure a proxy, CDN, or framework middleware to gzip or otherwise compress it. Crtr rejects compressed archive bytes. The handler sends an ETag and supports conditional `If-None-Match` requests automatically.
32
+
33
+ A non-streaming successful command response is a bare JSON result object. Error responses are JSON error envelopes on non-2xx statuses. See [[crouter-plugin/errors]] for the exact error shape.
@@ -0,0 +1,42 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When you need Errors and streaming, this knowledge should
4
+ be read because Throw `LeafError` when an expected application error should be
5
+ reported to crtr. Its `code` must be lowercase snake case and cannot be
6
+ `internal`, `unknown_path`, `command_collision`, or `cli_protocol_error`.
7
+ `status` defaults to `400` and must be an integer from `400` through `599`.
8
+ Use `field`, `next`, and `received` when they make the fix clearer.
9
+ ---
10
+
11
+ # Errors and streaming
12
+
13
+ Throw `LeafError` when an expected application error should be reported to crtr. Its `code` must be lowercase snake case and cannot be `internal`, `unknown_path`, `command_collision`, or `cli_protocol_error`. `status` defaults to `400` and must be an integer from `400` through `599`. Use `field`, `next`, and `received` when they make the fix clearer.
14
+
15
+ ```ts
16
+ import { LeafError } from '@north-light/crouter-plugin';
17
+
18
+ function missingApp(appId: string): never {
19
+ throw new LeafError({
20
+ code: 'app_not_found',
21
+ message: `No application ${appId}.`,
22
+ status: 404,
23
+ field: 'app-id',
24
+ next: 'Run acme app create first.',
25
+ });
26
+ }
27
+ ```
28
+
29
+ The handler turns that error into a non-2xx response with exactly `{ "error": { "code", "message", "field?", "next?", "received?", "manifest_stale?" } }`. A `400`, `401`, `403`, or `422` response is a crtr usage error; `404` is not found; `409` is ambiguous; other statuses are general errors. `cli_protocol_error` is reserved for crtr itself.
30
+
31
+ For a non-streaming leaf, an unexpected exception calls `onError(error, commandPath)` when supplied to `createFetchHandler`, then returns status `500` with code `handler_failed`. A streaming handler that throws before it yields its first frame follows the same path. After the first frame starts the `200 application/x-ndjson` response, a later exception calls `onError` and interrupts the response; it cannot send a `handler_failed` envelope or append an error frame. `LeafError({ manifestStale: true })` adds `manifest_stale: true` to the error response. Crtr refetches the archive and replays the original command once, so use it only when the refreshed manifest differs and replaying the command is safe.
32
+
33
+ A streaming handler declared with `defineStreamingLeaf` returns an async iterable. Each yielded object becomes `JSON.stringify(frame) + '\n'` in an `application/x-ndjson` response. Crtr relays each nonblank line without parsing it, so streaming frames have no package-defined schema and there is no terminal frame to yield. The declared output fields describe frames in command help but are not validated during streaming.
34
+
35
+ ```ts
36
+ async function* lines(): AsyncIterable<object> {
37
+ yield { line: 'Starting deployment.' };
38
+ yield { line: 'Deployment complete.' };
39
+ }
40
+ ```
41
+
42
+ Pass `lines` as a `defineStreamingLeaf` handler when the yielded objects match the fields you want agents to see.
@@ -0,0 +1,28 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When you need Getting started, this knowledge should be
4
+ read because Install `@north-light/crouter-plugin`, copy the complete
5
+ TypeScript file in the `node_modules/@north-light/crouter-plugin/README.md`,
6
+ and deploy its default export at the URL crtr will reach. The application must
7
+ provide `ACME_CRTR_TOKEN` to its handler and the machine running crtr must
8
+ provide the same value.
9
+ ---
10
+
11
+ # Getting started
12
+
13
+ Install `@north-light/crouter-plugin`, copy the complete TypeScript file in the `node_modules/@north-light/crouter-plugin/README.md`, and deploy its default export at the URL crtr will reach. The application must provide `ACME_CRTR_TOKEN` to its handler and the machine running crtr must provide the same value.
14
+
15
+ ```bash
16
+ npm install @north-light/crouter-plugin
17
+ export ACME_CRTR_TOKEN='replace-with-a-secret'
18
+ crtr pkg plugin install --endpoint https://acme.example.com/crtr --name acme --auth-env ACME_CRTR_TOKEN
19
+ crtr acme app create my-app --region eu-west
20
+ ```
21
+
22
+ `export` covers commands you type yourself. An agent's `crtr acme …` runs inside its broker, whose env comes from the profile env store, not from your shell — so for agents, store the token there too: `echo -n "$ACME_CRTR_TOKEN" | crtr profile env set <profile> --name ACME_CRTR_TOKEN`. Nodes launched under that profile from then on carry it.
23
+
24
+ The endpoint receives `GET` to provide the install archive and `POST` to run a command. A successful non-streaming command response is the bare JSON object returned by the handler, for example `{ "appId": "app_my-app", "url": "https://my-app.eu-west.acme.example.com" }`. It is not an exec-style protocol envelope.
25
+
26
+ Run `crtr pkg plugin show acme` after installation to inspect the installed command tree, and `crtr sys doctor` to check the installed package. A plugin name collision with crtr or another installed plugin is determined during installation and cannot be predicted from an application repository alone.
27
+
28
+ Continue with [[crouter-plugin/deploying]] before mounting the handler below the origin root or behind a proxy.
@@ -0,0 +1,30 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When you need Output fields, this knowledge should be read
4
+ because Each leaf declares an `output` object. Field object keys are returned
5
+ verbatim in the result object, so `appId` stays `appId`; unlike command and
6
+ parameter keys, output keys are not converted to kebab case.
7
+ ---
8
+
9
+ # Output fields
10
+
11
+ Each leaf declares an `output` object. Field object keys are returned verbatim in the result object, so `appId` stays `appId`; unlike command and parameter keys, output keys are not converted to kebab case.
12
+
13
+ | Builder | Required handler value |
14
+ | --- | --- |
15
+ | `field.string(constraint, options?)` | `string` |
16
+ | `field.int(constraint, options?)` | integer `number` |
17
+ | `field.number(constraint, options?)` | `number` |
18
+ | `field.bool(constraint, options?)` | `boolean` |
19
+ | `field.object(constraint, options?)` | object |
20
+ | `field.array(constraint, options?)` | array |
21
+ | `field.markdown(constraint, options?)` | `string` |
22
+ | `field.path(constraint, options?)` | `string` |
23
+ | `field.of(type, constraint, options?)` | `unknown` |
24
+ | `field.nullable(field)` | the wrapped field value or `null` |
25
+
26
+ Fields are required by default. Pass `{ required: false }` for a field that can be absent. `field.nullable(field.string('...'))` declares a present field that can be `string | null`; it does not make the field optional.
27
+
28
+ The handler result type is inferred from `output`. TypeScript reports a missing required field or a wrong value type at the `defineLeaf` call. The Fetch handler checks the returned object again before sending it. It rejects missing fields, known type mismatches, and keys that were not declared, returning a non-2xx `handler_output_invalid` error instead of a JSON success response.
29
+
30
+ `field.of` accepts any non-empty manifest type string. Its handler value is `unknown` because crtr only has built-in runtime checks for its recognized type grammar. Use one of the named builders when the result type is known.
@@ -0,0 +1,29 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: "When you need Parameters and handler input, this
4
+ knowledge should be read because Parameter object keys become handler input
5
+ keys. The generated manifest uses their kebab-case form: `appId` becomes
6
+ `app-id`, while the handler receives `input.appId`."
7
+ ---
8
+
9
+ # Parameters and handler input
10
+
11
+ Parameter object keys become handler input keys. The generated manifest uses their kebab-case form: `appId` becomes `app-id`, while the handler receives `input.appId`.
12
+
13
+ Every `param.*` builder takes constraint text first. Parameters are optional unless `required: true` is supplied. `repeatable: true` makes a string, integer, enum, or positional parameter an array. It cannot be used with boolean or path parameters. A parameter with `default` remains optional in handler input because crtr applies that default while parsing its command line; the server does not apply it.
14
+
15
+ | Builder | Handler value | Options |
16
+ | --- | --- | --- |
17
+ | `param.string(constraint, options?)` | `string` | `required`, `repeatable`, `default` |
18
+ | `param.int(constraint, options?)` | `number` | `required`, `repeatable`, `default` |
19
+ | `param.bool(constraint, options?)` | `boolean` | `required`, `default` |
20
+ | `param.enum(choices, constraint, options?)` | a member of `choices` | `required`, `repeatable`, `default` |
21
+ | `param.path(constraint, options?)` | `string` for `encoding: 'text'`, `Uint8Array` for `encoding: 'base64'` | `required`, `encoding` |
22
+ | `param.positional(constraint, options?)` | `string` | `required`, `repeatable` |
23
+ | `param.stdin(constraint, options?)` | `string` | `required` |
24
+
25
+ `param.positional` is the one positional argument a leaf may have. `param.stdin` receives the command's standard input. `param.path` receives the contents of the local file named by the crtr caller, never the path string. Text is UTF-8. Base64 mode decodes the caller's file bytes into a `Uint8Array` before the handler runs.
26
+
27
+ The handler input is inferred from the parameter object with no manual annotation. In the `node_modules/@north-light/crouter-plugin/README.md`, `input` is inferred as `{ name: string; region?: 'us-east' | 'eu-west' }`. A required parameter is present, optional parameters can be absent, enum choices remain literal values, and repeatable parameters are arrays.
28
+
29
+ `definePlugin` checks parameter descriptor objects when the module loads. A malformed hand-built descriptor throws `PluginDefinitionError` at that call. Use the builders instead of constructing descriptor objects.
@@ -0,0 +1,13 @@
1
+ ---
2
+ kind: knowledge
3
+ description: Driving a crouter daemon from an application with @north-light/crouter-sdk
4
+ when-and-why-to-read: When you are writing or reviewing application code that runs a crtr agent — installing @north-light/crouter-sdk, constructing a client, creating a node, watching its event stream, reading its result, reading or writing memory, or connecting a browser to a daemon — this reference should be read because `generate()` and `local()` were deleted, while the typed client keeps daemon transport and agent outcomes distinct.
5
+ short-form: The ESM-only application-facing client for a crtrd daemon. Construction and transports, `client.nodes` create/wait/parse/stream, activity snapshots, phase-2 resources including files and bash, `client.memory`, the returned-not-thrown outcome union, and error classes.
6
+ surfaces:
7
+ - on: boot
8
+ at: name
9
+ ---
10
+
11
+ # `@north-light/crouter-sdk`
12
+
13
+ Read `crouter-sdk/README` for the SDK overview and the directory pages for the specific client surface. The directory listing names every page.
@@ -0,0 +1,65 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: "When you need `@north-light/crouter-sdk`, this knowledge
4
+ should be read because The ESM-only client an application installs to drive a
5
+ crouter daemon: create agent runs, watch streamed events, wait for typed
6
+ results, read and write memory, and reach the rest of the daemon's `/v1` API."
7
+ ---
8
+
9
+ # `@north-light/crouter-sdk`
10
+
11
+ The ESM-only client an application installs to drive a crouter daemon: create agent runs, watch streamed events, wait for typed results, read and write memory, and reach the rest of the daemon's `/v1` API.
12
+
13
+ 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.
14
+
15
+ ```ts
16
+ import Crouter from '@north-light/crouter-sdk';
17
+ import { z } from 'zod';
18
+
19
+ const client = new Crouter();
20
+
21
+ const run = await client.nodes.parse({
22
+ prompt: 'Summarize the failing tests in this repo.',
23
+ cwd: '/path/to/repo',
24
+ output_schema: z.object({ failures: z.array(z.string()), root_cause: z.string() }),
25
+ });
26
+
27
+ if (run.kind === 'result') console.log(run.output_parsed.root_cause);
28
+ ```
29
+
30
+ ## Pages
31
+
32
+ | Page | Covers |
33
+ |---|---|
34
+ | [[crouter-sdk/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 |
35
+ | [[crouter-sdk/client]] | Every constructor option and its environment-variable fallback; per-request options |
36
+ | [[crouter-sdk/nodes]] | `create` parameters, the outcome union, `parse()` with a zod schema, `waitForOutcome`, `message`, `cancel`, nested resources |
37
+ | [[crouter-sdk/streaming]] | The event table, `stream()` / `events()`, the activity helper, resume with `after`, `stream_gap` and `stream_dropped` |
38
+ | [[crouter-sdk/memory]] | The scope object and every `client.memory` method |
39
+ | [[crouter-sdk/files]] | Absolute-path reads, writes, and one-level lists |
40
+ | [[crouter-sdk/bash]] | One bounded command and its output result |
41
+ | [[crouter-sdk/resources]] | Every namespace with its phase, what is deliberately excluded, and the `client.request()` escape hatch |
42
+ | [[crouter-sdk/errors]] | The error class table and the retry policy |
43
+ | [[crouter-sdk/docker]] | `start()`, `attach()`, `connection()` from `@north-light/crouter-env-docker` |
44
+ | [[crouter-sdk/migration]] | Moving off `generate()` and `local()` — a hard cut, with before/after |
45
+
46
+ ## Phases
47
+
48
+ The surface ships in three cuts. Phase 3 memory methods are shipped.
49
+
50
+ | Phase | What lands | State |
51
+ |---|---|---|
52
+ | **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 |
53
+ | **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 |
54
+ | **Phase 2 streaming** | `GET /v1/nodes/{id}/events`, `nodes.stream(params, options?)`, `nodes.events(id, options?)`, `NodeStream`, and the activity helper | Shipped |
55
+ | **Phase 3** | Memory routes and `client.memory`, plus the review, comment, and chat-inventory namespaces | Memory shipped; remaining namespaces not shipped |
56
+
57
+ 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.
58
+
59
+ ## Verification status
60
+
61
+ The SDK declarations, streaming examples, and memory examples are verified.
62
+
63
+ ## Where the agents read this
64
+
65
+ The same content routed for agents lives in the builtin memory document `crouter-sdk`, which ships with the `crtr` binary. An agent working in an application's repository reaches it with `crtr memory read crouter-sdk` and does not need this repository checked out.
@@ -0,0 +1,41 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When you need `client.bash`, this knowledge should be read
4
+ because `client.bash.run` executes one bounded `bash -c` command through the
5
+ daemon.
6
+ ---
7
+
8
+ # `client.bash`
9
+
10
+ `client.bash.run` executes one bounded `bash -c` command through the daemon.
11
+
12
+ ```ts
13
+ const result = await client.bash.run({
14
+ command: 'printf "%s\\n" "$MESSAGE"',
15
+ cwd: '/absolute/path/to/project',
16
+ env: { MESSAGE: 'hello' },
17
+ timeout_s: 30,
18
+ profile: 'my-app',
19
+ });
20
+
21
+ if (result.exit_code !== 0) console.error(result.stderr);
22
+ ```
23
+
24
+ `run(params, options?)` returns `Promise<BashRunDTO>`. Its final `options` object is the normal request-options object: `headers`, `signal`, `timeout`, and `maxRetries`.
25
+
26
+ | Result field | Type | Meaning |
27
+ |---|---|---|
28
+ | `exit_code` | `number \| null` | Process exit code, or `null` after a timeout. A non-zero code is returned, not thrown. |
29
+ | `signal` | `string \| null` | Signal that ended the process, when one did. |
30
+ | `stdout` / `stderr` | `string` | Captured output prefix. |
31
+ | `stdout_truncated` / `stderr_truncated` | `boolean` | Whether that output stream exceeded its 256 KiB capture limit. |
32
+ | `timed_out` | `boolean` | `true` after the daemon terminates the process group for a timeout. |
33
+ | `duration_ms` | `number` | Command duration in milliseconds. |
34
+
35
+ `cwd` is required, absolute, and must name an existing directory. The daemon never uses its own working directory as a fallback. `timeout_s` defaults to 60, must be greater than 0, and cannot exceed 600. The SDK keeps the HTTP request open for the command timeout plus 15 seconds, unless its configured request timeout is already longer.
36
+
37
+ Pass dynamic values through `env` and reference them from the command as `"$NAME"`, rather than interpolating them into shell source. The command receives the daemon's default-deny operational environment, selected profile environment, then your `env` values.
38
+
39
+ ## Authority
40
+
41
+ The token is the owner credential. Anyone holding it can already create an agent with a bash tool, so this command runs with the daemon user's authority and does not provide a restricted command area.
@@ -0,0 +1,98 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When you need Client construction, this knowledge should
4
+ be read because `Crouter` is the default export and the only class an
5
+ application constructs. `CrtrClient` from `@north-light/crouter-api` is an
6
+ implementation detail and is not re-exported.
7
+ ---
8
+
9
+ # Client construction
10
+
11
+ ```ts
12
+ import Crouter from '@north-light/crouter-sdk';
13
+
14
+ const localClient = new Crouter(); // owner's own daemon, unix socket
15
+ const remoteClient = new Crouter({ baseURL: 'http://localhost:8787', token: 'token' }); // TCP with a bearer token
16
+ const customSocketClient = new Crouter({ socketPath: '/custom/path/crtrd.sock' }); // an explicit socket
17
+ ```
18
+
19
+ `Crouter` is the default export and the only class an application constructs. `CrtrClient` from `@north-light/crouter-api` is an implementation detail and is not re-exported.
20
+
21
+ ## Options
22
+
23
+ | Option | Type | Default | Meaning |
24
+ |---|---|---|---|
25
+ | `baseURL` | `string` | `CRTR_BASE_URL`, else unset | `http(s)://host:port` of a daemon TCP listener. |
26
+ | `socketPath` | `string` | `CRTR_SOCKET`, else `${CRTR_HOME}/crtrd.sock`, else `~/.crouter/canvas/crtrd.sock` | Unix socket. Node only; throws in a browser. |
27
+ | `token` | `string` | `CRTRD_TOKEN` | Sent as `Authorization: Bearer <token>`. Ignored by a unix-socket daemon, which authenticates by filesystem permission. |
28
+ | `timeout` | `number` (ms) | `30_000` | Per-request wall clock. Does not apply to a stream. |
29
+ | `maxRetries` | `number` | `2` | Transient-failure retries. Never applied to `POST` or `PATCH` — see [[crouter-sdk/errors]]. |
30
+ | `defaultHeaders` | `Record<string, string>` | `{}` | Merged into every request. |
31
+ | `headers` | `Record<string, string>` | unset | Merged after `defaultHeaders`; a matching name wins unless `token` is set, in which case the generated `Authorization` header wins at construction. |
32
+ | `fetch` | `typeof fetch` | global `fetch`, or the socket implementation when `socketPath` is used | Transport override, for proxies and instrumentation. |
33
+ | `autostart` | `boolean` | `true` for a socket, `false` for `baseURL` | On a cold socket, run `crtr sys daemon start` and retry once. Node only. |
34
+
35
+ ### Environment-variable fallbacks
36
+
37
+ Every fallback above is read at construction, not at request time.
38
+
39
+ | Variable | Fills |
40
+ |---|---|
41
+ | `CRTR_BASE_URL` | `baseURL` |
42
+ | `CRTR_SOCKET` | `socketPath` |
43
+ | `CRTR_HOME` | the directory the default socket path is resolved under (`$CRTR_HOME/crtrd.sock`) |
44
+ | `CRTRD_TOKEN` | `token` |
45
+
46
+ An explicit option always beats its environment variable.
47
+
48
+ ## Transport selection
49
+
50
+ `baseURL` wins if it is set; otherwise `socketPath`; otherwise the default socket path. **Passing both `baseURL` and `socketPath` throws `TypeError` at construction** — exactly one transport per client. In a browser, `new Crouter({})` has no daemon transport; give it the saved `baseURL` and token before making requests.
51
+
52
+ There is one request path. The client issues every request through the Web `fetch` API, which a browser and Node both supply globally. The unix socket is not a second transport — it is a `fetch` implementation the SDK installs when `socketPath` is set, built on `node:http` with `{ socketPath }`, returning a standard `Response` whose `body` is a `ReadableStream`. Streaming, abort, headers, and error parsing therefore have exactly one implementation, and the browser build never sees Node code.
53
+
54
+ ## Autostart
55
+
56
+ With `autostart` on (the default for a socket), a request that finds a cold socket runs `crtr sys daemon start`, waits for the daemon to serve, and retries the request once. This is what makes `npm i -g @north-light/crouter` followed by `new Crouter()` work on a machine that has never run the daemon.
57
+
58
+ Autostart is Node-only and applies only to the socket transport. A `baseURL` client cannot start a daemon it may not even share a machine with, so `autostart` defaults to `false` there; setting it to `true` on a `baseURL` client throws `CrouterError` (`autostart is only valid for the local socket transport`).
59
+
60
+ ## Per-request options
61
+
62
+ Every request-capable method accepts an options object as its last argument, after any path, body, or query arguments. Each field overrides the client-level default for that one request.
63
+
64
+ ```ts
65
+ const id = 'example-node';
66
+ const signal = new AbortController().signal;
67
+
68
+ await client.nodes.retrieve(id, {
69
+ signal, // AbortSignal — real cancellation, passed straight to fetch
70
+ timeout: 5_000, // ms, this request only
71
+ maxRetries: 0, // this request only
72
+ headers: { 'x-trace': 't-9' } // merged over defaultHeaders
73
+ });
74
+ ```
75
+
76
+ | Field | Type | Effect |
77
+ |---|---|---|
78
+ | `signal` | `AbortSignal` | Aborts the underlying `fetch`. Raises `APIUserAbortError`. |
79
+ | `timeout` | `number` (ms) | Overrides the client `timeout`. |
80
+ | `maxRetries` | `number` | Overrides the client `maxRetries`. |
81
+ | `headers` | `Record<string, string>` | Merged over `defaultHeaders`, client `headers`, and the token-generated header; a matching name, including `Authorization`, wins. |
82
+
83
+ `signal` is real cancellation. The long-polling helpers (`waitForOutcome`, `createAndWait`, `parse`) honour it between polls and during the in-flight request, and aborting them stops the client — it does not cancel the node. To stop the run itself, call `client.nodes.cancel(id)` or `client.nodes.interrupt(id)`. Streams use `NodeEventsOptions`: the same fields except `timeout`, plus `after` for `nodes.events`; see [[crouter-sdk/streaming]].
84
+
85
+ ## What is not on the client
86
+
87
+ | Convention | Why it is absent |
88
+ |---|---|
89
+ | `.withResponse()` / `.asResponse()` | They exist to hand back the raw `Response` and an `x-request-id`. The daemon emits no request id, and a caller who needs raw bytes uses `client.request()`. |
90
+ | `local()` and the `Environment` interface | Deleted. The client *is* the connection — `new Crouter()` covers what `local()` did. See [[crouter-sdk/migration]]. |
91
+ | `apiKey` | The daemon's credential is a token (`CRTRD_TOKEN`) everywhere in the product. `apiKey` would be a second name for one thing. |
92
+ | Resource-prefixed ids | Node ids have their own format. The SDK validates unsafe path identifiers locally with `TypeError`, and the daemon validates the request too; nothing is re-prefixed. |
93
+
94
+ ## Wire naming
95
+
96
+ Wire fields are `snake_case` — `output_schema`, `root_lifecycle`, `pin_cwd`, `final_report_path`. The SDK does not camelize them. Namespaces and method names are camelCase (`client.nodes.waitForOutcome`, `client.nodes.worktree`).
97
+
98
+ Timestamps are ISO-8601 strings (`created`, `settled_at`, `finalized_at`, `deadline_at`), not Unix seconds.
@@ -0,0 +1,72 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When you need Docker environment, this knowledge should be
4
+ read because `@north-light/crouter-env-docker` runs a crouter daemon in a
5
+ container and tells you how to reach it. It is a separate package because
6
+ container lifecycle is real work the SDK does not otherwise do — and it **has
7
+ no dependencies**, including on the SDK itself.
8
+ ---
9
+
10
+ # Docker environment
11
+
12
+ Phase 1.
13
+
14
+ `@north-light/crouter-env-docker` runs a crouter daemon in a container and tells you how to reach it. It is a separate package because container lifecycle is real work the SDK does not otherwise do — and it **has no dependencies**, including on the SDK itself.
15
+
16
+ ```bash
17
+ npm i @north-light/crouter-env-docker
18
+ ```
19
+
20
+ ## Starting a container and connecting to it
21
+
22
+ ```ts
23
+ import { start } from '@north-light/crouter-env-docker';
24
+ import Crouter from '@north-light/crouter-sdk';
25
+
26
+ const env = await start({ volume: 'my-agent-home' });
27
+ const client = new Crouter(env.connection());
28
+
29
+ await client.nodes.create({ prompt: 'Do the thing.', root: true });
30
+
31
+ await env.stop();
32
+ ```
33
+
34
+ `connection()` returns `{ baseURL, headers }` — exactly the shape the `Crouter` constructor takes, so the result passes straight through with no adapter.
35
+
36
+ ## Methods
37
+
38
+ | Method | What it does |
39
+ |---|---|
40
+ | `start(opts?)` | Starts a container running `crtrd` and returns an environment handle. |
41
+ | `attach(name)` | Returns a handle for the already-running container named `name`. |
42
+ | `stop()` | On a `start()` handle, stops and removes the container. On an `attach()` handle, stops it without removing it. |
43
+ | `connection()` | `Connection` (`{ baseURL: string; headers?: Record<string, string> }`) — returned synchronously and passed to `new Crouter()`. |
44
+
45
+ | `start()` option | Type | Default | Effect |
46
+ |---|---|---|---|
47
+ | `image` | `string` | `ghcr.io/vallum-security/crtrd:latest` | Image to run. |
48
+ | `env` | `Record<string, string>` | unset | Extra container environment variables; they are not logged. |
49
+ | `name` | `string` | generated name | Container name for later `attach(name)`. |
50
+ | `volume` | `string` | unset | Named volume mounted at `/home/agent/.crouter`. |
51
+ | `port` | `number` | Docker-assigned port | Host port bound on `127.0.0.1`. |
52
+
53
+ ## `connection()` replaced `daemon()`
54
+
55
+ `daemon()` is gone, along with the package's `Environment` type. The new name says what the method returns, and its field is spelled `baseURL` to match the constructor option rather than `baseUrl`.
56
+
57
+ ```text
58
+ // before
59
+ const env = await start({ volume: 'my-agent-home' });
60
+ const { baseUrl, headers } = await env.daemon();
61
+ const client = new CrtrClient({ baseUrl, headers });
62
+
63
+ // after
64
+ const env = await start({ volume: 'my-agent-home' });
65
+ const client = new Crouter(env.connection());
66
+ ```
67
+
68
+ `connection()` is typed structurally — `{ baseURL: string; headers?: Record<string, string> }` — which is how the package stays dependency-free while producing something the SDK accepts directly.
69
+
70
+ ## The volume is the agent's home
71
+
72
+ The `volume` you pass is the container's canvas home: nodes, transcripts, artifacts, memory, and profiles all live there. Reuse the same volume across `start` calls and the agent keeps everything it learned; use a fresh one and it starts from nothing. `start()` mounts an existing named volume unchanged; it does not inspect or reject it based on the crouter version that last wrote it.