mini-coder 0.4.1 → 0.5.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 +87 -48
- package/assets/icon-1-minimal.svg +31 -0
- package/assets/icon-2-dark-terminal.svg +48 -0
- package/assets/icon-3-gradient-modern.svg +45 -0
- package/assets/icon-4-filled-bold.svg +54 -0
- package/assets/icon-5-community-badge.svg +63 -0
- package/assets/preview-0-5-0.png +0 -0
- package/assets/preview.gif +0 -0
- package/bin/mc.ts +14 -0
- package/bun.lock +438 -0
- package/package.json +12 -29
- package/src/agent.ts +592 -0
- package/src/cli.ts +124 -0
- package/src/git.ts +164 -0
- package/src/headless.ts +140 -0
- package/src/index.ts +645 -0
- package/src/input.ts +155 -0
- package/src/paths.ts +37 -0
- package/src/plugins.ts +183 -0
- package/src/prompt.ts +294 -0
- package/src/session.ts +838 -0
- package/src/settings.ts +184 -0
- package/src/skills.ts +258 -0
- package/src/submit.ts +323 -0
- package/src/theme.ts +147 -0
- package/src/tools.ts +636 -0
- package/src/ui/agent.test.ts +49 -0
- package/src/ui/agent.ts +210 -0
- package/src/ui/commands.test.ts +610 -0
- package/src/ui/commands.ts +638 -0
- package/src/ui/conversation.test.ts +892 -0
- package/src/ui/conversation.ts +926 -0
- package/src/ui/help.test.ts +26 -0
- package/src/ui/help.ts +119 -0
- package/src/ui/input.test.ts +74 -0
- package/src/ui/input.ts +138 -0
- package/src/ui/overlay.test.ts +42 -0
- package/src/ui/overlay.ts +59 -0
- package/src/ui/status.test.ts +450 -0
- package/src/ui/status.ts +357 -0
- package/src/ui.ts +615 -0
- package/.claude/settings.local.json +0 -54
- package/.prettierignore +0 -7
- package/dist/mc-edit.js +0 -275
- package/dist/mc.js +0 -7355
- package/docs/KNOWN_ISSUES.md +0 -13
- package/docs/design-decisions.md +0 -31
- package/docs/mini-coder.1.md +0 -227
- package/docs/superpowers/plans/2026-03-30-anthropic-oauth-removal.md +0 -61
- package/docs/superpowers/specs/2026-03-30-anthropic-oauth-removal-design.md +0 -47
- package/lefthook.yml +0 -4
package/dist/mc-edit.js
DELETED
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
// @bun
|
|
3
|
-
|
|
4
|
-
// src/cli/structured-output.ts
|
|
5
|
-
import { createTwoFilesPatch } from "diff";
|
|
6
|
-
import * as c from "yoctocolors";
|
|
7
|
-
function normalizePatchLines(patchText) {
|
|
8
|
-
const patchLines = patchText.split(`
|
|
9
|
-
`);
|
|
10
|
-
while (patchLines.at(-1) === "") {
|
|
11
|
-
patchLines.pop();
|
|
12
|
-
}
|
|
13
|
-
const diffLines = patchLines.filter((line) => !line.startsWith("Index: ") && line !== "===================================================================");
|
|
14
|
-
return diffLines.map((line) => {
|
|
15
|
-
if (line.startsWith("--- ") || line.startsWith("+++ ")) {
|
|
16
|
-
return line.split("\t", 1)[0] ?? line;
|
|
17
|
-
}
|
|
18
|
-
return line;
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
function colorizeDiffLine(line) {
|
|
22
|
-
if (line.startsWith("---") || line.startsWith("+++"))
|
|
23
|
-
return c.dim(line);
|
|
24
|
-
if (line.startsWith("@@"))
|
|
25
|
-
return c.cyan(line);
|
|
26
|
-
if (line.startsWith("+"))
|
|
27
|
-
return c.green(line);
|
|
28
|
-
if (line.startsWith("-"))
|
|
29
|
-
return c.red(line);
|
|
30
|
-
return line;
|
|
31
|
-
}
|
|
32
|
-
function renderUnifiedDiff(filePath, before, after, options) {
|
|
33
|
-
if (before === after) {
|
|
34
|
-
return "(no changes)";
|
|
35
|
-
}
|
|
36
|
-
const patchText = createTwoFilesPatch(filePath, filePath, before, after, "", "", {
|
|
37
|
-
context: 3
|
|
38
|
-
});
|
|
39
|
-
const lines = normalizePatchLines(patchText);
|
|
40
|
-
if (options?.colorize) {
|
|
41
|
-
return lines.map(colorizeDiffLine).join(`
|
|
42
|
-
`);
|
|
43
|
-
}
|
|
44
|
-
return lines.join(`
|
|
45
|
-
`);
|
|
46
|
-
}
|
|
47
|
-
function renderMetadataBlock(result) {
|
|
48
|
-
const lines = [`ok: ${result.ok}`];
|
|
49
|
-
if ("path" in result && result.path) {
|
|
50
|
-
lines.push(`path: ${result.path}`);
|
|
51
|
-
}
|
|
52
|
-
if (result.ok) {
|
|
53
|
-
lines.push(`changed: ${result.changed}`);
|
|
54
|
-
} else {
|
|
55
|
-
lines.push(`code: ${result.code}`);
|
|
56
|
-
lines.push(`message: ${result.message}`);
|
|
57
|
-
}
|
|
58
|
-
return lines.join(`
|
|
59
|
-
`);
|
|
60
|
-
}
|
|
61
|
-
function writeFileEditResult(io, result) {
|
|
62
|
-
if (result.ok) {
|
|
63
|
-
const colorize = process.env.FORCE_COLOR === "1" || process.env.FORCE_COLOR === "true";
|
|
64
|
-
const sections = [
|
|
65
|
-
renderUnifiedDiff(result.path, result.before, result.after, {
|
|
66
|
-
colorize
|
|
67
|
-
}),
|
|
68
|
-
renderMetadataBlock(result)
|
|
69
|
-
];
|
|
70
|
-
io.stdout(`${sections.join(`
|
|
71
|
-
|
|
72
|
-
`)}
|
|
73
|
-
`);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
io.stderr(`${renderMetadataBlock(result)}
|
|
77
|
-
`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// src/internal/file-edit/path.ts
|
|
81
|
-
import { homedir } from "os";
|
|
82
|
-
import { join, relative } from "path";
|
|
83
|
-
function stripMatchingQuotes(value) {
|
|
84
|
-
if (value.length < 2)
|
|
85
|
-
return value;
|
|
86
|
-
const first = value[0];
|
|
87
|
-
const last = value.at(-1);
|
|
88
|
-
if ((first === '"' || first === "'") && first === last) {
|
|
89
|
-
return value.slice(1, -1);
|
|
90
|
-
}
|
|
91
|
-
return value;
|
|
92
|
-
}
|
|
93
|
-
function normalizePathInput(pathInput) {
|
|
94
|
-
return stripMatchingQuotes(pathInput.trim());
|
|
95
|
-
}
|
|
96
|
-
function resolvePath(cwdInput, pathInput) {
|
|
97
|
-
const cwd = cwdInput ?? process.cwd();
|
|
98
|
-
const normalizedInput = normalizePathInput(pathInput);
|
|
99
|
-
let expanded = normalizedInput;
|
|
100
|
-
if (normalizedInput.startsWith("~/"))
|
|
101
|
-
expanded = join(homedir(), normalizedInput.slice(2));
|
|
102
|
-
else if (normalizedInput === "~")
|
|
103
|
-
expanded = homedir();
|
|
104
|
-
const filePath = expanded.startsWith("/") ? expanded : join(cwd, expanded);
|
|
105
|
-
const relPath = relative(cwd, filePath);
|
|
106
|
-
return { cwd, filePath, relPath };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// src/internal/file-edit/exact-text.ts
|
|
110
|
-
class FileEditError extends Error {
|
|
111
|
-
code;
|
|
112
|
-
constructor(code, message) {
|
|
113
|
-
super(message);
|
|
114
|
-
this.code = code;
|
|
115
|
-
this.name = "FileEditError";
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
function findExactMatchOffsets(source, target) {
|
|
119
|
-
if (target.length === 0) {
|
|
120
|
-
throw new FileEditError("empty_old_text", "Expected text must be non-empty.");
|
|
121
|
-
}
|
|
122
|
-
const matches = [];
|
|
123
|
-
let searchStart = 0;
|
|
124
|
-
while (searchStart <= source.length - target.length) {
|
|
125
|
-
const matchIndex = source.indexOf(target, searchStart);
|
|
126
|
-
if (matchIndex === -1)
|
|
127
|
-
break;
|
|
128
|
-
matches.push(matchIndex);
|
|
129
|
-
searchStart = matchIndex + 1;
|
|
130
|
-
}
|
|
131
|
-
return matches;
|
|
132
|
-
}
|
|
133
|
-
function planExactTextEdit(source, oldText, newText) {
|
|
134
|
-
const matches = findExactMatchOffsets(source, oldText);
|
|
135
|
-
if (matches.length === 0) {
|
|
136
|
-
throw new FileEditError("target_not_found", "Expected text was not found in the file.");
|
|
137
|
-
}
|
|
138
|
-
if (matches.length > 1) {
|
|
139
|
-
throw new FileEditError("target_not_unique", "Expected text matched multiple locations in the file.");
|
|
140
|
-
}
|
|
141
|
-
const matchIndex = matches[0] ?? 0;
|
|
142
|
-
const updated = source.slice(0, matchIndex) + newText + source.slice(matchIndex + oldText.length);
|
|
143
|
-
return {
|
|
144
|
-
updated,
|
|
145
|
-
changed: updated !== source
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
async function applyExactTextEdit(input) {
|
|
149
|
-
const { filePath, relPath } = resolvePath(input.cwd, input.path);
|
|
150
|
-
const file = Bun.file(filePath);
|
|
151
|
-
if (!await file.exists()) {
|
|
152
|
-
throw new FileEditError("file_not_found", `File not found: "${relPath}".`);
|
|
153
|
-
}
|
|
154
|
-
const original = await file.text();
|
|
155
|
-
const plan = planExactTextEdit(original, input.oldText, input.newText);
|
|
156
|
-
if (plan.changed) {
|
|
157
|
-
await Bun.write(filePath, plan.updated);
|
|
158
|
-
}
|
|
159
|
-
return {
|
|
160
|
-
path: relPath,
|
|
161
|
-
changed: plan.changed,
|
|
162
|
-
before: original,
|
|
163
|
-
after: plan.updated
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// src/internal/file-edit/cli.ts
|
|
168
|
-
var HELP = `Usage: mc-edit <path> (--old <text> | --old-file <path>) [--new <text> | --new-file <path>] [--cwd <path>]
|
|
169
|
-
|
|
170
|
-
Apply one safe exact-text edit to an existing file.
|
|
171
|
-
- The expected old text must match exactly once.
|
|
172
|
-
- Omit --new / --new-file to delete the matched text.
|
|
173
|
-
- Success output is human-oriented: plain unified diff first, metadata second.`;
|
|
174
|
-
async function readArgText(flag, filePath) {
|
|
175
|
-
const file = Bun.file(filePath);
|
|
176
|
-
if (!await file.exists()) {
|
|
177
|
-
throw new FileEditError("file_not_found", `${flag} file not found: "${filePath}".`);
|
|
178
|
-
}
|
|
179
|
-
return file.text();
|
|
180
|
-
}
|
|
181
|
-
async function parseFileEditCliArgs(argv) {
|
|
182
|
-
let cwd = process.cwd();
|
|
183
|
-
let path = null;
|
|
184
|
-
let oldText = null;
|
|
185
|
-
let oldFilePath = null;
|
|
186
|
-
let newText = null;
|
|
187
|
-
let newFilePath = null;
|
|
188
|
-
for (let i = 0;i < argv.length; i++) {
|
|
189
|
-
const arg = argv[i] ?? "";
|
|
190
|
-
switch (arg) {
|
|
191
|
-
case "--help":
|
|
192
|
-
case "-h":
|
|
193
|
-
return null;
|
|
194
|
-
case "--cwd":
|
|
195
|
-
cwd = argv[++i] ?? process.cwd();
|
|
196
|
-
break;
|
|
197
|
-
case "--old":
|
|
198
|
-
oldText = argv[++i] ?? "";
|
|
199
|
-
break;
|
|
200
|
-
case "--old-file":
|
|
201
|
-
oldFilePath = argv[++i] ?? null;
|
|
202
|
-
break;
|
|
203
|
-
case "--new":
|
|
204
|
-
newText = argv[++i] ?? "";
|
|
205
|
-
break;
|
|
206
|
-
case "--new-file":
|
|
207
|
-
newFilePath = argv[++i] ?? null;
|
|
208
|
-
break;
|
|
209
|
-
default:
|
|
210
|
-
if (arg.startsWith("-")) {
|
|
211
|
-
throw new Error(`Unknown flag: ${arg}`);
|
|
212
|
-
}
|
|
213
|
-
if (path !== null) {
|
|
214
|
-
throw new Error("Expected exactly one positional <path> argument.");
|
|
215
|
-
}
|
|
216
|
-
path = arg;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
if (path === null) {
|
|
220
|
-
throw new Error("Missing required <path> argument.");
|
|
221
|
-
}
|
|
222
|
-
if (oldText === null === (oldFilePath === null)) {
|
|
223
|
-
throw new Error("Provide exactly one of --old or --old-file.");
|
|
224
|
-
}
|
|
225
|
-
if (newText !== null && newFilePath !== null) {
|
|
226
|
-
throw new Error("Provide at most one of --new or --new-file.");
|
|
227
|
-
}
|
|
228
|
-
return {
|
|
229
|
-
cwd,
|
|
230
|
-
path,
|
|
231
|
-
oldText: oldText ?? await readArgText("--old-file", oldFilePath ?? ""),
|
|
232
|
-
newText: newText ?? (newFilePath ? await readArgText("--new-file", newFilePath) : "")
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
function buildCliFailure(code, message, path) {
|
|
236
|
-
return {
|
|
237
|
-
ok: false,
|
|
238
|
-
code,
|
|
239
|
-
message,
|
|
240
|
-
...path ? { path } : {}
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
function normalizeCliError(error, path) {
|
|
244
|
-
if (error instanceof FileEditError) {
|
|
245
|
-
return buildCliFailure(error.code, error.message, path);
|
|
246
|
-
}
|
|
247
|
-
if (error instanceof Error) {
|
|
248
|
-
return buildCliFailure("invalid_args", error.message, path);
|
|
249
|
-
}
|
|
250
|
-
return buildCliFailure("invalid_args", "Unknown error.", path);
|
|
251
|
-
}
|
|
252
|
-
async function runFileEditCli(argv, io = {
|
|
253
|
-
stdout: (text) => process.stdout.write(text),
|
|
254
|
-
stderr: (text) => process.stderr.write(text)
|
|
255
|
-
}) {
|
|
256
|
-
let parsed = null;
|
|
257
|
-
try {
|
|
258
|
-
parsed = await parseFileEditCliArgs(argv);
|
|
259
|
-
if (parsed === null) {
|
|
260
|
-
io.stderr(`${HELP}
|
|
261
|
-
`);
|
|
262
|
-
return 0;
|
|
263
|
-
}
|
|
264
|
-
const result = await applyExactTextEdit(parsed);
|
|
265
|
-
writeFileEditResult(io, { ok: true, ...result });
|
|
266
|
-
return 0;
|
|
267
|
-
} catch (error) {
|
|
268
|
-
writeFileEditResult(io, normalizeCliError(error, parsed?.path));
|
|
269
|
-
return 1;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// src/mc-edit.ts
|
|
274
|
-
var exitCode = await runFileEditCli(process.argv.slice(2));
|
|
275
|
-
process.exit(exitCode);
|