mini-coder 0.0.23 → 0.1.1
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 +39 -157
- package/dist/mc.js +135 -51
- package/docs/mini-coder.1.md +221 -102
- package/package.json +3 -2
- package/docs/configs.md +0 -118
- package/docs/custom-agents.md +0 -69
- package/docs/custom-commands.md +0 -141
- package/docs/skills.md +0 -83
package/README.md
CHANGED
|
@@ -6,194 +6,76 @@
|
|
|
6
6
|
|
|
7
7
|
> _Small. Fast. Gets out of your way._
|
|
8
8
|
|
|
9
|
-
[📖 Read the Full Manual](
|
|
9
|
+
[📖 Read the Full Manual](docs/mini-coder.1.md)
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## 🤙 Who Am I?
|
|
16
|
-
|
|
17
|
-
I'm `mc` — your new terminal companion. I live in your shell, speak to large language models, and help you explore, understand, and modify code at the speed of thought.
|
|
18
|
-
|
|
19
|
-
I was built with a simple philosophy: **dev flow first**. No slow startup. No clunky GUI. No vendor lock-in. Just you, your terminal, and an AI that keeps up.
|
|
11
|
+
A terminal coding agent for developers who want a sharp tool, not a bloated IDE plugin. Shell-first, multi-provider, minimal tool surface. Just you, your terminal, and an AI that keeps up.
|
|
20
12
|
|
|
21
13
|

|
|
22
14
|
|
|
23
15
|
---
|
|
24
16
|
|
|
25
|
-
##
|
|
26
|
-
|
|
27
|
-
My toolkit is lean on purpose — every tool earns its spot, no passengers:
|
|
28
|
-
|
|
29
|
-
| Tool | What it does |
|
|
30
|
-
| --------------- | -------------------------------------------------------------------------- |
|
|
31
|
-
| 🐚 `shell` | Run shell commands, inspect the repo, and use `mc-edit` for targeted edits |
|
|
32
|
-
| 🤖 `subagent` | Spawn a focused mini-me for parallel subtasks |
|
|
33
|
-
| 🧰 `listSkills` | List discovered skills without loading full skill bodies |
|
|
34
|
-
| 📘 `readSkill` | Load one `SKILL.md` on demand |
|
|
35
|
-
| 🌐 `webSearch` | Search the web when `EXA_API_KEY` is set |
|
|
36
|
-
| 📄 `webContent` | Fetch full page content from URLs when `EXA_API_KEY` is set |
|
|
37
|
-
|
|
38
|
-
mini-coder is intentionally **shell-first**: inspect with shell, edit with `mc-edit`, verify with shell.
|
|
39
|
-
|
|
40
|
-
Need more firepower? I also connect to **MCP servers** over HTTP or stdio — attached MCP tools show up dynamically whenever the job calls for them.
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## ⚡ Key Features
|
|
45
|
-
|
|
46
|
-
- **Multi-provider** — set `OPENCODE_API_KEY` for Zen, `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GOOGLE_API_KEY` or `GEMINI_API_KEY`, or just run Ollama locally (`OLLAMA_BASE_URL` optional). I auto-discover whatever's available.
|
|
47
|
-
- **Built-in web search** — set `EXA_API_KEY` and I expose `webSearch` + `webContent` tools.
|
|
48
|
-
- **Session memory** — conversations are saved in a local SQLite database. Resume where you left off with `-c` or pick a specific session with `-r <id>`.
|
|
49
|
-
- **Shell integration** — prefix with `!` to run shell commands inline. Use `@` to reference files in your prompt (with Tab completion).
|
|
50
|
-
- **Slash commands** — `/model` or `/models` to list/switch models, `/model effort <low|medium|high|xhigh|off>` for reasoning effort, `/reasoning [on|off]` to toggle reasoning display, `/context` to inspect or tune pruning/tool-result caps, `/cache` to configure prompt caching, `/review` for a code review (global custom command, auto-created at `~/.agents/commands/review.md`), `/agent [name]` to set or clear an active primary agent, `/undo` to remove the last conversation turn (it does not revert filesystem changes), `/new` for a clean session, `/mcp list|add|remove` to manage MCP servers, and `/exit` (`/quit`, `/q`) to leave. See all with `/help`.
|
|
51
|
-
|
|
52
|
-
- **Custom commands** — drop a `.md` file in `.agents/commands/` and it becomes a `/command`. Claude-compatible `.claude/commands/` works too. Supports argument placeholders (`$ARGUMENTS`, `$1`…`$9`) and shell interpolation (`` !`cmd` ``). Global commands live in `~/.agents/commands/` and `~/.claude/commands/`. Custom commands take precedence over built-ins. → [docs/custom-commands.md](docs/custom-commands.md)
|
|
53
|
-
- **Custom agents** — drop a `.md` file in `.agents/agents/` or `.claude/agents/` (or `~/.agents/agents/` / `~/.claude/agents/` globally) and activate it with `/agent [name]`. Agent definitions are also exposed to subagent delegation unless `mode: primary`. → [docs/custom-agents.md](docs/custom-agents.md)
|
|
54
|
-
- **Skills** — place a `SKILL.md` in `.agents/skills/<name>/` and inject it into any prompt with `@skill-name`. Claude-compatible `.claude/skills/<name>/SKILL.md` works too. Skills are _never_ auto-loaded — always explicit. → [docs/skills.md](docs/skills.md)
|
|
55
|
-
- **Beautiful, minimal output** — compact tool output, formatted trees for file searches, a live status bar with model, git branch, and token counts.
|
|
56
|
-
- **16 ANSI colors only** — my output inherits _your_ terminal theme. Dark mode, light mode, Solarized, Gruvbox — I fit right in.
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## 🧠 Interesting Things About Me
|
|
61
|
-
|
|
62
|
-
- **I eat my own dog food.** I was built _by_ a mini-coder agent. It's agents all the way down. 🐢
|
|
63
|
-
- **I'm tiny but mighty.** The whole runtime is [Bun.js](https://bun.com) — fast startup, native TypeScript, and a built-in SQLite driver.
|
|
64
|
-
- **I respect existing conventions.** Context lives in `AGENTS.md` or `CLAUDE.md`, commands in `.agents/commands/`, agents in `.agents/agents/`, skills in `.agents/skills/` — I follow the ecosystem instead of inventing new standards.
|
|
65
|
-
- **I spin while I think.** ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ (It's the little things.)
|
|
66
|
-
- **I can clone myself.** The `subagent` tool lets me spin up parallel instances of myself to tackle independent subtasks simultaneously. Divide and conquer! (Up to 10 levels deep.)
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## 📁 Config folders
|
|
71
|
-
|
|
72
|
-
I follow the [`.agents` convention](https://github.com/agentsmd/agents) — the shared standard across AI coding tools — and I also understand `.claude` layouts for **commands**, **skills**, and **agents**.
|
|
73
|
-
|
|
74
|
-
| Path | What it does |
|
|
75
|
-
| -------------------------------- | ----------------------------------------------------- |
|
|
76
|
-
| `.agents/commands/*.md` | Custom slash commands (`/name`) |
|
|
77
|
-
| `.claude/commands/*.md` | Claude-compatible custom commands |
|
|
78
|
-
| `.agents/agents/*.md` | Custom agents |
|
|
79
|
-
| `.claude/agents/*.md` | Alternate `.claude` path for custom agents |
|
|
80
|
-
| `.agents/skills/<name>/SKILL.md` | Reusable skill instructions (`@name`) |
|
|
81
|
-
| `.claude/skills/<name>/SKILL.md` | Claude-compatible skills |
|
|
82
|
-
| `.agents/AGENTS.md` | Preferred local project context |
|
|
83
|
-
| `CLAUDE.md` | Local fallback context if `.agents/AGENTS.md` is absent |
|
|
84
|
-
| `AGENTS.md` | Local fallback context if `.agents/AGENTS.md` and `CLAUDE.md` are absent |
|
|
85
|
-
| `~/.agents/AGENTS.md` | Preferred global context, prepended before local context |
|
|
86
|
-
| `~/.agents/CLAUDE.md` | Global fallback context if `~/.agents/AGENTS.md` is absent |
|
|
87
|
-
|
|
88
|
-
Global commands, agents, and skills also work from `~/.agents/...` and `~/.claude/...`.
|
|
89
|
-
|
|
90
|
-
For commands, skills, and agents: local overrides global, and `.agents` overrides `.claude` at the same scope. Context files are combined differently: global context is injected first, then local context. → [docs/configs.md](docs/configs.md)
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## 🚀 Getting Started
|
|
17
|
+
## ⚡ Quick Start
|
|
95
18
|
|
|
96
|
-
|
|
19
|
+
I run on [Bun](https://bun.com) — install me via bun or npm, but Bun needs to be on your machine.
|
|
97
20
|
|
|
98
21
|
```bash
|
|
99
|
-
# Install
|
|
100
|
-
bun add -g mini-coder
|
|
101
|
-
# or: npm install -g mini-coder
|
|
102
|
-
|
|
103
|
-
# Set a provider key (pick one — or run Ollama locally)
|
|
104
|
-
export OPENCODE_API_KEY=your-zen-key # recommended
|
|
105
|
-
export ANTHROPIC_API_KEY=your-key # direct Anthropic
|
|
106
|
-
export OPENAI_API_KEY=your-key # direct OpenAI
|
|
107
|
-
export GOOGLE_API_KEY=your-key # direct Gemini
|
|
108
|
-
# or: export GEMINI_API_KEY=your-key
|
|
109
|
-
|
|
110
|
-
# Optional extras
|
|
111
|
-
export OLLAMA_BASE_URL=http://localhost:11434
|
|
112
|
-
export EXA_API_KEY=your-exa-key # enables webSearch/webContent
|
|
113
|
-
|
|
114
|
-
# Launch
|
|
115
|
-
mc
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Or drop me a prompt straight away for one-shot mode (runs once, then exits):
|
|
22
|
+
# Install
|
|
23
|
+
bun add -g mini-coder # or: npm install -g mini-coder
|
|
119
24
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
25
|
+
# Set one API key (pick any)
|
|
26
|
+
export OPENCODE_API_KEY=your-key # recommended
|
|
27
|
+
export ANTHROPIC_API_KEY=your-key # direct Anthropic
|
|
28
|
+
export OPENAI_API_KEY=your-key # direct OpenAI
|
|
29
|
+
export GOOGLE_API_KEY=your-key # direct Gemini (or GEMINI_API_KEY)
|
|
123
30
|
|
|
124
|
-
|
|
31
|
+
# Optional
|
|
32
|
+
export OLLAMA_BASE_URL=http://localhost:11434 # local models
|
|
33
|
+
export EXA_API_KEY=your-key # web search tools
|
|
125
34
|
|
|
126
|
-
|
|
127
|
-
mc
|
|
128
|
-
mc -r <id> # resume a specific session
|
|
129
|
-
mc -l # list recent sessions
|
|
130
|
-
mc -m zen/claude-sonnet-4-6 # pick a model
|
|
131
|
-
mc --cwd ~/src/other-project # set working directory
|
|
132
|
-
mc -h # show help
|
|
35
|
+
# Go
|
|
36
|
+
mc
|
|
133
37
|
```
|
|
134
38
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
## 🗃️ App data
|
|
138
|
-
|
|
139
|
-
Everything I remember lives in `~/.config/mini-coder/` — here's what I'm holding onto:
|
|
39
|
+
One-shot mode: `mc "refactor auth to use async/await"` — runs once, then exits.
|
|
140
40
|
|
|
141
|
-
|
|
142
|
-
- `api.log` — a request/response log for every provider call this run, if you want to peek under the hood
|
|
143
|
-
- `errors.log` — anything that went sideways, caught and written down so you can actually debug it
|
|
41
|
+
Useful flags: `-c` continue last session, `-r <id>` resume, `-l` list sessions, `-m <model>` pick a model, `-h` help.
|
|
144
42
|
|
|
145
43
|
---
|
|
146
44
|
|
|
147
|
-
##
|
|
45
|
+
## 🔑 OAuth Login
|
|
148
46
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
- [docs/custom-commands.md](docs/custom-commands.md)
|
|
152
|
-
- [docs/custom-agents.md](docs/custom-agents.md)
|
|
153
|
-
- [docs/skills.md](docs/skills.md)
|
|
154
|
-
- [docs/configs.md](docs/configs.md)
|
|
155
|
-
|
|
156
|
-
## 🗂️ Project Structure
|
|
157
|
-
|
|
158
|
-
```
|
|
159
|
-
src/
|
|
160
|
-
index.ts # Entry point + CLI arg parsing
|
|
161
|
-
agent/ # Main REPL loop + tool registry
|
|
162
|
-
cli/ # Input, output, slash commands, markdown rendering
|
|
163
|
-
llm-api/ # Provider factory + streaming turn logic
|
|
164
|
-
tools/ # shell, subagent, skill tools
|
|
165
|
-
# + webSearch, webContent
|
|
166
|
-
internal/ # shared internals, including the mc-edit helper implementation
|
|
167
|
-
mcp/ # MCP server connections
|
|
168
|
-
session/ # SQLite-backed session & history management
|
|
169
|
-
```
|
|
47
|
+
Use `/login` inside the REPL to authenticate via browser-based OAuth (currently Anthropic). No need to manage API keys manually.
|
|
170
48
|
|
|
171
49
|
---
|
|
172
50
|
|
|
173
|
-
##
|
|
51
|
+
## 🛠️ Features
|
|
174
52
|
|
|
175
|
-
- **
|
|
176
|
-
- **
|
|
177
|
-
- **
|
|
178
|
-
- **
|
|
179
|
-
- **
|
|
180
|
-
- **
|
|
53
|
+
- **Multi-provider** — auto-discovers Anthropic, OpenAI, Gemini, Ollama, or any OpenAI-compatible endpoint
|
|
54
|
+
- **Session memory** — SQLite-backed. Resume with `-c` or `-r <id>`
|
|
55
|
+
- **Shell integration** — `!` prefix for inline commands, `@` to reference files with tab completion
|
|
56
|
+
- **Subagents** — spawn parallel mini-coder instances for independent subtasks
|
|
57
|
+
- **Web search** — `webSearch` + `webContent` tools when `EXA_API_KEY` is set
|
|
58
|
+
- **MCP support** — connect external tool servers over HTTP or stdio
|
|
59
|
+
- **Custom commands** — drop `.md` files in `.agents/commands/` → instant `/slash` commands
|
|
60
|
+
- **Custom agents** — `.agents/agents/*.md` for specialized personas, usable as primary or via subagent
|
|
61
|
+
- **Skills** — `.agents/skills/<name>/SKILL.md`, inject with `@name`
|
|
62
|
+
- **`mc-edit`** — safe, exact-text file editing (no full-file rewrites)
|
|
63
|
+
- **16 ANSI colors** — inherits your terminal theme. Always looks right.
|
|
181
64
|
|
|
182
65
|
---
|
|
183
66
|
|
|
184
|
-
##
|
|
67
|
+
## 📚 Getting Deeper
|
|
185
68
|
|
|
186
|
-
|
|
69
|
+
The README is the highlight reel. For the full story — slash commands, config folders, context files, app data, and everything else:
|
|
187
70
|
|
|
188
|
-
|
|
71
|
+
**[📖 Read the Full Manual](docs/mini-coder.1.md)**
|
|
189
72
|
|
|
190
73
|
---
|
|
191
74
|
|
|
192
|
-
##
|
|
75
|
+
## 🔮 Tech Stack
|
|
193
76
|
|
|
194
|
-
|
|
195
|
-
> — [vpr99](https://github.com/vpr99)
|
|
77
|
+
[Bun.js](https://bun.com) · [AI SDK](https://ai-sdk.dev) · [yoctocolors](https://github.com/sindresorhus/yoctocolors)
|
|
196
78
|
|
|
197
|
-
|
|
79
|
+
## 📄 License
|
|
198
80
|
|
|
199
|
-
|
|
81
|
+
MIT — [github.com/sacenox/mini-coder](https://github.com/sacenox/mini-coder)
|
package/dist/mc.js
CHANGED
|
@@ -9,7 +9,7 @@ import * as c23 from "yoctocolors";
|
|
|
9
9
|
import * as c12 from "yoctocolors";
|
|
10
10
|
|
|
11
11
|
// src/cli/load-markdown-configs.ts
|
|
12
|
-
import { existsSync as existsSync4, readdirSync as
|
|
12
|
+
import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
13
13
|
import { homedir as homedir4 } from "os";
|
|
14
14
|
import { basename, join as join5 } from "path";
|
|
15
15
|
|
|
@@ -99,16 +99,58 @@ ${content}
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
// src/cli/error-log.ts
|
|
102
|
-
import {
|
|
102
|
+
import { writeFileSync } from "fs";
|
|
103
|
+
|
|
104
|
+
// src/cli/log-paths.ts
|
|
105
|
+
import { mkdirSync, readdirSync, unlinkSync } from "fs";
|
|
103
106
|
import { homedir } from "os";
|
|
104
107
|
import { join as join2 } from "path";
|
|
108
|
+
var LOGS_DIR = join2(homedir(), ".config", "mini-coder", "logs");
|
|
109
|
+
function getLogsDir() {
|
|
110
|
+
mkdirSync(LOGS_DIR, { recursive: true });
|
|
111
|
+
return LOGS_DIR;
|
|
112
|
+
}
|
|
113
|
+
function pidLogPath(prefix) {
|
|
114
|
+
return join2(getLogsDir(), `${prefix}-${process.pid}.log`);
|
|
115
|
+
}
|
|
116
|
+
function isProcessAlive(pid) {
|
|
117
|
+
try {
|
|
118
|
+
process.kill(pid, 0);
|
|
119
|
+
return true;
|
|
120
|
+
} catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
var LOG_FILE_RE = /^(api|errors)-(\d+)\.log$/;
|
|
125
|
+
function cleanStaleLogs() {
|
|
126
|
+
let entries;
|
|
127
|
+
try {
|
|
128
|
+
entries = readdirSync(LOGS_DIR);
|
|
129
|
+
} catch {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
for (const name of entries) {
|
|
133
|
+
const m = LOG_FILE_RE.exec(name);
|
|
134
|
+
if (!m)
|
|
135
|
+
continue;
|
|
136
|
+
const pidStr = m[2];
|
|
137
|
+
const pid = Number.parseInt(pidStr, 10);
|
|
138
|
+
if (pid === process.pid)
|
|
139
|
+
continue;
|
|
140
|
+
if (!isProcessAlive(pid)) {
|
|
141
|
+
try {
|
|
142
|
+
unlinkSync(join2(LOGS_DIR, name));
|
|
143
|
+
} catch {}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/cli/error-log.ts
|
|
105
149
|
var writer = null;
|
|
106
150
|
function initErrorLog() {
|
|
107
151
|
if (writer)
|
|
108
152
|
return;
|
|
109
|
-
const
|
|
110
|
-
const logPath = join2(dirPath, "errors.log");
|
|
111
|
-
mkdirSync(dirPath, { recursive: true });
|
|
153
|
+
const logPath = pidLogPath("errors");
|
|
112
154
|
writeFileSync(logPath, "");
|
|
113
155
|
writer = Bun.file(logPath).writer();
|
|
114
156
|
process.on("uncaughtException", (err) => {
|
|
@@ -295,7 +337,7 @@ import {
|
|
|
295
337
|
closeSync,
|
|
296
338
|
existsSync as existsSync2,
|
|
297
339
|
openSync,
|
|
298
|
-
readdirSync,
|
|
340
|
+
readdirSync as readdirSync2,
|
|
299
341
|
readFileSync as readFileSync2,
|
|
300
342
|
readSync,
|
|
301
343
|
statSync
|
|
@@ -384,7 +426,7 @@ function listSkillCandidates(skillsDir, source, rootPath) {
|
|
|
384
426
|
return [];
|
|
385
427
|
let entries;
|
|
386
428
|
try {
|
|
387
|
-
entries =
|
|
429
|
+
entries = readdirSync2(skillsDir).sort((a, b) => a.localeCompare(b));
|
|
388
430
|
} catch {
|
|
389
431
|
return [];
|
|
390
432
|
}
|
|
@@ -1367,6 +1409,8 @@ function buildShellSummaryParts(opts) {
|
|
|
1367
1409
|
function shouldPreviewShellStdout(opts) {
|
|
1368
1410
|
if (opts.stdoutLines === 0)
|
|
1369
1411
|
return false;
|
|
1412
|
+
if (opts.stdoutSingleLine !== null && opts.stderrLines === 0)
|
|
1413
|
+
return false;
|
|
1370
1414
|
if (!opts.success || opts.stderrLines > 0)
|
|
1371
1415
|
return true;
|
|
1372
1416
|
return opts.stdoutSingleLine === null;
|
|
@@ -1514,6 +1558,7 @@ function renderWebContentResult(result, opts) {
|
|
|
1514
1558
|
function renderMcpResult(result, opts) {
|
|
1515
1559
|
const content = Array.isArray(result) ? result : [result];
|
|
1516
1560
|
const maxBlocks = opts?.verboseOutput ? content.length : 5;
|
|
1561
|
+
let rendered = false;
|
|
1517
1562
|
for (const block of content.slice(0, maxBlocks)) {
|
|
1518
1563
|
if (block?.type === "text" && block.text) {
|
|
1519
1564
|
const maxLines = opts?.verboseOutput ? Number.POSITIVE_INFINITY : 6;
|
|
@@ -1521,9 +1566,10 @@ function renderMcpResult(result, opts) {
|
|
|
1521
1566
|
`).slice(0, maxLines);
|
|
1522
1567
|
for (const line of lines)
|
|
1523
1568
|
writeln(` ${c4.dim("\u2502")} ${line}`);
|
|
1569
|
+
rendered = true;
|
|
1524
1570
|
}
|
|
1525
1571
|
}
|
|
1526
|
-
return
|
|
1572
|
+
return rendered;
|
|
1527
1573
|
}
|
|
1528
1574
|
var TOOL_RESULT_RENDERERS = {
|
|
1529
1575
|
shell: renderShellResult,
|
|
@@ -1553,6 +1599,10 @@ function toolGlyph(name) {
|
|
|
1553
1599
|
return G.read;
|
|
1554
1600
|
if (name === "listSkills")
|
|
1555
1601
|
return G.search;
|
|
1602
|
+
if (name === "webSearch")
|
|
1603
|
+
return G.search;
|
|
1604
|
+
if (name === "webContent")
|
|
1605
|
+
return G.read;
|
|
1556
1606
|
if (name.startsWith("mcp_"))
|
|
1557
1607
|
return G.mcp;
|
|
1558
1608
|
return G.info;
|
|
@@ -1580,6 +1630,17 @@ function buildToolCallLine(name, args) {
|
|
|
1580
1630
|
const skillName = typeof a.name === "string" ? a.name : "";
|
|
1581
1631
|
return `${G.read} ${c5.dim("read skill")}${skillName ? ` ${skillName}` : ""}`;
|
|
1582
1632
|
}
|
|
1633
|
+
if (name === "webSearch") {
|
|
1634
|
+
const query = typeof a.query === "string" ? a.query : "";
|
|
1635
|
+
const short = query.length > 60 ? `${query.slice(0, 57)}\u2026` : query;
|
|
1636
|
+
return `${G.search} ${c5.dim("search")}${short ? ` ${short}` : ""}`;
|
|
1637
|
+
}
|
|
1638
|
+
if (name === "webContent") {
|
|
1639
|
+
const urls = Array.isArray(a.urls) ? a.urls : [];
|
|
1640
|
+
const label = urls.length === 1 ? String(urls[0]) : `${urls.length} url${urls.length !== 1 ? "s" : ""}`;
|
|
1641
|
+
const short = label.length > 60 ? `${label.slice(0, 57)}\u2026` : label;
|
|
1642
|
+
return `${G.read} ${c5.dim("fetch")} ${short}`;
|
|
1643
|
+
}
|
|
1583
1644
|
if (name.startsWith("mcp_")) {
|
|
1584
1645
|
return `${G.mcp} ${c5.dim(name)}`;
|
|
1585
1646
|
}
|
|
@@ -1621,6 +1682,8 @@ async function renderTurn(events, spinner, opts) {
|
|
|
1621
1682
|
let contextTokens = 0;
|
|
1622
1683
|
let newMessages = [];
|
|
1623
1684
|
const startedToolCalls = new Set;
|
|
1685
|
+
const toolCallInfo = new Map;
|
|
1686
|
+
let parallelCallCount = 0;
|
|
1624
1687
|
let renderedVisibleOutput = false;
|
|
1625
1688
|
let reasoningComputed = false;
|
|
1626
1689
|
let reasoningText = "";
|
|
@@ -1657,11 +1720,19 @@ async function renderTurn(events, spinner, opts) {
|
|
|
1657
1720
|
if (startedToolCalls.has(event.toolCallId)) {
|
|
1658
1721
|
break;
|
|
1659
1722
|
}
|
|
1723
|
+
const isConsecutiveToolCall = startedToolCalls.size > 0 && toolCallInfo.size > 0;
|
|
1660
1724
|
startedToolCalls.add(event.toolCallId);
|
|
1725
|
+
toolCallInfo.set(event.toolCallId, {
|
|
1726
|
+
toolName: event.toolName,
|
|
1727
|
+
label: buildToolCallLine(event.toolName, event.args)
|
|
1728
|
+
});
|
|
1729
|
+
if (toolCallInfo.size > 1) {
|
|
1730
|
+
parallelCallCount = toolCallInfo.size;
|
|
1731
|
+
}
|
|
1661
1732
|
liveReasoning.finish();
|
|
1662
1733
|
content.flushOpenContent();
|
|
1663
1734
|
spinner.stop();
|
|
1664
|
-
if (renderedVisibleOutput)
|
|
1735
|
+
if (renderedVisibleOutput && !isConsecutiveToolCall)
|
|
1665
1736
|
writeln();
|
|
1666
1737
|
renderToolCall(event.toolName, event.args);
|
|
1667
1738
|
renderedVisibleOutput = true;
|
|
@@ -1670,8 +1741,15 @@ async function renderTurn(events, spinner, opts) {
|
|
|
1670
1741
|
}
|
|
1671
1742
|
case "tool-result": {
|
|
1672
1743
|
startedToolCalls.delete(event.toolCallId);
|
|
1744
|
+
const callInfo = toolCallInfo.get(event.toolCallId);
|
|
1745
|
+
toolCallInfo.delete(event.toolCallId);
|
|
1673
1746
|
liveReasoning.finish();
|
|
1674
1747
|
spinner.stop();
|
|
1748
|
+
if (parallelCallCount > 1 && callInfo) {
|
|
1749
|
+
writeln(` ${c6.dim("\u21B3")} ${callInfo.label}`);
|
|
1750
|
+
}
|
|
1751
|
+
if (toolCallInfo.size === 0)
|
|
1752
|
+
parallelCallCount = 0;
|
|
1675
1753
|
renderToolResult(event.toolName, event.result, event.isError, {
|
|
1676
1754
|
verboseOutput
|
|
1677
1755
|
});
|
|
@@ -1725,7 +1803,7 @@ async function renderTurn(events, spinner, opts) {
|
|
|
1725
1803
|
|
|
1726
1804
|
// src/cli/output.ts
|
|
1727
1805
|
var HOME = homedir3();
|
|
1728
|
-
var PACKAGE_VERSION = "0.
|
|
1806
|
+
var PACKAGE_VERSION = "0.1.1";
|
|
1729
1807
|
function tildePath(p) {
|
|
1730
1808
|
return p.startsWith(HOME) ? `~${p.slice(HOME.length)}` : p;
|
|
1731
1809
|
}
|
|
@@ -1948,7 +2026,7 @@ function loadMarkdownConfigs(opts) {
|
|
|
1948
2026
|
return configs;
|
|
1949
2027
|
let entries;
|
|
1950
2028
|
try {
|
|
1951
|
-
entries =
|
|
2029
|
+
entries = readdirSync3(dir);
|
|
1952
2030
|
} catch {
|
|
1953
2031
|
return configs;
|
|
1954
2032
|
}
|
|
@@ -2088,7 +2166,7 @@ async function connectMcpServer(config) {
|
|
|
2088
2166
|
|
|
2089
2167
|
// src/session/db/connection.ts
|
|
2090
2168
|
import { Database } from "bun:sqlite";
|
|
2091
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync2, unlinkSync } from "fs";
|
|
2169
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
2092
2170
|
import { homedir as homedir5 } from "os";
|
|
2093
2171
|
import { join as join6 } from "path";
|
|
2094
2172
|
function getConfigDir() {
|
|
@@ -2219,7 +2297,7 @@ function getDb() {
|
|
|
2219
2297
|
} catch {}
|
|
2220
2298
|
for (const path of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
2221
2299
|
if (existsSync5(path))
|
|
2222
|
-
|
|
2300
|
+
unlinkSync2(path);
|
|
2223
2301
|
}
|
|
2224
2302
|
db = new Database(dbPath, { create: true });
|
|
2225
2303
|
configureDb(db);
|
|
@@ -3502,17 +3580,13 @@ async function getAccessToken(providerId) {
|
|
|
3502
3580
|
}
|
|
3503
3581
|
|
|
3504
3582
|
// src/llm-api/api-log.ts
|
|
3505
|
-
import {
|
|
3506
|
-
import { homedir as homedir6 } from "os";
|
|
3507
|
-
import { join as join9 } from "path";
|
|
3583
|
+
import { writeFileSync as writeFileSync2 } from "fs";
|
|
3508
3584
|
var writer2 = null;
|
|
3509
3585
|
var MAX_ENTRY_BYTES = 8 * 1024;
|
|
3510
3586
|
function initApiLog() {
|
|
3511
3587
|
if (writer2)
|
|
3512
3588
|
return;
|
|
3513
|
-
const
|
|
3514
|
-
const logPath = join9(dirPath, "api.log");
|
|
3515
|
-
mkdirSync3(dirPath, { recursive: true });
|
|
3589
|
+
const logPath = pidLogPath("api");
|
|
3516
3590
|
writeFileSync2(logPath, "");
|
|
3517
3591
|
writer2 = Bun.file(logPath).writer();
|
|
3518
3592
|
}
|
|
@@ -3933,24 +4007,31 @@ async function fetchOpenAIModels() {
|
|
|
3933
4007
|
free: false
|
|
3934
4008
|
}));
|
|
3935
4009
|
}
|
|
3936
|
-
async function
|
|
4010
|
+
async function getAnthropicAuth() {
|
|
3937
4011
|
const envKey = process.env.ANTHROPIC_API_KEY;
|
|
3938
4012
|
if (envKey)
|
|
3939
|
-
return envKey;
|
|
3940
|
-
if (isLoggedIn("anthropic"))
|
|
3941
|
-
|
|
4013
|
+
return { key: envKey, oauth: false };
|
|
4014
|
+
if (isLoggedIn("anthropic")) {
|
|
4015
|
+
const token = await getAccessToken("anthropic");
|
|
4016
|
+
if (token)
|
|
4017
|
+
return { key: token, oauth: true };
|
|
4018
|
+
}
|
|
3942
4019
|
return null;
|
|
3943
4020
|
}
|
|
3944
4021
|
async function fetchAnthropicModels() {
|
|
3945
|
-
const
|
|
3946
|
-
if (!
|
|
4022
|
+
const auth = await getAnthropicAuth();
|
|
4023
|
+
if (!auth)
|
|
3947
4024
|
return null;
|
|
3948
|
-
const
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
}
|
|
3953
|
-
|
|
4025
|
+
const headers = {
|
|
4026
|
+
"anthropic-version": "2023-06-01"
|
|
4027
|
+
};
|
|
4028
|
+
if (auth.oauth) {
|
|
4029
|
+
headers.Authorization = `Bearer ${auth.key}`;
|
|
4030
|
+
headers["anthropic-beta"] = "oauth-2025-04-20";
|
|
4031
|
+
} else {
|
|
4032
|
+
headers["x-api-key"] = auth.key;
|
|
4033
|
+
}
|
|
4034
|
+
const payload = await fetchJson(`${ANTHROPIC_BASE}/v1/models`, { headers }, 6000);
|
|
3954
4035
|
return processModelsList(payload, "data", "id", (item, modelId) => {
|
|
3955
4036
|
const displayName = typeof item.display_name === "string" && item.display_name.trim().length > 0 ? item.display_name : modelId;
|
|
3956
4037
|
return {
|
|
@@ -5487,8 +5568,8 @@ function getMostRecentSession() {
|
|
|
5487
5568
|
|
|
5488
5569
|
// src/agent/system-prompt.ts
|
|
5489
5570
|
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
5490
|
-
import { homedir as
|
|
5491
|
-
import { join as
|
|
5571
|
+
import { homedir as homedir6 } from "os";
|
|
5572
|
+
import { join as join9 } from "path";
|
|
5492
5573
|
function tryReadFile(p) {
|
|
5493
5574
|
if (!existsSync6(p))
|
|
5494
5575
|
return null;
|
|
@@ -5499,11 +5580,11 @@ function tryReadFile(p) {
|
|
|
5499
5580
|
}
|
|
5500
5581
|
}
|
|
5501
5582
|
function loadGlobalContextFile(homeDir) {
|
|
5502
|
-
const globalDir =
|
|
5503
|
-
return tryReadFile(
|
|
5583
|
+
const globalDir = join9(homeDir, ".agents");
|
|
5584
|
+
return tryReadFile(join9(globalDir, "AGENTS.md")) ?? tryReadFile(join9(globalDir, "CLAUDE.md"));
|
|
5504
5585
|
}
|
|
5505
5586
|
function loadLocalContextFile(cwd) {
|
|
5506
|
-
return tryReadFile(
|
|
5587
|
+
return tryReadFile(join9(cwd, ".agents", "AGENTS.md")) ?? tryReadFile(join9(cwd, "CLAUDE.md")) ?? tryReadFile(join9(cwd, "AGENTS.md"));
|
|
5507
5588
|
}
|
|
5508
5589
|
var AUTONOMY = `
|
|
5509
5590
|
|
|
@@ -5545,7 +5626,7 @@ var FINAL_MESSAGE = `
|
|
|
5545
5626
|
- If verification could not be run, say so clearly.`;
|
|
5546
5627
|
var SUBAGENT_DELEGATION = `You are running as a subagent. Complete the task you have been given directly using your tools. Do not spawn further subagents unless the subtask is unambiguously separable and self-contained.`;
|
|
5547
5628
|
function buildSystemPrompt(sessionTimeAnchor, cwd, extraSystemPrompt, isSubagent, homeDir) {
|
|
5548
|
-
const globalContext = loadGlobalContextFile(homeDir ??
|
|
5629
|
+
const globalContext = loadGlobalContextFile(homeDir ?? homedir6());
|
|
5549
5630
|
const localContext = loadLocalContextFile(cwd);
|
|
5550
5631
|
const cwdDisplay = tildePath(cwd);
|
|
5551
5632
|
let prompt = `You are mini-coder, a small and fast CLI coding agent.
|
|
@@ -6094,7 +6175,7 @@ import { z as z2 } from "zod";
|
|
|
6094
6175
|
|
|
6095
6176
|
// src/internal/file-edit/command.ts
|
|
6096
6177
|
import { existsSync as existsSync7 } from "fs";
|
|
6097
|
-
import { dirname as dirname2, extname, join as
|
|
6178
|
+
import { dirname as dirname2, extname, join as join10 } from "path";
|
|
6098
6179
|
import { fileURLToPath } from "url";
|
|
6099
6180
|
function quoteShellArg(value) {
|
|
6100
6181
|
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
@@ -6106,7 +6187,7 @@ function resolveSiblingFileEditScript(scriptPath) {
|
|
|
6106
6187
|
const mainDir = dirname2(scriptPath);
|
|
6107
6188
|
const mainBase = scriptPath.slice(mainDir.length + 1);
|
|
6108
6189
|
if (mainBase === `index${ext}` || mainBase === `mc${ext}`) {
|
|
6109
|
-
return
|
|
6190
|
+
return join10(mainDir, `mc-edit${ext}`);
|
|
6110
6191
|
}
|
|
6111
6192
|
return null;
|
|
6112
6193
|
}
|
|
@@ -6115,7 +6196,7 @@ function resolveModuleLocalFileEditScript(moduleUrl) {
|
|
|
6115
6196
|
const ext = extname(modulePath);
|
|
6116
6197
|
if (!ext)
|
|
6117
6198
|
return null;
|
|
6118
|
-
const helperPath =
|
|
6199
|
+
const helperPath = join10(dirname2(modulePath), "..", "..", `mc-edit${ext}`);
|
|
6119
6200
|
return existsSync7(helperPath) ? helperPath : null;
|
|
6120
6201
|
}
|
|
6121
6202
|
function resolveFileEditCommand(execPath, mainModule, argv1, moduleUrl = import.meta.url) {
|
|
@@ -6482,7 +6563,7 @@ function parseArgs(argv) {
|
|
|
6482
6563
|
const args = {
|
|
6483
6564
|
model: null,
|
|
6484
6565
|
sessionId: null,
|
|
6485
|
-
|
|
6566
|
+
list: false,
|
|
6486
6567
|
resumeLast: false,
|
|
6487
6568
|
prompt: null,
|
|
6488
6569
|
cwd: process.cwd(),
|
|
@@ -6509,7 +6590,7 @@ function parseArgs(argv) {
|
|
|
6509
6590
|
break;
|
|
6510
6591
|
case "--list":
|
|
6511
6592
|
case "-l":
|
|
6512
|
-
args.
|
|
6593
|
+
args.list = true;
|
|
6513
6594
|
break;
|
|
6514
6595
|
case "--cwd":
|
|
6515
6596
|
args.cwd = argv[++i] ?? process.cwd();
|
|
@@ -6581,9 +6662,9 @@ ${c13.bold("Examples:")}`);
|
|
|
6581
6662
|
}
|
|
6582
6663
|
|
|
6583
6664
|
// src/cli/bootstrap.ts
|
|
6584
|
-
import { existsSync as existsSync8, mkdirSync as
|
|
6585
|
-
import { homedir as
|
|
6586
|
-
import { join as
|
|
6665
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
6666
|
+
import { homedir as homedir7 } from "os";
|
|
6667
|
+
import { join as join11 } from "path";
|
|
6587
6668
|
import * as c14 from "yoctocolors";
|
|
6588
6669
|
var REVIEW_COMMAND_CONTENT = `---
|
|
6589
6670
|
description: Review recent changes for correctness, code quality, and performance
|
|
@@ -6603,17 +6684,17 @@ Perform a sensible code review:
|
|
|
6603
6684
|
Output a small summary with only the issues found. If nothing is wrong, say so.
|
|
6604
6685
|
`;
|
|
6605
6686
|
function bootstrapGlobalDefaults() {
|
|
6606
|
-
const commandsDir =
|
|
6607
|
-
const reviewPath =
|
|
6687
|
+
const commandsDir = join11(homedir7(), ".agents", "commands");
|
|
6688
|
+
const reviewPath = join11(commandsDir, "review.md");
|
|
6608
6689
|
if (!existsSync8(reviewPath)) {
|
|
6609
|
-
|
|
6690
|
+
mkdirSync3(commandsDir, { recursive: true });
|
|
6610
6691
|
writeFileSync3(reviewPath, REVIEW_COMMAND_CONTENT, "utf-8");
|
|
6611
6692
|
writeln(`${c14.green("\u2713")} created ${c14.dim("~/.agents/commands/review.md")} ${c14.dim("(edit it to customise your reviews)")}`);
|
|
6612
6693
|
}
|
|
6613
6694
|
}
|
|
6614
6695
|
|
|
6615
6696
|
// src/cli/file-refs.ts
|
|
6616
|
-
import { join as
|
|
6697
|
+
import { join as join12 } from "path";
|
|
6617
6698
|
async function resolveFileRefs(text, cwd) {
|
|
6618
6699
|
const atPattern = /@([\w./\-_]+)/g;
|
|
6619
6700
|
let result = text;
|
|
@@ -6641,7 +6722,7 @@ ${content}
|
|
|
6641
6722
|
result = result.slice(0, match.index) + replacement + result.slice((match.index ?? 0) + match[0].length);
|
|
6642
6723
|
continue;
|
|
6643
6724
|
}
|
|
6644
|
-
const filePath = ref.startsWith("/") ? ref :
|
|
6725
|
+
const filePath = ref.startsWith("/") ? ref : join12(cwd, ref);
|
|
6645
6726
|
if (isImageFilename(ref)) {
|
|
6646
6727
|
const attachment = await loadImageFile(filePath);
|
|
6647
6728
|
if (attachment) {
|
|
@@ -7570,6 +7651,9 @@ function writeJsonLine(write2, payload) {
|
|
|
7570
7651
|
registerTerminalCleanup();
|
|
7571
7652
|
initErrorLog();
|
|
7572
7653
|
initApiLog();
|
|
7654
|
+
if (!process.env.MC_SUBAGENT_DEPTH || process.env.MC_SUBAGENT_DEPTH === "0") {
|
|
7655
|
+
cleanStaleLogs();
|
|
7656
|
+
}
|
|
7573
7657
|
initModelInfoCache();
|
|
7574
7658
|
pruneOldData();
|
|
7575
7659
|
refreshModelInfoInBackground().catch(() => {});
|
|
@@ -7603,7 +7687,7 @@ async function main() {
|
|
|
7603
7687
|
printHelp();
|
|
7604
7688
|
process.exit(0);
|
|
7605
7689
|
}
|
|
7606
|
-
if (args.
|
|
7690
|
+
if (args.list) {
|
|
7607
7691
|
printSessionList();
|
|
7608
7692
|
process.exit(0);
|
|
7609
7693
|
}
|