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.
Files changed (3) hide show
  1. package/README.md +139 -53
  2. package/dist/cli.js +74 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,8 +1,123 @@
1
1
  # miii
2
2
 
3
- The local-first AI coding agent for engineers who hate latency.
3
+ > Your code never leaves your machine. No API keys. No cloud. No bullshit.
4
4
 
5
- miii transforms your terminal into a high-performance development environment by pairing a tight Ink TUI with Ollama. It is a zero-config, private companion that can read your code, write your features, and run your testsall without a single byte leaving your machine.
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 bugsentirely on your hardware, at native speed.
6
+
7
+ [![npm](https://img.shields.io/npm/v/miii-agent)](https://www.npmjs.com/package/miii-agent)
8
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9
+ [![node](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
10
+
11
+ ---
12
+
13
+ ## Demo
14
+
15
+ ![miii demo](demo.gif)
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
- ## The Philosophy
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
- ## Quick Start
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
- ### 2. Install
83
- npm i -g miii-cli
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
- ### 3. Launch
86
- miii
178
+ ```bash
179
+ npm run build # production build
180
+ npm run start # run built output
181
+ ```
87
182
 
88
- ## TUI Cheat Sheet
183
+ ---
89
184
 
90
- - Type & Enter: Send a prompt to the agent.
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
- ## Configuration
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
- Global settings are stored in ~/.miii/config.json:
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
- ## Development
193
+ MIT © [maruakshay](https://github.com/maruakshay)
106
194
 
107
- git clone https://github.com/maruakshay/miii-cli.git
108
- cd miii-cli
109
- npm install
110
- npm run dev
195
+ ---
111
196
 
112
- ## License
113
- MIT
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 ? "white" : void 0, dimColor: i !== cursor, children: [
199
- i === cursor ? "\u25B6 " : " ",
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: "single",
285
- borderColor: "white dim",
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: 2, children: [
293
- /* @__PURE__ */ jsx5(Text5, { bold: active, color: active ? "white" : void 0, dimColor: !active, children: cmd.name }),
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: "single",
368
- borderColor: "white dim",
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 ? "white" : void 0, dimColor: !active, children: [
376
- active ? "\u203A " : " ",
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, and tell me what to do differently", key: "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: req.toolName }),
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miii-agent",
3
- "version": "0.1.0",
3
+ "version": "0.1.4",
4
4
  "description": "Terminal AI coding agent powered by Ollama",
5
5
  "type": "module",
6
6
  "bin": {