mini-coder 0.5.14 → 0.6.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.
Files changed (70) hide show
  1. package/README.md +26 -109
  2. package/bin/mc.ts +8 -11
  3. package/bun.lock +79 -269
  4. package/nono-mini-coder.json +42 -0
  5. package/package.json +17 -22
  6. package/src/agent.ts +243 -1403
  7. package/src/args.ts +289 -0
  8. package/src/headless.ts +41 -359
  9. package/src/index.ts +29 -1016
  10. package/src/oauth.ts +117 -0
  11. package/src/prompt.ts +219 -284
  12. package/src/session.ts +55 -1306
  13. package/src/shared.ts +117 -38
  14. package/src/tool-bash.ts +110 -0
  15. package/src/tool-edit.ts +133 -0
  16. package/src/tool-read.ts +80 -293
  17. package/src/tui-components.ts +150 -0
  18. package/src/tui-conversation.ts +271 -0
  19. package/src/tui-editor.ts +29 -0
  20. package/src/tui-overlay.ts +403 -0
  21. package/src/tui.ts +228 -0
  22. package/src/types.ts +164 -0
  23. package/tsconfig.json +17 -0
  24. package/BENCHMARK.md +0 -107
  25. package/LICENSE +0 -9
  26. package/PROGRESS.md +0 -5
  27. package/assets/icon-1-minimal.svg +0 -31
  28. package/assets/icon-2-dark-terminal.svg +0 -48
  29. package/assets/icon-3-gradient-modern.svg +0 -45
  30. package/assets/icon-4-filled-bold.svg +0 -54
  31. package/assets/icon-5-community-badge.svg +0 -63
  32. package/assets/mc-claude-smart.png +0 -0
  33. package/assets/mc-gpt-smart.png +0 -0
  34. package/assets/preview-0-5-0.png +0 -0
  35. package/assets/preview.gif +0 -0
  36. package/benchmark-baseline.sh +0 -15
  37. package/benchmark-loop.sh +0 -19
  38. package/skills-lock.json +0 -15
  39. package/src/assistant-output.ts +0 -73
  40. package/src/cli.ts +0 -134
  41. package/src/delegation.ts +0 -238
  42. package/src/errors.ts +0 -15
  43. package/src/git.ts +0 -247
  44. package/src/input.ts +0 -168
  45. package/src/mcp.ts +0 -609
  46. package/src/paths.ts +0 -37
  47. package/src/session-message.ts +0 -385
  48. package/src/settings.ts +0 -449
  49. package/src/skills.ts +0 -271
  50. package/src/submit.ts +0 -376
  51. package/src/text.ts +0 -71
  52. package/src/theme.ts +0 -330
  53. package/src/tool-common.ts +0 -93
  54. package/src/tool-delegate.ts +0 -125
  55. package/src/tool-grep.ts +0 -606
  56. package/src/tool-shell.ts +0 -1051
  57. package/src/tools.ts +0 -1179
  58. package/src/ui/agent.ts +0 -320
  59. package/src/ui/commands.test.ts +0 -957
  60. package/src/ui/commands.ts +0 -848
  61. package/src/ui/conversation.test.ts +0 -585
  62. package/src/ui/conversation.ts +0 -1836
  63. package/src/ui/help.ts +0 -158
  64. package/src/ui/input.test.ts +0 -64
  65. package/src/ui/input.ts +0 -138
  66. package/src/ui/overlay.ts +0 -59
  67. package/src/ui/runtime.ts +0 -69
  68. package/src/ui/status.ts +0 -220
  69. package/src/ui.ts +0 -1190
  70. package/src/version.ts +0 -48
@@ -1,585 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import type { Node, TextNode } from "@cel-tui/types";
3
- import type { AssistantMessage, ToolResultMessage } from "@mariozechner/pi-ai";
4
- import { DEFAULT_THEME } from "../theme.ts";
5
- import {
6
- buildConversationLogNodes,
7
- renderAssistantMessage,
8
- renderToolResult,
9
- } from "./conversation.ts";
10
-
11
- function collectInlineText(node: Node): string {
12
- if (node.type === "text") {
13
- return node.content;
14
- }
15
- if (node.type === "textinput") {
16
- return "";
17
- }
18
- return node.children.map((child) => collectInlineText(child)).join("");
19
- }
20
-
21
- function collectRenderedLines(node: Node | null): string[] {
22
- if (!node) {
23
- return [];
24
- }
25
- if (node.type === "text") {
26
- return [node.content];
27
- }
28
- if (node.type === "textinput") {
29
- return [];
30
- }
31
- if (
32
- node.type === "hstack" &&
33
- node.children.length === 2 &&
34
- node.children[0]?.type === "text" &&
35
- node.children[1]?.type === "vstack"
36
- ) {
37
- const prefix = node.children[0].content;
38
- return collectRenderedLines(node.children[1]).map(
39
- (line) => `${prefix}${line}`,
40
- );
41
- }
42
- if (node.type === "hstack") {
43
- return [collectInlineText(node)];
44
- }
45
- return node.children.flatMap((child) => collectRenderedLines(child));
46
- }
47
-
48
- function collectTextNodes(node: Node | null): TextNode[] {
49
- if (!node || node.type === "textinput") {
50
- return [];
51
- }
52
- if (node.type === "text") {
53
- return [node];
54
- }
55
- return node.children.flatMap((child) => collectTextNodes(child));
56
- }
57
-
58
- function makeAssistantToolCallMessage(): AssistantMessage {
59
- return {
60
- role: "assistant",
61
- content: [
62
- {
63
- type: "toolCall",
64
- id: "call-read",
65
- name: "read",
66
- arguments: { path: "hint.txt", limit: 3 },
67
- },
68
- ],
69
- api: "anthropic-messages",
70
- provider: "anthropic",
71
- model: "claude-sonnet-4-20250514",
72
- usage: {
73
- input: 0,
74
- output: 0,
75
- cacheRead: 0,
76
- cacheWrite: 0,
77
- totalTokens: 0,
78
- cost: {
79
- input: 0,
80
- output: 0,
81
- cacheRead: 0,
82
- cacheWrite: 0,
83
- total: 0,
84
- },
85
- },
86
- stopReason: "toolUse",
87
- timestamp: 1,
88
- };
89
- }
90
-
91
- function makeReadToolResultMessage(
92
- content: ToolResultMessage["content"],
93
- ): ToolResultMessage {
94
- return {
95
- role: "toolResult",
96
- toolCallId: "call-read",
97
- toolName: "read",
98
- content,
99
- isError: false,
100
- timestamp: 2,
101
- };
102
- }
103
-
104
- describe("ui/conversation", () => {
105
- test("read tool-call previews render structured arguments instead of raw JSON", () => {
106
- const node = renderAssistantMessage(
107
- {
108
- content: [
109
- {
110
- type: "toolCall",
111
- id: "call-read",
112
- name: "read",
113
- arguments: {
114
- path: "src/ui/conversation.ts",
115
- offset: 820,
116
- limit: 80,
117
- },
118
- },
119
- ],
120
- },
121
- {
122
- showReasoning: true,
123
- verbose: false,
124
- theme: DEFAULT_THEME,
125
- cwd: "/tmp/project",
126
- previewWidth: 80,
127
- },
128
- );
129
-
130
- const lines = collectRenderedLines(node);
131
- expect(lines).toContain("│ read ->");
132
- expect(lines).toContain("│ src/ui/conversation.ts");
133
- expect(lines).toContain("│ offset: 820");
134
- expect(lines).toContain("│ limit: 80");
135
- expect(lines.join("\n")).not.toContain('"path"');
136
- });
137
-
138
- test("shell tool-call previews render the command instead of raw JSON", () => {
139
- const node = renderAssistantMessage(
140
- {
141
- content: [
142
- {
143
- type: "toolCall",
144
- id: "call-shell",
145
- name: "shell",
146
- arguments: {
147
- command: 'if true; then echo "$HOME"; fi',
148
- },
149
- },
150
- ],
151
- },
152
- {
153
- showReasoning: true,
154
- verbose: true,
155
- theme: DEFAULT_THEME,
156
- cwd: "/tmp/project",
157
- previewWidth: 80,
158
- },
159
- );
160
-
161
- const lines = collectRenderedLines(node);
162
- expect(lines).toContain("│ shell ->");
163
- expect(lines.join("\n")).toContain('if true; then echo "$HOME"; fi');
164
- expect(lines.join("\n")).not.toContain('"command"');
165
- });
166
-
167
- test("compact shell tool-call previews only render the visible tail slice", () => {
168
- const command = Array.from(
169
- { length: 14 },
170
- (_, index) => `echo line ${index + 1}`,
171
- ).join("\n");
172
-
173
- const compactLines = collectRenderedLines(
174
- renderAssistantMessage(
175
- {
176
- content: [
177
- {
178
- type: "toolCall",
179
- id: "call-shell",
180
- name: "shell",
181
- arguments: { command },
182
- },
183
- ],
184
- },
185
- {
186
- showReasoning: true,
187
- verbose: false,
188
- theme: DEFAULT_THEME,
189
- cwd: "/tmp/project",
190
- previewWidth: 80,
191
- },
192
- ),
193
- );
194
- const verboseLines = collectRenderedLines(
195
- renderAssistantMessage(
196
- {
197
- content: [
198
- {
199
- type: "toolCall",
200
- id: "call-shell",
201
- name: "shell",
202
- arguments: { command },
203
- },
204
- ],
205
- },
206
- {
207
- showReasoning: true,
208
- verbose: true,
209
- theme: DEFAULT_THEME,
210
- cwd: "/tmp/project",
211
- previewWidth: 80,
212
- },
213
- ),
214
- );
215
-
216
- expect(compactLines).toContain("│ shell ->");
217
- expect(compactLines).toContain("│ echo line 14");
218
- expect(compactLines).toContain("│ And 6 lines more");
219
- expect(compactLines).not.toContain("│ echo line 1");
220
- expect(verboseLines).toContain("│ echo line 1");
221
- expect(compactLines.length).toBeLessThan(verboseLines.length);
222
- });
223
-
224
- test("MCP tool-call previews honor verbose mode", () => {
225
- const args = {
226
- query: "routing",
227
- section: "guides",
228
- limit: 10,
229
- offset: 0,
230
- filter1: "alpha",
231
- filter2: "beta",
232
- filter3: "gamma",
233
- filter4: "delta",
234
- filter5: "epsilon",
235
- filter6: "zeta",
236
- filter7: "eta",
237
- filter8: "theta",
238
- };
239
-
240
- const compactLines = collectRenderedLines(
241
- renderAssistantMessage(
242
- {
243
- content: [
244
- {
245
- type: "toolCall",
246
- id: "call-mcp",
247
- name: "docs__search",
248
- arguments: args,
249
- },
250
- ],
251
- },
252
- {
253
- showReasoning: true,
254
- verbose: false,
255
- theme: DEFAULT_THEME,
256
- cwd: "/tmp/project",
257
- previewWidth: 80,
258
- },
259
- ),
260
- );
261
- const verboseLines = collectRenderedLines(
262
- renderAssistantMessage(
263
- {
264
- content: [
265
- {
266
- type: "toolCall",
267
- id: "call-mcp",
268
- name: "docs__search",
269
- arguments: args,
270
- },
271
- ],
272
- },
273
- {
274
- showReasoning: true,
275
- verbose: true,
276
- theme: DEFAULT_THEME,
277
- cwd: "/tmp/project",
278
- previewWidth: 80,
279
- },
280
- ),
281
- );
282
-
283
- expect(compactLines).toContain("│ docs__search ->");
284
- expect(compactLines.some((line) => line.includes("And "))).toBe(true);
285
- expect(compactLines.join("\n")).not.toContain('"query": "routing"');
286
- expect(verboseLines.join("\n")).toContain('"query": "routing"');
287
- expect(compactLines.length).toBeLessThan(verboseLines.length);
288
- });
289
-
290
- test("shell tool results render structured stdout/stderr details without stderr labels or error styling", () => {
291
- const stdout = Array.from(
292
- { length: 12 },
293
- (_, index) => `line ${index + 1}`,
294
- ).join("\n");
295
- const node = renderToolResult(
296
- "shell",
297
- { command: "run-tests" },
298
- "Exit code: 1\nlegacy text should be ignored when details exist",
299
- true,
300
- {
301
- showReasoning: true,
302
- verbose: false,
303
- theme: DEFAULT_THEME,
304
- cwd: "/tmp/project",
305
- previewWidth: 48,
306
- },
307
- {
308
- stdout,
309
- stderr: "boom",
310
- exitCode: 1,
311
- },
312
- );
313
- const lines = collectRenderedLines(node);
314
- const textNodes = collectTextNodes(node);
315
-
316
- expect(lines).toContain("│ shell <-");
317
- expect(lines).toContain("│ boom");
318
- expect(lines).toContain("│ exit 1");
319
- expect(lines).toContain("│ And 6 lines more");
320
- expect(lines.join("\n")).not.toContain("stderr:");
321
- expect(lines.join("\n")).not.toContain("legacy text should be ignored");
322
- expect(
323
- textNodes.some(
324
- (textNode) =>
325
- textNode.content === "boom" &&
326
- textNode.props.fgColor === DEFAULT_THEME.toolText,
327
- ),
328
- ).toBe(true);
329
- expect(
330
- textNodes.some(
331
- (textNode) =>
332
- textNode.content === "exit 1" &&
333
- textNode.props.fgColor === DEFAULT_THEME.toolText,
334
- ),
335
- ).toBe(true);
336
- });
337
-
338
- test("shell tool results still render legacy flattened results from persisted history without stderr labels", () => {
339
- const node = renderToolResult(
340
- "shell",
341
- { command: "run-tests" },
342
- "Exit code: 1\nout\n\n[stderr]\nerr",
343
- true,
344
- {
345
- showReasoning: true,
346
- verbose: true,
347
- theme: DEFAULT_THEME,
348
- cwd: "/tmp/project",
349
- previewWidth: 80,
350
- },
351
- );
352
- const lines = collectRenderedLines(node);
353
- const textNodes = collectTextNodes(node);
354
-
355
- expect(lines).toContain("│ out");
356
- expect(lines).toContain("│ err");
357
- expect(lines).toContain("│ exit 1");
358
- expect(lines.join("\n")).not.toContain("stderr:");
359
- expect(lines.join("\n")).not.toContain("[stderr]");
360
- expect(lines.join("\n")).not.toContain("Exit code:");
361
- expect(
362
- textNodes.some(
363
- (textNode) =>
364
- textNode.content === "err" &&
365
- textNode.props.fgColor === DEFAULT_THEME.toolText,
366
- ),
367
- ).toBe(true);
368
- expect(
369
- textNodes.some(
370
- (textNode) =>
371
- textNode.content === "exit 1" &&
372
- textNode.props.fgColor === DEFAULT_THEME.toolText,
373
- ),
374
- ).toBe(true);
375
- });
376
-
377
- test("read tool results include the resolved path, hide model paging hints, and render fewer body lines when verbose is off", () => {
378
- const fileBody =
379
- Array.from({ length: 14 }, (_, index) => `line ${index + 1}`).join("\n") +
380
- "\n\n[use offset=14 limit=14 to continue]";
381
-
382
- const compactLines = collectRenderedLines(
383
- renderToolResult(
384
- "read",
385
- { path: "src/ui/conversation.ts" },
386
- fileBody,
387
- false,
388
- {
389
- showReasoning: true,
390
- verbose: false,
391
- theme: DEFAULT_THEME,
392
- cwd: "/tmp/project",
393
- previewWidth: 48,
394
- },
395
- ),
396
- );
397
- const verboseLines = collectRenderedLines(
398
- renderToolResult(
399
- "read",
400
- { path: "src/ui/conversation.ts" },
401
- fileBody,
402
- false,
403
- {
404
- showReasoning: true,
405
- verbose: true,
406
- theme: DEFAULT_THEME,
407
- cwd: "/tmp/project",
408
- previewWidth: 48,
409
- },
410
- ),
411
- );
412
-
413
- expect(compactLines[0]).toBe(
414
- "│ read <- /tmp/project/src/ui/conversation.ts",
415
- );
416
- expect(compactLines.join("\n")).not.toContain("Use offset=14 limit=14");
417
- expect(verboseLines.join("\n")).not.toContain("Use offset=14 limit=14");
418
- expect(verboseLines).toContain("│ line 14");
419
- expect(compactLines.length).toBeLessThan(verboseLines.length);
420
- });
421
-
422
- test("read tool results preserve literal continuation-looking lines from the file body without showing the model paging hint", () => {
423
- const nodes = buildConversationLogNodes(
424
- {
425
- messages: [
426
- makeAssistantToolCallMessage(),
427
- makeReadToolResultMessage([
428
- {
429
- type: "text",
430
- text: "alpha\n\n[use offset=99 limit=10 to continue]\n",
431
- },
432
- {
433
- type: "text",
434
- text: "[use offset=3 limit=3 to continue]",
435
- },
436
- ]),
437
- ],
438
- showReasoning: true,
439
- verbose: true,
440
- theme: DEFAULT_THEME,
441
- cwd: "/tmp/project",
442
- versionLabel: "test",
443
- },
444
- {
445
- isStreaming: false,
446
- content: [],
447
- pendingToolResults: [],
448
- },
449
- 0,
450
- 80,
451
- );
452
-
453
- const lines = nodes.flatMap((node) => collectRenderedLines(node));
454
- expect(lines).toContain("│ [use offset=99 limit=10 to continue]");
455
- expect(lines.join("\n")).not.toContain("Use offset=3 limit=3 to continue.");
456
- });
457
-
458
- test("read tool fallback rendering normalizes CRLF line endings", () => {
459
- const lines = collectRenderedLines(
460
- renderToolResult(
461
- "read",
462
- { path: "notes.txt" },
463
- "alpha\r\nbeta\r\n",
464
- false,
465
- {
466
- showReasoning: true,
467
- verbose: true,
468
- theme: DEFAULT_THEME,
469
- cwd: "/tmp/project",
470
- previewWidth: 80,
471
- },
472
- ),
473
- );
474
-
475
- expect(lines).toContain("│ alpha");
476
- expect(lines).toContain("│ beta");
477
- expect(lines.some((line) => line.includes("\r"))).toBe(false);
478
- });
479
-
480
- test("read tool results use direct syntax-highlight rendering without chunking long tokens", () => {
481
- const node = renderToolResult(
482
- "read",
483
- { path: "src/example.ts" },
484
- "const supercalifragilisticexpialidociousIdentifier = 42",
485
- false,
486
- {
487
- showReasoning: true,
488
- verbose: true,
489
- theme: DEFAULT_THEME,
490
- cwd: "/tmp/project",
491
- previewWidth: 20,
492
- },
493
- );
494
-
495
- expect(
496
- collectTextNodes(node).some(
497
- (textNode) =>
498
- textNode.content === "supercalifragilisticexpialidociousIdentifier",
499
- ),
500
- ).toBe(true);
501
- });
502
-
503
- test("grep tool results render grouped files and lines instead of raw JSON", () => {
504
- const resultText = JSON.stringify(
505
- {
506
- limit: 10,
507
- truncated: false,
508
- files: [
509
- {
510
- path: "src/ui/conversation.ts",
511
- lines: [
512
- {
513
- kind: "match",
514
- lineNumber: 857,
515
- text: "function renderToolBlock(\n",
516
- },
517
- {
518
- kind: "context",
519
- lineNumber: 858,
520
- text: " spec: ToolBlockSpec,\n",
521
- },
522
- ],
523
- },
524
- ],
525
- },
526
- null,
527
- 2,
528
- );
529
-
530
- const node = renderToolResult(
531
- "grep",
532
- { pattern: "renderToolBlock" },
533
- resultText,
534
- false,
535
- {
536
- showReasoning: true,
537
- verbose: true,
538
- theme: DEFAULT_THEME,
539
- cwd: "/tmp/project",
540
- previewWidth: 80,
541
- },
542
- );
543
-
544
- const lines = collectRenderedLines(node);
545
- expect(lines).toContain("│ grep <-");
546
- expect(lines).toContain("│ src/ui/conversation.ts");
547
- expect(lines).toContain("│ 857: function renderToolBlock(");
548
- expect(lines).toContain("│ 858: spec: ToolBlockSpec,");
549
- expect(lines.join("\n")).not.toContain('"files"');
550
- expect(lines.join("\n")).not.toContain('"kind"');
551
- });
552
-
553
- test("MCP tool results honor verbose mode", () => {
554
- const output = Array.from(
555
- { length: 14 },
556
- (_, index) => `result ${index + 1}`,
557
- ).join("\n");
558
-
559
- const compactLines = collectRenderedLines(
560
- renderToolResult("docs__search", { query: "routing" }, output, false, {
561
- showReasoning: true,
562
- verbose: false,
563
- theme: DEFAULT_THEME,
564
- cwd: "/tmp/project",
565
- previewWidth: 80,
566
- }),
567
- );
568
- const verboseLines = collectRenderedLines(
569
- renderToolResult("docs__search", { query: "routing" }, output, false, {
570
- showReasoning: true,
571
- verbose: true,
572
- theme: DEFAULT_THEME,
573
- cwd: "/tmp/project",
574
- previewWidth: 80,
575
- }),
576
- );
577
-
578
- expect(compactLines).toContain("│ docs__search <-");
579
- expect(compactLines).toContain("│ result 14");
580
- expect(compactLines).toContain("│ And 6 lines more");
581
- expect(compactLines).not.toContain("│ result 1");
582
- expect(verboseLines).toContain("│ result 1");
583
- expect(compactLines.length).toBeLessThan(verboseLines.length);
584
- });
585
- });