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.
- package/dist/index.js +1635 -1597
- 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/
|
|
39669
|
-
|
|
39670
|
-
|
|
39671
|
-
|
|
39672
|
-
|
|
39673
|
-
|
|
39674
|
-
|
|
39675
|
-
|
|
39676
|
-
|
|
39677
|
-
|
|
39678
|
-
|
|
39679
|
-
|
|
39680
|
-
|
|
39681
|
-
|
|
39682
|
-
|
|
39683
|
-
|
|
39684
|
-
|
|
39685
|
-
|
|
39686
|
-
|
|
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
|
|
39700
|
-
|
|
39701
|
-
|
|
39702
|
-
|
|
39703
|
-
|
|
39704
|
-
|
|
39705
|
-
|
|
39706
|
-
|
|
39707
|
-
|
|
39708
|
-
|
|
39709
|
-
|
|
39710
|
-
|
|
39711
|
-
|
|
39712
|
-
|
|
39713
|
-
|
|
39714
|
-
|
|
39715
|
-
|
|
39716
|
-
|
|
39717
|
-
|
|
39718
|
-
|
|
39719
|
-
|
|
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
|
-
|
|
39723
|
-
|
|
39724
|
-
|
|
39725
|
-
|
|
39726
|
-
|
|
39727
|
-
|
|
39728
|
-
|
|
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
|
-
|
|
39747
|
-
|
|
39748
|
-
|
|
39749
|
-
|
|
39750
|
-
|
|
39751
|
-
|
|
39752
|
-
|
|
39753
|
-
|
|
39754
|
-
|
|
39755
|
-
|
|
39756
|
-
|
|
39757
|
-
|
|
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
|
|
39761
|
-
|
|
39762
|
-
|
|
39763
|
-
|
|
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
|
-
|
|
39769
|
-
|
|
39770
|
-
|
|
39771
|
-
|
|
39772
|
-
|
|
39773
|
-
|
|
39774
|
-
|
|
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
|
-
|
|
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
|
-
|
|
39791
|
-
|
|
39792
|
-
|
|
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
|
|
39824
|
-
|
|
39825
|
-
|
|
39826
|
-
|
|
39827
|
-
|
|
39828
|
-
|
|
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
|
-
|
|
39842
|
-
|
|
39843
|
-
|
|
39844
|
-
|
|
39845
|
-
|
|
39846
|
-
|
|
39847
|
-
|
|
39848
|
-
|
|
39849
|
-
|
|
39850
|
-
|
|
39851
|
-
|
|
39852
|
-
|
|
39853
|
-
|
|
39854
|
-
|
|
39855
|
-
|
|
39856
|
-
|
|
39857
|
-
|
|
39858
|
-
|
|
39859
|
-
|
|
39860
|
-
|
|
39861
|
-
|
|
39862
|
-
|
|
39863
|
-
|
|
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
|
-
|
|
39872
|
-
|
|
39873
|
-
|
|
39874
|
-
|
|
39875
|
-
|
|
39876
|
-
|
|
39877
|
-
|
|
39878
|
-
|
|
39879
|
-
|
|
39880
|
-
|
|
39881
|
-
|
|
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
|
-
|
|
39903
|
-
|
|
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
|
-
|
|
39909
|
-
|
|
39910
|
-
|
|
39911
|
-
|
|
39912
|
-
|
|
39913
|
-
|
|
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
|
-
|
|
39917
|
-
|
|
39918
|
-
|
|
39919
|
-
|
|
39920
|
-
|
|
39921
|
-
|
|
39922
|
-
|
|
39923
|
-
|
|
39924
|
-
|
|
39925
|
-
|
|
39926
|
-
|
|
39927
|
-
|
|
39928
|
-
|
|
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
|
-
|
|
40462
|
-
|
|
40463
|
-
|
|
40464
|
-
|
|
40465
|
-
`)
|
|
40466
|
-
|
|
40467
|
-
|
|
40468
|
-
|
|
40469
|
-
|
|
40470
|
-
process.stdout.write(
|
|
40471
|
-
|
|
40472
|
-
process.stdout.write(` ${c2.
|
|
40473
|
-
|
|
40474
|
-
process.stdout.write(` ${c2.
|
|
40475
|
-
|
|
40476
|
-
|
|
40477
|
-
`);
|
|
40478
|
-
process.stdout.write(` ${c2.dim(
|
|
40479
|
-
|
|
40480
|
-
|
|
40481
|
-
`);
|
|
40482
|
-
process.stdout.write(` ${
|
|
40483
|
-
|
|
40484
|
-
|
|
40485
|
-
`);
|
|
40486
|
-
process.stdout.write(` ${
|
|
40487
|
-
|
|
40488
|
-
|
|
40489
|
-
`);
|
|
40490
|
-
process.stdout.write(` ${
|
|
40491
|
-
|
|
40492
|
-
process.stdout.write(` ${c2.
|
|
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
|
|
40501
|
-
const
|
|
40502
|
-
|
|
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(
|
|
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
|
-
|
|
40631
|
-
|
|
40632
|
-
`
|
|
40633
|
-
|
|
40634
|
-
|
|
40635
|
-
|
|
40636
|
-
|
|
40637
|
-
|
|
40638
|
-
|
|
40639
|
-
|
|
40640
|
-
|
|
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
|
-
|
|
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
|
|
40673
|
-
|
|
40674
|
-
|
|
40675
|
-
|
|
40676
|
-
|
|
40677
|
-
|
|
40678
|
-
|
|
40679
|
-
|
|
40680
|
-
|
|
40681
|
-
|
|
40682
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
41094
|
-
|
|
41095
|
-
}
|
|
41096
|
-
|
|
41097
|
-
|
|
41098
|
-
|
|
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
|
-
|
|
41105
|
-
|
|
41106
|
-
|
|
41107
|
-
|
|
41108
|
-
|
|
41109
|
-
|
|
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
|
|
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
|
|
41159
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
41449
|
+
return true;
|
|
41201
41450
|
}
|
|
41202
41451
|
}
|
|
41203
|
-
|
|
41204
|
-
const customName = expandedModelName(baseModel);
|
|
41205
|
-
const ctx = calculateContextWindow(specs, sizeGB, kvBytesPerToken, archMax);
|
|
41452
|
+
function hasCmd(cmd) {
|
|
41206
41453
|
try {
|
|
41207
|
-
const
|
|
41208
|
-
|
|
41209
|
-
|
|
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
|
|
41458
|
+
return false;
|
|
41225
41459
|
}
|
|
41226
41460
|
}
|
|
41227
|
-
|
|
41228
|
-
|
|
41229
|
-
|
|
41230
|
-
|
|
41231
|
-
|
|
41232
|
-
|
|
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
|
-
|
|
41273
|
-
|
|
41274
|
-
|
|
41468
|
+
if (plat === "darwin") {
|
|
41469
|
+
if (hasCmd("brew"))
|
|
41470
|
+
return "brew";
|
|
41471
|
+
return null;
|
|
41275
41472
|
}
|
|
41276
|
-
|
|
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
|
-
|
|
41483
|
+
function getVenvDir() {
|
|
41484
|
+
return join54(homedir13(), ".open-agents", "venv");
|
|
41485
|
+
}
|
|
41486
|
+
function hasVenvModule() {
|
|
41279
41487
|
try {
|
|
41280
|
-
|
|
41281
|
-
|
|
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
|
-
|
|
41290
|
-
|
|
41291
|
-
|
|
41292
|
-
|
|
41293
|
-
|
|
41294
|
-
|
|
41295
|
-
|
|
41296
|
-
|
|
41297
|
-
|
|
41298
|
-
|
|
41299
|
-
|
|
41300
|
-
|
|
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 (
|
|
41321
|
-
|
|
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
|
-
|
|
41336
|
-
|
|
41337
|
-
|
|
41338
|
-
|
|
41339
|
-
|
|
41340
|
-
|
|
41341
|
-
|
|
41342
|
-
|
|
41343
|
-
|
|
41344
|
-
|
|
41345
|
-
|
|
41346
|
-
|
|
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
|
|
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
|
-
|
|
41367
|
-
|
|
41368
|
-
|
|
41369
|
-
|
|
41370
|
-
|
|
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
|
|
41520
|
-
|
|
41521
|
-
|
|
41522
|
-
|
|
41523
|
-
|
|
41524
|
-
|
|
41525
|
-
|
|
41526
|
-
|
|
41527
|
-
|
|
41528
|
-
|
|
41529
|
-
|
|
41530
|
-
|
|
41531
|
-
|
|
41532
|
-
|
|
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
|
-
|
|
41550
|
-
|
|
41551
|
-
|
|
41552
|
-
|
|
41553
|
-
|
|
41554
|
-
|
|
41555
|
-
|
|
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
|
-
|
|
41565
|
-
|
|
41566
|
-
|
|
41567
|
-
|
|
41568
|
-
|
|
41569
|
-
|
|
41570
|
-
|
|
41571
|
-
|
|
41572
|
-
|
|
41573
|
-
|
|
41574
|
-
|
|
41575
|
-
|
|
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
|
-
|
|
41605
|
-
|
|
41606
|
-
|
|
41607
|
-
|
|
41608
|
-
|
|
41609
|
-
|
|
41610
|
-
|
|
41611
|
-
|
|
41612
|
-
|
|
41613
|
-
|
|
41614
|
-
|
|
41615
|
-
|
|
41616
|
-
|
|
41617
|
-
|
|
41618
|
-
|
|
41619
|
-
|
|
41620
|
-
|
|
41621
|
-
|
|
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
|
-
|
|
41624
|
-
|
|
41625
|
-
|
|
41626
|
-
|
|
41627
|
-
|
|
41628
|
-
|
|
41629
|
-
|
|
41630
|
-
|
|
41631
|
-
|
|
41632
|
-
|
|
41633
|
-
|
|
41634
|
-
|
|
41635
|
-
|
|
41636
|
-
|
|
41637
|
-
|
|
41638
|
-
|
|
41639
|
-
|
|
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
|
-
|
|
41662
|
-
|
|
41663
|
-
|
|
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
|
-
|
|
41671
|
-
|
|
41672
|
-
|
|
41673
|
-
|
|
41674
|
-
|
|
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
|
-
|
|
41805
|
-
|
|
41806
|
-
|
|
41807
|
-
|
|
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
|
-
|
|
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
|
-
|
|
41850
|
-
|
|
41851
|
-
|
|
41852
|
-
|
|
41853
|
-
|
|
41854
|
-
|
|
41855
|
-
|
|
41856
|
-
|
|
41857
|
-
|
|
41858
|
-
|
|
41859
|
-
|
|
41860
|
-
|
|
41861
|
-
|
|
41862
|
-
|
|
41863
|
-
|
|
41864
|
-
|
|
41865
|
-
|
|
41866
|
-
|
|
41867
|
-
|
|
41868
|
-
|
|
41869
|
-
|
|
41870
|
-
|
|
41871
|
-
|
|
41872
|
-
|
|
41873
|
-
|
|
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
|
-
}
|
|
41906
|
-
|
|
41907
|
-
|
|
41908
|
-
|
|
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
|
-
}
|
|
41911
|
-
|
|
41912
|
-
|
|
41913
|
-
|
|
41914
|
-
|
|
41915
|
-
|
|
41916
|
-
|
|
41917
|
-
|
|
41918
|
-
|
|
41919
|
-
|
|
41920
|
-
|
|
41921
|
-
|
|
41922
|
-
|
|
41923
|
-
|
|
41924
|
-
|
|
41925
|
-
|
|
41926
|
-
|
|
41927
|
-
|
|
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
|
-
|
|
41930
|
-
|
|
41721
|
+
} catch {
|
|
41722
|
+
log("moondream-station install completed.");
|
|
41931
41723
|
}
|
|
41932
|
-
}
|
|
41933
|
-
|
|
41934
|
-
|
|
41935
|
-
|
|
41936
|
-
|
|
41937
|
-
|
|
41938
|
-
|
|
41939
|
-
|
|
41940
|
-
|
|
41941
|
-
|
|
41942
|
-
|
|
41943
|
-
|
|
41944
|
-
|
|
41945
|
-
|
|
41946
|
-
|
|
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
|
-
}
|
|
41949
|
-
|
|
41950
|
-
|
|
41951
|
-
|
|
41952
|
-
|
|
41953
|
-
|
|
41954
|
-
|
|
41955
|
-
|
|
41956
|
-
|
|
41957
|
-
|
|
41958
|
-
|
|
41959
|
-
|
|
41960
|
-
|
|
41961
|
-
|
|
41962
|
-
|
|
41963
|
-
|
|
41964
|
-
|
|
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
|
-
|
|
41967
|
-
|
|
41968
|
-
|
|
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
|
-
|
|
41985
|
-
|
|
41986
|
-
|
|
41987
|
-
|
|
41988
|
-
|
|
41989
|
-
|
|
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
|
-
|
|
41992
|
-
render();
|
|
41794
|
+
} catch {
|
|
41993
41795
|
}
|
|
41994
|
-
}
|
|
41995
|
-
|
|
41996
|
-
|
|
41997
|
-
|
|
41998
|
-
|
|
41999
|
-
|
|
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
|
-
|
|
42015
|
-
|
|
42016
|
-
|
|
42017
|
-
|
|
42018
|
-
|
|
42019
|
-
|
|
42020
|
-
|
|
42021
|
-
|
|
42022
|
-
|
|
42023
|
-
|
|
42024
|
-
|
|
42025
|
-
|
|
42026
|
-
|
|
42027
|
-
|
|
42028
|
-
|
|
42029
|
-
|
|
42030
|
-
|
|
42031
|
-
|
|
42032
|
-
|
|
42033
|
-
|
|
42034
|
-
|
|
42035
|
-
|
|
42036
|
-
|
|
42037
|
-
|
|
42038
|
-
|
|
42039
|
-
|
|
42040
|
-
|
|
42041
|
-
|
|
42042
|
-
|
|
42043
|
-
|
|
42044
|
-
|
|
42045
|
-
|
|
42046
|
-
|
|
42047
|
-
|
|
42048
|
-
|
|
42049
|
-
|
|
42050
|
-
|
|
42051
|
-
|
|
42052
|
-
|
|
42053
|
-
|
|
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
|
-
|
|
42057
|
-
|
|
42058
|
-
renderInput();
|
|
42059
|
-
});
|
|
41920
|
+
}
|
|
41921
|
+
} catch {
|
|
42060
41922
|
}
|
|
42061
|
-
|
|
42062
|
-
|
|
42063
|
-
|
|
42064
|
-
|
|
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
|
-
|
|
42068
|
-
|
|
42069
|
-
|
|
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
|
-
|
|
42072
|
-
|
|
42073
|
-
|
|
42074
|
-
|
|
42075
|
-
|
|
42076
|
-
|
|
42077
|
-
|
|
42078
|
-
|
|
42079
|
-
|
|
42080
|
-
|
|
42081
|
-
|
|
42082
|
-
|
|
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
|
|
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
|
|
68877
|
-
__export(
|
|
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(),
|
|
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,
|