codemaxxing 0.4.11 → 0.4.13

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/index.js CHANGED
@@ -152,6 +152,10 @@ function App() {
152
152
  const [ollamaDeleteConfirm, setOllamaDeleteConfirm] = useState(null);
153
153
  const [ollamaPulling, setOllamaPulling] = useState(null);
154
154
  const [ollamaExitPrompt, setOllamaExitPrompt] = useState(false);
155
+ const [ollamaDeletePicker, setOllamaDeletePicker] = useState(null);
156
+ const [ollamaDeletePickerIndex, setOllamaDeletePickerIndex] = useState(0);
157
+ const [ollamaPullPicker, setOllamaPullPicker] = useState(false);
158
+ const [ollamaPullPickerIndex, setOllamaPullPickerIndex] = useState(0);
155
159
  const [wizardScreen, setWizardScreen] = useState(null);
156
160
  const [wizardIndex, setWizardIndex] = useState(0);
157
161
  const [wizardHardware, setWizardHardware] = useState(null);
@@ -341,8 +345,7 @@ function App() {
341
345
  // Commands that need args (like /commit, /model) — fill input instead of executing
342
346
  if (selected.cmd === "/commit" || selected.cmd === "/model" || selected.cmd === "/session delete" ||
343
347
  selected.cmd === "/skills install" || selected.cmd === "/skills remove" || selected.cmd === "/skills search" ||
344
- selected.cmd === "/skills on" || selected.cmd === "/skills off" || selected.cmd === "/architect" ||
345
- selected.cmd === "/ollama pull" || selected.cmd === "/ollama delete") {
348
+ selected.cmd === "/skills on" || selected.cmd === "/skills off" || selected.cmd === "/architect") {
346
349
  setInput(selected.cmd + " ");
347
350
  setCmdIndex(0);
348
351
  setInputKey((k) => k + 1);
@@ -678,10 +681,17 @@ function App() {
678
681
  addMsg(result.ok ? "info" : "error", result.ok ? `\u2705 ${result.message}` : `\u274C ${result.message}`);
679
682
  return;
680
683
  }
684
+ if (trimmed === "/ollama pull") {
685
+ // No model specified — show picker
686
+ setOllamaPullPicker(true);
687
+ setOllamaPullPickerIndex(0);
688
+ return;
689
+ }
681
690
  if (trimmed.startsWith("/ollama pull ")) {
682
691
  const modelId = trimmed.replace("/ollama pull ", "").trim();
683
692
  if (!modelId) {
684
- addMsg("info", "Usage: /ollama pull <model>\n Example: /ollama pull qwen2.5-coder:7b");
693
+ setOllamaPullPicker(true);
694
+ setOllamaPullPickerIndex(0);
685
695
  return;
686
696
  }
687
697
  if (!isOllamaInstalled()) {
@@ -719,10 +729,27 @@ function App() {
719
729
  }
720
730
  return;
721
731
  }
732
+ if (trimmed === "/ollama delete") {
733
+ // No model specified — show picker of installed models
734
+ const models = await listInstalledModelsDetailed();
735
+ if (models.length === 0) {
736
+ addMsg("info", "No models installed.");
737
+ return;
738
+ }
739
+ setOllamaDeletePicker({ models: models.map(m => ({ name: m.name, size: m.size })) });
740
+ setOllamaDeletePickerIndex(0);
741
+ return;
742
+ }
722
743
  if (trimmed.startsWith("/ollama delete ")) {
723
744
  const modelId = trimmed.replace("/ollama delete ", "").trim();
724
745
  if (!modelId) {
725
- addMsg("info", "Usage: /ollama delete <model>");
746
+ const models = await listInstalledModelsDetailed();
747
+ if (models.length === 0) {
748
+ addMsg("info", "No models installed.");
749
+ return;
750
+ }
751
+ setOllamaDeletePicker({ models: models.map(m => ({ name: m.name, size: m.size })) });
752
+ setOllamaDeletePickerIndex(0);
726
753
  return;
727
754
  }
728
755
  // Look up size for confirmation
@@ -1295,6 +1322,71 @@ function App() {
1295
1322
  }
1296
1323
  return;
1297
1324
  }
1325
+ // ── Ollama delete picker ──
1326
+ if (ollamaDeletePicker) {
1327
+ if (key.upArrow) {
1328
+ setOllamaDeletePickerIndex((prev) => (prev - 1 + ollamaDeletePicker.models.length) % ollamaDeletePicker.models.length);
1329
+ return;
1330
+ }
1331
+ if (key.downArrow) {
1332
+ setOllamaDeletePickerIndex((prev) => (prev + 1) % ollamaDeletePicker.models.length);
1333
+ return;
1334
+ }
1335
+ if (key.escape) {
1336
+ setOllamaDeletePicker(null);
1337
+ return;
1338
+ }
1339
+ if (key.return) {
1340
+ const selected = ollamaDeletePicker.models[ollamaDeletePickerIndex];
1341
+ if (selected) {
1342
+ setOllamaDeletePicker(null);
1343
+ setOllamaDeleteConfirm({ model: selected.name, size: selected.size });
1344
+ }
1345
+ return;
1346
+ }
1347
+ return;
1348
+ }
1349
+ // ── Ollama pull picker ──
1350
+ if (ollamaPullPicker) {
1351
+ const pullModels = [
1352
+ { id: "qwen2.5-coder:7b", name: "Qwen 2.5 Coder 7B", size: "5 GB", desc: "Best balance of speed & quality" },
1353
+ { id: "qwen2.5-coder:14b", name: "Qwen 2.5 Coder 14B", size: "9 GB", desc: "Higher quality, needs 16GB+ RAM" },
1354
+ { id: "qwen2.5-coder:3b", name: "Qwen 2.5 Coder 3B", size: "2 GB", desc: "Fast but limited quality" },
1355
+ { id: "qwen2.5-coder:32b", name: "Qwen 2.5 Coder 32B", size: "20 GB", desc: "Premium quality, needs 48GB+" },
1356
+ { id: "deepseek-coder-v2:16b", name: "DeepSeek Coder V2", size: "9 GB", desc: "Strong alternative" },
1357
+ { id: "codellama:7b", name: "CodeLlama 7B", size: "4 GB", desc: "Meta's coding model" },
1358
+ { id: "starcoder2:7b", name: "StarCoder2 7B", size: "4 GB", desc: "Code completion focused" },
1359
+ ];
1360
+ if (key.upArrow) {
1361
+ setOllamaPullPickerIndex((prev) => (prev - 1 + pullModels.length) % pullModels.length);
1362
+ return;
1363
+ }
1364
+ if (key.downArrow) {
1365
+ setOllamaPullPickerIndex((prev) => (prev + 1) % pullModels.length);
1366
+ return;
1367
+ }
1368
+ if (key.escape) {
1369
+ setOllamaPullPicker(false);
1370
+ return;
1371
+ }
1372
+ if (key.return) {
1373
+ const selected = pullModels[ollamaPullPickerIndex];
1374
+ if (selected) {
1375
+ setOllamaPullPicker(false);
1376
+ // Trigger the pull
1377
+ setInput(`/ollama pull ${selected.id}`);
1378
+ setInputKey((k) => k + 1);
1379
+ // Submit it
1380
+ setTimeout(() => {
1381
+ const submitInput = `/ollama pull ${selected.id}`;
1382
+ setInput("");
1383
+ handleSubmit(submitInput);
1384
+ }, 50);
1385
+ }
1386
+ return;
1387
+ }
1388
+ return;
1389
+ }
1298
1390
  // ── Ollama delete confirmation ──
1299
1391
  if (ollamaDeleteConfirm) {
1300
1392
  if (inputChar === "y" || inputChar === "Y") {
@@ -1820,7 +1912,15 @@ function App() {
1820
1912
  })(), skillsPicker === "remove" && (() => {
1821
1913
  const installed = listInstalledSkills();
1822
1914
  return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.error, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.error, children: "Remove a skill:" }), installed.map((s, i) => (_jsxs(Text, { children: [i === skillsPickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsxs(Text, { color: i === skillsPickerIndex ? theme.colors.suggestion : theme.colors.muted, children: [s.name, " \u2014 ", s.description] })] }, s.name))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter remove · Esc back" })] }));
1823
- })(), themePicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Choose a theme:" }), listThemes().map((key, i) => (_jsxs(Text, { children: [i === themePickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === themePickerIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: key }), _jsxs(Text, { color: theme.colors.muted, children: [" — ", THEMES[key].description] }), key === theme.name.toLowerCase() ? _jsx(Text, { color: theme.colors.muted, children: " (current)" }) : null] }, key))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), sessionPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.secondary, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Resume a session:" }), sessionPicker.map((s, i) => (_jsxs(Text, { children: [i === sessionPickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === sessionPickerIndex ? theme.colors.suggestion : theme.colors.muted, children: s.display })] }, s.id))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), deleteSessionPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.error, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.error, children: "Delete a session:" }), deleteSessionPicker.map((s, i) => (_jsxs(Text, { children: [i === deleteSessionPickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === deleteSessionPickerIndex ? theme.colors.suggestion : theme.colors.muted, children: s.display })] }, s.id))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), deleteSessionConfirm && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.warning, paddingX: 1, marginBottom: 0, children: [_jsxs(Text, { bold: true, color: theme.colors.warning, children: ["Delete session ", deleteSessionConfirm.id, "?"] }), _jsxs(Text, { color: theme.colors.muted, children: [" ", deleteSessionConfirm.display] }), _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.error, bold: true, children: " [y]" }), _jsx(Text, { children: "es " }), _jsx(Text, { color: theme.colors.success, bold: true, children: "[n]" }), _jsx(Text, { children: "o" })] })] })), ollamaDeleteConfirm && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.warning, paddingX: 1, marginBottom: 0, children: [_jsxs(Text, { bold: true, color: theme.colors.warning, children: ["Delete ", ollamaDeleteConfirm.model, " (", (ollamaDeleteConfirm.size / (1024 * 1024 * 1024)).toFixed(1), " GB)?"] }), _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.error, bold: true, children: " [y]" }), _jsx(Text, { children: "es " }), _jsx(Text, { color: theme.colors.success, bold: true, children: "[n]" }), _jsx(Text, { children: "o" })] })] })), ollamaPulling && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsxs(Text, { bold: true, color: theme.colors.secondary, children: [" Downloading ", ollamaPulling.model, "..."] }), ollamaPulling.progress.status === "downloading" || ollamaPulling.progress.percent > 0 ? (_jsxs(Text, { children: [" ", _jsxs(Text, { color: theme.colors.primary, children: ["\u2588".repeat(Math.floor(ollamaPulling.progress.percent / 5)), "\u2591".repeat(20 - Math.floor(ollamaPulling.progress.percent / 5))] }), " ", _jsxs(Text, { bold: true, children: [ollamaPulling.progress.percent, "%"] }), ollamaPulling.progress.completed != null && ollamaPulling.progress.total != null ? (_jsxs(Text, { color: theme.colors.muted, children: [" \u00B7 ", formatBytes(ollamaPulling.progress.completed), " / ", formatBytes(ollamaPulling.progress.total)] })) : null] })) : (_jsxs(Text, { color: theme.colors.muted, children: [" ", ollamaPulling.progress.status, "..."] }))] })), ollamaExitPrompt && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.warning, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.warning, children: "Ollama is still running." }), _jsx(Text, { color: theme.colors.muted, children: "Stop it to free GPU memory?" }), _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.success, bold: true, children: " [y]" }), _jsx(Text, { children: "es " }), _jsx(Text, { color: theme.colors.error, bold: true, children: "[n]" }), _jsx(Text, { children: "o " }), _jsx(Text, { color: theme.colors.primary, bold: true, children: "[a]" }), _jsx(Text, { children: "lways" })] })] })), wizardScreen === "connection" && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "No LLM detected. How do you want to connect?" }), _jsx(Text, { children: "" }), [
1915
+ })(), themePicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Choose a theme:" }), listThemes().map((key, i) => (_jsxs(Text, { children: [i === themePickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === themePickerIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: key }), _jsxs(Text, { color: theme.colors.muted, children: [" — ", THEMES[key].description] }), key === theme.name.toLowerCase() ? _jsx(Text, { color: theme.colors.muted, children: " (current)" }) : null] }, key))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), sessionPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.secondary, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Resume a session:" }), sessionPicker.map((s, i) => (_jsxs(Text, { children: [i === sessionPickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === sessionPickerIndex ? theme.colors.suggestion : theme.colors.muted, children: s.display })] }, s.id))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), deleteSessionPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.error, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.error, children: "Delete a session:" }), deleteSessionPicker.map((s, i) => (_jsxs(Text, { children: [i === deleteSessionPickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === deleteSessionPickerIndex ? theme.colors.suggestion : theme.colors.muted, children: s.display })] }, s.id))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), deleteSessionConfirm && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.warning, paddingX: 1, marginBottom: 0, children: [_jsxs(Text, { bold: true, color: theme.colors.warning, children: ["Delete session ", deleteSessionConfirm.id, "?"] }), _jsxs(Text, { color: theme.colors.muted, children: [" ", deleteSessionConfirm.display] }), _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.error, bold: true, children: " [y]" }), _jsx(Text, { children: "es " }), _jsx(Text, { color: theme.colors.success, bold: true, children: "[n]" }), _jsx(Text, { children: "o" })] })] })), ollamaDeletePicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Delete which model?" }), _jsx(Text, { children: "" }), ollamaDeletePicker.models.map((m, i) => (_jsxs(Text, { children: [" ", i === ollamaDeletePickerIndex ? _jsx(Text, { color: theme.colors.primary, bold: true, children: " " }) : " ", _jsx(Text, { color: i === ollamaDeletePickerIndex ? theme.colors.primary : undefined, children: m.name }), _jsxs(Text, { color: theme.colors.muted, children: [" (", (m.size / (1024 * 1024 * 1024)).toFixed(1), " GB)"] })] }, m.name))), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter to delete · Esc cancel" })] })), ollamaPullPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Download which model?" }), _jsx(Text, { children: "" }), [
1916
+ { id: "qwen2.5-coder:7b", name: "Qwen 2.5 Coder 7B", size: "5 GB", desc: "Best balance of speed & quality" },
1917
+ { id: "qwen2.5-coder:14b", name: "Qwen 2.5 Coder 14B", size: "9 GB", desc: "Higher quality, needs 16GB+ RAM" },
1918
+ { id: "qwen2.5-coder:3b", name: "Qwen 2.5 Coder 3B", size: "2 GB", desc: "Fast but limited quality" },
1919
+ { id: "qwen2.5-coder:32b", name: "Qwen 2.5 Coder 32B", size: "20 GB", desc: "Premium, needs 48GB+" },
1920
+ { id: "deepseek-coder-v2:16b", name: "DeepSeek Coder V2", size: "9 GB", desc: "Strong alternative" },
1921
+ { id: "codellama:7b", name: "CodeLlama 7B", size: "4 GB", desc: "Meta's coding model" },
1922
+ { id: "starcoder2:7b", name: "StarCoder2 7B", size: "4 GB", desc: "Code completion focused" },
1923
+ ].map((m, i) => (_jsxs(Text, { children: [" ", i === ollamaPullPickerIndex ? _jsx(Text, { color: theme.colors.primary, bold: true, children: "▸ " }) : " ", _jsx(Text, { color: i === ollamaPullPickerIndex ? theme.colors.primary : undefined, bold: true, children: m.name }), _jsxs(Text, { color: theme.colors.muted, children: [" · ", m.size, " · ", m.desc] })] }, m.id))), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter to download · Esc cancel" })] })), ollamaDeleteConfirm && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.warning, paddingX: 1, marginBottom: 0, children: [_jsxs(Text, { bold: true, color: theme.colors.warning, children: ["Delete ", ollamaDeleteConfirm.model, " (", (ollamaDeleteConfirm.size / (1024 * 1024 * 1024)).toFixed(1), " GB)?"] }), _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.error, bold: true, children: " [y]" }), _jsx(Text, { children: "es " }), _jsx(Text, { color: theme.colors.success, bold: true, children: "[n]" }), _jsx(Text, { children: "o" })] })] })), ollamaPulling && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsxs(Text, { bold: true, color: theme.colors.secondary, children: [" Downloading ", ollamaPulling.model, "..."] }), ollamaPulling.progress.status === "downloading" || ollamaPulling.progress.percent > 0 ? (_jsxs(Text, { children: [" ", _jsxs(Text, { color: theme.colors.primary, children: ["\u2588".repeat(Math.floor(ollamaPulling.progress.percent / 5)), "\u2591".repeat(20 - Math.floor(ollamaPulling.progress.percent / 5))] }), " ", _jsxs(Text, { bold: true, children: [ollamaPulling.progress.percent, "%"] }), ollamaPulling.progress.completed != null && ollamaPulling.progress.total != null ? (_jsxs(Text, { color: theme.colors.muted, children: [" \u00B7 ", formatBytes(ollamaPulling.progress.completed), " / ", formatBytes(ollamaPulling.progress.total)] })) : null] })) : (_jsxs(Text, { color: theme.colors.muted, children: [" ", ollamaPulling.progress.status, "..."] }))] })), ollamaExitPrompt && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.warning, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.warning, children: "Ollama is still running." }), _jsx(Text, { color: theme.colors.muted, children: "Stop it to free GPU memory?" }), _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.success, bold: true, children: " [y]" }), _jsx(Text, { children: "es " }), _jsx(Text, { color: theme.colors.error, bold: true, children: "[n]" }), _jsx(Text, { children: "o " }), _jsx(Text, { color: theme.colors.primary, bold: true, children: "[a]" }), _jsx(Text, { children: "lways" })] })] })), wizardScreen === "connection" && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "No LLM detected. How do you want to connect?" }), _jsx(Text, { children: "" }), [
1824
1924
  { key: "local", icon: "\uD83D\uDDA5\uFE0F", label: "Set up a local model", desc: "free, runs on your machine" },
1825
1925
  { key: "openrouter", icon: "\uD83C\uDF10", label: "OpenRouter", desc: "200+ cloud models, browser login" },
1826
1926
  { key: "apikey", icon: "\uD83D\uDD11", label: "Enter API key manually", desc: "" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemaxxing",
3
- "version": "0.4.11",
3
+ "version": "0.4.13",
4
4
  "description": "Open-source terminal coding agent. Connect any LLM. Max your code.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/index.tsx CHANGED
@@ -187,6 +187,10 @@ function App() {
187
187
  const [ollamaDeleteConfirm, setOllamaDeleteConfirm] = useState<{ model: string; size: number } | null>(null);
188
188
  const [ollamaPulling, setOllamaPulling] = useState<{ model: string; progress: PullProgress } | null>(null);
189
189
  const [ollamaExitPrompt, setOllamaExitPrompt] = useState(false);
190
+ const [ollamaDeletePicker, setOllamaDeletePicker] = useState<{ models: { name: string; size: number }[] } | null>(null);
191
+ const [ollamaDeletePickerIndex, setOllamaDeletePickerIndex] = useState(0);
192
+ const [ollamaPullPicker, setOllamaPullPicker] = useState(false);
193
+ const [ollamaPullPickerIndex, setOllamaPullPickerIndex] = useState(0);
190
194
 
191
195
  // ── Setup Wizard State ──
192
196
  type WizardScreen = "connection" | "models" | "install-ollama" | "pulling" | null;
@@ -396,8 +400,7 @@ function App() {
396
400
  // Commands that need args (like /commit, /model) — fill input instead of executing
397
401
  if (selected.cmd === "/commit" || selected.cmd === "/model" || selected.cmd === "/session delete" ||
398
402
  selected.cmd === "/skills install" || selected.cmd === "/skills remove" || selected.cmd === "/skills search" ||
399
- selected.cmd === "/skills on" || selected.cmd === "/skills off" || selected.cmd === "/architect" ||
400
- selected.cmd === "/ollama pull" || selected.cmd === "/ollama delete") {
403
+ selected.cmd === "/skills on" || selected.cmd === "/skills off" || selected.cmd === "/architect") {
401
404
  setInput(selected.cmd + " ");
402
405
  setCmdIndex(0);
403
406
  setInputKey((k) => k + 1);
@@ -722,10 +725,17 @@ function App() {
722
725
  addMsg(result.ok ? "info" : "error", result.ok ? `\u2705 ${result.message}` : `\u274C ${result.message}`);
723
726
  return;
724
727
  }
728
+ if (trimmed === "/ollama pull") {
729
+ // No model specified — show picker
730
+ setOllamaPullPicker(true);
731
+ setOllamaPullPickerIndex(0);
732
+ return;
733
+ }
725
734
  if (trimmed.startsWith("/ollama pull ")) {
726
735
  const modelId = trimmed.replace("/ollama pull ", "").trim();
727
736
  if (!modelId) {
728
- addMsg("info", "Usage: /ollama pull <model>\n Example: /ollama pull qwen2.5-coder:7b");
737
+ setOllamaPullPicker(true);
738
+ setOllamaPullPickerIndex(0);
729
739
  return;
730
740
  }
731
741
  if (!isOllamaInstalled()) {
@@ -759,10 +769,27 @@ function App() {
759
769
  }
760
770
  return;
761
771
  }
772
+ if (trimmed === "/ollama delete") {
773
+ // No model specified — show picker of installed models
774
+ const models = await listInstalledModelsDetailed();
775
+ if (models.length === 0) {
776
+ addMsg("info", "No models installed.");
777
+ return;
778
+ }
779
+ setOllamaDeletePicker({ models: models.map(m => ({ name: m.name, size: m.size })) });
780
+ setOllamaDeletePickerIndex(0);
781
+ return;
782
+ }
762
783
  if (trimmed.startsWith("/ollama delete ")) {
763
784
  const modelId = trimmed.replace("/ollama delete ", "").trim();
764
785
  if (!modelId) {
765
- addMsg("info", "Usage: /ollama delete <model>");
786
+ const models = await listInstalledModelsDetailed();
787
+ if (models.length === 0) {
788
+ addMsg("info", "No models installed.");
789
+ return;
790
+ }
791
+ setOllamaDeletePicker({ models: models.map(m => ({ name: m.name, size: m.size })) });
792
+ setOllamaDeletePickerIndex(0);
766
793
  return;
767
794
  }
768
795
  // Look up size for confirmation
@@ -1317,6 +1344,73 @@ function App() {
1317
1344
  return;
1318
1345
  }
1319
1346
 
1347
+ // ── Ollama delete picker ──
1348
+ if (ollamaDeletePicker) {
1349
+ if (key.upArrow) {
1350
+ setOllamaDeletePickerIndex((prev) => (prev - 1 + ollamaDeletePicker.models.length) % ollamaDeletePicker.models.length);
1351
+ return;
1352
+ }
1353
+ if (key.downArrow) {
1354
+ setOllamaDeletePickerIndex((prev) => (prev + 1) % ollamaDeletePicker.models.length);
1355
+ return;
1356
+ }
1357
+ if (key.escape) {
1358
+ setOllamaDeletePicker(null);
1359
+ return;
1360
+ }
1361
+ if (key.return) {
1362
+ const selected = ollamaDeletePicker.models[ollamaDeletePickerIndex];
1363
+ if (selected) {
1364
+ setOllamaDeletePicker(null);
1365
+ setOllamaDeleteConfirm({ model: selected.name, size: selected.size });
1366
+ }
1367
+ return;
1368
+ }
1369
+ return;
1370
+ }
1371
+
1372
+ // ── Ollama pull picker ──
1373
+ if (ollamaPullPicker) {
1374
+ const pullModels = [
1375
+ { id: "qwen2.5-coder:7b", name: "Qwen 2.5 Coder 7B", size: "5 GB", desc: "Best balance of speed & quality" },
1376
+ { id: "qwen2.5-coder:14b", name: "Qwen 2.5 Coder 14B", size: "9 GB", desc: "Higher quality, needs 16GB+ RAM" },
1377
+ { id: "qwen2.5-coder:3b", name: "Qwen 2.5 Coder 3B", size: "2 GB", desc: "Fast but limited quality" },
1378
+ { id: "qwen2.5-coder:32b", name: "Qwen 2.5 Coder 32B", size: "20 GB", desc: "Premium quality, needs 48GB+" },
1379
+ { id: "deepseek-coder-v2:16b", name: "DeepSeek Coder V2", size: "9 GB", desc: "Strong alternative" },
1380
+ { id: "codellama:7b", name: "CodeLlama 7B", size: "4 GB", desc: "Meta's coding model" },
1381
+ { id: "starcoder2:7b", name: "StarCoder2 7B", size: "4 GB", desc: "Code completion focused" },
1382
+ ];
1383
+ if (key.upArrow) {
1384
+ setOllamaPullPickerIndex((prev) => (prev - 1 + pullModels.length) % pullModels.length);
1385
+ return;
1386
+ }
1387
+ if (key.downArrow) {
1388
+ setOllamaPullPickerIndex((prev) => (prev + 1) % pullModels.length);
1389
+ return;
1390
+ }
1391
+ if (key.escape) {
1392
+ setOllamaPullPicker(false);
1393
+ return;
1394
+ }
1395
+ if (key.return) {
1396
+ const selected = pullModels[ollamaPullPickerIndex];
1397
+ if (selected) {
1398
+ setOllamaPullPicker(false);
1399
+ // Trigger the pull
1400
+ setInput(`/ollama pull ${selected.id}`);
1401
+ setInputKey((k) => k + 1);
1402
+ // Submit it
1403
+ setTimeout(() => {
1404
+ const submitInput = `/ollama pull ${selected.id}`;
1405
+ setInput("");
1406
+ handleSubmit(submitInput);
1407
+ }, 50);
1408
+ }
1409
+ return;
1410
+ }
1411
+ return;
1412
+ }
1413
+
1320
1414
  // ── Ollama delete confirmation ──
1321
1415
  if (ollamaDeleteConfirm) {
1322
1416
  if (inputChar === "y" || inputChar === "Y") {
@@ -2072,6 +2166,48 @@ function App() {
2072
2166
  </Box>
2073
2167
  )}
2074
2168
 
2169
+ {/* ═══ OLLAMA DELETE PICKER ═══ */}
2170
+ {ollamaDeletePicker && (
2171
+ <Box flexDirection="column" borderStyle="single" borderColor={theme.colors.border} paddingX={1} marginBottom={0}>
2172
+ <Text bold color={theme.colors.secondary}>Delete which model?</Text>
2173
+ <Text>{""}</Text>
2174
+ {ollamaDeletePicker.models.map((m, i) => (
2175
+ <Text key={m.name}>
2176
+ {" "}{i === ollamaDeletePickerIndex ? <Text color={theme.colors.primary} bold>{"▸ "}</Text> : " "}
2177
+ <Text color={i === ollamaDeletePickerIndex ? theme.colors.primary : undefined}>{m.name}</Text>
2178
+ <Text color={theme.colors.muted}>{" ("}{(m.size / (1024 * 1024 * 1024)).toFixed(1)}{" GB)"}</Text>
2179
+ </Text>
2180
+ ))}
2181
+ <Text>{""}</Text>
2182
+ <Text dimColor>{" ↑↓ navigate · Enter to delete · Esc cancel"}</Text>
2183
+ </Box>
2184
+ )}
2185
+
2186
+ {/* ═══ OLLAMA PULL PICKER ═══ */}
2187
+ {ollamaPullPicker && (
2188
+ <Box flexDirection="column" borderStyle="single" borderColor={theme.colors.border} paddingX={1} marginBottom={0}>
2189
+ <Text bold color={theme.colors.secondary}>Download which model?</Text>
2190
+ <Text>{""}</Text>
2191
+ {[
2192
+ { id: "qwen2.5-coder:7b", name: "Qwen 2.5 Coder 7B", size: "5 GB", desc: "Best balance of speed & quality" },
2193
+ { id: "qwen2.5-coder:14b", name: "Qwen 2.5 Coder 14B", size: "9 GB", desc: "Higher quality, needs 16GB+ RAM" },
2194
+ { id: "qwen2.5-coder:3b", name: "Qwen 2.5 Coder 3B", size: "2 GB", desc: "Fast but limited quality" },
2195
+ { id: "qwen2.5-coder:32b", name: "Qwen 2.5 Coder 32B", size: "20 GB", desc: "Premium, needs 48GB+" },
2196
+ { id: "deepseek-coder-v2:16b", name: "DeepSeek Coder V2", size: "9 GB", desc: "Strong alternative" },
2197
+ { id: "codellama:7b", name: "CodeLlama 7B", size: "4 GB", desc: "Meta's coding model" },
2198
+ { id: "starcoder2:7b", name: "StarCoder2 7B", size: "4 GB", desc: "Code completion focused" },
2199
+ ].map((m, i) => (
2200
+ <Text key={m.id}>
2201
+ {" "}{i === ollamaPullPickerIndex ? <Text color={theme.colors.primary} bold>{"▸ "}</Text> : " "}
2202
+ <Text color={i === ollamaPullPickerIndex ? theme.colors.primary : undefined} bold>{m.name}</Text>
2203
+ <Text color={theme.colors.muted}>{" · "}{m.size}{" · "}{m.desc}</Text>
2204
+ </Text>
2205
+ ))}
2206
+ <Text>{""}</Text>
2207
+ <Text dimColor>{" ↑↓ navigate · Enter to download · Esc cancel"}</Text>
2208
+ </Box>
2209
+ )}
2210
+
2075
2211
  {/* ═══ OLLAMA DELETE CONFIRM ═══ */}
2076
2212
  {ollamaDeleteConfirm && (
2077
2213
  <Box flexDirection="column" borderStyle="single" borderColor={theme.colors.warning} paddingX={1} marginBottom={0}>