@lazyingart/agintiflow 0.19.2 → 0.20.1

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.
@@ -34,6 +34,8 @@ Interactive commands:
34
34
  ```text
35
35
  /models
36
36
  /venice
37
+ /route
38
+ /model
37
39
  /route deepseek/deepseek-v4-flash
38
40
  /model deepseek/deepseek-v4-pro
39
41
  /spare openai/gpt-5.4 medium
@@ -41,6 +43,8 @@ Interactive commands:
41
43
  /auxiliary model grsai/nano-banana-2
42
44
  ```
43
45
 
46
+ In the interactive CLI, `/route` and `/model` without arguments open a selector. Use Up/Down/Left/Right to move through choices, Enter to confirm, and Esc to cancel. Slash-command hints use the same arrow selection behavior: type a prefix such as `/mo`, use arrows to choose `/model` or `/models`, then press Enter or Tab.
47
+
44
48
  `/venice` is a shortcut for:
45
49
 
46
50
  ```text
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.19.2",
3
+ "version": "0.20.1",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a web-first coding agent and CLI with DeepSeek routing, sandboxed tools, model providers, canvas artifacts, and optional wrappers.",
6
6
  "license": "Apache-2.0",
@@ -124,6 +124,17 @@ try {
124
124
  if (promptLayout.cursorRow < 0 || promptLayout.cursorColumn < 0) {
125
125
  throw new Error("terminal prompt layout returned an invalid cursor location");
126
126
  }
127
+ const paddedPromptLayout = buildPromptLayout("hello", 5, 80, 24, { commandCwd: "/tmp/aginti-project" });
128
+ const paddedRows = paddedPromptLayout.renderedRows.map((line) => line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, ""));
129
+ if (
130
+ paddedPromptLayout.cursorRow !== 1 ||
131
+ paddedRows[0].trim() !== "" ||
132
+ !paddedRows[1].includes("user> hello") ||
133
+ paddedRows[2].trim() !== "" ||
134
+ !paddedRows[3].includes("cwd /tmp/aginti-project")
135
+ ) {
136
+ throw new Error("terminal prompt layout did not render visual-only input padding safely");
137
+ }
127
138
  const hugePromptLayout = buildPromptLayout(Array.from({ length: 30 }, (_unused, index) => `line ${index + 1}`).join("\n"), 120, 80, 20);
128
139
  if (hugePromptLayout.renderedRows.length > 12 || !hugePromptLayout.renderedRows.some((line) => line.includes("earlier input row"))) {
129
140
  throw new Error("terminal prompt layout did not bound redraw size for large prompts");
@@ -137,9 +148,28 @@ try {
137
148
  const queuedText = queuedPromptLayout.renderedRows
138
149
  .map((line) => line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, ""))
139
150
  .join("\n");
140
- if (!queuedText.includes("run running · tool: apply_patch") || !queuedText.includes("→ apply this") || !queuedText.includes("↳ run this") || !queuedText.includes("cwd /tmp/aginti-project")) {
151
+ if (
152
+ !queuedText.includes("run running · tool: apply_patch") ||
153
+ !queuedText.includes("→ apply this") ||
154
+ !queuedText.includes("↳ run this") ||
155
+ !queuedText.includes("cwd /tmp/aginti-project")
156
+ ) {
141
157
  throw new Error("terminal prompt layout did not render live input queue and cwd footer");
142
158
  }
159
+ const hintPromptLayout = buildPromptLayout("/mo", 3, 90, 24, { suggestions: ["/models", "/model"], suggestionIndex: 1 });
160
+ const hintText = hintPromptLayout.renderedRows
161
+ .map((line) => line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, ""))
162
+ .join("\n");
163
+ if (!hintText.includes("user> /mo") || !hintText.includes("hint /models >/model")) {
164
+ throw new Error("terminal prompt layout did not align user and hint text columns");
165
+ }
166
+ const exactHintLayout = buildPromptLayout("/model", 6, 90, 24);
167
+ const exactHintText = exactHintLayout.renderedRows
168
+ .map((line) => line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, ""))
169
+ .join("\n");
170
+ if (!exactHintText.includes("hint >/model") || exactHintText.includes("/models")) {
171
+ throw new Error("exact slash commands should not show broader prefix matches");
172
+ }
143
173
  if (classifyEscapeAction({ active: false }) !== "noop") {
144
174
  throw new Error("idle Esc should not redraw or clear the prompt");
145
175
  }
@@ -171,9 +171,20 @@ function promptGutter() {
171
171
  function commandSuggestions(line = "") {
172
172
  const trimmed = String(line || "");
173
173
  if (!trimmed.startsWith("/") || /\s/.test(trimmed)) return [];
174
+ if (SLASH_COMMANDS.includes(trimmed)) return [trimmed];
174
175
  return SLASH_COMMANDS.filter((command) => command.startsWith(trimmed)).slice(0, 8);
175
176
  }
176
177
 
178
+ function renderSuggestionList(suggestions = [], selectedIndex = 0) {
179
+ return suggestions
180
+ .map((suggestion, index) => {
181
+ const active = index === selectedIndex;
182
+ if (!active) return suggestion;
183
+ return useColor ? color(suggestion, ansi.userBg, ansi.bold) : `>${suggestion}`;
184
+ })
185
+ .join(" ");
186
+ }
187
+
177
188
  function resolveSlashCommand(command = "") {
178
189
  const raw = String(command || "").trim();
179
190
  if (!raw) return raw;
@@ -574,9 +585,9 @@ function printHelp() {
574
585
  " /memory Alias for /instructions.",
575
586
  " /models Show route/main/spare/wrapper/auxiliary model roles.",
576
587
  " /venice Shortcut: use Venice Uncensored 1.2 for route and main roles.",
577
- " /route <mode|provider/model>",
578
- " Set routing mode or fast route model, e.g. /route deepseek/deepseek-v4-flash.",
579
- " /model <provider/model> Set the active/main model, e.g. /model deepseek/deepseek-v4-pro.",
588
+ " /route [mode|provider/model]",
589
+ " Open route selector, or set routing/fast route model.",
590
+ " /model [provider/model] Open main-model selector, or set the active/main model.",
580
591
  " /spare <provider/model> [reasoning]",
581
592
  " Set spare model, e.g. /spare openai/gpt-5.4 medium.",
582
593
  " /wrapper [on|off|codex model reasoning]",
@@ -730,14 +741,18 @@ export function buildPromptLayout(buffer = "", cursor = 0, width = terminalWidth
730
741
  }
731
742
  }
732
743
 
733
- const suggestions = commandSuggestions(safeBuffer.split("\n")[0] || "");
744
+ const suggestions = Array.isArray(options.suggestions)
745
+ ? options.suggestions
746
+ : commandSuggestions(safeBuffer.split("\n")[0] || "");
747
+ const suggestionIndex = clamp(Number(options.suggestionIndex) || 0, 0, Math.max(suggestions.length - 1, 0));
734
748
  const emptyHint = "type a request, /help, Enter to send, Ctrl+J for newline";
735
749
  const visible = promptVisibleWindow(rows, cursorRow, height);
736
750
  const renderedRows = [];
737
751
  let renderedCursorRow = cursorRow - visible.start;
752
+ const useInputPadding = options.inputPadding !== false && height >= 10;
738
753
 
739
754
  if (options.statusLine) {
740
- renderedRows.push(panelLine(` run ${compactLine(options.statusLine, lineWidth - 8)}`, ansi.statusBg, lineWidth));
755
+ renderedRows.push(panelLine(`${labelText("run", { prompt: true })}${compactLine(options.statusLine, lineWidth - 10)}`, ansi.statusBg, lineWidth));
741
756
  renderedCursorRow += 1;
742
757
  }
743
758
 
@@ -757,20 +772,29 @@ export function buildPromptLayout(buffer = "", cursor = 0, width = terminalWidth
757
772
  renderedCursorRow += 1;
758
773
  }
759
774
 
775
+ if (useInputPadding) {
776
+ renderedRows.push(panelLine("", ansi.userBg, lineWidth));
777
+ renderedCursorRow += 1;
778
+ }
779
+
760
780
  for (const row of rows.slice(visible.start, visible.end)) {
761
781
  const content = safeBuffer ? `${row.prefix}${row.text}` : `${row.prefix}${emptyHint}`;
762
782
  renderedRows.push(panelLine(content, ansi.userBg, lineWidth));
763
783
  }
764
784
 
785
+ if (useInputPadding) {
786
+ renderedRows.push(panelLine("", ansi.userBg, lineWidth));
787
+ }
788
+
765
789
  if (visible.bottomHidden > 0) {
766
790
  renderedRows.push(panelLine(` ... ${visible.bottomHidden} later input row${visible.bottomHidden === 1 ? "" : "s"}`, ansi.systemBg, lineWidth));
767
791
  }
768
792
 
769
793
  if (suggestions.length > 0) {
770
- renderedRows.push(panelLine(` hint ${suggestions.join(" ")}`, ansi.systemBg, lineWidth));
794
+ renderedRows.push(panelLine(`${labelText("hint", { prompt: true })}${renderSuggestionList(suggestions, suggestionIndex)}`, ansi.systemBg, lineWidth));
771
795
  }
772
796
  if (options.commandCwd) {
773
- renderedRows.push(panelLine(` cwd ${options.commandCwd}`, ansi.systemBg, lineWidth));
797
+ renderedRows.push(panelLine(`${labelText("cwd", { prompt: true })}${options.commandCwd}`, ansi.systemBg, lineWidth));
774
798
  }
775
799
 
776
800
  return {
@@ -884,6 +908,8 @@ function readTtyPrompt(options = {}) {
884
908
  let historyIndex = promptHistory.length;
885
909
  let draft = "";
886
910
  let redrawHandle = null;
911
+ let suggestionAnchor = "";
912
+ let suggestionIndex = 0;
887
913
 
888
914
  const cleanup = () => {
889
915
  if (redrawHandle) {
@@ -901,14 +927,19 @@ function readTtyPrompt(options = {}) {
901
927
  clearImmediate(redrawHandle);
902
928
  redrawHandle = null;
903
929
  }
904
- rendered = renderPromptBuffer(buffer, cursor, rendered, options);
930
+ const suggestions = commandSuggestions(suggestionAnchor || buffer.split("\n")[0] || "");
931
+ rendered = renderPromptBuffer(buffer, cursor, rendered, {
932
+ ...options,
933
+ suggestions,
934
+ suggestionIndex,
935
+ });
905
936
  };
906
937
 
907
938
  const redraw = () => {
908
939
  if (redrawHandle) return;
909
940
  redrawHandle = setImmediate(() => {
910
941
  redrawHandle = null;
911
- rendered = renderPromptBuffer(buffer, cursor, rendered, options);
942
+ renderNow();
912
943
  });
913
944
  };
914
945
 
@@ -922,13 +953,33 @@ function readTtyPrompt(options = {}) {
922
953
  resolve(buffer);
923
954
  };
924
955
 
925
- const setBuffer = (nextBuffer, nextCursor = nextBuffer.length) => {
956
+ const setBuffer = (nextBuffer, nextCursor = nextBuffer.length, { keepSuggestionAnchor = false } = {}) => {
926
957
  buffer = nextBuffer;
927
958
  cursor = clamp(nextCursor, 0, buffer.length);
928
959
  preferredColumn = null;
960
+ if (!keepSuggestionAnchor) {
961
+ suggestionAnchor = "";
962
+ suggestionIndex = 0;
963
+ }
929
964
  redraw();
930
965
  };
931
966
 
967
+ const cycleSuggestion = (delta) => {
968
+ const firstLine = buffer.split("\n")[0] || "";
969
+ if (!suggestionAnchor) suggestionAnchor = firstLine;
970
+ const suggestions = commandSuggestions(suggestionAnchor);
971
+ if (suggestions.length === 0) {
972
+ suggestionAnchor = "";
973
+ suggestionIndex = 0;
974
+ return false;
975
+ }
976
+ suggestionIndex = (suggestionIndex + delta + suggestions.length) % suggestions.length;
977
+ setBuffer(suggestions[suggestionIndex], suggestions[suggestionIndex].length, { keepSuggestionAnchor: true });
978
+ return true;
979
+ };
980
+
981
+ const suggestionModeActive = () => commandSuggestions(suggestionAnchor || buffer.split("\n")[0] || "").length > 1;
982
+
932
983
  const moveVertical = (direction) => {
933
984
  const layout = buildPromptLayout(buffer, cursor);
934
985
  const location = cursorLocation(layout, cursor);
@@ -979,32 +1030,40 @@ function readTtyPrompt(options = {}) {
979
1030
  if (key.name === "backspace") {
980
1031
  ({ buffer, cursor } = removeBefore(buffer, cursor));
981
1032
  preferredColumn = null;
1033
+ suggestionAnchor = "";
1034
+ suggestionIndex = 0;
982
1035
  redraw();
983
1036
  return;
984
1037
  }
985
1038
  if (key.name === "delete") {
986
1039
  ({ buffer, cursor } = removeAt(buffer, cursor));
987
1040
  preferredColumn = null;
1041
+ suggestionAnchor = "";
1042
+ suggestionIndex = 0;
988
1043
  redraw();
989
1044
  return;
990
1045
  }
991
1046
  if (key.name === "left") {
1047
+ if (suggestionModeActive() && cycleSuggestion(-1)) return;
992
1048
  cursor = Math.max(cursor - 1, 0);
993
1049
  preferredColumn = null;
994
1050
  redraw();
995
1051
  return;
996
1052
  }
997
1053
  if (key.name === "right") {
1054
+ if (suggestionModeActive() && cycleSuggestion(1)) return;
998
1055
  cursor = Math.min(cursor + 1, buffer.length);
999
1056
  preferredColumn = null;
1000
1057
  redraw();
1001
1058
  return;
1002
1059
  }
1003
1060
  if (key.name === "up") {
1061
+ if (suggestionModeActive() && cycleSuggestion(-1)) return;
1004
1062
  moveVertical(-1);
1005
1063
  return;
1006
1064
  }
1007
1065
  if (key.name === "down") {
1066
+ if (suggestionModeActive() && cycleSuggestion(1)) return;
1008
1067
  moveVertical(1);
1009
1068
  return;
1010
1069
  }
@@ -1036,10 +1095,12 @@ function readTtyPrompt(options = {}) {
1036
1095
  return;
1037
1096
  }
1038
1097
  if (key.name === "tab") {
1039
- const suggestions = commandSuggestions(buffer.split("\n")[0] || "");
1040
- if (suggestions.length === 1) {
1041
- buffer = suggestions[0];
1098
+ const suggestions = commandSuggestions(suggestionAnchor || buffer.split("\n")[0] || "");
1099
+ if (suggestions.length > 0) {
1100
+ buffer = suggestions[clamp(suggestionIndex, 0, suggestions.length - 1)];
1042
1101
  cursor = buffer.length;
1102
+ suggestionAnchor = "";
1103
+ suggestionIndex = 0;
1043
1104
  }
1044
1105
  preferredColumn = null;
1045
1106
  redraw();
@@ -1054,6 +1115,8 @@ function readTtyPrompt(options = {}) {
1054
1115
  const text = str.replace(/\r/g, "");
1055
1116
  ({ buffer, cursor } = insertAt(buffer, cursor, text));
1056
1117
  preferredColumn = null;
1118
+ suggestionAnchor = "";
1119
+ suggestionIndex = 0;
1057
1120
  redraw();
1058
1121
  }
1059
1122
  };
@@ -1601,6 +1664,193 @@ function printModelRoles(state) {
1601
1664
  );
1602
1665
  }
1603
1666
 
1667
+ function modelRoleChoices(role = "main") {
1668
+ const common = [
1669
+ {
1670
+ provider: "deepseek",
1671
+ model: "deepseek-v4-flash",
1672
+ label: "DeepSeek V4 Flash",
1673
+ description: "fast planner, short shell/browser/code work",
1674
+ route: true,
1675
+ },
1676
+ {
1677
+ provider: "deepseek",
1678
+ model: "deepseek-v4-pro",
1679
+ label: "DeepSeek V4 Pro",
1680
+ description: "complex coding, debugging, writing, and long tasks",
1681
+ main: true,
1682
+ },
1683
+ {
1684
+ provider: "venice",
1685
+ model: "venice-uncensored-1-2",
1686
+ label: "Venice Uncensored 1.2",
1687
+ description: "Venice text route; use /auth venice if missing",
1688
+ route: true,
1689
+ main: true,
1690
+ },
1691
+ {
1692
+ provider: "openai",
1693
+ model: "gpt-5.5",
1694
+ label: "OpenAI GPT-5.5",
1695
+ description: "frontier spare/manual model",
1696
+ main: true,
1697
+ },
1698
+ {
1699
+ provider: "openai",
1700
+ model: "gpt-5.4",
1701
+ label: "OpenAI GPT-5.4",
1702
+ description: "strong everyday coding spare",
1703
+ main: true,
1704
+ },
1705
+ {
1706
+ provider: "openai",
1707
+ model: "gpt-5.4-mini",
1708
+ label: "OpenAI GPT-5.4 Mini",
1709
+ description: "small fast spare route",
1710
+ route: true,
1711
+ },
1712
+ {
1713
+ provider: "qwen",
1714
+ model: "qwen-plus",
1715
+ label: "Qwen Plus",
1716
+ description: "general Qwen route",
1717
+ route: true,
1718
+ },
1719
+ {
1720
+ provider: "qwen",
1721
+ model: "qwen-max",
1722
+ label: "Qwen Max",
1723
+ description: "larger Qwen route",
1724
+ main: true,
1725
+ },
1726
+ {
1727
+ provider: "mock",
1728
+ model: "mock-agent",
1729
+ label: "Mock Agent",
1730
+ description: "local deterministic test route",
1731
+ route: true,
1732
+ main: true,
1733
+ },
1734
+ ];
1735
+ const filtered = common.filter((item) => (role === "route" ? item.route : item.main));
1736
+ return role === "route"
1737
+ ? filtered.sort((a, b) => Number(b.model === "deepseek-v4-flash") - Number(a.model === "deepseek-v4-flash"))
1738
+ : filtered.sort((a, b) => Number(b.model === "deepseek-v4-pro") - Number(a.model === "deepseek-v4-pro"));
1739
+ }
1740
+
1741
+ function modelChoiceLine(option) {
1742
+ return `${option.label} ${option.provider}/${option.model} ${option.description}`;
1743
+ }
1744
+
1745
+ function clearSelector(lineCount) {
1746
+ for (let index = 0; index < lineCount; index += 1) {
1747
+ output.write("\x1b[1A\r\x1b[2K");
1748
+ }
1749
+ }
1750
+
1751
+ function renderSelector({ title, subtitle, options, selectedIndex, lineCount = 0 }) {
1752
+ if (lineCount > 0) clearSelector(lineCount);
1753
+ const width = Math.min(Math.max(terminalWidth() - 2, 60), 110);
1754
+ const bodyWidth = width - 4;
1755
+ const rows = [
1756
+ `╭${"─".repeat(width - 2)}╮`,
1757
+ `│ ${padVisible(title, bodyWidth)} │`,
1758
+ `│ ${padVisible(subtitle, bodyWidth)} │`,
1759
+ `├${"─".repeat(width - 2)}┤`,
1760
+ ...options.map((option, index) => {
1761
+ const marker = index === selectedIndex ? ">" : " ";
1762
+ const rendered = `${marker} ${compactLine(modelChoiceLine(option), bodyWidth - 2)}`;
1763
+ return `│ ${padVisible(index === selectedIndex ? color(rendered, ansi.userBg, ansi.bold) : rendered, bodyWidth)} │`;
1764
+ }),
1765
+ `╰${"─".repeat(width - 2)}╯`,
1766
+ ];
1767
+ output.write(`${rows.join("\n")}\n`);
1768
+ return rows.length;
1769
+ }
1770
+
1771
+ function selectModelChoice({ title, subtitle, options, initialIndex = 0 }) {
1772
+ if (!input.isTTY || !output.isTTY || typeof input.setRawMode !== "function") return Promise.resolve(null);
1773
+ return new Promise((resolve) => {
1774
+ emitKeypressEvents(input);
1775
+ const wasRaw = Boolean(input.isRaw);
1776
+ let selectedIndex = clamp(initialIndex, 0, Math.max(options.length - 1, 0));
1777
+ let lineCount = 0;
1778
+ const cleanup = () => {
1779
+ input.off("keypress", handler);
1780
+ if (typeof input.setRawMode === "function") input.setRawMode(wasRaw);
1781
+ input.pause();
1782
+ output.write(ansi.cursorShow);
1783
+ };
1784
+ const redraw = () => {
1785
+ lineCount = renderSelector({ title, subtitle, options, selectedIndex, lineCount });
1786
+ };
1787
+ const finish = (value) => {
1788
+ if (lineCount > 0) clearSelector(lineCount);
1789
+ cleanup();
1790
+ resolve(value);
1791
+ };
1792
+ const handler = (_str = "", key = {}) => {
1793
+ if (key.ctrl && key.name === "c") {
1794
+ finish(null);
1795
+ return;
1796
+ }
1797
+ if (key.name === "escape") {
1798
+ finish(null);
1799
+ return;
1800
+ }
1801
+ if (key.name === "up" || key.name === "left") {
1802
+ selectedIndex = (selectedIndex - 1 + options.length) % options.length;
1803
+ redraw();
1804
+ return;
1805
+ }
1806
+ if (key.name === "down" || key.name === "right" || key.name === "tab") {
1807
+ selectedIndex = (selectedIndex + 1) % options.length;
1808
+ redraw();
1809
+ return;
1810
+ }
1811
+ if (key.name === "return" || key.name === "enter" || key.sequence === "\r") {
1812
+ finish(options[selectedIndex]);
1813
+ }
1814
+ };
1815
+ input.resume();
1816
+ input.setRawMode(true);
1817
+ input.on("keypress", handler);
1818
+ output.write(ansi.cursorHide);
1819
+ redraw();
1820
+ });
1821
+ }
1822
+
1823
+ async function pickModelRole(role, state) {
1824
+ const options = modelRoleChoices(role);
1825
+ const currentProvider = role === "route" ? state.routeProvider || "deepseek" : state.mainProvider || state.provider || "deepseek";
1826
+ const currentModel = role === "route" ? state.routeModel || "deepseek-v4-flash" : state.mainModel || "deepseek-v4-pro";
1827
+ const initialIndex = Math.max(
1828
+ options.findIndex((item) => item.provider === currentProvider && item.model === currentModel),
1829
+ 0
1830
+ );
1831
+ const selected = await selectModelChoice({
1832
+ title: role === "route" ? "Select route model" : "Select main model",
1833
+ subtitle: "Up/Down/Left/Right selects, Enter confirms, Esc cancels.",
1834
+ options,
1835
+ initialIndex,
1836
+ });
1837
+ if (!selected) return false;
1838
+
1839
+ state.routingMode = "smart";
1840
+ state.provider = "deepseek";
1841
+ state.model = "";
1842
+ if (role === "route") {
1843
+ state.routeProvider = selected.provider;
1844
+ state.routeModel = selected.model;
1845
+ printSystemLine(`route=${state.routeProvider}/${state.routeModel}`);
1846
+ } else {
1847
+ state.mainProvider = selected.provider;
1848
+ state.mainModel = selected.model;
1849
+ printSystemLine(`main=${state.mainProvider}/${state.mainModel}`);
1850
+ }
1851
+ return true;
1852
+ }
1853
+
1604
1854
  async function maybeOnboardDeepSeekKey(state) {
1605
1855
  if (!shouldPromptForDeepSeek(state, process.cwd())) return;
1606
1856
 
@@ -1843,6 +2093,11 @@ async function handleCommand(line, state, packageDir) {
1843
2093
  }
1844
2094
  if (command === "route") {
1845
2095
  if (!value) {
2096
+ if (input.isTTY && output.isTTY && typeof input.setRawMode === "function") {
2097
+ const changed = await pickModelRole("route", state);
2098
+ if (!changed) printSystemLine(`route=${state.routeProvider || "deepseek"}/${state.routeModel || "deepseek-v4-flash"}`);
2099
+ return true;
2100
+ }
1846
2101
  printAgentMessage(
1847
2102
  `Route model: ${state.routeProvider || "deepseek"}/${state.routeModel || "deepseek-v4-flash"}\nUse /route deepseek/deepseek-v4-flash or /route fast|smart|complex.`
1848
2103
  );
@@ -1860,7 +2115,18 @@ async function handleCommand(line, state, packageDir) {
1860
2115
  return true;
1861
2116
  }
1862
2117
  if (command === "main" || command === "model") {
1863
- if (!value || value === "auto") {
2118
+ if (!value) {
2119
+ if (input.isTTY && output.isTTY && typeof input.setRawMode === "function") {
2120
+ const changed = await pickModelRole("main", state);
2121
+ if (!changed) printSystemLine(`main=${state.mainProvider || state.provider || "deepseek"}/${state.mainModel || state.model || "deepseek-v4-pro"}`);
2122
+ return true;
2123
+ }
2124
+ printAgentMessage(
2125
+ `Main model: ${state.mainProvider || state.provider || "deepseek"}/${state.mainModel || state.model || "deepseek-v4-pro"}\nUse /model deepseek/deepseek-v4-pro or /model auto.`
2126
+ );
2127
+ return true;
2128
+ }
2129
+ if (value === "auto") {
1864
2130
  if (command === "main") {
1865
2131
  state.mainProvider = "";
1866
2132
  state.mainModel = "";