mini-coder 0.5.14 → 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 (70) hide show
  1. package/README.md +25 -109
  2. package/bin/mc.ts +8 -11
  3. package/bun.lock +79 -269
  4. package/package.json +17 -22
  5. package/src/agent.ts +237 -1403
  6. package/src/args.ts +289 -0
  7. package/src/headless.ts +43 -358
  8. package/src/index.ts +29 -1016
  9. package/src/oauth.ts +117 -0
  10. package/src/prompt.ts +227 -284
  11. package/src/session.ts +55 -1306
  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 -5
  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/assistant-output.ts +0 -73
  39. package/src/cli.ts +0 -134
  40. package/src/delegation.ts +0 -238
  41. package/src/errors.ts +0 -15
  42. package/src/git.ts +0 -247
  43. package/src/input.ts +0 -168
  44. package/src/mcp.ts +0 -609
  45. package/src/paths.ts +0 -37
  46. package/src/session-message.ts +0 -385
  47. package/src/settings.ts +0 -449
  48. package/src/skills.ts +0 -271
  49. package/src/submit.ts +0 -376
  50. package/src/text.ts +0 -71
  51. package/src/theme.ts +0 -330
  52. package/src/tool-common.ts +0 -93
  53. package/src/tool-delegate.ts +0 -125
  54. package/src/tool-grep.ts +0 -606
  55. package/src/tool-read.ts +0 -313
  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,385 +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
- /**
130
- * Parse and validate an assistant usage payload from unknown input.
131
- *
132
- * @param value - Unknown value to validate.
133
- * @returns The parsed assistant usage, or `null` when invalid.
134
- */
135
- export function readAssistantUsage(
136
- value: unknown,
137
- ): AssistantMessage["usage"] | null {
138
- const usageRecord = toRecord(value);
139
- const costRecord = toRecord(usageRecord?.cost);
140
- if (!usageRecord || !costRecord) {
141
- return null;
142
- }
143
-
144
- const input = readFiniteNumber(usageRecord, "input");
145
- const output = readFiniteNumber(usageRecord, "output");
146
- const cacheRead = readFiniteNumber(usageRecord, "cacheRead");
147
- const cacheWrite = readFiniteNumber(usageRecord, "cacheWrite");
148
- const totalTokens = readFiniteNumber(usageRecord, "totalTokens");
149
- const costInput = readFiniteNumber(costRecord, "input");
150
- const costOutput = readFiniteNumber(costRecord, "output");
151
- const costCacheRead = readFiniteNumber(costRecord, "cacheRead");
152
- const costCacheWrite = readFiniteNumber(costRecord, "cacheWrite");
153
- const costTotal = readFiniteNumber(costRecord, "total");
154
-
155
- if (
156
- input === null ||
157
- output === null ||
158
- cacheRead === null ||
159
- cacheWrite === null ||
160
- totalTokens === null ||
161
- costInput === null ||
162
- costOutput === null ||
163
- costCacheRead === null ||
164
- costCacheWrite === null ||
165
- costTotal === null
166
- ) {
167
- return null;
168
- }
169
-
170
- return {
171
- input,
172
- output,
173
- cacheRead,
174
- cacheWrite,
175
- totalTokens,
176
- cost: {
177
- input: costInput,
178
- output: costOutput,
179
- cacheRead: costCacheRead,
180
- cacheWrite: costCacheWrite,
181
- total: costTotal,
182
- },
183
- };
184
- }
185
-
186
- function isStopReason(value: unknown): value is AssistantMessage["stopReason"] {
187
- return (
188
- value === "stop" ||
189
- value === "length" ||
190
- value === "toolUse" ||
191
- value === "error" ||
192
- value === "aborted"
193
- );
194
- }
195
-
196
- function isUserMessageRecord(value: unknown): value is UserMessage {
197
- const record = toRecord(value);
198
- if (!record || record.role !== "user") {
199
- return false;
200
- }
201
-
202
- return (
203
- readFiniteNumber(record, "timestamp") !== null &&
204
- (typeof record.content === "string" ||
205
- (Array.isArray(record.content) &&
206
- record.content.every(
207
- (block) => isTextContentBlock(block) || isImageContentBlock(block),
208
- )))
209
- );
210
- }
211
-
212
- function parseAssistantMessageRecord(value: unknown): AssistantMessage | null {
213
- const record = toRecord(value);
214
- if (!record || record.role !== "assistant") {
215
- return null;
216
- }
217
-
218
- const timestamp = readFiniteNumber(record, "timestamp");
219
- const api = readString(record, "api");
220
- const provider = readString(record, "provider");
221
- const model = readString(record, "model");
222
- const errorMessage = readString(record, "errorMessage");
223
- if (
224
- !Array.isArray(record.content) ||
225
- !record.content.every(
226
- (block) =>
227
- isTextContentBlock(block) ||
228
- isThinkingContentBlock(block) ||
229
- isToolCallContentBlock(block),
230
- ) ||
231
- api === null ||
232
- provider === null ||
233
- model === null ||
234
- !isStopReason(record.stopReason) ||
235
- (record.errorMessage !== undefined && errorMessage === null) ||
236
- timestamp === null
237
- ) {
238
- return null;
239
- }
240
-
241
- return {
242
- role: "assistant",
243
- content: record.content,
244
- api,
245
- provider,
246
- model,
247
- usage:
248
- readAssistantUsage(record.usage) ??
249
- structuredClone(EMPTY_ASSISTANT_USAGE),
250
- stopReason: record.stopReason,
251
- ...(errorMessage !== null ? { errorMessage } : {}),
252
- timestamp,
253
- };
254
- }
255
-
256
- function isToolResultMessageRecord(value: unknown): value is ToolResultMessage {
257
- const record = toRecord(value);
258
- if (!record || record.role !== "toolResult") {
259
- return false;
260
- }
261
-
262
- return (
263
- readString(record, "toolCallId") !== null &&
264
- readString(record, "toolName") !== null &&
265
- readBoolean(record, "isError") !== null &&
266
- Array.isArray(record.content) &&
267
- record.content.every(
268
- (block) => isTextContentBlock(block) || isImageContentBlock(block),
269
- ) &&
270
- readFiniteNumber(record, "timestamp") !== null
271
- );
272
- }
273
-
274
- function isUiMessageRecord(value: unknown): value is UiMessage {
275
- const record = toRecord(value);
276
- if (!record || record.role !== "ui") {
277
- return false;
278
- }
279
-
280
- const timestamp = readFiniteNumber(record, "timestamp");
281
- if (timestamp === null) {
282
- return false;
283
- }
284
-
285
- if (record.kind === "info") {
286
- return (
287
- typeof record.content === "string" &&
288
- (record.format === undefined || record.format === "markdown")
289
- );
290
- }
291
-
292
- return (
293
- record.kind === "todo" &&
294
- Array.isArray(record.todos) &&
295
- record.todos.every(
296
- (todo) =>
297
- typeof todo === "object" &&
298
- todo !== null &&
299
- typeof (todo as { content?: unknown }).content === "string" &&
300
- ((todo as { status?: unknown }).status === "pending" ||
301
- (todo as { status?: unknown }).status === "in_progress" ||
302
- (todo as { status?: unknown }).status === "completed"),
303
- )
304
- );
305
- }
306
-
307
- /**
308
- * Parse one persisted message row from SQLite JSON.
309
- *
310
- * Invalid or unsupported rows return `null` so callers can skip corrupt data
311
- * without crashing session loading.
312
- *
313
- * @param data - Raw JSON-serialized message row.
314
- * @returns The validated persisted message, or `null` when invalid.
315
- */
316
- export function parsePersistedMessage(data: string): PersistedMessage | null {
317
- let parsed: unknown;
318
- try {
319
- parsed = JSON.parse(data) as unknown;
320
- } catch {
321
- return null;
322
- }
323
-
324
- if (
325
- isUserMessageRecord(parsed) ||
326
- isToolResultMessageRecord(parsed) ||
327
- isUiMessageRecord(parsed)
328
- ) {
329
- return parsed;
330
- }
331
-
332
- return parseAssistantMessageRecord(parsed);
333
- }
334
-
335
- /**
336
- * Read the first-user preview cached by the session-list query.
337
- *
338
- * @param messageData - Raw JSON from the first conversational message row.
339
- * @returns A collapsed single-line preview, or `null` when unavailable.
340
- */
341
- export function readFirstUserPreview(
342
- messageData: string | null,
343
- ): string | null {
344
- if (!messageData) {
345
- return null;
346
- }
347
-
348
- const message = parsePersistedMessage(messageData);
349
- if (!message || message.role !== "user") {
350
- return null;
351
- }
352
-
353
- return getMultipartUserPreview(message.content);
354
- }
355
-
356
- /**
357
- * Check whether a persisted message is a UI-only message.
358
- *
359
- * @param message - Message to inspect.
360
- * @returns `true` when the message is a {@link UiMessage}.
361
- */
362
- export function isUiMessage(message: PersistedMessage): message is UiMessage {
363
- return message.role === "ui";
364
- }
365
-
366
- /**
367
- * Return an assistant message's usage when the persisted shape is valid.
368
- *
369
- * Session rows are treated as untrusted at runtime because older builds or
370
- * external tooling may have stored assistant messages without a `usage`
371
- * payload. Invalid or missing usage is ignored instead of crashing session
372
- * loading or stats calculations.
373
- *
374
- * @param message - Message to inspect.
375
- * @returns The assistant usage payload, or `null` when it is missing/invalid.
376
- */
377
- export function getAssistantUsage(
378
- message: PersistedMessage | Message,
379
- ): AssistantMessage["usage"] | null {
380
- if (message.role !== "assistant") {
381
- return null;
382
- }
383
-
384
- return readAssistantUsage(toRecord(message)?.usage);
385
- }