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.
- package/README.md +36 -4
- package/dist/agent/animate.js +76 -0
- package/dist/agent/animate.js.map +1 -0
- package/dist/agent/box.js +89 -0
- package/dist/agent/box.js.map +1 -0
- package/dist/agent/dock-renderer.js +173 -0
- package/dist/agent/dock-renderer.js.map +1 -0
- package/dist/agent/logo.js +21 -0
- package/dist/agent/logo.js.map +1 -0
- package/dist/agent/loop.js +15 -5
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/providers/claude-code.js +1 -212
- package/dist/agent/providers/claude-code.js.map +1 -1
- package/dist/agent/providers/cursor.js +317 -0
- package/dist/agent/providers/cursor.js.map +1 -0
- package/dist/agent/providers/tool-protocol.js +205 -0
- package/dist/agent/providers/tool-protocol.js.map +1 -0
- package/dist/agent/render.js +32 -15
- package/dist/agent/render.js.map +1 -1
- package/dist/agent/runmeta.js +4 -5
- package/dist/agent/runmeta.js.map +1 -1
- package/dist/agent/theme.js +84 -0
- package/dist/agent/theme.js.map +1 -0
- package/dist/cli.js +134 -13
- package/dist/cli.js.map +1 -1
- package/dist/commands/create.js +41 -32
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/demo.js +146 -0
- package/dist/commands/demo.js.map +1 -0
- package/dist/commands/doctor.js +240 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/repl-inspect.js +342 -0
- package/dist/commands/repl-inspect.js.map +1 -0
- package/dist/commands/repl.js +618 -0
- package/dist/commands/repl.js.map +1 -0
- package/dist/config.js +5 -2
- package/dist/config.js.map +1 -1
- package/dist/kicad/cli.js +126 -6
- package/dist/kicad/cli.js.map +1 -1
- package/dist/util/cli-args.js +35 -0
- package/dist/util/cli-args.js.map +1 -0
- package/dist/util/dock.js +155 -0
- package/dist/util/dock.js.map +1 -0
- package/dist/util/git.js +129 -4
- package/dist/util/git.js.map +1 -1
- package/dist/util/live-prompt.js +542 -0
- package/dist/util/live-prompt.js.map +1 -0
- package/dist/util/paths.js +9 -0
- package/dist/util/paths.js.map +1 -1
- package/dist/util/select.js +172 -0
- package/dist/util/select.js.map +1 -0
- package/package.json +4 -6
- package/src/agent/animate.ts +90 -0
- package/src/agent/box.ts +99 -0
- package/src/agent/dock-renderer.ts +181 -0
- package/src/agent/logo.ts +23 -0
- package/src/agent/loop.ts +15 -5
- package/src/agent/providers/claude-code.ts +2 -216
- package/src/agent/providers/cursor.ts +364 -0
- package/src/agent/providers/tool-protocol.ts +212 -0
- package/src/agent/render.ts +33 -16
- package/src/agent/runmeta.ts +6 -7
- package/src/agent/theme.ts +91 -0
- package/src/cli.ts +139 -15
- package/src/commands/create.ts +81 -30
- package/src/commands/demo.ts +184 -0
- package/src/commands/doctor.ts +289 -0
- package/src/commands/repl-inspect.ts +353 -0
- package/src/commands/repl.ts +685 -0
- package/src/config.ts +6 -3
- package/src/kicad/cli.ts +132 -7
- package/src/layout/claude-ui-layout.md +72 -0
- package/src/layout/repl-ui-layout.md +139 -0
- package/src/util/cli-args.ts +42 -0
- package/src/util/dock.ts +161 -0
- package/src/util/git.ts +140 -4
- package/src/util/live-prompt.ts +595 -0
- package/src/util/paths.ts +10 -0
- package/src/util/select.ts +192 -0
package/README.md
CHANGED
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
|
|
11
11
|
**Cursor for circuit boards.** An AI agent that designs, documents, and validates real PCBs from a prompt, working directly on existing KiCad repositories.
|
|
12
12
|
|
|
13
|
+
<p align="center">
|
|
14
|
+
<img src="https://raw.githubusercontent.com/chouhanindustries/copperhead/main/assets/copperhead.gif" alt="copperhead agent shell demo" width="720">
|
|
15
|
+
</p>
|
|
16
|
+
|
|
13
17
|
> **Status: early.** Phase 1 is implemented and the CLI runs. The [technical specification](openspec/specs/SPEC.md) is the source of truth; expect the surface to move before 1.0.
|
|
14
18
|
|
|
15
19
|
Full documentation lives at [docs.copperhead.sh](https://docs.copperhead.sh).
|
|
@@ -29,19 +33,35 @@ It reads and edits real `.kicad_sch` / `.kicad_pcb` files (s-expression text), m
|
|
|
29
33
|
npm install -g copperhead # or: npx copperhead check
|
|
30
34
|
```
|
|
31
35
|
|
|
36
|
+
### Bootstrap script (macOS and Linux)
|
|
37
|
+
|
|
38
|
+
Prefer a one-command setup? [`install.sh`](install.sh) checks the prerequisites below, installs copperhead (globally via npm, or built from source when run inside a checkout), and verifies the result with `copperhead doctor`:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
./install.sh # interactive: asks before installing anything
|
|
42
|
+
./install.sh --yes # assume yes to the install prompts
|
|
43
|
+
./install.sh --check-only # report what is missing, install nothing
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The script is conservative by design: it never runs `sudo` and never edits shell config; whenever it cannot act safely on its own it prints the exact command for you to run instead. Rerunning is safe: on a ready machine it installs nothing and exits 0.
|
|
47
|
+
|
|
32
48
|
### Requirements
|
|
33
49
|
|
|
34
50
|
- Node.js ≥ 20
|
|
35
51
|
- [KiCad](https://www.kicad.org/) ≥ 8 with `kicad-cli` on PATH
|
|
36
|
-
- One model backend: a locally installed, ChatGPT-authenticated [Codex CLI](https://learn.chatgpt.com/docs/codex/cli), a logged-in Claude Code (see [Saved login](#saved-login-claude-code)), or `ANTHROPIC_API_KEY`/`OPENAI_API_KEY` in the environment. `check` never calls an LLM.
|
|
52
|
+
- One model backend: a locally installed, ChatGPT-authenticated [Codex CLI](https://learn.chatgpt.com/docs/codex/cli), a logged-in [Cursor Agent CLI](#saved-login-cursor-agent) (`agent login`), logged-in Claude Code (see [Saved login](#saved-login-claude-code)), or `ANTHROPIC_API_KEY`/`OPENAI_API_KEY` in the environment. `check` never calls an LLM.
|
|
37
53
|
|
|
38
54
|
## Quick start
|
|
39
55
|
|
|
40
56
|
In an existing KiCad repository:
|
|
41
57
|
|
|
42
58
|
```bash
|
|
43
|
-
export ANTHROPIC_API_KEY=... # or OPENAI_API_KEY
|
|
59
|
+
export ANTHROPIC_API_KEY=... # or OPENAI_API_KEY; optional: with no key, `copperhead` offers a model picker
|
|
44
60
|
copperhead init # scaffold docs/ from the schematic; idempotent
|
|
61
|
+
copperhead # interactive agent shell (Claude Code–style REPL)
|
|
62
|
+
copperhead demo --tour # what the agent does (no LLM)
|
|
63
|
+
copperhead demo --model cursor # full USB-C breakout create pipeline
|
|
64
|
+
# or one-shot:
|
|
45
65
|
copperhead do "add reverse-polarity protection on VIN"
|
|
46
66
|
copperhead check # ERC + DRC + doc drift; no LLM, CI-safe
|
|
47
67
|
```
|
|
@@ -88,14 +108,26 @@ Spec-gated in, verification-gated out: the design can't drift from its requireme
|
|
|
88
108
|
copperhead init [--path hardware/] # scaffold docs/ from an existing schematic; idempotent
|
|
89
109
|
copperhead do "<change request>" # the core loop: propose, edit, verify, propagate, commit
|
|
90
110
|
copperhead check # ERC + DRC + doc-drift + spec validation; no LLM calls (alias: verify)
|
|
111
|
+
copperhead doctor # env preflight: kicad-cli, git, node, provider credential; no LLM/network
|
|
91
112
|
copperhead sync [--dry-run] # verify the whole design state, resolve drift
|
|
92
113
|
copperhead create --brief brief.md # brief → full output package
|
|
93
114
|
copperhead export bom --supplier jlcpcb # supplier-ready ordering file from docs/BOM.md
|
|
94
115
|
```
|
|
95
116
|
|
|
96
|
-
Global flags: `--repo <path>` (default: cwd) and `--json` for machine-readable output. `do` and `
|
|
117
|
+
Global flags: `--repo <path>` (default: cwd) and `--json` for machine-readable output. `--model` is available on `do`, `sync`, `create`, and `doctor`; `--interactive` only on `do` and `create`; `do` also takes `--dry-run`, `--max-turns`, and `--allow-dirty`.
|
|
118
|
+
|
|
119
|
+
`--model` accepts `gpt-5` (OpenAI), `claude` / `claude-<id>` (Anthropic API), `claude-code` / `claude-code:<id>` (Claude Code, saved login), `cursor` / `cursor:<id>` (Cursor Agent CLI, saved login), and `codex` / `codex:<id>` (Codex CLI, saved login). Routing is by prefix; `claude-code` is matched before the `claude` prefix.
|
|
120
|
+
|
|
121
|
+
### Saved login (Cursor Agent)
|
|
122
|
+
|
|
123
|
+
`--model cursor` drives the Cursor Agent CLI with your saved login from `agent login`, so you can run copperhead with **no `OPENAI_API_KEY` or `ANTHROPIC_API_KEY`**. Cursor is used purely as a reasoning backend in plan mode: the agent loop, safety gates, and every file edit stay inside copperhead.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
agent status
|
|
127
|
+
copperhead do "add reverse-polarity protection on VIN" --model cursor
|
|
128
|
+
```
|
|
97
129
|
|
|
98
|
-
|
|
130
|
+
If `agent` is not on `PATH`, set `COPPERHEAD_CURSOR_PATH` to the CLI binary (also available as `cursor-agent` on some installs). Cursor's JSON schema does not report token usage, so run summaries show 0 tokens for `--model cursor`.
|
|
99
131
|
|
|
100
132
|
### Saved login (Claude Code)
|
|
101
133
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subtle TTY motion for attended copperhead chrome. Disabled when color is
|
|
3
|
+
* off, stdout is not a TTY, CI=1, or COPPERHEAD_NO_ANIM=1.
|
|
4
|
+
*/
|
|
5
|
+
import { copper, isColorEnabled } from './theme.js';
|
|
6
|
+
export function prefersAnimation() {
|
|
7
|
+
return (isColorEnabled() &&
|
|
8
|
+
Boolean(process.stdout.isTTY) &&
|
|
9
|
+
!process.env.CI &&
|
|
10
|
+
!process.env.COPPERHEAD_NO_ANIM);
|
|
11
|
+
}
|
|
12
|
+
export function sleep(ms) {
|
|
13
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
14
|
+
}
|
|
15
|
+
const HIDE = '\x1b[?25l';
|
|
16
|
+
const SHOW = '\x1b[?25h';
|
|
17
|
+
/** Growing fiducial frames (3 rows), converging on the block mark. Exported for tests. */
|
|
18
|
+
export function fiducialBootFrames() {
|
|
19
|
+
return [
|
|
20
|
+
[' ', ' ██ ', ' '],
|
|
21
|
+
[' ▗▄▄▖ ', ' █ █ ', ' ▝▀▀▘ '],
|
|
22
|
+
[' ▄▟▙▄ ', ' ██ ██ ', ' ▀▜▛▀ '],
|
|
23
|
+
[' ▄▟▙▄ ', ' ███ ███', ' ▀▜▛▀ '],
|
|
24
|
+
].map((frame) => frame.map((row) => copper(row)));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Pulse the fiducial mark in place over the already-rendered banner (rows
|
|
28
|
+
* `topRow..topRow+2`, column 1). The full screen loads first; this animates
|
|
29
|
+
* after, two grow cycles ending on the final mark. Frames are constant
|
|
30
|
+
* width so they overwrite each other without clearing the banner text
|
|
31
|
+
* beside them. Deliberately avoids DECSC/DECRC: the dock owns that slot for
|
|
32
|
+
* its caret-parking protocol; the caller repaints the dock afterwards to
|
|
33
|
+
* restore the caret.
|
|
34
|
+
*/
|
|
35
|
+
export async function animateMarkAt(out, topRow, opts) {
|
|
36
|
+
if (!prefersAnimation())
|
|
37
|
+
return;
|
|
38
|
+
const frames = fiducialBootFrames();
|
|
39
|
+
const frameMs = opts?.slow ? 110 : 45;
|
|
40
|
+
const holdMs = opts?.slow ? 220 : 90;
|
|
41
|
+
try {
|
|
42
|
+
for (let cycle = 0; cycle < 2; cycle++) {
|
|
43
|
+
for (const frame of frames) {
|
|
44
|
+
let seq = HIDE;
|
|
45
|
+
frame.forEach((row, i) => {
|
|
46
|
+
seq += `\x1b[${topRow + i};1H${row}`;
|
|
47
|
+
});
|
|
48
|
+
out.write(seq);
|
|
49
|
+
await sleep(frameMs);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
await sleep(holdMs);
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
// Never leave the terminal with a hidden cursor, even if a write or
|
|
56
|
+
// sleep throws mid-animation.
|
|
57
|
+
out.write(SHOW);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Copper horizontal rule (PCB-trace vibe). */
|
|
61
|
+
export function traceRule(width = 32) {
|
|
62
|
+
return copper(' ' + '─'.repeat(Math.max(8, Math.min(width, 48))));
|
|
63
|
+
}
|
|
64
|
+
/** Cascade lines (help / demo sections). Instant when animation is off. */
|
|
65
|
+
export async function staggerWrite(lines, write, delayMs = 12) {
|
|
66
|
+
if (!prefersAnimation()) {
|
|
67
|
+
for (const line of lines)
|
|
68
|
+
write(line);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
for (const line of lines) {
|
|
72
|
+
write(line);
|
|
73
|
+
await sleep(delayMs);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=animate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"animate.js","sourceRoot":"","sources":["../../src/agent/animate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Box-drawing primitives for the bottom-docked REPL chrome: input box,
|
|
3
|
+
* status bar, and notice callouts. All width math ignores SGR sequences so
|
|
4
|
+
* colored segments never break padding or wrap accounting.
|
|
5
|
+
*/
|
|
6
|
+
import { copper, err, isColorEnabled, ruleDim, warn } from './theme.js';
|
|
7
|
+
const SGR = /\x1b\[[0-9;]*m/g;
|
|
8
|
+
/** Visible width ignoring SGR color codes. */
|
|
9
|
+
export function visibleWidth(s) {
|
|
10
|
+
return s.replace(SGR, '').length;
|
|
11
|
+
}
|
|
12
|
+
/** Inverse video (synthetic caret / hover); no-op when color is off. */
|
|
13
|
+
export function inverse(s) {
|
|
14
|
+
if (!isColorEnabled() || s === '')
|
|
15
|
+
return s;
|
|
16
|
+
return `\x1b[7m${s}\x1b[0m`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Wrap styled spans into lines no wider than `width`. Styling is applied per
|
|
20
|
+
* slice, so a span can be cut at a wrap boundary without leaking SGR state.
|
|
21
|
+
*/
|
|
22
|
+
export function wrapSpans(spans, width) {
|
|
23
|
+
const w = Math.max(1, width);
|
|
24
|
+
const lines = [];
|
|
25
|
+
let cur = '';
|
|
26
|
+
let curLen = 0;
|
|
27
|
+
for (const span of spans) {
|
|
28
|
+
let text = span.text;
|
|
29
|
+
while (text.length) {
|
|
30
|
+
const take = text.slice(0, w - curLen);
|
|
31
|
+
cur += span.paint ? span.paint(take) : take;
|
|
32
|
+
curLen += take.length;
|
|
33
|
+
text = text.slice(take.length);
|
|
34
|
+
if (curLen >= w) {
|
|
35
|
+
lines.push(cur);
|
|
36
|
+
cur = '';
|
|
37
|
+
curLen = 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (cur !== '' || !lines.length)
|
|
42
|
+
lines.push(cur);
|
|
43
|
+
return lines;
|
|
44
|
+
}
|
|
45
|
+
/** Full-width horizontal rule (the input-area separators), #888888. */
|
|
46
|
+
export function rule(width) {
|
|
47
|
+
return ruleDim('─'.repeat(Math.max(1, width)));
|
|
48
|
+
}
|
|
49
|
+
/** Truncate to a visible width, keeping SGR sequences intact and closed. */
|
|
50
|
+
export function truncateVisible(s, width) {
|
|
51
|
+
if (visibleWidth(s) <= width)
|
|
52
|
+
return s;
|
|
53
|
+
let out = '';
|
|
54
|
+
let vis = 0;
|
|
55
|
+
let i = 0;
|
|
56
|
+
let hadSgr = false;
|
|
57
|
+
while (i < s.length && vis < width) {
|
|
58
|
+
if (s[i] === '\x1b') {
|
|
59
|
+
const m = /^\x1b\[[0-9;]*m/.exec(s.slice(i));
|
|
60
|
+
if (m) {
|
|
61
|
+
out += m[0];
|
|
62
|
+
hadSgr = true;
|
|
63
|
+
i += m[0].length;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
out += s[i];
|
|
68
|
+
vis++;
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
return out + (hadSgr ? '\x1b[0m' : '');
|
|
72
|
+
}
|
|
73
|
+
/** One status line with a left- and a right-justified half. */
|
|
74
|
+
export function statusBar(left, right, width) {
|
|
75
|
+
const w = Math.max(8, width);
|
|
76
|
+
const l = visibleWidth(left);
|
|
77
|
+
const r = visibleWidth(right);
|
|
78
|
+
// Too narrow for both: the hints matter more than the meta.
|
|
79
|
+
if (l + r + 2 > w)
|
|
80
|
+
return truncateVisible(left, w);
|
|
81
|
+
return left + ' '.repeat(w - l - r) + right;
|
|
82
|
+
}
|
|
83
|
+
/** Notice block: thin colored left bar + title + dim body (banner callouts). */
|
|
84
|
+
export function callout(kind, title, body) {
|
|
85
|
+
const paintBar = kind === 'err' ? err : kind === 'warn' ? warn : copper;
|
|
86
|
+
const bar = paintBar(' ▎');
|
|
87
|
+
return [`${bar} ${paintBar(title)}`, ...body.map((b) => `${bar} ${b}`)];
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=box.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"box.js","sourceRoot":"","sources":["../../src/agent/box.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REPL-session renderer: the single owner of the bottom dock during agent
|
|
3
|
+
* turns. Durable output (tool lines, turn markers, the outcome) flows into
|
|
4
|
+
* the content region through `emit`, which the REPL also records as
|
|
5
|
+
* scrollable history; the live observability line (spinner, turn, tokens,
|
|
6
|
+
* elapsed, busy text) is painted inside the dock, pinned to the bottom of
|
|
7
|
+
* the screen no matter how much output scrolls above it.
|
|
8
|
+
*/
|
|
9
|
+
import { rule, statusBar } from './box.js';
|
|
10
|
+
import { copper, dim, styleOutcome, toolLine, warn } from './theme.js';
|
|
11
|
+
import { fmtDuration, fmtTokens, turnMarker } from './render.js';
|
|
12
|
+
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
13
|
+
/** Claude Code-style working words, board-shop edition. One per turn. */
|
|
14
|
+
const WORKING = [
|
|
15
|
+
'Routing',
|
|
16
|
+
'Etching',
|
|
17
|
+
'Reflowing',
|
|
18
|
+
'Soldering',
|
|
19
|
+
'Drilling',
|
|
20
|
+
'Plating',
|
|
21
|
+
'Probing',
|
|
22
|
+
'Fluxing',
|
|
23
|
+
'Tinning',
|
|
24
|
+
'Laminating',
|
|
25
|
+
'Silkscreening',
|
|
26
|
+
'Panelizing',
|
|
27
|
+
];
|
|
28
|
+
/** Fixed word-slot width: longest word plus the static dots. */
|
|
29
|
+
const WORD_SLOT = Math.max(...WORKING.map((w) => w.length)) + 3;
|
|
30
|
+
export class DockRenderer {
|
|
31
|
+
dock;
|
|
32
|
+
emit;
|
|
33
|
+
chrome;
|
|
34
|
+
turn = 0;
|
|
35
|
+
maxTurns = 0;
|
|
36
|
+
tokensIn = 0;
|
|
37
|
+
tokensOut = 0;
|
|
38
|
+
streamedChars = 0;
|
|
39
|
+
busy = null;
|
|
40
|
+
frame = 0;
|
|
41
|
+
runSeed = 0;
|
|
42
|
+
startMs = Date.now();
|
|
43
|
+
timer = null;
|
|
44
|
+
constructor(dock,
|
|
45
|
+
/** Durable line sink: content region + session history. */
|
|
46
|
+
emit,
|
|
47
|
+
/** Dock chrome around the status row (meta right, bottom hints). */
|
|
48
|
+
chrome) {
|
|
49
|
+
this.dock = dock;
|
|
50
|
+
this.emit = emit;
|
|
51
|
+
this.chrome = chrome;
|
|
52
|
+
}
|
|
53
|
+
log(line) {
|
|
54
|
+
this.emit(line);
|
|
55
|
+
}
|
|
56
|
+
toolResult(name, firstLine) {
|
|
57
|
+
this.emit(toolLine(name, firstLine));
|
|
58
|
+
}
|
|
59
|
+
turnStartMs = Date.now();
|
|
60
|
+
turnStart(turn, maxTurns, tokensIn, tokensOut) {
|
|
61
|
+
if (turn === 1) {
|
|
62
|
+
this.startMs = Date.now();
|
|
63
|
+
this.runSeed++;
|
|
64
|
+
}
|
|
65
|
+
this.turnStartMs = Date.now();
|
|
66
|
+
this.turn = turn;
|
|
67
|
+
this.maxTurns = maxTurns;
|
|
68
|
+
this.tokensIn = tokensIn;
|
|
69
|
+
this.tokensOut = tokensOut;
|
|
70
|
+
this.emit(dim(turnMarker(turn, maxTurns, tokensIn, tokensOut)));
|
|
71
|
+
this.arm();
|
|
72
|
+
this.paint();
|
|
73
|
+
}
|
|
74
|
+
status(text) {
|
|
75
|
+
this.busy = text;
|
|
76
|
+
if (text === null)
|
|
77
|
+
this.streamedChars = 0;
|
|
78
|
+
this.paint();
|
|
79
|
+
}
|
|
80
|
+
heartbeat(info) {
|
|
81
|
+
this.streamedChars = info.streamedChars;
|
|
82
|
+
this.paint();
|
|
83
|
+
}
|
|
84
|
+
finish(line) {
|
|
85
|
+
this.disarm();
|
|
86
|
+
this.busy = null;
|
|
87
|
+
this.emit(styleOutcome(line));
|
|
88
|
+
// The next prompt's renderDock() takes the dock back over.
|
|
89
|
+
}
|
|
90
|
+
/** Currently displayed working word; morphs letter by letter on change. */
|
|
91
|
+
shownWord = '';
|
|
92
|
+
targetWord = '';
|
|
93
|
+
/** -1 = settled; otherwise progress through erase-then-type transition. */
|
|
94
|
+
morph = -1;
|
|
95
|
+
arm() {
|
|
96
|
+
if (this.timer)
|
|
97
|
+
return;
|
|
98
|
+
this.timer = setInterval(() => {
|
|
99
|
+
this.frame++;
|
|
100
|
+
// Sweep three positions per tick: a full word crossfade in under a second.
|
|
101
|
+
if (this.morph >= 0)
|
|
102
|
+
this.morph += 3;
|
|
103
|
+
this.paint();
|
|
104
|
+
}, 120);
|
|
105
|
+
this.timer.unref?.();
|
|
106
|
+
}
|
|
107
|
+
disarm() {
|
|
108
|
+
if (this.timer)
|
|
109
|
+
clearInterval(this.timer);
|
|
110
|
+
this.timer = null;
|
|
111
|
+
}
|
|
112
|
+
/** Pinned observability row painted inside the dock (same layout as the prompt). */
|
|
113
|
+
paint() {
|
|
114
|
+
const w = Math.max(10, this.dock.cols() - 1);
|
|
115
|
+
const spinner = copper(FRAMES[this.frame % FRAMES.length]);
|
|
116
|
+
// Claude Code-style working word, board-shop themed: rotates every ~6s
|
|
117
|
+
// while a turn runs, with a shimmering highlight sweeping the letters.
|
|
118
|
+
// A word change morphs letter by letter: the old word is erased into
|
|
119
|
+
// `_` slots left to right, then the new word types over them.
|
|
120
|
+
const wordIdx = (this.runSeed + this.turn + Math.floor((Date.now() - this.turnStartMs) / 6000)) %
|
|
121
|
+
WORKING.length;
|
|
122
|
+
const target = WORKING[wordIdx];
|
|
123
|
+
if (this.shownWord === '')
|
|
124
|
+
this.shownWord = target;
|
|
125
|
+
if (target !== this.targetWord) {
|
|
126
|
+
this.targetWord = target;
|
|
127
|
+
if (target !== this.shownWord)
|
|
128
|
+
this.morph = 0;
|
|
129
|
+
}
|
|
130
|
+
let text = `${this.shownWord}...`;
|
|
131
|
+
if (this.morph >= 0) {
|
|
132
|
+
// Single left-to-right sweep over the whole dotted string (dots
|
|
133
|
+
// included): each position flips old char -> `_` -> new char, so the
|
|
134
|
+
// words cross-fade character by character.
|
|
135
|
+
const oldS = `${this.shownWord}...`;
|
|
136
|
+
const newS = `${this.targetWord}...`;
|
|
137
|
+
const width = Math.max(oldS.length, newS.length);
|
|
138
|
+
const k = this.morph;
|
|
139
|
+
if (k >= width) {
|
|
140
|
+
this.shownWord = this.targetWord;
|
|
141
|
+
this.morph = -1;
|
|
142
|
+
text = `${this.shownWord}...`;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
let out = '';
|
|
146
|
+
for (let i = 0; i < width; i++) {
|
|
147
|
+
out += i < k ? (newS[i] ?? ' ') : i === k ? '_' : (oldS[i] ?? ' ');
|
|
148
|
+
}
|
|
149
|
+
text = out.trimEnd();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Fixed-width slot (longest word + dots) so the stats after it never
|
|
153
|
+
// shift; dots and morph slots share the word's copper.
|
|
154
|
+
const word = copper(text.padEnd(WORD_SLOT));
|
|
155
|
+
const parts = [
|
|
156
|
+
dim(`turn ${this.turn}/${this.maxTurns}`),
|
|
157
|
+
dim(`${fmtTokens(this.tokensIn)} in / ${fmtTokens(this.tokensOut)} out`),
|
|
158
|
+
dim(fmtDuration(Date.now() - this.startMs)),
|
|
159
|
+
];
|
|
160
|
+
if (this.busy) {
|
|
161
|
+
parts.push(warn(this.streamedChars ? `${this.busy} ~${fmtTokens(this.streamedChars)} ch` : this.busy));
|
|
162
|
+
}
|
|
163
|
+
const { meta, hints } = this.chrome();
|
|
164
|
+
this.dock.set([
|
|
165
|
+
...(meta ? [statusBar('', `${meta} `, w)] : []),
|
|
166
|
+
rule(w),
|
|
167
|
+
statusBar(`${spinner} ${word}`, `${parts.join(dim(' · '))} `, w),
|
|
168
|
+
rule(w),
|
|
169
|
+
...(hints ? [statusBar(` ${hints}`, '', w)] : []),
|
|
170
|
+
]);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=dock-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dock-renderer.js","sourceRoot":"","sources":["../../src/agent/dock-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAyB,MAAM,aAAa,CAAC;AAGxF,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAElE,yEAAyE;AACzE,MAAM,OAAO,GAAG;IACd,SAAS;IACT,SAAS;IACT,WAAW;IACX,WAAW;IACX,UAAU;IACV,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACT,YAAY;IACZ,eAAe;IACf,YAAY;CACb,CAAC;AAEF,gEAAgE;AAChE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC;AAEhE,MAAM,OAAO,YAAY;IAaJ;IAEA;IAEA;IAhBX,IAAI,GAAG,CAAC,CAAC;IACT,QAAQ,GAAG,CAAC,CAAC;IACb,QAAQ,GAAG,CAAC,CAAC;IACb,SAAS,GAAG,CAAC,CAAC;IACd,aAAa,GAAG,CAAC,CAAC;IAClB,IAAI,GAAkB,IAAI,CAAC;IAC3B,KAAK,GAAG,CAAC,CAAC;IACV,OAAO,GAAG,CAAC,CAAC;IACZ,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACrB,KAAK,GAA0C,IAAI,CAAC;IAE5D,YACmB,IAAkB;IACnC,2DAA2D;IAC1C,IAA4B;IAC7C,oEAAoE;IACnD,MAA2D;QAJ3D,SAAI,GAAJ,IAAI,CAAc;QAElB,SAAI,GAAJ,IAAI,CAAwB;QAE5B,WAAM,GAAN,MAAM,CAAqD;IAC3E,CAAC;IAEJ,GAAG,CAAC,IAAY;QACd,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC;IAED,UAAU,CAAC,IAAY,EAAE,SAAiB;QACxC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;IACvC,CAAC;IAEO,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEjC,SAAS,CAAC,IAAY,EAAE,QAAgB,EAAE,QAAgB,EAAE,SAAiB;QAC3E,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;YACf,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC1B,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;QAChE,IAAI,CAAC,GAAG,EAAE,CAAC;QACX,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC;IAED,MAAM,CAAC,IAAmB;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,IAAI,KAAK,IAAI;YAAE,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC;IAED,SAAS,CAAC,IAAkD;QAC1D,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC;QACxC,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC;IAED,MAAM,CAAC,IAAY;QACjB,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9B,2DAA2D;IAC7D,CAAC;IAED,2EAA2E;IACnE,SAAS,GAAG,EAAE,CAAC;IACf,UAAU,GAAG,EAAE,CAAC;IACxB,2EAA2E;IACnE,KAAK,GAAG,CAAC,CAAC,CAAC;IAEX,GAAG;QACT,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,2EAA2E;YAC3E,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC;gBAAE,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;YACrC,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC,EAAE,GAAG,CAAC,CAAC;QACR,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IACvB,CAAC;IAEO,MAAM;QACZ,IAAI,IAAI,CAAC,KAAK;YAAE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;IAED,oFAAoF;IAC5E,KAAK;QACX,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAE,CAAC,CAAC;QAC5D,uEAAuE;QACvE,uEAAuE;QACvE,qEAAqE;QACrE,8DAA8D;QAC9D,MAAM,OAAO,GACX,CAAC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,CAAC;YAC/E,OAAO,CAAC,MAAM,CAAC;QACjB,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAE,CAAC;QACjC,IAAI,IAAI,CAAC,SAAS,KAAK,EAAE;YAAE,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC;QACnD,IAAI,MAAM,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;YAC/B,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC;YACzB,IAAI,MAAM,KAAK,IAAI,CAAC,SAAS;gBAAE,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;QAChD,CAAC;QACD,IAAI,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,KAAK,CAAC;QAClC,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;YACpB,gEAAgE;YAChE,qEAAqE;YACrE,2CAA2C;YAC3C,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,KAAK,CAAC;YACpC,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,UAAU,KAAK,CAAC;YACrC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YACjD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;YACrB,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC;gBACjC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;gBAChB,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,KAAK,CAAC;YAChC,CAAC;iBAAM,CAAC;gBACN,IAAI,GAAG,GAAG,EAAE,CAAC;gBACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC/B,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;gBACrE,CAAC;gBACD,IAAI,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;YACvB,CAAC;QACH,CAAC;QACD,qEAAqE;QACrE,uDAAuD;QACvD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG;YACZ,GAAG,CAAC,QAAQ,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACzC,GAAG,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;YACxE,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;SAC5C,CAAC;QACF,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,KAAK,CAAC,IAAI,CACR,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAC3F,CAAC;QACJ,CAAC;QACD,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACtC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;YACZ,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/C,IAAI,CAAC,CAAC,CAAC;YACP,SAAS,CAAC,GAAG,OAAO,IAAI,IAAI,EAAE,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;YAChE,IAAI,CAAC,CAAC,CAAC;YACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SACnD,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal block-art mark derived from the website logo
|
|
3
|
+
* (docs/public/favicon.svg and docs.copperhead.sh): a via with a square
|
|
4
|
+
* drilled hole and long copper tracks routed out of both sides, copper
|
|
5
|
+
* #b87333 on dark. (`scripts/gen-logo.mjs` renders the exact favicon
|
|
6
|
+
* geometry at any size for reference.)
|
|
7
|
+
*/
|
|
8
|
+
import { copper } from './theme.js';
|
|
9
|
+
/** 3-row quadrant-block via with long tracks; rows are equal width. */
|
|
10
|
+
export function fiducialMark() {
|
|
11
|
+
return [
|
|
12
|
+
' ▄▟▙▄ ',
|
|
13
|
+
' ███ ███',
|
|
14
|
+
' ▀▜▛▀ ',
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
/** The mark painted in brand copper. */
|
|
18
|
+
export function fiducialLines() {
|
|
19
|
+
return fiducialMark().map((line) => copper(line));
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=logo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logo.js","sourceRoot":"","sources":["../../src/agent/logo.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,uEAAuE;AACvE,MAAM,UAAU,YAAY;IAC1B,OAAO;QACL,WAAW;QACX,WAAW;QACX,WAAW;KACZ,CAAC;AACJ,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,aAAa;IAC3B,OAAO,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AACpD,CAAC"}
|
package/dist/agent/loop.js
CHANGED
|
@@ -10,6 +10,7 @@ import { loadConfig, CONFIG_DIR } from '../config.js';
|
|
|
10
10
|
import { Transcript } from './transcript.js';
|
|
11
11
|
import { collectRunMeta, renderCliHeader } from './runmeta.js';
|
|
12
12
|
import { plainRenderer, fmtDuration, fmtTokens } from './render.js';
|
|
13
|
+
import { styleHeaderLines } from './theme.js';
|
|
13
14
|
import { ObligationsLedger } from './ledger.js';
|
|
14
15
|
import { gitPreflight, isDirty, snapshot, restore, commitAll, changedFiles, preserveFailedRun } from '../util/git.js';
|
|
15
16
|
import { withRetry, isRateLimit, sessionLimit } from '../util/retry.js';
|
|
@@ -19,6 +20,7 @@ import { OpenAIProvider } from './providers/openai.js';
|
|
|
19
20
|
import { AnthropicProvider } from './providers/anthropic.js';
|
|
20
21
|
import { CodexProvider } from './providers/codex.js';
|
|
21
22
|
import { ClaudeCodeProvider } from './providers/claude-code.js';
|
|
23
|
+
import { CursorProvider } from './providers/cursor.js';
|
|
22
24
|
import { openSynapMemory } from '../memory/synap.js';
|
|
23
25
|
export async function makeProvider(model, sessionResume = false) {
|
|
24
26
|
if (model === 'codex' || model.startsWith('codex:')) {
|
|
@@ -46,6 +48,14 @@ export async function makeProvider(model, sessionResume = false) {
|
|
|
46
48
|
}
|
|
47
49
|
return new ClaudeCodeProvider(claudeCodeModel, undefined, undefined, sessionResume);
|
|
48
50
|
}
|
|
51
|
+
// Saved-login Cursor Agent CLI (`agent login`). Matched as its own namespace.
|
|
52
|
+
if (model === 'cursor' || model.startsWith('cursor:')) {
|
|
53
|
+
const cursorModel = model.startsWith('cursor:') ? model.slice('cursor:'.length) : undefined;
|
|
54
|
+
if (cursorModel === '') {
|
|
55
|
+
throw new Error('cursor model override cannot be empty; use "cursor" or "cursor:<model-id>"');
|
|
56
|
+
}
|
|
57
|
+
return new CursorProvider(cursorModel, undefined, sessionResume);
|
|
58
|
+
}
|
|
49
59
|
if (model === 'claude' || model.startsWith('claude')) {
|
|
50
60
|
return new AnthropicProvider(model === 'claude' ? undefined : model);
|
|
51
61
|
}
|
|
@@ -53,7 +63,7 @@ export async function makeProvider(model, sessionResume = false) {
|
|
|
53
63
|
}
|
|
54
64
|
function otherProvider(current) {
|
|
55
65
|
// Only the two keyed providers fail over to each other. A rate-limited
|
|
56
|
-
// 'claude-code' run returns null here (no silent fallback to a paid API).
|
|
66
|
+
// 'claude-code' or 'cursor' run returns null here (no silent fallback to a paid API).
|
|
57
67
|
if (current.name === 'openai' && process.env.ANTHROPIC_API_KEY)
|
|
58
68
|
return new AnthropicProvider();
|
|
59
69
|
if (current.name === 'anthropic' && process.env.OPENAI_API_KEY)
|
|
@@ -141,9 +151,9 @@ async function runWithMemory(opts, memory, providers) {
|
|
|
141
151
|
repairCycles: 0,
|
|
142
152
|
finishRequest: null,
|
|
143
153
|
};
|
|
144
|
-
// Session resume for claude-code
|
|
145
|
-
// is off: the cache replays turns a resumed session never saw. So enable
|
|
146
|
-
// only when the env flag is set AND config.llmCache is disabled — the same
|
|
154
|
+
// Session resume for claude-code / cursor is only correct when the response
|
|
155
|
+
// cache is off: the cache replays turns a resumed session never saw. So enable
|
|
156
|
+
// it only when the env flag is set AND config.llmCache is disabled — the same
|
|
147
157
|
// condition under which we skip the CachingProvider wrap below.
|
|
148
158
|
const sessionResume = process.env.COPPERHEAD_CC_SESSION_RESUME === '1' && !config.llmCache;
|
|
149
159
|
let provider = opts.provider ?? (await makeProvider(opts.model, sessionResume));
|
|
@@ -173,7 +183,7 @@ async function runWithMemory(opts, memory, providers) {
|
|
|
173
183
|
interactive: opts.interactive ?? false,
|
|
174
184
|
input: opts.meta,
|
|
175
185
|
});
|
|
176
|
-
for (const line of renderCliHeader(meta))
|
|
186
|
+
for (const line of styleHeaderLines(renderCliHeader(meta)))
|
|
177
187
|
log(line);
|
|
178
188
|
// Revisit obligations deferred while their artifact didn't exist re-open now
|
|
179
189
|
// if it does (must run before loadConstraints so the prompt sees the updated
|