@bubblebrain-ai/bubble 0.0.20 → 0.0.21

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 (49) hide show
  1. package/dist/agent.d.ts +1 -0
  2. package/dist/agent.js +5 -1
  3. package/dist/checkpoints.d.ts +57 -0
  4. package/dist/checkpoints.js +0 -0
  5. package/dist/feishu/agent-host/run-driver.js +1 -0
  6. package/dist/main.js +54 -13
  7. package/dist/session.d.ts +31 -0
  8. package/dist/session.js +69 -0
  9. package/dist/slash-commands/commands.js +80 -0
  10. package/dist/slash-commands/types.d.ts +4 -0
  11. package/dist/tools/bash.js +4 -0
  12. package/dist/tools/edit.d.ts +2 -1
  13. package/dist/tools/edit.js +2 -1
  14. package/dist/tools/index.d.ts +7 -0
  15. package/dist/tools/index.js +2 -2
  16. package/dist/tools/write.d.ts +2 -1
  17. package/dist/tools/write.js +2 -1
  18. package/dist/tui/image-paste.d.ts +18 -0
  19. package/dist/tui/image-paste.js +60 -0
  20. package/dist/tui/run.js +309 -69
  21. package/dist/tui/trace-groups.d.ts +16 -0
  22. package/dist/tui/trace-groups.js +42 -1
  23. package/dist/tui/transcript-scroll.d.ts +25 -0
  24. package/dist/tui/transcript-scroll.js +20 -0
  25. package/dist/tui-ink/app.d.ts +4 -1
  26. package/dist/tui-ink/app.js +301 -247
  27. package/dist/tui-ink/display-history.d.ts +16 -1
  28. package/dist/tui-ink/display-history.js +50 -21
  29. package/dist/tui-ink/footer.d.ts +6 -12
  30. package/dist/tui-ink/footer.js +10 -29
  31. package/dist/tui-ink/image-paste.d.ts +59 -0
  32. package/dist/tui-ink/image-paste.js +277 -0
  33. package/dist/tui-ink/input-box.d.ts +26 -1
  34. package/dist/tui-ink/input-box.js +171 -41
  35. package/dist/tui-ink/message-list.d.ts +1 -1
  36. package/dist/tui-ink/message-list.js +46 -29
  37. package/dist/tui-ink/run.d.ts +7 -2
  38. package/dist/tui-ink/run.js +73 -23
  39. package/dist/tui-ink/terminal-mouse.d.ts +1 -0
  40. package/dist/tui-ink/terminal-mouse.js +4 -0
  41. package/dist/tui-ink/trace-groups.d.ts +16 -0
  42. package/dist/tui-ink/trace-groups.js +50 -2
  43. package/dist/tui-ink/transcript-viewport-math.d.ts +11 -0
  44. package/dist/tui-ink/transcript-viewport-math.js +17 -0
  45. package/dist/tui-ink/transcript-viewport.d.ts +24 -0
  46. package/dist/tui-ink/transcript-viewport.js +83 -0
  47. package/dist/tui-ink/welcome.d.ts +9 -7
  48. package/dist/tui-ink/welcome.js +7 -33
  49. package/package.json +1 -1
@@ -7,6 +7,10 @@ export interface TraceGroup {
7
7
  count?: number;
8
8
  noun?: string;
9
9
  command?: string;
10
+ /** Model-provided one-line summary of what the command does (bash `description` arg). */
11
+ description?: string;
12
+ /** Original command split into lines, line breaks preserved (execute groups only). */
13
+ commandLines?: string[];
10
14
  items: string[];
11
15
  previewLines: string[];
12
16
  errorLines: string[];
@@ -25,3 +29,15 @@ export declare function buildTraceGroups(toolCalls: DisplayToolCall[], options?:
25
29
  export declare function formatTracePath(value: unknown, homeDir?: string): string;
26
30
  export declare function formatElapsed(startedAt: number | undefined, now?: number): string | null;
27
31
  export declare function traceGroupLabel(group: TraceGroup): string;
32
+ /**
33
+ * An execute command is shown inline in the header only when nothing is lost:
34
+ * no description competing for the slot, a single logical line, and it fits
35
+ * the width budget. Otherwise the full command renders as a wrapped block
36
+ * below the header — commands are never clipped mid-line.
37
+ */
38
+ export declare function shouldInlineExecuteCommand(group: TraceGroup, widthBudget: number): boolean;
39
+ /** Visible command-block lines for compact rendering, capped at `maxLines`. */
40
+ export declare function executeCommandBlock(group: TraceGroup, maxLines: number): {
41
+ lines: string[];
42
+ omitted: number;
43
+ };
@@ -65,12 +65,36 @@ export function formatElapsed(startedAt, now = Date.now()) {
65
65
  return `${minutes}m${remainder.toString().padStart(2, "0")}s`;
66
66
  }
67
67
  export function traceGroupLabel(group) {
68
+ if (group.description)
69
+ return `${group.title} ${group.description}`;
68
70
  if (group.command)
69
71
  return `${group.title} ${group.command}`;
70
72
  if (group.count !== undefined && group.noun)
71
73
  return `${group.title} ${group.count} ${group.noun}`;
72
74
  return group.title;
73
75
  }
76
+ /**
77
+ * An execute command is shown inline in the header only when nothing is lost:
78
+ * no description competing for the slot, a single logical line, and it fits
79
+ * the width budget. Otherwise the full command renders as a wrapped block
80
+ * below the header — commands are never clipped mid-line.
81
+ */
82
+ export function shouldInlineExecuteCommand(group, widthBudget) {
83
+ if (group.kind !== "execute" || !group.command)
84
+ return false;
85
+ if (group.description)
86
+ return false;
87
+ const lines = group.commandLines ?? [];
88
+ if (lines.length > 1)
89
+ return false;
90
+ return group.command.length <= widthBudget;
91
+ }
92
+ /** Visible command-block lines for compact rendering, capped at `maxLines`. */
93
+ export function executeCommandBlock(group, maxLines) {
94
+ const lines = group.commandLines ?? [];
95
+ const shown = lines.slice(0, maxLines);
96
+ return { lines: shown, omitted: Math.max(0, lines.length - shown.length) };
97
+ }
74
98
  function classifyTool(toolCall) {
75
99
  if (toolCall.metadata?.kind === "subagent") {
76
100
  return { kind: "subagent", title: "Subagents", bucketKey: `subagent:${toolCall.id}`, groupable: false };
@@ -244,11 +268,15 @@ function buildSearchGroup(classifier, raw, options, pending, startedAt, hasError
244
268
  function buildExecuteGroup(classifier, tool, options, pending, startedAt, hasError, errorCount) {
245
269
  const lines = resultLines(tool.result).map((line) => formatTracePath(line, options.homeDir));
246
270
  const { shown, omitted } = take(lines, options.maxPreviewLines);
271
+ const rawCommand = String(tool.args.command ?? tool.args.cmd ?? commandFromRawArguments(tool.rawArguments) ?? "");
272
+ const description = String(tool.args.description ?? "").trim() || undefined;
247
273
  return {
248
274
  kind: "execute",
249
275
  title: classifier.title,
250
276
  raw: [tool],
251
- command: normalizeCommand(tool.args.command ?? tool.args.cmd ?? commandFromRawArguments(tool.rawArguments)),
277
+ command: normalizeCommand(rawCommand),
278
+ description,
279
+ commandLines: commandLinesOf(rawCommand),
252
280
  items: [],
253
281
  previewLines: shown,
254
282
  errorLines: [],
@@ -262,7 +290,7 @@ function buildExecuteGroup(classifier, tool, options, pending, startedAt, hasErr
262
290
  function buildMutationGroup(classifier, raw, options, pending, startedAt, hasError, errorCount) {
263
291
  const items = raw
264
292
  .map((tool) => {
265
- const path = formatTracePath(tool.args.path ?? "", options.homeDir);
293
+ const path = formatTracePath(tool.args.path ?? firstMetadataPath(tool) ?? "", options.homeDir);
266
294
  const details = tool.name === "edit" ? getEditDiffDetails(tool) : null;
267
295
  const suffix = details ? ` ${formatCompactEditStats(details.added, details.removed)}` : "";
268
296
  return path ? `${path}${suffix}` : "";
@@ -428,6 +456,19 @@ function displayToolName(name) {
428
456
  return "Tool";
429
457
  return name.charAt(0).toUpperCase() + name.slice(1).replace(/_/g, " ");
430
458
  }
459
+ // Preserves the command's own line structure (heredocs, && chains the model
460
+ // formatted across lines); only trims trailing whitespace and outer blank lines.
461
+ function commandLinesOf(rawCommand) {
462
+ const lines = rawCommand
463
+ .replace(/\r\n/g, "\n")
464
+ .split("\n")
465
+ .map((line) => line.trimEnd());
466
+ while (lines.length > 0 && lines[0].trim() === "")
467
+ lines.shift();
468
+ while (lines.length > 0 && lines[lines.length - 1].trim() === "")
469
+ lines.pop();
470
+ return lines;
471
+ }
431
472
  function toolHeader(tool, homeDir) {
432
473
  const args = tool.args || {};
433
474
  for (const key of ["path", "command", "pattern", "query", "url"]) {
@@ -436,8 +477,15 @@ function toolHeader(tool, homeDir) {
436
477
  return formatTracePath(value, homeDir);
437
478
  }
438
479
  }
480
+ const path = firstMetadataPath(tool);
481
+ if (path)
482
+ return formatTracePath(path, homeDir);
439
483
  return undefined;
440
484
  }
485
+ function firstMetadataPath(tool) {
486
+ const paths = tool.metadata?.paths;
487
+ return Array.isArray(paths) && typeof paths[0] === "string" ? paths[0] : undefined;
488
+ }
441
489
  function formatCompactEditStats(added, removed) {
442
490
  const parts = [];
443
491
  if (added > 0)
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Pure scroll arithmetic for the alt-screen transcript viewport.
3
+ *
4
+ * Mirrors the live OpenTUI scrollbox semantics (src/tui/run.ts
5
+ * transcriptMaxScrollTop / isTranscriptAtBottom): "at bottom" tolerates a
6
+ * one-line slack so sub-line rounding never flips the follow flag while the
7
+ * user sits at the end of the transcript.
8
+ */
9
+ export declare function maxScrollTop(contentHeight: number, viewportHeight: number): number;
10
+ export declare function clampScrollTop(scrollTop: number, contentHeight: number, viewportHeight: number): number;
11
+ export declare function isAtBottom(scrollTop: number, contentHeight: number, viewportHeight: number): boolean;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Pure scroll arithmetic for the alt-screen transcript viewport.
3
+ *
4
+ * Mirrors the live OpenTUI scrollbox semantics (src/tui/run.ts
5
+ * transcriptMaxScrollTop / isTranscriptAtBottom): "at bottom" tolerates a
6
+ * one-line slack so sub-line rounding never flips the follow flag while the
7
+ * user sits at the end of the transcript.
8
+ */
9
+ export function maxScrollTop(contentHeight, viewportHeight) {
10
+ return Math.max(0, contentHeight - viewportHeight);
11
+ }
12
+ export function clampScrollTop(scrollTop, contentHeight, viewportHeight) {
13
+ return Math.max(0, Math.min(scrollTop, maxScrollTop(contentHeight, viewportHeight)));
14
+ }
15
+ export function isAtBottom(scrollTop, contentHeight, viewportHeight) {
16
+ return scrollTop >= maxScrollTop(contentHeight, viewportHeight) - 1;
17
+ }
@@ -0,0 +1,24 @@
1
+ import React from "react";
2
+ export interface TranscriptViewportHandle {
3
+ /**
4
+ * Re-engage bottom-follow. Sets the pending force flag from
5
+ * transcript-scroll.ts, so the snap survives streaming renders that land
6
+ * between the request and the next measured layout.
7
+ */
8
+ forceScrollToBottom(): void;
9
+ /** Scroll by N lines (negative = up). A user gesture: cancels a pending force. */
10
+ scrollBy(lines: number): void;
11
+ scrollPage(direction: "up" | "down"): void;
12
+ }
13
+ interface TranscriptViewportProps {
14
+ children: React.ReactNode;
15
+ }
16
+ /**
17
+ * Height-clamped scrolling viewport for the alt-screen transcript. The outer
18
+ * box clips; the inner box carries the full transcript and slides via a
19
+ * negative top margin. Follow policy is the shared transcript-scroll.ts:
20
+ * stay snapped while at the bottom, hold position while reading history,
21
+ * snap back on send/approval via forceScrollToBottom().
22
+ */
23
+ export declare const TranscriptViewport: React.ForwardRefExoticComponent<TranscriptViewportProps & React.RefAttributes<TranscriptViewportHandle>>;
24
+ export {};
@@ -0,0 +1,83 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
3
+ import { Box, measureElement } from "ink";
4
+ import { resolveTranscriptScroll } from "../tui/transcript-scroll.js";
5
+ import { clampScrollTop, isAtBottom, maxScrollTop } from "./transcript-viewport-math.js";
6
+ /**
7
+ * Height-clamped scrolling viewport for the alt-screen transcript. The outer
8
+ * box clips; the inner box carries the full transcript and slides via a
9
+ * negative top margin. Follow policy is the shared transcript-scroll.ts:
10
+ * stay snapped while at the bottom, hold position while reading history,
11
+ * snap back on send/approval via forceScrollToBottom().
12
+ */
13
+ export const TranscriptViewport = forwardRef(function TranscriptViewport({ children }, ref) {
14
+ const viewportRef = useRef(null);
15
+ const contentRef = useRef(null);
16
+ const [scrollTop, setScrollTop] = useState(0);
17
+ const scrollTopRef = useRef(0);
18
+ const followingRef = useRef(true);
19
+ const forcePendingRef = useRef(false);
20
+ // forceScrollToBottom must work even when no other state changes in the
21
+ // same tick; bumping this guarantees a commit so the measuring effect runs.
22
+ const [, setScrollEpoch] = useState(0);
23
+ const applyScrollTop = (next) => {
24
+ scrollTopRef.current = next;
25
+ setScrollTop(next);
26
+ };
27
+ // measureElement returns the Yoga-computed size, valid only after layout —
28
+ // callable from effects and input handlers, never during render.
29
+ const measureHeights = () => ({
30
+ viewportHeight: viewportRef.current ? measureElement(viewportRef.current).height : 0,
31
+ contentHeight: contentRef.current ? measureElement(contentRef.current).height : 0,
32
+ });
33
+ // Content and viewport heights change with streaming text, tool expansion,
34
+ // resize, and bottom-stack visibility — no dependency list covers them
35
+ // all, so re-resolve after every commit. setState below bails out via
36
+ // Object.is when nothing moved, so the steady state does not loop.
37
+ useEffect(() => {
38
+ const { viewportHeight, contentHeight } = measureHeights();
39
+ if (viewportHeight <= 0)
40
+ return;
41
+ const action = resolveTranscriptScroll({
42
+ forcePending: forcePendingRef.current,
43
+ shouldFollow: followingRef.current,
44
+ following: followingRef.current,
45
+ });
46
+ if (action === "scroll-bottom") {
47
+ forcePendingRef.current = false;
48
+ followingRef.current = true;
49
+ applyScrollTop(maxScrollTop(contentHeight, viewportHeight));
50
+ }
51
+ else {
52
+ const clamped = clampScrollTop(scrollTopRef.current, contentHeight, viewportHeight);
53
+ followingRef.current = isAtBottom(clamped, contentHeight, viewportHeight);
54
+ if (clamped !== scrollTopRef.current)
55
+ applyScrollTop(clamped);
56
+ }
57
+ });
58
+ useImperativeHandle(ref, () => {
59
+ const scrollBy = (lines) => {
60
+ forcePendingRef.current = false; // the user's latest gesture wins
61
+ const { viewportHeight, contentHeight } = measureHeights();
62
+ if (viewportHeight <= 0)
63
+ return;
64
+ const next = clampScrollTop(scrollTopRef.current + lines, contentHeight, viewportHeight);
65
+ followingRef.current = isAtBottom(next, contentHeight, viewportHeight);
66
+ if (next !== scrollTopRef.current)
67
+ applyScrollTop(next);
68
+ };
69
+ return {
70
+ forceScrollToBottom() {
71
+ forcePendingRef.current = true;
72
+ setScrollEpoch((epoch) => epoch + 1);
73
+ },
74
+ scrollBy,
75
+ scrollPage(direction) {
76
+ const { viewportHeight } = measureHeights();
77
+ const step = Math.max(1, viewportHeight - 2);
78
+ scrollBy(direction === "up" ? -step : step);
79
+ },
80
+ };
81
+ }, []);
82
+ return (_jsx(Box, { ref: viewportRef, flexDirection: "column", flexGrow: 1, minHeight: 0, overflowY: "hidden", children: _jsx(Box, { ref: contentRef, flexDirection: "column", flexShrink: 0, marginTop: -scrollTop, children: children }) }));
83
+ });
@@ -1,18 +1,20 @@
1
1
  import type { DisplayMessage } from "./display-history.js";
2
2
  interface WelcomeBannerProps {
3
3
  terminalColumns: number;
4
- modelLabel?: string;
5
- cwd?: string;
6
4
  tips: string[];
7
- skillsCount?: number;
8
- mcpConnectedCount?: number;
9
- mcpTotalCount?: number;
10
- hasAgentsFile?: boolean;
5
+ /** One-line "update available" notice shown under the version. */
6
+ updateNotice?: string;
7
+ /** Friendly working directory (~ collapsed). */
8
+ cwd?: string;
9
+ providerId?: string;
10
+ modelLabel?: string;
11
+ /** Active thinking level, rendered as part of the model unit (e.g. "xhigh"). */
12
+ thinkingLabel?: string;
11
13
  }
12
14
  interface WelcomeVisibilityInput {
13
15
  messages: Pick<DisplayMessage, "role" | "syntheticKind">[];
14
16
  startedWithVisibleHistory: boolean;
15
17
  }
16
18
  export declare function shouldShowWelcomeBanner({ startedWithVisibleHistory, }: WelcomeVisibilityInput): boolean;
17
- export declare function WelcomeBanner({ terminalColumns, modelLabel, cwd, tips, skillsCount, mcpConnectedCount, mcpTotalCount, hasAgentsFile, }: WelcomeBannerProps): import("react/jsx-runtime").JSX.Element;
19
+ export declare function WelcomeBanner({ terminalColumns, tips, updateNotice, cwd, providerId, modelLabel, thinkingLabel, }: WelcomeBannerProps): import("react/jsx-runtime").JSX.Element;
18
20
  export {};
@@ -1,12 +1,11 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  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
+ import { bubbleWordmarkForWidth, } from "../tui/wordmark.js";
7
7
  const require = createRequire(import.meta.url);
8
8
  const PACKAGE_VERSION = readPackageVersion();
9
- const WIDE_LOGO_MIN_WIDTH = bubbleWordmarkMaxWidth(BUBBLE_WORDMARK) + 4;
10
9
  export function shouldShowWelcomeBanner({ startedWithVisibleHistory, }) {
11
10
  // Keep banner visibility tied to the initial history, not transient overlays,
12
11
  // so opening and closing a picker does not move it in the transcript.
@@ -14,20 +13,19 @@ export function shouldShowWelcomeBanner({ startedWithVisibleHistory, }) {
14
13
  return false;
15
14
  return true;
16
15
  }
17
- export function WelcomeBanner({ terminalColumns, modelLabel, cwd, tips, skillsCount = 0, mcpConnectedCount = 0, mcpTotalCount = 0, hasAgentsFile = false, }) {
16
+ export function WelcomeBanner({ terminalColumns, tips, updateNotice, cwd, providerId, modelLabel, thinkingLabel, }) {
18
17
  const theme = useTheme();
19
18
  const effectiveWidth = Math.max(20, Math.min(terminalColumns - 2, 118));
20
- const useWideLogo = effectiveWidth >= WIDE_LOGO_MIN_WIDTH;
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);
21
22
  const actionableTips = tips
22
23
  .filter((item) => !item.startsWith("Ready with") && item.trim().length > 0)
23
24
  .slice(0, 2);
24
25
  const tip = actionableTips.length > 0
25
26
  ? actionableTips.join(" · ")
26
27
  : "Type / for commands and @ to reference files";
27
- const modelLine = modelLabel ? `${modelLabel}${cwd ? ` · ${cwd}` : ""}` : cwd;
28
- return (_jsxs(Box, { width: effectiveWidth, flexDirection: "column", alignItems: "center", marginBottom: 1, children: [_jsx(Box, { flexDirection: "column", alignItems: "center", children: useWideLogo
29
- ? BUBBLE_WORDMARK.map((line, rowIndex) => (_jsx(LogoRow, { line: line }, `logo-row-${rowIndex}`)))
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 })] })] }));
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}` : ""] }))] }))] }));
31
29
  }
32
30
  function LogoRow({ line }) {
33
31
  const theme = useTheme();
@@ -36,14 +34,6 @@ function LogoRow({ line }) {
36
34
  }
37
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}`))) }));
38
36
  }
39
- function CompactLogo() {
40
- const theme = useTheme();
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
37
  function logoColor(theme, tone) {
48
38
  switch (tone) {
49
39
  case "brand": return theme.warning;
@@ -53,15 +43,6 @@ function logoColor(theme, tone) {
53
43
  case "caption": return theme.muted;
54
44
  }
55
45
  }
56
- function StatusItem({ label, count, total, ok, }) {
57
- const theme = useTheme();
58
- const countText = count === undefined
59
- ? ""
60
- : total !== undefined && total > count
61
- ? ` (${count}/${total})`
62
- : ` (${count})`;
63
- return (_jsxs(_Fragment, { children: [_jsxs(Text, { bold: true, color: theme.muted, children: [label, countText, " "] }), _jsx(Text, { bold: true, color: ok ? theme.success : theme.error, children: ok ? "✓" : "×" })] }));
64
- }
65
46
  function readPackageVersion() {
66
47
  try {
67
48
  const pkg = require("../../package.json");
@@ -71,10 +52,3 @@ function readPackageVersion() {
71
52
  return "v0.0.0";
72
53
  }
73
54
  }
74
- function truncateToWidth(text, maxWidth) {
75
- if (maxWidth <= 0)
76
- return "";
77
- if (text.length <= maxWidth)
78
- return text;
79
- return text.slice(0, Math.max(1, maxWidth - 1)) + "…";
80
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bubblebrain-ai/bubble",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "description": "A terminal coding agent",
5
5
  "type": "module",
6
6
  "engines": {