@rulvar/cli 1.24.1 → 1.25.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/cli.js +4 -2
- package/dist/index.d.ts +7 -1
- package/dist/index.js +1 -1
- package/dist/{io-CV_G44A2.js → io-CAXfPWq7.js} +44 -25
- package/package.json +7 -7
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { r as runCli, t as processIo } from "./io-
|
|
2
|
+
import { r as runCli, t as processIo } from "./io-CAXfPWq7.js";
|
|
3
|
+
import { sanitizeTerminalText } from "@rulvar/core";
|
|
3
4
|
import { inspect } from "node:util";
|
|
4
5
|
//#region src/cli.ts
|
|
5
6
|
/**
|
|
@@ -11,7 +12,8 @@ runCli(process.argv.slice(2), {
|
|
|
11
12
|
}).then((code) => {
|
|
12
13
|
process.exitCode = code;
|
|
13
14
|
}, (thrown) => {
|
|
14
|
-
|
|
15
|
+
const rendered = inspect(thrown).split("\n").map(sanitizeTerminalText).join("\n");
|
|
16
|
+
process.stderr.write(`${rendered}\n`);
|
|
15
17
|
process.exitCode = 1;
|
|
16
18
|
});
|
|
17
19
|
//#endregion
|
package/dist/index.d.ts
CHANGED
|
@@ -142,7 +142,13 @@ declare function driveRun(options: {
|
|
|
142
142
|
io: CliIo; /** Original run arguments: not journaled in v1, the host re-supplies them. */
|
|
143
143
|
args?: unknown;
|
|
144
144
|
}): Promise<RunOutcome<unknown>>;
|
|
145
|
-
/**
|
|
145
|
+
/**
|
|
146
|
+
* Renders the settled outcome; returns the process exit code. Error
|
|
147
|
+
* messages, suspension keys, model refs, and phase names originate from
|
|
148
|
+
* providers, tools, and workflow authors, so each is sanitized before
|
|
149
|
+
* it reaches a terminal line, matching the TUI renderer (v1.24.1 review
|
|
150
|
+
* P2-1). Values print as JSON, which escapes control bytes on its own.
|
|
151
|
+
*/
|
|
146
152
|
declare function reportOutcome(outcome: RunOutcome<unknown>, io: CliIo): number;
|
|
147
153
|
//#endregion
|
|
148
154
|
//#region src/server.d.ts
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as resumeCommand, c as driveRun, d as renderEventLine, f as DEFAULT_STORE_DIR, g as looksLikeFile, h as loadWorkflowModule, i as inspectCommand, l as reportOutcome, m as loadCliConfig, n as HELP, o as runCommand, p as assembleEngine, r as runCli, s as runsLsCommand, t as processIo, u as attachProgress } from "./io-
|
|
1
|
+
import { a as resumeCommand, c as driveRun, d as renderEventLine, f as DEFAULT_STORE_DIR, g as looksLikeFile, h as loadWorkflowModule, i as inspectCommand, l as reportOutcome, m as loadCliConfig, n as HELP, o as runCommand, p as assembleEngine, r as runCli, s as runsLsCommand, t as processIo, u as attachProgress } from "./io-CAXfPWq7.js";
|
|
2
2
|
import { ConfigError, InvalidResolutionError, JournalCompatibilityError, LeaseHeldError, Replayer, RulvarError, buildDeriverRegistry, costReportFromJournal, maskSecrets, normalizeEntry, scanJournalCompatibility, validateSchemaSpec } from "@rulvar/core";
|
|
3
3
|
//#region src/server.ts
|
|
4
4
|
/**
|
|
@@ -234,6 +234,13 @@ function attachProgress(handle, io) {
|
|
|
234
234
|
}
|
|
235
235
|
//#endregion
|
|
236
236
|
//#region src/drive.ts
|
|
237
|
+
/**
|
|
238
|
+
* The run/suspend/resolve/resume loop shared by `rulvar run` and
|
|
239
|
+
* `rulvar resume` (the CLI performs interactive resolution of suspended
|
|
240
|
+
* approvals and external inputs). Prompts read
|
|
241
|
+
* one line per pending suspension; EOF leaves the run suspended with a
|
|
242
|
+
* notice, never an error.
|
|
243
|
+
*/
|
|
237
244
|
const APPROVAL_PREFIX = "approval:";
|
|
238
245
|
/** Parses an approval answer; undefined = unusable input. */
|
|
239
246
|
function approvalDecision(answer) {
|
|
@@ -259,31 +266,32 @@ function approvalDecision(answer) {
|
|
|
259
266
|
async function resolvePending(handle, pending, io) {
|
|
260
267
|
let applied = 0;
|
|
261
268
|
for (const item of pending) {
|
|
269
|
+
const keyRef = sanitizeTerminalText(item.key);
|
|
262
270
|
if (item.key.startsWith(APPROVAL_PREFIX)) {
|
|
263
|
-
const answer = await io.prompt(`approve '${item.prompt ?? item.key}'? [allow/deny]`);
|
|
271
|
+
const answer = await io.prompt(`approve '${sanitizeTerminalText(item.prompt ?? item.key)}'? [allow/deny]`);
|
|
264
272
|
if (answer === void 0) return applied;
|
|
265
273
|
const decision = approvalDecision(answer);
|
|
266
274
|
if (decision === void 0) {
|
|
267
|
-
io.err(`unrecognized answer '${answer.trim()}'; leaving ${
|
|
275
|
+
io.err(`unrecognized answer '${sanitizeTerminalText(answer.trim())}'; leaving ${keyRef} suspended`);
|
|
268
276
|
continue;
|
|
269
277
|
}
|
|
270
278
|
const outcome = await handle.resolveExternal(item.key, decision);
|
|
271
|
-
io.err(`approval ${
|
|
279
|
+
io.err(`approval ${keyRef}: ${decision.decision} (${outcome.applied ? "applied" : sanitizeTerminalText(outcome.reason)})`);
|
|
272
280
|
if (outcome.applied) applied += 1;
|
|
273
281
|
continue;
|
|
274
282
|
}
|
|
275
|
-
const label = item.prompt === void 0 ?
|
|
283
|
+
const label = item.prompt === void 0 ? keyRef : `${keyRef} (${sanitizeTerminalText(item.prompt)})`;
|
|
276
284
|
const answer = await io.prompt(`value for external '${label}' as JSON:`);
|
|
277
285
|
if (answer === void 0) return applied;
|
|
278
286
|
let value;
|
|
279
287
|
try {
|
|
280
288
|
value = JSON.parse(answer);
|
|
281
289
|
} catch {
|
|
282
|
-
io.err(`not valid JSON; leaving '${
|
|
290
|
+
io.err(`not valid JSON; leaving '${keyRef}' suspended`);
|
|
283
291
|
continue;
|
|
284
292
|
}
|
|
285
293
|
const outcome = await handle.resolveExternal(item.key, value);
|
|
286
|
-
io.err(`external '${
|
|
294
|
+
io.err(`external '${keyRef}': ${outcome.applied ? "applied" : sanitizeTerminalText(outcome.reason)}`);
|
|
287
295
|
if (outcome.applied) applied += 1;
|
|
288
296
|
}
|
|
289
297
|
return applied;
|
|
@@ -320,29 +328,35 @@ async function reportDryRun(handle, io) {
|
|
|
320
328
|
io.err(` hits: ${preview.hits} misses: ${preview.misses} reruns: ${preview.reruns} skipped: ${preview.skipped}`);
|
|
321
329
|
io.err(preview.orphaned.length === 0 ? " orphaned effect roots: none" : ` orphaned effect roots (entryRefs): ${preview.orphaned.join(", ")}`);
|
|
322
330
|
if (preview.invalidResolutions.length === 0) io.err(" invalid resolutions: none");
|
|
323
|
-
else for (const invalid of preview.invalidResolutions) io.err(` invalid resolution at seq ${invalid.seq}: ${invalid.detail}`);
|
|
331
|
+
else for (const invalid of preview.invalidResolutions) io.err(` invalid resolution at seq ${invalid.seq}: ${sanitizeTerminalText(invalid.detail)}`);
|
|
324
332
|
if (outcome.error?.code === "journal_miss") {
|
|
325
|
-
io.err(` stopped at the first would-be-live call: ${outcome.error.message}`);
|
|
333
|
+
io.err(` stopped at the first would-be-live call: ${sanitizeTerminalText(outcome.error.message)}`);
|
|
326
334
|
io.err(" a real resume would perform new paid work from this point");
|
|
327
335
|
return 0;
|
|
328
336
|
}
|
|
329
337
|
io.err(` would settle: ${outcome.status}`);
|
|
330
|
-
if (outcome.error !== void 0) io.err(` error: ${outcome.error.message}`);
|
|
331
|
-
for (const pending of outcome.pending) io.err(` pending: ${pending.key} (entry ${pending.entryRef})`);
|
|
338
|
+
if (outcome.error !== void 0) io.err(` error: ${sanitizeTerminalText(outcome.error.message)}`);
|
|
339
|
+
for (const pending of outcome.pending) io.err(` pending: ${sanitizeTerminalText(pending.key)} (entry ${pending.entryRef})`);
|
|
332
340
|
if (outcome.value !== void 0) io.out(JSON.stringify(outcome.value, null, 2));
|
|
333
341
|
return 0;
|
|
334
342
|
}
|
|
335
|
-
/**
|
|
343
|
+
/**
|
|
344
|
+
* Renders the settled outcome; returns the process exit code. Error
|
|
345
|
+
* messages, suspension keys, model refs, and phase names originate from
|
|
346
|
+
* providers, tools, and workflow authors, so each is sanitized before
|
|
347
|
+
* it reaches a terminal line, matching the TUI renderer (v1.24.1 review
|
|
348
|
+
* P2-1). Values print as JSON, which escapes control bytes on its own.
|
|
349
|
+
*/
|
|
336
350
|
function reportOutcome(outcome, io) {
|
|
337
351
|
io.err(`status: ${outcome.status}`);
|
|
338
352
|
if (outcome.value !== void 0) io.out(JSON.stringify(outcome.value, null, 2));
|
|
339
|
-
if (outcome.error !== void 0) io.err(`error: ${outcome.error.message}`);
|
|
353
|
+
if (outcome.error !== void 0) io.err(`error: ${sanitizeTerminalText(outcome.error.message)}`);
|
|
340
354
|
if (outcome.dropped.length > 0) io.err(`dropped: ${outcome.dropped.length} item(s)`);
|
|
341
|
-
for (const pending of outcome.pending) io.err(`pending: ${pending.key} (entry ${pending.entryRef})`);
|
|
355
|
+
for (const pending of outcome.pending) io.err(`pending: ${sanitizeTerminalText(pending.key)} (entry ${pending.entryRef})`);
|
|
342
356
|
io.err(`cost: $${outcome.cost.totalUsd.toFixed(4)}`);
|
|
343
|
-
for (const [model, usd] of Object.entries(outcome.cost.byModel)) io.err(` by model ${model}: $${usd.toFixed(4)}`);
|
|
344
|
-
for (const [phase, usd] of Object.entries(outcome.cost.byPhase)) if (phase !== "") io.err(` by phase ${phase}: $${usd.toFixed(4)}`);
|
|
345
|
-
if (outcome.cost.unpriced.length > 0) io.err(`unpriced models: ${outcome.cost.unpriced.map((u) => u.model).join(", ")}`);
|
|
357
|
+
for (const [model, usd] of Object.entries(outcome.cost.byModel)) io.err(` by model ${sanitizeTerminalText(model)}: $${usd.toFixed(4)}`);
|
|
358
|
+
for (const [phase, usd] of Object.entries(outcome.cost.byPhase)) if (phase !== "") io.err(` by phase ${sanitizeTerminalText(phase)}: $${usd.toFixed(4)}`);
|
|
359
|
+
if (outcome.cost.unpriced.length > 0) io.err(`unpriced models: ${outcome.cost.unpriced.map((u) => sanitizeTerminalText(u.model)).join(", ")}`);
|
|
346
360
|
switch (outcome.status) {
|
|
347
361
|
case "ok":
|
|
348
362
|
case "suspended": return 0;
|
|
@@ -655,6 +669,10 @@ async function loadCompanion(loading, specifier, command, missingMessage) {
|
|
|
655
669
|
* later. In-process hosts keep the wider engine contract (functions,
|
|
656
670
|
* BigInt, cycles record presence without a hash); the CLI does not need
|
|
657
671
|
* it.
|
|
672
|
+
*
|
|
673
|
+
* Diagnostics name the failure class and the way out but never echo the
|
|
674
|
+
* value: workflow args may carry private data, and stderr routinely
|
|
675
|
+
* lands in CI logs (v1.24.1 review P2-1).
|
|
658
676
|
*/
|
|
659
677
|
function parseArgsJson(raw) {
|
|
660
678
|
if (raw === void 0) return;
|
|
@@ -662,12 +680,12 @@ function parseArgsJson(raw) {
|
|
|
662
680
|
try {
|
|
663
681
|
parsed = JSON.parse(raw);
|
|
664
682
|
} catch {
|
|
665
|
-
throw new ConfigError(
|
|
683
|
+
throw new ConfigError("--args is not valid JSON; check the JSON syntax and shell quoting (the value is withheld from diagnostics: workflow args may carry private data)");
|
|
666
684
|
}
|
|
667
685
|
try {
|
|
668
686
|
hashRunArgs(parsed);
|
|
669
687
|
} catch {
|
|
670
|
-
throw new ConfigError(
|
|
688
|
+
throw new ConfigError("--args is not representable as canonical JSON: a numeric value overflows JavaScript's finite range (e.g. 1e400 parses to Infinity). Supply finite JSON so the run's args binding can be hashed and later verified on resume (the value is withheld from diagnostics: workflow args may carry private data)");
|
|
671
689
|
}
|
|
672
690
|
return parsed;
|
|
673
691
|
}
|
|
@@ -711,20 +729,21 @@ async function runCommand(argv, context) {
|
|
|
711
729
|
*/
|
|
712
730
|
function enforceArgsBinding(input) {
|
|
713
731
|
const { meta, argsGiven, args, allowChange, io } = input;
|
|
732
|
+
const runRef = sanitizeTerminalText(meta.runId);
|
|
714
733
|
if (meta.argsProvided === void 0) {
|
|
715
734
|
if (!argsGiven && !allowChange) throw new ConfigError(`run '${meta.runId}' predates the args binding (rulvar < 1.24.0), so the CLI cannot tell whether it was started with --args, and resuming without them silently changes the logical run if any were used at start. Re-supply the original --args, or acknowledge explicitly with --allow-args-change; ${usageOf(GRAMMAR.resume)}`);
|
|
716
|
-
if (argsGiven) io.err(`warning: run '${
|
|
735
|
+
if (argsGiven) io.err(`warning: run '${runRef}' predates the args binding; the supplied --args cannot be verified against the original invocation`);
|
|
717
736
|
return;
|
|
718
737
|
}
|
|
719
738
|
if (meta.argsProvided) {
|
|
720
739
|
if (!argsGiven) {
|
|
721
740
|
if (!allowChange) throw new ConfigError(`run '${meta.runId}' was started WITH args, but this resume supplies none; the workflow would see undefined and every args-dependent call would become new paid work instead of a replay. Re-supply the original --args, or force the change with --allow-args-change; ${usageOf(GRAMMAR.resume)}`);
|
|
722
|
-
io.err(`warning: resuming '${
|
|
741
|
+
io.err(`warning: resuming '${runRef}' without its genesis args (--allow-args-change)`);
|
|
723
742
|
return;
|
|
724
743
|
}
|
|
725
744
|
if (meta.argsHash === void 0) {
|
|
726
745
|
if (!allowChange) throw new ConfigError(`run '${meta.runId}' started WITH args but recorded no verifiable hash (the genesis args were not JCS-serializable), so the CLI cannot confirm the supplied --args match the original; resuming risks silently changing the logical run and re-paying every args-dependent call. Force deliberately with --allow-args-change; ${usageOf(GRAMMAR.resume)}`);
|
|
727
|
-
io.err(`warning: run '${
|
|
746
|
+
io.err(`warning: run '${runRef}' recorded args presence but no hash (genesis args not JCS-serializable); the supplied --args cannot be verified (--allow-args-change)`);
|
|
728
747
|
return;
|
|
729
748
|
}
|
|
730
749
|
let supplied;
|
|
@@ -735,13 +754,13 @@ function enforceArgsBinding(input) {
|
|
|
735
754
|
}
|
|
736
755
|
if (supplied !== meta.argsHash) {
|
|
737
756
|
if (!allowChange) throw new ConfigError(`--args does not match the args run '${meta.runId}' was started with (recorded hash ${meta.argsHash.slice(0, 12)}, supplied ${supplied?.slice(0, 12) ?? "none"}); changed args silently change the logical run and re-pay every args-dependent call. Force deliberately with --allow-args-change; ${usageOf(GRAMMAR.resume)}`);
|
|
738
|
-
io.err(`warning: resuming '${
|
|
757
|
+
io.err(`warning: resuming '${runRef}' with changed args (--allow-args-change)`);
|
|
739
758
|
}
|
|
740
759
|
return;
|
|
741
760
|
}
|
|
742
761
|
if (argsGiven) {
|
|
743
762
|
if (!allowChange) throw new ConfigError(`run '${meta.runId}' was started WITHOUT args, but this resume supplies some; added args silently change the logical run. Drop --args, or force the change with --allow-args-change; ${usageOf(GRAMMAR.resume)}`);
|
|
744
|
-
io.err(`warning: resuming no-args run '${
|
|
763
|
+
io.err(`warning: resuming no-args run '${runRef}' with args (--allow-args-change)`);
|
|
745
764
|
}
|
|
746
765
|
}
|
|
747
766
|
async function resumeCommand(argv, context) {
|
|
@@ -872,7 +891,7 @@ async function planCommand(argv, context) {
|
|
|
872
891
|
});
|
|
873
892
|
const planned = await plannerModule.plan(assembled.engine, goal, planningBudgetUsd === void 0 ? void 0 : { run: { budgetUsd: planningBudgetUsd } });
|
|
874
893
|
context.io.err(`plan: accepted with ${String(planned.lint.length)} advisory diagnostic(s)`);
|
|
875
|
-
for (const diagnostic of planned.lint) context.io.err(` ${diagnostic.ruleId}: ${diagnostic.message}`);
|
|
894
|
+
for (const diagnostic of planned.lint) context.io.err(` ${diagnostic.ruleId}: ${sanitizeTerminalText(diagnostic.message)}`);
|
|
876
895
|
if (dryRun) {
|
|
877
896
|
context.io.out(planned.source);
|
|
878
897
|
return 0;
|
|
@@ -1339,7 +1358,7 @@ async function runCli(argv, options) {
|
|
|
1339
1358
|
}
|
|
1340
1359
|
} catch (thrown) {
|
|
1341
1360
|
if (thrown instanceof ConfigError) {
|
|
1342
|
-
options.io.err(`error: ${thrown.message}`);
|
|
1361
|
+
options.io.err(`error: ${sanitizeTerminalText(thrown.message)}`);
|
|
1343
1362
|
return 1;
|
|
1344
1363
|
}
|
|
1345
1364
|
throw thrown;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.0",
|
|
4
4
|
"description": "Rulvar shell: run/resume/runs/inspect/plan/kb commands, TUI progress, createServer, createWorker, OTel exporter.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,17 +22,17 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@rulvar/core": "1.
|
|
25
|
+
"@rulvar/core": "1.25.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.20.0",
|
|
29
29
|
"tsdown": "^0.22.3",
|
|
30
30
|
"typescript": "~6.0.3",
|
|
31
|
-
"@rulvar/
|
|
32
|
-
"@rulvar/testing": "1.
|
|
33
|
-
"@rulvar/
|
|
34
|
-
"@rulvar/
|
|
35
|
-
"@rulvar/evals": "1.
|
|
31
|
+
"@rulvar/planner": "1.25.0",
|
|
32
|
+
"@rulvar/testing": "1.25.0",
|
|
33
|
+
"@rulvar/plan": "1.25.0",
|
|
34
|
+
"@rulvar/store-sqlite": "1.25.0",
|
|
35
|
+
"@rulvar/evals": "1.25.0"
|
|
36
36
|
},
|
|
37
37
|
"bin": {
|
|
38
38
|
"rulvar": "./dist/cli.js"
|