open-agents-ai 0.23.1 → 0.24.0

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
@@ -6628,111 +6628,158 @@ var init_vision = __esm({
6628
6628
  if (!rawPath) {
6629
6629
  return { success: false, output: "", error: "image path is required", durationMs: 0 };
6630
6630
  }
6631
+ if ((action === "query" || action === "detect" || action === "point") && !prompt) {
6632
+ return {
6633
+ success: false,
6634
+ output: "",
6635
+ error: `prompt is required for ${action} action`,
6636
+ durationMs: performance.now() - start
6637
+ };
6638
+ }
6631
6639
  try {
6632
6640
  const { buffer, fullPath } = loadImageBuffer(this.workingDir, rawPath);
6633
- const client = await getMoondreamClient();
6634
- switch (action) {
6635
- case "caption": {
6636
- const result = await client.caption({ image: buffer, length });
6637
- const caption = typeof result.caption === "string" ? result.caption : "(streaming not supported)";
6638
- return {
6639
- success: true,
6640
- output: `Caption (${length}) for ${basename4(fullPath)}:
6641
- ${caption}`,
6642
- durationMs: performance.now() - start
6643
- };
6644
- }
6645
- case "query": {
6646
- if (!prompt) {
6647
- return {
6648
- success: false,
6649
- output: "",
6650
- error: "prompt is required for query action (the question to ask about the image)",
6651
- durationMs: performance.now() - start
6652
- };
6653
- }
6654
- const result = await client.query({ image: buffer, question: prompt });
6655
- const answer = typeof result.answer === "string" ? result.answer : "(streaming not supported)";
6656
- return {
6657
- success: true,
6658
- output: `Q: ${prompt}
6641
+ const filename = basename4(fullPath);
6642
+ let client = null;
6643
+ try {
6644
+ client = await getMoondreamClient();
6645
+ } catch {
6646
+ }
6647
+ if (client) {
6648
+ return await this.runMoondream(client, buffer, filename, action, prompt, length, start);
6649
+ }
6650
+ const ollamaResult = await this.tryOllamaVision(buffer, filename, action, prompt, length, start);
6651
+ if (ollamaResult)
6652
+ return ollamaResult;
6653
+ return {
6654
+ success: false,
6655
+ output: "",
6656
+ error: "No vision backend available.\nTo enable vision, either:\n 1. ollama pull moondream \u2014 uses Ollama (easiest)\n 2. pip install moondream-station \u2014 dedicated server\n 3. Set MOONDREAM_API_KEY for cloud inference",
6657
+ durationMs: performance.now() - start
6658
+ };
6659
+ } catch (error) {
6660
+ return {
6661
+ success: false,
6662
+ output: "",
6663
+ error: error instanceof Error ? error.message : String(error),
6664
+ durationMs: performance.now() - start
6665
+ };
6666
+ }
6667
+ }
6668
+ async runMoondream(client, buffer, filename, action, prompt, length, start) {
6669
+ switch (action) {
6670
+ case "caption": {
6671
+ const result = await client.caption({ image: buffer, length });
6672
+ const caption = typeof result.caption === "string" ? result.caption : "(streaming not supported)";
6673
+ return { success: true, output: `Caption (${length}) for ${filename}:
6674
+ ${caption}`, durationMs: performance.now() - start };
6675
+ }
6676
+ case "query": {
6677
+ const result = await client.query({ image: buffer, question: prompt });
6678
+ const answer = typeof result.answer === "string" ? result.answer : "(streaming not supported)";
6679
+ return { success: true, output: `Q: ${prompt}
6659
6680
  A: ${answer}
6660
6681
 
6661
- Image: ${basename4(fullPath)}`,
6662
- durationMs: performance.now() - start
6663
- };
6682
+ Image: ${filename}`, durationMs: performance.now() - start };
6683
+ }
6684
+ case "detect": {
6685
+ const result = await client.detect({ image: buffer, object: prompt });
6686
+ const objects = result.objects ?? [];
6687
+ if (objects.length === 0) {
6688
+ return { success: true, output: `No "${prompt}" detected in ${filename}`, durationMs: performance.now() - start };
6664
6689
  }
6665
- case "detect": {
6666
- if (!prompt) {
6667
- return {
6668
- success: false,
6669
- output: "",
6670
- error: "prompt is required for detect action (the object to find)",
6671
- durationMs: performance.now() - start
6672
- };
6673
- }
6674
- const result = await client.detect({ image: buffer, object: prompt });
6675
- const objects = result.objects ?? [];
6676
- if (objects.length === 0) {
6677
- return {
6678
- success: true,
6679
- output: `No "${prompt}" detected in ${basename4(fullPath)}`,
6680
- durationMs: performance.now() - start
6681
- };
6682
- }
6683
- const formatted = objects.map((obj, i) => ` ${i + 1}. bbox: [${obj.x_min.toFixed(3)}, ${obj.y_min.toFixed(3)}, ${obj.x_max.toFixed(3)}, ${obj.y_max.toFixed(3)}] (normalized 0-1)`).join("\n");
6684
- return {
6685
- success: true,
6686
- output: `Detected ${objects.length} "${prompt}" in ${basename4(fullPath)}:
6690
+ const formatted = objects.map((obj, i) => ` ${i + 1}. bbox: [${obj.x_min.toFixed(3)}, ${obj.y_min.toFixed(3)}, ${obj.x_max.toFixed(3)}, ${obj.y_max.toFixed(3)}] (normalized 0-1)`).join("\n");
6691
+ return {
6692
+ success: true,
6693
+ output: `Detected ${objects.length} "${prompt}" in ${filename}:
6687
6694
  ${formatted}
6688
6695
 
6689
6696
  Coordinates are normalized (0-1). Multiply by image width/height for pixel values.`,
6690
- durationMs: performance.now() - start
6691
- };
6697
+ durationMs: performance.now() - start
6698
+ };
6699
+ }
6700
+ case "point": {
6701
+ const result = await client.point({ image: buffer, object: prompt });
6702
+ const points = result.points ?? [];
6703
+ if (points.length === 0) {
6704
+ return { success: true, output: `No "${prompt}" found in ${filename}`, durationMs: performance.now() - start };
6692
6705
  }
6693
- case "point": {
6694
- if (!prompt) {
6695
- return {
6696
- success: false,
6697
- output: "",
6698
- error: "prompt is required for point action (the object to locate)",
6699
- durationMs: performance.now() - start
6700
- };
6701
- }
6702
- const result = await client.point({ image: buffer, object: prompt });
6703
- const points = result.points ?? [];
6704
- if (points.length === 0) {
6705
- return {
6706
- success: true,
6707
- output: `No "${prompt}" found in ${basename4(fullPath)}`,
6708
- durationMs: performance.now() - start
6709
- };
6710
- }
6711
- const formatted = points.map((pt, i) => ` ${i + 1}. (${pt.x.toFixed(4)}, ${pt.y.toFixed(4)}) \u2014 normalized 0-1`).join("\n");
6706
+ const formatted = points.map((pt, i) => ` ${i + 1}. (${pt.x.toFixed(4)}, ${pt.y.toFixed(4)}) \u2014 normalized 0-1`).join("\n");
6707
+ return {
6708
+ success: true,
6709
+ output: `Found ${points.length} "${prompt}" location(s) in ${filename}:
6710
+ ${formatted}
6711
+
6712
+ Coordinates are normalized (0-1). Multiply by image width/height for pixel values.`,
6713
+ durationMs: performance.now() - start
6714
+ };
6715
+ }
6716
+ default:
6717
+ return { success: false, output: "", error: `Unknown action: ${action}. Use: caption, query, detect, point`, durationMs: performance.now() - start };
6718
+ }
6719
+ }
6720
+ async tryOllamaVision(buffer, filename, action, prompt, length, start) {
6721
+ const ollamaHost = process.env["OLLAMA_HOST"] || "http://localhost:11434";
6722
+ const model = process.env["OLLAMA_VISION_MODEL"] || "moondream";
6723
+ const imageBase64 = buffer.toString("base64");
6724
+ let ollamaPrompt;
6725
+ switch (action) {
6726
+ case "caption":
6727
+ ollamaPrompt = length === "short" ? "Briefly describe this image." : length === "long" ? "Describe this image in detail." : "Describe this image.";
6728
+ break;
6729
+ case "query":
6730
+ ollamaPrompt = prompt;
6731
+ break;
6732
+ case "detect":
6733
+ ollamaPrompt = `Detect all instances of "${prompt}" in this image. For each, describe its location.`;
6734
+ break;
6735
+ case "point":
6736
+ ollamaPrompt = `Point to ${prompt}`;
6737
+ break;
6738
+ default:
6739
+ return null;
6740
+ }
6741
+ try {
6742
+ const res = await fetch(`${ollamaHost}/api/generate`, {
6743
+ method: "POST",
6744
+ headers: { "Content-Type": "application/json" },
6745
+ body: JSON.stringify({ model, prompt: ollamaPrompt, images: [imageBase64], stream: false }),
6746
+ signal: AbortSignal.timeout(6e4)
6747
+ });
6748
+ if (!res.ok)
6749
+ return null;
6750
+ const data = await res.json();
6751
+ const response = data.response || "";
6752
+ if (!response)
6753
+ return null;
6754
+ if (action === "point") {
6755
+ const matches = [...response.matchAll(/<point\s+x="([\d.]+)"\s+y="([\d.]+)"\s*\/?>/g)];
6756
+ if (matches.length > 0) {
6757
+ const formatted = matches.map((m, i) => ` ${i + 1}. (${parseFloat(m[1]).toFixed(4)}, ${parseFloat(m[2]).toFixed(4)}) \u2014 normalized 0-1`).join("\n");
6712
6758
  return {
6713
6759
  success: true,
6714
- output: `Found ${points.length} "${prompt}" location(s) in ${basename4(fullPath)}:
6760
+ output: `Found ${matches.length} "${prompt}" location(s) in ${filename} (via Ollama):
6715
6761
  ${formatted}
6716
6762
 
6717
6763
  Coordinates are normalized (0-1). Multiply by image width/height for pixel values.`,
6718
6764
  durationMs: performance.now() - start
6719
6765
  };
6720
6766
  }
6721
- default:
6722
- return {
6723
- success: false,
6724
- output: "",
6725
- error: `Unknown action: ${action}. Use: caption, query, detect, point`,
6726
- durationMs: performance.now() - start
6727
- };
6767
+ return { success: true, output: `Could not extract coordinates for "${prompt}" from ${filename}. Model response: ${response}`, durationMs: performance.now() - start };
6728
6768
  }
6729
- } catch (error) {
6730
- return {
6731
- success: false,
6732
- output: "",
6733
- error: error instanceof Error ? error.message : String(error),
6734
- durationMs: performance.now() - start
6735
- };
6769
+ if (action === "caption") {
6770
+ return { success: true, output: `Caption (${length}) for ${filename} (via Ollama):
6771
+ ${response}`, durationMs: performance.now() - start };
6772
+ }
6773
+ if (action === "query") {
6774
+ return { success: true, output: `Q: ${prompt}
6775
+ A: ${response}
6776
+
6777
+ Image: ${filename} (via Ollama)`, durationMs: performance.now() - start };
6778
+ }
6779
+ return { success: true, output: `Detection results for "${prompt}" in ${filename} (via Ollama):
6780
+ ${response}`, durationMs: performance.now() - start };
6781
+ } catch {
6782
+ return null;
6736
6783
  }
6737
6784
  }
6738
6785
  };
@@ -6981,18 +7028,44 @@ var init_desktop_click = __esm({
6981
7028
  visionWorked = true;
6982
7029
  } catch {
6983
7030
  }
7031
+ if (!visionWorked) {
7032
+ try {
7033
+ const ollamaHost = process.env["OLLAMA_HOST"] || "http://localhost:11434";
7034
+ const ollamaModel = process.env["OLLAMA_VISION_MODEL"] || "moondream";
7035
+ const imageBase64 = readFileSync11(screenshotPath).toString("base64");
7036
+ const res = await fetch(`${ollamaHost}/api/generate`, {
7037
+ method: "POST",
7038
+ headers: { "Content-Type": "application/json" },
7039
+ body: JSON.stringify({ model: ollamaModel, prompt: `Point to ${target}`, images: [imageBase64], stream: false }),
7040
+ signal: AbortSignal.timeout(6e4)
7041
+ });
7042
+ if (res.ok) {
7043
+ const data = await res.json();
7044
+ const response = data.response || "";
7045
+ const pointMatches = [...response.matchAll(/<point\s+x="([\d.]+)"\s+y="([\d.]+)"\s*\/?>/g)];
7046
+ if (pointMatches.length > 0) {
7047
+ points = pointMatches.map((m) => ({ x: parseFloat(m[1]), y: parseFloat(m[2]) }));
7048
+ visionWorked = true;
7049
+ }
7050
+ }
7051
+ } catch {
7052
+ }
7053
+ }
6984
7054
  if (!visionWorked) {
6985
7055
  const hints = [
6986
- `(Moondream vision not available \u2014 cannot locate "${target}" on screen)`,
7056
+ `(No vision backend available \u2014 cannot locate "${target}" on screen)`,
6987
7057
  `Screenshot saved: ${screenshotPath}`,
6988
7058
  `Screen: ${dims.width}x${dims.height}`,
6989
7059
  "",
7060
+ "To enable vision-guided clicking, either:",
7061
+ " ollama pull moondream \u2014 then Ollama handles point detection",
7062
+ " pip install moondream-station \u2014 dedicated Moondream server",
7063
+ "",
6990
7064
  "Use shell commands to interact with the desktop instead:",
6991
7065
  " xdotool search --name 'pattern' \u2014 find windows by title",
6992
7066
  " xdotool key 'ctrl+s' \u2014 send keyboard shortcuts",
6993
7067
  " xdotool mousemove X Y click 1 \u2014 click at known coordinates",
6994
- " wmctrl -a 'window title' \u2014 activate a window by name",
6995
- " xdg-open <url> \u2014 open a URL in the default browser"
7068
+ " wmctrl -a 'window title' \u2014 activate a window by name"
6996
7069
  ];
6997
7070
  return {
6998
7071
  success: true,
@@ -7120,26 +7193,61 @@ ${caption}`);
7120
7193
  } catch {
7121
7194
  }
7122
7195
  if (!visionWorked) {
7123
- let ocrText = "";
7124
7196
  try {
7125
- ocrText = execSync11(`tesseract ${JSON.stringify(screenshotPath)} stdout 2>/dev/null`, {
7126
- encoding: "utf8",
7127
- timeout: 15e3
7128
- }).trim();
7197
+ const ollamaHost = process.env["OLLAMA_HOST"] || "http://localhost:11434";
7198
+ const ollamaModel = process.env["OLLAMA_VISION_MODEL"] || "moondream";
7199
+ const imageBase64 = imageBuffer.toString("base64");
7200
+ const ollamaPrompt = question || "Describe what you see on this desktop screenshot in detail. Include visible applications, windows, text, and UI elements.";
7201
+ const res = await fetch(`${ollamaHost}/api/generate`, {
7202
+ method: "POST",
7203
+ headers: { "Content-Type": "application/json" },
7204
+ body: JSON.stringify({ model: ollamaModel, prompt: ollamaPrompt, images: [imageBase64], stream: false }),
7205
+ signal: AbortSignal.timeout(6e4)
7206
+ });
7207
+ if (res.ok) {
7208
+ const data = await res.json();
7209
+ if (data.response) {
7210
+ if (question) {
7211
+ parts.push(`Q: ${question}`);
7212
+ parts.push(`A: ${data.response}`);
7213
+ } else {
7214
+ parts.push(`Desktop description (via Ollama):
7215
+ ${data.response}`);
7216
+ }
7217
+ visionWorked = true;
7218
+ }
7219
+ }
7129
7220
  } catch {
7130
7221
  }
7222
+ }
7223
+ if (!visionWorked) {
7224
+ let ocrText = "";
7225
+ const tess = ensureCommand("tesseract");
7226
+ if (tess.available) {
7227
+ try {
7228
+ ocrText = execSync11(`tesseract ${JSON.stringify(screenshotPath)} stdout 2>/dev/null`, {
7229
+ encoding: "utf8",
7230
+ timeout: 15e3
7231
+ }).trim();
7232
+ } catch {
7233
+ }
7234
+ }
7131
7235
  if (ocrText) {
7132
- parts.push("(Moondream vision not available \u2014 using OCR text extraction)");
7236
+ parts.push("(Vision models not available \u2014 using OCR text extraction)");
7133
7237
  parts.push(`
7134
7238
  Visible text on screen:
7135
7239
  ${ocrText}`);
7136
7240
  } else {
7137
- parts.push("(Moondream vision not available, OCR failed)");
7241
+ parts.push("(No vision backend available)");
7138
7242
  parts.push("Screenshot captured but cannot describe contents.");
7243
+ parts.push("To enable desktop vision, either:");
7244
+ parts.push(" ollama pull moondream \u2014 then Ollama handles vision");
7245
+ parts.push(" pip install moondream-station \u2014 dedicated Moondream server");
7246
+ parts.push(" sudo apt install tesseract-ocr \u2014 basic OCR text extraction");
7247
+ parts.push("");
7139
7248
  parts.push("Use shell commands to check window state instead:");
7140
7249
  parts.push(" xdotool getactivewindow getwindowname \u2014 get active window title");
7141
7250
  parts.push(" wmctrl -l \u2014 list all open windows");
7142
- parts.push(" xdg-open <url> \u2014 open a URL in the default browser");
7143
7251
  }
7144
7252
  }
7145
7253
  if (dims) {
package/dist/launcher.cjs CHANGED
@@ -1,29 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  // Node version gate — uses only ES5 syntax so it parses on any Node version.
3
- // On old Node: offers to install Node 20 via nvm with y/n prompts.
3
+ // On old Node: walks through nvm install, then Node 20, then reinstall.
4
4
  var nodeVersion = parseInt(process.versions.node, 10);
5
5
 
6
6
  if (nodeVersion < 18) {
7
7
  var os = require("os");
8
8
  var path = require("path");
9
+ var fs = require("fs");
9
10
  var childProcess = require("child_process");
10
11
  var readline = require("readline");
11
12
 
12
13
  var platform = os.platform();
13
14
  var arch = os.arch();
14
15
 
15
- console.log(
16
- "\n open-agents requires Node.js >= 18 (you have " + process.version + ")" +
17
- "\n Platform: " + platform + "/" + arch + "\n"
18
- );
16
+ console.log("");
17
+ console.log(" ┌─────────────────────────────────────────────────┐");
18
+ console.log(" │ open-agents Node.js Upgrade │");
19
+ console.log(" └─────────────────────────────────────────────────┘");
20
+ console.log("");
21
+ console.log(" Your Node.js: " + process.version);
22
+ console.log(" Required: >= 18.0.0");
23
+ console.log(" Platform: " + platform + "/" + arch);
24
+ console.log("");
19
25
 
20
- // Check if nvm is already installed
26
+ // Detect if nvm is already installed
21
27
  var nvmDir = process.env.NVM_DIR || path.join(os.homedir(), ".nvm");
22
28
  var hasNvm = false;
23
- try {
24
- require("fs").statSync(path.join(nvmDir, "nvm.sh"));
25
- hasNvm = true;
26
- } catch (e) {}
29
+ try { fs.statSync(path.join(nvmDir, "nvm.sh")); hasNvm = true; } catch (e) {}
27
30
 
28
31
  var rl = readline.createInterface({ input: process.stdin, output: process.stdout });
29
32
 
@@ -33,10 +36,10 @@ if (nodeVersion < 18) {
33
36
  });
34
37
  }
35
38
 
36
- function run(cmd, opts) {
39
+ function run(cmd) {
37
40
  console.log(" $ " + cmd);
38
41
  try {
39
- childProcess.execSync(cmd, Object.assign({ stdio: "inherit" }, opts || {}));
42
+ childProcess.execSync(cmd, { stdio: "inherit" });
40
43
  return true;
41
44
  } catch (e) {
42
45
  console.error(" Command failed: " + (e.message || e));
@@ -44,70 +47,123 @@ if (nodeVersion < 18) {
44
47
  }
45
48
  }
46
49
 
47
- function installNode() {
48
- if (!hasNvm) {
49
- console.log("\n nvm not found. Installing nvm first...\n");
50
+ function bail(msg) {
51
+ console.log(msg);
52
+ rl.close();
53
+ process.exit(1);
54
+ }
55
+
56
+ function doInstallNvm(next) {
57
+ if (hasNvm) {
58
+ console.log(" nvm found at " + nvmDir);
59
+ console.log("");
60
+ next();
61
+ return;
62
+ }
63
+ ask(" nvm is not installed. Install nvm now? [Y/n] ", function(a) {
64
+ if (a === "n" || a === "no") {
65
+ bail(
66
+ "\n Install nvm manually:\n" +
67
+ " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\n" +
68
+ " source ~/.bashrc\n" +
69
+ " nvm install 20\n" +
70
+ " npm i -g open-agents-ai\n"
71
+ );
72
+ return;
73
+ }
74
+ console.log("");
50
75
  var ok = run("curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash");
51
76
  if (!ok) {
52
- console.error("\n nvm install failed. Install manually:");
53
- console.error(" https://github.com/nvm-sh/nvm#installing-and-updating\n");
54
- rl.close();
55
- process.exit(1);
77
+ bail("\n nvm install failed. See https://github.com/nvm-sh/nvm#installing-and-updating\n");
78
+ return;
56
79
  }
57
- // Re-source nvm for this session
58
80
  nvmDir = process.env.NVM_DIR || path.join(os.homedir(), ".nvm");
59
- } else {
60
- console.log("\n nvm found at " + nvmDir + "\n");
61
- }
62
-
63
- // nvm is a shell function, so we run through bash
64
- var shell = process.env.SHELL || "/bin/bash";
65
- var nvmCmd = 'export NVM_DIR="' + nvmDir + '" && ' +
66
- '[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && ' +
67
- "nvm install 20 && nvm use 20 && nvm alias default 20";
81
+ hasNvm = true;
82
+ console.log("");
83
+ next();
84
+ });
85
+ }
68
86
 
69
- console.log(" Installing Node.js 20...\n");
70
- var ok = run(shell + ' -c \'' + nvmCmd + "'");
71
- if (!ok) {
72
- console.error("\n Node 20 install failed.\n");
73
- rl.close();
74
- process.exit(1);
75
- }
87
+ function doInstallNode(next) {
88
+ ask(" Install Node.js 20 via nvm? [Y/n] ", function(a) {
89
+ if (a === "n" || a === "no") {
90
+ bail(
91
+ "\n Install manually:\n" +
92
+ " nvm install 20 && nvm alias default 20\n" +
93
+ " npm i -g open-agents-ai\n"
94
+ );
95
+ return;
96
+ }
97
+ console.log("");
98
+ var shell = process.env.SHELL || "/bin/bash";
99
+ var cmd = 'export NVM_DIR="' + nvmDir + '" && ' +
100
+ '[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && ' +
101
+ "nvm install 20 && nvm use 20 && nvm alias default 20";
102
+ var ok = run(shell + " -c '" + cmd + "'");
103
+ if (!ok) {
104
+ bail("\n Node 20 install failed.\n");
105
+ return;
106
+ }
107
+ console.log("");
108
+ next();
109
+ });
110
+ }
76
111
 
77
- // Now reinstall open-agents under the new Node
78
- console.log("\n Node 20 installed. Reinstalling open-agents...\n");
79
- var reinstallCmd = 'export NVM_DIR="' + nvmDir + '" && ' +
80
- '[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && ' +
81
- "npm i -g open-agents-ai";
82
- var ok2 = run(shell + ' -c \'' + reinstallCmd + "'");
83
- if (!ok2) {
84
- console.error("\n Reinstall failed. After opening a new terminal, run:");
85
- console.error(" npm i -g open-agents-ai\n");
112
+ function doReinstall() {
113
+ ask(" Reinstall open-agents under Node 20? [Y/n] ", function(a) {
114
+ if (a === "n" || a === "no") {
115
+ console.log(
116
+ "\n Open a new terminal, then run:\n" +
117
+ " npm i -g open-agents-ai\n"
118
+ );
119
+ rl.close();
120
+ process.exit(0);
121
+ return;
122
+ }
123
+ console.log("");
124
+ var shell = process.env.SHELL || "/bin/bash";
125
+ var cmd = 'export NVM_DIR="' + nvmDir + '" && ' +
126
+ '[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && ' +
127
+ "nvm use 20 && npm i -g open-agents-ai";
128
+ var ok = run(shell + " -c '" + cmd + "'");
129
+ if (!ok) {
130
+ console.log(
131
+ "\n Reinstall failed. Open a new terminal, then run:\n" +
132
+ " npm i -g open-agents-ai\n"
133
+ );
134
+ rl.close();
135
+ process.exit(1);
136
+ return;
137
+ }
138
+ console.log("");
139
+ console.log(" ┌─────────────────────────────────────────────────┐");
140
+ console.log(" │ Done! Open a new terminal, then run: oa │");
141
+ console.log(" └─────────────────────────────────────────────────┘");
142
+ console.log("");
86
143
  rl.close();
87
- process.exit(1);
88
- }
89
-
90
- console.log(
91
- "\n Done! Open a new terminal (or run: source ~/.bashrc) then run:" +
92
- "\n oa\n"
93
- );
94
- rl.close();
95
- process.exit(0);
144
+ process.exit(0);
145
+ });
96
146
  }
97
147
 
98
- ask(" Install Node.js 20 now? [Y/n] ", function(answer) {
99
- if (answer === "n" || answer === "no") {
100
- console.log(
148
+ // Walk through the steps
149
+ ask(" Upgrade to Node.js 20 now? [Y/n] ", function(a) {
150
+ if (a === "n" || a === "no") {
151
+ bail(
101
152
  "\n To install manually:\n" +
102
153
  " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\n" +
103
154
  " source ~/.bashrc\n" +
104
155
  " nvm install 20\n" +
156
+ " nvm alias default 20\n" +
105
157
  " npm i -g open-agents-ai\n"
106
158
  );
107
- rl.close();
108
- process.exit(1);
159
+ return;
109
160
  }
110
- installNode();
161
+ console.log("");
162
+ doInstallNvm(function() {
163
+ doInstallNode(function() {
164
+ doReinstall();
165
+ });
166
+ });
111
167
  });
112
168
  } else {
113
169
  // Node >= 18 — load the ESM bundle
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.23.1",
3
+ "version": "0.24.0",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",