codemaxxing 0.4.7 โ†’ 0.4.9

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
@@ -1465,34 +1465,39 @@ function App() {
1465
1465
  if (key.return) {
1466
1466
  // Auto-install Ollama if not present
1467
1467
  if (!isOllamaInstalled()) {
1468
- addMsg("info", "๐Ÿ“ฆ Installing Ollama... (this may take a minute)");
1468
+ setWizardPullProgress({ status: "Installing Ollama...", percent: 0 });
1469
+ setWizardScreen("pulling");
1470
+ // Run install async so the UI can update
1469
1471
  const installCmd = getOllamaInstallCommand(wizardHardware?.os ?? "linux");
1470
- try {
1471
- // Use pipe instead of inherit โ€” Ink TUI conflicts with inherit stdio
1472
- const parts = installCmd.split(" ");
1473
- const result = _require("child_process").spawnSync(parts[0], parts.slice(1), {
1474
- stdio: "pipe",
1475
- timeout: 180000,
1476
- shell: true,
1477
- encoding: "utf-8",
1478
- });
1479
- if (result.status === 0) {
1480
- addMsg("info", "โœ… Ollama installed!");
1472
+ (async () => {
1473
+ try {
1474
+ const { exec } = _require("child_process");
1475
+ await new Promise((resolve, reject) => {
1476
+ exec(installCmd, { timeout: 180000 }, (err, _stdout, stderr) => {
1477
+ if (err)
1478
+ reject(new Error(stderr || err.message));
1479
+ else
1480
+ resolve();
1481
+ });
1482
+ });
1483
+ addMsg("info", "โœ… Ollama installed! Proceeding to model download...");
1484
+ // Small delay for PATH to update on Windows
1485
+ await new Promise(r => setTimeout(r, 2000));
1486
+ // Go back to models screen so user can pick and it'll proceed to pull
1487
+ setWizardScreen("models");
1488
+ setWizardPullProgress(null);
1481
1489
  }
1482
- else {
1483
- const errMsg = (result.stderr || result.stdout || "").trim().split("\n").pop() || "Unknown error";
1484
- addMsg("error", `Install failed: ${errMsg}`);
1490
+ catch (e) {
1491
+ addMsg("error", `Install failed: ${e.message}`);
1485
1492
  addMsg("info", `Try manually in a separate terminal: ${installCmd}`);
1486
- return;
1493
+ setWizardScreen("install-ollama");
1494
+ setWizardPullProgress(null);
1487
1495
  }
1488
- }
1489
- catch (e) {
1490
- addMsg("error", `Install failed: ${e.message}`);
1491
- addMsg("info", `Try manually in a separate terminal: ${installCmd}`);
1492
- return;
1493
- }
1496
+ })();
1497
+ return;
1494
1498
  }
1495
- if (isOllamaInstalled()) {
1499
+ // Ollama already installed โ€” proceed to pull
1500
+ {
1496
1501
  const selected = wizardSelectedModel;
1497
1502
  if (selected) {
1498
1503
  setWizardScreen("pulling");
@@ -1820,7 +1825,7 @@ function App() {
1820
1825
  { key: "openrouter", icon: "\uD83C\uDF10", label: "OpenRouter", desc: "200+ cloud models, browser login" },
1821
1826
  { key: "apikey", icon: "\uD83D\uDD11", label: "Enter API key manually", desc: "" },
1822
1827
  { key: "existing", icon: "\u2699\uFE0F", label: "I already have a server running", desc: "" },
1823
- ].map((item, i) => (_jsxs(Text, { children: [i === wizardIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: " \u25B8 " }) : _jsx(Text, { children: " " }), _jsxs(Text, { color: i === wizardIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: [item.icon, " ", item.label] }), item.desc ? _jsxs(Text, { color: theme.colors.muted, children: [" (", item.desc, ")"] }) : null] }, item.key))), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " \u2191\u2193 navigate \u00B7 Enter to select" })] })), wizardScreen === "models" && wizardHardware && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Your hardware:" }), _jsxs(Text, { color: theme.colors.muted, children: [" CPU: ", wizardHardware.cpu.name, " (", wizardHardware.cpu.cores, " cores)"] }), _jsxs(Text, { color: theme.colors.muted, children: [" RAM: ", formatBytes(wizardHardware.ram)] }), wizardHardware.gpu ? (_jsxs(Text, { color: theme.colors.muted, children: [" GPU: ", wizardHardware.gpu.name, wizardHardware.gpu.vram > 0 ? ` (${formatBytes(wizardHardware.gpu.vram)})` : ""] })) : (_jsx(Text, { color: theme.colors.muted, children: " GPU: none detected" })), !isLlmfitAvailable() && (_jsx(Text, { dimColor: true, children: " Tip: Install llmfit for smarter recommendations: brew install llmfit" })), _jsx(Text, { children: "" }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: "Recommended models:" }), _jsx(Text, { children: "" }), wizardModels.map((m, i) => (_jsxs(Text, { children: [i === wizardIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: " \u25B8 " }) : _jsx(Text, { children: " " }), _jsxs(Text, { children: [getFitIcon(m.fit), " "] }), _jsx(Text, { color: i === wizardIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: m.name }), _jsxs(Text, { color: theme.colors.muted, children: [" ~", m.size, " GB \u00B7 ", m.quality === "best" ? "Best" : m.quality === "great" ? "Great" : "Good", " quality \u00B7 ", m.speed] })] }, m.ollamaId))), wizardModels.length === 0 && (_jsx(Text, { color: theme.colors.error, children: " No suitable models found for your hardware." })), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " \u2191\u2193 navigate \u00B7 Enter to install \u00B7 Esc back" })] })), wizardScreen === "install-ollama" && (_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 required for local models." }), _jsx(Text, { children: "" }), _jsx(Text, { color: theme.colors.primary, children: " Press Enter to install Ollama automatically" }), _jsxs(Text, { dimColor: true, children: [" Or install manually: ", _jsx(Text, { children: getOllamaInstallCommand(wizardHardware?.os ?? "linux") })] }), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " Enter to install ยท Esc to go back" })] })), wizardScreen === "pulling" && wizardSelectedModel && (_jsx(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: wizardPullError ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: theme.colors.error, bold: true, children: [" \u274C Error: ", wizardPullError] }), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " Press Enter to retry \u00B7 Esc to go back" })] })) : wizardPullProgress ? (_jsxs(_Fragment, { children: [_jsxs(Text, { bold: true, color: theme.colors.secondary, children: [" Downloading ", wizardSelectedModel.name, "..."] }), wizardPullProgress.status === "downloading" || wizardPullProgress.percent > 0 ? (_jsx(_Fragment, { children: _jsxs(Text, { children: [" ", _jsxs(Text, { color: theme.colors.primary, children: ["\u2588".repeat(Math.floor(wizardPullProgress.percent / 5)), "\u2591".repeat(20 - Math.floor(wizardPullProgress.percent / 5))] }), " ", _jsxs(Text, { bold: true, children: [wizardPullProgress.percent, "%"] }), wizardPullProgress.completed != null && wizardPullProgress.total != null ? (_jsxs(Text, { color: theme.colors.muted, children: [" \u00B7 ", formatBytes(wizardPullProgress.completed), " / ", formatBytes(wizardPullProgress.total)] })) : null] }) })) : (_jsxs(Text, { color: theme.colors.muted, children: [" ", wizardPullProgress.status, "..."] }))] })) : null })), showSuggestions && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.muted, paddingX: 1, marginBottom: 0, children: [cmdMatches.slice(0, 6).map((c, i) => (_jsxs(Text, { children: [i === cmdIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "โ–ธ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === cmdIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: c.cmd }), _jsxs(Text, { color: theme.colors.muted, children: [" โ€” ", c.desc] })] }, i))), _jsx(Text, { dimColor: true, children: " โ†‘โ†“ navigate ยท Tab select" })] })), _jsxs(Box, { borderStyle: "single", borderColor: approval ? theme.colors.warning : theme.colors.border, paddingX: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "> " }), approval ? (_jsx(Text, { color: theme.colors.warning, children: "waiting for approval..." })) : ready && !loading && !wizardScreen ? (_jsxs(Box, { children: [pastedChunks.map((p) => (_jsxs(Text, { color: theme.colors.muted, children: ["[Pasted text #", p.id, " +", p.lines, " lines]"] }, p.id))), _jsx(TextInput, { value: input, onChange: (v) => { setInput(v); setCmdIndex(0); }, onSubmit: handleSubmit }, inputKey)] })) : (_jsx(Text, { dimColor: true, children: loading ? "waiting for response..." : "initializing..." }))] }), agent && (_jsx(Box, { paddingX: 2, children: _jsxs(Text, { dimColor: true, children: ["๐Ÿ’ฌ ", agent.getContextLength(), " messages ยท ~", (() => {
1828
+ ].map((item, i) => (_jsxs(Text, { children: [i === wizardIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: " \u25B8 " }) : _jsx(Text, { children: " " }), _jsxs(Text, { color: i === wizardIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: [item.icon, " ", item.label] }), item.desc ? _jsxs(Text, { color: theme.colors.muted, children: [" (", item.desc, ")"] }) : null] }, item.key))), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " \u2191\u2193 navigate \u00B7 Enter to select" })] })), wizardScreen === "models" && wizardHardware && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Your hardware:" }), _jsxs(Text, { color: theme.colors.muted, children: [" CPU: ", wizardHardware.cpu.name, " (", wizardHardware.cpu.cores, " cores)"] }), _jsxs(Text, { color: theme.colors.muted, children: [" RAM: ", formatBytes(wizardHardware.ram)] }), wizardHardware.gpu ? (_jsxs(Text, { color: theme.colors.muted, children: [" GPU: ", wizardHardware.gpu.name, wizardHardware.gpu.vram > 0 ? ` (${formatBytes(wizardHardware.gpu.vram)})` : ""] })) : (_jsx(Text, { color: theme.colors.muted, children: " GPU: none detected" })), !isLlmfitAvailable() && (_jsx(Text, { dimColor: true, children: " Tip: Install llmfit for smarter recommendations: brew install llmfit" })), _jsx(Text, { children: "" }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: "Recommended models:" }), _jsx(Text, { children: "" }), wizardModels.map((m, i) => (_jsxs(Text, { children: [i === wizardIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: " \u25B8 " }) : _jsx(Text, { children: " " }), _jsxs(Text, { children: [getFitIcon(m.fit), " "] }), _jsx(Text, { color: i === wizardIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: m.name }), _jsxs(Text, { color: theme.colors.muted, children: [" ~", m.size, " GB \u00B7 ", m.quality === "best" ? "Best" : m.quality === "great" ? "Great" : "Good", " quality \u00B7 ", m.speed] })] }, m.ollamaId))), wizardModels.length === 0 && (_jsx(Text, { color: theme.colors.error, children: " No suitable models found for your hardware." })), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " \u2191\u2193 navigate \u00B7 Enter to install \u00B7 Esc back" })] })), wizardScreen === "install-ollama" && (_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 required for local models." }), _jsx(Text, { children: "" }), _jsx(Text, { color: theme.colors.primary, children: " Press Enter to install Ollama automatically" }), _jsxs(Text, { dimColor: true, children: [" Or install manually: ", _jsx(Text, { children: getOllamaInstallCommand(wizardHardware?.os ?? "linux") })] }), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " Enter to install ยท Esc to go back" })] })), wizardScreen === "pulling" && (wizardSelectedModel || wizardPullProgress) && (_jsx(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: wizardPullError ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: theme.colors.error, bold: true, children: [" \u274C Error: ", wizardPullError] }), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " Press Enter to retry \u00B7 Esc to go back" })] })) : wizardPullProgress ? (_jsxs(_Fragment, { children: [_jsxs(Text, { bold: true, color: theme.colors.secondary, children: [" ", wizardSelectedModel ? `Downloading ${wizardSelectedModel.name}...` : wizardPullProgress?.status || "Working..."] }), wizardPullProgress.status === "downloading" || wizardPullProgress.percent > 0 ? (_jsx(_Fragment, { children: _jsxs(Text, { children: [" ", _jsxs(Text, { color: theme.colors.primary, children: ["\u2588".repeat(Math.floor(wizardPullProgress.percent / 5)), "\u2591".repeat(20 - Math.floor(wizardPullProgress.percent / 5))] }), " ", _jsxs(Text, { bold: true, children: [wizardPullProgress.percent, "%"] }), wizardPullProgress.completed != null && wizardPullProgress.total != null ? (_jsxs(Text, { color: theme.colors.muted, children: [" \u00B7 ", formatBytes(wizardPullProgress.completed), " / ", formatBytes(wizardPullProgress.total)] })) : null] }) })) : (_jsxs(Text, { color: theme.colors.muted, children: [" ", wizardPullProgress.status, "..."] }))] })) : null })), showSuggestions && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.muted, paddingX: 1, marginBottom: 0, children: [cmdMatches.slice(0, 6).map((c, i) => (_jsxs(Text, { children: [i === cmdIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "โ–ธ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === cmdIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: c.cmd }), _jsxs(Text, { color: theme.colors.muted, children: [" โ€” ", c.desc] })] }, i))), _jsx(Text, { dimColor: true, children: " โ†‘โ†“ navigate ยท Tab select" })] })), _jsxs(Box, { borderStyle: "single", borderColor: approval ? theme.colors.warning : theme.colors.border, paddingX: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "> " }), approval ? (_jsx(Text, { color: theme.colors.warning, children: "waiting for approval..." })) : ready && !loading && !wizardScreen ? (_jsxs(Box, { children: [pastedChunks.map((p) => (_jsxs(Text, { color: theme.colors.muted, children: ["[Pasted text #", p.id, " +", p.lines, " lines]"] }, p.id))), _jsx(TextInput, { value: input, onChange: (v) => { setInput(v); setCmdIndex(0); }, onSubmit: handleSubmit }, inputKey)] })) : (_jsx(Text, { dimColor: true, children: loading ? "waiting for response..." : "initializing..." }))] }), agent && (_jsx(Box, { paddingX: 2, children: _jsxs(Text, { dimColor: true, children: ["๐Ÿ’ฌ ", agent.getContextLength(), " messages ยท ~", (() => {
1824
1829
  const tokens = agent.estimateTokens();
1825
1830
  return tokens >= 1000 ? `${(tokens / 1000).toFixed(1)}k` : String(tokens);
1826
1831
  })(), " tokens", (() => {
@@ -1,4 +1,4 @@
1
- /** Check if ollama binary exists on PATH */
1
+ /** Check if ollama binary exists on PATH or known install locations */
2
2
  export declare function isOllamaInstalled(): boolean;
3
3
  /** Check if ollama server is responding */
4
4
  export declare function isOllamaRunning(): Promise<boolean>;
@@ -1,5 +1,17 @@
1
1
  import { execSync, spawn } from "child_process";
2
- /** Check if ollama binary exists on PATH */
2
+ import { existsSync } from "fs";
3
+ import { join } from "path";
4
+ /** Get known Ollama binary paths for Windows */
5
+ function getWindowsOllamaPaths() {
6
+ const paths = [];
7
+ const localAppData = process.env.LOCALAPPDATA || join(process.env.USERPROFILE || "", "AppData", "Local");
8
+ const programFiles = process.env.ProgramFiles || "C:\\Program Files";
9
+ paths.push(join(localAppData, "Programs", "Ollama", "ollama.exe"));
10
+ paths.push(join(programFiles, "Ollama", "ollama.exe"));
11
+ paths.push(join(localAppData, "Ollama", "ollama.exe"));
12
+ return paths;
13
+ }
14
+ /** Check if ollama binary exists on PATH or known install locations */
3
15
  export function isOllamaInstalled() {
4
16
  try {
5
17
  const cmd = process.platform === "win32" ? "where ollama" : "which ollama";
@@ -7,6 +19,10 @@ export function isOllamaInstalled() {
7
19
  return true;
8
20
  }
9
21
  catch {
22
+ // Fallback: check known install paths on Windows
23
+ if (process.platform === "win32") {
24
+ return getWindowsOllamaPaths().some(p => existsSync(p));
25
+ }
10
26
  return false;
11
27
  }
12
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemaxxing",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
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
@@ -1489,32 +1489,37 @@ function App() {
1489
1489
  if (key.return) {
1490
1490
  // Auto-install Ollama if not present
1491
1491
  if (!isOllamaInstalled()) {
1492
- addMsg("info", "๐Ÿ“ฆ Installing Ollama... (this may take a minute)");
1492
+ setWizardPullProgress({ status: "Installing Ollama...", percent: 0 });
1493
+ setWizardScreen("pulling");
1494
+
1495
+ // Run install async so the UI can update
1493
1496
  const installCmd = getOllamaInstallCommand(wizardHardware?.os ?? "linux");
1494
- try {
1495
- // Use pipe instead of inherit โ€” Ink TUI conflicts with inherit stdio
1496
- const parts = installCmd.split(" ");
1497
- const result = _require("child_process").spawnSync(parts[0], parts.slice(1), {
1498
- stdio: "pipe",
1499
- timeout: 180000,
1500
- shell: true,
1501
- encoding: "utf-8",
1502
- });
1503
- if (result.status === 0) {
1504
- addMsg("info", "โœ… Ollama installed!");
1505
- } else {
1506
- const errMsg = (result.stderr || result.stdout || "").trim().split("\n").pop() || "Unknown error";
1507
- addMsg("error", `Install failed: ${errMsg}`);
1497
+ (async () => {
1498
+ try {
1499
+ const { exec } = _require("child_process");
1500
+ await new Promise<void>((resolve, reject) => {
1501
+ exec(installCmd, { timeout: 180000 }, (err: any, _stdout: string, stderr: string) => {
1502
+ if (err) reject(new Error(stderr || err.message));
1503
+ else resolve();
1504
+ });
1505
+ });
1506
+ addMsg("info", "โœ… Ollama installed! Proceeding to model download...");
1507
+ // Small delay for PATH to update on Windows
1508
+ await new Promise(r => setTimeout(r, 2000));
1509
+ // Go back to models screen so user can pick and it'll proceed to pull
1510
+ setWizardScreen("models");
1511
+ setWizardPullProgress(null);
1512
+ } catch (e: any) {
1513
+ addMsg("error", `Install failed: ${e.message}`);
1508
1514
  addMsg("info", `Try manually in a separate terminal: ${installCmd}`);
1509
- return;
1515
+ setWizardScreen("install-ollama");
1516
+ setWizardPullProgress(null);
1510
1517
  }
1511
- } catch (e: any) {
1512
- addMsg("error", `Install failed: ${e.message}`);
1513
- addMsg("info", `Try manually in a separate terminal: ${installCmd}`);
1514
- return;
1515
- }
1518
+ })();
1519
+ return;
1516
1520
  }
1517
- if (isOllamaInstalled()) {
1521
+ // Ollama already installed โ€” proceed to pull
1522
+ {
1518
1523
  const selected = wizardSelectedModel;
1519
1524
  if (selected) {
1520
1525
  setWizardScreen("pulling");
@@ -2178,7 +2183,7 @@ function App() {
2178
2183
  </Box>
2179
2184
  )}
2180
2185
 
2181
- {wizardScreen === "pulling" && wizardSelectedModel && (
2186
+ {wizardScreen === "pulling" && (wizardSelectedModel || wizardPullProgress) && (
2182
2187
  <Box flexDirection="column" borderStyle="single" borderColor={theme.colors.border} paddingX={1} marginBottom={0}>
2183
2188
  {wizardPullError ? (
2184
2189
  <>
@@ -2188,7 +2193,7 @@ function App() {
2188
2193
  </>
2189
2194
  ) : wizardPullProgress ? (
2190
2195
  <>
2191
- <Text bold color={theme.colors.secondary}>{" Downloading "}{wizardSelectedModel.name}{"..."}</Text>
2196
+ <Text bold color={theme.colors.secondary}>{" "}{wizardSelectedModel ? `Downloading ${wizardSelectedModel.name}...` : wizardPullProgress?.status || "Working..."}</Text>
2192
2197
  {wizardPullProgress.status === "downloading" || wizardPullProgress.percent > 0 ? (
2193
2198
  <>
2194
2199
  <Text>
@@ -1,12 +1,29 @@
1
1
  import { execSync, spawn } from "child_process";
2
+ import { existsSync } from "fs";
3
+ import { join } from "path";
4
+
5
+ /** Get known Ollama binary paths for Windows */
6
+ function getWindowsOllamaPaths(): string[] {
7
+ const paths: string[] = [];
8
+ const localAppData = process.env.LOCALAPPDATA || join(process.env.USERPROFILE || "", "AppData", "Local");
9
+ const programFiles = process.env.ProgramFiles || "C:\\Program Files";
10
+ paths.push(join(localAppData, "Programs", "Ollama", "ollama.exe"));
11
+ paths.push(join(programFiles, "Ollama", "ollama.exe"));
12
+ paths.push(join(localAppData, "Ollama", "ollama.exe"));
13
+ return paths;
14
+ }
2
15
 
3
- /** Check if ollama binary exists on PATH */
16
+ /** Check if ollama binary exists on PATH or known install locations */
4
17
  export function isOllamaInstalled(): boolean {
5
18
  try {
6
19
  const cmd = process.platform === "win32" ? "where ollama" : "which ollama";
7
20
  execSync(cmd, { stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
8
21
  return true;
9
22
  } catch {
23
+ // Fallback: check known install paths on Windows
24
+ if (process.platform === "win32") {
25
+ return getWindowsOllamaPaths().some(p => existsSync(p));
26
+ }
10
27
  return false;
11
28
  }
12
29
  }