mini-coder 0.5.13 → 0.6.0

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 (67) hide show
  1. package/README.md +25 -108
  2. package/bin/mc.ts +8 -11
  3. package/bun.lock +79 -269
  4. package/package.json +17 -22
  5. package/src/agent.ts +242 -915
  6. package/src/args.ts +289 -0
  7. package/src/headless.ts +43 -385
  8. package/src/index.ts +29 -836
  9. package/src/oauth.ts +117 -0
  10. package/src/prompt.ts +227 -276
  11. package/src/session.ts +57 -961
  12. package/src/shared.ts +117 -38
  13. package/src/tool-bash.ts +110 -0
  14. package/src/tool-edit.ts +133 -0
  15. package/src/tool-task.ts +114 -0
  16. package/src/tui-components.ts +150 -0
  17. package/src/tui-conversation.ts +262 -0
  18. package/src/tui-editor.ts +29 -0
  19. package/src/tui-overlay.ts +403 -0
  20. package/src/tui.ts +236 -0
  21. package/src/types.ts +160 -0
  22. package/tsconfig.json +17 -0
  23. package/BENCHMARK.md +0 -107
  24. package/LICENSE +0 -9
  25. package/PROGRESS.md +0 -4
  26. package/assets/icon-1-minimal.svg +0 -31
  27. package/assets/icon-2-dark-terminal.svg +0 -48
  28. package/assets/icon-3-gradient-modern.svg +0 -45
  29. package/assets/icon-4-filled-bold.svg +0 -54
  30. package/assets/icon-5-community-badge.svg +0 -63
  31. package/assets/mc-claude-smart.png +0 -0
  32. package/assets/mc-gpt-smart.png +0 -0
  33. package/assets/preview-0-5-0.png +0 -0
  34. package/assets/preview.gif +0 -0
  35. package/benchmark-baseline.sh +0 -15
  36. package/benchmark-loop.sh +0 -19
  37. package/skills-lock.json +0 -15
  38. package/src/cli.ts +0 -134
  39. package/src/errors.ts +0 -15
  40. package/src/git.ts +0 -247
  41. package/src/input.ts +0 -168
  42. package/src/mcp.ts +0 -609
  43. package/src/paths.ts +0 -37
  44. package/src/session-message.ts +0 -393
  45. package/src/settings.ts +0 -449
  46. package/src/skills.ts +0 -271
  47. package/src/submit.ts +0 -371
  48. package/src/text.ts +0 -71
  49. package/src/theme.ts +0 -330
  50. package/src/tool-common.ts +0 -93
  51. package/src/tool-grep.ts +0 -606
  52. package/src/tool-read.ts +0 -313
  53. package/src/tool-shell.ts +0 -1001
  54. package/src/tools.ts +0 -854
  55. package/src/ui/agent.ts +0 -317
  56. package/src/ui/commands.test.ts +0 -913
  57. package/src/ui/commands.ts +0 -834
  58. package/src/ui/conversation.test.ts +0 -585
  59. package/src/ui/conversation.ts +0 -1836
  60. package/src/ui/help.ts +0 -158
  61. package/src/ui/input.test.ts +0 -64
  62. package/src/ui/input.ts +0 -138
  63. package/src/ui/overlay.ts +0 -59
  64. package/src/ui/runtime.ts +0 -69
  65. package/src/ui/status.ts +0 -220
  66. package/src/ui.ts +0 -1190
  67. package/src/version.ts +0 -48
@@ -1,393 +0,0 @@
1
- /**
2
- * Persisted session-message types and parsing/validation helpers.
3
- *
4
- * @module
5
- */
6
-
7
- import type {
8
- AssistantMessage,
9
- Message,
10
- ToolResultMessage,
11
- UserMessage,
12
- } from "@mariozechner/pi-ai";
13
- import {
14
- readBoolean,
15
- readFiniteNumber,
16
- readString,
17
- toRecord,
18
- } from "./shared.ts";
19
- import { collapseWhitespaceToNull, joinTextBlocks } from "./text.ts";
20
- import type { TodoItem } from "./tools.ts";
21
-
22
- /** Rich-text format hints supported by persisted UI info messages. */
23
- export type UiInfoFormat = "markdown";
24
-
25
- /** A persisted UI-only info message shown in the conversation log. */
26
- export interface UiInfoMessage {
27
- /** Identifies this as an internal UI message. */
28
- role: "ui";
29
- /** UI message category for rendering and future behavior. */
30
- kind: "info";
31
- /** Display text shown in the conversation log. */
32
- content: string;
33
- /** Optional rich-text format hint for the content. */
34
- format?: UiInfoFormat;
35
- /** Unix timestamp in milliseconds. */
36
- timestamp: number;
37
- }
38
-
39
- /** A persisted UI-only todo snapshot shown in the conversation log. */
40
- export interface UiTodoMessage {
41
- /** Identifies this as an internal UI message. */
42
- role: "ui";
43
- /** UI message category for rendering and future behavior. */
44
- kind: "todo";
45
- /** Todo snapshot rendered in the conversation pane. */
46
- todos: TodoItem[];
47
- /** Unix timestamp in milliseconds. */
48
- timestamp: number;
49
- }
50
-
51
- /** A persisted UI-only message shown in the conversation log. */
52
- export type UiMessage = UiInfoMessage | UiTodoMessage;
53
-
54
- /** Any message persisted in session history. */
55
- export type PersistedMessage = Message | UiMessage;
56
-
57
- const EMPTY_ASSISTANT_USAGE: AssistantMessage["usage"] = {
58
- input: 0,
59
- output: 0,
60
- cacheRead: 0,
61
- cacheWrite: 0,
62
- totalTokens: 0,
63
- cost: {
64
- input: 0,
65
- output: 0,
66
- cacheRead: 0,
67
- cacheWrite: 0,
68
- total: 0,
69
- },
70
- };
71
-
72
- function getMultipartUserPreview(
73
- content: Extract<Message, { role: "user" }>["content"],
74
- ): string | null {
75
- if (typeof content === "string") {
76
- return collapseWhitespaceToNull(content);
77
- }
78
-
79
- return collapseWhitespaceToNull(joinTextBlocks(content));
80
- }
81
-
82
- function isTextContentBlock(
83
- value: unknown,
84
- ): value is { type: "text"; text: string } {
85
- const record = toRecord(value);
86
- return (
87
- record !== null &&
88
- record.type === "text" &&
89
- readString(record, "text") !== null
90
- );
91
- }
92
-
93
- function isImageContentBlock(
94
- value: unknown,
95
- ): value is { type: "image"; data: string; mimeType: string } {
96
- const record = toRecord(value);
97
- return (
98
- record !== null &&
99
- record.type === "image" &&
100
- readString(record, "data") !== null &&
101
- readString(record, "mimeType") !== null
102
- );
103
- }
104
-
105
- function isThinkingContentBlock(
106
- value: unknown,
107
- ): value is Extract<AssistantMessage["content"][number], { type: "thinking" }> {
108
- const record = toRecord(value);
109
- return (
110
- record !== null &&
111
- record.type === "thinking" &&
112
- readString(record, "thinking") !== null
113
- );
114
- }
115
-
116
- function isToolCallContentBlock(
117
- value: unknown,
118
- ): value is Extract<AssistantMessage["content"][number], { type: "toolCall" }> {
119
- const record = toRecord(value);
120
- return (
121
- record !== null &&
122
- record.type === "toolCall" &&
123
- readString(record, "id") !== null &&
124
- readString(record, "name") !== null &&
125
- toRecord(record.arguments) !== null
126
- );
127
- }
128
-
129
- function isAssistantUsage(value: unknown): value is AssistantMessage["usage"] {
130
- const usageRecord = toRecord(value);
131
- const costRecord = toRecord(usageRecord?.cost);
132
- return (
133
- usageRecord !== null &&
134
- costRecord !== null &&
135
- readFiniteNumber(usageRecord, "input") !== null &&
136
- readFiniteNumber(usageRecord, "output") !== null &&
137
- readFiniteNumber(usageRecord, "cacheRead") !== null &&
138
- readFiniteNumber(usageRecord, "cacheWrite") !== null &&
139
- readFiniteNumber(usageRecord, "totalTokens") !== null &&
140
- readFiniteNumber(costRecord, "input") !== null &&
141
- readFiniteNumber(costRecord, "output") !== null &&
142
- readFiniteNumber(costRecord, "cacheRead") !== null &&
143
- readFiniteNumber(costRecord, "cacheWrite") !== null &&
144
- readFiniteNumber(costRecord, "total") !== null
145
- );
146
- }
147
-
148
- function isStopReason(value: unknown): value is AssistantMessage["stopReason"] {
149
- return (
150
- value === "stop" ||
151
- value === "length" ||
152
- value === "toolUse" ||
153
- value === "error" ||
154
- value === "aborted"
155
- );
156
- }
157
-
158
- function isUserMessageRecord(value: unknown): value is UserMessage {
159
- const record = toRecord(value);
160
- if (!record || record.role !== "user") {
161
- return false;
162
- }
163
-
164
- return (
165
- readFiniteNumber(record, "timestamp") !== null &&
166
- (typeof record.content === "string" ||
167
- (Array.isArray(record.content) &&
168
- record.content.every(
169
- (block) => isTextContentBlock(block) || isImageContentBlock(block),
170
- )))
171
- );
172
- }
173
-
174
- function parseAssistantMessageRecord(value: unknown): AssistantMessage | null {
175
- const record = toRecord(value);
176
- if (!record || record.role !== "assistant") {
177
- return null;
178
- }
179
-
180
- const timestamp = readFiniteNumber(record, "timestamp");
181
- const api = readString(record, "api");
182
- const provider = readString(record, "provider");
183
- const model = readString(record, "model");
184
- const errorMessage = readString(record, "errorMessage");
185
- if (
186
- !Array.isArray(record.content) ||
187
- !record.content.every(
188
- (block) =>
189
- isTextContentBlock(block) ||
190
- isThinkingContentBlock(block) ||
191
- isToolCallContentBlock(block),
192
- ) ||
193
- api === null ||
194
- provider === null ||
195
- model === null ||
196
- !isStopReason(record.stopReason) ||
197
- (record.errorMessage !== undefined && errorMessage === null) ||
198
- timestamp === null
199
- ) {
200
- return null;
201
- }
202
-
203
- return {
204
- role: "assistant",
205
- content: record.content,
206
- api,
207
- provider,
208
- model,
209
- usage: isAssistantUsage(record.usage)
210
- ? record.usage
211
- : structuredClone(EMPTY_ASSISTANT_USAGE),
212
- stopReason: record.stopReason,
213
- ...(errorMessage !== null ? { errorMessage } : {}),
214
- timestamp,
215
- };
216
- }
217
-
218
- function isToolResultMessageRecord(value: unknown): value is ToolResultMessage {
219
- const record = toRecord(value);
220
- if (!record || record.role !== "toolResult") {
221
- return false;
222
- }
223
-
224
- return (
225
- readString(record, "toolCallId") !== null &&
226
- readString(record, "toolName") !== null &&
227
- readBoolean(record, "isError") !== null &&
228
- Array.isArray(record.content) &&
229
- record.content.every(
230
- (block) => isTextContentBlock(block) || isImageContentBlock(block),
231
- ) &&
232
- readFiniteNumber(record, "timestamp") !== null
233
- );
234
- }
235
-
236
- function isUiMessageRecord(value: unknown): value is UiMessage {
237
- const record = toRecord(value);
238
- if (!record || record.role !== "ui") {
239
- return false;
240
- }
241
-
242
- const timestamp = readFiniteNumber(record, "timestamp");
243
- if (timestamp === null) {
244
- return false;
245
- }
246
-
247
- if (record.kind === "info") {
248
- return (
249
- typeof record.content === "string" &&
250
- (record.format === undefined || record.format === "markdown")
251
- );
252
- }
253
-
254
- return (
255
- record.kind === "todo" &&
256
- Array.isArray(record.todos) &&
257
- record.todos.every(
258
- (todo) =>
259
- typeof todo === "object" &&
260
- todo !== null &&
261
- typeof (todo as { content?: unknown }).content === "string" &&
262
- ((todo as { status?: unknown }).status === "pending" ||
263
- (todo as { status?: unknown }).status === "in_progress" ||
264
- (todo as { status?: unknown }).status === "completed"),
265
- )
266
- );
267
- }
268
-
269
- /**
270
- * Parse one persisted message row from SQLite JSON.
271
- *
272
- * Invalid or unsupported rows return `null` so callers can skip corrupt data
273
- * without crashing session loading.
274
- *
275
- * @param data - Raw JSON-serialized message row.
276
- * @returns The validated persisted message, or `null` when invalid.
277
- */
278
- export function parsePersistedMessage(data: string): PersistedMessage | null {
279
- let parsed: unknown;
280
- try {
281
- parsed = JSON.parse(data) as unknown;
282
- } catch {
283
- return null;
284
- }
285
-
286
- if (
287
- isUserMessageRecord(parsed) ||
288
- isToolResultMessageRecord(parsed) ||
289
- isUiMessageRecord(parsed)
290
- ) {
291
- return parsed;
292
- }
293
-
294
- return parseAssistantMessageRecord(parsed);
295
- }
296
-
297
- /**
298
- * Read the first-user preview cached by the session-list query.
299
- *
300
- * @param messageData - Raw JSON from the first conversational message row.
301
- * @returns A collapsed single-line preview, or `null` when unavailable.
302
- */
303
- export function readFirstUserPreview(
304
- messageData: string | null,
305
- ): string | null {
306
- if (!messageData) {
307
- return null;
308
- }
309
-
310
- const message = parsePersistedMessage(messageData);
311
- if (!message || message.role !== "user") {
312
- return null;
313
- }
314
-
315
- return getMultipartUserPreview(message.content);
316
- }
317
-
318
- /**
319
- * Check whether a persisted message is a UI-only message.
320
- *
321
- * @param message - Message to inspect.
322
- * @returns `true` when the message is a {@link UiMessage}.
323
- */
324
- export function isUiMessage(message: PersistedMessage): message is UiMessage {
325
- return message.role === "ui";
326
- }
327
-
328
- /**
329
- * Return an assistant message's usage when the persisted shape is valid.
330
- *
331
- * Session rows are treated as untrusted at runtime because older builds or
332
- * external tooling may have stored assistant messages without a `usage`
333
- * payload. Invalid or missing usage is ignored instead of crashing session
334
- * loading or stats calculations.
335
- *
336
- * @param message - Message to inspect.
337
- * @returns The assistant usage payload, or `null` when it is missing/invalid.
338
- */
339
- export function getAssistantUsage(
340
- message: PersistedMessage | Message,
341
- ): AssistantMessage["usage"] | null {
342
- if (message.role !== "assistant") {
343
- return null;
344
- }
345
-
346
- const messageRecord = toRecord(message);
347
- const usageRecord = toRecord(messageRecord?.usage);
348
- const costRecord = toRecord(usageRecord?.cost);
349
- if (!usageRecord || !costRecord) {
350
- return null;
351
- }
352
-
353
- const input = readFiniteNumber(usageRecord, "input");
354
- const output = readFiniteNumber(usageRecord, "output");
355
- const cacheRead = readFiniteNumber(usageRecord, "cacheRead");
356
- const cacheWrite = readFiniteNumber(usageRecord, "cacheWrite");
357
- const totalTokens = readFiniteNumber(usageRecord, "totalTokens");
358
- const costInput = readFiniteNumber(costRecord, "input");
359
- const costOutput = readFiniteNumber(costRecord, "output");
360
- const costCacheRead = readFiniteNumber(costRecord, "cacheRead");
361
- const costCacheWrite = readFiniteNumber(costRecord, "cacheWrite");
362
- const costTotal = readFiniteNumber(costRecord, "total");
363
-
364
- if (
365
- input === null ||
366
- output === null ||
367
- cacheRead === null ||
368
- cacheWrite === null ||
369
- totalTokens === null ||
370
- costInput === null ||
371
- costOutput === null ||
372
- costCacheRead === null ||
373
- costCacheWrite === null ||
374
- costTotal === null
375
- ) {
376
- return null;
377
- }
378
-
379
- return {
380
- input,
381
- output,
382
- cacheRead,
383
- cacheWrite,
384
- totalTokens,
385
- cost: {
386
- input: costInput,
387
- output: costOutput,
388
- cacheRead: costCacheRead,
389
- cacheWrite: costCacheWrite,
390
- total: costTotal,
391
- },
392
- };
393
- }