codebase-cli 2.0.0-pre.33 → 2.0.0-pre.35

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.
@@ -0,0 +1,83 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { useEffect, useState } from "react";
4
+ import { DiffSummary, diffSummary } from "./diff-summary.js";
5
+ import { displayPath } from "./paths.js";
6
+ import { nounForReadTool, pastVerbForReadTool, presentVerbForReadTool, toolActionLabel, toolActionPast, truncate, } from "./tool-labels.js";
7
+ import { WrappedLines } from "./wrapped-lines.js";
8
+ const SPINNER_FRAMES = ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"];
9
+ export function useSpinner(active, intervalMs = 90) {
10
+ const [frame, setFrame] = useState(0);
11
+ useEffect(() => {
12
+ if (!active)
13
+ return;
14
+ const id = setInterval(() => setFrame((f) => (f + 1) % SPINNER_FRAMES.length), intervalMs);
15
+ return () => clearInterval(id);
16
+ }, [active, intervalMs]);
17
+ return SPINNER_FRAMES[frame];
18
+ }
19
+ /**
20
+ * Tool calls that are pure reads — runs of these collapse into a single
21
+ * "Read N files" / "Searched 3 patterns" line, the Claude Code pattern.
22
+ * Keep the set tight: anything that mutates state, runs shell, or has a
23
+ * meaningful argument shape (grep query, fetch URL) reads weird when
24
+ * collapsed and stays per-row.
25
+ */
26
+ export const COLLAPSIBLE_READ_TOOLS = new Set(["read_file"]);
27
+ /**
28
+ * One tool-call row that morphs through three states:
29
+ * running → spinner + present tense ("⣾ Reading src/index.ts")
30
+ * done → ✓ + past tense ("✓ Read src/index.ts")
31
+ * error → ✗ + past tense + red ("✗ Read src/index.ts")
32
+ *
33
+ * State source: the per-session `tools` Map on ChatState. If no entry
34
+ * exists for this id (e.g. an old session being replayed without
35
+ * inflight tracking), we render the past-tense "done" form — safe
36
+ * fallback that never strands the UI on a fake spinner.
37
+ */
38
+ export function ToolCallLine({ id, name, args, width, keyPrefix, tools, }) {
39
+ const exec = tools?.get(id);
40
+ const status = exec?.status ?? "done";
41
+ const isRunning = status === "running";
42
+ const spinner = useSpinner(isRunning);
43
+ if (isRunning) {
44
+ return (_jsx(WrappedLines, { text: `${spinner} ${toolActionLabel(name, args)}…`, width: width, keyPrefix: keyPrefix, color: "magenta" }));
45
+ }
46
+ const isError = status === "error";
47
+ const glyph = isError ? "✗" : "✓";
48
+ const past = toolActionPast(name, args);
49
+ const diff = !isError ? diffSummary(name, args) : null;
50
+ return (_jsxs(_Fragment, { children: [_jsx(WrappedLines, { text: `${glyph} ${past}`, width: width, keyPrefix: keyPrefix, color: isError ? "red" : "magenta" }), diff ? _jsx(DiffSummary, { diff: diff, width: width, keyPrefix: `${keyPrefix}-diff` }) : null] }));
51
+ }
52
+ /**
53
+ * Collapsed row for a run of pure-read tool calls. Renders as
54
+ * "✓ Read N files" with the per-file paths in a dim indented list
55
+ * beneath. If any call errored, the glyph flips to ✗ and the line
56
+ * goes red — we still show the paths so the user can see what
57
+ * failed.
58
+ */
59
+ export function CollapsedReadGroup({ calls, width, keyPrefix, tools, }) {
60
+ const statuses = calls.map((c) => tools?.get(c.id)?.status);
61
+ const anyRunning = statuses.some((s) => s === "running");
62
+ const anyError = statuses.some((s) => s === "error");
63
+ const doneCount = statuses.filter((s) => s !== "running").length;
64
+ const spinner = useSpinner(anyRunning);
65
+ const glyph = anyRunning ? spinner : anyError ? "✗" : "✓";
66
+ const color = anyError ? "red" : "magenta";
67
+ const verb = anyRunning ? presentVerbForReadTool(calls[0].name) : pastVerbForReadTool(calls[0].name);
68
+ const noun = nounForReadTool(calls[0].name, calls.length);
69
+ const header = anyRunning
70
+ ? `${glyph} ${verb} ${doneCount} of ${calls.length} ${noun}…`
71
+ : `${glyph} ${verb} ${calls.length} ${noun}`;
72
+ return (_jsxs(_Fragment, { children: [_jsx(WrappedLines, { text: header, width: width, keyPrefix: keyPrefix, color: color }), _jsx(Box, { flexDirection: "column", marginLeft: 2, children: calls.map((c) => {
73
+ const a = (c.arguments ?? {});
74
+ const rawPath = typeof a.path === "string" ? a.path : typeof a.file_path === "string" ? a.file_path : "";
75
+ const path = displayPath(rawPath);
76
+ const status = tools?.get(c.id)?.status;
77
+ const failed = status === "error";
78
+ const running = status === "running";
79
+ const marker = failed ? " ✗ " : running ? " → " : " · ";
80
+ return (_jsxs(Text, { color: failed ? "red" : running ? "magenta" : undefined, dimColor: !failed && !running, children: [marker, truncate(path, Math.max(20, width - 6))] }, `${keyPrefix}-f-${c.id}`));
81
+ }) })] }));
82
+ }
83
+ //# sourceMappingURL=tool-call-line.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-call-line.js","sourceRoot":"","sources":["../../src/ui/tool-call-line.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Text } from "ink";
3
+ import { WrappedLines } from "./wrapped-lines.js";
4
+ const DEFAULT_MAX_TOOL_OUTPUT_LINES = 12;
5
+ /**
6
+ * Per-tool display caps. Search-style tools (grep, find, glob) produce
7
+ * many matches, most of which the user doesn't need to read inline —
8
+ * the model still sees the full result. Default is 12 lines.
9
+ */
10
+ export const TOOL_OUTPUT_LIMITS = {
11
+ grep: 6,
12
+ search_files: 6,
13
+ glob: 8,
14
+ find: 8,
15
+ list_files: 10,
16
+ };
17
+ /**
18
+ * Truncate tool output past the per-tool limit into "head + (N hidden)
19
+ * + tail" — long shell or grep output otherwise dominates the
20
+ * transcript and pushes context off-screen. The agent still gets the
21
+ * full output; this is purely a display trim. Errors are NEVER
22
+ * truncated since the user needs to see exactly what blew up.
23
+ */
24
+ export function TruncatedOutput({ text, width, keyPrefix, color, toolName, }) {
25
+ const max = toolName && TOOL_OUTPUT_LIMITS[toolName] !== undefined
26
+ ? TOOL_OUTPUT_LIMITS[toolName]
27
+ : DEFAULT_MAX_TOOL_OUTPUT_LINES;
28
+ // Reserve at least 1 head + 1 tail line so the user can see the
29
+ // shape of the truncation; rest is head-weighted (where the
30
+ // interesting content usually is).
31
+ const tailLines = max >= 8 ? 3 : 2;
32
+ const headLines = Math.max(1, max - tailLines - 1);
33
+ const lines = text.split("\n");
34
+ if (color === "red" || lines.length <= max) {
35
+ return _jsx(WrappedLines, { text: text, width: width, keyPrefix: keyPrefix, color: color });
36
+ }
37
+ const head = lines.slice(0, headLines).join("\n");
38
+ const tail = lines.slice(lines.length - tailLines).join("\n");
39
+ const hidden = lines.length - headLines - tailLines;
40
+ return (_jsxs(_Fragment, { children: [_jsx(WrappedLines, { text: head, width: width, keyPrefix: `${keyPrefix}-h`, color: color }), _jsx(Text, { dimColor: true, children: `… ${hidden} line${hidden === 1 ? "" : "s"} hidden …` }), _jsx(WrappedLines, { text: tail, width: width, keyPrefix: `${keyPrefix}-t`, color: color })] }));
41
+ }
42
+ //# sourceMappingURL=truncated-output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"truncated-output.js","sourceRoot":"","sources":["../../src/ui/truncated-output.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,6BAA6B,GAAG,EAAE,CAAC;AAEzC;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAA2B;IACzD,IAAI,EAAE,CAAC;IACP,YAAY,EAAE,CAAC;IACf,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,UAAU,EAAE,EAAE;CACd,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,EAC/B,IAAI,EACJ,KAAK,EACL,SAAS,EACT,KAAK,EACL,QAAQ,GAOR;IACA,MAAM,GAAG,GACR,QAAQ,IAAI,kBAAkB,CAAC,QAAQ,CAAC,KAAK,SAAS;QACrD,CAAC,CAAC,kBAAkB,CAAC,QAAQ,CAAC;QAC9B,CAAC,CAAC,6BAA6B,CAAC;IAClC,gEAAgE;IAChE,4DAA4D;IAC5D,mCAAmC;IACnC,MAAM,SAAS,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;QAC5C,OAAO,KAAC,YAAY,IAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,GAAI,CAAC;IACvF,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9D,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IACpD,OAAO,CACN,8BACC,KAAC,YAAY,IAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,SAAS,IAAI,EAAE,KAAK,EAAE,KAAK,GAAI,EACrF,KAAC,IAAI,IAAC,QAAQ,kBAAE,KAAK,MAAM,QAAQ,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,WAAW,GAAQ,EAC7E,KAAC,YAAY,IAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,SAAS,IAAI,EAAE,KAAK,EAAE,KAAK,GAAI,IACnF,CACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,70 @@
1
+ import { useEffect, useRef } from "react";
2
+ /**
3
+ * Coalesce high-frequency streaming events (per-token assistant updates
4
+ * and per-chunk tool stdout) to one React commit per frame instead of
5
+ * per event. Pi-agent-core emits one message_update per token, and a
6
+ * fast model + long tool output can fire 100+ Hz — each dispatch runs
7
+ * the full reducer + React tree diff + Yoga layout for everything on
8
+ * screen. Throttling here is the single biggest cause of perceived
9
+ * scroll/render jankiness; everything else (Static for finalized
10
+ * messages, memoized children) is icing.
11
+ *
12
+ * Keyed coalescing: message_update has one slot, tool_execution_update
13
+ * has one slot per tool id. Latest event wins. Non-coalesceable events
14
+ * (message_start/end, tool_execution_start/end, turn_*, agent_*) flush
15
+ * any pending updates first so ordering stays correct.
16
+ */
17
+ export function useCoalescedAgentEvents(bundle, dispatch) {
18
+ const pendingRef = useRef(new Map());
19
+ const flushTimerRef = useRef(null);
20
+ useEffect(() => {
21
+ const STREAM_FRAME_MS = 16; // ~60fps cap
22
+ const flush = () => {
23
+ flushTimerRef.current = null;
24
+ if (pendingRef.current.size === 0)
25
+ return;
26
+ const events = [...pendingRef.current.values()];
27
+ pendingRef.current.clear();
28
+ for (const event of events) {
29
+ dispatch({ type: "agent-event", event });
30
+ }
31
+ };
32
+ const scheduleFlush = () => {
33
+ if (flushTimerRef.current != null)
34
+ return;
35
+ flushTimerRef.current = setTimeout(flush, STREAM_FRAME_MS);
36
+ };
37
+ const unsubscribe = bundle.subscribe((event) => {
38
+ if (event.type === "message_update") {
39
+ pendingRef.current.set("msg", event);
40
+ scheduleFlush();
41
+ return;
42
+ }
43
+ if (event.type === "tool_execution_update") {
44
+ pendingRef.current.set(`tool:${event.toolCallId}`, event);
45
+ scheduleFlush();
46
+ return;
47
+ }
48
+ // Any other event flushes the queue before dispatching so the
49
+ // reducer sees pending streaming updates before the terminal
50
+ // event (message_end, tool_execution_end, etc.).
51
+ if (pendingRef.current.size > 0) {
52
+ if (flushTimerRef.current != null) {
53
+ clearTimeout(flushTimerRef.current);
54
+ flushTimerRef.current = null;
55
+ }
56
+ flush();
57
+ }
58
+ dispatch({ type: "agent-event", event });
59
+ });
60
+ return () => {
61
+ unsubscribe();
62
+ if (flushTimerRef.current != null) {
63
+ clearTimeout(flushTimerRef.current);
64
+ flushTimerRef.current = null;
65
+ }
66
+ pendingRef.current.clear();
67
+ };
68
+ }, [bundle, dispatch]);
69
+ }
70
+ //# sourceMappingURL=use-coalesced-agent-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-coalesced-agent-events.js","sourceRoot":"","sources":["../../src/ui/use-coalesced-agent-events.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAK1C;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAmB,EAAE,QAAkB;IAC9E,MAAM,UAAU,GAAG,MAAM,CAA0B,IAAI,GAAG,EAAE,CAAC,CAAC;IAC9D,MAAM,aAAa,GAAG,MAAM,CAAwB,IAAI,CAAC,CAAC;IAE1D,SAAS,CAAC,GAAG,EAAE;QACd,MAAM,eAAe,GAAG,EAAE,CAAC,CAAC,aAAa;QAEzC,MAAM,KAAK,GAAG,GAAG,EAAE;YAClB,aAAa,CAAC,OAAO,GAAG,IAAI,CAAC;YAC7B,IAAI,UAAU,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC;gBAAE,OAAO;YAC1C,MAAM,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;YAChD,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAC3B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC5B,QAAQ,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,CAAC;YAC1C,CAAC;QACF,CAAC,CAAC;QAEF,MAAM,aAAa,GAAG,GAAG,EAAE;YAC1B,IAAI,aAAa,CAAC,OAAO,IAAI,IAAI;gBAAE,OAAO;YAC1C,aAAa,CAAC,OAAO,GAAG,UAAU,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;QAC5D,CAAC,CAAC;QAEF,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE;YAC9C,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB,EAAE,CAAC;gBACrC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;gBACrC,aAAa,EAAE,CAAC;gBAChB,OAAO;YACR,CAAC;YACD,IAAI,KAAK,CAAC,IAAI,KAAK,uBAAuB,EAAE,CAAC;gBAC5C,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,CAAC,UAAU,EAAE,EAAE,KAAK,CAAC,CAAC;gBAC1D,aAAa,EAAE,CAAC;gBAChB,OAAO;YACR,CAAC;YACD,8DAA8D;YAC9D,6DAA6D;YAC7D,iDAAiD;YACjD,IAAI,UAAU,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBACjC,IAAI,aAAa,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;oBACnC,YAAY,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;oBACpC,aAAa,CAAC,OAAO,GAAG,IAAI,CAAC;gBAC9B,CAAC;gBACD,KAAK,EAAE,CAAC;YACT,CAAC;YACD,QAAQ,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,OAAO,GAAG,EAAE;YACX,WAAW,EAAE,CAAC;YACd,IAAI,aAAa,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;gBACnC,YAAY,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;gBACpC,aAAa,CAAC,OAAO,GAAG,IAAI,CAAC;YAC9B,CAAC;YACD,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QAC5B,CAAC,CAAC;IACH,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AACxB,CAAC"}
@@ -0,0 +1,52 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { generateSuggestion } from "../agent/prompt-suggestion.js";
3
+ /**
4
+ * Inline prompt-suggestion ghost text. Schedules a single forecast
5
+ * call when the agent goes idle; cancels the prior call on every new
6
+ * state change so we never race two suggestions or show a stale one
7
+ * after the user starts a new turn. 500ms debounce lets idle settle
8
+ * (e.g. agent finishes, a quick status emit follows, we don't want to
9
+ * fire twice). Disabled via env so users on metered BYOK providers
10
+ * can opt out.
11
+ */
12
+ export function usePromptSuggestion(bundle, status, messageCount) {
13
+ const [suggestion, setSuggestion] = useState(null);
14
+ const abortRef = useRef(null);
15
+ useEffect(() => {
16
+ // Always clear any active suggestion on state change — it was
17
+ // computed for the previous turn and the user has moved on.
18
+ setSuggestion(null);
19
+ abortRef.current?.abort();
20
+ abortRef.current = null;
21
+ if (process.env.CODEBASE_NO_SUGGESTIONS === "1")
22
+ return;
23
+ if (status !== "idle")
24
+ return;
25
+ if (messageCount < 2)
26
+ return;
27
+ const ac = new AbortController();
28
+ abortRef.current = ac;
29
+ const timer = setTimeout(async () => {
30
+ if (ac.signal.aborted)
31
+ return;
32
+ try {
33
+ const text = await generateSuggestion(bundle, { signal: ac.signal });
34
+ if (ac.signal.aborted)
35
+ return;
36
+ if (text)
37
+ setSuggestion(text);
38
+ }
39
+ catch {
40
+ // Suggestion failures are silent — they're a nicety, not load-bearing.
41
+ }
42
+ }, 500);
43
+ return () => {
44
+ clearTimeout(timer);
45
+ ac.abort();
46
+ if (abortRef.current === ac)
47
+ abortRef.current = null;
48
+ };
49
+ }, [bundle, status, messageCount]);
50
+ return { suggestion, dismiss: () => setSuggestion(null) };
51
+ }
52
+ //# sourceMappingURL=use-prompt-suggestion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-prompt-suggestion.js","sourceRoot":"","sources":["../../src/ui/use-prompt-suggestion.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAEnE;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAClC,MAAmB,EACnB,MAAc,EACd,YAAoB;IAEpB,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAClE,MAAM,QAAQ,GAAG,MAAM,CAAyB,IAAI,CAAC,CAAC;IAEtD,SAAS,CAAC,GAAG,EAAE;QACd,8DAA8D;QAC9D,4DAA4D;QAC5D,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;QAC1B,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;QAExB,IAAI,OAAO,CAAC,GAAG,CAAC,uBAAuB,KAAK,GAAG;YAAE,OAAO;QACxD,IAAI,MAAM,KAAK,MAAM;YAAE,OAAO;QAC9B,IAAI,YAAY,GAAG,CAAC;YAAE,OAAO;QAE7B,MAAM,EAAE,GAAG,IAAI,eAAe,EAAE,CAAC;QACjC,QAAQ,CAAC,OAAO,GAAG,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,IAAI,EAAE;YACnC,IAAI,EAAE,CAAC,MAAM,CAAC,OAAO;gBAAE,OAAO;YAC9B,IAAI,CAAC;gBACJ,MAAM,IAAI,GAAG,MAAM,kBAAkB,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;gBACrE,IAAI,EAAE,CAAC,MAAM,CAAC,OAAO;oBAAE,OAAO;gBAC9B,IAAI,IAAI;oBAAE,aAAa,CAAC,IAAI,CAAC,CAAC;YAC/B,CAAC;YAAC,MAAM,CAAC;gBACR,uEAAuE;YACxE,CAAC;QACF,CAAC,EAAE,GAAG,CAAC,CAAC;QAER,OAAO,GAAG,EAAE;YACX,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,IAAI,QAAQ,CAAC,OAAO,KAAK,EAAE;gBAAE,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;QACtD,CAAC,CAAC;IACH,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;IAEnC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Text } from "ink";
3
+ import { wrapText } from "./wrap.js";
4
+ /**
5
+ * Render text as N <Text> elements, one per pre-wrapped line. Stacks
6
+ * vertically inside the parent column-flex Box. Pre-wrap means the
7
+ * wraps happen at word boundaries, so when the user select-and-copies
8
+ * they get clean line endings — no mid-word breaks at column edges.
9
+ */
10
+ export function WrappedLines({ text, width, keyPrefix, color, dimColor, italic }) {
11
+ const lines = wrapText(text, width);
12
+ return (_jsx(_Fragment, { children: lines.map((line, i) => (
13
+ // Wrapped lines have no per-line state — <Text> is pure-presentational —
14
+ // so reusing instances on re-wrap is harmless; index-as-key is fine here.
15
+ // biome-ignore lint/suspicious/noArrayIndexKey: stateless leaf, reuse is safe
16
+ _jsx(Text, { color: color, dimColor: dimColor, italic: italic, children: line.length === 0 ? " " : line }, `${keyPrefix}:${i}`))) }));
17
+ }
18
+ //# sourceMappingURL=wrapped-lines.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapped-lines.js","sourceRoot":"","sources":["../../src/ui/wrapped-lines.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAWrC;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAqB;IAClG,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACpC,OAAO,CACN,4BACE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;QACvB,yEAAyE;QACzE,0EAA0E;QAC1E,8EAA8E;QAC9E,KAAC,IAAI,IAA2B,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,YAC9E,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IADrB,GAAG,SAAS,IAAI,CAAC,EAAE,CAEvB,CACP,CAAC,GACA,CACH,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebase-cli",
3
- "version": "2.0.0-pre.33",
3
+ "version": "2.0.0-pre.35",
4
4
  "description": "Codebase CLI — a TypeScript coding agent on the pi-mono runtime. OAuth-aware, any LLM provider, single install.",
5
5
  "keywords": [
6
6
  "ai",