open-agents-ai 0.103.7 → 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 +576 -156
  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
  });
@@ -30086,12 +30141,24 @@ function ansi3(code, text) {
30086
30141
  function fg2562(code, text) {
30087
30142
  return isTTY3 ? `\x1B[38;5;${code}m${text}\x1B[0m` : text;
30088
30143
  }
30144
+ function stripAnsi(s) {
30145
+ return s.replace(/\x1B\[[0-9;]*m/g, "");
30146
+ }
30089
30147
  function defaultRenderRow(item, focused, isActive) {
30090
30148
  const marker = isActive ? selectColors.green("\u25CF") : focused ? selectColors.orange("\u25CF") : selectColors.dim("\u25CB");
30091
30149
  const label = focused ? selectColors.orange(selectColors.bold(item.label)) : isActive ? selectColors.green(item.label) : item.label;
30092
30150
  const detail = item.detail ? ` ${selectColors.dim(item.detail)}` : "";
30093
30151
  return ` ${marker} ${label}${detail}`;
30094
30152
  }
30153
+ function matchRow(item, focused, isActive) {
30154
+ if (focused || isActive) {
30155
+ return defaultRenderRow(item, focused, isActive);
30156
+ }
30157
+ const marker = selectColors.matchLight("\u25CB");
30158
+ const label = selectColors.matchLight(stripAnsi(item.label));
30159
+ const detail = item.detail ? ` ${selectColors.dim(stripAnsi(item.detail))}` : "";
30160
+ return ` ${marker} ${label}${detail}`;
30161
+ }
30095
30162
  function tuiSelect(opts) {
30096
30163
  const { items, title, rl } = opts;
30097
30164
  const renderRow = opts.renderRow ?? defaultRenderRow;
@@ -30101,20 +30168,44 @@ function tuiSelect(opts) {
30101
30168
  return Promise.resolve({ confirmed: false, key: null, index: -1 });
30102
30169
  }
30103
30170
  const isSkippable = (idx) => skipSet.has(items[idx].key);
30171
+ let filter = "";
30172
+ let matchSet = /* @__PURE__ */ new Set();
30173
+ function updateFilter() {
30174
+ if (!filter) {
30175
+ matchSet = new Set(items.map((_, i) => i));
30176
+ } else {
30177
+ const lower = filter.toLowerCase();
30178
+ matchSet = /* @__PURE__ */ new Set();
30179
+ for (let i = 0; i < items.length; i++) {
30180
+ if (isSkippable(i))
30181
+ continue;
30182
+ const plain = stripAnsi(items[i].label).toLowerCase();
30183
+ const detailPlain = items[i].detail ? stripAnsi(items[i].detail).toLowerCase() : "";
30184
+ if (plain.includes(lower) || detailPlain.includes(lower)) {
30185
+ matchSet.add(i);
30186
+ }
30187
+ }
30188
+ }
30189
+ }
30190
+ updateFilter();
30104
30191
  const findSelectable = (from, dir) => {
30105
30192
  let idx = from;
30106
- while (idx >= 0 && idx < items.length && isSkippable(idx)) {
30193
+ while (idx >= 0 && idx < items.length) {
30194
+ if (!isSkippable(idx) && matchSet.has(idx))
30195
+ return idx;
30107
30196
  idx += dir;
30108
30197
  }
30109
- return idx >= 0 && idx < items.length ? idx : from;
30198
+ return -1;
30110
30199
  };
30111
- let cursor = activeKey ? items.findIndex((i) => i.key === activeKey) : 0;
30112
- if (cursor < 0)
30113
- cursor = 0;
30114
- cursor = findSelectable(cursor, 1);
30200
+ let cursor = activeKey ? items.findIndex((i) => i.key === activeKey) : -1;
30201
+ if (cursor < 0 || isSkippable(cursor)) {
30202
+ const first = findSelectable(0, 1);
30203
+ cursor = first >= 0 ? first : 0;
30204
+ }
30115
30205
  const termRows = process.stdout.rows ?? 24;
30116
- const maxVisible = opts.maxVisible ?? Math.max(5, termRows - 6);
30117
- let scrollOffset = Math.max(0, cursor - Math.floor(maxVisible / 2));
30206
+ const overhead = 6;
30207
+ const maxVisible = opts.maxVisible ?? Math.max(3, termRows - overhead);
30208
+ let scrollOffset = 0;
30118
30209
  let lastRenderedLines = 0;
30119
30210
  return new Promise((resolve31) => {
30120
30211
  const stdin = process.stdin;
@@ -30135,15 +30226,17 @@ function tuiSelect(opts) {
30135
30226
  }
30136
30227
  stdin.resume();
30137
30228
  process.stdout.write("\x1B[?25l");
30138
- function clampScroll() {
30139
- if (cursor < scrollOffset) {
30140
- scrollOffset = cursor;
30141
- } else if (cursor >= scrollOffset + maxVisible) {
30142
- scrollOffset = cursor - maxVisible + 1;
30143
- }
30144
- scrollOffset = Math.max(0, Math.min(items.length - maxVisible, scrollOffset));
30145
- if (scrollOffset < 0)
30146
- scrollOffset = 0;
30229
+ function clampScroll(displayList) {
30230
+ const cursorPos = displayList.indexOf(cursor);
30231
+ if (cursorPos < 0)
30232
+ return;
30233
+ if (cursorPos < scrollOffset) {
30234
+ scrollOffset = cursorPos;
30235
+ } else if (cursorPos >= scrollOffset + maxVisible) {
30236
+ scrollOffset = cursorPos - maxVisible + 1;
30237
+ }
30238
+ const maxOffset = Math.max(0, displayList.length - maxVisible);
30239
+ scrollOffset = Math.max(0, Math.min(maxOffset, scrollOffset));
30147
30240
  }
30148
30241
  function render() {
30149
30242
  if (lastRenderedLines > 0) {
@@ -30153,33 +30246,67 @@ function tuiSelect(opts) {
30153
30246
  }
30154
30247
  process.stdout.write(`\x1B[${lastRenderedLines}A`);
30155
30248
  }
30156
- clampScroll();
30157
30249
  const lines = [];
30158
30250
  if (title) {
30159
30251
  lines.push(`
30160
30252
  ${selectColors.bold(title)}`);
30161
- lines.push("");
30162
30253
  }
30163
- if (scrollOffset > 0) {
30164
- lines.push(` ${selectColors.dim(` \u25B2 ${scrollOffset} more`)}`);
30254
+ if (filter) {
30255
+ const count = matchSet.size;
30256
+ lines.push(` ${selectColors.cyan("/")} ${selectColors.bold(filter)} ${selectColors.dim(`(${count} match${count !== 1 ? "es" : ""})`)}`);
30257
+ } else {
30258
+ lines.push(` ${selectColors.dim("Type to filter...")}`);
30165
30259
  }
30166
- const visibleEnd = Math.min(items.length, scrollOffset + maxVisible);
30167
- for (let i = scrollOffset; i < visibleEnd; i++) {
30168
- const item = items[i];
30169
- if (isSkippable(i)) {
30260
+ lines.push("");
30261
+ let displayList;
30262
+ if (filter) {
30263
+ displayList = [];
30264
+ for (let i = 0; i < items.length; i++) {
30265
+ if (matchSet.has(i) || isSkippable(i)) {
30266
+ displayList.push(i);
30267
+ }
30268
+ }
30269
+ displayList = displayList.filter((idx, pos) => {
30270
+ if (!isSkippable(idx))
30271
+ return true;
30272
+ for (let j = pos + 1; j < displayList.length; j++) {
30273
+ if (!isSkippable(displayList[j]))
30274
+ return true;
30275
+ break;
30276
+ }
30277
+ return false;
30278
+ });
30279
+ } else {
30280
+ displayList = items.map((_, i) => i);
30281
+ }
30282
+ clampScroll(displayList);
30283
+ const visibleStart = scrollOffset;
30284
+ const visibleEnd = Math.min(displayList.length, scrollOffset + maxVisible);
30285
+ if (visibleStart > 0) {
30286
+ lines.push(` ${selectColors.dim(` \u25B2 ${visibleStart} more`)}`);
30287
+ }
30288
+ for (let vi = visibleStart; vi < visibleEnd; vi++) {
30289
+ const idx = displayList[vi];
30290
+ const item = items[idx];
30291
+ if (isSkippable(idx)) {
30170
30292
  lines.push(` ${item.label}`);
30171
30293
  continue;
30172
30294
  }
30173
- const focused = i === cursor;
30295
+ const focused = idx === cursor;
30174
30296
  const isActive = item.key === activeKey;
30175
- lines.push(renderRow(item, focused, isActive));
30297
+ if (filter) {
30298
+ lines.push(matchRow(item, focused, isActive));
30299
+ } else {
30300
+ lines.push(renderRow(item, focused, isActive));
30301
+ }
30176
30302
  }
30177
- const remaining = items.length - visibleEnd;
30303
+ const remaining = displayList.length - visibleEnd;
30178
30304
  if (remaining > 0) {
30179
30305
  lines.push(` ${selectColors.dim(` \u25BC ${remaining} more`)}`);
30180
30306
  }
30181
30307
  lines.push("");
30182
- lines.push(` ${selectColors.dim("\u2191/\u2193 navigate Enter select Esc cancel")}`);
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")}`);
30183
30310
  lines.push("");
30184
30311
  const output = lines.join("\n");
30185
30312
  process.stdout.write(output);
@@ -30187,6 +30314,7 @@ function tuiSelect(opts) {
30187
30314
  }
30188
30315
  function cleanup() {
30189
30316
  stdin.removeListener("data", onData);
30317
+ process.stdout.removeListener("resize", onResize);
30190
30318
  process.stdout.write("\x1B[?25h");
30191
30319
  if (lastRenderedLines > 0) {
30192
30320
  process.stdout.write(`\x1B[${lastRenderedLines}A`);
@@ -30208,37 +30336,108 @@ function tuiSelect(opts) {
30208
30336
  }
30209
30337
  function onData(chunk) {
30210
30338
  const seq = chunk.toString("utf8");
30211
- if (seq === "\x1B[A" || seq === "k") {
30212
- const next = cursor - 1;
30213
- if (next >= 0) {
30214
- cursor = findSelectable(next, -1);
30339
+ if (seq === "\x1B[A") {
30340
+ const next = findSelectable(cursor - 1, -1);
30341
+ if (next >= 0 && next !== cursor) {
30342
+ cursor = next;
30215
30343
  render();
30216
30344
  }
30217
- } else if (seq === "\x1B[B" || seq === "j") {
30218
- const next = cursor + 1;
30219
- if (next < items.length) {
30220
- cursor = findSelectable(next, 1);
30345
+ } else if (seq === "\x1B[B") {
30346
+ const next = findSelectable(cursor + 1, 1);
30347
+ if (next >= 0 && next !== cursor) {
30348
+ cursor = next;
30221
30349
  render();
30222
30350
  }
30223
- } else if (seq === "\x1B[H" || seq === "g") {
30224
- cursor = findSelectable(0, 1);
30351
+ } else if (seq === "\x1B[5~") {
30352
+ for (let i = 0; i < maxVisible; i++) {
30353
+ const next = findSelectable(cursor - 1, -1);
30354
+ if (next < 0 || next === cursor)
30355
+ break;
30356
+ cursor = next;
30357
+ }
30225
30358
  render();
30226
- } else if (seq === "\x1B[F" || seq === "G") {
30227
- cursor = findSelectable(items.length - 1, -1);
30359
+ } else if (seq === "\x1B[6~") {
30360
+ for (let i = 0; i < maxVisible; i++) {
30361
+ const next = findSelectable(cursor + 1, 1);
30362
+ if (next < 0 || next === cursor)
30363
+ break;
30364
+ cursor = next;
30365
+ }
30228
30366
  render();
30367
+ } else if (seq === "\x1B[H") {
30368
+ const first = findSelectable(0, 1);
30369
+ if (first >= 0) {
30370
+ cursor = first;
30371
+ render();
30372
+ }
30373
+ } else if (seq === "\x1B[F") {
30374
+ const last = findSelectable(items.length - 1, -1);
30375
+ if (last >= 0) {
30376
+ cursor = last;
30377
+ render();
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
+ }
30229
30394
  } else if (seq === "\r" || seq === "\n") {
30230
- if (!isSkippable(cursor)) {
30395
+ if (!isSkippable(cursor) && matchSet.has(cursor)) {
30231
30396
  cleanup();
30232
30397
  resolve31({ confirmed: true, key: items[cursor].key, index: cursor });
30233
30398
  }
30234
- } else if (seq === "\x1B" || seq === "\x1B\x1B" || seq === "q") {
30235
- cleanup();
30236
- resolve31({ confirmed: false, key: null, index: cursor });
30399
+ } else if (seq === "\x1B" || seq === "\x1B\x1B") {
30400
+ if (filter) {
30401
+ filter = "";
30402
+ updateFilter();
30403
+ const valid = findSelectable(cursor, 1);
30404
+ if (valid >= 0)
30405
+ cursor = valid;
30406
+ scrollOffset = 0;
30407
+ render();
30408
+ } else {
30409
+ cleanup();
30410
+ resolve31({ confirmed: false, key: null, index: cursor });
30411
+ }
30237
30412
  } else if (seq === "") {
30238
30413
  cleanup();
30239
30414
  resolve31({ confirmed: false, key: null, index: cursor });
30415
+ } else if (seq === "\x7F" || seq === "\b") {
30416
+ if (filter.length > 0) {
30417
+ filter = filter.slice(0, -1);
30418
+ updateFilter();
30419
+ if (!matchSet.has(cursor) || isSkippable(cursor)) {
30420
+ const next = findSelectable(0, 1);
30421
+ if (next >= 0)
30422
+ cursor = next;
30423
+ }
30424
+ scrollOffset = 0;
30425
+ render();
30426
+ }
30427
+ } else if (seq.length === 1 && seq.charCodeAt(0) >= 32 && seq.charCodeAt(0) < 127) {
30428
+ filter += seq;
30429
+ updateFilter();
30430
+ if (matchSet.size > 0) {
30431
+ const first = findSelectable(0, 1);
30432
+ if (first >= 0)
30433
+ cursor = first;
30434
+ }
30435
+ scrollOffset = 0;
30436
+ render();
30240
30437
  }
30241
30438
  }
30439
+ const onResize = () => render();
30440
+ process.stdout.on("resize", onResize);
30242
30441
  stdin.on("data", onData);
30243
30442
  render();
30244
30443
  });
@@ -30253,7 +30452,11 @@ var init_tui_select = __esm({
30253
30452
  green: (t) => ansi3("32", t),
30254
30453
  dim: (t) => ansi3("2", t),
30255
30454
  bold: (t) => ansi3("1", t),
30256
- cyan: (t) => ansi3("36", t)
30455
+ cyan: (t) => ansi3("36", t),
30456
+ /** Lighter grey for filter matches (252 = near-white) */
30457
+ matchLight: (t) => fg2562(252, t),
30458
+ /** Dark grey for non-matching items (240 = dark grey) */
30459
+ matchDark: (t) => fg2562(240, t)
30257
30460
  };
30258
30461
  }
30259
30462
  });
@@ -30294,38 +30497,7 @@ async function handleSlashCommand(input, ctx) {
30294
30497
  return "handled";
30295
30498
  case "config":
30296
30499
  case "cfg": {
30297
- const resolved = loadProjectSettings(ctx.repoRoot);
30298
- const globalS = loadGlobalSettings();
30299
- const merged = { ...globalS, ...resolved };
30300
- renderConfig({
30301
- // -- Core inference --
30302
- model: ctx.config.model,
30303
- backendType: ctx.config.backendType,
30304
- backendUrl: ctx.config.backendUrl,
30305
- apiKey: ctx.config.apiKey ? "[set]" : "[not set]",
30306
- maxRetries: String(ctx.config.maxRetries),
30307
- timeoutMs: String(ctx.config.timeoutMs),
30308
- dbPath: ctx.config.dbPath,
30309
- // -- Behaviour --
30310
- dryRun: String(ctx.config.dryRun),
30311
- verbose: String(ctx.config.verbose),
30312
- stream: String(merged.stream ?? false),
30313
- bruteforce: String(merged.bruteforce ?? false),
30314
- deepContext: String(merged.deepContext ?? false),
30315
- // -- Presentation --
30316
- emojis: String(ctx.getEmojis?.() ?? merged.emojis ?? true),
30317
- colors: String(ctx.getColors?.() ?? merged.colors ?? true),
30318
- style: String(ctx.getStyle?.() ?? merged.style ?? "balanced"),
30319
- // -- Voice --
30320
- voice: String(merged.voice ?? false),
30321
- voiceModel: String(merged.voiceModel ?? "glados"),
30322
- // -- Autonomy --
30323
- commandsMode: String(ctx.getCommandsMode?.() ?? merged.commandsMode ?? "manual"),
30324
- updateMode: String(merged.updateMode ?? "auto"),
30325
- // -- Integrations --
30326
- telegramKey: merged.telegramKey ? "[set]" : "[not set]",
30327
- telegramAdmin: String(merged.telegramAdmin ?? "[not set]")
30328
- });
30500
+ await showConfigEditor(ctx);
30329
30501
  return "handled";
30330
30502
  }
30331
30503
  case "cost":
@@ -30739,25 +30911,26 @@ async function handleSlashCommand(input, ctx) {
30739
30911
  }
30740
30912
  case "commands":
30741
30913
  case "cmds": {
30742
- if (!ctx.getCommandsMode || !ctx.setCommandsMode) {
30743
- renderWarning("Commands mode not available in this context.");
30744
- return "handled";
30745
- }
30746
30914
  if (arg === "auto") {
30915
+ if (!ctx.setCommandsMode) {
30916
+ renderWarning("Commands mode not available.");
30917
+ return "handled";
30918
+ }
30747
30919
  ctx.setCommandsMode("auto");
30748
30920
  const save = hasLocal ? ctx.saveLocalSettings.bind(ctx) : ctx.saveSettings.bind(ctx);
30749
30921
  save({ commandsMode: "auto" });
30750
30922
  renderInfo("Commands mode: auto \u2014 the agent can now invoke slash commands as tools.");
30751
30923
  } else if (arg === "manual") {
30924
+ if (!ctx.setCommandsMode) {
30925
+ renderWarning("Commands mode not available.");
30926
+ return "handled";
30927
+ }
30752
30928
  ctx.setCommandsMode("manual");
30753
30929
  const save = hasLocal ? ctx.saveLocalSettings.bind(ctx) : ctx.saveSettings.bind(ctx);
30754
30930
  save({ commandsMode: "manual" });
30755
30931
  renderInfo("Commands mode: manual \u2014 slash commands are user-only.");
30756
30932
  } else {
30757
- const current = ctx.getCommandsMode();
30758
- renderInfo(`Commands mode: ${c2.bold(current)}`);
30759
- renderInfo(" /commands auto \u2014 agent can invoke slash commands as tools");
30760
- renderInfo(" /commands manual \u2014 slash commands are user-only (default)");
30933
+ renderSlashHelp();
30761
30934
  }
30762
30935
  return "handled";
30763
30936
  }
@@ -31364,6 +31537,162 @@ async function handleSlashCommand(input, ctx) {
31364
31537
  }
31365
31538
  }
31366
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
+ }
31367
31696
  async function listModels(ctx) {
31368
31697
  try {
31369
31698
  const models = await fetchModels(ctx.config.backendUrl, ctx.config.apiKey);
@@ -31920,7 +32249,7 @@ var init_commands = __esm({
31920
32249
  // packages/cli/dist/tui/project-context.js
31921
32250
  import { existsSync as existsSync30, readFileSync as readFileSync21, readdirSync as readdirSync8 } from "node:fs";
31922
32251
  import { join as join40, basename as basename10 } from "node:path";
31923
- import { execSync as execSync26 } from "node:child_process";
32252
+ import { execSync as execSync27 } from "node:child_process";
31924
32253
  import { homedir as homedir11, platform as platform2, release } from "node:os";
31925
32254
  function getModelTier(modelName) {
31926
32255
  const m = modelName.toLowerCase();
@@ -31966,19 +32295,19 @@ function loadProjectMap(repoRoot) {
31966
32295
  }
31967
32296
  function getGitInfo(repoRoot) {
31968
32297
  try {
31969
- 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" });
31970
32299
  } catch {
31971
32300
  return "";
31972
32301
  }
31973
32302
  const lines = [];
31974
32303
  try {
31975
- 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();
31976
32305
  if (branch)
31977
32306
  lines.push(`Branch: ${branch}`);
31978
32307
  } catch {
31979
32308
  }
31980
32309
  try {
31981
- 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();
31982
32311
  if (status) {
31983
32312
  const changed = status.split("\n").length;
31984
32313
  lines.push(`Working tree: ${changed} changed file(s)`);
@@ -31988,7 +32317,7 @@ function getGitInfo(repoRoot) {
31988
32317
  } catch {
31989
32318
  }
31990
32319
  try {
31991
- 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();
31992
32321
  if (log)
31993
32322
  lines.push(`Recent commits:
31994
32323
  ${log}`);
@@ -33405,7 +33734,7 @@ var init_carousel_descriptors = __esm({
33405
33734
  import { existsSync as existsSync32, mkdirSync as mkdirSync12, writeFileSync as writeFileSync12, readFileSync as readFileSync23, unlinkSync as unlinkSync4 } from "node:fs";
33406
33735
  import { join as join42 } from "node:path";
33407
33736
  import { homedir as homedir12, tmpdir as tmpdir6, platform as platform3 } from "node:os";
33408
- import { execSync as execSync27, spawn as nodeSpawn } from "node:child_process";
33737
+ import { execSync as execSync28, spawn as nodeSpawn } from "node:child_process";
33409
33738
  import { createRequire } from "node:module";
33410
33739
  function voiceDir() {
33411
33740
  return join42(homedir12(), ".open-agents", "voice");
@@ -34462,7 +34791,7 @@ var init_voice = __esm({
34462
34791
  }
34463
34792
  for (const player of ["paplay", "pw-play", "aplay"]) {
34464
34793
  try {
34465
- execSync27(`which ${player}`, { stdio: "pipe" });
34794
+ execSync28(`which ${player}`, { stdio: "pipe" });
34466
34795
  return [player, path];
34467
34796
  } catch {
34468
34797
  }
@@ -34491,7 +34820,7 @@ var init_voice = __esm({
34491
34820
  return this.python3Path;
34492
34821
  for (const bin of ["python3", "python"]) {
34493
34822
  try {
34494
- const path = execSync27(`which ${bin}`, { stdio: "pipe", timeout: 5e3 }).toString().trim();
34823
+ const path = execSync28(`which ${bin}`, { stdio: "pipe", timeout: 5e3 }).toString().trim();
34495
34824
  if (path) {
34496
34825
  this.python3Path = path;
34497
34826
  return path;
@@ -34511,7 +34840,7 @@ var init_voice = __esm({
34511
34840
  return false;
34512
34841
  }
34513
34842
  try {
34514
- execSync27(`${py} -c "import mlx_audio"`, { stdio: "pipe", timeout: 1e4 });
34843
+ execSync28(`${py} -c "import mlx_audio"`, { stdio: "pipe", timeout: 1e4 });
34515
34844
  this.mlxInstalled = true;
34516
34845
  return true;
34517
34846
  } catch {
@@ -34535,7 +34864,7 @@ var init_voice = __esm({
34535
34864
  return;
34536
34865
  renderInfo("Installing MLX Audio for voice synthesis (first time setup)...");
34537
34866
  try {
34538
- execSync27(`${py} -m pip install mlx-audio --quiet`, {
34867
+ execSync28(`${py} -m pip install mlx-audio --quiet`, {
34539
34868
  stdio: "pipe",
34540
34869
  timeout: 3e5
34541
34870
  // 5 min — may need to compile
@@ -34543,7 +34872,7 @@ var init_voice = __esm({
34543
34872
  this.mlxInstalled = true;
34544
34873
  } catch (err) {
34545
34874
  try {
34546
- execSync27(`${py} -m pip install mlx-audio --user --quiet`, {
34875
+ execSync28(`${py} -m pip install mlx-audio --user --quiet`, {
34547
34876
  stdio: "pipe",
34548
34877
  timeout: 3e5
34549
34878
  });
@@ -34579,11 +34908,11 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
34579
34908
  `tts_gen.main(["--model", ${JSON.stringify(mlxModelId)}, "--text", text, "--voice", ${JSON.stringify(mlxVoice)}, "--lang_code", ${JSON.stringify(mlxLangCode)}, "--audio_path", ${JSON.stringify(wavPath)}])`
34580
34909
  ].join("; ");
34581
34910
  try {
34582
- 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() });
34583
34912
  } catch (err) {
34584
34913
  try {
34585
34914
  const safeText = cleaned.replace(/'/g, "'\\''");
34586
- 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() });
34587
34916
  } catch (err2) {
34588
34917
  return;
34589
34918
  }
@@ -34647,11 +34976,11 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
34647
34976
  `tts_gen.main(["--model", ${JSON.stringify(mlxModelId)}, "--text", text, "--voice", ${JSON.stringify(mlxVoice)}, "--lang_code", ${JSON.stringify(mlxLangCode)}, "--audio_path", ${JSON.stringify(wavPath)}])`
34648
34977
  ].join("; ");
34649
34978
  try {
34650
- 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() });
34651
34980
  } catch {
34652
34981
  try {
34653
34982
  const safeText = cleaned.replace(/'/g, "'\\''");
34654
- 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() });
34655
34984
  } catch {
34656
34985
  return null;
34657
34986
  }
@@ -34699,7 +35028,7 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
34699
35028
  const voiceRequire = createRequire(join42(voiceDir(), "index.js"));
34700
35029
  const probeOnnx = () => {
34701
35030
  try {
34702
- 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") } });
34703
35032
  const output = result.toString().trim();
34704
35033
  return output === "OK";
34705
35034
  } catch {
@@ -34716,7 +35045,7 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
34716
35045
  } catch {
34717
35046
  renderInfo("Installing ONNX runtime for voice synthesis...");
34718
35047
  try {
34719
- execSync27("npm install --no-audit --no-fund", {
35048
+ execSync28("npm install --no-audit --no-fund", {
34720
35049
  cwd: voiceDir(),
34721
35050
  stdio: "pipe",
34722
35051
  timeout: 12e4
@@ -34741,7 +35070,7 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
34741
35070
  } catch {
34742
35071
  renderInfo("Installing phonemizer for voice synthesis...");
34743
35072
  try {
34744
- execSync27("npm install --no-audit --no-fund", {
35073
+ execSync28("npm install --no-audit --no-fund", {
34745
35074
  cwd: voiceDir(),
34746
35075
  stdio: "pipe",
34747
35076
  timeout: 12e4
@@ -35825,7 +36154,7 @@ var init_promptLoader3 = __esm({
35825
36154
  // packages/cli/dist/tui/dream-engine.js
35826
36155
  import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync13, readFileSync as readFileSync25, existsSync as existsSync34, cpSync, rmSync, readdirSync as readdirSync10 } from "node:fs";
35827
36156
  import { join as join45, basename as basename12 } from "node:path";
35828
- import { execSync as execSync28 } from "node:child_process";
36157
+ import { execSync as execSync29 } from "node:child_process";
35829
36158
  function loadAutoresearchMemory(repoRoot) {
35830
36159
  const memoryPath = join45(repoRoot, ".oa", "memory", "autoresearch.json");
35831
36160
  if (!existsSync34(memoryPath))
@@ -36189,7 +36518,7 @@ var init_dream_engine = __esm({
36189
36518
  }
36190
36519
  }
36191
36520
  try {
36192
- const output = execSync28(cmd, {
36521
+ const output = execSync29(cmd, {
36193
36522
  cwd: this.repoRoot,
36194
36523
  timeout: 3e4,
36195
36524
  encoding: "utf-8",
@@ -36966,17 +37295,17 @@ ${summaryResult}
36966
37295
  try {
36967
37296
  mkdirSync14(checkpointDir, { recursive: true });
36968
37297
  try {
36969
- const gitStatus = execSync28("git status --porcelain", {
37298
+ const gitStatus = execSync29("git status --porcelain", {
36970
37299
  cwd: this.repoRoot,
36971
37300
  encoding: "utf-8",
36972
37301
  timeout: 1e4
36973
37302
  });
36974
- const gitDiff = execSync28("git diff", {
37303
+ const gitDiff = execSync29("git diff", {
36975
37304
  cwd: this.repoRoot,
36976
37305
  encoding: "utf-8",
36977
37306
  timeout: 1e4
36978
37307
  });
36979
- 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'", {
36980
37309
  cwd: this.repoRoot,
36981
37310
  encoding: "utf-8",
36982
37311
  timeout: 5e3
@@ -40830,6 +41159,62 @@ var init_status_bar = __esm({
40830
41159
  if (this.active)
40831
41160
  this.renderFooterPreserveCursor();
40832
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
+ }
40833
41218
  /** Update token metrics from a token_usage event */
40834
41219
  updateMetrics(update) {
40835
41220
  if (update.promptTokens !== void 0)
@@ -41193,6 +41578,35 @@ var init_status_bar = __esm({
41193
41578
  empty: false
41194
41579
  });
41195
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
+ }
41196
41610
  if (this._recording) {
41197
41611
  const dot = this._recBlink ? pastel2(210, "\u25CF") : " ";
41198
41612
  const countdown = this._countdown > 0 ? c2.dim(` ${this._countdown}s`) : "";
@@ -42888,6 +43302,12 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
42888
43302
  };
42889
43303
  const newProvider = detectProvider(url);
42890
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
+ }
42891
43311
  },
42892
43312
  clearScreen() {
42893
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.7",
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",