miii-cli 0.2.5 → 0.2.7
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 +2 -2
- package/dist/tui/InputBar.js +5 -4
- package/dist/tui/hooks/useRunLoop.js +11 -2
- package/dist/tui/printer.js +77 -22
- package/dist/tui/thinking.js +25 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
│ model: qwen2.5-coder:7b │
|
|
9
9
|
├──────────────────────────────────────────────────────────────────────┤
|
|
10
10
|
│ ✦ cross-referencing vibes… 12s │
|
|
11
|
-
│ ⚙ running patch_file…
|
|
12
|
-
│ ⚙ running run_tests…
|
|
11
|
+
│ ⚙ running patch_file… │
|
|
12
|
+
│ ⚙ running run_tests… │
|
|
13
13
|
├──────────────────────────────────────────────────────────────────────┤
|
|
14
14
|
│ ❯ █ │
|
|
15
15
|
│ @ file / command enter send ctrl+c exit │
|
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
|
}
|
|
@@ -6,6 +6,7 @@ import { shouldCompact, compactContext } from '../../tasks/compactor.js';
|
|
|
6
6
|
import * as printer from '../printer.js';
|
|
7
7
|
const MAX_TOOL_DEPTH = 6;
|
|
8
8
|
const FILE_EDIT_TOOLS = new Set(['edit_file', 'create_file', 'patch_file', 'delete_file']);
|
|
9
|
+
const SHOW_RESULT_TOOLS = new Set(['run_tests', 'git_commit']);
|
|
9
10
|
export function useRunLoop(config, currentModelRef, pushHistory) {
|
|
10
11
|
const [status, setStatus] = useState('idle');
|
|
11
12
|
const [tick, setTick] = useState(0);
|
|
@@ -39,17 +40,22 @@ export function useRunLoop(config, currentModelRef, pushHistory) {
|
|
|
39
40
|
signal: abortRef.current.signal,
|
|
40
41
|
async onDone(fullText) {
|
|
41
42
|
const pendingTools = [];
|
|
43
|
+
const textParts = [];
|
|
42
44
|
const parser = new StreamParser();
|
|
43
45
|
for (const item of [...parser.feed(fullText), ...parser.flush()]) {
|
|
44
46
|
if (item.type === 'tool_call')
|
|
45
47
|
pendingTools.push({ name: item.toolName, args: item.toolArgs });
|
|
48
|
+
else
|
|
49
|
+
textParts.push(item.content);
|
|
46
50
|
}
|
|
47
51
|
if (!pendingTools.length) {
|
|
48
52
|
const bare = extractBareToolCall(fullText);
|
|
49
53
|
if (bare)
|
|
50
54
|
pendingTools.push({ name: bare.name, args: bare.args });
|
|
51
55
|
}
|
|
52
|
-
|
|
56
|
+
const displayText = textParts.join('').trim();
|
|
57
|
+
if (displayText)
|
|
58
|
+
printer.assistantMsg(displayText);
|
|
53
59
|
pushHistoryRef.current({ role: 'assistant', content: fullText });
|
|
54
60
|
if (!pendingTools.length) {
|
|
55
61
|
const hasFencedCode = /```[\w]*\n[\s\S]{50,}?\n```/.test(fullText);
|
|
@@ -72,8 +78,10 @@ export function useRunLoop(config, currentModelRef, pushHistory) {
|
|
|
72
78
|
setCurrentTool(tc.name);
|
|
73
79
|
if (tool) {
|
|
74
80
|
try {
|
|
81
|
+
printer.toolCallStart(tc.name, tc.args);
|
|
75
82
|
const result = await tool.execute(tc.args);
|
|
76
|
-
|
|
83
|
+
if (SHOW_RESULT_TOOLS.has(tc.name))
|
|
84
|
+
printer.toolMsg(tc.name, result);
|
|
77
85
|
next.push({ role: 'user', content: `Tool ${tc.name} result:\n${result}` });
|
|
78
86
|
}
|
|
79
87
|
catch (e) {
|
|
@@ -98,6 +106,7 @@ export function useRunLoop(config, currentModelRef, pushHistory) {
|
|
|
98
106
|
if (testTool) {
|
|
99
107
|
setCurrentTool('run_tests');
|
|
100
108
|
try {
|
|
109
|
+
printer.toolCallStart('run_tests', {});
|
|
101
110
|
const testResult = await testTool.execute({});
|
|
102
111
|
if (testResult && !testResult.startsWith('(no test script') && !testResult.startsWith('(no package.json')) {
|
|
103
112
|
printer.toolMsg('run_tests', testResult);
|
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
|
}
|
|
@@ -24,9 +26,18 @@ function stripMarkdown(s) {
|
|
|
24
26
|
function formatContent(text) {
|
|
25
27
|
const lines = text.split('\n');
|
|
26
28
|
let inCode = false;
|
|
29
|
+
let inToolCall = false;
|
|
27
30
|
const out = [];
|
|
28
31
|
for (const line of lines) {
|
|
29
|
-
if (line.startsWith('<tool_call>')
|
|
32
|
+
if (line.startsWith('<tool_call>')) {
|
|
33
|
+
inToolCall = true;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (line.startsWith('</tool_call>')) {
|
|
37
|
+
inToolCall = false;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (inToolCall)
|
|
30
41
|
continue;
|
|
31
42
|
if (line.startsWith('```')) {
|
|
32
43
|
inCode = !inCode;
|
|
@@ -41,6 +52,23 @@ function formatContent(text) {
|
|
|
41
52
|
}
|
|
42
53
|
return out.join('\n');
|
|
43
54
|
}
|
|
55
|
+
function truncate(s, n) {
|
|
56
|
+
return s.length > n ? s.slice(0, n) + '…' : s;
|
|
57
|
+
}
|
|
58
|
+
function toolArgSummary(args) {
|
|
59
|
+
if (args.message)
|
|
60
|
+
return `"${truncate(String(args.message), 60)}"`;
|
|
61
|
+
if (args.path)
|
|
62
|
+
return String(args.path);
|
|
63
|
+
if (args.command)
|
|
64
|
+
return truncate(String(args.command), 60);
|
|
65
|
+
if (args.query)
|
|
66
|
+
return `"${truncate(String(args.query), 60)}"`;
|
|
67
|
+
if (args.from)
|
|
68
|
+
return `${args.from} → ${args.to}`;
|
|
69
|
+
const first = Object.values(args)[0];
|
|
70
|
+
return first ? truncate(String(first), 60) : '';
|
|
71
|
+
}
|
|
44
72
|
export function welcome(provider, model, cwd, version, updateAvailable, linked) {
|
|
45
73
|
const cols = Math.min(process.stdout.columns ?? 80, 100);
|
|
46
74
|
const innerW = cols - 2;
|
|
@@ -58,18 +86,39 @@ export function welcome(provider, model, cwd, version, updateAvailable, linked)
|
|
|
58
86
|
function row(l, r) {
|
|
59
87
|
return gray('│') + cell(l, leftW) + gray('│') + cell(r, rightW) + gray('│');
|
|
60
88
|
}
|
|
61
|
-
function blank() {
|
|
62
|
-
return gray('│') + ' '.repeat(leftW) + gray('│') + ' '.repeat(rightW) + gray('│');
|
|
63
|
-
}
|
|
64
|
-
function rcmd(key, desc, keyW = 10) {
|
|
65
|
-
return ' ' + cyan(key) + ' '.repeat(Math.max(1, keyW - key.length)) + gray(desc);
|
|
66
|
-
}
|
|
67
89
|
const versionStr = version ? ` v${version}` : '';
|
|
68
90
|
const titleStr = `─ MIII - CLI${versionStr} `;
|
|
69
91
|
const dashCount = Math.max(0, cols - 2 - titleStr.length);
|
|
70
92
|
const top = gray('╭') + gray('─') + bold(cyan(` MIII - CLI${versionStr} `)) + gray('─'.repeat(dashCount) + '╮');
|
|
71
93
|
const bottom = gray('╰' + '─'.repeat(innerW) + '╯');
|
|
72
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]));
|
|
73
122
|
const upgradeCmd = linked ? 'cd <miii-dir> && npm run build' : 'npm install -g miii-cli';
|
|
74
123
|
const separator = gray('│') + bold(yellow(' ⬆ update available: v' + updateAvailable + ' — run: ' + upgradeCmd)).padEnd(innerW - 1) + gray('│');
|
|
75
124
|
const updateRow = updateAvailable
|
|
@@ -77,36 +126,42 @@ export function welcome(provider, model, cwd, version, updateAvailable, linked)
|
|
|
77
126
|
: [];
|
|
78
127
|
const lines = [
|
|
79
128
|
top,
|
|
80
|
-
|
|
81
|
-
row(` ${bold(cyan('MIII - CLI'))}`, ` ${bold(yellow('Getting started'))}`),
|
|
82
|
-
row(` ${gray('Claude Code-level terminal')}`, rcmd('@filename', 'inject file into context')),
|
|
83
|
-
row(` ${gray('workflows, local models.')}`, rcmd('/skill', 'run a skill or command')),
|
|
84
|
-
row('', rcmd('/models', 'switch or pull models')),
|
|
85
|
-
row('', rcmd('/list', 'list all skills')),
|
|
86
|
-
row('', rcmd('/session', 'manage sessions')),
|
|
87
|
-
blank(),
|
|
88
|
-
row(` ${gray(provider + '/' + model)}`, ` ${bold(yellow('Tips'))}`),
|
|
89
|
-
row(` ${gray(shortCwd)}`, rcmd('ctrl+c', 'stop thinking')),
|
|
90
|
-
row('', rcmd('ctrl+c x2', 'exit')),
|
|
129
|
+
...contentRows,
|
|
91
130
|
...updateRow,
|
|
92
|
-
blank(),
|
|
93
131
|
bottom,
|
|
94
132
|
];
|
|
95
133
|
process.stdout.write(lines.join('\n') + '\n');
|
|
96
134
|
}
|
|
97
135
|
export function userMsg(text) {
|
|
98
136
|
const atHighlighted = text.replace(/(@[\w./\-]+)/g, (m) => cyan(m));
|
|
99
|
-
console.log(`\n${
|
|
137
|
+
console.log(`\n${gray('>>')} ${atHighlighted}`);
|
|
100
138
|
}
|
|
101
139
|
export function assistantMsg(text) {
|
|
102
|
-
|
|
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 : ''}`);
|
|
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']);
|
|
153
|
+
export function toolCallStart(name, args) {
|
|
154
|
+
const summary = toolArgSummary(args);
|
|
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`);
|
|
103
157
|
}
|
|
104
158
|
export function toolMsg(name, result) {
|
|
105
159
|
const preview = result.length > 250 ? result.slice(0, 250) + '…' : result;
|
|
106
160
|
const body = preview.trim()
|
|
107
161
|
? preview.split('\n').map(l => gray(' ' + l)).join('\n')
|
|
108
162
|
: '';
|
|
109
|
-
|
|
163
|
+
if (body)
|
|
164
|
+
console.log(body);
|
|
110
165
|
}
|
|
111
166
|
export function systemMsg(text) {
|
|
112
167
|
console.log(gray(`─ ${text}`));
|
package/dist/tui/thinking.js
CHANGED
|
@@ -24,5 +24,30 @@ export const THINKING_PHRASES = [
|
|
|
24
24
|
'applying artificial to the intelligence…',
|
|
25
25
|
'phoning a friend who also doesn\'t know…',
|
|
26
26
|
'checking if this is even my problem to solve…',
|
|
27
|
+
'rebooting common sense… this may take a while…',
|
|
28
|
+
'performing a very convincing impression of thinking…',
|
|
29
|
+
'searching for wisdom in all the wrong places…',
|
|
30
|
+
'warming up the neurons (both of them)…',
|
|
31
|
+
'confidently striding toward the wrong answer…',
|
|
32
|
+
'consulting my gut. it says maybe…',
|
|
33
|
+
'loading… just kidding, still loading…',
|
|
34
|
+
'asking the universe. universe has not replied…',
|
|
35
|
+
'vigorously nodding while understanding nothing…',
|
|
36
|
+
'doing math on my fingers (ran out of fingers)…',
|
|
37
|
+
'the confidence is fake. the effort is real. probably…',
|
|
38
|
+
'entering a fugue state. for your benefit…',
|
|
39
|
+
'mining the depths of mediocrity…',
|
|
40
|
+
'compiling a list of plausible nonsense…',
|
|
41
|
+
'this would be faster if I knew what I was doing…',
|
|
42
|
+
'buffering at the speed of thought…',
|
|
43
|
+
'holding three contradictory opinions simultaneously…',
|
|
44
|
+
'interpolating between guesses…',
|
|
45
|
+
'rewinding the context window with a pencil…',
|
|
46
|
+
'waiting for a sign. any sign…',
|
|
47
|
+
'tracing the error back to its origin: me…',
|
|
48
|
+
'the logic checks out if you squint…',
|
|
49
|
+
'reasoning from first principles I just invented…',
|
|
50
|
+
'generating tokens and praying for coherence…',
|
|
51
|
+
'one sec — dropped all my thoughts, picking them up…',
|
|
27
52
|
];
|
|
28
53
|
export const SPARKLE = ['✦', '✧', '✶', '✷', '✸', '✹'];
|