@compaction/cli 0.1.4 → 0.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.
Files changed (170) hide show
  1. package/README.md +51 -12
  2. package/dist/cli/commands/activity.d.ts +8 -0
  3. package/dist/cli/commands/activity.js +35 -0
  4. package/dist/cli/commands/apply-context.js +68 -0
  5. package/dist/cli/commands/billing-delta.js +12 -1
  6. package/dist/cli/commands/capture-claude-code.d.ts +44 -0
  7. package/dist/cli/commands/capture-claude-code.js +206 -0
  8. package/dist/cli/commands/capture.js +283 -1
  9. package/dist/cli/commands/compact.js +24 -11
  10. package/dist/cli/commands/context.d.ts +2 -0
  11. package/dist/cli/commands/context.js +130 -0
  12. package/dist/cli/commands/dev.d.ts +17 -0
  13. package/dist/cli/commands/dev.js +131 -0
  14. package/dist/cli/commands/gateway.d.ts +31 -0
  15. package/dist/cli/commands/gateway.js +312 -0
  16. package/dist/cli/commands/hooks.d.ts +5 -0
  17. package/dist/cli/commands/hooks.js +181 -0
  18. package/dist/cli/commands/import.js +3 -1
  19. package/dist/cli/commands/init.js +422 -17
  20. package/dist/cli/commands/input-compaction-ab.d.ts +2 -0
  21. package/dist/cli/commands/input-compaction-ab.js +125 -0
  22. package/dist/cli/commands/optimize-hosted.d.ts +54 -0
  23. package/dist/cli/commands/optimize-hosted.js +123 -0
  24. package/dist/cli/commands/optimize.js +8 -0
  25. package/dist/cli/commands/output-shaping-ab.d.ts +2 -0
  26. package/dist/cli/commands/output-shaping-ab.js +132 -0
  27. package/dist/cli/commands/output-shaping.d.ts +7 -0
  28. package/dist/cli/commands/output-shaping.js +43 -0
  29. package/dist/cli/commands/policies.d.ts +2 -0
  30. package/dist/cli/commands/policies.js +80 -0
  31. package/dist/cli/commands/precall.d.ts +19 -0
  32. package/dist/cli/commands/precall.js +436 -0
  33. package/dist/cli/commands/recommend.js +1 -1
  34. package/dist/cli/commands/run.js +310 -18
  35. package/dist/cli/commands/upgrade-status.d.ts +42 -0
  36. package/dist/cli/commands/upgrade-status.js +152 -0
  37. package/dist/cli/cursor-export-read.d.ts +13 -0
  38. package/dist/cli/cursor-export-read.js +53 -0
  39. package/dist/cli/cursor-live-preflight.d.ts +19 -0
  40. package/dist/cli/cursor-live-preflight.js +46 -0
  41. package/dist/cli/index.js +39 -2
  42. package/dist/cli/onboarding/GatewayTui.d.ts +33 -0
  43. package/dist/cli/onboarding/GatewayTui.js +113 -0
  44. package/dist/cli/onboarding/model.d.ts +32 -1
  45. package/dist/cli/onboarding/model.js +27 -2
  46. package/dist/cli/terminal-logo.d.ts +27 -0
  47. package/dist/cli/terminal-logo.js +49 -0
  48. package/dist/core/activity-event.d.ts +107 -0
  49. package/dist/core/activity-event.js +182 -0
  50. package/dist/core/activity-store.d.ts +63 -0
  51. package/dist/core/activity-store.js +254 -0
  52. package/dist/core/activity-view.d.ts +76 -0
  53. package/dist/core/activity-view.js +120 -0
  54. package/dist/core/api-client/index.d.ts +3 -1
  55. package/dist/core/api-client/index.js +4 -0
  56. package/dist/core/api-client/payload.d.ts +5 -1
  57. package/dist/core/api-client/payload.js +8 -6
  58. package/dist/core/api-client/persisted-config.d.ts +57 -0
  59. package/dist/core/api-client/persisted-config.js +156 -0
  60. package/dist/core/api-client/tool.d.ts +29 -0
  61. package/dist/core/api-client/tool.js +50 -0
  62. package/dist/core/api-client/types.d.ts +15 -4
  63. package/dist/core/auto-apply-ask.d.ts +58 -0
  64. package/dist/core/auto-apply-ask.js +105 -0
  65. package/dist/core/auto-apply-gates.d.ts +76 -0
  66. package/dist/core/auto-apply-gates.js +113 -0
  67. package/dist/core/before-call-activity.d.ts +35 -0
  68. package/dist/core/before-call-activity.js +103 -0
  69. package/dist/core/before-call-recovery.d.ts +19 -0
  70. package/dist/core/before-call-recovery.js +46 -0
  71. package/dist/core/before-call-stdin.d.ts +59 -0
  72. package/dist/core/before-call-stdin.js +78 -0
  73. package/dist/core/before-call.d.ts +149 -0
  74. package/dist/core/before-call.js +358 -0
  75. package/dist/core/billing-delta/billing-delta-record.d.ts +28 -0
  76. package/dist/core/billing-delta/billing-delta-record.js +26 -0
  77. package/dist/core/capture-record.d.ts +40 -0
  78. package/dist/core/capture-record.js +55 -0
  79. package/dist/core/claude-code-before-call.d.ts +37 -0
  80. package/dist/core/claude-code-before-call.js +120 -0
  81. package/dist/core/claude-code-connect.d.ts +31 -0
  82. package/dist/core/claude-code-connect.js +87 -0
  83. package/dist/core/claude-code-hook-record.d.ts +71 -0
  84. package/dist/core/claude-code-hook-record.js +0 -0
  85. package/dist/core/claude-code-hooks.d.ts +77 -0
  86. package/dist/core/claude-code-hooks.js +141 -0
  87. package/dist/core/codex-capture.d.ts +45 -0
  88. package/dist/core/codex-capture.js +204 -0
  89. package/dist/core/command-runner.js +5 -1
  90. package/dist/core/compaction-artifacts.d.ts +51 -0
  91. package/dist/core/compaction-artifacts.js +242 -0
  92. package/dist/core/compactor.d.ts +9 -0
  93. package/dist/core/compactor.js +95 -0
  94. package/dist/core/context-store-eval-cases.d.ts +6 -0
  95. package/dist/core/context-store-eval-cases.js +331 -0
  96. package/dist/core/context-store-eval.d.ts +140 -0
  97. package/dist/core/context-store-eval.js +138 -0
  98. package/dist/core/context-store-fs.d.ts +73 -0
  99. package/dist/core/context-store-fs.js +157 -0
  100. package/dist/core/context-store-sufficiency.d.ts +98 -0
  101. package/dist/core/context-store-sufficiency.js +135 -0
  102. package/dist/core/context-store.d.ts +155 -0
  103. package/dist/core/context-store.js +228 -0
  104. package/dist/core/cross-surface-event.d.ts +306 -0
  105. package/dist/core/cross-surface-event.js +330 -0
  106. package/dist/core/cursor-capture.d.ts +54 -0
  107. package/dist/core/cursor-capture.js +215 -0
  108. package/dist/core/cursor-preflight-probe.d.ts +8 -0
  109. package/dist/core/cursor-preflight-probe.js +88 -0
  110. package/dist/core/cursor-preflight.d.ts +86 -0
  111. package/dist/core/cursor-preflight.js +126 -0
  112. package/dist/core/gateway/apply-activation.d.ts +39 -0
  113. package/dist/core/gateway/apply-activation.js +84 -0
  114. package/dist/core/gateway/apply-policy.d.ts +64 -0
  115. package/dist/core/gateway/apply-policy.js +221 -0
  116. package/dist/core/gateway/apply-receipt.d.ts +36 -0
  117. package/dist/core/gateway/apply-receipt.js +75 -0
  118. package/dist/core/gateway/cache-proof.d.ts +45 -0
  119. package/dist/core/gateway/cache-proof.js +65 -0
  120. package/dist/core/gateway/configure.d.ts +50 -0
  121. package/dist/core/gateway/configure.js +169 -0
  122. package/dist/core/gateway/openai-usage.d.ts +56 -0
  123. package/dist/core/gateway/openai-usage.js +128 -0
  124. package/dist/core/gateway/receipt.d.ts +138 -0
  125. package/dist/core/gateway/receipt.js +120 -0
  126. package/dist/core/gateway/recovery.d.ts +23 -0
  127. package/dist/core/gateway/recovery.js +68 -0
  128. package/dist/core/gateway/server.d.ts +51 -0
  129. package/dist/core/gateway/server.js +276 -0
  130. package/dist/core/gateway/status.d.ts +45 -0
  131. package/dist/core/gateway/status.js +109 -0
  132. package/dist/core/hook-usage-aggregate.d.ts +47 -0
  133. package/dist/core/hook-usage-aggregate.js +161 -0
  134. package/dist/core/input-compaction-ab.d.ts +111 -0
  135. package/dist/core/input-compaction-ab.js +158 -0
  136. package/dist/core/local-run-record.d.ts +109 -0
  137. package/dist/core/local-run-record.js +223 -0
  138. package/dist/core/output-shaping-ab.d.ts +140 -0
  139. package/dist/core/output-shaping-ab.js +146 -0
  140. package/dist/core/output-shaping-attach.d.ts +31 -0
  141. package/dist/core/output-shaping-attach.js +57 -0
  142. package/dist/core/output-shaping.d.ts +56 -0
  143. package/dist/core/output-shaping.js +89 -0
  144. package/dist/core/policy-middleware.d.ts +121 -0
  145. package/dist/core/policy-middleware.js +919 -0
  146. package/dist/core/policy-preferences.d.ts +99 -0
  147. package/dist/core/policy-preferences.js +232 -0
  148. package/dist/core/report-generator.d.ts +19 -1
  149. package/dist/core/report-generator.js +51 -0
  150. package/dist/core/run-aggregator.d.ts +56 -1
  151. package/dist/core/run-aggregator.js +93 -2
  152. package/dist/core/run-flow-report.d.ts +82 -0
  153. package/dist/core/run-flow-report.js +71 -0
  154. package/dist/core/safety-report.js +8 -1
  155. package/dist/core/shim-capture-bridge.d.ts +32 -0
  156. package/dist/core/shim-capture-bridge.js +88 -0
  157. package/dist/core/skill-injection-policy.d.ts +72 -0
  158. package/dist/core/skill-injection-policy.js +183 -0
  159. package/dist/core/spend-attribution.js +1 -1
  160. package/dist/core/token-accounting.d.ts +1 -1
  161. package/dist/core/tool-shim.d.ts +129 -0
  162. package/dist/core/tool-shim.js +447 -0
  163. package/dist/core/trace-adapters.d.ts +8 -0
  164. package/dist/core/trace-adapters.js +4 -0
  165. package/dist/core/trace-parser.d.ts +13 -13
  166. package/dist/core/trace-parser.js +6 -6
  167. package/dist/core/types.d.ts +45 -2
  168. package/dist/core/waste-detector.d.ts +20 -0
  169. package/dist/core/waste-detector.js +160 -6
  170. package/package.json +1 -1
@@ -1,13 +1,33 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
1
3
  import chalk from "chalk";
2
4
  import { parseRunCommand, executeLocalCommand, persistLocalCommandRun } from "../../core/command-runner.js";
3
- import { writeJsonArtifact } from "../../core/artifact-writer.js";
4
- import { withTraceFingerprint } from "../../core/report-generator.js";
5
+ import { writeJsonArtifact, writeTextArtifact } from "../../core/artifact-writer.js";
6
+ import { formatCompactionMarkdownReport, withSavingsEvidence, withTraceFingerprint } from "../../core/report-generator.js";
5
7
  import { localCommandRunToAgentTrace } from "../../core/run-trace-converter.js";
6
8
  import { runEngineCommand } from "../engine-degrade.js";
9
+ import { CODEX_UNKNOWN_MODEL, captureCodexCommand, captureCodexExport } from "../../core/codex-capture.js";
10
+ import { captureCursorCommand, captureCursorExport } from "../../core/cursor-capture.js";
11
+ import { gateCursorLiveRun } from "../cursor-live-preflight.js";
12
+ import { readCursorExportFile } from "../cursor-export-read.js";
13
+ import { buildRunFlowTokenReport, formatRunFlowTokenReport } from "../../core/run-flow-report.js";
14
+ import { buildLocalRunTokenRecord, writeLocalRunTokenRecord } from "../../core/local-run-record.js";
15
+ import { buildRunCrossSurfaceEvent } from "../../core/cross-surface-event.js";
16
+ import { buildMeasureOnlyActivityEvent } from "../../core/activity-event.js";
17
+ import { appendActivityEvent } from "../../core/activity-store.js";
18
+ import { createUsageMetadata } from "../../core/usage-metadata.js";
19
+ import { estimateTraceTokens } from "../../core/token-estimator.js";
20
+ /**
21
+ * The exact honest reason bare-run OUTPUT tokens are unavailable — shared VERBATIM by the printed
22
+ * limitation note and the cross-surface event's output axis, so the reason can never diverge
23
+ * between what the operator reads and what the record carries.
24
+ */
25
+ const BARE_RUN_OUTPUT_UNAVAILABLE_REASON = "a raw command's captured stdout/stderr is not safely separable as genuine model output";
7
26
  export function registerRunCommand(program) {
8
- program
27
+ const run = program
9
28
  .command("run")
10
- .description("Run a local command, capture output locally, convert it to a trace, and write compaction artifacts.")
29
+ .description("Unified capture→compact→report flow. Bare `run -- <cmd>` wraps any local command (local-estimate). " +
30
+ "Per-tool front-ends: `run codex -- …` (provider-reported) and `run cursor -- …` (local-estimate/unavailable).")
11
31
  .argument("[commandParts...]", "Command and arguments to execute after --")
12
32
  .allowUnknownOption(true)
13
33
  .action(async (commandParts) => {
@@ -15,22 +35,85 @@ export function registerRunCommand(program) {
15
35
  // `run`'s value IS the compaction report it produces, which needs the proprietary engine.
16
36
  // Lazy-load it FIRST so a public install degrades immediately — before spawning the child
17
37
  // command — rather than running the command and then failing to compact.
18
- const { writeCompactionArtifacts } = await import("../../engine/compaction-artifacts.js");
38
+ const { writeCompactionArtifacts } = await import("../../core/compaction-artifacts.js");
19
39
  const command = parseRunCommand(commandParts);
20
40
  console.log(chalk.cyan("compaction run"));
21
41
  console.log("Running local command and capturing stdout/stderr locally. Child output is not printed by compaction.");
22
- const run = await executeLocalCommand(command);
23
- const persistedRun = await persistLocalCommandRun(run);
24
- const trace = localCommandRunToAgentTrace(run);
42
+ const runResult = await executeLocalCommand(command);
43
+ const persistedRun = await persistLocalCommandRun(runResult);
44
+ const trace = localCommandRunToAgentTrace(runResult);
25
45
  const tracePath = await writeJsonArtifact(persistedRun.outputDirectory, "trace.json", trace);
26
- const artifacts = await writeCompactionArtifacts(trace, persistedRun.outputDirectory, run.runId);
27
- // Attribution/integrity (V0.2 savings-evidence): re-persist report.json with the content-addressed
28
- // trace fingerprint so `aggregate`/`summary` attribute each saving to a verifiable run and
29
- // de-duplicate a re-counted run. Additive; no existing report field/label changes.
30
- await writeJsonArtifact(persistedRun.outputDirectory, "report.json", withTraceFingerprint(artifacts.report, trace));
31
- console.log(`Exit code: ${run.exitCode ?? "none"}`);
32
- console.log(`Signal: ${run.signal ?? "none"}`);
33
- console.log(`Duration: ${run.durationMs} ms`);
46
+ // Shared HONEST token report — the SAME block the per-tool front-ends print, so output is
47
+ // consistent across every `run` path (unified-run-flow D1/D2). A bare wrapped command is an
48
+ // UNKNOWN tool with NO provider usage, so tokens are LOCAL-ESTIMATE (chars/4) — like Cursor.
49
+ // Genuine model output is NOT safely separable from a raw command's captured stdout/stderr
50
+ // (those are tool-role lines counted as input; the trace's single assistant message is a
51
+ // compaction-synthesized summary, not the command's output), so output is `unavailable` with
52
+ // a reason — never a silent zero, never provider-reported. Output is shown as TOKENS ONLY,
53
+ // NEVER a saving; the input before/after reduction below is the real (estimate-labeled) saving.
54
+ // Input is estimated locally (chars/4) from the trace; output is intentionally NOT carried so no
55
+ // synthesized-summary count is ever surfaced as if it were the command's model output.
56
+ const bareUsage = createUsageMetadata({
57
+ inputTokens: estimateTraceTokens(trace).inputTokens,
58
+ providerReportedTokens: false,
59
+ estimatedTokens: true,
60
+ model: trace.model,
61
+ limitations: [
62
+ "Bare `run -- <cmd>` wraps an arbitrary command with no provider usage; input is a local chars/4 estimate.",
63
+ `Output tokens are unavailable: ${BARE_RUN_OUTPUT_UNAVAILABLE_REASON}.`
64
+ ]
65
+ });
66
+ const bareTokenReport = buildRunFlowTokenReport({ tool: "command", usage: bareUsage, outputStatus: "unavailable" });
67
+ for (const line of formatRunFlowTokenReport(bareTokenReport))
68
+ console.log(line);
69
+ // LOCAL record step (unified-run-flow, local layer): persist the SAME honest token report as a
70
+ // content-free local record — counts + token_source only, no content, no savings figure — so runs
71
+ // accumulate under .compaction/run-records and `compaction summary` can roll them up per tool.
72
+ // Local file writes only; no hosted config read, no network.
73
+ // Cross-surface writers completion (charter 2026-07-03-run-5, completes #582 residual #5):
74
+ // the bare-run record additionally carries the ADDITIVE optional `cross_surface_event` —
75
+ // surface "cli", provider "other" (no observable provider), input LOCAL-ESTIMATE, output
76
+ // UNAVAILABLE with the exact command-surface reason above, cost UNAVAILABLE (no billing
77
+ // surface). Axes are copied VERBATIM from the SAME token report the record embeds. Record
78
+ // file content only — zero stdout change.
79
+ const bareCrossSurfaceEvent = buildRunCrossSurfaceEvent("cli", {
80
+ runId: runResult.runId,
81
+ tokenReport: bareTokenReport,
82
+ reasons: { output: BARE_RUN_OUTPUT_UNAVAILABLE_REASON }
83
+ });
84
+ const bareRecordPaths = await writeLocalRunTokenRecord(buildLocalRunTokenRecord({
85
+ runId: runResult.runId,
86
+ tokenReport: bareTokenReport,
87
+ crossSurfaceEvent: bareCrossSurfaceEvent
88
+ }), persistedRun.outputDirectory);
89
+ // ACTIVITY step (charter 2026-07-04-run-7, lane "shared activity/event model"): the SAME
90
+ // cross-surface event, extended measure-only, appends ONE metrics-only activity event to
91
+ // the local store (.compaction/activity/activity.jsonl). Measure-only honesty: nothing was
92
+ // applied, so approval_status "not-required"; auto_apply not eligible + default
93
+ // "ask-each-time" + applied_automatically false (no auto-apply logic exists);
94
+ // sync_status "local-only"; recovery = the trace artifact just written (what the record
95
+ // honestly knows). Best-effort local append — never fails the run, ZERO stdout change.
96
+ try {
97
+ await appendActivityEvent(buildMeasureOnlyActivityEvent(bareCrossSurfaceEvent, { original_retained: true, location: tracePath }));
98
+ }
99
+ catch {
100
+ // A local filesystem failure here must never fail the user's run (record-only step).
101
+ }
102
+ const artifacts = await writeCompactionArtifacts(trace, persistedRun.outputDirectory, runResult.runId);
103
+ // Attribution/integrity + per-run evidence label (V0.2 savings-evidence): re-persist
104
+ // report.json with the trace fingerprint (attribution/dedup) and the composed
105
+ // `savings_evidence` record (per-run label). `run` figures are local estimates (chars/4) —
106
+ // `local_estimate` rung 1. Additive; no existing report field/label changes.
107
+ const evidenceReport = withSavingsEvidence(withTraceFingerprint(artifacts.report, trace), {
108
+ costSource: "local_estimate"
109
+ });
110
+ await writeJsonArtifact(persistedRun.outputDirectory, "report.json", evidenceReport);
111
+ // Re-render report.md from the ENRICHED report so the per-run savings_evidence record
112
+ // appears in the human-readable artifact too (engine rendered it from the base report).
113
+ await writeTextArtifact(persistedRun.outputDirectory, "report.md", formatCompactionMarkdownReport(evidenceReport, artifacts.policy));
114
+ console.log(`Exit code: ${runResult.exitCode ?? "none"}`);
115
+ console.log(`Signal: ${runResult.signal ?? "none"}`);
116
+ console.log(`Duration: ${runResult.durationMs} ms`);
34
117
  console.log(chalk.green(`Wrote ${persistedRun.stdoutPath}`));
35
118
  console.log(chalk.green(`Wrote ${persistedRun.stderrPath}`));
36
119
  console.log(chalk.green(`Wrote ${persistedRun.rawOutputPath}`));
@@ -45,10 +128,219 @@ export function registerRunCommand(program) {
45
128
  console.log(chalk.green(`Wrote ${artifacts.paths.safetyReportPath}`));
46
129
  console.log(chalk.green(`Wrote ${artifacts.paths.safetyMarkdownReportPath}`));
47
130
  console.log(chalk.green(`Wrote ${artifacts.paths.prCommentReportPath}`));
48
- if (run.exitCode !== 0 && run.exitCode !== null) {
49
- process.exitCode = run.exitCode;
131
+ console.log(chalk.green(`Wrote ${bareRecordPaths.artifactPath}`));
132
+ console.log(` Local run record accumulated at ${bareRecordPaths.accumulatedPath} (content-free: token counts + sources only). ` +
133
+ "Roll runs up per tool with `compaction summary`.");
134
+ if (runResult.exitCode !== 0 && runResult.exitCode !== null) {
135
+ process.exitCode = runResult.exitCode;
50
136
  }
51
137
  });
52
138
  });
139
+ registerRunToolFrontEnds(run);
140
+ }
141
+ /**
142
+ * Register the per-tool front-ends of the unified flow (accepted design D1: unify the back half with
143
+ * per-tool front-ends). `run codex` / `run cursor` route to the EXISTING capture code (no duplication),
144
+ * then compact via the SAME engine artifact writer as bare `run`, then print the shared HONEST token
145
+ * report (`token_source` explicit; input/output SEPARATE; output as tokens, NEVER a saving).
146
+ *
147
+ * Local-only: no record wiring is added here (that is roadmap item 5 / `/app`, separately gated). Hosted
148
+ * recording remains on the `capture codex` / `capture cursor` commands, gated on URL+key as before.
149
+ */
150
+ function registerRunToolFrontEnds(run) {
151
+ run
152
+ .command("codex")
153
+ .description("Unified flow for Codex: wrap a LIVE `codex exec --json` run (or --export a saved one), compact, and " +
154
+ "report. Tokens are PROVIDER-REPORTED (turn.completed.usage). Local only — no upload, no proxying.")
155
+ .requiredOption("--out <dir>", "Output directory for capture + compaction artifacts")
156
+ .option("--export <file>", "Read a saved `codex exec --json` JSONL export instead of running a command")
157
+ .argument("[commandParts...]", 'Command and args after -- (e.g. -- codex exec --json "do X")')
158
+ .allowUnknownOption(true)
159
+ .action(async (commandParts, options) => {
160
+ await runToolFrontEnd({
161
+ tool: "codex",
162
+ label: "compaction run codex",
163
+ intro: "Capturing a live codex exec --json run, then compacting + reporting. No upload, no proxying. " +
164
+ "Review artifacts before sharing (they may contain code/output).",
165
+ options,
166
+ commandParts,
167
+ capture: async () => {
168
+ const result = options.export
169
+ ? captureCodexExport(await readFile(options.export, "utf8"))
170
+ : await captureCodexCommand(commandParts);
171
+ return {
172
+ trace: result.trace,
173
+ usage: result.usageMetadata,
174
+ // Codex has no per-field "unavailable" output split; provider-reported usage is whole.
175
+ outputStatus: "present",
176
+ warnings: result.warnings,
177
+ exitCode: result.commandRun?.exitCode
178
+ };
179
+ }
180
+ });
181
+ });
182
+ run
183
+ .command("cursor")
184
+ .description("Unified flow for Cursor: wrap a LIVE Cursor headless run (or --export saved output), compact, and " +
185
+ "report. Tokens are LOCAL-ESTIMATE only (Cursor emits no usage); output is counted where separable " +
186
+ "(use --output-format json), else UNAVAILABLE. Local only — no upload, no SQLite.")
187
+ .requiredOption("--out <dir>", "Output directory for capture + compaction artifacts")
188
+ .option("--export <file>", "Read saved Cursor headless output (json / stream-json) instead of running a command")
189
+ .argument("[commandParts...]", 'Command and args after -- (e.g. -- cursor agent -p "do X" --output-format json)')
190
+ .allowUnknownOption(true)
191
+ .action(async (commandParts, options) => {
192
+ // LIVE path (no --export) with a command to run: run the SAFE preflight FIRST (CLI-presence +
193
+ // capability only; never a real prompt, never login, never a CURSOR_API_KEY read). If the CLI is
194
+ // missing/incapable, print guidance and STOP before spawning — the operator sees exact next steps,
195
+ // not an opaque failure. (A bare `run cursor` with no command + no --export falls through to the
196
+ // existing usage error inside runToolFrontEnd.)
197
+ if (!options.export && commandParts.length > 0) {
198
+ console.log(chalk.cyan("compaction run cursor"));
199
+ console.log("Preflight for a LIVE Cursor headless run. LOCAL-ESTIMATE tokens only (Cursor emits no provider usage).");
200
+ const ready = await gateCursorLiveRun();
201
+ if (!ready) {
202
+ process.exitCode = 1;
203
+ return;
204
+ }
205
+ }
206
+ // Export path: read the saved output HONESTLY up front — a missing/unreadable --export file is an
207
+ // actionable error (what failed, which path, what a valid export is), exit 1; never a raw errno,
208
+ // never a fabricated capture, no artifacts written.
209
+ let exportRawOutput;
210
+ if (options.export) {
211
+ const exportRead = await readCursorExportFile(options.export);
212
+ if (!exportRead.ok) {
213
+ console.log(chalk.cyan("compaction run cursor"));
214
+ for (const line of exportRead.errorLines)
215
+ console.error(line);
216
+ process.exitCode = 1;
217
+ return;
218
+ }
219
+ exportRawOutput = exportRead.rawOutput;
220
+ }
221
+ await runToolFrontEnd({
222
+ tool: "cursor",
223
+ label: "compaction run cursor",
224
+ intro: "Capturing a live Cursor headless run, then compacting + reporting. LOCAL-ESTIMATE tokens only " +
225
+ "(Cursor emits no provider usage). No upload, no SQLite. Review artifacts before sharing.",
226
+ options,
227
+ commandParts,
228
+ capture: async () => {
229
+ const result = exportRawOutput !== undefined
230
+ ? captureCursorExport(exportRawOutput, commandParts.length > 0 ? commandParts : undefined)
231
+ : await captureCursorCommand(commandParts);
232
+ return {
233
+ trace: result.trace,
234
+ usage: result.usageMetadata,
235
+ outputStatus: result.outputStatus,
236
+ // TRUE per-axis unavailability reasons (e.g. export-only input) so the printed token lines
237
+ // say WHY an axis is unavailable — never a bare "unavailable" with a generic guess.
238
+ inputUnavailableReason: result.inputUnavailableReason,
239
+ outputUnavailableReason: result.outputUnavailableReason,
240
+ warnings: result.warnings,
241
+ exitCode: result.commandRun?.exitCode
242
+ };
243
+ }
244
+ });
245
+ });
246
+ }
247
+ /**
248
+ * Shared per-tool front-end body: capture (existing code) → compact (existing engine writer) → HONEST
249
+ * report. The engine is lazy-loaded through `runEngineCommand` so a public (engine-free) install degrades
250
+ * with the sanctioned boundary message instead of crashing — identical posture to bare `run`.
251
+ */
252
+ async function runToolFrontEnd(params) {
253
+ await runEngineCommand(async () => {
254
+ const { writeCompactionArtifacts } = await import("../../core/compaction-artifacts.js");
255
+ console.log(chalk.cyan(params.label));
256
+ console.log(params.intro);
257
+ if (!params.options.export && params.commandParts.length === 0) {
258
+ console.error("error: provide a command after -- or use --export <file>.");
259
+ process.exitCode = 1;
260
+ return;
261
+ }
262
+ const captured = await params.capture();
263
+ const outDir = params.options.out;
264
+ await mkdir(outDir, { recursive: true });
265
+ const tracePath = path.join(outDir, "captured-trace.json");
266
+ await writeFile(tracePath, JSON.stringify(captured.trace, null, 2), "utf8");
267
+ console.log(chalk.green(`Wrote ${tracePath}`));
268
+ // Shared HONEST token report: token_source explicit per field; input/output SEPARATE; output as
269
+ // tokens only (NEVER a saving). This is printed for every tool front-end.
270
+ const tokenReport = buildRunFlowTokenReport({ tool: params.tool, usage: captured.usage, outputStatus: captured.outputStatus });
271
+ const unavailableReasons = { input: captured.inputUnavailableReason, output: captured.outputUnavailableReason };
272
+ for (const line of formatRunFlowTokenReport(tokenReport, { reasons: unavailableReasons }))
273
+ console.log(line);
274
+ for (const warning of captured.warnings)
275
+ console.log(chalk.yellow(` ! ${warning}`));
276
+ const runId = `${params.tool}-${Date.now()}`;
277
+ // LOCAL record step (unified-run-flow, local layer): persist the SAME honest token report as a
278
+ // content-free local record (counts + per-field token_source only — no prompt/output text, no
279
+ // savings figure). One copy stays with the run's --out artifacts; one accumulates under
280
+ // .compaction/run-records so `compaction summary` rolls runs up per tool. Local writes only —
281
+ // this is NOT the hosted record step (that stays on `capture <tool>`, gated on URL+key).
282
+ // Cross-surface event writers (contract docs/ops/cross-surface-evidence-contract.md): every
283
+ // `run` front-end record additionally carries the ADDITIVE optional `cross_surface_event`,
284
+ // built from the SAME token report so per-axis sources/reasons can never diverge (Cursor
285
+ // shipped first — D3, charter 2026-07-03-run-5; codex completes #582 residual #5). Axis
286
+ // sources are copied VERBATIM (never laundered); cost_source is UNAVAILABLE with the exact
287
+ // per-surface reason on every `run` path (no billing data exists here). Record file content
288
+ // only — zero stdout change.
289
+ const eventSurface = params.tool === "codex" ? "codex" : params.tool === "cursor" ? "cursor" : undefined;
290
+ const crossSurfaceEvent = eventSurface !== undefined
291
+ ? buildRunCrossSurfaceEvent(eventSurface, {
292
+ runId,
293
+ tokenReport,
294
+ reasons: unavailableReasons,
295
+ // model_label only where the capture HONESTLY knows the model: the codex event stream
296
+ // may report one; codex's "codex-unknown-model" placeholder maps to the contract's
297
+ // canonical "unknown" (unknown stays unknown — a placeholder is not a model id).
298
+ // Cursor stays "unknown" (never inferred — D3).
299
+ ...(params.tool === "codex" &&
300
+ captured.usage.model !== undefined &&
301
+ captured.usage.model !== CODEX_UNKNOWN_MODEL
302
+ ? { modelLabel: captured.usage.model }
303
+ : {})
304
+ })
305
+ : undefined;
306
+ const recordPaths = await writeLocalRunTokenRecord(buildLocalRunTokenRecord({ runId, tokenReport, ...(crossSurfaceEvent !== undefined ? { crossSurfaceEvent } : {}) }), outDir);
307
+ // ACTIVITY step (charter 2026-07-04-run-7, lane "shared activity/event model"): every front-end
308
+ // that builds a cross-surface event (codex/cursor) also appends ONE metrics-only activity event
309
+ // to the local store — measure-only, so approval_status "not-required", auto_apply not eligible
310
+ // + default "ask-each-time" + applied_automatically false (no auto-apply logic exists),
311
+ // sync_status "local-only", recovery = the captured-trace artifact written above (what the
312
+ // record honestly knows). Best-effort local append — never fails the run, ZERO stdout change.
313
+ if (crossSurfaceEvent !== undefined) {
314
+ try {
315
+ await appendActivityEvent(buildMeasureOnlyActivityEvent(crossSurfaceEvent, { original_retained: true, location: tracePath }));
316
+ }
317
+ catch {
318
+ // A local filesystem failure here must never fail the user's run (record-only step).
319
+ }
320
+ }
321
+ console.log(chalk.green(`Wrote ${recordPaths.artifactPath}`));
322
+ console.log(` Local run record accumulated at ${recordPaths.accumulatedPath} (content-free: token counts + sources only). ` +
323
+ "Roll runs up per tool with `compaction summary`.");
324
+ // Compact via the SAME engine artifact writer as bare `run` (input reduction = the REAL saving).
325
+ const artifacts = await writeCompactionArtifacts(captured.trace, outDir, runId);
326
+ console.log(artifacts.consoleReport);
327
+ console.log(`Input reduction above is ${tokenReport.input_reduction_label} (input before/after — a real compaction saving). ` +
328
+ "No output-token savings is shown (gated on measured + eval-confirmed output-shaping).");
329
+ console.log(chalk.green(`Wrote ${artifacts.paths.reportPath}`));
330
+ console.log(chalk.green(`Wrote ${artifacts.paths.markdownReportPath}`));
331
+ console.log(chalk.green(`Wrote ${artifacts.paths.policyPath}`));
332
+ console.log(chalk.green(`Wrote ${artifacts.paths.capsulePath}`));
333
+ console.log(chalk.green(`Wrote ${artifacts.paths.compactedTracePath}`));
334
+ console.log(chalk.green(`Wrote ${artifacts.paths.safetyReportPath}`));
335
+ console.log(chalk.green(`Wrote ${artifacts.paths.safetyMarkdownReportPath}`));
336
+ console.log(chalk.green(`Wrote ${artifacts.paths.prCommentReportPath}`));
337
+ // Local-only: this front-end does NOT record to the hosted control-plane (roadmap item 5, gated).
338
+ // The run record written above is LOCAL accumulation only — never an upload.
339
+ console.log(` Not recorded to the hosted control-plane from \`run\` (local-only). To record content-free usage there, ` +
340
+ `use \`capture ${params.tool}\` with COMPACTION_API_URL + COMPACTION_API_KEY set.`);
341
+ console.log(` Next: compaction compact ${tracePath} --eval --out <dir>`);
342
+ if (typeof captured.exitCode === "number" && captured.exitCode !== 0)
343
+ process.exitCode = captured.exitCode;
344
+ });
53
345
  }
54
346
  //# sourceMappingURL=run.js.map
@@ -0,0 +1,42 @@
1
+ import { Command } from "commander";
2
+ import { type EnvLike } from "../../core/api-client/index.js";
3
+ /**
4
+ * `compaction upgrade` + `compaction status` — the key-first hosted-upgrade UX (charter
5
+ * 2026-07-04-run-7, founder hosted-upgrade decision).
6
+ *
7
+ * HONESTY / HARD RAILS:
8
+ * - There is NO live public hosted endpoint. The default URL is the LOCAL-dev server. A `--key`
9
+ * with no private endpoint FAILS GRACEFULLY with the exact `NO_LIVE_ENDPOINT_MESSAGE` — it never
10
+ * pretends `api.compaction.dev` (or any remote) is live.
11
+ * - The API key is validated against `GET <url>/v0/status` with a short timeout. Config is
12
+ * persisted (0600) ONLY on a 200. A 401 fails CLOSED and persists nothing. Transport/timeout/
13
+ * other failures persist nothing.
14
+ * - The full API key is NEVER printed, logged, or written anywhere except the 0600 config file.
15
+ * Every user-facing surface shows `maskKey(...)` only.
16
+ */
17
+ /**
18
+ * EXACT graceful-failure message for a `--key` with no private endpoint configured. Printed
19
+ * verbatim (no color wrapping) so it is byte-stable regardless of TTY.
20
+ */
21
+ export declare const NO_LIVE_ENDPOINT_MESSAGE = "no public hosted Compaction endpoint is live yet; hosted access is private-beta; pass `--api-url <url>` (private-beta/staging/self-hosted) or set `COMPACTION_API_URL`.";
22
+ /**
23
+ * Run `compaction upgrade`. Validates a key against a configured private endpoint and persists
24
+ * `{api_url, api_key}` (0600) ONLY on success. Sets a non-zero exit code on every failure path and
25
+ * persists NOTHING on any failure. Never prints/logs the key (masked only).
26
+ */
27
+ export declare function runUpgrade(opts: {
28
+ key?: string;
29
+ apiUrl?: string;
30
+ env?: EnvLike;
31
+ }): Promise<void>;
32
+ /**
33
+ * Run `compaction status`. Reports local core + whether a hosted endpoint is configured (masked key
34
+ * + host only) + a LIVE reachability re-check. Hosted-tier capabilities are shown ONLY when
35
+ * reachable, phrased as what the tier UNLOCKS (not per-capability liveness). Never prints the key.
36
+ */
37
+ export declare function runStatus(opts?: {
38
+ env?: EnvLike;
39
+ version?: string;
40
+ }): Promise<void>;
41
+ export declare function registerUpgradeCommand(program: Command): void;
42
+ export declare function registerStatusCommand(program: Command): void;
@@ -0,0 +1,152 @@
1
+ import chalk from "chalk";
2
+ import { ApiTransportError, apiStatus, healthCheckConfig, isLocalDefaultUrl, maskKey, resolveTarget, urlHost, writePersistedConfig } from "../../core/api-client/index.js";
3
+ /**
4
+ * `compaction upgrade` + `compaction status` — the key-first hosted-upgrade UX (charter
5
+ * 2026-07-04-run-7, founder hosted-upgrade decision).
6
+ *
7
+ * HONESTY / HARD RAILS:
8
+ * - There is NO live public hosted endpoint. The default URL is the LOCAL-dev server. A `--key`
9
+ * with no private endpoint FAILS GRACEFULLY with the exact `NO_LIVE_ENDPOINT_MESSAGE` — it never
10
+ * pretends `api.compaction.dev` (or any remote) is live.
11
+ * - The API key is validated against `GET <url>/v0/status` with a short timeout. Config is
12
+ * persisted (0600) ONLY on a 200. A 401 fails CLOSED and persists nothing. Transport/timeout/
13
+ * other failures persist nothing.
14
+ * - The full API key is NEVER printed, logged, or written anywhere except the 0600 config file.
15
+ * Every user-facing surface shows `maskKey(...)` only.
16
+ */
17
+ /**
18
+ * EXACT graceful-failure message for a `--key` with no private endpoint configured. Printed
19
+ * verbatim (no color wrapping) so it is byte-stable regardless of TTY.
20
+ */
21
+ export const NO_LIVE_ENDPOINT_MESSAGE = "no public hosted Compaction endpoint is live yet; hosted access is private-beta; pass `--api-url <url>` (private-beta/staging/self-hosted) or set `COMPACTION_API_URL`.";
22
+ /**
23
+ * Live health check via the shared api-client (`GET /v0/status`, Bearer key attached by the client,
24
+ * bounded timeout). Never leaks the key: the returned reason is our own concise string, never the
25
+ * raw request/headers.
26
+ */
27
+ async function healthCheck(target) {
28
+ try {
29
+ const res = await apiStatus(healthCheckConfig(target));
30
+ if (res.ok)
31
+ return { kind: "ok" };
32
+ if (res.status === 401)
33
+ return { kind: "unauthorized" };
34
+ return { kind: "http-error", status: res.status };
35
+ }
36
+ catch (err) {
37
+ // ApiTransportError's message contains the URL (never the key); we still surface only a concise
38
+ // reason so nothing incidental leaks.
39
+ return {
40
+ kind: "unreachable",
41
+ reason: err instanceof ApiTransportError ? "transport error or timeout" : "unexpected error"
42
+ };
43
+ }
44
+ }
45
+ /**
46
+ * Run `compaction upgrade`. Validates a key against a configured private endpoint and persists
47
+ * `{api_url, api_key}` (0600) ONLY on success. Sets a non-zero exit code on every failure path and
48
+ * persists NOTHING on any failure. Never prints/logs the key (masked only).
49
+ */
50
+ export async function runUpgrade(opts) {
51
+ const env = opts.env ?? process.env;
52
+ const target = resolveTarget({ flagUrl: opts.apiUrl, flagKey: opts.key, env });
53
+ console.log(chalk.cyan("compaction upgrade"));
54
+ if (!target.apiKey) {
55
+ console.error(chalk.red("hosted upgrade needs an API key: pass --key <key> or set COMPACTION_API_KEY. Nothing was saved."));
56
+ process.exitCode = 1;
57
+ return;
58
+ }
59
+ // Key present, but the resolved URL is the LOCAL-dev default → NO private endpoint configured.
60
+ // Fail gracefully; persist NOTHING. Printed verbatim (no chalk) for a byte-stable exact message.
61
+ if (isLocalDefaultUrl(target.url)) {
62
+ console.error(NO_LIVE_ENDPOINT_MESSAGE);
63
+ process.exitCode = 1;
64
+ return;
65
+ }
66
+ const host = urlHost(target.url);
67
+ console.log(`Validating the API key against ${host} …`); // host only; never the key
68
+ const health = await healthCheck(target);
69
+ switch (health.kind) {
70
+ case "ok": {
71
+ const path = writePersistedConfig({ api_url: target.url, api_key: target.apiKey }, env);
72
+ console.log(chalk.green("Hosted upgrade configured (endpoint reachable, key accepted)."));
73
+ console.log(` Endpoint: ${host}`);
74
+ console.log(` API key: ${maskKey(target.apiKey)} (masked; the full key is saved only to the local 0600 config)`);
75
+ console.log(` Saved to: ${path} (mode 0600)`);
76
+ console.log("Mode: private beta (hosted). Run `compaction status` to re-check reachability any time.");
77
+ return;
78
+ }
79
+ case "unauthorized":
80
+ console.error(chalk.red("hosted upgrade failed: the endpoint rejected this API key (HTTP 401). Nothing was saved."));
81
+ process.exitCode = 1;
82
+ return;
83
+ case "http-error":
84
+ console.error(chalk.red(`hosted upgrade failed: the endpoint returned HTTP ${health.status}. Nothing was saved.`));
85
+ process.exitCode = 1;
86
+ return;
87
+ case "unreachable":
88
+ console.error(chalk.red(`hosted upgrade failed: could not reach ${host} (${health.reason}). Nothing was saved.`));
89
+ process.exitCode = 1;
90
+ return;
91
+ }
92
+ }
93
+ /**
94
+ * Run `compaction status`. Reports local core + whether a hosted endpoint is configured (masked key
95
+ * + host only) + a LIVE reachability re-check. Hosted-tier capabilities are shown ONLY when
96
+ * reachable, phrased as what the tier UNLOCKS (not per-capability liveness). Never prints the key.
97
+ */
98
+ export async function runStatus(opts = {}) {
99
+ const env = opts.env ?? process.env;
100
+ const version = opts.version ?? "0.3.0";
101
+ const target = resolveTarget({ env });
102
+ const configured = target.apiKey !== undefined && !isLocalDefaultUrl(target.url);
103
+ console.log(chalk.cyan("compaction status"));
104
+ console.log(`Local core: enabled (compaction v${version})`);
105
+ if (!configured) {
106
+ console.log("Hosted API: not configured");
107
+ console.log("Mode: local-only");
108
+ console.log("Connect a private-beta / self-hosted endpoint: compaction upgrade --key <key> --api-url <url>");
109
+ return;
110
+ }
111
+ const host = urlHost(target.url);
112
+ console.log(`Hosted API: configured — ${host} · key ${maskKey(target.apiKey)}`);
113
+ const health = await healthCheck(target);
114
+ switch (health.kind) {
115
+ case "ok":
116
+ console.log(" Reachability: reachable");
117
+ break;
118
+ case "unauthorized":
119
+ console.log(" Reachability: unreachable (endpoint returned HTTP 401 — key may be invalid)");
120
+ break;
121
+ case "http-error":
122
+ console.log(` Reachability: unreachable (endpoint returned HTTP ${health.status})`);
123
+ break;
124
+ case "unreachable":
125
+ console.log(` Reachability: unreachable (${health.reason})`);
126
+ break;
127
+ }
128
+ console.log("Mode: private beta (hosted)");
129
+ if (health.kind === "ok") {
130
+ console.log("Capabilities (unlocked by the hosted tier): hosted optimize · hosted evaluate · report sync · " +
131
+ "policy-managed auto-apply — unlocked by the hosted tier (not individually confirmed live here).");
132
+ }
133
+ }
134
+ export function registerUpgradeCommand(program) {
135
+ program
136
+ .command("upgrade")
137
+ .description("Connect the CLI to a private-beta / self-hosted Compaction endpoint (validate + save an API key).")
138
+ .option("--key <key>", "API key for the hosted endpoint (or set COMPACTION_API_KEY)")
139
+ .option("--api-url <url>", "Hosted endpoint base URL — advanced (or set COMPACTION_API_URL)")
140
+ .action(async (options) => {
141
+ await runUpgrade({ key: options.key, apiUrl: options.apiUrl });
142
+ });
143
+ }
144
+ export function registerStatusCommand(program) {
145
+ program
146
+ .command("status")
147
+ .description("Show local core status and whether a hosted endpoint is configured + reachable.")
148
+ .action(async () => {
149
+ await runStatus({ version: program.version() ?? "0.3.0" });
150
+ });
151
+ }
152
+ //# sourceMappingURL=upgrade-status.js.map
@@ -0,0 +1,13 @@
1
+ export type CursorExportRead = {
2
+ ok: true;
3
+ rawOutput: string;
4
+ } | {
5
+ ok: false;
6
+ errorLines: string[];
7
+ };
8
+ /**
9
+ * Read a saved Cursor headless output for `--export`. On failure returns honest, actionable
10
+ * error lines for the caller to print (stderr) alongside `process.exitCode = 1` — never a throw,
11
+ * never a stack trace, never a fabricated capture.
12
+ */
13
+ export declare function readCursorExportFile(exportPath: string): Promise<CursorExportRead>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Honest `--export <file>` read for the Cursor commands (`capture cursor` / `run cursor`).
3
+ *
4
+ * Before this helper, an unreadable export surfaced as a raw Node errno line (e.g.
5
+ * "ENOENT: no such file or directory, open '…'") with no guidance. This wraps the read so a
6
+ * missing / unreadable / directory / too-large export produces an ACTIONABLE, honest error:
7
+ * what failed, on which path, and what a valid Cursor export is — then exit code 1.
8
+ *
9
+ * Honesty rails (unchanged by this helper): a failed read NEVER fabricates a trace or token
10
+ * counts — nothing is written, nothing is counted, no silent zeros. Tokens on the Cursor path
11
+ * remain LOCAL-ESTIMATE / unavailable-with-reason only (Cursor emits no provider usage).
12
+ */
13
+ import { readFile } from "node:fs/promises";
14
+ /** One-line errno-ish reason without the raw stack/prefix noise. */
15
+ function readFailureReason(error) {
16
+ const code = error?.code;
17
+ switch (code) {
18
+ case "ENOENT":
19
+ return "file not found";
20
+ case "EACCES":
21
+ case "EPERM":
22
+ return "permission denied";
23
+ case "EISDIR":
24
+ return "path is a directory, not a file";
25
+ case "EFBIG":
26
+ case "ERR_FS_FILE_TOO_LARGE":
27
+ return "file is too large to read into memory";
28
+ default:
29
+ return error instanceof Error ? error.message : String(error);
30
+ }
31
+ }
32
+ /**
33
+ * Read a saved Cursor headless output for `--export`. On failure returns honest, actionable
34
+ * error lines for the caller to print (stderr) alongside `process.exitCode = 1` — never a throw,
35
+ * never a stack trace, never a fabricated capture.
36
+ */
37
+ export async function readCursorExportFile(exportPath) {
38
+ try {
39
+ return { ok: true, rawOutput: await readFile(exportPath, "utf8") };
40
+ }
41
+ catch (error) {
42
+ return {
43
+ ok: false,
44
+ errorLines: [
45
+ `error: could not read --export file '${exportPath}': ${readFailureReason(error)}.`,
46
+ " Expected a saved Cursor headless output file: the stdout of `cursor agent -p \"…\" --output-format json` (or stream-json).",
47
+ " Nothing was captured or counted (no artifacts written). Fix the path/file and retry,",
48
+ " or run the LIVE path instead (preflight included): compaction run cursor --out <dir> -- cursor agent -p \"…\" --output-format json"
49
+ ]
50
+ };
51
+ }
52
+ }
53
+ //# sourceMappingURL=cursor-export-read.js.map
@@ -0,0 +1,19 @@
1
+ import { type CursorPreflightReport } from "../core/cursor-preflight.js";
2
+ /**
3
+ * Run the preflight (safe probes) and return the report. Never throws.
4
+ *
5
+ * `COMPACTION_CURSOR_BIN` (optional): when set to a non-empty path, it is used as the SOLE candidate the
6
+ * safe probe checks — instead of the default PATH names + the hardcoded macOS app path. This is (a) a real
7
+ * operator override for a non-standard Cursor CLI location, and (b) what makes this path deterministic in
8
+ * tests: pointing it at a non-existent binary makes the probe fail fast (ENOENT) so the CLI never spawns
9
+ * the real, possibly-hanging Cursor binary. Still a `--help`/`-v` capability probe only — never `-p`,
10
+ * never `login`, never a key read.
11
+ */
12
+ export declare function cursorLivePreflight(): Promise<CursorPreflightReport>;
13
+ /**
14
+ * Print the preflight guidance and decide whether the live run may proceed.
15
+ * - CLI not resolvable OR capability absent → print guidance, return false (do NOT spawn a live run).
16
+ * - CLI present + capable → print the auth reminder, return true (proceed; auth verified only by a real
17
+ * run the operator has authorized).
18
+ */
19
+ export declare function gateCursorLiveRun(): Promise<boolean>;