miii-cli 0.2.6 → 0.2.8
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 +28 -18
- package/dist/tui/InputBar.js +5 -4
- package/dist/tui/printer.js +45 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
╭──────────────────────────────────────────────────────────────────────╮
|
|
7
|
-
│ miii v0.2.
|
|
7
|
+
│ miii v0.2.8 │
|
|
8
8
|
│ model: qwen2.5-coder:7b │
|
|
9
9
|
├──────────────────────────────────────────────────────────────────────┤
|
|
10
10
|
│ ✦ cross-referencing vibes… 12s │
|
|
@@ -74,7 +74,7 @@ miii
|
|
|
74
74
|
Model picker opens on launch. Select a model. Start coding.
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
|
-
miii #
|
|
77
|
+
miii # new session, named from first message
|
|
78
78
|
miii --model qwen2.5-coder # specific model
|
|
79
79
|
miii --session myproject # named session
|
|
80
80
|
miii -s work -m codellama # short flags
|
|
@@ -83,7 +83,7 @@ miii -s work -m codellama # short flags
|
|
|
83
83
|
miii checks for updates on startup and lets you know when a new version is available:
|
|
84
84
|
|
|
85
85
|
```
|
|
86
|
-
├── miii v0.2.
|
|
86
|
+
├── miii v0.2.7 → v0.2.8 available run: npm install -g miii-cli ───┤
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
---
|
|
@@ -268,18 +268,19 @@ Chains up to 6 tool hops per response — read, edit, test, verify, commit in on
|
|
|
268
268
|
|
|
269
269
|
## Sessions
|
|
270
270
|
|
|
271
|
-
Every
|
|
271
|
+
Every `miii` run starts a fresh session automatically. The session is named after your first message — so `fix the auth bug` becomes the session `fix-the-auth-bug`. Use `--session` to resume a specific one.
|
|
272
272
|
|
|
273
273
|
```bash
|
|
274
|
-
miii #
|
|
274
|
+
miii # new session every time, named from first message
|
|
275
275
|
miii --session feature-auth # resumes or creates "feature-auth"
|
|
276
276
|
```
|
|
277
277
|
|
|
278
278
|
```
|
|
279
|
-
/session <name>
|
|
280
|
-
/
|
|
281
|
-
/
|
|
282
|
-
/
|
|
279
|
+
/session <name> switch to a session (creates if new)
|
|
280
|
+
/session delete <name> delete a saved session
|
|
281
|
+
/sessions list all sessions with message counts
|
|
282
|
+
/new fresh auto-named session
|
|
283
|
+
/clear clear current session history
|
|
283
284
|
```
|
|
284
285
|
|
|
285
286
|
Sessions at `~/.config/miii/sessions/`. History capped at 100 messages in-context, full history on disk. Debounced writes — no I/O on every message.
|
|
@@ -301,6 +302,7 @@ Type `/` to open the command palette with fuzzy search.
|
|
|
301
302
|
| `/model <name>` | Switch model mid-session — no restart |
|
|
302
303
|
| `/models` | Model picker, pull new Ollama models |
|
|
303
304
|
| `/session <name>` | Switch or create session |
|
|
305
|
+
| `/session delete <name>` | Delete a saved session |
|
|
304
306
|
| `/sessions` | List all sessions |
|
|
305
307
|
| `/new` | Fresh auto-named session |
|
|
306
308
|
| `/clear` | Clear current history |
|
|
@@ -374,6 +376,18 @@ Loaded in order from `.miii.json` (project) → `~/.config/miii/config.json` (gl
|
|
|
374
376
|
| `↑ / ↓` | Navigate command palette or file picker |
|
|
375
377
|
| `esc` | Close overlay / abort in-flight request |
|
|
376
378
|
| `ctrl+c` | Abort current request or exit |
|
|
379
|
+
| `backspace` | Remove pasted content chip |
|
|
380
|
+
|
|
381
|
+
## Paste detection
|
|
382
|
+
|
|
383
|
+
Paste a large file or code block and miii collapses it into a chip instead of flooding the input:
|
|
384
|
+
|
|
385
|
+
```
|
|
386
|
+
❯ ⎘ pasted 84 lines
|
|
387
|
+
backspace removes paste enter to send
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
The full content is sent with your message when you press enter. Threshold: ≥ 3 lines or ≥ 200 characters.
|
|
377
391
|
|
|
378
392
|
---
|
|
379
393
|
|
|
@@ -390,13 +404,9 @@ npm test # 8 integration tests
|
|
|
390
404
|
|
|
391
405
|
---
|
|
392
406
|
|
|
393
|
-
## What's new in 0.2.
|
|
407
|
+
## What's new in 0.2.8
|
|
394
408
|
|
|
395
|
-
- **
|
|
396
|
-
- **
|
|
397
|
-
- **
|
|
398
|
-
- **
|
|
399
|
-
- **Update check** — startup banner when a new version is available
|
|
400
|
-
- **Hook architecture** — `useSession`, `useModelPicker`, `useRunLoop` for clean internals
|
|
401
|
-
- **Ambiguous patch detection** — `patch_file` throws on multiple matches
|
|
402
|
-
- **176K bundle** — vs ~50MB for the Python alternatives
|
|
409
|
+
- **Auto-named sessions** — every run starts fresh; session named from first message (`fix-the-auth-bug`)
|
|
410
|
+
- **Session delete** — `/session delete <name>` to remove saved sessions
|
|
411
|
+
- **Paste detection** — large pastes collapse to `⎘ pasted N lines` chip; full content sent on enter
|
|
412
|
+
- **Thinking animation fix** — messages and tool calls no longer bleed into the scrollback buffer
|
package/dist/tui/InputBar.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useCallback, useRef } from 'react';
|
|
2
|
+
import { useState, useCallback, useRef, useMemo } from 'react';
|
|
3
3
|
import { Box, Text, useStdout } from 'ink';
|
|
4
4
|
import { InputArea } from './components/InputArea.js';
|
|
5
5
|
import { ModelPicker } from './components/ModelPicker.js';
|
|
@@ -42,6 +42,7 @@ function buildAtContext(text) {
|
|
|
42
42
|
export function InputBar({ config, skills, cwd, session, version }) {
|
|
43
43
|
const { stdout } = useStdout();
|
|
44
44
|
const cols = stdout.columns ?? 80;
|
|
45
|
+
const phraseSeq = useMemo(() => Array.from({ length: 100 }, () => Math.floor(Math.random() * THINKING_PHRASES.length)), []);
|
|
45
46
|
const [planningMode, setPlanningMode] = useState(false);
|
|
46
47
|
const macroQueueRef = useRef(new MacroQueue());
|
|
47
48
|
const executorRef = useRef(new TaskExecutor(tools));
|
|
@@ -446,7 +447,7 @@ export function InputBar({ config, skills, cwd, session, version }) {
|
|
|
446
447
|
}, [skills, runLoop, openPicker]);
|
|
447
448
|
const skillList = skills.list();
|
|
448
449
|
// ─── render ────────────────────────────────────────────────────────────────
|
|
449
|
-
return (_jsxs(Box, { flexDirection: "column", children: [pickerOpen ? (_jsxs(_Fragment, { children: [_jsx(ModelPicker, { models: pickerModels, current: currentModel, loading: pickerLoading, error: pickerError, pull: pullState, onSelect: handleModelSelect, onPull: handleModelPull, onClose: () => { setPickerOpen(false); } }), _jsx(Divider, { cols: cols })] })) : (status === 'thinking' || status === 'tool') ? (_jsxs(_Fragment, { children: [
|
|
450
|
-
|
|
451
|
-
|
|
450
|
+
return (_jsxs(Box, { flexDirection: "column", children: [pickerOpen ? (_jsxs(_Fragment, { children: [_jsx(ModelPicker, { models: pickerModels, current: currentModel, loading: pickerLoading, error: pickerError, pull: pullState, onSelect: handleModelSelect, onPull: handleModelPull, onClose: () => { setPickerOpen(false); } }), _jsx(Divider, { cols: cols })] })) : (status === 'thinking' || status === 'tool') ? (_jsxs(_Fragment, { children: [_jsx(Box, { flexDirection: "column", paddingX: 1, children: _jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { children: status === 'thinking'
|
|
451
|
+
? _jsxs(_Fragment, { children: [_jsxs(Text, { color: "yellow", children: [SPARKLE[tick % SPARKLE.length], " "] }), _jsx(Text, { color: "gray", dimColor: true, italic: true, children: THINKING_PHRASES[phraseSeq[Math.floor(tick / 62) % phraseSeq.length]] })] })
|
|
452
|
+
: _jsxs(Text, { color: "yellow", dimColor: true, children: ["\u2699 running ", currentTool ?? 'tool', "\u2026"] }) }), _jsxs(Box, { gap: 2, children: [_jsxs(Text, { color: "gray", dimColor: true, children: [Math.floor((Date.now() - thinkingStartRef.current) / 1000), "s"] }), taskLabel && _jsx(Text, { color: "cyan", dimColor: true, children: taskLabel })] })] }) }), _jsx(Divider, { cols: cols })] })) : null, _jsx(InputArea, { status: status, skills: skillList, cwd: cwd, planningMode: planningMode, onSubmit: handleSubmit, onAbort: handleAbort })] }));
|
|
452
453
|
}
|
package/dist/tui/printer.js
CHANGED
|
@@ -10,6 +10,8 @@ const green = (s) => col(92, s);
|
|
|
10
10
|
const cyan = (s) => col(96, s);
|
|
11
11
|
const gray = (s) => col(90, s);
|
|
12
12
|
const yellow = (s) => col(93, s);
|
|
13
|
+
const purple = (s) => col(95, s);
|
|
14
|
+
const red = (s) => col(91, s);
|
|
13
15
|
function indent(text, pad = ' ') {
|
|
14
16
|
return text.split('\n').map(l => pad + l).join('\n');
|
|
15
17
|
}
|
|
@@ -84,18 +86,39 @@ export function welcome(provider, model, cwd, version, updateAvailable, linked)
|
|
|
84
86
|
function row(l, r) {
|
|
85
87
|
return gray('│') + cell(l, leftW) + gray('│') + cell(r, rightW) + gray('│');
|
|
86
88
|
}
|
|
87
|
-
function blank() {
|
|
88
|
-
return gray('│') + ' '.repeat(leftW) + gray('│') + ' '.repeat(rightW) + gray('│');
|
|
89
|
-
}
|
|
90
|
-
function rcmd(key, desc, keyW = 10) {
|
|
91
|
-
return ' ' + cyan(key) + ' '.repeat(Math.max(1, keyW - key.length)) + gray(desc);
|
|
92
|
-
}
|
|
93
89
|
const versionStr = version ? ` v${version}` : '';
|
|
94
90
|
const titleStr = `─ MIII - CLI${versionStr} `;
|
|
95
91
|
const dashCount = Math.max(0, cols - 2 - titleStr.length);
|
|
96
92
|
const top = gray('╭') + gray('─') + bold(cyan(` MIII - CLI${versionStr} `)) + gray('─'.repeat(dashCount) + '╮');
|
|
97
93
|
const bottom = gray('╰' + '─'.repeat(innerW) + '╯');
|
|
98
94
|
const shortCwd = cwd.replace(process.env.HOME ?? '', '~');
|
|
95
|
+
const username = process.env.USER ?? 'there';
|
|
96
|
+
const miniArt = [
|
|
97
|
+
` ${purple(' ● ● ')}`,
|
|
98
|
+
` ${purple(' ╱ ╲ ╱ ╲ ')}`,
|
|
99
|
+
` ${purple(' ╱ ╲ ╱ ╲ ')}`,
|
|
100
|
+
` ${purple('● ● ●')}`,
|
|
101
|
+
];
|
|
102
|
+
const leftLines = [
|
|
103
|
+
'',
|
|
104
|
+
...miniArt,
|
|
105
|
+
'',
|
|
106
|
+
` ${gray(model + ' · ' + provider)}`,
|
|
107
|
+
` ${gray(shortCwd)}`,
|
|
108
|
+
'',
|
|
109
|
+
];
|
|
110
|
+
const rightLines = [
|
|
111
|
+
'',
|
|
112
|
+
` ${bold(yellow('Tips for getting started'))}`,
|
|
113
|
+
` Type ${cyan('@filename')} to inject file into context`,
|
|
114
|
+
` Use ${cyan('/skill')} to run a skill or command`,
|
|
115
|
+
` Use ${cyan('/models')} to switch or pull models`,
|
|
116
|
+
'',
|
|
117
|
+
];
|
|
118
|
+
const maxLen = Math.max(leftLines.length, rightLines.length);
|
|
119
|
+
const pl = [...leftLines, ...Array(Math.max(0, maxLen - leftLines.length)).fill('')];
|
|
120
|
+
const pr = [...rightLines, ...Array(Math.max(0, maxLen - rightLines.length)).fill('')];
|
|
121
|
+
const contentRows = pl.map((l, i) => row(l, pr[i]));
|
|
99
122
|
const upgradeCmd = linked ? 'cd <miii-dir> && npm run build' : 'npm install -g miii-cli';
|
|
100
123
|
const separator = gray('│') + bold(yellow(' ⬆ update available: v' + updateAvailable + ' — run: ' + upgradeCmd)).padEnd(innerW - 1) + gray('│');
|
|
101
124
|
const updateRow = updateAvailable
|
|
@@ -103,33 +126,34 @@ export function welcome(provider, model, cwd, version, updateAvailable, linked)
|
|
|
103
126
|
: [];
|
|
104
127
|
const lines = [
|
|
105
128
|
top,
|
|
106
|
-
|
|
107
|
-
row(` ${bold(cyan('MIII - CLI'))}`, ` ${bold(yellow('Getting started'))}`),
|
|
108
|
-
row(` ${gray('Claude Code-level terminal')}`, rcmd('@filename', 'inject file into context')),
|
|
109
|
-
row(` ${gray('workflows, local models.')}`, rcmd('/skill', 'run a skill or command')),
|
|
110
|
-
row('', rcmd('/models', 'switch or pull models')),
|
|
111
|
-
row('', rcmd('/list', 'list all skills')),
|
|
112
|
-
row('', rcmd('/session', 'manage sessions')),
|
|
113
|
-
blank(),
|
|
114
|
-
row(` ${gray(provider + '/' + model)}`, ` ${bold(yellow('Tips'))}`),
|
|
115
|
-
row(` ${gray(shortCwd)}`, rcmd('ctrl+c', 'stop thinking')),
|
|
116
|
-
row('', rcmd('ctrl+c x2', 'exit')),
|
|
129
|
+
...contentRows,
|
|
117
130
|
...updateRow,
|
|
118
|
-
blank(),
|
|
119
131
|
bottom,
|
|
120
132
|
];
|
|
121
133
|
process.stdout.write(lines.join('\n') + '\n');
|
|
122
134
|
}
|
|
123
135
|
export function userMsg(text) {
|
|
124
136
|
const atHighlighted = text.replace(/(@[\w./\-]+)/g, (m) => cyan(m));
|
|
125
|
-
console.log(`\n${
|
|
137
|
+
console.log(`\n${gray('>>')} ${atHighlighted}`);
|
|
126
138
|
}
|
|
127
139
|
export function assistantMsg(text) {
|
|
128
|
-
|
|
140
|
+
const content = formatContent(text);
|
|
141
|
+
if (!content.trim())
|
|
142
|
+
return;
|
|
143
|
+
const lines = content.split('\n');
|
|
144
|
+
const idx = lines.findIndex(l => l.trim());
|
|
145
|
+
if (idx === -1)
|
|
146
|
+
return;
|
|
147
|
+
const head = lines[idx].replace(/^ {2}/, '');
|
|
148
|
+
const tail = lines.slice(idx + 1).join('\n');
|
|
149
|
+
console.log(`\n${blue('●')} ${head}${tail ? '\n' + tail : ''}`);
|
|
129
150
|
}
|
|
151
|
+
const EDIT_TOOLS = new Set(['edit_file', 'patch_file', 'create_file', 'write_file']);
|
|
152
|
+
const DELETE_TOOLS = new Set(['delete_file', 'remove_file']);
|
|
130
153
|
export function toolCallStart(name, args) {
|
|
131
154
|
const summary = toolArgSummary(args);
|
|
132
|
-
|
|
155
|
+
const dot = DELETE_TOOLS.has(name) ? red('●') : EDIT_TOOLS.has(name) ? green('●') : blue('●');
|
|
156
|
+
process.stdout.write(` ${dot} ${cyan(name)}${summary ? gray('(' + summary + ')') : ''}\n`);
|
|
133
157
|
}
|
|
134
158
|
export function toolMsg(name, result) {
|
|
135
159
|
const preview = result.length > 250 ? result.slice(0, 250) + '…' : result;
|