mini-coder 0.6.5 → 0.7.1
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/bun.lock +14 -14
- package/package.json +5 -5
- package/src/tui-conversation.ts +3 -2
- package/src/tui-overlay.ts +116 -8
- package/src/tui.ts +80 -5
- package/src/types.ts +1 -0
package/bun.lock
CHANGED
|
@@ -7,15 +7,15 @@
|
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"@cel-tui/components": "^0.8.3",
|
|
9
9
|
"@cel-tui/core": "^0.8.3",
|
|
10
|
-
"@earendil-works/pi-ai": "^0.
|
|
10
|
+
"@earendil-works/pi-ai": "^0.79.3",
|
|
11
11
|
"diff": "^9.0.0",
|
|
12
12
|
"simple-git": "^3.36.0",
|
|
13
13
|
"yaml": "^2.9.0",
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@biomejs/biome": "^2.
|
|
16
|
+
"@biomejs/biome": "^2.5.0",
|
|
17
17
|
"@types/bun": "^1.3.14",
|
|
18
|
-
"prettier": "^3.8.
|
|
18
|
+
"prettier": "^3.8.4",
|
|
19
19
|
"typescript": "^6.0.3",
|
|
20
20
|
},
|
|
21
21
|
},
|
|
@@ -75,23 +75,23 @@
|
|
|
75
75
|
|
|
76
76
|
"@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
|
|
77
77
|
|
|
78
|
-
"@biomejs/biome": ["@biomejs/biome@2.
|
|
78
|
+
"@biomejs/biome": ["@biomejs/biome@2.5.0", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.0", "@biomejs/cli-darwin-x64": "2.5.0", "@biomejs/cli-linux-arm64": "2.5.0", "@biomejs/cli-linux-arm64-musl": "2.5.0", "@biomejs/cli-linux-x64": "2.5.0", "@biomejs/cli-linux-x64-musl": "2.5.0", "@biomejs/cli-win32-arm64": "2.5.0", "@biomejs/cli-win32-x64": "2.5.0" }, "bin": { "biome": "bin/biome" } }, "sha512-4kURkd9hAPrdDM3C9n82ycYgx8hvQcW6MjKTEejruj8rK0N8P3OPpdy8BvI8kt3KWY4ycF5XtDOrktetEfhfuw=="],
|
|
79
79
|
|
|
80
|
-
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.
|
|
80
|
+
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Mn3Fwi3SA5fgmfCPqmzpWF2DLZnms3BVAhM088nTnGrTZmHS3wwIjcoZPqpXeNgd3DrrLH6xp8vTLIBuJoZiXw=="],
|
|
81
81
|
|
|
82
|
-
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.
|
|
82
|
+
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.5.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-rg3VPL5P8mYro6pqlXYXuJWph21slVp3SZtAqWSrkZs40d2gTzYmHF8E/X1iTID25btmNKltNDJ926sqVBp7DQ=="],
|
|
83
83
|
|
|
84
|
-
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.
|
|
84
|
+
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.5.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-tl+LW8fdD96/xdeWtWwc82LIOc5CoY7N2AsogLTp5R4ECErYt+8Jl/N68ezN9vzSiqPTxw6vjcihoLPYKZHrlw=="],
|
|
85
85
|
|
|
86
|
-
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.
|
|
86
|
+
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.5.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-vQdM4oSGaf7ZNeGO9w5+Y8SBtyser9M6znxYbm7Ec8wInxJu1WiKxFYZW5Auj2d80bcVvefuGGRxoFOE0eee8g=="],
|
|
87
87
|
|
|
88
|
-
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.
|
|
88
|
+
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.5.0", "", { "os": "linux", "cpu": "x64" }, "sha512-zpEGf4RQbFEh8Vt7OmavLyyOzRbtcE9osCqrS1kfvt8jDvxwhKXLSf7n0ebr/ov0RJ9ssP+lhs6C8a9WwFvrQA=="],
|
|
89
89
|
|
|
90
|
-
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.
|
|
90
|
+
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.5.0", "", { "os": "linux", "cpu": "x64" }, "sha512-+9hIcMngJ+yGUahXqZuZ8CoWKJE9SAZsFsM3QDvXpNsLbXZ9lqVzgBhOk/jTSYkOA0GLP9eu3teukqpLUojHMg=="],
|
|
91
91
|
|
|
92
|
-
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.
|
|
92
|
+
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.5.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-jB0wAvTLI4itx5VidqVUejPQFhRUxiZ9l9FvZ26D5fl6t3qme+ZB4PD3bTSeL1vZ8NI2Rx/zj6H9zcESuGHKGw=="],
|
|
93
93
|
|
|
94
|
-
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.
|
|
94
|
+
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.0", "", { "os": "win32", "cpu": "x64" }, "sha512-VT/lF+GId+67j8aDfLkxdxNoVApsPSTbyAtB3jJq0IWTrY77WXfbPfpngxq0bA6JCEv/7k8C9qWjDRKRznDlyw=="],
|
|
95
95
|
|
|
96
96
|
"@cel-tui/clew": ["@cel-tui/clew@0.8.3", "", { "peerDependencies": { "typescript": ">=5.0.0" } }, "sha512-7OFUe8ECleXjq9+vv2EvDcTXsiWNSQqVALD1LC/Gi9DzVp1aw0S6SloJuAv1KUVkwjZxil+mvYlk2LoC28mETQ=="],
|
|
97
97
|
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
|
|
102
102
|
"@cel-tui/types": ["@cel-tui/types@0.8.3", "", { "peerDependencies": { "typescript": ">=5.0.0" } }, "sha512-yEKXCY16U/W4A4JaVysv2/x/IBEHQAfb93CzL6HugN+N15ETB3Vn8tZrQwdyrOT/9WdlCQkeBH8Vj7BJHs4Hkw=="],
|
|
103
103
|
|
|
104
|
-
"@earendil-works/pi-ai": ["@earendil-works/pi-ai@0.
|
|
104
|
+
"@earendil-works/pi-ai": ["@earendil-works/pi-ai@0.79.3", "", { "dependencies": { "@anthropic-ai/sdk": "0.91.1", "@aws-sdk/client-bedrock-runtime": "3.1048.0", "@google/genai": "1.52.0", "@mistralai/mistralai": "2.2.1", "@smithy/node-http-handler": "4.7.3", "http-proxy-agent": "7.0.2", "https-proxy-agent": "7.0.6", "openai": "6.26.0", "partial-json": "0.1.7", "typebox": "1.1.38" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-lMSput/haP5uZAGbXhS5rAYd3GB7GYdJkoAUxg3VFummBeqGqGqllaTWrbHFN12kVGyVfWHhdySNXkiqVh65Iw=="],
|
|
105
105
|
|
|
106
106
|
"@google/genai": ["@google/genai@1.52.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q=="],
|
|
107
107
|
|
|
@@ -231,7 +231,7 @@
|
|
|
231
231
|
|
|
232
232
|
"path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="],
|
|
233
233
|
|
|
234
|
-
"prettier": ["prettier@3.8.
|
|
234
|
+
"prettier": ["prettier@3.8.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q=="],
|
|
235
235
|
|
|
236
236
|
"protobufjs": ["protobufjs@7.5.6", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.1", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg=="],
|
|
237
237
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mini-coder",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"packageManager": "bun@1.3.
|
|
5
|
+
"packageManager": "bun@1.3.13",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mc": "bin/mc.ts"
|
|
8
8
|
},
|
|
@@ -14,15 +14,15 @@
|
|
|
14
14
|
"format": "bun run prettier --write *.md && bun run biome format --write . && bun run biome check --write ."
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@biomejs/biome": "^2.
|
|
17
|
+
"@biomejs/biome": "^2.5.0",
|
|
18
18
|
"@types/bun": "^1.3.14",
|
|
19
|
-
"prettier": "^3.8.
|
|
19
|
+
"prettier": "^3.8.4",
|
|
20
20
|
"typescript": "^6.0.3"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@cel-tui/components": "^0.8.3",
|
|
24
24
|
"@cel-tui/core": "^0.8.3",
|
|
25
|
-
"@earendil-works/pi-ai": "^0.
|
|
25
|
+
"@earendil-works/pi-ai": "^0.79.3",
|
|
26
26
|
"diff": "^9.0.0",
|
|
27
27
|
"simple-git": "^3.36.0",
|
|
28
28
|
"yaml": "^2.9.0"
|
package/src/tui-conversation.ts
CHANGED
|
@@ -50,15 +50,16 @@ function ConversationMessageToolCall(call: TUIToolCall) {
|
|
|
50
50
|
const tail = call.output.trim().slice(-200);
|
|
51
51
|
const blocks: Node[] = [];
|
|
52
52
|
|
|
53
|
-
blocks.push(Text(tail, { fgColor: theme.white, wrap: "word" }));
|
|
54
|
-
|
|
55
53
|
if (tail.length !== call.output.trim().length) {
|
|
56
54
|
blocks.push(
|
|
57
55
|
Text("Showing the last 200 characters", {
|
|
58
56
|
fgColor: theme.bblack,
|
|
59
57
|
italic: true,
|
|
60
58
|
}),
|
|
59
|
+
Text(`[...] ${tail}`, { fgColor: theme.white, wrap: "word" }),
|
|
61
60
|
);
|
|
61
|
+
} else {
|
|
62
|
+
blocks.push(Text(tail, { fgColor: theme.white, wrap: "word" }));
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
outputNode = VStack({ width: "100%" }, blocks);
|
package/src/tui-overlay.ts
CHANGED
|
@@ -183,6 +183,14 @@ export function useSelectOverlay(initialOptions: SelectOptions) {
|
|
|
183
183
|
);
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
function cleanSnippet(text: string): string {
|
|
187
|
+
return text
|
|
188
|
+
.replaceAll(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "")
|
|
189
|
+
.trim()
|
|
190
|
+
.replace(/\s+/g, " ")
|
|
191
|
+
.slice(0, 80);
|
|
192
|
+
}
|
|
193
|
+
|
|
186
194
|
function sessionLabel(session: Session): string {
|
|
187
195
|
const firstUserMessage = session.messages.find(
|
|
188
196
|
(message) => message.role === "user",
|
|
@@ -195,16 +203,79 @@ function sessionLabel(session: Session): string {
|
|
|
195
203
|
: firstUserMessage.content
|
|
196
204
|
.map((block) => (block.type === "text" ? block.text : ""))
|
|
197
205
|
.join(" ");
|
|
198
|
-
const snippet = text
|
|
199
|
-
.replaceAll(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "")
|
|
200
|
-
.trim()
|
|
201
|
-
.replace(/\s+/g, " ")
|
|
202
|
-
.slice(0, 80);
|
|
206
|
+
const snippet = cleanSnippet(text);
|
|
203
207
|
|
|
204
208
|
return snippet || session.id;
|
|
205
209
|
}
|
|
206
210
|
|
|
207
|
-
|
|
211
|
+
function messageText(message: Message): string {
|
|
212
|
+
if (message.role === "user") {
|
|
213
|
+
return typeof message.content === "string"
|
|
214
|
+
? message.content
|
|
215
|
+
: message.content
|
|
216
|
+
.map((content) =>
|
|
217
|
+
content.type === "text" ? content.text : "[image]",
|
|
218
|
+
)
|
|
219
|
+
.join(" ");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (message.role === "toolResult") {
|
|
223
|
+
return message.content
|
|
224
|
+
.map((content) => (content.type === "text" ? content.text : "[image]"))
|
|
225
|
+
.join(" ");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return message.content
|
|
229
|
+
.map((content) => {
|
|
230
|
+
if (content.type === "text") return content.text;
|
|
231
|
+
if (content.type === "thinking") return content.thinking;
|
|
232
|
+
if (content.type === "toolCall") return `tool call: ${content.name}`;
|
|
233
|
+
return "";
|
|
234
|
+
})
|
|
235
|
+
.join(" ");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function messageLabel(message: Message, index: number): string {
|
|
239
|
+
const role =
|
|
240
|
+
message.role === "toolResult"
|
|
241
|
+
? `toolResult:${message.toolName}`
|
|
242
|
+
: message.role;
|
|
243
|
+
const snippet = cleanSnippet(messageText(message));
|
|
244
|
+
const prefix = `${index + 1}. ${role} ${formatTimestamp(message.timestamp)}`;
|
|
245
|
+
|
|
246
|
+
return snippet ? `${prefix} — ${snippet}` : prefix;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function forkMessages(messages: Message[], selectedIndex: number): Message[] {
|
|
250
|
+
const forkedMessages = messages.slice(0, selectedIndex + 1);
|
|
251
|
+
const toolCallIds = new Set<string>();
|
|
252
|
+
const includedToolResultIds = new Set<string>();
|
|
253
|
+
|
|
254
|
+
for (const message of forkedMessages) {
|
|
255
|
+
if (message.role === "assistant") {
|
|
256
|
+
for (const content of message.content) {
|
|
257
|
+
if (content.type === "toolCall") toolCallIds.add(content.id);
|
|
258
|
+
}
|
|
259
|
+
} else if (message.role === "toolResult") {
|
|
260
|
+
includedToolResultIds.add(message.toolCallId);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
for (const message of messages) {
|
|
265
|
+
if (
|
|
266
|
+
message.role === "toolResult" &&
|
|
267
|
+
toolCallIds.has(message.toolCallId) &&
|
|
268
|
+
!includedToolResultIds.has(message.toolCallId)
|
|
269
|
+
) {
|
|
270
|
+
forkedMessages.push(message);
|
|
271
|
+
includedToolResultIds.add(message.toolCallId);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return forkedMessages;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function mainMenu(state: TUIState, initialPane = "main") {
|
|
208
279
|
type MenuPane = Omit<SelectOptions, "onCancel" | "onSelect">;
|
|
209
280
|
|
|
210
281
|
const oauthProviders = getOAuthProviders();
|
|
@@ -264,6 +335,15 @@ export function mainMenu(state: TUIState) {
|
|
|
264
335
|
};
|
|
265
336
|
};
|
|
266
337
|
|
|
338
|
+
const forkPane = (): MenuPane => ({
|
|
339
|
+
label: "fork",
|
|
340
|
+
filter: "",
|
|
341
|
+
list: state.messages.map((message, index) => ({
|
|
342
|
+
label: messageLabel(message, index),
|
|
343
|
+
value: String(index),
|
|
344
|
+
})),
|
|
345
|
+
});
|
|
346
|
+
|
|
267
347
|
const mainPane: MenuPane = {
|
|
268
348
|
label: "main",
|
|
269
349
|
filter: state.prompt.length ? state.prompt : "",
|
|
@@ -271,6 +351,7 @@ export function mainMenu(state: TUIState) {
|
|
|
271
351
|
{ label: "models and providers", value: "providers" },
|
|
272
352
|
{ label: "reasoning effort", value: "effort" },
|
|
273
353
|
{ label: "sessions", value: "sessions" },
|
|
354
|
+
{ label: "fork", value: "fork" },
|
|
274
355
|
],
|
|
275
356
|
};
|
|
276
357
|
const effortPane: MenuPane = {
|
|
@@ -279,7 +360,7 @@ export function mainMenu(state: TUIState) {
|
|
|
279
360
|
list: efforts,
|
|
280
361
|
};
|
|
281
362
|
const panes: MenuPane[] = [effortPane];
|
|
282
|
-
let currentPane = mainPane;
|
|
363
|
+
let currentPane = initialPane === "fork" ? forkPane() : mainPane;
|
|
283
364
|
let selectedProvider: string | undefined;
|
|
284
365
|
|
|
285
366
|
const openPane = (s: SelectState, pane: MenuPane) => {
|
|
@@ -362,7 +443,7 @@ export function mainMenu(state: TUIState) {
|
|
|
362
443
|
};
|
|
363
444
|
|
|
364
445
|
const select = useSelectOverlay({
|
|
365
|
-
...
|
|
446
|
+
...currentPane,
|
|
366
447
|
onSelect: (s) => {
|
|
367
448
|
if (!s.selected) return;
|
|
368
449
|
|
|
@@ -379,6 +460,11 @@ export function mainMenu(state: TUIState) {
|
|
|
379
460
|
});
|
|
380
461
|
}
|
|
381
462
|
|
|
463
|
+
if (s.selected === "fork") {
|
|
464
|
+
openPane(s, forkPane());
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
382
468
|
const nextPane = panes.find((pane) => pane.label === s.selected);
|
|
383
469
|
if (nextPane) openPane(s, nextPane);
|
|
384
470
|
return;
|
|
@@ -401,6 +487,28 @@ export function mainMenu(state: TUIState) {
|
|
|
401
487
|
return;
|
|
402
488
|
}
|
|
403
489
|
|
|
490
|
+
if (currentPane.label === "fork") {
|
|
491
|
+
const selectedIndex = Number(s.selected);
|
|
492
|
+
if (
|
|
493
|
+
!Number.isInteger(selectedIndex) ||
|
|
494
|
+
!state.messages[selectedIndex]
|
|
495
|
+
) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
state.sessionId = undefined;
|
|
500
|
+
state.messages = forkMessages(state.messages, selectedIndex);
|
|
501
|
+
state.tuiMessages = state.messages
|
|
502
|
+
.filter((message) => message.role !== "toolResult")
|
|
503
|
+
.map(toTUIMessage);
|
|
504
|
+
state.prompt = "";
|
|
505
|
+
state.contextSize = estimateTokens(JSON.stringify(state.messages));
|
|
506
|
+
state.scrollOffset = 0;
|
|
507
|
+
state.stickToBottom = true;
|
|
508
|
+
closeMenu(s);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
404
512
|
if (currentPane.label === "providers") {
|
|
405
513
|
const provider = currentProviders.find((v) => v === s.selected);
|
|
406
514
|
if (!provider) return;
|
package/src/tui.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
1
4
|
import { cel, HStack, ProcessTerminal, VStack } from "@cel-tui/core";
|
|
2
5
|
import type {
|
|
3
6
|
AssistantMessage,
|
|
@@ -42,6 +45,52 @@ function clearOrAbort(state: TUIState) {
|
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
async function directoryExists(path: string): Promise<boolean> {
|
|
49
|
+
try {
|
|
50
|
+
return (await stat(path)).isDirectory();
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function handleSkillSlashCommand(prompt: string) {
|
|
57
|
+
const match = prompt.trim().match(/^\/([a-zA-Z0-9_-]+)(?:\s|$)/);
|
|
58
|
+
|
|
59
|
+
if (!match) return;
|
|
60
|
+
|
|
61
|
+
const skillName = match[1];
|
|
62
|
+
const skillRoots = [
|
|
63
|
+
join(homedir(), ".agents", "skills"),
|
|
64
|
+
join(process.cwd(), ".agents", "skills"),
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
for (const root of skillRoots) {
|
|
68
|
+
if (!(await directoryExists(root))) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const glob = new Bun.Glob("*/SKILL.md");
|
|
73
|
+
|
|
74
|
+
for await (const path of glob.scan({ cwd: root, absolute: true })) {
|
|
75
|
+
const file = Bun.file(path);
|
|
76
|
+
|
|
77
|
+
if (!(await file.exists())) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const body = await file.text();
|
|
82
|
+
const frontmatter = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
83
|
+
const name = frontmatter?.[1]
|
|
84
|
+
.match(/^name:\s*["']?([^"'\n]+)["']?\s*$/m)?.[1]
|
|
85
|
+
?.trim();
|
|
86
|
+
|
|
87
|
+
if (name === skillName) {
|
|
88
|
+
return body;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
45
94
|
export function initTUI(state: TUIState, leave: (s: string) => void) {
|
|
46
95
|
// TODO: Cleanup accumulated sessions for this cwd.
|
|
47
96
|
const { spinnerEvery, currentSpinner } = Spinner();
|
|
@@ -59,6 +108,8 @@ export function initTUI(state: TUIState, leave: (s: string) => void) {
|
|
|
59
108
|
cel.render();
|
|
60
109
|
}, 1000 / fps);
|
|
61
110
|
|
|
111
|
+
let menu = mainMenu(state);
|
|
112
|
+
|
|
62
113
|
const onWindowKeyPress = (key: string) => {
|
|
63
114
|
if (key === "ctrl+q" || key === "ctrl+c" || key === "ctrl+d") {
|
|
64
115
|
// Quit
|
|
@@ -69,6 +120,10 @@ export function initTUI(state: TUIState, leave: (s: string) => void) {
|
|
|
69
120
|
// Abort or clear prompt
|
|
70
121
|
clearOrAbort(state);
|
|
71
122
|
} else if (key === "ctrl+p") {
|
|
123
|
+
menu = mainMenu(state);
|
|
124
|
+
state.overlay = true;
|
|
125
|
+
} else if (key === "ctrl+f") {
|
|
126
|
+
menu = mainMenu(state, "fork");
|
|
72
127
|
state.overlay = true;
|
|
73
128
|
}
|
|
74
129
|
};
|
|
@@ -78,6 +133,19 @@ export function initTUI(state: TUIState, leave: (s: string) => void) {
|
|
|
78
133
|
};
|
|
79
134
|
|
|
80
135
|
const onEditorKeyPress = (key: string) => {
|
|
136
|
+
const submit = async () => {
|
|
137
|
+
await streamAgentTUI(state);
|
|
138
|
+
};
|
|
139
|
+
const skillSubmit = async () => {
|
|
140
|
+
const skill = await handleSkillSlashCommand(state.prompt);
|
|
141
|
+
if (skill) {
|
|
142
|
+
state.streaming = true;
|
|
143
|
+
// This cause the prompt to flash the body before vanishing on submit.
|
|
144
|
+
// To avoid this I added a new state property
|
|
145
|
+
state.promptSkill = skill;
|
|
146
|
+
await streamAgentTUI(state);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
81
149
|
// onKeyPress
|
|
82
150
|
if (key === "enter") {
|
|
83
151
|
if (state.prompt === ":q") {
|
|
@@ -96,16 +164,15 @@ export function initTUI(state: TUIState, leave: (s: string) => void) {
|
|
|
96
164
|
state.stickToBottom = true;
|
|
97
165
|
return false;
|
|
98
166
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
167
|
+
if (state.prompt && state.prompt[0] === "/" && !state.streaming) {
|
|
168
|
+
skillSubmit();
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
102
171
|
if (state.prompt && !state.streaming) submit();
|
|
103
172
|
return false;
|
|
104
173
|
}
|
|
105
174
|
};
|
|
106
175
|
|
|
107
|
-
const menu = mainMenu(state);
|
|
108
|
-
|
|
109
176
|
cel.init(new ProcessTerminal());
|
|
110
177
|
cel.viewport(() => {
|
|
111
178
|
const layers = [
|
|
@@ -140,6 +207,8 @@ export function initTUI(state: TUIState, leave: (s: string) => void) {
|
|
|
140
207
|
}
|
|
141
208
|
|
|
142
209
|
async function streamAgentTUI(state: TUIState) {
|
|
210
|
+
// Just as a defensive thought, callers should set this ahead of time if doing other
|
|
211
|
+
// ops before starting the stream.
|
|
143
212
|
state.streaming = true;
|
|
144
213
|
|
|
145
214
|
const abortController = new AbortController();
|
|
@@ -152,6 +221,12 @@ async function streamAgentTUI(state: TUIState) {
|
|
|
152
221
|
];
|
|
153
222
|
|
|
154
223
|
let userContent = state.prompt;
|
|
224
|
+
if (state.promptSkill) {
|
|
225
|
+
// TODO: Skill arguments? replace template marks in skill body for arguments?
|
|
226
|
+
// Right now we just appead the user prompt to the skill body
|
|
227
|
+
userContent = `${state.promptSkill}\n${state.prompt}`;
|
|
228
|
+
state.promptSkill = undefined;
|
|
229
|
+
}
|
|
155
230
|
if (state.messages.length === 0) {
|
|
156
231
|
const envReminder = await injectEnvReminder();
|
|
157
232
|
userContent = `${envReminder}\n\n${userContent}`;
|