@coryrylan/cradle 0.0.0 → 1.3.0

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,41 @@
1
+ ## [1.3.0](https://github.com/coryrylan/cradle/compare/cradle-v1.2.0...cradle-v1.3.0) (2026-07-26)
2
+
3
+ ### Features
4
+
5
+ * **cli:** enhance package filtering and extensions handling ([03ae139](https://github.com/coryrylan/cradle/commit/03ae1390de541f94f701413ac6853fede7b6c73a))
6
+
7
+ ## [1.2.0](https://github.com/coryrylan/cradle/compare/cradle-v1.1.0...cradle-v1.2.0) (2026-07-26)
8
+
9
+ ### Features
10
+
11
+ * **cli:** enhance sandbox configuration with docker support ([542cd79](https://github.com/coryrylan/cradle/commit/542cd79445cfc275ca8fa49f9b8e046a4fb23c9e))
12
+
13
+ ### Bug Fixes
14
+
15
+ * **cli:** rename start command to run and update related documentation ([7324f82](https://github.com/coryrylan/cradle/commit/7324f823ad821b25e7d8e3db07b6cb585acfdd4e))
16
+
17
+ ## [1.1.0](https://github.com/coryrylan/cradle/compare/cradle-v1.0.1...cradle-v1.1.0) (2026-07-20)
18
+
19
+ ### Features
20
+
21
+ * **cli:** enhance sandbox configuration for agent-browser ([363e4b1](https://github.com/coryrylan/cradle/commit/363e4b1b8b7896001ac459c9c508f8a1176868e2))
22
+
23
+ ## [1.0.1](https://github.com/coryrylan/cradle/compare/cradle-v1.0.0...cradle-v1.0.1) (2026-07-19)
24
+
25
+ ### Bug Fixes
26
+
27
+ * **cli:** update agent folder specifications to include system.md ([52f8aa9](https://github.com/coryrylan/cradle/commit/52f8aa9a1aad73d34baf58f647b4ecbedf0dfd5a))
28
+
29
+ ## 1.0.0 (2026-07-19)
30
+
31
+ ### Features
32
+
33
+ * init ([74684ae](https://github.com/coryrylan/cradle/commit/74684ae89643d062d906e1c9d311501ab18b6db6))
34
+
35
+ ### Bug Fixes
36
+
37
+ * **cli:** mise cache paths ([a13f78b](https://github.com/coryrylan/cradle/commit/a13f78b9f65f427ee982bbcb78fa9f1335e285af))
38
+
1
39
  # Changelog
2
40
 
3
41
  ## 0.0.0
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![CI Build](https://github.com/coryrylan/cradle/actions/workflows/pull-request.yml/badge.svg)
4
4
 
5
- A runtime for portable agents defined as folders. `cradle start <dir>` reads an agent folder — an `APPEND_SYSTEM.md` plus optional pi-native config, skills, extensions, and sandbox posture — and launches the [pi](https://github.com/earendil-works/pi-mono) coding agent configured from it. An agent declaring `sandbox/nono.json` runs inside the [nono](https://github.com/always-further/nono) filesystem sandbox. See [ARCHITECTURE.md](../../ARCHITECTURE.md) for the folder format and [`examples/hello`](../../examples/hello) for a minimal agent. Built with Bun and TypeScript; install the standalone binary via `install.sh` (primary) or the npm package [`@coryrylan/cradle`](https://www.npmjs.com/package/@coryrylan/cradle) (alternative).
5
+ A runtime for portable agents defined as folders. `cradle run <dir>` reads an agent folder — a `SYSTEM.md` or `APPEND_SYSTEM.md` (at least one) plus optional pi-native config, skills, extensions, and sandbox posture — and launches the [pi](https://github.com/earendil-works/pi-mono) coding agent configured from it. An agent declaring `sandbox/nono.json` or `sandbox/sbx.json` runs sandboxed — inside the [nono](https://github.com/always-further/nono) filesystem sandbox or the [Docker Sandboxes](https://docs.docker.com/ai/sandboxes/) microVM, respectively. See [ARCHITECTURE.md](../../ARCHITECTURE.md) for the folder format and [`examples/hello`](../../examples/hello) for a minimal agent. Built with Bun and TypeScript; install the standalone binary via `install.sh` (primary) or the npm package [`@coryrylan/cradle`](https://www.npmjs.com/package/@coryrylan/cradle) (alternative).
6
6
 
7
7
  ## Dependencies
8
8
 
@@ -10,8 +10,9 @@ A runtime for portable agents defined as folders. `cradle start <dir>` reads an
10
10
 
11
11
  | Tool | Status | Why |
12
12
  | ------------------------------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
13
- | [`pi`](https://github.com/earendil-works/pi-mono) | **Required** | The coding agent cradle launches. Every `cradle start` spawns it. |
14
- | [`nono`](https://github.com/always-further/nono) | **Recommended** | The filesystem sandbox pi runs inside for a sandboxed run — a folder declaring `sandbox/nono.json`, or `--sandbox`/`--offline`/`--allow-host`; required for that run, not needed otherwise. |
13
+ | [`pi`](https://github.com/earendil-works/pi-mono) | **Required** | The coding agent cradle launches. Every `cradle run` spawns it. |
14
+ | [`nono`](https://github.com/always-further/nono) | **Recommended** | The filesystem sandbox pi runs inside for a nono-backend run — a folder declaring `sandbox/nono.json`, or `--sandbox`/`--sandbox-backend nono`/`--offline`/`--allow-host` with no `sandbox/sbx.json`; required for that run, not needed otherwise. |
15
+ | [`sbx`](https://docs.docker.com/ai/sandboxes/) | **Recommended** | The Docker Sandboxes microVM pi runs inside for an sbx-backend run — a folder declaring `sandbox/sbx.json`, or `--sandbox-backend sbx`; required for that run, not needed otherwise. |
15
16
  | [`mise`](https://mise.jdx.dev) | **Recommended** | The supported way to install and manage `pi` and `nono`. cradle doesn't invoke mise directly, but it falls back to mise's shims when resolving the tools, and the generated sandbox profile grants mise's trees so a sandboxed pi finds its runtime. |
16
17
 
17
18
  ## Installation
@@ -53,32 +54,32 @@ bun start
53
54
  | `bun run test` | Run tests (no coverage) |
54
55
  | `bun run test:coverage` | Run tests with coverage; enforces thresholds from `bunfig.toml` |
55
56
  | `bun run lint` | Lint this package with ESLint (typescript-eslint strict) |
56
- | `bun run format` | Check formatting with Prettier |
57
- | `bun run format:fix` | Auto-fix formatting |
58
57
  | `bun run ci` | Run lint + build + test:coverage (used in CI) |
58
+ | `bun run ci:nocache` | Clean `dist/` then run `ci` (useful for cache debugging) |
59
59
  | `bun run install:local` | Build and install binary to `~/.local/bin` |
60
60
  | `bun run uninstall:local` | Remove locally installed binary |
61
61
 
62
- > Knip (unused files / deps / exports) is configured at the monorepo root and runs across all workspaces. From the repo root, run `bun run lint:knip`.
62
+ > Formatting (Prettier) and Knip (unused files / deps / exports) run at the monorepo root, not per-package. From the repo root: `bun run format` / `bun run format:fix` and `bun run lint:knip`.
63
63
 
64
64
  ## CLI Usage
65
65
 
66
66
  ```bash
67
67
  cradle --version
68
- cradle doctor # check pi (required), nono/mise (recommended) on PATH, with versions
69
- cradle start ./my-agent # run an agent folder with pi; sandboxed when sandbox/nono.json exists
70
- cradle start my-agent # run a name from ~/.cradle/settings.json instead of a path
71
- cradle start . --offline # block all outbound network (exfil protection)
72
- cradle start . --allow-host api.z.ai # restrict network to these hosts (repeatable)
73
- cradle start . --no-sandbox # run pi directly (debug)
74
- cradle start . --dry-run -- --resume # print the write plan + command; forward `--resume` to pi
68
+ cradle doctor # check pi (required), nono/sbx/mise (recommended) on PATH, with versions
69
+ cradle run ./my-agent # run an agent folder with pi; sandboxed when sandbox/nono.json or sandbox/sbx.json exists
70
+ cradle run my-agent # run a name from ~/.cradle/settings.json instead of a path
71
+ cradle run . --offline # block all outbound network (exfil protection)
72
+ cradle run . --allow-host api.example.com # restrict network to these hosts (repeatable)
73
+ cradle run . --sandbox-backend sbx # run under the sbx Docker Sandboxes microVM instead of nono
74
+ cradle run . --no-sandbox # run pi directly (debug)
75
+ cradle run . --dry-run -- --resume # print the write plan + command; forward `--resume` to pi
75
76
  ```
76
77
 
77
78
  The agent runs in _your_ working directory; the agent folder is a parameter (default `.`). Everything after `--` is forwarded verbatim to pi. `--dry-run` prints the generated-extension write plan and the composed command without spawning (and without requiring the bins to be installed). Per-agent state (generated extensions + session history) lives under `~/.cradle/agents/<name>-<hash>/`.
78
79
 
79
80
  ### Global agent aliases (`~/.cradle/settings.json`)
80
81
 
81
- The `dir` positional accepts a bare name instead of a path — `cradle start my-agent` resolves against a global name → folder map, so agent folders you keep far from any project don't need a full path from every cwd:
82
+ The `dir` positional accepts a bare name instead of a path — `cradle run my-agent` resolves against a global name → folder map, so agent folders you keep far from any project don't need a full path from every cwd:
82
83
 
83
84
  ```json
84
85
  {
@@ -90,7 +91,9 @@ The `dir` positional accepts a bare name instead of a path — `cradle start my-
90
91
 
91
92
  A bare name (no `/`, not `.`/`~`-led) checks the alias table first, falling back to the cwd-relative path (`./my-agent`) when no alias is defined — anything already path-shaped (`./x`, `../x`, `/abs/x`, `~/x`, `.`) is never looked up as an alias. See [ARCHITECTURE.md](../../ARCHITECTURE.md#global-agent-aliases) for the full resolution rules.
92
93
 
93
- Each sandboxed run generates its own nono profile at `~/.cradle/agents/<id>/nono-profile.json` the built-in base merged with that agent's `sandbox/nono.json` grantsand points `nono run --profile` at it. There's no shared global profile and no separate setup step: an agent's whole sandbox posture lives in its own directory. To widen it (e.g. granting a tool's data dir), add a `sandbox/` folder to the agent with a `nono.json`:
94
+ An agent folder can declare a sandbox posture for either backend: `sandbox/nono.json` (the OS-policy sandbox covered in this section) or `sandbox/sbx.json` (the Docker Sandboxes microVM see [Docker Sandboxes backend](#docker-sandboxes-backend-sandboxsbxjson) below), or both. `--sandbox-backend <nono|sbx>` picks the backend explicitly and forces it on; bare `--sandbox` uses whichever backend the folder declares, defaulting to nono, and nono wins when a folder declares both (with a warning naming `--sandbox-backend sbx` as the override).
95
+
96
+ Each sandboxed nono run generates its own profile at `~/.cradle/agents/<id>/nono-profile.json` — the built-in base merged with that agent's `sandbox/nono.json` grants — and points `nono run --profile` at it. There's no shared global profile and no separate setup step: an agent's whole sandbox posture lives in its own directory. To widen it (e.g. granting a tool's data dir), add a `sandbox/` folder to the agent with a `nono.json`:
94
97
 
95
98
  ```json
96
99
  {
@@ -108,20 +111,20 @@ Outbound network is **open by default within a sandboxed run**. A `network` bloc
108
111
  {
109
112
  "network": {
110
113
  "block": false,
111
- "allow_domain": ["api.z.ai", "localhost"],
114
+ "allow_domain": ["api.example.com", "localhost"],
112
115
  "open_port": [11434],
113
116
  "listen_port": [8080]
114
117
  }
115
118
  }
116
119
  ```
117
120
 
118
- | Key | Effect |
119
- | ----------------- | ----------------------------------------------------------------------------------------------- |
120
- | `block` | `true` denies **all** outbound (full offline). |
121
- | `allow_domain` | Host allowlist. **Presence flips nono to default-deny** — unlisted hosts are refused (403). |
122
- | `open_port` | localhost TCP ports the agent may connect/bind (e.g. a local model or dev server). |
123
- | `listen_port` | TCP ports the agent may listen on. |
124
- | `network_profile` | Named nono network-policy profile (opaque pass-through; requires a host `network-policy.json`). |
121
+ | Key | Effect |
122
+ | ----------------- | ------------------------------------------------------------------------------------------------ |
123
+ | `block` | `true` denies **all** outbound (full offline). |
124
+ | `allow_domain` | Host allowlist. **Presence flips nono to default-deny** — unlisted hosts are refused (403). |
125
+ | `open_port` | localhost TCP ports the agent may connect/bind; `0` allows any outbound localhost port on macOS. |
126
+ | `listen_port` | TCP ports the agent may listen on. |
127
+ | `network_profile` | Named nono network-policy profile (opaque pass-through; requires a host `network-policy.json`). |
125
128
 
126
129
  An `allow_domain` allowlist blocks localhost too, so a local-model agent must list `localhost`/`127.0.0.1` **and** open its port (see [`examples/hello`](../../examples/hello), locked to Ollama on `localhost:11434`). CLI flags override the folder: `--offline` (full block) and repeatable `--allow-host <host>` (allowlist). Precedence: `--offline` > `--allow-host` > `sandbox/nono.json` > open. Requesting a network policy only means something inside the sandbox, so `--offline`/`--allow-host` force the sandbox on (same as `--sandbox`) unless `--no-sandbox` is passed explicitly, in which case cradle warns `network policy has no effect without the sandbox — pi runs with no network isolation (--sandbox to enforce it)` and nothing is enforced. cradle doesn't echo the resolved posture itself — run with `--verbose` to drop nono's `--silent` flag and see its own capabilities banner (grants + network mode), or read the generated per-agent profile (`nono-profile.json` in the agent's state dir). nono **fails closed** — a malformed `network` key or a platform that can't enforce proxy filtering makes the run refuse to start rather than silently ship an unenforced allowlist.
127
130
 
@@ -129,35 +132,58 @@ An `allow_domain` allowlist blocks localhost too, so a local-model agent must li
129
132
 
130
133
  Some tools need OS capabilities the conservative base profile denies. `sandbox/nono.json` can append raw macOS Seatbelt rules — s-expressions merged verbatim after the base's rules (nono validates the syntax at load). Each one widens the OS sandbox, so audit them where they live: the folder's `sandbox/nono.json` or the generated per-agent profile (`nono-profile.json` in the agent's state dir) — nono's own capabilities banner (shown with `--verbose`) never lists seatbelt rules. Ignored on Linux.
131
134
 
132
- **Browser automation is the motivating case.** [agent-browser](https://agent-browser.dev/) + Chrome for Testing runs sandboxed under nono with exactly two rules plus Chrome's own `--no-sandbox` flag:
135
+ **Browser automation is the motivating case.** [agent-browser](https://agent-browser.dev/) + Chrome for Testing runs sandboxed under nono with a directory grant, a direct-child Unix socket grant, exactly two macOS rules, and Chrome's own `--no-sandbox` flag:
133
136
 
134
137
  ```json
135
138
  {
136
139
  "filesystem": {
137
- "allow": ["~/.agent-browser"]
140
+ "allow": ["~/.agent-browser"],
141
+ "unix_socket_dir_bind": ["~/.agent-browser"]
142
+ },
143
+ "network": {
144
+ "open_port": [0]
138
145
  },
139
146
  "unsafe_macos_seatbelt_rules": ["(allow mach-register)", "(allow iokit-open)"]
140
147
  }
141
148
  ```
142
149
 
143
- | Entry | Why it's needed |
144
- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
145
- | `allow ~/.agent-browser` | agent-browser's own state dir — daemon socket, downloaded Chrome, config. Without it the daemon can't create its socket (`Operation not permitted`). |
146
- | `(allow mach-register)` | Chrome's Crashpad handler registers a Mach service (`bootstrap_check_in org.chromium.crashpad.*`); the base profile denies it, so the browser process aborts. |
147
- | `(allow iokit-open)` | Chrome opens IOKit user clients during startup even headless; without it the browser process crashes before serving CDP. |
150
+ | Entry | Why it's needed |
151
+ | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
152
+ | `allow ~/.agent-browser` | agent-browser's state dir — downloaded Chrome, config, and daemon socket files. |
153
+ | `unix_socket_dir_bind ~/.agent-browser` | Lets the CLI and daemon connect to and bind direct-child Unix sockets; without it, `connect()` returns `EPERM`. |
154
+ | `open_port 0` | Lets the daemon connect to Chrome's random localhost DevTools port on macOS; without it, the CDP WebSocket returns `EPERM`. |
155
+ | `(allow mach-register)` | Chrome's Crashpad handler registers a Mach service (`bootstrap_check_in org.chromium.crashpad.*`); the base profile denies it, so the browser process aborts. |
156
+ | `(allow iokit-open)` | Chrome opens IOKit user clients during startup even headless; without it the browser process crashes before serving CDP. |
157
+
158
+ `unix_socket_dir_bind` is non-recursive. Point it only at a dedicated socket directory, never a broad parent such as `~` or `/tmp`. Port `0` is nono's macOS-only outbound localhost wildcard; Linux requires explicit ports. The two macOS rules are IPC/IOKit capabilities only — the filesystem and network boundaries stay intact, so the sandbox still denies an ungranted path (verified: a granted read succeeds, `~/some-secret` returns `Operation not permitted`). No grant for Chrome's own `~/Library/…/Chrome for Testing` dir is needed — its Crashpad-database `stat` failure under the sandbox is non-fatal noise.
148
159
 
149
- The two rules are IPC/IOKit capabilities only the filesystem and network boundaries stay intact, so the sandbox still denies an ungranted path (verified: a granted read succeeds, `~/some-secret` returns `Operation not permitted`). No grant for Chrome's own `~/Library/…/Chrome for Testing` dir is needed its Crashpad-database `stat` failure under the sandbox is non-fatal noise.
160
+ Chrome's **own** nested sandbox can't initialize inside nono's seatbelt (macOS forbids nesting), so its child processes need `--no-sandbox`. On every sandboxed run, cradle generates and loads `agent-browser-nono-fallback.ts` before package and agent extensions. On macOS it appends `--no-sandbox` to `AGENT_BROWSER_ARGS`; on every platform it maps nono's dynamically injected `HTTPS_PROXY`/`HTTP_PROXY` to `AGENT_BROWSER_PROXY`. Explicit agent-browser proxy configuration still wins. Unsandboxed runs do not load the fallback, so Chrome keeps its own sandbox. See [`examples/browser`](../../examples/browser) for the complete folder.
150
161
 
151
- Chrome's **own** nested sandbox can't initialize inside nono's seatbelt (macOS forbids nesting), so its child processes need `--no-sandbox`. Deliver it through a pi extension rather than a shell env var an extension travels with the agent folder and pi propagates it into every tool's env regardless of how the extension itself set it. The simplest is a one-line agent extension:
162
+ An agent that still cannot run sandboxed can declare `{ "sandbox": false }` in either sandbox file to opt that backend out. cradle then runs pi bare, warns loudly on every run, and an explicit `--sandbox`/`--sandbox-backend` flag always forces isolation back on.
152
163
 
153
- ```ts
154
- // extensions/browser-sandbox.ts — set the flag before agent-browser launches Chrome
155
- process.env.AGENT_BROWSER_ARGS ??= '--no-sandbox';
164
+ ### Docker Sandboxes backend (`sandbox/sbx.json`)
165
+
166
+ A folder can opt into the [Docker Sandboxes](https://docs.docker.com/ai/sandboxes/) (`sbx`) microVM instead of (or alongside) nono by declaring `sandbox/sbx.json` — a sibling file with a restricted subset of `sandbox/nono.json`'s schema: `sandbox: false` to opt out, `filesystem` `read`/`write`/`allow` only, `network` `block`/`allow_domain` only. Keys with no VM-boundary equivalent — `unix_socket_dir_bind`, `network_profile`, `open_port`, `listen_port`, `unsafe_macos_seatbelt_rules` — are warned and dropped rather than silently accepted. Force it explicitly with `--sandbox-backend sbx`; see [ARCHITECTURE.md](../../ARCHITECTURE.md#sandboxsbxjson) for the full schema, backend precedence, and run model.
167
+
168
+ ```json
169
+ {
170
+ "network": { "allow_domain": ["api.example.com"] },
171
+ "filesystem": {
172
+ "allow": ["~/scratch"]
173
+ }
174
+ }
156
175
  ```
157
176
 
158
- (Or pass `agent-browser --args --no-sandbox` on each call.) See [`examples/browser`](../../examples/browser) for the complete folder.
177
+ An sbx run mounts your cwd (rw), the agent folder (ro), the state dir (rw), and `~/.pi/agent` (rw, so a host `pi login` and its OAuth refreshes carry over) into a sandbox named `cradle-<agentId>-<hash8>` — the hash keys the mount set, so a changed grant gets a fresh sandbox rather than attaching to stale mounts (clean those up with `sbx rm`). cradle then provisions `@earendil-works/pi-coding-agent` in-guest, pinned to the host's own `pi` version, and runs `sbx exec` with your host paths preserved verbatim, so the composed pi command is identical to a nono or unsandboxed run.
178
+
179
+ Network policy differs from nono's: `block: true` denies all outbound; `allow_domain` allows the listed hosts (expanded to cover subdomains, with `localhost`/`127.0.0.1` rewritten to `host.docker.internal` — guest loopback is the microVM itself, not the host). Unlike nono's proxy, a per-sandbox `allow` rule only **adds** to your machine's global `sbx` policy and cannot subtract from it — cradle warns on every allowlist run. For strict, nono-style allowlist semantics, run `sbx policy init deny-all` once. One-time machine setup before the first sandboxed `sbx` run:
180
+
181
+ ```bash
182
+ sbx login
183
+ sbx policy init deny-all # or allow-all / balanced — see sbx's own docs
184
+ ```
159
185
 
160
- An agent that still cannot run sandboxed can declare `{ "sandbox": false }` instead. cradle then runs pi bare, warns loudly on every run, and an explicit `--sandbox` flag always forces isolation back on.
186
+ `cradle doctor` checks `sbx` alongside `pi`/`nono`/`mise`.
161
187
 
162
188
  ## Build Targets
163
189
 
@@ -3,11 +3,11 @@ export interface ResolveRefDeps {
3
3
  readonly cwd: string;
4
4
  }
5
5
  /**
6
- * Resolve a `cradle start` ref into a folder path `loadAgentFolder` can load.
6
+ * Resolve a `cradle run` ref into a folder path `loadAgentFolder` can load.
7
7
  * Path-shaped refs pass through untouched (`loadAgentFolder`'s own `resolve()`
8
8
  * handles them, and they never consult `deps.cwd`); a bare name resolves
9
9
  * against the global alias table into an ABSOLUTE, normalized path, falling
10
- * back to the cwd-relative folder when no alias matches (so `cradle start
10
+ * back to the cwd-relative folder when no alias matches (so `cradle run
11
11
  * hello` from `examples/` keeps working) — and throwing only when neither
12
12
  * resolves. Every bare-name branch resolves against the injected `deps.cwd`,
13
13
  * never `process.cwd()`, so the dep is the single source of truth for what the
@@ -0,0 +1,3 @@
1
+ export declare const AGENT_BROWSER_NONO_FALLBACK_EXTENSION_FILE = "agent-browser-nono-fallback.ts";
2
+ /** Emit the sandbox-only environment bridge agent-browser needs when launched as a pi subprocess. */
3
+ export declare function emitAgentBrowserNonoFallbackExtension(): string;
@@ -1 +1,12 @@
1
- export declare function emitProvidersExtension(providersJson: string): string;
1
+ /** Options for `emitProvidersExtension` see `commands/run.ts`'s `emitExtensionFiles`, the single call site. */
2
+ export interface ProvidersEmitOptions {
3
+ /**
4
+ * Rewrite `localhost`/`127.0.0.1` provider baseUrls to the sbx host gateway
5
+ * (see `sbx/compose.ts`'s `rewriteLocalhostBaseUrl`): guest loopback is the
6
+ * microVM itself, so a host-bound local-model endpoint is unreachable under
7
+ * its own name inside the sbx backend. Off (byte-identical output) for nono
8
+ * and unsandboxed runs.
9
+ */
10
+ readonly rewriteLocalhostBaseUrls?: boolean;
11
+ }
12
+ export declare function emitProvidersExtension(providersJson: string, options?: ProvidersEmitOptions): string;
@@ -16,6 +16,8 @@ export interface AgentSandboxGrants {
16
16
  readonly read: readonly string[];
17
17
  readonly write: readonly string[];
18
18
  readonly allow: readonly string[];
19
+ /** Direct-child Unix socket paths the process may connect to and bind. */
20
+ readonly unixSocketDirBind: readonly string[];
19
21
  }
20
22
  /**
21
23
  * The agent's declared network posture — a curated subset of nono's profile
@@ -40,11 +42,20 @@ export interface AgentNetwork {
40
42
  readonly listenPort?: readonly number[];
41
43
  }
42
44
  export type AgentSandboxPosture = 'unconfigured' | 'enabled' | 'disabled';
45
+ /**
46
+ * The sandbox enforcement backend a folder can declare a posture for: `nono`
47
+ * (Seatbelt/bubblewrap, `sandbox/nono.json`) or `sbx` (Docker Sandboxes
48
+ * microVM, `sandbox/sbx.json`). Backend resolution — which one actually runs
49
+ * — is a `commands/run.ts` concern; this module only reads each file's
50
+ * declared posture.
51
+ */
52
+ export type SandboxBackend = 'nono' | 'sbx';
43
53
  export interface AgentSandbox {
44
54
  /**
45
- * The folder's declared sandbox posture. `'unconfigured'` when the folder has
46
- * no `sandbox/nono.json`; otherwise mirrors the file's `sandbox` key
47
- * (absent `'enabled'`).
55
+ * The declaring file's sandbox posture. `'unconfigured'` when the folder
56
+ * has no `sandbox/nono.json` (for the `sandbox` field) or no
57
+ * `sandbox/sbx.json` (for the `sbx` field); otherwise mirrors the file's
58
+ * `sandbox` key (absent ⇒ `'enabled'`).
48
59
  */
49
60
  readonly posture: AgentSandboxPosture;
50
61
  readonly network?: AgentNetwork;
@@ -62,15 +73,30 @@ export interface AgentSandbox {
62
73
  }
63
74
  export interface AgentFolder {
64
75
  readonly dir: string;
65
- /** Abs path of APPEND_SYSTEM.md — appended to pi's system prompt via `--append-system-prompt`. */
66
- readonly appendSystemFilePath: string;
76
+ /**
77
+ * Abs path of SYSTEM.md — REPLACES pi's default system prompt via
78
+ * `--system-prompt`; `null` when the folder has no SYSTEM.md. `loadAgentFolder`
79
+ * guarantees at least one of `systemFilePath`/`appendSystemFilePath` is non-null.
80
+ */
81
+ readonly systemFilePath: string | null;
82
+ /** Abs path of APPEND_SYSTEM.md — appended to pi's system prompt via `--append-system-prompt`; `null` when absent. */
83
+ readonly appendSystemFilePath: string | null;
67
84
  readonly settings: AgentSettings;
68
85
  /** Serialized `providers` object from models.json, `null` when absent. */
69
86
  readonly providersJson: string | null;
70
87
  readonly skillsDir: string | null;
71
88
  /** pi-native extensions (top-level `extensions/*.ts` plus each subdir's `index.ts`), absolute paths. */
72
89
  readonly extensionFiles: readonly string[];
90
+ /** `sandbox/nono.json`'s declared posture — the Seatbelt/bubblewrap backend. */
73
91
  readonly sandbox: AgentSandbox;
92
+ /**
93
+ * `sandbox/sbx.json`'s declared posture — the Docker Sandboxes microVM
94
+ * backend. A restricted subset of nono's schema: no Unix-socket grants, no
95
+ * named network-profile pass-through, no Seatbelt escape hatch — the VM
96
+ * boundary has no equivalent for them, so those keys are warned-and-dropped
97
+ * rather than silently accepted. See `readSbxSandbox`.
98
+ */
99
+ readonly sbx: AgentSandbox;
74
100
  readonly warnings: readonly string[];
75
101
  }
76
102
  export declare function loadAgentFolder(dir: string): Promise<AgentFolder>;
@@ -1,13 +1,13 @@
1
- import type { AgentFolder } from './folder.js';
1
+ import type { AgentFolder, SandboxBackend } from './folder.js';
2
2
  export interface LaunchSpec {
3
3
  readonly folder: AgentFolder;
4
4
  readonly stateDir: string;
5
5
  /**
6
6
  * Where the generated providers extension is written (see
7
7
  * `folder.providersJson`) and where `--session-dir` points, sourced from the
8
- * plan's own `extensionsDir`/`sessionsDir` (`commands/start.ts` — the single
8
+ * plan's own `extensionsDir`/`sessionsDir` (`commands/run.ts` — the single
9
9
  * derivation site; re-deriving from `stateDir` here risks silently
10
- * disagreeing with a plan whose dirs were overridden after `planStart`).
10
+ * disagreeing with a plan whose dirs were overridden after `planRun`).
11
11
  */
12
12
  readonly extensionsDir: string;
13
13
  readonly sessionsDir: string;
@@ -16,21 +16,30 @@ export interface LaunchSpec {
16
16
  * from the plan's `statePaths` derivation like `extensionsDir`/`sessionsDir`.
17
17
  */
18
18
  readonly miseCacheDir: string;
19
- readonly sandbox: boolean;
19
+ /**
20
+ * Which sandbox backend wraps this run, or `null` for unsandboxed. `'nono'`
21
+ * wraps the argv here — see `composeArgv`. `'sbx'` does NOT wrap here: its
22
+ * `sbx exec` wrapper is composed at materialization in `commands/run.ts`,
23
+ * so `composeArgv` returns the bare pi argv for both `'sbx'` and `null`.
24
+ * For `'sbx'`, `piBin` is the literal `pi` — resolved on the guest's PATH,
25
+ * since host paths (mise-shim included) are meaningless inside the
26
+ * microVM.
27
+ */
28
+ readonly backend: SandboxBackend | null;
20
29
  readonly passthrough: readonly string[];
21
30
  /** Resolved path, or bare `nono` for dry-run previews. */
22
31
  readonly nonoBin: string;
23
32
  /**
24
33
  * Resolved path (mise-shim fallback included, see `util/which.ts`), or bare
25
34
  * `pi` for dry-run previews. Used consistently whether or not the run is
26
- * sandboxed — the generated profile already grants read on the mise install
27
- * tree (see `nono/cradle-pi.json`), so the absolute path resolves inside the
28
- * sandbox too; a bare `pi` only resolves on whatever PATH the spawning
29
- * process happens to have, which is exactly the gap a fresh mise-only
30
- * install falls into.
35
+ * nono-sandboxed — the generated profile already grants read on the mise
36
+ * install tree (see `nono/cradle-pi.json`), so the absolute path resolves
37
+ * inside the sandbox too; a bare `pi` only resolves on whatever PATH the
38
+ * spawning process happens to have, which is exactly the gap a fresh
39
+ * mise-only install falls into. See `backend` for the `'sbx'` exception.
31
40
  */
32
41
  readonly piBin: string;
33
- /** Abs path of the generated per-agent nono profile; used only when `sandbox` is true. */
42
+ /** Abs path of the generated per-agent nono profile; used only when `backend` is `'nono'`. */
34
43
  readonly profilePath: string;
35
44
  /**
36
45
  * When true, nono runs without `--silent` so its capabilities banner
@@ -41,7 +50,7 @@ export interface LaunchSpec {
41
50
  /**
42
51
  * Abs paths of the pi extension entry files resolved from settings.json's
43
52
  * `packages` (see `./packages.js`), installed into `<stateDir>/npm` by
44
- * `commands/start.ts`. Absent/empty on the argv-only preview used for
53
+ * `commands/run.ts`. Absent/empty on the argv-only preview used for
45
54
  * `--dry-run` (packages resolve at install time, after the preview is
46
55
  * printed) and on folders that declare no packages.
47
56
  */
@@ -50,15 +59,16 @@ export interface LaunchSpec {
50
59
  /**
51
60
  * Build the bare pi argv. `passthrough` lands last so user-passed flags win
52
61
  * under pi's last-wins parsing. The `-e` order is load-bearing: the generated
53
- * providers extension goes first, then the resolved package entries, then the
54
- * agent's own `extensions/` files which load with the agent's providers
55
- * registered and any package-provided tools already available, since the
56
- * agent's own extensions may depend on them.
62
+ * providers extension goes first, followed by the nono-only agent-browser
63
+ * host-socket fallback, the resolved package entries, then the agent's own
64
+ * `extensions/` files which load with the agent's providers registered,
65
+ * nono subprocess compatibility configured, and any package-provided tools
66
+ * already available, since the agent's own extensions may depend on them.
57
67
  */
58
68
  export declare function composePiArgv(spec: LaunchSpec): string[];
59
69
  /**
60
- * Compose the spawn env for a sandboxed run: `{ MISE_CACHE_DIR: spec.miseCacheDir }`;
61
- * `{}` when unsandboxed.
70
+ * Compose the spawn env for a nono-sandboxed run: `{ MISE_CACHE_DIR: spec.miseCacheDir }`;
71
+ * `{}` for `'sbx'` and unsandboxed runs alike.
62
72
  *
63
73
  * The generated profile denies the shared `~/Library/Caches/mise` on purpose
64
74
  * — a poisoned `bin_paths` cache would redirect which binaries the user's
@@ -68,9 +78,11 @@ export declare function composePiArgv(spec: LaunchSpec): string[];
68
78
  * at a private cache inside the already-granted state dir instead lets mise
69
79
  * cache writes succeed (no warnings, working cache) while the shared host
70
80
  * cache stays untouched; mise creates the directory itself, cradle never
71
- * pre-creates or wipes it. Unsandboxed runs return `{}` and keep the shared
81
+ * pre-creates or wipes it. Unsandboxed runs produce `{}` and keep the shared
72
82
  * host cache — no override needed since there's no sandbox denial to work
73
- * around.
83
+ * around; the sbx guest has no mise at all, and its HOME override rides the
84
+ * `sbx exec` argv composed in `commands/run.ts` rather than env, so the
85
+ * argv-only rule holds there too.
74
86
  *
75
87
  * This deliberately overrides any user-set `MISE_CACHE_DIR` for sandboxed
76
88
  * runs — the profile wouldn't grant a custom location either, so honoring one
@@ -84,8 +96,10 @@ export declare function composePiArgv(spec: LaunchSpec): string[];
84
96
  */
85
97
  export declare function composeEnv(spec: LaunchSpec): Record<string, string>;
86
98
  /**
87
- * Build the argv for `nono run … -- pi …`, or the bare pi argv when
88
- * sandboxing is disabled (`--no-sandbox`).
99
+ * Build the argv for `nono run … -- pi …` when `spec.backend` is `'nono'`, or
100
+ * the bare pi argv otherwise (`'sbx'` or `null`) — the `'sbx'` backend's own
101
+ * `sbx exec` wrapper is composed later, at materialization in
102
+ * `commands/run.ts`, not here (see `LaunchSpec.backend`).
89
103
  *
90
104
  * All filesystem grants (cwd, agent dir, state dir, and the agent's own
91
105
  * `sandbox/nono.json` entries) AND the network posture live inside the
@@ -0,0 +1,13 @@
1
+ /** `!`/`+`/`-` prefixed entries decide what loads; everything else names a source path or glob. */
2
+ export declare function isOverridePattern(pattern: string): boolean;
3
+ /**
4
+ * Narrow `files` to what `patterns` selects, preserving input order: plain
5
+ * globs include (none listed includes everything), `!` globs then exclude,
6
+ * `+`/`-` exact paths force a file back in or out — force-exclude wins.
7
+ */
8
+ export declare function applyPatterns(files: readonly string[], patterns: readonly string[], baseDir: string): string[];
9
+ /**
10
+ * pi's `autoload: false` delta: nothing loads except what a pattern names, and
11
+ * for a file matched by several patterns the last one decides.
12
+ */
13
+ export declare function applyDeltaPatterns(files: readonly string[], patterns: readonly string[], baseDir: string): string[];
@@ -1,14 +1,25 @@
1
1
  import { type JsonValue } from '../setup/utils.js';
2
+ /**
3
+ * The object form's resource filter, narrowed to the one resource type cradle
4
+ * delivers. Absent `extensions` loads everything the package declares, `[]`
5
+ * loads none, patterns decide otherwise (see `./package-filters.js`).
6
+ */
7
+ interface PackageFilter {
8
+ readonly extensions?: readonly string[];
9
+ /** pi's `autoload: false` — nothing loads except what an `extensions` pattern names. */
10
+ readonly autoloadDisabled?: boolean;
11
+ }
2
12
  /** A parsed `npm:<name>[@<version>]` package source from settings.json's `packages` key. */
3
- export interface NpmPackageSpec {
13
+ export interface NpmPackageSpec extends PackageFilter {
4
14
  readonly name: string;
5
15
  readonly version: string;
6
16
  }
7
17
  /**
8
- * Parse a settings.json record's `packages` key into validated npm: package
9
- * specs. Only `npm:` sources are supported pi also accepts `git:`,
10
- * `https://`, `ssh://`, and local paths, all warned and dropped here, since
11
- * cradle installs the package itself rather than delegating to pi's loader.
18
+ * Parse a settings.json record's `packages` key pi's source strings, pi's
19
+ * filter objects, or a mixinto validated npm: package specs. Only `npm:`
20
+ * sources are supported pi also accepts `git:`, `https://`, `ssh://`, and
21
+ * local paths, all warned and dropped here, since cradle installs the package
22
+ * itself rather than delegating to pi's loader.
12
23
  */
13
24
  export declare function readPackageSpecs(record: {
14
25
  readonly [key: string]: JsonValue;
@@ -16,13 +27,16 @@ export declare function readPackageSpecs(record: {
16
27
  /** Deterministic npm manifest for a per-agent packages install, dependencies sorted by name. */
17
28
  export declare function emitPackagesManifest(specs: readonly NpmPackageSpec[]): string;
18
29
  /**
19
- * Resolve each spec's installed package into pi extension entry files, ready
20
- * to pass as explicit `-e` flags. Every failure (not installed, malformed
21
- * manifest, an entry that escapes the package directory, a missing entry
22
- * file, no declared extensions) is a warning + skip — packages are
23
- * warn-don't-throw like every other agent-folder input.
30
+ * Resolve each spec's installed package into the pi extension entry files its
31
+ * settings.json entry selects, ready to pass as explicit `-e` flags. Every
32
+ * failure (not installed, malformed manifest, an entry that escapes the
33
+ * package directory, a missing entry file, no declared extensions) is a
34
+ * warning + skip — packages are warn-don't-throw like every other
35
+ * agent-folder input. A filter selecting nothing is silent: that is the
36
+ * author asking for nothing, not a failure.
24
37
  */
25
38
  export declare function resolvePackageEntries(npmDir: string, specs: readonly NpmPackageSpec[]): Promise<{
26
39
  entries: string[];
27
40
  warnings: string[];
28
41
  }>;
42
+ export {};
@@ -9,14 +9,13 @@ export interface DoctorCheck {
9
9
  }
10
10
  interface DoctorDeps {
11
11
  readonly which?: WhichFn;
12
- /** Read a bin's `--version` output; injected so in-process tests never spawn. */
13
- readonly readVersion?: (binPath: string) => Promise<string | null>;
12
+ /** Read a bin's version output (default argv `--version`; sbx passes `['version']`); injected so in-process tests never spawn. */
13
+ readonly readVersion?: (binPath: string, args?: readonly string[]) => Promise<string | null>;
14
14
  }
15
15
  /**
16
- * Probe the environment cradle depends on: `pi` (always required), `nono`
17
- * (required for sandboxed runs — a folder declaring sandbox/nono.json, or
18
- * --sandbox/--offline/--allow-host; not needed otherwise), and `mise`
19
- * (recommended toolchain manager).
16
+ * Probe the environment cradle depends on: `pi` (always required), `nono` and
17
+ * `sbx` (each required only for runs on its backend see the notes), and
18
+ * `mise` (recommended toolchain manager).
20
19
  */
21
20
  export declare function runDoctor(deps?: DoctorDeps): Promise<DoctorCheck[]>;
22
21
  export declare function formatDoctorReport(checks: readonly DoctorCheck[]): string;