@bubblebrain-ai/bubble 0.0.24 → 0.0.26

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 (171) hide show
  1. package/README.md +5 -3
  2. package/dist/agent.js +1 -1
  3. package/dist/clipboard.d.ts +14 -0
  4. package/dist/clipboard.js +132 -0
  5. package/dist/config.d.ts +3 -0
  6. package/dist/config.js +22 -6
  7. package/dist/goal/format.js +34 -4
  8. package/dist/goal/store.d.ts +3 -0
  9. package/dist/goal/store.js +14 -1
  10. package/dist/goal/usage.d.ts +2 -0
  11. package/dist/goal/usage.js +3 -0
  12. package/dist/main.js +23 -42
  13. package/dist/model-catalog.d.ts +3 -1
  14. package/dist/model-catalog.js +17 -28
  15. package/dist/prompt/compose.js +1 -1
  16. package/dist/provider-anthropic.d.ts +4 -0
  17. package/dist/provider-anthropic.js +31 -0
  18. package/dist/provider-ark-responses.d.ts +17 -0
  19. package/dist/provider-ark-responses.js +462 -0
  20. package/dist/provider-transform.js +7 -0
  21. package/dist/provider.d.ts +7 -0
  22. package/dist/provider.js +170 -27
  23. package/dist/slash-commands/commands.js +22 -0
  24. package/dist/tools/todo.js +22 -38
  25. package/dist/tui/detect-theme.d.ts +1 -0
  26. package/dist/tui/detect-theme.js +23 -0
  27. package/dist/tui/image-display.d.ts +13 -0
  28. package/dist/tui/image-display.js +49 -0
  29. package/dist/tui/input-history.d.ts +37 -6
  30. package/dist/tui/input-history.js +194 -23
  31. package/dist/tui/model-switch.d.ts +42 -0
  32. package/dist/tui/model-switch.js +55 -0
  33. package/dist/tui-ink/app.d.ts +32 -2
  34. package/dist/tui-ink/app.js +1409 -549
  35. package/dist/tui-ink/approval/select.js +10 -0
  36. package/dist/tui-ink/detect-theme.d.ts +1 -2
  37. package/dist/tui-ink/detect-theme.js +1 -87
  38. package/dist/tui-ink/display-history.d.ts +1 -0
  39. package/dist/tui-ink/display-history.js +11 -0
  40. package/dist/tui-ink/feedback-dialog.js +10 -0
  41. package/dist/tui-ink/feishu-setup-picker.js +10 -0
  42. package/dist/tui-ink/footer.d.ts +1 -0
  43. package/dist/tui-ink/footer.js +8 -2
  44. package/dist/tui-ink/input-box.d.ts +71 -9
  45. package/dist/tui-ink/input-box.js +359 -121
  46. package/dist/tui-ink/input-history.d.ts +1 -16
  47. package/dist/tui-ink/input-history.js +1 -79
  48. package/dist/tui-ink/input-queue.d.ts +12 -0
  49. package/dist/tui-ink/input-queue.js +17 -0
  50. package/dist/tui-ink/key-events.d.ts +9 -0
  51. package/dist/tui-ink/key-events.js +8 -0
  52. package/dist/tui-ink/markdown.js +1 -1
  53. package/dist/tui-ink/message-list.d.ts +19 -1
  54. package/dist/tui-ink/message-list.js +111 -32
  55. package/dist/tui-ink/model-picker.d.ts +25 -2
  56. package/dist/tui-ink/model-picker.js +237 -20
  57. package/dist/tui-ink/plan-confirm.js +10 -0
  58. package/dist/tui-ink/question-dialog.js +46 -10
  59. package/dist/tui-ink/run.d.ts +10 -1
  60. package/dist/tui-ink/run.js +27 -42
  61. package/dist/tui-ink/session-picker.js +3 -0
  62. package/dist/tui-ink/submit-dedupe.d.ts +5 -0
  63. package/dist/tui-ink/submit-dedupe.js +25 -0
  64. package/dist/tui-ink/terminal-mouse.d.ts +24 -1
  65. package/dist/tui-ink/terminal-mouse.js +76 -21
  66. package/dist/tui-ink/theme.d.ts +6 -3
  67. package/dist/tui-ink/theme.js +10 -4
  68. package/dist/tui-ink/welcome.d.ts +1 -0
  69. package/dist/tui-ink/welcome.js +34 -27
  70. package/dist/variant/variant-resolver.js +4 -1
  71. package/package.json +1 -5
  72. package/dist/tui/clipboard.d.ts +0 -1
  73. package/dist/tui/clipboard.js +0 -53
  74. package/dist/tui/escape-confirmation.d.ts +0 -15
  75. package/dist/tui/escape-confirmation.js +0 -30
  76. package/dist/tui/global-key-router.d.ts +0 -3
  77. package/dist/tui/global-key-router.js +0 -87
  78. package/dist/tui/markdown-inline.d.ts +0 -22
  79. package/dist/tui/markdown-inline.js +0 -68
  80. package/dist/tui/markdown-theme-rules.d.ts +0 -23
  81. package/dist/tui/markdown-theme-rules.js +0 -164
  82. package/dist/tui/markdown-theme.d.ts +0 -5
  83. package/dist/tui/markdown-theme.js +0 -27
  84. package/dist/tui/opencode-spinner.d.ts +0 -22
  85. package/dist/tui/opencode-spinner.js +0 -216
  86. package/dist/tui/prompt-keybindings.d.ts +0 -42
  87. package/dist/tui/prompt-keybindings.js +0 -35
  88. package/dist/tui/render-signature.d.ts +0 -1
  89. package/dist/tui/render-signature.js +0 -7
  90. package/dist/tui/run.d.ts +0 -67
  91. package/dist/tui/run.js +0 -10166
  92. package/dist/tui/sidebar-mcp.d.ts +0 -31
  93. package/dist/tui/sidebar-mcp.js +0 -62
  94. package/dist/tui/sidebar-state.d.ts +0 -12
  95. package/dist/tui/sidebar-state.js +0 -69
  96. package/dist/tui/streaming-tool-args.d.ts +0 -15
  97. package/dist/tui/streaming-tool-args.js +0 -30
  98. package/dist/tui/tool-renderers/fallback.d.ts +0 -2
  99. package/dist/tui/tool-renderers/fallback.js +0 -75
  100. package/dist/tui/tool-renderers/registry.d.ts +0 -3
  101. package/dist/tui/tool-renderers/registry.js +0 -11
  102. package/dist/tui/tool-renderers/subagent.d.ts +0 -2
  103. package/dist/tui/tool-renderers/subagent.js +0 -135
  104. package/dist/tui/tool-renderers/types.d.ts +0 -36
  105. package/dist/tui/tool-renderers/types.js +0 -1
  106. package/dist/tui/tool-renderers/write-preview.d.ts +0 -12
  107. package/dist/tui/tool-renderers/write-preview.js +0 -32
  108. package/dist/tui/tool-renderers/write.d.ts +0 -6
  109. package/dist/tui/tool-renderers/write.js +0 -88
  110. package/dist/tui/transcript-scroll.d.ts +0 -25
  111. package/dist/tui/transcript-scroll.js +0 -20
  112. package/dist/tui-ink/transcript-viewport-math.d.ts +0 -11
  113. package/dist/tui-ink/transcript-viewport-math.js +0 -17
  114. package/dist/tui-ink/transcript-viewport.d.ts +0 -24
  115. package/dist/tui-ink/transcript-viewport.js +0 -83
  116. package/dist/tui-opentui/app.d.ts +0 -54
  117. package/dist/tui-opentui/app.js +0 -1371
  118. package/dist/tui-opentui/approval/approval-dialog.d.ts +0 -15
  119. package/dist/tui-opentui/approval/approval-dialog.js +0 -155
  120. package/dist/tui-opentui/approval/diff-view.d.ts +0 -9
  121. package/dist/tui-opentui/approval/diff-view.js +0 -43
  122. package/dist/tui-opentui/approval/select.d.ts +0 -37
  123. package/dist/tui-opentui/approval/select.js +0 -91
  124. package/dist/tui-opentui/detect-theme.d.ts +0 -2
  125. package/dist/tui-opentui/detect-theme.js +0 -87
  126. package/dist/tui-opentui/display-history.d.ts +0 -56
  127. package/dist/tui-opentui/display-history.js +0 -130
  128. package/dist/tui-opentui/edit-diff.d.ts +0 -11
  129. package/dist/tui-opentui/edit-diff.js +0 -57
  130. package/dist/tui-opentui/feedback-dialog.d.ts +0 -21
  131. package/dist/tui-opentui/feedback-dialog.js +0 -164
  132. package/dist/tui-opentui/feishu-setup-picker.d.ts +0 -7
  133. package/dist/tui-opentui/feishu-setup-picker.js +0 -272
  134. package/dist/tui-opentui/file-mentions.d.ts +0 -29
  135. package/dist/tui-opentui/file-mentions.js +0 -174
  136. package/dist/tui-opentui/footer.d.ts +0 -26
  137. package/dist/tui-opentui/footer.js +0 -40
  138. package/dist/tui-opentui/image-paste.d.ts +0 -54
  139. package/dist/tui-opentui/image-paste.js +0 -288
  140. package/dist/tui-opentui/input-box.d.ts +0 -32
  141. package/dist/tui-opentui/input-box.js +0 -462
  142. package/dist/tui-opentui/input-history.d.ts +0 -16
  143. package/dist/tui-opentui/input-history.js +0 -79
  144. package/dist/tui-opentui/markdown.d.ts +0 -66
  145. package/dist/tui-opentui/markdown.js +0 -127
  146. package/dist/tui-opentui/message-list.d.ts +0 -31
  147. package/dist/tui-opentui/message-list.js +0 -131
  148. package/dist/tui-opentui/model-picker.d.ts +0 -63
  149. package/dist/tui-opentui/model-picker.js +0 -450
  150. package/dist/tui-opentui/plan-confirm.d.ts +0 -9
  151. package/dist/tui-opentui/plan-confirm.js +0 -124
  152. package/dist/tui-opentui/question-dialog.d.ts +0 -10
  153. package/dist/tui-opentui/question-dialog.js +0 -110
  154. package/dist/tui-opentui/recent-activity.d.ts +0 -8
  155. package/dist/tui-opentui/recent-activity.js +0 -71
  156. package/dist/tui-opentui/run-session-picker.d.ts +0 -10
  157. package/dist/tui-opentui/run-session-picker.js +0 -28
  158. package/dist/tui-opentui/run.d.ts +0 -38
  159. package/dist/tui-opentui/run.js +0 -48
  160. package/dist/tui-opentui/session-picker.d.ts +0 -12
  161. package/dist/tui-opentui/session-picker.js +0 -120
  162. package/dist/tui-opentui/theme.d.ts +0 -89
  163. package/dist/tui-opentui/theme.js +0 -157
  164. package/dist/tui-opentui/todos.d.ts +0 -9
  165. package/dist/tui-opentui/todos.js +0 -45
  166. package/dist/tui-opentui/trace-groups.d.ts +0 -27
  167. package/dist/tui-opentui/trace-groups.js +0 -455
  168. package/dist/tui-opentui/use-terminal-size.d.ts +0 -4
  169. package/dist/tui-opentui/use-terminal-size.js +0 -5
  170. package/dist/tui-opentui/welcome.d.ts +0 -25
  171. package/dist/tui-opentui/welcome.js +0 -77
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo, useState } from "react";
3
3
  import { Box, Text, useInput, useStdout } from "ink";
4
+ import { isKeyReleaseEvent } from "./key-events.js";
4
5
  import { useTheme } from "./theme.js";
5
6
  import { formatRelativeTime } from "./recent-activity.js";
6
7
  import { padVisual, truncateVisual } from "../text-display.js";
@@ -19,6 +20,8 @@ export function SessionPicker({ currentCwd, currentSessions, allSessions, onSele
19
20
  : Math.min(selectedSessionIdx, sessionRowIndices.length - 1);
20
21
  const selectedRowIndex = sessionRowIndices[clampedIdx] ?? -1;
21
22
  useInput((input, key) => {
23
+ if (isKeyReleaseEvent(key))
24
+ return;
22
25
  if (key.escape) {
23
26
  onCancel();
24
27
  return;
@@ -0,0 +1,5 @@
1
+ import type { SubmitPayload } from "./input-box.js";
2
+ export type StartingSubmitDecision = "accept" | "ignore" | "queue";
3
+ export declare function submitPayloadFingerprint(payload: SubmitPayload): string;
4
+ export declare function decideStartingSubmit(activeFingerprint: string | null, payload: SubmitPayload): StartingSubmitDecision;
5
+ export declare function decideStartingSubmitFingerprint(activeFingerprint: string | null, submitFingerprint: string): StartingSubmitDecision;
@@ -0,0 +1,25 @@
1
+ import { createHash } from "node:crypto";
2
+ function hashValue(value) {
3
+ return createHash("sha256").update(value).digest("hex");
4
+ }
5
+ export function submitPayloadFingerprint(payload) {
6
+ return JSON.stringify({
7
+ text: payload.text,
8
+ displayText: payload.displayText ?? "",
9
+ images: payload.images.map((image) => ({
10
+ mediaType: image.mediaType,
11
+ bytes: image.bytes,
12
+ filename: image.filename ?? "",
13
+ sourcePath: image.sourcePath ?? "",
14
+ dataUrlHash: hashValue(image.dataUrl),
15
+ })),
16
+ });
17
+ }
18
+ export function decideStartingSubmit(activeFingerprint, payload) {
19
+ return decideStartingSubmitFingerprint(activeFingerprint, submitPayloadFingerprint(payload));
20
+ }
21
+ export function decideStartingSubmitFingerprint(activeFingerprint, submitFingerprint) {
22
+ if (!activeFingerprint)
23
+ return "accept";
24
+ return activeFingerprint === submitFingerprint ? "ignore" : "queue";
25
+ }
@@ -1,5 +1,28 @@
1
- export declare const MOUSE_REPORTING_DISABLE = "\u001B[?1006l\u001B[?1000l";
1
+ export declare const ALTERNATE_SCROLL_ENABLE = "\u001B[?1007h";
2
+ export declare const ALTERNATE_SCROLL_DISABLE = "\u001B[?1007l";
3
+ export declare const MOUSE_REPORTING_ENABLE = "\u001B[?1000h\u001B[?1006h";
4
+ export declare const MOUSE_REPORTING_DISABLE = "\u001B[?1003l\u001B[?1002l\u001B[?1000l\u001B[?1005l\u001B[?1006l\u001B[?1015l";
5
+ interface TerminalMouseEnv {
6
+ BUBBLE_ENABLE_MOUSE?: string;
7
+ BUBBLE_TUI_MOUSE?: string;
8
+ }
9
+ /**
10
+ * Terminal mouse reporting captures drag events before the host terminal can
11
+ * create a native text selection. Keep it opt-in until Bubble owns a full
12
+ * renderer-level selection/copy pipeline.
13
+ */
14
+ export declare function isTerminalMouseReportingEnabled(env?: TerminalMouseEnv): boolean;
2
15
  export type MouseWheelDirection = "up" | "down";
16
+ export interface TerminalMouseInput {
17
+ strippedInput: string;
18
+ wheelDirections: MouseWheelDirection[];
19
+ hasMouse: boolean;
20
+ }
21
+ export declare function sanitizeTerminalMouseInput(input: string): TerminalMouseInput;
22
+ export declare function transcriptScrollLinesFromMouseInput(mouseInput: TerminalMouseInput, options: {
23
+ overlayActive: boolean;
24
+ }): number[];
3
25
  export declare function stripTerminalMouseSequences(input: string): string;
4
26
  export declare function hasTerminalMouseSequence(input: string): boolean;
5
27
  export declare function parseTerminalMouseWheel(input: string): MouseWheelDirection[];
28
+ export {};
@@ -1,27 +1,82 @@
1
- // Bubble does NOT enable mouse reporting plain drag-select and copy keep
2
- // their native terminal behavior. This disable sequence is written
3
- // defensively on teardown in case a previous crash left reporting on.
4
- export const MOUSE_REPORTING_DISABLE = "\x1b[?1006l\x1b[?1000l";
5
- const SGR_MOUSE_SEQUENCE_RE = /\x1b?\[?<\d+;\d+;\d+[mM]/g;
6
- const SGR_MOUSE_WHEEL_RE = /\x1b?\[?<(\d+);\d+;\d+([mM])/g;
1
+ // DECSET 1007 makes terminals translate wheel gestures into Up/Down arrows
2
+ // while the alternate screen is active. Keep it disabled in Ink; otherwise
3
+ // terminals without kitty event metadata make wheel and keyboard arrows
4
+ // indistinguishable.
5
+ export const ALTERNATE_SCROLL_ENABLE = "\x1b[?1007h";
6
+ export const ALTERNATE_SCROLL_DISABLE = "\x1b[?1007l";
7
+ // Use normal mouse tracking + SGR encoding so wheel events reach the app as
8
+ // mouse reports instead of being translated into keyboard arrows.
9
+ export const MOUSE_REPORTING_ENABLE = "\x1b[?1000h\x1b[?1006h";
10
+ // Disable every common tracking mode defensively in case a crash or another
11
+ // renderer left the terminal in a reporting state.
12
+ export const MOUSE_REPORTING_DISABLE = "\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?1005l\x1b[?1006l\x1b[?1015l";
13
+ function envValueEnabled(value) {
14
+ if (!value)
15
+ return false;
16
+ return ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
17
+ }
18
+ /**
19
+ * Terminal mouse reporting captures drag events before the host terminal can
20
+ * create a native text selection. Keep it opt-in until Bubble owns a full
21
+ * renderer-level selection/copy pipeline.
22
+ */
23
+ export function isTerminalMouseReportingEnabled(env = process.env) {
24
+ return envValueEnabled(env.BUBBLE_ENABLE_MOUSE) || envValueEnabled(env.BUBBLE_TUI_MOUSE);
25
+ }
26
+ const ESCAPED_MOUSE_SEQUENCE_RE = /\x1b(?:\[<(\d+);\d+;\d+([mM])|\[M([\s\S])[\s\S]{2})/g;
27
+ const RAW_SGR_MOUSE_SEQUENCE_RE = /\[?<(\d+);\d+;\d+([mM])/g;
28
+ const RAW_SGR_MOUSE_INPUT_RE = /^(?:\[?<\d+;\d+;\d+[mM])+$/;
29
+ function wheelDirectionFromButtonCode(code) {
30
+ if ((code & 64) !== 64)
31
+ return undefined;
32
+ const wheelButton = code & 0b11;
33
+ if (wheelButton === 0)
34
+ return "up";
35
+ if (wheelButton === 1)
36
+ return "down";
37
+ return undefined;
38
+ }
39
+ function collectWheelDirection(directions, code, final) {
40
+ if (final !== undefined && final !== "M")
41
+ return;
42
+ const direction = wheelDirectionFromButtonCode(code);
43
+ if (direction)
44
+ directions.push(direction);
45
+ }
46
+ export function sanitizeTerminalMouseInput(input) {
47
+ const wheelDirections = [];
48
+ let hasMouse = false;
49
+ ESCAPED_MOUSE_SEQUENCE_RE.lastIndex = 0;
50
+ let strippedInput = input.replace(ESCAPED_MOUSE_SEQUENCE_RE, (_sequence, sgrCode, sgrFinal, x10Button) => {
51
+ hasMouse = true;
52
+ const code = sgrCode !== undefined
53
+ ? Number(sgrCode)
54
+ : (x10Button?.charCodeAt(0) ?? 32) - 32;
55
+ collectWheelDirection(wheelDirections, code, sgrFinal);
56
+ return "";
57
+ });
58
+ if (RAW_SGR_MOUSE_INPUT_RE.test(strippedInput)) {
59
+ hasMouse = true;
60
+ RAW_SGR_MOUSE_SEQUENCE_RE.lastIndex = 0;
61
+ strippedInput = strippedInput.replace(RAW_SGR_MOUSE_SEQUENCE_RE, (_sequence, sgrCode, sgrFinal) => {
62
+ collectWheelDirection(wheelDirections, Number(sgrCode), sgrFinal);
63
+ return "";
64
+ });
65
+ }
66
+ return { strippedInput, wheelDirections, hasMouse };
67
+ }
68
+ export function transcriptScrollLinesFromMouseInput(mouseInput, options) {
69
+ if (options.overlayActive)
70
+ return [];
71
+ return mouseInput.wheelDirections.map((direction) => direction === "up" ? -1 : 1);
72
+ }
7
73
  export function stripTerminalMouseSequences(input) {
8
- return input.replace(SGR_MOUSE_SEQUENCE_RE, "");
74
+ return sanitizeTerminalMouseInput(input).strippedInput;
9
75
  }
10
76
  export function hasTerminalMouseSequence(input) {
11
- SGR_MOUSE_SEQUENCE_RE.lastIndex = 0;
12
- return SGR_MOUSE_SEQUENCE_RE.test(input);
77
+ ESCAPED_MOUSE_SEQUENCE_RE.lastIndex = 0;
78
+ return ESCAPED_MOUSE_SEQUENCE_RE.test(input) || RAW_SGR_MOUSE_INPUT_RE.test(input);
13
79
  }
14
80
  export function parseTerminalMouseWheel(input) {
15
- const directions = [];
16
- SGR_MOUSE_WHEEL_RE.lastIndex = 0;
17
- for (const match of input.matchAll(SGR_MOUSE_WHEEL_RE)) {
18
- if (match[2] !== "M")
19
- continue;
20
- const code = Number(match[1]);
21
- if (code === 64)
22
- directions.push("up");
23
- if (code === 65)
24
- directions.push("down");
25
- }
26
- return directions;
81
+ return sanitizeTerminalMouseInput(input).wheelDirections;
27
82
  }
@@ -15,9 +15,12 @@ export interface Theme {
15
15
  error: string;
16
16
  warning: string;
17
17
  success: string;
18
+ background: string;
18
19
  accent: string;
19
20
  border: string;
20
21
  borderActive: string;
22
+ backgroundPanel: string;
23
+ backgroundElement: string;
21
24
  inputBorder: string;
22
25
  inputBorderDisabled: string;
23
26
  inputBg: string;
@@ -49,9 +52,9 @@ export interface Theme {
49
52
  }
50
53
  export declare const darkTheme: Theme;
51
54
  /**
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.
55
+ * Light palette tuned for paper-neutral surfaces, blue focus/user rails, warm
56
+ * command accent, and semantic tool colors with readable contrast on a light
57
+ * terminal background.
55
58
  */
56
59
  export declare const lightTheme: Theme;
57
60
  export declare const ThemeProvider: import("react").Provider<Theme>;
@@ -14,9 +14,12 @@ export const darkTheme = {
14
14
  error: "red",
15
15
  warning: "yellow",
16
16
  success: "green",
17
+ background: "#0A0A0A",
17
18
  accent: "cyan",
18
19
  border: "gray",
19
20
  borderActive: "cyan",
21
+ backgroundPanel: "#141414",
22
+ backgroundElement: "#1c1c24",
20
23
  inputBorder: "#8A7FC6",
21
24
  inputBorderDisabled: "#4a4754",
22
25
  inputBg: "#1c1c24",
@@ -47,9 +50,9 @@ export const darkTheme = {
47
50
  diffRemoveFg: "#F48771",
48
51
  };
49
52
  /**
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.
53
+ * Light palette tuned for paper-neutral surfaces, blue focus/user rails, warm
54
+ * command accent, and semantic tool colors with readable contrast on a light
55
+ * terminal background.
53
56
  */
54
57
  export const lightTheme = {
55
58
  user: "#356FD2",
@@ -57,13 +60,16 @@ export const lightTheme = {
57
60
  error: "#B62633",
58
61
  warning: "#8B4A00",
59
62
  success: "#2F7D4A",
63
+ background: "#FCFCFA",
60
64
  accent: "#8B4A00",
61
65
  border: "#B9BDB8",
62
66
  borderActive: "#356FD2",
67
+ backgroundPanel: "#F6F6F3",
68
+ backgroundElement: "#ECEDEA",
63
69
  inputBorder: "#356FD2",
64
70
  inputBorderDisabled: "#D7DAD4",
65
71
  inputBg: "#F1F3F0",
66
- inputBgDisabled: "#E6E8E3",
72
+ inputBgDisabled: "#F6F6F3",
67
73
  inputText: "#171717",
68
74
  inputPlaceholder: "#6F7377",
69
75
  muted: "#6F7377",
@@ -17,4 +17,5 @@ interface WelcomeVisibilityInput {
17
17
  }
18
18
  export declare function shouldShowWelcomeBanner({ startedWithVisibleHistory, }: WelcomeVisibilityInput): boolean;
19
19
  export declare function WelcomeBanner({ terminalColumns, tips, updateNotice, cwd, providerId, modelLabel, thinkingLabel, }: WelcomeBannerProps): import("react/jsx-runtime").JSX.Element;
20
+ export declare function formatModelLine({ providerId, modelLabel, thinkingLabel, tips, }: Pick<WelcomeBannerProps, "providerId" | "modelLabel" | "thinkingLabel" | "tips">): string;
20
21
  export {};
@@ -1,11 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
3
2
  import { Box, Text } from "ink";
4
3
  import { createRequire } from "node:module";
5
4
  import { useTheme } from "./theme.js";
6
- import { bubbleWordmarkForWidth, } from "../tui/wordmark.js";
7
5
  const require = createRequire(import.meta.url);
8
6
  const PACKAGE_VERSION = readPackageVersion();
7
+ const COMPACT_LOGO = [
8
+ " ▄ ▄ ",
9
+ "██████",
10
+ "█ ██ █",
11
+ "██████",
12
+ " ▀ ▀ ",
13
+ ];
9
14
  export function shouldShowWelcomeBanner({ startedWithVisibleHistory, }) {
10
15
  // Keep banner visibility tied to the initial history, not transient overlays,
11
16
  // so opening and closing a picker does not move it in the transcript.
@@ -15,33 +20,35 @@ export function shouldShowWelcomeBanner({ startedWithVisibleHistory, }) {
15
20
  }
16
21
  export function WelcomeBanner({ terminalColumns, tips, updateNotice, cwd, providerId, modelLabel, thinkingLabel, }) {
17
22
  const theme = useTheme();
18
- const effectiveWidth = Math.max(20, Math.min(terminalColumns - 2, 118));
19
- // Adaptive sizing: large pixel logo on wide terminals, standard, then the
20
- // single-line compact mark — same thresholds as the OpenTUI home screen.
21
- const logoLines = bubbleWordmarkForWidth(effectiveWidth);
22
- const actionableTips = tips
23
- .filter((item) => !item.startsWith("Ready with") && item.trim().length > 0)
24
- .slice(0, 2);
25
- const tip = actionableTips.length > 0
26
- ? actionableTips.join(" · ")
27
- : "Type / for commands and @ to reference files";
28
- return (_jsxs(Box, { width: effectiveWidth, flexDirection: "column", alignItems: "center", marginBottom: 1, children: [_jsx(Box, { flexDirection: "column", alignItems: "center", children: logoLines.map((line, rowIndex) => (_jsx(LogoRow, { line: line }, `logo-row-${rowIndex}`))) }), _jsx(Box, { marginTop: 2, children: _jsx(Text, { bold: true, color: theme.muted, children: PACKAGE_VERSION }) }), updateNotice && (_jsx(Box, { children: _jsx(Text, { color: theme.accent, children: updateNotice }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, color: theme.userMessageText, children: "TIP: " }), _jsx(Text, { bold: true, color: theme.userMessageText, children: tip })] }), (cwd || modelLabel) && (_jsxs(Box, { marginTop: 1, children: [cwd && _jsx(Text, { color: theme.muted, children: cwd }), cwd && (providerId || modelLabel) && _jsx(Text, { children: " " }), providerId && _jsxs(Text, { color: theme.muted, dimColor: true, children: [providerId, " \u00B7 "] }), modelLabel && (_jsxs(Text, { bold: true, color: theme.toolName, children: [modelLabel, thinkingLabel ? ` ${thinkingLabel}` : ""] }))] }))] }));
23
+ const effectiveWidth = Math.max(24, Math.min(terminalColumns - 2, 96));
24
+ const modelLine = formatModelLine({
25
+ providerId,
26
+ modelLabel,
27
+ thinkingLabel,
28
+ tips,
29
+ });
30
+ return (_jsxs(Box, { width: effectiveWidth, flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexDirection: "column", marginRight: 2, flexShrink: 0, children: COMPACT_LOGO.map((line, rowIndex) => (_jsx(Text, { color: theme.warning, bold: true, children: line }, `logo-row-${rowIndex}`))) }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.inputText, children: "Bubble" }), _jsxs(Text, { color: theme.muted, children: [" ", PACKAGE_VERSION] })] }), modelLine && (_jsx(Text, { color: theme.muted, children: modelLine })), cwd && (_jsx(Text, { color: theme.muted, children: cwd }))] })] }), updateNotice && (_jsx(Box, { children: _jsx(Text, { color: theme.accent, children: updateNotice }) }))] }));
29
31
  }
30
- function LogoRow({ line }) {
31
- const theme = useTheme();
32
- if (!line.segments) {
33
- return _jsx(Text, { bold: true, color: logoColor(theme, line.tone ?? "caption"), children: line.text ?? "" });
34
- }
35
- 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}`))) }));
36
- }
37
- function logoColor(theme, tone) {
38
- switch (tone) {
39
- case "brand": return theme.warning;
40
- case "ink": return theme.userMessageText;
41
- case "stone": return theme.muted;
42
- case "soft": return theme.dim;
43
- case "caption": return theme.muted;
32
+ export function formatModelLine({ providerId, modelLabel, thinkingLabel, tips, }) {
33
+ const parts = [];
34
+ // MiniMax thinking is a binary toggle (adaptive thinking), so label it
35
+ // "thinking mode" rather than "<level> effort"; and its provider id
36
+ // ("minimax-anthropic") is redundant with the model name, so omit it.
37
+ const isMiniMax = (providerId || "").toLowerCase().includes("minimax");
38
+ if (modelLabel) {
39
+ if (thinkingLabel && isMiniMax)
40
+ parts.push(modelLabel, "thinking mode");
41
+ else if (thinkingLabel)
42
+ parts.push(`${modelLabel} with ${thinkingLabel} effort`);
43
+ else
44
+ parts.push(modelLabel);
44
45
  }
46
+ const readyTip = tips.find((item) => item.startsWith("Ready with"));
47
+ if (!modelLabel && readyTip)
48
+ parts.push(readyTip.replace(/^Ready with\s+/, ""));
49
+ if (providerId && !isMiniMax)
50
+ parts.push(providerId);
51
+ return parts.join(" · ");
45
52
  }
46
53
  function readPackageVersion() {
47
54
  try {
@@ -1,10 +1,13 @@
1
- import { getBuiltinModel } from "../model-catalog.js";
1
+ import { getBuiltinModel, getModelDefaultReasoningLevel } from "../model-catalog.js";
2
2
  import { clampThinkingLevel } from "./thinking-level.js";
3
3
  export function getAvailableThinkingLevels(providerId, modelId) {
4
4
  return getBuiltinModel(providerId, modelId)?.reasoningLevels ?? ["off"];
5
5
  }
6
6
  export function getDefaultThinkingLevel(providerId, modelId) {
7
7
  const levels = getAvailableThinkingLevels(providerId, modelId);
8
+ const explicitDefault = getModelDefaultReasoningLevel(providerId, modelId);
9
+ if (explicitDefault && levels.includes(explicitDefault))
10
+ return explicitDefault;
8
11
  return levels.includes("medium") ? "medium" : levels[0] || "off";
9
12
  }
10
13
  export function normalizeThinkingLevel(level, supportedLevels) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bubblebrain-ai/bubble",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "description": "A terminal coding agent",
5
5
  "type": "module",
6
6
  "engines": {
@@ -25,9 +25,6 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@larksuiteoapi/node-sdk": "^1.65.0",
28
- "@opentui/core": "^0.2.15",
29
- "@opentui/react": "^0.2.15",
30
- "@opentui/solid": "^0.2.15",
31
28
  "@types/better-sqlite3": "^7.6.13",
32
29
  "@types/react": "^19.2.14",
33
30
  "@vue/language-server": "^3.2.7",
@@ -41,7 +38,6 @@
41
38
  "qrcode-terminal": "^0.12.0",
42
39
  "react": "^19.2.6",
43
40
  "shiki": "^4.0.2",
44
- "solid-js": "^1.9.12",
45
41
  "string-width": "^8.2.1",
46
42
  "typescript-language-server": "^5.1.3",
47
43
  "undici": "^6.26.0",
@@ -1 +0,0 @@
1
- export declare function copyTextToClipboard(text: string): Promise<void>;
@@ -1,53 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- export async function copyTextToClipboard(text) {
3
- if (process.platform === "darwin") {
4
- await writeToProcess("pbcopy", [], text);
5
- return;
6
- }
7
- if (process.platform === "win32") {
8
- await writeToProcess("powershell", [
9
- "-NoProfile",
10
- "-Command",
11
- "Set-Clipboard -Value ([Console]::In.ReadToEnd())",
12
- ], text);
13
- return;
14
- }
15
- const candidates = [
16
- ["wl-copy", []],
17
- ["xclip", ["-selection", "clipboard"]],
18
- ["xsel", ["--clipboard", "--input"]],
19
- ];
20
- let lastError;
21
- for (const [command, args] of candidates) {
22
- try {
23
- await writeToProcess(command, args, text);
24
- return;
25
- }
26
- catch (error) {
27
- lastError = error;
28
- }
29
- }
30
- throw lastError instanceof Error ? lastError : new Error("No clipboard command available");
31
- }
32
- function writeToProcess(command, args, input) {
33
- return new Promise((resolve, reject) => {
34
- const child = spawn(command, args, {
35
- stdio: ["pipe", "ignore", "pipe"],
36
- windowsHide: true,
37
- });
38
- let stderr = "";
39
- child.stderr.setEncoding("utf8");
40
- child.stderr.on("data", (chunk) => {
41
- stderr += chunk;
42
- });
43
- child.on("error", reject);
44
- child.on("close", (code) => {
45
- if (code === 0) {
46
- resolve();
47
- return;
48
- }
49
- reject(new Error(stderr.trim() || `${command} exited with code ${code}`));
50
- });
51
- child.stdin.end(input);
52
- });
53
- }
@@ -1,15 +0,0 @@
1
- export type EscapeConfirmationDecision = {
2
- action: "arm";
3
- expiresAt: number;
4
- } | {
5
- action: "confirm";
6
- };
7
- export declare class EscapeConfirmationGate {
8
- private readonly windowMs;
9
- private armedRunId;
10
- private deadline;
11
- constructor(windowMs: number);
12
- press(runId: number, now?: number): EscapeConfirmationDecision;
13
- isArmed(runId: number, now?: number): boolean;
14
- clear(): void;
15
- }
@@ -1,30 +0,0 @@
1
- export class EscapeConfirmationGate {
2
- windowMs;
3
- armedRunId;
4
- deadline = 0;
5
- constructor(windowMs) {
6
- this.windowMs = windowMs;
7
- }
8
- press(runId, now = Date.now()) {
9
- if (this.armedRunId === runId && now <= this.deadline) {
10
- this.clear();
11
- return { action: "confirm" };
12
- }
13
- this.armedRunId = runId;
14
- this.deadline = now + this.windowMs;
15
- return { action: "arm", expiresAt: this.deadline };
16
- }
17
- isArmed(runId, now = Date.now()) {
18
- if (this.armedRunId !== runId)
19
- return false;
20
- if (now > this.deadline) {
21
- this.clear();
22
- return false;
23
- }
24
- return true;
25
- }
26
- clear() {
27
- this.armedRunId = undefined;
28
- this.deadline = 0;
29
- }
30
- }
@@ -1,3 +0,0 @@
1
- export declare function normalizeKeyName(name?: string): string;
2
- export declare function keyNameFromSequence(sequence?: string): string;
3
- export declare function keyNameFromEvent(event: any): string;
@@ -1,87 +0,0 @@
1
- import { isEscapeSequence } from "./prompt-keybindings.js";
2
- export function normalizeKeyName(name) {
3
- const rawName = String(name || "").toLowerCase();
4
- if (["arrowleft", "left_arrow", "leftarrow", "cursorleft", "left"].includes(rawName))
5
- return "left";
6
- if (["arrowright", "right_arrow", "rightarrow", "cursorright", "right"].includes(rawName))
7
- return "right";
8
- if (["arrowup", "up_arrow", "uparrow", "cursorup", "up"].includes(rawName))
9
- return "up";
10
- if (["arrowdown", "down_arrow", "downarrow", "cursordown", "down"].includes(rawName))
11
- return "down";
12
- if (rawName === "return" || rawName === "enter")
13
- return "enter";
14
- if (rawName === "esc" || rawName === "escape")
15
- return "escape";
16
- if (rawName === "tab")
17
- return "tab";
18
- return rawName;
19
- }
20
- export function keyNameFromSequence(sequence) {
21
- if (!sequence)
22
- return "";
23
- const kittyName = keyNameFromKittySequence(sequence);
24
- if (kittyName)
25
- return kittyName;
26
- if (sequence === "\x1b[D" || /^\x1b\[[0-9;]*D$/.test(sequence))
27
- return "left";
28
- if (sequence === "\x1b[C" || /^\x1b\[[0-9;]*C$/.test(sequence))
29
- return "right";
30
- if (sequence === "\x1b[A" || /^\x1b\[[0-9;]*A$/.test(sequence))
31
- return "up";
32
- if (sequence === "\x1b[B" || /^\x1b\[[0-9;]*B$/.test(sequence))
33
- return "down";
34
- if (sequence === "\x1bOD")
35
- return "left";
36
- if (sequence === "\x1bOC")
37
- return "right";
38
- if (sequence === "\x1bOA")
39
- return "up";
40
- if (sequence === "\x1bOB")
41
- return "down";
42
- if (sequence === "\t")
43
- return "tab";
44
- if (sequence === "\r" || sequence === "\n")
45
- return "enter";
46
- if (isEscapeSequence(sequence))
47
- return "escape";
48
- return "";
49
- }
50
- function keyNameFromKittySequence(sequence) {
51
- const kittyMatch = /^\x1b\[(\d+)(?:;[1-9]\d*(?::[1-3])?)?u$/.exec(sequence);
52
- const kittyCode = kittyMatch?.[1] ? Number(kittyMatch[1]) : NaN;
53
- if (!Number.isNaN(kittyCode))
54
- return keyNameFromKittyCode(kittyCode);
55
- const modifyOtherKeysMatch = /^\x1b\[27;[1-9]\d*(?::[1-3])?;(\d+)~$/.exec(sequence);
56
- const modifyOtherKeysCode = modifyOtherKeysMatch?.[1] ? Number(modifyOtherKeysMatch[1]) : NaN;
57
- if (!Number.isNaN(modifyOtherKeysCode))
58
- return keyNameFromKittyCode(modifyOtherKeysCode);
59
- return "";
60
- }
61
- function keyNameFromKittyCode(code) {
62
- switch (code) {
63
- case 27:
64
- case 57344:
65
- return "escape";
66
- case 9:
67
- case 57346:
68
- return "tab";
69
- case 13:
70
- case 57345:
71
- return "enter";
72
- case 57350:
73
- return "left";
74
- case 57351:
75
- return "right";
76
- case 57352:
77
- return "up";
78
- case 57353:
79
- return "down";
80
- default:
81
- return "";
82
- }
83
- }
84
- export function keyNameFromEvent(event) {
85
- const rawName = normalizeKeyName(event?.name || event?.key || event?.input);
86
- return rawName || keyNameFromSequence(event?.raw || event?.sequence);
87
- }
@@ -1,22 +0,0 @@
1
- export interface MarkdownInlineSegment {
2
- text: string;
3
- color?: "text" | "textMuted" | "success" | "warning" | "secondary";
4
- bold?: boolean;
5
- italic?: boolean;
6
- dim?: boolean;
7
- }
8
- type InlineToken = {
9
- type?: string;
10
- text?: string;
11
- raw?: string;
12
- href?: string;
13
- tokens?: InlineToken[];
14
- };
15
- interface InlineStyle {
16
- bold?: boolean;
17
- italic?: boolean;
18
- dim?: boolean;
19
- color?: MarkdownInlineSegment["color"];
20
- }
21
- export declare function markdownInlineSegments(tokens: InlineToken[] | undefined, fallback?: string, style?: InlineStyle): MarkdownInlineSegment[];
22
- export {};