miii-agent 0.1.13 → 0.1.15

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.
Files changed (3) hide show
  1. package/README.md +31 -2
  2. package/dist/cli.js +722 -195
  3. package/package.json +2 -1
package/dist/cli.js CHANGED
@@ -9,10 +9,98 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
- // src/ollama/client.ts
12
+ // src/config.ts
13
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
14
+ import { join } from "path";
15
+ import { homedir } from "os";
16
+ function defaultProviders() {
17
+ return {
18
+ ollama: {
19
+ type: "ollama",
20
+ baseUrl: process.env.OLLAMA_HOST ?? "http://localhost:11434"
21
+ },
22
+ lmstudio: {
23
+ type: "openai",
24
+ baseUrl: process.env.LMSTUDIO_HOST ?? process.env.LLM_HOST ?? "http://localhost:1234",
25
+ ...process.env.LMSTUDIO_API_KEY ? { apiKey: process.env.LMSTUDIO_API_KEY } : {}
26
+ }
27
+ };
28
+ }
29
+ function migrate(raw) {
30
+ const providers = { ...defaultProviders(), ...raw.providers ?? {} };
31
+ if (raw.ollamaHost) providers.ollama = { ...providers.ollama, baseUrl: raw.ollamaHost };
32
+ if (raw.lmstudioHost) providers.lmstudio = { ...providers.lmstudio, baseUrl: raw.lmstudioHost };
33
+ return {
34
+ model: raw.model,
35
+ provider: raw.provider,
36
+ effort: raw.effort,
37
+ providers
38
+ };
39
+ }
40
+ function readRawConfig() {
41
+ if (!existsSync(CONFIG_PATH)) return {};
42
+ try {
43
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
44
+ } catch {
45
+ return {};
46
+ }
47
+ }
48
+ function loadConfig() {
49
+ return migrate(readRawConfig());
50
+ }
51
+ function saveConfig(config) {
52
+ mkdirSync(CONFIG_DIR, { recursive: true });
53
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
54
+ }
55
+ function providersOf(cfg) {
56
+ return cfg.providers && Object.keys(cfg.providers).length ? cfg.providers : defaultProviders();
57
+ }
58
+ function resolveProvider(cfg = loadConfig()) {
59
+ const providers = providersOf(cfg);
60
+ const name = cfg.provider && providers[cfg.provider] ? cfg.provider : providers.ollama ? "ollama" : Object.keys(providers)[0];
61
+ return { name, entry: providers[name] };
62
+ }
63
+ function listProviders(cfg = loadConfig()) {
64
+ return Object.keys(providersOf(cfg));
65
+ }
66
+ function providerEntries(cfg = loadConfig()) {
67
+ const providers = providersOf(cfg);
68
+ return Object.entries(providers).map(([name, entry]) => {
69
+ const local = entry.type === "ollama" || /localhost|127\.0\.0\.1|0\.0\.0\.0/.test(entry.baseUrl);
70
+ return { name, entry, kind: local ? "local" : "api" };
71
+ });
72
+ }
73
+ function setModel(model) {
74
+ saveConfig({ ...readRawConfig(), model });
75
+ }
76
+ function setEffort(effort) {
77
+ saveConfig({ ...readRawConfig(), effort });
78
+ }
79
+ function setProvider(provider) {
80
+ saveConfig({ ...readRawConfig(), provider });
81
+ }
82
+ var CONFIG_DIR, CONFIG_PATH;
83
+ var init_config = __esm({
84
+ "src/config.ts"() {
85
+ "use strict";
86
+ CONFIG_DIR = join(homedir(), ".miii");
87
+ CONFIG_PATH = join(CONFIG_DIR, "config.json");
88
+ }
89
+ });
90
+
91
+ // src/llm/ollama.ts
13
92
  import { Ollama } from "ollama";
14
93
  import { execFileSync } from "child_process";
15
- function ollamaInstalled() {
94
+ function makeClient(entry, signal) {
95
+ const opts = { host: entry.baseUrl };
96
+ if (entry.apiKey) opts.headers = { Authorization: `Bearer ${entry.apiKey}` };
97
+ if (signal) {
98
+ opts.fetch = ((input, init) => fetch(input, { ...init, signal }));
99
+ }
100
+ return new Ollama(opts);
101
+ }
102
+ function isAvailable(entry) {
103
+ if (!LOCAL_HOST_RE.test(entry.baseUrl)) return true;
16
104
  try {
17
105
  const cmd2 = process.platform === "win32" ? "where" : "which";
18
106
  execFileSync(cmd2, ["ollama"], { stdio: "ignore" });
@@ -31,20 +119,20 @@ function isConnectionError(err) {
31
119
  const msg = err instanceof Error ? err.message : String(err);
32
120
  return msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("connect");
33
121
  }
34
- async function listModels() {
122
+ async function listModels(entry) {
35
123
  try {
36
- const { models } = await ollama.list();
124
+ const { models } = await makeClient(entry).list();
37
125
  return models.map((m) => m.name);
38
126
  } catch (err) {
39
127
  if (isConnectionError(err)) {
40
- throw new Error(OLLAMA_NOT_RUNNING);
128
+ throw new Error(NOT_RUNNING);
41
129
  }
42
130
  throw err;
43
131
  }
44
132
  }
45
- async function modelContext(model) {
133
+ async function modelContext(entry, model) {
46
134
  try {
47
- const info = await ollama.show({ model });
135
+ const info = await makeClient(entry).show({ model });
48
136
  const modelInfo = info.model_info;
49
137
  if (modelInfo) {
50
138
  const ctxKey = Object.keys(modelInfo).find((k) => k.includes("context_length"));
@@ -56,7 +144,7 @@ async function modelContext(model) {
56
144
  return 2048;
57
145
  } catch (err) {
58
146
  if (isConnectionError(err)) {
59
- throw new Error(OLLAMA_NOT_RUNNING);
147
+ throw new Error(NOT_RUNNING);
60
148
  }
61
149
  const msg = err instanceof Error ? err.message : String(err);
62
150
  if (msg.toLowerCase().includes("not found") || msg.toLowerCase().includes("unknown model")) {
@@ -65,13 +153,10 @@ async function modelContext(model) {
65
153
  throw err;
66
154
  }
67
155
  }
68
- async function* chat(model, messages, tools, opts) {
156
+ async function* chat(entry, model, messages, tools, opts) {
69
157
  if (opts?.signal?.aborted) return;
70
158
  const signal = opts?.signal;
71
- const client = signal ? new Ollama({
72
- host: process.env.OLLAMA_HOST ?? "http://localhost:11434",
73
- fetch: ((input, init) => fetch(input, { ...init, signal }))
74
- }) : ollama;
159
+ const client = makeClient(entry, signal);
75
160
  let stream;
76
161
  const onAbort = () => {
77
162
  try {
@@ -99,7 +184,7 @@ async function* chat(model, messages, tools, opts) {
99
184
  } catch (err) {
100
185
  if (signal?.aborted) return;
101
186
  if (isConnectionError(err)) {
102
- throw new Error(OLLAMA_NOT_RUNNING);
187
+ throw new Error(NOT_RUNNING);
103
188
  }
104
189
  const msg = err instanceof Error ? err.message : String(err);
105
190
  if (msg.toLowerCase().includes("not found") || msg.toLowerCase().includes("unknown model")) {
@@ -123,27 +208,278 @@ async function* chat(model, messages, tools, opts) {
123
208
  } catch (err) {
124
209
  if (opts?.signal?.aborted) return;
125
210
  if (isConnectionError(err)) {
126
- throw new Error(OLLAMA_NOT_RUNNING);
211
+ throw new Error(NOT_RUNNING);
127
212
  }
128
213
  throw err;
129
214
  } finally {
130
215
  if (opts?.signal) opts.signal.removeEventListener("abort", onAbort);
131
216
  }
132
217
  }
133
- var ollama, OLLAMA_NOT_INSTALLED, OLLAMA_NOT_RUNNING, HARMONY_RE, CHANNEL_LABEL_RE;
134
- var init_client = __esm({
135
- "src/ollama/client.ts"() {
218
+ var NOT_INSTALLED, NOT_RUNNING, LOCAL_HOST_RE, HARMONY_RE, CHANNEL_LABEL_RE;
219
+ var init_ollama = __esm({
220
+ "src/llm/ollama.ts"() {
136
221
  "use strict";
137
- ollama = new Ollama({
138
- host: process.env.OLLAMA_HOST ?? "http://localhost:11434"
139
- });
140
- OLLAMA_NOT_INSTALLED = "Ollama is not installed. Install it with: npm i -g ollama\nOr download from https://ollama.com/download";
141
- OLLAMA_NOT_RUNNING = "Ollama is not running. Start it with: ollama serve";
222
+ NOT_INSTALLED = "Ollama is not installed. Install it with: npm i -g ollama\nOr download from https://ollama.com/download";
223
+ NOT_RUNNING = "Ollama is not running. Start it with: ollama serve";
224
+ LOCAL_HOST_RE = /localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\]/;
142
225
  HARMONY_RE = /<\|?\/?(?:channel|message|start|end|return|constrain|assistant|user|system|developer|tool|tool_call|tool_response|final|analysis|commentary)\|?>/gi;
143
226
  CHANNEL_LABEL_RE = /^(?:analysis|commentary|final)\s*(?=\w)/i;
144
227
  }
145
228
  });
146
229
 
230
+ // src/llm/openai.ts
231
+ function notAvailable(entry) {
232
+ return `Cannot reach OpenAI-compatible provider at ${entry.baseUrl}. Make sure the server is running and the baseUrl is correct.`;
233
+ }
234
+ function headers(entry) {
235
+ const h = { "Content-Type": "application/json" };
236
+ if (entry.apiKey) h["Authorization"] = `Bearer ${entry.apiKey}`;
237
+ return h;
238
+ }
239
+ function isConnectionError2(err) {
240
+ const msg = err instanceof Error ? err.message : String(err);
241
+ return msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("connect");
242
+ }
243
+ function isAvailable2(_entry) {
244
+ return true;
245
+ }
246
+ async function listModels2(entry) {
247
+ try {
248
+ const res = await fetch(`${entry.baseUrl}/v1/models`, {
249
+ headers: headers(entry),
250
+ signal: AbortSignal.timeout(5e3)
251
+ });
252
+ if (!res.ok) {
253
+ let detail = "";
254
+ try {
255
+ detail = await res.text();
256
+ } catch {
257
+ }
258
+ throw new Error(`Provider error (HTTP ${res.status}): ${detail || res.statusText}`);
259
+ }
260
+ const body = await res.json();
261
+ return body.data.map((m) => m.id);
262
+ } catch (err) {
263
+ if (isConnectionError2(err)) {
264
+ throw new Error(notAvailable(entry));
265
+ }
266
+ throw err;
267
+ }
268
+ }
269
+ async function modelContext2(_entry, _model) {
270
+ return DEFAULT_CONTEXT;
271
+ }
272
+ function toOpenAIMessages(msgs) {
273
+ return msgs.map((m) => {
274
+ if (m.role === "assistant" && m.tool_calls && m.tool_calls.length > 0) {
275
+ return {
276
+ role: "assistant",
277
+ content: m.content || null,
278
+ tool_calls: m.tool_calls.map((tc) => ({
279
+ id: tc.id ?? `call_${Math.random().toString(36).slice(2, 10)}`,
280
+ type: "function",
281
+ function: {
282
+ name: tc.function.name,
283
+ arguments: JSON.stringify(tc.function.arguments)
284
+ }
285
+ }))
286
+ };
287
+ }
288
+ if (m.role === "tool") {
289
+ return {
290
+ role: "tool",
291
+ content: m.content,
292
+ tool_call_id: m.tool_call_id ?? ""
293
+ };
294
+ }
295
+ return { role: m.role, content: m.content };
296
+ });
297
+ }
298
+ function toOpenAITools(tools) {
299
+ if (!tools || tools.length === 0) return void 0;
300
+ return tools.map((t) => ({
301
+ type: "function",
302
+ function: {
303
+ name: t.function.name,
304
+ description: t.function.description,
305
+ parameters: t.function.parameters
306
+ }
307
+ }));
308
+ }
309
+ function parseSSELine(line) {
310
+ if (!line.startsWith("data: ")) return null;
311
+ const data = line.slice(6).trim();
312
+ if (data === "[DONE]") return null;
313
+ try {
314
+ return JSON.parse(data);
315
+ } catch {
316
+ return null;
317
+ }
318
+ }
319
+ async function* chat2(entry, model, messages, tools, opts) {
320
+ if (opts?.signal?.aborted) return;
321
+ const oaMessages = toOpenAIMessages(messages);
322
+ const oaTools = toOpenAITools(tools);
323
+ const body = {
324
+ model,
325
+ messages: oaMessages,
326
+ stream: true,
327
+ temperature: opts?.temperature ?? 0.2
328
+ };
329
+ if (oaTools) body.tools = oaTools;
330
+ if (opts?.num_predict && opts.num_predict > 0) body.max_tokens = opts.num_predict;
331
+ const toolCallAccum = /* @__PURE__ */ new Map();
332
+ const TIMEOUT_MS = 18e4;
333
+ const timeoutSignal = AbortSignal.timeout(TIMEOUT_MS);
334
+ const combinedSignal = opts?.signal && typeof AbortSignal.any === "function" ? AbortSignal.any([opts.signal, timeoutSignal]) : opts?.signal ?? timeoutSignal;
335
+ try {
336
+ const res = await fetch(`${entry.baseUrl}/v1/chat/completions`, {
337
+ method: "POST",
338
+ headers: headers(entry),
339
+ body: JSON.stringify(body),
340
+ signal: combinedSignal
341
+ });
342
+ if (!res.ok) {
343
+ let detail = "";
344
+ try {
345
+ detail = await res.text();
346
+ } catch {
347
+ }
348
+ if (res.status === 404) {
349
+ throw new Error(`Model "${model}" not found at ${entry.baseUrl}. Make sure it's available.`);
350
+ }
351
+ throw new Error(`Provider error (HTTP ${res.status}): ${detail || res.statusText}`);
352
+ }
353
+ const reader = res.body?.getReader();
354
+ if (!reader) throw new Error("No response body");
355
+ const decoder = new TextDecoder();
356
+ let buffer = "";
357
+ try {
358
+ readLoop: while (true) {
359
+ const { done: readerDone, value } = await reader.read();
360
+ if (readerDone || opts?.signal?.aborted) break;
361
+ buffer += decoder.decode(value, { stream: true });
362
+ const lines = buffer.split("\n");
363
+ buffer = lines.pop() ?? "";
364
+ for (const line of lines) {
365
+ const parsed = parseSSELine(line);
366
+ if (!parsed) continue;
367
+ const choices = parsed.choices;
368
+ if (!choices || choices.length === 0) continue;
369
+ const delta = choices[0].delta ?? {};
370
+ const finishReason = choices[0].finish_reason;
371
+ if (delta.content) {
372
+ yield { content: delta.content, done: false };
373
+ }
374
+ const deltaToolCalls = delta.tool_calls;
375
+ if (deltaToolCalls) {
376
+ for (const tc of deltaToolCalls) {
377
+ const idx = tc.index;
378
+ if (!toolCallAccum.has(idx)) {
379
+ toolCallAccum.set(idx, { args: "" });
380
+ }
381
+ const acc = toolCallAccum.get(idx);
382
+ if (tc.id) acc.id = tc.id;
383
+ if (tc.type) acc.type = tc.type;
384
+ if (tc.function) {
385
+ if (tc.function.name) acc.name = tc.function.name;
386
+ if (tc.function.arguments) acc.args += tc.function.arguments;
387
+ }
388
+ }
389
+ }
390
+ if (finishReason) {
391
+ break readLoop;
392
+ }
393
+ }
394
+ }
395
+ } finally {
396
+ reader.cancel().catch(() => {
397
+ });
398
+ }
399
+ } catch (err) {
400
+ if (opts?.signal?.aborted) {
401
+ yield { content: "", done: true, prompt_eval_count: 0, eval_count: 0 };
402
+ return;
403
+ }
404
+ if (isConnectionError2(err)) {
405
+ throw new Error(notAvailable(entry));
406
+ }
407
+ if (timeoutSignal.aborted && !opts?.signal?.aborted) {
408
+ throw new Error(`Provider request timed out after ${TIMEOUT_MS / 1e3}s. The model may still be loading or thinking.`);
409
+ }
410
+ throw err;
411
+ }
412
+ if (opts?.signal?.aborted) {
413
+ yield { content: "", done: true, prompt_eval_count: 0, eval_count: 0 };
414
+ return;
415
+ }
416
+ const toolCalls = [];
417
+ for (const [, acc] of toolCallAccum) {
418
+ let parsedArgs = {};
419
+ try {
420
+ parsedArgs = JSON.parse(acc.args);
421
+ } catch {
422
+ parsedArgs = { _raw: acc.args };
423
+ }
424
+ toolCalls.push({
425
+ id: acc.id,
426
+ function: {
427
+ name: acc.name ?? "",
428
+ arguments: parsedArgs
429
+ }
430
+ });
431
+ }
432
+ yield {
433
+ content: "",
434
+ done: true,
435
+ tool_calls: toolCalls.length > 0 ? toolCalls : void 0
436
+ };
437
+ }
438
+ var DEFAULT_CONTEXT;
439
+ var init_openai = __esm({
440
+ "src/llm/openai.ts"() {
441
+ "use strict";
442
+ DEFAULT_CONTEXT = 4096;
443
+ }
444
+ });
445
+
446
+ // src/llm/client.ts
447
+ function active() {
448
+ return resolveProvider();
449
+ }
450
+ function isAvailable3() {
451
+ const { entry } = active();
452
+ return entry.type === "ollama" ? isAvailable(entry) : isAvailable2(entry);
453
+ }
454
+ function NOT_AVAILABLE() {
455
+ const { entry } = active();
456
+ return entry.type === "ollama" ? NOT_INSTALLED : notAvailable(entry);
457
+ }
458
+ async function listModels3() {
459
+ const { entry } = active();
460
+ return entry.type === "ollama" ? listModels(entry) : listModels2(entry);
461
+ }
462
+ async function modelContext3(model) {
463
+ const { entry } = active();
464
+ return entry.type === "ollama" ? modelContext(entry, model) : modelContext2(entry, model);
465
+ }
466
+ async function* chat3(model, messages, tools, opts) {
467
+ const { entry } = active();
468
+ if (entry.type === "ollama") {
469
+ yield* chat(entry, model, messages, tools, opts);
470
+ } else {
471
+ yield* chat2(entry, model, messages, tools, opts);
472
+ }
473
+ }
474
+ var init_client = __esm({
475
+ "src/llm/client.ts"() {
476
+ "use strict";
477
+ init_config();
478
+ init_ollama();
479
+ init_openai();
480
+ }
481
+ });
482
+
147
483
  // src/tools/paths.ts
148
484
  import { resolve, relative as relative2, isAbsolute, sep, join as join4 } from "path";
149
485
  import { homedir as homedir3 } from "os";
@@ -533,18 +869,18 @@ var init_grep = __esm({
533
869
  const limit = max_results ?? 200;
534
870
  const ci = case_insensitive === true || String(case_insensitive) === "true";
535
871
  const tryRg = async () => {
536
- const args = ["--line-number", "--no-heading", "--color=never", "-m", String(limit)];
537
- if (ci) args.push("-i");
538
- if (glob2) args.push("--glob", glob2);
539
- args.push("--", pattern, root);
540
- return execa2("rg", args, { reject: false, timeout: 2e4 });
872
+ const args2 = ["--line-number", "--no-heading", "--color=never", "-m", String(limit)];
873
+ if (ci) args2.push("-i");
874
+ if (glob2) args2.push("--glob", glob2);
875
+ args2.push("--", pattern, root);
876
+ return execa2("rg", args2, { reject: false, timeout: 2e4 });
541
877
  };
542
878
  const tryGrep = async () => {
543
- const args = ["-R", "-n", "--color=never"];
544
- if (ci) args.push("-i");
545
- if (glob2) args.push("--include", glob2);
546
- args.push("--", pattern, root);
547
- return execa2("grep", args, { reject: false, timeout: 2e4 });
879
+ const args2 = ["-R", "-n", "--color=never"];
880
+ if (ci) args2.push("-i");
881
+ if (glob2) args2.push("--include", glob2);
882
+ args2.push("--", pattern, root);
883
+ return execa2("grep", args2, { reject: false, timeout: 2e4 });
548
884
  };
549
885
  try {
550
886
  let res;
@@ -891,6 +1227,7 @@ function toOllamaMessages(history, system) {
891
1227
  const ollamaMsg = { role: "assistant", content: text };
892
1228
  if (tool_uses.length > 0) {
893
1229
  ollamaMsg.tool_calls = tool_uses.map((u) => ({
1230
+ id: u.id,
894
1231
  function: { name: u.name, arguments: u.input }
895
1232
  }));
896
1233
  }
@@ -901,7 +1238,7 @@ function toOllamaMessages(history, system) {
901
1238
  const tool_results = msg.content.filter((b) => b.type === "tool_result");
902
1239
  const texts = msg.content.filter((b) => b.type === "text");
903
1240
  for (const tr of tool_results) {
904
- out.push({ role: "tool", content: tr.content });
1241
+ out.push({ role: "tool", content: tr.content, tool_call_id: tr.tool_use_id });
905
1242
  }
906
1243
  if (texts.length > 0) {
907
1244
  out.push({ role: "user", content: texts.map((t) => t.text).join("") });
@@ -947,9 +1284,9 @@ function tryParse(raw, knownToolNames) {
947
1284
  try {
948
1285
  const obj = JSON.parse(s);
949
1286
  const name = typeof obj.name === "string" ? obj.name : void 0;
950
- const args = obj.arguments ?? obj.parameters ?? obj.input ?? {};
1287
+ const args2 = obj.arguments ?? obj.parameters ?? obj.input ?? {};
951
1288
  if (!name || !knownToolNames.includes(name)) return null;
952
- return { function: { name, arguments: args } };
1289
+ return { function: { name, arguments: args2 } };
953
1290
  } catch {
954
1291
  return null;
955
1292
  }
@@ -1032,7 +1369,7 @@ async function* runAgent(opts) {
1032
1369
  const composedSignal = signal ? AbortSignal.any ? AbortSignal.any([signal, ac.signal]) : ac.signal : ac.signal;
1033
1370
  if (signal) signal.addEventListener("abort", () => ac.abort(), { once: true });
1034
1371
  try {
1035
- for await (const chunk of chat(model, toOllamaMessages(history, system), ollamaTools, { signal: composedSignal, num_ctx, num_predict: NUM_PREDICT })) {
1372
+ for await (const chunk of chat3(model, toOllamaMessages(history, system), ollamaTools, { signal: composedSignal, num_ctx, num_predict: NUM_PREDICT })) {
1036
1373
  if (signal?.aborted) break;
1037
1374
  if (chunk.content) {
1038
1375
  text += chunk.content;
@@ -1191,7 +1528,7 @@ var init_loop = __esm({
1191
1528
  init_policy();
1192
1529
  init_adapter();
1193
1530
  MAX_TURNS = 25;
1194
- NUM_PREDICT = 4096;
1531
+ NUM_PREDICT = 8192;
1195
1532
  REPEAT_TAIL = 120;
1196
1533
  REPEAT_KILL = 4;
1197
1534
  }
@@ -1338,7 +1675,7 @@ function pad(s, n) {
1338
1675
  }
1339
1676
  async function resolveModels(modelsArg) {
1340
1677
  if (modelsArg !== "all") return modelsArg.split(",").map((m) => m.trim()).filter(Boolean);
1341
- return (await listModels()).filter((m) => !m.includes("cloud"));
1678
+ return (await listModels3()).filter((m) => !m.includes("cloud"));
1342
1679
  }
1343
1680
  function verdict(passed, total) {
1344
1681
  const ratio = total === 0 ? 0 : passed / total;
@@ -1385,10 +1722,10 @@ function printMatrix(models, picked, grid) {
1385
1722
  }
1386
1723
  console.log("\n + pass . fail ? not run");
1387
1724
  }
1388
- async function runEval(args) {
1725
+ async function runEval(args2) {
1389
1726
  const strip = (s) => (s ?? "").replace(/^-+/, "");
1390
- const modelsArg = strip(args[0]) || process.env.MIII_EVAL_MODEL || "all";
1391
- const filter = strip(args[1]);
1727
+ const modelsArg = strip(args2[0]) || process.env.MIII_EVAL_MODEL || "all";
1728
+ const filter = strip(args2[1]);
1392
1729
  const picked = filter ? scenarios.filter((s) => s.name.includes(filter)) : scenarios;
1393
1730
  if (picked.length === 0) {
1394
1731
  console.error(`No scenarios match "${filter}"`);
@@ -1421,40 +1758,16 @@ import { createElement } from "react";
1421
1758
 
1422
1759
  // src/ui/App.tsx
1423
1760
  init_client();
1424
- import { useState as useState5, useEffect as useEffect4 } from "react";
1761
+ init_config();
1762
+ import { useState as useState5, useEffect as useEffect4, useRef as useRef2 } from "react";
1425
1763
  import { Box as Box10, Text as Text10, useApp } from "ink";
1426
1764
  import { homedir as homedir6 } from "os";
1427
1765
  import { sep as sep2 } from "path";
1428
1766
 
1429
- // src/config.ts
1430
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
1431
- import { join } from "path";
1432
- import { homedir } from "os";
1433
- var CONFIG_DIR = join(homedir(), ".miii");
1434
- var CONFIG_PATH = join(CONFIG_DIR, "config.json");
1435
- function loadConfig() {
1436
- if (!existsSync(CONFIG_PATH)) return {};
1437
- try {
1438
- return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
1439
- } catch {
1440
- return {};
1441
- }
1442
- }
1443
- function saveConfig(config) {
1444
- mkdirSync(CONFIG_DIR, { recursive: true });
1445
- writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
1446
- }
1447
- function setModel(model) {
1448
- saveConfig({ ...loadConfig(), model });
1449
- }
1450
- function setEffort(effort) {
1451
- saveConfig({ ...loadConfig(), effort });
1452
- }
1453
-
1454
1767
  // src/ui/WelcomeBlock.tsx
1455
1768
  import { Box, Text } from "ink";
1456
1769
  import { jsx, jsxs } from "react/jsx-runtime";
1457
- function WelcomeBlock({ model, activeCtx, effort, cwd }) {
1770
+ function WelcomeBlock({ model, activeCtx, effort, cwd, updateAvailable }) {
1458
1771
  const ctxLabel = activeCtx != null ? `${Math.round(activeCtx / 1024)}k ctx` : "\u2014 ctx";
1459
1772
  return /* @__PURE__ */ jsxs(
1460
1773
  Box,
@@ -1477,43 +1790,27 @@ function WelcomeBlock({ model, activeCtx, effort, cwd }) {
1477
1790
  " effort"
1478
1791
  ] })
1479
1792
  ] }),
1480
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: cwd })
1793
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: cwd }),
1794
+ updateAvailable && /* @__PURE__ */ jsx(Text, { color: "yellow", children: `\u2191 update available: v${updateAvailable} \u2014 run: miii --update` })
1481
1795
  ]
1482
1796
  }
1483
1797
  );
1484
1798
  }
1485
1799
 
1486
- // src/ui/ModelList.tsx
1487
- import { Box as Box2, Text as Text2 } from "ink";
1488
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1489
- function ModelList({ models, cursor, activeModel, showActive }) {
1490
- if (models.length === 0) {
1491
- return /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
1492
- "no models found. run: ollama pull ",
1493
- "<model>"
1494
- ] });
1495
- }
1496
- return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: models.map((m, i) => /* @__PURE__ */ jsxs2(Text2, { color: i === cursor ? "blue" : void 0, dimColor: i !== cursor, children: [
1497
- i === cursor ? "\u276F " : " ",
1498
- m,
1499
- showActive && m === activeModel ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " (active)" }) : null
1500
- ] }, m)) });
1501
- }
1502
-
1503
1800
  // src/ui/InputBar.tsx
1504
- import { useEffect, useState } from "react";
1505
- import { Box as Box3, Text as Text3 } from "ink";
1506
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1801
+ import { memo, useEffect, useState } from "react";
1802
+ import { Box as Box2, Text as Text2 } from "ink";
1803
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1507
1804
  var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1508
- function InputBar({ input, disabled, processingLabel }) {
1805
+ var InputBar = memo(function InputBar2({ input, disabled, processingLabel }) {
1509
1806
  const [frame, setFrame] = useState(0);
1510
1807
  useEffect(() => {
1511
1808
  if (!disabled) return;
1512
1809
  const t = setInterval(() => setFrame((f) => (f + 1) % SPIN.length), 150);
1513
1810
  return () => clearInterval(t);
1514
1811
  }, [disabled]);
1515
- return /* @__PURE__ */ jsx3(
1516
- Box3,
1812
+ return /* @__PURE__ */ jsx2(
1813
+ Box2,
1517
1814
  {
1518
1815
  borderStyle: "single",
1519
1816
  borderTop: true,
@@ -1522,51 +1819,94 @@ function InputBar({ input, disabled, processingLabel }) {
1522
1819
  borderRight: false,
1523
1820
  borderColor: disabled ? "yellow" : "white dim",
1524
1821
  paddingX: 1,
1525
- children: disabled ? /* @__PURE__ */ jsxs3(Fragment, { children: [
1526
- /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: SPIN[frame] + " " }),
1527
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, italic: true, children: processingLabel ?? "processing\u2026" }),
1528
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " (esc to cancel)" })
1529
- ] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
1530
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "> " }),
1531
- /* @__PURE__ */ jsx3(Text3, { children: input }),
1532
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u258C" })
1822
+ children: disabled ? /* @__PURE__ */ jsxs2(Fragment, { children: [
1823
+ /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: SPIN[frame] + " " }),
1824
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, italic: true, children: processingLabel ?? "processing\u2026" }),
1825
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " (esc to cancel)" })
1826
+ ] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
1827
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "> " }),
1828
+ /* @__PURE__ */ jsx2(Text2, { children: input }),
1829
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u258C" })
1533
1830
  ] })
1534
1831
  }
1535
1832
  );
1536
- }
1833
+ });
1537
1834
 
1538
1835
  // src/ui/ModelsView.tsx
1836
+ import { Box as Box3, Text as Text3 } from "ink";
1837
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1838
+ function ModelsView({ models, cursor, model, host, provider, effort, query, requireSelection }) {
1839
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 2, children: [
1840
+ /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginBottom: 1, children: [
1841
+ /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", children: [
1842
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "provider " }),
1843
+ /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: provider }),
1844
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
1845
+ " ",
1846
+ "host "
1847
+ ] }),
1848
+ /* @__PURE__ */ jsx3(Text3, { children: host })
1849
+ ] }),
1850
+ /* @__PURE__ */ jsxs3(Text3, { children: [
1851
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "effort " }),
1852
+ /* @__PURE__ */ jsx3(Text3, { children: effort }),
1853
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " (\u2190 \u2192)" })
1854
+ ] })
1855
+ ] }),
1856
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "select model" }),
1857
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: models.length === 0 ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: query ? `no models match "${query}"` : provider === "lmstudio" ? "no models. load a model in LM Studio and start the server." : "no models found." }) : models.map((m, i) => {
1858
+ const sel = i === cursor;
1859
+ return /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", color: sel ? "blue" : void 0, dimColor: !sel, children: [
1860
+ sel ? "\u276F " : " ",
1861
+ m,
1862
+ m === model ? /* @__PURE__ */ jsx3(Text3, { color: "green", children: " \u25CF" }) : null
1863
+ ] }, m);
1864
+ }) }),
1865
+ /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
1866
+ query ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: `filter: ${query}` }) : null,
1867
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: `\u2191\u2193 navigate enter select \u2190\u2192 effort tab provider type to filter${requireSelection ? " ctrl+c quit" : " esc close"}` })
1868
+ ] })
1869
+ ] });
1870
+ }
1871
+
1872
+ // src/ui/ProviderPicker.tsx
1539
1873
  import { Box as Box4, Text as Text4 } from "ink";
1540
1874
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1541
- function ModelsView({ models, cursor, model, ollamaHost, effort }) {
1875
+ function ProviderPicker({ entries, cursor, activeName, query }) {
1876
+ const nameWidth = Math.max(8, ...entries.map((e) => e.name.length));
1542
1877
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginLeft: 2, children: [
1543
- /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginBottom: 1, children: [
1544
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "config" }),
1545
- /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
1546
- /* @__PURE__ */ jsxs4(Text4, { children: [
1547
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "model " }),
1548
- /* @__PURE__ */ jsx4(Text4, { children: model ?? "\u2014" })
1878
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "select provider" }),
1879
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: entries.length === 0 ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "no providers configured \u2014 add one in ~/.miii/config.json" }) : entries.map((e, i) => {
1880
+ const sel = i === cursor;
1881
+ return /* @__PURE__ */ jsxs4(Text4, { color: sel ? "blue" : void 0, dimColor: !sel, children: [
1882
+ sel ? "\u276F " : " ",
1883
+ e.name.padEnd(nameWidth),
1884
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1885
+ " ",
1886
+ e.kind.padEnd(5)
1549
1887
  ] }),
1550
- /* @__PURE__ */ jsxs4(Text4, { children: [
1551
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "host " }),
1552
- /* @__PURE__ */ jsx4(Text4, { children: ollamaHost ?? "http://localhost:11434" })
1888
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1889
+ " ",
1890
+ e.entry.baseUrl
1553
1891
  ] }),
1554
- /* @__PURE__ */ jsxs4(Text4, { children: [
1555
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "effort " }),
1556
- /* @__PURE__ */ jsx4(Text4, { children: effort }),
1557
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " (\u2190 \u2192)" })
1558
- ] })
1559
- ] })
1560
- ] }),
1561
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "switch model" }),
1562
- /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(ModelList, { models, cursor, activeModel: model, showActive: true }) }),
1563
- /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2191\u2193 navigate enter switch \u2190\u2192 effort esc close" }) })
1892
+ e.name === activeName ? /* @__PURE__ */ jsx4(Text4, { color: "green", children: " \u25CF" }) : null
1893
+ ] }, e.name);
1894
+ }) }),
1895
+ /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
1896
+ query ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: `filter: ${query}` }) : null,
1897
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2191\u2193 navigate enter select type to filter esc back" })
1898
+ ] })
1564
1899
  ] });
1565
1900
  }
1566
1901
 
1567
1902
  // src/ui/SessionsView.tsx
1568
1903
  import { Box as Box5, Text as Text5 } from "ink";
1569
1904
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1905
+ function truncate(s, max) {
1906
+ if (max <= 0) return "";
1907
+ if (s.length <= max) return s;
1908
+ return s.slice(0, Math.max(0, max - 1)) + "\u2026";
1909
+ }
1570
1910
  function relativeTime(iso) {
1571
1911
  const diff = Date.now() - new Date(iso).getTime();
1572
1912
  const min = Math.floor(diff / 6e4);
@@ -1581,14 +1921,17 @@ function SessionsView({ sessions, cursor }) {
1581
1921
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginLeft: 2, children: [
1582
1922
  /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "resume session" }),
1583
1923
  /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: sessions.length === 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "no saved sessions yet" }) : sessions.map((s, i) => {
1584
- const active = i === cursor;
1585
- const label = s.title;
1924
+ const active2 = i === cursor;
1925
+ const meta = `\xB7 ${s.messageCount} msgs \xB7 ${relativeTime(s.updatedAt)}`;
1926
+ const cols = process.stdout.columns ?? 80;
1927
+ const titleMax = cols - 9 - meta.length;
1928
+ const label = truncate(s.title, titleMax);
1586
1929
  return /* @__PURE__ */ jsxs5(Box5, { gap: 1, children: [
1587
- /* @__PURE__ */ jsxs5(Text5, { color: active ? "blue" : void 0, dimColor: !active, children: [
1588
- active ? "\u276F " : " ",
1930
+ /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", color: active2 ? "blue" : void 0, dimColor: !active2, children: [
1931
+ active2 ? "\u276F " : " ",
1589
1932
  label
1590
1933
  ] }),
1591
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: `\xB7 ${s.messageCount} msgs \xB7 ${relativeTime(s.updatedAt)}` })
1934
+ /* @__PURE__ */ jsx5(Text5, { wrap: "truncate", dimColor: true, children: meta })
1592
1935
  ] }, s.id);
1593
1936
  }) }),
1594
1937
  /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2191\u2193 navigate enter resume d delete esc cancel" }) })
@@ -1599,7 +1942,8 @@ function SessionsView({ sessions, cursor }) {
1599
1942
  import { Box as Box6, Text as Text6 } from "ink";
1600
1943
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1601
1944
  var COMMANDS = [
1602
- { name: "/models", description: "switch model or adjust effort" },
1945
+ { name: "/models", description: "pick model \xB7 tab to change provider \xB7 \u2190\u2192 effort" },
1946
+ { name: "/provider", description: "open provider picker (configured in ~/.miii/config.json)" },
1603
1947
  { name: "/new", description: "save current session and start fresh" },
1604
1948
  { name: "/sessions", description: "list sessions and resume one" },
1605
1949
  { name: "/clear", description: "clear chat and reset context" },
@@ -1620,10 +1964,10 @@ function CommandPalette({ filter, cursor }) {
1620
1964
  paddingX: 1,
1621
1965
  children: [
1622
1966
  filtered.map((cmd2, i) => {
1623
- const active = i === cursor;
1967
+ const active2 = i === cursor;
1624
1968
  return /* @__PURE__ */ jsxs6(Box6, { gap: 2, children: [
1625
- /* @__PURE__ */ jsxs6(Text6, { bold: active, color: active ? "blue" : void 0, dimColor: !active, children: [
1626
- active ? "\u276F " : " ",
1969
+ /* @__PURE__ */ jsxs6(Text6, { bold: active2, color: active2 ? "blue" : void 0, dimColor: !active2, children: [
1970
+ active2 ? "\u276F " : " ",
1627
1971
  cmd2.name.padEnd(nameWidth)
1628
1972
  ] }),
1629
1973
  /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: cmd2.description })
@@ -1645,7 +1989,7 @@ import { join as join2 } from "path";
1645
1989
  import { homedir as homedir2 } from "os";
1646
1990
  import { randomUUID } from "crypto";
1647
1991
  function encodeProjectDir(cwd) {
1648
- return cwd.replace(/[/\\]/g, "-").replace(/^-+/, "");
1992
+ return cwd.replace(/[:/\\]+/g, "-").replace(/^-+/, "");
1649
1993
  }
1650
1994
  var SESSION_DIR = join2(homedir2(), ".miii", "projects", encodeProjectDir(process.cwd()), "session");
1651
1995
  function newSessionId() {
@@ -1768,7 +2112,7 @@ Request:
1768
2112
  ${text.slice(0, 2e3)}`;
1769
2113
  try {
1770
2114
  let out = "";
1771
- for await (const chunk of chat(
2115
+ for await (const chunk of chat3(
1772
2116
  model,
1773
2117
  [{ role: "user", content: prompt }],
1774
2118
  void 0,
@@ -1850,9 +2194,9 @@ function FilePicker({ matches: matches2, cursor }) {
1850
2194
  paddingX: 1,
1851
2195
  children: [
1852
2196
  matches2.map((f, i) => {
1853
- const active = i === cursor;
1854
- return /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text7, { bold: active, color: active ? "blue" : void 0, dimColor: !active, children: [
1855
- active ? "\u276F " : " ",
2197
+ const active2 = i === cursor;
2198
+ return /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text7, { bold: active2, color: active2 ? "blue" : void 0, dimColor: !active2, children: [
2199
+ active2 ? "\u276F " : " ",
1856
2200
  f
1857
2201
  ] }) }, f);
1858
2202
  }),
@@ -1863,8 +2207,9 @@ function FilePicker({ matches: matches2, cursor }) {
1863
2207
  }
1864
2208
 
1865
2209
  // src/ui/ChatView.tsx
1866
- import { useState as useState3, useEffect as useEffect3 } from "react";
2210
+ import { memo as memo2, useState as useState3, useEffect as useEffect3 } from "react";
1867
2211
  import { Box as Box9, Text as Text9 } from "ink";
2212
+ import { highlight } from "cli-highlight";
1868
2213
 
1869
2214
  // src/ui/ThinkingBlock.tsx
1870
2215
  import { useState as useState2, useEffect as useEffect2 } from "react";
@@ -1957,6 +2302,55 @@ function countLines(s) {
1957
2302
  if (!s) return 0;
1958
2303
  return s.split("\n").length;
1959
2304
  }
2305
+ var EXT_LANG = {
2306
+ ts: "typescript",
2307
+ tsx: "typescript",
2308
+ mts: "typescript",
2309
+ cts: "typescript",
2310
+ js: "javascript",
2311
+ jsx: "javascript",
2312
+ mjs: "javascript",
2313
+ cjs: "javascript",
2314
+ json: "json",
2315
+ py: "python",
2316
+ rb: "ruby",
2317
+ go: "go",
2318
+ rs: "rust",
2319
+ java: "java",
2320
+ c: "c",
2321
+ h: "c",
2322
+ cpp: "cpp",
2323
+ cc: "cpp",
2324
+ hpp: "cpp",
2325
+ cs: "csharp",
2326
+ php: "php",
2327
+ swift: "swift",
2328
+ kt: "kotlin",
2329
+ scala: "scala",
2330
+ sh: "bash",
2331
+ bash: "bash",
2332
+ zsh: "bash",
2333
+ yml: "yaml",
2334
+ yaml: "yaml",
2335
+ html: "xml",
2336
+ xml: "xml",
2337
+ css: "css",
2338
+ scss: "scss",
2339
+ sql: "sql",
2340
+ md: "markdown"
2341
+ };
2342
+ function langFromPath(path) {
2343
+ const ext = path.split(".").pop()?.toLowerCase();
2344
+ return ext ? EXT_LANG[ext] : void 0;
2345
+ }
2346
+ function highlightLine(text, lang) {
2347
+ if (!lang) return text;
2348
+ try {
2349
+ return highlight(text, { language: lang, ignoreIllegals: true });
2350
+ } catch {
2351
+ return text;
2352
+ }
2353
+ }
1960
2354
  function FileEditBlock({
1961
2355
  label,
1962
2356
  path,
@@ -1967,6 +2361,7 @@ function FileEditBlock({
1967
2361
  const expanded = useToolExpanded();
1968
2362
  const shown = expanded ? previewLines : previewLines.slice(0, COLLAPSED_LINES);
1969
2363
  const extra = previewLines.length - shown.length;
2364
+ const lang = langFromPath(path);
1970
2365
  return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginLeft: 2, children: [
1971
2366
  /* @__PURE__ */ jsxs9(Box9, { children: [
1972
2367
  /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "\u25CF " }),
@@ -1984,15 +2379,19 @@ function FileEditBlock({
1984
2379
  ] }) }),
1985
2380
  shown.map((ln, i) => {
1986
2381
  const width = (process.stdout.columns ?? 80) - 6 - 20;
1987
- const raw = `${ln.sign} ${ln.text}`;
1988
- const content = raw.length > width ? raw.slice(0, width) : raw.padEnd(width);
1989
- return /* @__PURE__ */ jsx9(Box9, { marginLeft: 4, children: /* @__PURE__ */ jsx9(
2382
+ const textWidth = Math.max(0, width - 2);
2383
+ const plain = ln.text.length > textWidth ? ln.text.slice(0, textWidth) : ln.text.padEnd(textWidth);
2384
+ const code = ln.sign === " " ? plain : highlightLine(plain, lang);
2385
+ return /* @__PURE__ */ jsx9(Box9, { marginLeft: 4, children: /* @__PURE__ */ jsxs9(
1990
2386
  Text9,
1991
2387
  {
1992
2388
  wrap: "truncate",
1993
2389
  backgroundColor: ln.sign === "+" ? "#13351f" : ln.sign === "-" ? "#3b1414" : void 0,
1994
2390
  dimColor: ln.sign === " ",
1995
- children: content
2391
+ children: [
2392
+ `${ln.sign} `,
2393
+ code
2394
+ ]
1996
2395
  }
1997
2396
  ) }, i);
1998
2397
  }),
@@ -2011,7 +2410,7 @@ var TOOL_LABEL = {
2011
2410
  glob: "Glob",
2012
2411
  grep: "Grep"
2013
2412
  };
2014
- function truncate(s, max) {
2413
+ function truncate2(s, max) {
2015
2414
  if (s.length <= max) return s;
2016
2415
  return s.slice(0, max - 1) + "\u2026";
2017
2416
  }
@@ -2027,15 +2426,15 @@ function toolHeader(use) {
2027
2426
  break;
2028
2427
  case "run_bash": {
2029
2428
  const cmd2 = String(input.command ?? "").replace(/\s+/g, " ");
2030
- arg = truncate(cmd2, 120);
2429
+ arg = truncate2(cmd2, 120);
2031
2430
  break;
2032
2431
  }
2033
2432
  case "glob":
2034
2433
  case "grep":
2035
- arg = truncate(String(input.pattern ?? ""), 120);
2434
+ arg = truncate2(String(input.pattern ?? ""), 120);
2036
2435
  break;
2037
2436
  default: {
2038
- arg = truncate(JSON.stringify(input), 80);
2437
+ arg = truncate2(JSON.stringify(input), 80);
2039
2438
  }
2040
2439
  }
2041
2440
  return { label, arg };
@@ -2077,7 +2476,7 @@ function ToolResultBlock({ result, toolName }) {
2077
2476
  }
2078
2477
  const MAX_LINE_WIDTH = 200;
2079
2478
  const visible = expanded ? lines : lines.slice(0, COLLAPSED_LINES);
2080
- const shown = visible.map((l) => truncate(l, MAX_LINE_WIDTH));
2479
+ const shown = visible.map((l) => truncate2(l, MAX_LINE_WIDTH));
2081
2480
  const extra = lines.length - shown.length;
2082
2481
  return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginLeft: 2, children: [
2083
2482
  /* @__PURE__ */ jsxs9(Text9, { color: result.is_error ? "red" : void 0, dimColor: !result.is_error, children: [
@@ -2127,7 +2526,13 @@ function ToolUseLine({ use, result }) {
2127
2526
  result && /* @__PURE__ */ jsx9(ToolResultBlock, { result, toolName: use.name })
2128
2527
  ] });
2129
2528
  }
2130
- function AssistantMessage({ msg }) {
2529
+ var UserMessage = memo2(function UserMessage2({ msg }) {
2530
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", marginBottom: 1, children: [
2531
+ /* @__PURE__ */ jsx9(Text9, { color: "blue", children: "\u25CF " }),
2532
+ /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx9(Text9, { children: msg.content }) })
2533
+ ] });
2534
+ });
2535
+ var AssistantMessage = memo2(function AssistantMessage2({ msg }) {
2131
2536
  return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
2132
2537
  msg.content && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", children: [
2133
2538
  /* @__PURE__ */ jsx9(Text9, { color: "white", children: "\u25CF " }),
@@ -2142,14 +2547,16 @@ function AssistantMessage({ msg }) {
2142
2547
  msg.duration != null ? ` \xB7 ${formatDuration(msg.duration)}` : ""
2143
2548
  ] }) })
2144
2549
  ] });
2145
- }
2550
+ });
2146
2551
  function summarizeInput(input) {
2147
2552
  if (!input || typeof input !== "object") return "";
2148
2553
  const obj = input;
2149
2554
  const priority = ["path", "file_path", "command", "pattern", "query"];
2150
2555
  for (const k of priority) {
2151
2556
  const v = obj[k];
2152
- if (typeof v === "string" && v.length > 0) return `${k}: ${v}`;
2557
+ if (typeof v === "string" && v.length > 0) {
2558
+ return `${k}: ${v.length > 120 ? v.slice(0, 120) + "\u2026" : v}`;
2559
+ }
2153
2560
  }
2154
2561
  const first = Object.entries(obj).find(([, v]) => typeof v === "string");
2155
2562
  if (first) {
@@ -2174,7 +2581,7 @@ function PermissionPrompt({ req, cursor }) {
2174
2581
  /* @__PURE__ */ jsx9(Text9, { bold: true, children: label }),
2175
2582
  "?"
2176
2583
  ] }) }),
2177
- summary && /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: summary }) }),
2584
+ summary && /* @__PURE__ */ jsx9(Box9, { marginLeft: 2, children: /* @__PURE__ */ jsx9(Text9, { wrap: "truncate", dimColor: true, children: summary }) }),
2178
2585
  /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", marginTop: 1, children: options.map((opt, i) => /* @__PURE__ */ jsxs9(Text9, { color: i === cursor ? "blue" : void 0, children: [
2179
2586
  i === cursor ? "\u276F " : " ",
2180
2587
  i + 1,
@@ -2205,10 +2612,7 @@ function ChatView({
2205
2612
  ] }, i))
2206
2613
  ] }),
2207
2614
  messages.map(
2208
- (msg, i) => msg.role === "user" ? /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", marginBottom: 1, children: [
2209
- /* @__PURE__ */ jsx9(Text9, { color: "blue", children: "\u25CF " }),
2210
- /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx9(Text9, { children: msg.content }) })
2211
- ] }, i) : /* @__PURE__ */ jsx9(AssistantMessage, { msg }, i)
2615
+ (msg, i) => msg.role === "user" ? /* @__PURE__ */ jsx9(UserMessage, { msg }, i) : /* @__PURE__ */ jsx9(AssistantMessage, { msg }, i)
2212
2616
  ),
2213
2617
  thinking && /* @__PURE__ */ jsx9(ThinkingBlock, { content: thinkingContent }),
2214
2618
  streaming && streamingContent && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", marginBottom: 1, children: [
@@ -2451,6 +2855,7 @@ function useAgentRunner(model, activeCtx) {
2451
2855
  }
2452
2856
 
2453
2857
  // src/ui/hooks/useKeyboard.ts
2858
+ init_config();
2454
2859
  import { useInput } from "ink";
2455
2860
  var EFFORTS = ["low", "medium", "high"];
2456
2861
  function useKeyboard(opts) {
@@ -2465,6 +2870,9 @@ function useKeyboard(opts) {
2465
2870
  cfg,
2466
2871
  setCfg,
2467
2872
  setActiveCtx,
2873
+ providers,
2874
+ pickerQuery,
2875
+ setPickerQuery,
2468
2876
  agent,
2469
2877
  input,
2470
2878
  setInput,
@@ -2476,7 +2884,8 @@ function useKeyboard(opts) {
2476
2884
  setSessionId,
2477
2885
  sessions,
2478
2886
  setSessions,
2479
- setNotice
2887
+ setNotice,
2888
+ switchProvider
2480
2889
  } = opts;
2481
2890
  const {
2482
2891
  pendingPermissionRef,
@@ -2523,6 +2932,38 @@ function useKeyboard(opts) {
2523
2932
  abortRef.current.abort();
2524
2933
  return;
2525
2934
  }
2935
+ if (state === "providers") {
2936
+ if (key.upArrow) {
2937
+ setCursor((i) => Math.max(0, i - 1));
2938
+ return;
2939
+ }
2940
+ if (key.downArrow) {
2941
+ setCursor((i) => Math.min(providers.length - 1, i + 1));
2942
+ return;
2943
+ }
2944
+ if (key.escape) {
2945
+ setPickerQuery("");
2946
+ setCursor(() => 0);
2947
+ setState(cfg.model ? "models" : "select-model");
2948
+ return;
2949
+ }
2950
+ if (key.return && providers[cursor]) {
2951
+ const chosen = providers[cursor].name;
2952
+ setNotice(`switched to ${chosen}`);
2953
+ switchProvider(chosen);
2954
+ return;
2955
+ }
2956
+ if (key.backspace || key.delete) {
2957
+ setPickerQuery(pickerQuery.slice(0, -1));
2958
+ setCursor(() => 0);
2959
+ return;
2960
+ }
2961
+ if (char && !key.ctrl && !key.meta && char.length === 1 && char >= " ") {
2962
+ setPickerQuery(pickerQuery + char);
2963
+ setCursor(() => 0);
2964
+ }
2965
+ return;
2966
+ }
2526
2967
  if (state === "select-model" || state === "models") {
2527
2968
  if (key.upArrow) {
2528
2969
  setCursor((i) => Math.max(0, i - 1));
@@ -2537,21 +2978,45 @@ function useKeyboard(opts) {
2537
2978
  setModel(chosen);
2538
2979
  setCfg((c) => ({ ...c, model: chosen }));
2539
2980
  if (contexts[chosen]) setActiveCtx(contexts[chosen]);
2981
+ setPickerQuery("");
2982
+ setCursor(() => 0);
2540
2983
  setState("ready");
2541
2984
  return;
2542
2985
  }
2543
- if (state === "models") {
2544
- if (key.rightArrow) {
2545
- const next = EFFORTS[Math.min(EFFORTS.indexOf(effort) + 1, EFFORTS.length - 1)];
2546
- setEffort(next);
2547
- setCfg((c) => ({ ...c, effort: next }));
2548
- } else if (key.leftArrow) {
2549
- const next = EFFORTS[Math.max(EFFORTS.indexOf(effort) - 1, 0)];
2550
- setEffort(next);
2551
- setCfg((c) => ({ ...c, effort: next }));
2552
- } else if (key.escape) {
2986
+ if (key.tab) {
2987
+ setPickerQuery("");
2988
+ setCursor(() => 0);
2989
+ setState("providers");
2990
+ return;
2991
+ }
2992
+ if (key.rightArrow) {
2993
+ const next = EFFORTS[Math.min(EFFORTS.indexOf(effort) + 1, EFFORTS.length - 1)];
2994
+ setEffort(next);
2995
+ setCfg((c) => ({ ...c, effort: next }));
2996
+ return;
2997
+ }
2998
+ if (key.leftArrow) {
2999
+ const next = EFFORTS[Math.max(EFFORTS.indexOf(effort) - 1, 0)];
3000
+ setEffort(next);
3001
+ setCfg((c) => ({ ...c, effort: next }));
3002
+ return;
3003
+ }
3004
+ if (key.escape) {
3005
+ if (state === "models") {
3006
+ setPickerQuery("");
3007
+ setCursor(() => 0);
2553
3008
  setState("ready");
2554
3009
  }
3010
+ return;
3011
+ }
3012
+ if (key.backspace || key.delete) {
3013
+ setPickerQuery(pickerQuery.slice(0, -1));
3014
+ setCursor(() => 0);
3015
+ return;
3016
+ }
3017
+ if (char && !key.ctrl && !key.meta && char.length === 1 && char >= " ") {
3018
+ setPickerQuery(pickerQuery + char);
3019
+ setCursor(() => 0);
2555
3020
  }
2556
3021
  return;
2557
3022
  }
@@ -2654,8 +3119,13 @@ function useKeyboard(opts) {
2654
3119
  if (key.return) {
2655
3120
  const trimmed = input.trim();
2656
3121
  if (trimmed === "/models") {
3122
+ setPickerQuery("");
2657
3123
  setCursor(() => Math.max(0, models.findIndex((m) => m === cfg.model)));
2658
3124
  setState("models");
3125
+ } else if (trimmed === "/provider" || trimmed === "/providers") {
3126
+ setPickerQuery("");
3127
+ setCursor(() => Math.max(0, providers.findIndex((p) => p.name === cfg.provider)));
3128
+ setState("providers");
2659
3129
  } else if (trimmed === "/clear") {
2660
3130
  clearSession();
2661
3131
  } else if (trimmed === "/new") {
@@ -2668,6 +3138,15 @@ function useKeyboard(opts) {
2668
3138
  setState("sessions");
2669
3139
  } else if (trimmed === "/exit") {
2670
3140
  exit();
3141
+ } else if (trimmed.startsWith("/provider ")) {
3142
+ const p = trimmed.slice("/provider ".length).trim();
3143
+ const names = providers.map((x) => x.name);
3144
+ if (names.includes(p)) {
3145
+ setNotice(`switched to ${p}`);
3146
+ switchProvider(p);
3147
+ } else {
3148
+ setNotice(`unknown provider "${p}" \u2014 configured: ${names.join(", ")}`);
3149
+ }
2671
3150
  } else if (trimmed) {
2672
3151
  setNotice(null);
2673
3152
  if (!agentHistory.length && cfg.model) {
@@ -2750,8 +3229,9 @@ function App() {
2750
3229
  const [activeCtx, setActiveCtx] = useState5(null);
2751
3230
  const [state, setState] = useState5("loading");
2752
3231
  const [cursor, setCursor] = useState5(0);
3232
+ const [pickerQuery, setPickerQuery] = useState5("");
2753
3233
  const [updateAvailable, setUpdateAvailable] = useState5(null);
2754
- const [ollamaDown, setOllamaDown] = useState5(false);
3234
+ const [providerDown, setProviderDown] = useState5(false);
2755
3235
  const [sessionId, setSessionId] = useState5(() => newSessionId());
2756
3236
  const [sessions, setSessions] = useState5([]);
2757
3237
  const [notice, setNotice] = useState5(null);
@@ -2767,36 +3247,67 @@ function App() {
2767
3247
  useEffect4(() => {
2768
3248
  if (agent.agentHistory.length) persistSession(sessionId, agent.agentHistory);
2769
3249
  }, [agent.agentHistory, sessionId]);
2770
- useEffect4(() => {
2771
- listModels().then((m) => {
3250
+ const loadGen = useRef2(0);
3251
+ const loadModels = (afterProvider = false) => {
3252
+ const gen = ++loadGen.current;
3253
+ const stale = () => gen !== loadGen.current;
3254
+ setProviderDown(false);
3255
+ listModels3().then((m) => {
3256
+ if (stale()) return;
2772
3257
  setModels(m);
2773
- setState(cfg.model ? "ready" : "select-model");
2774
- Promise.all(m.map((name) => modelContext(name).then((ctx) => [name, ctx]))).then((pairs) => {
3258
+ const hasModel = !!cfg.model && m.includes(cfg.model);
3259
+ if (afterProvider) {
3260
+ setState(hasModel ? "models" : "select-model");
3261
+ } else {
3262
+ setState(hasModel ? "ready" : "select-model");
3263
+ }
3264
+ Promise.all(m.map((name) => modelContext3(name).then((ctx) => [name, ctx]))).then((pairs) => {
3265
+ if (stale()) return;
2775
3266
  const map = Object.fromEntries(pairs);
2776
3267
  setContexts(map);
2777
- const active = cfg.model ?? m[0];
2778
- if (active && map[active]) setActiveCtx(map[active]);
3268
+ const active2 = (hasModel ? cfg.model : void 0) ?? m[0];
3269
+ if (active2 && map[active2]) setActiveCtx(map[active2]);
2779
3270
  }).catch(() => {
2780
3271
  });
2781
3272
  }).catch((err) => {
3273
+ if (stale()) return;
2782
3274
  const msg = err instanceof Error ? err.message : String(err);
2783
- agent.setError(ollamaInstalled() ? msg : OLLAMA_NOT_INSTALLED);
2784
- setOllamaDown(true);
3275
+ agent.setError(isAvailable3() ? msg : NOT_AVAILABLE());
3276
+ setProviderDown(true);
2785
3277
  setModels([]);
2786
- setState(cfg.model ? "ready" : "select-model");
3278
+ setPickerQuery("");
3279
+ setCursor(() => 0);
3280
+ setState("ready");
2787
3281
  });
2788
- }, []);
3282
+ };
3283
+ useEffect4(loadModels, []);
3284
+ function switchProvider(p) {
3285
+ setProvider(p);
3286
+ setCfg((c) => ({ ...c, provider: p }));
3287
+ setPickerQuery("");
3288
+ setCursor(() => 0);
3289
+ agent.setError(null);
3290
+ loadModels(true);
3291
+ }
3292
+ const { name: provName, entry: provEntry } = resolveProvider(cfg);
3293
+ const q = pickerQuery.toLowerCase();
3294
+ const filteredModels = q ? models.filter((m) => m.toLowerCase().includes(q)) : models;
3295
+ const allProviders = providerEntries(cfg);
3296
+ const filteredProviders = q ? allProviders.filter((p) => p.name.toLowerCase().includes(q)) : allProviders;
2789
3297
  useKeyboard({
2790
3298
  exit,
2791
3299
  state,
2792
3300
  setState,
2793
- models,
3301
+ models: filteredModels,
2794
3302
  cursor,
2795
3303
  setCursor,
2796
3304
  contexts,
2797
3305
  cfg,
2798
3306
  setCfg,
2799
3307
  setActiveCtx,
3308
+ providers: filteredProviders,
3309
+ pickerQuery,
3310
+ setPickerQuery,
2800
3311
  agent,
2801
3312
  input,
2802
3313
  setInput,
@@ -2808,7 +3319,8 @@ function App() {
2808
3319
  setSessionId,
2809
3320
  sessions,
2810
3321
  setSessions,
2811
- setNotice
3322
+ setNotice,
3323
+ switchProvider
2812
3324
  });
2813
3325
  const effort = cfg.effort ?? "medium";
2814
3326
  const contextWarning = (() => {
@@ -2819,9 +3331,8 @@ function App() {
2819
3331
  return Math.round(used / activeCtx * 100);
2820
3332
  })();
2821
3333
  return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingX: 1, children: [
2822
- /* @__PURE__ */ jsx10(WelcomeBlock, { model: cfg.model, activeCtx, effort, cwd, error: agent.error }),
2823
- updateAvailable && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: `\u2191 update available: v${updateAvailable} \u2014 run: miii --update` }) }),
2824
- state === "loading" && !agent.error && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "connecting to ollama\u2026" }) }),
3334
+ /* @__PURE__ */ jsx10(WelcomeBlock, { model: cfg.model, activeCtx, effort, cwd, error: agent.error, updateAvailable }),
3335
+ state === "loading" && !agent.error && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: `connecting to ${provName}\u2026` }) }),
2825
3336
  agent.error && state !== "ready" && /* @__PURE__ */ jsx10(
2826
3337
  ChatView,
2827
3338
  {
@@ -2832,19 +3343,26 @@ function App() {
2832
3343
  error: agent.error
2833
3344
  }
2834
3345
  ),
2835
- state === "select-model" && /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", marginLeft: 2, children: [
2836
- /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "no model configured \u2014 select one" }),
2837
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(ModelList, { models, cursor }) }),
2838
- models.length > 0 && /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\u2191\u2193 navigate enter select ctrl+c quit" }) })
2839
- ] }),
2840
- state === "models" && /* @__PURE__ */ jsx10(
3346
+ (state === "select-model" || state === "models") && /* @__PURE__ */ jsx10(
2841
3347
  ModelsView,
2842
3348
  {
2843
- models,
3349
+ models: filteredModels,
2844
3350
  cursor,
2845
3351
  model: cfg.model,
2846
- ollamaHost: cfg.ollamaHost,
2847
- effort
3352
+ host: provEntry.baseUrl,
3353
+ provider: provName,
3354
+ effort,
3355
+ query: pickerQuery,
3356
+ requireSelection: state === "select-model"
3357
+ }
3358
+ ),
3359
+ state === "providers" && /* @__PURE__ */ jsx10(
3360
+ ProviderPicker,
3361
+ {
3362
+ entries: filteredProviders,
3363
+ cursor,
3364
+ activeName: provName,
3365
+ query: pickerQuery
2848
3366
  }
2849
3367
  ),
2850
3368
  state === "sessions" && /* @__PURE__ */ jsx10(SessionsView, { sessions, cursor }),
@@ -2872,24 +3390,33 @@ function App() {
2872
3390
  if (!m) return null;
2873
3391
  return /* @__PURE__ */ jsx10(FilePicker, { matches: searchFiles(process.cwd(), m.query), cursor: filePickerCursor });
2874
3392
  })(),
2875
- !ollamaDown && /* @__PURE__ */ jsxs10(Fragment2, { children: [
2876
- /* @__PURE__ */ jsx10(InputBar, { input, disabled: agent.busy, processingLabel: agent.processingLabel }),
2877
- !agent.busy && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "type / to see commands" }) })
2878
- ] })
3393
+ /* @__PURE__ */ jsx10(InputBar, { input, disabled: agent.busy, processingLabel: agent.processingLabel }),
3394
+ !agent.busy && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: providerDown ? "provider unavailable \u2014 /provider to switch \xB7 /models to pick a model" : "type / to see commands" }) })
2879
3395
  ] })
2880
3396
  ] });
2881
3397
  }
2882
3398
 
2883
3399
  // src/cli.tsx
2884
3400
  init_spill();
3401
+ init_config();
2885
3402
  cleanupSpill();
2886
- var [, , cmd, ...rest] = process.argv;
3403
+ var args = process.argv.slice(2);
3404
+ var cmd;
3405
+ for (let i = 0; i < args.length; i++) {
3406
+ if ((args[i] === "--provider" || args[i] === "-p") && i + 1 < args.length) {
3407
+ const p = args[++i];
3408
+ if (listProviders().includes(p)) setProvider(p);
3409
+ } else if (!cmd) {
3410
+ cmd = args[i];
3411
+ }
3412
+ }
2887
3413
  if (cmd === "update" || cmd === "--update" || cmd === "-u") {
2888
3414
  const { spawnSync } = await import("child_process");
2889
3415
  console.log("Updating miii-agent\u2026");
2890
3416
  const r = spawnSync("npm", ["i", "-g", "miii-agent@latest"], { stdio: "inherit", shell: process.platform === "win32" });
2891
3417
  process.exit(r.status ?? 1);
2892
3418
  } else if (cmd === "doctor" || cmd === "eval") {
3419
+ const rest = args.filter((a) => a !== cmd);
2893
3420
  const { runEval: runEval2 } = await Promise.resolve().then(() => (init_run(), run_exports));
2894
3421
  process.exit(await runEval2(rest));
2895
3422
  } else {