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.
- package/README.md +25 -109
- package/bin/mc.ts +8 -11
- package/bun.lock +79 -269
- package/package.json +17 -22
- package/src/agent.ts +237 -1403
- package/src/args.ts +289 -0
- package/src/headless.ts +43 -358
- package/src/index.ts +29 -1016
- package/src/oauth.ts +117 -0
- package/src/prompt.ts +227 -284
- package/src/session.ts +55 -1306
- package/src/shared.ts +117 -38
- package/src/tool-bash.ts +110 -0
- package/src/tool-edit.ts +133 -0
- package/src/tool-task.ts +114 -0
- package/src/tui-components.ts +150 -0
- package/src/tui-conversation.ts +262 -0
- package/src/tui-editor.ts +29 -0
- package/src/tui-overlay.ts +403 -0
- package/src/tui.ts +236 -0
- package/src/types.ts +160 -0
- package/tsconfig.json +17 -0
- package/BENCHMARK.md +0 -107
- package/LICENSE +0 -9
- package/PROGRESS.md +0 -5
- package/assets/icon-1-minimal.svg +0 -31
- package/assets/icon-2-dark-terminal.svg +0 -48
- package/assets/icon-3-gradient-modern.svg +0 -45
- package/assets/icon-4-filled-bold.svg +0 -54
- package/assets/icon-5-community-badge.svg +0 -63
- package/assets/mc-claude-smart.png +0 -0
- package/assets/mc-gpt-smart.png +0 -0
- package/assets/preview-0-5-0.png +0 -0
- package/assets/preview.gif +0 -0
- package/benchmark-baseline.sh +0 -15
- package/benchmark-loop.sh +0 -19
- package/skills-lock.json +0 -15
- package/src/assistant-output.ts +0 -73
- package/src/cli.ts +0 -134
- package/src/delegation.ts +0 -238
- package/src/errors.ts +0 -15
- package/src/git.ts +0 -247
- package/src/input.ts +0 -168
- package/src/mcp.ts +0 -609
- package/src/paths.ts +0 -37
- package/src/session-message.ts +0 -385
- package/src/settings.ts +0 -449
- package/src/skills.ts +0 -271
- package/src/submit.ts +0 -376
- package/src/text.ts +0 -71
- package/src/theme.ts +0 -330
- package/src/tool-common.ts +0 -93
- package/src/tool-delegate.ts +0 -125
- package/src/tool-grep.ts +0 -606
- package/src/tool-read.ts +0 -313
- package/src/tool-shell.ts +0 -1051
- package/src/tools.ts +0 -1179
- package/src/ui/agent.ts +0 -320
- package/src/ui/commands.test.ts +0 -957
- package/src/ui/commands.ts +0 -848
- package/src/ui/conversation.test.ts +0 -585
- package/src/ui/conversation.ts +0 -1836
- package/src/ui/help.ts +0 -158
- package/src/ui/input.test.ts +0 -64
- package/src/ui/input.ts +0 -138
- package/src/ui/overlay.ts +0 -59
- package/src/ui/runtime.ts +0 -69
- package/src/ui/status.ts +0 -220
- package/src/ui.ts +0 -1190
- 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
|
-
}
|