@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.
- package/README.md +51 -12
- package/dist/cli/commands/activity.d.ts +8 -0
- package/dist/cli/commands/activity.js +35 -0
- package/dist/cli/commands/apply-context.js +68 -0
- package/dist/cli/commands/billing-delta.js +12 -1
- package/dist/cli/commands/capture-claude-code.d.ts +44 -0
- package/dist/cli/commands/capture-claude-code.js +206 -0
- package/dist/cli/commands/capture.js +283 -1
- package/dist/cli/commands/compact.js +24 -11
- package/dist/cli/commands/context.d.ts +2 -0
- package/dist/cli/commands/context.js +130 -0
- package/dist/cli/commands/dev.d.ts +17 -0
- package/dist/cli/commands/dev.js +131 -0
- package/dist/cli/commands/gateway.d.ts +31 -0
- package/dist/cli/commands/gateway.js +312 -0
- package/dist/cli/commands/hooks.d.ts +5 -0
- package/dist/cli/commands/hooks.js +181 -0
- package/dist/cli/commands/import.js +3 -1
- package/dist/cli/commands/init.js +422 -17
- package/dist/cli/commands/input-compaction-ab.d.ts +2 -0
- package/dist/cli/commands/input-compaction-ab.js +125 -0
- package/dist/cli/commands/optimize-hosted.d.ts +54 -0
- package/dist/cli/commands/optimize-hosted.js +123 -0
- package/dist/cli/commands/optimize.js +8 -0
- package/dist/cli/commands/output-shaping-ab.d.ts +2 -0
- package/dist/cli/commands/output-shaping-ab.js +132 -0
- package/dist/cli/commands/output-shaping.d.ts +7 -0
- package/dist/cli/commands/output-shaping.js +43 -0
- package/dist/cli/commands/policies.d.ts +2 -0
- package/dist/cli/commands/policies.js +80 -0
- package/dist/cli/commands/precall.d.ts +19 -0
- package/dist/cli/commands/precall.js +436 -0
- package/dist/cli/commands/recommend.js +1 -1
- package/dist/cli/commands/run.js +310 -18
- package/dist/cli/commands/upgrade-status.d.ts +42 -0
- package/dist/cli/commands/upgrade-status.js +152 -0
- package/dist/cli/cursor-export-read.d.ts +13 -0
- package/dist/cli/cursor-export-read.js +53 -0
- package/dist/cli/cursor-live-preflight.d.ts +19 -0
- package/dist/cli/cursor-live-preflight.js +46 -0
- package/dist/cli/index.js +39 -2
- package/dist/cli/onboarding/GatewayTui.d.ts +33 -0
- package/dist/cli/onboarding/GatewayTui.js +113 -0
- package/dist/cli/onboarding/model.d.ts +32 -1
- package/dist/cli/onboarding/model.js +27 -2
- package/dist/cli/terminal-logo.d.ts +27 -0
- package/dist/cli/terminal-logo.js +49 -0
- package/dist/core/activity-event.d.ts +107 -0
- package/dist/core/activity-event.js +182 -0
- package/dist/core/activity-store.d.ts +63 -0
- package/dist/core/activity-store.js +254 -0
- package/dist/core/activity-view.d.ts +76 -0
- package/dist/core/activity-view.js +120 -0
- package/dist/core/api-client/index.d.ts +3 -1
- package/dist/core/api-client/index.js +4 -0
- package/dist/core/api-client/payload.d.ts +5 -1
- package/dist/core/api-client/payload.js +8 -6
- package/dist/core/api-client/persisted-config.d.ts +57 -0
- package/dist/core/api-client/persisted-config.js +156 -0
- package/dist/core/api-client/tool.d.ts +29 -0
- package/dist/core/api-client/tool.js +50 -0
- package/dist/core/api-client/types.d.ts +15 -4
- package/dist/core/auto-apply-ask.d.ts +58 -0
- package/dist/core/auto-apply-ask.js +105 -0
- package/dist/core/auto-apply-gates.d.ts +76 -0
- package/dist/core/auto-apply-gates.js +113 -0
- package/dist/core/before-call-activity.d.ts +35 -0
- package/dist/core/before-call-activity.js +103 -0
- package/dist/core/before-call-recovery.d.ts +19 -0
- package/dist/core/before-call-recovery.js +46 -0
- package/dist/core/before-call-stdin.d.ts +59 -0
- package/dist/core/before-call-stdin.js +78 -0
- package/dist/core/before-call.d.ts +149 -0
- package/dist/core/before-call.js +358 -0
- package/dist/core/billing-delta/billing-delta-record.d.ts +28 -0
- package/dist/core/billing-delta/billing-delta-record.js +26 -0
- package/dist/core/capture-record.d.ts +40 -0
- package/dist/core/capture-record.js +55 -0
- package/dist/core/claude-code-before-call.d.ts +37 -0
- package/dist/core/claude-code-before-call.js +120 -0
- package/dist/core/claude-code-connect.d.ts +31 -0
- package/dist/core/claude-code-connect.js +87 -0
- package/dist/core/claude-code-hook-record.d.ts +71 -0
- package/dist/core/claude-code-hook-record.js +0 -0
- package/dist/core/claude-code-hooks.d.ts +77 -0
- package/dist/core/claude-code-hooks.js +141 -0
- package/dist/core/codex-capture.d.ts +45 -0
- package/dist/core/codex-capture.js +204 -0
- package/dist/core/command-runner.js +5 -1
- package/dist/core/compaction-artifacts.d.ts +51 -0
- package/dist/core/compaction-artifacts.js +242 -0
- package/dist/core/compactor.d.ts +9 -0
- package/dist/core/compactor.js +95 -0
- package/dist/core/context-store-eval-cases.d.ts +6 -0
- package/dist/core/context-store-eval-cases.js +331 -0
- package/dist/core/context-store-eval.d.ts +140 -0
- package/dist/core/context-store-eval.js +138 -0
- package/dist/core/context-store-fs.d.ts +73 -0
- package/dist/core/context-store-fs.js +157 -0
- package/dist/core/context-store-sufficiency.d.ts +98 -0
- package/dist/core/context-store-sufficiency.js +135 -0
- package/dist/core/context-store.d.ts +155 -0
- package/dist/core/context-store.js +228 -0
- package/dist/core/cross-surface-event.d.ts +306 -0
- package/dist/core/cross-surface-event.js +330 -0
- package/dist/core/cursor-capture.d.ts +54 -0
- package/dist/core/cursor-capture.js +215 -0
- package/dist/core/cursor-preflight-probe.d.ts +8 -0
- package/dist/core/cursor-preflight-probe.js +88 -0
- package/dist/core/cursor-preflight.d.ts +86 -0
- package/dist/core/cursor-preflight.js +126 -0
- package/dist/core/gateway/apply-activation.d.ts +39 -0
- package/dist/core/gateway/apply-activation.js +84 -0
- package/dist/core/gateway/apply-policy.d.ts +64 -0
- package/dist/core/gateway/apply-policy.js +221 -0
- package/dist/core/gateway/apply-receipt.d.ts +36 -0
- package/dist/core/gateway/apply-receipt.js +75 -0
- package/dist/core/gateway/cache-proof.d.ts +45 -0
- package/dist/core/gateway/cache-proof.js +65 -0
- package/dist/core/gateway/configure.d.ts +50 -0
- package/dist/core/gateway/configure.js +169 -0
- package/dist/core/gateway/openai-usage.d.ts +56 -0
- package/dist/core/gateway/openai-usage.js +128 -0
- package/dist/core/gateway/receipt.d.ts +138 -0
- package/dist/core/gateway/receipt.js +120 -0
- package/dist/core/gateway/recovery.d.ts +23 -0
- package/dist/core/gateway/recovery.js +68 -0
- package/dist/core/gateway/server.d.ts +51 -0
- package/dist/core/gateway/server.js +276 -0
- package/dist/core/gateway/status.d.ts +45 -0
- package/dist/core/gateway/status.js +109 -0
- package/dist/core/hook-usage-aggregate.d.ts +47 -0
- package/dist/core/hook-usage-aggregate.js +161 -0
- package/dist/core/input-compaction-ab.d.ts +111 -0
- package/dist/core/input-compaction-ab.js +158 -0
- package/dist/core/local-run-record.d.ts +109 -0
- package/dist/core/local-run-record.js +223 -0
- package/dist/core/output-shaping-ab.d.ts +140 -0
- package/dist/core/output-shaping-ab.js +146 -0
- package/dist/core/output-shaping-attach.d.ts +31 -0
- package/dist/core/output-shaping-attach.js +57 -0
- package/dist/core/output-shaping.d.ts +56 -0
- package/dist/core/output-shaping.js +89 -0
- package/dist/core/policy-middleware.d.ts +121 -0
- package/dist/core/policy-middleware.js +919 -0
- package/dist/core/policy-preferences.d.ts +99 -0
- package/dist/core/policy-preferences.js +232 -0
- package/dist/core/report-generator.d.ts +19 -1
- package/dist/core/report-generator.js +51 -0
- package/dist/core/run-aggregator.d.ts +56 -1
- package/dist/core/run-aggregator.js +93 -2
- package/dist/core/run-flow-report.d.ts +82 -0
- package/dist/core/run-flow-report.js +71 -0
- package/dist/core/safety-report.js +8 -1
- package/dist/core/shim-capture-bridge.d.ts +32 -0
- package/dist/core/shim-capture-bridge.js +88 -0
- package/dist/core/skill-injection-policy.d.ts +72 -0
- package/dist/core/skill-injection-policy.js +183 -0
- package/dist/core/spend-attribution.js +1 -1
- package/dist/core/token-accounting.d.ts +1 -1
- package/dist/core/tool-shim.d.ts +129 -0
- package/dist/core/tool-shim.js +447 -0
- package/dist/core/trace-adapters.d.ts +8 -0
- package/dist/core/trace-adapters.js +4 -0
- package/dist/core/trace-parser.d.ts +13 -13
- package/dist/core/trace-parser.js +6 -6
- package/dist/core/types.d.ts +45 -2
- package/dist/core/waste-detector.d.ts +20 -0
- package/dist/core/waste-detector.js +160 -6
- package/package.json +1 -1
package/dist/cli/commands/run.js
CHANGED
|
@@ -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("
|
|
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("../../
|
|
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
|
|
23
|
-
const persistedRun = await persistLocalCommandRun(
|
|
24
|
-
const trace = localCommandRunToAgentTrace(
|
|
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
|
-
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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>;
|