copperhead 0.7.0 → 0.8.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.
Files changed (79) hide show
  1. package/README.md +36 -4
  2. package/dist/agent/animate.js +76 -0
  3. package/dist/agent/animate.js.map +1 -0
  4. package/dist/agent/box.js +89 -0
  5. package/dist/agent/box.js.map +1 -0
  6. package/dist/agent/dock-renderer.js +173 -0
  7. package/dist/agent/dock-renderer.js.map +1 -0
  8. package/dist/agent/logo.js +21 -0
  9. package/dist/agent/logo.js.map +1 -0
  10. package/dist/agent/loop.js +15 -5
  11. package/dist/agent/loop.js.map +1 -1
  12. package/dist/agent/providers/claude-code.js +1 -212
  13. package/dist/agent/providers/claude-code.js.map +1 -1
  14. package/dist/agent/providers/cursor.js +317 -0
  15. package/dist/agent/providers/cursor.js.map +1 -0
  16. package/dist/agent/providers/tool-protocol.js +205 -0
  17. package/dist/agent/providers/tool-protocol.js.map +1 -0
  18. package/dist/agent/render.js +32 -15
  19. package/dist/agent/render.js.map +1 -1
  20. package/dist/agent/runmeta.js +4 -5
  21. package/dist/agent/runmeta.js.map +1 -1
  22. package/dist/agent/theme.js +84 -0
  23. package/dist/agent/theme.js.map +1 -0
  24. package/dist/cli.js +134 -13
  25. package/dist/cli.js.map +1 -1
  26. package/dist/commands/create.js +41 -32
  27. package/dist/commands/create.js.map +1 -1
  28. package/dist/commands/demo.js +146 -0
  29. package/dist/commands/demo.js.map +1 -0
  30. package/dist/commands/doctor.js +240 -0
  31. package/dist/commands/doctor.js.map +1 -0
  32. package/dist/commands/repl-inspect.js +342 -0
  33. package/dist/commands/repl-inspect.js.map +1 -0
  34. package/dist/commands/repl.js +618 -0
  35. package/dist/commands/repl.js.map +1 -0
  36. package/dist/config.js +5 -2
  37. package/dist/config.js.map +1 -1
  38. package/dist/kicad/cli.js +126 -6
  39. package/dist/kicad/cli.js.map +1 -1
  40. package/dist/util/cli-args.js +35 -0
  41. package/dist/util/cli-args.js.map +1 -0
  42. package/dist/util/dock.js +155 -0
  43. package/dist/util/dock.js.map +1 -0
  44. package/dist/util/git.js +129 -4
  45. package/dist/util/git.js.map +1 -1
  46. package/dist/util/live-prompt.js +542 -0
  47. package/dist/util/live-prompt.js.map +1 -0
  48. package/dist/util/paths.js +9 -0
  49. package/dist/util/paths.js.map +1 -1
  50. package/dist/util/select.js +172 -0
  51. package/dist/util/select.js.map +1 -0
  52. package/package.json +4 -6
  53. package/src/agent/animate.ts +90 -0
  54. package/src/agent/box.ts +99 -0
  55. package/src/agent/dock-renderer.ts +181 -0
  56. package/src/agent/logo.ts +23 -0
  57. package/src/agent/loop.ts +15 -5
  58. package/src/agent/providers/claude-code.ts +2 -216
  59. package/src/agent/providers/cursor.ts +364 -0
  60. package/src/agent/providers/tool-protocol.ts +212 -0
  61. package/src/agent/render.ts +33 -16
  62. package/src/agent/runmeta.ts +6 -7
  63. package/src/agent/theme.ts +91 -0
  64. package/src/cli.ts +139 -15
  65. package/src/commands/create.ts +81 -30
  66. package/src/commands/demo.ts +184 -0
  67. package/src/commands/doctor.ts +289 -0
  68. package/src/commands/repl-inspect.ts +353 -0
  69. package/src/commands/repl.ts +685 -0
  70. package/src/config.ts +6 -3
  71. package/src/kicad/cli.ts +132 -7
  72. package/src/layout/claude-ui-layout.md +72 -0
  73. package/src/layout/repl-ui-layout.md +139 -0
  74. package/src/util/cli-args.ts +42 -0
  75. package/src/util/dock.ts +161 -0
  76. package/src/util/git.ts +140 -4
  77. package/src/util/live-prompt.ts +595 -0
  78. package/src/util/paths.ts +10 -0
  79. package/src/util/select.ts +192 -0
package/dist/config.js CHANGED
@@ -61,6 +61,8 @@ export async function loadConfig(repoRoot) {
61
61
  * Accepted values (same set for `--model`, COPPERHEAD_MODEL, and `model` in
62
62
  * .copperhead/config.json):
63
63
  *
64
+ * - `cursor` : the Cursor Agent CLI using saved login (`agent login`).
65
+ * - `cursor:<id>` : the same provider on a specific model id.
64
66
  * - `claude-code` : the Claude Code saved-login provider on its default
65
67
  * model. Needs NO API key — it reuses the logged-in Claude
66
68
  * Code CLI / CLAUDE_CODE_OAUTH_TOKEN via the Agent SDK.
@@ -81,7 +83,8 @@ export async function loadConfig(repoRoot) {
81
83
  * a typo like `claud-sonnet-5` silently routes to OpenAI and fails there.
82
84
  * Anthropic and direct OpenAI providers require their API keys; `codex` requires
83
85
  * a locally installed and authenticated Codex CLI, and `claude-code` requires a
84
- * Claude Code login (CLAUDE_CODE_OAUTH_TOKEN); neither needs a model API key.
86
+ * Claude Code login (CLAUDE_CODE_OAUTH_TOKEN); `cursor` requires `agent login`.
87
+ * None of the saved-login providers need a model API key.
85
88
  */
86
89
  export function resolveModel(flag, config, env = process.env) {
87
90
  if (flag)
@@ -94,6 +97,6 @@ export function resolveModel(flag, config, env = process.env) {
94
97
  return { model: 'gpt-5', source: 'openai-key' };
95
98
  if (env.ANTHROPIC_API_KEY)
96
99
  return { model: 'claude', source: 'anthropic-key' };
97
- throw new Error('no model configured: pass --model codex (uses your local Codex login), set COPPERHEAD_MODEL, set model in .copperhead/config.json, or provide OPENAI_API_KEY/ANTHROPIC_API_KEY');
100
+ throw new Error('no model configured: pass --model, set COPPERHEAD_MODEL, or export an API key; see https://docs.copperhead.sh/reference/configuration/');
98
101
  }
99
102
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/kicad/cli.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { execa } from 'execa';
2
+ import { existsSync } from 'node:fs';
2
3
  import { mkdtemp, readFile, rm } from 'node:fs/promises';
3
4
  import { tmpdir } from 'node:os';
4
5
  import path from 'node:path';
@@ -9,19 +10,134 @@ export class KicadCliMissingError extends PreflightError {
9
10
  super('kicad-cli not found on PATH', 'copperhead verifies every mutation with kicad-cli ERC/DRC; without it no edit can be checked, so no run can start', [
10
11
  'install KiCad ≥ 8: https://www.kicad.org/download/',
11
12
  'ensure the kicad-cli binary is on PATH (on macOS it ships inside KiCad.app/Contents/MacOS)',
13
+ 'or set COPPERHEAD_KICAD_CLI=/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli',
12
14
  'confirm with "kicad-cli version", then rerun',
13
15
  ]);
14
16
  this.name = 'KicadCliMissingError';
15
17
  }
16
18
  }
19
+ /**
20
+ * `COPPERHEAD_KICAD_CLI` points somewhere that does not exist. Distinct from
21
+ * KicadCliMissingError because the advice is the opposite: the override was
22
+ * seen and rejected, so telling the user to set it would be nonsense.
23
+ */
24
+ export class KicadCliBadOverrideError extends PreflightError {
25
+ constructor(configured) {
26
+ super(`COPPERHEAD_KICAD_CLI points to a path that does not exist: ${configured}`, 'the override wins over PATH, so falling back silently would run a different binary than the one you named and make the failure impossible to diagnose', [
27
+ `check the path: ls -l "${configured}"`,
28
+ 'on macOS the binary lives at /Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli',
29
+ 'or unset COPPERHEAD_KICAD_CLI to fall back to PATH',
30
+ 'confirm with "$COPPERHEAD_KICAD_CLI version", then rerun',
31
+ ]);
32
+ this.name = 'KicadCliBadOverrideError';
33
+ }
34
+ }
35
+ /** Well-known install locations when `kicad-cli` is not on PATH (macOS app bundle). */
36
+ const FALLBACK_BINARIES = [
37
+ '/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli',
38
+ '/Applications/KiCad-10.0/KiCad.app/Contents/MacOS/kicad-cli',
39
+ '/Applications/KiCad-9.0/KiCad.app/Contents/MacOS/kicad-cli',
40
+ '/Applications/KiCad-8.0/KiCad.app/Contents/MacOS/kicad-cli',
41
+ ];
42
+ let fallbackBinaries = FALLBACK_BINARIES;
43
+ let cachedBinary;
44
+ /**
45
+ * Resolve the kicad-cli executable: `COPPERHEAD_KICAD_CLI` > PATH name
46
+ * (`kicad-cli`). On PATH miss, `runKicad` falls back to macOS KiCad.app paths.
47
+ */
48
+ export function resolveKicadCli() {
49
+ if (cachedBinary !== undefined) {
50
+ if (cachedBinary === null)
51
+ throw new KicadCliMissingError();
52
+ return cachedBinary;
53
+ }
54
+ const fromEnv = envOverride();
55
+ if (fromEnv) {
56
+ cachedBinary = fromEnv;
57
+ return fromEnv;
58
+ }
59
+ cachedBinary = 'kicad-cli';
60
+ return cachedBinary;
61
+ }
62
+ /**
63
+ * The `COPPERHEAD_KICAD_CLI` override, or null when unset. Set-but-missing is
64
+ * a hard error rather than a silent fallthrough to PATH: the user told us
65
+ * which binary to run, and quietly running a different one (or reporting
66
+ * "not found on PATH" with advice to set the variable they already set) is
67
+ * the worst possible answer.
68
+ */
69
+ function envOverride() {
70
+ const fromEnv = process.env.COPPERHEAD_KICAD_CLI?.trim();
71
+ if (!fromEnv)
72
+ return null;
73
+ if (!existsSync(fromEnv))
74
+ throw new KicadCliBadOverrideError(fromEnv);
75
+ return fromEnv;
76
+ }
77
+ /**
78
+ * After ENOENT on PATH, retry with a known macOS install path.
79
+ *
80
+ * Deliberately does not re-read `COPPERHEAD_KICAD_CLI`: this is reached only
81
+ * when resolveKicadCli() cached the bare PATH name, which in turn happens only
82
+ * when the override was unset (set-but-missing throws there instead), so an
83
+ * override re-check here could never fire.
84
+ */
85
+ function fallbackAfterMissing() {
86
+ for (const candidate of fallbackBinaries) {
87
+ if (existsSync(candidate)) {
88
+ cachedBinary = candidate;
89
+ return candidate;
90
+ }
91
+ }
92
+ cachedBinary = null;
93
+ throw new KicadCliMissingError();
94
+ }
95
+ async function runKicad(args, opts) {
96
+ let bin = resolveKicadCli();
97
+ let res = await execa(bin, args, { reject: false });
98
+ if (res.failed && res.code === 'ENOENT') {
99
+ if (bin === 'kicad-cli') {
100
+ bin = fallbackAfterMissing();
101
+ res = await execa(bin, args, { reject: false });
102
+ }
103
+ else {
104
+ throw new KicadCliMissingError();
105
+ }
106
+ }
107
+ if (opts?.reject === false)
108
+ return res;
109
+ if (res.failed && res.code === 'ENOENT') {
110
+ throw new KicadCliMissingError();
111
+ }
112
+ if (res.failed) {
113
+ throw Object.assign(new Error(res.stderr || res.stdout || `kicad-cli exited ${res.exitCode}`), res);
114
+ }
115
+ return res;
116
+ }
117
+ /** Test helper: clear the resolved-binary cache. */
118
+ export function resetKicadCliCache() {
119
+ cachedBinary = undefined;
120
+ }
121
+ /**
122
+ * Test helper: point the app-bundle probe at fixture paths, or call with no
123
+ * argument to restore the real list. Without this the fallback branch is only
124
+ * exercisable on a macOS host that happens to have KiCad installed, which
125
+ * makes the outcome depend on the developer's machine.
126
+ */
127
+ export function setKicadFallbackBinaries(paths) {
128
+ fallbackBinaries = paths ?? FALLBACK_BINARIES;
129
+ }
17
130
  export async function kicadCliVersion() {
18
131
  try {
19
- const { stdout } = await execa('kicad-cli', ['version']);
20
- return stdout.trim();
132
+ const res = await runKicad(['version']);
133
+ return String(res.stdout ?? '').trim();
21
134
  }
22
135
  catch (err) {
23
136
  if (err.code === 'ENOENT')
24
137
  throw new KicadCliMissingError();
138
+ // runKicad already maps PATH ENOENT → fallback → KicadCliMissingError
139
+ if (err instanceof KicadCliMissingError)
140
+ throw err;
25
141
  throw err;
26
142
  }
27
143
  }
@@ -30,7 +146,7 @@ async function runCheck(kind, filePath, extraArgs = []) {
30
146
  const out = path.join(dir, `${kind}.json`);
31
147
  const sub = kind === 'erc' ? ['sch', 'erc'] : ['pcb', 'drc'];
32
148
  try {
33
- const res = await execa('kicad-cli', [...sub, '--format', 'json', '--exit-code-violations', '--output', out, ...extraArgs, filePath], { reject: false });
149
+ const res = await runKicad([...sub, '--format', 'json', '--exit-code-violations', '--output', out, ...extraArgs, filePath], { reject: false });
34
150
  if (res.failed && res.code === 'ENOENT') {
35
151
  throw new KicadCliMissingError();
36
152
  }
@@ -79,7 +195,7 @@ export async function kicadLoadError(filePath) {
79
195
  ? ['sch', 'export', 'netlist', '--output', path.join(dir, 'probe.net'), filePath]
80
196
  : ['pcb', 'export', 'pos', '--output', path.join(dir, 'probe.pos'), filePath];
81
197
  try {
82
- const res = await execa('kicad-cli', args, { reject: false });
198
+ const res = await runKicad(args, { reject: false });
83
199
  if (res.failed && res.code === 'ENOENT')
84
200
  throw new KicadCliMissingError();
85
201
  if (res.exitCode === 0)
@@ -112,10 +228,12 @@ export async function exportFab(pcbPath, schPath, outDir) {
112
228
  }
113
229
  for (const job of jobs) {
114
230
  try {
115
- await execa('kicad-cli', job.args);
231
+ await runKicad(job.args);
116
232
  result.produced.push(job.artifact);
117
233
  }
118
234
  catch (err) {
235
+ if (err instanceof KicadCliMissingError)
236
+ throw err;
119
237
  if (err.code === 'ENOENT')
120
238
  throw new KicadCliMissingError();
121
239
  result.failed.push({ artifact: job.artifact, reason: String(err.stderr ?? err.message).slice(0, 200) });
@@ -129,9 +247,11 @@ export async function exportSvg(kind, filePath, outDir) {
129
247
  ? ['sch', 'export', 'svg', '--output', outDir, filePath]
130
248
  : ['pcb', 'export', 'svg', '--output', path.join(outDir, 'board.svg'), '--layers', 'F.Cu,B.Cu,Edge.Cuts', filePath];
131
249
  try {
132
- await execa('kicad-cli', args);
250
+ await runKicad(args);
133
251
  }
134
252
  catch (err) {
253
+ if (err instanceof KicadCliMissingError)
254
+ throw err;
135
255
  if (err.code === 'ENOENT')
136
256
  throw new KicadCliMissingError();
137
257
  throw err;
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/kicad/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/kicad/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Pure argument helpers for the CLI surface. They live here rather than in
3
+ * cli.ts because that module wires up commander and parses argv at import
4
+ * time, which makes it untestable in-process: importing it runs the program.
5
+ * Anything with a branch worth pinning belongs on this side of the line.
6
+ */
7
+ import path from 'node:path';
8
+ /** Resolve `--repo` against the working directory; absolute paths pass through. */
9
+ export function repoOf(opts) {
10
+ return path.resolve(opts.repo ?? process.cwd());
11
+ }
12
+ /** `--max-turns` accepts only a positive integer; "5oops" and "NaN" refuse to start. */
13
+ export function parseMaxTurns(raw) {
14
+ const n = Number(raw);
15
+ if (!Number.isInteger(n) || n <= 0) {
16
+ throw new Error(`--max-turns must be a positive integer, got "${raw}"`);
17
+ }
18
+ return n;
19
+ }
20
+ /**
21
+ * Turns offered when the budget runs out: ceil of the ORIGINAL budget (design
22
+ * D1), so repeat extensions offer the same increment instead of escalating
23
+ * with the already-extended turn count.
24
+ */
25
+ export function budgetExtraTurns(stats) {
26
+ return Math.ceil(stats.maxTurns / 2);
27
+ }
28
+ /** The attended "continue?" question, with the cost of the run so far spelled out. */
29
+ export function budgetPromptText(stats) {
30
+ const k = (n) => `${(n / 1000).toFixed(1)}k`;
31
+ return (`Turn budget exhausted (${stats.turnsUsed} turns, ${k(stats.tokensIn)} in / ${k(stats.tokensOut)} out, ` +
32
+ `${stats.filesTouched.length} file(s) touched, ${stats.openObligations} open obligation(s)). ` +
33
+ `Continue with ${budgetExtraTurns(stats)} more turns?`);
34
+ }
35
+ //# sourceMappingURL=cli-args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-args.js","sourceRoot":"","sources":["../../src/util/cli-args.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,mFAAmF;AACnF,MAAM,UAAU,MAAM,CAAC,IAAuB;IAC5C,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;AAClD,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,gDAAgD,GAAG,GAAG,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAA6C;IAC5E,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;AACvC,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,gBAAgB,CAAC,KAA2B;IAC1D,MAAM,CAAC,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAC7D,OAAO,CACL,0BAA0B,KAAK,CAAC,SAAS,WAAW,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ;QACxG,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,qBAAqB,KAAK,CAAC,eAAe,wBAAwB;QAC9F,iBAAiB,gBAAgB,CAAC,KAAK,CAAC,cAAc,CACvD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Bottom-of-viewport dock built on DECSTBM scroll regions: content is fenced
3
+ * into rows [1, H - dockHeight] and the dock is painted with absolute cursor
4
+ * addressing below the scroll margin, so no amount of content output (or
5
+ * mouse wheel) can ever move it. Runs inside the alternate screen buffer the
6
+ * REPL enters at startup. This is the FrankenTUI/ratatui-inline technique;
7
+ * see also DECSTBM (CSI r).
8
+ */
9
+ import { truncateVisible } from '../agent/box.js';
10
+ const HIDE = '\x1b[?25l';
11
+ const SHOW = '\x1b[?25h';
12
+ /** Synchronized-output guards; terminals without support ignore them. */
13
+ const SYNC_ON = '\x1b[?2026h';
14
+ const SYNC_OFF = '\x1b[?2026l';
15
+ /** Reset the scroll region to the full screen. Homes the cursor (hence DECSC/DECRC around it). */
16
+ const REGION_RESET = '\x1b[r';
17
+ const SAVE = '\x1b7';
18
+ const RESTORE = '\x1b8';
19
+ export class TerminalDock {
20
+ out;
21
+ /** Rows the dock currently occupies at the bottom of the screen. */
22
+ dockH = 0;
23
+ /**
24
+ * True while the real cursor is parked at a caret position inside the dock
25
+ * (the DECSC slot then holds the content-region position to return to).
26
+ */
27
+ parked = false;
28
+ /** Last set() payload, replayable after an external overdraw (animations). */
29
+ last = null;
30
+ constructor(out) {
31
+ this.out = out;
32
+ }
33
+ cols() {
34
+ return typeof this.out.columns === 'number' && this.out.columns ? this.out.columns : 80;
35
+ }
36
+ rows() {
37
+ return typeof this.out.rows === 'number' && this.out.rows ? this.out.rows : 24;
38
+ }
39
+ /**
40
+ * Replace the docked region. On a height change the content scroll region
41
+ * is re-fenced; same-height repaints are pure absolute-addressed writes and
42
+ * can never scroll anything. With `caret` (dock-relative 1-based row and
43
+ * column) the real terminal cursor is left parked there, Claude Code-style.
44
+ */
45
+ set(lines, caret) {
46
+ if (!lines.length) {
47
+ this.release();
48
+ return;
49
+ }
50
+ const h = this.rows();
51
+ const shown = lines.slice(0, Math.max(1, h - 3)).map((l) => truncateVisible(l, this.cols() - 1));
52
+ const newH = shown.length;
53
+ let seq = SYNC_ON + HIDE;
54
+ if (this.parked) {
55
+ // Return to the content position first so the SAVE/RESTORE pairs below
56
+ // keep preserving it, not the old caret.
57
+ seq += RESTORE;
58
+ this.parked = false;
59
+ }
60
+ if (this.dockH === 0) {
61
+ // First activation: free the bottom rows by scrolling exactly as much
62
+ // as needed (preserves the banner above), then fence content into
63
+ // [1, h - newH]. DECSTBM homes the cursor; save/restore keeps it.
64
+ seq += SAVE + REGION_RESET + RESTORE;
65
+ seq += '\n'.repeat(newH) + `\x1b[${newH}A`;
66
+ seq += SAVE + `\x1b[1;${h - newH}r` + RESTORE;
67
+ }
68
+ else if (newH !== this.dockH) {
69
+ // Height change while active (menu open/close): overlay mode. Re-fence
70
+ // and clear any rows freed by a shrink; never scroll content.
71
+ seq += SAVE + `\x1b[1;${h - newH}r`;
72
+ for (let r = h - Math.max(this.dockH, newH) + 1; r <= h - newH; r++) {
73
+ seq += `\x1b[${r};1H\x1b[2K`;
74
+ }
75
+ seq += RESTORE;
76
+ }
77
+ seq += SAVE;
78
+ for (let i = 0; i < newH; i++) {
79
+ seq += `\x1b[${h - newH + 1 + i};1H\x1b[2K${shown[i]}`;
80
+ }
81
+ seq += RESTORE;
82
+ if (caret) {
83
+ const row = Math.min(h, h - newH + Math.max(1, caret.row));
84
+ const col = Math.max(1, Math.min(this.cols(), caret.col));
85
+ seq += SAVE + `\x1b[${row};${col}H`;
86
+ this.parked = true;
87
+ }
88
+ seq += SHOW + SYNC_OFF;
89
+ this.out.write(seq);
90
+ this.dockH = newH;
91
+ this.last = caret ? { lines, caret } : { lines };
92
+ }
93
+ /** Replay the last set() (e.g. after an animation overdrew part of the screen). */
94
+ repaint() {
95
+ if (this.last)
96
+ this.set(this.last.lines, this.last.caret);
97
+ }
98
+ /** Rows available to content above the dock. */
99
+ contentRows() {
100
+ return Math.max(1, this.rows() - this.dockH);
101
+ }
102
+ /**
103
+ * Absolute-paint the whole content region (rows 1..contentRows) from the
104
+ * given lines — used by history scrolling to show any window of the session
105
+ * log. Does not touch the DECSC slot or the fence; callers repaint() the
106
+ * dock afterwards to restore the caret.
107
+ */
108
+ paintContent(lines) {
109
+ const rowsN = this.contentRows();
110
+ const shown = lines.slice(-rowsN).map((l) => truncateVisible(l, this.cols() - 1));
111
+ let seq = SYNC_ON + HIDE;
112
+ for (let r = 1; r <= rowsN; r++) {
113
+ seq += `\x1b[${r};1H\x1b[2K${shown[r - 1] ?? ''}`;
114
+ }
115
+ seq += SYNC_OFF;
116
+ this.out.write(seq);
117
+ }
118
+ /**
119
+ * Write a scrollback line above the dock. With the fence in place a plain
120
+ * write cannot touch the dock rows; if the cursor is parked at the caret,
121
+ * hop back to the content region for the write and re-park after.
122
+ */
123
+ log(line) {
124
+ if (!this.parked) {
125
+ this.out.write(line + '\n');
126
+ return;
127
+ }
128
+ this.out.write(SYNC_ON + RESTORE + line + '\n' + SAVE + SYNC_OFF);
129
+ // The slot now holds the advanced content position; the caret will be
130
+ // re-established by the next set() with a caret argument.
131
+ this.parked = false;
132
+ }
133
+ /** Drop the fence, clear the dock rows, and restore the cursor. */
134
+ release() {
135
+ if (!this.dockH) {
136
+ this.out.write(SHOW);
137
+ return;
138
+ }
139
+ const h = this.rows();
140
+ let seq = SYNC_ON;
141
+ if (this.parked) {
142
+ seq += RESTORE;
143
+ this.parked = false;
144
+ }
145
+ seq += SAVE + REGION_RESET + RESTORE;
146
+ seq += SAVE;
147
+ for (let r = h - this.dockH + 1; r <= h; r++)
148
+ seq += `\x1b[${r};1H\x1b[2K`;
149
+ seq += RESTORE + SHOW + SYNC_OFF;
150
+ this.out.write(seq);
151
+ this.dockH = 0;
152
+ this.last = null;
153
+ }
154
+ }
155
+ //# sourceMappingURL=dock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dock.js","sourceRoot":"","sources":["../../src/util/dock.ts"],"names":[],"mappings":"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"}
package/dist/util/git.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execa } from 'execa';
2
- import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { access, constants, cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
3
3
  import { existsSync } from 'node:fs';
4
4
  import { tmpdir } from 'node:os';
5
5
  import path from 'node:path';
@@ -42,6 +42,117 @@ async function git(repo, args) {
42
42
  const { stdout } = await execa('git', args, { cwd: repo });
43
43
  return stdout.trim();
44
44
  }
45
+ /** Same as git(), with a scratch index so the repo's real index is untouched. */
46
+ async function gitWithIndex(repo, indexFile, args) {
47
+ const { stdout } = await execa('git', args, {
48
+ cwd: repo,
49
+ env: { GIT_INDEX_FILE: indexFile },
50
+ });
51
+ return stdout.trim();
52
+ }
53
+ /** Monotonic suffix so two scratch indexes in one process never collide. */
54
+ let scratchIndexSeq = 0;
55
+ /**
56
+ * Path for a throwaway index, inside the repo's own git dir rather than
57
+ * TMPDIR: git guarantees that directory exists and is writable, so a hostile
58
+ * or missing temp dir cannot make a snapshot fail and block a run from
59
+ * starting. Absolute, because git resolves GIT_INDEX_FILE against cwd.
60
+ */
61
+ async function scratchIndexPath(repo) {
62
+ const gitDir = await git(repo, ['rev-parse', '--absolute-git-dir']);
63
+ return path.join(gitDir, `copperhead-index-${process.pid}-${scratchIndexSeq++}`);
64
+ }
65
+ /**
66
+ * Write the untracked-but-not-ignored files to a tree object and return its
67
+ * sha, or null when there are none.
68
+ *
69
+ * `git stash create` only ever captures tracked changes, so on its own it
70
+ * leaves every new file a run (or the user) has not added yet outside the
71
+ * snapshot — and restore()'s `git clean -fd` then deletes exactly those. The
72
+ * two sets line up: `--exclude-standard` skips ignored paths and plain
73
+ * `clean -fd` (no -x) leaves them alone, so what is captured here is precisely
74
+ * what the rollback would otherwise destroy.
75
+ *
76
+ * Built through a scratch GIT_INDEX_FILE rather than `git add -A`, because
77
+ * this runs at the *start* of a run: staging the user's files would outlive a
78
+ * successful run and silently rewrite their staged/unstaged split.
79
+ */
80
+ async function snapshotUntracked(repo) {
81
+ const listed = await git(repo, ['ls-files', '--others', '--exclude-standard', '-z']);
82
+ const paths = listed.split('\0').filter(Boolean);
83
+ if (!paths.length)
84
+ return null;
85
+ const usable = await readableOnly(repo, paths);
86
+ if (!usable.length)
87
+ return null;
88
+ const indexFile = await scratchIndexPath(repo);
89
+ try {
90
+ await execa('git', ['update-index', '-z', '--add', '--stdin'], {
91
+ cwd: repo,
92
+ env: { GIT_INDEX_FILE: indexFile },
93
+ input: usable.join('\0') + '\0',
94
+ });
95
+ return (await gitWithIndex(repo, indexFile, ['write-tree'])) || null;
96
+ }
97
+ catch (err) {
98
+ // A path that passed the readability check above and still failed here
99
+ // lost the race (permissions or existence changed in between). Same
100
+ // refusal, since the same file would be destroyed by the rollback.
101
+ throw unsnapshottable(String(err.message).split('\n')[0] ?? 'unknown path');
102
+ }
103
+ finally {
104
+ await rm(indexFile, { force: true }).catch(() => { });
105
+ }
106
+ }
107
+ /**
108
+ * Drop untracked paths that no longer exist, and refuse on any that exist but
109
+ * cannot be read.
110
+ *
111
+ * `git update-index` aborts the whole batch with exit 128 on the first path it
112
+ * cannot open, and this runs before the first turn, so one stray root-owned or
113
+ * mode-000 file would otherwise refuse every `--allow-dirty` run with a bare
114
+ * `fatal: Unable to process path …`. A vanished path is dropped rather than
115
+ * refused: a file that no longer exists cannot be lost. One that exists but is
116
+ * unreadable is refused deliberately rather than skipped, because `restore()`'s
117
+ * `git clean -fd` deletes it either way, and skipping would quietly reinstate
118
+ * exactly the data loss the untracked snapshot exists to prevent.
119
+ */
120
+ async function readableOnly(repo, paths) {
121
+ const usable = [];
122
+ for (const p of paths) {
123
+ try {
124
+ await access(path.join(repo, p), constants.R_OK);
125
+ usable.push(p);
126
+ }
127
+ catch (err) {
128
+ if (err.code === 'ENOENT')
129
+ continue;
130
+ throw unsnapshottable(p);
131
+ }
132
+ }
133
+ return usable;
134
+ }
135
+ function unsnapshottable(what) {
136
+ return new PreflightError(`cannot read untracked file: ${what}`, 'a run started with --allow-dirty promises your uncommitted work survives a failed run, but a file copperhead cannot read cannot be snapshotted, and the rollback would delete it with nothing to restore it from', [
137
+ `make it readable: chmod +r "${what}"`,
138
+ 'or delete it, or add it to .gitignore so the rollback leaves it alone',
139
+ 'or commit your work and rerun without --allow-dirty',
140
+ ]);
141
+ }
142
+ /**
143
+ * Re-materialize the untracked files captured by snapshotUntracked(). Runs
144
+ * after `stash apply` so tracked restores win any path collision.
145
+ */
146
+ async function restoreUntracked(repo, tree) {
147
+ const indexFile = await scratchIndexPath(repo);
148
+ try {
149
+ await gitWithIndex(repo, indexFile, ['read-tree', tree]);
150
+ await gitWithIndex(repo, indexFile, ['checkout-index', '-a', '-f']);
151
+ }
152
+ finally {
153
+ await rm(indexFile, { force: true }).catch(() => { });
154
+ }
155
+ }
45
156
  export async function isGitRepo(repo) {
46
157
  try {
47
158
  await git(repo, ['rev-parse', '--git-dir']);
@@ -87,16 +198,19 @@ export async function gitPreflight(repo, opts = {}) {
87
198
  }
88
199
  /**
89
200
  * Snapshot the working tree before a run. On a clean tree HEAD is enough;
90
- * with --allow-dirty we keep a `git stash create` object so uncommitted work
91
- * survives a rollback (SPEC §7).
201
+ * with --allow-dirty we keep a `git stash create` object for tracked changes
202
+ * plus a tree of the untracked files it cannot see, so uncommitted work
203
+ * survives a rollback intact (SPEC §7).
92
204
  */
93
205
  export async function snapshot(repo) {
94
206
  const head = await git(repo, ['rev-parse', 'HEAD']);
95
207
  let stash = null;
208
+ let untracked = null;
96
209
  if (await isDirty(repo)) {
97
210
  stash = (await git(repo, ['stash', 'create'])) || null;
211
+ untracked = await snapshotUntracked(repo);
98
212
  }
99
- return { head, stash };
213
+ return { head, stash, untracked };
100
214
  }
101
215
  /**
102
216
  * Hard-restore the working tree to a snapshot (AC-3.6). The run audit trail
@@ -127,6 +241,17 @@ export async function restore(repo, snap) {
127
241
  if (snap.stash) {
128
242
  await git(repo, ['stash', 'apply', snap.stash]);
129
243
  }
244
+ // The clean above deleted every untracked file; put back the ones that
245
+ // were there before the run. Never fatal: a rollback that restored the
246
+ // tracked state is still better than one that threw halfway.
247
+ if (snap.untracked) {
248
+ try {
249
+ await restoreUntracked(repo, snap.untracked);
250
+ }
251
+ catch (err) {
252
+ console.warn(`warning: could not restore untracked files after rollback: ${err.message}`);
253
+ }
254
+ }
130
255
  }
131
256
  finally {
132
257
  if (backup && existsSync(backup)) {