mini-coder 0.5.12 → 0.5.13
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/BENCHMARK.md +15 -316
- package/PROGRESS.md +1 -2
- package/README.md +53 -21
- package/benchmark-baseline.sh +15 -0
- package/bun.lock +265 -90
- package/package.json +8 -7
- package/skills-lock.json +15 -0
- package/src/agent.ts +20 -0
- package/src/cli.ts +2 -1
- package/src/headless.ts +97 -24
- package/src/index.ts +78 -93
- package/src/input.ts +13 -1
- package/src/mcp.ts +609 -0
- package/src/prompt.ts +2 -12
- package/src/settings.ts +199 -7
- package/src/skills.ts +12 -3
- package/src/submit.ts +17 -0
- package/src/theme.ts +186 -3
- package/src/tool-common.ts +2 -0
- package/src/tool-shell.ts +138 -6
- package/src/tools.ts +4 -0
- package/src/ui/agent.ts +7 -0
- package/src/ui/commands.test.ts +475 -4
- package/src/ui/commands.ts +210 -8
- package/src/ui/conversation.test.ts +252 -27
- package/src/ui/conversation.ts +468 -390
- package/src/ui/help.ts +27 -11
- package/src/ui.ts +230 -75
- package/src/plugins.ts +0 -183
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mini-coder",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.13",
|
|
4
4
|
"description": "A small, fast CLI coding agent",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -18,17 +18,18 @@
|
|
|
18
18
|
"test": "bun test"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@cel-tui/components": "^0.
|
|
22
|
-
"@cel-tui/core": "^0.
|
|
23
|
-
"@cel-tui/types": "^0.
|
|
24
|
-
"@mariozechner/pi-ai": "^0.
|
|
21
|
+
"@cel-tui/components": "^0.8.1",
|
|
22
|
+
"@cel-tui/core": "^0.8.1",
|
|
23
|
+
"@cel-tui/types": "^0.8.1",
|
|
24
|
+
"@mariozechner/pi-ai": "^0.67.68",
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
27
28
|
"@biomejs/biome": "^2.4.12",
|
|
28
29
|
"@types/bun": "^1.3.12",
|
|
29
|
-
"lefthook": "^2.1.
|
|
30
|
+
"lefthook": "^2.1.6",
|
|
30
31
|
"prettier": "^3.8.3",
|
|
31
|
-
"typescript": "^6.0.
|
|
32
|
+
"typescript": "^6.0.3"
|
|
32
33
|
},
|
|
33
34
|
"license": "MIT"
|
|
34
35
|
}
|
package/skills-lock.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"skills": {
|
|
4
|
+
"bun-development": {
|
|
5
|
+
"source": "sickn33/antigravity-awesome-skills",
|
|
6
|
+
"sourceType": "github",
|
|
7
|
+
"computedHash": "87e59a1e7f7fe512333aad64022955501a4e36be6e65487196dfc311e5b786b7"
|
|
8
|
+
},
|
|
9
|
+
"typescript-advanced-types": {
|
|
10
|
+
"source": "wshobson/agents",
|
|
11
|
+
"sourceType": "github",
|
|
12
|
+
"computedHash": "3a3be8c925f96ac4e1280db28a01677e7ca5197f3792de2bbf35ab3bb324b6dd"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/agent.ts
CHANGED
|
@@ -418,6 +418,19 @@ function buildIncompleteAssistantMessage(
|
|
|
418
418
|
};
|
|
419
419
|
}
|
|
420
420
|
|
|
421
|
+
async function resolveStreamResultSoon(
|
|
422
|
+
streamResult: Promise<AssistantMessage>,
|
|
423
|
+
): Promise<AssistantMessage | undefined> {
|
|
424
|
+
const pending = Symbol("pending");
|
|
425
|
+
const result = await Promise.race([
|
|
426
|
+
streamResult,
|
|
427
|
+
new Promise<typeof pending>((resolve) => {
|
|
428
|
+
setTimeout(() => resolve(pending), 0);
|
|
429
|
+
}),
|
|
430
|
+
]);
|
|
431
|
+
return result === pending ? undefined : result;
|
|
432
|
+
}
|
|
433
|
+
|
|
421
434
|
async function streamAssistantMessage(
|
|
422
435
|
opts: Pick<
|
|
423
436
|
RunAgentOpts,
|
|
@@ -455,7 +468,13 @@ async function streamAssistantMessage(
|
|
|
455
468
|
}
|
|
456
469
|
|
|
457
470
|
// `end(result)` resolves the final result without emitting a terminal event.
|
|
471
|
+
// Some wrappers settle that promise on the next task, so give it one more
|
|
472
|
+
// turn before treating the stream as incomplete.
|
|
458
473
|
await Promise.resolve();
|
|
474
|
+
if (!assistantMessage && !settledStreamResult) {
|
|
475
|
+
settledStreamResult = await resolveStreamResultSoon(streamResult);
|
|
476
|
+
}
|
|
477
|
+
|
|
459
478
|
const finalAssistantMessage =
|
|
460
479
|
assistantMessage ??
|
|
461
480
|
settledStreamResult ??
|
|
@@ -596,6 +615,7 @@ function appendToolResultMessage(
|
|
|
596
615
|
toolCallId: toolCall.id,
|
|
597
616
|
toolName: toolCall.name,
|
|
598
617
|
content: result.content,
|
|
618
|
+
...(result.details !== undefined ? { details: result.details } : {}),
|
|
599
619
|
isError: result.isError,
|
|
600
620
|
timestamp: Date.now(),
|
|
601
621
|
};
|
package/src/cli.ts
CHANGED
|
@@ -32,7 +32,8 @@ export interface TtyState {
|
|
|
32
32
|
* Parse supported CLI arguments.
|
|
33
33
|
*
|
|
34
34
|
* Supports `-p, --prompt <text>` for headless one-shot mode and `--json`
|
|
35
|
-
* to stream NDJSON events instead of the default final-text
|
|
35
|
+
* to stream NDJSON events instead of the default final-text mode
|
|
36
|
+
* (stdout final answer plus stderr activity snippets).
|
|
36
37
|
* Unknown flags and positional arguments fail eagerly.
|
|
37
38
|
*
|
|
38
39
|
* @param argv - Process arguments excluding the Bun executable and script path.
|
package/src/headless.ts
CHANGED
|
@@ -12,6 +12,11 @@ import {
|
|
|
12
12
|
type SubmitTurnHooks,
|
|
13
13
|
submitResolvedInput,
|
|
14
14
|
} from "./submit.ts";
|
|
15
|
+
import {
|
|
16
|
+
collapseWhitespaceToNull,
|
|
17
|
+
joinTextBlocks,
|
|
18
|
+
truncateText,
|
|
19
|
+
} from "./text.ts";
|
|
15
20
|
|
|
16
21
|
// ---------------------------------------------------------------------------
|
|
17
22
|
// Types
|
|
@@ -27,31 +32,47 @@ export interface HeadlessRunOptions {
|
|
|
27
32
|
|
|
28
33
|
/** Options for a headless final-text run. */
|
|
29
34
|
export interface HeadlessTextRunOptions {
|
|
35
|
+
/** Optional writer for lightweight assistant-activity snippets. */
|
|
36
|
+
writeActivity?: (text: string) => void | Promise<void>;
|
|
30
37
|
/** Optional writer for the final assistant text output. */
|
|
31
38
|
writeText?: (text: string) => void | Promise<void>;
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
interface HeadlessOutputController {
|
|
35
|
-
/** Queue text for
|
|
42
|
+
/** Queue text for one output stream with broken-pipe handling. */
|
|
36
43
|
write(text: string): void;
|
|
37
|
-
/** Attach
|
|
44
|
+
/** Attach error handlers for the active run. */
|
|
38
45
|
attach(): void;
|
|
39
|
-
/** Remove
|
|
46
|
+
/** Remove error handlers after the run. */
|
|
40
47
|
detach(): void;
|
|
41
48
|
/** Wait for queued writes and resolve the final stop reason. */
|
|
42
49
|
finalize(stopReason: HeadlessStopReason): Promise<HeadlessStopReason>;
|
|
43
50
|
}
|
|
44
51
|
|
|
52
|
+
interface HeadlessProcessStream {
|
|
53
|
+
/** Register an output-stream error handler. */
|
|
54
|
+
on(event: "error", listener: (error: unknown) => void): void;
|
|
55
|
+
/** Remove an output-stream error handler. */
|
|
56
|
+
off(event: "error", listener: (error: unknown) => void): void;
|
|
57
|
+
/** Write a text chunk to the stream. */
|
|
58
|
+
write(text: string, callback?: () => void): boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const HEADLESS_ACTIVITY_MAX_CHARS = 160;
|
|
62
|
+
|
|
45
63
|
// ---------------------------------------------------------------------------
|
|
46
64
|
// Helpers
|
|
47
65
|
// ---------------------------------------------------------------------------
|
|
48
66
|
|
|
49
|
-
function defaultWrite(
|
|
67
|
+
function defaultWrite(
|
|
68
|
+
stream: HeadlessProcessStream,
|
|
69
|
+
text: string,
|
|
70
|
+
): Promise<void> {
|
|
50
71
|
return new Promise((resolve, reject) => {
|
|
51
72
|
let settled = false;
|
|
52
73
|
|
|
53
74
|
const cleanup = (): void => {
|
|
54
|
-
|
|
75
|
+
stream.off("error", handleError);
|
|
55
76
|
};
|
|
56
77
|
|
|
57
78
|
const settle = (callback: () => void): void => {
|
|
@@ -69,9 +90,9 @@ function defaultWrite(text: string): Promise<void> {
|
|
|
69
90
|
});
|
|
70
91
|
};
|
|
71
92
|
|
|
72
|
-
|
|
93
|
+
stream.on("error", handleError);
|
|
73
94
|
try {
|
|
74
|
-
|
|
95
|
+
stream.write(text, () => {
|
|
75
96
|
settle(resolve);
|
|
76
97
|
});
|
|
77
98
|
} catch (error) {
|
|
@@ -142,6 +163,17 @@ function extractAssistantText(message: AssistantMessage | null): string {
|
|
|
142
163
|
.join("");
|
|
143
164
|
}
|
|
144
165
|
|
|
166
|
+
function extractAssistantActivitySnippet(
|
|
167
|
+
message: AssistantMessage,
|
|
168
|
+
): string | null {
|
|
169
|
+
if (!message.content.some((block) => block.type === "toolCall")) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const text = collapseWhitespaceToNull(joinTextBlocks(message.content));
|
|
174
|
+
return text ? truncateText(text, HEADLESS_ACTIVITY_MAX_CHARS) : null;
|
|
175
|
+
}
|
|
176
|
+
|
|
145
177
|
function shouldWriteHeadlessJsonEvent(event: AgentEvent): boolean {
|
|
146
178
|
switch (event.type) {
|
|
147
179
|
case "user_message":
|
|
@@ -158,12 +190,17 @@ function shouldWriteHeadlessJsonEvent(event: AgentEvent): boolean {
|
|
|
158
190
|
|
|
159
191
|
function createHeadlessOutputController(
|
|
160
192
|
state: AppState,
|
|
193
|
+
stream: HeadlessProcessStream,
|
|
161
194
|
writeImpl: (text: string) => void | Promise<void>,
|
|
195
|
+
options?: {
|
|
196
|
+
attachSigint?: boolean;
|
|
197
|
+
},
|
|
162
198
|
): HeadlessOutputController {
|
|
163
199
|
let brokenPipe = false;
|
|
164
200
|
let outputError: unknown = null;
|
|
165
201
|
let pendingWrite = Promise.resolve();
|
|
166
202
|
const sigintHandler = createSigintHandler(state);
|
|
203
|
+
const attachSigint = options?.attachSigint ?? true;
|
|
167
204
|
|
|
168
205
|
const stopForBrokenPipe = (): void => {
|
|
169
206
|
if (brokenPipe) {
|
|
@@ -183,7 +220,7 @@ function createHeadlessOutputController(
|
|
|
183
220
|
state.abortController?.abort();
|
|
184
221
|
};
|
|
185
222
|
|
|
186
|
-
const
|
|
223
|
+
const streamErrorHandler = (error: unknown): void => {
|
|
187
224
|
failOutput(error);
|
|
188
225
|
};
|
|
189
226
|
|
|
@@ -206,12 +243,16 @@ function createHeadlessOutputController(
|
|
|
206
243
|
});
|
|
207
244
|
},
|
|
208
245
|
attach() {
|
|
209
|
-
|
|
210
|
-
|
|
246
|
+
stream.on("error", streamErrorHandler);
|
|
247
|
+
if (attachSigint) {
|
|
248
|
+
process.on("SIGINT", sigintHandler);
|
|
249
|
+
}
|
|
211
250
|
},
|
|
212
251
|
detach() {
|
|
213
|
-
|
|
214
|
-
|
|
252
|
+
stream.off("error", streamErrorHandler);
|
|
253
|
+
if (attachSigint) {
|
|
254
|
+
process.off("SIGINT", sigintHandler);
|
|
255
|
+
}
|
|
215
256
|
},
|
|
216
257
|
async finalize(stopReason) {
|
|
217
258
|
await pendingWrite;
|
|
@@ -244,7 +285,8 @@ export async function runHeadlessPrompt(
|
|
|
244
285
|
const content = resolveHeadlessContent(state, rawInput);
|
|
245
286
|
const output = createHeadlessOutputController(
|
|
246
287
|
state,
|
|
247
|
-
|
|
288
|
+
process.stdout,
|
|
289
|
+
options?.writeLine ?? ((line) => defaultWrite(process.stdout, `${line}\n`)),
|
|
248
290
|
);
|
|
249
291
|
const hooks: SubmitTurnHooks = {
|
|
250
292
|
onEvent: (event) => {
|
|
@@ -270,11 +312,12 @@ export async function runHeadlessPrompt(
|
|
|
270
312
|
}
|
|
271
313
|
|
|
272
314
|
/**
|
|
273
|
-
* Run a single headless prompt to completion and write
|
|
315
|
+
* Run a single headless prompt to completion and write the final assistant text.
|
|
274
316
|
*
|
|
275
317
|
* The raw input is parsed with the same rules as interactive input. Slash
|
|
276
|
-
* commands are rejected in headless mode.
|
|
277
|
-
*
|
|
318
|
+
* commands are rejected in headless mode. The final assistant text is written
|
|
319
|
+
* to stdout, while lightweight assistant commentary snippets from tool-use
|
|
320
|
+
* turns are written to stderr.
|
|
278
321
|
*
|
|
279
322
|
* @param state - Mutable application state for the run.
|
|
280
323
|
* @param rawInput - Exact raw prompt text supplied by the user.
|
|
@@ -287,20 +330,43 @@ export async function runHeadlessPromptText(
|
|
|
287
330
|
options?: HeadlessTextRunOptions,
|
|
288
331
|
): Promise<HeadlessStopReason> {
|
|
289
332
|
const content = resolveHeadlessContent(state, rawInput);
|
|
290
|
-
const
|
|
333
|
+
const finalOutput = createHeadlessOutputController(
|
|
334
|
+
state,
|
|
335
|
+
process.stdout,
|
|
336
|
+
options?.writeText ?? ((text) => defaultWrite(process.stdout, text)),
|
|
337
|
+
);
|
|
338
|
+
const activityOutput = createHeadlessOutputController(
|
|
291
339
|
state,
|
|
292
|
-
|
|
340
|
+
process.stderr,
|
|
341
|
+
options?.writeActivity ?? ((text) => defaultWrite(process.stderr, text)),
|
|
342
|
+
{ attachSigint: false },
|
|
293
343
|
);
|
|
294
344
|
let finalAssistantMessage: AssistantMessage | null = null;
|
|
295
345
|
const hooks: SubmitTurnHooks = {
|
|
296
346
|
onEvent: (event) => {
|
|
297
|
-
|
|
298
|
-
|
|
347
|
+
switch (event.type) {
|
|
348
|
+
case "assistant_message": {
|
|
349
|
+
const activitySnippet = extractAssistantActivitySnippet(
|
|
350
|
+
event.message,
|
|
351
|
+
);
|
|
352
|
+
if (activitySnippet) {
|
|
353
|
+
activityOutput.write(`${activitySnippet}\n`);
|
|
354
|
+
}
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
case "done":
|
|
358
|
+
case "error":
|
|
359
|
+
case "aborted":
|
|
360
|
+
finalAssistantMessage = event.message;
|
|
361
|
+
return;
|
|
362
|
+
default:
|
|
363
|
+
return;
|
|
299
364
|
}
|
|
300
365
|
},
|
|
301
366
|
};
|
|
302
367
|
|
|
303
|
-
|
|
368
|
+
finalOutput.attach();
|
|
369
|
+
activityOutput.attach();
|
|
304
370
|
try {
|
|
305
371
|
const stopReason = await submitResolvedInput(
|
|
306
372
|
rawInput,
|
|
@@ -310,10 +376,17 @@ export async function runHeadlessPromptText(
|
|
|
310
376
|
);
|
|
311
377
|
const finalText = extractAssistantText(finalAssistantMessage);
|
|
312
378
|
if (finalText.length > 0) {
|
|
313
|
-
|
|
379
|
+
finalOutput.write(finalText);
|
|
314
380
|
}
|
|
315
|
-
|
|
381
|
+
const [finalStopReason, activityStopReason] = await Promise.all([
|
|
382
|
+
finalOutput.finalize(stopReason),
|
|
383
|
+
activityOutput.finalize(stopReason),
|
|
384
|
+
]);
|
|
385
|
+
return finalStopReason === "stop" || activityStopReason === "stop"
|
|
386
|
+
? "stop"
|
|
387
|
+
: stopReason;
|
|
316
388
|
} finally {
|
|
317
|
-
|
|
389
|
+
activityOutput.detach();
|
|
390
|
+
finalOutput.detach();
|
|
318
391
|
}
|
|
319
392
|
}
|