open-agents-ai 0.184.67 → 0.184.69

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 +1635 -1597
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -41,6 +41,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
41
41
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
42
42
 
43
43
  // packages/cli/dist/config.js
44
+ var config_exports = {};
45
+ __export(config_exports, {
46
+ DEFAULT_CONFIG: () => DEFAULT_CONFIG,
47
+ loadConfig: () => loadConfig,
48
+ mergeConfig: () => mergeConfig,
49
+ setConfigValue: () => setConfigValue
50
+ });
44
51
  import { readFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
45
52
  import { homedir } from "node:os";
46
53
  import { join } from "node:path";
@@ -39665,267 +39672,952 @@ var init_oa_directory = __esm({
39665
39672
  }
39666
39673
  });
39667
39674
 
39668
- // packages/cli/dist/tui/setup.js
39669
- import * as readline from "node:readline";
39670
- import { execSync as execSync27, spawn as spawn19, exec as exec2 } from "node:child_process";
39671
- import { promisify as promisify6 } from "node:util";
39672
- import { existsSync as existsSync37, writeFileSync as writeFileSync16, readFileSync as readFileSync28, appendFileSync as appendFileSync2, mkdirSync as mkdirSync15 } from "node:fs";
39673
- import { join as join54 } from "node:path";
39674
- import { homedir as homedir13, platform as platform2 } from "node:os";
39675
- async function checkToolSupport(modelName, backendUrl = "http://localhost:11434") {
39676
- if (_toolSupportCache.has(modelName))
39677
- return _toolSupportCache.get(modelName);
39678
- try {
39679
- const resp = await fetch(`${backendUrl.replace(/\/+$/, "")}/api/chat`, {
39680
- method: "POST",
39681
- headers: { "Content-Type": "application/json" },
39682
- body: JSON.stringify({
39683
- model: modelName,
39684
- messages: [{ role: "user", content: "test" }],
39685
- tools: [{ type: "function", function: { name: "test", description: "test", parameters: { type: "object", properties: {} } } }],
39686
- stream: false,
39687
- options: { num_predict: 1 }
39688
- }),
39689
- signal: AbortSignal.timeout(1e4)
39690
- });
39691
- const hasTools = resp.ok;
39692
- _toolSupportCache.set(modelName, hasTools);
39693
- return hasTools;
39694
- } catch {
39695
- _toolSupportCache.set(modelName, true);
39696
- return true;
39675
+ // packages/cli/dist/tui/overlay-lock.js
39676
+ var overlay_lock_exports = {};
39677
+ __export(overlay_lock_exports, {
39678
+ bufferIfOverlay: () => bufferIfOverlay,
39679
+ enterOverlay: () => enterOverlay,
39680
+ isOverlayActive: () => isOverlayActive,
39681
+ leaveOverlay: () => leaveOverlay,
39682
+ onOverlayLeave: () => onOverlayLeave,
39683
+ overlayWrite: () => overlayWrite
39684
+ });
39685
+ function onOverlayLeave(cb) {
39686
+ _onLeaveCallback = cb;
39687
+ }
39688
+ function isOverlayActive() {
39689
+ return _overlayActive;
39690
+ }
39691
+ function overlayWrite(data) {
39692
+ if (_origStdoutWrite) {
39693
+ return _origStdoutWrite.call(process.stdout, data);
39697
39694
  }
39695
+ return process.stdout.write(data);
39698
39696
  }
39699
- function detectSystemSpecs() {
39700
- let totalRamGB = 0;
39701
- let availableRamGB = 0;
39702
- let gpuVramGB = 0;
39703
- let gpuName = "";
39704
- try {
39705
- const memInfo = execSync27("free -b 2>/dev/null || sysctl -n hw.memsize 2>/dev/null", {
39706
- encoding: "utf8",
39707
- timeout: 5e3
39708
- });
39709
- if (memInfo.includes("Mem:")) {
39710
- const match = memInfo.match(/^Mem:\s+(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)/m);
39711
- if (match) {
39712
- totalRamGB = parseInt(match[1], 10) / 1024 ** 3;
39713
- availableRamGB = parseInt(match[2], 10) / 1024 ** 3;
39714
- }
39715
- } else {
39716
- const bytes = parseInt(memInfo.trim(), 10);
39717
- if (!isNaN(bytes)) {
39718
- totalRamGB = bytes / 1024 ** 3;
39719
- availableRamGB = totalRamGB * 0.7;
39697
+ function bufferIfOverlay(stream, data) {
39698
+ if (!_overlayActive)
39699
+ return false;
39700
+ _buffer.push({ stream, data });
39701
+ return true;
39702
+ }
39703
+ function enterOverlay() {
39704
+ _depth++;
39705
+ if (_depth === 1) {
39706
+ _overlayActive = true;
39707
+ _origStdoutWrite = process.stdout.write.bind(process.stdout);
39708
+ _origStderrWrite = process.stderr.write.bind(process.stderr);
39709
+ process.stdout.write = function(chunk, ...args) {
39710
+ _buffer.push({ stream: "stdout", data: chunk });
39711
+ const cb = typeof args[args.length - 1] === "function" ? args[args.length - 1] : void 0;
39712
+ if (cb)
39713
+ cb();
39714
+ return true;
39715
+ };
39716
+ process.stderr.write = function(chunk, ...args) {
39717
+ _buffer.push({ stream: "stderr", data: chunk });
39718
+ const cb = typeof args[args.length - 1] === "function" ? args[args.length - 1] : void 0;
39719
+ if (cb)
39720
+ cb();
39721
+ return true;
39722
+ };
39723
+ }
39724
+ }
39725
+ function leaveOverlay() {
39726
+ _depth = Math.max(0, _depth - 1);
39727
+ if (_depth === 0) {
39728
+ _overlayActive = false;
39729
+ if (_origStdoutWrite) {
39730
+ process.stdout.write = _origStdoutWrite;
39731
+ _origStdoutWrite = null;
39732
+ }
39733
+ if (_origStderrWrite) {
39734
+ process.stderr.write = _origStderrWrite;
39735
+ _origStderrWrite = null;
39736
+ }
39737
+ if (_onLeaveCallback) {
39738
+ try {
39739
+ _onLeaveCallback();
39740
+ } catch {
39720
39741
  }
39721
39742
  }
39722
- } catch {
39723
- }
39724
- try {
39725
- const nvidiaSmi = execSync27("nvidia-smi --query-gpu=memory.total,name --format=csv,noheader,nounits 2>/dev/null", { encoding: "utf8", timeout: 5e3 });
39726
- const lines = nvidiaSmi.trim().split("\n");
39727
- if (lines.length > 0) {
39728
- for (const line of lines) {
39729
- const parts = line.split(",").map((s) => s.trim());
39730
- const vramMB = parseInt(parts[0] ?? "0", 10);
39731
- if (!isNaN(vramMB))
39732
- gpuVramGB += vramMB / 1024;
39733
- if (!gpuName && parts[1])
39734
- gpuName = parts[1];
39743
+ const pending = _buffer;
39744
+ _buffer = [];
39745
+ for (const { stream, data } of pending) {
39746
+ if (stream === "stdout") {
39747
+ process.stdout.write(data);
39748
+ } else {
39749
+ process.stderr.write(data);
39735
39750
  }
39736
39751
  }
39737
- } catch {
39738
39752
  }
39739
- return {
39740
- totalRamGB: Math.round(totalRamGB * 10) / 10,
39741
- availableRamGB: Math.round(availableRamGB * 10) / 10,
39742
- gpuVramGB: Math.round(gpuVramGB * 10) / 10,
39743
- gpuName
39744
- };
39745
39753
  }
39746
- async function detectSystemSpecsAsync() {
39747
- let totalRamGB = 0;
39748
- let availableRamGB = 0;
39749
- let gpuVramGB = 0;
39750
- let gpuName = "";
39751
- try {
39752
- const { stdout: memInfo } = await execAsync("free -b 2>/dev/null || sysctl -n hw.memsize 2>/dev/null", { timeout: 5e3 });
39753
- if (memInfo.includes("Mem:")) {
39754
- const match = memInfo.match(/^Mem:\s+(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)/m);
39755
- if (match) {
39756
- totalRamGB = parseInt(match[1], 10) / 1024 ** 3;
39757
- availableRamGB = parseInt(match[2], 10) / 1024 ** 3;
39758
- }
39754
+ var _overlayActive, _depth, _buffer, _origStdoutWrite, _origStderrWrite, _onLeaveCallback;
39755
+ var init_overlay_lock = __esm({
39756
+ "packages/cli/dist/tui/overlay-lock.js"() {
39757
+ "use strict";
39758
+ _overlayActive = false;
39759
+ _depth = 0;
39760
+ _buffer = [];
39761
+ _origStdoutWrite = null;
39762
+ _origStderrWrite = null;
39763
+ _onLeaveCallback = null;
39764
+ }
39765
+ });
39766
+
39767
+ // packages/cli/dist/tui/tui-select.js
39768
+ function ansi3(code, text) {
39769
+ return isTTY3 ? `\x1B[${code}m${text}\x1B[0m` : text;
39770
+ }
39771
+ function fg2562(code, text) {
39772
+ return isTTY3 ? `\x1B[38;5;${code}m${text}\x1B[0m` : text;
39773
+ }
39774
+ function stripAnsi(s) {
39775
+ return s.replace(/\x1B\[[0-9;]*m/g, "");
39776
+ }
39777
+ function defaultRenderRow(item, focused, isActive) {
39778
+ const marker = isActive ? selectColors.green("\u25CF") : focused ? selectColors.orange("\u25CF") : selectColors.dim("\u25CB");
39779
+ const label = focused ? selectColors.orange(selectColors.bold(item.label)) : isActive ? selectColors.green(item.label) : item.label;
39780
+ const detail = item.detail ? ` ${selectColors.dim(item.detail)}` : "";
39781
+ return ` ${marker} ${label}${detail}`;
39782
+ }
39783
+ function matchRow(item, focused, isActive) {
39784
+ if (focused || isActive) {
39785
+ return defaultRenderRow(item, focused, isActive);
39786
+ }
39787
+ const marker = selectColors.matchLight("\u25CB");
39788
+ const label = selectColors.matchLight(stripAnsi(item.label));
39789
+ const detail = item.detail ? ` ${selectColors.dim(stripAnsi(item.detail))}` : "";
39790
+ return ` ${marker} ${label}${detail}`;
39791
+ }
39792
+ function tuiSelect(opts) {
39793
+ const { items, title, rl } = opts;
39794
+ const renderRow = opts.renderRow ?? defaultRenderRow;
39795
+ const activeKey = opts.activeKey ?? null;
39796
+ const skipSet = new Set(opts.skipKeys ?? []);
39797
+ if (items.length === 0) {
39798
+ return Promise.resolve({ confirmed: false, key: null, index: -1 });
39799
+ }
39800
+ const isSkippable = (idx) => skipSet.has(items[idx].key);
39801
+ let filter = "";
39802
+ let matchSet = /* @__PURE__ */ new Set();
39803
+ let deleteConfirmIdx = -1;
39804
+ let deleteConfirmSel = false;
39805
+ function updateFilter() {
39806
+ if (!filter) {
39807
+ matchSet = new Set(items.map((_, i) => i));
39759
39808
  } else {
39760
- const bytes = parseInt(memInfo.trim(), 10);
39761
- if (!isNaN(bytes)) {
39762
- totalRamGB = bytes / 1024 ** 3;
39763
- availableRamGB = totalRamGB * 0.7;
39809
+ const lower = filter.toLowerCase();
39810
+ matchSet = /* @__PURE__ */ new Set();
39811
+ for (let i = 0; i < items.length; i++) {
39812
+ if (isSkippable(i))
39813
+ continue;
39814
+ const plain = stripAnsi(items[i].label).toLowerCase();
39815
+ const detailPlain = items[i].detail ? stripAnsi(items[i].detail).toLowerCase() : "";
39816
+ if (plain.includes(lower) || detailPlain.includes(lower)) {
39817
+ matchSet.add(i);
39818
+ }
39764
39819
  }
39765
39820
  }
39766
- } catch {
39767
39821
  }
39768
- try {
39769
- const { stdout: nvidiaSmi } = await execAsync("nvidia-smi --query-gpu=memory.total,name --format=csv,noheader,nounits 2>/dev/null", { timeout: 5e3 });
39770
- const lines = nvidiaSmi.trim().split("\n");
39771
- if (lines.length > 0) {
39772
- for (const line of lines) {
39773
- const parts = line.split(",").map((s) => s.trim());
39774
- const vramMB = parseInt(parts[0] ?? "0", 10);
39775
- if (!isNaN(vramMB))
39776
- gpuVramGB += vramMB / 1024;
39777
- if (!gpuName && parts[1])
39778
- gpuName = parts[1];
39779
- }
39822
+ updateFilter();
39823
+ const findSelectable = (from, dir) => {
39824
+ let idx = from;
39825
+ while (idx >= 0 && idx < items.length) {
39826
+ if (!isSkippable(idx) && matchSet.has(idx))
39827
+ return idx;
39828
+ idx += dir;
39780
39829
  }
39781
- } catch {
39782
- }
39783
- return {
39784
- totalRamGB: Math.round(totalRamGB * 10) / 10,
39785
- availableRamGB: Math.round(availableRamGB * 10) / 10,
39786
- gpuVramGB: Math.round(gpuVramGB * 10) / 10,
39787
- gpuName
39830
+ return -1;
39788
39831
  };
39789
- }
39790
- function recommendModel(specs) {
39791
- const effectiveGB = Math.max(specs.gpuVramGB, specs.availableRamGB);
39792
- const budget = effectiveGB * 0.8;
39793
- const localVariants = QWEN_VARIANTS.filter((v) => !v.cloud);
39794
- for (let i = localVariants.length - 1; i >= 0; i--) {
39795
- if (localVariants[i].sizeGB <= budget) {
39796
- return localVariants[i];
39797
- }
39798
- }
39799
- return QWEN_VARIANTS.find((v) => v.tag === "qwen3.5:cloud");
39800
- }
39801
- function calculateContextWindow(specs, modelSizeGB2, kvBytesPerToken, archMax) {
39802
- const totalAvail = Math.max(specs.gpuVramGB, specs.totalRamGB);
39803
- const remaining = Math.max(0, totalAvail - modelSizeGB2);
39804
- const usableGB = remaining * 0.85;
39805
- let numCtx;
39806
- if (kvBytesPerToken && kvBytesPerToken > 0) {
39807
- const maxTokens = Math.floor(usableGB * 1024 ** 3 / kvBytesPerToken);
39808
- numCtx = Math.max(2048, Math.floor(maxTokens / 1024) * 1024);
39809
- } else {
39810
- const kvEstimate = modelSizeGB2 <= 5 ? 524288 : modelSizeGB2 <= 20 ? 1048576 : 1572864;
39811
- const maxTokens = Math.floor(usableGB * 1024 ** 3 / kvEstimate);
39812
- numCtx = Math.max(2048, Math.floor(maxTokens / 1024) * 1024);
39813
- }
39814
- numCtx = Math.min(numCtx, 131072);
39815
- if (archMax && archMax > 0)
39816
- numCtx = Math.min(numCtx, archMax);
39817
- if (kvBytesPerToken && kvBytesPerToken > 0 && modelSizeGB2 > 0) {
39818
- const maxKVBytes = modelSizeGB2 * 4 * 1024 ** 3;
39819
- const maxTokensFromBudget = Math.floor(maxKVBytes / kvBytesPerToken);
39820
- const budgetCap = Math.max(2048, Math.floor(maxTokensFromBudget / 1024) * 1024);
39821
- numCtx = Math.min(numCtx, budgetCap);
39832
+ let cursor = activeKey ? items.findIndex((i) => i.key === activeKey) : -1;
39833
+ if (cursor < 0 || isSkippable(cursor)) {
39834
+ const first = findSelectable(0, 1);
39835
+ cursor = first >= 0 ? first : 0;
39822
39836
  }
39823
- const label = numCtx >= 1024 ? `${Math.floor(numCtx / 1024)}K` : String(numCtx);
39824
- return { numCtx, label };
39825
- }
39826
- function ask(rl, question) {
39827
- return new Promise((resolve36) => {
39828
- rl.question(question, (answer) => resolve36(answer.trim()));
39829
- });
39830
- }
39831
- function askSecret(rl, question) {
39837
+ const reservedTopBottom = 6;
39838
+ const hasCrumbs = opts.breadcrumbs && opts.breadcrumbs.length > 0;
39839
+ const selectChrome = (hasCrumbs ? 11 : 10) + 3;
39840
+ const contentArea = opts.availableRows ? opts.availableRows + reservedTopBottom : process.stdout.rows ?? 24;
39841
+ const maxVisible = opts.maxVisible ?? Math.max(3, contentArea - selectChrome);
39842
+ let scrollOffset = 0;
39843
+ let lastRenderedLines = 0;
39832
39844
  return new Promise((resolve36) => {
39833
- process.stdout.write(question);
39834
- let secret = "";
39835
39845
  const stdin = process.stdin;
39836
39846
  const hadRawMode = stdin.isRaw;
39847
+ const savedRlListeners = [];
39848
+ if (rl) {
39849
+ rl.pause();
39850
+ for (const event of ["keypress", "data"]) {
39851
+ const listeners = stdin.listeners(event);
39852
+ for (const fn of listeners) {
39853
+ savedRlListeners.push({ event, fn });
39854
+ stdin.removeListener(event, fn);
39855
+ }
39856
+ }
39857
+ }
39837
39858
  if (typeof stdin.setRawMode === "function") {
39838
39859
  stdin.setRawMode(true);
39839
39860
  }
39840
39861
  stdin.resume();
39841
- const onData = (chunk) => {
39842
- const ch = chunk.toString("utf8");
39843
- for (const c3 of ch) {
39844
- if (c3 === "\r" || c3 === "\n") {
39845
- stdin.removeListener("data", onData);
39846
- if (typeof stdin.setRawMode === "function") {
39847
- stdin.setRawMode(hadRawMode ?? false);
39848
- }
39849
- process.stdout.write("\n");
39850
- resolve36(secret.trim());
39851
- return;
39852
- } else if (c3 === "") {
39853
- stdin.removeListener("data", onData);
39854
- if (typeof stdin.setRawMode === "function") {
39855
- stdin.setRawMode(hadRawMode ?? false);
39856
- }
39857
- process.stdout.write("\n");
39858
- resolve36("");
39859
- return;
39860
- } else if (c3 === "\x7F" || c3 === "\b") {
39861
- if (secret.length > 0) {
39862
- secret = secret.slice(0, -1);
39863
- process.stdout.write("\b \b");
39862
+ enterOverlay();
39863
+ overlayWrite("\x1B[?1049h\x1B[48;5;234m\x1B[2J\x1B[H\x1B[?25l\x1B[?1003h\x1B[?1006h");
39864
+ let listRowOffset = 0;
39865
+ function clampScroll(displayList) {
39866
+ const cursorPos = displayList.indexOf(cursor);
39867
+ if (cursorPos < 0)
39868
+ return;
39869
+ if (cursorPos < scrollOffset) {
39870
+ scrollOffset = cursorPos;
39871
+ } else if (cursorPos >= scrollOffset + maxVisible) {
39872
+ scrollOffset = cursorPos - maxVisible + 1;
39873
+ }
39874
+ const maxOffset = Math.max(0, displayList.length - maxVisible);
39875
+ scrollOffset = Math.max(0, Math.min(maxOffset, scrollOffset));
39876
+ }
39877
+ const hasBreadcrumbs = opts.breadcrumbs && opts.breadcrumbs.length > 0;
39878
+ function render() {
39879
+ overlayWrite("\x1B[48;5;234m\x1B[H\x1B[2J");
39880
+ const lines = [];
39881
+ lines.push("", "", "");
39882
+ if (hasBreadcrumbs) {
39883
+ const trail = opts.breadcrumbs.map((b) => selectColors.dim(b)).join(selectColors.dim(" \u203A "));
39884
+ lines.push(`
39885
+ ${selectColors.cyan("\u2190")} ${trail}`);
39886
+ }
39887
+ if (title) {
39888
+ if (!hasBreadcrumbs)
39889
+ lines.push("");
39890
+ lines.push(` ${selectColors.bold(title)}`);
39891
+ }
39892
+ if (filter) {
39893
+ const count = matchSet.size;
39894
+ lines.push(` ${selectColors.cyan("/")} ${selectColors.bold(filter)} ${selectColors.dim(`(${count} match${count !== 1 ? "es" : ""})`)}`);
39895
+ } else {
39896
+ lines.push(` ${selectColors.dim("Type to filter...")}`);
39897
+ }
39898
+ lines.push("");
39899
+ let displayList;
39900
+ if (filter) {
39901
+ displayList = [];
39902
+ for (let i = 0; i < items.length; i++) {
39903
+ if (matchSet.has(i) || isSkippable(i)) {
39904
+ displayList.push(i);
39864
39905
  }
39865
- } else if (c3.charCodeAt(0) >= 32) {
39866
- secret += c3;
39867
- process.stdout.write("*");
39868
39906
  }
39907
+ displayList = displayList.filter((idx, pos) => {
39908
+ if (!isSkippable(idx))
39909
+ return true;
39910
+ for (let j = pos + 1; j < displayList.length; j++) {
39911
+ if (!isSkippable(displayList[j]))
39912
+ return true;
39913
+ break;
39914
+ }
39915
+ return false;
39916
+ });
39917
+ } else {
39918
+ displayList = items.map((_, i) => i);
39869
39919
  }
39870
- };
39871
- stdin.on("data", onData);
39872
- });
39873
- }
39874
- function ensureCurl() {
39875
- if (hasCmd("curl"))
39876
- return true;
39877
- const plat = platform2();
39878
- if (plat === "win32") {
39879
- process.stdout.write(` ${c2.yellow("\u26A0")} curl not found on Windows \u2014 install it manually.
39880
- `);
39881
- return false;
39882
- }
39883
- process.stdout.write(` ${c2.cyan("\u25CF")} curl not found. Installing...
39884
- `);
39885
- const strategies = [
39886
- { check: "apt-get", install: "sudo apt-get update -qq && sudo apt-get install -y -qq curl", label: "apt" },
39887
- { check: "dnf", install: "sudo dnf install -y -q curl", label: "dnf" },
39888
- { check: "yum", install: "sudo yum install -y -q curl", label: "yum" },
39889
- { check: "pacman", install: "sudo pacman -S --noconfirm curl", label: "pacman" },
39890
- { check: "apk", install: "sudo apk add --quiet curl", label: "apk" },
39891
- { check: "zypper", install: "sudo zypper install -y curl", label: "zypper" }
39892
- ];
39893
- for (const s of strategies) {
39894
- if (hasCmd(s.check)) {
39895
- try {
39896
- execSync27(s.install, { stdio: "inherit", timeout: 12e4 });
39897
- if (hasCmd("curl")) {
39898
- process.stdout.write(` ${c2.green("\u2714")} curl installed via ${s.label}.
39899
- `);
39900
- return true;
39920
+ clampScroll(displayList);
39921
+ const visibleStart = scrollOffset;
39922
+ const visibleEnd = Math.min(displayList.length, scrollOffset + maxVisible);
39923
+ if (visibleStart > 0) {
39924
+ lines.push(` ${selectColors.dim(` \u25B2 ${visibleStart} more`)}`);
39925
+ }
39926
+ listRowOffset = lines.length;
39927
+ for (let vi = visibleStart; vi < visibleEnd; vi++) {
39928
+ const idx = displayList[vi];
39929
+ const item = items[idx];
39930
+ if (isSkippable(idx)) {
39931
+ lines.push(` ${item.label}`);
39932
+ continue;
39901
39933
  }
39902
- } catch {
39903
- process.stdout.write(` ${c2.yellow("\u26A0")} ${s.label} install failed, trying next...
39904
- `);
39934
+ const focused = idx === cursor;
39935
+ const isActive = item.key === activeKey;
39936
+ if (deleteConfirmIdx === idx) {
39937
+ const yesLabel = deleteConfirmSel ? selectColors.bold(selectColors.green("[Yes]")) : selectColors.dim("[Yes]");
39938
+ const noLabel = !deleteConfirmSel ? selectColors.bold(selectColors.orange("[No]")) : selectColors.dim("[No]");
39939
+ lines.push(` ${ansi3("31", "\u2715")} ${ansi3("31", stripAnsi(item.label))} Delete? ${yesLabel} ${noLabel}`);
39940
+ } else if (filter) {
39941
+ lines.push(matchRow(item, focused, isActive));
39942
+ } else {
39943
+ lines.push(renderRow(item, focused, isActive));
39944
+ }
39945
+ }
39946
+ const remaining = displayList.length - visibleEnd;
39947
+ if (remaining > 0) {
39948
+ lines.push(` ${selectColors.dim(` \u25BC ${remaining} more`)}`);
39949
+ }
39950
+ lines.push("");
39951
+ if (deleteConfirmIdx >= 0) {
39952
+ lines.push(` ${selectColors.dim("\u2190/\u2192 select Enter confirm Esc cancel")}`);
39953
+ } else {
39954
+ const actionHint = opts.onAction ? " \u2190/\u2192/Space toggle" : "";
39955
+ const deleteHint = opts.onDelete ? " Del remove" : "";
39956
+ const customHint = opts.customKeyHint ?? "";
39957
+ const escLabel = filter ? "clear filter" : hasBreadcrumbs ? "\u2190 back" : "cancel";
39958
+ lines.push(` ${selectColors.dim("\u2191/\u2193 navigate Enter/Click select" + actionHint + deleteHint + customHint + " Esc " + escLabel + " Type to filter")}`);
39905
39959
  }
39960
+ lines.push("");
39961
+ let output = lines.join("\n").replace(/\x1B\[0m/g, "\x1B[0m\x1B[48;5;234m").replace(/\n/g, "\x1B[K\n\x1B[48;5;234m");
39962
+ overlayWrite("\x1B[48;5;234m" + output + "\x1B[K");
39963
+ lastRenderedLines = lines.length;
39906
39964
  }
39907
- }
39908
- if (plat === "darwin") {
39909
- try {
39910
- execSync27("xcode-select --install", { stdio: "inherit", timeout: 3e5 });
39911
- if (hasCmd("curl"))
39912
- return true;
39913
- } catch {
39965
+ function cleanup() {
39966
+ stdin.removeListener("data", onData);
39967
+ process.stdout.removeListener("resize", onResize);
39968
+ overlayWrite("\x1B[?1003l\x1B[?1002l\x1B[?1006l\x1B[?1049l\x1B[?25h");
39969
+ leaveOverlay();
39970
+ if (typeof stdin.setRawMode === "function") {
39971
+ stdin.setRawMode(hadRawMode ?? false);
39972
+ }
39973
+ for (const { event, fn } of savedRlListeners) {
39974
+ stdin.on(event, fn);
39975
+ }
39976
+ if (rl) {
39977
+ rl.resume();
39978
+ rl.prompt(false);
39979
+ }
39914
39980
  }
39915
- }
39916
- process.stdout.write(` ${c2.red("\u2716")} Could not install curl. Install it manually and retry.
39917
- `);
39918
- return false;
39919
- }
39920
- async function autoInstallOllama(rl) {
39921
- const plat = platform2();
39922
- if (plat !== "win32" && !ensureCurl()) {
39923
- return false;
39924
- }
39925
- if (plat === "linux") {
39926
- return installOllamaLinux();
39927
- } else if (plat === "darwin") {
39928
- return await installOllamaMac(rl);
39981
+ function onData(chunk) {
39982
+ let seq = chunk.toString("utf8");
39983
+ const mouseRe = /\x1B\[<(\d+);(\d+);(\d+)([Mm])/g;
39984
+ let mouseProcessed = false;
39985
+ let mouseM;
39986
+ while ((mouseM = mouseRe.exec(seq)) !== null) {
39987
+ mouseProcessed = true;
39988
+ const btn = parseInt(mouseM[1]);
39989
+ const mCol = parseInt(mouseM[2]);
39990
+ const mRow = parseInt(mouseM[3]);
39991
+ const suffix = mouseM[4];
39992
+ if (btn === 0 && suffix === "M") {
39993
+ const listIdx = mRow - listRowOffset - 1;
39994
+ if (listIdx >= 0 && listIdx < maxVisible) {
39995
+ let displayList;
39996
+ if (filter) {
39997
+ displayList = [];
39998
+ for (let i = 0; i < items.length; i++) {
39999
+ if (matchSet.has(i) || isSkippable(i))
40000
+ displayList.push(i);
40001
+ }
40002
+ displayList = displayList.filter((idx, pos) => {
40003
+ if (!isSkippable(idx))
40004
+ return true;
40005
+ for (let j = pos + 1; j < displayList.length; j++) {
40006
+ if (!isSkippable(displayList[j]))
40007
+ return true;
40008
+ break;
40009
+ }
40010
+ return false;
40011
+ });
40012
+ } else {
40013
+ displayList = items.map((_, i) => i);
40014
+ }
40015
+ const vi = scrollOffset + listIdx;
40016
+ if (vi < displayList.length) {
40017
+ const itemIdx = displayList[vi];
40018
+ if (!isSkippable(itemIdx) && matchSet.has(itemIdx)) {
40019
+ cursor = itemIdx;
40020
+ cleanup();
40021
+ resolve36({ confirmed: true, key: items[cursor].key, index: cursor });
40022
+ return;
40023
+ } else if (!isSkippable(itemIdx)) {
40024
+ cursor = itemIdx;
40025
+ render();
40026
+ }
40027
+ }
40028
+ }
40029
+ }
40030
+ if ((btn === 35 || btn === 32 || btn === 67) && suffix === "M") {
40031
+ const listIdx = mRow - listRowOffset - 1;
40032
+ if (listIdx >= 0 && listIdx < maxVisible) {
40033
+ let displayList;
40034
+ if (filter) {
40035
+ displayList = [];
40036
+ for (let i = 0; i < items.length; i++) {
40037
+ if (matchSet.has(i) || isSkippable(i))
40038
+ displayList.push(i);
40039
+ }
40040
+ displayList = displayList.filter((idx, pos) => {
40041
+ if (!isSkippable(idx))
40042
+ return true;
40043
+ for (let j = pos + 1; j < displayList.length; j++) {
40044
+ if (!isSkippable(displayList[j]))
40045
+ return true;
40046
+ break;
40047
+ }
40048
+ return false;
40049
+ });
40050
+ } else {
40051
+ displayList = items.map((_, i) => i);
40052
+ }
40053
+ const vi = scrollOffset + listIdx;
40054
+ if (vi < displayList.length) {
40055
+ const itemIdx = displayList[vi];
40056
+ if (!isSkippable(itemIdx) && itemIdx !== cursor) {
40057
+ cursor = itemIdx;
40058
+ render();
40059
+ }
40060
+ }
40061
+ }
40062
+ }
40063
+ if (btn === 64) {
40064
+ const next = findSelectable(cursor - 1, -1);
40065
+ if (next >= 0 && next !== cursor) {
40066
+ cursor = next;
40067
+ render();
40068
+ }
40069
+ } else if (btn === 65) {
40070
+ const next = findSelectable(cursor + 1, 1);
40071
+ if (next >= 0 && next !== cursor) {
40072
+ cursor = next;
40073
+ render();
40074
+ }
40075
+ }
40076
+ }
40077
+ seq = seq.replace(mouseRe, "");
40078
+ if (!seq && mouseProcessed)
40079
+ return;
40080
+ if (deleteConfirmIdx >= 0) {
40081
+ if (seq === "\x1B[D") {
40082
+ deleteConfirmSel = true;
40083
+ render();
40084
+ } else if (seq === "\x1B[C") {
40085
+ deleteConfirmSel = false;
40086
+ render();
40087
+ } else if (seq === "\r" || seq === "\n") {
40088
+ if (deleteConfirmSel && opts.onDelete) {
40089
+ const deletedIdx = deleteConfirmIdx;
40090
+ deleteConfirmIdx = -1;
40091
+ deleteConfirmSel = false;
40092
+ opts.onDelete(items[deletedIdx], (removed) => {
40093
+ if (removed) {
40094
+ items.splice(deletedIdx, 1);
40095
+ if (items.length === 0) {
40096
+ cleanup();
40097
+ resolve36({ confirmed: false, key: null, index: -1 });
40098
+ return;
40099
+ }
40100
+ updateFilter();
40101
+ if (cursor >= items.length)
40102
+ cursor = items.length - 1;
40103
+ const valid = findSelectable(cursor, 1) ?? findSelectable(cursor, -1);
40104
+ if (valid >= 0)
40105
+ cursor = valid;
40106
+ scrollOffset = 0;
40107
+ }
40108
+ render();
40109
+ });
40110
+ } else {
40111
+ deleteConfirmIdx = -1;
40112
+ deleteConfirmSel = false;
40113
+ render();
40114
+ }
40115
+ } else if (seq === "\x1B" || seq === "\x1B\x1B" || seq === "") {
40116
+ deleteConfirmIdx = -1;
40117
+ deleteConfirmSel = false;
40118
+ render();
40119
+ }
40120
+ return;
40121
+ }
40122
+ if (seq === "\x1B[A") {
40123
+ const next = findSelectable(cursor - 1, -1);
40124
+ if (next >= 0 && next !== cursor) {
40125
+ cursor = next;
40126
+ render();
40127
+ }
40128
+ } else if (seq === "\x1B[B") {
40129
+ const next = findSelectable(cursor + 1, 1);
40130
+ if (next >= 0 && next !== cursor) {
40131
+ cursor = next;
40132
+ render();
40133
+ }
40134
+ } else if (seq === "\x1B[5~") {
40135
+ for (let i = 0; i < maxVisible; i++) {
40136
+ const next = findSelectable(cursor - 1, -1);
40137
+ if (next < 0 || next === cursor)
40138
+ break;
40139
+ cursor = next;
40140
+ }
40141
+ render();
40142
+ } else if (seq === "\x1B[6~") {
40143
+ for (let i = 0; i < maxVisible; i++) {
40144
+ const next = findSelectable(cursor + 1, 1);
40145
+ if (next < 0 || next === cursor)
40146
+ break;
40147
+ cursor = next;
40148
+ }
40149
+ render();
40150
+ } else if (seq === "\x1B[H") {
40151
+ const first = findSelectable(0, 1);
40152
+ if (first >= 0) {
40153
+ cursor = first;
40154
+ render();
40155
+ }
40156
+ } else if (seq === "\x1B[F") {
40157
+ const last = findSelectable(items.length - 1, -1);
40158
+ if (last >= 0) {
40159
+ cursor = last;
40160
+ render();
40161
+ }
40162
+ } else if (seq === "\x1B[3~" && opts.onDelete) {
40163
+ if (!isSkippable(cursor) && matchSet.has(cursor) && items[cursor].key !== activeKey) {
40164
+ deleteConfirmIdx = cursor;
40165
+ deleteConfirmSel = false;
40166
+ render();
40167
+ }
40168
+ } else if (seq === "\x1B[D" && opts.onAction) {
40169
+ if (!isSkippable(cursor) && matchSet.has(cursor)) {
40170
+ if (opts.onAction(items[cursor], "left"))
40171
+ render();
40172
+ }
40173
+ } else if (seq === "\x1B[C" && opts.onAction) {
40174
+ if (!isSkippable(cursor) && matchSet.has(cursor)) {
40175
+ if (opts.onAction(items[cursor], "right"))
40176
+ render();
40177
+ }
40178
+ } else if (seq === " " && opts.onAction) {
40179
+ if (!isSkippable(cursor) && matchSet.has(cursor)) {
40180
+ if (opts.onAction(items[cursor], "space"))
40181
+ render();
40182
+ }
40183
+ } else if (seq === "\r" || seq === "\n") {
40184
+ if (!isSkippable(cursor) && matchSet.has(cursor)) {
40185
+ if (opts.onEnter) {
40186
+ const consumed = opts.onEnter(items[cursor], {
40187
+ done: () => render(),
40188
+ resolve: (result) => {
40189
+ cleanup();
40190
+ resolve36(result);
40191
+ },
40192
+ getInput: (prompt, prefill) => getInputFromUser(prompt, prefill),
40193
+ render: () => render(),
40194
+ updateItem: (index, updates) => {
40195
+ Object.assign(items[index], updates);
40196
+ render();
40197
+ }
40198
+ });
40199
+ if (consumed)
40200
+ return;
40201
+ }
40202
+ cleanup();
40203
+ resolve36({ confirmed: true, key: items[cursor].key, index: cursor });
40204
+ }
40205
+ } else if (seq === "\x1B" || seq === "\x1B\x1B") {
40206
+ if (filter) {
40207
+ filter = "";
40208
+ updateFilter();
40209
+ const valid = findSelectable(cursor, 1);
40210
+ if (valid >= 0)
40211
+ cursor = valid;
40212
+ scrollOffset = 0;
40213
+ render();
40214
+ } else if (hasBreadcrumbs) {
40215
+ cleanup();
40216
+ resolve36({ confirmed: false, key: "__back__", index: cursor });
40217
+ } else {
40218
+ cleanup();
40219
+ resolve36({ confirmed: false, key: null, index: cursor });
40220
+ }
40221
+ } else if (seq === "") {
40222
+ cleanup();
40223
+ resolve36({ confirmed: false, key: null, index: cursor });
40224
+ } else if (seq === "\x7F" || seq === "\b") {
40225
+ if (filter.length > 0) {
40226
+ filter = filter.slice(0, -1);
40227
+ updateFilter();
40228
+ if (!matchSet.has(cursor) || isSkippable(cursor)) {
40229
+ const next = findSelectable(0, 1);
40230
+ if (next >= 0)
40231
+ cursor = next;
40232
+ }
40233
+ scrollOffset = 0;
40234
+ render();
40235
+ } else if (hasBreadcrumbs) {
40236
+ cleanup();
40237
+ resolve36({ confirmed: false, key: "__back__", index: cursor });
40238
+ }
40239
+ } else if (seq.length === 1 && seq.charCodeAt(0) >= 32 && seq.charCodeAt(0) < 127) {
40240
+ if (opts.onCustomKey && !isSkippable(cursor) && matchSet.has(cursor)) {
40241
+ const consumed = opts.onCustomKey(items[cursor], seq, {
40242
+ done: () => render(),
40243
+ resolve: (result) => {
40244
+ cleanup();
40245
+ resolve36(result);
40246
+ },
40247
+ getInput: (prompt, prefill) => getInputFromUser(prompt, prefill),
40248
+ render: () => render(),
40249
+ updateItem: (index, updates) => {
40250
+ Object.assign(items[index], updates);
40251
+ render();
40252
+ }
40253
+ });
40254
+ if (consumed)
40255
+ return;
40256
+ }
40257
+ filter += seq;
40258
+ updateFilter();
40259
+ if (matchSet.size > 0) {
40260
+ const first = findSelectable(0, 1);
40261
+ if (first >= 0)
40262
+ cursor = first;
40263
+ }
40264
+ scrollOffset = 0;
40265
+ render();
40266
+ }
40267
+ }
40268
+ function getInputFromUser(prompt, prefill = "") {
40269
+ return new Promise((inputResolve) => {
40270
+ let inputBuf = prefill;
40271
+ let inputCursor = prefill.length;
40272
+ overlayWrite("\x1B[?25h");
40273
+ function renderInput() {
40274
+ overlayWrite(`\x1B[${lastRenderedLines}A`);
40275
+ const saveFooter = lastRenderedLines;
40276
+ for (let i = 0; i < saveFooter; i++)
40277
+ overlayWrite("\x1B[B");
40278
+ overlayWrite("\x1B[2A");
40279
+ overlayWrite("\x1B[2K");
40280
+ const before = inputBuf.slice(0, inputCursor);
40281
+ const after = inputBuf.slice(inputCursor);
40282
+ overlayWrite(` ${selectColors.cyan("\u203A")} ${selectColors.bold(prompt)} ${before}\x1B[7m${after.charAt(0) || " "}\x1B[27m${after.slice(1)}`);
40283
+ overlayWrite("\n\x1B[2K");
40284
+ overlayWrite(` ${selectColors.dim("Enter confirm Esc cancel")}`);
40285
+ overlayWrite("\n");
40286
+ }
40287
+ function onInputData(chunk) {
40288
+ const s = chunk.toString("utf8");
40289
+ if (s === "\r" || s === "\n") {
40290
+ stdin.removeListener("data", onInputData);
40291
+ stdin.on("data", onData);
40292
+ overlayWrite("\x1B[?25l");
40293
+ render();
40294
+ inputResolve(inputBuf);
40295
+ } else if (s === "\x1B" || s === "\x1B\x1B" || s === "") {
40296
+ stdin.removeListener("data", onInputData);
40297
+ stdin.on("data", onData);
40298
+ overlayWrite("\x1B[?25l");
40299
+ render();
40300
+ inputResolve(null);
40301
+ } else if (s === "\x7F" || s === "\b") {
40302
+ if (inputCursor > 0) {
40303
+ inputBuf = inputBuf.slice(0, inputCursor - 1) + inputBuf.slice(inputCursor);
40304
+ inputCursor--;
40305
+ renderInput();
40306
+ }
40307
+ } else if (s === "\x1B[D") {
40308
+ if (inputCursor > 0) {
40309
+ inputCursor--;
40310
+ renderInput();
40311
+ }
40312
+ } else if (s === "\x1B[C") {
40313
+ if (inputCursor < inputBuf.length) {
40314
+ inputCursor++;
40315
+ renderInput();
40316
+ }
40317
+ } else if (s === "\x1B[H") {
40318
+ inputCursor = 0;
40319
+ renderInput();
40320
+ } else if (s === "\x1B[F") {
40321
+ inputCursor = inputBuf.length;
40322
+ renderInput();
40323
+ } else if (s.length === 1 && s.charCodeAt(0) >= 32 && s.charCodeAt(0) < 127) {
40324
+ inputBuf = inputBuf.slice(0, inputCursor) + s + inputBuf.slice(inputCursor);
40325
+ inputCursor++;
40326
+ renderInput();
40327
+ }
40328
+ }
40329
+ stdin.removeListener("data", onData);
40330
+ stdin.on("data", onInputData);
40331
+ renderInput();
40332
+ });
40333
+ }
40334
+ const onResize = () => render();
40335
+ process.stdout.on("resize", onResize);
40336
+ stdin.on("data", onData);
40337
+ render();
40338
+ });
40339
+ }
40340
+ var isTTY3, selectColors;
40341
+ var init_tui_select = __esm({
40342
+ "packages/cli/dist/tui/tui-select.js"() {
40343
+ "use strict";
40344
+ init_overlay_lock();
40345
+ isTTY3 = process.stdout.isTTY ?? false;
40346
+ selectColors = {
40347
+ orange: (t) => fg2562(208, t),
40348
+ green: (t) => ansi3("32", t),
40349
+ dim: (t) => ansi3("2", t),
40350
+ bold: (t) => ansi3("1", t),
40351
+ cyan: (t) => ansi3("36", t),
40352
+ /** Lighter grey for filter matches (252 = near-white) */
40353
+ matchLight: (t) => fg2562(252, t),
40354
+ /** Dark grey for non-matching items (240 = dark grey) */
40355
+ matchDark: (t) => fg2562(240, t)
40356
+ };
40357
+ }
40358
+ });
40359
+
40360
+ // packages/cli/dist/tui/setup.js
40361
+ import * as readline from "node:readline";
40362
+ import { execSync as execSync27, spawn as spawn19, exec as exec2 } from "node:child_process";
40363
+ import { promisify as promisify6 } from "node:util";
40364
+ import { existsSync as existsSync37, writeFileSync as writeFileSync16, readFileSync as readFileSync28, appendFileSync as appendFileSync2, mkdirSync as mkdirSync15 } from "node:fs";
40365
+ import { join as join54 } from "node:path";
40366
+ import { homedir as homedir13, platform as platform2 } from "node:os";
40367
+ async function checkToolSupport(modelName, backendUrl = "http://localhost:11434") {
40368
+ if (_toolSupportCache.has(modelName))
40369
+ return _toolSupportCache.get(modelName);
40370
+ try {
40371
+ const resp = await fetch(`${backendUrl.replace(/\/+$/, "")}/api/chat`, {
40372
+ method: "POST",
40373
+ headers: { "Content-Type": "application/json" },
40374
+ body: JSON.stringify({
40375
+ model: modelName,
40376
+ messages: [{ role: "user", content: "test" }],
40377
+ tools: [{ type: "function", function: { name: "test", description: "test", parameters: { type: "object", properties: {} } } }],
40378
+ stream: false,
40379
+ options: { num_predict: 1 }
40380
+ }),
40381
+ signal: AbortSignal.timeout(1e4)
40382
+ });
40383
+ const hasTools = resp.ok;
40384
+ _toolSupportCache.set(modelName, hasTools);
40385
+ return hasTools;
40386
+ } catch {
40387
+ _toolSupportCache.set(modelName, true);
40388
+ return true;
40389
+ }
40390
+ }
40391
+ function detectSystemSpecs() {
40392
+ let totalRamGB = 0;
40393
+ let availableRamGB = 0;
40394
+ let gpuVramGB = 0;
40395
+ let gpuName = "";
40396
+ try {
40397
+ const memInfo = execSync27("free -b 2>/dev/null || sysctl -n hw.memsize 2>/dev/null", {
40398
+ encoding: "utf8",
40399
+ timeout: 5e3
40400
+ });
40401
+ if (memInfo.includes("Mem:")) {
40402
+ const match = memInfo.match(/^Mem:\s+(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)/m);
40403
+ if (match) {
40404
+ totalRamGB = parseInt(match[1], 10) / 1024 ** 3;
40405
+ availableRamGB = parseInt(match[2], 10) / 1024 ** 3;
40406
+ }
40407
+ } else {
40408
+ const bytes = parseInt(memInfo.trim(), 10);
40409
+ if (!isNaN(bytes)) {
40410
+ totalRamGB = bytes / 1024 ** 3;
40411
+ availableRamGB = totalRamGB * 0.7;
40412
+ }
40413
+ }
40414
+ } catch {
40415
+ }
40416
+ try {
40417
+ const nvidiaSmi = execSync27("nvidia-smi --query-gpu=memory.total,name --format=csv,noheader,nounits 2>/dev/null", { encoding: "utf8", timeout: 5e3 });
40418
+ const lines = nvidiaSmi.trim().split("\n");
40419
+ if (lines.length > 0) {
40420
+ for (const line of lines) {
40421
+ const parts = line.split(",").map((s) => s.trim());
40422
+ const vramMB = parseInt(parts[0] ?? "0", 10);
40423
+ if (!isNaN(vramMB))
40424
+ gpuVramGB += vramMB / 1024;
40425
+ if (!gpuName && parts[1])
40426
+ gpuName = parts[1];
40427
+ }
40428
+ }
40429
+ } catch {
40430
+ }
40431
+ return {
40432
+ totalRamGB: Math.round(totalRamGB * 10) / 10,
40433
+ availableRamGB: Math.round(availableRamGB * 10) / 10,
40434
+ gpuVramGB: Math.round(gpuVramGB * 10) / 10,
40435
+ gpuName
40436
+ };
40437
+ }
40438
+ async function detectSystemSpecsAsync() {
40439
+ let totalRamGB = 0;
40440
+ let availableRamGB = 0;
40441
+ let gpuVramGB = 0;
40442
+ let gpuName = "";
40443
+ try {
40444
+ const { stdout: memInfo } = await execAsync("free -b 2>/dev/null || sysctl -n hw.memsize 2>/dev/null", { timeout: 5e3 });
40445
+ if (memInfo.includes("Mem:")) {
40446
+ const match = memInfo.match(/^Mem:\s+(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)/m);
40447
+ if (match) {
40448
+ totalRamGB = parseInt(match[1], 10) / 1024 ** 3;
40449
+ availableRamGB = parseInt(match[2], 10) / 1024 ** 3;
40450
+ }
40451
+ } else {
40452
+ const bytes = parseInt(memInfo.trim(), 10);
40453
+ if (!isNaN(bytes)) {
40454
+ totalRamGB = bytes / 1024 ** 3;
40455
+ availableRamGB = totalRamGB * 0.7;
40456
+ }
40457
+ }
40458
+ } catch {
40459
+ }
40460
+ try {
40461
+ const { stdout: nvidiaSmi } = await execAsync("nvidia-smi --query-gpu=memory.total,name --format=csv,noheader,nounits 2>/dev/null", { timeout: 5e3 });
40462
+ const lines = nvidiaSmi.trim().split("\n");
40463
+ if (lines.length > 0) {
40464
+ for (const line of lines) {
40465
+ const parts = line.split(",").map((s) => s.trim());
40466
+ const vramMB = parseInt(parts[0] ?? "0", 10);
40467
+ if (!isNaN(vramMB))
40468
+ gpuVramGB += vramMB / 1024;
40469
+ if (!gpuName && parts[1])
40470
+ gpuName = parts[1];
40471
+ }
40472
+ }
40473
+ } catch {
40474
+ }
40475
+ return {
40476
+ totalRamGB: Math.round(totalRamGB * 10) / 10,
40477
+ availableRamGB: Math.round(availableRamGB * 10) / 10,
40478
+ gpuVramGB: Math.round(gpuVramGB * 10) / 10,
40479
+ gpuName
40480
+ };
40481
+ }
40482
+ function recommendModel(specs) {
40483
+ const effectiveGB = Math.max(specs.gpuVramGB, specs.availableRamGB);
40484
+ const budget = effectiveGB * 0.8;
40485
+ const localVariants = QWEN_VARIANTS.filter((v) => !v.cloud);
40486
+ for (let i = localVariants.length - 1; i >= 0; i--) {
40487
+ if (localVariants[i].sizeGB <= budget) {
40488
+ return localVariants[i];
40489
+ }
40490
+ }
40491
+ return QWEN_VARIANTS.find((v) => v.tag === "qwen3.5:cloud");
40492
+ }
40493
+ function calculateContextWindow(specs, modelSizeGB2, kvBytesPerToken, archMax) {
40494
+ const totalAvail = Math.max(specs.gpuVramGB, specs.totalRamGB);
40495
+ const remaining = Math.max(0, totalAvail - modelSizeGB2);
40496
+ const usableGB = remaining * 0.85;
40497
+ let numCtx;
40498
+ if (kvBytesPerToken && kvBytesPerToken > 0) {
40499
+ const maxTokens = Math.floor(usableGB * 1024 ** 3 / kvBytesPerToken);
40500
+ numCtx = Math.max(2048, Math.floor(maxTokens / 1024) * 1024);
40501
+ } else {
40502
+ const kvEstimate = modelSizeGB2 <= 5 ? 524288 : modelSizeGB2 <= 20 ? 1048576 : 1572864;
40503
+ const maxTokens = Math.floor(usableGB * 1024 ** 3 / kvEstimate);
40504
+ numCtx = Math.max(2048, Math.floor(maxTokens / 1024) * 1024);
40505
+ }
40506
+ numCtx = Math.min(numCtx, 131072);
40507
+ if (archMax && archMax > 0)
40508
+ numCtx = Math.min(numCtx, archMax);
40509
+ if (kvBytesPerToken && kvBytesPerToken > 0 && modelSizeGB2 > 0) {
40510
+ const maxKVBytes = modelSizeGB2 * 4 * 1024 ** 3;
40511
+ const maxTokensFromBudget = Math.floor(maxKVBytes / kvBytesPerToken);
40512
+ const budgetCap = Math.max(2048, Math.floor(maxTokensFromBudget / 1024) * 1024);
40513
+ numCtx = Math.min(numCtx, budgetCap);
40514
+ }
40515
+ const label = numCtx >= 1024 ? `${Math.floor(numCtx / 1024)}K` : String(numCtx);
40516
+ return { numCtx, label };
40517
+ }
40518
+ function ask(rl, question) {
40519
+ return new Promise((resolve36) => {
40520
+ rl.question(question, (answer) => resolve36(answer.trim()));
40521
+ });
40522
+ }
40523
+ function askSecret(rl, question) {
40524
+ return new Promise((resolve36) => {
40525
+ process.stdout.write(question);
40526
+ let secret = "";
40527
+ const stdin = process.stdin;
40528
+ const hadRawMode = stdin.isRaw;
40529
+ if (typeof stdin.setRawMode === "function") {
40530
+ stdin.setRawMode(true);
40531
+ }
40532
+ stdin.resume();
40533
+ const onData = (chunk) => {
40534
+ const ch = chunk.toString("utf8");
40535
+ for (const c3 of ch) {
40536
+ if (c3 === "\r" || c3 === "\n") {
40537
+ stdin.removeListener("data", onData);
40538
+ if (typeof stdin.setRawMode === "function") {
40539
+ stdin.setRawMode(hadRawMode ?? false);
40540
+ }
40541
+ process.stdout.write("\n");
40542
+ resolve36(secret.trim());
40543
+ return;
40544
+ } else if (c3 === "") {
40545
+ stdin.removeListener("data", onData);
40546
+ if (typeof stdin.setRawMode === "function") {
40547
+ stdin.setRawMode(hadRawMode ?? false);
40548
+ }
40549
+ process.stdout.write("\n");
40550
+ resolve36("");
40551
+ return;
40552
+ } else if (c3 === "\x7F" || c3 === "\b") {
40553
+ if (secret.length > 0) {
40554
+ secret = secret.slice(0, -1);
40555
+ process.stdout.write("\b \b");
40556
+ }
40557
+ } else if (c3.charCodeAt(0) >= 32) {
40558
+ secret += c3;
40559
+ process.stdout.write("*");
40560
+ }
40561
+ }
40562
+ };
40563
+ stdin.on("data", onData);
40564
+ });
40565
+ }
40566
+ function ensureCurl() {
40567
+ if (hasCmd("curl"))
40568
+ return true;
40569
+ const plat = platform2();
40570
+ if (plat === "win32") {
40571
+ process.stdout.write(` ${c2.yellow("\u26A0")} curl not found on Windows \u2014 install it manually.
40572
+ `);
40573
+ return false;
40574
+ }
40575
+ process.stdout.write(` ${c2.cyan("\u25CF")} curl not found. Installing...
40576
+ `);
40577
+ const strategies = [
40578
+ { check: "apt-get", install: "sudo apt-get update -qq && sudo apt-get install -y -qq curl", label: "apt" },
40579
+ { check: "dnf", install: "sudo dnf install -y -q curl", label: "dnf" },
40580
+ { check: "yum", install: "sudo yum install -y -q curl", label: "yum" },
40581
+ { check: "pacman", install: "sudo pacman -S --noconfirm curl", label: "pacman" },
40582
+ { check: "apk", install: "sudo apk add --quiet curl", label: "apk" },
40583
+ { check: "zypper", install: "sudo zypper install -y curl", label: "zypper" }
40584
+ ];
40585
+ for (const s of strategies) {
40586
+ if (hasCmd(s.check)) {
40587
+ try {
40588
+ execSync27(s.install, { stdio: "inherit", timeout: 12e4 });
40589
+ if (hasCmd("curl")) {
40590
+ process.stdout.write(` ${c2.green("\u2714")} curl installed via ${s.label}.
40591
+ `);
40592
+ return true;
40593
+ }
40594
+ } catch {
40595
+ process.stdout.write(` ${c2.yellow("\u26A0")} ${s.label} install failed, trying next...
40596
+ `);
40597
+ }
40598
+ }
40599
+ }
40600
+ if (plat === "darwin") {
40601
+ try {
40602
+ execSync27("xcode-select --install", { stdio: "inherit", timeout: 3e5 });
40603
+ if (hasCmd("curl"))
40604
+ return true;
40605
+ } catch {
40606
+ }
40607
+ }
40608
+ process.stdout.write(` ${c2.red("\u2716")} Could not install curl. Install it manually and retry.
40609
+ `);
40610
+ return false;
40611
+ }
40612
+ async function autoInstallOllama(rl) {
40613
+ const plat = platform2();
40614
+ if (plat !== "win32" && !ensureCurl()) {
40615
+ return false;
40616
+ }
40617
+ if (plat === "linux") {
40618
+ return installOllamaLinux();
40619
+ } else if (plat === "darwin") {
40620
+ return await installOllamaMac(rl);
39929
40621
  } else if (plat === "win32") {
39930
40622
  return installOllamaWindows();
39931
40623
  }
@@ -40456,56 +41148,50 @@ async function doSetup(config, rl) {
40456
41148
  `);
40457
41149
  }
40458
41150
  const score = computeInferenceScore(specs);
40459
- const w = 24;
40460
41151
  const bw = 30;
40461
- process.stdout.write(`
40462
- ${c2.dim("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510")}
40463
- `);
40464
- process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold(" Inference Capability")} ${c2.dim("\u2502")}
40465
- `);
40466
- process.stdout.write(` ${c2.dim("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524")}
40467
- `);
40468
- process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold("Overall")} ${c2.dim("\u2502")}
40469
- `);
40470
- process.stdout.write(` ${c2.dim("\u2502")} ${renderScoreBar(score.overall, bw)} ${c2.bold(String(score.overall).padStart(3))}${c2.dim("/100")} ${c2.dim("\u2502")}
40471
- `);
40472
- process.stdout.write(` ${c2.dim("\u2502")} ${c2.dim(score.summary.padEnd(46))} ${c2.dim("\u2502")}
40473
- `);
40474
- process.stdout.write(` ${c2.dim("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524")}
40475
- `);
40476
- process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold("Memory")} ${c2.dim(`${specs.totalRamGB.toFixed(0)} GB RAM` + (specs.gpuVramGB > 0 ? ` + ${specs.gpuVramGB.toFixed(0)} GB VRAM` : ""))}${" ".repeat(Math.max(0, 27 - (`${specs.totalRamGB.toFixed(0)} GB RAM` + (specs.gpuVramGB > 0 ? ` + ${specs.gpuVramGB.toFixed(0)} GB VRAM` : "")).length))} ${c2.dim("\u2502")}
40477
- `);
40478
- process.stdout.write(` ${c2.dim("\u2502")} ${renderScoreBar(score.memory, w)} ${String(score.memory).padStart(3)} ${c2.dim("\u2502")}
40479
- `);
40480
- process.stdout.write(` ${c2.dim("\u2502")} ${c2.dim("\u2502")}
40481
- `);
40482
- process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold("Compute")} ${c2.dim(specs.gpuVramGB > 0 ? specs.gpuName || "NVIDIA GPU" : "CPU only")}${" ".repeat(Math.max(0, 28 - (specs.gpuVramGB > 0 ? specs.gpuName || "NVIDIA GPU" : "CPU only").length))} ${c2.dim("\u2502")}
40483
- `);
40484
- process.stdout.write(` ${c2.dim("\u2502")} ${renderScoreBar(score.compute, w)} ${String(score.compute).padStart(3)} ${c2.dim("\u2502")}
40485
- `);
40486
- process.stdout.write(` ${c2.dim("\u2502")} ${c2.dim("\u2502")}
40487
- `);
40488
- process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold("Speed")} ${c2.dim(specs.gpuVramGB > 0 ? "GPU accelerated" : "CPU inference")}${" ".repeat(Math.max(0, 28 - (specs.gpuVramGB > 0 ? "GPU accelerated" : "CPU inference").length))} ${c2.dim("\u2502")}
40489
- `);
40490
- process.stdout.write(` ${c2.dim("\u2502")} ${renderScoreBar(score.speed, w)} ${String(score.speed).padStart(3)} ${c2.dim("\u2502")}
40491
- `);
40492
- process.stdout.write(` ${c2.dim("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524")}
40493
- `);
40494
- process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold(" Model Compatibility")} ${c2.dim("\u2502")}
40495
- `);
41152
+ const sw = 24;
41153
+ const boxW = 52;
41154
+ const boxLine = (content, visLen) => {
41155
+ const pad = Math.max(0, boxW - visLen);
41156
+ return ` ${c2.dim("\u2502")}${content}${" ".repeat(pad)}${c2.dim("\u2502")}
41157
+ `;
41158
+ };
41159
+ const hLine = (ch) => ` ${c2.dim(ch + "\u2500".repeat(boxW) + (ch === "\u250C" ? "\u2510" : ch === "\u251C" ? "\u2524" : "\u2518"))}
41160
+ `;
41161
+ process.stdout.write("\n");
41162
+ process.stdout.write(hLine("\u250C"));
41163
+ process.stdout.write(boxLine(` ${c2.bold("Inference Capability")}`, 22));
41164
+ process.stdout.write(hLine("\u251C"));
41165
+ process.stdout.write(boxLine(` ${c2.bold("Overall")}`, 9));
41166
+ const overallBar = renderScoreBar(score.overall, bw);
41167
+ const overallLabel = `${String(score.overall).padStart(3)}/100`;
41168
+ process.stdout.write(boxLine(` ${overallBar} ${c2.bold(overallLabel)}`, 2 + bw + 1 + overallLabel.length));
41169
+ process.stdout.write(boxLine(` ${c2.dim(score.summary)}`, 2 + score.summary.length));
41170
+ process.stdout.write(hLine("\u251C"));
41171
+ const memLabel = `${specs.totalRamGB.toFixed(0)} GB RAM` + (specs.gpuVramGB > 0 ? ` + ${specs.gpuVramGB.toFixed(0)} GB VRAM` : "");
41172
+ process.stdout.write(boxLine(` ${c2.bold("Memory")} ${c2.dim(memLabel)}`, 11 + memLabel.length));
41173
+ process.stdout.write(boxLine(` ${renderScoreBar(score.memory, sw)} ${String(score.memory).padStart(3)}`, 2 + sw + 1 + 3));
41174
+ process.stdout.write(boxLine("", 0));
41175
+ const gpuLabel = specs.gpuVramGB > 0 ? specs.gpuName || "NVIDIA GPU" : "CPU only";
41176
+ process.stdout.write(boxLine(` ${c2.bold("Compute")} ${c2.dim(gpuLabel)}`, 11 + gpuLabel.length));
41177
+ process.stdout.write(boxLine(` ${renderScoreBar(score.compute, sw)} ${String(score.compute).padStart(3)}`, 2 + sw + 1 + 3));
41178
+ process.stdout.write(boxLine("", 0));
41179
+ const speedLabel = specs.gpuVramGB > 0 ? "GPU accelerated" : "CPU inference";
41180
+ process.stdout.write(boxLine(` ${c2.bold("Speed")} ${c2.dim(speedLabel)}`, 11 + speedLabel.length));
41181
+ process.stdout.write(boxLine(` ${renderScoreBar(score.speed, sw)} ${String(score.speed).padStart(3)}`, 2 + sw + 1 + 3));
41182
+ process.stdout.write(hLine("\u251C"));
41183
+ process.stdout.write(boxLine(` ${c2.bold("Model Compatibility")}`, 21));
40496
41184
  for (const compat of score.modelCompat) {
40497
41185
  const icon = compat.fits ? c2.green("\u2714") : c2.red("\u2716");
40498
41186
  const tag = compat.fits ? compat.tag : c2.dim(compat.tag);
40499
41187
  const note = c2.dim(compat.note);
40500
- const visTag = compat.tag;
40501
- const visNote = compat.note;
40502
- const pad = Math.max(0, 44 - 2 - visTag.length - 1 - visNote.length);
40503
- process.stdout.write(` ${c2.dim("\u2502")} ${icon} ${tag} ${" ".repeat(pad)}${note} ${c2.dim("\u2502")}
41188
+ const visLen = 2 + 1 + 1 + compat.tag.length;
41189
+ const notePad = Math.max(1, boxW - visLen - compat.note.length);
41190
+ process.stdout.write(` ${c2.dim("\u2502")} ${icon} ${tag}${" ".repeat(notePad)}${note}${c2.dim("\u2502")}
40504
41191
  `);
40505
41192
  }
40506
- process.stdout.write(` ${c2.dim("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518")}
40507
-
40508
- `);
41193
+ process.stdout.write(hLine("\u2514"));
41194
+ process.stdout.write("\n");
40509
41195
  let hasPython = hasCmd("python3") || hasCmd("python");
40510
41196
  if (!hasPython) {
40511
41197
  process.stdout.write(` ${c2.yellow("\u26A0")} Python3 not found (needed for vision, OCR, browser automation).
@@ -40627,26 +41313,18 @@ async function doSetup(config, rl) {
40627
41313
  ...m,
40628
41314
  hasTools: await checkToolSupport(m.name, backendUrl)
40629
41315
  })));
40630
- process.stdout.write(` ${c2.cyan("\u25CF")} Available models:
40631
-
40632
- `);
40633
- for (let i = 0; i < modelChecks.length; i++) {
40634
- const m = modelChecks[i];
40635
- const toolTag = m.hasTools ? c2.green("tools \u2713") : c2.yellow("text mode");
40636
- process.stdout.write(` ${c2.bold(String(i + 1))}. ${m.name} ${c2.dim(`(${m.size})`)} ${toolTag}
40637
- `);
40638
- }
40639
- process.stdout.write(`
40640
- ${c2.dim("0")}. Pull a new qwen3.5 model instead
40641
- `);
40642
- process.stdout.write(`
40643
- ${c2.dim("Models marked 'text mode' work via instruct-and-parse (no native tool API needed)")}
40644
- `);
40645
- process.stdout.write("\n");
40646
- const choice = await ask(rl, ` ${c2.bold("Select a model")} (1-${modelChecks.length}, or 0 to pull new): `);
40647
- const idx = parseInt(choice, 10);
40648
- if (idx > 0 && idx <= modelChecks.length) {
40649
- const selected = modelChecks[idx - 1];
41316
+ const modelItems = modelChecks.map((m) => {
41317
+ const toolTag = m.hasTools ? "tools \u2713" : "text mode";
41318
+ return { key: m.name, label: m.name, detail: `${m.size} ${toolTag}` };
41319
+ });
41320
+ modelItems.push({ key: "__pull_new__", label: "Pull a new qwen3.5 model instead", detail: "" });
41321
+ const modelResult = await tuiSelect({
41322
+ items: modelItems,
41323
+ title: "Select a Model",
41324
+ rl
41325
+ });
41326
+ if (modelResult.confirmed && modelResult.key && modelResult.key !== "__pull_new__") {
41327
+ const selected = modelChecks.find((m) => m.name === modelResult.key);
40650
41328
  setConfigValue("model", selected.name);
40651
41329
  const mode = selected.hasTools ? "native tool calling" : "text mode (instruct-and-parse)";
40652
41330
  process.stdout.write(`
@@ -40661,48 +41339,30 @@ async function doSetup(config, rl) {
40661
41339
  `);
40662
41340
  }
40663
41341
  const recommended = recommendModel(specs);
40664
- process.stdout.write(` ${c2.cyan("\u25CF")} Recommended model based on your system:
40665
-
40666
- `);
40667
41342
  const localVariants = QWEN_VARIANTS.filter((v) => !v.cloud);
40668
- for (let i = 0; i < localVariants.length; i++) {
40669
- const v = localVariants[i];
41343
+ const pullItems = localVariants.map((v) => {
40670
41344
  const fits = v.sizeGB <= Math.max(specs.gpuVramGB, specs.availableRamGB) * 0.8;
40671
41345
  const isRec = v.tag === recommended.tag;
40672
- const marker = isRec ? c2.green("\u2192") : fits ? c2.dim(" ") : c2.red("\u2716");
40673
- const name = isRec ? c2.bold(c2.green(v.tag)) : fits ? v.tag : c2.dim(v.tag);
40674
- const label = isRec ? c2.bold(v.label) : c2.dim(v.label);
40675
- const tooLarge = !fits && !v.cloud ? c2.red(" (exceeds available memory)") : "";
40676
- process.stdout.write(` ${marker} ${String(i + 1).padStart(2)}. ${name.padEnd(isRec ? 45 : 25)} ${label}${tooLarge}
40677
- `);
40678
- }
40679
- process.stdout.write(`
40680
- ${c2.dim(" ")} ${String(localVariants.length + 1).padStart(2)}. ${c2.dim("qwen3.5:cloud")} ${c2.dim("Ollama Cloud")}
40681
- `);
40682
- process.stdout.write(` ${c2.dim(" ")} ${String(localVariants.length + 2).padStart(2)}. ${c2.dim("qwen3.5:397b-cloud")} ${c2.dim("397B Ollama Cloud")}
40683
- `);
40684
- process.stdout.write("\n");
40685
- const pullChoice = await ask(rl, ` ${c2.bold("Select a model to pull")} (1-${localVariants.length + 2}, or Enter for recommended): `);
40686
- const pullIdx = pullChoice ? parseInt(pullChoice, 10) : 0;
41346
+ const suffix = isRec ? " \u2190 recommended" : !fits ? " (exceeds memory)" : "";
41347
+ return { key: v.tag, label: v.tag, detail: `${v.label}${suffix}` };
41348
+ });
41349
+ pullItems.push({ key: "qwen3.5:cloud", label: "qwen3.5:cloud", detail: "Ollama Cloud" });
41350
+ pullItems.push({ key: "qwen3.5:397b-cloud", label: "qwen3.5:397b-cloud", detail: "397B Ollama Cloud" });
41351
+ const pullResult = await tuiSelect({
41352
+ items: pullItems,
41353
+ title: "Select a Model to Pull",
41354
+ activeKey: recommended.tag,
41355
+ rl
41356
+ });
40687
41357
  let selectedVariant;
40688
- if (pullIdx === 0 || isNaN(pullIdx)) {
40689
- selectedVariant = recommended;
40690
- } else if (pullIdx <= localVariants.length) {
40691
- selectedVariant = localVariants[pullIdx - 1];
40692
- } else if (pullIdx === localVariants.length + 1) {
40693
- selectedVariant = QWEN_VARIANTS.find((v) => v.tag === "qwen3.5:cloud");
40694
- } else {
40695
- selectedVariant = QWEN_VARIANTS.find((v) => v.tag === "qwen3.5:397b-cloud");
40696
- }
40697
- const confirmPull = await ask(rl, `
40698
- Pull ${c2.bold(selectedVariant.tag)} (${selectedVariant.label})? (Y/n) `);
40699
- if (confirmPull.toLowerCase() === "n") {
41358
+ if (!pullResult.confirmed || !pullResult.key) {
40700
41359
  process.stdout.write(`
40701
41360
  ${c2.dim("Skipping model pull. You can pull manually with: ollama pull <model>")}
40702
41361
 
40703
41362
  `);
40704
41363
  return config.model;
40705
41364
  }
41365
+ selectedVariant = QWEN_VARIANTS.find((v) => v.tag === pullResult.key) ?? recommended;
40706
41366
  process.stdout.write(`
40707
41367
  ${c2.cyan("\u25CF")} Pulling ${c2.bold(selectedVariant.tag)}... (this may take a while)
40708
41368
  `);
@@ -40752,1335 +41412,651 @@ async function doSetup(config, rl) {
40752
41412
  ${c2.green("\u2714")} Model ${c2.bold(customName)} created with ${ctx.label} context.
40753
41413
  `);
40754
41414
  process.stdout.write(` ${c2.green("\u2714")} Saved as default model in config.
40755
-
40756
- `);
40757
- return customName;
40758
- } catch (err) {
40759
- renderWarning(`Could not create custom model: ${err instanceof Error ? err.message : String(err)}`);
40760
- renderInfo(`Using base model ${selectedVariant.tag} instead.`);
40761
- }
40762
- }
40763
- setConfigValue("model", selectedVariant.tag);
40764
- process.stdout.write(`
40765
- ${c2.green("\u2714")} Saved ${c2.bold(selectedVariant.tag)} as default model.
40766
-
40767
- `);
40768
- return selectedVariant.tag;
40769
- }
40770
- setConfigValue("model", selectedVariant.tag);
40771
- process.stdout.write(`
40772
- ${c2.green("\u2714")} Saved ${c2.bold(selectedVariant.tag)} as default model.
40773
-
40774
- `);
40775
- return selectedVariant.tag;
40776
- }
40777
- async function isModelAvailable(config) {
40778
- try {
40779
- const models = await fetchModels(config.backendUrl, config.apiKey);
40780
- return !!findModel(models, config.model);
40781
- } catch {
40782
- return false;
40783
- }
40784
- }
40785
- function isFirstRun() {
40786
- try {
40787
- return !existsSync37(join54(homedir13(), ".open-agents", "config.json"));
40788
- } catch {
40789
- return true;
40790
- }
40791
- }
40792
- function hasCmd(cmd) {
40793
- try {
40794
- const whichCmd = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
40795
- execSync27(whichCmd, { stdio: "pipe", timeout: 3e3 });
40796
- return true;
40797
- } catch {
40798
- return false;
40799
- }
40800
- }
40801
- function detectPkgManager() {
40802
- const plat = process.platform;
40803
- if (plat === "win32") {
40804
- if (hasCmd("choco"))
40805
- return "choco";
40806
- return null;
40807
- }
40808
- if (plat === "darwin") {
40809
- if (hasCmd("brew"))
40810
- return "brew";
40811
- return null;
40812
- }
40813
- if (hasCmd("apt-get"))
40814
- return "apt";
40815
- if (hasCmd("dnf"))
40816
- return "dnf";
40817
- if (hasCmd("pacman"))
40818
- return "pacman";
40819
- if (hasCmd("brew"))
40820
- return "brew";
40821
- return null;
40822
- }
40823
- function getVenvDir() {
40824
- return join54(homedir13(), ".open-agents", "venv");
40825
- }
40826
- function hasVenvModule() {
40827
- try {
40828
- execSync27("python3 -m venv --help", { stdio: "pipe", timeout: 5e3 });
40829
- return true;
40830
- } catch {
40831
- return false;
40832
- }
40833
- }
40834
- function ensureVenv(log) {
40835
- const venvDir = getVenvDir();
40836
- const isWin2 = process.platform === "win32";
40837
- const pipPath = isWin2 ? join54(venvDir, "Scripts", "pip.exe") : join54(venvDir, "bin", "pip");
40838
- const pythonCmd = isWin2 ? "python" : "python3";
40839
- if (existsSync37(pipPath))
40840
- return venvDir;
40841
- log("Creating Python venv for vision deps...");
40842
- if (!hasCmd(pythonCmd) && !hasCmd("python3")) {
40843
- log(`${pythonCmd} not found \u2014 cannot create venv.`);
40844
- return null;
40845
- }
40846
- if (!isWin2 && !hasVenvModule()) {
40847
- log("python3 venv module not available \u2014 venv creation skipped.");
40848
- return null;
40849
- }
40850
- try {
40851
- mkdirSync15(join54(homedir13(), ".open-agents"), { recursive: true });
40852
- const pyCmd = hasCmd(pythonCmd) ? pythonCmd : "python3";
40853
- execSync27(`${pyCmd} -m venv "${venvDir}"`, { stdio: "pipe", timeout: 3e4 });
40854
- execSync27(`"${pipPath}" install --upgrade pip`, {
40855
- stdio: "pipe",
40856
- timeout: 6e4
40857
- });
40858
- log("Python venv created at ~/.open-agents/venv");
40859
- return venvDir;
40860
- } catch (err) {
40861
- log(`Failed to create venv: ${err instanceof Error ? err.message : String(err)}`);
40862
- return null;
40863
- }
40864
- }
40865
- function trySudoPasswordless(cmd, timeoutMs = 12e4) {
40866
- try {
40867
- execSync27(`sudo -n ${cmd}`, {
40868
- stdio: "pipe",
40869
- timeout: timeoutMs,
40870
- env: { ...process.env, DEBIAN_FRONTEND: "noninteractive" }
40871
- });
40872
- return true;
40873
- } catch {
40874
- return false;
40875
- }
40876
- }
40877
- function runWithSudo(cmd, password, timeoutMs = 12e4) {
40878
- try {
40879
- const escaped = cmd.replace(/'/g, "'\\''");
40880
- execSync27(`sudo -S bash -c '${escaped}'`, {
40881
- input: password + "\n",
40882
- stdio: ["pipe", "pipe", "pipe"],
40883
- timeout: timeoutMs,
40884
- env: { ...process.env, DEBIAN_FRONTEND: "noninteractive" }
40885
- });
40886
- return "ok";
40887
- } catch (err) {
40888
- const stderr = err?.stderr?.toString() ?? "";
40889
- if (/incorrect password|authentication failure|Sorry, try again|not in the sudoers/i.test(stderr)) {
40890
- return "auth_fail";
40891
- }
40892
- return "cmd_fail";
40893
- }
40894
- }
40895
- function validateSudoPassword(password) {
40896
- return runWithSudo("true", password, 1e4) === "ok";
40897
- }
40898
- async function acquireSudoPassword(getSudoPassword, log, cachedPasswordRef) {
40899
- if (cachedPasswordRef.value && validateSudoPassword(cachedPasswordRef.value)) {
40900
- return cachedPasswordRef.value;
40901
- }
40902
- if (trySudoPasswordless("true", 1e4)) {
40903
- return "";
40904
- }
40905
- for (let attempt = 0; attempt < 2; attempt++) {
40906
- if (attempt > 0)
40907
- log("Authentication failed \u2014 please re-enter password.");
40908
- const pw = await getSudoPassword();
40909
- if (!pw)
40910
- return null;
40911
- if (validateSudoPassword(pw)) {
40912
- cachedPasswordRef.value = pw;
40913
- return pw;
40914
- }
40915
- }
40916
- return null;
40917
- }
40918
- async function sudoInstall(cmd, getSudoPassword, log, cachedPasswordRef, timeoutMs = 12e4) {
40919
- if (trySudoPasswordless(cmd, timeoutMs))
40920
- return true;
40921
- const pw = await acquireSudoPassword(getSudoPassword, log, cachedPasswordRef);
40922
- if (pw === null)
40923
- return false;
40924
- if (pw === "")
40925
- return false;
40926
- const result = runWithSudo(cmd, pw, timeoutMs);
40927
- if (result === "ok")
40928
- return true;
40929
- if (result === "cmd_fail") {
40930
- log(`Install command failed (not an auth issue) \u2014 package may not be available for this OS.`);
40931
- }
40932
- return false;
40933
- }
40934
- async function ensureVisionDeps(onInfo, getSudoPassword) {
40935
- const log = onInfo ?? (() => {
40936
- });
40937
- const cachedPasswordRef = { value: null };
40938
- const getPassword = getSudoPassword ?? (() => Promise.resolve(null));
40939
- const allDeps = [
40940
- { binary: "tesseract", label: "tesseract-ocr", pkgs: { apt: "tesseract-ocr", dnf: "tesseract", pacman: "tesseract", brew: "tesseract", choco: "tesseract" } },
40941
- { binary: "pdftotext", label: "poppler-utils", pkgs: { apt: "poppler-utils", dnf: "poppler-utils", pacman: "poppler", brew: "poppler", choco: "poppler" } },
40942
- { binary: "gs", label: "ghostscript", pkgs: { apt: "ghostscript", dnf: "ghostscript", pacman: "ghostscript", brew: "ghostscript", choco: "ghostscript" } },
40943
- { binary: "ocrmypdf", label: "ocrmypdf", pkgs: { apt: "ocrmypdf", dnf: "ocrmypdf", pacman: "ocrmypdf", brew: "ocrmypdf", choco: "ocrmypdf" } }
40944
- ];
40945
- {
40946
- const pm2 = detectPkgManager();
40947
- const missing = allDeps.filter((d) => !hasCmd(d.binary));
40948
- if (missing.length === 0) {
40949
- } else if (!pm2) {
40950
- for (const d of missing)
40951
- log(`No supported package manager \u2014 ${d.label} unavailable.`);
40952
- } else {
40953
- const pkgNames = missing.map((d) => d.pkgs[pm2]).filter(Boolean);
40954
- if (pkgNames.length === 0) {
40955
- for (const d of missing)
40956
- log(`${d.label} not available for ${pm2}.`);
40957
- } else {
40958
- const labels = missing.map((d) => d.label).join(", ");
40959
- log(`Installing ${labels}...`);
40960
- const needsSudo = pm2 !== "brew" && pm2 !== "choco";
40961
- let batchCmd;
40962
- switch (pm2) {
40963
- case "apt":
40964
- batchCmd = `apt-get update -qq && apt-get install -y -qq ${pkgNames.join(" ")}`;
40965
- break;
40966
- case "dnf":
40967
- batchCmd = `dnf install -y -q ${pkgNames.join(" ")}`;
40968
- break;
40969
- case "pacman":
40970
- batchCmd = `pacman -S --noconfirm ${pkgNames.join(" ")}`;
40971
- break;
40972
- case "brew":
40973
- batchCmd = `brew install ${pkgNames.join(" ")}`;
40974
- break;
40975
- case "choco":
40976
- batchCmd = `choco install -y ${pkgNames.join(" ")}`;
40977
- break;
40978
- default:
40979
- batchCmd = `echo "unsupported pm: ${pm2}" && exit 1`;
40980
- break;
40981
- }
40982
- let ok = false;
40983
- if (needsSudo) {
40984
- ok = await sudoInstall(batchCmd, getPassword, log, cachedPasswordRef, 18e4);
40985
- } else {
40986
- try {
40987
- execSync27(batchCmd, { stdio: "pipe", timeout: 18e4 });
40988
- ok = true;
40989
- } catch {
40990
- ok = false;
40991
- }
40992
- }
40993
- for (const d of missing) {
40994
- if (hasCmd(d.binary)) {
40995
- log(`${d.label} installed.`);
40996
- } else if (!ok) {
40997
- log(`${d.label} could not be installed \u2014 features will be limited.`);
40998
- } else {
40999
- log(`${d.label} install completed but binary not found.`);
41000
- }
41001
- }
41002
- }
41003
- }
41004
- }
41005
- const pm = detectPkgManager();
41006
- if (!hasCmd("pip3") && !hasCmd("pip") && pm) {
41007
- const pipCmds = {
41008
- apt: "apt-get install -y python3-pip",
41009
- dnf: "dnf install -y python3-pip"
41010
- };
41011
- const pipCmd = pipCmds[pm];
41012
- if (pipCmd) {
41013
- log("Installing python3-pip...");
41014
- const ok = await sudoInstall(pipCmd, getPassword, log, cachedPasswordRef);
41015
- if (!ok) {
41016
- log("python3-pip could not be installed \u2014 moondream-station may be unavailable.");
41017
- }
41018
- }
41019
- }
41020
- if (hasCmd("python3") && !hasVenvModule() && pm) {
41021
- const venvCmds = {
41022
- apt: () => {
41023
- try {
41024
- const pyVer = execSync27(`python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 }).trim();
41025
- return `apt-get install -y python3-venv python${pyVer}-venv`;
41026
- } catch {
41027
- return "apt-get install -y python3-venv";
41028
- }
41029
- },
41030
- dnf: () => "dnf install -y python3-venv"
41031
- };
41032
- const cmdFn = venvCmds[pm];
41033
- if (cmdFn) {
41034
- const cmd = cmdFn();
41035
- if (cmd) {
41036
- log("Installing python3-venv...");
41037
- const ok = await sudoInstall(cmd, getPassword, log, cachedPasswordRef);
41038
- if (!ok) {
41039
- log("python3-venv could not be installed \u2014 moondream-station may be unavailable.");
41040
- }
41041
- }
41042
- }
41043
- }
41044
- const venvDir = getVenvDir();
41045
- const venvBin = join54(venvDir, "bin");
41046
- const venvMoondream = join54(venvBin, "moondream-station");
41047
- const venv = ensureVenv(log);
41048
- if (venv && !hasCmd("moondream-station") && !existsSync37(venvMoondream)) {
41049
- const venvPip2 = join54(venvBin, "pip");
41050
- log("Installing moondream-station in ~/.open-agents/venv...");
41051
- try {
41052
- execSync27(`"${venvPip2}" install moondream-station`, { stdio: "pipe", timeout: 3e5 });
41053
- if (existsSync37(venvMoondream)) {
41054
- log("moondream-station installed successfully.");
41055
- } else {
41056
- try {
41057
- const check = execSync27(`"${venvPip2}" show moondream-station`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 });
41058
- if (check.includes("moondream")) {
41059
- log("moondream-station package installed.");
41060
- }
41061
- } catch {
41062
- log("moondream-station install completed.");
41063
- }
41064
- }
41065
- } catch (err) {
41066
- log(`moondream-station install failed: ${err instanceof Error ? err.message : String(err)}`);
41067
- }
41068
- }
41069
- if (venv) {
41070
- const venvPython2 = join54(venvBin, "python");
41071
- const venvPip2 = join54(venvBin, "pip");
41072
- let ocrStackInstalled = false;
41073
- try {
41074
- execSync27(`"${venvPython2}" -c "import cv2, pytesseract, numpy, PIL"`, { stdio: "pipe", timeout: 1e4 });
41075
- ocrStackInstalled = true;
41076
- } catch {
41077
- }
41078
- if (!ocrStackInstalled) {
41079
- const ocrPackages = "pytesseract Pillow opencv-python-headless numpy";
41080
- log("Installing OCR Python stack (pytesseract, OpenCV, Pillow, numpy)...");
41081
- try {
41082
- execSync27(`"${venvPip2}" install ${ocrPackages}`, { stdio: "pipe", timeout: 3e5 });
41083
- try {
41084
- execSync27(`"${venvPython2}" -c "import cv2, pytesseract, numpy, PIL"`, { stdio: "pipe", timeout: 1e4 });
41085
- log("OCR Python stack installed successfully.");
41086
- } catch {
41087
- log("OCR Python stack install completed but import verification failed.");
41088
- }
41415
+
41416
+ `);
41417
+ return customName;
41089
41418
  } catch (err) {
41090
- log(`OCR Python stack install failed: ${err instanceof Error ? err.message : String(err)}`);
41419
+ renderWarning(`Could not create custom model: ${err instanceof Error ? err.message : String(err)}`);
41420
+ renderInfo(`Using base model ${selectedVariant.tag} instead.`);
41091
41421
  }
41092
41422
  }
41093
- } else {
41094
- log("Python venv unavailable \u2014 advanced OCR pipeline will fall back to basic tesseract.");
41095
- }
41096
- }
41097
- function ensureCloudflaredBackground(onInfo) {
41098
- if (_cloudflaredInstallPromise)
41099
- return;
41100
- if (hasCmd("cloudflared")) {
41101
- _cloudflaredInstallPromise = Promise.resolve(true);
41102
- return;
41423
+ setConfigValue("model", selectedVariant.tag);
41424
+ process.stdout.write(`
41425
+ ${c2.green("\u2714")} Saved ${c2.bold(selectedVariant.tag)} as default model.
41426
+
41427
+ `);
41428
+ return selectedVariant.tag;
41103
41429
  }
41104
- const log = onInfo ?? (() => {
41105
- });
41106
- log("Installing cloudflared for live voice sessions...");
41107
- _cloudflaredInstallPromise = (async () => {
41108
- const arch2 = process.arch;
41109
- const os = platform2();
41110
- if (os !== "win32" && !ensureCurl()) {
41111
- log("curl not available \u2014 cannot install cloudflared.");
41112
- return false;
41113
- }
41114
- if (os === "linux") {
41115
- const archMap = { x64: "amd64", arm64: "arm64", arm: "arm" };
41116
- const cfArch = archMap[arch2] ?? "amd64";
41117
- try {
41118
- execSync27(`curl -fsSL "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${cfArch}" -o /tmp/cloudflared && chmod +x /tmp/cloudflared && mkdir -p "${homedir13()}/.local/bin" && mv /tmp/cloudflared "${homedir13()}/.local/bin/cloudflared"`, { stdio: "pipe", timeout: 6e4 });
41119
- if (!process.env.PATH?.includes(`${homedir13()}/.local/bin`)) {
41120
- process.env.PATH = `${homedir13()}/.local/bin:${process.env.PATH}`;
41121
- }
41122
- if (hasCmd("cloudflared")) {
41123
- log("cloudflared installed.");
41124
- return true;
41125
- }
41126
- } catch {
41127
- }
41128
- try {
41129
- execSync27(`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 });
41130
- if (hasCmd("cloudflared")) {
41131
- log("cloudflared installed.");
41132
- return true;
41133
- }
41134
- } catch {
41135
- }
41136
- } else if (os === "darwin") {
41137
- try {
41138
- execSync27("brew install cloudflared", { stdio: "pipe", timeout: 12e4 });
41139
- if (hasCmd("cloudflared")) {
41140
- log("cloudflared installed via Homebrew.");
41141
- return true;
41142
- }
41143
- } catch {
41144
- }
41145
- }
41146
- log("cloudflared not available \u2014 live voice sessions disabled.");
41147
- return false;
41148
- })();
41149
- }
41150
- function expandedModelName(baseModel) {
41151
- return `open-agents-${baseModel.replace(":", "-").replace(/\./g, "")}`;
41430
+ setConfigValue("model", selectedVariant.tag);
41431
+ process.stdout.write(`
41432
+ ${c2.green("\u2714")} Saved ${c2.bold(selectedVariant.tag)} as default model.
41433
+
41434
+ `);
41435
+ return selectedVariant.tag;
41152
41436
  }
41153
- async function checkExpandedVariant(modelName, backendUrl) {
41154
- if (modelName.startsWith("open-agents-"))
41155
- return null;
41156
- const target = expandedModelName(modelName);
41437
+ async function isModelAvailable(config) {
41157
41438
  try {
41158
- const models = await fetchOllamaModels(backendUrl);
41159
- const found = models.find((m) => m.name === target || m.name.startsWith(target + ":"));
41160
- return found ? found.name : false;
41439
+ const models = await fetchModels(config.backendUrl, config.apiKey);
41440
+ return !!findModel(models, config.model);
41161
41441
  } catch {
41162
41442
  return false;
41163
41443
  }
41164
41444
  }
41165
- function modelSizeGB(models, modelName) {
41166
- const m = findModel(models, modelName);
41167
- if (m)
41168
- return m.sizeBytes / 1024 ** 3;
41169
- const known = QWEN_VARIANTS.find((v) => modelName.includes(v.tag.split(":")[1] ?? ""));
41170
- return known?.sizeGB ?? 4;
41171
- }
41172
- async function queryModelKVInfo(backendUrl, modelName) {
41445
+ function isFirstRun() {
41173
41446
  try {
41174
- const normalized = backendUrl.replace(/\/+$/, "");
41175
- const res = await fetch(`${normalized}/api/show`, {
41176
- method: "POST",
41177
- headers: { "Content-Type": "application/json" },
41178
- body: JSON.stringify({ name: modelName }),
41179
- signal: AbortSignal.timeout(5e3)
41180
- });
41181
- if (!res.ok)
41182
- return null;
41183
- const data = await res.json();
41184
- if (!data.model_info)
41185
- return null;
41186
- const info = data.model_info;
41187
- const arch2 = info["general.architecture"];
41188
- if (!arch2)
41189
- return null;
41190
- const nLayers = info[`${arch2}.block_count`];
41191
- const nKVHeads = info[`${arch2}.attention.head_count_kv`] ?? info[`${arch2}.attention.head_count`];
41192
- const keyDim = info[`${arch2}.attention.key_length`];
41193
- const valDim = info[`${arch2}.attention.value_length`] ?? keyDim;
41194
- const archMax = info[`${arch2}.context_length`];
41195
- if (!nLayers || !nKVHeads || !keyDim || !valDim || !archMax)
41196
- return null;
41197
- const kvBytesPerToken = nLayers * nKVHeads * (keyDim + valDim) * 2;
41198
- return { kvBytesPerToken, archMax };
41447
+ return !existsSync37(join54(homedir13(), ".open-agents", "config.json"));
41199
41448
  } catch {
41200
- return null;
41449
+ return true;
41201
41450
  }
41202
41451
  }
41203
- async function createExpandedVariantAsync(baseModel, specs, sizeGB, kvBytesPerToken, archMax) {
41204
- const customName = expandedModelName(baseModel);
41205
- const ctx = calculateContextWindow(specs, sizeGB, kvBytesPerToken, archMax);
41452
+ function hasCmd(cmd) {
41206
41453
  try {
41207
- const numPredict = Math.min(16384, Math.max(2048, Math.floor(ctx.numCtx * 0.25)));
41208
- const modelfileContent = [
41209
- `FROM ${baseModel}`,
41210
- `PARAMETER num_ctx ${ctx.numCtx}`,
41211
- `PARAMETER temperature 0`,
41212
- `PARAMETER num_predict ${numPredict}`,
41213
- `PARAMETER stop "<|endoftext|>"`
41214
- ].join("\n");
41215
- const modelDir2 = join54(homedir13(), ".open-agents", "models");
41216
- mkdirSync15(modelDir2, { recursive: true });
41217
- const modelfilePath = join54(modelDir2, `Modelfile.${customName}`);
41218
- writeFileSync16(modelfilePath, modelfileContent + "\n", "utf8");
41219
- await execAsync(`ollama create ${customName} -f ${modelfilePath}`, {
41220
- timeout: 12e4
41221
- });
41222
- return customName;
41454
+ const whichCmd = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
41455
+ execSync27(whichCmd, { stdio: "pipe", timeout: 3e3 });
41456
+ return true;
41223
41457
  } catch {
41224
- return null;
41458
+ return false;
41225
41459
  }
41226
41460
  }
41227
- async function ensureExpandedContext(modelName, backendUrl) {
41228
- if (modelName.includes("cloud") || modelName.includes(":cloud")) {
41229
- return { model: modelName, created: false, contextLabel: "remote", numCtx: 0 };
41230
- }
41231
- const specs = await detectSystemSpecsAsync();
41232
- const kvInfo = await queryModelKVInfo(backendUrl, modelName);
41233
- let sizeGB = 4;
41234
- try {
41235
- const models = await fetchOllamaModels(backendUrl);
41236
- sizeGB = modelSizeGB(models, modelName);
41237
- } catch {
41238
- }
41239
- const ctx = calculateContextWindow(specs, sizeGB, kvInfo?.kvBytesPerToken, kvInfo?.archMax);
41240
- if (modelName.startsWith("open-agents-")) {
41241
- try {
41242
- const normalized = backendUrl.replace(/\/+$/, "");
41243
- const showRes = await fetch(`${normalized}/api/show`, {
41244
- method: "POST",
41245
- headers: { "Content-Type": "application/json" },
41246
- body: JSON.stringify({ name: modelName }),
41247
- signal: AbortSignal.timeout(5e3)
41248
- });
41249
- if (showRes.ok) {
41250
- const showData = await showRes.json();
41251
- const numCtxMatch = showData.parameters?.match(/num_ctx\s+(\d+)/);
41252
- const currentNumCtx = numCtxMatch ? parseInt(numCtxMatch[1], 10) : 0;
41253
- if (currentNumCtx !== ctx.numCtx) {
41254
- const fromMatch = showData.modelfile?.match(/^FROM\s+(.+)$/m);
41255
- const baseModel = fromMatch?.[1]?.trim();
41256
- if (baseModel && !baseModel.startsWith("open-agents-")) {
41257
- await createExpandedVariantAsync(baseModel, specs, sizeGB, kvInfo?.kvBytesPerToken, kvInfo?.archMax);
41258
- }
41259
- }
41260
- }
41261
- } catch {
41262
- }
41263
- return { model: modelName, created: false, contextLabel: ctx.label, numCtx: ctx.numCtx };
41264
- }
41265
- const existing = await checkExpandedVariant(modelName, backendUrl);
41266
- if (existing === null) {
41267
- return { model: modelName, created: false, contextLabel: "", numCtx: 0 };
41268
- }
41269
- if (typeof existing === "string") {
41270
- return { model: existing, created: false, contextLabel: ctx.label, numCtx: ctx.numCtx };
41461
+ function detectPkgManager() {
41462
+ const plat = process.platform;
41463
+ if (plat === "win32") {
41464
+ if (hasCmd("choco"))
41465
+ return "choco";
41466
+ return null;
41271
41467
  }
41272
- const created = await createExpandedVariantAsync(modelName, specs, sizeGB, kvInfo?.kvBytesPerToken, kvInfo?.archMax);
41273
- if (created) {
41274
- return { model: created, created: true, contextLabel: ctx.label, numCtx: ctx.numCtx };
41468
+ if (plat === "darwin") {
41469
+ if (hasCmd("brew"))
41470
+ return "brew";
41471
+ return null;
41275
41472
  }
41276
- return { model: modelName, created: false, contextLabel: ctx.label, numCtx: ctx.numCtx };
41473
+ if (hasCmd("apt-get"))
41474
+ return "apt";
41475
+ if (hasCmd("dnf"))
41476
+ return "dnf";
41477
+ if (hasCmd("pacman"))
41478
+ return "pacman";
41479
+ if (hasCmd("brew"))
41480
+ return "brew";
41481
+ return null;
41277
41482
  }
41278
- async function ensureNeovim() {
41483
+ function getVenvDir() {
41484
+ return join54(homedir13(), ".open-agents", "venv");
41485
+ }
41486
+ function hasVenvModule() {
41279
41487
  try {
41280
- const nvimPath = execSync27("which nvim 2>/dev/null || where nvim 2>nul", {
41281
- encoding: "utf8",
41282
- stdio: "pipe",
41283
- timeout: 5e3
41284
- }).trim();
41285
- if (nvimPath)
41286
- return nvimPath;
41488
+ execSync27("python3 -m venv --help", { stdio: "pipe", timeout: 5e3 });
41489
+ return true;
41287
41490
  } catch {
41491
+ return false;
41288
41492
  }
41289
- const platform6 = process.platform;
41290
- const arch2 = process.arch;
41291
- if (platform6 === "linux") {
41292
- const binDir = join54(homedir13(), ".local", "bin");
41293
- const nvimDest = join54(binDir, "nvim");
41294
- try {
41295
- mkdirSync15(binDir, { recursive: true });
41296
- } catch {
41297
- }
41298
- const appImageName = arch2 === "arm64" ? "nvim-linux-arm64.appimage" : "nvim-linux-x86_64.appimage";
41299
- const url = `https://github.com/neovim/neovim/releases/latest/download/${appImageName}`;
41300
- console.log(` Downloading Neovim (${appImageName})...`);
41301
- try {
41302
- execSync27(`curl -fsSL "${url}" -o "${nvimDest}"`, { stdio: "pipe", timeout: 6e4 });
41303
- execSync27(`chmod +x "${nvimDest}"`, { stdio: "pipe", timeout: 3e3 });
41304
- } catch (err) {
41305
- console.log(` Failed to download Neovim: ${err instanceof Error ? err.message : String(err)}`);
41306
- return null;
41307
- }
41308
- try {
41309
- const ver = execSync27(`"${nvimDest}" --version`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 }).split("\n")[0];
41310
- console.log(` Installed: ${ver}`);
41311
- } catch {
41312
- console.log(" Warning: nvim binary downloaded but may not work (missing FUSE? Try: nvim --appimage-extract)");
41313
- }
41314
- if (!process.env.PATH?.includes(binDir)) {
41315
- process.env.PATH = `${binDir}:${process.env.PATH}`;
41316
- }
41317
- ensurePathInShellRc(binDir);
41318
- return nvimDest;
41493
+ }
41494
+ function ensureVenv(log) {
41495
+ const venvDir = getVenvDir();
41496
+ const isWin2 = process.platform === "win32";
41497
+ const pipPath = isWin2 ? join54(venvDir, "Scripts", "pip.exe") : join54(venvDir, "bin", "pip");
41498
+ const pythonCmd = isWin2 ? "python" : "python3";
41499
+ if (existsSync37(pipPath))
41500
+ return venvDir;
41501
+ log("Creating Python venv for vision deps...");
41502
+ if (!hasCmd(pythonCmd) && !hasCmd("python3")) {
41503
+ log(`${pythonCmd} not found \u2014 cannot create venv.`);
41504
+ return null;
41319
41505
  }
41320
- if (platform6 === "darwin") {
41321
- if (hasCmd("brew")) {
41322
- console.log(" Installing Neovim via Homebrew...");
41323
- try {
41324
- execSync27("brew install neovim", { stdio: "inherit", timeout: 12e4 });
41325
- const nvimPath = execSync27("which nvim", { encoding: "utf8", stdio: "pipe", timeout: 3e3 }).trim();
41326
- return nvimPath || null;
41327
- } catch {
41328
- console.log(" brew install neovim failed.");
41329
- return null;
41330
- }
41331
- }
41332
- console.log(" Homebrew not found. Install Neovim: brew install neovim");
41506
+ if (!isWin2 && !hasVenvModule()) {
41507
+ log("python3 venv module not available \u2014 venv creation skipped.");
41333
41508
  return null;
41334
41509
  }
41335
- if (platform6 === "win32") {
41336
- if (hasCmd("choco")) {
41337
- console.log(" Installing Neovim via Chocolatey...");
41338
- try {
41339
- execSync27("choco install neovim -y", { stdio: "inherit", timeout: 12e4 });
41340
- return "nvim";
41341
- } catch {
41342
- console.log(" choco install neovim failed.");
41343
- }
41344
- }
41345
- if (hasCmd("winget")) {
41346
- console.log(" Installing Neovim via winget...");
41347
- try {
41348
- execSync27("winget install Neovim.Neovim --accept-source-agreements --accept-package-agreements", {
41349
- stdio: "inherit",
41350
- timeout: 12e4
41351
- });
41352
- return "nvim";
41353
- } catch {
41354
- console.log(" winget install neovim failed.");
41355
- }
41356
- }
41357
- console.log(" Install Neovim manually: https://neovim.io");
41510
+ try {
41511
+ mkdirSync15(join54(homedir13(), ".open-agents"), { recursive: true });
41512
+ const pyCmd = hasCmd(pythonCmd) ? pythonCmd : "python3";
41513
+ execSync27(`${pyCmd} -m venv "${venvDir}"`, { stdio: "pipe", timeout: 3e4 });
41514
+ execSync27(`"${pipPath}" install --upgrade pip`, {
41515
+ stdio: "pipe",
41516
+ timeout: 6e4
41517
+ });
41518
+ log("Python venv created at ~/.open-agents/venv");
41519
+ return venvDir;
41520
+ } catch (err) {
41521
+ log(`Failed to create venv: ${err instanceof Error ? err.message : String(err)}`);
41358
41522
  return null;
41359
41523
  }
41360
- return null;
41361
41524
  }
41362
- function ensurePathInShellRc(binDir) {
41363
- const shell = process.env.SHELL ?? "";
41364
- const rcFile = shell.includes("zsh") ? join54(homedir13(), ".zshrc") : join54(homedir13(), ".bashrc");
41525
+ function trySudoPasswordless(cmd, timeoutMs = 12e4) {
41365
41526
  try {
41366
- const rcContent = existsSync37(rcFile) ? readFileSync28(rcFile, "utf8") : "";
41367
- if (rcContent.includes(binDir))
41368
- return;
41369
- const exportLine = `
41370
- export PATH="${binDir}:$PATH" # Added by open-agents for nvim
41371
- `;
41372
- appendFileSync2(rcFile, exportLine, "utf8");
41373
- console.log(` Added ${binDir} to ${rcFile}`);
41527
+ execSync27(`sudo -n ${cmd}`, {
41528
+ stdio: "pipe",
41529
+ timeout: timeoutMs,
41530
+ env: { ...process.env, DEBIAN_FRONTEND: "noninteractive" }
41531
+ });
41532
+ return true;
41374
41533
  } catch {
41375
- }
41376
- }
41377
- var execAsync, QWEN_VARIANTS, _toolSupportCache, _cloudflaredInstallPromise;
41378
- var init_setup = __esm({
41379
- "packages/cli/dist/tui/setup.js"() {
41380
- "use strict";
41381
- init_model_picker();
41382
- init_render();
41383
- init_config();
41384
- init_dist();
41385
- execAsync = promisify6(exec2);
41386
- QWEN_VARIANTS = [
41387
- { tag: "qwen3.5:0.8b", sizeGB: 1, label: "0.8B params (1.0 GB)", cloud: false },
41388
- { tag: "qwen3.5:2b", sizeGB: 2.7, label: "2B params (2.7 GB)", cloud: false },
41389
- { tag: "qwen3.5:4b", sizeGB: 3.4, label: "4B params (3.4 GB)", cloud: false },
41390
- { tag: "qwen3.5:9b", sizeGB: 6.6, label: "9B params (6.6 GB) \u2014 recommended minimum", cloud: false },
41391
- { tag: "qwen3.5:27b", sizeGB: 17, label: "27B params (17 GB)", cloud: false },
41392
- { tag: "qwen3.5:35b", sizeGB: 24, label: "35B params (24 GB)", cloud: false },
41393
- { tag: "qwen3.5:122b", sizeGB: 81, label: "122B params (81 GB) \u2014 best local", cloud: false },
41394
- { tag: "qwen3.5:cloud", sizeGB: 0, label: "Cloud (Ollama Cloud)", cloud: true },
41395
- { tag: "qwen3.5:397b-cloud", sizeGB: 0, label: "397B Cloud (Ollama Cloud)", cloud: true }
41396
- ];
41397
- _toolSupportCache = /* @__PURE__ */ new Map();
41398
- _cloudflaredInstallPromise = null;
41399
- }
41400
- });
41401
-
41402
- // packages/cli/dist/tui/overlay-lock.js
41403
- var overlay_lock_exports = {};
41404
- __export(overlay_lock_exports, {
41405
- bufferIfOverlay: () => bufferIfOverlay,
41406
- enterOverlay: () => enterOverlay,
41407
- isOverlayActive: () => isOverlayActive,
41408
- leaveOverlay: () => leaveOverlay,
41409
- onOverlayLeave: () => onOverlayLeave,
41410
- overlayWrite: () => overlayWrite
41411
- });
41412
- function onOverlayLeave(cb) {
41413
- _onLeaveCallback = cb;
41414
- }
41415
- function isOverlayActive() {
41416
- return _overlayActive;
41417
- }
41418
- function overlayWrite(data) {
41419
- if (_origStdoutWrite) {
41420
- return _origStdoutWrite.call(process.stdout, data);
41421
- }
41422
- return process.stdout.write(data);
41423
- }
41424
- function bufferIfOverlay(stream, data) {
41425
- if (!_overlayActive)
41426
41534
  return false;
41427
- _buffer.push({ stream, data });
41428
- return true;
41429
- }
41430
- function enterOverlay() {
41431
- _depth++;
41432
- if (_depth === 1) {
41433
- _overlayActive = true;
41434
- _origStdoutWrite = process.stdout.write.bind(process.stdout);
41435
- _origStderrWrite = process.stderr.write.bind(process.stderr);
41436
- process.stdout.write = function(chunk, ...args) {
41437
- _buffer.push({ stream: "stdout", data: chunk });
41438
- const cb = typeof args[args.length - 1] === "function" ? args[args.length - 1] : void 0;
41439
- if (cb)
41440
- cb();
41441
- return true;
41442
- };
41443
- process.stderr.write = function(chunk, ...args) {
41444
- _buffer.push({ stream: "stderr", data: chunk });
41445
- const cb = typeof args[args.length - 1] === "function" ? args[args.length - 1] : void 0;
41446
- if (cb)
41447
- cb();
41448
- return true;
41449
- };
41450
- }
41451
- }
41452
- function leaveOverlay() {
41453
- _depth = Math.max(0, _depth - 1);
41454
- if (_depth === 0) {
41455
- _overlayActive = false;
41456
- if (_origStdoutWrite) {
41457
- process.stdout.write = _origStdoutWrite;
41458
- _origStdoutWrite = null;
41459
- }
41460
- if (_origStderrWrite) {
41461
- process.stderr.write = _origStderrWrite;
41462
- _origStderrWrite = null;
41463
- }
41464
- if (_onLeaveCallback) {
41465
- try {
41466
- _onLeaveCallback();
41467
- } catch {
41468
- }
41469
- }
41470
- const pending = _buffer;
41471
- _buffer = [];
41472
- for (const { stream, data } of pending) {
41473
- if (stream === "stdout") {
41474
- process.stdout.write(data);
41475
- } else {
41476
- process.stderr.write(data);
41477
- }
41478
- }
41479
- }
41480
- }
41481
- var _overlayActive, _depth, _buffer, _origStdoutWrite, _origStderrWrite, _onLeaveCallback;
41482
- var init_overlay_lock = __esm({
41483
- "packages/cli/dist/tui/overlay-lock.js"() {
41484
- "use strict";
41485
- _overlayActive = false;
41486
- _depth = 0;
41487
- _buffer = [];
41488
- _origStdoutWrite = null;
41489
- _origStderrWrite = null;
41490
- _onLeaveCallback = null;
41491
- }
41492
- });
41493
-
41494
- // packages/cli/dist/tui/tui-select.js
41495
- function ansi3(code, text) {
41496
- return isTTY3 ? `\x1B[${code}m${text}\x1B[0m` : text;
41497
- }
41498
- function fg2562(code, text) {
41499
- return isTTY3 ? `\x1B[38;5;${code}m${text}\x1B[0m` : text;
41500
- }
41501
- function stripAnsi(s) {
41502
- return s.replace(/\x1B\[[0-9;]*m/g, "");
41503
- }
41504
- function defaultRenderRow(item, focused, isActive) {
41505
- const marker = isActive ? selectColors.green("\u25CF") : focused ? selectColors.orange("\u25CF") : selectColors.dim("\u25CB");
41506
- const label = focused ? selectColors.orange(selectColors.bold(item.label)) : isActive ? selectColors.green(item.label) : item.label;
41507
- const detail = item.detail ? ` ${selectColors.dim(item.detail)}` : "";
41508
- return ` ${marker} ${label}${detail}`;
41509
- }
41510
- function matchRow(item, focused, isActive) {
41511
- if (focused || isActive) {
41512
- return defaultRenderRow(item, focused, isActive);
41513
41535
  }
41514
- const marker = selectColors.matchLight("\u25CB");
41515
- const label = selectColors.matchLight(stripAnsi(item.label));
41516
- const detail = item.detail ? ` ${selectColors.dim(stripAnsi(item.detail))}` : "";
41517
- return ` ${marker} ${label}${detail}`;
41518
41536
  }
41519
- function tuiSelect(opts) {
41520
- const { items, title, rl } = opts;
41521
- const renderRow = opts.renderRow ?? defaultRenderRow;
41522
- const activeKey = opts.activeKey ?? null;
41523
- const skipSet = new Set(opts.skipKeys ?? []);
41524
- if (items.length === 0) {
41525
- return Promise.resolve({ confirmed: false, key: null, index: -1 });
41526
- }
41527
- const isSkippable = (idx) => skipSet.has(items[idx].key);
41528
- let filter = "";
41529
- let matchSet = /* @__PURE__ */ new Set();
41530
- let deleteConfirmIdx = -1;
41531
- let deleteConfirmSel = false;
41532
- function updateFilter() {
41533
- if (!filter) {
41534
- matchSet = new Set(items.map((_, i) => i));
41535
- } else {
41536
- const lower = filter.toLowerCase();
41537
- matchSet = /* @__PURE__ */ new Set();
41538
- for (let i = 0; i < items.length; i++) {
41539
- if (isSkippable(i))
41540
- continue;
41541
- const plain = stripAnsi(items[i].label).toLowerCase();
41542
- const detailPlain = items[i].detail ? stripAnsi(items[i].detail).toLowerCase() : "";
41543
- if (plain.includes(lower) || detailPlain.includes(lower)) {
41544
- matchSet.add(i);
41545
- }
41546
- }
41537
+ function runWithSudo(cmd, password, timeoutMs = 12e4) {
41538
+ try {
41539
+ const escaped = cmd.replace(/'/g, "'\\''");
41540
+ execSync27(`sudo -S bash -c '${escaped}'`, {
41541
+ input: password + "\n",
41542
+ stdio: ["pipe", "pipe", "pipe"],
41543
+ timeout: timeoutMs,
41544
+ env: { ...process.env, DEBIAN_FRONTEND: "noninteractive" }
41545
+ });
41546
+ return "ok";
41547
+ } catch (err) {
41548
+ const stderr = err?.stderr?.toString() ?? "";
41549
+ if (/incorrect password|authentication failure|Sorry, try again|not in the sudoers/i.test(stderr)) {
41550
+ return "auth_fail";
41547
41551
  }
41552
+ return "cmd_fail";
41548
41553
  }
41549
- updateFilter();
41550
- const findSelectable = (from, dir) => {
41551
- let idx = from;
41552
- while (idx >= 0 && idx < items.length) {
41553
- if (!isSkippable(idx) && matchSet.has(idx))
41554
- return idx;
41555
- idx += dir;
41556
- }
41557
- return -1;
41558
- };
41559
- let cursor = activeKey ? items.findIndex((i) => i.key === activeKey) : -1;
41560
- if (cursor < 0 || isSkippable(cursor)) {
41561
- const first = findSelectable(0, 1);
41562
- cursor = first >= 0 ? first : 0;
41554
+ }
41555
+ function validateSudoPassword(password) {
41556
+ return runWithSudo("true", password, 1e4) === "ok";
41557
+ }
41558
+ async function acquireSudoPassword(getSudoPassword, log, cachedPasswordRef) {
41559
+ if (cachedPasswordRef.value && validateSudoPassword(cachedPasswordRef.value)) {
41560
+ return cachedPasswordRef.value;
41563
41561
  }
41564
- const reservedTopBottom = 6;
41565
- const hasCrumbs = opts.breadcrumbs && opts.breadcrumbs.length > 0;
41566
- const selectChrome = (hasCrumbs ? 11 : 10) + 3;
41567
- const contentArea = opts.availableRows ? opts.availableRows + reservedTopBottom : process.stdout.rows ?? 24;
41568
- const maxVisible = opts.maxVisible ?? Math.max(3, contentArea - selectChrome);
41569
- let scrollOffset = 0;
41570
- let lastRenderedLines = 0;
41571
- return new Promise((resolve36) => {
41572
- const stdin = process.stdin;
41573
- const hadRawMode = stdin.isRaw;
41574
- const savedRlListeners = [];
41575
- if (rl) {
41576
- rl.pause();
41577
- for (const event of ["keypress", "data"]) {
41578
- const listeners = stdin.listeners(event);
41579
- for (const fn of listeners) {
41580
- savedRlListeners.push({ event, fn });
41581
- stdin.removeListener(event, fn);
41582
- }
41583
- }
41584
- }
41585
- if (typeof stdin.setRawMode === "function") {
41586
- stdin.setRawMode(true);
41587
- }
41588
- stdin.resume();
41589
- enterOverlay();
41590
- overlayWrite("\x1B[?1049h\x1B[48;5;234m\x1B[2J\x1B[H\x1B[?25l\x1B[?1003h\x1B[?1006h");
41591
- let listRowOffset = 0;
41592
- function clampScroll(displayList) {
41593
- const cursorPos = displayList.indexOf(cursor);
41594
- if (cursorPos < 0)
41595
- return;
41596
- if (cursorPos < scrollOffset) {
41597
- scrollOffset = cursorPos;
41598
- } else if (cursorPos >= scrollOffset + maxVisible) {
41599
- scrollOffset = cursorPos - maxVisible + 1;
41600
- }
41601
- const maxOffset = Math.max(0, displayList.length - maxVisible);
41602
- scrollOffset = Math.max(0, Math.min(maxOffset, scrollOffset));
41562
+ if (trySudoPasswordless("true", 1e4)) {
41563
+ return "";
41564
+ }
41565
+ for (let attempt = 0; attempt < 2; attempt++) {
41566
+ if (attempt > 0)
41567
+ log("Authentication failed \u2014 please re-enter password.");
41568
+ const pw = await getSudoPassword();
41569
+ if (!pw)
41570
+ return null;
41571
+ if (validateSudoPassword(pw)) {
41572
+ cachedPasswordRef.value = pw;
41573
+ return pw;
41603
41574
  }
41604
- const hasBreadcrumbs = opts.breadcrumbs && opts.breadcrumbs.length > 0;
41605
- function render() {
41606
- overlayWrite("\x1B[48;5;234m\x1B[H\x1B[2J");
41607
- const lines = [];
41608
- lines.push("", "", "");
41609
- if (hasBreadcrumbs) {
41610
- const trail = opts.breadcrumbs.map((b) => selectColors.dim(b)).join(selectColors.dim(" \u203A "));
41611
- lines.push(`
41612
- ${selectColors.cyan("\u2190")} ${trail}`);
41613
- }
41614
- if (title) {
41615
- if (!hasBreadcrumbs)
41616
- lines.push("");
41617
- lines.push(` ${selectColors.bold(title)}`);
41618
- }
41619
- if (filter) {
41620
- const count = matchSet.size;
41621
- lines.push(` ${selectColors.cyan("/")} ${selectColors.bold(filter)} ${selectColors.dim(`(${count} match${count !== 1 ? "es" : ""})`)}`);
41575
+ }
41576
+ return null;
41577
+ }
41578
+ async function sudoInstall(cmd, getSudoPassword, log, cachedPasswordRef, timeoutMs = 12e4) {
41579
+ if (trySudoPasswordless(cmd, timeoutMs))
41580
+ return true;
41581
+ const pw = await acquireSudoPassword(getSudoPassword, log, cachedPasswordRef);
41582
+ if (pw === null)
41583
+ return false;
41584
+ if (pw === "")
41585
+ return false;
41586
+ const result = runWithSudo(cmd, pw, timeoutMs);
41587
+ if (result === "ok")
41588
+ return true;
41589
+ if (result === "cmd_fail") {
41590
+ log(`Install command failed (not an auth issue) \u2014 package may not be available for this OS.`);
41591
+ }
41592
+ return false;
41593
+ }
41594
+ async function ensureVisionDeps(onInfo, getSudoPassword) {
41595
+ const log = onInfo ?? (() => {
41596
+ });
41597
+ const cachedPasswordRef = { value: null };
41598
+ const getPassword = getSudoPassword ?? (() => Promise.resolve(null));
41599
+ const allDeps = [
41600
+ { binary: "tesseract", label: "tesseract-ocr", pkgs: { apt: "tesseract-ocr", dnf: "tesseract", pacman: "tesseract", brew: "tesseract", choco: "tesseract" } },
41601
+ { binary: "pdftotext", label: "poppler-utils", pkgs: { apt: "poppler-utils", dnf: "poppler-utils", pacman: "poppler", brew: "poppler", choco: "poppler" } },
41602
+ { binary: "gs", label: "ghostscript", pkgs: { apt: "ghostscript", dnf: "ghostscript", pacman: "ghostscript", brew: "ghostscript", choco: "ghostscript" } },
41603
+ { binary: "ocrmypdf", label: "ocrmypdf", pkgs: { apt: "ocrmypdf", dnf: "ocrmypdf", pacman: "ocrmypdf", brew: "ocrmypdf", choco: "ocrmypdf" } }
41604
+ ];
41605
+ {
41606
+ const pm2 = detectPkgManager();
41607
+ const missing = allDeps.filter((d) => !hasCmd(d.binary));
41608
+ if (missing.length === 0) {
41609
+ } else if (!pm2) {
41610
+ for (const d of missing)
41611
+ log(`No supported package manager \u2014 ${d.label} unavailable.`);
41612
+ } else {
41613
+ const pkgNames = missing.map((d) => d.pkgs[pm2]).filter(Boolean);
41614
+ if (pkgNames.length === 0) {
41615
+ for (const d of missing)
41616
+ log(`${d.label} not available for ${pm2}.`);
41622
41617
  } else {
41623
- lines.push(` ${selectColors.dim("Type to filter...")}`);
41624
- }
41625
- lines.push("");
41626
- let displayList;
41627
- if (filter) {
41628
- displayList = [];
41629
- for (let i = 0; i < items.length; i++) {
41630
- if (matchSet.has(i) || isSkippable(i)) {
41631
- displayList.push(i);
41632
- }
41633
- }
41634
- displayList = displayList.filter((idx, pos) => {
41635
- if (!isSkippable(idx))
41636
- return true;
41637
- for (let j = pos + 1; j < displayList.length; j++) {
41638
- if (!isSkippable(displayList[j]))
41639
- return true;
41618
+ const labels = missing.map((d) => d.label).join(", ");
41619
+ log(`Installing ${labels}...`);
41620
+ const needsSudo = pm2 !== "brew" && pm2 !== "choco";
41621
+ let batchCmd;
41622
+ switch (pm2) {
41623
+ case "apt":
41624
+ batchCmd = `apt-get update -qq && apt-get install -y -qq ${pkgNames.join(" ")}`;
41625
+ break;
41626
+ case "dnf":
41627
+ batchCmd = `dnf install -y -q ${pkgNames.join(" ")}`;
41628
+ break;
41629
+ case "pacman":
41630
+ batchCmd = `pacman -S --noconfirm ${pkgNames.join(" ")}`;
41631
+ break;
41632
+ case "brew":
41633
+ batchCmd = `brew install ${pkgNames.join(" ")}`;
41634
+ break;
41635
+ case "choco":
41636
+ batchCmd = `choco install -y ${pkgNames.join(" ")}`;
41637
+ break;
41638
+ default:
41639
+ batchCmd = `echo "unsupported pm: ${pm2}" && exit 1`;
41640
41640
  break;
41641
- }
41642
- return false;
41643
- });
41644
- } else {
41645
- displayList = items.map((_, i) => i);
41646
- }
41647
- clampScroll(displayList);
41648
- const visibleStart = scrollOffset;
41649
- const visibleEnd = Math.min(displayList.length, scrollOffset + maxVisible);
41650
- if (visibleStart > 0) {
41651
- lines.push(` ${selectColors.dim(` \u25B2 ${visibleStart} more`)}`);
41652
- }
41653
- listRowOffset = lines.length;
41654
- for (let vi = visibleStart; vi < visibleEnd; vi++) {
41655
- const idx = displayList[vi];
41656
- const item = items[idx];
41657
- if (isSkippable(idx)) {
41658
- lines.push(` ${item.label}`);
41659
- continue;
41660
41641
  }
41661
- const focused = idx === cursor;
41662
- const isActive = item.key === activeKey;
41663
- if (deleteConfirmIdx === idx) {
41664
- const yesLabel = deleteConfirmSel ? selectColors.bold(selectColors.green("[Yes]")) : selectColors.dim("[Yes]");
41665
- const noLabel = !deleteConfirmSel ? selectColors.bold(selectColors.orange("[No]")) : selectColors.dim("[No]");
41666
- lines.push(` ${ansi3("31", "\u2715")} ${ansi3("31", stripAnsi(item.label))} Delete? ${yesLabel} ${noLabel}`);
41667
- } else if (filter) {
41668
- lines.push(matchRow(item, focused, isActive));
41642
+ let ok = false;
41643
+ if (needsSudo) {
41644
+ ok = await sudoInstall(batchCmd, getPassword, log, cachedPasswordRef, 18e4);
41669
41645
  } else {
41670
- lines.push(renderRow(item, focused, isActive));
41671
- }
41672
- }
41673
- const remaining = displayList.length - visibleEnd;
41674
- if (remaining > 0) {
41675
- lines.push(` ${selectColors.dim(` \u25BC ${remaining} more`)}`);
41676
- }
41677
- lines.push("");
41678
- if (deleteConfirmIdx >= 0) {
41679
- lines.push(` ${selectColors.dim("\u2190/\u2192 select Enter confirm Esc cancel")}`);
41680
- } else {
41681
- const actionHint = opts.onAction ? " \u2190/\u2192/Space toggle" : "";
41682
- const deleteHint = opts.onDelete ? " Del remove" : "";
41683
- const customHint = opts.customKeyHint ?? "";
41684
- const escLabel = filter ? "clear filter" : hasBreadcrumbs ? "\u2190 back" : "cancel";
41685
- lines.push(` ${selectColors.dim("\u2191/\u2193 navigate Enter/Click select" + actionHint + deleteHint + customHint + " Esc " + escLabel + " Type to filter")}`);
41686
- }
41687
- lines.push("");
41688
- let output = lines.join("\n").replace(/\x1B\[0m/g, "\x1B[0m\x1B[48;5;234m").replace(/\n/g, "\x1B[K\n\x1B[48;5;234m");
41689
- overlayWrite("\x1B[48;5;234m" + output + "\x1B[K");
41690
- lastRenderedLines = lines.length;
41691
- }
41692
- function cleanup() {
41693
- stdin.removeListener("data", onData);
41694
- process.stdout.removeListener("resize", onResize);
41695
- overlayWrite("\x1B[?1003l\x1B[?1002l\x1B[?1006l\x1B[?1049l\x1B[?25h");
41696
- leaveOverlay();
41697
- if (typeof stdin.setRawMode === "function") {
41698
- stdin.setRawMode(hadRawMode ?? false);
41699
- }
41700
- for (const { event, fn } of savedRlListeners) {
41701
- stdin.on(event, fn);
41702
- }
41703
- if (rl) {
41704
- rl.resume();
41705
- rl.prompt(false);
41706
- }
41707
- }
41708
- function onData(chunk) {
41709
- let seq = chunk.toString("utf8");
41710
- const mouseRe = /\x1B\[<(\d+);(\d+);(\d+)([Mm])/g;
41711
- let mouseProcessed = false;
41712
- let mouseM;
41713
- while ((mouseM = mouseRe.exec(seq)) !== null) {
41714
- mouseProcessed = true;
41715
- const btn = parseInt(mouseM[1]);
41716
- const mCol = parseInt(mouseM[2]);
41717
- const mRow = parseInt(mouseM[3]);
41718
- const suffix = mouseM[4];
41719
- if (btn === 0 && suffix === "M") {
41720
- const listIdx = mRow - listRowOffset - 1;
41721
- if (listIdx >= 0 && listIdx < maxVisible) {
41722
- let displayList;
41723
- if (filter) {
41724
- displayList = [];
41725
- for (let i = 0; i < items.length; i++) {
41726
- if (matchSet.has(i) || isSkippable(i))
41727
- displayList.push(i);
41728
- }
41729
- displayList = displayList.filter((idx, pos) => {
41730
- if (!isSkippable(idx))
41731
- return true;
41732
- for (let j = pos + 1; j < displayList.length; j++) {
41733
- if (!isSkippable(displayList[j]))
41734
- return true;
41735
- break;
41736
- }
41737
- return false;
41738
- });
41739
- } else {
41740
- displayList = items.map((_, i) => i);
41741
- }
41742
- const vi = scrollOffset + listIdx;
41743
- if (vi < displayList.length) {
41744
- const itemIdx = displayList[vi];
41745
- if (!isSkippable(itemIdx) && matchSet.has(itemIdx)) {
41746
- cursor = itemIdx;
41747
- cleanup();
41748
- resolve36({ confirmed: true, key: items[cursor].key, index: cursor });
41749
- return;
41750
- } else if (!isSkippable(itemIdx)) {
41751
- cursor = itemIdx;
41752
- render();
41753
- }
41754
- }
41755
- }
41756
- }
41757
- if ((btn === 35 || btn === 32 || btn === 67) && suffix === "M") {
41758
- const listIdx = mRow - listRowOffset - 1;
41759
- if (listIdx >= 0 && listIdx < maxVisible) {
41760
- let displayList;
41761
- if (filter) {
41762
- displayList = [];
41763
- for (let i = 0; i < items.length; i++) {
41764
- if (matchSet.has(i) || isSkippable(i))
41765
- displayList.push(i);
41766
- }
41767
- displayList = displayList.filter((idx, pos) => {
41768
- if (!isSkippable(idx))
41769
- return true;
41770
- for (let j = pos + 1; j < displayList.length; j++) {
41771
- if (!isSkippable(displayList[j]))
41772
- return true;
41773
- break;
41774
- }
41775
- return false;
41776
- });
41777
- } else {
41778
- displayList = items.map((_, i) => i);
41779
- }
41780
- const vi = scrollOffset + listIdx;
41781
- if (vi < displayList.length) {
41782
- const itemIdx = displayList[vi];
41783
- if (!isSkippable(itemIdx) && itemIdx !== cursor) {
41784
- cursor = itemIdx;
41785
- render();
41786
- }
41787
- }
41788
- }
41789
- }
41790
- if (btn === 64) {
41791
- const next = findSelectable(cursor - 1, -1);
41792
- if (next >= 0 && next !== cursor) {
41793
- cursor = next;
41794
- render();
41795
- }
41796
- } else if (btn === 65) {
41797
- const next = findSelectable(cursor + 1, 1);
41798
- if (next >= 0 && next !== cursor) {
41799
- cursor = next;
41800
- render();
41646
+ try {
41647
+ execSync27(batchCmd, { stdio: "pipe", timeout: 18e4 });
41648
+ ok = true;
41649
+ } catch {
41650
+ ok = false;
41801
41651
  }
41802
41652
  }
41803
- }
41804
- seq = seq.replace(mouseRe, "");
41805
- if (!seq && mouseProcessed)
41806
- return;
41807
- if (deleteConfirmIdx >= 0) {
41808
- if (seq === "\x1B[D") {
41809
- deleteConfirmSel = true;
41810
- render();
41811
- } else if (seq === "\x1B[C") {
41812
- deleteConfirmSel = false;
41813
- render();
41814
- } else if (seq === "\r" || seq === "\n") {
41815
- if (deleteConfirmSel && opts.onDelete) {
41816
- const deletedIdx = deleteConfirmIdx;
41817
- deleteConfirmIdx = -1;
41818
- deleteConfirmSel = false;
41819
- opts.onDelete(items[deletedIdx], (removed) => {
41820
- if (removed) {
41821
- items.splice(deletedIdx, 1);
41822
- if (items.length === 0) {
41823
- cleanup();
41824
- resolve36({ confirmed: false, key: null, index: -1 });
41825
- return;
41826
- }
41827
- updateFilter();
41828
- if (cursor >= items.length)
41829
- cursor = items.length - 1;
41830
- const valid = findSelectable(cursor, 1) ?? findSelectable(cursor, -1);
41831
- if (valid >= 0)
41832
- cursor = valid;
41833
- scrollOffset = 0;
41834
- }
41835
- render();
41836
- });
41653
+ for (const d of missing) {
41654
+ if (hasCmd(d.binary)) {
41655
+ log(`${d.label} installed.`);
41656
+ } else if (!ok) {
41657
+ log(`${d.label} could not be installed \u2014 features will be limited.`);
41837
41658
  } else {
41838
- deleteConfirmIdx = -1;
41839
- deleteConfirmSel = false;
41840
- render();
41659
+ log(`${d.label} install completed but binary not found.`);
41841
41660
  }
41842
- } else if (seq === "\x1B" || seq === "\x1B\x1B" || seq === "") {
41843
- deleteConfirmIdx = -1;
41844
- deleteConfirmSel = false;
41845
- render();
41846
41661
  }
41847
- return;
41848
41662
  }
41849
- if (seq === "\x1B[A") {
41850
- const next = findSelectable(cursor - 1, -1);
41851
- if (next >= 0 && next !== cursor) {
41852
- cursor = next;
41853
- render();
41854
- }
41855
- } else if (seq === "\x1B[B") {
41856
- const next = findSelectable(cursor + 1, 1);
41857
- if (next >= 0 && next !== cursor) {
41858
- cursor = next;
41859
- render();
41860
- }
41861
- } else if (seq === "\x1B[5~") {
41862
- for (let i = 0; i < maxVisible; i++) {
41863
- const next = findSelectable(cursor - 1, -1);
41864
- if (next < 0 || next === cursor)
41865
- break;
41866
- cursor = next;
41867
- }
41868
- render();
41869
- } else if (seq === "\x1B[6~") {
41870
- for (let i = 0; i < maxVisible; i++) {
41871
- const next = findSelectable(cursor + 1, 1);
41872
- if (next < 0 || next === cursor)
41873
- break;
41874
- cursor = next;
41875
- }
41876
- render();
41877
- } else if (seq === "\x1B[H") {
41878
- const first = findSelectable(0, 1);
41879
- if (first >= 0) {
41880
- cursor = first;
41881
- render();
41882
- }
41883
- } else if (seq === "\x1B[F") {
41884
- const last = findSelectable(items.length - 1, -1);
41885
- if (last >= 0) {
41886
- cursor = last;
41887
- render();
41888
- }
41889
- } else if (seq === "\x1B[3~" && opts.onDelete) {
41890
- if (!isSkippable(cursor) && matchSet.has(cursor) && items[cursor].key !== activeKey) {
41891
- deleteConfirmIdx = cursor;
41892
- deleteConfirmSel = false;
41893
- render();
41894
- }
41895
- } else if (seq === "\x1B[D" && opts.onAction) {
41896
- if (!isSkippable(cursor) && matchSet.has(cursor)) {
41897
- if (opts.onAction(items[cursor], "left"))
41898
- render();
41899
- }
41900
- } else if (seq === "\x1B[C" && opts.onAction) {
41901
- if (!isSkippable(cursor) && matchSet.has(cursor)) {
41902
- if (opts.onAction(items[cursor], "right"))
41903
- render();
41663
+ }
41664
+ }
41665
+ const pm = detectPkgManager();
41666
+ if (!hasCmd("pip3") && !hasCmd("pip") && pm) {
41667
+ const pipCmds = {
41668
+ apt: "apt-get install -y python3-pip",
41669
+ dnf: "dnf install -y python3-pip"
41670
+ };
41671
+ const pipCmd = pipCmds[pm];
41672
+ if (pipCmd) {
41673
+ log("Installing python3-pip...");
41674
+ const ok = await sudoInstall(pipCmd, getPassword, log, cachedPasswordRef);
41675
+ if (!ok) {
41676
+ log("python3-pip could not be installed \u2014 moondream-station may be unavailable.");
41677
+ }
41678
+ }
41679
+ }
41680
+ if (hasCmd("python3") && !hasVenvModule() && pm) {
41681
+ const venvCmds = {
41682
+ apt: () => {
41683
+ try {
41684
+ const pyVer = execSync27(`python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 }).trim();
41685
+ return `apt-get install -y python3-venv python${pyVer}-venv`;
41686
+ } catch {
41687
+ return "apt-get install -y python3-venv";
41904
41688
  }
41905
- } else if (seq === " " && opts.onAction) {
41906
- if (!isSkippable(cursor) && matchSet.has(cursor)) {
41907
- if (opts.onAction(items[cursor], "space"))
41908
- render();
41689
+ },
41690
+ dnf: () => "dnf install -y python3-venv"
41691
+ };
41692
+ const cmdFn = venvCmds[pm];
41693
+ if (cmdFn) {
41694
+ const cmd = cmdFn();
41695
+ if (cmd) {
41696
+ log("Installing python3-venv...");
41697
+ const ok = await sudoInstall(cmd, getPassword, log, cachedPasswordRef);
41698
+ if (!ok) {
41699
+ log("python3-venv could not be installed \u2014 moondream-station may be unavailable.");
41909
41700
  }
41910
- } else if (seq === "\r" || seq === "\n") {
41911
- if (!isSkippable(cursor) && matchSet.has(cursor)) {
41912
- if (opts.onEnter) {
41913
- const consumed = opts.onEnter(items[cursor], {
41914
- done: () => render(),
41915
- resolve: (result) => {
41916
- cleanup();
41917
- resolve36(result);
41918
- },
41919
- getInput: (prompt, prefill) => getInputFromUser(prompt, prefill),
41920
- render: () => render(),
41921
- updateItem: (index, updates) => {
41922
- Object.assign(items[index], updates);
41923
- render();
41924
- }
41925
- });
41926
- if (consumed)
41927
- return;
41701
+ }
41702
+ }
41703
+ }
41704
+ const venvDir = getVenvDir();
41705
+ const venvBin = join54(venvDir, "bin");
41706
+ const venvMoondream = join54(venvBin, "moondream-station");
41707
+ const venv = ensureVenv(log);
41708
+ if (venv && !hasCmd("moondream-station") && !existsSync37(venvMoondream)) {
41709
+ const venvPip2 = join54(venvBin, "pip");
41710
+ log("Installing moondream-station in ~/.open-agents/venv...");
41711
+ try {
41712
+ execSync27(`"${venvPip2}" install moondream-station`, { stdio: "pipe", timeout: 3e5 });
41713
+ if (existsSync37(venvMoondream)) {
41714
+ log("moondream-station installed successfully.");
41715
+ } else {
41716
+ try {
41717
+ const check = execSync27(`"${venvPip2}" show moondream-station`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 });
41718
+ if (check.includes("moondream")) {
41719
+ log("moondream-station package installed.");
41928
41720
  }
41929
- cleanup();
41930
- resolve36({ confirmed: true, key: items[cursor].key, index: cursor });
41721
+ } catch {
41722
+ log("moondream-station install completed.");
41931
41723
  }
41932
- } else if (seq === "\x1B" || seq === "\x1B\x1B") {
41933
- if (filter) {
41934
- filter = "";
41935
- updateFilter();
41936
- const valid = findSelectable(cursor, 1);
41937
- if (valid >= 0)
41938
- cursor = valid;
41939
- scrollOffset = 0;
41940
- render();
41941
- } else if (hasBreadcrumbs) {
41942
- cleanup();
41943
- resolve36({ confirmed: false, key: "__back__", index: cursor });
41944
- } else {
41945
- cleanup();
41946
- resolve36({ confirmed: false, key: null, index: cursor });
41724
+ }
41725
+ } catch (err) {
41726
+ log(`moondream-station install failed: ${err instanceof Error ? err.message : String(err)}`);
41727
+ }
41728
+ }
41729
+ if (venv) {
41730
+ const venvPython2 = join54(venvBin, "python");
41731
+ const venvPip2 = join54(venvBin, "pip");
41732
+ let ocrStackInstalled = false;
41733
+ try {
41734
+ execSync27(`"${venvPython2}" -c "import cv2, pytesseract, numpy, PIL"`, { stdio: "pipe", timeout: 1e4 });
41735
+ ocrStackInstalled = true;
41736
+ } catch {
41737
+ }
41738
+ if (!ocrStackInstalled) {
41739
+ const ocrPackages = "pytesseract Pillow opencv-python-headless numpy";
41740
+ log("Installing OCR Python stack (pytesseract, OpenCV, Pillow, numpy)...");
41741
+ try {
41742
+ execSync27(`"${venvPip2}" install ${ocrPackages}`, { stdio: "pipe", timeout: 3e5 });
41743
+ try {
41744
+ execSync27(`"${venvPython2}" -c "import cv2, pytesseract, numpy, PIL"`, { stdio: "pipe", timeout: 1e4 });
41745
+ log("OCR Python stack installed successfully.");
41746
+ } catch {
41747
+ log("OCR Python stack install completed but import verification failed.");
41947
41748
  }
41948
- } else if (seq === "") {
41949
- cleanup();
41950
- resolve36({ confirmed: false, key: null, index: cursor });
41951
- } else if (seq === "\x7F" || seq === "\b") {
41952
- if (filter.length > 0) {
41953
- filter = filter.slice(0, -1);
41954
- updateFilter();
41955
- if (!matchSet.has(cursor) || isSkippable(cursor)) {
41956
- const next = findSelectable(0, 1);
41957
- if (next >= 0)
41958
- cursor = next;
41959
- }
41960
- scrollOffset = 0;
41961
- render();
41962
- } else if (hasBreadcrumbs) {
41963
- cleanup();
41964
- resolve36({ confirmed: false, key: "__back__", index: cursor });
41749
+ } catch (err) {
41750
+ log(`OCR Python stack install failed: ${err instanceof Error ? err.message : String(err)}`);
41751
+ }
41752
+ }
41753
+ } else {
41754
+ log("Python venv unavailable \u2014 advanced OCR pipeline will fall back to basic tesseract.");
41755
+ }
41756
+ }
41757
+ function ensureCloudflaredBackground(onInfo) {
41758
+ if (_cloudflaredInstallPromise)
41759
+ return;
41760
+ if (hasCmd("cloudflared")) {
41761
+ _cloudflaredInstallPromise = Promise.resolve(true);
41762
+ return;
41763
+ }
41764
+ const log = onInfo ?? (() => {
41765
+ });
41766
+ log("Installing cloudflared for live voice sessions...");
41767
+ _cloudflaredInstallPromise = (async () => {
41768
+ const arch2 = process.arch;
41769
+ const os = platform2();
41770
+ if (os !== "win32" && !ensureCurl()) {
41771
+ log("curl not available \u2014 cannot install cloudflared.");
41772
+ return false;
41773
+ }
41774
+ if (os === "linux") {
41775
+ const archMap = { x64: "amd64", arm64: "arm64", arm: "arm" };
41776
+ const cfArch = archMap[arch2] ?? "amd64";
41777
+ try {
41778
+ execSync27(`curl -fsSL "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${cfArch}" -o /tmp/cloudflared && chmod +x /tmp/cloudflared && mkdir -p "${homedir13()}/.local/bin" && mv /tmp/cloudflared "${homedir13()}/.local/bin/cloudflared"`, { stdio: "pipe", timeout: 6e4 });
41779
+ if (!process.env.PATH?.includes(`${homedir13()}/.local/bin`)) {
41780
+ process.env.PATH = `${homedir13()}/.local/bin:${process.env.PATH}`;
41965
41781
  }
41966
- } else if (seq.length === 1 && seq.charCodeAt(0) >= 32 && seq.charCodeAt(0) < 127) {
41967
- if (opts.onCustomKey && !isSkippable(cursor) && matchSet.has(cursor)) {
41968
- const consumed = opts.onCustomKey(items[cursor], seq, {
41969
- done: () => render(),
41970
- resolve: (result) => {
41971
- cleanup();
41972
- resolve36(result);
41973
- },
41974
- getInput: (prompt, prefill) => getInputFromUser(prompt, prefill),
41975
- render: () => render(),
41976
- updateItem: (index, updates) => {
41977
- Object.assign(items[index], updates);
41978
- render();
41979
- }
41980
- });
41981
- if (consumed)
41982
- return;
41782
+ if (hasCmd("cloudflared")) {
41783
+ log("cloudflared installed.");
41784
+ return true;
41983
41785
  }
41984
- filter += seq;
41985
- updateFilter();
41986
- if (matchSet.size > 0) {
41987
- const first = findSelectable(0, 1);
41988
- if (first >= 0)
41989
- cursor = first;
41786
+ } catch {
41787
+ }
41788
+ try {
41789
+ execSync27(`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 });
41790
+ if (hasCmd("cloudflared")) {
41791
+ log("cloudflared installed.");
41792
+ return true;
41990
41793
  }
41991
- scrollOffset = 0;
41992
- render();
41794
+ } catch {
41993
41795
  }
41994
- }
41995
- function getInputFromUser(prompt, prefill = "") {
41996
- return new Promise((inputResolve) => {
41997
- let inputBuf = prefill;
41998
- let inputCursor = prefill.length;
41999
- overlayWrite("\x1B[?25h");
42000
- function renderInput() {
42001
- overlayWrite(`\x1B[${lastRenderedLines}A`);
42002
- const saveFooter = lastRenderedLines;
42003
- for (let i = 0; i < saveFooter; i++)
42004
- overlayWrite("\x1B[B");
42005
- overlayWrite("\x1B[2A");
42006
- overlayWrite("\x1B[2K");
42007
- const before = inputBuf.slice(0, inputCursor);
42008
- const after = inputBuf.slice(inputCursor);
42009
- overlayWrite(` ${selectColors.cyan("\u203A")} ${selectColors.bold(prompt)} ${before}\x1B[7m${after.charAt(0) || " "}\x1B[27m${after.slice(1)}`);
42010
- overlayWrite("\n\x1B[2K");
42011
- overlayWrite(` ${selectColors.dim("Enter confirm Esc cancel")}`);
42012
- overlayWrite("\n");
41796
+ } else if (os === "darwin") {
41797
+ try {
41798
+ execSync27("brew install cloudflared", { stdio: "pipe", timeout: 12e4 });
41799
+ if (hasCmd("cloudflared")) {
41800
+ log("cloudflared installed via Homebrew.");
41801
+ return true;
42013
41802
  }
42014
- function onInputData(chunk) {
42015
- const s = chunk.toString("utf8");
42016
- if (s === "\r" || s === "\n") {
42017
- stdin.removeListener("data", onInputData);
42018
- stdin.on("data", onData);
42019
- overlayWrite("\x1B[?25l");
42020
- render();
42021
- inputResolve(inputBuf);
42022
- } else if (s === "\x1B" || s === "\x1B\x1B" || s === "") {
42023
- stdin.removeListener("data", onInputData);
42024
- stdin.on("data", onData);
42025
- overlayWrite("\x1B[?25l");
42026
- render();
42027
- inputResolve(null);
42028
- } else if (s === "\x7F" || s === "\b") {
42029
- if (inputCursor > 0) {
42030
- inputBuf = inputBuf.slice(0, inputCursor - 1) + inputBuf.slice(inputCursor);
42031
- inputCursor--;
42032
- renderInput();
42033
- }
42034
- } else if (s === "\x1B[D") {
42035
- if (inputCursor > 0) {
42036
- inputCursor--;
42037
- renderInput();
42038
- }
42039
- } else if (s === "\x1B[C") {
42040
- if (inputCursor < inputBuf.length) {
42041
- inputCursor++;
42042
- renderInput();
42043
- }
42044
- } else if (s === "\x1B[H") {
42045
- inputCursor = 0;
42046
- renderInput();
42047
- } else if (s === "\x1B[F") {
42048
- inputCursor = inputBuf.length;
42049
- renderInput();
42050
- } else if (s.length === 1 && s.charCodeAt(0) >= 32 && s.charCodeAt(0) < 127) {
42051
- inputBuf = inputBuf.slice(0, inputCursor) + s + inputBuf.slice(inputCursor);
42052
- inputCursor++;
42053
- renderInput();
41803
+ } catch {
41804
+ }
41805
+ }
41806
+ log("cloudflared not available \u2014 live voice sessions disabled.");
41807
+ return false;
41808
+ })();
41809
+ }
41810
+ function expandedModelName(baseModel) {
41811
+ return `open-agents-${baseModel.replace(":", "-").replace(/\./g, "")}`;
41812
+ }
41813
+ async function checkExpandedVariant(modelName, backendUrl) {
41814
+ if (modelName.startsWith("open-agents-"))
41815
+ return null;
41816
+ const target = expandedModelName(modelName);
41817
+ try {
41818
+ const models = await fetchOllamaModels(backendUrl);
41819
+ const found = models.find((m) => m.name === target || m.name.startsWith(target + ":"));
41820
+ return found ? found.name : false;
41821
+ } catch {
41822
+ return false;
41823
+ }
41824
+ }
41825
+ function modelSizeGB(models, modelName) {
41826
+ const m = findModel(models, modelName);
41827
+ if (m)
41828
+ return m.sizeBytes / 1024 ** 3;
41829
+ const known = QWEN_VARIANTS.find((v) => modelName.includes(v.tag.split(":")[1] ?? ""));
41830
+ return known?.sizeGB ?? 4;
41831
+ }
41832
+ async function queryModelKVInfo(backendUrl, modelName) {
41833
+ try {
41834
+ const normalized = backendUrl.replace(/\/+$/, "");
41835
+ const res = await fetch(`${normalized}/api/show`, {
41836
+ method: "POST",
41837
+ headers: { "Content-Type": "application/json" },
41838
+ body: JSON.stringify({ name: modelName }),
41839
+ signal: AbortSignal.timeout(5e3)
41840
+ });
41841
+ if (!res.ok)
41842
+ return null;
41843
+ const data = await res.json();
41844
+ if (!data.model_info)
41845
+ return null;
41846
+ const info = data.model_info;
41847
+ const arch2 = info["general.architecture"];
41848
+ if (!arch2)
41849
+ return null;
41850
+ const nLayers = info[`${arch2}.block_count`];
41851
+ const nKVHeads = info[`${arch2}.attention.head_count_kv`] ?? info[`${arch2}.attention.head_count`];
41852
+ const keyDim = info[`${arch2}.attention.key_length`];
41853
+ const valDim = info[`${arch2}.attention.value_length`] ?? keyDim;
41854
+ const archMax = info[`${arch2}.context_length`];
41855
+ if (!nLayers || !nKVHeads || !keyDim || !valDim || !archMax)
41856
+ return null;
41857
+ const kvBytesPerToken = nLayers * nKVHeads * (keyDim + valDim) * 2;
41858
+ return { kvBytesPerToken, archMax };
41859
+ } catch {
41860
+ return null;
41861
+ }
41862
+ }
41863
+ async function createExpandedVariantAsync(baseModel, specs, sizeGB, kvBytesPerToken, archMax) {
41864
+ const customName = expandedModelName(baseModel);
41865
+ const ctx = calculateContextWindow(specs, sizeGB, kvBytesPerToken, archMax);
41866
+ try {
41867
+ const numPredict = Math.min(16384, Math.max(2048, Math.floor(ctx.numCtx * 0.25)));
41868
+ const modelfileContent = [
41869
+ `FROM ${baseModel}`,
41870
+ `PARAMETER num_ctx ${ctx.numCtx}`,
41871
+ `PARAMETER temperature 0`,
41872
+ `PARAMETER num_predict ${numPredict}`,
41873
+ `PARAMETER stop "<|endoftext|>"`
41874
+ ].join("\n");
41875
+ const modelDir2 = join54(homedir13(), ".open-agents", "models");
41876
+ mkdirSync15(modelDir2, { recursive: true });
41877
+ const modelfilePath = join54(modelDir2, `Modelfile.${customName}`);
41878
+ writeFileSync16(modelfilePath, modelfileContent + "\n", "utf8");
41879
+ await execAsync(`ollama create ${customName} -f ${modelfilePath}`, {
41880
+ timeout: 12e4
41881
+ });
41882
+ return customName;
41883
+ } catch {
41884
+ return null;
41885
+ }
41886
+ }
41887
+ async function ensureExpandedContext(modelName, backendUrl) {
41888
+ if (modelName.includes("cloud") || modelName.includes(":cloud")) {
41889
+ return { model: modelName, created: false, contextLabel: "remote", numCtx: 0 };
41890
+ }
41891
+ const specs = await detectSystemSpecsAsync();
41892
+ const kvInfo = await queryModelKVInfo(backendUrl, modelName);
41893
+ let sizeGB = 4;
41894
+ try {
41895
+ const models = await fetchOllamaModels(backendUrl);
41896
+ sizeGB = modelSizeGB(models, modelName);
41897
+ } catch {
41898
+ }
41899
+ const ctx = calculateContextWindow(specs, sizeGB, kvInfo?.kvBytesPerToken, kvInfo?.archMax);
41900
+ if (modelName.startsWith("open-agents-")) {
41901
+ try {
41902
+ const normalized = backendUrl.replace(/\/+$/, "");
41903
+ const showRes = await fetch(`${normalized}/api/show`, {
41904
+ method: "POST",
41905
+ headers: { "Content-Type": "application/json" },
41906
+ body: JSON.stringify({ name: modelName }),
41907
+ signal: AbortSignal.timeout(5e3)
41908
+ });
41909
+ if (showRes.ok) {
41910
+ const showData = await showRes.json();
41911
+ const numCtxMatch = showData.parameters?.match(/num_ctx\s+(\d+)/);
41912
+ const currentNumCtx = numCtxMatch ? parseInt(numCtxMatch[1], 10) : 0;
41913
+ if (currentNumCtx !== ctx.numCtx) {
41914
+ const fromMatch = showData.modelfile?.match(/^FROM\s+(.+)$/m);
41915
+ const baseModel = fromMatch?.[1]?.trim();
41916
+ if (baseModel && !baseModel.startsWith("open-agents-")) {
41917
+ await createExpandedVariantAsync(baseModel, specs, sizeGB, kvInfo?.kvBytesPerToken, kvInfo?.archMax);
42054
41918
  }
42055
41919
  }
42056
- stdin.removeListener("data", onData);
42057
- stdin.on("data", onInputData);
42058
- renderInput();
42059
- });
41920
+ }
41921
+ } catch {
42060
41922
  }
42061
- const onResize = () => render();
42062
- process.stdout.on("resize", onResize);
42063
- stdin.on("data", onData);
42064
- render();
42065
- });
41923
+ return { model: modelName, created: false, contextLabel: ctx.label, numCtx: ctx.numCtx };
41924
+ }
41925
+ const existing = await checkExpandedVariant(modelName, backendUrl);
41926
+ if (existing === null) {
41927
+ return { model: modelName, created: false, contextLabel: "", numCtx: 0 };
41928
+ }
41929
+ if (typeof existing === "string") {
41930
+ return { model: existing, created: false, contextLabel: ctx.label, numCtx: ctx.numCtx };
41931
+ }
41932
+ const created = await createExpandedVariantAsync(modelName, specs, sizeGB, kvInfo?.kvBytesPerToken, kvInfo?.archMax);
41933
+ if (created) {
41934
+ return { model: created, created: true, contextLabel: ctx.label, numCtx: ctx.numCtx };
41935
+ }
41936
+ return { model: modelName, created: false, contextLabel: ctx.label, numCtx: ctx.numCtx };
42066
41937
  }
42067
- var isTTY3, selectColors;
42068
- var init_tui_select = __esm({
42069
- "packages/cli/dist/tui/tui-select.js"() {
41938
+ async function ensureNeovim() {
41939
+ try {
41940
+ const nvimPath = execSync27("which nvim 2>/dev/null || where nvim 2>nul", {
41941
+ encoding: "utf8",
41942
+ stdio: "pipe",
41943
+ timeout: 5e3
41944
+ }).trim();
41945
+ if (nvimPath)
41946
+ return nvimPath;
41947
+ } catch {
41948
+ }
41949
+ const platform6 = process.platform;
41950
+ const arch2 = process.arch;
41951
+ if (platform6 === "linux") {
41952
+ const binDir = join54(homedir13(), ".local", "bin");
41953
+ const nvimDest = join54(binDir, "nvim");
41954
+ try {
41955
+ mkdirSync15(binDir, { recursive: true });
41956
+ } catch {
41957
+ }
41958
+ const appImageName = arch2 === "arm64" ? "nvim-linux-arm64.appimage" : "nvim-linux-x86_64.appimage";
41959
+ const url = `https://github.com/neovim/neovim/releases/latest/download/${appImageName}`;
41960
+ console.log(` Downloading Neovim (${appImageName})...`);
41961
+ try {
41962
+ execSync27(`curl -fsSL "${url}" -o "${nvimDest}"`, { stdio: "pipe", timeout: 6e4 });
41963
+ execSync27(`chmod +x "${nvimDest}"`, { stdio: "pipe", timeout: 3e3 });
41964
+ } catch (err) {
41965
+ console.log(` Failed to download Neovim: ${err instanceof Error ? err.message : String(err)}`);
41966
+ return null;
41967
+ }
41968
+ try {
41969
+ const ver = execSync27(`"${nvimDest}" --version`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 }).split("\n")[0];
41970
+ console.log(` Installed: ${ver}`);
41971
+ } catch {
41972
+ console.log(" Warning: nvim binary downloaded but may not work (missing FUSE? Try: nvim --appimage-extract)");
41973
+ }
41974
+ if (!process.env.PATH?.includes(binDir)) {
41975
+ process.env.PATH = `${binDir}:${process.env.PATH}`;
41976
+ }
41977
+ ensurePathInShellRc(binDir);
41978
+ return nvimDest;
41979
+ }
41980
+ if (platform6 === "darwin") {
41981
+ if (hasCmd("brew")) {
41982
+ console.log(" Installing Neovim via Homebrew...");
41983
+ try {
41984
+ execSync27("brew install neovim", { stdio: "inherit", timeout: 12e4 });
41985
+ const nvimPath = execSync27("which nvim", { encoding: "utf8", stdio: "pipe", timeout: 3e3 }).trim();
41986
+ return nvimPath || null;
41987
+ } catch {
41988
+ console.log(" brew install neovim failed.");
41989
+ return null;
41990
+ }
41991
+ }
41992
+ console.log(" Homebrew not found. Install Neovim: brew install neovim");
41993
+ return null;
41994
+ }
41995
+ if (platform6 === "win32") {
41996
+ if (hasCmd("choco")) {
41997
+ console.log(" Installing Neovim via Chocolatey...");
41998
+ try {
41999
+ execSync27("choco install neovim -y", { stdio: "inherit", timeout: 12e4 });
42000
+ return "nvim";
42001
+ } catch {
42002
+ console.log(" choco install neovim failed.");
42003
+ }
42004
+ }
42005
+ if (hasCmd("winget")) {
42006
+ console.log(" Installing Neovim via winget...");
42007
+ try {
42008
+ execSync27("winget install Neovim.Neovim --accept-source-agreements --accept-package-agreements", {
42009
+ stdio: "inherit",
42010
+ timeout: 12e4
42011
+ });
42012
+ return "nvim";
42013
+ } catch {
42014
+ console.log(" winget install neovim failed.");
42015
+ }
42016
+ }
42017
+ console.log(" Install Neovim manually: https://neovim.io");
42018
+ return null;
42019
+ }
42020
+ return null;
42021
+ }
42022
+ function ensurePathInShellRc(binDir) {
42023
+ const shell = process.env.SHELL ?? "";
42024
+ const rcFile = shell.includes("zsh") ? join54(homedir13(), ".zshrc") : join54(homedir13(), ".bashrc");
42025
+ try {
42026
+ const rcContent = existsSync37(rcFile) ? readFileSync28(rcFile, "utf8") : "";
42027
+ if (rcContent.includes(binDir))
42028
+ return;
42029
+ const exportLine = `
42030
+ export PATH="${binDir}:$PATH" # Added by open-agents for nvim
42031
+ `;
42032
+ appendFileSync2(rcFile, exportLine, "utf8");
42033
+ console.log(` Added ${binDir} to ${rcFile}`);
42034
+ } catch {
42035
+ }
42036
+ }
42037
+ var execAsync, QWEN_VARIANTS, _toolSupportCache, _cloudflaredInstallPromise;
42038
+ var init_setup = __esm({
42039
+ "packages/cli/dist/tui/setup.js"() {
42070
42040
  "use strict";
42071
- init_overlay_lock();
42072
- isTTY3 = process.stdout.isTTY ?? false;
42073
- selectColors = {
42074
- orange: (t) => fg2562(208, t),
42075
- green: (t) => ansi3("32", t),
42076
- dim: (t) => ansi3("2", t),
42077
- bold: (t) => ansi3("1", t),
42078
- cyan: (t) => ansi3("36", t),
42079
- /** Lighter grey for filter matches (252 = near-white) */
42080
- matchLight: (t) => fg2562(252, t),
42081
- /** Dark grey for non-matching items (240 = dark grey) */
42082
- matchDark: (t) => fg2562(240, t)
42083
- };
42041
+ init_model_picker();
42042
+ init_render();
42043
+ init_config();
42044
+ init_dist();
42045
+ init_tui_select();
42046
+ execAsync = promisify6(exec2);
42047
+ QWEN_VARIANTS = [
42048
+ { tag: "qwen3.5:0.8b", sizeGB: 1, label: "0.8B params (1.0 GB)", cloud: false },
42049
+ { tag: "qwen3.5:2b", sizeGB: 2.7, label: "2B params (2.7 GB)", cloud: false },
42050
+ { tag: "qwen3.5:4b", sizeGB: 3.4, label: "4B params (3.4 GB)", cloud: false },
42051
+ { tag: "qwen3.5:9b", sizeGB: 6.6, label: "9B params (6.6 GB) \u2014 recommended minimum", cloud: false },
42052
+ { tag: "qwen3.5:27b", sizeGB: 17, label: "27B params (17 GB)", cloud: false },
42053
+ { tag: "qwen3.5:35b", sizeGB: 24, label: "35B params (24 GB)", cloud: false },
42054
+ { tag: "qwen3.5:122b", sizeGB: 81, label: "122B params (81 GB) \u2014 best local", cloud: false },
42055
+ { tag: "qwen3.5:cloud", sizeGB: 0, label: "Cloud (Ollama Cloud)", cloud: true },
42056
+ { tag: "qwen3.5:397b-cloud", sizeGB: 0, label: "397B Cloud (Ollama Cloud)", cloud: true }
42057
+ ];
42058
+ _toolSupportCache = /* @__PURE__ */ new Map();
42059
+ _cloudflaredInstallPromise = null;
42084
42060
  }
42085
42061
  });
42086
42062
 
@@ -47801,6 +47777,19 @@ async function handleSlashCommand(input, ctx) {
47801
47777
  case "upgrade":
47802
47778
  await handleUpdate(arg, ctx);
47803
47779
  return "handled";
47780
+ case "setup":
47781
+ case "wizard": {
47782
+ const setupModel = await runSetupWizard(ctx.config);
47783
+ if (setupModel) {
47784
+ const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
47785
+ const freshConfig = loadConfig2();
47786
+ Object.assign(ctx.config, freshConfig, { model: setupModel });
47787
+ renderInfo(`Setup complete \u2014 model set to ${setupModel}`);
47788
+ } else {
47789
+ renderInfo("Setup cancelled \u2014 no changes made.");
47790
+ }
47791
+ return "handled";
47792
+ }
47804
47793
  case "voice": {
47805
47794
  const save = (settings) => {
47806
47795
  ctx.saveSettings(settings);
@@ -60980,6 +60969,17 @@ ${CONTENT_BG_SEQ}`);
60980
60969
  }
60981
60970
  }
60982
60971
  }
60972
+ /**
60973
+ * Remove the last N lines from the content scrollback buffer and repaint.
60974
+ * Used by Esc-to-recall to erase the just-rendered user prompt.
60975
+ */
60976
+ popLastContentLines(n) {
60977
+ if (n <= 0)
60978
+ return;
60979
+ this._contentLines.splice(-n, n);
60980
+ if (this.active)
60981
+ this.refreshDisplay();
60982
+ }
60983
60983
  /** Number of visible content rows */
60984
60984
  get contentHeight() {
60985
60985
  const rows = process.stdout.rows ?? 24;
@@ -64308,7 +64308,7 @@ ${entry.fullContent}`
64308
64308
  if (slashCommandHandler) {
64309
64309
  runner.registerTool({
64310
64310
  name: "slash_command",
64311
- description: "Invoke a slash command programmatically. Available when /commands auto is set by the user. Safe commands: help, config, models, skills, tools, stats, cost, score, nexus, commands, verbose, stream, brute-force, deep-context, flow, style, emojis, colors, task-type, compact, context, evaluate. Skill commands: /<skill-name> <args>. BLOCKED commands (user-only): quit, exit, destroy, model, endpoint, update, telegram, call, listen, expose, p2p, secrets, dream, bless, clear. Use this to check configuration, run evaluations, discover skills, or adjust modes during a task.",
64311
+ description: "Invoke a slash command programmatically. Available when /commands auto is set by the user. Safe commands: help, config, models, skills, tools, stats, cost, score, nexus, commands, verbose, stream, brute-force, deep-context, flow, style, emojis, colors, task-type, compact, context, evaluate. Skill commands: /<skill-name> <args>. BLOCKED commands (user-only): quit, exit, destroy, model, endpoint, update, setup, telegram, call, listen, expose, p2p, secrets, dream, bless, clear. Use this to check configuration, run evaluations, discover skills, or adjust modes during a task.",
64312
64312
  parameters: {
64313
64313
  type: "object",
64314
64314
  properties: {
@@ -64336,6 +64336,8 @@ ${entry.fullContent}`
64336
64336
  "ep",
64337
64337
  "update",
64338
64338
  "upgrade",
64339
+ "setup",
64340
+ "wizard",
64339
64341
  "parallel",
64340
64342
  "think",
64341
64343
  "telegram",
@@ -64895,7 +64897,7 @@ async function startInteractive(config, repoPath) {
64895
64897
  initOaDirectory(repoRoot);
64896
64898
  const savedSettings = resolveSettings(repoRoot);
64897
64899
  if (process.stdout.isTTY) {
64898
- process.stdout.write("\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?1015l\x1B[?1049h\x1B[48;5;233m\x1B[2J\x1B[3J\x1B[H\x1B[?25l\x1B[?1002h\x1B[?1006h");
64900
+ process.stdout.write("\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?1015l\x1B[?1049h\x1B[48;5;233m\x1B[2J\x1B[3J\x1B[H\x1B[?25l");
64899
64901
  const restoreScreen = () => {
64900
64902
  process.stdout.write("\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?1015l\x1B[?25h\x1B[?1049l");
64901
64903
  };
@@ -65302,6 +65304,9 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
65302
65304
  let sessionToolCallCount = 0;
65303
65305
  let lastSteeringInput = "";
65304
65306
  let lastSteeringRetracted = false;
65307
+ let _recallText = null;
65308
+ let _recallTimer = null;
65309
+ const RECALL_WINDOW_MS = 1500;
65305
65310
  let restoredSessionContext = null;
65306
65311
  let sessionSudoPassword = null;
65307
65312
  let sudoPromptPending = false;
@@ -65417,6 +65422,9 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
65417
65422
  statusBar.handlePointerEvent(type, col, row);
65418
65423
  });
65419
65424
  process.stdin.pipe(mouseFilter);
65425
+ if (process.stdout.isTTY) {
65426
+ process.stdout.write("\x1B[?1002h\x1B[?1006h");
65427
+ }
65420
65428
  process.stdin.on("error", (err) => {
65421
65429
  if (err.code === "EIO" || err.code === "ENOTTY")
65422
65430
  return;
@@ -67629,6 +67637,13 @@ Summarize or analyze this transcription as appropriate.`;
67629
67637
  const inputLineCount = fullInput.split("\n").length;
67630
67638
  const displayText = isImage ? `[Image: ${cleanPath}]` : inputLineCount > 1 ? `[pasted ${inputLineCount} lines]` : fullInput;
67631
67639
  writeContent(() => renderUserMessage(displayText));
67640
+ _recallText = input;
67641
+ if (_recallTimer)
67642
+ clearTimeout(_recallTimer);
67643
+ _recallTimer = setTimeout(() => {
67644
+ _recallText = null;
67645
+ _recallTimer = null;
67646
+ }, RECALL_WINDOW_MS);
67632
67647
  lastSubmittedPrompt = fullInput;
67633
67648
  const taskPreview = fullInput.length > 100 ? fullInput.slice(0, 100) + "..." : fullInput;
67634
67649
  emotionEngine.setCurrentTask(taskPreview);
@@ -67666,6 +67681,11 @@ NEW TASK: ${fullInput}`;
67666
67681
  setTerminalTitle(summary?.slice(0, 60), version);
67667
67682
  }, currentTaskType, resolvedContextWindowSize, resolvedCaps, currentStyle, deepContextEnabled, compactionSNRCallback(), emotionEngine, flowEnabled, buildSlashCommandHandler(), thinkingEnabled, handleAskUser);
67668
67683
  activeTask = task;
67684
+ _recallText = null;
67685
+ if (_recallTimer) {
67686
+ clearTimeout(_recallTimer);
67687
+ _recallTimer = null;
67688
+ }
67669
67689
  setTerminalTitle(input.slice(0, 60), version);
67670
67690
  showPrompt();
67671
67691
  if (isNeovimActive() && !isNeovimFocused()) {
@@ -67865,6 +67885,24 @@ ${c2.dim("(Use /quit to exit)")}
67865
67885
  showPrompt();
67866
67886
  });
67867
67887
  _escapeHandler = () => {
67888
+ if (_recallText !== null) {
67889
+ const recalled = _recallText;
67890
+ _recallText = null;
67891
+ if (_recallTimer) {
67892
+ clearTimeout(_recallTimer);
67893
+ _recallTimer = null;
67894
+ }
67895
+ if (activeTask) {
67896
+ activeTask.runner.abort();
67897
+ activeTask = null;
67898
+ }
67899
+ statusBar.setProcessing(false);
67900
+ statusBar.popLastContentLines(1);
67901
+ rl.line = recalled;
67902
+ rl.cursor = recalled.length;
67903
+ showPrompt();
67904
+ return;
67905
+ }
67868
67906
  if (!activeTask)
67869
67907
  return;
67870
67908
  if (activeTask.runner.isPaused) {
@@ -68873,8 +68911,8 @@ var init_status = __esm({
68873
68911
  });
68874
68912
 
68875
68913
  // packages/cli/dist/commands/config.js
68876
- var config_exports = {};
68877
- __export(config_exports, {
68914
+ var config_exports2 = {};
68915
+ __export(config_exports2, {
68878
68916
  configCommand: () => configCommand
68879
68917
  });
68880
68918
  import { join as join73, resolve as resolve35 } from "node:path";
@@ -69660,7 +69698,7 @@ async function main() {
69660
69698
  break;
69661
69699
  }
69662
69700
  case "config": {
69663
- const { configCommand: configCommand2 } = await Promise.resolve().then(() => (init_config3(), config_exports));
69701
+ const { configCommand: configCommand2 } = await Promise.resolve().then(() => (init_config3(), config_exports2));
69664
69702
  await configCommand2({
69665
69703
  subCommand: parsed.configSubCommand,
69666
69704
  key: parsed.configKey,