@bubblebrain-ai/bubble 0.0.8 → 0.0.9

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 (74) hide show
  1. package/dist/agent/categories.d.ts +34 -0
  2. package/dist/agent/categories.js +98 -0
  3. package/dist/agent/profiles.d.ts +4 -0
  4. package/dist/agent/profiles.js +2 -3
  5. package/dist/agent/subagent-control.d.ts +5 -0
  6. package/dist/agent/subagent-control.js +4 -0
  7. package/dist/agent/subagent-lifecycle-reminder.d.ts +3 -0
  8. package/dist/agent/subagent-lifecycle-reminder.js +102 -0
  9. package/dist/agent/subagent-route-format.d.ts +8 -0
  10. package/dist/agent/subagent-route-format.js +18 -0
  11. package/dist/agent/subtask-policy.d.ts +0 -1
  12. package/dist/agent/subtask-policy.js +0 -4
  13. package/dist/agent.d.ts +12 -0
  14. package/dist/agent.js +152 -13
  15. package/dist/config.d.ts +23 -3
  16. package/dist/config.js +59 -6
  17. package/dist/context/budget.d.ts +3 -3
  18. package/dist/context/budget.js +29 -15
  19. package/dist/context/compact.d.ts +23 -0
  20. package/dist/context/compact.js +129 -0
  21. package/dist/context/llm-compactor.d.ts +19 -0
  22. package/dist/context/llm-compactor.js +200 -0
  23. package/dist/context/projector.js +28 -12
  24. package/dist/context/token-estimator.d.ts +14 -0
  25. package/dist/context/token-estimator.js +106 -0
  26. package/dist/context/tool-output-truncate.d.ts +8 -0
  27. package/dist/context/tool-output-truncate.js +59 -0
  28. package/dist/context/usage.js +9 -9
  29. package/dist/main.js +43 -6
  30. package/dist/model-catalog.d.ts +9 -0
  31. package/dist/model-catalog.js +16 -0
  32. package/dist/orchestrator/default-hooks.js +18 -0
  33. package/dist/provider-openai-codex.d.ts +13 -2
  34. package/dist/provider-openai-codex.js +81 -32
  35. package/dist/provider-registry.js +20 -4
  36. package/dist/slash-commands/commands.js +24 -0
  37. package/dist/slash-commands/types.d.ts +7 -0
  38. package/dist/tools/agent-lifecycle.js +22 -4
  39. package/dist/tools/edit.js +2 -2
  40. package/dist/tools/glob.js +2 -1
  41. package/dist/tools/grep.js +2 -2
  42. package/dist/tools/lsp.js +2 -2
  43. package/dist/tools/path-utils.d.ts +2 -0
  44. package/dist/tools/path-utils.js +16 -0
  45. package/dist/tools/read.js +117 -5
  46. package/dist/tools/write.js +3 -2
  47. package/dist/tui-ink/app.d.ts +11 -2
  48. package/dist/tui-ink/app.js +191 -78
  49. package/dist/tui-ink/approval/approval-dialog.js +4 -1
  50. package/dist/tui-ink/approval/diff-view.js +2 -1
  51. package/dist/tui-ink/approval/select.js +2 -1
  52. package/dist/tui-ink/code-highlight.d.ts +2 -0
  53. package/dist/tui-ink/code-highlight.js +30 -2
  54. package/dist/tui-ink/detect-theme.d.ts +19 -0
  55. package/dist/tui-ink/detect-theme.js +123 -0
  56. package/dist/tui-ink/footer.js +4 -3
  57. package/dist/tui-ink/input-box.js +83 -26
  58. package/dist/tui-ink/input-history.d.ts +16 -0
  59. package/dist/tui-ink/input-history.js +81 -0
  60. package/dist/tui-ink/markdown.js +30 -20
  61. package/dist/tui-ink/message-list.js +112 -16
  62. package/dist/tui-ink/model-picker.js +6 -1
  63. package/dist/tui-ink/plan-confirm.js +2 -1
  64. package/dist/tui-ink/question-dialog.js +2 -1
  65. package/dist/tui-ink/run.d.ts +5 -1
  66. package/dist/tui-ink/run.js +30 -2
  67. package/dist/tui-ink/theme.d.ts +64 -35
  68. package/dist/tui-ink/theme.js +81 -8
  69. package/dist/tui-ink/todos.js +5 -3
  70. package/dist/tui-ink/trace-groups.d.ts +3 -1
  71. package/dist/tui-ink/trace-groups.js +93 -14
  72. package/dist/tui-ink/welcome.js +23 -4
  73. package/dist/types.d.ts +6 -0
  74. package/package.json +2 -1
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
- import { theme } from "./theme.js";
3
+ import { useTheme } from "./theme.js";
4
4
  const MAX_ROWS = 8;
5
5
  export function TodosPanel({ todos, terminalColumns }) {
6
+ const theme = useTheme();
6
7
  if (todos.length === 0) {
7
8
  return null;
8
9
  }
@@ -28,12 +29,13 @@ function selectVisibleRows(todos) {
28
29
  return todos.slice(start, end);
29
30
  }
30
31
  function TodoRow({ todo, maxWidth }) {
31
- const { glyph, color, dim, label } = statusStyle(todo);
32
+ const theme = useTheme();
33
+ const { glyph, color, dim, label } = statusStyle(todo, theme);
32
34
  const text = label || todo.content;
33
35
  const trimmed = text.length > maxWidth - 4 ? text.slice(0, maxWidth - 5) + "…" : text;
34
36
  return (_jsx(Box, { height: 1, children: _jsxs(Text, { color: color, dimColor: dim, children: [glyph, " ", trimmed] }) }));
35
37
  }
36
- function statusStyle(todo) {
38
+ function statusStyle(todo, theme) {
37
39
  if (todo.status === "completed") {
38
40
  return { glyph: "✔", color: theme.muted, dim: true, label: todo.content };
39
41
  }
@@ -1,5 +1,5 @@
1
1
  import type { DisplayToolCall } from "./display-history.js";
2
- export type TraceGroupKind = "list" | "read" | "search" | "execute" | "edit" | "write" | "other";
2
+ export type TraceGroupKind = "list" | "read" | "search" | "execute" | "edit" | "write" | "subagent" | "other";
3
3
  export interface TraceGroup {
4
4
  kind: TraceGroupKind;
5
5
  title: string;
@@ -9,9 +9,11 @@ export interface TraceGroup {
9
9
  command?: string;
10
10
  items: string[];
11
11
  previewLines: string[];
12
+ errorLines: string[];
12
13
  omitted: number;
13
14
  pending: boolean;
14
15
  hasError: boolean;
16
+ errorCount: number;
15
17
  startedAt?: number;
16
18
  }
17
19
  export interface TraceGroupOptions {
@@ -1,5 +1,6 @@
1
1
  import os from "node:os";
2
2
  import { getEditDiffDetails } from "./edit-diff.js";
3
+ import { formatSubagentRoute } from "../agent/subagent-route-format.js";
3
4
  const DEFAULT_MAX_ITEMS = 6;
4
5
  const DEFAULT_MAX_PREVIEW_LINES = 8;
5
6
  export function buildTraceGroups(toolCalls, options = {}) {
@@ -71,6 +72,9 @@ export function traceGroupLabel(group) {
71
72
  return group.title;
72
73
  }
73
74
  function classifyTool(toolCall) {
75
+ if (toolCall.metadata?.kind === "subagent") {
76
+ return { kind: "subagent", title: "Subagents", bucketKey: `subagent:${toolCall.id}`, groupable: false };
77
+ }
74
78
  switch (toolCall.name) {
75
79
  case "glob": {
76
80
  const pattern = String(toolCall.args.pattern ?? "");
@@ -109,23 +113,26 @@ function buildTraceGroup(classifier, raw, options) {
109
113
  .filter((value) => typeof value === "number")
110
114
  .sort((a, b) => a - b)[0];
111
115
  const hasError = raw.some((tool) => !!tool.isError);
116
+ const errorCount = raw.filter((tool) => !!tool.isError).length;
112
117
  switch (classifier.kind) {
113
118
  case "list":
114
- return buildListGroup(classifier, raw, options, pending, startedAt, hasError);
119
+ return buildListGroup(classifier, raw, options, pending, startedAt, hasError, errorCount);
115
120
  case "read":
116
- return buildPathGroup(classifier, raw, options, pending, startedAt, hasError, "files");
121
+ return buildPathGroup(classifier, raw, options, pending, startedAt, hasError, errorCount, "files");
117
122
  case "search":
118
- return buildSearchGroup(classifier, raw, options, pending, startedAt, hasError);
123
+ return buildSearchGroup(classifier, raw, options, pending, startedAt, hasError, errorCount);
119
124
  case "execute":
120
- return buildExecuteGroup(classifier, raw[0], options, pending, startedAt, hasError);
125
+ return buildExecuteGroup(classifier, raw[0], options, pending, startedAt, hasError, errorCount);
121
126
  case "edit":
122
127
  case "write":
123
- return buildMutationGroup(classifier, raw, options, pending, startedAt, hasError);
128
+ return buildMutationGroup(classifier, raw, options, pending, startedAt, hasError, errorCount);
129
+ case "subagent":
130
+ return buildSubagentGroup(classifier, raw[0], options, pending, startedAt);
124
131
  default:
125
- return buildOtherGroup(classifier, raw, options, pending, startedAt, hasError);
132
+ return buildOtherGroup(classifier, raw, options, pending, startedAt, hasError, errorCount);
126
133
  }
127
134
  }
128
- function buildListGroup(classifier, raw, options, pending, startedAt, hasError) {
135
+ function buildListGroup(classifier, raw, options, pending, startedAt, hasError, errorCount) {
129
136
  const resultItems = raw.flatMap((tool) => resultLines(tool.result).map((line) => formatTracePath(line, options.homeDir)));
130
137
  const fallbackItems = raw
131
138
  .map((tool) => String(tool.args.pattern ?? tool.args.path ?? "").trim())
@@ -143,16 +150,18 @@ function buildListGroup(classifier, raw, options, pending, startedAt, hasError)
143
150
  noun,
144
151
  items: shown,
145
152
  previewLines: [],
153
+ errorLines: collectErrorLines(raw, options),
146
154
  omitted,
147
155
  pending,
148
156
  hasError,
157
+ errorCount,
149
158
  startedAt,
150
159
  };
151
160
  }
152
- function buildPathGroup(classifier, raw, options, pending, startedAt, hasError, nounBase) {
153
- const items = raw
161
+ function buildPathGroup(classifier, raw, options, pending, startedAt, hasError, errorCount, nounBase) {
162
+ const items = unique(raw
154
163
  .map((tool) => formatTracePath(tool.args.path ?? tool.args.file ?? "", options.homeDir))
155
- .filter(Boolean);
164
+ .filter(Boolean));
156
165
  const { shown, omitted } = take(items, options.maxItems);
157
166
  const count = items.length || raw.length;
158
167
  return {
@@ -163,13 +172,15 @@ function buildPathGroup(classifier, raw, options, pending, startedAt, hasError,
163
172
  noun: plural(count, nounBase.slice(0, -1), nounBase),
164
173
  items: shown,
165
174
  previewLines: [],
175
+ errorLines: collectErrorLines(raw, options),
166
176
  omitted,
167
177
  pending,
168
178
  hasError,
179
+ errorCount,
169
180
  startedAt,
170
181
  };
171
182
  }
172
- function buildSearchGroup(classifier, raw, options, pending, startedAt, hasError) {
183
+ function buildSearchGroup(classifier, raw, options, pending, startedAt, hasError, errorCount) {
173
184
  const items = raw.map((tool) => {
174
185
  const pattern = String(tool.args.pattern ?? tool.args.query ?? "").trim();
175
186
  const scope = String(tool.args.path ?? tool.args.glob ?? tool.args.include ?? "").trim();
@@ -186,13 +197,15 @@ function buildSearchGroup(classifier, raw, options, pending, startedAt, hasError
186
197
  noun: plural(count, "search", "searches"),
187
198
  items: shown,
188
199
  previewLines: [],
200
+ errorLines: collectErrorLines(raw, options),
189
201
  omitted,
190
202
  pending,
191
203
  hasError,
204
+ errorCount,
192
205
  startedAt,
193
206
  };
194
207
  }
195
- function buildExecuteGroup(classifier, tool, options, pending, startedAt, hasError) {
208
+ function buildExecuteGroup(classifier, tool, options, pending, startedAt, hasError, errorCount) {
196
209
  const lines = resultLines(tool.result).map((line) => formatTracePath(line, options.homeDir));
197
210
  const { shown, omitted } = take(lines, options.maxPreviewLines);
198
211
  return {
@@ -202,13 +215,15 @@ function buildExecuteGroup(classifier, tool, options, pending, startedAt, hasErr
202
215
  command: normalizeCommand(tool.args.command ?? ""),
203
216
  items: [],
204
217
  previewLines: shown,
218
+ errorLines: [],
205
219
  omitted,
206
220
  pending,
207
221
  hasError,
222
+ errorCount,
208
223
  startedAt,
209
224
  };
210
225
  }
211
- function buildMutationGroup(classifier, raw, options, pending, startedAt, hasError) {
226
+ function buildMutationGroup(classifier, raw, options, pending, startedAt, hasError, errorCount) {
212
227
  const items = raw
213
228
  .map((tool) => {
214
229
  const path = formatTracePath(tool.args.path ?? "", options.homeDir);
@@ -233,13 +248,38 @@ function buildMutationGroup(classifier, raw, options, pending, startedAt, hasErr
233
248
  noun: plural(count, "file", "files"),
234
249
  items: shown,
235
250
  previewLines: errorPreview,
251
+ errorLines: [],
236
252
  omitted,
237
253
  pending,
238
254
  hasError,
255
+ errorCount,
239
256
  startedAt,
240
257
  };
241
258
  }
242
- function buildOtherGroup(classifier, raw, options, pending, startedAt, hasError) {
259
+ function buildSubagentGroup(classifier, tool, options, pending, startedAt) {
260
+ const subagents = subagentsFromMetadata(tool);
261
+ const rows = subagents.length > 0
262
+ ? subagents.map(formatSubagentRow)
263
+ : resultLines(tool.result).map((line) => formatTracePath(line, options.homeDir));
264
+ const { shown, omitted } = take(rows, options.maxPreviewLines);
265
+ const errorCount = subagents.filter(isFailedSubagent).length + (tool.isError ? 1 : 0);
266
+ return {
267
+ kind: "subagent",
268
+ title: classifier.title,
269
+ raw: [tool],
270
+ count: subagents.length || 1,
271
+ noun: plural(subagents.length || 1, "agent", "agents"),
272
+ items: [],
273
+ previewLines: shown,
274
+ errorLines: [],
275
+ omitted,
276
+ pending: pending || subagents.some((subagent) => ["queued", "running"].includes(subagent.status ?? "running")),
277
+ hasError: !!tool.isError || errorCount > 0,
278
+ errorCount,
279
+ startedAt,
280
+ };
281
+ }
282
+ function buildOtherGroup(classifier, raw, options, pending, startedAt, hasError, errorCount) {
243
283
  const tool = raw[0];
244
284
  const header = toolHeader(tool, options.homeDir);
245
285
  const preview = resultLines(tool.result).map((line) => formatTracePath(line, options.homeDir));
@@ -252,12 +292,36 @@ function buildOtherGroup(classifier, raw, options, pending, startedAt, hasError)
252
292
  noun: header ? undefined : plural(raw.length, "call", "calls"),
253
293
  items: header ? [header] : [],
254
294
  previewLines: shown,
295
+ errorLines: [],
255
296
  omitted,
256
297
  pending,
257
298
  hasError,
299
+ errorCount,
258
300
  startedAt,
259
301
  };
260
302
  }
303
+ function subagentsFromMetadata(tool) {
304
+ const raw = tool.metadata?.subagents;
305
+ if (!Array.isArray(raw))
306
+ return [];
307
+ return raw.filter((item) => typeof item === "object" && item !== null);
308
+ }
309
+ function formatSubagentRow(subagent) {
310
+ const label = subagent.nickname || subagent.agentName || subagent.subAgentId || "subagent";
311
+ const role = [subagent.agentName, subagent.category ? `/${subagent.category}` : ""].join("") || "default";
312
+ const route = formatSubagentRoute(subagent.route);
313
+ const descriptor = route ? `${role} @ ${route}` : role;
314
+ const status = subagent.status || "running";
315
+ const note = subagent.error
316
+ || subagent.toolNotes?.filter(Boolean).at(-1)
317
+ || subagent.summary
318
+ || subagent.task
319
+ || "";
320
+ return [label, `(${descriptor})`, status, note].filter(Boolean).join(" ");
321
+ }
322
+ function isFailedSubagent(subagent) {
323
+ return subagent.status === "failed" || subagent.status === "blocked" || subagent.status === "cancelled";
324
+ }
261
325
  function isToolPending(tool) {
262
326
  return tool.result === undefined;
263
327
  }
@@ -278,6 +342,21 @@ function take(items, max) {
278
342
  const shown = items.slice(0, max);
279
343
  return { shown, omitted: Math.max(0, items.length - shown.length) };
280
344
  }
345
+ function unique(items) {
346
+ return [...new Set(items)];
347
+ }
348
+ function collectErrorLines(raw, options) {
349
+ return raw
350
+ .filter((tool) => tool.isError)
351
+ .flatMap((tool) => resultLines(tool.result).map((line) => formatTraceLine(line, options.homeDir)))
352
+ .slice(0, options.maxPreviewLines);
353
+ }
354
+ function formatTraceLine(value, homeDir) {
355
+ const text = String(value ?? "").trimEnd();
356
+ if (!homeDir)
357
+ return text;
358
+ return text.split(homeDir + "/").join("~/").split(homeDir).join("~");
359
+ }
281
360
  function plural(count, singular, pluralValue) {
282
361
  return count === 1 ? singular : pluralValue;
283
362
  }
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import React from "react";
3
3
  import { Box, Text } from "ink";
4
4
  import { createRequire } from "node:module";
5
- import { theme } from "./theme.js";
5
+ import { useTheme } from "./theme.js";
6
6
  const require = createRequire(import.meta.url);
7
7
  const PACKAGE_VERSION = readPackageVersion();
8
8
  const BUBBLE_LOGO_LETTERS = [
@@ -61,7 +61,20 @@ const BUBBLE_LOGO_LETTERS = [
61
61
  "███████",
62
62
  ],
63
63
  ];
64
- const LOGO_COLORS = ["#f3f3f7", "#f3f3f7", "#d8c7ff", "#d8c7ff", "#a9c7ff", "#a9c7ff"];
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
+ }
65
78
  const COMPACT_LOGO = ["B", "U", "B", "B", "L", "E"];
66
79
  const WIDE_LOGO_MIN_WIDTH = 52;
67
80
  export function shouldShowWelcomeBanner({ startedWithVisibleHistory, }) {
@@ -74,6 +87,7 @@ export function shouldShowWelcomeBanner({ startedWithVisibleHistory, }) {
74
87
  return true;
75
88
  }
76
89
  export function WelcomeBanner({ terminalColumns, modelLabel, cwd, tips, skillsCount = 0, mcpConnectedCount = 0, mcpTotalCount = 0, hasAgentsFile = false, }) {
90
+ const theme = useTheme();
77
91
  const effectiveWidth = Math.max(20, Math.min(terminalColumns - 2, 118));
78
92
  const useWideLogo = effectiveWidth >= WIDE_LOGO_MIN_WIDTH;
79
93
  const actionableTips = tips
@@ -88,12 +102,17 @@ export function WelcomeBanner({ terminalColumns, modelLabel, cwd, tips, skillsCo
88
102
  : _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 })] })] }));
89
103
  }
90
104
  function LogoRow({ rowIndex }) {
91
- return (_jsx(Box, { children: BUBBLE_LOGO_LETTERS.map((letter, index) => (_jsxs(React.Fragment, { children: [_jsx(Text, { bold: true, color: LOGO_COLORS[index], children: letter[rowIndex] }), index < BUBBLE_LOGO_LETTERS.length - 1 && _jsx(Text, { children: " " })] }, `${index}-${rowIndex}`))) }));
105
+ const theme = useTheme();
106
+ const colors = logoColors(theme);
107
+ 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}`))) }));
92
108
  }
93
109
  function CompactLogo() {
94
- return (_jsx(Box, { children: COMPACT_LOGO.map((letter, index) => (_jsx(Text, { bold: true, color: LOGO_COLORS[index], children: letter }, `${letter}-${index}`))) }));
110
+ const theme = useTheme();
111
+ const colors = logoColors(theme);
112
+ return (_jsx(Box, { children: COMPACT_LOGO.map((letter, index) => (_jsx(Text, { bold: true, color: colors[index], children: letter }, `${letter}-${index}`))) }));
95
113
  }
96
114
  function StatusItem({ label, count, total, ok, }) {
115
+ const theme = useTheme();
97
116
  const countText = count === undefined
98
117
  ? ""
99
118
  : total !== undefined && total > count
package/dist/types.d.ts CHANGED
@@ -120,6 +120,8 @@ export interface ToolUpdate {
120
120
  subAgentId: string;
121
121
  agentName: string;
122
122
  nickname?: string;
123
+ category?: string;
124
+ route?: import("./agent/categories.js").ResolvedSubagentRoute;
123
125
  status: "queued" | "running" | "completed" | "failed" | "blocked" | "cancelled";
124
126
  childEvent?: AgentEvent;
125
127
  summaryDelta?: string;
@@ -147,6 +149,8 @@ export interface ToolContext {
147
149
  runId: string;
148
150
  subAgentId: string;
149
151
  parentToolCallId: string;
152
+ category?: string;
153
+ route?: import("./agent/categories.js").ResolvedSubagentRoute;
150
154
  approval?: "fail" | "disabled";
151
155
  emitUpdate?: (update: ToolUpdate) => void;
152
156
  description?: string;
@@ -157,6 +161,8 @@ export interface ToolContext {
157
161
  spawnSubAgent?: (input: string | ContentPart[], cwd: string, options: {
158
162
  profile: import("./agent/profiles.js").AgentProfile;
159
163
  parentToolCallId: string;
164
+ category?: string;
165
+ route?: import("./agent/categories.js").ResolvedSubagentRoute;
160
166
  approval?: "fail" | "disabled";
161
167
  description?: string;
162
168
  abortSignal?: AbortSignal;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bubblebrain-ai/bubble",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "A terminal coding agent",
5
5
  "type": "module",
6
6
  "engines": {
@@ -33,6 +33,7 @@
33
33
  "chalk": "^5.3.0",
34
34
  "diff": "^7.0.0",
35
35
  "ink": "^7.0.3",
36
+ "js-tiktoken": "^1.0.21",
36
37
  "openai": "^4.77.0",
37
38
  "opentui-spinner": "^0.0.6",
38
39
  "picomatch": "^4.0.4",