miii-cli 0.2.4 → 0.2.6
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 +193 -152
- package/dist/__tests__/integration.test.js +50 -0
- package/dist/config.js +1 -1
- package/dist/init.js +67 -5
- package/dist/skills/loader.js +65 -1
- package/dist/tavily/client.js +64 -0
- package/dist/tools/index.js +34 -1
- package/dist/tui/InputBar.js +89 -316
- package/dist/tui/components/InputArea.js +3 -0
- package/dist/tui/git-context.js +59 -0
- package/dist/tui/hooks/useModelPicker.js +63 -0
- package/dist/tui/hooks/useRunLoop.js +146 -0
- package/dist/tui/hooks/useSession.js +50 -0
- package/dist/tui/printer.js +43 -5
- package/dist/tui/thinking.js +53 -0
- package/package.json +5 -3
- package/dist/tui/App.js +0 -285
- package/dist/tui/components/MessageList.js +0 -127
- package/dist/workers/context.worker.js +0 -71
- package/dist/workers/spawn.js +0 -17
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
|
+
import { listModels, pullModel } from '../../llm/ollama.js';
|
|
3
|
+
import * as printer from '../printer.js';
|
|
4
|
+
export function useModelPicker(config) {
|
|
5
|
+
const [currentModel, setCurrentModel] = useState(config.model);
|
|
6
|
+
const currentModelRef = useRef(config.model);
|
|
7
|
+
const [pickerOpen, setPickerOpen] = useState(true);
|
|
8
|
+
const [pickerModels, setPickerModels] = useState([]);
|
|
9
|
+
const [pickerLoading, setPickerLoading] = useState(false);
|
|
10
|
+
const [pickerError, setPickerError] = useState();
|
|
11
|
+
const [pullState, setPullState] = useState();
|
|
12
|
+
const pullAbortRef = useRef(null);
|
|
13
|
+
useEffect(() => { currentModelRef.current = currentModel; }, [currentModel]);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
setPickerLoading(true);
|
|
16
|
+
listModels(config.baseUrl)
|
|
17
|
+
.then(m => { setPickerModels(m); setPickerLoading(false); })
|
|
18
|
+
.catch(e => { setPickerError(String(e)); setPickerLoading(false); });
|
|
19
|
+
}, []);
|
|
20
|
+
const openPicker = useCallback(async () => {
|
|
21
|
+
setPickerOpen(true);
|
|
22
|
+
setPickerLoading(true);
|
|
23
|
+
setPickerError(undefined);
|
|
24
|
+
try {
|
|
25
|
+
setPickerModels(await listModels(config.baseUrl));
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
setPickerError(String(e));
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
setPickerLoading(false);
|
|
32
|
+
}
|
|
33
|
+
}, [config.baseUrl]);
|
|
34
|
+
const handleModelSelect = useCallback((name) => {
|
|
35
|
+
setCurrentModel(name);
|
|
36
|
+
currentModelRef.current = name;
|
|
37
|
+
setPickerOpen(false);
|
|
38
|
+
printer.systemMsg(`model → ${name}`);
|
|
39
|
+
}, []);
|
|
40
|
+
const handleModelPull = useCallback(async (name) => {
|
|
41
|
+
setPullState({ name, status: 'starting...', pct: undefined });
|
|
42
|
+
pullAbortRef.current = new AbortController();
|
|
43
|
+
try {
|
|
44
|
+
await pullModel(config.baseUrl, name, (s, p) => setPullState({ name, status: s, pct: p }), pullAbortRef.current.signal);
|
|
45
|
+
setPickerModels(await listModels(config.baseUrl));
|
|
46
|
+
setPullState(undefined);
|
|
47
|
+
setCurrentModel(name);
|
|
48
|
+
currentModelRef.current = name;
|
|
49
|
+
setPickerOpen(false);
|
|
50
|
+
printer.systemMsg(`pulled ${name} → active`);
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
setPullState(undefined);
|
|
54
|
+
setPickerError(`pull failed: ${e}`);
|
|
55
|
+
}
|
|
56
|
+
}, [config.baseUrl]);
|
|
57
|
+
return {
|
|
58
|
+
currentModel, setCurrentModel, currentModelRef,
|
|
59
|
+
pickerOpen, setPickerOpen,
|
|
60
|
+
pickerModels, pickerLoading, pickerError, pullState,
|
|
61
|
+
openPicker, handleModelSelect, handleModelPull,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
|
+
import { chat } from '../../llm/stream.js';
|
|
3
|
+
import { tools } from '../../tools/index.js';
|
|
4
|
+
import { StreamParser, extractBareToolCall } from '../../parser/stream-parser.js';
|
|
5
|
+
import { shouldCompact, compactContext } from '../../tasks/compactor.js';
|
|
6
|
+
import * as printer from '../printer.js';
|
|
7
|
+
const MAX_TOOL_DEPTH = 6;
|
|
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']);
|
|
10
|
+
export function useRunLoop(config, currentModelRef, pushHistory) {
|
|
11
|
+
const [status, setStatus] = useState('idle');
|
|
12
|
+
const [tick, setTick] = useState(0);
|
|
13
|
+
const [currentTool, setCurrentTool] = useState();
|
|
14
|
+
const [taskLabel, setTaskLabel] = useState();
|
|
15
|
+
const abortRef = useRef(null);
|
|
16
|
+
const thinkingStartRef = useRef(0);
|
|
17
|
+
const pushHistoryRef = useRef(pushHistory);
|
|
18
|
+
useEffect(() => { pushHistoryRef.current = pushHistory; }, [pushHistory]);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (status === 'idle')
|
|
21
|
+
return;
|
|
22
|
+
const t = setInterval(() => setTick(n => n + 1), 80);
|
|
23
|
+
return () => clearInterval(t);
|
|
24
|
+
}, [status]);
|
|
25
|
+
const runLoop = useCallback(async (contextMsgs, depth = 0, goal) => {
|
|
26
|
+
if (depth >= MAX_TOOL_DEPTH) {
|
|
27
|
+
setStatus('idle');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
setStatus('thinking');
|
|
31
|
+
if (depth === 0)
|
|
32
|
+
thinkingStartRef.current = Date.now();
|
|
33
|
+
const msgs = shouldCompact(contextMsgs) ? compactContext(contextMsgs, goal) : contextMsgs;
|
|
34
|
+
abortRef.current = new AbortController();
|
|
35
|
+
await chat({
|
|
36
|
+
provider: config.provider,
|
|
37
|
+
model: currentModelRef.current,
|
|
38
|
+
baseUrl: config.baseUrl,
|
|
39
|
+
messages: msgs,
|
|
40
|
+
signal: abortRef.current.signal,
|
|
41
|
+
async onDone(fullText) {
|
|
42
|
+
const pendingTools = [];
|
|
43
|
+
const textParts = [];
|
|
44
|
+
const parser = new StreamParser();
|
|
45
|
+
for (const item of [...parser.feed(fullText), ...parser.flush()]) {
|
|
46
|
+
if (item.type === 'tool_call')
|
|
47
|
+
pendingTools.push({ name: item.toolName, args: item.toolArgs });
|
|
48
|
+
else
|
|
49
|
+
textParts.push(item.content);
|
|
50
|
+
}
|
|
51
|
+
if (!pendingTools.length) {
|
|
52
|
+
const bare = extractBareToolCall(fullText);
|
|
53
|
+
if (bare)
|
|
54
|
+
pendingTools.push({ name: bare.name, args: bare.args });
|
|
55
|
+
}
|
|
56
|
+
const displayText = textParts.join('').trim();
|
|
57
|
+
if (displayText)
|
|
58
|
+
printer.assistantMsg(displayText);
|
|
59
|
+
pushHistoryRef.current({ role: 'assistant', content: fullText });
|
|
60
|
+
if (!pendingTools.length) {
|
|
61
|
+
const hasFencedCode = /```[\w]*\n[\s\S]{50,}?\n```/.test(fullText);
|
|
62
|
+
if (hasFencedCode && depth < MAX_TOOL_DEPTH - 1) {
|
|
63
|
+
const nudge = {
|
|
64
|
+
role: 'user',
|
|
65
|
+
content: 'You showed code in your response but did not use any file tools. Use edit_file or patch_file to actually write the changes to disk.',
|
|
66
|
+
};
|
|
67
|
+
await runLoop([...msgs, { role: 'assistant', content: fullText }, nudge], depth + 1, goal);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
setStatus('idle');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
setStatus('tool');
|
|
74
|
+
const next = [...msgs, { role: 'assistant', content: fullText }];
|
|
75
|
+
try {
|
|
76
|
+
for (const tc of pendingTools) {
|
|
77
|
+
const tool = tools.find(t => t.name === tc.name);
|
|
78
|
+
setCurrentTool(tc.name);
|
|
79
|
+
if (tool) {
|
|
80
|
+
try {
|
|
81
|
+
printer.toolCallStart(tc.name, tc.args);
|
|
82
|
+
const result = await tool.execute(tc.args);
|
|
83
|
+
if (SHOW_RESULT_TOOLS.has(tc.name))
|
|
84
|
+
printer.toolMsg(tc.name, result);
|
|
85
|
+
next.push({ role: 'user', content: `Tool ${tc.name} result:\n${result}` });
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
const err = `Tool ${tc.name} error: ${e}`;
|
|
89
|
+
printer.errorMsg(err);
|
|
90
|
+
next.push({ role: 'user', content: err });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
printer.errorMsg(`unknown tool: ${tc.name}`);
|
|
95
|
+
next.push({ role: 'user', content: `unknown tool: ${tc.name}` });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
setCurrentTool(undefined);
|
|
101
|
+
}
|
|
102
|
+
// Auto-run tests after file edits
|
|
103
|
+
const didEditFiles = pendingTools.some(tc => FILE_EDIT_TOOLS.has(tc.name));
|
|
104
|
+
if (didEditFiles) {
|
|
105
|
+
const testTool = tools.find(t => t.name === 'run_tests');
|
|
106
|
+
if (testTool) {
|
|
107
|
+
setCurrentTool('run_tests');
|
|
108
|
+
try {
|
|
109
|
+
printer.toolCallStart('run_tests', {});
|
|
110
|
+
const testResult = await testTool.execute({});
|
|
111
|
+
if (testResult && !testResult.startsWith('(no test script') && !testResult.startsWith('(no package.json')) {
|
|
112
|
+
printer.toolMsg('run_tests', testResult);
|
|
113
|
+
next.push({ role: 'user', content: `Test results after edits:\n${testResult}` });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
const err = `run_tests error: ${e}`;
|
|
118
|
+
printer.errorMsg(err);
|
|
119
|
+
next.push({ role: 'user', content: err });
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
setCurrentTool(undefined);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
await runLoop(next, depth + 1, goal);
|
|
127
|
+
},
|
|
128
|
+
onError(err) {
|
|
129
|
+
if (err.name !== 'AbortError')
|
|
130
|
+
printer.errorMsg(err.message);
|
|
131
|
+
setStatus('idle');
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
}, [config]);
|
|
135
|
+
const handleAbort = useCallback(() => {
|
|
136
|
+
abortRef.current?.abort();
|
|
137
|
+
setStatus('idle');
|
|
138
|
+
}, []);
|
|
139
|
+
return {
|
|
140
|
+
status, setStatus, tick,
|
|
141
|
+
currentTool, setCurrentTool,
|
|
142
|
+
taskLabel, setTaskLabel,
|
|
143
|
+
thinkingStartRef, abortRef,
|
|
144
|
+
runLoop, handleAbort,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { loadSession, saveSession } from '../../sessions.js';
|
|
3
|
+
import { getSystemPrompt } from '../../tools/index.js';
|
|
4
|
+
import { getTavilyKey, saveTavilyKey } from '../../tavily/client.js';
|
|
5
|
+
import * as printer from '../printer.js';
|
|
6
|
+
export function useSession(initialSession, cwd, config) {
|
|
7
|
+
const [sessionName, setSessionName] = useState(initialSession);
|
|
8
|
+
const sessionNameRef = useRef(initialSession);
|
|
9
|
+
const historyRef = useRef([]);
|
|
10
|
+
const saveTimerRef = useRef(null);
|
|
11
|
+
const systemPromptRef = useRef(getSystemPrompt(`\n- CWD: ${cwd}`));
|
|
12
|
+
useEffect(() => { sessionNameRef.current = sessionName; }, [sessionName]);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const history = loadSession(initialSession);
|
|
15
|
+
historyRef.current = history;
|
|
16
|
+
if (history.length)
|
|
17
|
+
printer.systemMsg(`resumed "${initialSession}" — ${history.length} messages`);
|
|
18
|
+
if (config.tavilyApiKey && !getTavilyKey())
|
|
19
|
+
saveTavilyKey(config.tavilyApiKey);
|
|
20
|
+
if (!getTavilyKey()) {
|
|
21
|
+
printer.systemMsg('Tavily API key not set — web search disabled. Run /tavily-key <key> to enable. Get a free key at https://tavily.com');
|
|
22
|
+
}
|
|
23
|
+
}, []);
|
|
24
|
+
function scheduleSave() {
|
|
25
|
+
if (saveTimerRef.current)
|
|
26
|
+
clearTimeout(saveTimerRef.current);
|
|
27
|
+
saveTimerRef.current = setTimeout(() => {
|
|
28
|
+
saveSession(sessionNameRef.current, historyRef.current);
|
|
29
|
+
saveTimerRef.current = null;
|
|
30
|
+
}, 2000);
|
|
31
|
+
}
|
|
32
|
+
function pushHistory(msg) {
|
|
33
|
+
historyRef.current.push(msg);
|
|
34
|
+
if (historyRef.current.length > 100)
|
|
35
|
+
historyRef.current.splice(0, historyRef.current.length - 100);
|
|
36
|
+
scheduleSave();
|
|
37
|
+
}
|
|
38
|
+
function buildContext(extra) {
|
|
39
|
+
const ctx = [{ role: 'system', content: systemPromptRef.current }];
|
|
40
|
+
ctx.push(...historyRef.current);
|
|
41
|
+
if (extra)
|
|
42
|
+
ctx.push(extra);
|
|
43
|
+
return ctx;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
sessionName, setSessionName, sessionNameRef,
|
|
47
|
+
historyRef, saveTimerRef, systemPromptRef,
|
|
48
|
+
pushHistory, buildContext,
|
|
49
|
+
};
|
|
50
|
+
}
|
package/dist/tui/printer.js
CHANGED
|
@@ -24,9 +24,18 @@ function stripMarkdown(s) {
|
|
|
24
24
|
function formatContent(text) {
|
|
25
25
|
const lines = text.split('\n');
|
|
26
26
|
let inCode = false;
|
|
27
|
+
let inToolCall = false;
|
|
27
28
|
const out = [];
|
|
28
29
|
for (const line of lines) {
|
|
29
|
-
if (line.startsWith('<tool_call>')
|
|
30
|
+
if (line.startsWith('<tool_call>')) {
|
|
31
|
+
inToolCall = true;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (line.startsWith('</tool_call>')) {
|
|
35
|
+
inToolCall = false;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (inToolCall)
|
|
30
39
|
continue;
|
|
31
40
|
if (line.startsWith('```')) {
|
|
32
41
|
inCode = !inCode;
|
|
@@ -41,7 +50,24 @@ function formatContent(text) {
|
|
|
41
50
|
}
|
|
42
51
|
return out.join('\n');
|
|
43
52
|
}
|
|
44
|
-
|
|
53
|
+
function truncate(s, n) {
|
|
54
|
+
return s.length > n ? s.slice(0, n) + '…' : s;
|
|
55
|
+
}
|
|
56
|
+
function toolArgSummary(args) {
|
|
57
|
+
if (args.message)
|
|
58
|
+
return `"${truncate(String(args.message), 60)}"`;
|
|
59
|
+
if (args.path)
|
|
60
|
+
return String(args.path);
|
|
61
|
+
if (args.command)
|
|
62
|
+
return truncate(String(args.command), 60);
|
|
63
|
+
if (args.query)
|
|
64
|
+
return `"${truncate(String(args.query), 60)}"`;
|
|
65
|
+
if (args.from)
|
|
66
|
+
return `${args.from} → ${args.to}`;
|
|
67
|
+
const first = Object.values(args)[0];
|
|
68
|
+
return first ? truncate(String(first), 60) : '';
|
|
69
|
+
}
|
|
70
|
+
export function welcome(provider, model, cwd, version, updateAvailable, linked) {
|
|
45
71
|
const cols = Math.min(process.stdout.columns ?? 80, 100);
|
|
46
72
|
const innerW = cols - 2;
|
|
47
73
|
const leftW = Math.floor(innerW * 0.44);
|
|
@@ -64,11 +90,17 @@ export function welcome(provider, model, cwd) {
|
|
|
64
90
|
function rcmd(key, desc, keyW = 10) {
|
|
65
91
|
return ' ' + cyan(key) + ' '.repeat(Math.max(1, keyW - key.length)) + gray(desc);
|
|
66
92
|
}
|
|
67
|
-
const
|
|
93
|
+
const versionStr = version ? ` v${version}` : '';
|
|
94
|
+
const titleStr = `─ MIII - CLI${versionStr} `;
|
|
68
95
|
const dashCount = Math.max(0, cols - 2 - titleStr.length);
|
|
69
|
-
const top = gray('╭') + gray('─') + bold(cyan(
|
|
96
|
+
const top = gray('╭') + gray('─') + bold(cyan(` MIII - CLI${versionStr} `)) + gray('─'.repeat(dashCount) + '╮');
|
|
70
97
|
const bottom = gray('╰' + '─'.repeat(innerW) + '╯');
|
|
71
98
|
const shortCwd = cwd.replace(process.env.HOME ?? '', '~');
|
|
99
|
+
const upgradeCmd = linked ? 'cd <miii-dir> && npm run build' : 'npm install -g miii-cli';
|
|
100
|
+
const separator = gray('│') + bold(yellow(' ⬆ update available: v' + updateAvailable + ' — run: ' + upgradeCmd)).padEnd(innerW - 1) + gray('│');
|
|
101
|
+
const updateRow = updateAvailable
|
|
102
|
+
? [gray('├' + '─'.repeat(innerW) + '┤'), separator, gray('├' + '─'.repeat(innerW) + '┤')]
|
|
103
|
+
: [];
|
|
72
104
|
const lines = [
|
|
73
105
|
top,
|
|
74
106
|
blank(),
|
|
@@ -82,6 +114,7 @@ export function welcome(provider, model, cwd) {
|
|
|
82
114
|
row(` ${gray(provider + '/' + model)}`, ` ${bold(yellow('Tips'))}`),
|
|
83
115
|
row(` ${gray(shortCwd)}`, rcmd('ctrl+c', 'stop thinking')),
|
|
84
116
|
row('', rcmd('ctrl+c x2', 'exit')),
|
|
117
|
+
...updateRow,
|
|
85
118
|
blank(),
|
|
86
119
|
bottom,
|
|
87
120
|
];
|
|
@@ -94,12 +127,17 @@ export function userMsg(text) {
|
|
|
94
127
|
export function assistantMsg(text) {
|
|
95
128
|
console.log(`\n${bold(green('miii'))}\n${formatContent(text)}`);
|
|
96
129
|
}
|
|
130
|
+
export function toolCallStart(name, args) {
|
|
131
|
+
const summary = toolArgSummary(args);
|
|
132
|
+
process.stdout.write(` ${gray('⎿')} ${cyan(name)}${summary ? gray('(' + summary + ')') : ''}\n`);
|
|
133
|
+
}
|
|
97
134
|
export function toolMsg(name, result) {
|
|
98
135
|
const preview = result.length > 250 ? result.slice(0, 250) + '…' : result;
|
|
99
136
|
const body = preview.trim()
|
|
100
137
|
? preview.split('\n').map(l => gray(' ' + l)).join('\n')
|
|
101
138
|
: '';
|
|
102
|
-
|
|
139
|
+
if (body)
|
|
140
|
+
console.log(body);
|
|
103
141
|
}
|
|
104
142
|
export function systemMsg(text) {
|
|
105
143
|
console.log(gray(`─ ${text}`));
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const THINKING_PHRASES = [
|
|
2
|
+
'oh wow, a question. let me pretend to care…',
|
|
3
|
+
'consulting the void…',
|
|
4
|
+
'making something up, just a sec…',
|
|
5
|
+
'definitely not hallucinating right now…',
|
|
6
|
+
'running 47 mental tabs…',
|
|
7
|
+
'staring into the abyss (it blinked)…',
|
|
8
|
+
'calculating your fate, no pressure…',
|
|
9
|
+
'doing the thinking you pay me for…',
|
|
10
|
+
'processing your questionable life choices…',
|
|
11
|
+
'summoning coherent thoughts, rarely works…',
|
|
12
|
+
'asking my imaginary friend for help…',
|
|
13
|
+
'pretending this is a hard problem…',
|
|
14
|
+
'yes, yes, very interesting. anyway…',
|
|
15
|
+
'googling it (not really, I can\'t)…',
|
|
16
|
+
'simulating intelligence… please wait…',
|
|
17
|
+
'having a brief existential crisis…',
|
|
18
|
+
'cross-referencing vibes…',
|
|
19
|
+
'totally not making this up…',
|
|
20
|
+
'the answer is 42. now finding the question…',
|
|
21
|
+
'my other tab is loading…',
|
|
22
|
+
'channelling the spirit of stack overflow…',
|
|
23
|
+
'trying not to confidently be wrong…',
|
|
24
|
+
'applying artificial to the intelligence…',
|
|
25
|
+
'phoning a friend who also doesn\'t know…',
|
|
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…',
|
|
52
|
+
];
|
|
53
|
+
export const SPARKLE = ['✦', '✧', '✶', '✷', '✸', '✹'];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "miii-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Claude Code-level terminal workflows powered by your local models.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"dev": "tsx src/index.ts",
|
|
37
37
|
"build": "tsc",
|
|
38
38
|
"start": "node dist/index.js",
|
|
39
|
-
"link": "npm run build && npm link"
|
|
39
|
+
"link": "npm run build && npm link",
|
|
40
|
+
"test": "vitest run"
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
43
|
"ink": "^5.2.0",
|
|
@@ -49,6 +50,7 @@
|
|
|
49
50
|
"@types/node": "^22.10.0",
|
|
50
51
|
"@types/react": "^18.3.1",
|
|
51
52
|
"tsx": "^4.19.1",
|
|
52
|
-
"typescript": "^5.7.3"
|
|
53
|
+
"typescript": "^5.7.3",
|
|
54
|
+
"vitest": "^4.1.6"
|
|
53
55
|
}
|
|
54
56
|
}
|