@compaction/cli 0.2.0 → 0.3.1
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 +142 -64
- 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 +9 -5
- 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/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 +297 -12
- 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 +36 -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/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/run-aggregator.d.ts +11 -1
- package/dist/core/run-aggregator.js +29 -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-parser.d.ts +13 -13
- package/dist/core/trace-parser.js +6 -6
- package/dist/core/types.d.ts +12 -2
- package/dist/core/waste-detector.d.ts +20 -0
- package/dist/core/waste-detector.js +160 -6
- package/package.json +1 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type UsageMetadata } from "./usage-metadata.js";
|
|
2
|
+
import type { AgentTrace } from "./types.js";
|
|
3
|
+
import { type LocalCommandRun } from "./command-runner.js";
|
|
4
|
+
/**
|
|
5
|
+
* Extract the assistant `result` text + session id from Cursor headless output. Supports the single
|
|
6
|
+
* JSON object (`--output-format json`) and a stream-json stream (scans lines for a `result` object).
|
|
7
|
+
* Returns `outputSeparable: false` when no `result` field can be safely isolated.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseCursorAgentOutput(rawOutput: string): {
|
|
10
|
+
resultText?: string;
|
|
11
|
+
sessionId?: string;
|
|
12
|
+
outputSeparable: boolean;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Best-effort prompt extraction from the wrapped Cursor invocation: the non-flag positional args,
|
|
16
|
+
* excluding the executable + known subcommand keywords. Used ONLY for a local-estimate of input tokens
|
|
17
|
+
* (content-free count of text WE passed). Returns undefined when no prompt can be identified.
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractCursorPrompt(commandParts: string[]): string | undefined;
|
|
20
|
+
export interface CursorNormalizationResult {
|
|
21
|
+
trace: AgentTrace;
|
|
22
|
+
usageMetadata: UsageMetadata;
|
|
23
|
+
/** "present" when a prompt was identified (input locally estimated from it), else "unavailable". */
|
|
24
|
+
inputStatus: "present" | "unavailable";
|
|
25
|
+
/**
|
|
26
|
+
* The TRUE reason input is unavailable (set iff `inputStatus === "unavailable"`). Export-only runs
|
|
27
|
+
* (no invocation declared) get a DIFFERENT reason from declared-but-unidentifiable invocations, so the
|
|
28
|
+
* operator is never told "no prompt in the wrapped invocation" when there was no wrapped invocation.
|
|
29
|
+
*/
|
|
30
|
+
inputUnavailableReason?: string;
|
|
31
|
+
/** "present" when the result field was separable (output estimated), else "unavailable". */
|
|
32
|
+
outputStatus: "present" | "unavailable";
|
|
33
|
+
/**
|
|
34
|
+
* The TRUE reason output is unavailable (set iff `outputStatus === "unavailable"`), so callers print
|
|
35
|
+
* the honest reason (e.g. "the captured output is empty") — never a hardcoded, possibly-wrong one.
|
|
36
|
+
*/
|
|
37
|
+
outputUnavailableReason?: string;
|
|
38
|
+
warnings: string[];
|
|
39
|
+
}
|
|
40
|
+
/** Build an `AgentTrace` + LOCAL-ESTIMATE usage from captured Cursor headless output. */
|
|
41
|
+
export declare function normalizeCursorAgentOutput(params: {
|
|
42
|
+
captureId: string;
|
|
43
|
+
rawOutput: string;
|
|
44
|
+
commandParts?: string[];
|
|
45
|
+
commandRun?: LocalCommandRun;
|
|
46
|
+
generatedAt?: string;
|
|
47
|
+
}): CursorNormalizationResult;
|
|
48
|
+
export interface CursorCaptureResult extends CursorNormalizationResult {
|
|
49
|
+
commandRun?: LocalCommandRun;
|
|
50
|
+
}
|
|
51
|
+
/** Live wrapper: spawn the Cursor headless CLI (no manual export), capture stdout, normalize. */
|
|
52
|
+
export declare function captureCursorCommand(commandParts: string[], generatedAt?: string): Promise<CursorCaptureResult>;
|
|
53
|
+
/** Offline/fallback: normalize a saved Cursor headless output (json / stream-json) string. */
|
|
54
|
+
export declare function captureCursorExport(rawOutput: string, commandParts?: string[], generatedAt?: string): CursorCaptureResult;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor live-wrapper capture (PUBLIC CLI/SDK code — engine-free, ships in the npm package).
|
|
3
|
+
*
|
|
4
|
+
* Wraps a real Cursor **headless CLI agent** run (e.g. `cursor agent -p "<prompt>" --output-format
|
|
5
|
+
* json`) and normalizes its output to an `AgentTrace`. Design: `docs/design/cursor-live-wrapper.md`
|
|
6
|
+
* (accepted 2026-06-28) + the amended honesty matrix in `docs/design/unified-run-flow.md` (founder D2).
|
|
7
|
+
*
|
|
8
|
+
* HONESTY (the defining rails of this module):
|
|
9
|
+
* - **NO provider-reported tokens.** Cursor's CLI emits no usage; tokens here are **LOCAL-ESTIMATE
|
|
10
|
+
* only** (chars/4), never provider-reported, never billing-confirmed.
|
|
11
|
+
* - **Input** is locally estimated from the prompt extracted from the wrapped invocation (where
|
|
12
|
+
* available). **Output** is locally estimated from the headless **`result`** field when the output
|
|
13
|
+
* can be safely separated (`--output-format json`); when it cannot, output is **UNAVAILABLE** (left
|
|
14
|
+
* absent, with a stated reason) — never silently zero, never fabricated.
|
|
15
|
+
* - **NO output-token savings** (that is gated on the output-shaping policy family + eval).
|
|
16
|
+
* - **NO SQLite / private-storage reverse engineering.** Only the wrapped CLI's own stdout is read.
|
|
17
|
+
*
|
|
18
|
+
* Evidence tier: `source: "local_command"` (the same `imported_local` tier as the import path; below
|
|
19
|
+
* `real_captured`). The cross-tool record attributes `tool: "cursor"`, `token_source: local-estimate`.
|
|
20
|
+
*/
|
|
21
|
+
import { CURRENT_AGENT_TRACE_ARTIFACT_VERSION } from "./trace-parser.js";
|
|
22
|
+
import { createUsageMetadata } from "./usage-metadata.js";
|
|
23
|
+
import { estimateTextTokens } from "./token-estimator.js";
|
|
24
|
+
import { executeLocalCommand, parseRunCommand } from "./command-runner.js";
|
|
25
|
+
function toRecord(value) {
|
|
26
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
27
|
+
}
|
|
28
|
+
function stringValue(value) {
|
|
29
|
+
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Extract the assistant `result` text + session id from Cursor headless output. Supports the single
|
|
33
|
+
* JSON object (`--output-format json`) and a stream-json stream (scans lines for a `result` object).
|
|
34
|
+
* Returns `outputSeparable: false` when no `result` field can be safely isolated.
|
|
35
|
+
*/
|
|
36
|
+
export function parseCursorAgentOutput(rawOutput) {
|
|
37
|
+
// A saved export may carry a UTF-8 BOM (e.g. written on Windows). Strip it so a real, parseable
|
|
38
|
+
// `result` object is not silently mislabeled "unavailable" — the label must match the evidence.
|
|
39
|
+
const trimmed = rawOutput.replace(/^\uFEFF/, "").trim();
|
|
40
|
+
// Case 1: a single JSON result object.
|
|
41
|
+
const whole = (() => {
|
|
42
|
+
try {
|
|
43
|
+
return toRecord(JSON.parse(trimmed));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
})();
|
|
49
|
+
if (whole) {
|
|
50
|
+
const resultText = stringValue(whole.result);
|
|
51
|
+
return { resultText, sessionId: stringValue(whole.session_id), outputSeparable: resultText !== undefined };
|
|
52
|
+
}
|
|
53
|
+
// Case 2: stream-json — scan lines for a `result` object (last one wins) + a session id.
|
|
54
|
+
let resultText;
|
|
55
|
+
let sessionId;
|
|
56
|
+
for (const line of trimmed.split(/\r?\n/)) {
|
|
57
|
+
const t = line.trim();
|
|
58
|
+
if (!t.startsWith("{") || !t.endsWith("}"))
|
|
59
|
+
continue;
|
|
60
|
+
let rec;
|
|
61
|
+
try {
|
|
62
|
+
rec = toRecord(JSON.parse(t));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!rec)
|
|
68
|
+
continue;
|
|
69
|
+
sessionId = sessionId ?? stringValue(rec.session_id);
|
|
70
|
+
const r = stringValue(rec.result);
|
|
71
|
+
if (r !== undefined)
|
|
72
|
+
resultText = r;
|
|
73
|
+
}
|
|
74
|
+
return { resultText, sessionId, outputSeparable: resultText !== undefined };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Best-effort prompt extraction from the wrapped Cursor invocation: the non-flag positional args,
|
|
78
|
+
* excluding the executable + known subcommand keywords. Used ONLY for a local-estimate of input tokens
|
|
79
|
+
* (content-free count of text WE passed). Returns undefined when no prompt can be identified.
|
|
80
|
+
*/
|
|
81
|
+
export function extractCursorPrompt(commandParts) {
|
|
82
|
+
const KEYWORDS = new Set(["cursor", "cursor-agent", "agent", "chat", "exec", "run"]);
|
|
83
|
+
// Boolean flags take no value, so the token after them is still a positional (e.g. the prompt).
|
|
84
|
+
// (Cursor selects machine output via `--output-format json` — a value-taking flag, handled below —
|
|
85
|
+
// so a bare `--json` boolean is intentionally NOT assumed here.)
|
|
86
|
+
const BOOLEAN_FLAGS = new Set(["-p", "--print", "--force"]);
|
|
87
|
+
const positionals = [];
|
|
88
|
+
let skipNext = false;
|
|
89
|
+
for (let i = 1; i < commandParts.length; i++) {
|
|
90
|
+
const part = commandParts[i];
|
|
91
|
+
if (skipNext) {
|
|
92
|
+
skipNext = false; // the value of a preceding value-taking flag (e.g. `--output-format json`)
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (part.startsWith("-")) {
|
|
96
|
+
if (!BOOLEAN_FLAGS.has(part) && !part.includes("="))
|
|
97
|
+
skipNext = true;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (KEYWORDS.has(part))
|
|
101
|
+
continue;
|
|
102
|
+
positionals.push(part);
|
|
103
|
+
}
|
|
104
|
+
const prompt = positionals.join(" ").trim();
|
|
105
|
+
return prompt === "" ? undefined : prompt;
|
|
106
|
+
}
|
|
107
|
+
function addMessage(messages, role, content, timestamp, metadata) {
|
|
108
|
+
messages.push({ id: `cursor_msg_${messages.length + 1}`, role, content, timestamp, metadata });
|
|
109
|
+
}
|
|
110
|
+
const CURSOR_LOCAL_ESTIMATE_NOTE = "Cursor CLI emits no provider usage; tokens are LOCAL-ESTIMATE only (chars/4) — never provider-reported, never billing-confirmed.";
|
|
111
|
+
/** Build an `AgentTrace` + LOCAL-ESTIMATE usage from captured Cursor headless output. */
|
|
112
|
+
export function normalizeCursorAgentOutput(params) {
|
|
113
|
+
const generatedAt = params.generatedAt ?? new Date().toISOString();
|
|
114
|
+
const startedAt = params.commandRun?.startedAt ?? generatedAt;
|
|
115
|
+
const { resultText, sessionId, outputSeparable } = parseCursorAgentOutput(params.rawOutput);
|
|
116
|
+
const prompt = params.commandParts ? extractCursorPrompt(params.commandParts) : undefined;
|
|
117
|
+
const warnings = [];
|
|
118
|
+
const messages = [];
|
|
119
|
+
addMessage(messages, "system", "Cursor headless CLI capture (command wrapper).", startedAt, {
|
|
120
|
+
integration: "cursor",
|
|
121
|
+
captureMode: "command wrapper"
|
|
122
|
+
});
|
|
123
|
+
if (prompt)
|
|
124
|
+
addMessage(messages, "user", prompt, startedAt, { eventType: "prompt", tokenSource: "local-estimate" });
|
|
125
|
+
if (resultText)
|
|
126
|
+
addMessage(messages, "assistant", resultText, generatedAt, { eventType: "result", tokenSource: "local-estimate" });
|
|
127
|
+
// Input: local-estimate from the prompt where available. Output: local-estimate from the separable
|
|
128
|
+
// result field, else UNAVAILABLE (absent + a stated reason). Never provider-reported, never fabricated.
|
|
129
|
+
const inputTokens = prompt ? estimateTextTokens(prompt) : undefined;
|
|
130
|
+
const outputTokens = resultText ? estimateTextTokens(resultText) : undefined;
|
|
131
|
+
// The TRUE per-run unavailability reason: an EMPTY export/captured output is a different failure from
|
|
132
|
+
// present-but-not-separable output, and the guidance differs (`--output-format json` cannot fix an
|
|
133
|
+
// empty file). Stated exactly so the operator is never sent down the wrong path.
|
|
134
|
+
const outputUnavailableReason = outputSeparable
|
|
135
|
+
? undefined
|
|
136
|
+
: params.rawOutput.trim() === ""
|
|
137
|
+
? "the Cursor headless output is empty (no output was captured/saved — nothing to count)"
|
|
138
|
+
: "no separable `result` field in the Cursor headless output (use `--output-format json`)";
|
|
139
|
+
// The TRUE per-run reason input is unavailable. Export-only (no invocation declared at all) is a
|
|
140
|
+
// DIFFERENT situation from a declared invocation in which no prompt could be identified — the reason
|
|
141
|
+
// and the fix differ, so both are stated precisely (never a bare "unknown", never a fabricated count).
|
|
142
|
+
// The documented Cursor headless output (json / stream-json) carries result/session_id/duration —
|
|
143
|
+
// NOT the prompt — so input can never be derived from the export itself without format-guessing.
|
|
144
|
+
const inputUnavailableReason = prompt
|
|
145
|
+
? undefined
|
|
146
|
+
: params.commandParts === undefined
|
|
147
|
+
? "a saved Cursor export does not contain the prompt (the documented headless output carries the result, not the user input), and no invocation was declared after --"
|
|
148
|
+
: "no prompt could be identified in the wrapped invocation";
|
|
149
|
+
if (inputUnavailableReason) {
|
|
150
|
+
warnings.push(`Input tokens UNAVAILABLE: ${inputUnavailableReason}.`, params.commandParts === undefined
|
|
151
|
+
? 'To get a LOCAL-ESTIMATE input count (chars/4 of the prompt YOU declare — never provider-reported), re-declare the original invocation after --: compaction run cursor --out <dir> --export <file> -- cursor agent -p "<your prompt>" --output-format json (same for capture cursor).'
|
|
152
|
+
: 'To get a LOCAL-ESTIMATE input count, pass the prompt in the invocation (e.g. cursor agent -p "<your prompt>" --output-format json).');
|
|
153
|
+
}
|
|
154
|
+
if (outputUnavailableReason) {
|
|
155
|
+
warnings.push(`Output tokens UNAVAILABLE: ${outputUnavailableReason}. ` +
|
|
156
|
+
"Provider-reported output is unavailable (Cursor emits no usage).");
|
|
157
|
+
}
|
|
158
|
+
const limitations = [CURSOR_LOCAL_ESTIMATE_NOTE];
|
|
159
|
+
// Both per-axis unavailability reasons ride in limitations so the SAME reason reaches every surface
|
|
160
|
+
// that renders this capture (CLI stdout notes, the local run record's notes, the summary rollup).
|
|
161
|
+
if (inputUnavailableReason)
|
|
162
|
+
limitations.push(`Input tokens are unavailable for this run (${inputUnavailableReason}).`);
|
|
163
|
+
if (outputUnavailableReason)
|
|
164
|
+
limitations.push(`Output tokens are unavailable for this run (${outputUnavailableReason}).`);
|
|
165
|
+
const usageMetadata = createUsageMetadata({
|
|
166
|
+
...(inputTokens !== undefined ? { inputTokens } : {}),
|
|
167
|
+
...(outputTokens !== undefined ? { outputTokens } : {}),
|
|
168
|
+
providerReportedTokens: false,
|
|
169
|
+
estimatedTokens: true,
|
|
170
|
+
provider: "cursor",
|
|
171
|
+
limitations
|
|
172
|
+
});
|
|
173
|
+
const trace = {
|
|
174
|
+
id: sessionId ?? `trace_cursor_${params.captureId}`,
|
|
175
|
+
title: "Cursor headless captured trace",
|
|
176
|
+
artifactVersion: CURRENT_AGENT_TRACE_ARTIFACT_VERSION,
|
|
177
|
+
source: "local_command",
|
|
178
|
+
createdAt: startedAt,
|
|
179
|
+
generatedAt,
|
|
180
|
+
model: "cursor-unknown-model",
|
|
181
|
+
command: params.commandRun
|
|
182
|
+
? { command: params.commandRun.command.executable, args: params.commandRun.command.args, cwd: process.cwd() }
|
|
183
|
+
: { command: "cursor-export", args: [], cwd: process.cwd() },
|
|
184
|
+
durationMs: params.commandRun?.durationMs ?? 0,
|
|
185
|
+
exitCode: params.commandRun?.exitCode ?? 0,
|
|
186
|
+
messages
|
|
187
|
+
};
|
|
188
|
+
return {
|
|
189
|
+
trace,
|
|
190
|
+
usageMetadata,
|
|
191
|
+
inputStatus: prompt ? "present" : "unavailable",
|
|
192
|
+
...(inputUnavailableReason !== undefined ? { inputUnavailableReason } : {}),
|
|
193
|
+
outputStatus: outputSeparable ? "present" : "unavailable",
|
|
194
|
+
...(outputUnavailableReason !== undefined ? { outputUnavailableReason } : {}),
|
|
195
|
+
warnings
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/** Live wrapper: spawn the Cursor headless CLI (no manual export), capture stdout, normalize. */
|
|
199
|
+
export async function captureCursorCommand(commandParts, generatedAt) {
|
|
200
|
+
const parsed = parseRunCommand(commandParts);
|
|
201
|
+
const commandRun = await executeLocalCommand(parsed);
|
|
202
|
+
const result = normalizeCursorAgentOutput({
|
|
203
|
+
captureId: `${parsed.executable}-${commandRun.startedAt}`,
|
|
204
|
+
rawOutput: commandRun.rawOutput,
|
|
205
|
+
commandParts,
|
|
206
|
+
commandRun,
|
|
207
|
+
generatedAt
|
|
208
|
+
});
|
|
209
|
+
return { ...result, commandRun };
|
|
210
|
+
}
|
|
211
|
+
/** Offline/fallback: normalize a saved Cursor headless output (json / stream-json) string. */
|
|
212
|
+
export function captureCursorExport(rawOutput, commandParts, generatedAt) {
|
|
213
|
+
return normalizeCursorAgentOutput({ captureId: "cursor-export", rawOutput, commandParts, generatedAt });
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=cursor-capture.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CursorProbe } from "./cursor-preflight.js";
|
|
2
|
+
/** True when EVERY arg is a recognized safe help/version token (no prompt, no `-p`, no `login`). */
|
|
3
|
+
export declare function isSafeProbeArgs(args: string[]): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Create the real SAFE probe. The returned probe spawns `<candidate> <args>` ONLY when `args` is a
|
|
6
|
+
* recognized safe help probe; otherwise it refuses (returns `resolved:false` without spawning).
|
|
7
|
+
*/
|
|
8
|
+
export declare function createSafeCursorProbe(timeoutMs?: number): CursorProbe;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SAFE real probe adapter for the Cursor live-prep preflight (`cursor-preflight.ts`).
|
|
3
|
+
*
|
|
4
|
+
* This is the ONLY place the preflight spawns a child process, and it spawns ONLY a `--help`/`-v`-style
|
|
5
|
+
* capability probe. Defense-in-depth: it hard-rejects any arg set that is not a recognized safe help
|
|
6
|
+
* probe, so it can NEVER be coaxed into a real `-p` prompt, `cursor agent login`, or a credential read.
|
|
7
|
+
*
|
|
8
|
+
* - No stdin (closed): a probe never waits for input.
|
|
9
|
+
* - stdout/stderr captured only to inspect for the headless flags in --help text (content-free).
|
|
10
|
+
* - CURSOR_API_KEY is never read, set, or forwarded specially — the probe inherits the caller's env
|
|
11
|
+
* unchanged (we never touch the key), and never prints env values.
|
|
12
|
+
* - Fail-friendly: an ENOENT / spawn error resolves to `resolved:false`, never a throw.
|
|
13
|
+
*/
|
|
14
|
+
import { spawn } from "node:child_process";
|
|
15
|
+
/** Only these tokens may appear in a probe arg list. Anything else is refused (never spawned). */
|
|
16
|
+
const SAFE_PROBE_TOKENS = new Set(["agent", "--help", "-h", "--version", "-v"]);
|
|
17
|
+
/** True when EVERY arg is a recognized safe help/version token (no prompt, no `-p`, no `login`). */
|
|
18
|
+
export function isSafeProbeArgs(args) {
|
|
19
|
+
if (args.length === 0)
|
|
20
|
+
return false;
|
|
21
|
+
return args.every((a) => SAFE_PROBE_TOKENS.has(a));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Create the real SAFE probe. The returned probe spawns `<candidate> <args>` ONLY when `args` is a
|
|
25
|
+
* recognized safe help probe; otherwise it refuses (returns `resolved:false` without spawning).
|
|
26
|
+
*/
|
|
27
|
+
export function createSafeCursorProbe(timeoutMs = 5000) {
|
|
28
|
+
return (candidate, args) => {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
const notRun = (helpText) => ({ candidate, resolved: false, helpText, exitCode: null });
|
|
31
|
+
// Defense-in-depth: refuse anything that is not a pure help/version probe.
|
|
32
|
+
if (!isSafeProbeArgs(args)) {
|
|
33
|
+
resolve(notRun("refused: non-help probe args (safety guard)"));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let helpText = "";
|
|
37
|
+
let settled = false;
|
|
38
|
+
let child;
|
|
39
|
+
try {
|
|
40
|
+
child = spawn(candidate, args, {
|
|
41
|
+
shell: false,
|
|
42
|
+
windowsHide: true,
|
|
43
|
+
// Closed stdin so a probe never blocks waiting for input; capture stdout/stderr to read --help.
|
|
44
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
resolve(notRun(""));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const finish = (result) => {
|
|
52
|
+
if (settled)
|
|
53
|
+
return;
|
|
54
|
+
settled = true;
|
|
55
|
+
resolve(result);
|
|
56
|
+
};
|
|
57
|
+
const timer = setTimeout(() => {
|
|
58
|
+
try {
|
|
59
|
+
child.kill("SIGKILL");
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
/* ignore */
|
|
63
|
+
}
|
|
64
|
+
// A hung help probe: treat as resolved (it spawned) but with whatever text we saw.
|
|
65
|
+
finish({ candidate, resolved: true, helpText, exitCode: null });
|
|
66
|
+
}, timeoutMs);
|
|
67
|
+
timer.unref?.();
|
|
68
|
+
child.stdout?.setEncoding("utf8");
|
|
69
|
+
child.stderr?.setEncoding("utf8");
|
|
70
|
+
child.stdout?.on("data", (c) => {
|
|
71
|
+
helpText += c;
|
|
72
|
+
});
|
|
73
|
+
child.stderr?.on("data", (c) => {
|
|
74
|
+
helpText += c;
|
|
75
|
+
});
|
|
76
|
+
child.on("error", () => {
|
|
77
|
+
// ENOENT / not executable → the binary is not resolvable. Fail-friendly, never throw.
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
finish(notRun(""));
|
|
80
|
+
});
|
|
81
|
+
child.on("close", (exitCode) => {
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
finish({ candidate, resolved: true, helpText, exitCode: exitCode ?? null });
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=cursor-preflight-probe.js.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor LIVE-PREP preflight/diagnostics (PUBLIC CLI code — engine-free, ships in the npm package).
|
|
3
|
+
*
|
|
4
|
+
* A content-free readiness check surfaced when a LIVE `run cursor` / `capture cursor` is requested
|
|
5
|
+
* (no `--export`). It answers two questions and prints exact actionable guidance so a live Cursor run
|
|
6
|
+
* validates cleanly once the operator authenticates — WITHOUT this tool ever authenticating or spending:
|
|
7
|
+
*
|
|
8
|
+
* 1. **CLI resolvable?** Is a Cursor headless CLI binary on PATH (`cursor` / `cursor-agent`) or at a
|
|
9
|
+
* known macOS app path (`/Applications/Cursor.app/Contents/Resources/app/bin/cursor`)?
|
|
10
|
+
* 2. **Capability present?** Does `<cli> agent --help` (or `--help`) advertise the headless flags we
|
|
11
|
+
* need (`-p`/`--print` + `--output-format`)?
|
|
12
|
+
*
|
|
13
|
+
* HARD SAFETY RAILS (the defining constraints of this module):
|
|
14
|
+
* - **SAFE probes only.** Probing is limited to `--help` / `-v` / `--version` style invocations. This
|
|
15
|
+
* module NEVER runs a real `-p` prompt, NEVER runs `cursor agent login`, and NEVER reads, sets, or
|
|
16
|
+
* prints `CURSOR_API_KEY`.
|
|
17
|
+
* - **Auth-state is NOT detected.** Cursor's auth state cannot be observed without a real (spending)
|
|
18
|
+
* call, so this module detects **CLI-presence + capability only** and tells the operator to run
|
|
19
|
+
* `cursor agent login` (or set `CURSOR_API_KEY`) and retry. It never guesses "authenticated".
|
|
20
|
+
* - **Fail friendly.** A probe that errors/crashes is treated as "not detectable", never a thrown
|
|
21
|
+
* exception — the pure logic here is total over its synthetic inputs.
|
|
22
|
+
* - **Content-free.** No prompt, no output, no credential value is ever read or emitted.
|
|
23
|
+
*
|
|
24
|
+
* Honesty binding (unchanged): Cursor = LOCAL-ESTIMATE only; provider-reported UNAVAILABLE; never a
|
|
25
|
+
* savings claim. This module surfaces none of those figures — it is a readiness check + guidance only.
|
|
26
|
+
*
|
|
27
|
+
* Design: `docs/design/cursor-live-wrapper.md` (Verify-first precondition V) + the honesty rails in
|
|
28
|
+
* `docs/ops/cursor-support-matrix.md`.
|
|
29
|
+
*/
|
|
30
|
+
/** Candidate CLI locations, in probe order. Kept as data so the pure logic is fully testable. */
|
|
31
|
+
export declare const CURSOR_CLI_PATH_CANDIDATES: readonly ["cursor-agent", "cursor"];
|
|
32
|
+
export declare const CURSOR_CLI_KNOWN_MACOS_PATH = "/Applications/Cursor.app/Contents/Resources/app/bin/cursor";
|
|
33
|
+
/** The exact command an operator runs once authed (surfaced verbatim in the guidance). */
|
|
34
|
+
export declare const CURSOR_EXAMPLE_RUN_COMMAND = "compaction run cursor --out <dir> -- cursor agent -p \"<your prompt>\" --output-format json";
|
|
35
|
+
/**
|
|
36
|
+
* Result of a single SAFE probe of one candidate binary. `helpText` comes from a `--help`/`-v`-style
|
|
37
|
+
* invocation only. `resolved` is false when the binary could not be spawned at all (ENOENT / not
|
|
38
|
+
* executable). This is the ONLY input the pure classifier consumes — so tests can feed synthetic probe
|
|
39
|
+
* outputs with no live `cursor agent` call.
|
|
40
|
+
*/
|
|
41
|
+
export interface CursorProbeResult {
|
|
42
|
+
/** The candidate that was probed (a PATH name or an absolute path). */
|
|
43
|
+
candidate: string;
|
|
44
|
+
/** True when the binary spawned (even if it exited non-zero); false on ENOENT / spawn failure. */
|
|
45
|
+
resolved: boolean;
|
|
46
|
+
/** Combined help/version text observed from the SAFE probe (stdout + stderr). Content-free (help text). */
|
|
47
|
+
helpText: string;
|
|
48
|
+
/** Exit code of the SAFE probe, or null if it never ran. */
|
|
49
|
+
exitCode: number | null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A probe function: given a candidate + safe args, returns what the SAFE probe observed. Injected so
|
|
53
|
+
* the classifier is pure/testable; the real adapter (`createSafeCursorProbe`) spawns `--help`/`-v` only.
|
|
54
|
+
*/
|
|
55
|
+
export type CursorProbe = (candidate: string, args: string[]) => Promise<CursorProbeResult>;
|
|
56
|
+
export interface CursorPreflightReport {
|
|
57
|
+
/** Whether a Cursor CLI binary could be resolved (PATH or known macOS app path). */
|
|
58
|
+
cliResolvable: boolean;
|
|
59
|
+
/** The resolved binary path/name, if any. */
|
|
60
|
+
resolvedCli?: string;
|
|
61
|
+
/** Whether the resolved CLI advertises the headless capability (`-p`/`--print` + `--output-format`). */
|
|
62
|
+
capabilityPresent: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Auth is intentionally NEVER probed (would require a real, spending call). Always "not-checked" so
|
|
65
|
+
* no caller can mistake this for an "authenticated" signal.
|
|
66
|
+
*/
|
|
67
|
+
authState: "not-checked";
|
|
68
|
+
/** Overall readiness of the LOCAL PREP surface: ready = CLI resolvable AND capability present. */
|
|
69
|
+
ready: boolean;
|
|
70
|
+
/** Human-readable, content-free diagnostic + guidance lines (safe to print to stdout). */
|
|
71
|
+
guidance: string[];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* PURE classifier: given the SAFE probe results for the CLI candidates, decide resolvable/capable and
|
|
75
|
+
* build the exact guidance. No I/O, no spawning, no env reads — this is what the tests target directly.
|
|
76
|
+
*
|
|
77
|
+
* A candidate is "capable" when its help text advertises BOTH `-p`/`--print` AND `--output-format`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function classifyCursorPreflight(probes: CursorProbeResult[]): CursorPreflightReport;
|
|
80
|
+
/**
|
|
81
|
+
* Run the preflight using an injectable probe. Tries each candidate with a SAFE capability probe
|
|
82
|
+
* (`agent --help`), then classifies. Pure over `probe` — the default real adapter
|
|
83
|
+
* (`createSafeCursorProbe`) is the only place a child process is spawned, and only with `--help`-style
|
|
84
|
+
* args. The candidate order puts the known macOS app path LAST as a fallback when PATH names miss.
|
|
85
|
+
*/
|
|
86
|
+
export declare function runCursorPreflight(probe: CursorProbe, candidates?: readonly string[]): Promise<CursorPreflightReport>;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor LIVE-PREP preflight/diagnostics (PUBLIC CLI code — engine-free, ships in the npm package).
|
|
3
|
+
*
|
|
4
|
+
* A content-free readiness check surfaced when a LIVE `run cursor` / `capture cursor` is requested
|
|
5
|
+
* (no `--export`). It answers two questions and prints exact actionable guidance so a live Cursor run
|
|
6
|
+
* validates cleanly once the operator authenticates — WITHOUT this tool ever authenticating or spending:
|
|
7
|
+
*
|
|
8
|
+
* 1. **CLI resolvable?** Is a Cursor headless CLI binary on PATH (`cursor` / `cursor-agent`) or at a
|
|
9
|
+
* known macOS app path (`/Applications/Cursor.app/Contents/Resources/app/bin/cursor`)?
|
|
10
|
+
* 2. **Capability present?** Does `<cli> agent --help` (or `--help`) advertise the headless flags we
|
|
11
|
+
* need (`-p`/`--print` + `--output-format`)?
|
|
12
|
+
*
|
|
13
|
+
* HARD SAFETY RAILS (the defining constraints of this module):
|
|
14
|
+
* - **SAFE probes only.** Probing is limited to `--help` / `-v` / `--version` style invocations. This
|
|
15
|
+
* module NEVER runs a real `-p` prompt, NEVER runs `cursor agent login`, and NEVER reads, sets, or
|
|
16
|
+
* prints `CURSOR_API_KEY`.
|
|
17
|
+
* - **Auth-state is NOT detected.** Cursor's auth state cannot be observed without a real (spending)
|
|
18
|
+
* call, so this module detects **CLI-presence + capability only** and tells the operator to run
|
|
19
|
+
* `cursor agent login` (or set `CURSOR_API_KEY`) and retry. It never guesses "authenticated".
|
|
20
|
+
* - **Fail friendly.** A probe that errors/crashes is treated as "not detectable", never a thrown
|
|
21
|
+
* exception — the pure logic here is total over its synthetic inputs.
|
|
22
|
+
* - **Content-free.** No prompt, no output, no credential value is ever read or emitted.
|
|
23
|
+
*
|
|
24
|
+
* Honesty binding (unchanged): Cursor = LOCAL-ESTIMATE only; provider-reported UNAVAILABLE; never a
|
|
25
|
+
* savings claim. This module surfaces none of those figures — it is a readiness check + guidance only.
|
|
26
|
+
*
|
|
27
|
+
* Design: `docs/design/cursor-live-wrapper.md` (Verify-first precondition V) + the honesty rails in
|
|
28
|
+
* `docs/ops/cursor-support-matrix.md`.
|
|
29
|
+
*/
|
|
30
|
+
/** Candidate CLI locations, in probe order. Kept as data so the pure logic is fully testable. */
|
|
31
|
+
export const CURSOR_CLI_PATH_CANDIDATES = ["cursor-agent", "cursor"];
|
|
32
|
+
export const CURSOR_CLI_KNOWN_MACOS_PATH = "/Applications/Cursor.app/Contents/Resources/app/bin/cursor";
|
|
33
|
+
/** The exact command an operator runs once authed (surfaced verbatim in the guidance). */
|
|
34
|
+
export const CURSOR_EXAMPLE_RUN_COMMAND = 'compaction run cursor --out <dir> -- cursor agent -p "<your prompt>" --output-format json';
|
|
35
|
+
/** True when help text advertises the headless print flag (`-p` or `--print`). */
|
|
36
|
+
function hasPrintFlag(helpText) {
|
|
37
|
+
return /(^|\s)-p(\s|,|$)/.test(helpText) || /--print\b/.test(helpText);
|
|
38
|
+
}
|
|
39
|
+
/** True when help text advertises `--output-format` (the flag that makes output separable). */
|
|
40
|
+
function hasOutputFormatFlag(helpText) {
|
|
41
|
+
return /--output-format\b/.test(helpText);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* PURE classifier: given the SAFE probe results for the CLI candidates, decide resolvable/capable and
|
|
45
|
+
* build the exact guidance. No I/O, no spawning, no env reads — this is what the tests target directly.
|
|
46
|
+
*
|
|
47
|
+
* A candidate is "capable" when its help text advertises BOTH `-p`/`--print` AND `--output-format`.
|
|
48
|
+
*/
|
|
49
|
+
export function classifyCursorPreflight(probes) {
|
|
50
|
+
const resolvedProbes = probes.filter((p) => p.resolved);
|
|
51
|
+
const cliResolvable = resolvedProbes.length > 0;
|
|
52
|
+
// Prefer a resolved candidate that ALSO shows the capability; else the first resolved one.
|
|
53
|
+
const capableProbe = resolvedProbes.find((p) => hasPrintFlag(p.helpText) && hasOutputFormatFlag(p.helpText));
|
|
54
|
+
const resolvedCli = capableProbe?.candidate ?? resolvedProbes[0]?.candidate;
|
|
55
|
+
const capabilityPresent = capableProbe !== undefined;
|
|
56
|
+
const ready = cliResolvable && capabilityPresent;
|
|
57
|
+
const guidance = [];
|
|
58
|
+
guidance.push("Cursor live-run preflight (content-free; no prompt run, no login, no API key read):");
|
|
59
|
+
guidance.push(` - CLI resolvable: ${cliResolvable ? `yes (${resolvedCli})` : "no"}`);
|
|
60
|
+
guidance.push(` - Headless capability (-p/--print + --output-format): ${capabilityPresent ? "yes" : cliResolvable ? "not detected in --help" : "n/a (CLI not resolved)"}`);
|
|
61
|
+
// Auth is deliberately NOT probed — say so explicitly so nobody reads readiness as "authenticated".
|
|
62
|
+
guidance.push(" - Auth: NOT checked here (detecting it needs a real, spending call). After the steps below, run `cursor agent login` (or set CURSOR_API_KEY) and retry.");
|
|
63
|
+
if (!cliResolvable) {
|
|
64
|
+
guidance.push("");
|
|
65
|
+
guidance.push("Next steps — the Cursor headless CLI was not found:");
|
|
66
|
+
guidance.push(" 1. Install / locate it: install the Cursor CLI (see cursor.com docs), or if the Cursor app is installed on macOS it ships at:");
|
|
67
|
+
guidance.push(` ${CURSOR_CLI_KNOWN_MACOS_PATH}`);
|
|
68
|
+
guidance.push(" Ensure `cursor` or `cursor-agent` is on your PATH (the `agent` subcommand provides `-p`/`--output-format`).");
|
|
69
|
+
guidance.push(" 2. Authenticate (does NOT happen automatically here): run `cursor agent login`, OR export CURSOR_API_KEY in your shell.");
|
|
70
|
+
guidance.push(" 3. Then run the live flow:");
|
|
71
|
+
guidance.push(` ${CURSOR_EXAMPLE_RUN_COMMAND}`);
|
|
72
|
+
}
|
|
73
|
+
else if (!capabilityPresent) {
|
|
74
|
+
guidance.push("");
|
|
75
|
+
guidance.push(`Next steps — a Cursor CLI was found (${resolvedCli}) but its --help did not advertise the headless flags (-p/--print + --output-format):`);
|
|
76
|
+
guidance.push(" - This may be the Cursor editor launcher rather than the headless agent CLI. Install/verify the headless `cursor agent` CLI, then retry.");
|
|
77
|
+
guidance.push(" - Authenticate first (not done here): `cursor agent login`, OR set CURSOR_API_KEY.");
|
|
78
|
+
guidance.push(" - Then run the live flow:");
|
|
79
|
+
guidance.push(` ${CURSOR_EXAMPLE_RUN_COMMAND}`);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
guidance.push("");
|
|
83
|
+
guidance.push("The Cursor headless CLI is present and capable. Auth is the only remaining prerequisite (not checked here):");
|
|
84
|
+
guidance.push(" 1. Authenticate: run `cursor agent login`, OR export CURSOR_API_KEY.");
|
|
85
|
+
guidance.push(" 2. Then run the live flow:");
|
|
86
|
+
guidance.push(` ${CURSOR_EXAMPLE_RUN_COMMAND}`);
|
|
87
|
+
guidance.push(" Note: Cursor tokens are LOCAL-ESTIMATE only (Cursor emits no provider usage) — provider-reported tokens are unavailable and no savings is claimed.");
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
cliResolvable,
|
|
91
|
+
...(resolvedCli !== undefined ? { resolvedCli } : {}),
|
|
92
|
+
capabilityPresent,
|
|
93
|
+
authState: "not-checked",
|
|
94
|
+
ready,
|
|
95
|
+
guidance
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Run the preflight using an injectable probe. Tries each candidate with a SAFE capability probe
|
|
100
|
+
* (`agent --help`), then classifies. Pure over `probe` — the default real adapter
|
|
101
|
+
* (`createSafeCursorProbe`) is the only place a child process is spawned, and only with `--help`-style
|
|
102
|
+
* args. The candidate order puts the known macOS app path LAST as a fallback when PATH names miss.
|
|
103
|
+
*/
|
|
104
|
+
export async function runCursorPreflight(probe, candidates = [...CURSOR_CLI_PATH_CANDIDATES, CURSOR_CLI_KNOWN_MACOS_PATH]) {
|
|
105
|
+
const probes = [];
|
|
106
|
+
for (const candidate of candidates) {
|
|
107
|
+
// SAFE capability probes ONLY. Never a real prompt, never `login`. We try BOTH help forms because
|
|
108
|
+
// the flags live under different help surfaces across shapes: the standalone `cursor-agent` binary
|
|
109
|
+
// IS the agent (headless flags on its top-level `--help`), while the editor `cursor` launcher
|
|
110
|
+
// exposes them under the `agent` subcommand (`cursor agent --help`). A candidate that resolves in
|
|
111
|
+
// EITHER probe counts as resolved; its help text is the one that best advertises the capability.
|
|
112
|
+
const subcommandHelp = await probe(candidate, ["agent", "--help"]);
|
|
113
|
+
const topLevelHelp = await probe(candidate, ["--help"]);
|
|
114
|
+
probes.push(mergeCandidateProbes(candidate, [subcommandHelp, topLevelHelp]));
|
|
115
|
+
}
|
|
116
|
+
return classifyCursorPreflight(probes);
|
|
117
|
+
}
|
|
118
|
+
/** Merge the help forms tried for one candidate: resolved if EITHER spawned; keep the help text that
|
|
119
|
+
* advertises the capability (so a candidate is judged capable if ANY safe help form shows the flags). */
|
|
120
|
+
function mergeCandidateProbes(candidate, forms) {
|
|
121
|
+
const resolved = forms.some((f) => f.resolved);
|
|
122
|
+
const capableForm = forms.find((f) => f.resolved && hasPrintFlag(f.helpText) && hasOutputFormatFlag(f.helpText));
|
|
123
|
+
const chosen = capableForm ?? forms.find((f) => f.resolved) ?? forms[0];
|
|
124
|
+
return { candidate, resolved, helpText: chosen?.helpText ?? "", exitCode: chosen?.exitCode ?? null };
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=cursor-preflight.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compaction Gateway APPLY activation resolver (PUBLIC CLI/SDK core — engine-free). Charter run-17.2.
|
|
3
|
+
*
|
|
4
|
+
* Apply mode is OPT-IN and EXPLICIT. It activates only through explicit user intent — the gateway's
|
|
5
|
+
* `--mode apply --policy deterministic-dedupe` OR the per-request headers `x-compaction-mode: apply` +
|
|
6
|
+
* `x-compaction-policy: deterministic-dedupe`. When the server's configured intent and the request
|
|
7
|
+
* headers CONFLICT, this resolver FAILS CLOSED (falls back to record, records the reason). Default is
|
|
8
|
+
* always record (byte-safe, no mutation).
|
|
9
|
+
*/
|
|
10
|
+
import { type ApplyPolicyName } from "./apply-policy.js";
|
|
11
|
+
export type GatewayEffectiveMode = "record" | "apply" | "dry-run";
|
|
12
|
+
export interface ApplyActivation {
|
|
13
|
+
/** The mode the server will actually execute. Forced to "record" whenever we fail closed. */
|
|
14
|
+
mode: GatewayEffectiveMode;
|
|
15
|
+
/** True when apply OR dry-run was requested (by server config or header) — even if we fail closed. */
|
|
16
|
+
requested: boolean;
|
|
17
|
+
/** The resolved policy (only `deterministic-dedupe` is known). */
|
|
18
|
+
policy?: ApplyPolicyName;
|
|
19
|
+
/** Content-free label for the receipt's approval_status / activation source. */
|
|
20
|
+
activation: "record" | "explicit-mode" | "explicit-header" | "dry-run-mode" | "dry-run-header" | "fail-closed";
|
|
21
|
+
/** Set when apply/dry-run was requested but we fell back to record (conflict / unknown policy). */
|
|
22
|
+
failClosedReason?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the effective apply activation from the server config + request headers. PURE.
|
|
26
|
+
*
|
|
27
|
+
* Precedence + conflict rules (fail closed on any disagreement):
|
|
28
|
+
* - Default server mode `record` carries no mutating intent → a header alone may activate apply/dry-run.
|
|
29
|
+
* - If BOTH the server AND a header express a mutating intent and they DIFFER (mode or policy) → conflict.
|
|
30
|
+
* - An explicit header `record` is an opt-OUT → record (never a conflict).
|
|
31
|
+
* - Apply/dry-run requires the known policy `deterministic-dedupe`; anything else fails closed.
|
|
32
|
+
* - An unknown mode value fails closed.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveApplyActivation(params: {
|
|
35
|
+
serverMode: string;
|
|
36
|
+
serverPolicy?: string;
|
|
37
|
+
headerMode?: string;
|
|
38
|
+
headerPolicy?: string;
|
|
39
|
+
}): ApplyActivation;
|