@coryrylan/cradle 1.3.1 → 1.4.1

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.
@@ -0,0 +1,22 @@
1
+ /** A parsed cron field: the allowed values, or `null` meaning "every". */
2
+ export type CronField = readonly number[] | null;
3
+ export interface CronFields {
4
+ readonly minute: CronField;
5
+ readonly hour: CronField;
6
+ readonly dayOfMonth: CronField;
7
+ readonly month: CronField;
8
+ readonly dayOfWeek: CronField;
9
+ }
10
+ /**
11
+ * Parse a 5-field cron expression or a `@`-macro into structured fields.
12
+ * Throws a named error when the expression is malformed, or when
13
+ * day-of-month and day-of-week are both restricted (see the check below).
14
+ */
15
+ export declare function parseCron(expression: string): CronFields;
16
+ /**
17
+ * The next occurrence strictly after `from`, in local time, truncated to the
18
+ * minute. Returns `null` when nothing matches within 366 days — the guard
19
+ * against an expression that can never fire, such as day-of-month 30 paired
20
+ * with February.
21
+ */
22
+ export declare function nextFire(fields: CronFields, from: Date): Date | null;
@@ -0,0 +1,12 @@
1
+ import type { CronFields } from './cron.js';
2
+ import type { TimerContext, TimerPlan } from './timer.js';
3
+ /** Thrown when a cron expression's constrained fields would expand past `MAX_CALENDAR_INTERVALS` dicts. */
4
+ export declare class LaunchdIntervalOverflowError extends Error {
5
+ constructor(dictCount: number);
6
+ }
7
+ /**
8
+ * Compose the LaunchAgent plist plus its `launchctl` install/remove steps. `dayOfMonth`/`dayOfWeek` are never both constrained (`parseCron`
9
+ * rejects that combination), so the cartesian product below never needs to
10
+ * reproduce cron's OR semantics between them.
11
+ */
12
+ export declare function composeLaunchdTimer(context: TimerContext, fields: CronFields): TimerPlan;
@@ -0,0 +1,17 @@
1
+ import type { CronFields } from './cron.js';
2
+ import type { TimerContext, TimerPlan } from './timer.js';
3
+ /** Thrown when a value bound for a unit file contains a raw newline, which would corrupt the ini-style syntax. */
4
+ export declare class SystemdUnitValueError extends Error {
5
+ constructor(value: string);
6
+ }
7
+ /**
8
+ * Compose the `.service`/`.timer` unit pair plus their `systemctl --user`
9
+ * install/remove steps. `dayOfMonth`/`dayOfWeek` are never both
10
+ * constrained (`parseCron` rejects that combination), so `OnCalendar=`
11
+ * below never needs to reproduce cron's OR semantics between them.
12
+ */
13
+ export declare function composeSystemdTimer(context: TimerContext, fields: CronFields): TimerPlan;
14
+ /** `loginctl show-user <user> --property=Linger` — user timers do not fire without a session unless lingering is on. */
15
+ export declare function composeLingerCheckArgv(user: string): readonly string[];
16
+ /** One-line hint for the command layer to surface when lingering is off. */
17
+ export declare const LINGER_HINT = "systemd user timers stop firing once you log out unless lingering is enabled \u2014 run `loginctl enable-linger <user>` to keep them running";
@@ -0,0 +1,34 @@
1
+ import type { Schedule } from '../agent/schedules.js';
2
+ /** Everything both timer emitters need to compose their platform's artifacts. */
3
+ export interface TimerContext {
4
+ readonly schedule: Schedule;
5
+ /** From `agent/state.ts`'s `agentId()` — e.g. `my-agent-a1b2c3d4`. Keeps two same-named folders from colliding. */
6
+ readonly agentId: string;
7
+ readonly agentDir: string;
8
+ /** Absolute path to the `cradle` binary — launchd and systemd load no shell profile, so this is never a bare name. */
9
+ readonly cradleBin: string;
10
+ /** Where the task's stdout/stderr is captured. */
11
+ readonly logPath: string;
12
+ readonly home: string;
13
+ /** launchd domain target (`gui/<uid>`); systemd ignores it. */
14
+ readonly uid: number;
15
+ }
16
+ /** One file the command layer must write before running `installSteps`. */
17
+ export interface TimerFile {
18
+ readonly path: string;
19
+ readonly content: string;
20
+ }
21
+ /** One argv to run, in order, as part of installing, removing, or one-shot firing a timer. */
22
+ export interface TimerStep {
23
+ readonly argv: readonly string[];
24
+ /** A non-zero exit is expected and must be ignored — e.g. `launchctl bootout` when nothing is loaded yet. */
25
+ readonly ignoreFailure?: boolean;
26
+ }
27
+ /** The platform-neutral result of composing a schedule's OS timer — the command layer's only input. */
28
+ export interface TimerPlan {
29
+ /** The launchd label or the systemd unit base name — shown in `cradle schedule list`. */
30
+ readonly id: string;
31
+ readonly files: readonly TimerFile[];
32
+ readonly installSteps: readonly TimerStep[];
33
+ readonly removeSteps: readonly TimerStep[];
34
+ }
@@ -14,8 +14,11 @@ export declare function killOn(proc: Killable, signal: ForwardableSignal): () =>
14
14
  *
15
15
  * `env` entries override the inherited `process.env` (used for the
16
16
  * sandboxed-run `MISE_CACHE_DIR`, see `agent/launch.ts`'s `composeEnv`).
17
+ * `cwd`, when given, is `RunPlan.cwd` — the effective working directory,
18
+ * which differs from `process.cwd()` only for a `--schedule` run (see
19
+ * `commands/run.ts`'s `resolveScheduledRun`).
17
20
  */
18
- export declare function runForeground(argv: readonly string[], env?: Record<string, string>): Promise<number>;
21
+ export declare function runForeground(argv: readonly string[], env?: Record<string, string>, cwd?: string): Promise<number>;
19
22
  /**
20
23
  * Run a setup command silently, capturing stderr for the caller's error
21
24
  * message — the sbx create/policy/provision sequence, see
@@ -27,6 +30,19 @@ export declare function runCapture(argv: readonly string[]): Promise<{
27
30
  exitCode: number;
28
31
  stderr: string;
29
32
  }>;
33
+ /**
34
+ * Run a command capturing BOTH streams — `commands/schedule.ts`'s
35
+ * launchctl/systemctl/loginctl steps, which (unlike `runCapture`'s
36
+ * sbx setup, stderr-only) need stdout too: the linger check
37
+ * (`loginctl show-user --property=Linger`) reports its answer there. Never
38
+ * throws on a non-zero exit; the caller decides which failures matter
39
+ * (`TimerStep.ignoreFailure`).
40
+ */
41
+ export declare function runCaptureAll(argv: readonly string[]): Promise<{
42
+ exitCode: number;
43
+ stdout: string;
44
+ stderr: string;
45
+ }>;
30
46
  /** Run a package install (e.g. `npm install`) in `cwd`, inheriting output; throws on non-zero exit. */
31
47
  export declare function runInstall(command: readonly string[], cwd: string): Promise<void>;
32
48
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coryrylan/cradle",
3
- "version": "1.3.1",
3
+ "version": "1.4.1",
4
4
  "description": "A runtime for portable agents defined as folders — launches the pi coding agent configured from an agent folder, sandboxed with nono",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "homepage": "https://github.com/coryrylan/cradle/tree/main/projects/cli#readme",
47
47
  "engines": {
48
- "bun": ">=1.4.0"
48
+ "bun": ">=1.4.2"
49
49
  },
50
50
  "publishConfig": {
51
51
  "access": "public"
@@ -226,7 +226,7 @@
226
226
  "@coryrylan/tools": "1.1.1",
227
227
  "@eslint/js": "10.0.1",
228
228
  "@eslint/json": "2.0.1",
229
- "@types/bun": "1.4.0",
229
+ "@types/bun": "1.4.2",
230
230
  "@types/yargs": "17.0.35",
231
231
  "eslint": "10.9.1",
232
232
  "eslint-plugin-jsdoc": "64.2.1",