@modeloslab/modelcode 0.1.2 → 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
@@ -1356,10 +1356,15 @@ async function interactiveLogin(rl, cfg) {
1356
1356
  ` + ` ${C.cyan}3${C.reset}) skip for now
1357
1357
  `);
1358
1358
  const choice = (await rl.question(`${C.green}choose 1-3\u203A${C.reset} `)).trim();
1359
- if (choice === "1" || choice.startsWith("mdlk_")) {
1360
- const key = choice.startsWith("mdlk_") ? choice : (await rl.question("API key (mdlk_\u2026): ")).trim();
1361
- if (!key.startsWith("mdlk_")) {
1362
- 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}
1363
1368
  `);
1364
1369
  return false;
1365
1370
  }
@@ -1581,7 +1586,7 @@ async function main() {
1581
1586
  await interactiveLogin(rl, cfg);
1582
1587
  rl.close();
1583
1588
  }
1584
- const { runTui } = await import("./tui-2wxjcqb2.mjs");
1589
+ const { runTui } = await import("./tui-gh76vcf8.mjs");
1585
1590
  return runTui(cfg, resume);
1586
1591
  }
1587
1592
  if (cmd === "tui")
@@ -21480,6 +21480,92 @@ ${c.dim}Tips show on startup (one at a time). Toggle off with MODELCODE_TIPS=0.$
21480
21480
  `);
21481
21481
  }
21482
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
+
21483
21569
  // src/ui/themes.ts
21484
21570
  var THEMES = {
21485
21571
  amber: { accent: "yellow", user: "green", tool: "cyan", dim: "gray", ok: "green", warn: "yellow" },
@@ -21492,11 +21578,11 @@ function getTheme(name) {
21492
21578
 
21493
21579
  // src/ui/App.tsx
21494
21580
  var jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
21495
- var SPINNER = ["", "", "", "", "", "", "⠦", "⠧", "⠇", "⠏"];
21581
+ var SPINNER = ["", "", "", "", "", ""];
21496
21582
  function Spinner({ color }) {
21497
21583
  const [i, setI] = import_react34.useState(0);
21498
21584
  import_react34.useEffect(() => {
21499
- const t = setInterval(() => setI((n) => (n + 1) % SPINNER.length), 80);
21585
+ const t = setInterval(() => setI((n) => (n + 1) % SPINNER.length), 120);
21500
21586
  return () => clearInterval(t);
21501
21587
  }, []);
21502
21588
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
@@ -21504,6 +21590,46 @@ function Spinner({ color }) {
21504
21590
  children: SPINNER[i]
21505
21591
  }, undefined, false, undefined, this);
21506
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
+ }
21507
21633
  function Chip({ label, color }) {
21508
21634
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21509
21635
  backgroundColor: color,
@@ -21527,15 +21653,38 @@ function C_KEY(color, k) {
21527
21653
  ]
21528
21654
  }, undefined, true, undefined, this);
21529
21655
  }
21530
- 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 }) {
21531
21657
  const t = getTheme(theme);
21532
21658
  const [input, setInput] = import_react34.useState("");
21533
21659
  const [histIdx, setHistIdx] = import_react34.useState(-1);
21660
+ const [exitHint, setExitHint] = import_react34.useState(false);
21661
+ const exitTimer = import_react34.useRef(null);
21534
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
+ };
21535
21675
  use_input_default((ch, key) => {
21676
+ if (key.ctrl && ch === "d") {
21677
+ doExit();
21678
+ return;
21679
+ }
21536
21680
  if (key.ctrl && ch === "c") {
21537
- onExit();
21538
- exit();
21681
+ if (exitHint) {
21682
+ doExit();
21683
+ return;
21684
+ }
21685
+ if (busy)
21686
+ onInterrupt();
21687
+ armExit();
21539
21688
  return;
21540
21689
  }
21541
21690
  if (confirmReq) {
@@ -21711,22 +21860,21 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21711
21860
  }, undefined, true, undefined, this)
21712
21861
  ]
21713
21862
  }, undefined, true, undefined, this) : null,
21714
- busy && !streaming ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21715
- marginTop: 1,
21716
- children: [
21717
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Spinner, {
21718
- color: t.accent
21719
- }, undefined, false, undefined, this),
21720
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21721
- color: t.dim,
21722
- children: [
21723
- " thinking… ",
21724
- C_KEY(t.dim, "esc"),
21725
- " to interrupt"
21726
- ]
21727
- }, undefined, true, undefined, this)
21728
- ]
21729
- }, 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
21730
21878
  ]
21731
21879
  }, undefined, true, undefined, this),
21732
21880
  suggestions.length ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
@@ -21795,7 +21943,7 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21795
21943
  ]
21796
21944
  }, undefined, true, undefined, this),
21797
21945
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21798
- children: input
21946
+ children: input || (busy ? "" : "")
21799
21947
  }, undefined, false, undefined, this),
21800
21948
  !busy ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21801
21949
  color: t.accent,
@@ -21831,16 +21979,22 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21831
21979
  ctxBar,
21832
21980
  " ",
21833
21981
  contextPct,
21834
- "%"
21982
+ "%",
21983
+ turnTokens > 0 ? ` · ↓${turnTokens.toLocaleString()} tok` : ""
21835
21984
  ]
21836
21985
  }, undefined, true, undefined, this),
21837
- /* @__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, {
21838
21990
  color: t.dim,
21839
21991
  children: [
21840
- sessionMdl.toFixed(6),
21992
+ sessionMdl.toFixed(4),
21841
21993
  " MDL",
21842
21994
  usd,
21843
- " · /help · ^C"
21995
+ " · ",
21996
+ busy ? "esc=stop · " : "",
21997
+ "^C ^C=exit"
21844
21998
  ]
21845
21999
  }, undefined, true, undefined, this)
21846
22000
  ]
@@ -22584,13 +22738,15 @@ async function handleSlash(line, ctx) {
22584
22738
  }
22585
22739
  case "/model": {
22586
22740
  if (!arg)
22587
- 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)" : ""}` };
22588
22742
  const id = resolveModelName(arg);
22589
22743
  if (!id)
22590
22744
  return { output: `unknown model '${arg}' — /models to list` };
22745
+ const wasRouting = cfg.autoRoute;
22591
22746
  cfg.model = id;
22592
- saveConfig({ model: id });
22593
- 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 };
22594
22750
  }
22595
22751
  case "/submodel": {
22596
22752
  if (!arg) {
@@ -23606,7 +23762,10 @@ function imageMentions(line, cwd2) {
23606
23762
  // src/ui/tui.tsx
23607
23763
  var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
23608
23764
  function Root({ cfg, resume, mcpCount }) {
23609
- 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
+ ]);
23610
23769
  const [streaming, setStreaming] = import_react35.useState("");
23611
23770
  const [busy, setBusy] = import_react35.useState(false);
23612
23771
  const [sessionMdl, setSessionMdl] = import_react35.useState(0);
@@ -23615,14 +23774,25 @@ function Root({ cfg, resume, mcpCount }) {
23615
23774
  const [mode, setMode] = import_react35.useState("default");
23616
23775
  const [confirmReq, setConfirmReq] = import_react35.useState(null);
23617
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);
23618
23780
  const mdlUsd = import_react35.useRef(null);
23619
23781
  const streamBuf = import_react35.useRef("");
23782
+ const tokensRef = import_react35.useRef(0);
23620
23783
  const confirmResolve = import_react35.useRef(null);
23621
23784
  const sessionIdRef = import_react35.useRef(newSessionId());
23622
23785
  const sessionMdlRef = import_react35.useRef(0);
23623
23786
  const mcpCountRef = import_react35.useRef(mcpCount);
23624
23787
  const cwd2 = process.cwd();
23625
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
+ }, []);
23626
23796
  const confirm = import_react35.useCallback((name, args) => {
23627
23797
  if (isAllowed(name, cwd2))
23628
23798
  return Promise.resolve(true);
@@ -23643,13 +23813,19 @@ function Root({ cfg, resume, mcpCount }) {
23643
23813
  onAssistantDelta: (txt) => {
23644
23814
  streamBuf.current += txt;
23645
23815
  setStreaming(streamBuf.current);
23816
+ tokensRef.current += Math.max(1, Math.round(txt.length / 4));
23817
+ setTurnTokens(tokensRef.current);
23646
23818
  },
23647
- onToolStart: (name, args) => add2({ kind: "tool", text: `${name} ${JSON.stringify(args).slice(0, 80)}` }),
23648
- onToolResult: (_n, r) => add2({ kind: "system", text: r.slice(0, 300) }),
23649
- onToolStream: (chunk2) => {
23650
- streamBuf.current += chunk2;
23651
- setStreaming(streamBuf.current);
23819
+ onToolStart: (name, args) => {
23820
+ flushAssistant();
23821
+ setToolStream("");
23822
+ add2({ kind: "tool", text: `${name} ${JSON.stringify(args).slice(0, 80)}` });
23652
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)),
23653
23829
  onCost: (g) => setSessionMdl((s) => {
23654
23830
  const v = s + g / 1e8;
23655
23831
  sessionMdlRef.current = v;
@@ -23695,33 +23871,38 @@ function Root({ cfg, resume, mcpCount }) {
23695
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." });
23696
23872
  return;
23697
23873
  }
23698
- setBusy(true);
23699
23874
  streamBuf.current = "";
23700
23875
  setStreaming("");
23876
+ setToolStream("");
23877
+ tokensRef.current = 0;
23878
+ setTurnTokens(0);
23879
+ setTurnStart(Date.now());
23880
+ setBusy(true);
23701
23881
  try {
23702
23882
  await agent.send(expandFileMentions(prompt, cwd2), imageMentions(prompt, cwd2));
23703
- if (streamBuf.current)
23704
- add2({ kind: "assistant", text: streamBuf.current });
23883
+ flushAssistant();
23705
23884
  saveSession(sessionIdRef.current, cwd2, agent.messages);
23706
23885
  const heavyMin = Number(process.env.MODELCODE_AUTOSUMMARY_MIN || "8");
23707
23886
  if (cfg.autoSummary && agent.lastTurnToolCount >= heavyMin) {
23708
- streamBuf.current = "";
23887
+ tokensRef.current = 0;
23888
+ setTurnTokens(0);
23889
+ setTurnStart(Date.now());
23709
23890
  await agent.send("Recap concisely (bullets): what we worked on, key changes/decisions (files touched), current state, and any open/next steps.");
23710
- if (streamBuf.current)
23711
- add2({ kind: "system", text: `— recap —
23712
- ` + streamBuf.current });
23891
+ flushAssistant();
23713
23892
  }
23714
23893
  } catch (e) {
23894
+ flushAssistant();
23715
23895
  add2({ kind: "error", text: e.message });
23716
23896
  } finally {
23717
23897
  setStreaming("");
23898
+ setToolStream("");
23718
23899
  streamBuf.current = "";
23719
23900
  setBusy(false);
23720
23901
  setContextPct(agent.contextStatus().pct);
23721
23902
  setMode(agent.mode);
23722
23903
  refreshWallet();
23723
23904
  }
23724
- }, [agent, add2, refreshWallet, cfg, cwd2]);
23905
+ }, [agent, add2, refreshWallet, cfg, cwd2, flushAssistant]);
23725
23906
  const onInterrupt = import_react35.useCallback(() => {
23726
23907
  agent.abort();
23727
23908
  }, [agent]);
@@ -23770,6 +23951,9 @@ function Root({ cfg, resume, mcpCount }) {
23770
23951
  walletMdl,
23771
23952
  mode,
23772
23953
  busy,
23954
+ toolStream,
23955
+ turnStart,
23956
+ turnTokens,
23773
23957
  confirmReq,
23774
23958
  history,
23775
23959
  onConfirm,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modeloslab/modelcode",
3
- "version": "0.1.2",
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": {