@cruxy/cli 0.8.0 → 0.10.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/README.md +40 -13
- package/dist/agent/loop.d.ts +28 -1
- package/dist/agent/loop.js +36 -4
- package/dist/agent/prompts.d.ts +2 -0
- package/dist/agent/prompts.js +8 -0
- package/dist/approval/classify.js +26 -0
- package/dist/approval/prompt.js +4 -27
- package/dist/checkpoint/capture.d.ts +17 -0
- package/dist/checkpoint/capture.js +73 -0
- package/dist/checkpoint/git-store.d.ts +61 -0
- package/dist/checkpoint/git-store.js +171 -0
- package/dist/checkpoint/index.d.ts +6 -0
- package/dist/checkpoint/index.js +6 -0
- package/dist/checkpoint/restore.d.ts +23 -0
- package/dist/checkpoint/restore.js +195 -0
- package/dist/checkpoint/service.d.ts +80 -0
- package/dist/checkpoint/service.js +276 -0
- package/dist/checkpoint/shadow-store.d.ts +23 -0
- package/dist/checkpoint/shadow-store.js +93 -0
- package/dist/checkpoint/types.d.ts +117 -0
- package/dist/checkpoint/types.js +18 -0
- package/dist/cli/commands/checkpoint.d.ts +7 -0
- package/dist/cli/commands/checkpoint.js +31 -0
- package/dist/cli/commands/rollback.d.ts +10 -0
- package/dist/cli/commands/rollback.js +96 -0
- package/dist/cli/commands/run.js +10 -2
- package/dist/cli/program.js +4 -0
- package/dist/cli/repl.d.ts +7 -1
- package/dist/cli/repl.js +23 -3
- package/dist/cli/session-factory.d.ts +14 -1
- package/dist/cli/session-factory.js +87 -22
- package/dist/components/autocomplete.d.ts +32 -0
- package/dist/components/autocomplete.js +50 -0
- package/dist/components/frame.d.ts +25 -0
- package/dist/components/frame.js +49 -0
- package/dist/components/fuzzy.d.ts +61 -0
- package/dist/components/fuzzy.js +174 -0
- package/dist/components/index.d.ts +6 -0
- package/dist/components/index.js +6 -0
- package/dist/components/input.d.ts +78 -0
- package/dist/components/input.js +111 -0
- package/dist/components/keys.d.ts +48 -0
- package/dist/components/keys.js +105 -0
- package/dist/components/select.d.ts +28 -0
- package/dist/components/select.js +69 -0
- package/dist/config/schema.d.ts +133 -0
- package/dist/config/schema.js +40 -0
- package/dist/errors/constructors.d.ts +32 -0
- package/dist/errors/constructors.js +101 -0
- package/dist/errors/types.d.ts +8 -0
- package/dist/errors/types.js +18 -0
- package/dist/indexing/walker.d.ts +11 -0
- package/dist/indexing/walker.js +11 -6
- package/dist/onboarding/io.d.ts +3 -2
- package/dist/onboarding/io.js +35 -81
- package/dist/plan/execute.d.ts +8 -0
- package/dist/plan/execute.js +36 -22
- package/dist/plan/service.js +5 -1
- package/dist/plan/submit-plan.d.ts +4 -4
- package/dist/render/diff.js +27 -0
- package/dist/render/index.d.ts +2 -1
- package/dist/render/index.js +1 -0
- package/dist/render/plain-renderer.d.ts +7 -1
- package/dist/render/plain-renderer.js +26 -0
- package/dist/render/state.d.ts +31 -0
- package/dist/render/state.js +83 -0
- package/dist/render/tty-renderer.d.ts +41 -5
- package/dist/render/tty-renderer.js +150 -23
- package/dist/render/types.d.ts +85 -1
- package/dist/subagent/budget.d.ts +34 -0
- package/dist/subagent/budget.js +57 -0
- package/dist/subagent/index.d.ts +5 -0
- package/dist/subagent/index.js +5 -0
- package/dist/subagent/orchestrator.d.ts +67 -0
- package/dist/subagent/orchestrator.js +241 -0
- package/dist/subagent/registry-scope.d.ts +28 -0
- package/dist/subagent/registry-scope.js +63 -0
- package/dist/subagent/spawn-tool.d.ts +29 -0
- package/dist/subagent/spawn-tool.js +94 -0
- package/dist/subagent/types.d.ts +55 -0
- package/dist/subagent/types.js +1 -0
- package/dist/tools/types.d.ts +20 -2
- package/package.json +1 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { interactiveRequired } from "../errors/index.js";
|
|
2
|
+
import { detectCapabilities } from "../render/index.js";
|
|
3
|
+
import { decodeKeys } from "./keys.js";
|
|
4
|
+
/** Build the real reader over `stdin` (or an injected fake in tests). */
|
|
5
|
+
export function createKeyReader(stdin = process.stdin) {
|
|
6
|
+
const queue = [];
|
|
7
|
+
const waiters = [];
|
|
8
|
+
let active = false;
|
|
9
|
+
let ended = false;
|
|
10
|
+
const push = (key) => {
|
|
11
|
+
const waiter = waiters.shift();
|
|
12
|
+
if (waiter)
|
|
13
|
+
waiter(key);
|
|
14
|
+
else
|
|
15
|
+
queue.push(key);
|
|
16
|
+
};
|
|
17
|
+
const onData = (chunk) => {
|
|
18
|
+
for (const key of decodeKeys(chunk))
|
|
19
|
+
push(key);
|
|
20
|
+
};
|
|
21
|
+
const onEnd = () => {
|
|
22
|
+
ended = true;
|
|
23
|
+
// Wake every waiter: EOF is a key, not an error.
|
|
24
|
+
while (waiters.length > 0)
|
|
25
|
+
push({ kind: "eof" });
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
begin() {
|
|
29
|
+
if (active)
|
|
30
|
+
return;
|
|
31
|
+
active = true;
|
|
32
|
+
if (stdin.isTTY)
|
|
33
|
+
stdin.setRawMode?.(true);
|
|
34
|
+
stdin.resume();
|
|
35
|
+
stdin.on("data", onData);
|
|
36
|
+
stdin.on("end", onEnd);
|
|
37
|
+
},
|
|
38
|
+
read() {
|
|
39
|
+
const next = queue.shift();
|
|
40
|
+
if (next)
|
|
41
|
+
return Promise.resolve(next);
|
|
42
|
+
if (ended || !active)
|
|
43
|
+
return Promise.resolve({ kind: "eof" });
|
|
44
|
+
return new Promise((resolve) => waiters.push(resolve));
|
|
45
|
+
},
|
|
46
|
+
restore() {
|
|
47
|
+
if (!active)
|
|
48
|
+
return;
|
|
49
|
+
active = false;
|
|
50
|
+
stdin.removeListener("data", onData);
|
|
51
|
+
stdin.removeListener("end", onEnd);
|
|
52
|
+
if (stdin.isTTY)
|
|
53
|
+
stdin.setRawMode?.(false);
|
|
54
|
+
stdin.pause();
|
|
55
|
+
// Unblock anything still awaiting a key — restore means the interaction
|
|
56
|
+
// is over, and a hung promise would leak the caller.
|
|
57
|
+
while (waiters.length > 0)
|
|
58
|
+
waiters.shift()({ kind: "eof" });
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Read exactly one key with the full begin/restore lifecycle — the shared
|
|
64
|
+
* backend for the approval prompt's single-key read and the onboarding
|
|
65
|
+
* `readKey`. Resolves the printable character, `"\n"` for enter, and `""` for
|
|
66
|
+
* anything that means "no answer" (Ctrl-C, EOF, escape, arrows) — preserving
|
|
67
|
+
* the callers' default-deny mapping.
|
|
68
|
+
*/
|
|
69
|
+
export async function readSingleKey(stdin = process.stdin) {
|
|
70
|
+
const keys = createKeyReader(stdin);
|
|
71
|
+
keys.begin();
|
|
72
|
+
try {
|
|
73
|
+
const key = await keys.read();
|
|
74
|
+
switch (key.kind) {
|
|
75
|
+
case "char":
|
|
76
|
+
return key.char;
|
|
77
|
+
case "enter":
|
|
78
|
+
return "\n";
|
|
79
|
+
default:
|
|
80
|
+
return "";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
keys.restore();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** The real environment: frames to stderr, keys from stdin, caps from stderr. */
|
|
88
|
+
export function defaultComponentIO() {
|
|
89
|
+
const caps = detectCapabilities(process.stderr);
|
|
90
|
+
return {
|
|
91
|
+
caps,
|
|
92
|
+
interactive: Boolean(process.stdin.isTTY) && caps.cursor,
|
|
93
|
+
write: (text) => void process.stderr.write(text),
|
|
94
|
+
keys: createKeyReader(process.stdin),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The shared non-TTY gate, run before any frame byte or key read:
|
|
99
|
+
* - interactive → proceed;
|
|
100
|
+
* - not interactive + a default was supplied → resolve to it immediately;
|
|
101
|
+
* - otherwise → throw `CRUXY_E_INTERACTIVE_REQUIRED` (usage, exit 2).
|
|
102
|
+
* Never blocks on a pipe, never silently picks an option.
|
|
103
|
+
*/
|
|
104
|
+
export function resolveNonInteractive(io, what, defaultValue, alternatives = []) {
|
|
105
|
+
if (io.interactive)
|
|
106
|
+
return null;
|
|
107
|
+
if (defaultValue !== undefined) {
|
|
108
|
+
return { kind: "selected", value: defaultValue };
|
|
109
|
+
}
|
|
110
|
+
throw interactiveRequired(what, alternatives);
|
|
111
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Byte → key decoding for the interactive components (U.7). Pure data
|
|
3
|
+
* transformation — no terminal, no state — so every mapping row is directly
|
|
4
|
+
* unit-testable. The stateful raw-mode plumbing lives in `input.ts`.
|
|
5
|
+
*/
|
|
6
|
+
/** One decoded keypress. The full vocabulary the U.7 components consume. */
|
|
7
|
+
export type Key =
|
|
8
|
+
/** A printable character (UTF-8; may be multi-byte, never a control char). */
|
|
9
|
+
{
|
|
10
|
+
kind: "char";
|
|
11
|
+
char: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: "enter";
|
|
14
|
+
} | {
|
|
15
|
+
kind: "backspace";
|
|
16
|
+
} | {
|
|
17
|
+
kind: "tab";
|
|
18
|
+
} | {
|
|
19
|
+
kind: "escape";
|
|
20
|
+
} | {
|
|
21
|
+
kind: "up";
|
|
22
|
+
} | {
|
|
23
|
+
kind: "down";
|
|
24
|
+
} | {
|
|
25
|
+
kind: "left";
|
|
26
|
+
} | {
|
|
27
|
+
kind: "right";
|
|
28
|
+
}
|
|
29
|
+
/** Ctrl-C in raw mode (no SIGINT is delivered) — components must cancel. */
|
|
30
|
+
| {
|
|
31
|
+
kind: "ctrl-c";
|
|
32
|
+
}
|
|
33
|
+
/** Ctrl-D or stream end — components must cancel. */
|
|
34
|
+
| {
|
|
35
|
+
kind: "eof";
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Decode one raw-mode chunk into keys. A paste or fast typing delivers many
|
|
39
|
+
* keys per chunk; all are returned in order.
|
|
40
|
+
*
|
|
41
|
+
* Escape handling is deliberately simple: `ESC [ A..D` decodes to an arrow,
|
|
42
|
+
* any other CSI sequence (`ESC [ …final`) is swallowed whole (unmapped keys
|
|
43
|
+
* must not leak garbage chars into a query), and a lone ESC decodes to
|
|
44
|
+
* `escape`. Terminals send arrow sequences atomically in practice; a sequence
|
|
45
|
+
* split across chunks degrades to `escape` + literal chars, which is safe
|
|
46
|
+
* (escape cancels).
|
|
47
|
+
*/
|
|
48
|
+
export declare function decodeKeys(chunk: Buffer | string): Key[];
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Byte → key decoding for the interactive components (U.7). Pure data
|
|
3
|
+
* transformation — no terminal, no state — so every mapping row is directly
|
|
4
|
+
* unit-testable. The stateful raw-mode plumbing lives in `input.ts`.
|
|
5
|
+
*/
|
|
6
|
+
const CTRL_C = 0x03;
|
|
7
|
+
const CTRL_D = 0x04;
|
|
8
|
+
const BACKSPACE = 0x08;
|
|
9
|
+
const TAB = 0x09;
|
|
10
|
+
const LF = 0x0a;
|
|
11
|
+
const CR = 0x0d;
|
|
12
|
+
const ESC = 0x1b;
|
|
13
|
+
const DELETE = 0x7f;
|
|
14
|
+
/** CSI final byte → arrow key, for `ESC [ <final>` sequences. */
|
|
15
|
+
const ARROWS = {
|
|
16
|
+
0x41: "up", // A
|
|
17
|
+
0x42: "down", // B
|
|
18
|
+
0x43: "right", // C
|
|
19
|
+
0x44: "left", // D
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Decode one raw-mode chunk into keys. A paste or fast typing delivers many
|
|
23
|
+
* keys per chunk; all are returned in order.
|
|
24
|
+
*
|
|
25
|
+
* Escape handling is deliberately simple: `ESC [ A..D` decodes to an arrow,
|
|
26
|
+
* any other CSI sequence (`ESC [ …final`) is swallowed whole (unmapped keys
|
|
27
|
+
* must not leak garbage chars into a query), and a lone ESC decodes to
|
|
28
|
+
* `escape`. Terminals send arrow sequences atomically in practice; a sequence
|
|
29
|
+
* split across chunks degrades to `escape` + literal chars, which is safe
|
|
30
|
+
* (escape cancels).
|
|
31
|
+
*/
|
|
32
|
+
export function decodeKeys(chunk) {
|
|
33
|
+
const buf = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
34
|
+
const keys = [];
|
|
35
|
+
for (let i = 0; i < buf.length;) {
|
|
36
|
+
const byte = buf[i];
|
|
37
|
+
if (byte === ESC) {
|
|
38
|
+
if (buf[i + 1] === 0x5b /* [ */) {
|
|
39
|
+
// CSI: consume parameter/intermediate bytes (0x20–0x3f) up to the
|
|
40
|
+
// final byte (0x40–0x7e); map arrows, swallow everything else.
|
|
41
|
+
let j = i + 2;
|
|
42
|
+
while (j < buf.length && buf[j] >= 0x20 && buf[j] <= 0x3f)
|
|
43
|
+
j++;
|
|
44
|
+
if (j < buf.length) {
|
|
45
|
+
// Only a bare `ESC [ <final>` maps to an arrow; parameterized
|
|
46
|
+
// sequences (modifier arrows, Home/End variants) are swallowed —
|
|
47
|
+
// an unmapped combo must do nothing, not act as a plain arrow.
|
|
48
|
+
const arrow = j === i + 2 ? ARROWS[buf[j]] : undefined;
|
|
49
|
+
if (arrow)
|
|
50
|
+
keys.push({ kind: arrow });
|
|
51
|
+
i = j + 1;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
// Truncated CSI at chunk end: treat as escape, drop the partial tail.
|
|
55
|
+
keys.push({ kind: "escape" });
|
|
56
|
+
i = buf.length;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
keys.push({ kind: "escape" });
|
|
60
|
+
i++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (byte === CTRL_C) {
|
|
64
|
+
keys.push({ kind: "ctrl-c" });
|
|
65
|
+
i++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (byte === CTRL_D) {
|
|
69
|
+
keys.push({ kind: "eof" });
|
|
70
|
+
i++;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (byte === CR || byte === LF) {
|
|
74
|
+
keys.push({ kind: "enter" });
|
|
75
|
+
// Swallow a CRLF pair as one enter.
|
|
76
|
+
if (byte === CR && buf[i + 1] === LF)
|
|
77
|
+
i++;
|
|
78
|
+
i++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (byte === BACKSPACE || byte === DELETE) {
|
|
82
|
+
keys.push({ kind: "backspace" });
|
|
83
|
+
i++;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (byte === TAB) {
|
|
87
|
+
keys.push({ kind: "tab" });
|
|
88
|
+
i++;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (byte < 0x20) {
|
|
92
|
+
// Other control chars: ignore (same rule as the secret reader).
|
|
93
|
+
i++;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// Printable: consume one UTF-8 code point (1–4 bytes by lead byte).
|
|
97
|
+
const len = byte >= 0xf0 ? 4 : byte >= 0xe0 ? 3 : byte >= 0xc0 ? 2 : 1;
|
|
98
|
+
keys.push({
|
|
99
|
+
kind: "char",
|
|
100
|
+
char: buf.subarray(i, Math.min(i + len, buf.length)).toString("utf8"),
|
|
101
|
+
});
|
|
102
|
+
i += len;
|
|
103
|
+
}
|
|
104
|
+
return keys;
|
|
105
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ComponentIO, type InteractiveResult } from "./input.js";
|
|
2
|
+
/**
|
|
3
|
+
* SelectList (U.7): arrow-key pick from a short list of options. Single-select
|
|
4
|
+
* only — multi-select is a deliberate seam for a later build (the result type
|
|
5
|
+
* and key loop extend without breaking callers).
|
|
6
|
+
*/
|
|
7
|
+
export interface SelectOptions<T> {
|
|
8
|
+
/** Label an item renders under. Defaults to `String(item)`. */
|
|
9
|
+
toLabel?: (item: T) => string;
|
|
10
|
+
/** Header row above the options. */
|
|
11
|
+
title?: string;
|
|
12
|
+
/** Index highlighted first (so Enter-once picks a sensible default). */
|
|
13
|
+
initialIndex?: number;
|
|
14
|
+
/** Non-TTY fallback: resolve to this instead of failing loud. */
|
|
15
|
+
defaultValue?: T;
|
|
16
|
+
/** `--flag` alternatives listed in the non-TTY error. */
|
|
17
|
+
nonInteractiveHint?: string[];
|
|
18
|
+
/** Visible rows before the list scrolls (default 10). */
|
|
19
|
+
maxVisible?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Pick one item: ↑/↓ move (wrapping past either end), Enter selects the
|
|
23
|
+
* highlighted item, Esc / Ctrl-C / EOF cancel. Renders a transient frame via
|
|
24
|
+
* the shared U.7 frame (width-truncated rows, no soft-wrap) and erases it
|
|
25
|
+
* completely before resolving. Empty `items` resolves cancelled — there is
|
|
26
|
+
* nothing to select.
|
|
27
|
+
*/
|
|
28
|
+
export declare function selectList<T>(items: readonly T[], opts?: SelectOptions<T>, io?: ComponentIO): Promise<InteractiveResult<T>>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import { createFrame } from "./frame.js";
|
|
3
|
+
import { defaultComponentIO, resolveNonInteractive, } from "./input.js";
|
|
4
|
+
/**
|
|
5
|
+
* Pick one item: ↑/↓ move (wrapping past either end), Enter selects the
|
|
6
|
+
* highlighted item, Esc / Ctrl-C / EOF cancel. Renders a transient frame via
|
|
7
|
+
* the shared U.7 frame (width-truncated rows, no soft-wrap) and erases it
|
|
8
|
+
* completely before resolving. Empty `items` resolves cancelled — there is
|
|
9
|
+
* nothing to select.
|
|
10
|
+
*/
|
|
11
|
+
export async function selectList(items, opts = {}, io = defaultComponentIO()) {
|
|
12
|
+
const fallback = resolveNonInteractive(io, opts.title ?? "this picker", opts.defaultValue, opts.nonInteractiveHint);
|
|
13
|
+
if (fallback)
|
|
14
|
+
return fallback;
|
|
15
|
+
if (items.length === 0)
|
|
16
|
+
return { kind: "cancelled" };
|
|
17
|
+
const toLabel = opts.toLabel ?? ((item) => String(item));
|
|
18
|
+
const colors = pc.createColors(io.caps.color);
|
|
19
|
+
const maxVisible = opts.maxVisible ?? 10;
|
|
20
|
+
const frame = createFrame(io.write, io.caps);
|
|
21
|
+
let cursor = Math.min(Math.max(opts.initialIndex ?? 0, 0), items.length - 1);
|
|
22
|
+
const paint = () => {
|
|
23
|
+
const lines = [];
|
|
24
|
+
if (opts.title)
|
|
25
|
+
lines.push(colors.bold(opts.title));
|
|
26
|
+
const top = Math.min(Math.max(0, cursor - maxVisible + 1), Math.max(0, items.length - maxVisible));
|
|
27
|
+
const visible = items.slice(top, top + maxVisible);
|
|
28
|
+
for (const [i, item] of visible.entries()) {
|
|
29
|
+
const selected = top + i === cursor;
|
|
30
|
+
const marker = selected ? colors.cyan("❯") : " ";
|
|
31
|
+
const label = toLabel(item);
|
|
32
|
+
lines.push(`${marker} ${selected ? label : colors.dim(label)}`);
|
|
33
|
+
}
|
|
34
|
+
const hidden = items.length - visible.length;
|
|
35
|
+
if (hidden > 0)
|
|
36
|
+
lines.push(colors.dim(` … ${hidden} more`));
|
|
37
|
+
lines.push(colors.dim(" ↑/↓ move · enter select · esc cancel"));
|
|
38
|
+
frame.render(lines);
|
|
39
|
+
};
|
|
40
|
+
io.keys.begin();
|
|
41
|
+
try {
|
|
42
|
+
paint();
|
|
43
|
+
for (;;) {
|
|
44
|
+
const key = await io.keys.read();
|
|
45
|
+
switch (key.kind) {
|
|
46
|
+
case "ctrl-c":
|
|
47
|
+
case "eof":
|
|
48
|
+
case "escape":
|
|
49
|
+
return { kind: "cancelled" };
|
|
50
|
+
case "enter":
|
|
51
|
+
return { kind: "selected", value: items[cursor] };
|
|
52
|
+
case "up":
|
|
53
|
+
cursor = (cursor - 1 + items.length) % items.length;
|
|
54
|
+
break;
|
|
55
|
+
case "down":
|
|
56
|
+
cursor = (cursor + 1) % items.length;
|
|
57
|
+
break;
|
|
58
|
+
default:
|
|
59
|
+
break; // chars/tab have no meaning in a plain select
|
|
60
|
+
}
|
|
61
|
+
paint();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
// Selection or cancel alike: no leftover frame bytes, raw mode released.
|
|
66
|
+
frame.clear();
|
|
67
|
+
io.keys.restore();
|
|
68
|
+
}
|
|
69
|
+
}
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -203,6 +203,65 @@ export declare const IndexConfigSchema: z.ZodObject<{
|
|
|
203
203
|
overlapLines?: number | undefined;
|
|
204
204
|
} | undefined;
|
|
205
205
|
}>;
|
|
206
|
+
/**
|
|
207
|
+
* Working-tree checkpoints (C.32): a snapshot taken before an agent run's first
|
|
208
|
+
* file mutation, so `cruxy rollback` can undo the whole run atomically.
|
|
209
|
+
*/
|
|
210
|
+
export declare const CheckpointConfigSchema: z.ZodObject<{
|
|
211
|
+
/** Auto-checkpoint before a run's first mutation (and enable `cruxy rollback`). */
|
|
212
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
213
|
+
/** How many checkpoints to keep; older ones are pruned oldest-first. */
|
|
214
|
+
retention: z.ZodDefault<z.ZodNumber>;
|
|
215
|
+
}, "strict", z.ZodTypeAny, {
|
|
216
|
+
enabled: boolean;
|
|
217
|
+
retention: number;
|
|
218
|
+
}, {
|
|
219
|
+
enabled?: boolean | undefined;
|
|
220
|
+
retention?: number | undefined;
|
|
221
|
+
}>;
|
|
222
|
+
/**
|
|
223
|
+
* Subagent orchestration (C.14): scoped child agents the main agent can spawn
|
|
224
|
+
* for bounded subtasks. Every cap here is a hard bound — a subagent can narrow
|
|
225
|
+
* its budget at spawn time but never exceed these ceilings.
|
|
226
|
+
*/
|
|
227
|
+
export declare const SubagentConfigSchema: z.ZodObject<{
|
|
228
|
+
/**
|
|
229
|
+
* Maximum subagent nesting depth. The main agent is depth 0; the default of
|
|
230
|
+
* 1 lets it spawn subagents that cannot themselves spawn (no fork bombs).
|
|
231
|
+
*/
|
|
232
|
+
maxDepth: z.ZodDefault<z.ZodNumber>;
|
|
233
|
+
/** Per-subagent budget ceilings; spawn-time overrides are clamped to these. */
|
|
234
|
+
defaultBudget: z.ZodDefault<z.ZodObject<{
|
|
235
|
+
/** Hard cap on the subagent's model turns. */
|
|
236
|
+
maxIterations: z.ZodDefault<z.ZodNumber>;
|
|
237
|
+
/** Hard cap on the subagent's combined input+output tokens. */
|
|
238
|
+
maxTokens: z.ZodDefault<z.ZodNumber>;
|
|
239
|
+
/** Optional wall-clock cap; unset means no time limit. */
|
|
240
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
241
|
+
}, "strict", z.ZodTypeAny, {
|
|
242
|
+
maxTokens: number;
|
|
243
|
+
maxIterations: number;
|
|
244
|
+
timeoutMs?: number | undefined;
|
|
245
|
+
}, {
|
|
246
|
+
maxTokens?: number | undefined;
|
|
247
|
+
maxIterations?: number | undefined;
|
|
248
|
+
timeoutMs?: number | undefined;
|
|
249
|
+
}>>;
|
|
250
|
+
}, "strict", z.ZodTypeAny, {
|
|
251
|
+
maxDepth: number;
|
|
252
|
+
defaultBudget: {
|
|
253
|
+
maxTokens: number;
|
|
254
|
+
maxIterations: number;
|
|
255
|
+
timeoutMs?: number | undefined;
|
|
256
|
+
};
|
|
257
|
+
}, {
|
|
258
|
+
maxDepth?: number | undefined;
|
|
259
|
+
defaultBudget?: {
|
|
260
|
+
maxTokens?: number | undefined;
|
|
261
|
+
maxIterations?: number | undefined;
|
|
262
|
+
timeoutMs?: number | undefined;
|
|
263
|
+
} | undefined;
|
|
264
|
+
}>;
|
|
206
265
|
/** MCP server entry — stdio or URL transport (wired up in a later phase). */
|
|
207
266
|
export declare const McpServerSchema: z.ZodObject<{
|
|
208
267
|
command: z.ZodOptional<z.ZodString>;
|
|
@@ -405,6 +464,56 @@ export declare const CruxyConfigSchema: z.ZodObject<{
|
|
|
405
464
|
overlapLines?: number | undefined;
|
|
406
465
|
} | undefined;
|
|
407
466
|
}>>;
|
|
467
|
+
checkpoint: z.ZodDefault<z.ZodObject<{
|
|
468
|
+
/** Auto-checkpoint before a run's first mutation (and enable `cruxy rollback`). */
|
|
469
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
470
|
+
/** How many checkpoints to keep; older ones are pruned oldest-first. */
|
|
471
|
+
retention: z.ZodDefault<z.ZodNumber>;
|
|
472
|
+
}, "strict", z.ZodTypeAny, {
|
|
473
|
+
enabled: boolean;
|
|
474
|
+
retention: number;
|
|
475
|
+
}, {
|
|
476
|
+
enabled?: boolean | undefined;
|
|
477
|
+
retention?: number | undefined;
|
|
478
|
+
}>>;
|
|
479
|
+
subagent: z.ZodDefault<z.ZodObject<{
|
|
480
|
+
/**
|
|
481
|
+
* Maximum subagent nesting depth. The main agent is depth 0; the default of
|
|
482
|
+
* 1 lets it spawn subagents that cannot themselves spawn (no fork bombs).
|
|
483
|
+
*/
|
|
484
|
+
maxDepth: z.ZodDefault<z.ZodNumber>;
|
|
485
|
+
/** Per-subagent budget ceilings; spawn-time overrides are clamped to these. */
|
|
486
|
+
defaultBudget: z.ZodDefault<z.ZodObject<{
|
|
487
|
+
/** Hard cap on the subagent's model turns. */
|
|
488
|
+
maxIterations: z.ZodDefault<z.ZodNumber>;
|
|
489
|
+
/** Hard cap on the subagent's combined input+output tokens. */
|
|
490
|
+
maxTokens: z.ZodDefault<z.ZodNumber>;
|
|
491
|
+
/** Optional wall-clock cap; unset means no time limit. */
|
|
492
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
493
|
+
}, "strict", z.ZodTypeAny, {
|
|
494
|
+
maxTokens: number;
|
|
495
|
+
maxIterations: number;
|
|
496
|
+
timeoutMs?: number | undefined;
|
|
497
|
+
}, {
|
|
498
|
+
maxTokens?: number | undefined;
|
|
499
|
+
maxIterations?: number | undefined;
|
|
500
|
+
timeoutMs?: number | undefined;
|
|
501
|
+
}>>;
|
|
502
|
+
}, "strict", z.ZodTypeAny, {
|
|
503
|
+
maxDepth: number;
|
|
504
|
+
defaultBudget: {
|
|
505
|
+
maxTokens: number;
|
|
506
|
+
maxIterations: number;
|
|
507
|
+
timeoutMs?: number | undefined;
|
|
508
|
+
};
|
|
509
|
+
}, {
|
|
510
|
+
maxDepth?: number | undefined;
|
|
511
|
+
defaultBudget?: {
|
|
512
|
+
maxTokens?: number | undefined;
|
|
513
|
+
maxIterations?: number | undefined;
|
|
514
|
+
timeoutMs?: number | undefined;
|
|
515
|
+
} | undefined;
|
|
516
|
+
}>>;
|
|
408
517
|
mcpServers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
409
518
|
command: z.ZodOptional<z.ZodString>;
|
|
410
519
|
args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
@@ -471,6 +580,18 @@ export declare const CruxyConfigSchema: z.ZodObject<{
|
|
|
471
580
|
overlapLines: number;
|
|
472
581
|
};
|
|
473
582
|
};
|
|
583
|
+
checkpoint: {
|
|
584
|
+
enabled: boolean;
|
|
585
|
+
retention: number;
|
|
586
|
+
};
|
|
587
|
+
subagent: {
|
|
588
|
+
maxDepth: number;
|
|
589
|
+
defaultBudget: {
|
|
590
|
+
maxTokens: number;
|
|
591
|
+
maxIterations: number;
|
|
592
|
+
timeoutMs?: number | undefined;
|
|
593
|
+
};
|
|
594
|
+
};
|
|
474
595
|
mcpServers: Record<string, {
|
|
475
596
|
command?: string | undefined;
|
|
476
597
|
args?: string[] | undefined;
|
|
@@ -529,6 +650,18 @@ export declare const CruxyConfigSchema: z.ZodObject<{
|
|
|
529
650
|
overlapLines?: number | undefined;
|
|
530
651
|
} | undefined;
|
|
531
652
|
} | undefined;
|
|
653
|
+
checkpoint?: {
|
|
654
|
+
enabled?: boolean | undefined;
|
|
655
|
+
retention?: number | undefined;
|
|
656
|
+
} | undefined;
|
|
657
|
+
subagent?: {
|
|
658
|
+
maxDepth?: number | undefined;
|
|
659
|
+
defaultBudget?: {
|
|
660
|
+
maxTokens?: number | undefined;
|
|
661
|
+
maxIterations?: number | undefined;
|
|
662
|
+
timeoutMs?: number | undefined;
|
|
663
|
+
} | undefined;
|
|
664
|
+
} | undefined;
|
|
532
665
|
mcpServers?: Record<string, {
|
|
533
666
|
command?: string | undefined;
|
|
534
667
|
args?: string[] | undefined;
|
package/dist/config/schema.js
CHANGED
|
@@ -137,6 +137,44 @@ export const IndexConfigSchema = z
|
|
|
137
137
|
.default({}),
|
|
138
138
|
})
|
|
139
139
|
.strict();
|
|
140
|
+
/**
|
|
141
|
+
* Working-tree checkpoints (C.32): a snapshot taken before an agent run's first
|
|
142
|
+
* file mutation, so `cruxy rollback` can undo the whole run atomically.
|
|
143
|
+
*/
|
|
144
|
+
export const CheckpointConfigSchema = z
|
|
145
|
+
.object({
|
|
146
|
+
/** Auto-checkpoint before a run's first mutation (and enable `cruxy rollback`). */
|
|
147
|
+
enabled: z.boolean().default(true),
|
|
148
|
+
/** How many checkpoints to keep; older ones are pruned oldest-first. */
|
|
149
|
+
retention: z.number().int().positive().default(10),
|
|
150
|
+
})
|
|
151
|
+
.strict();
|
|
152
|
+
/**
|
|
153
|
+
* Subagent orchestration (C.14): scoped child agents the main agent can spawn
|
|
154
|
+
* for bounded subtasks. Every cap here is a hard bound — a subagent can narrow
|
|
155
|
+
* its budget at spawn time but never exceed these ceilings.
|
|
156
|
+
*/
|
|
157
|
+
export const SubagentConfigSchema = z
|
|
158
|
+
.object({
|
|
159
|
+
/**
|
|
160
|
+
* Maximum subagent nesting depth. The main agent is depth 0; the default of
|
|
161
|
+
* 1 lets it spawn subagents that cannot themselves spawn (no fork bombs).
|
|
162
|
+
*/
|
|
163
|
+
maxDepth: z.number().int().nonnegative().default(1),
|
|
164
|
+
/** Per-subagent budget ceilings; spawn-time overrides are clamped to these. */
|
|
165
|
+
defaultBudget: z
|
|
166
|
+
.object({
|
|
167
|
+
/** Hard cap on the subagent's model turns. */
|
|
168
|
+
maxIterations: z.number().int().positive().default(10),
|
|
169
|
+
/** Hard cap on the subagent's combined input+output tokens. */
|
|
170
|
+
maxTokens: z.number().int().positive().default(32000),
|
|
171
|
+
/** Optional wall-clock cap; unset means no time limit. */
|
|
172
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
173
|
+
})
|
|
174
|
+
.strict()
|
|
175
|
+
.default({}),
|
|
176
|
+
})
|
|
177
|
+
.strict();
|
|
140
178
|
/** MCP server entry — stdio or URL transport (wired up in a later phase). */
|
|
141
179
|
export const McpServerSchema = z
|
|
142
180
|
.object({
|
|
@@ -156,6 +194,8 @@ export const CruxyConfigSchema = z
|
|
|
156
194
|
context: ContextConfigSchema.default({}),
|
|
157
195
|
approval: ApprovalConfigSchema.default({}),
|
|
158
196
|
index: IndexConfigSchema.default({}),
|
|
197
|
+
checkpoint: CheckpointConfigSchema.default({}),
|
|
198
|
+
subagent: SubagentConfigSchema.default({}),
|
|
159
199
|
mcpServers: z.record(z.string(), McpServerSchema).default({}),
|
|
160
200
|
logLevel: z.enum(LOG_LEVELS).default("info"),
|
|
161
201
|
})
|
|
@@ -2,6 +2,13 @@ import { CruxyError } from "./types.js";
|
|
|
2
2
|
/** Best-effort human message for an arbitrary thrown value. */
|
|
3
3
|
export declare function messageOf(underlying: unknown): string | undefined;
|
|
4
4
|
export declare function usageError(title: string, nextSteps?: string[]): CruxyError;
|
|
5
|
+
/**
|
|
6
|
+
* An interactive component (picker, fuzzy finder) was needed but stdin is not
|
|
7
|
+
* an interactive terminal and the caller supplied no default (U.7). Never
|
|
8
|
+
* silently picks an option, never blocks on a pipe — same discipline as the
|
|
9
|
+
* approval/onboarding layers.
|
|
10
|
+
*/
|
|
11
|
+
export declare function interactiveRequired(what: string, alternatives?: string[]): CruxyError;
|
|
5
12
|
export declare function configKeyUnknown(key: string): CruxyError;
|
|
6
13
|
export declare function providerUnsupported(provider: string): CruxyError;
|
|
7
14
|
export declare function configParse(path: string, underlying?: unknown): CruxyError;
|
|
@@ -53,6 +60,31 @@ export declare function planRevisionLimit(limit: number): CruxyError;
|
|
|
53
60
|
* tell it apart from a per-action approval requirement.
|
|
54
61
|
*/
|
|
55
62
|
export declare function planApprovalRequired(): CruxyError;
|
|
63
|
+
/**
|
|
64
|
+
* Creating, reading, or restoring a working-tree checkpoint failed (C.32).
|
|
65
|
+
* Fail-loud by design: an agent run never mutates files without its undo
|
|
66
|
+
* protection unless the user explicitly disables it.
|
|
67
|
+
*/
|
|
68
|
+
export declare function checkpointFailed(reason: string, underlying?: unknown): CruxyError;
|
|
69
|
+
/** The requested checkpoint id doesn't exist (or no checkpoints exist at all). */
|
|
70
|
+
export declare function checkpointNotFound(id?: string): CruxyError;
|
|
71
|
+
/**
|
|
72
|
+
* Rollback needs interactive approval but cruxy is running non-interactively.
|
|
73
|
+
* Restoring is destructive and deliberate — there is no auto-rollback path, ever.
|
|
74
|
+
*/
|
|
75
|
+
export declare function rollbackApprovalRequired(): CruxyError;
|
|
76
|
+
/**
|
|
77
|
+
* A subagent spawn was attempted past the configured nesting cap (C.14). The
|
|
78
|
+
* spawn tool is structurally withheld at the cap, so reaching this means the
|
|
79
|
+
* orchestrator seam was driven directly — fail loud, never spawn.
|
|
80
|
+
*/
|
|
81
|
+
export declare function subagentDepthExceeded(depth: number, maxDepth: number): CruxyError;
|
|
82
|
+
/**
|
|
83
|
+
* A subagent run failed outright (provider error, tool crash) before producing
|
|
84
|
+
* a result. Normally folded into the structured `SubagentResult` the parent
|
|
85
|
+
* reasons over; thrown only when the orchestrator itself cannot proceed.
|
|
86
|
+
*/
|
|
87
|
+
export declare function subagentFailed(underlying?: unknown): CruxyError;
|
|
56
88
|
export declare function internal(underlying?: unknown): CruxyError;
|
|
57
89
|
/**
|
|
58
90
|
* Map a known provider/transport error (from `@cruxy/sdk`) to a typed
|