@compaction/cli 0.1.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +51 -12
  2. package/dist/cli/commands/activity.d.ts +8 -0
  3. package/dist/cli/commands/activity.js +35 -0
  4. package/dist/cli/commands/apply-context.js +68 -0
  5. package/dist/cli/commands/billing-delta.js +12 -1
  6. package/dist/cli/commands/capture-claude-code.d.ts +44 -0
  7. package/dist/cli/commands/capture-claude-code.js +206 -0
  8. package/dist/cli/commands/capture.js +283 -1
  9. package/dist/cli/commands/compact.js +24 -11
  10. package/dist/cli/commands/context.d.ts +2 -0
  11. package/dist/cli/commands/context.js +130 -0
  12. package/dist/cli/commands/dev.d.ts +17 -0
  13. package/dist/cli/commands/dev.js +131 -0
  14. package/dist/cli/commands/gateway.d.ts +31 -0
  15. package/dist/cli/commands/gateway.js +312 -0
  16. package/dist/cli/commands/hooks.d.ts +5 -0
  17. package/dist/cli/commands/hooks.js +181 -0
  18. package/dist/cli/commands/import.js +3 -1
  19. package/dist/cli/commands/init.js +422 -17
  20. package/dist/cli/commands/input-compaction-ab.d.ts +2 -0
  21. package/dist/cli/commands/input-compaction-ab.js +125 -0
  22. package/dist/cli/commands/optimize-hosted.d.ts +54 -0
  23. package/dist/cli/commands/optimize-hosted.js +123 -0
  24. package/dist/cli/commands/optimize.js +8 -0
  25. package/dist/cli/commands/output-shaping-ab.d.ts +2 -0
  26. package/dist/cli/commands/output-shaping-ab.js +132 -0
  27. package/dist/cli/commands/output-shaping.d.ts +7 -0
  28. package/dist/cli/commands/output-shaping.js +43 -0
  29. package/dist/cli/commands/policies.d.ts +2 -0
  30. package/dist/cli/commands/policies.js +80 -0
  31. package/dist/cli/commands/precall.d.ts +19 -0
  32. package/dist/cli/commands/precall.js +436 -0
  33. package/dist/cli/commands/recommend.js +1 -1
  34. package/dist/cli/commands/run.js +310 -18
  35. package/dist/cli/commands/upgrade-status.d.ts +42 -0
  36. package/dist/cli/commands/upgrade-status.js +152 -0
  37. package/dist/cli/cursor-export-read.d.ts +13 -0
  38. package/dist/cli/cursor-export-read.js +53 -0
  39. package/dist/cli/cursor-live-preflight.d.ts +19 -0
  40. package/dist/cli/cursor-live-preflight.js +46 -0
  41. package/dist/cli/index.js +39 -2
  42. package/dist/cli/onboarding/GatewayTui.d.ts +33 -0
  43. package/dist/cli/onboarding/GatewayTui.js +113 -0
  44. package/dist/cli/onboarding/model.d.ts +32 -1
  45. package/dist/cli/onboarding/model.js +27 -2
  46. package/dist/cli/terminal-logo.d.ts +27 -0
  47. package/dist/cli/terminal-logo.js +49 -0
  48. package/dist/core/activity-event.d.ts +107 -0
  49. package/dist/core/activity-event.js +182 -0
  50. package/dist/core/activity-store.d.ts +63 -0
  51. package/dist/core/activity-store.js +254 -0
  52. package/dist/core/activity-view.d.ts +76 -0
  53. package/dist/core/activity-view.js +120 -0
  54. package/dist/core/api-client/index.d.ts +3 -1
  55. package/dist/core/api-client/index.js +4 -0
  56. package/dist/core/api-client/payload.d.ts +5 -1
  57. package/dist/core/api-client/payload.js +8 -6
  58. package/dist/core/api-client/persisted-config.d.ts +57 -0
  59. package/dist/core/api-client/persisted-config.js +156 -0
  60. package/dist/core/api-client/tool.d.ts +29 -0
  61. package/dist/core/api-client/tool.js +50 -0
  62. package/dist/core/api-client/types.d.ts +15 -4
  63. package/dist/core/auto-apply-ask.d.ts +58 -0
  64. package/dist/core/auto-apply-ask.js +105 -0
  65. package/dist/core/auto-apply-gates.d.ts +76 -0
  66. package/dist/core/auto-apply-gates.js +113 -0
  67. package/dist/core/before-call-activity.d.ts +35 -0
  68. package/dist/core/before-call-activity.js +103 -0
  69. package/dist/core/before-call-recovery.d.ts +19 -0
  70. package/dist/core/before-call-recovery.js +46 -0
  71. package/dist/core/before-call-stdin.d.ts +59 -0
  72. package/dist/core/before-call-stdin.js +78 -0
  73. package/dist/core/before-call.d.ts +149 -0
  74. package/dist/core/before-call.js +358 -0
  75. package/dist/core/billing-delta/billing-delta-record.d.ts +28 -0
  76. package/dist/core/billing-delta/billing-delta-record.js +26 -0
  77. package/dist/core/capture-record.d.ts +40 -0
  78. package/dist/core/capture-record.js +55 -0
  79. package/dist/core/claude-code-before-call.d.ts +37 -0
  80. package/dist/core/claude-code-before-call.js +120 -0
  81. package/dist/core/claude-code-connect.d.ts +31 -0
  82. package/dist/core/claude-code-connect.js +87 -0
  83. package/dist/core/claude-code-hook-record.d.ts +71 -0
  84. package/dist/core/claude-code-hook-record.js +0 -0
  85. package/dist/core/claude-code-hooks.d.ts +77 -0
  86. package/dist/core/claude-code-hooks.js +141 -0
  87. package/dist/core/codex-capture.d.ts +45 -0
  88. package/dist/core/codex-capture.js +204 -0
  89. package/dist/core/command-runner.js +5 -1
  90. package/dist/core/compaction-artifacts.d.ts +51 -0
  91. package/dist/core/compaction-artifacts.js +242 -0
  92. package/dist/core/compactor.d.ts +9 -0
  93. package/dist/core/compactor.js +95 -0
  94. package/dist/core/context-store-eval-cases.d.ts +6 -0
  95. package/dist/core/context-store-eval-cases.js +331 -0
  96. package/dist/core/context-store-eval.d.ts +140 -0
  97. package/dist/core/context-store-eval.js +138 -0
  98. package/dist/core/context-store-fs.d.ts +73 -0
  99. package/dist/core/context-store-fs.js +157 -0
  100. package/dist/core/context-store-sufficiency.d.ts +98 -0
  101. package/dist/core/context-store-sufficiency.js +135 -0
  102. package/dist/core/context-store.d.ts +155 -0
  103. package/dist/core/context-store.js +228 -0
  104. package/dist/core/cross-surface-event.d.ts +306 -0
  105. package/dist/core/cross-surface-event.js +330 -0
  106. package/dist/core/cursor-capture.d.ts +54 -0
  107. package/dist/core/cursor-capture.js +215 -0
  108. package/dist/core/cursor-preflight-probe.d.ts +8 -0
  109. package/dist/core/cursor-preflight-probe.js +88 -0
  110. package/dist/core/cursor-preflight.d.ts +86 -0
  111. package/dist/core/cursor-preflight.js +126 -0
  112. package/dist/core/gateway/apply-activation.d.ts +39 -0
  113. package/dist/core/gateway/apply-activation.js +84 -0
  114. package/dist/core/gateway/apply-policy.d.ts +64 -0
  115. package/dist/core/gateway/apply-policy.js +221 -0
  116. package/dist/core/gateway/apply-receipt.d.ts +36 -0
  117. package/dist/core/gateway/apply-receipt.js +75 -0
  118. package/dist/core/gateway/cache-proof.d.ts +45 -0
  119. package/dist/core/gateway/cache-proof.js +65 -0
  120. package/dist/core/gateway/configure.d.ts +50 -0
  121. package/dist/core/gateway/configure.js +169 -0
  122. package/dist/core/gateway/openai-usage.d.ts +56 -0
  123. package/dist/core/gateway/openai-usage.js +128 -0
  124. package/dist/core/gateway/receipt.d.ts +138 -0
  125. package/dist/core/gateway/receipt.js +120 -0
  126. package/dist/core/gateway/recovery.d.ts +23 -0
  127. package/dist/core/gateway/recovery.js +68 -0
  128. package/dist/core/gateway/server.d.ts +51 -0
  129. package/dist/core/gateway/server.js +276 -0
  130. package/dist/core/gateway/status.d.ts +45 -0
  131. package/dist/core/gateway/status.js +109 -0
  132. package/dist/core/hook-usage-aggregate.d.ts +47 -0
  133. package/dist/core/hook-usage-aggregate.js +161 -0
  134. package/dist/core/input-compaction-ab.d.ts +111 -0
  135. package/dist/core/input-compaction-ab.js +158 -0
  136. package/dist/core/local-run-record.d.ts +109 -0
  137. package/dist/core/local-run-record.js +223 -0
  138. package/dist/core/output-shaping-ab.d.ts +140 -0
  139. package/dist/core/output-shaping-ab.js +146 -0
  140. package/dist/core/output-shaping-attach.d.ts +31 -0
  141. package/dist/core/output-shaping-attach.js +57 -0
  142. package/dist/core/output-shaping.d.ts +56 -0
  143. package/dist/core/output-shaping.js +89 -0
  144. package/dist/core/policy-middleware.d.ts +121 -0
  145. package/dist/core/policy-middleware.js +919 -0
  146. package/dist/core/policy-preferences.d.ts +99 -0
  147. package/dist/core/policy-preferences.js +232 -0
  148. package/dist/core/report-generator.d.ts +19 -1
  149. package/dist/core/report-generator.js +51 -0
  150. package/dist/core/run-aggregator.d.ts +56 -1
  151. package/dist/core/run-aggregator.js +93 -2
  152. package/dist/core/run-flow-report.d.ts +82 -0
  153. package/dist/core/run-flow-report.js +71 -0
  154. package/dist/core/safety-report.js +8 -1
  155. package/dist/core/shim-capture-bridge.d.ts +32 -0
  156. package/dist/core/shim-capture-bridge.js +88 -0
  157. package/dist/core/skill-injection-policy.d.ts +72 -0
  158. package/dist/core/skill-injection-policy.js +183 -0
  159. package/dist/core/spend-attribution.js +1 -1
  160. package/dist/core/token-accounting.d.ts +1 -1
  161. package/dist/core/tool-shim.d.ts +129 -0
  162. package/dist/core/tool-shim.js +447 -0
  163. package/dist/core/trace-adapters.d.ts +8 -0
  164. package/dist/core/trace-adapters.js +4 -0
  165. package/dist/core/trace-parser.d.ts +13 -13
  166. package/dist/core/trace-parser.js +6 -6
  167. package/dist/core/types.d.ts +45 -2
  168. package/dist/core/waste-detector.d.ts +20 -0
  169. package/dist/core/waste-detector.js +160 -6
  170. package/package.json +1 -1
@@ -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;
@@ -0,0 +1,84 @@
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 { DEDUPE_POLICY } from "./apply-policy.js";
11
+ /** Normalize a mode string to a known effective mode, or undefined when unset/unknown. */
12
+ function normalizeMode(v) {
13
+ if (v === undefined)
14
+ return undefined;
15
+ const s = v.trim().toLowerCase();
16
+ if (s === "")
17
+ return undefined;
18
+ if (s === "record" || s === "apply" || s === "dry-run")
19
+ return s;
20
+ return "unknown";
21
+ }
22
+ /** A mutating intent is apply or dry-run; record is the non-mutating default. */
23
+ function isMutating(m) {
24
+ return m === "apply" || m === "dry-run";
25
+ }
26
+ function record() {
27
+ return { mode: "record", requested: false, activation: "record" };
28
+ }
29
+ function failClosed(reason) {
30
+ return { mode: "record", requested: true, activation: "fail-closed", failClosedReason: reason };
31
+ }
32
+ /**
33
+ * Resolve the effective apply activation from the server config + request headers. PURE.
34
+ *
35
+ * Precedence + conflict rules (fail closed on any disagreement):
36
+ * - Default server mode `record` carries no mutating intent → a header alone may activate apply/dry-run.
37
+ * - If BOTH the server AND a header express a mutating intent and they DIFFER (mode or policy) → conflict.
38
+ * - An explicit header `record` is an opt-OUT → record (never a conflict).
39
+ * - Apply/dry-run requires the known policy `deterministic-dedupe`; anything else fails closed.
40
+ * - An unknown mode value fails closed.
41
+ */
42
+ export function resolveApplyActivation(params) {
43
+ const serverMode = normalizeMode(params.serverMode);
44
+ const headerMode = normalizeMode(params.headerMode);
45
+ if (serverMode === "unknown")
46
+ return failClosed(`unknown gateway --mode '${params.serverMode}'`);
47
+ if (headerMode === "unknown")
48
+ return failClosed(`unknown x-compaction-mode header '${params.headerMode}'`);
49
+ // Explicit header opt-out → record, no matter the server mode.
50
+ if (headerMode === "record")
51
+ return record();
52
+ const serverIntent = isMutating(serverMode) ? serverMode : undefined; // record server → no intent
53
+ const headerIntent = isMutating(headerMode) ? headerMode : undefined;
54
+ // No mutating intent anywhere → plain record.
55
+ if (!serverIntent && !headerIntent)
56
+ return record();
57
+ const serverPolicy = params.serverPolicy?.trim() || undefined;
58
+ const headerPolicy = params.headerPolicy?.trim() || undefined;
59
+ // Conflict: both sides express a mutating intent and disagree on mode or policy.
60
+ if (serverIntent && headerIntent) {
61
+ if (serverIntent !== headerIntent) {
62
+ return failClosed(`gateway --mode '${serverIntent}' conflicts with x-compaction-mode '${headerIntent}' — fail closed`);
63
+ }
64
+ if (serverPolicy && headerPolicy && serverPolicy !== headerPolicy) {
65
+ return failClosed(`gateway --policy '${serverPolicy}' conflicts with x-compaction-policy '${headerPolicy}' — fail closed`);
66
+ }
67
+ }
68
+ const mode = (headerIntent ?? serverIntent);
69
+ const policy = headerPolicy ?? serverPolicy;
70
+ if (policy !== DEDUPE_POLICY) {
71
+ return failClosed(policy === undefined
72
+ ? `${mode} requested without a policy — apply requires policy '${DEDUPE_POLICY}'`
73
+ : `unknown policy '${policy}' — apply requires policy '${DEDUPE_POLICY}'`);
74
+ }
75
+ const bySource = mode === "apply"
76
+ ? headerIntent
77
+ ? "explicit-header"
78
+ : "explicit-mode"
79
+ : headerIntent
80
+ ? "dry-run-header"
81
+ : "dry-run-mode";
82
+ return { mode, requested: true, policy: DEDUPE_POLICY, activation: bySource };
83
+ }
84
+ //# sourceMappingURL=apply-activation.js.map
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Compaction Gateway DETERMINISTIC APPLY policy (PUBLIC CLI/SDK core — engine-free). Charter run-17.2.
3
+ *
4
+ * The FIRST real Compaction apply layer. It is **deterministic only** — NO model call, NO paraphrase, NO
5
+ * semantic summary, NO LCM. The single policy is `deterministic-dedupe`: within a known-safe text field,
6
+ * remove EXACT-duplicate LARGE blocks (preserve the first occurrence, never reorder). It fails CLOSED on
7
+ * any request shape it does not explicitly understand, and it never touches system/developer instructions,
8
+ * tool schemas, auth/config fields, or short strings.
9
+ *
10
+ * This module is PURE (no I/O): it takes the request endpoint + body text and returns a plan describing
11
+ * whether a safe mutation is possible, the mutated body (only when something actually changed), and a
12
+ * LOCAL-ESTIMATE model-visible-input reduction. The server decides what to do with the plan.
13
+ */
14
+ /** The only policy implemented in run-17.2. */
15
+ export declare const DEDUPE_POLICY = "deterministic-dedupe";
16
+ export type ApplyPolicyName = typeof DEDUPE_POLICY;
17
+ /** Blocks shorter than this are NEVER dedupe candidates (short strings are never touched). */
18
+ export declare const DEFAULT_MIN_BLOCK_CHARS = 512;
19
+ export type RequestShape = "responses-string" | "chat-messages" | "unsupported";
20
+ export interface DedupePlan {
21
+ policy: ApplyPolicyName;
22
+ shape: RequestShape;
23
+ /** The shape is one we handle. */
24
+ supported: boolean;
25
+ /** A safe mutation was actually produced (duplicates removed). Only then is `mutatedBody` set. */
26
+ changed: boolean;
27
+ /** Why we will NOT apply (present whenever `supported` is false, or parsing failed). */
28
+ failClosedReason?: string;
29
+ /** Serialized JSON of the mutated request — present ONLY when `changed` is true. */
30
+ mutatedBody?: string;
31
+ /** How many exact-duplicate large blocks were removed. */
32
+ removedBlocks: number;
33
+ /** Model-visible input size over the SAFE text fields only (chars) — the honest before/after basis. */
34
+ charsBefore: number;
35
+ charsAfter: number;
36
+ /** LOCAL-ESTIMATE token counts (chars/4) — never provider-reported, never a billing figure. */
37
+ estTokensBefore: number;
38
+ estTokensAfter: number;
39
+ /** Estimated model-visible input reduction percent over the safe fields (0 when nothing changed). */
40
+ reductionPercent: number;
41
+ }
42
+ /** chars/4 — the repo's local-estimate token convention. NEVER provider-reported. */
43
+ export declare function estimateTokens(chars: number): number;
44
+ /**
45
+ * Remove EXACT-duplicate large blocks from a single text field. Blocks are paragraphs split on runs of
46
+ * 2+ newlines. A block is a candidate only when it is >= `minBlockChars`; the FIRST occurrence is always
47
+ * kept, later byte-identical occurrences are dropped. Order is never changed; nothing is paraphrased.
48
+ *
49
+ * When nothing is removed the ORIGINAL string is returned unchanged (byte-exact). When blocks are removed
50
+ * the kept blocks are re-joined with a normalized `\n\n` separator (only ever in the mutated path).
51
+ */
52
+ export declare function dedupeTextField(text: string, minBlockChars: number): {
53
+ text: string;
54
+ removed: number;
55
+ };
56
+ /**
57
+ * Plan a deterministic-dedupe apply for one request. PURE — no I/O. Fails CLOSED (supported:false) on any
58
+ * shape not explicitly handled: non-JSON, unknown endpoint, multimodal/array content, tool-bearing
59
+ * requests, unknown roles, or missing safe fields. The caller forwards the ORIGINAL body whenever this
60
+ * plan is not `changed` (fail-closed or nothing-to-remove both mean byte-identical passthrough).
61
+ */
62
+ export declare function planDeterministicDedupe(endpoint: string, bodyText: string, opts?: {
63
+ minBlockChars?: number;
64
+ }): DedupePlan;