miii-cli 0.2.1 → 0.2.3
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 +190 -83
- package/dist/config.js +0 -1
- package/dist/files/ops.js +22 -4
- package/dist/index.js +0 -1
- package/dist/init.js +0 -1
- package/dist/llm/ollama.js +0 -1
- package/dist/llm/stream.js +4 -3
- package/dist/parser/stream-parser.js +1 -13
- package/dist/sessions.js +0 -1
- package/dist/skills/loader.js +0 -1
- package/dist/tasks/compactor.js +68 -0
- package/dist/tasks/executor.js +88 -0
- package/dist/tasks/queue.js +72 -0
- package/dist/tools/index.js +108 -5
- package/dist/tui/App.js +0 -1
- package/dist/tui/InputBar.js +379 -32
- package/dist/tui/components/AtPicker.js +0 -1
- package/dist/tui/components/CommandPalette.js +4 -3
- package/dist/tui/components/InputArea.js +25 -13
- package/dist/tui/components/MessageList.js +12 -1
- package/dist/tui/components/ModelPicker.js +0 -1
- package/dist/tui/components/StatusBar.js +0 -1
- package/dist/tui/printer.js +0 -1
- package/dist/types.js +0 -1
- package/dist/workers/context.worker.js +0 -1
- package/dist/workers/spawn.js +0 -1
- package/package.json +6 -3
- package/.claude/settings.local.json +0 -28
- package/CONTRIBUTING.md +0 -55
- package/Makefile +0 -13
- package/dist/config.d.ts +0 -2
- package/dist/config.js.map +0 -1
- package/dist/files/ops.d.ts +0 -14
- package/dist/files/ops.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js.map +0 -1
- package/dist/init.d.ts +0 -1
- package/dist/init.js.map +0 -1
- package/dist/llm/ollama.d.ts +0 -10
- package/dist/llm/ollama.js.map +0 -1
- package/dist/llm/stream.d.ts +0 -12
- package/dist/llm/stream.js.map +0 -1
- package/dist/parser/stream-parser.d.ts +0 -21
- package/dist/parser/stream-parser.js.map +0 -1
- package/dist/sessions.d.ts +0 -9
- package/dist/sessions.js.map +0 -1
- package/dist/skills/loader.d.ts +0 -23
- package/dist/skills/loader.js.map +0 -1
- package/dist/tools/index.d.ts +0 -8
- package/dist/tools/index.js.map +0 -1
- package/dist/tui/App.d.ts +0 -9
- package/dist/tui/App.js.map +0 -1
- package/dist/tui/InputBar.d.ts +0 -10
- package/dist/tui/InputBar.js.map +0 -1
- package/dist/tui/components/AtPicker.d.ts +0 -8
- package/dist/tui/components/AtPicker.js.map +0 -1
- package/dist/tui/components/CommandPalette.d.ts +0 -8
- package/dist/tui/components/CommandPalette.js.map +0 -1
- package/dist/tui/components/InputArea.d.ts +0 -12
- package/dist/tui/components/InputArea.js.map +0 -1
- package/dist/tui/components/MessageList.d.ts +0 -11
- package/dist/tui/components/MessageList.js.map +0 -1
- package/dist/tui/components/ModelPicker.d.ts +0 -18
- package/dist/tui/components/ModelPicker.js.map +0 -1
- package/dist/tui/components/StatusBar.d.ts +0 -12
- package/dist/tui/components/StatusBar.js.map +0 -1
- package/dist/tui/printer.d.ts +0 -7
- package/dist/tui/printer.js.map +0 -1
- package/dist/types.d.ts +0 -20
- package/dist/types.js.map +0 -1
- package/dist/workers/context.worker.js.map +0 -1
- package/dist/workers/diff.worker.d.ts +0 -1
- package/dist/workers/diff.worker.js +0 -12
- package/dist/workers/diff.worker.js.map +0 -1
- package/dist/workers/spawn.d.ts +0 -1
- package/dist/workers/spawn.js.map +0 -1
- package/install.sh +0 -6
- package/mii-cli.gif +0 -0
- package/src/config.ts +0 -32
- package/src/files/ops.ts +0 -89
- package/src/index.ts +0 -11
- package/src/init.ts +0 -41
- package/src/llm/ollama.ts +0 -110
- package/src/llm/stream.ts +0 -55
- package/src/parser/stream-parser.ts +0 -196
- package/src/sessions.ts +0 -54
- package/src/skills/loader.ts +0 -144
- package/src/tools/index.ts +0 -151
- package/src/tui/App.tsx +0 -355
- package/src/tui/InputBar.tsx +0 -381
- package/src/tui/components/AtPicker.tsx +0 -49
- package/src/tui/components/CommandPalette.tsx +0 -50
- package/src/tui/components/InputArea.tsx +0 -297
- package/src/tui/components/MessageList.tsx +0 -219
- package/src/tui/components/ModelPicker.tsx +0 -134
- package/src/tui/components/StatusBar.tsx +0 -36
- package/src/tui/printer.ts +0 -130
- package/src/types.ts +0 -26
- package/src/workers/context.worker.ts +0 -66
- package/src/workers/diff.worker.ts +0 -20
- package/src/workers/spawn.ts +0 -19
- package/tsconfig.json +0 -18
- /package/dist/{workers/context.worker.d.ts → tasks/types.js} +0 -0
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# miii
|
|
1
|
+
# miii
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> A local AI coding assistant that actually works. No cloud. No Python. No API keys required.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
|
-
|
|
7
|
-
│ miii — Claude Code-level workflows,
|
|
8
|
-
|
|
6
|
+
╭──────────────────────────────────────────────────────────────╮
|
|
7
|
+
│ miii — Claude Code-level workflows, fully offline │
|
|
8
|
+
╰──────────────────────────────────────────────────────────────╯
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
[](https://www.npmjs.com/package/miii-cli)
|
|
@@ -16,21 +16,27 @@
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
## Why
|
|
19
|
+
## Why miii exists
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Every local AI coding tool is either too clunky to set up, requires cloud APIs, or has terminal output that's genuinely painful to read. miii is what happens when you build a local coding assistant that takes UX seriously — real Ink-based terminal UI, automatic git context, multi-file refactors with task queues, and a context compactor that keeps local models on-track.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Your code never leaves your machine.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
---
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
## What makes it different
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
| Feature | miii | aider | shell_gpt | open-interpreter |
|
|
30
|
+
|---|---|---|---|---|
|
|
31
|
+
| Ink terminal UI | ✅ | ❌ | ❌ | ❌ |
|
|
32
|
+
| Zero Python | ✅ | ❌ | ❌ | ❌ |
|
|
33
|
+
| Auto git context | ✅ | ✅ | ❌ | ❌ |
|
|
34
|
+
| Context compaction | ✅ | ✅ | ❌ | ❌ |
|
|
35
|
+
| Multi-file refactor queue | ✅ | partial | ❌ | ❌ |
|
|
36
|
+
| `.miiiignore` | ✅ | ✅ | ❌ | ❌ |
|
|
37
|
+
| Session persistence | ✅ | ❌ | ❌ | ❌ |
|
|
38
|
+
| Planning mode | ✅ | ❌ | ❌ | ❌ |
|
|
39
|
+
| Bundle size | 176K | ~50MB | ~40MB | ~100MB |
|
|
34
40
|
|
|
35
41
|
---
|
|
36
42
|
|
|
@@ -47,112 +53,206 @@ npm install -g miii-cli
|
|
|
47
53
|
## Quick start
|
|
48
54
|
|
|
49
55
|
```bash
|
|
50
|
-
#
|
|
56
|
+
# Start Ollama
|
|
51
57
|
ollama serve
|
|
52
58
|
|
|
53
|
-
#
|
|
59
|
+
# Pull a model
|
|
60
|
+
ollama pull qwen2.5-coder:7b
|
|
61
|
+
|
|
62
|
+
# Launch miii
|
|
54
63
|
miii
|
|
55
64
|
```
|
|
56
65
|
|
|
57
|
-
|
|
66
|
+
A model picker opens on launch. Select a model and start coding.
|
|
58
67
|
|
|
59
68
|
```bash
|
|
60
69
|
miii # default session
|
|
61
|
-
miii --model
|
|
70
|
+
miii --model qwen2.5-coder # specific model
|
|
62
71
|
miii --session myproject # named session
|
|
63
|
-
miii -s work -m
|
|
72
|
+
miii -s work -m codellama # short flags
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Auto git context
|
|
78
|
+
|
|
79
|
+
miii automatically detects changed files via `git status` and injects their contents into the model's context — no `@file` needed for files you're actively working on.
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
❯ fix the type error in the auth middleware
|
|
83
|
+
|
|
84
|
+
[auto-loaded 3 changed file(s)]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Only fires for code-related messages. Pure questions ("what is a closure?") skip the git scan entirely. Deduped — same files don't re-inject unless they change.
|
|
88
|
+
|
|
89
|
+
Disable per-project:
|
|
90
|
+
```json
|
|
91
|
+
{ "gitContext": false }
|
|
64
92
|
```
|
|
65
93
|
|
|
66
94
|
---
|
|
67
95
|
|
|
68
96
|
## File context with `@`
|
|
69
97
|
|
|
70
|
-
Type `@`
|
|
98
|
+
Type `@` to fuzzy-search and inject any project file:
|
|
71
99
|
|
|
72
100
|
```
|
|
73
101
|
❯ review the auth logic in @src/auth/middleware.ts
|
|
74
|
-
❯
|
|
102
|
+
❯ what does @src/utils/parser.ts return when input is empty?
|
|
75
103
|
```
|
|
76
104
|
|
|
77
105
|
Automatically excluded: `node_modules`, `dist`, `.git`, lock files, binaries, images.
|
|
78
106
|
|
|
79
107
|
---
|
|
80
108
|
|
|
109
|
+
## `.miiiignore`
|
|
110
|
+
|
|
111
|
+
Create `.miiiignore` in your project root to exclude files from `@` picker and auto-context:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
# .miiiignore
|
|
115
|
+
secrets/
|
|
116
|
+
*.generated.ts
|
|
117
|
+
fixtures/
|
|
118
|
+
*.sql
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Supports exact names, relative paths, and `*.ext` glob patterns.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Git integration
|
|
126
|
+
|
|
127
|
+
Full git workflow from the terminal:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
/git status show working tree
|
|
131
|
+
/git diff unstaged changes
|
|
132
|
+
/git diff --staged staged changes
|
|
133
|
+
/git log recent commits
|
|
134
|
+
/git review AI reviews current changes for bugs + improvements
|
|
135
|
+
/git branch list branches
|
|
136
|
+
/git commit <msg> stage all and commit
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The model also has access to git tools directly — it can check status, read diffs, and commit as part of autonomous workflows.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Multi-file refactor
|
|
144
|
+
|
|
145
|
+
Describe a goal, miii plans and executes across multiple files with isolated context per file:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
/refactor extract all database queries into a repository layer
|
|
149
|
+
/refactor rename UserService to AccountService everywhere
|
|
150
|
+
/refactor add input validation to all API route handlers
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Uses a priority task queue (P0=blocking, P1=reads parallel, P2=writes sequential, P3=verify). Each file gets its own fresh context — the local model never loses the thread on large codebases.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Planning mode
|
|
158
|
+
|
|
159
|
+
Structured step-by-step planning before writing any code:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
/plan add OAuth2 to this Express app
|
|
163
|
+
/plan refactor the frontend to use React Query
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
In planning mode:
|
|
167
|
+
```
|
|
168
|
+
/plan:next suggest next concrete steps
|
|
169
|
+
/plan:breakdown break topic into subtasks
|
|
170
|
+
/plan:review critique the plan so far
|
|
171
|
+
/plan:done exit planning mode
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
81
176
|
## Built-in commands
|
|
82
177
|
|
|
83
|
-
Type `/` to open the command palette.
|
|
178
|
+
Type `/` to open the command palette with fuzzy search.
|
|
84
179
|
|
|
85
180
|
| Command | Description |
|
|
86
181
|
|---|---|
|
|
87
|
-
| `/
|
|
182
|
+
| `/model <name>` | Switch model mid-session — no restart needed |
|
|
183
|
+
| `/models` | Open model picker, pull new Ollama models |
|
|
88
184
|
| `/session <name>` | Switch to or create a named session |
|
|
89
185
|
| `/sessions` | List all sessions with message counts |
|
|
186
|
+
| `/new` | Start a fresh auto-named session |
|
|
90
187
|
| `/clear` | Clear current session history |
|
|
91
|
-
| `/
|
|
92
|
-
| `/
|
|
93
|
-
| `/
|
|
94
|
-
| `/list` | Show loaded skills |
|
|
188
|
+
| `/plan [topic]` | Enter planning mode |
|
|
189
|
+
| `/refactor <goal>` | Multi-file AI refactor |
|
|
190
|
+
| `/git <subcommand>` | Git commands (see above) |
|
|
191
|
+
| `/list` | Show all loaded skills |
|
|
95
192
|
| `/exit` | Exit miii |
|
|
96
193
|
|
|
97
194
|
---
|
|
98
195
|
|
|
99
196
|
## Built-in tools
|
|
100
197
|
|
|
101
|
-
The model
|
|
198
|
+
The model calls these autonomously — reads, writes, edits, runs, tests, and commits on its own.
|
|
102
199
|
|
|
103
200
|
| Tool | Description |
|
|
104
201
|
|---|---|
|
|
105
202
|
| `read_file` | Read any file |
|
|
106
203
|
| `list_files` | List directory contents |
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
204
|
+
| `create_file` | Create a new file (fails if exists) |
|
|
205
|
+
| `edit_file` | Create or fully rewrite a file |
|
|
206
|
+
| `patch_file` | Targeted string replacement — throws if match is ambiguous |
|
|
207
|
+
| `delete_file` | Delete a file |
|
|
109
208
|
| `move_file` | Move or rename a file or directory |
|
|
110
|
-
| `
|
|
111
|
-
| `run_command` | Run
|
|
209
|
+
| `create_folder` | Create directory with parents |
|
|
210
|
+
| `run_command` | Run shell command in cwd (30s timeout) |
|
|
211
|
+
| `run_tests` | Run test suite, auto-detects jest/vitest/mocha from package.json |
|
|
212
|
+
| `git_status` | Working tree status |
|
|
213
|
+
| `git_diff` | Unstaged or staged diff (truncated at 8K) |
|
|
214
|
+
| `git_log` | Recent commit history |
|
|
215
|
+
| `git_commit` | Stage files and commit |
|
|
112
216
|
|
|
113
|
-
Tool calls chain up to 6 hops
|
|
217
|
+
Tool calls chain up to 6 hops — model reads, edits, runs tests, and verifies autonomously.
|
|
114
218
|
|
|
115
219
|
---
|
|
116
220
|
|
|
117
|
-
##
|
|
221
|
+
## Context compaction
|
|
118
222
|
|
|
119
|
-
|
|
223
|
+
Local models lose the thread after ~15-20 messages. miii auto-compacts context at threshold: keeps system prompt + original goal + tool result summary + last 6 exchanges. The session history is preserved on disk — only the LLM context window gets trimmed.
|
|
120
224
|
|
|
121
|
-
|
|
122
|
-
miii
|
|
123
|
-
✦ staring into the abyss (it blinked)…
|
|
124
|
-
```
|
|
225
|
+
---
|
|
125
226
|
|
|
126
|
-
|
|
227
|
+
## Thinking indicator
|
|
127
228
|
|
|
128
229
|
```
|
|
129
|
-
|
|
130
|
-
⚙ running read_file…
|
|
230
|
+
✦ staring into the abyss (it blinked)… [0:12]
|
|
131
231
|
|
|
132
|
-
|
|
133
|
-
|
|
232
|
+
⚙ running patch_file…
|
|
233
|
+
⚙ running run_tests…
|
|
134
234
|
```
|
|
135
235
|
|
|
136
|
-
|
|
236
|
+
Phrase rotates every 5 seconds. Tool name updates live as each call fires. Elapsed time shown throughout.
|
|
137
237
|
|
|
138
238
|
---
|
|
139
239
|
|
|
140
240
|
## Sessions
|
|
141
241
|
|
|
142
|
-
Every conversation
|
|
242
|
+
Every conversation persists automatically.
|
|
143
243
|
|
|
144
244
|
```bash
|
|
145
245
|
miii # resumes "default" session
|
|
146
246
|
miii --session feature-auth # resumes or creates "feature-auth"
|
|
147
247
|
```
|
|
148
248
|
|
|
149
|
-
Sessions stored at `~/.config/miii/sessions/`.
|
|
249
|
+
Sessions stored at `~/.config/miii/sessions/`. History capped at 100 messages in memory, full history on disk.
|
|
150
250
|
|
|
151
251
|
---
|
|
152
252
|
|
|
153
253
|
## Skills
|
|
154
254
|
|
|
155
|
-
|
|
255
|
+
Custom `/` commands via Markdown in `~/.config/miii/skills/`:
|
|
156
256
|
|
|
157
257
|
```markdown
|
|
158
258
|
---
|
|
@@ -164,32 +264,30 @@ Review the code I'm about to share. Look for bugs, edge cases, and improvements.
|
|
|
164
264
|
Be direct and specific. No markdown.
|
|
165
265
|
```
|
|
166
266
|
|
|
167
|
-
Use it:
|
|
168
|
-
|
|
169
267
|
```
|
|
170
268
|
/review
|
|
171
269
|
```
|
|
172
270
|
|
|
173
|
-
|
|
271
|
+
TypeScript skills with `execute` functions available for programmatic behavior.
|
|
174
272
|
|
|
175
273
|
---
|
|
176
274
|
|
|
177
275
|
## Configuration
|
|
178
276
|
|
|
179
|
-
Config
|
|
180
|
-
1. `.miii.json` in
|
|
277
|
+
Config loaded from (in order):
|
|
278
|
+
1. `.miii.json` in current directory
|
|
181
279
|
2. `~/.config/miii/config.json`
|
|
182
280
|
|
|
183
|
-
**Ollama
|
|
281
|
+
**Ollama:**
|
|
184
282
|
```json
|
|
185
283
|
{
|
|
186
|
-
"model": "
|
|
284
|
+
"model": "qwen2.5-coder:7b",
|
|
187
285
|
"provider": "ollama",
|
|
188
286
|
"baseUrl": "http://localhost:11434"
|
|
189
287
|
}
|
|
190
288
|
```
|
|
191
289
|
|
|
192
|
-
**OpenAI-compatible
|
|
290
|
+
**OpenAI-compatible (LM Studio, vLLM, Groq, Together, etc.):**
|
|
193
291
|
```json
|
|
194
292
|
{
|
|
195
293
|
"model": "gpt-4o",
|
|
@@ -199,7 +297,17 @@ Config is loaded from (in order):
|
|
|
199
297
|
}
|
|
200
298
|
```
|
|
201
299
|
|
|
202
|
-
|
|
300
|
+
**All options:**
|
|
301
|
+
```json
|
|
302
|
+
{
|
|
303
|
+
"model": "qwen2.5-coder:7b",
|
|
304
|
+
"provider": "ollama",
|
|
305
|
+
"baseUrl": "http://localhost:11434",
|
|
306
|
+
"apiKey": "",
|
|
307
|
+
"gitContext": true,
|
|
308
|
+
"systemPrompt": "optional override"
|
|
309
|
+
}
|
|
310
|
+
```
|
|
203
311
|
|
|
204
312
|
---
|
|
205
313
|
|
|
@@ -208,42 +316,25 @@ Works with LM Studio, vLLM, Groq, Together, and any other OpenAI-compatible serv
|
|
|
208
316
|
| Key | Action |
|
|
209
317
|
|---|---|
|
|
210
318
|
| `enter` | Send message |
|
|
211
|
-
| `ctrl+c` | Abort current request |
|
|
212
|
-
| `ctrl+c` x2 | Exit miii |
|
|
213
|
-
| `esc` | Close overlay or cancel |
|
|
214
319
|
| `↑ / ↓` | Navigate command palette or file picker |
|
|
320
|
+
| `esc` | Close overlay / abort request |
|
|
321
|
+
| `ctrl+c` | Abort current request or exit |
|
|
215
322
|
|
|
216
323
|
---
|
|
217
324
|
|
|
218
325
|
## Security
|
|
219
326
|
|
|
220
|
-
miii **0.1.5+** addresses the following OWASP issues:
|
|
221
|
-
|
|
222
327
|
| Issue | Fix |
|
|
223
328
|
|---|---|
|
|
224
|
-
| Path traversal (A01) | All file
|
|
225
|
-
| Path traversal (A01) | `@
|
|
226
|
-
| Path traversal (A01) |
|
|
227
|
-
|
|
|
228
|
-
|
|
|
229
|
-
| Insecure deserialization (A08) | Config loading whitelists allowed keys; session data validated as array |
|
|
230
|
-
| XML injection | File paths in context XML attributes are properly escaped |
|
|
231
|
-
| Configurable API key | OpenAI-compatible provider token configurable via `apiKey` in config |
|
|
232
|
-
|
|
233
|
-
`delete_file` requires explicit user confirmation (`y/n`) before executing.
|
|
329
|
+
| Path traversal (OWASP A01) | All file operations restricted to cwd via `guardPath()` |
|
|
330
|
+
| Path traversal (OWASP A01) | `@file` refs validated against cwd before reading |
|
|
331
|
+
| Path traversal (OWASP A01) | Session names sanitized to alphanumeric + hyphens |
|
|
332
|
+
| Injection (OWASP A03) | `run_command` enforces 30s execution timeout |
|
|
333
|
+
| Insecure deserialization (OWASP A08) | Config whitelists allowed keys; session data validated as array |
|
|
234
334
|
|
|
235
335
|
---
|
|
236
336
|
|
|
237
|
-
##
|
|
238
|
-
|
|
239
|
-
- **No streaming** — responses delivered in full, no partial renders or flickering
|
|
240
|
-
- **Thinking indicator** — rotating sarcastic phrases + sparkle icon while model processes
|
|
241
|
-
- **Tool status** — live indicator when a tool call is in flight
|
|
242
|
-
- **Status renamed** — internal status `streaming` → `thinking` throughout
|
|
243
|
-
|
|
244
|
-
---
|
|
245
|
-
|
|
246
|
-
## Source
|
|
337
|
+
## Build from source
|
|
247
338
|
|
|
248
339
|
```bash
|
|
249
340
|
git clone https://github.com/maruakshay/miii-cli
|
|
@@ -252,3 +343,19 @@ npm install
|
|
|
252
343
|
npm run build
|
|
253
344
|
npm link
|
|
254
345
|
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## What's in 0.2.x
|
|
350
|
+
|
|
351
|
+
- **Auto git context** — changed files injected automatically, code-gated heuristic
|
|
352
|
+
- **`.miiiignore`** — per-project file exclusions
|
|
353
|
+
- **Multi-file refactor** — macro/micro task queue, isolated context per file
|
|
354
|
+
- **Planning mode** — structured `/plan` workflow
|
|
355
|
+
- **Git integration** — full git toolkit in commands and model tools
|
|
356
|
+
- **`run_tests` tool** — model runs and retries tests autonomously
|
|
357
|
+
- **`/model` live switch** — change models mid-session
|
|
358
|
+
- **Context compaction** — auto-trim at 18 messages, keep goal + summary + recent
|
|
359
|
+
- **Ambiguous patch detection** — `patch_file` throws on multiple matches instead of silently corrupting
|
|
360
|
+
- **176K bundle** — removed dead workers, sourcemaps, unused deps (was 468K)
|
|
361
|
+
- **Debounced session saves** — writes at most once per 2s, no I/O on every keypress
|
package/dist/config.js
CHANGED
package/dist/files/ops.js
CHANGED
|
@@ -27,6 +27,14 @@ const SKIP_NAMES = new Set([
|
|
|
27
27
|
'poetry.lock', 'Gemfile.lock', 'composer.lock',
|
|
28
28
|
'.DS_Store', 'Thumbs.db', '.env.local', 'LICENSE', 'LICENSE.md',
|
|
29
29
|
]);
|
|
30
|
+
function loadIgnorePatterns(cwd) {
|
|
31
|
+
const p = join(cwd, '.miiiignore');
|
|
32
|
+
if (!existsSync(p))
|
|
33
|
+
return new Set();
|
|
34
|
+
return new Set(readFileSync(p, 'utf-8').split('\n')
|
|
35
|
+
.map(l => l.trim())
|
|
36
|
+
.filter(l => l && !l.startsWith('#')));
|
|
37
|
+
}
|
|
30
38
|
export function readFile(p) {
|
|
31
39
|
if (!existsSync(p))
|
|
32
40
|
return '';
|
|
@@ -46,9 +54,10 @@ export function moveFile(from, to) {
|
|
|
46
54
|
mkdirSync(dirname(to), { recursive: true });
|
|
47
55
|
renameSync(from, to);
|
|
48
56
|
}
|
|
49
|
-
export function listFiles(dir, recursive = false, cwd = process.cwd()) {
|
|
57
|
+
export function listFiles(dir, recursive = false, cwd = process.cwd(), _ignore) {
|
|
50
58
|
if (!existsSync(dir))
|
|
51
59
|
return [];
|
|
60
|
+
const ignore = _ignore ?? loadIgnorePatterns(cwd);
|
|
52
61
|
const entries = [];
|
|
53
62
|
for (const name of readdirSync(dir)) {
|
|
54
63
|
if (name.startsWith('.'))
|
|
@@ -70,12 +79,21 @@ export function listFiles(dir, recursive = false, cwd = process.cwd()) {
|
|
|
70
79
|
continue;
|
|
71
80
|
}
|
|
72
81
|
const full = join(dir, name);
|
|
82
|
+
const rel = relative(cwd, full);
|
|
73
83
|
const type = stat.isDirectory() ? 'dir' : 'file';
|
|
74
|
-
|
|
84
|
+
// check ignore patterns: match by name, rel path, or name/ for dirs
|
|
85
|
+
if (ignore.size) {
|
|
86
|
+
const dirSuffix = type === 'dir' ? name + '/' : '';
|
|
87
|
+
if (ignore.has(name) || ignore.has(rel) || (dirSuffix && ignore.has(dirSuffix)))
|
|
88
|
+
continue;
|
|
89
|
+
// *.ext pattern
|
|
90
|
+
if ([...ignore].some(p => p.startsWith('*.') && name.endsWith(p.slice(1))))
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
entries.push({ name, path: full, rel, type, size: stat.isFile() ? stat.size : undefined });
|
|
75
94
|
if (recursive && type === 'dir') {
|
|
76
|
-
entries.push(...listFiles(full, true, cwd));
|
|
95
|
+
entries.push(...listFiles(full, true, cwd, ignore));
|
|
77
96
|
}
|
|
78
97
|
}
|
|
79
98
|
return entries;
|
|
80
99
|
}
|
|
81
|
-
//# sourceMappingURL=ops.js.map
|
package/dist/index.js
CHANGED
package/dist/init.js
CHANGED
package/dist/llm/ollama.js
CHANGED
package/dist/llm/stream.js
CHANGED
|
@@ -4,7 +4,7 @@ export async function chat(cfg) {
|
|
|
4
4
|
return chatOllama(cfg);
|
|
5
5
|
}
|
|
6
6
|
async function chatOllama(cfg) {
|
|
7
|
-
const { model, messages, baseUrl, signal, onDone, onError } = cfg;
|
|
7
|
+
const { model, messages, baseUrl, signal, onDone, onError, onUsage } = cfg;
|
|
8
8
|
try {
|
|
9
9
|
const res = await fetch(`${baseUrl}/api/chat`, {
|
|
10
10
|
method: 'POST',
|
|
@@ -17,6 +17,7 @@ async function chatOllama(cfg) {
|
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
19
|
const obj = await res.json();
|
|
20
|
+
onUsage?.(obj?.prompt_eval_count ?? 0, obj?.eval_count ?? 0);
|
|
20
21
|
await onDone(obj?.message?.content ?? '');
|
|
21
22
|
}
|
|
22
23
|
catch (err) {
|
|
@@ -25,7 +26,7 @@ async function chatOllama(cfg) {
|
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
async function chatOpenAI(cfg) {
|
|
28
|
-
const { model, messages, baseUrl, apiKey, signal, onDone, onError } = cfg;
|
|
29
|
+
const { model, messages, baseUrl, apiKey, signal, onDone, onError, onUsage } = cfg;
|
|
29
30
|
try {
|
|
30
31
|
const res = await fetch(`${baseUrl}/v1/chat/completions`, {
|
|
31
32
|
method: 'POST',
|
|
@@ -38,6 +39,7 @@ async function chatOpenAI(cfg) {
|
|
|
38
39
|
return;
|
|
39
40
|
}
|
|
40
41
|
const obj = await res.json();
|
|
42
|
+
onUsage?.(obj?.usage?.prompt_tokens ?? 0, obj?.usage?.completion_tokens ?? 0);
|
|
41
43
|
await onDone(obj?.choices?.[0]?.message?.content ?? '');
|
|
42
44
|
}
|
|
43
45
|
catch (err) {
|
|
@@ -48,4 +50,3 @@ async function chatOpenAI(cfg) {
|
|
|
48
50
|
function toError(e) {
|
|
49
51
|
return e instanceof Error ? e : new Error(String(e));
|
|
50
52
|
}
|
|
51
|
-
//# sourceMappingURL=stream.js.map
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { appendFileSync } from 'fs';
|
|
2
1
|
const OPEN = '<tool_call>';
|
|
3
2
|
const CLOSE = '</tool_call>';
|
|
4
3
|
const CTAG_OPEN = '<content>';
|
|
@@ -7,13 +6,6 @@ const OLD_OPEN = '<old>';
|
|
|
7
6
|
const OLD_CLOSE = '</old>';
|
|
8
7
|
const NEW_OPEN = '<new>';
|
|
9
8
|
const NEW_CLOSE = '</new>';
|
|
10
|
-
const DEBUG_LOG = '/tmp/miii-debug.log';
|
|
11
|
-
function dbg(msg) {
|
|
12
|
-
try {
|
|
13
|
-
appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] ${msg}\n`);
|
|
14
|
-
}
|
|
15
|
-
catch { }
|
|
16
|
-
}
|
|
17
9
|
// Fix literal newlines/tabs inside JSON string values — common LLM output mistake
|
|
18
10
|
function sanitizeJson(s) {
|
|
19
11
|
let result = '';
|
|
@@ -157,7 +149,6 @@ export class StreamParser {
|
|
|
157
149
|
this.buf = this.buf.slice(end + CLOSE.length);
|
|
158
150
|
this.inTool = false;
|
|
159
151
|
try {
|
|
160
|
-
dbg(`raw block (${raw.length} chars): ${raw.slice(0, 300)}`);
|
|
161
152
|
// Extract named content blocks so file content never needs JSON escaping
|
|
162
153
|
const extraArgs = {};
|
|
163
154
|
let jsonPart = raw;
|
|
@@ -181,11 +172,9 @@ export class StreamParser {
|
|
|
181
172
|
extractBlock(NEW_OPEN, NEW_CLOSE, 'new');
|
|
182
173
|
const obj = parseToolJson(jsonPart);
|
|
183
174
|
obj.args = { ...(obj.args ?? {}), ...extraArgs };
|
|
184
|
-
dbg(`parsed ok: name=${obj.name} args_keys=${Object.keys(obj.args).join(',')}`);
|
|
185
175
|
out.push({ type: 'tool_call', content: raw, toolName: obj.name, toolArgs: obj.args });
|
|
186
176
|
}
|
|
187
|
-
catch
|
|
188
|
-
dbg(`parse FAILED: ${e} | raw: ${raw.slice(0, 300)}`);
|
|
177
|
+
catch {
|
|
189
178
|
out.push({ type: 'text', content: `${OPEN}${raw}${CLOSE}` });
|
|
190
179
|
}
|
|
191
180
|
}
|
|
@@ -218,4 +207,3 @@ export class StreamParser {
|
|
|
218
207
|
return out;
|
|
219
208
|
}
|
|
220
209
|
}
|
|
221
|
-
//# sourceMappingURL=stream-parser.js.map
|
package/dist/sessions.js
CHANGED
package/dist/skills/loader.js
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const COMPACT_THRESHOLD = 18; // compact when context exceeds this many messages
|
|
2
|
+
const KEEP_RECENT = 6; // always keep last N messages verbatim
|
|
3
|
+
export function shouldCompact(messages) {
|
|
4
|
+
return messages.length > COMPACT_THRESHOLD;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Compact context to keep local models on track during long refactors.
|
|
8
|
+
*
|
|
9
|
+
* Strategy:
|
|
10
|
+
* 1. Keep system prompt (index 0)
|
|
11
|
+
* 2. Keep first user message (original goal)
|
|
12
|
+
* 3. Summarise completed tool results in the middle into one message
|
|
13
|
+
* 4. Keep last KEEP_RECENT messages verbatim (model's working memory)
|
|
14
|
+
*/
|
|
15
|
+
export function compactContext(messages, goal) {
|
|
16
|
+
if (messages.length <= COMPACT_THRESHOLD)
|
|
17
|
+
return messages;
|
|
18
|
+
const system = messages[0]?.role === 'system' ? messages[0] : null;
|
|
19
|
+
const userGoal = messages.find(m => m.role === 'user' && !m.content.startsWith('['));
|
|
20
|
+
const anchorCount = (system ? 1 : 0) + (userGoal ? 1 : 0);
|
|
21
|
+
const middle = messages.slice(anchorCount, messages.length - KEEP_RECENT);
|
|
22
|
+
const recent = messages.slice(messages.length - KEEP_RECENT);
|
|
23
|
+
const toolResults = middle
|
|
24
|
+
.filter(m => m.role === 'user' && m.content.startsWith('Tool '))
|
|
25
|
+
.map(m => {
|
|
26
|
+
const lines = m.content.split('\n');
|
|
27
|
+
return `• ${lines[0]}`; // just the "Tool X result:" line
|
|
28
|
+
});
|
|
29
|
+
const assistantSummaries = middle
|
|
30
|
+
.filter(m => m.role === 'assistant' && m.content.trim().length > 0)
|
|
31
|
+
.map(m => m.content.slice(0, 120).replace(/\n/g, ' '));
|
|
32
|
+
const parts = [`[context compacted — ${middle.length} messages summarised]`];
|
|
33
|
+
if (goal)
|
|
34
|
+
parts.push(`Goal: ${goal}`);
|
|
35
|
+
if (toolResults.length)
|
|
36
|
+
parts.push(`Completed:\n${toolResults.join('\n')}`);
|
|
37
|
+
if (assistantSummaries.length)
|
|
38
|
+
parts.push(`Last reasoning: ${assistantSummaries.at(-1)}`);
|
|
39
|
+
const summary = { role: 'user', content: parts.join('\n\n') };
|
|
40
|
+
return [
|
|
41
|
+
...(system ? [system] : []),
|
|
42
|
+
...(userGoal ? [userGoal] : []),
|
|
43
|
+
summary,
|
|
44
|
+
...recent,
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Build a fresh isolated context for a single-file edit step.
|
|
49
|
+
* Keeps context tiny — avoids cross-file noise polluting the model.
|
|
50
|
+
*/
|
|
51
|
+
export function fileEditContext(systemPrompt, goal, filePath, fileContent, instruction) {
|
|
52
|
+
return [
|
|
53
|
+
{ role: 'system', content: systemPrompt },
|
|
54
|
+
{
|
|
55
|
+
role: 'user',
|
|
56
|
+
content: [
|
|
57
|
+
`Overall goal: ${goal}`,
|
|
58
|
+
``,
|
|
59
|
+
`File to edit: ${filePath}`,
|
|
60
|
+
`<file>`,
|
|
61
|
+
fileContent,
|
|
62
|
+
`</file>`,
|
|
63
|
+
``,
|
|
64
|
+
`Instruction: ${instruction}`,
|
|
65
|
+
].join('\n'),
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
}
|