@bubblebrain-ai/bubble 0.0.11 → 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 (160) 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 +39 -2
  5. package/dist/feishu/agent-host/run-driver.js +13 -6
  6. package/dist/feishu/agent-host/runtime-deps.d.ts +2 -2
  7. package/dist/feishu/router/commands.js +2 -1
  8. package/dist/feishu/scope/session-binder.js +1 -1
  9. package/dist/feishu/serve.js +3 -3
  10. package/dist/main.js +78 -12
  11. package/dist/prompt/compose.js +3 -3
  12. package/dist/prompt/environment.js +2 -0
  13. package/dist/prompt/reminders.js +1 -1
  14. package/dist/provider-openai-codex.d.ts +8 -1
  15. package/dist/provider-openai-codex.js +33 -9
  16. package/dist/provider.d.ts +2 -0
  17. package/dist/session-title.d.ts +16 -0
  18. package/dist/session-title.js +134 -0
  19. package/dist/session-types.d.ts +5 -0
  20. package/dist/session.d.ts +5 -0
  21. package/dist/session.js +75 -9
  22. package/dist/skills/invocation.js +0 -18
  23. package/dist/skills/registry.d.ts +1 -0
  24. package/dist/skills/registry.js +2 -0
  25. package/dist/slash-commands/commands.js +29 -22
  26. package/dist/slash-commands/registry.js +1 -1
  27. package/dist/slash-commands/types.d.ts +10 -0
  28. package/dist/text-display.d.ts +3 -0
  29. package/dist/text-display.js +25 -0
  30. package/dist/tools/index.d.ts +1 -0
  31. package/dist/tools/index.js +3 -1
  32. package/dist/tools/skill-search.d.ts +10 -0
  33. package/dist/tools/skill-search.js +134 -0
  34. package/dist/tools/skill.js +1 -4
  35. package/dist/tui/clipboard.d.ts +1 -0
  36. package/dist/tui/clipboard.js +53 -0
  37. package/dist/tui/detect-theme.d.ts +2 -0
  38. package/dist/tui/detect-theme.js +87 -0
  39. package/dist/tui/display-history.d.ts +62 -0
  40. package/dist/tui/display-history.js +305 -0
  41. package/dist/tui/edit-diff.d.ts +11 -0
  42. package/dist/tui/edit-diff.js +52 -0
  43. package/dist/tui/escape-confirmation.d.ts +15 -0
  44. package/dist/tui/escape-confirmation.js +30 -0
  45. package/dist/tui/file-mentions.d.ts +29 -0
  46. package/dist/tui/file-mentions.js +174 -0
  47. package/dist/tui/global-key-router.d.ts +3 -0
  48. package/dist/tui/global-key-router.js +87 -0
  49. package/dist/tui/image-paste.d.ts +95 -0
  50. package/dist/tui/image-paste.js +505 -0
  51. package/dist/tui/input-history.d.ts +16 -0
  52. package/dist/tui/input-history.js +79 -0
  53. package/dist/tui/markdown-inline.d.ts +22 -0
  54. package/dist/tui/markdown-inline.js +68 -0
  55. package/dist/tui/markdown-theme-rules.d.ts +23 -0
  56. package/dist/tui/markdown-theme-rules.js +164 -0
  57. package/dist/tui/markdown-theme.d.ts +5 -0
  58. package/dist/tui/markdown-theme.js +27 -0
  59. package/dist/tui/opencode-spinner.d.ts +22 -0
  60. package/dist/tui/opencode-spinner.js +216 -0
  61. package/dist/tui/prompt-keybindings.d.ts +42 -0
  62. package/dist/tui/prompt-keybindings.js +35 -0
  63. package/dist/tui/recent-activity.d.ts +8 -0
  64. package/dist/tui/recent-activity.js +71 -0
  65. package/dist/tui/render-signature.d.ts +1 -0
  66. package/dist/tui/render-signature.js +7 -0
  67. package/dist/tui/run.d.ts +45 -0
  68. package/dist/tui/run.js +8816 -0
  69. package/dist/tui/session-display.d.ts +6 -0
  70. package/dist/tui/session-display.js +12 -0
  71. package/dist/tui/sidebar-mcp.d.ts +31 -0
  72. package/dist/tui/sidebar-mcp.js +62 -0
  73. package/dist/tui/sidebar-state.d.ts +12 -0
  74. package/dist/tui/sidebar-state.js +69 -0
  75. package/dist/tui/streaming-tool-args.d.ts +15 -0
  76. package/dist/tui/streaming-tool-args.js +30 -0
  77. package/dist/tui/tool-renderers/fallback.d.ts +2 -0
  78. package/dist/tui/tool-renderers/fallback.js +75 -0
  79. package/dist/tui/tool-renderers/registry.d.ts +3 -0
  80. package/dist/tui/tool-renderers/registry.js +11 -0
  81. package/dist/tui/tool-renderers/subagent.d.ts +2 -0
  82. package/dist/tui/tool-renderers/subagent.js +135 -0
  83. package/dist/tui/tool-renderers/types.d.ts +36 -0
  84. package/dist/tui/tool-renderers/types.js +1 -0
  85. package/dist/tui/tool-renderers/write-preview.d.ts +12 -0
  86. package/dist/tui/tool-renderers/write-preview.js +30 -0
  87. package/dist/tui/tool-renderers/write.d.ts +6 -0
  88. package/dist/tui/tool-renderers/write.js +88 -0
  89. package/dist/tui/trace-groups.d.ts +27 -0
  90. package/dist/tui/trace-groups.js +412 -0
  91. package/dist/tui/wordmark.d.ts +15 -0
  92. package/dist/tui/wordmark.js +179 -0
  93. package/dist/tui-ink/app.js +98 -70
  94. package/dist/tui-ink/input-box.d.ts +22 -1
  95. package/dist/tui-ink/input-box.js +105 -11
  96. package/dist/tui-ink/message-list.js +12 -3
  97. package/dist/tui-ink/model-picker.d.ts +18 -0
  98. package/dist/tui-ink/model-picker.js +80 -23
  99. package/dist/tui-ink/session-picker.js +5 -7
  100. package/dist/tui-ink/theme.d.ts +3 -9
  101. package/dist/tui-ink/theme.js +39 -45
  102. package/dist/tui-ink/welcome.js +22 -78
  103. package/dist/tui-opentui/app.d.ts +54 -0
  104. package/dist/tui-opentui/app.js +1363 -0
  105. package/dist/tui-opentui/approval/approval-dialog.d.ts +15 -0
  106. package/dist/tui-opentui/approval/approval-dialog.js +139 -0
  107. package/dist/tui-opentui/approval/diff-view.d.ts +9 -0
  108. package/dist/tui-opentui/approval/diff-view.js +43 -0
  109. package/dist/tui-opentui/approval/select.d.ts +37 -0
  110. package/dist/tui-opentui/approval/select.js +91 -0
  111. package/dist/tui-opentui/detect-theme.d.ts +2 -0
  112. package/dist/tui-opentui/detect-theme.js +87 -0
  113. package/dist/tui-opentui/display-history.d.ts +55 -0
  114. package/dist/tui-opentui/display-history.js +129 -0
  115. package/dist/tui-opentui/edit-diff.d.ts +11 -0
  116. package/dist/tui-opentui/edit-diff.js +52 -0
  117. package/dist/tui-opentui/feedback-dialog.d.ts +21 -0
  118. package/dist/tui-opentui/feedback-dialog.js +164 -0
  119. package/dist/tui-opentui/feishu-setup-picker.d.ts +7 -0
  120. package/dist/tui-opentui/feishu-setup-picker.js +272 -0
  121. package/dist/tui-opentui/file-mentions.d.ts +29 -0
  122. package/dist/tui-opentui/file-mentions.js +174 -0
  123. package/dist/tui-opentui/footer.d.ts +26 -0
  124. package/dist/tui-opentui/footer.js +40 -0
  125. package/dist/tui-opentui/image-paste.d.ts +54 -0
  126. package/dist/tui-opentui/image-paste.js +288 -0
  127. package/dist/tui-opentui/input-box.d.ts +34 -0
  128. package/dist/tui-opentui/input-box.js +471 -0
  129. package/dist/tui-opentui/input-history.d.ts +16 -0
  130. package/dist/tui-opentui/input-history.js +79 -0
  131. package/dist/tui-opentui/markdown.d.ts +66 -0
  132. package/dist/tui-opentui/markdown.js +127 -0
  133. package/dist/tui-opentui/message-list.d.ts +31 -0
  134. package/dist/tui-opentui/message-list.js +125 -0
  135. package/dist/tui-opentui/model-picker.d.ts +63 -0
  136. package/dist/tui-opentui/model-picker.js +450 -0
  137. package/dist/tui-opentui/plan-confirm.d.ts +9 -0
  138. package/dist/tui-opentui/plan-confirm.js +124 -0
  139. package/dist/tui-opentui/question-dialog.d.ts +10 -0
  140. package/dist/tui-opentui/question-dialog.js +110 -0
  141. package/dist/tui-opentui/recent-activity.d.ts +8 -0
  142. package/dist/tui-opentui/recent-activity.js +71 -0
  143. package/dist/tui-opentui/run-session-picker.d.ts +10 -0
  144. package/dist/tui-opentui/run-session-picker.js +28 -0
  145. package/dist/tui-opentui/run.d.ts +38 -0
  146. package/dist/tui-opentui/run.js +48 -0
  147. package/dist/tui-opentui/session-picker.d.ts +12 -0
  148. package/dist/tui-opentui/session-picker.js +120 -0
  149. package/dist/tui-opentui/theme.d.ts +89 -0
  150. package/dist/tui-opentui/theme.js +157 -0
  151. package/dist/tui-opentui/todos.d.ts +9 -0
  152. package/dist/tui-opentui/todos.js +45 -0
  153. package/dist/tui-opentui/trace-groups.d.ts +27 -0
  154. package/dist/tui-opentui/trace-groups.js +412 -0
  155. package/dist/tui-opentui/use-terminal-size.d.ts +4 -0
  156. package/dist/tui-opentui/use-terminal-size.js +5 -0
  157. package/dist/tui-opentui/welcome.d.ts +25 -0
  158. package/dist/tui-opentui/welcome.js +77 -0
  159. package/dist/types.d.ts +24 -0
  160. package/package.json +5 -1
@@ -1,10 +1,28 @@
1
1
  import { ProviderRegistry } from "../provider-registry.js";
2
+ export { padVisual, truncateVisual } from "../text-display.js";
2
3
  export interface ModelPickerOption {
3
4
  id: string;
4
5
  label: string;
5
6
  group: string;
6
7
  providerBadge: string;
7
8
  }
9
+ export type PickerKeyAction = "up" | "down" | "enter" | "escape" | "backspace" | "delete";
10
+ export declare function resolvePickerKeyAction(input: string, key: {
11
+ upArrow?: boolean;
12
+ downArrow?: boolean;
13
+ return?: boolean;
14
+ escape?: boolean;
15
+ backspace?: boolean;
16
+ delete?: boolean;
17
+ }): PickerKeyAction | undefined;
18
+ export declare function isPrintablePickerInput(input: string): boolean;
19
+ export declare function formatSkillPickerRow(skill: {
20
+ name: string;
21
+ description?: string;
22
+ }, options: {
23
+ selected: boolean;
24
+ width: number;
25
+ }): string;
8
26
  export interface ModelPickerProps {
9
27
  registry: ProviderRegistry;
10
28
  current: string;
@@ -4,6 +4,55 @@ import { Box, Text, useInput, usePaste, useStdout } from "ink";
4
4
  import { useTheme } from "./theme.js";
5
5
  import { encodeModel, decodeModel, displayModel, isUserVisibleProvider } from "../provider-registry.js";
6
6
  import { listBuiltinModels } from "../model-catalog.js";
7
+ import { padVisual, truncateVisual } from "../text-display.js";
8
+ export { padVisual, truncateVisual } from "../text-display.js";
9
+ export function resolvePickerKeyAction(input, key) {
10
+ if (key.escape)
11
+ return "escape";
12
+ if (key.return)
13
+ return "enter";
14
+ if (key.upArrow)
15
+ return "up";
16
+ if (key.downArrow)
17
+ return "down";
18
+ if (key.backspace)
19
+ return "backspace";
20
+ if (key.delete)
21
+ return "delete";
22
+ const sequence = normalizeEscapeSequence(input);
23
+ if (/^(?:O|\[[\d;:]*)A$/.test(sequence))
24
+ return "up";
25
+ if (/^(?:O|\[[\d;:]*)B$/.test(sequence))
26
+ return "down";
27
+ return undefined;
28
+ }
29
+ export function isPrintablePickerInput(input) {
30
+ if (!input)
31
+ return false;
32
+ if (input.startsWith("\x1b"))
33
+ return false;
34
+ if (isRawEscapeTail(input))
35
+ return false;
36
+ return !/[\x00-\x1f\x7f]/.test(input);
37
+ }
38
+ export function formatSkillPickerRow(skill, options) {
39
+ const width = Math.max(12, options.width);
40
+ const marker = options.selected ? "> " : " ";
41
+ const nameBudget = Math.max(8, Math.min(28, Math.floor(width * 0.35)));
42
+ const name = truncateVisual(skill.name, nameBudget);
43
+ const nameCell = padVisual(name, nameBudget);
44
+ const description = (skill.description ?? "").replace(/\s+/g, " ").trim();
45
+ const row = description
46
+ ? `${marker}${nameCell} ${description}`
47
+ : `${marker}${nameCell}`;
48
+ return padVisual(truncateVisual(row, width), width);
49
+ }
50
+ function normalizeEscapeSequence(input) {
51
+ return input.startsWith("\x1b") ? input.slice(1) : input;
52
+ }
53
+ function isRawEscapeTail(input) {
54
+ return /^(?:O[ABCDHF]|\[[\d;:]*[A-Za-z~])$/.test(input);
55
+ }
7
56
  export function ModelPicker({ registry, current, recent, onSelect, onCancel }) {
8
57
  const theme = useTheme();
9
58
  const { stdout } = useStdout();
@@ -76,25 +125,26 @@ export function ModelPicker({ registry, current, recent, onSelect, onCancel }) {
76
125
  return rawOptions.filter((opt) => opt.label.toLowerCase().includes(q) || opt.providerBadge.toLowerCase().includes(q));
77
126
  }, [rawOptions, query]);
78
127
  useInput((input, key) => {
79
- if (key.escape) {
128
+ const action = resolvePickerKeyAction(input, key);
129
+ if (action === "escape") {
80
130
  onCancel();
81
131
  return;
82
132
  }
83
- if (key.return) {
133
+ if (action === "enter") {
84
134
  const opt = options[selectedIndex];
85
135
  if (opt)
86
136
  onSelect(opt.id);
87
137
  return;
88
138
  }
89
- if (key.upArrow) {
139
+ if (action === "up") {
90
140
  setSelectedIndex((i) => Math.max(0, i - 1));
91
141
  return;
92
142
  }
93
- if (key.downArrow) {
143
+ if (action === "down") {
94
144
  setSelectedIndex((i) => Math.min(options.length - 1, i + 1));
95
145
  return;
96
146
  }
97
- if (key.backspace || key.delete) {
147
+ if (action === "backspace" || action === "delete") {
98
148
  setQuery((q) => {
99
149
  const next = q.slice(0, -1);
100
150
  setSelectedIndex(0);
@@ -102,7 +152,7 @@ export function ModelPicker({ registry, current, recent, onSelect, onCancel }) {
102
152
  });
103
153
  return;
104
154
  }
105
- if (input && !key.ctrl && !key.meta) {
155
+ if (isPrintablePickerInput(input) && !key.ctrl && !key.meta) {
106
156
  setQuery((q) => {
107
157
  const next = q + input;
108
158
  setSelectedIndex(0);
@@ -197,25 +247,26 @@ export function ProviderPicker({ providers, current, onSelect, onCancel, title }
197
247
  return idx >= 0 ? idx : 0;
198
248
  });
199
249
  useInput((input, key) => {
200
- if (key.escape) {
250
+ const action = resolvePickerKeyAction(input, key);
251
+ if (action === "escape") {
201
252
  onCancel();
202
253
  return;
203
254
  }
204
- if (key.return) {
255
+ if (action === "enter") {
205
256
  const p = providers[selectedIndex];
206
257
  if (p)
207
258
  onSelect(p.id);
208
259
  return;
209
260
  }
210
- if (key.upArrow) {
261
+ if (action === "up") {
211
262
  setSelectedIndex((i) => Math.max(0, i - 1));
212
263
  return;
213
264
  }
214
- if (key.downArrow) {
265
+ if (action === "down") {
215
266
  setSelectedIndex((i) => Math.min(providers.length - 1, i + 1));
216
267
  return;
217
268
  }
218
- if (input && input.length === 1 && /[a-z]/i.test(input)) {
269
+ if (isPrintablePickerInput(input) && input.length === 1 && /[a-z]/i.test(input)) {
219
270
  const char = input.toLowerCase();
220
271
  for (let i = selectedIndex + 1; i < providers.length; i++) {
221
272
  if (providers[i].name.toLowerCase().startsWith(char)) {
@@ -243,20 +294,21 @@ export function KeyPicker({ providerName, onSubmit, onCancel }) {
243
294
  const theme = useTheme();
244
295
  const [value, setValue] = useState("");
245
296
  useInput((input, key) => {
246
- if (key.escape) {
297
+ const action = resolvePickerKeyAction(input, key);
298
+ if (action === "escape") {
247
299
  onCancel();
248
300
  return;
249
301
  }
250
- if (key.return) {
302
+ if (action === "enter") {
251
303
  if (value.trim())
252
304
  onSubmit(value.trim());
253
305
  return;
254
306
  }
255
- if (key.backspace || key.delete) {
307
+ if (action === "backspace" || action === "delete") {
256
308
  setValue((v) => v.slice(0, -1));
257
309
  return;
258
310
  }
259
- if (input && !key.ctrl && !key.meta) {
311
+ if (isPrintablePickerInput(input) && !key.ctrl && !key.meta) {
260
312
  setValue((v) => v + input);
261
313
  }
262
314
  });
@@ -274,7 +326,9 @@ export function SkillPicker({ skills, onSelect, onCancel }) {
274
326
  const theme = useTheme();
275
327
  const { stdout } = useStdout();
276
328
  const termHeight = stdout?.rows || 24;
329
+ const terminalColumns = stdout?.columns || 80;
277
330
  const maxVisible = Math.max(5, termHeight - 8);
331
+ const rowWidth = Math.max(36, Math.min(96, terminalColumns - 6));
278
332
  const [query, setQuery] = useState("");
279
333
  const [selectedIndex, setSelectedIndex] = useState(0);
280
334
  const options = useMemo(() => {
@@ -284,25 +338,26 @@ export function SkillPicker({ skills, onSelect, onCancel }) {
284
338
  return skills.filter((skill) => skill.name.toLowerCase().includes(q) || skill.description.toLowerCase().includes(q));
285
339
  }, [query, skills]);
286
340
  useInput((input, key) => {
287
- if (key.escape) {
341
+ const action = resolvePickerKeyAction(input, key);
342
+ if (action === "escape") {
288
343
  onCancel();
289
344
  return;
290
345
  }
291
- if (key.return) {
346
+ if (action === "enter") {
292
347
  const skill = options[selectedIndex];
293
348
  if (skill)
294
349
  onSelect(skill.name);
295
350
  return;
296
351
  }
297
- if (key.upArrow) {
352
+ if (action === "up") {
298
353
  setSelectedIndex((i) => Math.max(0, i - 1));
299
354
  return;
300
355
  }
301
- if (key.downArrow) {
356
+ if (action === "down") {
302
357
  setSelectedIndex((i) => Math.min(Math.max(0, options.length - 1), i + 1));
303
358
  return;
304
359
  }
305
- if (key.backspace || key.delete) {
360
+ if (action === "backspace" || action === "delete") {
306
361
  setQuery((q) => {
307
362
  const next = q.slice(0, -1);
308
363
  setSelectedIndex(0);
@@ -310,7 +365,7 @@ export function SkillPicker({ skills, onSelect, onCancel }) {
310
365
  });
311
366
  return;
312
367
  }
313
- if (input && !key.ctrl && !key.meta) {
368
+ if (isPrintablePickerInput(input) && !key.ctrl && !key.meta) {
314
369
  setQuery((q) => {
315
370
  const next = q + input;
316
371
  setSelectedIndex(0);
@@ -318,11 +373,13 @@ export function SkillPicker({ skills, onSelect, onCancel }) {
318
373
  });
319
374
  }
320
375
  });
321
- const start = Math.max(0, Math.min(selectedIndex, options.length - maxVisible));
376
+ const maxStart = Math.max(0, options.length - maxVisible);
377
+ const start = Math.max(0, Math.min(maxStart, selectedIndex - Math.floor(maxVisible / 2)));
322
378
  const visible = options.slice(start, start + maxVisible);
323
379
  return (_jsxs(Box, { flexDirection: "column", marginY: 1, paddingX: 1, borderStyle: "round", borderColor: theme.borderActive, children: [_jsx(Text, { bold: true, color: theme.accent, children: "Select Skill" }), _jsx(SearchField, { query: query, placeholder: "Type to search skills..." }), _jsx(Text, { color: theme.muted, children: "\u2191/\u2193 navigate \u00B7 Enter load \u00B7 Esc cancel \u00B7 Backspace clear" }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [options.length === 0 && (_jsxs(Text, { color: theme.muted, children: ["No skills match \"", query, "\""] })), visible.map((skill, i) => {
324
380
  const actualIndex = start + i;
325
381
  const isSelected = actualIndex === selectedIndex;
326
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: isSelected ? theme.accent : undefined, children: [isSelected ? "> " : " ", skill.name] }), skill.description && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: theme.muted, dimColor: true, children: skill.description }) }))] }, skill.name));
382
+ const row = formatSkillPickerRow(skill, { selected: isSelected, width: rowWidth });
383
+ return (_jsx(Box, { children: _jsx(Text, { inverse: isSelected, color: isSelected ? theme.accent : undefined, bold: isSelected, children: row }) }, skill.name));
327
384
  })] })] }));
328
385
  }
@@ -3,10 +3,12 @@ import { useMemo, useState } from "react";
3
3
  import { Box, Text, useInput, useStdout } from "ink";
4
4
  import { useTheme } from "./theme.js";
5
5
  import { formatRelativeTime } from "./recent-activity.js";
6
+ import { padVisual, truncateVisual } from "../text-display.js";
6
7
  export function SessionPicker({ currentCwd, currentSessions, allSessions, onSelect, onCancel }) {
7
8
  const theme = useTheme();
8
9
  const { stdout } = useStdout();
9
10
  const termHeight = stdout?.rows || 24;
11
+ const termWidth = stdout?.columns || 80;
10
12
  const maxVisible = Math.max(6, termHeight - 10);
11
13
  const [mode, setMode] = useState("current");
12
14
  const [selectedSessionIdx, setSelectedSessionIdx] = useState(0);
@@ -55,8 +57,9 @@ export function SessionPicker({ currentCwd, currentSessions, allSessions, onSele
55
57
  }
56
58
  const session = row.session;
57
59
  const isSelected = actualIndex === selectedRowIndex;
58
- const time = formatRelativeTime(session.mtime).padEnd(9);
59
- return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ? theme.accent : undefined, children: [isSelected ? "> " : " ", time, " ", truncate(session.firstUserMessage, 60)] }), _jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: theme.muted, dimColor: true, children: ["\u00B7 ", session.messageCount, " msg", session.messageCount === 1 ? "" : "s"] }) })] }, session.file));
60
+ const time = padVisual(formatRelativeTime(session.mtime), 9);
61
+ const titleWidth = Math.max(20, Math.min(80, termWidth - 30));
62
+ return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ? theme.accent : undefined, children: [isSelected ? "> " : " ", time, " ", padVisual(truncateVisual(session.title, titleWidth), titleWidth)] }), _jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: theme.muted, dimColor: true, children: ["\u00B7 ", session.messageCount, " msg", session.messageCount === 1 ? "" : "s"] }) })] }, session.file));
60
63
  })] })] }));
61
64
  }
62
65
  function buildRows(mode, currentCwd, currentSessions, allSessions) {
@@ -105,8 +108,3 @@ function clampWindowStart(rows, selectedRowIndex, maxVisible) {
105
108
  start = rows.length - maxVisible;
106
109
  return Math.max(0, start);
107
110
  }
108
- function truncate(text, max) {
109
- if (text.length <= max)
110
- return text.padEnd(max);
111
- return text.slice(0, max - 1) + "…";
112
- }
@@ -49,15 +49,9 @@ export interface Theme {
49
49
  }
50
50
  export declare const darkTheme: Theme;
51
51
  /**
52
- * Light palette. Two ground rules drove the color choices:
53
- * 1. Named ANSI colors that render OK on both backgrounds (red/green/blue)
54
- * are kept by name so the user's terminal palette overrides remain
55
- * effective.
56
- * 2. Specific hex values are used wherever the dark palette assumed a dark
57
- * background (notably accent/code/trace colors and message bubbles).
58
- * Each hex was picked to clear WCAG AA contrast (4.5:1) against a near-
59
- * white background (#fafafa) or, when applicable, against the explicit
60
- * surface color in the same palette (e.g. diffAddFg vs diffAdd).
52
+ * Light palette aligned with the restored OpenTUI runtime: paper-neutral
53
+ * surfaces, blue focus/user rails, warm command accent, and semantic tool
54
+ * colors tuned for readable contrast on a light terminal background.
61
55
  */
62
56
  export declare const lightTheme: Theme;
63
57
  export declare const ThemeProvider: import("react").Provider<Theme>;
@@ -47,53 +47,47 @@ export const darkTheme = {
47
47
  diffRemoveFg: "#F48771",
48
48
  };
49
49
  /**
50
- * Light palette. Two ground rules drove the color choices:
51
- * 1. Named ANSI colors that render OK on both backgrounds (red/green/blue)
52
- * are kept by name so the user's terminal palette overrides remain
53
- * effective.
54
- * 2. Specific hex values are used wherever the dark palette assumed a dark
55
- * background (notably accent/code/trace colors and message bubbles).
56
- * Each hex was picked to clear WCAG AA contrast (4.5:1) against a near-
57
- * white background (#fafafa) or, when applicable, against the explicit
58
- * surface color in the same palette (e.g. diffAddFg vs diffAdd).
50
+ * Light palette aligned with the restored OpenTUI runtime: paper-neutral
51
+ * surfaces, blue focus/user rails, warm command accent, and semantic tool
52
+ * colors tuned for readable contrast on a light terminal background.
59
53
  */
60
54
  export const lightTheme = {
61
- user: "green",
62
- agent: "blue",
63
- error: "red",
64
- warning: "#9A6500", // ANSI yellow is invisible on white — go to dark amber.
65
- success: "green",
66
- accent: "#0E5A85", // dark teal — replaces "cyan" which washes out on white.
67
- border: "gray",
68
- borderActive: "#0E5A85",
69
- inputBorder: "#6B5FB8",
70
- inputBorderDisabled: "#c5c3d0",
71
- inputBg: "#f5f5fa",
72
- inputBgDisabled: "#ebebf2",
73
- inputText: "#1c1c24",
74
- inputPlaceholder: "#7a7886",
75
- muted: "gray",
76
- dim: "gray",
77
- thinking: "magenta",
78
- thinkingDim: "gray",
79
- toolName: "#0E5A85",
80
- toolResult: "gray",
81
- toolError: "red",
82
- toolPending: "#9A6500",
83
- code: "#9A6500",
84
- traceAction: "#B85A20",
85
- traceCount: "#5a5a5a",
86
- traceDetail: "gray",
87
- traceCommand: "#1A5FA0",
88
- tracePending: "#9A6500",
89
- userMessageBorder: "#6B5FB8",
90
- userMessageBg: "#e8e6f4",
91
- userMessageText: "#1c1c24",
92
- userRail: "#6B5FB8",
93
- diffAdd: "#d4f4d4",
94
- diffRemove: "#f4d4d4",
95
- diffAddFg: "#1c1c24",
96
- diffRemoveFg: "#1c1c24",
55
+ user: "#356FD2",
56
+ agent: "#171717",
57
+ error: "#B62633",
58
+ warning: "#8B4A00",
59
+ success: "#2F7D4A",
60
+ accent: "#8B4A00",
61
+ border: "#B9BDB8",
62
+ borderActive: "#356FD2",
63
+ inputBorder: "#356FD2",
64
+ inputBorderDisabled: "#D7DAD4",
65
+ inputBg: "#F1F3F0",
66
+ inputBgDisabled: "#E6E8E3",
67
+ inputText: "#171717",
68
+ inputPlaceholder: "#6F7377",
69
+ muted: "#6F7377",
70
+ dim: "#8B9094",
71
+ thinking: "#5F666D",
72
+ thinkingDim: "#8B9094",
73
+ toolName: "#495057",
74
+ toolResult: "#171717",
75
+ toolError: "#B62633",
76
+ toolPending: "#8B4A00",
77
+ code: "#2F7D4A",
78
+ traceAction: "#8B4A00",
79
+ traceCount: "#6F7377",
80
+ traceDetail: "#8B9094",
81
+ traceCommand: "#257E8A",
82
+ tracePending: "#8B4A00",
83
+ userMessageBorder: "#356FD2",
84
+ userMessageBg: "#F1F3F0",
85
+ userMessageText: "#234B93",
86
+ userRail: "#356FD2",
87
+ diffAdd: "#D7E8D8",
88
+ diffRemove: "#F7DADC",
89
+ diffAddFg: "#173D2D",
90
+ diffRemoveFg: "#5D1922",
97
91
  };
98
92
  const ThemeContext = createContext(darkTheme);
99
93
  export const ThemeProvider = ThemeContext.Provider;
@@ -3,80 +3,10 @@ import React from "react";
3
3
  import { Box, Text } from "ink";
4
4
  import { createRequire } from "node:module";
5
5
  import { useTheme } from "./theme.js";
6
+ import { BUBBLE_COMPACT_WORDMARK, BUBBLE_WORDMARK, bubbleWordmarkLineText, bubbleWordmarkMaxWidth, } from "../tui/wordmark.js";
6
7
  const require = createRequire(import.meta.url);
7
8
  const PACKAGE_VERSION = readPackageVersion();
8
- const BUBBLE_LOGO_LETTERS = [
9
- [
10
- "██████ ",
11
- "██ ██",
12
- "██ ██",
13
- "██████ ",
14
- "██ ██",
15
- "██ ██",
16
- "██████ ",
17
- ],
18
- [
19
- "██ ██",
20
- "██ ██",
21
- "██ ██",
22
- "██ ██",
23
- "██ ██",
24
- "██ ██",
25
- " █████ ",
26
- ],
27
- [
28
- "██████ ",
29
- "██ ██",
30
- "██ ██",
31
- "██████ ",
32
- "██ ██",
33
- "██ ██",
34
- "██████ ",
35
- ],
36
- [
37
- "██████ ",
38
- "██ ██",
39
- "██ ██",
40
- "██████ ",
41
- "██ ██",
42
- "██ ██",
43
- "██████ ",
44
- ],
45
- [
46
- "██ ",
47
- "██ ",
48
- "██ ",
49
- "██ ",
50
- "██ ",
51
- "██ ",
52
- "███████",
53
- ],
54
- [
55
- "███████",
56
- "██ ",
57
- "██ ",
58
- "██████ ",
59
- "██ ",
60
- "██ ",
61
- "███████",
62
- ],
63
- ];
64
- /**
65
- * Derive a 6-step logo gradient from the active theme tokens so the banner
66
- * stays readable on both dark and light backgrounds.
67
- */
68
- function logoColors(theme) {
69
- return [
70
- theme.userMessageText,
71
- theme.userMessageText,
72
- theme.inputBorder,
73
- theme.inputBorder,
74
- theme.traceCommand,
75
- theme.traceCommand,
76
- ];
77
- }
78
- const COMPACT_LOGO = ["B", "U", "B", "B", "L", "E"];
79
- const WIDE_LOGO_MIN_WIDTH = 52;
9
+ const WIDE_LOGO_MIN_WIDTH = bubbleWordmarkMaxWidth(BUBBLE_WORDMARK) + 4;
80
10
  export function shouldShowWelcomeBanner({ startedWithVisibleHistory, }) {
81
11
  // Keep banner visibility tied to the initial history, not transient overlays,
82
12
  // so opening and closing a picker does not move it in the transcript.
@@ -96,18 +26,32 @@ export function WelcomeBanner({ terminalColumns, modelLabel, cwd, tips, skillsCo
96
26
  : "Type / for commands and @ to reference files";
97
27
  const modelLine = modelLabel ? `${modelLabel}${cwd ? ` · ${cwd}` : ""}` : cwd;
98
28
  return (_jsxs(Box, { width: effectiveWidth, flexDirection: "column", alignItems: "center", marginBottom: 1, children: [_jsx(Box, { flexDirection: "column", alignItems: "center", children: useWideLogo
99
- ? BUBBLE_LOGO_LETTERS[0].map((_, rowIndex) => (_jsx(LogoRow, { rowIndex: rowIndex }, `logo-row-${rowIndex}`)))
29
+ ? BUBBLE_WORDMARK.map((line, rowIndex) => (_jsx(LogoRow, { line: line }, `logo-row-${rowIndex}`)))
100
30
  : _jsx(CompactLogo, {}) }), _jsx(Box, { marginTop: 2, children: _jsx(Text, { bold: true, color: theme.muted, children: PACKAGE_VERSION }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, color: theme.userMessageText, children: "TIP: " }), _jsx(Text, { bold: true, color: theme.userMessageText, children: tip })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: "shift+tab to cycle modes \u00B7 ctrl+r for reasoning \u00B7 ctrl+o for trace" }) }), modelLine && (_jsx(Box, { children: _jsx(Text, { color: theme.muted, children: truncateToWidth(modelLine, effectiveWidth - 4) }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(StatusItem, { label: "Skills", count: skillsCount, ok: skillsCount > 0 }), _jsx(Text, { color: theme.muted, children: " " }), _jsx(StatusItem, { label: "MCPs", count: mcpConnectedCount, total: mcpTotalCount, ok: mcpTotalCount === 0 || mcpConnectedCount === mcpTotalCount }), _jsx(Text, { color: theme.muted, children: " " }), _jsx(StatusItem, { label: "AGENTS.md", ok: hasAgentsFile })] })] }));
101
31
  }
102
- function LogoRow({ rowIndex }) {
32
+ function LogoRow({ line }) {
103
33
  const theme = useTheme();
104
- const colors = logoColors(theme);
105
- return (_jsx(Box, { children: BUBBLE_LOGO_LETTERS.map((letter, index) => (_jsxs(React.Fragment, { children: [_jsx(Text, { bold: true, color: colors[index], children: letter[rowIndex] }), index < BUBBLE_LOGO_LETTERS.length - 1 && _jsx(Text, { children: " " })] }, `${index}-${rowIndex}`))) }));
34
+ if (!line.segments) {
35
+ return _jsx(Text, { bold: true, color: logoColor(theme, line.tone ?? "caption"), children: line.text ?? "" });
36
+ }
37
+ return (_jsx(Box, { children: line.segments.map((segment, index) => (_jsx(React.Fragment, { children: _jsx(Text, { bold: true, color: logoColor(theme, segment.tone), children: segment.text }) }, `${index}-${segment.text}`))) }));
106
38
  }
107
39
  function CompactLogo() {
108
40
  const theme = useTheme();
109
- const colors = logoColors(theme);
110
- return (_jsx(Box, { children: COMPACT_LOGO.map((letter, index) => (_jsx(Text, { bold: true, color: colors[index], children: letter }, `${letter}-${index}`))) }));
41
+ const line = BUBBLE_COMPACT_WORDMARK[0];
42
+ if (!line?.segments) {
43
+ return _jsx(Text, { bold: true, color: theme.warning, children: bubbleWordmarkLineText(line ?? { text: "" }) });
44
+ }
45
+ return (_jsx(Box, { children: line.segments.map((segment, index) => (_jsx(Text, { bold: true, color: logoColor(theme, segment.tone), children: segment.text }, `${segment.text}-${index}`))) }));
46
+ }
47
+ function logoColor(theme, tone) {
48
+ switch (tone) {
49
+ case "brand": return theme.warning;
50
+ case "ink": return theme.userMessageText;
51
+ case "stone": return theme.muted;
52
+ case "soft": return theme.dim;
53
+ case "caption": return theme.muted;
54
+ }
111
55
  }
112
56
  function StatusItem({ label, count, total, ok, }) {
113
57
  const theme = useTheme();
@@ -0,0 +1,54 @@
1
+ /** @jsxImportSource @opentui/react */
2
+ import React from "react";
3
+ import { type Agent } from "../agent.js";
4
+ import type { CliArgs } from "../cli.js";
5
+ import type { SessionManager } from "../session.js";
6
+ import type { PlanDecision, Provider } from "../types.js";
7
+ import { type ResolvedTheme, type ThemeMode } from "./theme.js";
8
+ import { ProviderRegistry } from "../provider-registry.js";
9
+ import { SkillRegistry } from "../skills/registry.js";
10
+ import type { ApprovalDecision, ApprovalRequest } from "../approval/types.js";
11
+ import type { BashAllowlist } from "../approval/session-cache.js";
12
+ import type { SettingsManager } from "../permissions/settings.js";
13
+ import type { McpManager } from "../mcp/manager.js";
14
+ import type { LspService } from "../lsp/index.js";
15
+ import type { QuestionController } from "../question/index.js";
16
+ import type { MemoryScope } from "../memory/index.js";
17
+ export interface PlanHandlerRef {
18
+ current?: (plan: string) => Promise<PlanDecision>;
19
+ }
20
+ export interface ApprovalHandlerRef {
21
+ current?: (req: ApprovalRequest) => Promise<ApprovalDecision>;
22
+ }
23
+ interface AppProps {
24
+ agent: Agent;
25
+ args: CliArgs;
26
+ sessionManager?: SessionManager;
27
+ createProvider?: (providerId: string, apiKey: string, baseURL: string) => Provider;
28
+ registry?: ProviderRegistry;
29
+ skillRegistry?: SkillRegistry;
30
+ planHandlerRef?: PlanHandlerRef;
31
+ approvalHandlerRef?: ApprovalHandlerRef;
32
+ questionController?: QuestionController;
33
+ bashAllowlist?: BashAllowlist;
34
+ settingsManager?: SettingsManager;
35
+ lspService?: LspService;
36
+ mcpManager?: McpManager;
37
+ themeMode?: ThemeMode;
38
+ themeOverrides?: Record<string, string>;
39
+ detectedTheme?: ResolvedTheme;
40
+ onThemeModeChange?: (mode: ThemeMode) => void;
41
+ flushMemory?: () => Promise<void>;
42
+ runMemoryCompaction?: () => Promise<string>;
43
+ runMemorySummary?: (scope?: MemoryScope) => Promise<string>;
44
+ runMemoryRefresh?: (scope?: MemoryScope) => Promise<string>;
45
+ /** Whether the bypassPermissions mode is reachable via Shift+Tab cycling. */
46
+ bypassEnabled?: boolean;
47
+ onExit?: (summary: ExitSummary) => void;
48
+ }
49
+ export interface ExitSummary {
50
+ /** Wall-clock duration of the session, in milliseconds. */
51
+ wallMs: number;
52
+ }
53
+ export declare function App({ agent, args, sessionManager, createProvider, registry, skillRegistry, planHandlerRef, approvalHandlerRef, questionController, bashAllowlist, settingsManager, lspService, mcpManager, themeMode: initialThemeMode, themeOverrides, detectedTheme, onThemeModeChange, flushMemory, runMemoryCompaction, runMemorySummary, runMemoryRefresh, bypassEnabled, onExit }: AppProps): React.ReactNode;
54
+ export {};