codebase-cli 2.0.0-pre.13 → 2.0.0-pre.19

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/dist/ui/Status.js CHANGED
@@ -1,12 +1,32 @@
1
- import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { basename } from "node:path";
3
3
  import { Box, Text } from "ink";
4
+ import { useEffect, useRef, useState } from "react";
4
5
  import { Throbber } from "./Throbber.js";
6
+ /**
7
+ * Playful verbs we cycle through while the agent is thinking — the
8
+ * Claude Code signature. They all read as "the model is working".
9
+ * Kept ASCII-clean so they line up in any terminal font.
10
+ */
11
+ const THINKING_VERBS = [
12
+ "Thinking",
13
+ "Pondering",
14
+ "Synthesizing",
15
+ "Cogitating",
16
+ "Ruminating",
17
+ "Deliberating",
18
+ "Mulling",
19
+ "Marinating",
20
+ "Brewing",
21
+ "Contemplating",
22
+ "Reasoning",
23
+ "Considering",
24
+ ];
5
25
  const STATUS_LABEL = {
6
26
  idle: "ready",
7
- thinking: "thinking",
8
- streaming: "responding",
9
- tool: "tool",
27
+ thinking: "Thinking",
28
+ streaming: "Writing",
29
+ tool: "Working",
10
30
  aborted: "aborted",
11
31
  error: "error",
12
32
  };
@@ -26,14 +46,189 @@ const STATUS_COLOR = {
26
46
  */
27
47
  export function Status({ state, cwd, contextWindow = 200_000 }) {
28
48
  const busy = state.status === "thinking" || state.status === "streaming" || state.status === "tool";
29
- const label = STATUS_LABEL[state.status];
49
+ const verb = useThinkingVerb(state.status === "thinking");
50
+ let label = state.status === "thinking" ? verb : STATUS_LABEL[state.status];
51
+ if (state.status === "tool") {
52
+ const running = findRunningTool(state);
53
+ if (running)
54
+ label = `${STATUS_LABEL.tool} · ${running}`;
55
+ }
30
56
  const color = STATUS_COLOR[state.status];
57
+ const tokRate = useTokenRate(state);
58
+ const elapsedSec = useBusyElapsed(busy);
31
59
  const u = state.usage;
32
60
  const usedTokens = u.input + u.cacheRead;
33
61
  const ctxPct = contextWindow > 0 ? Math.min(100, Math.round((usedTokens / contextWindow) * 100)) : 0;
34
62
  const cwdLabel = cwd ? basename(cwd) || "/" : "";
35
63
  const modelLabel = state.model.name || state.model.id;
36
- return (_jsxs(Box, { flexDirection: "column", children: [state.error ? (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: "red", children: ["! ", state.error] }) })) : null, _jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [busy ? (_jsxs(_Fragment, { children: [_jsx(Throbber, { color: color }), _jsx(Text, { children: " " })] })) : null, _jsx(Text, { color: color, children: label })] }), _jsx(Box, { children: _jsxs(Text, { dimColor: true, children: [modelLabel, cwdLabel ? ` · ${cwdLabel}` : "", " \u00B7 ", ctxPct, "% ctx \u00B7 $", formatCost(u.cost.total)] }) })] })] }));
64
+ return (_jsxs(Box, { flexDirection: "column", children: [state.error ? _jsx(ErrorCard, { message: state.error }) : null, ctxPct >= 85 ? _jsx(ContextWarning, { pct: ctxPct }) : null, _jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [busy ? (_jsxs(_Fragment, { children: [_jsx(Throbber, { color: color }), _jsx(Text, { children: " " })] })) : null, _jsx(Text, { color: color, children: label }), elapsedSec !== undefined ? _jsxs(Text, { dimColor: true, children: [" (", elapsedSec, "s)"] }) : null] }), _jsxs(Box, { children: [_jsxs(Text, { dimColor: true, children: [modelLabel, cwdLabel ? ` · ${cwdLabel}` : "", " \u00B7", " "] }), _jsxs(Text, { color: ctxColor(ctxPct), children: [ctxBar(ctxPct), " ", ctxPct, "%"] }), _jsxs(Text, { dimColor: true, children: [tokRate !== undefined ? ` · ${tokRate} tok/s` : "", " \u00B7 $", formatCost(u.cost.total)] })] })] })] }));
65
+ }
66
+ /**
67
+ * Track how long the agent has been busy. Returns undefined unless the
68
+ * elapsed time has crossed 3 seconds — short turns shouldn't carry an
69
+ * "(0s)" suffix on the status bar. Resets cleanly when the agent
70
+ * goes idle so consecutive turns each start their own timer.
71
+ */
72
+ function useBusyElapsed(busy) {
73
+ const startRef = useRef(undefined);
74
+ const [tick, setTick] = useState(0);
75
+ useEffect(() => {
76
+ if (!busy) {
77
+ startRef.current = undefined;
78
+ return;
79
+ }
80
+ startRef.current = Date.now();
81
+ const id = setInterval(() => setTick((t) => t + 1), 1000);
82
+ return () => clearInterval(id);
83
+ }, [busy]);
84
+ if (!busy || !startRef.current)
85
+ return undefined;
86
+ const elapsed = Math.floor((Date.now() - startRef.current) / 1000);
87
+ void tick;
88
+ return elapsed >= 3 ? elapsed : undefined;
89
+ }
90
+ /**
91
+ * Pluck the most-recently-started in-flight tool so the status bar can
92
+ * say "Working · shell" instead of just "Working". Falls back to no
93
+ * tool name when the map is empty — keeps the bar terse.
94
+ */
95
+ function findRunningTool(state) {
96
+ let best;
97
+ for (const tool of state.tools.values()) {
98
+ if (tool.status !== "running")
99
+ continue;
100
+ if (!best || tool.startedAt > best.startedAt) {
101
+ best = { name: tool.name, startedAt: tool.startedAt };
102
+ }
103
+ }
104
+ return best?.name;
105
+ }
106
+ /**
107
+ * Estimate the live token-output rate during streaming. Pi-ai only
108
+ * surfaces accurate `usage` at message_end, so for the live counter
109
+ * we approximate from the streaming message's character length using
110
+ * the common ~4-chars-per-token rule. Cheap, no extra deps, and
111
+ * accurate enough for a status-bar readout.
112
+ *
113
+ * Returns undefined when not streaming, or when too few chars have
114
+ * accumulated for the rate to be meaningful (so the bar doesn't
115
+ * flicker a noisy "9999 tok/s" in the first 100ms).
116
+ */
117
+ function useTokenRate(state) {
118
+ const startRef = useRef(undefined);
119
+ const [tick, setTick] = useState(0);
120
+ const streaming = state.status === "streaming";
121
+ useEffect(() => {
122
+ if (!streaming) {
123
+ startRef.current = undefined;
124
+ return;
125
+ }
126
+ if (startRef.current === undefined)
127
+ startRef.current = Date.now();
128
+ const id = setInterval(() => setTick((t) => t + 1), 500);
129
+ return () => clearInterval(id);
130
+ }, [streaming]);
131
+ if (!streaming || !startRef.current)
132
+ return undefined;
133
+ const elapsedSec = (Date.now() - startRef.current) / 1000;
134
+ if (elapsedSec < 0.5)
135
+ return undefined;
136
+ void tick; // force re-eval on each interval
137
+ const chars = streamingChars(state);
138
+ if (chars < 40)
139
+ return undefined;
140
+ const tokens = chars / 4;
141
+ const rate = tokens / elapsedSec;
142
+ return Math.round(rate);
143
+ }
144
+ /** Sum the visible text length of all text/thinking blocks in the live streaming message. */
145
+ function streamingChars(state) {
146
+ const m = state.streaming;
147
+ if (!m || m.role !== "assistant")
148
+ return 0;
149
+ let total = 0;
150
+ for (const block of m.content) {
151
+ if (block.type === "text")
152
+ total += block.text.length;
153
+ else if (block.type === "thinking")
154
+ total += block.thinking.length;
155
+ }
156
+ return total;
157
+ }
158
+ /**
159
+ * While the agent is thinking, swap the verb every 3 seconds. We pick
160
+ * the next verb at random (excluding the current one) instead of
161
+ * cycling in order so the same word doesn't reappear at predictable
162
+ * beats. When the status leaves thinking we drop back to the first
163
+ * verb so re-entry starts fresh.
164
+ */
165
+ function useThinkingVerb(active) {
166
+ const [verb, setVerb] = useState(THINKING_VERBS[0]);
167
+ useEffect(() => {
168
+ if (!active) {
169
+ setVerb(THINKING_VERBS[0]);
170
+ return;
171
+ }
172
+ const id = setInterval(() => {
173
+ setVerb((current) => {
174
+ let next = current;
175
+ while (next === current) {
176
+ next = THINKING_VERBS[Math.floor(Math.random() * THINKING_VERBS.length)];
177
+ }
178
+ return next;
179
+ });
180
+ }, 3000);
181
+ return () => clearInterval(id);
182
+ }, [active]);
183
+ return verb;
184
+ }
185
+ /**
186
+ * Banner shown when the context window is past 85%. Suggests /compact
187
+ * so the user can take action before auto-compaction kicks in, and
188
+ * shifts to red past 95% where the next turn might actually trip the
189
+ * model's hard limit.
190
+ */
191
+ function ContextWarning({ pct }) {
192
+ const urgent = pct >= 95;
193
+ return (_jsxs(Box, { paddingX: 1, children: [_jsxs(Text, { color: urgent ? "red" : "yellow", bold: true, children: [urgent ? "⚠" : "•", " ", pct, "% of context used"] }), _jsx(Text, { dimColor: true, children: " \u2014 run /compact to free space" })] }));
194
+ }
195
+ /**
196
+ * Boxed error card. Headers the error with ERROR + a one-line summary,
197
+ * then shows the rest of the message body (if multi-line) in dim text.
198
+ * Matches Claude Code's pattern of giving fatal errors visual weight
199
+ * so the user doesn't miss them in a busy transcript.
200
+ */
201
+ function ErrorCard({ message }) {
202
+ const lines = message.split("\n");
203
+ const head = lines[0] ?? message;
204
+ const body = lines.slice(1).filter((l) => l.trim().length > 0);
205
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "red", paddingX: 1, marginY: 0, children: [_jsxs(Box, { children: [_jsx(Text, { color: "red", bold: true, children: "ERROR" }), _jsx(Text, { children: " " }), _jsx(Text, { children: head })] }), body.length > 0 ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: body.map((line, i) => (_jsx(Text, { dimColor: true, children: line }, `err-${i}-${line.slice(0, 12)}`))) })) : null] }));
206
+ }
207
+ /**
208
+ * Render a tiny 6-cell bar for the context-window meter. Eighth-block
209
+ * glyphs give us 48 effective steps in 6 chars — enough resolution
210
+ * that 12% / 25% / 50% all look visibly different. Empty cells stay
211
+ * as a dim track so the bar always reads as a meter, not a slider.
212
+ */
213
+ function ctxBar(pct) {
214
+ const cells = 6;
215
+ const totalEighths = Math.round((pct / 100) * cells * 8);
216
+ const full = Math.floor(totalEighths / 8);
217
+ const remainder = totalEighths - full * 8;
218
+ const partials = ["", "▏", "▎", "▍", "▌", "▋", "▊", "▉"];
219
+ let out = "█".repeat(Math.min(full, cells));
220
+ if (full < cells && remainder > 0)
221
+ out += partials[remainder] ?? "";
222
+ while (out.length < cells)
223
+ out += "░";
224
+ return out;
225
+ }
226
+ function ctxColor(pct) {
227
+ if (pct >= 90)
228
+ return "red";
229
+ if (pct >= 75)
230
+ return "yellow";
231
+ return "gray";
37
232
  }
38
233
  function formatCost(value) {
39
234
  if (value === 0)
@@ -1 +1 @@
1
- {"version":3,"file":"Status.js","sourceRoot":"","sources":["../../src/ui/Status.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Status.js","sourceRoot":"","sources":["../../src/ui/Status.tsx"],"names":[],"mappings":"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"}
@@ -1,16 +1,59 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { execSync } from "node:child_process";
2
3
  import { basename } from "node:path";
3
4
  import { Box, Text } from "ink";
4
5
  import { PixelC } from "./PixelC.js";
6
+ /**
7
+ * Best-effort git probe for the welcome banner. Returns null if the
8
+ * cwd isn't a git repo (or if `git` isn't on PATH), so non-git
9
+ * projects get a cleaner banner instead of an empty line. We swallow
10
+ * all errors — the banner is decorative; a slow / failing git
11
+ * shouldn't block startup.
12
+ */
13
+ function readGitInfo(cwd) {
14
+ try {
15
+ const branch = execSync("git rev-parse --abbrev-ref HEAD", {
16
+ cwd,
17
+ encoding: "utf8",
18
+ stdio: ["ignore", "pipe", "ignore"],
19
+ }).trim();
20
+ if (!branch)
21
+ return null;
22
+ const status = execSync("git status --porcelain", {
23
+ cwd,
24
+ encoding: "utf8",
25
+ stdio: ["ignore", "pipe", "ignore"],
26
+ });
27
+ const dirty = status.split("\n").filter((l) => l.trim().length > 0).length;
28
+ return { branch, dirty };
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ }
34
+ /** Humanize an absolute timestamp into "5m ago" / "3h ago" / "2d ago" — sub-minute reads as "just now". */
35
+ function formatAgo(ts) {
36
+ const sec = Math.max(0, Math.floor((Date.now() - ts) / 1000));
37
+ if (sec < 60)
38
+ return "just now";
39
+ if (sec < 3600)
40
+ return `${Math.floor(sec / 60)}m ago`;
41
+ if (sec < 86400)
42
+ return `${Math.floor(sec / 3600)}h ago`;
43
+ return `${Math.floor(sec / 86400)}d ago`;
44
+ }
5
45
  /**
6
46
  * Empty-state banner shown above the input while the transcript is
7
47
  * empty. Pixel-C logo on the left, contextual info + tips on the
8
48
  * right. Renders once and gets pushed up by the first user message —
9
49
  * not Static-rendered, but only a few rows so it's cheap.
10
50
  */
11
- export function Welcome({ modelName, source, cwd }) {
51
+ export function Welcome({ modelName, source, cwd, resumedFrom }) {
12
52
  const cwdLabel = basename(cwd) || cwd;
13
53
  const sourceLabel = source === "proxy" ? "signed in via codebase.design" : source === "byok" ? "BYOK" : `${source}`;
14
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { marginRight: 2, children: _jsx(PixelC, { animate: false }) }), _jsxs(Box, { flexDirection: "column", justifyContent: "center", children: [_jsx(Text, { bold: true, color: "cyan", children: "codebase" }), _jsx(Text, { dimColor: true, children: modelName }), _jsxs(Text, { dimColor: true, children: [cwdLabel, " \u00B7 ", sourceLabel] })] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "Ask me to read code, edit files, run commands, or anything in between." }), _jsxs(Text, { dimColor: true, children: ["Try: ", _jsx(Text, { color: "cyan", children: "/help" }), " for commands \u00B7 ", _jsx(Text, { color: "cyan", children: "/cost" }), " for tokens \u00B7", " ", _jsx(Text, { color: "cyan", children: "/copy" }), " to clipboard \u00B7 ", _jsx(Text, { color: "cyan", children: "/diff" }), " for the working tree"] }), _jsx(Text, { dimColor: true, children: "Ctrl-C twice to exit." })] })] }));
54
+ const gitInfo = readGitInfo(cwd);
55
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { marginRight: 2, children: _jsx(PixelC, {}) }), _jsxs(Box, { flexDirection: "column", justifyContent: "center", children: [_jsx(Text, { bold: true, color: "cyan", children: "codebase" }), _jsx(Text, { dimColor: true, children: modelName }), _jsxs(Text, { dimColor: true, children: [cwdLabel, " \u00B7 ", sourceLabel] }), gitInfo ? (_jsxs(Text, { dimColor: true, children: [gitInfo.branch, gitInfo.dirty > 0
56
+ ? ` · ${gitInfo.dirty} uncommitted change${gitInfo.dirty === 1 ? "" : "s"}`
57
+ : " · clean"] })) : null] })] }), resumedFrom ? (_jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: "cyan", children: ["\u21BB Resumed from ", formatAgo(resumedFrom.updatedAt)] }), _jsxs(Text, { dimColor: true, children: [" ", "\u00B7 ", resumedFrom.messageCount, " message", resumedFrom.messageCount === 1 ? "" : "s"] })] })) : null, _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "Ask me to read code, edit files, run commands, or anything in between." }), _jsxs(Text, { dimColor: true, children: [_jsx(Text, { color: "cyan", children: "/" }), " commands \u00B7 ", _jsx(Text, { color: "cyan", children: "!" }), "shell \u00B7 ", _jsx(Text, { color: "cyan", children: "\u2191\u2193" }), " ", "history \u00B7 ", _jsx(Text, { color: "cyan", children: "Tab" }), " complete \u00B7 ", _jsx(Text, { color: "cyan", children: "\\" }), "+Enter for newline"] }), _jsx(Text, { dimColor: true, children: "Ctrl-C twice to exit." })] })] }));
15
58
  }
16
59
  //# sourceMappingURL=Welcome.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Welcome.js","sourceRoot":"","sources":["../../src/ui/Welcome.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAQrC;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAgB;IAC/D,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC;IACtC,MAAM,WAAW,GAAG,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC;IAEpH,OAAO,CACN,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,QAAQ,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,aACvD,MAAC,GAAG,IAAC,aAAa,EAAC,KAAK,aACvB,KAAC,GAAG,IAAC,WAAW,EAAE,CAAC,YAClB,KAAC,MAAM,IAAC,OAAO,EAAE,KAAK,GAAI,GACrB,EACN,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,cAAc,EAAC,QAAQ,aAClD,KAAC,IAAI,IAAC,IAAI,QAAC,KAAK,EAAC,MAAM,yBAEhB,EACP,KAAC,IAAI,IAAC,QAAQ,kBAAE,SAAS,GAAQ,EACjC,MAAC,IAAI,IAAC,QAAQ,mBACZ,QAAQ,cAAK,WAAW,IACnB,IACF,IACD,EACN,MAAC,GAAG,IAAC,SAAS,EAAE,CAAC,EAAE,aAAa,EAAC,QAAQ,aACxC,KAAC,IAAI,IAAC,QAAQ,6FAA8E,EAC5F,MAAC,IAAI,IAAC,QAAQ,4BACR,KAAC,IAAI,IAAC,KAAK,EAAC,MAAM,sBAAa,2BAAgB,KAAC,IAAI,IAAC,KAAK,EAAC,MAAM,sBAAa,wBAAc,GAAG,EACpG,KAAC,IAAI,IAAC,KAAK,EAAC,MAAM,sBAAa,2BAAgB,KAAC,IAAI,IAAC,KAAK,EAAC,MAAM,sBAAa,6BACxE,EACP,KAAC,IAAI,IAAC,QAAQ,4CAA6B,IACtC,IACD,CACN,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"Welcome.js","sourceRoot":"","sources":["../../src/ui/Welcome.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ import { readFileSync, statSync } from "node:fs";
2
+ import { isAbsolute, join } from "node:path";
3
+ export const MAX_ATTACHMENT_BYTES = 128 * 1024;
4
+ export const MAX_ATTACHMENTS = 8;
5
+ /**
6
+ * Scan the prompt for `@<path>` tokens and resolve each to a readable
7
+ * file under (or adjacent to) the cwd. Returns one entry per resolved
8
+ * file; unresolved `@` mentions don't appear here and stay as literal
9
+ * text — we never silently drop or rewrite user input.
10
+ *
11
+ * Skip rules:
12
+ * - tokens without a slash or dot (email-style @alice mentions)
13
+ * - paths over 256 chars (clearly not a real path)
14
+ * - non-files (directories, sockets, ...)
15
+ * - files larger than MAX_ATTACHMENT_BYTES
16
+ * - past MAX_ATTACHMENTS total attachments per prompt
17
+ */
18
+ export function collectAttachments(text, cwd) {
19
+ const out = [];
20
+ const seen = new Set();
21
+ const pattern = /@([A-Za-z0-9_./-]+)/g;
22
+ for (const match of text.matchAll(pattern)) {
23
+ if (out.length >= MAX_ATTACHMENTS)
24
+ break;
25
+ const rel = match[1];
26
+ if (!rel || rel.length > 256)
27
+ continue;
28
+ if (!rel.includes("/") && !rel.includes("."))
29
+ continue;
30
+ const abs = isAbsolute(rel) ? rel : join(cwd, rel);
31
+ if (seen.has(abs))
32
+ continue;
33
+ seen.add(abs);
34
+ try {
35
+ const stat = statSync(abs);
36
+ if (!stat.isFile())
37
+ continue;
38
+ if (stat.size > MAX_ATTACHMENT_BYTES)
39
+ continue;
40
+ const content = readFileSync(abs, "utf8");
41
+ out.push({ token: match[0], relPath: rel, absPath: abs, content });
42
+ }
43
+ catch {
44
+ // File doesn't exist or isn't readable — leave the token in text.
45
+ }
46
+ }
47
+ return out;
48
+ }
49
+ /**
50
+ * Build the agent-bound prompt with attachments inlined as fenced code
51
+ * blocks above the user's actual ask. The original `@path` tokens stay
52
+ * in the text so the model can correlate the references with the
53
+ * attached content.
54
+ */
55
+ export function buildAttachmentPrompt(text, attachments) {
56
+ const parts = ["Attached files (auto-inlined from @ mentions):", ""];
57
+ for (const a of attachments) {
58
+ parts.push(`### ${a.relPath}`);
59
+ parts.push("```");
60
+ parts.push(a.content);
61
+ parts.push("```");
62
+ parts.push("");
63
+ }
64
+ parts.push("---");
65
+ parts.push(text);
66
+ return parts.join("\n");
67
+ }
68
+ //# sourceMappingURL=attachments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachments.js","sourceRoot":"","sources":["../../src/ui/attachments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAS7C,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,GAAG,IAAI,CAAC;AAC/C,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC;AAEjC;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,GAAW;IAC3D,MAAM,GAAG,GAAiB,EAAE,CAAC;IAC7B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,OAAO,GAAG,sBAAsB,CAAC;IACvC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5C,IAAI,GAAG,CAAC,MAAM,IAAI,eAAe;YAAE,MAAM;QACzC,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACrB,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG;YAAE,SAAS;QACvC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,SAAS;QACvD,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACnD,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,IAAI,CAAC;YACJ,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC3B,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;gBAAE,SAAS;YAC7B,IAAI,IAAI,CAAC,IAAI,GAAG,oBAAoB;gBAAE,SAAS;YAC/C,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAC1C,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;QACpE,CAAC;QAAC,MAAM,CAAC;YACR,kEAAkE;QACnE,CAAC;IACF,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,WAAkC;IACrF,MAAM,KAAK,GAAa,CAAC,gDAAgD,EAAE,EAAE,CAAC,CAAC;IAC/E,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClB,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChB,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { appendFileSync, mkdirSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ let logPath = null;
5
+ let warned = false;
6
+ export function isDebugInputEnabled() {
7
+ return process.env.CODEBASE_DEBUG_INPUT === "1";
8
+ }
9
+ function resolveLogPath() {
10
+ if (logPath)
11
+ return logPath;
12
+ const dir = join(homedir(), ".codebase", "logs");
13
+ try {
14
+ mkdirSync(dir, { recursive: true });
15
+ }
16
+ catch {
17
+ // The fs error is non-fatal; the appendFileSync below will throw
18
+ // the same problem and we'll surface it once via the `warned`
19
+ // guard, not on every keystroke.
20
+ }
21
+ logPath = join(dir, "input.log");
22
+ return logPath;
23
+ }
24
+ export function logInputEvent(input, key) {
25
+ if (!isDebugInputEnabled())
26
+ return;
27
+ try {
28
+ const entry = {
29
+ t: new Date().toISOString(),
30
+ // Show the actual code points so a stray \x7f or \x1b is visible.
31
+ input,
32
+ codes: Array.from(input).map((ch) => ch.charCodeAt(0)),
33
+ key,
34
+ };
35
+ appendFileSync(resolveLogPath(), `${JSON.stringify(entry)}\n`);
36
+ }
37
+ catch (err) {
38
+ if (warned)
39
+ return;
40
+ warned = true;
41
+ process.stderr.write(`\n[debug-input] failed to write log: ${err instanceof Error ? err.message : String(err)}\n`);
42
+ }
43
+ }
44
+ //# sourceMappingURL=debug-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug-input.js","sourceRoot":"","sources":["../../src/ui/debug-input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AA8BjC,IAAI,OAAO,GAAkB,IAAI,CAAC;AAClC,IAAI,MAAM,GAAG,KAAK,CAAC;AAEnB,MAAM,UAAU,mBAAmB;IAClC,OAAO,OAAO,CAAC,GAAG,CAAC,oBAAoB,KAAK,GAAG,CAAC;AACjD,CAAC;AAED,SAAS,cAAc;IACtB,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;IACjD,IAAI,CAAC;QACJ,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACR,iEAAiE;QACjE,8DAA8D;QAC9D,iCAAiC;IAClC,CAAC;IACD,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IACjC,OAAO,OAAO,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,GAAgB;IAC5D,IAAI,CAAC,mBAAmB,EAAE;QAAE,OAAO;IACnC,IAAI,CAAC;QACJ,MAAM,KAAK,GAAG;YACb,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC3B,kEAAkE;YAClE,KAAK;YACL,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YACtD,GAAG;SACH,CAAC;QACF,cAAc,CAAC,cAAc,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAChE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,IAAI,MAAM;YAAE,OAAO;QACnB,MAAM,GAAG,IAAI,CAAC;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,CACnB,wCAAwC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAC5F,CAAC;IACH,CAAC;AACF,CAAC"}