@cruxy/cli 1.2.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/dist/agent/context.js +178 -0
- package/dist/agent/index.js +1 -0
- package/dist/agent/loop.js +41 -2
- package/dist/agent/mode.js +103 -0
- package/dist/agent/prompts.js +1 -1
- package/dist/agent/session.js +185 -72
- package/dist/approval/classify.js +204 -0
- package/dist/approval/policy.js +41 -3
- package/dist/approval/prompt.js +49 -22
- package/dist/checkpoint/gate.js +12 -0
- package/dist/cli/commands/run.js +374 -227
- package/dist/cli/commands/usage.js +45 -45
- package/dist/cli/onboard.js +2 -1
- package/dist/cli/program.js +60 -18
- package/dist/cli/repl.js +67 -249
- package/dist/cli/session-commands.js +755 -0
- package/dist/cli/session-factory.js +198 -76
- package/dist/cli/suggest.js +77 -0
- package/dist/components/fuzzy.js +3 -3
- package/dist/components/input.js +17 -2
- package/dist/components/keys.js +27 -3
- package/dist/components/select.js +3 -3
- package/dist/config/project.js +53 -1
- package/dist/config/schema.js +49 -16
- package/dist/jobs/log-renderer.js +47 -0
- package/dist/onboarding/steps.js +13 -22
- package/dist/plan/approve.js +36 -24
- package/dist/plan/execute.js +9 -7
- package/dist/plan/render.js +10 -23
- package/dist/plan/service.js +4 -1
- package/dist/render/capabilities.js +30 -1
- package/dist/render/context-view.js +106 -0
- package/dist/render/diff.js +198 -12
- package/dist/render/index.js +31 -5
- package/dist/render/plain-renderer.js +38 -2
- package/dist/render/plan-view.js +108 -0
- package/dist/render/resize.js +7 -2
- package/dist/render/status-view.js +66 -0
- package/dist/render/test-view.js +89 -0
- package/dist/render/tty-renderer.js +40 -0
- package/dist/routing/index.js +1 -0
- package/dist/routing/router.js +13 -4
- package/dist/routing/session-model.js +109 -0
- package/dist/routing/types.js +14 -0
- package/dist/session/export.js +88 -0
- package/dist/session/index.js +20 -0
- package/dist/session/list.js +137 -0
- package/dist/session/log.js +137 -0
- package/dist/session/paths.js +73 -0
- package/dist/session/replay.js +169 -0
- package/dist/session/resume.js +128 -0
- package/dist/session/types.js +223 -0
- package/dist/subagent/orchestrator.js +23 -0
- package/dist/testing/run-tests-tool.js +8 -0
- package/dist/tools/registry.js +3 -3
- package/dist/tui/app.js +385 -0
- package/dist/tui/approval-overlay.js +160 -0
- package/dist/tui/context-gauge.js +48 -0
- package/dist/tui/git-status.js +63 -0
- package/dist/tui/index.js +10 -0
- package/dist/tui/layout.js +269 -0
- package/dist/tui/overlay.js +105 -0
- package/dist/tui/palette.js +73 -0
- package/dist/tui/panels.js +235 -0
- package/dist/tui/renderer.js +776 -0
- package/dist/tui/supports.js +20 -0
- package/dist/tui/tool-versions.js +129 -0
- package/dist/usage/collect.js +21 -3
- package/dist/usage/index.js +10 -2
- package/dist/usage/report.js +76 -0
- package/dist/usage/store.js +7 -1
- package/dist/usage/summary.js +106 -17
- package/dist/usage/types.js +73 -4
- package/dist/usage/weighted.js +77 -0
- package/dist/utils/git.js +50 -4
- package/package.json +2 -2
- package/dist/usage/cost.js +0 -29
package/dist/approval/policy.js
CHANGED
|
@@ -75,20 +75,58 @@ export function scopeCovers(scope, request) {
|
|
|
75
75
|
export class InteractivePolicy {
|
|
76
76
|
allowlist;
|
|
77
77
|
io;
|
|
78
|
-
|
|
78
|
+
autoApprove;
|
|
79
|
+
constructor(allowlist, io,
|
|
80
|
+
/**
|
|
81
|
+
* Whether the session is in an auto-approving mode (P5 track 3).
|
|
82
|
+
*
|
|
83
|
+
* A THUNK, not a boolean: the mode is runtime state the user can change at
|
|
84
|
+
* any point, including between two actions of the same turn. Reading it
|
|
85
|
+
* once at construction would let a session that has been switched back to
|
|
86
|
+
* `manual` keep auto-approving for the rest of its life — a stale copy of
|
|
87
|
+
* exactly the fact this must never be wrong about.
|
|
88
|
+
*
|
|
89
|
+
* Omitted → never auto-approves, which is every non-interactive caller.
|
|
90
|
+
*/
|
|
91
|
+
autoApprove = () => false) {
|
|
79
92
|
this.allowlist = allowlist;
|
|
80
93
|
this.io = io;
|
|
94
|
+
this.autoApprove = autoApprove;
|
|
81
95
|
}
|
|
82
96
|
async decide(request) {
|
|
97
|
+
// A grant the allowlist answers shows the user nothing (P3) — left
|
|
98
|
+
// unflagged so a caller can render the change instead.
|
|
83
99
|
if (this.allowlist.allows(request))
|
|
84
100
|
return { allow: true };
|
|
101
|
+
// Auto-approve: allowed without a prompt, and deliberately NOT flagged as
|
|
102
|
+
// `prompted` — so `previewSilentApprovals` renders the diff. An unattended
|
|
103
|
+
// mode that showed nothing would let a session's whole body of work land
|
|
104
|
+
// invisibly, which is a different failure from the one it is meant to save
|
|
105
|
+
// the user: skipping the QUESTION is the point, skipping the RECORD is not.
|
|
106
|
+
//
|
|
107
|
+
// THE CEILING (P5 track 3). Auto-approve suppresses the prompt for
|
|
108
|
+
// reversible actions ONLY. An action the run's checkpoint cannot restore —
|
|
109
|
+
// a delete outside the workspace, a force-push, a history rewrite, an MCP
|
|
110
|
+
// call, a rollback, anything unprovable — falls through to the prompt even
|
|
111
|
+
// here, and even unattended.
|
|
112
|
+
//
|
|
113
|
+
// This is the line the product position rests on. "Only change what was
|
|
114
|
+
// asked, no silent deletions" is not a claim a mode may switch off:
|
|
115
|
+
// suppressing prompts is a convenience, and suppressing the last gate before
|
|
116
|
+
// irreversible damage is a different thing wearing the same word. Note the
|
|
117
|
+
// gate is `request.irreversible`, NOT `request.tier` — the tier is an
|
|
118
|
+
// unprovability signal (every shell command is `destructive`, `ls`
|
|
119
|
+
// included), so gating on it would make the mode either useless or
|
|
120
|
+
// dishonest. See `ApprovalRequest.irreversible`.
|
|
121
|
+
if (this.autoApprove() && !request.irreversible)
|
|
122
|
+
return { allow: true };
|
|
85
123
|
const choice = await promptForApproval(request, this.io);
|
|
86
124
|
switch (choice.kind) {
|
|
87
125
|
case "once":
|
|
88
|
-
return { allow: true };
|
|
126
|
+
return { allow: true, prompted: true };
|
|
89
127
|
case "session":
|
|
90
128
|
this.allowlist.grant(request);
|
|
91
|
-
return { allow: true };
|
|
129
|
+
return { allow: true, prompted: true };
|
|
92
130
|
case "reject":
|
|
93
131
|
return choice.reason
|
|
94
132
|
? {
|
package/dist/approval/prompt.js
CHANGED
|
@@ -10,29 +10,39 @@ import { themeForColor } from "../theme/index.js";
|
|
|
10
10
|
* is a reject.
|
|
11
11
|
*/
|
|
12
12
|
export async function promptForApproval(request, io) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
? { kind: "
|
|
31
|
-
|
|
13
|
+
// The terminal is about to belong to this prompt: yield the live region
|
|
14
|
+
// BEFORE the question lands, or it would be painted into a region the next
|
|
15
|
+
// repaint erases (U.2/U.4). `finally` gives it back on every path, including
|
|
16
|
+
// the default-deny ones.
|
|
17
|
+
io.beginPrompt?.();
|
|
18
|
+
try {
|
|
19
|
+
io.write(render(request, io.color, io.columns ?? resolveColumns()));
|
|
20
|
+
const key = (await io.readKey()).toLowerCase();
|
|
21
|
+
io.write("\n");
|
|
22
|
+
switch (key) {
|
|
23
|
+
case "y":
|
|
24
|
+
return { kind: "once" };
|
|
25
|
+
case "a":
|
|
26
|
+
return { kind: "session" };
|
|
27
|
+
case "n": {
|
|
28
|
+
io.write(" reason (optional, sent to the agent): ");
|
|
29
|
+
const reason = (await io.readLine()).trim();
|
|
30
|
+
return reason ? { kind: "reject", reason } : { kind: "reject" };
|
|
31
|
+
}
|
|
32
|
+
case "t": {
|
|
33
|
+
io.write(" what should the agent do instead? ");
|
|
34
|
+
const instruction = (await io.readLine()).trim();
|
|
35
|
+
return instruction
|
|
36
|
+
? { kind: "instruct", instruction }
|
|
37
|
+
: { kind: "reject" };
|
|
38
|
+
}
|
|
39
|
+
default:
|
|
40
|
+
// n/a key, empty, EOF, Ctrl-C → default-deny.
|
|
41
|
+
return { kind: "reject" };
|
|
32
42
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
io.endPrompt?.();
|
|
36
46
|
}
|
|
37
47
|
}
|
|
38
48
|
/** Render the full prompt block: header, detail (diff or command+cwd), choices. */
|
|
@@ -48,10 +58,27 @@ export function render(request, color, columns = resolveColumns()) {
|
|
|
48
58
|
const label = tierLabel(request.tier, t);
|
|
49
59
|
const lines = [];
|
|
50
60
|
lines.push(...header(request.summary, mark, label, t, columns));
|
|
61
|
+
lines.push(...whyAsking(request.irreversible, t, columns));
|
|
51
62
|
lines.push(detail(request, t, columns));
|
|
52
63
|
lines.push(choices(request.scope, t));
|
|
53
64
|
return lines.filter((l) => l !== "").join("\n") + " ";
|
|
54
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* The one-clause reason the checkpoint cannot restore this action, when there is
|
|
68
|
+
* one. Rendered for every mode, not just the auto ones — the prompt has no way
|
|
69
|
+
* to know the session's mode, and a user in `manual` benefits from the same
|
|
70
|
+
* sentence.
|
|
71
|
+
*
|
|
72
|
+
* It earns its line in auto-approve, where an unexplained prompt is actively
|
|
73
|
+
* misleading: the user asked not to be asked, so the only useful thing to say is
|
|
74
|
+
* why this one is different. Reflowed, never truncated — a half-sentence about
|
|
75
|
+
* irreversibility is worse than none.
|
|
76
|
+
*/
|
|
77
|
+
function whyAsking(irreversible, t, width) {
|
|
78
|
+
if (!irreversible)
|
|
79
|
+
return [];
|
|
80
|
+
return reflow(`no undo: ${irreversible}`, Math.max(1, width - 2)).map((l) => t.muted(` ${l}`));
|
|
81
|
+
}
|
|
55
82
|
/**
|
|
56
83
|
* The header, width-aware (U.12). Wide: the one inline line `! cruxy wants to
|
|
57
84
|
* <summary> (destructive)`. Narrow: the risk (mark + tier label) stands on its
|
package/dist/checkpoint/gate.js
CHANGED
|
@@ -81,6 +81,18 @@ export class CheckpointGate {
|
|
|
81
81
|
});
|
|
82
82
|
await writeSet(this.primaryRoot, this.set);
|
|
83
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* The current run's id, or undefined before the first {@link beginRun}.
|
|
86
|
+
*
|
|
87
|
+
* This is the ONE per-turn id in the CLI, and the session log (P2) borrows it
|
|
88
|
+
* rather than minting a second: `cruxy rollback <id>` reverts a turn's
|
|
89
|
+
* mutations and the session log records that turn's messages, so if the two
|
|
90
|
+
* used different ids the undo unit would mean two different things depending
|
|
91
|
+
* on which surface you asked. Exposed read-only — only `beginRun` sets it.
|
|
92
|
+
*/
|
|
93
|
+
currentRunId() {
|
|
94
|
+
return this.runId ?? undefined;
|
|
95
|
+
}
|
|
84
96
|
/** Root names that got a per-root service this process (inspection/tests). */
|
|
85
97
|
get touchedRoots() {
|
|
86
98
|
return [...this.services.keys()];
|