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
package/src/git.ts DELETED
@@ -1,247 +0,0 @@
1
- /**
2
- * Git state gathering.
3
- *
4
- * Runs fast git commands to collect branch name, working tree counts,
5
- * and ahead/behind status. Used by the system prompt footer and the
6
- * status bar to give the model and user situational awareness.
7
- *
8
- * @module
9
- */
10
-
11
- // ---------------------------------------------------------------------------
12
- // Types
13
- // ---------------------------------------------------------------------------
14
-
15
- /**
16
- * Snapshot of the current git repository state.
17
- *
18
- * All counts are non-negative integers. When there is no upstream
19
- * tracking branch, `ahead` and `behind` are both `0`.
20
- */
21
- export interface GitState {
22
- /** Absolute path to the repository root. */
23
- root: string;
24
- /** Current branch name (empty string for detached HEAD). */
25
- branch: string;
26
- /** Upstream tracking ref such as `origin/main`, or `null` when none exists. */
27
- upstream: string | null;
28
- /** Number of staged (index) changes. */
29
- staged: number;
30
- /** Number of unstaged working-tree modifications. */
31
- modified: number;
32
- /** Number of untracked files. */
33
- untracked: number;
34
- /** Commits ahead of the upstream tracking branch. */
35
- ahead: number;
36
- /** Commits behind the upstream tracking branch. */
37
- behind: number;
38
- }
39
-
40
- // ---------------------------------------------------------------------------
41
- // Helpers
42
- // ---------------------------------------------------------------------------
43
-
44
- /**
45
- * Run a git command and return its trimmed stdout.
46
- * Returns `null` if the command fails (non-zero exit).
47
- */
48
- async function run(
49
- args: string[],
50
- cwd: string,
51
- trim = true,
52
- ): Promise<string | null> {
53
- const proc = Bun.spawn(["git", ...args], {
54
- cwd,
55
- stdout: "pipe",
56
- stderr: "pipe",
57
- });
58
- const out = await new Response(proc.stdout as ReadableStream).text();
59
- const code = await proc.exited;
60
- if (code !== 0) return null;
61
- return trim ? out.trim() : out;
62
- }
63
-
64
- function getErrorStringProperty(
65
- error: unknown,
66
- key: string,
67
- ): string | undefined {
68
- if (typeof error !== "object" || error === null) {
69
- return undefined;
70
- }
71
-
72
- const value = Reflect.get(error, key);
73
- return typeof value === "string" ? value : undefined;
74
- }
75
-
76
- function isMissingGitError(error: unknown): boolean {
77
- const code = getErrorStringProperty(error, "code");
78
- if (code !== "ENOENT") {
79
- return false;
80
- }
81
-
82
- const message = getErrorStringProperty(error, "message");
83
- return (
84
- typeof message === "string" &&
85
- message.includes('Executable not found in $PATH: "git"')
86
- );
87
- }
88
-
89
- async function safeRun(
90
- runGit: (
91
- args: string[],
92
- cwd: string,
93
- trim?: boolean,
94
- ) => Promise<string | null>,
95
- args: string[],
96
- cwd: string,
97
- trim = true,
98
- ): Promise<string | null> {
99
- try {
100
- return await runGit(args, cwd, trim);
101
- } catch (error) {
102
- if (isMissingGitError(error)) {
103
- return null;
104
- }
105
- throw error;
106
- }
107
- }
108
-
109
- function isUntrackedStatus(
110
- indexStatus: string,
111
- workingTreeStatus: string,
112
- ): boolean {
113
- return indexStatus === "?" && workingTreeStatus === "?";
114
- }
115
-
116
- function hasTrackedChange(status: string): boolean {
117
- return status !== " " && status !== "?";
118
- }
119
-
120
- /**
121
- * Parse `git status --porcelain` output into staged, modified, and untracked counts.
122
- *
123
- * Porcelain v1 format: two-character status code per line.
124
- * - Column 1 = index (staged) status
125
- * - Column 2 = working tree status
126
- * - `?` in both columns = untracked
127
- *
128
- * @param output - Raw `git status --porcelain` output.
129
- * @returns Counts of staged, modified, and untracked files.
130
- */
131
- export function parseGitStatus(output: string): {
132
- staged: number;
133
- modified: number;
134
- untracked: number;
135
- } {
136
- let staged = 0;
137
- let modified = 0;
138
- let untracked = 0;
139
-
140
- for (const line of output.split("\n")) {
141
- if (line.length < 2) {
142
- continue;
143
- }
144
-
145
- const indexStatus = line[0];
146
- const workingTreeStatus = line[1];
147
- if (!indexStatus || !workingTreeStatus) {
148
- continue;
149
- }
150
- if (isUntrackedStatus(indexStatus, workingTreeStatus)) {
151
- untracked++;
152
- continue;
153
- }
154
- if (hasTrackedChange(indexStatus)) {
155
- staged++;
156
- }
157
- if (hasTrackedChange(workingTreeStatus)) {
158
- modified++;
159
- }
160
- }
161
-
162
- return { staged, modified, untracked };
163
- }
164
-
165
- /**
166
- * Parse `git rev-list --left-right --count HEAD...@{upstream}` output.
167
- *
168
- * The command returns two whitespace-separated integers: ahead then behind.
169
- * Invalid or missing values are treated as `0`.
170
- *
171
- * @param output - Raw `git rev-list --left-right --count` output.
172
- * @returns Parsed ahead and behind counts.
173
- */
174
- export function parseGitAheadBehind(output: string): {
175
- ahead: number;
176
- behind: number;
177
- } {
178
- const parts = output.trim().split(/\s+/);
179
- return {
180
- ahead: parseInt(parts[0] ?? "0", 10) || 0,
181
- behind: parseInt(parts[1] ?? "0", 10) || 0,
182
- };
183
- }
184
-
185
- // ---------------------------------------------------------------------------
186
- // Public API
187
- // ---------------------------------------------------------------------------
188
-
189
- /**
190
- * Gather the current git state for a directory.
191
- *
192
- * Runs several fast git commands in parallel to collect branch, working
193
- * tree status, and ahead/behind counts. Returns `null` if git is not
194
- * installed or the directory is not inside a git repository.
195
- *
196
- * @param cwd - The directory to query (can be a subdirectory of the repo).
197
- * @param opts - Optional runtime overrides used by tests.
198
- * @returns A {@link GitState} snapshot, or `null` if not in a git repo.
199
- */
200
- export async function getGitState(
201
- cwd: string,
202
- opts?: {
203
- run?: (
204
- args: string[],
205
- cwd: string,
206
- trim?: boolean,
207
- ) => Promise<string | null>;
208
- },
209
- ): Promise<GitState | null> {
210
- const exec = opts?.run ?? run;
211
-
212
- // Check if we're in a repo and get the root
213
- const root = await safeRun(exec, ["rev-parse", "--show-toplevel"], cwd);
214
- if (root === null) return null;
215
-
216
- // Run remaining commands in parallel
217
- const [branch, upstream, status, revList] = await Promise.all([
218
- safeRun(exec, ["branch", "--show-current"], cwd),
219
- safeRun(
220
- exec,
221
- ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"],
222
- cwd,
223
- ),
224
- safeRun(exec, ["status", "--porcelain"], cwd, false),
225
- safeRun(
226
- exec,
227
- ["rev-list", "--left-right", "--count", "HEAD...@{upstream}"],
228
- cwd,
229
- ),
230
- ]);
231
-
232
- const { staged, modified, untracked } = parseGitStatus(status ?? "");
233
- const { ahead, behind } = revList
234
- ? parseGitAheadBehind(revList)
235
- : { ahead: 0, behind: 0 };
236
-
237
- return {
238
- root,
239
- branch: branch ?? "",
240
- upstream,
241
- staged,
242
- modified,
243
- untracked,
244
- ahead,
245
- behind,
246
- };
247
- }
package/src/input.ts DELETED
@@ -1,168 +0,0 @@
1
- /**
2
- * User input parsing.
3
- *
4
- * Pure logic for detecting slash commands, skill references, image paths,
5
- * and plain text from raw user input. No UI or IO beyond `existsSync`
6
- * for image path validation.
7
- *
8
- * @module
9
- */
10
-
11
- import { existsSync } from "node:fs";
12
- import { extname, isAbsolute, join } from "node:path";
13
-
14
- // ---------------------------------------------------------------------------
15
- // Constants
16
- // ---------------------------------------------------------------------------
17
-
18
- /** All recognized slash commands. */
19
- export const COMMANDS = [
20
- "session",
21
- "new",
22
- "fork",
23
- "undo",
24
- "reasoning",
25
- "verbose",
26
- "mcp",
27
- "todo",
28
- "login",
29
- "logout",
30
- "help",
31
- "model",
32
- "effort",
33
- ] as const;
34
-
35
- /** Slash helper that opens the interactive skill picker when submitted alone. */
36
- export const SKILL_COMMAND = "skill" as const;
37
-
38
- /** A recognized slash command name. */
39
- type Command = (typeof COMMANDS)[number] | typeof SKILL_COMMAND;
40
-
41
- const COMMAND_SET: ReadonlySet<string> = new Set(COMMANDS);
42
-
43
- /** Image file extensions we recognize for embedding. */
44
- const IMAGE_EXTENSIONS: ReadonlySet<string> = new Set([
45
- ".png",
46
- ".jpg",
47
- ".jpeg",
48
- ".gif",
49
- ".webp",
50
- ]);
51
-
52
- // ---------------------------------------------------------------------------
53
- // Types
54
- // ---------------------------------------------------------------------------
55
-
56
- /** Result of parsing user input. */
57
- type ParsedInput =
58
- | { type: "command"; command: Command; args: string }
59
- | { type: "skill"; skillName: string; userText: string }
60
- | { type: "image"; path: string }
61
- | { type: "text"; text: string };
62
-
63
- /** Options for input parsing. */
64
- interface ParseInputOpts {
65
- /** Whether the current model supports image input. */
66
- supportsImages?: boolean;
67
- /** Working directory for resolving relative image paths. */
68
- cwd?: string;
69
- }
70
-
71
- // ---------------------------------------------------------------------------
72
- // Parsing
73
- // ---------------------------------------------------------------------------
74
-
75
- function isCommand(value: string): value is Command {
76
- return COMMAND_SET.has(value);
77
- }
78
-
79
- function parseSlashInput(trimmed: string): ParsedInput | null {
80
- if (trimmed === `/${SKILL_COMMAND}`) {
81
- return {
82
- type: "command",
83
- command: SKILL_COMMAND,
84
- args: "",
85
- };
86
- }
87
-
88
- const skillMatch = trimmed.match(/^\/skill:(\S+)(?:\s+(.*))?$/s);
89
- if (skillMatch?.[1]) {
90
- return {
91
- type: "skill",
92
- skillName: skillMatch[1],
93
- userText: skillMatch[2]?.trim() ?? "",
94
- };
95
- }
96
-
97
- const commandMatch = trimmed.match(/^\/(\S+)(?:\s+(.*))?$/s);
98
- const command = commandMatch?.[1];
99
- if (!command || !isCommand(command)) {
100
- return null;
101
- }
102
-
103
- return {
104
- type: "command",
105
- command,
106
- args: commandMatch[2]?.trim() ?? "",
107
- };
108
- }
109
-
110
- function resolveImagePath(input: string, cwd?: string): string {
111
- if (isAbsolute(input) || !cwd) {
112
- return input;
113
- }
114
- return join(cwd, input);
115
- }
116
-
117
- function parseImageInput(
118
- trimmed: string,
119
- opts?: ParseInputOpts,
120
- ): Extract<ParsedInput, { type: "image" }> | null {
121
- if (!opts?.supportsImages) {
122
- return null;
123
- }
124
- if (!IMAGE_EXTENSIONS.has(extname(trimmed).toLowerCase())) {
125
- return null;
126
- }
127
-
128
- const resolvedPath = resolveImagePath(trimmed, opts.cwd);
129
- if (!existsSync(resolvedPath)) {
130
- return null;
131
- }
132
-
133
- return { type: "image", path: resolvedPath };
134
- }
135
-
136
- /**
137
- * Parse raw user input into a structured result.
138
- *
139
- * Priority order:
140
- * 1. Slash commands (`/model`, `/help`, etc.)
141
- * 2. Skill references (`/skill:name rest of message`)
142
- * 3. Image paths (entire input is an existing image file)
143
- * 4. Plain text
144
- *
145
- * @param raw - The raw input string from the user.
146
- * @param opts - Optional parsing context (image support, cwd).
147
- * @returns A {@link ParsedInput} describing what the input represents.
148
- */
149
- export function parseInput(raw: string, opts?: ParseInputOpts): ParsedInput {
150
- const trimmed = raw.trim();
151
- if (trimmed.length === 0) {
152
- return { type: "text", text: "" };
153
- }
154
-
155
- if (trimmed[0] === "/") {
156
- const slashInput = parseSlashInput(trimmed);
157
- if (slashInput) {
158
- return slashInput;
159
- }
160
- }
161
-
162
- const imageInput = parseImageInput(trimmed, opts);
163
- if (imageInput) {
164
- return imageInput;
165
- }
166
-
167
- return { type: "text", text: trimmed };
168
- }