miii-agent 0.1.0 → 0.1.4
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 +139 -53
- package/dist/cli.js +74 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,123 @@
|
|
|
1
1
|
# miii
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Your code never leaves your machine. No API keys. No cloud. No bullshit.
|
|
4
4
|
|
|
5
|
-
miii
|
|
5
|
+
**miii** is a local-first AI coding agent that lives in your terminal. Powered by [Ollama](https://ollama.com), it reads your code, writes features, runs tests, and fixes bugs — entirely on your hardware, at native speed.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/miii-agent)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://nodejs.org)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Demo
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Why miii?
|
|
20
|
+
|
|
21
|
+
Most AI coding tools are wrappers around cloud APIs. They're slow, expensive, and send your private code to someone else's server.
|
|
22
|
+
|
|
23
|
+
miii is different:
|
|
24
|
+
|
|
25
|
+
- **Local-first** — Powered by Ollama. Your code stays on your disk, period.
|
|
26
|
+
- **Zero ceremony** — No API keys. No billing. No accounts. Just `miii`.
|
|
27
|
+
- **Actually agentic** — miii doesn't just chat. It decomposes problems, calls tools, and verifies results like an engineer would.
|
|
28
|
+
- **Fast** — No network round-trips. Response time is limited by your GPU, not a CDN.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
### Prerequisites
|
|
35
|
+
|
|
36
|
+
- **Node.js** ≥ 18
|
|
37
|
+
- **Ollama** running locally — [install here](https://ollama.com/download)
|
|
38
|
+
- A coding model pulled locally:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
ollama pull qwen2.5-coder:14b
|
|
42
|
+
# or any model you prefer
|
|
43
|
+
ollama pull deepseek-coder-v2
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Install miii
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install -g miii-agent
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Launch
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
miii
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
That's it.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
Once inside the TUI, just type naturally:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
> refactor the auth module to use async/await
|
|
68
|
+
> @src/server.ts add rate limiting to all POST routes
|
|
69
|
+
> why are my tests failing in utils/parser.ts
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Keyboard Shortcuts
|
|
73
|
+
|
|
74
|
+
| Key | Action |
|
|
75
|
+
|-----|--------|
|
|
76
|
+
| `Enter` | Send prompt |
|
|
77
|
+
| `@filename` | Attach file to context |
|
|
78
|
+
| `/models` | Switch active Ollama model |
|
|
79
|
+
| `/clear` | Reset conversation history |
|
|
80
|
+
| `Esc` | Stop current generation or tool run |
|
|
81
|
+
| `Ctrl+C` | Quit |
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
Settings live in `~/.miii/config.json` and are created on first run.
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"model": "qwen2.5-coder:14b",
|
|
92
|
+
"ollamaHost": "http://localhost:11434",
|
|
93
|
+
"effort": "medium"
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
| Field | Description | Values |
|
|
98
|
+
|-------|-------------|--------|
|
|
99
|
+
| `model` | Default Ollama model | any `ollama list` model |
|
|
100
|
+
| `ollamaHost` | Ollama API endpoint | URL string |
|
|
101
|
+
| `effort` | Controls temperature & limits | `low` \| `medium` \| `high` |
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Capabilities
|
|
106
|
+
|
|
107
|
+
miii ships with a built-in tool suite the agent can invoke autonomously:
|
|
108
|
+
|
|
109
|
+
| Tool | What it does |
|
|
110
|
+
|------|-------------|
|
|
111
|
+
| `read_file` | Read any file in your workspace |
|
|
112
|
+
| `write_file` | Create new files |
|
|
113
|
+
| `edit_file` | Precise string-level edits (no rewrites) |
|
|
114
|
+
| `glob` | Pattern-match files across the project |
|
|
115
|
+
| `grep` | Regex search across files |
|
|
116
|
+
| `run_bash` | Execute shell commands |
|
|
117
|
+
|
|
118
|
+
Every sensitive operation is gated by a permission system — you approve what the agent can touch.
|
|
119
|
+
|
|
120
|
+
---
|
|
6
121
|
|
|
7
122
|
## Architecture
|
|
8
123
|
|
|
@@ -49,65 +164,36 @@ graph TD
|
|
|
49
164
|
App -.->|"reads"| Config
|
|
50
165
|
```
|
|
51
166
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
Most AI agents are wrappers around cloud APIs. They are slow, expensive, and a privacy nightmare. miii is different:
|
|
55
|
-
|
|
56
|
-
1. Local-First: Powered by Ollama. Your code stays on your disk.
|
|
57
|
-
2. Zero Ceremony: No API keys. No billing. Just run miii and start coding.
|
|
58
|
-
3. Engineering Mindset: miii doesn't just "chat". It treats every request as a bug, feature, or fix. It decomposes problems, executes tools, and verifies results.
|
|
59
|
-
|
|
60
|
-
## Project Status
|
|
61
|
-
|
|
62
|
-
This project is currently an MVP designed to demonstrate and refine basic AI coding skills. I am refurbishing older implementations and experimenting with the agent loop. Feel free to fork, modify, or do whatever you want with this codebase.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
## Capabilities
|
|
66
|
-
|
|
67
|
-
miii is equipped with a suite of tools to interact with your workspace:
|
|
68
|
-
|
|
69
|
-
- File System: read_file, write_file, edit_file (precise string replacement).
|
|
70
|
-
- Discovery: glob (pattern matching), grep (regex search).
|
|
71
|
-
- Execution: run_bash (shell command execution).
|
|
72
|
-
|
|
73
|
-
Every sensitive operation is gated by a permission system. You decide what the agent can touch.
|
|
167
|
+
---
|
|
74
168
|
|
|
75
|
-
##
|
|
76
|
-
|
|
77
|
-
### 1. Prerequisites
|
|
78
|
-
- Node.js 18+
|
|
79
|
-
- Ollama (running locally via ollama serve)
|
|
80
|
-
- A coder model (e.g., ollama pull qwen2.5-coder:14b)
|
|
169
|
+
## Development
|
|
81
170
|
|
|
82
|
-
|
|
83
|
-
|
|
171
|
+
```bash
|
|
172
|
+
git clone https://github.com/maruakshay/miii-cli.git
|
|
173
|
+
cd miii-cli
|
|
174
|
+
npm install
|
|
175
|
+
npm run dev
|
|
176
|
+
```
|
|
84
177
|
|
|
85
|
-
|
|
86
|
-
|
|
178
|
+
```bash
|
|
179
|
+
npm run build # production build
|
|
180
|
+
npm run start # run built output
|
|
181
|
+
```
|
|
87
182
|
|
|
88
|
-
|
|
183
|
+
---
|
|
89
184
|
|
|
90
|
-
|
|
91
|
-
- @file: Inline a file's content into the context.
|
|
92
|
-
- /models: Switch your active Ollama model.
|
|
93
|
-
- /clear: Reset conversation history.
|
|
94
|
-
- Esc: Stop the current generation or tool execution.
|
|
95
|
-
- Ctrl+C: Quit.
|
|
185
|
+
## Project Status
|
|
96
186
|
|
|
97
|
-
|
|
187
|
+
MVP. Core agent loop works. Actively refining tool execution, streaming, and the permission model. PRs welcome — fork it, break it, improve it.
|
|
98
188
|
|
|
99
|
-
|
|
100
|
-
- model: Your default LLM.
|
|
101
|
-
- ollamaHost: Your Ollama API endpoint.
|
|
102
|
-
- effort: Tuning for temperature and limits (low | medium | high).
|
|
189
|
+
---
|
|
103
190
|
|
|
191
|
+
## License
|
|
104
192
|
|
|
105
|
-
|
|
193
|
+
MIT © [maruakshay](https://github.com/maruakshay)
|
|
106
194
|
|
|
107
|
-
|
|
108
|
-
cd miii-cli
|
|
109
|
-
npm install
|
|
110
|
-
npm run dev
|
|
195
|
+
---
|
|
111
196
|
|
|
112
|
-
|
|
113
|
-
|
|
197
|
+
<p align="center">
|
|
198
|
+
Built for engineers who'd rather own their tools than rent them.
|
|
199
|
+
</p>
|
package/dist/cli.js
CHANGED
|
@@ -195,8 +195,8 @@ function ModelList({ models, cursor, activeModel, showActive }) {
|
|
|
195
195
|
"<model>"
|
|
196
196
|
] });
|
|
197
197
|
}
|
|
198
|
-
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: models.map((m, i) => /* @__PURE__ */ jsxs2(Text2, { color: i === cursor ? "
|
|
199
|
-
i === cursor ? "\
|
|
198
|
+
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: models.map((m, i) => /* @__PURE__ */ jsxs2(Text2, { color: i === cursor ? "blue" : void 0, dimColor: i !== cursor, children: [
|
|
199
|
+
i === cursor ? "\u276F " : " ",
|
|
200
200
|
m,
|
|
201
201
|
showActive && m === activeModel ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " (active)" }) : null
|
|
202
202
|
] }, m)) });
|
|
@@ -281,16 +281,19 @@ function CommandPalette({ filter, cursor }) {
|
|
|
281
281
|
Box5,
|
|
282
282
|
{
|
|
283
283
|
flexDirection: "column",
|
|
284
|
-
borderStyle: "
|
|
285
|
-
borderColor: "
|
|
284
|
+
borderStyle: "round",
|
|
285
|
+
borderColor: "gray",
|
|
286
286
|
marginX: 1,
|
|
287
287
|
marginBottom: 0,
|
|
288
288
|
paddingX: 1,
|
|
289
289
|
children: [
|
|
290
290
|
filtered.map((cmd, i) => {
|
|
291
291
|
const active = i === cursor;
|
|
292
|
-
return /* @__PURE__ */ jsxs5(Box5, { gap:
|
|
293
|
-
/* @__PURE__ */
|
|
292
|
+
return /* @__PURE__ */ jsxs5(Box5, { gap: 1, children: [
|
|
293
|
+
/* @__PURE__ */ jsxs5(Text5, { bold: active, color: active ? "blue" : void 0, dimColor: !active, children: [
|
|
294
|
+
active ? "\u276F " : " ",
|
|
295
|
+
cmd.name
|
|
296
|
+
] }),
|
|
294
297
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: cmd.description })
|
|
295
298
|
] }, cmd.name);
|
|
296
299
|
}),
|
|
@@ -364,16 +367,16 @@ function FilePicker({ matches, cursor }) {
|
|
|
364
367
|
Box6,
|
|
365
368
|
{
|
|
366
369
|
flexDirection: "column",
|
|
367
|
-
borderStyle: "
|
|
368
|
-
borderColor: "
|
|
370
|
+
borderStyle: "round",
|
|
371
|
+
borderColor: "gray",
|
|
369
372
|
marginX: 1,
|
|
370
373
|
marginBottom: 0,
|
|
371
374
|
paddingX: 1,
|
|
372
375
|
children: [
|
|
373
376
|
matches.map((f, i) => {
|
|
374
377
|
const active = i === cursor;
|
|
375
|
-
return /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsxs6(Text6, { bold: active, color: active ? "
|
|
376
|
-
active ? "\
|
|
378
|
+
return /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsxs6(Text6, { bold: active, color: active ? "blue" : void 0, dimColor: !active, children: [
|
|
379
|
+
active ? "\u276F " : " ",
|
|
377
380
|
f
|
|
378
381
|
] }) }, f);
|
|
379
382
|
}),
|
|
@@ -432,6 +435,14 @@ function ThinkingBlock({ content }) {
|
|
|
432
435
|
] });
|
|
433
436
|
}
|
|
434
437
|
|
|
438
|
+
// src/ui/constants.ts
|
|
439
|
+
var EMPTY_STATE_HINTS = [
|
|
440
|
+
"\u2022 explain @file \u2014 reference a file with @",
|
|
441
|
+
"\u2022 /models \u2014 switch model or effort",
|
|
442
|
+
"\u2022 ctrl+t \u2014 toggle thinking \xB7 esc \u2014 cancel"
|
|
443
|
+
];
|
|
444
|
+
var EMPTY_STATE_TITLE = "Ask anything, or try:";
|
|
445
|
+
|
|
435
446
|
// src/ui/ChatView.tsx
|
|
436
447
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
437
448
|
function formatTokens(n) {
|
|
@@ -642,16 +653,17 @@ function summarizeInput(input) {
|
|
|
642
653
|
return "";
|
|
643
654
|
}
|
|
644
655
|
function PermissionPrompt({ req, cursor }) {
|
|
656
|
+
const label = TOOL_LABEL[req.toolName] ?? req.toolName;
|
|
645
657
|
const options = [
|
|
646
658
|
{ label: "Yes", key: "yes" },
|
|
647
|
-
{ label: "No
|
|
659
|
+
{ label: "No", key: "no" }
|
|
648
660
|
];
|
|
649
661
|
const summary = summarizeInput(req.input);
|
|
650
662
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
|
|
651
663
|
/* @__PURE__ */ jsx8(Text8, { color: "blue", bold: true, children: "Tool use" }),
|
|
652
664
|
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
|
|
653
665
|
"Allow ",
|
|
654
|
-
/* @__PURE__ */ jsx8(Text8, { bold: true, children:
|
|
666
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, children: label }),
|
|
655
667
|
"?"
|
|
656
668
|
] }) }),
|
|
657
669
|
summary && /* @__PURE__ */ jsx8(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: summary }) }),
|
|
@@ -675,7 +687,15 @@ function ChatView({
|
|
|
675
687
|
activeToolUses,
|
|
676
688
|
activeToolResults
|
|
677
689
|
}) {
|
|
690
|
+
const empty = messages.length === 0 && !streaming && !thinking && !pendingPermission && !error;
|
|
678
691
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginLeft: 1, marginBottom: 1, children: [
|
|
692
|
+
empty && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
|
|
693
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: EMPTY_STATE_TITLE }),
|
|
694
|
+
EMPTY_STATE_HINTS.map((h, i) => /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
695
|
+
" ",
|
|
696
|
+
h
|
|
697
|
+
] }, i))
|
|
698
|
+
] }),
|
|
679
699
|
messages.map(
|
|
680
700
|
(msg, i) => msg.role === "user" ? /* @__PURE__ */ jsxs8(Box8, { flexDirection: "row", marginBottom: 1, children: [
|
|
681
701
|
/* @__PURE__ */ jsx8(Text8, { color: "blue", children: "\u25CF " }),
|
|
@@ -1726,6 +1746,41 @@ function useKeyboard(opts) {
|
|
|
1726
1746
|
});
|
|
1727
1747
|
}
|
|
1728
1748
|
|
|
1749
|
+
// src/updateCheck.ts
|
|
1750
|
+
import { createRequire } from "module";
|
|
1751
|
+
var require2 = createRequire(import.meta.url);
|
|
1752
|
+
var PKG_NAME = "miii-agent";
|
|
1753
|
+
function currentVersion() {
|
|
1754
|
+
try {
|
|
1755
|
+
return require2("../package.json").version;
|
|
1756
|
+
} catch {
|
|
1757
|
+
return "";
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
function newerVersion(current, latest) {
|
|
1761
|
+
const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
|
|
1762
|
+
const [ca, cb, cc] = parse(current);
|
|
1763
|
+
const [la, lb, lc] = parse(latest);
|
|
1764
|
+
if (la !== ca) return la > ca;
|
|
1765
|
+
if (lb !== cb) return lb > cb;
|
|
1766
|
+
return lc > cc;
|
|
1767
|
+
}
|
|
1768
|
+
async function checkForUpdate() {
|
|
1769
|
+
try {
|
|
1770
|
+
const res = await fetch(`https://registry.npmjs.org/${PKG_NAME}/latest`, {
|
|
1771
|
+
signal: AbortSignal.timeout(3e3)
|
|
1772
|
+
});
|
|
1773
|
+
if (!res.ok) return null;
|
|
1774
|
+
const data = await res.json();
|
|
1775
|
+
const latest = data.version;
|
|
1776
|
+
const current = currentVersion();
|
|
1777
|
+
if (current && newerVersion(current, latest)) return latest;
|
|
1778
|
+
return null;
|
|
1779
|
+
} catch {
|
|
1780
|
+
return null;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1729
1784
|
// src/ui/App.tsx
|
|
1730
1785
|
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1731
1786
|
function App() {
|
|
@@ -1737,10 +1792,16 @@ function App() {
|
|
|
1737
1792
|
const [activeCtx, setActiveCtx] = useState4(null);
|
|
1738
1793
|
const [state, setState] = useState4("loading");
|
|
1739
1794
|
const [cursor, setCursor] = useState4(0);
|
|
1795
|
+
const [updateAvailable, setUpdateAvailable] = useState4(null);
|
|
1740
1796
|
const [input, setInput] = useState4("");
|
|
1741
1797
|
const [paletteCursor, setPaletteCursor] = useState4(0);
|
|
1742
1798
|
const [filePickerCursor, setFilePickerCursor] = useState4(0);
|
|
1743
1799
|
const agent = useAgentRunner(cfg.model, activeCtx);
|
|
1800
|
+
useEffect3(() => {
|
|
1801
|
+
checkForUpdate().then((v) => {
|
|
1802
|
+
if (v) setUpdateAvailable(v);
|
|
1803
|
+
});
|
|
1804
|
+
}, []);
|
|
1744
1805
|
useEffect3(() => {
|
|
1745
1806
|
listModels().then((m) => {
|
|
1746
1807
|
setModels(m);
|
|
@@ -1801,6 +1862,7 @@ function App() {
|
|
|
1801
1862
|
})();
|
|
1802
1863
|
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingX: 1, children: [
|
|
1803
1864
|
/* @__PURE__ */ jsx9(WelcomeBlock, { model: cfg.model, activeCtx, effort, cwd, error: agent.error }),
|
|
1865
|
+
updateAvailable && /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: `\u2191 update available: v${updateAvailable} \u2014 run: npm i -g miii-agent` }) }),
|
|
1804
1866
|
state === "loading" && !agent.error && /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "connecting to ollama\u2026" }) }),
|
|
1805
1867
|
agent.error && state !== "ready" && /* @__PURE__ */ jsx9(
|
|
1806
1868
|
ChatView,
|