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 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 │
@@ -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: [_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "green", children: "miii" }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsx(Box, { children: status === 'thinking'
450
- ? _jsxs(_Fragment, { children: [_jsxs(Text, { color: "yellow", children: [SPARKLE[tick % SPARKLE.length], " "] }), _jsx(Text, { color: "gray", dimColor: true, italic: true, children: THINKING_PHRASES[Math.floor(tick / 62) % THINKING_PHRASES.length] })] })
451
- : _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 })] }));
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
- printer.assistantMsg(fullText);
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
- printer.toolMsg(tc.name, result);
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);
@@ -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>') || 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
- blank(),
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${bold(blue('You'))}\n${indent(atHighlighted)}`);
137
+ console.log(`\n${gray('>>')} ${atHighlighted}`);
100
138
  }
101
139
  export function assistantMsg(text) {
102
- console.log(`\n${bold(green('miii'))}\n${formatContent(text)}`);
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
- console.log(` ${green('✓')} ${cyan(name)}${body ? '\n' + body : ''}`);
163
+ if (body)
164
+ console.log(body);
110
165
  }
111
166
  export function systemMsg(text) {
112
167
  console.log(gray(`─ ${text}`));
@@ -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 = ['✦', '✧', '✶', '✷', '✸', '✹'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miii-cli",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "type": "module",
5
5
  "description": "Claude Code-level terminal workflows powered by your local models.",
6
6
  "license": "MIT",