@bubblebrain-ai/bubble 0.0.12 → 0.0.13

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 (128) hide show
  1. package/dist/agent/input-controller.d.ts +11 -0
  2. package/dist/agent/input-controller.js +30 -0
  3. package/dist/agent.d.ts +6 -4
  4. package/dist/agent.js +38 -0
  5. package/dist/main.js +58 -9
  6. package/dist/slash-commands/commands.js +27 -0
  7. package/dist/slash-commands/types.d.ts +10 -0
  8. package/dist/tui/clipboard.d.ts +1 -0
  9. package/dist/tui/clipboard.js +53 -0
  10. package/dist/tui/detect-theme.d.ts +2 -0
  11. package/dist/tui/detect-theme.js +87 -0
  12. package/dist/tui/display-history.d.ts +62 -0
  13. package/dist/tui/display-history.js +305 -0
  14. package/dist/tui/edit-diff.d.ts +11 -0
  15. package/dist/tui/edit-diff.js +52 -0
  16. package/dist/tui/escape-confirmation.d.ts +15 -0
  17. package/dist/tui/escape-confirmation.js +30 -0
  18. package/dist/tui/file-mentions.d.ts +29 -0
  19. package/dist/tui/file-mentions.js +174 -0
  20. package/dist/tui/global-key-router.d.ts +3 -0
  21. package/dist/tui/global-key-router.js +87 -0
  22. package/dist/tui/image-paste.d.ts +95 -0
  23. package/dist/tui/image-paste.js +505 -0
  24. package/dist/tui/input-history.d.ts +16 -0
  25. package/dist/tui/input-history.js +79 -0
  26. package/dist/tui/markdown-inline.d.ts +22 -0
  27. package/dist/tui/markdown-inline.js +68 -0
  28. package/dist/tui/markdown-theme-rules.d.ts +23 -0
  29. package/dist/tui/markdown-theme-rules.js +164 -0
  30. package/dist/tui/markdown-theme.d.ts +5 -0
  31. package/dist/tui/markdown-theme.js +27 -0
  32. package/dist/tui/opencode-spinner.d.ts +22 -0
  33. package/dist/tui/opencode-spinner.js +216 -0
  34. package/dist/tui/prompt-keybindings.d.ts +42 -0
  35. package/dist/tui/prompt-keybindings.js +35 -0
  36. package/dist/tui/recent-activity.d.ts +8 -0
  37. package/dist/tui/recent-activity.js +71 -0
  38. package/dist/tui/render-signature.d.ts +1 -0
  39. package/dist/tui/render-signature.js +7 -0
  40. package/dist/tui/run.d.ts +45 -0
  41. package/dist/tui/run.js +8816 -0
  42. package/dist/tui/session-display.d.ts +6 -0
  43. package/dist/tui/session-display.js +12 -0
  44. package/dist/tui/sidebar-mcp.d.ts +31 -0
  45. package/dist/tui/sidebar-mcp.js +62 -0
  46. package/dist/tui/sidebar-state.d.ts +12 -0
  47. package/dist/tui/sidebar-state.js +69 -0
  48. package/dist/tui/streaming-tool-args.d.ts +15 -0
  49. package/dist/tui/streaming-tool-args.js +30 -0
  50. package/dist/tui/tool-renderers/fallback.d.ts +2 -0
  51. package/dist/tui/tool-renderers/fallback.js +75 -0
  52. package/dist/tui/tool-renderers/registry.d.ts +3 -0
  53. package/dist/tui/tool-renderers/registry.js +11 -0
  54. package/dist/tui/tool-renderers/subagent.d.ts +2 -0
  55. package/dist/tui/tool-renderers/subagent.js +135 -0
  56. package/dist/tui/tool-renderers/types.d.ts +36 -0
  57. package/dist/tui/tool-renderers/types.js +1 -0
  58. package/dist/tui/tool-renderers/write-preview.d.ts +12 -0
  59. package/dist/tui/tool-renderers/write-preview.js +30 -0
  60. package/dist/tui/tool-renderers/write.d.ts +6 -0
  61. package/dist/tui/tool-renderers/write.js +88 -0
  62. package/dist/tui/trace-groups.d.ts +27 -0
  63. package/dist/tui/trace-groups.js +412 -0
  64. package/dist/tui/wordmark.d.ts +15 -0
  65. package/dist/tui/wordmark.js +179 -0
  66. package/dist/tui-ink/app.js +44 -5
  67. package/dist/tui-ink/message-list.js +9 -1
  68. package/dist/tui-ink/theme.d.ts +3 -9
  69. package/dist/tui-ink/theme.js +39 -45
  70. package/dist/tui-ink/welcome.js +22 -78
  71. package/dist/tui-opentui/app.d.ts +54 -0
  72. package/dist/tui-opentui/app.js +1363 -0
  73. package/dist/tui-opentui/approval/approval-dialog.d.ts +15 -0
  74. package/dist/tui-opentui/approval/approval-dialog.js +139 -0
  75. package/dist/tui-opentui/approval/diff-view.d.ts +9 -0
  76. package/dist/tui-opentui/approval/diff-view.js +43 -0
  77. package/dist/tui-opentui/approval/select.d.ts +37 -0
  78. package/dist/tui-opentui/approval/select.js +91 -0
  79. package/dist/tui-opentui/detect-theme.d.ts +2 -0
  80. package/dist/tui-opentui/detect-theme.js +87 -0
  81. package/dist/tui-opentui/display-history.d.ts +55 -0
  82. package/dist/tui-opentui/display-history.js +129 -0
  83. package/dist/tui-opentui/edit-diff.d.ts +11 -0
  84. package/dist/tui-opentui/edit-diff.js +52 -0
  85. package/dist/tui-opentui/feedback-dialog.d.ts +21 -0
  86. package/dist/tui-opentui/feedback-dialog.js +164 -0
  87. package/dist/tui-opentui/feishu-setup-picker.d.ts +7 -0
  88. package/dist/tui-opentui/feishu-setup-picker.js +272 -0
  89. package/dist/tui-opentui/file-mentions.d.ts +29 -0
  90. package/dist/tui-opentui/file-mentions.js +174 -0
  91. package/dist/tui-opentui/footer.d.ts +26 -0
  92. package/dist/tui-opentui/footer.js +40 -0
  93. package/dist/tui-opentui/image-paste.d.ts +54 -0
  94. package/dist/tui-opentui/image-paste.js +288 -0
  95. package/dist/tui-opentui/input-box.d.ts +34 -0
  96. package/dist/tui-opentui/input-box.js +471 -0
  97. package/dist/tui-opentui/input-history.d.ts +16 -0
  98. package/dist/tui-opentui/input-history.js +79 -0
  99. package/dist/tui-opentui/markdown.d.ts +66 -0
  100. package/dist/tui-opentui/markdown.js +127 -0
  101. package/dist/tui-opentui/message-list.d.ts +31 -0
  102. package/dist/tui-opentui/message-list.js +125 -0
  103. package/dist/tui-opentui/model-picker.d.ts +63 -0
  104. package/dist/tui-opentui/model-picker.js +450 -0
  105. package/dist/tui-opentui/plan-confirm.d.ts +9 -0
  106. package/dist/tui-opentui/plan-confirm.js +124 -0
  107. package/dist/tui-opentui/question-dialog.d.ts +10 -0
  108. package/dist/tui-opentui/question-dialog.js +110 -0
  109. package/dist/tui-opentui/recent-activity.d.ts +8 -0
  110. package/dist/tui-opentui/recent-activity.js +71 -0
  111. package/dist/tui-opentui/run-session-picker.d.ts +10 -0
  112. package/dist/tui-opentui/run-session-picker.js +28 -0
  113. package/dist/tui-opentui/run.d.ts +38 -0
  114. package/dist/tui-opentui/run.js +48 -0
  115. package/dist/tui-opentui/session-picker.d.ts +12 -0
  116. package/dist/tui-opentui/session-picker.js +120 -0
  117. package/dist/tui-opentui/theme.d.ts +89 -0
  118. package/dist/tui-opentui/theme.js +157 -0
  119. package/dist/tui-opentui/todos.d.ts +9 -0
  120. package/dist/tui-opentui/todos.js +45 -0
  121. package/dist/tui-opentui/trace-groups.d.ts +27 -0
  122. package/dist/tui-opentui/trace-groups.js +412 -0
  123. package/dist/tui-opentui/use-terminal-size.d.ts +4 -0
  124. package/dist/tui-opentui/use-terminal-size.js +5 -0
  125. package/dist/tui-opentui/welcome.d.ts +25 -0
  126. package/dist/tui-opentui/welcome.js +77 -0
  127. package/dist/types.d.ts +24 -0
  128. package/package.json +5 -1
@@ -0,0 +1,127 @@
1
+ import { jsx as _jsx } from "@opentui/react/jsx-runtime";
2
+ /** @jsxImportSource @opentui/react */
3
+ /**
4
+ * Markdown rendering for the OpenTUI-based TUI.
5
+ *
6
+ * The previous Ink implementation hand-parsed markdown into block primitives
7
+ * because Ink had no native markdown support. OpenTUI ships
8
+ * `MarkdownRenderable` as a built-in (tree-sitter backed), so most of the
9
+ * 600+ lines of parsing logic collapse to a single intrinsic element.
10
+ *
11
+ * Public exports are kept compatible with the old Ink module so consumers
12
+ * (message-list, plan-confirm) don't need import changes.
13
+ */
14
+ import React from "react";
15
+ import { useTheme } from "./theme.js";
16
+ /**
17
+ * Detect the byte offset where the last complete block ends. Used by the
18
+ * streaming renderer to keep already-finalized blocks stable while the
19
+ * trailing block is still being typed.
20
+ */
21
+ export function findLastBlockStart(text) {
22
+ // Walk backwards to find the start of the trailing incomplete block.
23
+ // A block break is a blank line (two newlines) at column 0. If we are
24
+ // inside an unclosed ```/~~~ fence, the trailing block extends back to
25
+ // the fence opener.
26
+ const lastFenceOpen = findUnclosedFenceStart(text);
27
+ if (lastFenceOpen !== -1)
28
+ return lastFenceOpen;
29
+ const idx = text.lastIndexOf("\n\n");
30
+ return idx === -1 ? 0 : idx + 2;
31
+ }
32
+ function findUnclosedFenceStart(text) {
33
+ let cursor = 0;
34
+ let openAt = -1;
35
+ let openMarker = null;
36
+ for (const line of text.split("\n")) {
37
+ const trimmed = line.replace(/^ {0,3}/, "");
38
+ if (openMarker === null) {
39
+ if (trimmed.startsWith("```")) {
40
+ openMarker = "`";
41
+ openAt = cursor;
42
+ }
43
+ else if (trimmed.startsWith("~~~")) {
44
+ openMarker = "~";
45
+ openAt = cursor;
46
+ }
47
+ }
48
+ else if (trimmed.startsWith(openMarker.repeat(3))) {
49
+ openMarker = null;
50
+ openAt = -1;
51
+ }
52
+ cursor += line.length + 1;
53
+ }
54
+ return openMarker === null ? -1 : openAt;
55
+ }
56
+ /** Stub kept for test compatibility — OpenTUI parses internally. */
57
+ export function parseMarkdownBlocks(text) {
58
+ const lines = text.split("\n");
59
+ const blocks = [];
60
+ let i = 0;
61
+ while (i < lines.length) {
62
+ const line = lines[i];
63
+ if (line.startsWith("```")) {
64
+ const lang = line.slice(3).trim();
65
+ i++;
66
+ const codeLines = [];
67
+ while (i < lines.length && !lines[i].startsWith("```")) {
68
+ codeLines.push(lines[i]);
69
+ i++;
70
+ }
71
+ blocks.push({ type: "code", lang, lines: codeLines });
72
+ i++;
73
+ continue;
74
+ }
75
+ if (/^#{1,6}\s/.test(line)) {
76
+ const match = /^(#{1,6})\s+(.*)$/.exec(line);
77
+ if (match) {
78
+ blocks.push({ type: "heading", level: match[1].length, text: match[2] });
79
+ i++;
80
+ continue;
81
+ }
82
+ }
83
+ if (line.trim() === "") {
84
+ i++;
85
+ continue;
86
+ }
87
+ const paragraph = [];
88
+ while (i < lines.length && lines[i].trim() !== "" && !lines[i].startsWith("```")) {
89
+ paragraph.push(lines[i]);
90
+ i++;
91
+ }
92
+ blocks.push({ type: "paragraph", lines: paragraph });
93
+ }
94
+ return blocks;
95
+ }
96
+ /** Stub kept for test compatibility. */
97
+ export function parseMarkdownInlineSegments(text, style = {}) {
98
+ return [{ text, ...style }];
99
+ }
100
+ /**
101
+ * Render a complete (non-streaming) markdown blob. OpenTUI's MarkdownRenderable
102
+ * handles parse + style + code highlighting via tree-sitter internally.
103
+ */
104
+ export function MarkdownContent({ content, terminalColumns }) {
105
+ const theme = useTheme();
106
+ if (!content)
107
+ return null;
108
+ return (_jsx("box", { style: { flexDirection: "column", width: terminalColumns ? terminalColumns - 2 : undefined }, children: React.createElement("markdown", {
109
+ content,
110
+ style: {
111
+ textColor: theme.userMessageText,
112
+ codeBackgroundColor: theme.inputBg,
113
+ codeTextColor: theme.code,
114
+ headerColor: theme.brand,
115
+ linkColor: theme.traceCommand,
116
+ },
117
+ }) }));
118
+ }
119
+ /**
120
+ * Streaming variant. With OpenTUI's double-buffered renderer there's no
121
+ * tearing penalty for re-parsing on every token — the renderer composes the
122
+ * full frame natively before swapping. So we just delegate to the same
123
+ * primitive and let it handle partial input.
124
+ */
125
+ export function StreamingMarkdown({ content, terminalColumns }) {
126
+ return _jsx(MarkdownContent, { content: content, terminalColumns: terminalColumns });
127
+ }
@@ -0,0 +1,31 @@
1
+ /** @jsxImportSource @opentui/react */
2
+ import React from "react";
3
+ import type { DisplayMessage, DisplayMessagePart, DisplayToolCall } from "./display-history.js";
4
+ export interface PendingApprovalHint {
5
+ toolName: "edit" | "write" | "bash";
6
+ path?: string;
7
+ command?: string;
8
+ }
9
+ interface MessageListProps {
10
+ messages: DisplayMessage[];
11
+ streamingContent: string;
12
+ streamingReasoning: string;
13
+ streamingTools: DisplayToolCall[];
14
+ streamingParts: DisplayMessagePart[];
15
+ terminalColumns: number;
16
+ verboseTrace: boolean;
17
+ pendingApproval?: PendingApprovalHint | null;
18
+ nowTick?: number;
19
+ welcomeBanner?: React.ReactNode;
20
+ }
21
+ /**
22
+ * Scrollback-style message list following opencode visual rules:
23
+ *
24
+ * - User messages: `›` (U+203A) prefix in accent color, plain text body
25
+ * - Assistant messages: full-width body in `theme.text`, no chrome
26
+ * - Reasoning: `Thinking:` prefix in textMuted
27
+ * - Tools: dim title row + paddingLeft=1 body
28
+ * - No borders, no bubbles, no role badges — everything is text rhythm
29
+ */
30
+ export declare function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, verboseTrace, welcomeBanner, }: MessageListProps): React.ReactNode;
31
+ export {};
@@ -0,0 +1,125 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@opentui/react/jsx-runtime";
2
+ import { useTheme } from "./theme.js";
3
+ import { MarkdownContent, StreamingMarkdown } from "./markdown.js";
4
+ import { EDIT_COLLAPSED_DIFF_LINES, getEditDiffDetails } from "./edit-diff.js";
5
+ /**
6
+ * Scrollback-style message list following opencode visual rules:
7
+ *
8
+ * - User messages: `›` (U+203A) prefix in accent color, plain text body
9
+ * - Assistant messages: full-width body in `theme.text`, no chrome
10
+ * - Reasoning: `Thinking:` prefix in textMuted
11
+ * - Tools: dim title row + paddingLeft=1 body
12
+ * - No borders, no bubbles, no role badges — everything is text rhythm
13
+ */
14
+ export function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, verboseTrace, welcomeBanner, }) {
15
+ const hasStreaming = !!(streamingContent ||
16
+ streamingReasoning ||
17
+ streamingTools.length > 0 ||
18
+ streamingParts.length > 0);
19
+ return (_jsxs("box", { style: { flexDirection: "column", flexShrink: 0 }, children: [welcomeBanner, messages.map((msg, i) => (_jsx(MessageItem, { message: msg, terminalColumns: terminalColumns, verboseTrace: verboseTrace }, msg.key ?? `message-${i}`))), hasStreaming && (_jsx(StreamingMessage, { content: streamingContent, reasoning: streamingReasoning, tools: streamingTools, parts: streamingParts, terminalColumns: terminalColumns, verboseTrace: verboseTrace }))] }));
20
+ }
21
+ function MessageItem({ message, terminalColumns, verboseTrace, }) {
22
+ const theme = useTheme();
23
+ if (message.role === "user") {
24
+ return _jsx(UserMessageBlock, { content: message.content, theme: theme });
25
+ }
26
+ if (message.role === "error") {
27
+ return (_jsx("box", { style: { marginBottom: 1, flexDirection: "column" }, children: _jsx("text", { fg: theme.error, content: `Error: ${message.content}` }) }));
28
+ }
29
+ if (message.syntheticKind === "ui_compact_summary") {
30
+ return _jsx(CompactionSummaryBlock, { message: message, theme: theme });
31
+ }
32
+ const hasVisible = !!message.content ||
33
+ (message.toolCalls?.length ?? 0) > 0 ||
34
+ (message.parts?.length ?? 0) > 0 ||
35
+ (!!message.reasoning && verboseTrace);
36
+ if (!hasVisible)
37
+ return null;
38
+ return (_jsxs("box", { style: { marginTop: 1, marginBottom: 1, flexDirection: "column" }, children: [message.reasoning && verboseTrace && _jsx(ReasoningBlock, { reasoning: message.reasoning, theme: theme }), message.parts && message.parts.length > 0 ? (_jsx(MessageParts, { parts: message.parts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, theme: theme })) : (_jsxs(_Fragment, { children: [message.toolCalls && _jsx(ToolsPart, { toolCalls: message.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, theme: theme }), message.content && _jsx(MarkdownContent, { content: message.content, terminalColumns: terminalColumns })] }))] }));
39
+ }
40
+ function StreamingMessage({ content, reasoning, tools, parts, terminalColumns, verboseTrace, }) {
41
+ const theme = useTheme();
42
+ const visibleParts = parts.length > 0 ? parts : fallbackStreamingParts(content, tools);
43
+ return (_jsxs("box", { style: { flexDirection: "column", marginTop: 1 }, children: [reasoning && verboseTrace && _jsx(ReasoningBlock, { reasoning: reasoning, theme: theme }), visibleParts.length > 0 && (_jsx(MessageParts, { parts: visibleParts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, streaming: true, theme: theme }))] }));
44
+ }
45
+ function fallbackStreamingParts(content, tools) {
46
+ const out = [];
47
+ if (tools.length > 0)
48
+ out.push({ type: "tools", toolCalls: tools });
49
+ if (content)
50
+ out.push({ type: "text", content });
51
+ return out;
52
+ }
53
+ function MessageParts({ parts, terminalColumns, verboseTrace, streaming = false, theme, }) {
54
+ return (_jsx("box", { style: { flexDirection: "column" }, children: parts.map((part, idx) => {
55
+ if (part.type === "text") {
56
+ if (streaming && idx === parts.length - 1) {
57
+ return _jsx(StreamingMarkdown, { content: part.content, terminalColumns: terminalColumns }, `text-${idx}`);
58
+ }
59
+ return _jsx(MarkdownContent, { content: part.content, terminalColumns: terminalColumns }, `text-${idx}`);
60
+ }
61
+ return _jsx(ToolsPart, { toolCalls: part.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, theme: theme }, `tools-${idx}`);
62
+ }) }));
63
+ }
64
+ /**
65
+ * User message: `›` prefix in accent color, body in accent color. No border
66
+ * or background — flows in scrollback as raw colored text. opencode pattern.
67
+ */
68
+ function UserMessageBlock({ content, theme }) {
69
+ const lines = content.split("\n");
70
+ return (_jsx("box", { style: { marginTop: 1, marginBottom: 1, flexDirection: "column" }, children: lines.map((line, i) => (_jsxs("text", { fg: theme.userMessageText, children: [i === 0 ? "› " : " ", line || " "] }, `u-${i}`))) }));
71
+ }
72
+ function ReasoningBlock({ reasoning, theme }) {
73
+ return (_jsx("box", { style: { flexDirection: "column", marginBottom: 1 }, children: reasoning.split("\n").map((line, i) => (_jsxs("text", { fg: theme.textMuted, children: [i === 0 ? "Thinking: " : " ", line] }, `r-${i}`))) }));
74
+ }
75
+ function CompactionSummaryBlock({ message, theme }) {
76
+ return (_jsxs("box", { style: { flexDirection: "column", marginTop: 1, marginBottom: 1 }, children: [_jsx("text", { fg: theme.textMuted, attributes: 1, content: "\u2500\u2500 context compacted \u2500\u2500" }), message.content && (_jsx("text", { fg: theme.textMuted, content: message.content }))] }));
77
+ }
78
+ function ToolsPart({ toolCalls, terminalColumns, verboseTrace, theme, }) {
79
+ return (_jsx("box", { style: { flexDirection: "column", marginTop: 1 }, children: toolCalls.map((tc) => (_jsx(ToolCard, { tool: tc, terminalColumns: terminalColumns, verboseTrace: verboseTrace, theme: theme }, tc.id))) }));
80
+ }
81
+ /**
82
+ * Tool card: dim title line + indented body. No icons, no per-tool colors —
83
+ * pure text rhythm so the eye reads the header as a section break and the
84
+ * body as content. Matches opencode's `theme.block` pattern.
85
+ */
86
+ function ToolCard({ tool, terminalColumns, verboseTrace, theme, }) {
87
+ const pending = tool.result === undefined;
88
+ const error = tool.isError;
89
+ const target = describeToolTarget(tool);
90
+ const headerColor = pending ? theme.toolPending : error ? theme.toolError : theme.textMuted;
91
+ const titleText = target ? `${tool.name} ${target}` : tool.name;
92
+ const editDetails = (tool.name === "edit" || tool.name === "multiedit" || tool.name === "write")
93
+ ? getEditDiffDetails(tool)
94
+ : null;
95
+ return (_jsxs("box", { style: { flexDirection: "column", marginBottom: 1 }, children: [_jsx("text", { fg: headerColor, content: titleText }), _jsxs("box", { style: { paddingLeft: 1, flexDirection: "column" }, children: [!pending && tool.result && verboseTrace && (_jsx(ToolResultPreview, { result: tool.result, error: error, terminalColumns: terminalColumns, theme: theme })), editDetails && editDetails.diff && (_jsx(EditDiffPreview, { diff: editDetails.diff, theme: theme }))] })] }));
96
+ }
97
+ function describeToolTarget(tool) {
98
+ const args = tool.args || {};
99
+ if (typeof args.path === "string")
100
+ return args.path;
101
+ if (typeof args.command === "string")
102
+ return args.command.slice(0, 80);
103
+ if (typeof args.pattern === "string")
104
+ return args.pattern;
105
+ if (typeof args.url === "string")
106
+ return args.url;
107
+ if (typeof args.query === "string")
108
+ return args.query;
109
+ return "";
110
+ }
111
+ function ToolResultPreview({ result, error, terminalColumns, theme, }) {
112
+ const lines = result.split("\n").slice(0, 8);
113
+ const maxLen = Math.max(20, terminalColumns - 8);
114
+ return (_jsx("box", { style: { flexDirection: "column" }, children: lines.map((line, i) => (_jsx("text", { fg: error ? theme.toolError : theme.text, content: line.length > maxLen ? line.slice(0, maxLen - 1) + "…" : line }, `r-${i}`))) }));
115
+ }
116
+ function EditDiffPreview({ diff, theme }) {
117
+ const allLines = diff.split("\n");
118
+ const shown = allLines.slice(0, EDIT_COLLAPSED_DIFF_LINES);
119
+ const overflow = allLines.length - shown.length;
120
+ return (_jsxs("box", { style: { flexDirection: "column" }, children: [shown.map((line, i) => {
121
+ const kind = line.startsWith("+") ? "+" : line.startsWith("-") ? "-" : " ";
122
+ const fg = kind === "+" ? theme.diffAddFg : kind === "-" ? theme.diffRemoveFg : theme.textMuted;
123
+ return _jsx("text", { fg: fg, content: line }, `d-${i}`);
124
+ }), overflow > 0 && _jsx("text", { fg: theme.textMuted, content: ` … +${overflow} more lines` })] }));
125
+ }
@@ -0,0 +1,63 @@
1
+ /** @jsxImportSource @opentui/react */
2
+ import React from "react";
3
+ import { ProviderRegistry } from "../provider-registry.js";
4
+ export { padVisual, truncateVisual } from "../text-display.js";
5
+ export interface ModelPickerOption {
6
+ id: string;
7
+ label: string;
8
+ group: string;
9
+ providerBadge: string;
10
+ }
11
+ export type PickerKeyAction = "up" | "down" | "enter" | "escape" | "backspace" | "delete";
12
+ export declare function resolvePickerKeyAction(input: string, key: {
13
+ upArrow?: boolean;
14
+ downArrow?: boolean;
15
+ return?: boolean;
16
+ escape?: boolean;
17
+ backspace?: boolean;
18
+ delete?: boolean;
19
+ }): PickerKeyAction | undefined;
20
+ export declare function isPrintablePickerInput(input: string): boolean;
21
+ export declare function formatSkillPickerRow(skill: {
22
+ name: string;
23
+ description?: string;
24
+ }, options: {
25
+ selected: boolean;
26
+ width: number;
27
+ }): string;
28
+ export interface ModelPickerProps {
29
+ registry: ProviderRegistry;
30
+ current: string;
31
+ recent: string[];
32
+ onSelect: (model: string) => void;
33
+ onCancel: () => void;
34
+ }
35
+ export declare function ModelPicker({ registry, current, recent, onSelect, onCancel }: ModelPickerProps): React.ReactNode;
36
+ export declare function buildLocalModelOptions(registry: ProviderRegistry, current: string, recent: string[]): ModelPickerOption[];
37
+ export interface ProviderPickerProps {
38
+ providers: Array<{
39
+ id: string;
40
+ name: string;
41
+ enabled: boolean;
42
+ }>;
43
+ current?: string;
44
+ onSelect: (providerId: string) => void;
45
+ onCancel: () => void;
46
+ title?: string;
47
+ }
48
+ export declare function ProviderPicker({ providers, current, onSelect, onCancel, title }: ProviderPickerProps): React.ReactNode;
49
+ export interface KeyPickerProps {
50
+ providerName: string;
51
+ onSubmit: (key: string) => void;
52
+ onCancel: () => void;
53
+ }
54
+ export declare function KeyPicker({ providerName, onSubmit, onCancel }: KeyPickerProps): React.ReactNode;
55
+ export interface SkillPickerProps {
56
+ skills: Array<{
57
+ name: string;
58
+ description: string;
59
+ }>;
60
+ onSelect: (name: string) => void;
61
+ onCancel: () => void;
62
+ }
63
+ export declare function SkillPicker({ skills, onSelect, onCancel }: SkillPickerProps): React.ReactNode;