@bubblebrain-ai/bubble 0.0.11 → 0.0.12
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.
- package/dist/agent.js +1 -2
- package/dist/feishu/agent-host/run-driver.js +13 -6
- package/dist/feishu/agent-host/runtime-deps.d.ts +2 -2
- package/dist/feishu/router/commands.js +2 -1
- package/dist/feishu/scope/session-binder.js +1 -1
- package/dist/feishu/serve.js +3 -3
- package/dist/main.js +20 -3
- package/dist/prompt/compose.js +3 -3
- package/dist/prompt/environment.js +2 -0
- package/dist/prompt/reminders.js +1 -1
- package/dist/provider-openai-codex.d.ts +8 -1
- package/dist/provider-openai-codex.js +33 -9
- package/dist/provider.d.ts +2 -0
- package/dist/session-title.d.ts +16 -0
- package/dist/session-title.js +134 -0
- package/dist/session-types.d.ts +5 -0
- package/dist/session.d.ts +5 -0
- package/dist/session.js +75 -9
- package/dist/skills/invocation.js +0 -18
- package/dist/skills/registry.d.ts +1 -0
- package/dist/skills/registry.js +2 -0
- package/dist/slash-commands/commands.js +2 -22
- package/dist/slash-commands/registry.js +1 -1
- package/dist/text-display.d.ts +3 -0
- package/dist/text-display.js +25 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +3 -1
- package/dist/tools/skill-search.d.ts +10 -0
- package/dist/tools/skill-search.js +134 -0
- package/dist/tools/skill.js +1 -4
- package/dist/tui-ink/app.js +54 -65
- package/dist/tui-ink/input-box.d.ts +22 -1
- package/dist/tui-ink/input-box.js +105 -11
- package/dist/tui-ink/message-list.js +3 -2
- package/dist/tui-ink/model-picker.d.ts +18 -0
- package/dist/tui-ink/model-picker.js +80 -23
- package/dist/tui-ink/session-picker.js +5 -7
- package/dist/tui-ink/theme.js +2 -2
- package/package.json +1 -1
|
@@ -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
|
-
|
|
128
|
+
const action = resolvePickerKeyAction(input, key);
|
|
129
|
+
if (action === "escape") {
|
|
80
130
|
onCancel();
|
|
81
131
|
return;
|
|
82
132
|
}
|
|
83
|
-
if (
|
|
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 (
|
|
139
|
+
if (action === "up") {
|
|
90
140
|
setSelectedIndex((i) => Math.max(0, i - 1));
|
|
91
141
|
return;
|
|
92
142
|
}
|
|
93
|
-
if (
|
|
143
|
+
if (action === "down") {
|
|
94
144
|
setSelectedIndex((i) => Math.min(options.length - 1, i + 1));
|
|
95
145
|
return;
|
|
96
146
|
}
|
|
97
|
-
if (
|
|
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
|
-
|
|
250
|
+
const action = resolvePickerKeyAction(input, key);
|
|
251
|
+
if (action === "escape") {
|
|
201
252
|
onCancel();
|
|
202
253
|
return;
|
|
203
254
|
}
|
|
204
|
-
if (
|
|
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 (
|
|
261
|
+
if (action === "up") {
|
|
211
262
|
setSelectedIndex((i) => Math.max(0, i - 1));
|
|
212
263
|
return;
|
|
213
264
|
}
|
|
214
|
-
if (
|
|
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
|
-
|
|
297
|
+
const action = resolvePickerKeyAction(input, key);
|
|
298
|
+
if (action === "escape") {
|
|
247
299
|
onCancel();
|
|
248
300
|
return;
|
|
249
301
|
}
|
|
250
|
-
if (
|
|
302
|
+
if (action === "enter") {
|
|
251
303
|
if (value.trim())
|
|
252
304
|
onSubmit(value.trim());
|
|
253
305
|
return;
|
|
254
306
|
}
|
|
255
|
-
if (
|
|
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
|
-
|
|
341
|
+
const action = resolvePickerKeyAction(input, key);
|
|
342
|
+
if (action === "escape") {
|
|
288
343
|
onCancel();
|
|
289
344
|
return;
|
|
290
345
|
}
|
|
291
|
-
if (
|
|
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 (
|
|
352
|
+
if (action === "up") {
|
|
298
353
|
setSelectedIndex((i) => Math.max(0, i - 1));
|
|
299
354
|
return;
|
|
300
355
|
}
|
|
301
|
-
if (
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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)
|
|
59
|
-
|
|
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
|
-
}
|
package/dist/tui-ink/theme.js
CHANGED
|
@@ -68,8 +68,8 @@ export const lightTheme = {
|
|
|
68
68
|
borderActive: "#0E5A85",
|
|
69
69
|
inputBorder: "#6B5FB8",
|
|
70
70
|
inputBorderDisabled: "#c5c3d0",
|
|
71
|
-
inputBg: "#
|
|
72
|
-
inputBgDisabled: "#
|
|
71
|
+
inputBg: "#eeeef6",
|
|
72
|
+
inputBgDisabled: "#e2e2ec",
|
|
73
73
|
inputText: "#1c1c24",
|
|
74
74
|
inputPlaceholder: "#7a7886",
|
|
75
75
|
muted: "gray",
|