@coryrylan/cradle 1.3.0 → 1.4.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 +13 -0
- package/README.md +24 -2
- package/dist/agent/folder.d.ts +3 -1
- package/dist/agent/schedules.d.ts +27 -0
- package/dist/commands/doctor.d.ts +6 -2
- package/dist/commands/run.d.ts +19 -0
- package/dist/commands/schedule.d.ts +85 -0
- package/dist/index.js +50 -30
- package/dist/schedule/cron.d.ts +22 -0
- package/dist/schedule/launchd.d.ts +12 -0
- package/dist/schedule/systemd.d.ts +17 -0
- package/dist/schedule/timer.d.ts +34 -0
- package/dist/util/proc.d.ts +17 -1
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## [1.4.0](https://github.com/coryrylan/cradle/compare/cradle-v1.3.1...cradle-v1.4.0) (2026-08-31)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* **cli:** support schedules ([28f2e71](https://github.com/coryrylan/cradle/commit/28f2e714209e1e0e33cf22b7783e8730cd6ba232))
|
|
6
|
+
|
|
7
|
+
## [1.3.1](https://github.com/coryrylan/cradle/compare/cradle-v1.3.0...cradle-v1.3.1) (2026-08-30)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **clI:** update dependencies ([24e2afe](https://github.com/coryrylan/cradle/commit/24e2afe3827a4f2916015fd1676464cb66a2dcd5))
|
|
12
|
+
* **cli:** update deps ([07e4065](https://github.com/coryrylan/cradle/commit/07e4065826a2193d5c08ff7295441f0b496f89fe))
|
|
13
|
+
|
|
1
14
|
## [1.3.0](https://github.com/coryrylan/cradle/compare/cradle-v1.2.0...cradle-v1.3.0) (2026-07-26)
|
|
2
15
|
|
|
3
16
|
### Features
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
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/
|
|
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/nolabs-ai/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
|
|
|
@@ -11,7 +11,7 @@ A runtime for portable agents defined as folders. `cradle run <dir>` reads an ag
|
|
|
11
11
|
| Tool | Status | Why |
|
|
12
12
|
| ------------------------------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
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/
|
|
14
|
+
| [`nono`](https://github.com/nolabs-ai/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
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. |
|
|
16
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. |
|
|
17
17
|
|
|
@@ -73,10 +73,32 @@ cradle run . --allow-host api.example.com # restrict network to these hosts (re
|
|
|
73
73
|
cradle run . --sandbox-backend sbx # run under the sbx Docker Sandboxes microVM instead of nono
|
|
74
74
|
cradle run . --no-sandbox # run pi directly (debug)
|
|
75
75
|
cradle run . --dry-run -- --resume # print the write plan + command; forward `--resume` to pi
|
|
76
|
+
|
|
77
|
+
cradle schedule list ./my-agent # scheduled tasks, cron, next fire
|
|
78
|
+
cradle schedule list my-agent # a name from ~/.cradle/settings.json works here too
|
|
79
|
+
cradle schedule install ./my-agent # write + load a native OS timer per task
|
|
80
|
+
cradle schedule run ./my-agent daily-report # fire one task now, in the foreground
|
|
81
|
+
cradle schedule remove ./my-agent # unload + delete
|
|
76
82
|
```
|
|
77
83
|
|
|
78
84
|
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>/`.
|
|
79
85
|
|
|
86
|
+
### Scheduled runs (`schedule/`)
|
|
87
|
+
|
|
88
|
+
An agent folder can carry a `schedule/` directory; each `schedule/<task>.md` is one cron-driven run — YAML frontmatter (`cron`, `cwd`, optional `name`/`description`) plus a markdown body that becomes the prompt.
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
---
|
|
92
|
+
name: Daily standup report
|
|
93
|
+
cron: '0 9 * * 1-5'
|
|
94
|
+
cwd: ~/dev/my-project
|
|
95
|
+
---
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`cradle schedule install` writes one native OS timer per task — a launchd LaunchAgent on macOS, a systemd user timer on Linux — each invoking `cradle schedule run <folder> <task>`. Both beat cron because they run a missed calendar job after the machine wakes. Task output lands in `~/.cradle/agents/<id>/schedule/<task>.log`.
|
|
99
|
+
|
|
100
|
+
A scheduled run is an ordinary `cradle run` with the task's `cwd` and prompt, so it adds no new sandbox policy: `cwd` is read+write exactly as it is interactively, and reaching outside it still needs a `sandbox/nono.json` grant. Runs pass pi's `--print` and `--no-approve` so an unattended job never blocks on a trust prompt. Cron expressions constraining both day-of-month and day-of-week are rejected — cron ORs those fields while launchd and systemd both AND them. See [ARCHITECTURE.md](../../ARCHITECTURE.md#schedules) for the full format.
|
|
101
|
+
|
|
80
102
|
### Global agent aliases (`~/.cradle/settings.json`)
|
|
81
103
|
|
|
82
104
|
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:
|
package/dist/agent/folder.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type NpmPackageSpec } from './packages.js';
|
|
2
|
-
export type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
|
|
2
|
+
export type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
3
3
|
export interface AgentSettings {
|
|
4
4
|
readonly defaultProvider?: string;
|
|
5
5
|
readonly defaultModel?: string;
|
|
@@ -85,6 +85,8 @@ export interface AgentFolder {
|
|
|
85
85
|
/** Serialized `providers` object from models.json, `null` when absent. */
|
|
86
86
|
readonly providersJson: string | null;
|
|
87
87
|
readonly skillsDir: string | null;
|
|
88
|
+
/** `schedule/` dir, `null` when absent. Parsing is lazy — see `./schedules.js`'s `loadSchedules`. */
|
|
89
|
+
readonly scheduleDir: string | null;
|
|
88
90
|
/** pi-native extensions (top-level `extensions/*.ts` plus each subdir's `index.ts`), absolute paths. */
|
|
89
91
|
readonly extensionFiles: readonly string[];
|
|
90
92
|
/** `sandbox/nono.json`'s declared posture — the Seatbelt/bubblewrap backend. */
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface Schedule {
|
|
2
|
+
/** Filename without `.md` — the identity used on the CLI and in timer labels. */
|
|
3
|
+
readonly slug: string;
|
|
4
|
+
/** Display label from frontmatter `name`; falls back to `slug`. */
|
|
5
|
+
readonly name: string;
|
|
6
|
+
/** Absolute path of the source `.md`. */
|
|
7
|
+
readonly path: string;
|
|
8
|
+
/** Raw cron expression, unparsed. */
|
|
9
|
+
readonly cron: string;
|
|
10
|
+
/** Absolute working directory, `~`/`$HOME` already expanded. */
|
|
11
|
+
readonly cwd: string;
|
|
12
|
+
readonly description?: string;
|
|
13
|
+
/** The markdown body — the prompt handed to pi. */
|
|
14
|
+
readonly prompt: string;
|
|
15
|
+
}
|
|
16
|
+
export interface LoadedSchedules {
|
|
17
|
+
readonly schedules: readonly Schedule[];
|
|
18
|
+
readonly warnings: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The "folder has no `schedule/`" error. `schedules/` is the intuitive guess
|
|
22
|
+
* for a directory holding many schedules, so when that is what the folder
|
|
23
|
+
* actually has, name the rename — the same targeted hint `folder.ts` gives for
|
|
24
|
+
* a legacy `AGENTS.md`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function missingScheduleDirError(agentDir: string): Promise<Error>;
|
|
27
|
+
export declare function loadSchedules(scheduleDir: string, home: string): Promise<LoadedSchedules>;
|
|
@@ -11,11 +11,15 @@ interface DoctorDeps {
|
|
|
11
11
|
readonly which?: WhichFn;
|
|
12
12
|
/** Read a bin's version output (default argv `--version`; sbx passes `['version']`); injected so in-process tests never spawn. */
|
|
13
13
|
readonly readVersion?: (binPath: string, args?: readonly string[]) => Promise<string | null>;
|
|
14
|
+
/** Injectable so both the launchd and systemd probes are exercised from any one machine's tests. */
|
|
15
|
+
readonly platform?: NodeJS.Platform;
|
|
14
16
|
}
|
|
15
17
|
/**
|
|
16
18
|
* Probe the environment cradle depends on: `pi` (always required), `nono` and
|
|
17
|
-
* `sbx` (each required only for runs on its backend — see the notes),
|
|
18
|
-
*
|
|
19
|
+
* `sbx` (each required only for runs on its backend — see the notes), `mise`
|
|
20
|
+
* (recommended toolchain manager), and the platform's scheduling backend
|
|
21
|
+
* (`launchctl`/`systemctl`, recommended for `cradle schedule`; skipped
|
|
22
|
+
* entirely on a platform with neither).
|
|
19
23
|
*/
|
|
20
24
|
export declare function runDoctor(deps?: DoctorDeps): Promise<DoctorCheck[]>;
|
|
21
25
|
export declare function formatDoctorReport(checks: readonly DoctorCheck[]): string;
|
package/dist/commands/run.d.ts
CHANGED
|
@@ -18,6 +18,18 @@ export interface RunFlags {
|
|
|
18
18
|
readonly passthrough?: readonly string[];
|
|
19
19
|
/** `--verbose` → show nono's full sandbox capabilities banner instead of the one-line status. */
|
|
20
20
|
readonly verbose?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* A `schedule/<task>.md` slug to run headless instead of starting an
|
|
23
|
+
* interactive session. The named schedule's `cwd` replaces the run's
|
|
24
|
+
* effective working directory (see `RunPlan.cwd`), and its body is appended
|
|
25
|
+
* to the launch passthrough as `--print --name <name> --no-approve --
|
|
26
|
+
* <prompt>`, after any user `--` passthrough so it always terminates the
|
|
27
|
+
* argv. Set only by `cradle schedule run` — and therefore by the OS timers,
|
|
28
|
+
* whose composed argv is exactly that command (see `schedule/launchd.ts`
|
|
29
|
+
* and `schedule/systemd.ts`). There is deliberately no `--schedule` flag on
|
|
30
|
+
* `cradle run`: two spellings of one action is one too many.
|
|
31
|
+
*/
|
|
32
|
+
readonly schedule?: string;
|
|
21
33
|
}
|
|
22
34
|
interface RunDeps {
|
|
23
35
|
readonly cwd?: string;
|
|
@@ -88,6 +100,13 @@ export interface RunPlan {
|
|
|
88
100
|
readonly packages: PackagesPlan | null;
|
|
89
101
|
/** The single argv source: `composeArgv(plan.launch)` — package-entry-free until `materializeRun` recomposes it with resolved package entries. */
|
|
90
102
|
readonly launch: LaunchSpec;
|
|
103
|
+
/**
|
|
104
|
+
* The effective working directory: `deps.cwd`, or `flags.schedule`'s
|
|
105
|
+
* `Schedule.cwd` when a schedule is running. Callers spawn here — `cli.ts`
|
|
106
|
+
* passes it to `runForeground` — since a scheduled run's launchd/systemd
|
|
107
|
+
* invocation carries no shell `cd` of its own.
|
|
108
|
+
*/
|
|
109
|
+
readonly cwd: string;
|
|
91
110
|
readonly dryRun: boolean;
|
|
92
111
|
}
|
|
93
112
|
/**
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type AgentFolder } from '../agent/folder.js';
|
|
2
|
+
import { type Schedule } from '../agent/schedules.js';
|
|
3
|
+
import type { TimerPlan } from '../schedule/timer.js';
|
|
4
|
+
import { type WhichFn } from '../util/which.js';
|
|
5
|
+
/** `cradle schedule run` is NOT here: it executes the task in-process through `commands/run.ts`, so `cli.ts` routes it there instead. */
|
|
6
|
+
export type ScheduleAction = 'list' | 'install' | 'remove';
|
|
7
|
+
/** launchd (macOS) and systemd `--user` (Linux) are the only supported timer backends. */
|
|
8
|
+
type SupportedSchedulePlatform = 'darwin' | 'linux';
|
|
9
|
+
export interface ScheduleFlags {
|
|
10
|
+
readonly dir: string;
|
|
11
|
+
readonly action: ScheduleAction;
|
|
12
|
+
/** Required for `run`; an optional filter for `install`/`remove` (every schedule when absent); unused for `list`, which always lists everything. */
|
|
13
|
+
readonly slug?: string;
|
|
14
|
+
/** `install` only — print the write plan and touch nothing. */
|
|
15
|
+
readonly dryRun?: boolean;
|
|
16
|
+
}
|
|
17
|
+
interface ScheduleDeps {
|
|
18
|
+
readonly cwd?: string;
|
|
19
|
+
readonly home?: string;
|
|
20
|
+
readonly which?: WhichFn;
|
|
21
|
+
readonly platform?: NodeJS.Platform;
|
|
22
|
+
readonly uid?: number;
|
|
23
|
+
/** The OS account name `loginctl show-user <user>` checks — default the real login name. */
|
|
24
|
+
readonly user?: string;
|
|
25
|
+
readonly now?: () => Date;
|
|
26
|
+
}
|
|
27
|
+
/** One `cradle schedule list` row — a broken cron is reported inline rather than aborting the whole listing. */
|
|
28
|
+
export interface ScheduleRow {
|
|
29
|
+
readonly slug: string;
|
|
30
|
+
readonly name: string;
|
|
31
|
+
readonly cron: string;
|
|
32
|
+
readonly cwd: string;
|
|
33
|
+
readonly cronError?: string;
|
|
34
|
+
readonly nextFire: Date | null;
|
|
35
|
+
readonly installed: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** One resolved schedule's composed timer, for `install`/`remove`/`run`. */
|
|
38
|
+
interface ScheduleTarget {
|
|
39
|
+
readonly schedule: Schedule;
|
|
40
|
+
readonly timerPlan: TimerPlan;
|
|
41
|
+
/** Not carried by `TimerPlan` itself — `materializeSchedule` `mkdir -p`s its parent before running `installSteps`. */
|
|
42
|
+
readonly logPath: string;
|
|
43
|
+
}
|
|
44
|
+
export interface SchedulePlan {
|
|
45
|
+
readonly action: ScheduleAction;
|
|
46
|
+
readonly folder: AgentFolder;
|
|
47
|
+
readonly platform: SupportedSchedulePlatform;
|
|
48
|
+
/** Populated for `list` only. */
|
|
49
|
+
readonly rows: readonly ScheduleRow[];
|
|
50
|
+
/** Populated for `install`/`remove`/`run` — every selected schedule with its composed `TimerPlan`. */
|
|
51
|
+
readonly targets: readonly ScheduleTarget[];
|
|
52
|
+
readonly warnings: readonly string[];
|
|
53
|
+
readonly dryRun: boolean;
|
|
54
|
+
/** `install` on Linux only — `materializeSchedule` runs this first and warns `LINGER_HINT` when lingering is off. */
|
|
55
|
+
readonly lingerCheckArgv: readonly string[] | null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve the ref, load the agent folder's `schedule/`, and — for every
|
|
59
|
+
* action but `list` — compose the selected schedule(s)' `TimerPlan`s. `list`
|
|
60
|
+
* never throws on a single bad schedule (see `buildScheduleRow`); every other
|
|
61
|
+
* action does, since installing/removing/firing a broken timer can't proceed.
|
|
62
|
+
*/
|
|
63
|
+
export declare function planSchedule(flags: ScheduleFlags, deps?: ScheduleDeps): Promise<SchedulePlan>;
|
|
64
|
+
/** Format `list`'s rows into the printed report — pure, mirroring `doctor.ts`'s `runDoctor`/`formatDoctorReport` split. */
|
|
65
|
+
export declare function formatScheduleList(rows: readonly ScheduleRow[]): string;
|
|
66
|
+
export interface ScheduleRunResult {
|
|
67
|
+
readonly exitCode: number;
|
|
68
|
+
readonly stdout: string;
|
|
69
|
+
readonly stderr: string;
|
|
70
|
+
}
|
|
71
|
+
export interface MaterializeScheduleDeps {
|
|
72
|
+
/** Runs one launchctl/systemctl/loginctl argv, capturing both streams; `cli.ts` passes `runCaptureAll` from `util/proc.js`. */
|
|
73
|
+
readonly run?: (argv: readonly string[]) => Promise<ScheduleRunResult>;
|
|
74
|
+
}
|
|
75
|
+
export interface MaterializeScheduleResult {
|
|
76
|
+
readonly warnings: string[];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Apply a non-`list` plan's side effects. The caller is responsible for
|
|
80
|
+
* skipping this entirely on `--dry-run` (`plan.dryRun`) — same precedent as
|
|
81
|
+
* `commands/run.ts`'s `materializeRun`, which `cli.ts` never calls when
|
|
82
|
+
* `plan.dryRun` is set.
|
|
83
|
+
*/
|
|
84
|
+
export declare function materializeSchedule(plan: SchedulePlan, deps?: MaterializeScheduleDeps): Promise<MaterializeScheduleResult>;
|
|
85
|
+
export {};
|