miii-cli 1.0.1 → 1.1.0

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.
@@ -1,4 +1,5 @@
1
1
  import { useState, useRef, useCallback, useEffect } from 'react';
2
+ import { readFileSync, writeFileSync, unlinkSync, existsSync } from 'fs';
2
3
  import { chat } from '../../llm/stream.js';
3
4
  import { tools as staticTools } from '../../tools/index.js';
4
5
  import { StreamParser, extractBareToolCall } from '../../parser/stream-parser.js';
@@ -8,6 +9,7 @@ const MAX_TOOL_DEPTH = 6;
8
9
  const FILE_EDIT_TOOLS = new Set(['edit_file', 'create_file', 'patch_file', 'delete_file']);
9
10
  const SHOW_RESULT_TOOLS = new Set(['run_tests', 'git_commit']);
10
11
  const PERMISSION_TOOLS = new Set(['edit_file', 'patch_file', 'delete_file', 'create_file', 'move_file', 'run_command', 'git_commit']);
12
+ const CHECKPOINT_TOOLS = new Set(['edit_file', 'patch_file', 'create_file', 'delete_file']);
11
13
  export function useRunLoop(config, currentModelRef, pushHistory, extraTools = [], abortRef) {
12
14
  const [status, setStatus] = useState('idle');
13
15
  const [tick, setTick] = useState(0);
@@ -15,16 +17,25 @@ export function useRunLoop(config, currentModelRef, pushHistory, extraTools = []
15
17
  const [taskLabel, setTaskLabel] = useState();
16
18
  const [permissionRequest, setPermissionRequest] = useState(null);
17
19
  const permissionResolveRef = useRef(null);
20
+ const [compactRequest, setCompactRequest] = useState(null);
21
+ const compactResolveRef = useRef(null);
22
+ const checkpointRef = useRef(new Map());
23
+ const sessionApprovedRef = useRef(new Set());
18
24
  const thinkingStartRef = useRef(0);
19
25
  const extraToolsRef = useRef(extraTools);
20
26
  extraToolsRef.current = extraTools;
21
27
  const pushHistoryRef = useRef(pushHistory);
22
28
  useEffect(() => { pushHistoryRef.current = pushHistory; }, [pushHistory]);
23
- const resolvePermission = useCallback((approved) => {
24
- permissionResolveRef.current?.(approved);
29
+ const resolvePermission = useCallback((result) => {
30
+ permissionResolveRef.current?.(result);
25
31
  permissionResolveRef.current = null;
26
32
  setPermissionRequest(null);
27
33
  }, []);
34
+ const resolveCompact = useCallback((approved) => {
35
+ compactResolveRef.current?.(approved);
36
+ compactResolveRef.current = null;
37
+ setCompactRequest(null);
38
+ }, []);
28
39
  useEffect(() => {
29
40
  if (status === 'idle')
30
41
  return;
@@ -38,9 +49,30 @@ export function useRunLoop(config, currentModelRef, pushHistory, extraTools = []
38
49
  return;
39
50
  }
40
51
  setStatus('thinking');
41
- if (depth === 0)
52
+ if (depth === 0) {
42
53
  thinkingStartRef.current = Date.now();
43
- const msgs = shouldCompact(contextMsgs) ? compactContext(contextMsgs, goal) : contextMsgs;
54
+ checkpointRef.current.clear();
55
+ }
56
+ let msgs = contextMsgs;
57
+ if (shouldCompact(contextMsgs)) {
58
+ const approved = await new Promise(resolve => {
59
+ compactResolveRef.current = resolve;
60
+ setCompactRequest({ messageCount: contextMsgs.length });
61
+ });
62
+ if (approved) {
63
+ printer.systemMsg('compacting context…');
64
+ msgs = await compactContext(contextMsgs, {
65
+ provider: config.provider,
66
+ model: currentModelRef.current,
67
+ baseUrl: config.baseUrl,
68
+ apiKey: config.apiKey,
69
+ }, goal);
70
+ printer.systemMsg(`compacted: ${contextMsgs.length} → ${msgs.length} messages`);
71
+ }
72
+ else {
73
+ printer.systemMsg('keeping full context — responses may be slower');
74
+ }
75
+ }
44
76
  abortRef.current = new AbortController();
45
77
  await chat({
46
78
  provider: config.provider,
@@ -67,6 +99,8 @@ export function useRunLoop(config, currentModelRef, pushHistory, extraTools = []
67
99
  if (displayText)
68
100
  printer.assistantMsg(displayText);
69
101
  pushHistoryRef.current({ role: 'assistant', content: fullText });
102
+ if (pendingTools.length)
103
+ printer.planSummary(pendingTools);
70
104
  if (!pendingTools.length) {
71
105
  const hasFencedCode = /```[\w]*\n[\s\S]{50,}?\n```/.test(fullText);
72
106
  if (hasFencedCode && depth < MAX_TOOL_DEPTH - 1) {
@@ -88,20 +122,42 @@ export function useRunLoop(config, currentModelRef, pushHistory, extraTools = []
88
122
  const tool = allTools.find(t => t.name === tc.name);
89
123
  setCurrentTool(tc.name);
90
124
  if (PERMISSION_TOOLS.has(tc.name)) {
91
- const approved = await new Promise(resolve => {
92
- permissionResolveRef.current = resolve;
93
- setPermissionRequest({ toolName: tc.name, args: tc.args });
94
- });
95
- if (!approved) {
125
+ const sessionKey = tc.name;
126
+ let decision;
127
+ if (sessionApprovedRef.current.has(sessionKey)) {
128
+ decision = 'yes';
129
+ }
130
+ else {
131
+ decision = await new Promise(resolve => {
132
+ permissionResolveRef.current = resolve;
133
+ setPermissionRequest({ toolName: tc.name, args: tc.args });
134
+ });
135
+ }
136
+ if (decision === 'session')
137
+ sessionApprovedRef.current.add(sessionKey);
138
+ if (decision === 'no') {
96
139
  printer.systemMsg(`denied: ${tc.name}`);
97
140
  next.push({ role: 'user', content: `Tool ${tc.name} was denied by the user` });
98
141
  break;
99
142
  }
143
+ // Checkpoint: store pre-execution file state
144
+ if (CHECKPOINT_TOOLS.has(tc.name)) {
145
+ const path = tc.args.path;
146
+ if (path && !checkpointRef.current.has(path)) {
147
+ try {
148
+ checkpointRef.current.set(path, readFileSync(path, 'utf-8'));
149
+ }
150
+ catch {
151
+ checkpointRef.current.set(path, null);
152
+ }
153
+ }
154
+ }
100
155
  }
101
156
  if (tool) {
102
157
  try {
103
158
  printer.toolCallStart(tc.name, tc.args);
104
159
  const result = await tool.execute(tc.args);
160
+ printer.toolResultSummary(tc.name, tc.args, result);
105
161
  if (SHOW_RESULT_TOOLS.has(tc.name))
106
162
  printer.toolMsg(tc.name, result);
107
163
  next.push({ role: 'user', content: `Tool ${tc.name} result:\n${result}` });
@@ -131,6 +187,7 @@ export function useRunLoop(config, currentModelRef, pushHistory, extraTools = []
131
187
  printer.toolCallStart('run_tests', {});
132
188
  const testResult = await testTool.execute({});
133
189
  if (testResult && !testResult.startsWith('(no test script') && !testResult.startsWith('(no package.json')) {
190
+ printer.toolResultSummary('run_tests', {}, testResult);
134
191
  printer.toolMsg('run_tests', testResult);
135
192
  next.push({ role: 'user', content: `Test results after edits:\n${testResult}` });
136
193
  }
@@ -156,11 +213,37 @@ export function useRunLoop(config, currentModelRef, pushHistory, extraTools = []
156
213
  }, [config]);
157
214
  const handleAbort = useCallback(() => {
158
215
  abortRef.current?.abort();
216
+ sessionApprovedRef.current.clear();
159
217
  if (permissionResolveRef.current) {
160
- permissionResolveRef.current(false);
218
+ permissionResolveRef.current('no');
161
219
  permissionResolveRef.current = null;
162
220
  setPermissionRequest(null);
163
221
  }
222
+ if (compactResolveRef.current) {
223
+ compactResolveRef.current(false);
224
+ compactResolveRef.current = null;
225
+ setCompactRequest(null);
226
+ }
227
+ // Restore checkpointed files
228
+ if (checkpointRef.current.size > 0) {
229
+ let restored = 0;
230
+ for (const [path, content] of checkpointRef.current) {
231
+ try {
232
+ if (content === null) {
233
+ if (existsSync(path))
234
+ unlinkSync(path);
235
+ }
236
+ else {
237
+ writeFileSync(path, content, 'utf-8');
238
+ }
239
+ restored++;
240
+ }
241
+ catch { }
242
+ }
243
+ checkpointRef.current.clear();
244
+ if (restored > 0)
245
+ printer.systemMsg(`restored ${restored} file(s) to pre-session state`);
246
+ }
164
247
  setStatus('idle');
165
248
  }, []);
166
249
  return {
@@ -170,5 +253,6 @@ export function useRunLoop(config, currentModelRef, pushHistory, extraTools = []
170
253
  thinkingStartRef,
171
254
  runLoop, handleAbort,
172
255
  permissionRequest, resolvePermission,
256
+ compactRequest, resolveCompact,
173
257
  };
174
258
  }
@@ -6,22 +6,22 @@ import * as printer from '../printer.js';
6
6
  import { loadLongMemory, saveLongMemory, mergeFacts, formatMemoryBlock } from '../../memory/store.js';
7
7
  import { extractFacts } from '../../memory/extractor.js';
8
8
  const SHORT_MEMORY_SIZE = 40;
9
- function buildSystemPrompt(cwd, facts) {
10
- return getSystemPrompt(`\n- CWD: ${cwd}`) + formatMemoryBlock(facts);
9
+ function buildSystemPrompt(cwd, facts, extraTools = []) {
10
+ return getSystemPrompt(`\n- CWD: ${cwd}`, extraTools) + formatMemoryBlock(facts);
11
11
  }
12
- export function useSession(initialSession, cwd, config) {
12
+ export function useSession(initialSession, cwd, config, extraTools = []) {
13
13
  const [sessionName, setSessionName] = useState(initialSession);
14
14
  const sessionNameRef = useRef(initialSession);
15
15
  const historyRef = useRef([]);
16
16
  const saveTimerRef = useRef(null);
17
17
  const firstMessageSentRef = useRef(false);
18
18
  const longMemoryRef = useRef([]);
19
- const systemPromptRef = useRef(buildSystemPrompt(cwd, []));
19
+ const systemPromptRef = useRef(buildSystemPrompt(cwd, [], extraTools));
20
20
  useEffect(() => { sessionNameRef.current = sessionName; }, [sessionName]);
21
21
  useEffect(() => {
22
22
  const facts = loadLongMemory(initialSession);
23
23
  longMemoryRef.current = facts;
24
- systemPromptRef.current = buildSystemPrompt(cwd, facts);
24
+ systemPromptRef.current = buildSystemPrompt(cwd, facts, extraTools);
25
25
  if (facts.length)
26
26
  printer.systemMsg(`long memory: ${facts.length} facts loaded`);
27
27
  const history = loadSession(initialSession);
@@ -51,7 +51,7 @@ export function useSession(initialSession, cwd, config) {
51
51
  return;
52
52
  const updated = mergeFacts(longMemoryRef.current, newFacts);
53
53
  longMemoryRef.current = updated;
54
- systemPromptRef.current = buildSystemPrompt(cwd, updated);
54
+ systemPromptRef.current = buildSystemPrompt(cwd, updated, extraTools);
55
55
  saveLongMemory(sessionNameRef.current, updated);
56
56
  });
57
57
  }
@@ -1,10 +1,11 @@
1
1
  import { useCallback, useRef } from 'react';
2
2
  import { readFile, guardPath } from '../../files/ops.js';
3
3
  import { getSystemPrompt } from '../../tools/index.js';
4
+ import { saveConfig } from '../../config.js';
4
5
  import { loadSession, saveSession, listSessions, deleteSession, deleteAllSessions } from '../../sessions.js';
6
+ import { compactContext } from '../../tasks/compactor.js';
5
7
  import { runDeepThink } from '../deepThink.js';
6
8
  import { buildGitContext, looksCodeRelated } from '../git-context.js';
7
- import { getTavilyKey, saveTavilyKey } from '../../tavily/client.js';
8
9
  import { buildIndex } from '../../index/indexer.js';
9
10
  import { indexStats, clearIndex } from '../../index/store.js';
10
11
  import { embed } from '../../index/embedder.js';
@@ -34,7 +35,7 @@ export function useSubmit(deps) {
34
35
  const depsRef = useRef(deps);
35
36
  depsRef.current = deps;
36
37
  const handleSubmit = useCallback(async (text) => {
37
- const { config, skills, cwd, version, currentModelRef, setCurrentModel, historyRef, sessionNameRef, saveTimerRef, systemPromptRef, abortRef, planningMode, setPlanningMode, runLoop, buildContext, pushHistory, setSessionName, renameFromMessage, openPicker, setStatus, setTaskLabel, setCurrentTool, runRefactor, handleGit, lastGitStatusRef, } = depsRef.current;
38
+ const { config, skills, cwd, version, currentModelRef, setCurrentModel, historyRef, sessionNameRef, saveTimerRef, systemPromptRef, abortRef, setPlanningMode, runLoop, buildContext, pushHistory, setSessionName, renameFromMessage, setStatus, setTaskLabel, setCurrentTool, runRefactor, handleGit, lastGitStatusRef, mcpTools, setConfig, setConfigOpen, } = depsRef.current;
38
39
  const cmd = text.trim();
39
40
  if (cmd === '?') {
40
41
  printer.systemMsg('shortcuts:\n' +
@@ -56,21 +57,6 @@ export function useSubmit(deps) {
56
57
  printer.systemMsg(`miii v${version ?? 'unknown'}`);
57
58
  return;
58
59
  }
59
- if (cmd === '/tavily-key' || cmd.startsWith('/tavily-key ')) {
60
- const key = cmd.slice(11).trim();
61
- if (!key) {
62
- const existing = getTavilyKey();
63
- printer.systemMsg(existing ? 'Tavily key set (use /tavily-key <key> to update)' : 'No Tavily key set. Usage: /tavily-key tvly-...');
64
- return;
65
- }
66
- if (!key.startsWith('tvly-')) {
67
- printer.systemMsg('Key should start with tvly-. Get yours at https://tavily.com');
68
- return;
69
- }
70
- saveTavilyKey(key);
71
- printer.systemMsg('Tavily API key saved to ~/.config/miii/tavily.key (mode 600)');
72
- return;
73
- }
74
60
  if (cmd === '/skills' || cmd.startsWith('/skills ')) {
75
61
  const sub = cmd.slice(7).trim();
76
62
  if (!sub || sub === 'list') {
@@ -110,6 +96,62 @@ export function useSubmit(deps) {
110
96
  printer.systemMsg('usage: /skills install <name> | /skills uninstall <name> | /skills list');
111
97
  return;
112
98
  }
99
+ if (cmd === '/config' || cmd.startsWith('/config ')) {
100
+ const sub = cmd.slice(7).trim();
101
+ if (!sub) {
102
+ setConfigOpen(true);
103
+ return;
104
+ }
105
+ if (sub.startsWith('provider ')) {
106
+ const val = sub.slice(9).trim();
107
+ const valid = ['ollama', 'anthropic', 'openai-compat'];
108
+ if (!valid.includes(val)) {
109
+ printer.systemMsg(`valid providers: ${valid.join(', ')}`);
110
+ return;
111
+ }
112
+ setConfig(c => ({ ...c, provider: val }));
113
+ saveConfig({ provider: val });
114
+ printer.systemMsg(`provider → ${val}`);
115
+ return;
116
+ }
117
+ if (sub.startsWith('model ')) {
118
+ const val = sub.slice(6).trim();
119
+ if (!val) {
120
+ printer.systemMsg('usage: /config model <name>');
121
+ return;
122
+ }
123
+ setConfig(c => ({ ...c, model: val }));
124
+ saveConfig({ model: val });
125
+ setCurrentModel(val);
126
+ currentModelRef.current = val;
127
+ printer.systemMsg(`model → ${val}`);
128
+ return;
129
+ }
130
+ if (sub.startsWith('key ')) {
131
+ const val = sub.slice(4).trim();
132
+ if (!val) {
133
+ printer.systemMsg('usage: /config key <api-key>');
134
+ return;
135
+ }
136
+ setConfig(c => ({ ...c, apiKey: val }));
137
+ saveConfig({ apiKey: val });
138
+ printer.systemMsg(`apiKey → ${val.slice(0, 8)}…`);
139
+ return;
140
+ }
141
+ if (sub.startsWith('url ')) {
142
+ const val = sub.slice(4).trim();
143
+ if (!val) {
144
+ printer.systemMsg('usage: /config url <base-url>');
145
+ return;
146
+ }
147
+ setConfig(c => ({ ...c, baseUrl: val }));
148
+ saveConfig({ baseUrl: val });
149
+ printer.systemMsg(`baseUrl → ${val}`);
150
+ return;
151
+ }
152
+ printer.systemMsg('usage: /config [provider|model|key|url] <value>');
153
+ return;
154
+ }
113
155
  if (cmd === '/model' || cmd.startsWith('/model ')) {
114
156
  const name = cmd.slice(6).trim();
115
157
  if (!name) {
@@ -121,8 +163,31 @@ export function useSubmit(deps) {
121
163
  printer.systemMsg(`model → ${name}`);
122
164
  return;
123
165
  }
124
- if (cmd === '/models') {
125
- await openPicker();
166
+ if (cmd === '/compact') {
167
+ const full = buildContext();
168
+ if (full.length <= 2) {
169
+ printer.systemMsg('nothing to compact');
170
+ return;
171
+ }
172
+ printer.systemMsg(`compacting ${full.length} messages…`);
173
+ setStatus('thinking');
174
+ try {
175
+ const compacted = await compactContext(full, {
176
+ provider: config.provider,
177
+ model: currentModelRef.current,
178
+ baseUrl: config.baseUrl,
179
+ apiKey: config.apiKey,
180
+ }, undefined);
181
+ historyRef.current = compacted.filter(m => m.role !== 'system');
182
+ saveSession(sessionNameRef.current, historyRef.current);
183
+ printer.systemMsg(`compacted: ${full.length} → ${compacted.length} messages`);
184
+ }
185
+ catch (e) {
186
+ printer.errorMsg(`compact failed: ${e}`);
187
+ }
188
+ finally {
189
+ setStatus('idle');
190
+ }
126
191
  return;
127
192
  }
128
193
  if (cmd === '/new') {
@@ -135,7 +200,7 @@ export function useSubmit(deps) {
135
200
  historyRef.current = [];
136
201
  setSessionName(newName);
137
202
  setPlanningMode(false);
138
- systemPromptRef.current = getSystemPrompt(`\n- CWD: ${cwd}`);
203
+ systemPromptRef.current = getSystemPrompt(`\n- CWD: ${cwd}`, mcpTools);
139
204
  printer.systemMsg(`new session → ${newName}`);
140
205
  return;
141
206
  }
@@ -143,7 +208,7 @@ export function useSubmit(deps) {
143
208
  historyRef.current = [];
144
209
  saveSession(sessionNameRef.current, []);
145
210
  setPlanningMode(false);
146
- systemPromptRef.current = getSystemPrompt(`\n- CWD: ${cwd}`);
211
+ systemPromptRef.current = getSystemPrompt(`\n- CWD: ${cwd}`, mcpTools);
147
212
  printer.systemMsg('chat cleared');
148
213
  return;
149
214
  }
@@ -201,7 +266,7 @@ export function useSubmit(deps) {
201
266
  if (cmd === '/plan' || cmd.startsWith('/plan ')) {
202
267
  const topic = cmd.slice(5).trim();
203
268
  setPlanningMode(true);
204
- systemPromptRef.current = getSystemPrompt(`\n- CWD: ${cwd}\n- MODE: Planning assistant. Help the user plan step by step. Ask clarifying questions. Suggest concrete next steps. Use plain text only — no markdown, no headers, no bold, no bullets with asterisks, no backtick blocks. Use numbered lists and plain indentation for structure.`);
269
+ systemPromptRef.current = getSystemPrompt(`\n- CWD: ${cwd}\n- MODE: Planning assistant. Help the user plan step by step. Ask clarifying questions. Suggest concrete next steps. Use plain text only — no markdown, no headers, no bold, no bullets with asterisks, no backtick blocks. Use numbered lists and plain indentation for structure.`, mcpTools);
205
270
  const msg = topic ? `I want to plan: ${topic}` : 'I want to start planning. Help me think through my goals step by step.';
206
271
  printer.userMsg(msg);
207
272
  pushHistory({ role: 'user', content: msg });
@@ -210,7 +275,7 @@ export function useSubmit(deps) {
210
275
  }
211
276
  if (cmd === '/plan:done') {
212
277
  setPlanningMode(false);
213
- systemPromptRef.current = getSystemPrompt(`\n- CWD: ${cwd}`);
278
+ systemPromptRef.current = getSystemPrompt(`\n- CWD: ${cwd}`, mcpTools);
214
279
  printer.systemMsg('planning mode off');
215
280
  return;
216
281
  }
@@ -192,12 +192,82 @@ function toolLabel(name, args) {
192
192
  }
193
193
  }
194
194
  }
195
+ export function planSummary(tools) {
196
+ if (!tools.length)
197
+ return;
198
+ const header = gray(`─ plan (${tools.length} action${tools.length === 1 ? '' : 's'})`);
199
+ write(header + '\n');
200
+ for (const t of tools) {
201
+ const dot = DELETE_TOOLS.has(t.name) ? red('◦') : EDIT_TOOLS.has(t.name) ? green('◦') : blue('◦');
202
+ const label = toolLabel(t.name, t.args);
203
+ write(` ${dot} ${gray(label)}\n`);
204
+ }
205
+ }
195
206
  export function toolCallStart(name, args) {
196
207
  const dot = DELETE_TOOLS.has(name) ? red('●') : EDIT_TOOLS.has(name) ? green('●') : blue('●');
197
- write(` ${dot} ${toolLabel(name, args)}\n`);
208
+ write(`\n${dot} ${bold(toolLabel(name, args))}\n`);
209
+ }
210
+ export function toolResultSummary(name, args, result) {
211
+ const a = args;
212
+ const lines = result.trim().split('\n').filter(Boolean);
213
+ let summary = '';
214
+ switch (name) {
215
+ case 'edit_file':
216
+ case 'write_file': {
217
+ const n = (a.content ?? '').split('\n').length;
218
+ summary = `Wrote ${n} line${n === 1 ? '' : 's'}`;
219
+ break;
220
+ }
221
+ case 'create_file': {
222
+ const n = (a.content ?? '').split('\n').length;
223
+ summary = `Created file · ${n} line${n === 1 ? '' : 's'}`;
224
+ break;
225
+ }
226
+ case 'patch_file':
227
+ summary = lines[0] ?? 'Applied patch';
228
+ break;
229
+ case 'delete_file':
230
+ summary = 'Deleted';
231
+ break;
232
+ case 'move_file':
233
+ summary = `Moved → ${a.to ?? ''}`;
234
+ break;
235
+ case 'read_file': {
236
+ const n = lines.length;
237
+ summary = `Read ${n} line${n === 1 ? '' : 's'}`;
238
+ break;
239
+ }
240
+ case 'list_files':
241
+ summary = `Found ${lines.length} file${lines.length === 1 ? '' : 's'}`;
242
+ break;
243
+ case 'run_command':
244
+ case 'run_tests':
245
+ case 'git_commit':
246
+ case 'git_status':
247
+ case 'git_diff':
248
+ case 'git_log': {
249
+ const first = lines[0]?.slice(0, 80) ?? '';
250
+ const more = lines.length > 1 ? ` (+${lines.length - 1} more)` : '';
251
+ summary = first + more;
252
+ break;
253
+ }
254
+ case 'web_search':
255
+ summary = `Found ${lines.length} result${lines.length === 1 ? '' : 's'}`;
256
+ break;
257
+ case 'web_extract':
258
+ summary = `Extracted ${lines.length} line${lines.length === 1 ? '' : 's'}`;
259
+ break;
260
+ case 'search_codebase':
261
+ summary = lines[0]?.slice(0, 80) ?? 'Done';
262
+ break;
263
+ default:
264
+ summary = lines[0]?.slice(0, 80) ?? 'Done';
265
+ }
266
+ if (summary)
267
+ write(gray(` ${summary}`) + '\n');
198
268
  }
199
269
  export function toolMsg(name, result) {
200
- const preview = result.length > 250 ? result.slice(0, 250) + '…' : result;
270
+ const preview = result.length > 600 ? result.slice(0, 600) + '…' : result;
201
271
  const body = preview.trim()
202
272
  ? preview.split('\n').map(l => gray(' ' + l)).join('\n')
203
273
  : '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miii-cli",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "The high-performance local AI coding agent for your terminal. Automate complex workflows with local LLMs.",
6
6
  "license": "MIT",