@rulvar/cli 1.60.0 → 1.62.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 CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { r as runCli, t as processIo } from "./io-DzlCfHd9.js";
2
+ import { r as runCli, t as processIo } from "./io-DQqS6oAt.js";
3
3
  import { sanitizeTerminalText } from "@rulvar/core";
4
4
  import { inspect } from "node:util";
5
5
  //#region src/cli.ts
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CreateEngineOptions, Engine, JournalStore, KeyDeriver, LeasableStore, ModelRef, RunHandle, RunMeta, RunOutcome, Usage, Workflow, WorkflowEvent, WorkflowRegistry } from "@rulvar/core";
1
+ import { CreateEngineOptions, Engine, JournalStore, KeyDeriver, LeasableStore, ModelRef, PreflightInput, RunHandle, RunMeta, RunOutcome, Usage, Workflow, WorkflowEvent, WorkflowRegistry } from "@rulvar/core";
2
2
 
3
3
  //#region src/io.d.ts
4
4
  interface CliIo {
@@ -31,12 +31,49 @@ declare function runCommand(argv: string[], context: CommandContext): Promise<nu
31
31
  declare function resumeCommand(argv: string[], context: CommandContext): Promise<number>;
32
32
  declare function runsLsCommand(argv: string[], context: CommandContext): Promise<number>;
33
33
  declare function inspectCommand(argv: string[], context: CommandContext): Promise<number>;
34
+ /**
35
+ * rulvar invoice (P1.3): the per-dispatch reconciliation export from
36
+ * the journal's providerCalls ledger, one row per billable provider
37
+ * call with the provider's response id when the adapter surfaced one,
38
+ * plus the gross/net ledger totals (`totalUsd` here is the GROSS
39
+ * figure: abandoned subtrees included, exactly what a provider invoice
40
+ * bills). --json prints the machine-readable InvoiceExport; the text
41
+ * form prints one line per row. Pricing folds at read time from the
42
+ * assembled price table, the same numbers rulvar inspect reports.
43
+ */
44
+ declare function invoiceCommand(argv: string[], context: CommandContext): Promise<number>;
45
+ /**
46
+ * rulvar preflight (the experiment-review P2.2; grammar in grammar.ts):
47
+ * the effective-config linter and dry-run estimator. Loads the SAME
48
+ * config, module, and run-profile merge `rulvar run` would assemble,
49
+ * but constructs no engine, opens no store, and dispatches nothing:
50
+ * the report is computed by preflightEstimate over options alone, so
51
+ * the command cannot pay for a single provider token by construction.
52
+ * The declared spawn wave comes from the `preflight` export of the
53
+ * config or workflow module (module wins), and --spawns JSON overrides
54
+ * it from the command line. --json prints the machine-readable report.
55
+ * Exit 1 when any finding has severity 'error' (the linter contract:
56
+ * green preflight means the run can at least start), 0 otherwise.
57
+ */
58
+ declare function preflightCommand(argv: string[], context: CommandContext): Promise<number>;
34
59
  //#endregion
35
60
  //#region src/config.d.ts
61
+ /**
62
+ * The preflight declaration a config or workflow module may export
63
+ * (the experiment-review P2.2): the declared spawn wave, the
64
+ * orchestrator spec, and the quota rule set behind the configured
65
+ * limiter, exactly the PreflightInput slices the estimator cannot
66
+ * derive from engineOptions alone. `rulvar preflight` merges the
67
+ * workflow module's declaration over the config file's, and --spawns
68
+ * overrides the spawn wave from the command line.
69
+ */
70
+ type PreflightDeclaration = Pick<PreflightInput, "spawns" | "orchestrator" | "quotaRules">;
36
71
  /** The shape both the config module and a workflow module may export. */
37
72
  interface CliConfig {
38
73
  engineOptions?: Partial<CreateEngineOptions>;
39
74
  workflows?: WorkflowRegistry;
75
+ /** rulvar preflight declaration (P2.2). */
76
+ preflight?: PreflightDeclaration;
40
77
  /** rulvar kb sweep configuration (M11-T05). */
41
78
  kbSweep?: KbSweepCliConfig;
42
79
  }
@@ -107,6 +144,7 @@ interface LoadedWorkflowModule {
107
144
  workflow?: Workflow<never, unknown>;
108
145
  engineOptions?: Partial<CreateEngineOptions>;
109
146
  workflows?: WorkflowRegistry;
147
+ preflight?: PreflightDeclaration;
110
148
  }
111
149
  /** Imports a workflow module given on the command line. */
112
150
  declare function loadWorkflowModule(file: string, cwd: string): Promise<LoadedWorkflowModule>;
@@ -375,4 +413,4 @@ declare function toOtel(run: {
375
413
  result: Promise<RunOutcome<unknown>>;
376
414
  }, tracer: TracerLike, options?: ToOtelOptions): Promise<number>;
377
415
  //#endregion
378
- export { type AssembledCli, type CliConfig, type CliIo, type CommandContext, type CreateServerOptions, type CreateWorkerOptions, DEFAULT_MAX_PENDING_EVENTS_PER_CLIENT, DEFAULT_STORE_DIR, DEFAULT_WORKER_TTL_MS, HELP, type KbSweepCliConfig, type LoadedWorkflowModule, type OtelContextApi, type RulvarServer, type SpanLike, type ToOtelOptions, type TracerLike, type Worker, assembleEngine, attachProgress, createServer, createWorker, driveRun, inspectCommand, loadCliConfig, loadWorkflowModule, looksLikeFile, processIo, renderEventLine, reportOutcome, resumeCommand, runCli, runCommand, runsLsCommand, strictExitCode, toOtel };
416
+ export { type AssembledCli, type CliConfig, type CliIo, type CommandContext, type CreateServerOptions, type CreateWorkerOptions, DEFAULT_MAX_PENDING_EVENTS_PER_CLIENT, DEFAULT_STORE_DIR, DEFAULT_WORKER_TTL_MS, HELP, type KbSweepCliConfig, type LoadedWorkflowModule, type OtelContextApi, type PreflightDeclaration, type RulvarServer, type SpanLike, type ToOtelOptions, type TracerLike, type Worker, assembleEngine, attachProgress, createServer, createWorker, driveRun, inspectCommand, invoiceCommand, loadCliConfig, loadWorkflowModule, looksLikeFile, preflightCommand, processIo, renderEventLine, reportOutcome, resumeCommand, runCli, runCommand, runsLsCommand, strictExitCode, toOtel };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as looksLikeFile, a as resumeCommand, c as driveRun, d as attachProgress, f as renderEventLine, g as loadWorkflowModule, h as loadCliConfig, i as inspectCommand, l as reportOutcome, m as assembleEngine, n as HELP, o as runCommand, p as DEFAULT_STORE_DIR, r as runCli, s as runsLsCommand, t as processIo, u as strictExitCode } from "./io-DzlCfHd9.js";
1
+ import { _ as loadCliConfig, a as invoiceCommand, c as runCommand, d as reportOutcome, f as strictExitCode, g as assembleEngine, h as DEFAULT_STORE_DIR, i as inspectCommand, l as runsLsCommand, m as renderEventLine, n as HELP, o as preflightCommand, p as attachProgress, r as runCli, s as resumeCommand, t as processIo, u as driveRun, v as loadWorkflowModule, y as looksLikeFile } from "./io-DQqS6oAt.js";
2
2
  import { ConfigError, InvalidResolutionError, JournalCompatibilityError, LeaseHeldError, Replayer, RulvarError, buildDeriverRegistry, compileSecretMasker, costReportFromJournal, maskSecrets, normalizeEntry, readRunMeta, scanJournalCompatibility, validateSchemaSpec } from "@rulvar/core";
3
3
  //#region src/server.ts
4
4
  /**
@@ -961,4 +961,4 @@ async function toOtel(run, tracer, options = {}) {
961
961
  return created;
962
962
  }
963
963
  //#endregion
964
- export { DEFAULT_MAX_PENDING_EVENTS_PER_CLIENT, DEFAULT_STORE_DIR, DEFAULT_WORKER_TTL_MS, HELP, assembleEngine, attachProgress, createServer, createWorker, driveRun, inspectCommand, loadCliConfig, loadWorkflowModule, looksLikeFile, processIo, renderEventLine, reportOutcome, resumeCommand, runCli, runCommand, runsLsCommand, strictExitCode, toOtel };
964
+ export { DEFAULT_MAX_PENDING_EVENTS_PER_CLIENT, DEFAULT_STORE_DIR, DEFAULT_WORKER_TTL_MS, HELP, assembleEngine, attachProgress, createServer, createWorker, driveRun, inspectCommand, invoiceCommand, loadCliConfig, loadWorkflowModule, looksLikeFile, preflightCommand, processIo, renderEventLine, reportOutcome, resumeCommand, runCli, runCommand, runsLsCommand, strictExitCode, toOtel };
@@ -1,4 +1,4 @@
1
- import { ConfigError, FileModelKnowledgeStore, INBOX_PROPOSAL_TTL_DAYS, JsonlFileStore, LeaseHeldError, auditRuns, claimExpired, claimExpiry, compilePermissionPreset, costReportFromJournal, createEngine, hashRunArgs, hashRunOutput, lastRunSettle, parseModelRef, priceUsdOf, proposalStatement, readRunMeta, reconcileRunMeta, remeasureQueue, resolvePricing, runProfile, sanitizeTerminalText } from "@rulvar/core";
1
+ import { ConfigError, FileModelKnowledgeStore, INBOX_PROPOSAL_TTL_DAYS, JsonlFileStore, LeaseHeldError, auditRuns, claimExpired, claimExpiry, compilePermissionPreset, costReportFromJournal, createEngine, hashRunArgs, hashRunOutput, invoiceFromJournal, lastRunSettle, parseModelRef, preflightEstimate, priceUsdOf, proposalStatement, readRunMeta, reconcileRunMeta, remeasureQueue, resolvePricing, runProfile, sanitizeTerminalText } from "@rulvar/core";
2
2
  import { join, resolve } from "node:path";
3
3
  import { existsSync, statSync } from "node:fs";
4
4
  import { pathToFileURL } from "node:url";
@@ -43,7 +43,8 @@ async function loadCliConfig(cwd) {
43
43
  return {
44
44
  ...config ?? {},
45
45
  ...mod.engineOptions === void 0 ? {} : { engineOptions: mod.engineOptions },
46
- ...mod.workflows === void 0 ? {} : { workflows: mod.workflows }
46
+ ...mod.workflows === void 0 ? {} : { workflows: mod.workflows },
47
+ ...mod.preflight === void 0 ? {} : { preflight: mod.preflight }
47
48
  };
48
49
  }
49
50
  return {};
@@ -58,6 +59,7 @@ async function loadWorkflowModule(file, cwd) {
58
59
  if (candidate !== void 0 && isWorkflowValue(candidate)) loaded.workflow = candidate;
59
60
  if (mod.engineOptions !== void 0) loaded.engineOptions = mod.engineOptions;
60
61
  if (mod.workflows !== void 0) loaded.workflows = mod.workflows;
62
+ if (mod.preflight !== void 0) loaded.preflight = mod.preflight;
61
63
  return loaded;
62
64
  }
63
65
  /** True when the `run` target names a file rather than a registry entry. */
@@ -474,6 +476,11 @@ const GRAMMAR = {
474
476
  positionals: ["<runId>"],
475
477
  flags: [STORE]
476
478
  },
479
+ invoice: {
480
+ command: "invoice",
481
+ positionals: ["<runId>"],
482
+ flags: [STORE, { name: "json" }]
483
+ },
477
484
  plan: {
478
485
  command: "plan",
479
486
  positionals: ["\"<goal>\""],
@@ -490,6 +497,25 @@ const GRAMMAR = {
490
497
  { name: "dry-run" }
491
498
  ]
492
499
  },
500
+ preflight: {
501
+ command: "preflight",
502
+ positionals: ["<file|name>"],
503
+ flags: [
504
+ {
505
+ name: "budget-usd",
506
+ placeholder: "N"
507
+ },
508
+ {
509
+ name: "profile",
510
+ placeholder: "NAME"
511
+ },
512
+ {
513
+ name: "spawns",
514
+ placeholder: "JSON"
515
+ },
516
+ { name: "json" }
517
+ ]
518
+ },
493
519
  "kb list": {
494
520
  command: "kb list",
495
521
  positionals: [],
@@ -585,7 +611,9 @@ function helpCommandLines() {
585
611
  GRAMMAR["runs ls"],
586
612
  GRAMMAR["runs audit"],
587
613
  GRAMMAR.inspect,
588
- GRAMMAR.plan
614
+ GRAMMAR.invoice,
615
+ GRAMMAR.plan,
616
+ GRAMMAR.preflight
589
617
  ];
590
618
  const heads = top.map((grammar) => [
591
619
  "rulvar",
@@ -1074,6 +1102,7 @@ async function inspectCommand(argv, context) {
1074
1102
  context.io.out(`open suspensions: ${openSuspensions}`);
1075
1103
  const cost = costReportFromJournal(entries, assembled.priceUsd);
1076
1104
  context.io.out(`cost: $${cost.totalUsd.toFixed(4)}`);
1105
+ if (cost.abandoned.usd > 0) context.io.out(`gross: $${cost.grossUsd.toFixed(4)} (abandoned: $${cost.abandoned.usd.toFixed(4)}; see rulvar invoice)`);
1077
1106
  for (const [model, usd] of Object.entries(cost.byModel)) context.io.out(` ${model}: $${usd.toFixed(4)}`);
1078
1107
  for (const item of cost.unpriced) context.io.out(` unpriced: ${item.model} (${item.usage.inputTokens + item.usage.outputTokens} tok)`);
1079
1108
  for (const entry of entries) {
@@ -1084,6 +1113,142 @@ async function inspectCommand(argv, context) {
1084
1113
  return 0;
1085
1114
  }
1086
1115
  /**
1116
+ * rulvar invoice (P1.3): the per-dispatch reconciliation export from
1117
+ * the journal's providerCalls ledger, one row per billable provider
1118
+ * call with the provider's response id when the adapter surfaced one,
1119
+ * plus the gross/net ledger totals (`totalUsd` here is the GROSS
1120
+ * figure: abandoned subtrees included, exactly what a provider invoice
1121
+ * bills). --json prints the machine-readable InvoiceExport; the text
1122
+ * form prints one line per row. Pricing folds at read time from the
1123
+ * assembled price table, the same numbers rulvar inspect reports.
1124
+ */
1125
+ async function invoiceCommand(argv, context) {
1126
+ const parsed = parseCommand(GRAMMAR.invoice, argv);
1127
+ const runId = parsed.positionals[0];
1128
+ const store = parsed.values.store;
1129
+ const json = parsed.values.json === true;
1130
+ const assembled = assembleEngine({
1131
+ config: await loadCliConfig(context.cwd),
1132
+ ...store === void 0 ? {} : { storePath: store },
1133
+ cwd: context.cwd
1134
+ });
1135
+ const meta = await readRunMeta(assembled.store, runId);
1136
+ if (meta === void 0) throw new ConfigError(`run '${runId}' not found in the store`);
1137
+ const invoice = invoiceFromJournal(await assembled.store.load(runId), assembled.priceUsd);
1138
+ if (json) {
1139
+ context.io.out(JSON.stringify(invoice, null, 2));
1140
+ return 0;
1141
+ }
1142
+ context.io.out(`run ${meta.runId}: invoice (${meta.status})`);
1143
+ context.io.out(`gross: $${invoice.totalUsd.toFixed(4)} | net: $${invoice.netUsd.toFixed(4)} | abandoned: $${invoice.abandonedUsd.toFixed(4)}${invoice.usageApprox === true ? " (approx)" : ""}`);
1144
+ context.io.out(`rows: ${invoice.rows.length} (reconciliation failures: ${invoice.reconciliationFailures})`);
1145
+ for (const row of invoice.rows) {
1146
+ const usd = row.usd === void 0 ? "unpriced" : `$${row.usd.toFixed(4)}`;
1147
+ const tokens = row.usage.inputTokens + row.usage.outputTokens;
1148
+ const id = row.responseId ?? "no-id";
1149
+ const flags = `${row.usageApprox === true ? " approx" : ""}${row.abandoned === true ? " abandoned" : ""}`;
1150
+ context.io.out(`#${row.entrySeq}.${row.ordinal} ${row.servedBy}${row.role === void 0 ? "" : ` ${row.role}`}${row.attempt === void 0 ? "" : ` attempt=${row.attempt}`} ${row.outcome} ${id} ${tokens} tok ${usd}${flags} [${row.reconciliation}]`);
1151
+ }
1152
+ for (const item of invoice.unpriced) context.io.out(`unpriced: ${item.model} (${item.usage.inputTokens + item.usage.outputTokens} tok)`);
1153
+ return 0;
1154
+ }
1155
+ /** Formats an optional USD number for the preflight text rows. */
1156
+ function usdOf(value) {
1157
+ return value === void 0 ? "n/a" : `$${value.toFixed(4)}`;
1158
+ }
1159
+ function renderPreflight(report, io) {
1160
+ io.out("preflight: effective limits and admission projection (zero provider dispatches)");
1161
+ const perProvider = report.concurrency.perProvider;
1162
+ io.out(`concurrency: perRun=${report.concurrency.perRun}` + (perProvider === void 0 ? "" : ` perProvider={${Object.entries(perProvider).map(([id, cap]) => `${id}:${cap}`).join(", ")}}`));
1163
+ const budget = report.budget;
1164
+ io.out(`budget: ceiling=${usdOf(budget.ceilingUsd)} flatReserve=${usdOf(budget.flatReserveUsd)} lifetimeSpawnCap=${budget.lifetimeSpawnCap} childFraction=${budget.childBudgetFraction} maxDepth=${budget.maxDepth}`);
1165
+ if (budget.orchestrator !== void 0) {
1166
+ const orch = budget.orchestrator;
1167
+ io.out(`orchestrator: effectiveCap=${usdOf(orch.effectiveCapUsd)} finalizeReserve=${usdOf(orch.finalizeReserveUsd)} over ${orch.finalizeTurns} turns (${orch.reserveCommitted ? "committed against the run root" : "not committed: no plan extension"})`);
1168
+ }
1169
+ io.out(`quota: ${report.quota.configured ? "configured" : "none"}` + (report.quota.tenant === void 0 ? "" : ` tenant=${report.quota.tenant}`) + (report.quota.rules === void 0 ? "" : ` rules=${report.quota.rules}`));
1170
+ io.out(`run limits: ${JSON.stringify(report.runLimits)}`);
1171
+ for (const spawn of report.spawns) {
1172
+ io.out(`spawn '${spawn.label}' role=${spawn.role} x${spawn.count} servedBy=${spawn.servedBy ?? "UNROUTED"}${spawn.unpriced === true ? " (unpriced)" : ""} reserve=${usdOf(spawn.admissionReserveUsd)} (${spawn.reserveSource})` + (spawn.maxOutputTokensPerTurn === void 0 ? "" : ` maxOutput=${spawn.maxOutputTokensPerTurn}`) + (spawn.turnFloorUsd === void 0 ? "" : ` turnFloor=${usdOf(spawn.turnFloorUsd)}`) + ` toolCallCeiling=${spawn.executedToolCallCeiling ?? "unlimited"}`);
1173
+ io.out(` limits: ${JSON.stringify(spawn.limits)}`);
1174
+ for (const row of spawn.toolCeilings) {
1175
+ if (row.tool === "(any)" && row.ceiling === null) continue;
1176
+ io.out(` tool ${row.tool}: ceiling=${row.ceiling ?? "unlimited"}` + (row.boundBy === void 0 ? "" : ` (${row.boundBy})`));
1177
+ }
1178
+ }
1179
+ const admission = report.admission;
1180
+ if (admission.wave.length > 0) {
1181
+ io.out(`admission: ${admission.admitted} of ${admission.wave.length} admitted` + (admission.ceilingUsd === void 0 ? "" : ` under ceiling=${usdOf(admission.ceilingUsd)}`) + (admission.reservedForFinalizationUsd === 0 ? "" : ` (finalization holds ${usdOf(admission.reservedForFinalizationUsd)})`));
1182
+ for (const row of admission.wave) io.out(` ${row.admitted ? "admit" : "DENY "} ${row.label} reserve=${usdOf(row.reserveUsd)}` + (row.deniedBy === void 0 ? "" : ` [${row.deniedBy}]`));
1183
+ }
1184
+ io.out(`exposure: maxInFlight=${report.exposure.maxInFlight}` + (report.exposure.overshootOneTurnFloorUsd === void 0 ? "" : ` overshootOneTurnFloor=${usdOf(report.exposure.overshootOneTurnFloorUsd)}`));
1185
+ for (const [provider, row] of Object.entries(report.exposure.perProvider)) io.out(` provider ${provider}: inFlight=${row.inFlight} requestsPerWave=${row.requestsPerWave} tokensPerWaveFloor=${row.tokensPerWaveFloor}`);
1186
+ io.out(`findings: ${report.findings.length}`);
1187
+ for (const finding of report.findings) io.out(` ${finding.severity} ${finding.code}: ${finding.message}` + (finding.spawn === void 0 ? "" : ` [spawn ${finding.spawn}]`));
1188
+ }
1189
+ /**
1190
+ * rulvar preflight (the experiment-review P2.2; grammar in grammar.ts):
1191
+ * the effective-config linter and dry-run estimator. Loads the SAME
1192
+ * config, module, and run-profile merge `rulvar run` would assemble,
1193
+ * but constructs no engine, opens no store, and dispatches nothing:
1194
+ * the report is computed by preflightEstimate over options alone, so
1195
+ * the command cannot pay for a single provider token by construction.
1196
+ * The declared spawn wave comes from the `preflight` export of the
1197
+ * config or workflow module (module wins), and --spawns JSON overrides
1198
+ * it from the command line. --json prints the machine-readable report.
1199
+ * Exit 1 when any finding has severity 'error' (the linter contract:
1200
+ * green preflight means the run can at least start), 0 otherwise.
1201
+ */
1202
+ async function preflightCommand(argv, context) {
1203
+ const parsed = parseCommand(GRAMMAR.preflight, argv);
1204
+ const target = parsed.positionals[0];
1205
+ const profile = parsed.values.profile;
1206
+ const json = parsed.values.json === true;
1207
+ const budgetUsd = parsed.values["budget-usd"] === void 0 ? void 0 : parseBudgetValue("budget-usd", parsed.values["budget-usd"]);
1208
+ const config = await loadCliConfig(context.cwd);
1209
+ const module = looksLikeFile(target) ? await loadWorkflowModule(target, context.cwd) : void 0;
1210
+ const workflows = {
1211
+ ...config.workflows,
1212
+ ...module?.workflows
1213
+ };
1214
+ if ((module?.workflow ?? workflows[target]) === void 0) throw new ConfigError(looksLikeFile(target) ? `${target} exports no workflow (default export or named 'workflow')` : `no workflow named '${target}' in the registry; register it in rulvar.config.mjs`);
1215
+ let engineOptions = {
1216
+ ...config.engineOptions,
1217
+ ...module?.engineOptions
1218
+ };
1219
+ if (profile !== void 0) {
1220
+ const preset = runProfile(profile);
1221
+ if (preset === void 0) throw new ConfigError(`unknown run profile '${profile}'; shipped: fast, standard, deep, ultra`);
1222
+ engineOptions = applyRunProfile(preset, engineOptions);
1223
+ }
1224
+ const declaration = {
1225
+ ...config.preflight,
1226
+ ...module?.preflight
1227
+ };
1228
+ let spawns = declaration.spawns;
1229
+ const spawnsJson = parsed.values.spawns;
1230
+ if (spawnsJson !== void 0) {
1231
+ let parsedSpawns;
1232
+ try {
1233
+ parsedSpawns = JSON.parse(spawnsJson);
1234
+ } catch (error) {
1235
+ throw new ConfigError(`--spawns is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
1236
+ }
1237
+ if (!Array.isArray(parsedSpawns)) throw new ConfigError("--spawns must be a JSON array of spawn specs");
1238
+ spawns = parsedSpawns;
1239
+ }
1240
+ const report = preflightEstimate({
1241
+ engine: engineOptions,
1242
+ run: { ...budgetUsd === void 0 ? {} : { budgetUsd } },
1243
+ ...declaration.orchestrator === void 0 ? {} : { orchestrator: declaration.orchestrator },
1244
+ ...spawns === void 0 ? {} : { spawns },
1245
+ ...declaration.quotaRules === void 0 ? {} : { quotaRules: declaration.quotaRules }
1246
+ });
1247
+ if (json) context.io.out(JSON.stringify(report, null, 2));
1248
+ else renderPreflight(report, context.io);
1249
+ return report.findings.some((finding) => finding.severity === "error") ? 1 : 0;
1250
+ }
1251
+ /**
1087
1252
  * rulvar plan (M6-T11; grammar in grammar.ts): plans a workflow script
1088
1253
  * through @rulvar/planner (loaded dynamically: the CLI's static
1089
1254
  * dependency stays @rulvar/core only), prints the accepted script and
@@ -1546,6 +1711,21 @@ accounting, localized determinism warnings, and the output digest;
1546
1711
  --assert-no-live exits 1 unless the replay is pure, and
1547
1712
  --compare-output-hash exits 1 unless the replayed result's digest
1548
1713
  equals the journaled one.
1714
+ invoice prints the per-dispatch reconciliation export: one row per
1715
+ billable provider call (failed and retried attempts included) with the
1716
+ provider's response id when the adapter surfaced one, plus the
1717
+ gross/net ledger totals (gross includes abandoned subtrees, exactly
1718
+ what the provider bills); --json emits the machine-readable form.
1719
+ preflight is the effective-config linter and dry-run estimator: it
1720
+ assembles the SAME options rulvar run would (config, module exports,
1721
+ --profile, --budget-usd) but constructs no engine and dispatches
1722
+ nothing, then prints the effective merged limits per declared spawn,
1723
+ the layer-1 admission projection over the declared wave (which spawns
1724
+ admit, which are denied and by what), the per-tool and weighted-unit
1725
+ bottleneck ordering, the concurrency and quota exposure floors, and
1726
+ the linter findings. The declared wave comes from the preflight export
1727
+ of the config or workflow module, or --spawns JSON; --json emits the
1728
+ machine-readable report; exit 1 when any finding is severity error.
1549
1729
  plan asks the planner model (role plan) to write a workflow script,
1550
1730
  lints and self-repairs it, then runs it in the worker sandbox; --dry-run
1551
1731
  prints the accepted script without running. Both stages are paid runs
@@ -1582,7 +1762,9 @@ async function runCli(argv, options) {
1582
1762
  throw new ConfigError(RUNS_FAMILY_USAGE);
1583
1763
  }
1584
1764
  case "inspect": return await inspectCommand(rest, context);
1765
+ case "invoice": return await invoiceCommand(rest, context);
1585
1766
  case "plan": return await planCommand(rest, context);
1767
+ case "preflight": return await preflightCommand(rest, context);
1586
1768
  case "kb": return await kbCommand(rest, context);
1587
1769
  case void 0:
1588
1770
  case "help":
@@ -1632,4 +1814,4 @@ function processIo() {
1632
1814
  };
1633
1815
  }
1634
1816
  //#endregion
1635
- export { looksLikeFile as _, resumeCommand as a, driveRun as c, attachProgress as d, renderEventLine as f, loadWorkflowModule as g, loadCliConfig as h, inspectCommand as i, reportOutcome as l, assembleEngine as m, HELP as n, runCommand as o, DEFAULT_STORE_DIR as p, runCli as r, runsLsCommand as s, processIo as t, strictExitCode as u };
1817
+ export { loadCliConfig as _, invoiceCommand as a, runCommand as c, reportOutcome as d, strictExitCode as f, assembleEngine as g, DEFAULT_STORE_DIR as h, inspectCommand as i, runsLsCommand as l, renderEventLine as m, HELP as n, preflightCommand as o, attachProgress as p, runCli as r, resumeCommand as s, processIo as t, driveRun as u, loadWorkflowModule as v, looksLikeFile as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/cli",
3
- "version": "1.60.0",
3
+ "version": "1.62.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.60.0"
25
+ "@rulvar/core": "1.62.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/planner": "1.60.0",
32
- "@rulvar/plan": "1.60.0",
33
- "@rulvar/store-sqlite": "1.60.0",
34
- "@rulvar/testing": "1.60.0",
35
- "@rulvar/evals": "1.60.0"
31
+ "@rulvar/planner": "1.62.0",
32
+ "@rulvar/testing": "1.62.0",
33
+ "@rulvar/plan": "1.62.0",
34
+ "@rulvar/store-sqlite": "1.62.0",
35
+ "@rulvar/evals": "1.62.0"
36
36
  },
37
37
  "bin": {
38
38
  "rulvar": "./dist/cli.js"