@modeloslab/modelcode 0.1.1 → 0.1.3

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/dist/cli.mjs CHANGED
@@ -55,6 +55,7 @@ import {
55
55
  runTeam,
56
56
  searchFacts,
57
57
  searchSessions,
58
+ shellInvocation,
58
59
  snapshot,
59
60
  spawnProc,
60
61
  toolSchemas,
@@ -62,7 +63,7 @@ import {
62
63
  validateMnemonic,
63
64
  walletFromMnemonic,
64
65
  which
65
- } from "./main-2aqyq9g6.mjs";
66
+ } from "./main-k6vk3448.mjs";
66
67
  import"./main-p2xnn95s.mjs";
67
68
  import {
68
69
  __require
@@ -153,7 +154,7 @@ var bash = {
153
154
  async run(args, ctx) {
154
155
  const cmd = String(args.command ?? "");
155
156
  const timeout = Number(args.timeout_ms) || 120000;
156
- const r = await runProc(["bash", "-lc", cmd], { cwd: ctx.cwd, timeoutMs: timeout, onChunk: (s) => ctx.onStream?.(s) });
157
+ const r = await runProc(shellInvocation(cmd), { cwd: ctx.cwd, timeoutMs: timeout, onChunk: (s) => ctx.onStream?.(s) });
157
158
  return `exit ${r.code}
158
159
  ${(r.stdout + (r.stderr ? `
159
160
  [stderr]
@@ -250,9 +251,11 @@ var grep = {
250
251
  async run(args, ctx) {
251
252
  const pat = String(args.pattern ?? "");
252
253
  const where = args.path ? inside(ctx.cwd, String(args.path)) : ctx.cwd;
253
- const tool = which("rg") ? `rg -n --no-heading -e ${JSON.stringify(pat)} ${JSON.stringify(where)}` : `grep -rnE ${JSON.stringify(pat)} ${JSON.stringify(where)}`;
254
- const r = await runProc(["bash", "-lc", `${tool} 2>/dev/null | head -200`], { cwd: ctx.cwd });
255
- return r.stdout.trim() || "(no matches)";
254
+ const argv = which("rg") ? ["rg", "-n", "--no-heading", "-e", pat, where] : ["grep", "-rnE", pat, where];
255
+ const r = await runProc(argv, { cwd: ctx.cwd });
256
+ return r.stdout.split(`
257
+ `).slice(0, 200).join(`
258
+ `).trim() || "(no matches)";
256
259
  }
257
260
  };
258
261
  function registerCoreTools() {
@@ -908,9 +911,11 @@ var lsp = {
908
911
  existsSync5(resolve2(ctx.cwd, "Cargo.toml")) && "cargo check -q"
909
912
  ].filter(Boolean);
910
913
  if (action === "diagnostics" && cmds.length) {
911
- const r = await runProc(["bash", "-lc", `${cmds[0]} 2>&1 | head -120`], { cwd: ctx.cwd });
914
+ const r = await runProc(shellInvocation(cmds[0]), { cwd: ctx.cwd });
912
915
  return `(no language server installed; shelled typecheck)
913
- ${r.stdout.trim() || "no diagnostics"}`;
916
+ ${(r.stdout + r.stderr).split(`
917
+ `).slice(0, 120).join(`
918
+ `).trim() || "no diagnostics"}`;
914
919
  }
915
920
  return "no language server for this file type (install e.g. typescript-language-server, pyright, gopls)";
916
921
  }
@@ -1351,10 +1356,15 @@ async function interactiveLogin(rl, cfg) {
1351
1356
  ` + ` ${C.cyan}3${C.reset}) skip for now
1352
1357
  `);
1353
1358
  const choice = (await rl.question(`${C.green}choose 1-3\u203A${C.reset} `)).trim();
1354
- if (choice === "1" || choice.startsWith("mdlk_")) {
1355
- const key = choice.startsWith("mdlk_") ? choice : (await rl.question("API key (mdlk_\u2026): ")).trim();
1356
- if (!key.startsWith("mdlk_")) {
1357
- stdout.write(`${C.amber}that doesn't look like an mdlk_ key${C.reset}
1359
+ const extractKey = (s) => s.match(/mdlk_[A-Za-z0-9]+/)?.[0] ?? "";
1360
+ if (choice === "1" || /mdlk_/.test(choice)) {
1361
+ let key = extractKey(choice);
1362
+ if (!key)
1363
+ key = extractKey(await rl.question("paste your API key (mdlk_\u2026): "));
1364
+ if (!key)
1365
+ key = extractKey(await rl.question("API key (mdlk_\u2026): "));
1366
+ if (!key) {
1367
+ stdout.write(`${C.amber}that doesn't look like an mdlk_ key \u2014 it should start with "mdlk_"${C.reset}
1358
1368
  `);
1359
1369
  return false;
1360
1370
  }
@@ -1576,7 +1586,7 @@ async function main() {
1576
1586
  await interactiveLogin(rl, cfg);
1577
1587
  rl.close();
1578
1588
  }
1579
- const { runTui } = await import("./tui-5an9t3j5.mjs");
1589
+ const { runTui } = await import("./tui-gh76vcf8.mjs");
1580
1590
  return runTui(cfg, resume);
1581
1591
  }
1582
1592
  if (cmd === "tui")
@@ -7962,6 +7962,23 @@ import { readdirSync, existsSync as existsSync2 } from "fs";
7962
7962
  import { join as join2, extname, dirname } from "path";
7963
7963
  import { spawn, spawnSync } from "node:child_process";
7964
7964
  import { StringDecoder } from "node:string_decoder";
7965
+ var isWin = process.platform === "win32";
7966
+ function shellInvocation(cmd) {
7967
+ if (!isWin)
7968
+ return ["bash", "-lc", cmd];
7969
+ return hasBash() ? ["bash", "-c", cmd] : ["cmd", "/c", cmd];
7970
+ }
7971
+ var _bash = null;
7972
+ function hasBash() {
7973
+ if (_bash !== null)
7974
+ return _bash;
7975
+ try {
7976
+ _bash = spawnSync("bash", ["-c", "exit 0"], { stdio: "ignore" }).status === 0;
7977
+ } catch {
7978
+ _bash = false;
7979
+ }
7980
+ return _bash;
7981
+ }
7965
7982
  function packageRoot(startDir) {
7966
7983
  let dir = startDir;
7967
7984
  for (let i = 0;i < 12; i++) {
@@ -8065,7 +8082,8 @@ function runProc(cmd, opts = {}) {
8065
8082
  }
8066
8083
  function which(bin) {
8067
8084
  try {
8068
- return spawnSync("bash", ["-c", `command -v ${bin}`], { stdio: "ignore" }).status === 0;
8085
+ const probe = isWin ? spawnSync("where", [bin], { stdio: "ignore" }) : spawnSync("bash", ["-c", `command -v ${bin}`], { stdio: "ignore" });
8086
+ return probe.status === 0;
8069
8087
  } catch {
8070
8088
  return false;
8071
8089
  }
@@ -21320,7 +21338,7 @@ function cfg(cwd) {
21320
21338
  return _cfg = c;
21321
21339
  }
21322
21340
  async function runHook(command, cwd, env) {
21323
- const r = await runProc(["bash", "-lc", command], { cwd, env });
21341
+ const r = await runProc(shellInvocation(command), { cwd, env });
21324
21342
  return { code: r.code, out: (r.stdout + r.stderr).trim() };
21325
21343
  }
21326
21344
  async function preToolUse(toolName, args, cwd) {
@@ -24236,4 +24254,4 @@ async function decryptWallet(enc, password, network = MAINNET) {
24236
24254
  }
24237
24255
  return w;
24238
24256
  }
24239
- export { exports_external, register, getTool, toolSchemas, renderDiff, globMatch, runProc, which, spawnProc, globalDir, loadConfig, saveConfig, rememberFact, allFacts, searchFacts, deleteFact, recordTurn, searchSessions, parseFrontmatter, loadSubagents, chat2 as chat, route, loadProjectContext, addEntity, addRelation, query, indexFile, indexCodebaseIncremental, indexCodebase, loadSkills, buildTurnContext, preToolUse, postToolUse, userPromptSubmit, fireEvent, runSubagent, runTeam, isImagePath, imageToDataUrl, estimateTokens, contextStatus, compactionThreshold, findCompactionCut, newTurn, snapshot, undo, depth, MUTATING_TOOLS, isProtectedBuiltin, recordUse, recordPatch, recordCreate, getUsage, allUsage, activityCount, analyzeImpact, impactSummary, lspFor, closeAllLsp, COLUMNS, addCard, moveCard, removeCard, renderBoard, addJob, listJobs, removeJob, startScheduler, MAINNET, generateMnemonic2 as generateMnemonic, validateMnemonic2 as validateMnemonic, walletFromMnemonic, importWalletHex, encryptWallet, decryptWallet, createTransfer };
24257
+ export { exports_external, register, getTool, toolSchemas, renderDiff, shellInvocation, globMatch, runProc, which, spawnProc, globalDir, loadConfig, saveConfig, rememberFact, allFacts, searchFacts, deleteFact, recordTurn, searchSessions, parseFrontmatter, loadSubagents, chat2 as chat, route, loadProjectContext, addEntity, addRelation, query, indexFile, indexCodebaseIncremental, indexCodebase, loadSkills, buildTurnContext, preToolUse, postToolUse, userPromptSubmit, fireEvent, runSubagent, runTeam, isImagePath, imageToDataUrl, estimateTokens, contextStatus, compactionThreshold, findCompactionCut, newTurn, snapshot, undo, depth, MUTATING_TOOLS, isProtectedBuiltin, recordUse, recordPatch, recordCreate, getUsage, allUsage, activityCount, analyzeImpact, impactSummary, lspFor, closeAllLsp, COLUMNS, addCard, moveCard, removeCard, renderBoard, addJob, listJobs, removeJob, startScheduler, MAINNET, generateMnemonic2 as generateMnemonic, validateMnemonic2 as validateMnemonic, walletFromMnemonic, importWalletHex, encryptWallet, decryptWallet, createTransfer };
@@ -59,6 +59,7 @@ import {
59
59
  saveConfig,
60
60
  searchFacts,
61
61
  searchSessions,
62
+ shellInvocation,
62
63
  snapshot,
63
64
  spawnProc,
64
65
  startScheduler,
@@ -66,7 +67,7 @@ import {
66
67
  undo,
67
68
  userPromptSubmit,
68
69
  which
69
- } from "./main-2aqyq9g6.mjs";
70
+ } from "./main-k6vk3448.mjs";
70
71
  import"./main-p2xnn95s.mjs";
71
72
  import {
72
73
  __commonJS,
@@ -21479,6 +21480,92 @@ ${c.dim}Tips show on startup (one at a time). Toggle off with MODELCODE_TIPS=0.$
21479
21480
  `);
21480
21481
  }
21481
21482
 
21483
+ // src/ui/spinnerVerbs.ts
21484
+ var SPINNER_VERBS = [
21485
+ "Accomplishing",
21486
+ "Architecting",
21487
+ "Baking",
21488
+ "Brewing",
21489
+ "Calculating",
21490
+ "Cerebrating",
21491
+ "Channeling",
21492
+ "Churning",
21493
+ "Cogitating",
21494
+ "Composing",
21495
+ "Computing",
21496
+ "Concocting",
21497
+ "Considering",
21498
+ "Contemplating",
21499
+ "Cooking",
21500
+ "Crafting",
21501
+ "Creating",
21502
+ "Crunching",
21503
+ "Crystallizing",
21504
+ "Deciphering",
21505
+ "Deliberating",
21506
+ "Determining",
21507
+ "Digging",
21508
+ "Distilling",
21509
+ "Doing",
21510
+ "Effecting",
21511
+ "Elaborating",
21512
+ "Engineering",
21513
+ "Envisioning",
21514
+ "Excavating",
21515
+ "Exploring",
21516
+ "Fabricating",
21517
+ "Fashioning",
21518
+ "Finagling",
21519
+ "Forging",
21520
+ "Formulating",
21521
+ "Generating",
21522
+ "Germinating",
21523
+ "Hatching",
21524
+ "Herding",
21525
+ "Honing",
21526
+ "Hustling",
21527
+ "Ideating",
21528
+ "Imagining",
21529
+ "Incubating",
21530
+ "Inferring",
21531
+ "Manifesting",
21532
+ "Marinating",
21533
+ "Mulling",
21534
+ "Musing",
21535
+ "Noodling",
21536
+ "Orchestrating",
21537
+ "Percolating",
21538
+ "Philosophizing",
21539
+ "Plotting",
21540
+ "Pondering",
21541
+ "Processing",
21542
+ "Puttering",
21543
+ "Puzzling",
21544
+ "Reticulating",
21545
+ "Ruminating",
21546
+ "Scheming",
21547
+ "Sculpting",
21548
+ "Shimmying",
21549
+ "Simmering",
21550
+ "Smooshing",
21551
+ "Spinning",
21552
+ "Stewing",
21553
+ "Summoning",
21554
+ "Synthesizing",
21555
+ "Thinking",
21556
+ "Tinkering",
21557
+ "Transmuting",
21558
+ "Unfurling",
21559
+ "Vibing",
21560
+ "Weaving",
21561
+ "Whirring",
21562
+ "Working",
21563
+ "Wrangling"
21564
+ ];
21565
+ function randomVerb() {
21566
+ return SPINNER_VERBS[Math.floor(Math.random() * SPINNER_VERBS.length)];
21567
+ }
21568
+
21482
21569
  // src/ui/themes.ts
21483
21570
  var THEMES = {
21484
21571
  amber: { accent: "yellow", user: "green", tool: "cyan", dim: "gray", ok: "green", warn: "yellow" },
@@ -21491,11 +21578,11 @@ function getTheme(name) {
21491
21578
 
21492
21579
  // src/ui/App.tsx
21493
21580
  var jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
21494
- var SPINNER = ["", "", "", "", "", "", "⠦", "⠧", "⠇", "⠏"];
21581
+ var SPINNER = ["", "", "", "", "", ""];
21495
21582
  function Spinner({ color }) {
21496
21583
  const [i, setI] = import_react34.useState(0);
21497
21584
  import_react34.useEffect(() => {
21498
- const t = setInterval(() => setI((n) => (n + 1) % SPINNER.length), 80);
21585
+ const t = setInterval(() => setI((n) => (n + 1) % SPINNER.length), 120);
21499
21586
  return () => clearInterval(t);
21500
21587
  }, []);
21501
21588
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
@@ -21503,6 +21590,46 @@ function Spinner({ color }) {
21503
21590
  children: SPINNER[i]
21504
21591
  }, undefined, false, undefined, this);
21505
21592
  }
21593
+ function WorkingRow({ start, tokens, color, dim }) {
21594
+ const [, tick] = import_react34.useState(0);
21595
+ const [verb, setVerb] = import_react34.useState(() => randomVerb());
21596
+ import_react34.useEffect(() => {
21597
+ const t = setInterval(() => tick((n) => n + 1), 1000);
21598
+ const v = setInterval(() => setVerb(randomVerb()), 4000);
21599
+ return () => {
21600
+ clearInterval(t);
21601
+ clearInterval(v);
21602
+ };
21603
+ }, []);
21604
+ const secs = Math.max(0, Math.floor((Date.now() - start) / 1000));
21605
+ const tok = tokens > 0 ? ` · ↓ ${tokens.toLocaleString()} tok` : "";
21606
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21607
+ children: [
21608
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Spinner, {
21609
+ color
21610
+ }, undefined, false, undefined, this),
21611
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21612
+ color,
21613
+ bold: true,
21614
+ children: [
21615
+ " ",
21616
+ verb,
21617
+ "…"
21618
+ ]
21619
+ }, undefined, true, undefined, this),
21620
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21621
+ color: dim,
21622
+ children: [
21623
+ " (",
21624
+ secs,
21625
+ "s",
21626
+ tok,
21627
+ " · esc to interrupt)"
21628
+ ]
21629
+ }, undefined, true, undefined, this)
21630
+ ]
21631
+ }, undefined, true, undefined, this);
21632
+ }
21506
21633
  function Chip({ label, color }) {
21507
21634
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21508
21635
  backgroundColor: color,
@@ -21526,15 +21653,38 @@ function C_KEY(color, k) {
21526
21653
  ]
21527
21654
  }, undefined, true, undefined, this);
21528
21655
  }
21529
- function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct, walletMdl, mode, busy, confirmReq, history, onConfirm, onSubmit, onInterrupt, onExit }) {
21656
+ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct, walletMdl, mode, busy, toolStream, turnStart, turnTokens, confirmReq, history, onConfirm, onSubmit, onInterrupt, onExit }) {
21530
21657
  const t = getTheme(theme);
21531
21658
  const [input, setInput] = import_react34.useState("");
21532
21659
  const [histIdx, setHistIdx] = import_react34.useState(-1);
21660
+ const [exitHint, setExitHint] = import_react34.useState(false);
21661
+ const exitTimer = import_react34.useRef(null);
21533
21662
  const { exit } = use_app_default();
21663
+ const armExit = () => {
21664
+ setExitHint(true);
21665
+ if (exitTimer.current)
21666
+ clearTimeout(exitTimer.current);
21667
+ exitTimer.current = setTimeout(() => setExitHint(false), 800);
21668
+ };
21669
+ const doExit = () => {
21670
+ if (exitTimer.current)
21671
+ clearTimeout(exitTimer.current);
21672
+ onExit();
21673
+ exit();
21674
+ };
21534
21675
  use_input_default((ch, key) => {
21676
+ if (key.ctrl && ch === "d") {
21677
+ doExit();
21678
+ return;
21679
+ }
21535
21680
  if (key.ctrl && ch === "c") {
21536
- onExit();
21537
- exit();
21681
+ if (exitHint) {
21682
+ doExit();
21683
+ return;
21684
+ }
21685
+ if (busy)
21686
+ onInterrupt();
21687
+ armExit();
21538
21688
  return;
21539
21689
  }
21540
21690
  if (confirmReq) {
@@ -21710,22 +21860,21 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21710
21860
  }, undefined, true, undefined, this)
21711
21861
  ]
21712
21862
  }, undefined, true, undefined, this) : null,
21713
- busy && !streaming ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21714
- marginTop: 1,
21715
- children: [
21716
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Spinner, {
21717
- color: t.accent
21718
- }, undefined, false, undefined, this),
21719
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21720
- color: t.dim,
21721
- children: [
21722
- " thinking… ",
21723
- C_KEY(t.dim, "esc"),
21724
- " to interrupt"
21725
- ]
21726
- }, undefined, true, undefined, this)
21727
- ]
21728
- }, undefined, true, undefined, this) : null
21863
+ toolStream ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21864
+ color: t.dim,
21865
+ children: toolStream.split(`
21866
+ `).slice(-6).join(`
21867
+ `)
21868
+ }, undefined, false, undefined, this) : null,
21869
+ busy ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21870
+ marginTop: streaming || toolStream ? 0 : 1,
21871
+ children: /* @__PURE__ */ jsx_dev_runtime.jsxDEV(WorkingRow, {
21872
+ start: turnStart,
21873
+ tokens: turnTokens,
21874
+ color: t.accent,
21875
+ dim: t.dim
21876
+ }, undefined, false, undefined, this)
21877
+ }, undefined, false, undefined, this) : null
21729
21878
  ]
21730
21879
  }, undefined, true, undefined, this),
21731
21880
  suggestions.length ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
@@ -21794,7 +21943,7 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21794
21943
  ]
21795
21944
  }, undefined, true, undefined, this),
21796
21945
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21797
- children: input
21946
+ children: input || (busy ? "" : "")
21798
21947
  }, undefined, false, undefined, this),
21799
21948
  !busy ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21800
21949
  color: t.accent,
@@ -21830,16 +21979,22 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21830
21979
  ctxBar,
21831
21980
  " ",
21832
21981
  contextPct,
21833
- "%"
21982
+ "%",
21983
+ turnTokens > 0 ? ` · ↓${turnTokens.toLocaleString()} tok` : ""
21834
21984
  ]
21835
21985
  }, undefined, true, undefined, this),
21836
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21986
+ exitHint ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21987
+ color: t.warn,
21988
+ children: "press Ctrl-C again to exit"
21989
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21837
21990
  color: t.dim,
21838
21991
  children: [
21839
- sessionMdl.toFixed(6),
21992
+ sessionMdl.toFixed(4),
21840
21993
  " MDL",
21841
21994
  usd,
21842
- " · /help · ^C"
21995
+ " · ",
21996
+ busy ? "esc=stop · " : "",
21997
+ "^C ^C=exit"
21843
21998
  ]
21844
21999
  }, undefined, true, undefined, this)
21845
22000
  ]
@@ -22583,13 +22738,15 @@ async function handleSlash(line, ctx) {
22583
22738
  }
22584
22739
  case "/model": {
22585
22740
  if (!arg)
22586
- return { output: `build model: ${cfg.model}${cfg.autoRoute ? " (auto-route on)" : ""}` };
22741
+ return { output: `build model: ${cfg.model}${cfg.autoRoute ? " (auto-route ON — /route to turn off)" : ""}` };
22587
22742
  const id = resolveModelName(arg);
22588
22743
  if (!id)
22589
22744
  return { output: `unknown model '${arg}' — /models to list` };
22745
+ const wasRouting = cfg.autoRoute;
22590
22746
  cfg.model = id;
22591
- saveConfig({ model: id });
22592
- return { output: `✓ build model → ${id}` };
22747
+ cfg.autoRoute = false;
22748
+ saveConfig({ model: id, autoRoute: false });
22749
+ return { output: `✓ build model → ${id}${wasRouting ? " (auto-route turned OFF — your model is used for every turn)" : ""}`, modeChanged: true };
22593
22750
  }
22594
22751
  case "/submodel": {
22595
22752
  if (!arg) {
@@ -22803,7 +22960,7 @@ var bash = {
22803
22960
  async run(args, ctx) {
22804
22961
  const cmd = String(args.command ?? "");
22805
22962
  const timeout = Number(args.timeout_ms) || 120000;
22806
- const r = await runProc(["bash", "-lc", cmd], { cwd: ctx.cwd, timeoutMs: timeout, onChunk: (s) => ctx.onStream?.(s) });
22963
+ const r = await runProc(shellInvocation(cmd), { cwd: ctx.cwd, timeoutMs: timeout, onChunk: (s) => ctx.onStream?.(s) });
22807
22964
  return `exit ${r.code}
22808
22965
  ${(r.stdout + (r.stderr ? `
22809
22966
  [stderr]
@@ -22900,9 +23057,11 @@ var grep = {
22900
23057
  async run(args, ctx) {
22901
23058
  const pat = String(args.pattern ?? "");
22902
23059
  const where = args.path ? inside(ctx.cwd, String(args.path)) : ctx.cwd;
22903
- const tool = which("rg") ? `rg -n --no-heading -e ${JSON.stringify(pat)} ${JSON.stringify(where)}` : `grep -rnE ${JSON.stringify(pat)} ${JSON.stringify(where)}`;
22904
- const r = await runProc(["bash", "-lc", `${tool} 2>/dev/null | head -200`], { cwd: ctx.cwd });
22905
- return r.stdout.trim() || "(no matches)";
23060
+ const argv = which("rg") ? ["rg", "-n", "--no-heading", "-e", pat, where] : ["grep", "-rnE", pat, where];
23061
+ const r = await runProc(argv, { cwd: ctx.cwd });
23062
+ return r.stdout.split(`
23063
+ `).slice(0, 200).join(`
23064
+ `).trim() || "(no matches)";
22906
23065
  }
22907
23066
  };
22908
23067
  function registerCoreTools() {
@@ -23125,9 +23284,11 @@ var lsp = {
23125
23284
  existsSync8(resolve2(ctx.cwd, "Cargo.toml")) && "cargo check -q"
23126
23285
  ].filter(Boolean);
23127
23286
  if (action === "diagnostics" && cmds.length) {
23128
- const r = await runProc(["bash", "-lc", `${cmds[0]} 2>&1 | head -120`], { cwd: ctx.cwd });
23287
+ const r = await runProc(shellInvocation(cmds[0]), { cwd: ctx.cwd });
23129
23288
  return `(no language server installed; shelled typecheck)
23130
- ${r.stdout.trim() || "no diagnostics"}`;
23289
+ ${(r.stdout + r.stderr).split(`
23290
+ `).slice(0, 120).join(`
23291
+ `).trim() || "no diagnostics"}`;
23131
23292
  }
23132
23293
  return "no language server for this file type (install e.g. typescript-language-server, pyright, gopls)";
23133
23294
  }
@@ -23601,7 +23762,10 @@ function imageMentions(line, cwd2) {
23601
23762
  // src/ui/tui.tsx
23602
23763
  var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
23603
23764
  function Root({ cfg, resume, mcpCount }) {
23604
- const [lines, setLines] = import_react35.useState([{ kind: "system", text: "ready — ask me anything, or /help" }]);
23765
+ const [lines, setLines] = import_react35.useState([
23766
+ { kind: "system", text: `ready · model ${cfg.model} — ask me anything, or /help` },
23767
+ ...cfg.autoRoute ? [{ kind: "system", text: `⚠ auto-route is ON — turns may use a different model than ${cfg.model}. Run /route to turn it off (or /model ${cfg.model} to lock it in).` }] : []
23768
+ ]);
23605
23769
  const [streaming, setStreaming] = import_react35.useState("");
23606
23770
  const [busy, setBusy] = import_react35.useState(false);
23607
23771
  const [sessionMdl, setSessionMdl] = import_react35.useState(0);
@@ -23610,14 +23774,25 @@ function Root({ cfg, resume, mcpCount }) {
23610
23774
  const [mode, setMode] = import_react35.useState("default");
23611
23775
  const [confirmReq, setConfirmReq] = import_react35.useState(null);
23612
23776
  const [history, setHistory] = import_react35.useState([]);
23777
+ const [toolStream, setToolStream] = import_react35.useState("");
23778
+ const [turnStart, setTurnStart] = import_react35.useState(0);
23779
+ const [turnTokens, setTurnTokens] = import_react35.useState(0);
23613
23780
  const mdlUsd = import_react35.useRef(null);
23614
23781
  const streamBuf = import_react35.useRef("");
23782
+ const tokensRef = import_react35.useRef(0);
23615
23783
  const confirmResolve = import_react35.useRef(null);
23616
23784
  const sessionIdRef = import_react35.useRef(newSessionId());
23617
23785
  const sessionMdlRef = import_react35.useRef(0);
23618
23786
  const mcpCountRef = import_react35.useRef(mcpCount);
23619
23787
  const cwd2 = process.cwd();
23620
23788
  const add2 = import_react35.useCallback((l) => setLines((prev) => [...prev, l]), []);
23789
+ const flushAssistant = import_react35.useCallback(() => {
23790
+ const txt = streamBuf.current.trim();
23791
+ streamBuf.current = "";
23792
+ setStreaming("");
23793
+ if (txt)
23794
+ setLines((prev) => [...prev, { kind: "assistant", text: txt }]);
23795
+ }, []);
23621
23796
  const confirm = import_react35.useCallback((name, args) => {
23622
23797
  if (isAllowed(name, cwd2))
23623
23798
  return Promise.resolve(true);
@@ -23638,13 +23813,19 @@ function Root({ cfg, resume, mcpCount }) {
23638
23813
  onAssistantDelta: (txt) => {
23639
23814
  streamBuf.current += txt;
23640
23815
  setStreaming(streamBuf.current);
23816
+ tokensRef.current += Math.max(1, Math.round(txt.length / 4));
23817
+ setTurnTokens(tokensRef.current);
23641
23818
  },
23642
- onToolStart: (name, args) => add2({ kind: "tool", text: `${name} ${JSON.stringify(args).slice(0, 80)}` }),
23643
- onToolResult: (_n, r) => add2({ kind: "system", text: r.slice(0, 300) }),
23644
- onToolStream: (chunk2) => {
23645
- streamBuf.current += chunk2;
23646
- setStreaming(streamBuf.current);
23819
+ onToolStart: (name, args) => {
23820
+ flushAssistant();
23821
+ setToolStream("");
23822
+ add2({ kind: "tool", text: `${name} ${JSON.stringify(args).slice(0, 80)}` });
23647
23823
  },
23824
+ onToolResult: (_n, r) => {
23825
+ setToolStream("");
23826
+ add2({ kind: "system", text: r.slice(0, 300) });
23827
+ },
23828
+ onToolStream: (chunk2) => setToolStream((s) => (s + chunk2).slice(-2000)),
23648
23829
  onCost: (g) => setSessionMdl((s) => {
23649
23830
  const v = s + g / 1e8;
23650
23831
  sessionMdlRef.current = v;
@@ -23690,33 +23871,38 @@ function Root({ cfg, resume, mcpCount }) {
23690
23871
  add2({ kind: "system", text: "not logged in — run `modelcode login` (or option 1/2 at startup) to set an API key. /help works without one." });
23691
23872
  return;
23692
23873
  }
23693
- setBusy(true);
23694
23874
  streamBuf.current = "";
23695
23875
  setStreaming("");
23876
+ setToolStream("");
23877
+ tokensRef.current = 0;
23878
+ setTurnTokens(0);
23879
+ setTurnStart(Date.now());
23880
+ setBusy(true);
23696
23881
  try {
23697
23882
  await agent.send(expandFileMentions(prompt, cwd2), imageMentions(prompt, cwd2));
23698
- if (streamBuf.current)
23699
- add2({ kind: "assistant", text: streamBuf.current });
23883
+ flushAssistant();
23700
23884
  saveSession(sessionIdRef.current, cwd2, agent.messages);
23701
23885
  const heavyMin = Number(process.env.MODELCODE_AUTOSUMMARY_MIN || "8");
23702
23886
  if (cfg.autoSummary && agent.lastTurnToolCount >= heavyMin) {
23703
- streamBuf.current = "";
23887
+ tokensRef.current = 0;
23888
+ setTurnTokens(0);
23889
+ setTurnStart(Date.now());
23704
23890
  await agent.send("Recap concisely (bullets): what we worked on, key changes/decisions (files touched), current state, and any open/next steps.");
23705
- if (streamBuf.current)
23706
- add2({ kind: "system", text: `— recap —
23707
- ` + streamBuf.current });
23891
+ flushAssistant();
23708
23892
  }
23709
23893
  } catch (e) {
23894
+ flushAssistant();
23710
23895
  add2({ kind: "error", text: e.message });
23711
23896
  } finally {
23712
23897
  setStreaming("");
23898
+ setToolStream("");
23713
23899
  streamBuf.current = "";
23714
23900
  setBusy(false);
23715
23901
  setContextPct(agent.contextStatus().pct);
23716
23902
  setMode(agent.mode);
23717
23903
  refreshWallet();
23718
23904
  }
23719
- }, [agent, add2, refreshWallet, cfg, cwd2]);
23905
+ }, [agent, add2, refreshWallet, cfg, cwd2, flushAssistant]);
23720
23906
  const onInterrupt = import_react35.useCallback(() => {
23721
23907
  agent.abort();
23722
23908
  }, [agent]);
@@ -23765,6 +23951,9 @@ function Root({ cfg, resume, mcpCount }) {
23765
23951
  walletMdl,
23766
23952
  mode,
23767
23953
  busy,
23954
+ toolStream,
23955
+ turnStart,
23956
+ turnTokens,
23768
23957
  confirmReq,
23769
23958
  history,
23770
23959
  onConfirm,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modeloslab/modelcode",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "modelOS-native AI coding agent CLI — remembers like Hermes, codes like Claude Code, runs on modelOS (pay-per-use in MDL, no rate limits). Knowledge-graph memory, subagents, MCP, vision, spend caps.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,10 +25,6 @@
25
25
  "engines": {
26
26
  "node": ">=22.5.0"
27
27
  },
28
- "os": [
29
- "darwin",
30
- "linux"
31
- ],
32
28
  "publishConfig": {
33
29
  "access": "public"
34
30
  },