open-agents-ai 0.103.8 → 0.103.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.
Files changed (2) hide show
  1. package/dist/index.js +399 -111
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -25086,6 +25086,13 @@ function renderSlashHelp() {
25086
25086
  ["/nexus", "Show nexus P2P network status (or connect/disconnect/rooms)"],
25087
25087
  ["/nexus connect", "Connect to the agent mesh network"],
25088
25088
  ["/nexus wallet", "Show wallet address and x402 payment status"],
25089
+ ["/expose <backend>", "Share local inference via cloudflared tunnel"],
25090
+ ["/expose <backend> --key", "Share with auto-generated auth key"],
25091
+ ["/expose status", "Show gateway stats (requests, models, errors)"],
25092
+ ["/expose stop", "Stop the expose gateway"],
25093
+ ["/p2p start", "Join the P2P agent mesh network"],
25094
+ ["/p2p status", "Show mesh peers and rooms"],
25095
+ ["/p2p stop", "Leave the mesh network"],
25089
25096
  ["/style", "Show current response style"],
25090
25097
  ["/style <preset>", "Set style: concise, balanced, verbose, pedagogical"],
25091
25098
  ["/commands auto", "Allow agent to invoke slash commands as tools"],
@@ -25148,17 +25155,6 @@ function renderModelSwitch(oldModel, newModel) {
25148
25155
 
25149
25156
  `);
25150
25157
  }
25151
- function renderConfig(config) {
25152
- process.stdout.write(`
25153
- ${c2.bold("Configuration:")}
25154
-
25155
- `);
25156
- for (const [key, value] of Object.entries(config)) {
25157
- process.stdout.write(` ${c2.cyan(key.padEnd(20))} ${value}
25158
- `);
25159
- }
25160
- process.stdout.write("\n");
25161
- }
25162
25158
  function formatToolArgs(toolName, args, verbose) {
25163
25159
  const maxArg = verbose ? 1e4 : Math.max(40, getTermWidth() - 20);
25164
25160
  switch (toolName) {
@@ -26198,10 +26194,57 @@ var init_voice_session = __esm({
26198
26194
 
26199
26195
  // packages/cli/dist/tui/expose.js
26200
26196
  import { createServer as createServer2, request as httpRequest } from "node:http";
26201
- import { spawn as spawn14 } from "node:child_process";
26197
+ import { spawn as spawn14, execSync as execSync25 } from "node:child_process";
26202
26198
  import { EventEmitter as EventEmitter3 } from "node:events";
26203
26199
  import { randomBytes as randomBytes7 } from "node:crypto";
26204
26200
  import { URL as URL2 } from "node:url";
26201
+ import { loadavg, cpus, totalmem, freemem } from "node:os";
26202
+ function collectSystemMetrics() {
26203
+ const [l1, l5, l15] = loadavg();
26204
+ const cores = cpus().length;
26205
+ const totalMem = totalmem();
26206
+ const freeMem = freemem();
26207
+ const usedMem = totalMem - freeMem;
26208
+ const gpu = {
26209
+ available: false,
26210
+ name: "",
26211
+ utilization: 0,
26212
+ vramUsedMB: 0,
26213
+ vramTotalMB: 0,
26214
+ vramUtilization: 0
26215
+ };
26216
+ try {
26217
+ const smi = execSync25("nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total,name --format=csv,noheader,nounits 2>/dev/null", { encoding: "utf8", timeout: 3e3 });
26218
+ const line = smi.trim().split("\n")[0];
26219
+ if (line) {
26220
+ const parts = line.split(",").map((s) => s.trim());
26221
+ gpu.available = true;
26222
+ gpu.utilization = parseInt(parts[0] ?? "0", 10) || 0;
26223
+ gpu.vramUsedMB = parseInt(parts[1] ?? "0", 10) || 0;
26224
+ gpu.vramTotalMB = parseInt(parts[2] ?? "0", 10) || 0;
26225
+ gpu.name = parts[3] ?? "";
26226
+ gpu.vramUtilization = gpu.vramTotalMB > 0 ? Math.round(gpu.vramUsedMB / gpu.vramTotalMB * 100) : 0;
26227
+ }
26228
+ } catch {
26229
+ }
26230
+ return {
26231
+ cpu: {
26232
+ loadAvg1m: Math.round(l1 * 100) / 100,
26233
+ loadAvg5m: Math.round(l5 * 100) / 100,
26234
+ loadAvg15m: Math.round(l15 * 100) / 100,
26235
+ cores,
26236
+ utilization: Math.min(100, Math.round(l1 / cores * 100))
26237
+ },
26238
+ memory: {
26239
+ totalGB: Math.round(totalMem / 1024 ** 3 * 10) / 10,
26240
+ freeGB: Math.round(freeMem / 1024 ** 3 * 10) / 10,
26241
+ usedGB: Math.round(usedMem / 1024 ** 3 * 10) / 10,
26242
+ utilization: Math.round(usedMem / totalMem * 100)
26243
+ },
26244
+ gpu,
26245
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
26246
+ };
26247
+ }
26205
26248
  var DEFAULT_TARGETS, ExposeGateway;
26206
26249
  var init_expose = __esm({
26207
26250
  "packages/cli/dist/tui/expose.js"() {
@@ -26300,6 +26343,18 @@ var init_expose = __esm({
26300
26343
  this.emitStats();
26301
26344
  return;
26302
26345
  }
26346
+ if (url.pathname === "/v1/system/metrics" && req.method === "GET") {
26347
+ const metrics = collectSystemMetrics();
26348
+ const gatewayStats = {
26349
+ totalRequests: this._stats.totalRequests,
26350
+ activeConnections: this._stats.activeConnections,
26351
+ errors: this._stats.errors,
26352
+ modelUsage: Object.fromEntries(this._stats.modelUsage)
26353
+ };
26354
+ res.writeHead(200, { "Content-Type": "application/json" });
26355
+ res.end(JSON.stringify({ ...metrics, gateway: gatewayStats }));
26356
+ return;
26357
+ }
26303
26358
  this._stats.totalRequests++;
26304
26359
  this._stats.activeConnections++;
26305
26360
  this.emitStats();
@@ -28563,7 +28618,7 @@ var init_oa_directory = __esm({
28563
28618
 
28564
28619
  // packages/cli/dist/tui/setup.js
28565
28620
  import * as readline from "node:readline";
28566
- import { execSync as execSync25, spawn as spawn15 } from "node:child_process";
28621
+ import { execSync as execSync26, spawn as spawn15 } from "node:child_process";
28567
28622
  import { existsSync as existsSync29, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10 } from "node:fs";
28568
28623
  import { join as join39 } from "node:path";
28569
28624
  import { homedir as homedir10, platform } from "node:os";
@@ -28573,7 +28628,7 @@ function detectSystemSpecs() {
28573
28628
  let gpuVramGB = 0;
28574
28629
  let gpuName = "";
28575
28630
  try {
28576
- const memInfo = execSync25("free -b 2>/dev/null || sysctl -n hw.memsize 2>/dev/null", {
28631
+ const memInfo = execSync26("free -b 2>/dev/null || sysctl -n hw.memsize 2>/dev/null", {
28577
28632
  encoding: "utf8",
28578
28633
  timeout: 5e3
28579
28634
  });
@@ -28593,7 +28648,7 @@ function detectSystemSpecs() {
28593
28648
  } catch {
28594
28649
  }
28595
28650
  try {
28596
- const nvidiaSmi = execSync25("nvidia-smi --query-gpu=memory.total,name --format=csv,noheader,nounits 2>/dev/null", { encoding: "utf8", timeout: 5e3 });
28651
+ const nvidiaSmi = execSync26("nvidia-smi --query-gpu=memory.total,name --format=csv,noheader,nounits 2>/dev/null", { encoding: "utf8", timeout: 5e3 });
28597
28652
  const lines = nvidiaSmi.trim().split("\n");
28598
28653
  if (lines.length > 0) {
28599
28654
  for (const line of lines) {
@@ -28718,7 +28773,7 @@ function ensureCurl() {
28718
28773
  for (const s of strategies) {
28719
28774
  if (hasCmd(s.check)) {
28720
28775
  try {
28721
- execSync25(s.install, { stdio: "inherit", timeout: 12e4 });
28776
+ execSync26(s.install, { stdio: "inherit", timeout: 12e4 });
28722
28777
  if (hasCmd("curl")) {
28723
28778
  process.stdout.write(` ${c2.green("\u2714")} curl installed via ${s.label}.
28724
28779
  `);
@@ -28732,7 +28787,7 @@ function ensureCurl() {
28732
28787
  }
28733
28788
  if (plat === "darwin") {
28734
28789
  try {
28735
- execSync25("xcode-select --install", { stdio: "inherit", timeout: 3e5 });
28790
+ execSync26("xcode-select --install", { stdio: "inherit", timeout: 3e5 });
28736
28791
  if (hasCmd("curl"))
28737
28792
  return true;
28738
28793
  } catch {
@@ -28767,7 +28822,7 @@ function installOllamaLinux() {
28767
28822
 
28768
28823
  `);
28769
28824
  try {
28770
- execSync25("curl -fsSL https://ollama.com/install.sh | sh", {
28825
+ execSync26("curl -fsSL https://ollama.com/install.sh | sh", {
28771
28826
  stdio: "inherit",
28772
28827
  timeout: 3e5
28773
28828
  });
@@ -28795,7 +28850,7 @@ async function installOllamaMac(_rl) {
28795
28850
 
28796
28851
  `);
28797
28852
  try {
28798
- execSync25('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"', { stdio: "inherit", timeout: 6e5 });
28853
+ execSync26('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"', { stdio: "inherit", timeout: 6e5 });
28799
28854
  if (!hasCmd("brew")) {
28800
28855
  try {
28801
28856
  const brewPrefix = existsSync29("/opt/homebrew/bin/brew") ? "/opt/homebrew" : "/usr/local";
@@ -28828,7 +28883,7 @@ async function installOllamaMac(_rl) {
28828
28883
 
28829
28884
  `);
28830
28885
  try {
28831
- execSync25("brew install ollama", {
28886
+ execSync26("brew install ollama", {
28832
28887
  stdio: "inherit",
28833
28888
  timeout: 3e5
28834
28889
  });
@@ -28855,7 +28910,7 @@ function installOllamaWindows() {
28855
28910
 
28856
28911
  `);
28857
28912
  try {
28858
- execSync25('powershell -Command "irm https://ollama.com/install.ps1 | iex"', {
28913
+ execSync26('powershell -Command "irm https://ollama.com/install.ps1 | iex"', {
28859
28914
  stdio: "inherit",
28860
28915
  timeout: 3e5
28861
28916
  });
@@ -28936,7 +28991,7 @@ async function ensureOllamaRunning(backendUrl, rl) {
28936
28991
  }
28937
28992
  function pullModelWithAutoUpdate(tag) {
28938
28993
  try {
28939
- execSync25(`ollama pull ${tag}`, {
28994
+ execSync26(`ollama pull ${tag}`, {
28940
28995
  stdio: "inherit",
28941
28996
  timeout: 36e5
28942
28997
  // 1 hour max
@@ -28956,7 +29011,7 @@ function pullModelWithAutoUpdate(tag) {
28956
29011
 
28957
29012
  `);
28958
29013
  try {
28959
- execSync25("curl -fsSL https://ollama.com/install.sh | sh", {
29014
+ execSync26("curl -fsSL https://ollama.com/install.sh | sh", {
28960
29015
  stdio: "inherit",
28961
29016
  timeout: 3e5
28962
29017
  // 5 min max for install
@@ -28967,7 +29022,7 @@ function pullModelWithAutoUpdate(tag) {
28967
29022
  process.stdout.write(` ${c2.cyan("\u25CF")} Retrying pull of ${c2.bold(tag)}...
28968
29023
 
28969
29024
  `);
28970
- execSync25(`ollama pull ${tag}`, {
29025
+ execSync26(`ollama pull ${tag}`, {
28971
29026
  stdio: "inherit",
28972
29027
  timeout: 36e5
28973
29028
  });
@@ -29069,7 +29124,7 @@ function ensurePython3() {
29069
29124
  if (plat === "darwin") {
29070
29125
  if (hasCmd("brew")) {
29071
29126
  try {
29072
- execSync25("brew install python3", { stdio: "inherit", timeout: 3e5 });
29127
+ execSync26("brew install python3", { stdio: "inherit", timeout: 3e5 });
29073
29128
  if (hasCmd("python3")) {
29074
29129
  process.stdout.write(` ${c2.green("\u2714")} Python3 installed via Homebrew.
29075
29130
  `);
@@ -29082,7 +29137,7 @@ function ensurePython3() {
29082
29137
  for (const s of strategies) {
29083
29138
  if (hasCmd(s.check)) {
29084
29139
  try {
29085
- execSync25(s.install, { stdio: "inherit", timeout: 12e4 });
29140
+ execSync26(s.install, { stdio: "inherit", timeout: 12e4 });
29086
29141
  if (hasCmd("python3") || hasCmd("python")) {
29087
29142
  process.stdout.write(` ${c2.green("\u2714")} Python3 installed via ${s.label}.
29088
29143
  `);
@@ -29098,11 +29153,11 @@ function ensurePython3() {
29098
29153
  }
29099
29154
  function checkPythonVenv() {
29100
29155
  try {
29101
- execSync25("python3 -m venv --help", { stdio: "pipe", timeout: 5e3 });
29156
+ execSync26("python3 -m venv --help", { stdio: "pipe", timeout: 5e3 });
29102
29157
  return true;
29103
29158
  } catch {
29104
29159
  try {
29105
- execSync25("python -m venv --help", { stdio: "pipe", timeout: 5e3 });
29160
+ execSync26("python -m venv --help", { stdio: "pipe", timeout: 5e3 });
29106
29161
  return true;
29107
29162
  } catch {
29108
29163
  return false;
@@ -29121,7 +29176,7 @@ function ensurePythonVenv() {
29121
29176
  for (const s of strategies) {
29122
29177
  if (hasCmd(s.check)) {
29123
29178
  try {
29124
- execSync25(s.install, { stdio: "inherit", timeout: 12e4 });
29179
+ execSync26(s.install, { stdio: "inherit", timeout: 12e4 });
29125
29180
  if (checkPythonVenv()) {
29126
29181
  process.stdout.write(` ${c2.green("\u2714")} python3-venv installed via ${s.label}.
29127
29182
  `);
@@ -29557,7 +29612,7 @@ async function doSetup(config, rl) {
29557
29612
  const modelfilePath = join39(modelDir2, `Modelfile.${customName}`);
29558
29613
  writeFileSync10(modelfilePath, modelfileContent + "\n", "utf8");
29559
29614
  process.stdout.write(` ${c2.dim("Creating model...")} `);
29560
- execSync25(`ollama create ${customName} -f ${modelfilePath}`, {
29615
+ execSync26(`ollama create ${customName} -f ${modelfilePath}`, {
29561
29616
  stdio: "pipe",
29562
29617
  timeout: 12e4
29563
29618
  });
@@ -29608,7 +29663,7 @@ function isFirstRun() {
29608
29663
  function hasCmd(cmd) {
29609
29664
  try {
29610
29665
  const whichCmd = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
29611
- execSync25(whichCmd, { stdio: "pipe", timeout: 3e3 });
29666
+ execSync26(whichCmd, { stdio: "pipe", timeout: 3e3 });
29612
29667
  return true;
29613
29668
  } catch {
29614
29669
  return false;
@@ -29641,7 +29696,7 @@ function getVenvDir() {
29641
29696
  }
29642
29697
  function hasVenvModule() {
29643
29698
  try {
29644
- execSync25("python3 -m venv --help", { stdio: "pipe", timeout: 5e3 });
29699
+ execSync26("python3 -m venv --help", { stdio: "pipe", timeout: 5e3 });
29645
29700
  return true;
29646
29701
  } catch {
29647
29702
  return false;
@@ -29663,8 +29718,8 @@ function ensureVenv(log) {
29663
29718
  }
29664
29719
  try {
29665
29720
  mkdirSync10(join39(homedir10(), ".open-agents"), { recursive: true });
29666
- execSync25(`python3 -m venv "${venvDir}"`, { stdio: "pipe", timeout: 3e4 });
29667
- execSync25(`"${join39(venvDir, "bin", "pip")}" install --upgrade pip`, {
29721
+ execSync26(`python3 -m venv "${venvDir}"`, { stdio: "pipe", timeout: 3e4 });
29722
+ execSync26(`"${join39(venvDir, "bin", "pip")}" install --upgrade pip`, {
29668
29723
  stdio: "pipe",
29669
29724
  timeout: 6e4
29670
29725
  });
@@ -29677,7 +29732,7 @@ function ensureVenv(log) {
29677
29732
  }
29678
29733
  function trySudoPasswordless(cmd, timeoutMs = 12e4) {
29679
29734
  try {
29680
- execSync25(`sudo -n ${cmd}`, {
29735
+ execSync26(`sudo -n ${cmd}`, {
29681
29736
  stdio: "pipe",
29682
29737
  timeout: timeoutMs,
29683
29738
  env: { ...process.env, DEBIAN_FRONTEND: "noninteractive" }
@@ -29689,7 +29744,7 @@ function trySudoPasswordless(cmd, timeoutMs = 12e4) {
29689
29744
  }
29690
29745
  function runWithSudo(cmd, password, timeoutMs = 12e4) {
29691
29746
  try {
29692
- execSync25(`sudo -S ${cmd}`, {
29747
+ execSync26(`sudo -S ${cmd}`, {
29693
29748
  input: password + "\n",
29694
29749
  stdio: ["pipe", "pipe", "pipe"],
29695
29750
  timeout: timeoutMs,
@@ -29796,7 +29851,7 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
29796
29851
  ok = await sudoInstall(batchCmd, getPassword, log, cachedPasswordRef, 18e4);
29797
29852
  } else {
29798
29853
  try {
29799
- execSync25(batchCmd, { stdio: "pipe", timeout: 18e4 });
29854
+ execSync26(batchCmd, { stdio: "pipe", timeout: 18e4 });
29800
29855
  ok = true;
29801
29856
  } catch {
29802
29857
  ok = false;
@@ -29833,7 +29888,7 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
29833
29888
  const venvCmds = {
29834
29889
  apt: () => {
29835
29890
  try {
29836
- const pyVer = execSync25(`python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 }).trim();
29891
+ const pyVer = execSync26(`python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 }).trim();
29837
29892
  return `apt-get install -y python3-venv python${pyVer}-venv`;
29838
29893
  } catch {
29839
29894
  return "apt-get install -y python3-venv";
@@ -29861,12 +29916,12 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
29861
29916
  const venvPip = join39(venvBin, "pip");
29862
29917
  log("Installing moondream-station in ~/.open-agents/venv...");
29863
29918
  try {
29864
- execSync25(`"${venvPip}" install moondream-station`, { stdio: "pipe", timeout: 3e5 });
29919
+ execSync26(`"${venvPip}" install moondream-station`, { stdio: "pipe", timeout: 3e5 });
29865
29920
  if (existsSync29(venvMoondream)) {
29866
29921
  log("moondream-station installed successfully.");
29867
29922
  } else {
29868
29923
  try {
29869
- const check = execSync25(`"${venvPip}" show moondream-station`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 });
29924
+ const check = execSync26(`"${venvPip}" show moondream-station`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 });
29870
29925
  if (check.includes("moondream")) {
29871
29926
  log("moondream-station package installed.");
29872
29927
  }
@@ -29883,7 +29938,7 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
29883
29938
  const venvPip2 = join39(venvBin, "pip");
29884
29939
  let ocrStackInstalled = false;
29885
29940
  try {
29886
- execSync25(`"${venvPython}" -c "import cv2, pytesseract, numpy, PIL"`, { stdio: "pipe", timeout: 1e4 });
29941
+ execSync26(`"${venvPython}" -c "import cv2, pytesseract, numpy, PIL"`, { stdio: "pipe", timeout: 1e4 });
29887
29942
  ocrStackInstalled = true;
29888
29943
  } catch {
29889
29944
  }
@@ -29891,9 +29946,9 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
29891
29946
  const ocrPackages = "pytesseract Pillow opencv-python-headless numpy";
29892
29947
  log("Installing OCR Python stack (pytesseract, OpenCV, Pillow, numpy)...");
29893
29948
  try {
29894
- execSync25(`"${venvPip2}" install ${ocrPackages}`, { stdio: "pipe", timeout: 3e5 });
29949
+ execSync26(`"${venvPip2}" install ${ocrPackages}`, { stdio: "pipe", timeout: 3e5 });
29895
29950
  try {
29896
- execSync25(`"${venvPython}" -c "import cv2, pytesseract, numpy, PIL"`, { stdio: "pipe", timeout: 1e4 });
29951
+ execSync26(`"${venvPython}" -c "import cv2, pytesseract, numpy, PIL"`, { stdio: "pipe", timeout: 1e4 });
29897
29952
  log("OCR Python stack installed successfully.");
29898
29953
  } catch {
29899
29954
  log("OCR Python stack install completed but import verification failed.");
@@ -29927,7 +29982,7 @@ function ensureCloudflaredBackground(onInfo) {
29927
29982
  const archMap = { x64: "amd64", arm64: "arm64", arm: "arm" };
29928
29983
  const cfArch = archMap[arch] ?? "amd64";
29929
29984
  try {
29930
- execSync25(`curl -fsSL "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${cfArch}" -o /tmp/cloudflared && chmod +x /tmp/cloudflared && mkdir -p "${homedir10()}/.local/bin" && mv /tmp/cloudflared "${homedir10()}/.local/bin/cloudflared"`, { stdio: "pipe", timeout: 6e4 });
29985
+ execSync26(`curl -fsSL "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${cfArch}" -o /tmp/cloudflared && chmod +x /tmp/cloudflared && mkdir -p "${homedir10()}/.local/bin" && mv /tmp/cloudflared "${homedir10()}/.local/bin/cloudflared"`, { stdio: "pipe", timeout: 6e4 });
29931
29986
  if (!process.env.PATH?.includes(`${homedir10()}/.local/bin`)) {
29932
29987
  process.env.PATH = `${homedir10()}/.local/bin:${process.env.PATH}`;
29933
29988
  }
@@ -29938,7 +29993,7 @@ function ensureCloudflaredBackground(onInfo) {
29938
29993
  } catch {
29939
29994
  }
29940
29995
  try {
29941
- execSync25(`curl -fsSL "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${cfArch}" -o /tmp/cloudflared && chmod +x /tmp/cloudflared && sudo mv /tmp/cloudflared /usr/local/bin/cloudflared 2>/dev/null`, { stdio: "pipe", timeout: 6e4 });
29996
+ execSync26(`curl -fsSL "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${cfArch}" -o /tmp/cloudflared && chmod +x /tmp/cloudflared && sudo mv /tmp/cloudflared /usr/local/bin/cloudflared 2>/dev/null`, { stdio: "pipe", timeout: 6e4 });
29942
29997
  if (hasCmd("cloudflared")) {
29943
29998
  log("cloudflared installed.");
29944
29999
  return true;
@@ -29947,7 +30002,7 @@ function ensureCloudflaredBackground(onInfo) {
29947
30002
  }
29948
30003
  } else if (os === "darwin") {
29949
30004
  try {
29950
- execSync25("brew install cloudflared", { stdio: "pipe", timeout: 12e4 });
30005
+ execSync26("brew install cloudflared", { stdio: "pipe", timeout: 12e4 });
29951
30006
  if (hasCmd("cloudflared")) {
29952
30007
  log("cloudflared installed via Homebrew.");
29953
30008
  return true;
@@ -29997,7 +30052,7 @@ function createExpandedVariant(baseModel, specs, sizeGB) {
29997
30052
  mkdirSync10(modelDir2, { recursive: true });
29998
30053
  const modelfilePath = join39(modelDir2, `Modelfile.${customName}`);
29999
30054
  writeFileSync10(modelfilePath, modelfileContent + "\n", "utf8");
30000
- execSync25(`ollama create ${customName} -f ${modelfilePath}`, {
30055
+ execSync26(`ollama create ${customName} -f ${modelfilePath}`, {
30001
30056
  stdio: "pipe",
30002
30057
  timeout: 12e4
30003
30058
  });
@@ -30250,7 +30305,8 @@ function tuiSelect(opts) {
30250
30305
  lines.push(` ${selectColors.dim(` \u25BC ${remaining} more`)}`);
30251
30306
  }
30252
30307
  lines.push("");
30253
- lines.push(` ${selectColors.dim("\u2191/\u2193 navigate Enter select Esc " + (filter ? "clear filter" : "cancel") + " Type to filter")}`);
30308
+ const actionHint = opts.onAction ? " \u2190/\u2192/Space toggle" : "";
30309
+ lines.push(` ${selectColors.dim("\u2191/\u2193 navigate Enter select" + actionHint + " Esc " + (filter ? "clear filter" : "cancel") + " Type to filter")}`);
30254
30310
  lines.push("");
30255
30311
  const output = lines.join("\n");
30256
30312
  process.stdout.write(output);
@@ -30320,6 +30376,21 @@ function tuiSelect(opts) {
30320
30376
  cursor = last;
30321
30377
  render();
30322
30378
  }
30379
+ } else if (seq === "\x1B[D" && opts.onAction) {
30380
+ if (!isSkippable(cursor) && matchSet.has(cursor)) {
30381
+ if (opts.onAction(items[cursor], "left"))
30382
+ render();
30383
+ }
30384
+ } else if (seq === "\x1B[C" && opts.onAction) {
30385
+ if (!isSkippable(cursor) && matchSet.has(cursor)) {
30386
+ if (opts.onAction(items[cursor], "right"))
30387
+ render();
30388
+ }
30389
+ } else if (seq === " " && opts.onAction) {
30390
+ if (!isSkippable(cursor) && matchSet.has(cursor)) {
30391
+ if (opts.onAction(items[cursor], "space"))
30392
+ render();
30393
+ }
30323
30394
  } else if (seq === "\r" || seq === "\n") {
30324
30395
  if (!isSkippable(cursor) && matchSet.has(cursor)) {
30325
30396
  cleanup();
@@ -30426,38 +30497,7 @@ async function handleSlashCommand(input, ctx) {
30426
30497
  return "handled";
30427
30498
  case "config":
30428
30499
  case "cfg": {
30429
- const resolved = loadProjectSettings(ctx.repoRoot);
30430
- const globalS = loadGlobalSettings();
30431
- const merged = { ...globalS, ...resolved };
30432
- renderConfig({
30433
- // -- Core inference --
30434
- model: ctx.config.model,
30435
- backendType: ctx.config.backendType,
30436
- backendUrl: ctx.config.backendUrl,
30437
- apiKey: ctx.config.apiKey ? "[set]" : "[not set]",
30438
- maxRetries: String(ctx.config.maxRetries),
30439
- timeoutMs: String(ctx.config.timeoutMs),
30440
- dbPath: ctx.config.dbPath,
30441
- // -- Behaviour --
30442
- dryRun: String(ctx.config.dryRun),
30443
- verbose: String(ctx.config.verbose),
30444
- stream: String(merged.stream ?? false),
30445
- bruteforce: String(merged.bruteforce ?? false),
30446
- deepContext: String(merged.deepContext ?? false),
30447
- // -- Presentation --
30448
- emojis: String(ctx.getEmojis?.() ?? merged.emojis ?? true),
30449
- colors: String(ctx.getColors?.() ?? merged.colors ?? true),
30450
- style: String(ctx.getStyle?.() ?? merged.style ?? "balanced"),
30451
- // -- Voice --
30452
- voice: String(merged.voice ?? false),
30453
- voiceModel: String(merged.voiceModel ?? "glados"),
30454
- // -- Autonomy --
30455
- commandsMode: String(ctx.getCommandsMode?.() ?? merged.commandsMode ?? "manual"),
30456
- updateMode: String(merged.updateMode ?? "auto"),
30457
- // -- Integrations --
30458
- telegramKey: merged.telegramKey ? "[set]" : "[not set]",
30459
- telegramAdmin: String(merged.telegramAdmin ?? "[not set]")
30460
- });
30500
+ await showConfigEditor(ctx);
30461
30501
  return "handled";
30462
30502
  }
30463
30503
  case "cost":
@@ -30871,25 +30911,26 @@ async function handleSlashCommand(input, ctx) {
30871
30911
  }
30872
30912
  case "commands":
30873
30913
  case "cmds": {
30874
- if (!ctx.getCommandsMode || !ctx.setCommandsMode) {
30875
- renderWarning("Commands mode not available in this context.");
30876
- return "handled";
30877
- }
30878
30914
  if (arg === "auto") {
30915
+ if (!ctx.setCommandsMode) {
30916
+ renderWarning("Commands mode not available.");
30917
+ return "handled";
30918
+ }
30879
30919
  ctx.setCommandsMode("auto");
30880
30920
  const save = hasLocal ? ctx.saveLocalSettings.bind(ctx) : ctx.saveSettings.bind(ctx);
30881
30921
  save({ commandsMode: "auto" });
30882
30922
  renderInfo("Commands mode: auto \u2014 the agent can now invoke slash commands as tools.");
30883
30923
  } else if (arg === "manual") {
30924
+ if (!ctx.setCommandsMode) {
30925
+ renderWarning("Commands mode not available.");
30926
+ return "handled";
30927
+ }
30884
30928
  ctx.setCommandsMode("manual");
30885
30929
  const save = hasLocal ? ctx.saveLocalSettings.bind(ctx) : ctx.saveSettings.bind(ctx);
30886
30930
  save({ commandsMode: "manual" });
30887
30931
  renderInfo("Commands mode: manual \u2014 slash commands are user-only.");
30888
30932
  } else {
30889
- const current = ctx.getCommandsMode();
30890
- renderInfo(`Commands mode: ${c2.bold(current)}`);
30891
- renderInfo(" /commands auto \u2014 agent can invoke slash commands as tools");
30892
- renderInfo(" /commands manual \u2014 slash commands are user-only (default)");
30933
+ renderSlashHelp();
30893
30934
  }
30894
30935
  return "handled";
30895
30936
  }
@@ -31496,6 +31537,162 @@ async function handleSlashCommand(input, ctx) {
31496
31537
  }
31497
31538
  }
31498
31539
  }
31540
+ async function showConfigEditor(ctx) {
31541
+ const merged = { ...loadGlobalSettings(), ...loadProjectSettings(ctx.repoRoot) };
31542
+ const pendingChanges = {};
31543
+ let hasChanges = false;
31544
+ const entries = [
31545
+ // -- Headers & categories --
31546
+ { key: "__h_core__", label: c2.dim("\u2500\u2500 Core Inference \u2500\u2500"), detail: "", kind: "header", value: "" },
31547
+ { key: "model", label: "model", kind: "readonly", value: ctx.config.model, detail: ctx.config.model },
31548
+ { key: "backendUrl", label: "backendUrl", kind: "readonly", value: ctx.config.backendUrl, detail: ctx.config.backendUrl },
31549
+ { key: "backendType", label: "backendType", kind: "readonly", value: ctx.config.backendType, detail: ctx.config.backendType },
31550
+ { key: "apiKey", label: "apiKey", kind: "readonly", value: ctx.config.apiKey ? "[set]" : "[not set]", detail: ctx.config.apiKey ? "[set]" : "[not set]" },
31551
+ { key: "maxRetries", label: "maxRetries", kind: "string", value: String(ctx.config.maxRetries), detail: String(ctx.config.maxRetries), settingsKey: "maxRetries" },
31552
+ { key: "timeoutMs", label: "timeoutMs", kind: "string", value: String(ctx.config.timeoutMs), detail: String(ctx.config.timeoutMs), settingsKey: "timeoutMs" },
31553
+ { key: "__h_behav__", label: c2.dim("\u2500\u2500 Behaviour \u2500\u2500"), detail: "", kind: "header", value: "" },
31554
+ { key: "verbose", label: "verbose", kind: "boolean", value: String(ctx.config.verbose), detail: String(ctx.config.verbose), settingsKey: "verbose" },
31555
+ { key: "stream", label: "stream", kind: "boolean", value: String(merged.stream ?? false), detail: String(merged.stream ?? false), settingsKey: "stream" },
31556
+ { key: "bruteforce", label: "bruteforce", kind: "boolean", value: String(merged.bruteforce ?? false), detail: String(merged.bruteforce ?? false), settingsKey: "bruteforce" },
31557
+ { key: "deepContext", label: "deepContext", kind: "boolean", value: String(merged.deepContext ?? false), detail: String(merged.deepContext ?? false), settingsKey: "deepContext" },
31558
+ { key: "dryRun", label: "dryRun", kind: "boolean", value: String(ctx.config.dryRun), detail: String(ctx.config.dryRun), settingsKey: "dryRun" },
31559
+ { key: "__h_pres__", label: c2.dim("\u2500\u2500 Presentation \u2500\u2500"), detail: "", kind: "header", value: "" },
31560
+ { key: "emojis", label: "emojis", kind: "boolean", value: String(ctx.getEmojis?.() ?? merged.emojis ?? true), detail: String(ctx.getEmojis?.() ?? merged.emojis ?? true), settingsKey: "emojis" },
31561
+ { key: "colors", label: "colors", kind: "boolean", value: String(ctx.getColors?.() ?? merged.colors ?? true), detail: String(ctx.getColors?.() ?? merged.colors ?? true), settingsKey: "colors" },
31562
+ { key: "style", label: "style", kind: "enum", value: String(ctx.getStyle?.() ?? merged.style ?? "balanced"), detail: String(ctx.getStyle?.() ?? merged.style ?? "balanced"), options: [...PRESET_NAMES], settingsKey: "style" },
31563
+ { key: "__h_voice__", label: c2.dim("\u2500\u2500 Voice \u2500\u2500"), detail: "", kind: "header", value: "" },
31564
+ { key: "voice", label: "voice", kind: "boolean", value: String(merged.voice ?? false), detail: String(merged.voice ?? false), settingsKey: "voice" },
31565
+ { key: "voiceModel", label: "voiceModel", kind: "enum", value: String(merged.voiceModel ?? "glados"), detail: String(merged.voiceModel ?? "glados"), options: ["glados", "overwatch"], settingsKey: "voiceModel" },
31566
+ { key: "__h_auto__", label: c2.dim("\u2500\u2500 Autonomy \u2500\u2500"), detail: "", kind: "header", value: "" },
31567
+ { key: "commandsMode", label: "commandsMode", kind: "enum", value: String(ctx.getCommandsMode?.() ?? merged.commandsMode ?? "manual"), detail: String(ctx.getCommandsMode?.() ?? merged.commandsMode ?? "manual"), options: ["manual", "auto"], settingsKey: "commandsMode" },
31568
+ { key: "updateMode", label: "updateMode", kind: "enum", value: String(merged.updateMode ?? "auto"), detail: String(merged.updateMode ?? "auto"), options: ["auto", "manual"], settingsKey: "updateMode" },
31569
+ { key: "__h_int__", label: c2.dim("\u2500\u2500 Integrations \u2500\u2500"), detail: "", kind: "header", value: "" },
31570
+ { key: "telegramKey", label: "telegramKey", kind: "readonly", value: merged.telegramKey ? "[set]" : "[not set]", detail: merged.telegramKey ? "[set]" : "[not set]" },
31571
+ { key: "telegramAdmin", label: "telegramAdmin", kind: "readonly", value: String(merged.telegramAdmin ?? "[not set]"), detail: String(merged.telegramAdmin ?? "[not set]") },
31572
+ // -- Actions --
31573
+ { key: "__h_actions__", label: c2.dim("\u2500\u2500 Actions \u2500\u2500"), detail: "", kind: "header", value: "" },
31574
+ { key: "__save_global__", label: c2.bold("Save to global settings"), detail: "~/.open-agents/settings.json", kind: "action", value: "save_global" },
31575
+ { key: "__save_local__", label: c2.bold("Save to project settings"), detail: ".oa/settings.json", kind: "action", value: "save_local" }
31576
+ ];
31577
+ function renderConfigRow(item, focused, _isActive) {
31578
+ if (item.kind === "header") {
31579
+ return ` ${item.label}`;
31580
+ }
31581
+ const marker = focused ? selectColors.orange("\u25CF") : selectColors.dim("\u25CB");
31582
+ const nameStr = focused ? selectColors.orange(selectColors.bold(item.label)) : item.label;
31583
+ if (item.kind === "action") {
31584
+ return ` ${marker} ${nameStr} ${selectColors.dim(item.detail ?? "")}`;
31585
+ }
31586
+ let valueStr;
31587
+ if (item.kind === "boolean") {
31588
+ const val = item.value === "true";
31589
+ valueStr = val ? selectColors.green("true") : c2.red("false");
31590
+ if (focused)
31591
+ valueStr += selectColors.dim(" \u2190/\u2192/Space toggle");
31592
+ } else if (item.kind === "enum") {
31593
+ valueStr = selectColors.cyan(item.value);
31594
+ if (focused && item.options) {
31595
+ const idx = item.options.indexOf(item.value);
31596
+ const prev = item.options[(idx - 1 + item.options.length) % item.options.length];
31597
+ const next = item.options[(idx + 1) % item.options.length];
31598
+ valueStr += selectColors.dim(` \u2190 ${prev} | ${next} \u2192`);
31599
+ }
31600
+ } else if (item.kind === "readonly") {
31601
+ valueStr = selectColors.dim(item.value);
31602
+ } else {
31603
+ valueStr = item.value;
31604
+ if (focused)
31605
+ valueStr += selectColors.dim(" Enter to edit");
31606
+ }
31607
+ return ` ${marker} ${nameStr.padEnd(focused ? 20 : 20)} ${valueStr}`;
31608
+ }
31609
+ function onAction(item, action) {
31610
+ if (item.kind === "boolean") {
31611
+ const newVal = item.value === "true" ? "false" : "true";
31612
+ item.value = newVal;
31613
+ item.detail = newVal;
31614
+ if (item.settingsKey) {
31615
+ pendingChanges[item.settingsKey] = newVal === "true";
31616
+ }
31617
+ hasChanges = true;
31618
+ return true;
31619
+ }
31620
+ if (item.kind === "enum" && item.options) {
31621
+ const idx = item.options.indexOf(item.value);
31622
+ let newIdx;
31623
+ if (action === "left") {
31624
+ newIdx = (idx - 1 + item.options.length) % item.options.length;
31625
+ } else {
31626
+ newIdx = (idx + 1) % item.options.length;
31627
+ }
31628
+ item.value = item.options[newIdx];
31629
+ item.detail = item.value;
31630
+ if (item.settingsKey) {
31631
+ pendingChanges[item.settingsKey] = item.value;
31632
+ }
31633
+ hasChanges = true;
31634
+ return true;
31635
+ }
31636
+ return false;
31637
+ }
31638
+ const skipKeys = entries.filter((e) => e.kind === "header").map((e) => e.key);
31639
+ const result = await tuiSelect({
31640
+ items: entries,
31641
+ title: "Configuration",
31642
+ rl: ctx.rl,
31643
+ skipKeys,
31644
+ renderRow: renderConfigRow,
31645
+ onAction
31646
+ });
31647
+ if (result.confirmed && result.key) {
31648
+ const entry = entries.find((e) => e.key === result.key);
31649
+ if (entry?.kind === "action") {
31650
+ if (entry.value === "save_global") {
31651
+ applyConfigChanges(ctx, pendingChanges);
31652
+ ctx.saveSettings(pendingChanges);
31653
+ renderInfo("Settings saved globally.");
31654
+ hasChanges = false;
31655
+ return;
31656
+ }
31657
+ if (entry.value === "save_local") {
31658
+ applyConfigChanges(ctx, pendingChanges);
31659
+ ctx.saveLocalSettings(pendingChanges);
31660
+ renderInfo("Settings saved to project.");
31661
+ hasChanges = false;
31662
+ return;
31663
+ }
31664
+ }
31665
+ if (entry?.kind === "boolean" || entry?.kind === "enum") {
31666
+ onAction(entry, "right");
31667
+ applyConfigChanges(ctx, pendingChanges);
31668
+ ctx.saveSettings(pendingChanges);
31669
+ renderInfo(`${entry.key} set to ${c2.bold(entry.value)}`);
31670
+ return;
31671
+ }
31672
+ if (entry?.kind === "readonly") {
31673
+ renderInfo(`${entry.key} is read-only. Use the dedicated command (e.g. /model, /endpoint).`);
31674
+ return;
31675
+ }
31676
+ }
31677
+ if (hasChanges) {
31678
+ renderWarning("Unsaved config changes. Saving to global settings...");
31679
+ applyConfigChanges(ctx, pendingChanges);
31680
+ ctx.saveSettings(pendingChanges);
31681
+ renderInfo("Changes saved.");
31682
+ }
31683
+ }
31684
+ function applyConfigChanges(ctx, changes) {
31685
+ if (changes.verbose !== void 0)
31686
+ ctx.setVerbose(changes.verbose);
31687
+ if (changes.emojis !== void 0)
31688
+ ctx.setEmojis?.(changes.emojis);
31689
+ if (changes.colors !== void 0)
31690
+ ctx.setColors?.(changes.colors);
31691
+ if (changes.style !== void 0)
31692
+ ctx.setStyle?.(changes.style);
31693
+ if (changes.commandsMode !== void 0)
31694
+ ctx.setCommandsMode?.(changes.commandsMode);
31695
+ }
31499
31696
  async function listModels(ctx) {
31500
31697
  try {
31501
31698
  const models = await fetchModels(ctx.config.backendUrl, ctx.config.apiKey);
@@ -32052,7 +32249,7 @@ var init_commands = __esm({
32052
32249
  // packages/cli/dist/tui/project-context.js
32053
32250
  import { existsSync as existsSync30, readFileSync as readFileSync21, readdirSync as readdirSync8 } from "node:fs";
32054
32251
  import { join as join40, basename as basename10 } from "node:path";
32055
- import { execSync as execSync26 } from "node:child_process";
32252
+ import { execSync as execSync27 } from "node:child_process";
32056
32253
  import { homedir as homedir11, platform as platform2, release } from "node:os";
32057
32254
  function getModelTier(modelName) {
32058
32255
  const m = modelName.toLowerCase();
@@ -32098,19 +32295,19 @@ function loadProjectMap(repoRoot) {
32098
32295
  }
32099
32296
  function getGitInfo(repoRoot) {
32100
32297
  try {
32101
- execSync26("git rev-parse --is-inside-work-tree", { cwd: repoRoot, stdio: "pipe" });
32298
+ execSync27("git rev-parse --is-inside-work-tree", { cwd: repoRoot, stdio: "pipe" });
32102
32299
  } catch {
32103
32300
  return "";
32104
32301
  }
32105
32302
  const lines = [];
32106
32303
  try {
32107
- const branch = execSync26("git branch --show-current", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
32304
+ const branch = execSync27("git branch --show-current", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
32108
32305
  if (branch)
32109
32306
  lines.push(`Branch: ${branch}`);
32110
32307
  } catch {
32111
32308
  }
32112
32309
  try {
32113
- const status = execSync26("git status --porcelain", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
32310
+ const status = execSync27("git status --porcelain", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
32114
32311
  if (status) {
32115
32312
  const changed = status.split("\n").length;
32116
32313
  lines.push(`Working tree: ${changed} changed file(s)`);
@@ -32120,7 +32317,7 @@ function getGitInfo(repoRoot) {
32120
32317
  } catch {
32121
32318
  }
32122
32319
  try {
32123
- const log = execSync26("git log --oneline -5 --no-decorate", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
32320
+ const log = execSync27("git log --oneline -5 --no-decorate", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
32124
32321
  if (log)
32125
32322
  lines.push(`Recent commits:
32126
32323
  ${log}`);
@@ -33537,7 +33734,7 @@ var init_carousel_descriptors = __esm({
33537
33734
  import { existsSync as existsSync32, mkdirSync as mkdirSync12, writeFileSync as writeFileSync12, readFileSync as readFileSync23, unlinkSync as unlinkSync4 } from "node:fs";
33538
33735
  import { join as join42 } from "node:path";
33539
33736
  import { homedir as homedir12, tmpdir as tmpdir6, platform as platform3 } from "node:os";
33540
- import { execSync as execSync27, spawn as nodeSpawn } from "node:child_process";
33737
+ import { execSync as execSync28, spawn as nodeSpawn } from "node:child_process";
33541
33738
  import { createRequire } from "node:module";
33542
33739
  function voiceDir() {
33543
33740
  return join42(homedir12(), ".open-agents", "voice");
@@ -34594,7 +34791,7 @@ var init_voice = __esm({
34594
34791
  }
34595
34792
  for (const player of ["paplay", "pw-play", "aplay"]) {
34596
34793
  try {
34597
- execSync27(`which ${player}`, { stdio: "pipe" });
34794
+ execSync28(`which ${player}`, { stdio: "pipe" });
34598
34795
  return [player, path];
34599
34796
  } catch {
34600
34797
  }
@@ -34623,7 +34820,7 @@ var init_voice = __esm({
34623
34820
  return this.python3Path;
34624
34821
  for (const bin of ["python3", "python"]) {
34625
34822
  try {
34626
- const path = execSync27(`which ${bin}`, { stdio: "pipe", timeout: 5e3 }).toString().trim();
34823
+ const path = execSync28(`which ${bin}`, { stdio: "pipe", timeout: 5e3 }).toString().trim();
34627
34824
  if (path) {
34628
34825
  this.python3Path = path;
34629
34826
  return path;
@@ -34643,7 +34840,7 @@ var init_voice = __esm({
34643
34840
  return false;
34644
34841
  }
34645
34842
  try {
34646
- execSync27(`${py} -c "import mlx_audio"`, { stdio: "pipe", timeout: 1e4 });
34843
+ execSync28(`${py} -c "import mlx_audio"`, { stdio: "pipe", timeout: 1e4 });
34647
34844
  this.mlxInstalled = true;
34648
34845
  return true;
34649
34846
  } catch {
@@ -34667,7 +34864,7 @@ var init_voice = __esm({
34667
34864
  return;
34668
34865
  renderInfo("Installing MLX Audio for voice synthesis (first time setup)...");
34669
34866
  try {
34670
- execSync27(`${py} -m pip install mlx-audio --quiet`, {
34867
+ execSync28(`${py} -m pip install mlx-audio --quiet`, {
34671
34868
  stdio: "pipe",
34672
34869
  timeout: 3e5
34673
34870
  // 5 min — may need to compile
@@ -34675,7 +34872,7 @@ var init_voice = __esm({
34675
34872
  this.mlxInstalled = true;
34676
34873
  } catch (err) {
34677
34874
  try {
34678
- execSync27(`${py} -m pip install mlx-audio --user --quiet`, {
34875
+ execSync28(`${py} -m pip install mlx-audio --user --quiet`, {
34679
34876
  stdio: "pipe",
34680
34877
  timeout: 3e5
34681
34878
  });
@@ -34711,11 +34908,11 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
34711
34908
  `tts_gen.main(["--model", ${JSON.stringify(mlxModelId)}, "--text", text, "--voice", ${JSON.stringify(mlxVoice)}, "--lang_code", ${JSON.stringify(mlxLangCode)}, "--audio_path", ${JSON.stringify(wavPath)}])`
34712
34909
  ].join("; ");
34713
34910
  try {
34714
- execSync27(`${py} -c ${JSON.stringify(pyScript)} ${JSON.stringify(JSON.stringify(cleaned))}`, { stdio: "pipe", timeout: 6e4, cwd: tmpdir6() });
34911
+ execSync28(`${py} -c ${JSON.stringify(pyScript)} ${JSON.stringify(JSON.stringify(cleaned))}`, { stdio: "pipe", timeout: 6e4, cwd: tmpdir6() });
34715
34912
  } catch (err) {
34716
34913
  try {
34717
34914
  const safeText = cleaned.replace(/'/g, "'\\''");
34718
- execSync27(`${py} -m mlx_audio.tts.generate --model ${mlxModelId} --text '${safeText}' --voice ${mlxVoice} --lang_code ${mlxLangCode} --audio_path ${JSON.stringify(wavPath)}`, { stdio: "pipe", timeout: 6e4, cwd: tmpdir6() });
34915
+ execSync28(`${py} -m mlx_audio.tts.generate --model ${mlxModelId} --text '${safeText}' --voice ${mlxVoice} --lang_code ${mlxLangCode} --audio_path ${JSON.stringify(wavPath)}`, { stdio: "pipe", timeout: 6e4, cwd: tmpdir6() });
34719
34916
  } catch (err2) {
34720
34917
  return;
34721
34918
  }
@@ -34779,11 +34976,11 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
34779
34976
  `tts_gen.main(["--model", ${JSON.stringify(mlxModelId)}, "--text", text, "--voice", ${JSON.stringify(mlxVoice)}, "--lang_code", ${JSON.stringify(mlxLangCode)}, "--audio_path", ${JSON.stringify(wavPath)}])`
34780
34977
  ].join("; ");
34781
34978
  try {
34782
- execSync27(`${py} -c ${JSON.stringify(pyScript)} ${JSON.stringify(JSON.stringify(cleaned))}`, { stdio: "pipe", timeout: 6e4, cwd: tmpdir6() });
34979
+ execSync28(`${py} -c ${JSON.stringify(pyScript)} ${JSON.stringify(JSON.stringify(cleaned))}`, { stdio: "pipe", timeout: 6e4, cwd: tmpdir6() });
34783
34980
  } catch {
34784
34981
  try {
34785
34982
  const safeText = cleaned.replace(/'/g, "'\\''");
34786
- execSync27(`${py} -m mlx_audio.tts.generate --model ${mlxModelId} --text '${safeText}' --voice ${mlxVoice} --lang_code ${mlxLangCode} --audio_path ${JSON.stringify(wavPath)}`, { stdio: "pipe", timeout: 6e4, cwd: tmpdir6() });
34983
+ execSync28(`${py} -m mlx_audio.tts.generate --model ${mlxModelId} --text '${safeText}' --voice ${mlxVoice} --lang_code ${mlxLangCode} --audio_path ${JSON.stringify(wavPath)}`, { stdio: "pipe", timeout: 6e4, cwd: tmpdir6() });
34787
34984
  } catch {
34788
34985
  return null;
34789
34986
  }
@@ -34831,7 +35028,7 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
34831
35028
  const voiceRequire = createRequire(join42(voiceDir(), "index.js"));
34832
35029
  const probeOnnx = () => {
34833
35030
  try {
34834
- const result = execSync27(`node -e "try { require('onnxruntime-node'); console.log('OK'); } catch(e) { console.log('FAIL:' + e.message); }"`, { cwd: voiceDir(), stdio: "pipe", timeout: 15e3, env: { ...process.env, NODE_PATH: join42(voiceDir(), "node_modules") } });
35031
+ const result = execSync28(`node -e "try { require('onnxruntime-node'); console.log('OK'); } catch(e) { console.log('FAIL:' + e.message); }"`, { cwd: voiceDir(), stdio: "pipe", timeout: 15e3, env: { ...process.env, NODE_PATH: join42(voiceDir(), "node_modules") } });
34835
35032
  const output = result.toString().trim();
34836
35033
  return output === "OK";
34837
35034
  } catch {
@@ -34848,7 +35045,7 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
34848
35045
  } catch {
34849
35046
  renderInfo("Installing ONNX runtime for voice synthesis...");
34850
35047
  try {
34851
- execSync27("npm install --no-audit --no-fund", {
35048
+ execSync28("npm install --no-audit --no-fund", {
34852
35049
  cwd: voiceDir(),
34853
35050
  stdio: "pipe",
34854
35051
  timeout: 12e4
@@ -34873,7 +35070,7 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
34873
35070
  } catch {
34874
35071
  renderInfo("Installing phonemizer for voice synthesis...");
34875
35072
  try {
34876
- execSync27("npm install --no-audit --no-fund", {
35073
+ execSync28("npm install --no-audit --no-fund", {
34877
35074
  cwd: voiceDir(),
34878
35075
  stdio: "pipe",
34879
35076
  timeout: 12e4
@@ -35957,7 +36154,7 @@ var init_promptLoader3 = __esm({
35957
36154
  // packages/cli/dist/tui/dream-engine.js
35958
36155
  import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync13, readFileSync as readFileSync25, existsSync as existsSync34, cpSync, rmSync, readdirSync as readdirSync10 } from "node:fs";
35959
36156
  import { join as join45, basename as basename12 } from "node:path";
35960
- import { execSync as execSync28 } from "node:child_process";
36157
+ import { execSync as execSync29 } from "node:child_process";
35961
36158
  function loadAutoresearchMemory(repoRoot) {
35962
36159
  const memoryPath = join45(repoRoot, ".oa", "memory", "autoresearch.json");
35963
36160
  if (!existsSync34(memoryPath))
@@ -36321,7 +36518,7 @@ var init_dream_engine = __esm({
36321
36518
  }
36322
36519
  }
36323
36520
  try {
36324
- const output = execSync28(cmd, {
36521
+ const output = execSync29(cmd, {
36325
36522
  cwd: this.repoRoot,
36326
36523
  timeout: 3e4,
36327
36524
  encoding: "utf-8",
@@ -37098,17 +37295,17 @@ ${summaryResult}
37098
37295
  try {
37099
37296
  mkdirSync14(checkpointDir, { recursive: true });
37100
37297
  try {
37101
- const gitStatus = execSync28("git status --porcelain", {
37298
+ const gitStatus = execSync29("git status --porcelain", {
37102
37299
  cwd: this.repoRoot,
37103
37300
  encoding: "utf-8",
37104
37301
  timeout: 1e4
37105
37302
  });
37106
- const gitDiff = execSync28("git diff", {
37303
+ const gitDiff = execSync29("git diff", {
37107
37304
  cwd: this.repoRoot,
37108
37305
  encoding: "utf-8",
37109
37306
  timeout: 1e4
37110
37307
  });
37111
- const gitHash = execSync28("git rev-parse HEAD 2>/dev/null || echo 'no-git'", {
37308
+ const gitHash = execSync29("git rev-parse HEAD 2>/dev/null || echo 'no-git'", {
37112
37309
  cwd: this.repoRoot,
37113
37310
  encoding: "utf-8",
37114
37311
  timeout: 5e3
@@ -40962,6 +41159,62 @@ var init_status_bar = __esm({
40962
41159
  if (this.active)
40963
41160
  this.renderFooterPreserveCursor();
40964
41161
  }
41162
+ /** Remote host metrics (when connected to a remote endpoint) */
41163
+ _remoteMetrics = null;
41164
+ _remoteMetricsTimer = null;
41165
+ /** Update remote host system metrics (from polling /v1/system/metrics) */
41166
+ setRemoteMetrics(metrics) {
41167
+ this._remoteMetrics = metrics;
41168
+ if (this.active)
41169
+ this.renderFooterPreserveCursor();
41170
+ }
41171
+ /** Clear remote metrics (e.g. when switching to local endpoint) */
41172
+ clearRemoteMetrics() {
41173
+ this._remoteMetrics = null;
41174
+ if (this._remoteMetricsTimer) {
41175
+ clearInterval(this._remoteMetricsTimer);
41176
+ this._remoteMetricsTimer = null;
41177
+ }
41178
+ if (this.active)
41179
+ this.renderFooterPreserveCursor();
41180
+ }
41181
+ /**
41182
+ * Start polling a remote endpoint for system metrics.
41183
+ * Call when /endpoint is set to a remote URL that has /v1/system/metrics.
41184
+ */
41185
+ startRemoteMetricsPolling(endpointUrl, authKey) {
41186
+ this.stopRemoteMetricsPolling();
41187
+ const poll = async () => {
41188
+ try {
41189
+ const url = new URL("/v1/system/metrics", endpointUrl);
41190
+ const headers = {};
41191
+ if (authKey)
41192
+ headers["Authorization"] = `Bearer ${authKey}`;
41193
+ const resp = await fetch(url.toString(), { headers, signal: AbortSignal.timeout(5e3) });
41194
+ if (resp.ok) {
41195
+ const data = await resp.json();
41196
+ this.setRemoteMetrics({
41197
+ cpuUtil: data.cpu?.utilization ?? 0,
41198
+ gpuUtil: data.gpu?.available ? data.gpu.utilization ?? 0 : -1,
41199
+ gpuName: data.gpu?.name ?? "",
41200
+ vramUtil: data.gpu?.available ? data.gpu.vramUtilization ?? 0 : -1,
41201
+ memUtil: data.memory?.utilization ?? 0
41202
+ });
41203
+ }
41204
+ } catch {
41205
+ }
41206
+ };
41207
+ poll();
41208
+ this._remoteMetricsTimer = setInterval(poll, 1e4);
41209
+ }
41210
+ /** Stop polling remote metrics */
41211
+ stopRemoteMetricsPolling() {
41212
+ if (this._remoteMetricsTimer) {
41213
+ clearInterval(this._remoteMetricsTimer);
41214
+ this._remoteMetricsTimer = null;
41215
+ }
41216
+ this._remoteMetrics = null;
41217
+ }
40965
41218
  /** Update token metrics from a token_usage event */
40966
41219
  updateMetrics(update) {
40967
41220
  if (update.promptTokens !== void 0)
@@ -41325,6 +41578,35 @@ var init_status_bar = __esm({
41325
41578
  empty: false
41326
41579
  });
41327
41580
  }
41581
+ if (this._remoteMetrics) {
41582
+ const rm2 = this._remoteMetrics;
41583
+ const cpuColor = rm2.cpuUtil > 80 ? c2.red : rm2.cpuUtil > 50 ? c2.yellow : c2.green;
41584
+ const cpuStr = `CPU:${cpuColor(rm2.cpuUtil + "%")}`;
41585
+ const cpuW = 4 + `${rm2.cpuUtil}%`.length;
41586
+ let gpuStr = "";
41587
+ let gpuW = 0;
41588
+ if (rm2.gpuUtil >= 0) {
41589
+ const gpuColor = rm2.gpuUtil > 80 ? c2.red : rm2.gpuUtil > 50 ? c2.yellow : c2.green;
41590
+ gpuStr = ` GPU:${gpuColor(rm2.gpuUtil + "%")}`;
41591
+ gpuW = 5 + `${rm2.gpuUtil}%`.length;
41592
+ }
41593
+ let vramStr = "";
41594
+ let vramW = 0;
41595
+ if (rm2.vramUtil >= 0) {
41596
+ const vramColor = rm2.vramUtil > 80 ? c2.red : rm2.vramUtil > 50 ? c2.yellow : c2.green;
41597
+ vramStr = ` VRAM:${vramColor(rm2.vramUtil + "%")}`;
41598
+ vramW = 6 + `${rm2.vramUtil}%`.length;
41599
+ }
41600
+ const remoteExpanded = pastel2(117, "\u{1F4E1}") + " " + cpuStr + gpuStr + vramStr;
41601
+ const remoteCompact = pastel2(117, "\u{1F4E1}") + " " + cpuStr + gpuStr;
41602
+ sections.push({
41603
+ expanded: remoteExpanded,
41604
+ compact: remoteCompact,
41605
+ expandedW: 3 + cpuW + gpuW + vramW,
41606
+ compactW: 3 + cpuW + gpuW,
41607
+ empty: false
41608
+ });
41609
+ }
41328
41610
  if (this._recording) {
41329
41611
  const dot = this._recBlink ? pastel2(210, "\u25CF") : " ";
41330
41612
  const countdown = this._countdown > 0 ? c2.dim(` ${this._countdown}s`) : "";
@@ -43020,6 +43302,12 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
43020
43302
  };
43021
43303
  const newProvider = detectProvider(url);
43022
43304
  costTracker.setProvider(newProvider.id);
43305
+ const isRemote = !url.includes("127.0.0.1") && !url.includes("localhost");
43306
+ if (isRemote) {
43307
+ statusBar.startRemoteMetricsPolling(url, apiKey);
43308
+ } else {
43309
+ statusBar.stopRemoteMetricsPolling();
43310
+ }
43023
43311
  },
43024
43312
  clearScreen() {
43025
43313
  process.stdout.write("\x1B[2J\x1B[H");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.8",
3
+ "version": "0.103.9",
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",