open-agents-ai 0.184.66 → 0.184.68
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 +2187 -2195
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -11893,8 +11893,8 @@ async function loadTranscribeCli() {
|
|
|
11893
11893
|
}).trim();
|
|
11894
11894
|
const tcPath = join19(globalRoot, "transcribe-cli");
|
|
11895
11895
|
if (existsSync16(join19(tcPath, "dist", "index.js"))) {
|
|
11896
|
-
const { createRequire:
|
|
11897
|
-
const req =
|
|
11896
|
+
const { createRequire: createRequire6 } = await import("node:module");
|
|
11897
|
+
const req = createRequire6(import.meta.url);
|
|
11898
11898
|
_tcModule = req(join19(tcPath, "dist", "index.js"));
|
|
11899
11899
|
return _tcModule;
|
|
11900
11900
|
}
|
|
@@ -11907,8 +11907,8 @@ async function loadTranscribeCli() {
|
|
|
11907
11907
|
for (const ver of readdirSync23(nvmBase)) {
|
|
11908
11908
|
const tcPath = join19(nvmBase, ver, "lib", "node_modules", "transcribe-cli");
|
|
11909
11909
|
if (existsSync16(join19(tcPath, "dist", "index.js"))) {
|
|
11910
|
-
const { createRequire:
|
|
11911
|
-
const req =
|
|
11910
|
+
const { createRequire: createRequire6 } = await import("node:module");
|
|
11911
|
+
const req = createRequire6(import.meta.url);
|
|
11912
11912
|
_tcModule = req(join19(tcPath, "dist", "index.js"));
|
|
11913
11913
|
return _tcModule;
|
|
11914
11914
|
}
|
|
@@ -30101,8 +30101,8 @@ var init_listen = __esm({
|
|
|
30101
30101
|
/** Load transcribe-cli — bundled as a dependency of open-agents-ai. */
|
|
30102
30102
|
async loadTranscribeCli() {
|
|
30103
30103
|
try {
|
|
30104
|
-
const { createRequire:
|
|
30105
|
-
const req =
|
|
30104
|
+
const { createRequire: createRequire6 } = await import("node:module");
|
|
30105
|
+
const req = createRequire6(import.meta.url);
|
|
30106
30106
|
return req("transcribe-cli");
|
|
30107
30107
|
} catch {
|
|
30108
30108
|
}
|
|
@@ -30114,8 +30114,8 @@ var init_listen = __esm({
|
|
|
30114
30114
|
}).trim();
|
|
30115
30115
|
const tcPath = join48(globalRoot, "transcribe-cli");
|
|
30116
30116
|
if (existsSync32(join48(tcPath, "dist", "index.js"))) {
|
|
30117
|
-
const { createRequire:
|
|
30118
|
-
const req =
|
|
30117
|
+
const { createRequire: createRequire6 } = await import("node:module");
|
|
30118
|
+
const req = createRequire6(import.meta.url);
|
|
30119
30119
|
return req(join48(tcPath, "dist", "index.js"));
|
|
30120
30120
|
}
|
|
30121
30121
|
} catch {
|
|
@@ -30127,8 +30127,8 @@ var init_listen = __esm({
|
|
|
30127
30127
|
for (const ver of readdirSync23(nvmBase)) {
|
|
30128
30128
|
const tcPath = join48(nvmBase, ver, "lib", "node_modules", "transcribe-cli");
|
|
30129
30129
|
if (existsSync32(join48(tcPath, "dist", "index.js"))) {
|
|
30130
|
-
const { createRequire:
|
|
30131
|
-
const req =
|
|
30130
|
+
const { createRequire: createRequire6 } = await import("node:module");
|
|
30131
|
+
const req = createRequire6(import.meta.url);
|
|
30132
30132
|
return req(join48(tcPath, "dist", "index.js"));
|
|
30133
30133
|
}
|
|
30134
30134
|
}
|
|
@@ -39665,2422 +39665,2397 @@ var init_oa_directory = __esm({
|
|
|
39665
39665
|
}
|
|
39666
39666
|
});
|
|
39667
39667
|
|
|
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;
|
|
39668
|
+
// packages/cli/dist/tui/overlay-lock.js
|
|
39669
|
+
var overlay_lock_exports = {};
|
|
39670
|
+
__export(overlay_lock_exports, {
|
|
39671
|
+
bufferIfOverlay: () => bufferIfOverlay,
|
|
39672
|
+
enterOverlay: () => enterOverlay,
|
|
39673
|
+
isOverlayActive: () => isOverlayActive,
|
|
39674
|
+
leaveOverlay: () => leaveOverlay,
|
|
39675
|
+
onOverlayLeave: () => onOverlayLeave,
|
|
39676
|
+
overlayWrite: () => overlayWrite
|
|
39677
|
+
});
|
|
39678
|
+
function onOverlayLeave(cb) {
|
|
39679
|
+
_onLeaveCallback = cb;
|
|
39680
|
+
}
|
|
39681
|
+
function isOverlayActive() {
|
|
39682
|
+
return _overlayActive;
|
|
39683
|
+
}
|
|
39684
|
+
function overlayWrite(data) {
|
|
39685
|
+
if (_origStdoutWrite) {
|
|
39686
|
+
return _origStdoutWrite.call(process.stdout, data);
|
|
39697
39687
|
}
|
|
39688
|
+
return process.stdout.write(data);
|
|
39698
39689
|
}
|
|
39699
|
-
function
|
|
39700
|
-
|
|
39701
|
-
|
|
39702
|
-
|
|
39703
|
-
|
|
39704
|
-
|
|
39705
|
-
|
|
39706
|
-
|
|
39707
|
-
|
|
39708
|
-
|
|
39709
|
-
|
|
39710
|
-
|
|
39711
|
-
|
|
39712
|
-
|
|
39713
|
-
|
|
39714
|
-
|
|
39715
|
-
|
|
39716
|
-
|
|
39717
|
-
|
|
39718
|
-
|
|
39719
|
-
|
|
39690
|
+
function bufferIfOverlay(stream, data) {
|
|
39691
|
+
if (!_overlayActive)
|
|
39692
|
+
return false;
|
|
39693
|
+
_buffer.push({ stream, data });
|
|
39694
|
+
return true;
|
|
39695
|
+
}
|
|
39696
|
+
function enterOverlay() {
|
|
39697
|
+
_depth++;
|
|
39698
|
+
if (_depth === 1) {
|
|
39699
|
+
_overlayActive = true;
|
|
39700
|
+
_origStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
39701
|
+
_origStderrWrite = process.stderr.write.bind(process.stderr);
|
|
39702
|
+
process.stdout.write = function(chunk, ...args) {
|
|
39703
|
+
_buffer.push({ stream: "stdout", data: chunk });
|
|
39704
|
+
const cb = typeof args[args.length - 1] === "function" ? args[args.length - 1] : void 0;
|
|
39705
|
+
if (cb)
|
|
39706
|
+
cb();
|
|
39707
|
+
return true;
|
|
39708
|
+
};
|
|
39709
|
+
process.stderr.write = function(chunk, ...args) {
|
|
39710
|
+
_buffer.push({ stream: "stderr", 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
|
+
}
|
|
39717
|
+
}
|
|
39718
|
+
function leaveOverlay() {
|
|
39719
|
+
_depth = Math.max(0, _depth - 1);
|
|
39720
|
+
if (_depth === 0) {
|
|
39721
|
+
_overlayActive = false;
|
|
39722
|
+
if (_origStdoutWrite) {
|
|
39723
|
+
process.stdout.write = _origStdoutWrite;
|
|
39724
|
+
_origStdoutWrite = null;
|
|
39725
|
+
}
|
|
39726
|
+
if (_origStderrWrite) {
|
|
39727
|
+
process.stderr.write = _origStderrWrite;
|
|
39728
|
+
_origStderrWrite = null;
|
|
39729
|
+
}
|
|
39730
|
+
if (_onLeaveCallback) {
|
|
39731
|
+
try {
|
|
39732
|
+
_onLeaveCallback();
|
|
39733
|
+
} catch {
|
|
39720
39734
|
}
|
|
39721
39735
|
}
|
|
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];
|
|
39736
|
+
const pending = _buffer;
|
|
39737
|
+
_buffer = [];
|
|
39738
|
+
for (const { stream, data } of pending) {
|
|
39739
|
+
if (stream === "stdout") {
|
|
39740
|
+
process.stdout.write(data);
|
|
39741
|
+
} else {
|
|
39742
|
+
process.stderr.write(data);
|
|
39735
39743
|
}
|
|
39736
39744
|
}
|
|
39737
|
-
} catch {
|
|
39738
39745
|
}
|
|
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
39746
|
}
|
|
39746
|
-
|
|
39747
|
-
|
|
39748
|
-
|
|
39749
|
-
|
|
39750
|
-
|
|
39751
|
-
|
|
39752
|
-
|
|
39753
|
-
|
|
39754
|
-
|
|
39755
|
-
|
|
39756
|
-
|
|
39757
|
-
|
|
39758
|
-
|
|
39747
|
+
var _overlayActive, _depth, _buffer, _origStdoutWrite, _origStderrWrite, _onLeaveCallback;
|
|
39748
|
+
var init_overlay_lock = __esm({
|
|
39749
|
+
"packages/cli/dist/tui/overlay-lock.js"() {
|
|
39750
|
+
"use strict";
|
|
39751
|
+
_overlayActive = false;
|
|
39752
|
+
_depth = 0;
|
|
39753
|
+
_buffer = [];
|
|
39754
|
+
_origStdoutWrite = null;
|
|
39755
|
+
_origStderrWrite = null;
|
|
39756
|
+
_onLeaveCallback = null;
|
|
39757
|
+
}
|
|
39758
|
+
});
|
|
39759
|
+
|
|
39760
|
+
// packages/cli/dist/tui/tui-select.js
|
|
39761
|
+
function ansi3(code, text) {
|
|
39762
|
+
return isTTY3 ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
39763
|
+
}
|
|
39764
|
+
function fg2562(code, text) {
|
|
39765
|
+
return isTTY3 ? `\x1B[38;5;${code}m${text}\x1B[0m` : text;
|
|
39766
|
+
}
|
|
39767
|
+
function stripAnsi(s) {
|
|
39768
|
+
return s.replace(/\x1B\[[0-9;]*m/g, "");
|
|
39769
|
+
}
|
|
39770
|
+
function defaultRenderRow(item, focused, isActive) {
|
|
39771
|
+
const marker = isActive ? selectColors.green("\u25CF") : focused ? selectColors.orange("\u25CF") : selectColors.dim("\u25CB");
|
|
39772
|
+
const label = focused ? selectColors.orange(selectColors.bold(item.label)) : isActive ? selectColors.green(item.label) : item.label;
|
|
39773
|
+
const detail = item.detail ? ` ${selectColors.dim(item.detail)}` : "";
|
|
39774
|
+
return ` ${marker} ${label}${detail}`;
|
|
39775
|
+
}
|
|
39776
|
+
function matchRow(item, focused, isActive) {
|
|
39777
|
+
if (focused || isActive) {
|
|
39778
|
+
return defaultRenderRow(item, focused, isActive);
|
|
39779
|
+
}
|
|
39780
|
+
const marker = selectColors.matchLight("\u25CB");
|
|
39781
|
+
const label = selectColors.matchLight(stripAnsi(item.label));
|
|
39782
|
+
const detail = item.detail ? ` ${selectColors.dim(stripAnsi(item.detail))}` : "";
|
|
39783
|
+
return ` ${marker} ${label}${detail}`;
|
|
39784
|
+
}
|
|
39785
|
+
function tuiSelect(opts) {
|
|
39786
|
+
const { items, title, rl } = opts;
|
|
39787
|
+
const renderRow = opts.renderRow ?? defaultRenderRow;
|
|
39788
|
+
const activeKey = opts.activeKey ?? null;
|
|
39789
|
+
const skipSet = new Set(opts.skipKeys ?? []);
|
|
39790
|
+
if (items.length === 0) {
|
|
39791
|
+
return Promise.resolve({ confirmed: false, key: null, index: -1 });
|
|
39792
|
+
}
|
|
39793
|
+
const isSkippable = (idx) => skipSet.has(items[idx].key);
|
|
39794
|
+
let filter = "";
|
|
39795
|
+
let matchSet = /* @__PURE__ */ new Set();
|
|
39796
|
+
let deleteConfirmIdx = -1;
|
|
39797
|
+
let deleteConfirmSel = false;
|
|
39798
|
+
function updateFilter() {
|
|
39799
|
+
if (!filter) {
|
|
39800
|
+
matchSet = new Set(items.map((_, i) => i));
|
|
39759
39801
|
} else {
|
|
39760
|
-
const
|
|
39761
|
-
|
|
39762
|
-
|
|
39763
|
-
|
|
39802
|
+
const lower = filter.toLowerCase();
|
|
39803
|
+
matchSet = /* @__PURE__ */ new Set();
|
|
39804
|
+
for (let i = 0; i < items.length; i++) {
|
|
39805
|
+
if (isSkippable(i))
|
|
39806
|
+
continue;
|
|
39807
|
+
const plain = stripAnsi(items[i].label).toLowerCase();
|
|
39808
|
+
const detailPlain = items[i].detail ? stripAnsi(items[i].detail).toLowerCase() : "";
|
|
39809
|
+
if (plain.includes(lower) || detailPlain.includes(lower)) {
|
|
39810
|
+
matchSet.add(i);
|
|
39811
|
+
}
|
|
39764
39812
|
}
|
|
39765
39813
|
}
|
|
39766
|
-
} catch {
|
|
39767
39814
|
}
|
|
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
|
-
}
|
|
39815
|
+
updateFilter();
|
|
39816
|
+
const findSelectable = (from, dir) => {
|
|
39817
|
+
let idx = from;
|
|
39818
|
+
while (idx >= 0 && idx < items.length) {
|
|
39819
|
+
if (!isSkippable(idx) && matchSet.has(idx))
|
|
39820
|
+
return idx;
|
|
39821
|
+
idx += dir;
|
|
39780
39822
|
}
|
|
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
|
|
39823
|
+
return -1;
|
|
39788
39824
|
};
|
|
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);
|
|
39825
|
+
let cursor = activeKey ? items.findIndex((i) => i.key === activeKey) : -1;
|
|
39826
|
+
if (cursor < 0 || isSkippable(cursor)) {
|
|
39827
|
+
const first = findSelectable(0, 1);
|
|
39828
|
+
cursor = first >= 0 ? first : 0;
|
|
39822
39829
|
}
|
|
39823
|
-
const
|
|
39824
|
-
|
|
39825
|
-
|
|
39826
|
-
|
|
39827
|
-
|
|
39828
|
-
|
|
39829
|
-
|
|
39830
|
-
}
|
|
39831
|
-
function askSecret(rl, question) {
|
|
39830
|
+
const reservedTopBottom = 6;
|
|
39831
|
+
const hasCrumbs = opts.breadcrumbs && opts.breadcrumbs.length > 0;
|
|
39832
|
+
const selectChrome = (hasCrumbs ? 11 : 10) + 3;
|
|
39833
|
+
const contentArea = opts.availableRows ? opts.availableRows + reservedTopBottom : process.stdout.rows ?? 24;
|
|
39834
|
+
const maxVisible = opts.maxVisible ?? Math.max(3, contentArea - selectChrome);
|
|
39835
|
+
let scrollOffset = 0;
|
|
39836
|
+
let lastRenderedLines = 0;
|
|
39832
39837
|
return new Promise((resolve36) => {
|
|
39833
|
-
process.stdout.write(question);
|
|
39834
|
-
let secret = "";
|
|
39835
39838
|
const stdin = process.stdin;
|
|
39836
39839
|
const hadRawMode = stdin.isRaw;
|
|
39840
|
+
const savedRlListeners = [];
|
|
39841
|
+
if (rl) {
|
|
39842
|
+
rl.pause();
|
|
39843
|
+
for (const event of ["keypress", "data"]) {
|
|
39844
|
+
const listeners = stdin.listeners(event);
|
|
39845
|
+
for (const fn of listeners) {
|
|
39846
|
+
savedRlListeners.push({ event, fn });
|
|
39847
|
+
stdin.removeListener(event, fn);
|
|
39848
|
+
}
|
|
39849
|
+
}
|
|
39850
|
+
}
|
|
39837
39851
|
if (typeof stdin.setRawMode === "function") {
|
|
39838
39852
|
stdin.setRawMode(true);
|
|
39839
39853
|
}
|
|
39840
39854
|
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
|
-
|
|
39855
|
+
enterOverlay();
|
|
39856
|
+
overlayWrite("\x1B[?1049h\x1B[48;5;234m\x1B[2J\x1B[H\x1B[?25l\x1B[?1003h\x1B[?1006h");
|
|
39857
|
+
let listRowOffset = 0;
|
|
39858
|
+
function clampScroll(displayList) {
|
|
39859
|
+
const cursorPos = displayList.indexOf(cursor);
|
|
39860
|
+
if (cursorPos < 0)
|
|
39861
|
+
return;
|
|
39862
|
+
if (cursorPos < scrollOffset) {
|
|
39863
|
+
scrollOffset = cursorPos;
|
|
39864
|
+
} else if (cursorPos >= scrollOffset + maxVisible) {
|
|
39865
|
+
scrollOffset = cursorPos - maxVisible + 1;
|
|
39866
|
+
}
|
|
39867
|
+
const maxOffset = Math.max(0, displayList.length - maxVisible);
|
|
39868
|
+
scrollOffset = Math.max(0, Math.min(maxOffset, scrollOffset));
|
|
39869
|
+
}
|
|
39870
|
+
const hasBreadcrumbs = opts.breadcrumbs && opts.breadcrumbs.length > 0;
|
|
39871
|
+
function render() {
|
|
39872
|
+
overlayWrite("\x1B[48;5;234m\x1B[H\x1B[2J");
|
|
39873
|
+
const lines = [];
|
|
39874
|
+
lines.push("", "", "");
|
|
39875
|
+
if (hasBreadcrumbs) {
|
|
39876
|
+
const trail = opts.breadcrumbs.map((b) => selectColors.dim(b)).join(selectColors.dim(" \u203A "));
|
|
39877
|
+
lines.push(`
|
|
39878
|
+
${selectColors.cyan("\u2190")} ${trail}`);
|
|
39879
|
+
}
|
|
39880
|
+
if (title) {
|
|
39881
|
+
if (!hasBreadcrumbs)
|
|
39882
|
+
lines.push("");
|
|
39883
|
+
lines.push(` ${selectColors.bold(title)}`);
|
|
39884
|
+
}
|
|
39885
|
+
if (filter) {
|
|
39886
|
+
const count = matchSet.size;
|
|
39887
|
+
lines.push(` ${selectColors.cyan("/")} ${selectColors.bold(filter)} ${selectColors.dim(`(${count} match${count !== 1 ? "es" : ""})`)}`);
|
|
39888
|
+
} else {
|
|
39889
|
+
lines.push(` ${selectColors.dim("Type to filter...")}`);
|
|
39890
|
+
}
|
|
39891
|
+
lines.push("");
|
|
39892
|
+
let displayList;
|
|
39893
|
+
if (filter) {
|
|
39894
|
+
displayList = [];
|
|
39895
|
+
for (let i = 0; i < items.length; i++) {
|
|
39896
|
+
if (matchSet.has(i) || isSkippable(i)) {
|
|
39897
|
+
displayList.push(i);
|
|
39864
39898
|
}
|
|
39865
|
-
} else if (c3.charCodeAt(0) >= 32) {
|
|
39866
|
-
secret += c3;
|
|
39867
|
-
process.stdout.write("*");
|
|
39868
39899
|
}
|
|
39900
|
+
displayList = displayList.filter((idx, pos) => {
|
|
39901
|
+
if (!isSkippable(idx))
|
|
39902
|
+
return true;
|
|
39903
|
+
for (let j = pos + 1; j < displayList.length; j++) {
|
|
39904
|
+
if (!isSkippable(displayList[j]))
|
|
39905
|
+
return true;
|
|
39906
|
+
break;
|
|
39907
|
+
}
|
|
39908
|
+
return false;
|
|
39909
|
+
});
|
|
39910
|
+
} else {
|
|
39911
|
+
displayList = items.map((_, i) => i);
|
|
39869
39912
|
}
|
|
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;
|
|
39913
|
+
clampScroll(displayList);
|
|
39914
|
+
const visibleStart = scrollOffset;
|
|
39915
|
+
const visibleEnd = Math.min(displayList.length, scrollOffset + maxVisible);
|
|
39916
|
+
if (visibleStart > 0) {
|
|
39917
|
+
lines.push(` ${selectColors.dim(` \u25B2 ${visibleStart} more`)}`);
|
|
39918
|
+
}
|
|
39919
|
+
listRowOffset = lines.length;
|
|
39920
|
+
for (let vi = visibleStart; vi < visibleEnd; vi++) {
|
|
39921
|
+
const idx = displayList[vi];
|
|
39922
|
+
const item = items[idx];
|
|
39923
|
+
if (isSkippable(idx)) {
|
|
39924
|
+
lines.push(` ${item.label}`);
|
|
39925
|
+
continue;
|
|
39901
39926
|
}
|
|
39902
|
-
|
|
39903
|
-
|
|
39904
|
-
|
|
39927
|
+
const focused = idx === cursor;
|
|
39928
|
+
const isActive = item.key === activeKey;
|
|
39929
|
+
if (deleteConfirmIdx === idx) {
|
|
39930
|
+
const yesLabel = deleteConfirmSel ? selectColors.bold(selectColors.green("[Yes]")) : selectColors.dim("[Yes]");
|
|
39931
|
+
const noLabel = !deleteConfirmSel ? selectColors.bold(selectColors.orange("[No]")) : selectColors.dim("[No]");
|
|
39932
|
+
lines.push(` ${ansi3("31", "\u2715")} ${ansi3("31", stripAnsi(item.label))} Delete? ${yesLabel} ${noLabel}`);
|
|
39933
|
+
} else if (filter) {
|
|
39934
|
+
lines.push(matchRow(item, focused, isActive));
|
|
39935
|
+
} else {
|
|
39936
|
+
lines.push(renderRow(item, focused, isActive));
|
|
39937
|
+
}
|
|
39938
|
+
}
|
|
39939
|
+
const remaining = displayList.length - visibleEnd;
|
|
39940
|
+
if (remaining > 0) {
|
|
39941
|
+
lines.push(` ${selectColors.dim(` \u25BC ${remaining} more`)}`);
|
|
39942
|
+
}
|
|
39943
|
+
lines.push("");
|
|
39944
|
+
if (deleteConfirmIdx >= 0) {
|
|
39945
|
+
lines.push(` ${selectColors.dim("\u2190/\u2192 select Enter confirm Esc cancel")}`);
|
|
39946
|
+
} else {
|
|
39947
|
+
const actionHint = opts.onAction ? " \u2190/\u2192/Space toggle" : "";
|
|
39948
|
+
const deleteHint = opts.onDelete ? " Del remove" : "";
|
|
39949
|
+
const customHint = opts.customKeyHint ?? "";
|
|
39950
|
+
const escLabel = filter ? "clear filter" : hasBreadcrumbs ? "\u2190 back" : "cancel";
|
|
39951
|
+
lines.push(` ${selectColors.dim("\u2191/\u2193 navigate Enter/Click select" + actionHint + deleteHint + customHint + " Esc " + escLabel + " Type to filter")}`);
|
|
39905
39952
|
}
|
|
39953
|
+
lines.push("");
|
|
39954
|
+
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");
|
|
39955
|
+
overlayWrite("\x1B[48;5;234m" + output + "\x1B[K");
|
|
39956
|
+
lastRenderedLines = lines.length;
|
|
39906
39957
|
}
|
|
39907
|
-
|
|
39908
|
-
|
|
39909
|
-
|
|
39910
|
-
|
|
39911
|
-
|
|
39912
|
-
|
|
39913
|
-
|
|
39958
|
+
function cleanup() {
|
|
39959
|
+
stdin.removeListener("data", onData);
|
|
39960
|
+
process.stdout.removeListener("resize", onResize);
|
|
39961
|
+
overlayWrite("\x1B[?1003l\x1B[?1002l\x1B[?1006l\x1B[?1049l\x1B[?25h");
|
|
39962
|
+
leaveOverlay();
|
|
39963
|
+
if (typeof stdin.setRawMode === "function") {
|
|
39964
|
+
stdin.setRawMode(hadRawMode ?? false);
|
|
39965
|
+
}
|
|
39966
|
+
for (const { event, fn } of savedRlListeners) {
|
|
39967
|
+
stdin.on(event, fn);
|
|
39968
|
+
}
|
|
39969
|
+
if (rl) {
|
|
39970
|
+
rl.resume();
|
|
39971
|
+
rl.prompt(false);
|
|
39972
|
+
}
|
|
39914
39973
|
}
|
|
39915
|
-
|
|
39916
|
-
|
|
39917
|
-
|
|
39918
|
-
|
|
39919
|
-
|
|
39920
|
-
|
|
39921
|
-
|
|
39922
|
-
|
|
39923
|
-
|
|
39924
|
-
|
|
39925
|
-
|
|
39926
|
-
|
|
39927
|
-
|
|
39928
|
-
|
|
39929
|
-
|
|
39930
|
-
|
|
39931
|
-
|
|
39932
|
-
|
|
39933
|
-
|
|
39934
|
-
|
|
39935
|
-
|
|
39936
|
-
|
|
39937
|
-
|
|
39938
|
-
|
|
39939
|
-
|
|
39940
|
-
|
|
39941
|
-
|
|
39942
|
-
|
|
39943
|
-
|
|
39944
|
-
|
|
39945
|
-
|
|
39946
|
-
|
|
39947
|
-
|
|
39948
|
-
|
|
39949
|
-
|
|
39950
|
-
|
|
39951
|
-
|
|
39952
|
-
|
|
39953
|
-
|
|
39954
|
-
|
|
39955
|
-
|
|
39956
|
-
|
|
39957
|
-
|
|
39958
|
-
|
|
39959
|
-
|
|
39960
|
-
|
|
39961
|
-
|
|
39962
|
-
|
|
39963
|
-
|
|
39964
|
-
|
|
39965
|
-
|
|
39966
|
-
|
|
39967
|
-
|
|
39968
|
-
|
|
39969
|
-
|
|
39970
|
-
|
|
39971
|
-
|
|
39972
|
-
|
|
39973
|
-
|
|
39974
|
-
|
|
39975
|
-
|
|
39976
|
-
|
|
39977
|
-
|
|
39978
|
-
|
|
39974
|
+
function onData(chunk) {
|
|
39975
|
+
let seq = chunk.toString("utf8");
|
|
39976
|
+
const mouseRe = /\x1B\[<(\d+);(\d+);(\d+)([Mm])/g;
|
|
39977
|
+
let mouseProcessed = false;
|
|
39978
|
+
let mouseM;
|
|
39979
|
+
while ((mouseM = mouseRe.exec(seq)) !== null) {
|
|
39980
|
+
mouseProcessed = true;
|
|
39981
|
+
const btn = parseInt(mouseM[1]);
|
|
39982
|
+
const mCol = parseInt(mouseM[2]);
|
|
39983
|
+
const mRow = parseInt(mouseM[3]);
|
|
39984
|
+
const suffix = mouseM[4];
|
|
39985
|
+
if (btn === 0 && suffix === "M") {
|
|
39986
|
+
const listIdx = mRow - listRowOffset - 1;
|
|
39987
|
+
if (listIdx >= 0 && listIdx < maxVisible) {
|
|
39988
|
+
let displayList;
|
|
39989
|
+
if (filter) {
|
|
39990
|
+
displayList = [];
|
|
39991
|
+
for (let i = 0; i < items.length; i++) {
|
|
39992
|
+
if (matchSet.has(i) || isSkippable(i))
|
|
39993
|
+
displayList.push(i);
|
|
39994
|
+
}
|
|
39995
|
+
displayList = displayList.filter((idx, pos) => {
|
|
39996
|
+
if (!isSkippable(idx))
|
|
39997
|
+
return true;
|
|
39998
|
+
for (let j = pos + 1; j < displayList.length; j++) {
|
|
39999
|
+
if (!isSkippable(displayList[j]))
|
|
40000
|
+
return true;
|
|
40001
|
+
break;
|
|
40002
|
+
}
|
|
40003
|
+
return false;
|
|
40004
|
+
});
|
|
40005
|
+
} else {
|
|
40006
|
+
displayList = items.map((_, i) => i);
|
|
40007
|
+
}
|
|
40008
|
+
const vi = scrollOffset + listIdx;
|
|
40009
|
+
if (vi < displayList.length) {
|
|
40010
|
+
const itemIdx = displayList[vi];
|
|
40011
|
+
if (!isSkippable(itemIdx) && matchSet.has(itemIdx)) {
|
|
40012
|
+
cursor = itemIdx;
|
|
40013
|
+
cleanup();
|
|
40014
|
+
resolve36({ confirmed: true, key: items[cursor].key, index: cursor });
|
|
40015
|
+
return;
|
|
40016
|
+
} else if (!isSkippable(itemIdx)) {
|
|
40017
|
+
cursor = itemIdx;
|
|
40018
|
+
render();
|
|
40019
|
+
}
|
|
40020
|
+
}
|
|
40021
|
+
}
|
|
40022
|
+
}
|
|
40023
|
+
if ((btn === 35 || btn === 32 || btn === 67) && suffix === "M") {
|
|
40024
|
+
const listIdx = mRow - listRowOffset - 1;
|
|
40025
|
+
if (listIdx >= 0 && listIdx < maxVisible) {
|
|
40026
|
+
let displayList;
|
|
40027
|
+
if (filter) {
|
|
40028
|
+
displayList = [];
|
|
40029
|
+
for (let i = 0; i < items.length; i++) {
|
|
40030
|
+
if (matchSet.has(i) || isSkippable(i))
|
|
40031
|
+
displayList.push(i);
|
|
40032
|
+
}
|
|
40033
|
+
displayList = displayList.filter((idx, pos) => {
|
|
40034
|
+
if (!isSkippable(idx))
|
|
40035
|
+
return true;
|
|
40036
|
+
for (let j = pos + 1; j < displayList.length; j++) {
|
|
40037
|
+
if (!isSkippable(displayList[j]))
|
|
40038
|
+
return true;
|
|
40039
|
+
break;
|
|
40040
|
+
}
|
|
40041
|
+
return false;
|
|
40042
|
+
});
|
|
40043
|
+
} else {
|
|
40044
|
+
displayList = items.map((_, i) => i);
|
|
40045
|
+
}
|
|
40046
|
+
const vi = scrollOffset + listIdx;
|
|
40047
|
+
if (vi < displayList.length) {
|
|
40048
|
+
const itemIdx = displayList[vi];
|
|
40049
|
+
if (!isSkippable(itemIdx) && itemIdx !== cursor) {
|
|
40050
|
+
cursor = itemIdx;
|
|
40051
|
+
render();
|
|
40052
|
+
}
|
|
40053
|
+
}
|
|
40054
|
+
}
|
|
40055
|
+
}
|
|
40056
|
+
if (btn === 64) {
|
|
40057
|
+
const next = findSelectable(cursor - 1, -1);
|
|
40058
|
+
if (next >= 0 && next !== cursor) {
|
|
40059
|
+
cursor = next;
|
|
40060
|
+
render();
|
|
40061
|
+
}
|
|
40062
|
+
} else if (btn === 65) {
|
|
40063
|
+
const next = findSelectable(cursor + 1, 1);
|
|
40064
|
+
if (next >= 0 && next !== cursor) {
|
|
40065
|
+
cursor = next;
|
|
40066
|
+
render();
|
|
40067
|
+
}
|
|
39979
40068
|
}
|
|
39980
40069
|
}
|
|
39981
|
-
|
|
39982
|
-
|
|
39983
|
-
|
|
39984
|
-
|
|
39985
|
-
|
|
39986
|
-
|
|
39987
|
-
|
|
39988
|
-
|
|
39989
|
-
|
|
39990
|
-
|
|
39991
|
-
|
|
39992
|
-
|
|
40070
|
+
seq = seq.replace(mouseRe, "");
|
|
40071
|
+
if (!seq && mouseProcessed)
|
|
40072
|
+
return;
|
|
40073
|
+
if (deleteConfirmIdx >= 0) {
|
|
40074
|
+
if (seq === "\x1B[D") {
|
|
40075
|
+
deleteConfirmSel = true;
|
|
40076
|
+
render();
|
|
40077
|
+
} else if (seq === "\x1B[C") {
|
|
40078
|
+
deleteConfirmSel = false;
|
|
40079
|
+
render();
|
|
40080
|
+
} else if (seq === "\r" || seq === "\n") {
|
|
40081
|
+
if (deleteConfirmSel && opts.onDelete) {
|
|
40082
|
+
const deletedIdx = deleteConfirmIdx;
|
|
40083
|
+
deleteConfirmIdx = -1;
|
|
40084
|
+
deleteConfirmSel = false;
|
|
40085
|
+
opts.onDelete(items[deletedIdx], (removed) => {
|
|
40086
|
+
if (removed) {
|
|
40087
|
+
items.splice(deletedIdx, 1);
|
|
40088
|
+
if (items.length === 0) {
|
|
40089
|
+
cleanup();
|
|
40090
|
+
resolve36({ confirmed: false, key: null, index: -1 });
|
|
40091
|
+
return;
|
|
40092
|
+
}
|
|
40093
|
+
updateFilter();
|
|
40094
|
+
if (cursor >= items.length)
|
|
40095
|
+
cursor = items.length - 1;
|
|
40096
|
+
const valid = findSelectable(cursor, 1) ?? findSelectable(cursor, -1);
|
|
40097
|
+
if (valid >= 0)
|
|
40098
|
+
cursor = valid;
|
|
40099
|
+
scrollOffset = 0;
|
|
40100
|
+
}
|
|
40101
|
+
render();
|
|
40102
|
+
});
|
|
40103
|
+
} else {
|
|
40104
|
+
deleteConfirmIdx = -1;
|
|
40105
|
+
deleteConfirmSel = false;
|
|
40106
|
+
render();
|
|
40107
|
+
}
|
|
40108
|
+
} else if (seq === "\x1B" || seq === "\x1B\x1B" || seq === "") {
|
|
40109
|
+
deleteConfirmIdx = -1;
|
|
40110
|
+
deleteConfirmSel = false;
|
|
40111
|
+
render();
|
|
40112
|
+
}
|
|
40113
|
+
return;
|
|
39993
40114
|
}
|
|
39994
|
-
|
|
39995
|
-
|
|
39996
|
-
|
|
39997
|
-
|
|
39998
|
-
|
|
40115
|
+
if (seq === "\x1B[A") {
|
|
40116
|
+
const next = findSelectable(cursor - 1, -1);
|
|
40117
|
+
if (next >= 0 && next !== cursor) {
|
|
40118
|
+
cursor = next;
|
|
40119
|
+
render();
|
|
40120
|
+
}
|
|
40121
|
+
} else if (seq === "\x1B[B") {
|
|
40122
|
+
const next = findSelectable(cursor + 1, 1);
|
|
40123
|
+
if (next >= 0 && next !== cursor) {
|
|
40124
|
+
cursor = next;
|
|
40125
|
+
render();
|
|
40126
|
+
}
|
|
40127
|
+
} else if (seq === "\x1B[5~") {
|
|
40128
|
+
for (let i = 0; i < maxVisible; i++) {
|
|
40129
|
+
const next = findSelectable(cursor - 1, -1);
|
|
40130
|
+
if (next < 0 || next === cursor)
|
|
40131
|
+
break;
|
|
40132
|
+
cursor = next;
|
|
40133
|
+
}
|
|
40134
|
+
render();
|
|
40135
|
+
} else if (seq === "\x1B[6~") {
|
|
40136
|
+
for (let i = 0; i < maxVisible; i++) {
|
|
40137
|
+
const next = findSelectable(cursor + 1, 1);
|
|
40138
|
+
if (next < 0 || next === cursor)
|
|
40139
|
+
break;
|
|
40140
|
+
cursor = next;
|
|
40141
|
+
}
|
|
40142
|
+
render();
|
|
40143
|
+
} else if (seq === "\x1B[H") {
|
|
40144
|
+
const first = findSelectable(0, 1);
|
|
40145
|
+
if (first >= 0) {
|
|
40146
|
+
cursor = first;
|
|
40147
|
+
render();
|
|
40148
|
+
}
|
|
40149
|
+
} else if (seq === "\x1B[F") {
|
|
40150
|
+
const last = findSelectable(items.length - 1, -1);
|
|
40151
|
+
if (last >= 0) {
|
|
40152
|
+
cursor = last;
|
|
40153
|
+
render();
|
|
40154
|
+
}
|
|
40155
|
+
} else if (seq === "\x1B[3~" && opts.onDelete) {
|
|
40156
|
+
if (!isSkippable(cursor) && matchSet.has(cursor) && items[cursor].key !== activeKey) {
|
|
40157
|
+
deleteConfirmIdx = cursor;
|
|
40158
|
+
deleteConfirmSel = false;
|
|
40159
|
+
render();
|
|
40160
|
+
}
|
|
40161
|
+
} else if (seq === "\x1B[D" && opts.onAction) {
|
|
40162
|
+
if (!isSkippable(cursor) && matchSet.has(cursor)) {
|
|
40163
|
+
if (opts.onAction(items[cursor], "left"))
|
|
40164
|
+
render();
|
|
40165
|
+
}
|
|
40166
|
+
} else if (seq === "\x1B[C" && opts.onAction) {
|
|
40167
|
+
if (!isSkippable(cursor) && matchSet.has(cursor)) {
|
|
40168
|
+
if (opts.onAction(items[cursor], "right"))
|
|
40169
|
+
render();
|
|
40170
|
+
}
|
|
40171
|
+
} else if (seq === " " && opts.onAction) {
|
|
40172
|
+
if (!isSkippable(cursor) && matchSet.has(cursor)) {
|
|
40173
|
+
if (opts.onAction(items[cursor], "space"))
|
|
40174
|
+
render();
|
|
40175
|
+
}
|
|
40176
|
+
} else if (seq === "\r" || seq === "\n") {
|
|
40177
|
+
if (!isSkippable(cursor) && matchSet.has(cursor)) {
|
|
40178
|
+
if (opts.onEnter) {
|
|
40179
|
+
const consumed = opts.onEnter(items[cursor], {
|
|
40180
|
+
done: () => render(),
|
|
40181
|
+
resolve: (result) => {
|
|
40182
|
+
cleanup();
|
|
40183
|
+
resolve36(result);
|
|
40184
|
+
},
|
|
40185
|
+
getInput: (prompt, prefill) => getInputFromUser(prompt, prefill),
|
|
40186
|
+
render: () => render(),
|
|
40187
|
+
updateItem: (index, updates) => {
|
|
40188
|
+
Object.assign(items[index], updates);
|
|
40189
|
+
render();
|
|
40190
|
+
}
|
|
40191
|
+
});
|
|
40192
|
+
if (consumed)
|
|
40193
|
+
return;
|
|
40194
|
+
}
|
|
40195
|
+
cleanup();
|
|
40196
|
+
resolve36({ confirmed: true, key: items[cursor].key, index: cursor });
|
|
40197
|
+
}
|
|
40198
|
+
} else if (seq === "\x1B" || seq === "\x1B\x1B") {
|
|
40199
|
+
if (filter) {
|
|
40200
|
+
filter = "";
|
|
40201
|
+
updateFilter();
|
|
40202
|
+
const valid = findSelectable(cursor, 1);
|
|
40203
|
+
if (valid >= 0)
|
|
40204
|
+
cursor = valid;
|
|
40205
|
+
scrollOffset = 0;
|
|
40206
|
+
render();
|
|
40207
|
+
} else if (hasBreadcrumbs) {
|
|
40208
|
+
cleanup();
|
|
40209
|
+
resolve36({ confirmed: false, key: "__back__", index: cursor });
|
|
40210
|
+
} else {
|
|
40211
|
+
cleanup();
|
|
40212
|
+
resolve36({ confirmed: false, key: null, index: cursor });
|
|
40213
|
+
}
|
|
40214
|
+
} else if (seq === "") {
|
|
40215
|
+
cleanup();
|
|
40216
|
+
resolve36({ confirmed: false, key: null, index: cursor });
|
|
40217
|
+
} else if (seq === "\x7F" || seq === "\b") {
|
|
40218
|
+
if (filter.length > 0) {
|
|
40219
|
+
filter = filter.slice(0, -1);
|
|
40220
|
+
updateFilter();
|
|
40221
|
+
if (!matchSet.has(cursor) || isSkippable(cursor)) {
|
|
40222
|
+
const next = findSelectable(0, 1);
|
|
40223
|
+
if (next >= 0)
|
|
40224
|
+
cursor = next;
|
|
40225
|
+
}
|
|
40226
|
+
scrollOffset = 0;
|
|
40227
|
+
render();
|
|
40228
|
+
} else if (hasBreadcrumbs) {
|
|
40229
|
+
cleanup();
|
|
40230
|
+
resolve36({ confirmed: false, key: "__back__", index: cursor });
|
|
40231
|
+
}
|
|
40232
|
+
} else if (seq.length === 1 && seq.charCodeAt(0) >= 32 && seq.charCodeAt(0) < 127) {
|
|
40233
|
+
if (opts.onCustomKey && !isSkippable(cursor) && matchSet.has(cursor)) {
|
|
40234
|
+
const consumed = opts.onCustomKey(items[cursor], seq, {
|
|
40235
|
+
done: () => render(),
|
|
40236
|
+
resolve: (result) => {
|
|
40237
|
+
cleanup();
|
|
40238
|
+
resolve36(result);
|
|
40239
|
+
},
|
|
40240
|
+
getInput: (prompt, prefill) => getInputFromUser(prompt, prefill),
|
|
40241
|
+
render: () => render(),
|
|
40242
|
+
updateItem: (index, updates) => {
|
|
40243
|
+
Object.assign(items[index], updates);
|
|
40244
|
+
render();
|
|
40245
|
+
}
|
|
40246
|
+
});
|
|
40247
|
+
if (consumed)
|
|
40248
|
+
return;
|
|
40249
|
+
}
|
|
40250
|
+
filter += seq;
|
|
40251
|
+
updateFilter();
|
|
40252
|
+
if (matchSet.size > 0) {
|
|
40253
|
+
const first = findSelectable(0, 1);
|
|
40254
|
+
if (first >= 0)
|
|
40255
|
+
cursor = first;
|
|
40256
|
+
}
|
|
40257
|
+
scrollOffset = 0;
|
|
40258
|
+
render();
|
|
40259
|
+
}
|
|
40260
|
+
}
|
|
40261
|
+
function getInputFromUser(prompt, prefill = "") {
|
|
40262
|
+
return new Promise((inputResolve) => {
|
|
40263
|
+
let inputBuf = prefill;
|
|
40264
|
+
let inputCursor = prefill.length;
|
|
40265
|
+
overlayWrite("\x1B[?25h");
|
|
40266
|
+
function renderInput() {
|
|
40267
|
+
overlayWrite(`\x1B[${lastRenderedLines}A`);
|
|
40268
|
+
const saveFooter = lastRenderedLines;
|
|
40269
|
+
for (let i = 0; i < saveFooter; i++)
|
|
40270
|
+
overlayWrite("\x1B[B");
|
|
40271
|
+
overlayWrite("\x1B[2A");
|
|
40272
|
+
overlayWrite("\x1B[2K");
|
|
40273
|
+
const before = inputBuf.slice(0, inputCursor);
|
|
40274
|
+
const after = inputBuf.slice(inputCursor);
|
|
40275
|
+
overlayWrite(` ${selectColors.cyan("\u203A")} ${selectColors.bold(prompt)} ${before}\x1B[7m${after.charAt(0) || " "}\x1B[27m${after.slice(1)}`);
|
|
40276
|
+
overlayWrite("\n\x1B[2K");
|
|
40277
|
+
overlayWrite(` ${selectColors.dim("Enter confirm Esc cancel")}`);
|
|
40278
|
+
overlayWrite("\n");
|
|
40279
|
+
}
|
|
40280
|
+
function onInputData(chunk) {
|
|
40281
|
+
const s = chunk.toString("utf8");
|
|
40282
|
+
if (s === "\r" || s === "\n") {
|
|
40283
|
+
stdin.removeListener("data", onInputData);
|
|
40284
|
+
stdin.on("data", onData);
|
|
40285
|
+
overlayWrite("\x1B[?25l");
|
|
40286
|
+
render();
|
|
40287
|
+
inputResolve(inputBuf);
|
|
40288
|
+
} else if (s === "\x1B" || s === "\x1B\x1B" || s === "") {
|
|
40289
|
+
stdin.removeListener("data", onInputData);
|
|
40290
|
+
stdin.on("data", onData);
|
|
40291
|
+
overlayWrite("\x1B[?25l");
|
|
40292
|
+
render();
|
|
40293
|
+
inputResolve(null);
|
|
40294
|
+
} else if (s === "\x7F" || s === "\b") {
|
|
40295
|
+
if (inputCursor > 0) {
|
|
40296
|
+
inputBuf = inputBuf.slice(0, inputCursor - 1) + inputBuf.slice(inputCursor);
|
|
40297
|
+
inputCursor--;
|
|
40298
|
+
renderInput();
|
|
40299
|
+
}
|
|
40300
|
+
} else if (s === "\x1B[D") {
|
|
40301
|
+
if (inputCursor > 0) {
|
|
40302
|
+
inputCursor--;
|
|
40303
|
+
renderInput();
|
|
40304
|
+
}
|
|
40305
|
+
} else if (s === "\x1B[C") {
|
|
40306
|
+
if (inputCursor < inputBuf.length) {
|
|
40307
|
+
inputCursor++;
|
|
40308
|
+
renderInput();
|
|
40309
|
+
}
|
|
40310
|
+
} else if (s === "\x1B[H") {
|
|
40311
|
+
inputCursor = 0;
|
|
40312
|
+
renderInput();
|
|
40313
|
+
} else if (s === "\x1B[F") {
|
|
40314
|
+
inputCursor = inputBuf.length;
|
|
40315
|
+
renderInput();
|
|
40316
|
+
} else if (s.length === 1 && s.charCodeAt(0) >= 32 && s.charCodeAt(0) < 127) {
|
|
40317
|
+
inputBuf = inputBuf.slice(0, inputCursor) + s + inputBuf.slice(inputCursor);
|
|
40318
|
+
inputCursor++;
|
|
40319
|
+
renderInput();
|
|
40320
|
+
}
|
|
40321
|
+
}
|
|
40322
|
+
stdin.removeListener("data", onData);
|
|
40323
|
+
stdin.on("data", onInputData);
|
|
40324
|
+
renderInput();
|
|
40325
|
+
});
|
|
39999
40326
|
}
|
|
40327
|
+
const onResize = () => render();
|
|
40328
|
+
process.stdout.on("resize", onResize);
|
|
40329
|
+
stdin.on("data", onData);
|
|
40330
|
+
render();
|
|
40331
|
+
});
|
|
40332
|
+
}
|
|
40333
|
+
var isTTY3, selectColors;
|
|
40334
|
+
var init_tui_select = __esm({
|
|
40335
|
+
"packages/cli/dist/tui/tui-select.js"() {
|
|
40336
|
+
"use strict";
|
|
40337
|
+
init_overlay_lock();
|
|
40338
|
+
isTTY3 = process.stdout.isTTY ?? false;
|
|
40339
|
+
selectColors = {
|
|
40340
|
+
orange: (t) => fg2562(208, t),
|
|
40341
|
+
green: (t) => ansi3("32", t),
|
|
40342
|
+
dim: (t) => ansi3("2", t),
|
|
40343
|
+
bold: (t) => ansi3("1", t),
|
|
40344
|
+
cyan: (t) => ansi3("36", t),
|
|
40345
|
+
/** Lighter grey for filter matches (252 = near-white) */
|
|
40346
|
+
matchLight: (t) => fg2562(252, t),
|
|
40347
|
+
/** Dark grey for non-matching items (240 = dark grey) */
|
|
40348
|
+
matchDark: (t) => fg2562(240, t)
|
|
40349
|
+
};
|
|
40000
40350
|
}
|
|
40001
|
-
|
|
40002
|
-
${c2.cyan("\u25CF")} Installing Ollama via Homebrew...
|
|
40351
|
+
});
|
|
40003
40352
|
|
|
40004
|
-
|
|
40353
|
+
// packages/cli/dist/tui/setup.js
|
|
40354
|
+
import * as readline from "node:readline";
|
|
40355
|
+
import { execSync as execSync27, spawn as spawn19, exec as exec2 } from "node:child_process";
|
|
40356
|
+
import { promisify as promisify6 } from "node:util";
|
|
40357
|
+
import { existsSync as existsSync37, writeFileSync as writeFileSync16, readFileSync as readFileSync28, appendFileSync as appendFileSync2, mkdirSync as mkdirSync15 } from "node:fs";
|
|
40358
|
+
import { join as join54 } from "node:path";
|
|
40359
|
+
import { homedir as homedir13, platform as platform2 } from "node:os";
|
|
40360
|
+
async function checkToolSupport(modelName, backendUrl = "http://localhost:11434") {
|
|
40361
|
+
if (_toolSupportCache.has(modelName))
|
|
40362
|
+
return _toolSupportCache.get(modelName);
|
|
40005
40363
|
try {
|
|
40006
|
-
|
|
40007
|
-
|
|
40008
|
-
|
|
40364
|
+
const resp = await fetch(`${backendUrl.replace(/\/+$/, "")}/api/chat`, {
|
|
40365
|
+
method: "POST",
|
|
40366
|
+
headers: { "Content-Type": "application/json" },
|
|
40367
|
+
body: JSON.stringify({
|
|
40368
|
+
model: modelName,
|
|
40369
|
+
messages: [{ role: "user", content: "test" }],
|
|
40370
|
+
tools: [{ type: "function", function: { name: "test", description: "test", parameters: { type: "object", properties: {} } } }],
|
|
40371
|
+
stream: false,
|
|
40372
|
+
options: { num_predict: 1 }
|
|
40373
|
+
}),
|
|
40374
|
+
signal: AbortSignal.timeout(1e4)
|
|
40009
40375
|
});
|
|
40010
|
-
|
|
40011
|
-
|
|
40012
|
-
|
|
40013
|
-
|
|
40014
|
-
|
|
40015
|
-
|
|
40016
|
-
process.stdout.write(`
|
|
40017
|
-
${c2.yellow("\u26A0")} brew install completed but ollama not found on PATH.
|
|
40018
|
-
`);
|
|
40019
|
-
return false;
|
|
40020
|
-
} catch (err) {
|
|
40021
|
-
process.stdout.write(`
|
|
40022
|
-
${c2.red("\u2716")} Ollama install failed: ${err instanceof Error ? err.message : String(err)}
|
|
40023
|
-
`);
|
|
40024
|
-
return false;
|
|
40376
|
+
const hasTools = resp.ok;
|
|
40377
|
+
_toolSupportCache.set(modelName, hasTools);
|
|
40378
|
+
return hasTools;
|
|
40379
|
+
} catch {
|
|
40380
|
+
_toolSupportCache.set(modelName, true);
|
|
40381
|
+
return true;
|
|
40025
40382
|
}
|
|
40026
40383
|
}
|
|
40027
|
-
function
|
|
40028
|
-
|
|
40029
|
-
|
|
40030
|
-
|
|
40031
|
-
|
|
40384
|
+
function detectSystemSpecs() {
|
|
40385
|
+
let totalRamGB = 0;
|
|
40386
|
+
let availableRamGB = 0;
|
|
40387
|
+
let gpuVramGB = 0;
|
|
40388
|
+
let gpuName = "";
|
|
40032
40389
|
try {
|
|
40033
|
-
execSync27(
|
|
40034
|
-
|
|
40035
|
-
timeout:
|
|
40390
|
+
const memInfo = execSync27("free -b 2>/dev/null || sysctl -n hw.memsize 2>/dev/null", {
|
|
40391
|
+
encoding: "utf8",
|
|
40392
|
+
timeout: 5e3
|
|
40036
40393
|
});
|
|
40037
|
-
if (
|
|
40038
|
-
|
|
40039
|
-
|
|
40040
|
-
|
|
40041
|
-
|
|
40042
|
-
}
|
|
40043
|
-
process.stdout.write(`
|
|
40044
|
-
${c2.yellow("\u26A0")} Install script ran but ollama not found on PATH.
|
|
40045
|
-
`);
|
|
40046
|
-
return false;
|
|
40047
|
-
} catch (err) {
|
|
40048
|
-
process.stdout.write(`
|
|
40049
|
-
${c2.red("\u2716")} Ollama install failed: ${err instanceof Error ? err.message : String(err)}
|
|
40050
|
-
`);
|
|
40051
|
-
return false;
|
|
40052
|
-
}
|
|
40053
|
-
}
|
|
40054
|
-
async function ensureOllamaRunning(backendUrl, rl) {
|
|
40055
|
-
const ollamaInstalled = hasCmd("ollama");
|
|
40056
|
-
if (!ollamaInstalled) {
|
|
40057
|
-
if (rl) {
|
|
40058
|
-
process.stdout.write(`
|
|
40059
|
-
${c2.yellow("\u26A0")} Ollama is not installed on this system.
|
|
40060
|
-
`);
|
|
40061
|
-
const answer = await ask(rl, ` ${c2.bold("Install Ollama now?")} (Y/n) `);
|
|
40062
|
-
if (answer.toLowerCase() === "n") {
|
|
40063
|
-
return false;
|
|
40394
|
+
if (memInfo.includes("Mem:")) {
|
|
40395
|
+
const match = memInfo.match(/^Mem:\s+(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)/m);
|
|
40396
|
+
if (match) {
|
|
40397
|
+
totalRamGB = parseInt(match[1], 10) / 1024 ** 3;
|
|
40398
|
+
availableRamGB = parseInt(match[2], 10) / 1024 ** 3;
|
|
40064
40399
|
}
|
|
40065
40400
|
} else {
|
|
40066
|
-
|
|
40067
|
-
|
|
40068
|
-
|
|
40069
|
-
|
|
40070
|
-
|
|
40071
|
-
const installed = await autoInstallOllama(installRl);
|
|
40072
|
-
if (!rl)
|
|
40073
|
-
installRl.close();
|
|
40074
|
-
if (!installed) {
|
|
40075
|
-
process.stdout.write(` ${c2.red("\u2716")} Ollama installation failed.
|
|
40076
|
-
|
|
40077
|
-
`);
|
|
40078
|
-
return false;
|
|
40401
|
+
const bytes = parseInt(memInfo.trim(), 10);
|
|
40402
|
+
if (!isNaN(bytes)) {
|
|
40403
|
+
totalRamGB = bytes / 1024 ** 3;
|
|
40404
|
+
availableRamGB = totalRamGB * 0.7;
|
|
40405
|
+
}
|
|
40079
40406
|
}
|
|
40080
|
-
}
|
|
40081
|
-
process.stdout.write(` ${c2.cyan("\u25CF")} Starting ollama serve...
|
|
40082
|
-
`);
|
|
40083
|
-
try {
|
|
40084
|
-
const child = spawn19("ollama", ["serve"], { stdio: "ignore", detached: true });
|
|
40085
|
-
child.unref();
|
|
40086
40407
|
} catch {
|
|
40087
|
-
process.stdout.write(` ${c2.yellow("\u26A0")} Could not start ollama serve.
|
|
40088
|
-
|
|
40089
|
-
`);
|
|
40090
|
-
return false;
|
|
40091
40408
|
}
|
|
40092
|
-
|
|
40093
|
-
|
|
40094
|
-
|
|
40095
|
-
|
|
40096
|
-
|
|
40097
|
-
|
|
40098
|
-
|
|
40099
|
-
|
|
40100
|
-
|
|
40101
|
-
|
|
40102
|
-
|
|
40409
|
+
try {
|
|
40410
|
+
const nvidiaSmi = execSync27("nvidia-smi --query-gpu=memory.total,name --format=csv,noheader,nounits 2>/dev/null", { encoding: "utf8", timeout: 5e3 });
|
|
40411
|
+
const lines = nvidiaSmi.trim().split("\n");
|
|
40412
|
+
if (lines.length > 0) {
|
|
40413
|
+
for (const line of lines) {
|
|
40414
|
+
const parts = line.split(",").map((s) => s.trim());
|
|
40415
|
+
const vramMB = parseInt(parts[0] ?? "0", 10);
|
|
40416
|
+
if (!isNaN(vramMB))
|
|
40417
|
+
gpuVramGB += vramMB / 1024;
|
|
40418
|
+
if (!gpuName && parts[1])
|
|
40419
|
+
gpuName = parts[1];
|
|
40103
40420
|
}
|
|
40104
|
-
} catch {
|
|
40105
40421
|
}
|
|
40422
|
+
} catch {
|
|
40106
40423
|
}
|
|
40107
|
-
|
|
40108
|
-
|
|
40109
|
-
|
|
40110
|
-
|
|
40424
|
+
return {
|
|
40425
|
+
totalRamGB: Math.round(totalRamGB * 10) / 10,
|
|
40426
|
+
availableRamGB: Math.round(availableRamGB * 10) / 10,
|
|
40427
|
+
gpuVramGB: Math.round(gpuVramGB * 10) / 10,
|
|
40428
|
+
gpuName
|
|
40429
|
+
};
|
|
40111
40430
|
}
|
|
40112
|
-
function
|
|
40431
|
+
async function detectSystemSpecsAsync() {
|
|
40432
|
+
let totalRamGB = 0;
|
|
40433
|
+
let availableRamGB = 0;
|
|
40434
|
+
let gpuVramGB = 0;
|
|
40435
|
+
let gpuName = "";
|
|
40113
40436
|
try {
|
|
40114
|
-
|
|
40115
|
-
|
|
40116
|
-
|
|
40117
|
-
|
|
40118
|
-
|
|
40119
|
-
|
|
40120
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
40121
|
-
const stderr = err?.stderr?.toString?.() ?? errMsg;
|
|
40122
|
-
const combined = errMsg + "\n" + stderr;
|
|
40123
|
-
if (combined.includes("412") || combined.includes("newer version") || combined.includes("requires a newer version")) {
|
|
40124
|
-
process.stdout.write(`
|
|
40125
|
-
${c2.yellow("\u26A0")} Ollama needs to be updated for this model.
|
|
40126
|
-
`);
|
|
40127
|
-
if (!ensureCurl()) {
|
|
40128
|
-
throw new Error("curl is required to update Ollama but could not be installed.");
|
|
40129
|
-
}
|
|
40130
|
-
process.stdout.write(` ${c2.cyan("\u25CF")} Updating Ollama via official install script...
|
|
40131
|
-
|
|
40132
|
-
`);
|
|
40133
|
-
try {
|
|
40134
|
-
execSync27("curl -fsSL https://ollama.com/install.sh | sh", {
|
|
40135
|
-
stdio: "inherit",
|
|
40136
|
-
timeout: 3e5
|
|
40137
|
-
// 5 min max for install
|
|
40138
|
-
});
|
|
40139
|
-
process.stdout.write(`
|
|
40140
|
-
${c2.green("\u2714")} Ollama updated successfully.
|
|
40141
|
-
`);
|
|
40142
|
-
process.stdout.write(` ${c2.cyan("\u25CF")} Retrying pull of ${c2.bold(tag)}...
|
|
40143
|
-
|
|
40144
|
-
`);
|
|
40145
|
-
execSync27(`ollama pull ${tag}`, {
|
|
40146
|
-
stdio: "inherit",
|
|
40147
|
-
timeout: 36e5
|
|
40148
|
-
});
|
|
40149
|
-
} catch (updateErr) {
|
|
40150
|
-
const updateMsg = updateErr instanceof Error ? updateErr.message : String(updateErr);
|
|
40151
|
-
throw new Error(`Failed to update Ollama and retry pull: ${updateMsg}
|
|
40152
|
-
Try manually:
|
|
40153
|
-
curl -fsSL https://ollama.com/install.sh | sh
|
|
40154
|
-
ollama pull ${tag}`);
|
|
40437
|
+
const { stdout: memInfo } = await execAsync("free -b 2>/dev/null || sysctl -n hw.memsize 2>/dev/null", { timeout: 5e3 });
|
|
40438
|
+
if (memInfo.includes("Mem:")) {
|
|
40439
|
+
const match = memInfo.match(/^Mem:\s+(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)/m);
|
|
40440
|
+
if (match) {
|
|
40441
|
+
totalRamGB = parseInt(match[1], 10) / 1024 ** 3;
|
|
40442
|
+
availableRamGB = parseInt(match[2], 10) / 1024 ** 3;
|
|
40155
40443
|
}
|
|
40156
40444
|
} else {
|
|
40157
|
-
|
|
40445
|
+
const bytes = parseInt(memInfo.trim(), 10);
|
|
40446
|
+
if (!isNaN(bytes)) {
|
|
40447
|
+
totalRamGB = bytes / 1024 ** 3;
|
|
40448
|
+
availableRamGB = totalRamGB * 0.7;
|
|
40449
|
+
}
|
|
40158
40450
|
}
|
|
40451
|
+
} catch {
|
|
40159
40452
|
}
|
|
40160
|
-
|
|
40161
|
-
|
|
40162
|
-
|
|
40163
|
-
|
|
40164
|
-
|
|
40165
|
-
|
|
40166
|
-
|
|
40167
|
-
|
|
40168
|
-
|
|
40169
|
-
|
|
40170
|
-
|
|
40171
|
-
else if (specs.gpuVramGB >= 4)
|
|
40172
|
-
computeScore = 35;
|
|
40173
|
-
else if (specs.totalRamGB >= 32)
|
|
40174
|
-
computeScore = 25;
|
|
40175
|
-
else
|
|
40176
|
-
computeScore = Math.max(5, Math.round(specs.totalRamGB * 1.5));
|
|
40177
|
-
let speedScore;
|
|
40178
|
-
if (specs.gpuVramGB >= 48)
|
|
40179
|
-
speedScore = 95;
|
|
40180
|
-
else if (specs.gpuVramGB >= 24)
|
|
40181
|
-
speedScore = 80;
|
|
40182
|
-
else if (specs.gpuVramGB >= 12)
|
|
40183
|
-
speedScore = 65;
|
|
40184
|
-
else if (specs.gpuVramGB >= 8)
|
|
40185
|
-
speedScore = 50;
|
|
40186
|
-
else if (specs.gpuVramGB > 0)
|
|
40187
|
-
speedScore = 35;
|
|
40188
|
-
else
|
|
40189
|
-
speedScore = Math.min(30, Math.round(specs.totalRamGB * 0.6));
|
|
40190
|
-
const overall = Math.round(memoryScore * 0.45 + computeScore * 0.35 + speedScore * 0.2);
|
|
40191
|
-
const modelCompat = QWEN_VARIANTS.filter((v) => !v.cloud).map((v) => {
|
|
40192
|
-
const budget = effectiveGB * 0.8;
|
|
40193
|
-
const fits = v.sizeGB <= budget;
|
|
40194
|
-
let note;
|
|
40195
|
-
if (fits) {
|
|
40196
|
-
const headroom = budget - v.sizeGB;
|
|
40197
|
-
note = headroom > v.sizeGB * 0.5 ? "comfortable" : headroom > v.sizeGB * 0.15 ? "good fit" : "tight fit";
|
|
40198
|
-
if (specs.gpuVramGB > 0 && v.sizeGB <= specs.gpuVramGB * 0.8) {
|
|
40199
|
-
note += " (GPU accelerated)";
|
|
40453
|
+
try {
|
|
40454
|
+
const { stdout: nvidiaSmi } = await execAsync("nvidia-smi --query-gpu=memory.total,name --format=csv,noheader,nounits 2>/dev/null", { timeout: 5e3 });
|
|
40455
|
+
const lines = nvidiaSmi.trim().split("\n");
|
|
40456
|
+
if (lines.length > 0) {
|
|
40457
|
+
for (const line of lines) {
|
|
40458
|
+
const parts = line.split(",").map((s) => s.trim());
|
|
40459
|
+
const vramMB = parseInt(parts[0] ?? "0", 10);
|
|
40460
|
+
if (!isNaN(vramMB))
|
|
40461
|
+
gpuVramGB += vramMB / 1024;
|
|
40462
|
+
if (!gpuName && parts[1])
|
|
40463
|
+
gpuName = parts[1];
|
|
40200
40464
|
}
|
|
40201
|
-
} else {
|
|
40202
|
-
const overshoot = v.sizeGB - budget;
|
|
40203
|
-
note = `need ${overshoot.toFixed(0)} GB more`;
|
|
40204
40465
|
}
|
|
40205
|
-
|
|
40206
|
-
}
|
|
40207
|
-
|
|
40208
|
-
|
|
40209
|
-
|
|
40210
|
-
|
|
40211
|
-
|
|
40212
|
-
|
|
40213
|
-
summary = "Moderate \u2014 small to medium models, reasonable performance";
|
|
40214
|
-
else if (overall >= 20)
|
|
40215
|
-
summary = "Basic \u2014 small models only, limited context";
|
|
40216
|
-
else
|
|
40217
|
-
summary = "Minimal \u2014 very small models or cloud inference recommended";
|
|
40218
|
-
return { overall, memory: memoryScore, compute: computeScore, speed: speedScore, summary, modelCompat };
|
|
40466
|
+
} catch {
|
|
40467
|
+
}
|
|
40468
|
+
return {
|
|
40469
|
+
totalRamGB: Math.round(totalRamGB * 10) / 10,
|
|
40470
|
+
availableRamGB: Math.round(availableRamGB * 10) / 10,
|
|
40471
|
+
gpuVramGB: Math.round(gpuVramGB * 10) / 10,
|
|
40472
|
+
gpuName
|
|
40473
|
+
};
|
|
40219
40474
|
}
|
|
40220
|
-
function
|
|
40221
|
-
const
|
|
40222
|
-
const
|
|
40223
|
-
const
|
|
40224
|
-
|
|
40475
|
+
function recommendModel(specs) {
|
|
40476
|
+
const effectiveGB = Math.max(specs.gpuVramGB, specs.availableRamGB);
|
|
40477
|
+
const budget = effectiveGB * 0.8;
|
|
40478
|
+
const localVariants = QWEN_VARIANTS.filter((v) => !v.cloud);
|
|
40479
|
+
for (let i = localVariants.length - 1; i >= 0; i--) {
|
|
40480
|
+
if (localVariants[i].sizeGB <= budget) {
|
|
40481
|
+
return localVariants[i];
|
|
40482
|
+
}
|
|
40483
|
+
}
|
|
40484
|
+
return QWEN_VARIANTS.find((v) => v.tag === "qwen3.5:cloud");
|
|
40225
40485
|
}
|
|
40226
|
-
function
|
|
40227
|
-
|
|
40228
|
-
|
|
40229
|
-
const
|
|
40230
|
-
|
|
40231
|
-
|
|
40232
|
-
|
|
40233
|
-
|
|
40234
|
-
|
|
40486
|
+
function calculateContextWindow(specs, modelSizeGB2, kvBytesPerToken, archMax) {
|
|
40487
|
+
const totalAvail = Math.max(specs.gpuVramGB, specs.totalRamGB);
|
|
40488
|
+
const remaining = Math.max(0, totalAvail - modelSizeGB2);
|
|
40489
|
+
const usableGB = remaining * 0.85;
|
|
40490
|
+
let numCtx;
|
|
40491
|
+
if (kvBytesPerToken && kvBytesPerToken > 0) {
|
|
40492
|
+
const maxTokens = Math.floor(usableGB * 1024 ** 3 / kvBytesPerToken);
|
|
40493
|
+
numCtx = Math.max(2048, Math.floor(maxTokens / 1024) * 1024);
|
|
40494
|
+
} else {
|
|
40495
|
+
const kvEstimate = modelSizeGB2 <= 5 ? 524288 : modelSizeGB2 <= 20 ? 1048576 : 1572864;
|
|
40496
|
+
const maxTokens = Math.floor(usableGB * 1024 ** 3 / kvEstimate);
|
|
40497
|
+
numCtx = Math.max(2048, Math.floor(maxTokens / 1024) * 1024);
|
|
40235
40498
|
}
|
|
40236
|
-
|
|
40237
|
-
|
|
40238
|
-
|
|
40239
|
-
|
|
40240
|
-
|
|
40241
|
-
|
|
40242
|
-
|
|
40243
|
-
|
|
40244
|
-
if (plat === "darwin") {
|
|
40245
|
-
if (hasCmd("brew")) {
|
|
40246
|
-
try {
|
|
40247
|
-
execSync27("brew install python3", { stdio: "inherit", timeout: 3e5 });
|
|
40248
|
-
if (hasCmd("python3")) {
|
|
40249
|
-
process.stdout.write(` ${c2.green("\u2714")} Python3 installed via Homebrew.
|
|
40250
|
-
`);
|
|
40251
|
-
return;
|
|
40252
|
-
}
|
|
40253
|
-
} catch {
|
|
40254
|
-
}
|
|
40255
|
-
}
|
|
40499
|
+
numCtx = Math.min(numCtx, 131072);
|
|
40500
|
+
if (archMax && archMax > 0)
|
|
40501
|
+
numCtx = Math.min(numCtx, archMax);
|
|
40502
|
+
if (kvBytesPerToken && kvBytesPerToken > 0 && modelSizeGB2 > 0) {
|
|
40503
|
+
const maxKVBytes = modelSizeGB2 * 4 * 1024 ** 3;
|
|
40504
|
+
const maxTokensFromBudget = Math.floor(maxKVBytes / kvBytesPerToken);
|
|
40505
|
+
const budgetCap = Math.max(2048, Math.floor(maxTokensFromBudget / 1024) * 1024);
|
|
40506
|
+
numCtx = Math.min(numCtx, budgetCap);
|
|
40256
40507
|
}
|
|
40257
|
-
|
|
40258
|
-
|
|
40259
|
-
|
|
40260
|
-
|
|
40261
|
-
|
|
40262
|
-
|
|
40263
|
-
|
|
40508
|
+
const label = numCtx >= 1024 ? `${Math.floor(numCtx / 1024)}K` : String(numCtx);
|
|
40509
|
+
return { numCtx, label };
|
|
40510
|
+
}
|
|
40511
|
+
function ask(rl, question) {
|
|
40512
|
+
return new Promise((resolve36) => {
|
|
40513
|
+
rl.question(question, (answer) => resolve36(answer.trim()));
|
|
40514
|
+
});
|
|
40515
|
+
}
|
|
40516
|
+
function askSecret(rl, question) {
|
|
40517
|
+
return new Promise((resolve36) => {
|
|
40518
|
+
process.stdout.write(question);
|
|
40519
|
+
let secret = "";
|
|
40520
|
+
const stdin = process.stdin;
|
|
40521
|
+
const hadRawMode = stdin.isRaw;
|
|
40522
|
+
if (typeof stdin.setRawMode === "function") {
|
|
40523
|
+
stdin.setRawMode(true);
|
|
40524
|
+
}
|
|
40525
|
+
stdin.resume();
|
|
40526
|
+
const onData = (chunk) => {
|
|
40527
|
+
const ch = chunk.toString("utf8");
|
|
40528
|
+
for (const c3 of ch) {
|
|
40529
|
+
if (c3 === "\r" || c3 === "\n") {
|
|
40530
|
+
stdin.removeListener("data", onData);
|
|
40531
|
+
if (typeof stdin.setRawMode === "function") {
|
|
40532
|
+
stdin.setRawMode(hadRawMode ?? false);
|
|
40533
|
+
}
|
|
40534
|
+
process.stdout.write("\n");
|
|
40535
|
+
resolve36(secret.trim());
|
|
40264
40536
|
return;
|
|
40537
|
+
} else if (c3 === "") {
|
|
40538
|
+
stdin.removeListener("data", onData);
|
|
40539
|
+
if (typeof stdin.setRawMode === "function") {
|
|
40540
|
+
stdin.setRawMode(hadRawMode ?? false);
|
|
40541
|
+
}
|
|
40542
|
+
process.stdout.write("\n");
|
|
40543
|
+
resolve36("");
|
|
40544
|
+
return;
|
|
40545
|
+
} else if (c3 === "\x7F" || c3 === "\b") {
|
|
40546
|
+
if (secret.length > 0) {
|
|
40547
|
+
secret = secret.slice(0, -1);
|
|
40548
|
+
process.stdout.write("\b \b");
|
|
40549
|
+
}
|
|
40550
|
+
} else if (c3.charCodeAt(0) >= 32) {
|
|
40551
|
+
secret += c3;
|
|
40552
|
+
process.stdout.write("*");
|
|
40265
40553
|
}
|
|
40266
|
-
} catch {
|
|
40267
40554
|
}
|
|
40268
|
-
}
|
|
40269
|
-
|
|
40270
|
-
|
|
40271
|
-
|
|
40272
|
-
`);
|
|
40555
|
+
};
|
|
40556
|
+
stdin.on("data", onData);
|
|
40557
|
+
});
|
|
40273
40558
|
}
|
|
40274
|
-
function
|
|
40275
|
-
|
|
40276
|
-
execSync27("python3 -m venv --help", { stdio: "pipe", timeout: 5e3 });
|
|
40559
|
+
function ensureCurl() {
|
|
40560
|
+
if (hasCmd("curl"))
|
|
40277
40561
|
return true;
|
|
40278
|
-
|
|
40279
|
-
|
|
40280
|
-
|
|
40281
|
-
|
|
40282
|
-
|
|
40283
|
-
return false;
|
|
40284
|
-
}
|
|
40562
|
+
const plat = platform2();
|
|
40563
|
+
if (plat === "win32") {
|
|
40564
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} curl not found on Windows \u2014 install it manually.
|
|
40565
|
+
`);
|
|
40566
|
+
return false;
|
|
40285
40567
|
}
|
|
40286
|
-
}
|
|
40287
|
-
function ensurePythonVenv() {
|
|
40288
|
-
process.stdout.write(` ${c2.cyan("\u25CF")} Installing python3-venv...
|
|
40568
|
+
process.stdout.write(` ${c2.cyan("\u25CF")} curl not found. Installing...
|
|
40289
40569
|
`);
|
|
40290
40570
|
const strategies = [
|
|
40291
|
-
{ check: "apt-get", install: "sudo apt-get update -qq && sudo apt-get install -y -qq
|
|
40292
|
-
{ check: "dnf", install: "sudo dnf install -y -q
|
|
40293
|
-
{ check: "
|
|
40294
|
-
{ check: "
|
|
40571
|
+
{ check: "apt-get", install: "sudo apt-get update -qq && sudo apt-get install -y -qq curl", label: "apt" },
|
|
40572
|
+
{ check: "dnf", install: "sudo dnf install -y -q curl", label: "dnf" },
|
|
40573
|
+
{ check: "yum", install: "sudo yum install -y -q curl", label: "yum" },
|
|
40574
|
+
{ check: "pacman", install: "sudo pacman -S --noconfirm curl", label: "pacman" },
|
|
40575
|
+
{ check: "apk", install: "sudo apk add --quiet curl", label: "apk" },
|
|
40576
|
+
{ check: "zypper", install: "sudo zypper install -y curl", label: "zypper" }
|
|
40295
40577
|
];
|
|
40296
40578
|
for (const s of strategies) {
|
|
40297
40579
|
if (hasCmd(s.check)) {
|
|
40298
40580
|
try {
|
|
40299
40581
|
execSync27(s.install, { stdio: "inherit", timeout: 12e4 });
|
|
40300
|
-
if (
|
|
40301
|
-
process.stdout.write(` ${c2.green("\u2714")}
|
|
40582
|
+
if (hasCmd("curl")) {
|
|
40583
|
+
process.stdout.write(` ${c2.green("\u2714")} curl installed via ${s.label}.
|
|
40302
40584
|
`);
|
|
40303
|
-
return;
|
|
40585
|
+
return true;
|
|
40304
40586
|
}
|
|
40305
40587
|
} catch {
|
|
40306
|
-
|
|
40307
|
-
}
|
|
40308
|
-
}
|
|
40309
|
-
process.stdout.write(` ${c2.yellow("\u26A0")} Could not install python3-venv. Install manually: sudo apt install python3-venv
|
|
40310
|
-
|
|
40311
|
-
`);
|
|
40312
|
-
}
|
|
40313
|
-
async function runSetupWizard(config) {
|
|
40314
|
-
const rl = readline.createInterface({
|
|
40315
|
-
input: process.stdin,
|
|
40316
|
-
output: process.stdout,
|
|
40317
|
-
terminal: true
|
|
40318
|
-
});
|
|
40319
|
-
try {
|
|
40320
|
-
return await doSetup(config, rl);
|
|
40321
|
-
} finally {
|
|
40322
|
-
rl.close();
|
|
40323
|
-
}
|
|
40324
|
-
}
|
|
40325
|
-
async function promptForCustomEndpoint(config, rl) {
|
|
40326
|
-
process.stdout.write(`
|
|
40327
|
-
${c2.cyan("\u25CF")} Enter an OpenAI-compatible inference endpoint.
|
|
40328
|
-
`);
|
|
40329
|
-
process.stdout.write(` ${c2.dim("Examples:")}
|
|
40330
|
-
`);
|
|
40331
|
-
process.stdout.write(` ${c2.dim(" https://chutes.ai/v1")}
|
|
40332
|
-
`);
|
|
40333
|
-
process.stdout.write(` ${c2.dim(" http://10.0.0.5:11434")}
|
|
40334
|
-
`);
|
|
40335
|
-
process.stdout.write(` ${c2.dim(" https://api.together.xyz/v1")}
|
|
40336
|
-
|
|
40337
|
-
`);
|
|
40338
|
-
const endpoint = await ask(rl, ` ${c2.bold("Endpoint URL:")} `);
|
|
40339
|
-
if (!endpoint) {
|
|
40340
|
-
process.stdout.write(` ${c2.dim("No endpoint entered.")}
|
|
40341
|
-
`);
|
|
40342
|
-
const startAnyway = await ask(rl, `
|
|
40343
|
-
${c2.bold("Start anyway without a backend?")} (y/n) `);
|
|
40344
|
-
if (startAnyway.toLowerCase() === "y" || startAnyway.toLowerCase() === "yes") {
|
|
40345
|
-
return config.model;
|
|
40346
|
-
}
|
|
40347
|
-
return config.model;
|
|
40348
|
-
}
|
|
40349
|
-
const cleanUrl = normalizeBaseUrl(endpoint);
|
|
40350
|
-
const needsKey = await ask(rl, `
|
|
40351
|
-
${c2.bold("Does this endpoint require an API key?")} (y/n) `);
|
|
40352
|
-
let apiKey = "";
|
|
40353
|
-
if (needsKey.toLowerCase() === "y" || needsKey.toLowerCase() === "yes") {
|
|
40354
|
-
apiKey = await askSecret(rl, ` ${c2.bold("API key:")} `);
|
|
40355
|
-
}
|
|
40356
|
-
process.stdout.write(`
|
|
40357
|
-
${c2.cyan("\u25CF")} Enter the model name for this endpoint.
|
|
40358
|
-
`);
|
|
40359
|
-
process.stdout.write(` ${c2.dim("Examples: qwen3.5:122b, meta-llama/Llama-3.3-70B, etc.")}
|
|
40360
|
-
|
|
40361
|
-
`);
|
|
40362
|
-
const modelName = await ask(rl, ` ${c2.bold("Model name")} (Enter for ${c2.dim(config.model)}): `);
|
|
40363
|
-
const chosenModel = modelName || config.model;
|
|
40364
|
-
const provider = detectProvider(endpoint);
|
|
40365
|
-
process.stdout.write(`
|
|
40366
|
-
${c2.cyan("\u25CF")} Detected provider: ${c2.bold(provider.label)}
|
|
40367
|
-
`);
|
|
40368
|
-
process.stdout.write(` ${c2.cyan("\u25CF")} Testing endpoint ${c2.bold(cleanUrl)}...
|
|
40369
|
-
`);
|
|
40370
|
-
let testOk = false;
|
|
40371
|
-
try {
|
|
40372
|
-
const testUrl = `${cleanUrl}${provider.modelsPath}`;
|
|
40373
|
-
const headers = { "Content-Type": "application/json" };
|
|
40374
|
-
if (apiKey) {
|
|
40375
|
-
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
40376
|
-
}
|
|
40377
|
-
const resp = await fetch(testUrl, { headers, signal: AbortSignal.timeout(1e4) });
|
|
40378
|
-
if (resp.ok) {
|
|
40379
|
-
process.stdout.write(` ${c2.green("\u2714")} Endpoint reachable.
|
|
40380
|
-
`);
|
|
40381
|
-
testOk = true;
|
|
40382
|
-
} else {
|
|
40383
|
-
if (provider.id !== "ollama") {
|
|
40384
|
-
try {
|
|
40385
|
-
const ollamaResp = await fetch(`${cleanUrl}/api/tags`, { signal: AbortSignal.timeout(1e4) });
|
|
40386
|
-
if (ollamaResp.ok) {
|
|
40387
|
-
process.stdout.write(` ${c2.green("\u2714")} Ollama endpoint detected.
|
|
40388
|
-
`);
|
|
40389
|
-
testOk = true;
|
|
40390
|
-
}
|
|
40391
|
-
} catch {
|
|
40392
|
-
}
|
|
40393
|
-
}
|
|
40394
|
-
if (!testOk) {
|
|
40395
|
-
process.stdout.write(` ${c2.yellow("\u26A0")} Endpoint returned HTTP ${resp.status}
|
|
40588
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} ${s.label} install failed, trying next...
|
|
40396
40589
|
`);
|
|
40397
40590
|
}
|
|
40398
40591
|
}
|
|
40399
|
-
} catch (err) {
|
|
40400
|
-
process.stdout.write(` ${c2.yellow("\u26A0")} Could not reach endpoint: ${err instanceof Error ? err.message : String(err)}
|
|
40401
|
-
`);
|
|
40402
40592
|
}
|
|
40403
|
-
if (
|
|
40404
|
-
|
|
40405
|
-
|
|
40406
|
-
|
|
40593
|
+
if (plat === "darwin") {
|
|
40594
|
+
try {
|
|
40595
|
+
execSync27("xcode-select --install", { stdio: "inherit", timeout: 3e5 });
|
|
40596
|
+
if (hasCmd("curl"))
|
|
40597
|
+
return true;
|
|
40598
|
+
} catch {
|
|
40407
40599
|
}
|
|
40408
|
-
|
|
40409
|
-
${c2.
|
|
40410
|
-
if (startAnyway.toLowerCase() !== "y" && startAnyway.toLowerCase() !== "yes") {
|
|
40411
|
-
process.stdout.write(` ${c2.dim("You can configure the endpoint later with /endpoint")}
|
|
40412
|
-
|
|
40600
|
+
}
|
|
40601
|
+
process.stdout.write(` ${c2.red("\u2716")} Could not install curl. Install it manually and retry.
|
|
40413
40602
|
`);
|
|
40414
|
-
|
|
40415
|
-
|
|
40603
|
+
return false;
|
|
40604
|
+
}
|
|
40605
|
+
async function autoInstallOllama(rl) {
|
|
40606
|
+
const plat = platform2();
|
|
40607
|
+
if (plat !== "win32" && !ensureCurl()) {
|
|
40608
|
+
return false;
|
|
40416
40609
|
}
|
|
40417
|
-
|
|
40418
|
-
|
|
40419
|
-
if (
|
|
40420
|
-
|
|
40610
|
+
if (plat === "linux") {
|
|
40611
|
+
return installOllamaLinux();
|
|
40612
|
+
} else if (plat === "darwin") {
|
|
40613
|
+
return await installOllamaMac(rl);
|
|
40614
|
+
} else if (plat === "win32") {
|
|
40615
|
+
return installOllamaWindows();
|
|
40421
40616
|
}
|
|
40422
|
-
|
|
40423
|
-
setConfigValue("backendType", backendType);
|
|
40424
|
-
process.stdout.write(`
|
|
40425
|
-
${c2.green("\u2714")} Configured: ${c2.bold(chosenModel)} at ${c2.bold(cleanUrl)}
|
|
40426
|
-
`);
|
|
40427
|
-
process.stdout.write(` ${c2.green("\u2714")} Provider: ${c2.bold(provider.label)}
|
|
40428
|
-
`);
|
|
40429
|
-
if (apiKey)
|
|
40430
|
-
process.stdout.write(` ${c2.green("\u2714")} API key saved.
|
|
40617
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Unsupported platform: ${plat}
|
|
40431
40618
|
`);
|
|
40432
|
-
process.stdout.write(` ${c2.
|
|
40619
|
+
process.stdout.write(` ${c2.dim("Visit https://ollama.com to install manually.")}
|
|
40433
40620
|
|
|
40434
40621
|
`);
|
|
40435
|
-
return
|
|
40622
|
+
return false;
|
|
40436
40623
|
}
|
|
40437
|
-
|
|
40438
|
-
process.stdout.write(`
|
|
40439
|
-
${c2.bold(c2.cyan("open-agents"))}
|
|
40440
|
-
`);
|
|
40441
|
-
process.stdout.write(` ${c2.dim("\u2500".repeat(60))}
|
|
40442
|
-
`);
|
|
40443
|
-
process.stdout.write(` ${c2.bold("First-run setup")}
|
|
40444
|
-
|
|
40445
|
-
`);
|
|
40446
|
-
process.stdout.write(` ${c2.cyan("\u25CF")} Detecting system specs...
|
|
40447
|
-
`);
|
|
40448
|
-
const specs = detectSystemSpecs();
|
|
40449
|
-
process.stdout.write(` ${c2.dim(" RAM:")} ${specs.totalRamGB.toFixed(1)} GB total, ${specs.availableRamGB.toFixed(1)} GB available
|
|
40450
|
-
`);
|
|
40451
|
-
if (specs.gpuVramGB > 0) {
|
|
40452
|
-
process.stdout.write(` ${c2.dim(" GPU:")} ${specs.gpuName || "NVIDIA"} \u2014 ${specs.gpuVramGB.toFixed(1)} GB VRAM
|
|
40453
|
-
`);
|
|
40454
|
-
} else {
|
|
40455
|
-
process.stdout.write(` ${c2.dim(" GPU:")} No NVIDIA GPU detected (CPU inference)
|
|
40456
|
-
`);
|
|
40457
|
-
}
|
|
40458
|
-
const score = computeInferenceScore(specs);
|
|
40459
|
-
const w = 24;
|
|
40460
|
-
const bw = 30;
|
|
40624
|
+
function installOllamaLinux() {
|
|
40461
40625
|
process.stdout.write(`
|
|
40462
|
-
${c2.
|
|
40463
|
-
`);
|
|
40464
|
-
process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold(" Inference Capability")} ${c2.dim("\u2502")}
|
|
40465
|
-
`);
|
|
40466
|
-
process.stdout.write(` ${c2.dim("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524")}
|
|
40467
|
-
`);
|
|
40468
|
-
process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold("Overall")} ${c2.dim("\u2502")}
|
|
40469
|
-
`);
|
|
40470
|
-
process.stdout.write(` ${c2.dim("\u2502")} ${renderScoreBar(score.overall, bw)} ${c2.bold(String(score.overall).padStart(3))}${c2.dim("/100")} ${c2.dim("\u2502")}
|
|
40471
|
-
`);
|
|
40472
|
-
process.stdout.write(` ${c2.dim("\u2502")} ${c2.dim(score.summary.padEnd(46))} ${c2.dim("\u2502")}
|
|
40473
|
-
`);
|
|
40474
|
-
process.stdout.write(` ${c2.dim("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524")}
|
|
40475
|
-
`);
|
|
40476
|
-
process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold("Memory")} ${c2.dim(`${specs.totalRamGB.toFixed(0)} GB RAM` + (specs.gpuVramGB > 0 ? ` + ${specs.gpuVramGB.toFixed(0)} GB VRAM` : ""))}${" ".repeat(Math.max(0, 27 - (`${specs.totalRamGB.toFixed(0)} GB RAM` + (specs.gpuVramGB > 0 ? ` + ${specs.gpuVramGB.toFixed(0)} GB VRAM` : "")).length))} ${c2.dim("\u2502")}
|
|
40477
|
-
`);
|
|
40478
|
-
process.stdout.write(` ${c2.dim("\u2502")} ${renderScoreBar(score.memory, w)} ${String(score.memory).padStart(3)} ${c2.dim("\u2502")}
|
|
40479
|
-
`);
|
|
40480
|
-
process.stdout.write(` ${c2.dim("\u2502")} ${c2.dim("\u2502")}
|
|
40481
|
-
`);
|
|
40482
|
-
process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold("Compute")} ${c2.dim(specs.gpuVramGB > 0 ? specs.gpuName || "NVIDIA GPU" : "CPU only")}${" ".repeat(Math.max(0, 28 - (specs.gpuVramGB > 0 ? specs.gpuName || "NVIDIA GPU" : "CPU only").length))} ${c2.dim("\u2502")}
|
|
40483
|
-
`);
|
|
40484
|
-
process.stdout.write(` ${c2.dim("\u2502")} ${renderScoreBar(score.compute, w)} ${String(score.compute).padStart(3)} ${c2.dim("\u2502")}
|
|
40485
|
-
`);
|
|
40486
|
-
process.stdout.write(` ${c2.dim("\u2502")} ${c2.dim("\u2502")}
|
|
40487
|
-
`);
|
|
40488
|
-
process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold("Speed")} ${c2.dim(specs.gpuVramGB > 0 ? "GPU accelerated" : "CPU inference")}${" ".repeat(Math.max(0, 28 - (specs.gpuVramGB > 0 ? "GPU accelerated" : "CPU inference").length))} ${c2.dim("\u2502")}
|
|
40489
|
-
`);
|
|
40490
|
-
process.stdout.write(` ${c2.dim("\u2502")} ${renderScoreBar(score.speed, w)} ${String(score.speed).padStart(3)} ${c2.dim("\u2502")}
|
|
40491
|
-
`);
|
|
40492
|
-
process.stdout.write(` ${c2.dim("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524")}
|
|
40493
|
-
`);
|
|
40494
|
-
process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold(" Model Compatibility")} ${c2.dim("\u2502")}
|
|
40495
|
-
`);
|
|
40496
|
-
for (const compat of score.modelCompat) {
|
|
40497
|
-
const icon = compat.fits ? c2.green("\u2714") : c2.red("\u2716");
|
|
40498
|
-
const tag = compat.fits ? compat.tag : c2.dim(compat.tag);
|
|
40499
|
-
const note = c2.dim(compat.note);
|
|
40500
|
-
const visTag = compat.tag;
|
|
40501
|
-
const visNote = compat.note;
|
|
40502
|
-
const pad = Math.max(0, 44 - 2 - visTag.length - 1 - visNote.length);
|
|
40503
|
-
process.stdout.write(` ${c2.dim("\u2502")} ${icon} ${tag} ${" ".repeat(pad)}${note} ${c2.dim("\u2502")}
|
|
40504
|
-
`);
|
|
40505
|
-
}
|
|
40506
|
-
process.stdout.write(` ${c2.dim("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518")}
|
|
40626
|
+
${c2.cyan("\u25CF")} Installing Ollama via official install script...
|
|
40507
40627
|
|
|
40508
40628
|
`);
|
|
40509
|
-
|
|
40510
|
-
|
|
40511
|
-
|
|
40629
|
+
try {
|
|
40630
|
+
execSync27("curl -fsSL https://ollama.com/install.sh | sh", {
|
|
40631
|
+
stdio: "inherit",
|
|
40632
|
+
timeout: 3e5
|
|
40633
|
+
});
|
|
40634
|
+
if (hasCmd("ollama")) {
|
|
40635
|
+
process.stdout.write(`
|
|
40636
|
+
${c2.green("\u2714")} Ollama installed successfully.
|
|
40512
40637
|
`);
|
|
40513
|
-
|
|
40514
|
-
if (installPy.toLowerCase() !== "n") {
|
|
40515
|
-
ensurePython3();
|
|
40516
|
-
hasPython = hasCmd("python3") || hasCmd("python");
|
|
40638
|
+
return true;
|
|
40517
40639
|
}
|
|
40518
|
-
|
|
40519
|
-
|
|
40520
|
-
const hasVenv = checkPythonVenv();
|
|
40521
|
-
if (!hasVenv) {
|
|
40522
|
-
process.stdout.write(` ${c2.yellow("\u26A0")} Python3 venv module not found (needed for Moondream, OCR, browser automation).
|
|
40640
|
+
process.stdout.write(`
|
|
40641
|
+
${c2.yellow("\u26A0")} Install script ran but ollama not found on PATH.
|
|
40523
40642
|
`);
|
|
40524
|
-
|
|
40525
|
-
|
|
40526
|
-
|
|
40527
|
-
|
|
40528
|
-
|
|
40643
|
+
return false;
|
|
40644
|
+
} catch (err) {
|
|
40645
|
+
process.stdout.write(`
|
|
40646
|
+
${c2.red("\u2716")} Ollama install failed: ${err instanceof Error ? err.message : String(err)}
|
|
40647
|
+
`);
|
|
40648
|
+
return false;
|
|
40529
40649
|
}
|
|
40530
|
-
|
|
40531
|
-
|
|
40532
|
-
|
|
40533
|
-
|
|
40534
|
-
}
|
|
40535
|
-
process.stdout.write(` ${c2.yellow("\u26A0")} Cannot reach Ollama at ${c2.bold(config.backendUrl)}
|
|
40650
|
+
}
|
|
40651
|
+
async function installOllamaMac(_rl) {
|
|
40652
|
+
if (!hasCmd("brew")) {
|
|
40653
|
+
process.stdout.write(`
|
|
40654
|
+
${c2.cyan("\u25CF")} Homebrew not found. Installing automatically (needed for Ollama on macOS)...
|
|
40536
40655
|
|
|
40537
40656
|
`);
|
|
40538
|
-
|
|
40539
|
-
|
|
40540
|
-
|
|
40541
|
-
if (endpointResult) {
|
|
40542
|
-
return endpointResult;
|
|
40543
|
-
}
|
|
40544
|
-
usingCustomEndpoint = true;
|
|
40545
|
-
} else {
|
|
40546
|
-
const ollamaInstalled = hasCmd("ollama");
|
|
40547
|
-
if (ollamaInstalled) {
|
|
40548
|
-
process.stdout.write(`
|
|
40549
|
-
${c2.cyan("\u25CF")} Ollama is installed but not running. Starting automatically...
|
|
40550
|
-
`);
|
|
40657
|
+
try {
|
|
40658
|
+
execSync27('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"', { stdio: "inherit", timeout: 6e5 });
|
|
40659
|
+
if (!hasCmd("brew")) {
|
|
40551
40660
|
try {
|
|
40552
|
-
const
|
|
40553
|
-
|
|
40554
|
-
await new Promise((resolve36) => setTimeout(resolve36, 3e3));
|
|
40555
|
-
try {
|
|
40556
|
-
models = await fetchOllamaModels(config.backendUrl);
|
|
40557
|
-
process.stdout.write(` ${c2.green("\u2714")} Ollama is running.
|
|
40558
|
-
|
|
40559
|
-
`);
|
|
40560
|
-
} catch {
|
|
40561
|
-
process.stdout.write(` ${c2.yellow("\u26A0")} Ollama started but not responding yet. It may need a moment.
|
|
40562
|
-
|
|
40563
|
-
`);
|
|
40564
|
-
}
|
|
40661
|
+
const brewPrefix = existsSync37("/opt/homebrew/bin/brew") ? "/opt/homebrew" : "/usr/local";
|
|
40662
|
+
process.env["PATH"] = `${brewPrefix}/bin:${process.env["PATH"]}`;
|
|
40565
40663
|
} catch {
|
|
40566
|
-
process.stdout.write(` ${c2.yellow("\u26A0")} Could not start Ollama. Try running ${c2.bold("ollama serve")} manually.
|
|
40567
|
-
|
|
40568
|
-
`);
|
|
40569
40664
|
}
|
|
40570
|
-
}
|
|
40571
|
-
|
|
40572
|
-
|
|
40573
|
-
|
|
40574
|
-
const installed = await autoInstallOllama(rl);
|
|
40575
|
-
if (installed) {
|
|
40576
|
-
process.stdout.write(`
|
|
40577
|
-
${c2.cyan("\u25CF")} Starting ollama serve...
|
|
40578
|
-
`);
|
|
40579
|
-
try {
|
|
40580
|
-
const child = spawn19("ollama", ["serve"], { stdio: "ignore", detached: true });
|
|
40581
|
-
child.unref();
|
|
40582
|
-
await new Promise((resolve36) => setTimeout(resolve36, 3e3));
|
|
40583
|
-
try {
|
|
40584
|
-
models = await fetchOllamaModels(config.backendUrl);
|
|
40585
|
-
process.stdout.write(` ${c2.green("\u2714")} Ollama is running.
|
|
40586
|
-
|
|
40587
|
-
`);
|
|
40588
|
-
} catch {
|
|
40589
|
-
process.stdout.write(` ${c2.yellow("\u26A0")} Ollama installed but not responding yet. Try ${c2.bold("ollama serve")} manually.
|
|
40590
|
-
|
|
40591
|
-
`);
|
|
40592
|
-
}
|
|
40593
|
-
} catch {
|
|
40594
|
-
process.stdout.write(` ${c2.yellow("\u26A0")} Ollama installed. Start it with: ${c2.bold("ollama serve")}
|
|
40595
|
-
|
|
40665
|
+
}
|
|
40666
|
+
if (hasCmd("brew")) {
|
|
40667
|
+
process.stdout.write(`
|
|
40668
|
+
${c2.green("\u2714")} Homebrew installed.
|
|
40596
40669
|
`);
|
|
40597
|
-
|
|
40598
|
-
|
|
40599
|
-
|
|
40600
|
-
process.stdout.write(`
|
|
40601
|
-
${c2.dim("You'll need to configure an inference endpoint.")}
|
|
40670
|
+
} else {
|
|
40671
|
+
process.stdout.write(`
|
|
40672
|
+
${c2.yellow("\u26A0")} Homebrew install completed but brew not found on PATH.
|
|
40602
40673
|
`);
|
|
40603
|
-
|
|
40674
|
+
process.stdout.write(` ${c2.dim('Try: eval "$(/opt/homebrew/bin/brew shellenv)"')}
|
|
40604
40675
|
|
|
40605
40676
|
`);
|
|
40606
|
-
|
|
40607
|
-
}
|
|
40677
|
+
return false;
|
|
40608
40678
|
}
|
|
40679
|
+
} catch (err) {
|
|
40680
|
+
process.stdout.write(`
|
|
40681
|
+
${c2.red("\u2716")} Homebrew install failed: ${err instanceof Error ? err.message : String(err)}
|
|
40682
|
+
`);
|
|
40683
|
+
return false;
|
|
40609
40684
|
}
|
|
40610
40685
|
}
|
|
40611
|
-
|
|
40612
|
-
|
|
40613
|
-
}
|
|
40614
|
-
const currentModel = findModel(models, config.model);
|
|
40615
|
-
if (currentModel) {
|
|
40616
|
-
process.stdout.write(` ${c2.green("\u2714")} Model ${c2.bold(currentModel.name)} is available.
|
|
40686
|
+
process.stdout.write(`
|
|
40687
|
+
${c2.cyan("\u25CF")} Installing Ollama via Homebrew...
|
|
40617
40688
|
|
|
40618
40689
|
`);
|
|
40619
|
-
|
|
40620
|
-
|
|
40621
|
-
|
|
40622
|
-
|
|
40690
|
+
try {
|
|
40691
|
+
execSync27("brew install ollama", {
|
|
40692
|
+
stdio: "inherit",
|
|
40693
|
+
timeout: 3e5
|
|
40694
|
+
});
|
|
40695
|
+
if (hasCmd("ollama")) {
|
|
40696
|
+
process.stdout.write(`
|
|
40697
|
+
${c2.green("\u2714")} Ollama installed successfully.
|
|
40623
40698
|
`);
|
|
40624
|
-
|
|
40625
|
-
|
|
40626
|
-
|
|
40627
|
-
|
|
40628
|
-
|
|
40629
|
-
|
|
40630
|
-
|
|
40699
|
+
return true;
|
|
40700
|
+
}
|
|
40701
|
+
process.stdout.write(`
|
|
40702
|
+
${c2.yellow("\u26A0")} brew install completed but ollama not found on PATH.
|
|
40703
|
+
`);
|
|
40704
|
+
return false;
|
|
40705
|
+
} catch (err) {
|
|
40706
|
+
process.stdout.write(`
|
|
40707
|
+
${c2.red("\u2716")} Ollama install failed: ${err instanceof Error ? err.message : String(err)}
|
|
40708
|
+
`);
|
|
40709
|
+
return false;
|
|
40710
|
+
}
|
|
40711
|
+
}
|
|
40712
|
+
function installOllamaWindows() {
|
|
40713
|
+
process.stdout.write(`
|
|
40714
|
+
${c2.cyan("\u25CF")} Installing Ollama via PowerShell...
|
|
40631
40715
|
|
|
40632
40716
|
`);
|
|
40633
|
-
|
|
40634
|
-
|
|
40635
|
-
|
|
40636
|
-
|
|
40717
|
+
try {
|
|
40718
|
+
execSync27('powershell -Command "irm https://ollama.com/install.ps1 | iex"', {
|
|
40719
|
+
stdio: "inherit",
|
|
40720
|
+
timeout: 3e5
|
|
40721
|
+
});
|
|
40722
|
+
if (hasCmd("ollama")) {
|
|
40723
|
+
process.stdout.write(`
|
|
40724
|
+
${c2.green("\u2714")} Ollama installed successfully.
|
|
40637
40725
|
`);
|
|
40726
|
+
return true;
|
|
40638
40727
|
}
|
|
40639
40728
|
process.stdout.write(`
|
|
40640
|
-
|
|
40729
|
+
${c2.yellow("\u26A0")} Install script ran but ollama not found on PATH.
|
|
40641
40730
|
`);
|
|
40731
|
+
return false;
|
|
40732
|
+
} catch (err) {
|
|
40642
40733
|
process.stdout.write(`
|
|
40643
|
-
|
|
40734
|
+
${c2.red("\u2716")} Ollama install failed: ${err instanceof Error ? err.message : String(err)}
|
|
40644
40735
|
`);
|
|
40645
|
-
|
|
40646
|
-
|
|
40647
|
-
|
|
40648
|
-
|
|
40649
|
-
|
|
40650
|
-
|
|
40651
|
-
|
|
40736
|
+
return false;
|
|
40737
|
+
}
|
|
40738
|
+
}
|
|
40739
|
+
async function ensureOllamaRunning(backendUrl, rl) {
|
|
40740
|
+
const ollamaInstalled = hasCmd("ollama");
|
|
40741
|
+
if (!ollamaInstalled) {
|
|
40742
|
+
if (rl) {
|
|
40652
40743
|
process.stdout.write(`
|
|
40653
|
-
${c2.
|
|
40654
|
-
|
|
40744
|
+
${c2.yellow("\u26A0")} Ollama is not installed on this system.
|
|
40655
40745
|
`);
|
|
40656
|
-
|
|
40657
|
-
|
|
40658
|
-
|
|
40659
|
-
|
|
40660
|
-
|
|
40746
|
+
const answer = await ask(rl, ` ${c2.bold("Install Ollama now?")} (Y/n) `);
|
|
40747
|
+
if (answer.toLowerCase() === "n") {
|
|
40748
|
+
return false;
|
|
40749
|
+
}
|
|
40750
|
+
} else {
|
|
40751
|
+
process.stdout.write(`
|
|
40752
|
+
${c2.cyan("\u25CF")} Ollama not found. Installing automatically...
|
|
40661
40753
|
`);
|
|
40662
|
-
|
|
40663
|
-
|
|
40664
|
-
|
|
40754
|
+
}
|
|
40755
|
+
const installRl = rl ?? readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
40756
|
+
const installed = await autoInstallOllama(installRl);
|
|
40757
|
+
if (!rl)
|
|
40758
|
+
installRl.close();
|
|
40759
|
+
if (!installed) {
|
|
40760
|
+
process.stdout.write(` ${c2.red("\u2716")} Ollama installation failed.
|
|
40665
40761
|
|
|
40666
40762
|
`);
|
|
40667
|
-
|
|
40668
|
-
|
|
40669
|
-
const v = localVariants[i];
|
|
40670
|
-
const fits = v.sizeGB <= Math.max(specs.gpuVramGB, specs.availableRamGB) * 0.8;
|
|
40671
|
-
const isRec = v.tag === recommended.tag;
|
|
40672
|
-
const marker = isRec ? c2.green("\u2192") : fits ? c2.dim(" ") : c2.red("\u2716");
|
|
40673
|
-
const name = isRec ? c2.bold(c2.green(v.tag)) : fits ? v.tag : c2.dim(v.tag);
|
|
40674
|
-
const label = isRec ? c2.bold(v.label) : c2.dim(v.label);
|
|
40675
|
-
const tooLarge = !fits && !v.cloud ? c2.red(" (exceeds available memory)") : "";
|
|
40676
|
-
process.stdout.write(` ${marker} ${String(i + 1).padStart(2)}. ${name.padEnd(isRec ? 45 : 25)} ${label}${tooLarge}
|
|
40677
|
-
`);
|
|
40763
|
+
return false;
|
|
40764
|
+
}
|
|
40678
40765
|
}
|
|
40679
|
-
process.stdout.write(`
|
|
40680
|
-
${c2.dim(" ")} ${String(localVariants.length + 1).padStart(2)}. ${c2.dim("qwen3.5:cloud")} ${c2.dim("Ollama Cloud")}
|
|
40766
|
+
process.stdout.write(` ${c2.cyan("\u25CF")} Starting ollama serve...
|
|
40681
40767
|
`);
|
|
40682
|
-
|
|
40768
|
+
try {
|
|
40769
|
+
const child = spawn19("ollama", ["serve"], { stdio: "ignore", detached: true });
|
|
40770
|
+
child.unref();
|
|
40771
|
+
} catch {
|
|
40772
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Could not start ollama serve.
|
|
40773
|
+
|
|
40683
40774
|
`);
|
|
40684
|
-
|
|
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;
|
|
40687
|
-
let selectedVariant;
|
|
40688
|
-
if (pullIdx === 0 || isNaN(pullIdx)) {
|
|
40689
|
-
selectedVariant = recommended;
|
|
40690
|
-
} else if (pullIdx <= localVariants.length) {
|
|
40691
|
-
selectedVariant = localVariants[pullIdx - 1];
|
|
40692
|
-
} else if (pullIdx === localVariants.length + 1) {
|
|
40693
|
-
selectedVariant = QWEN_VARIANTS.find((v) => v.tag === "qwen3.5:cloud");
|
|
40694
|
-
} else {
|
|
40695
|
-
selectedVariant = QWEN_VARIANTS.find((v) => v.tag === "qwen3.5:397b-cloud");
|
|
40775
|
+
return false;
|
|
40696
40776
|
}
|
|
40697
|
-
|
|
40698
|
-
|
|
40699
|
-
|
|
40700
|
-
|
|
40701
|
-
|
|
40777
|
+
for (let i = 0; i < 5; i++) {
|
|
40778
|
+
await new Promise((resolve36) => setTimeout(resolve36, 2e3));
|
|
40779
|
+
try {
|
|
40780
|
+
const resp = await fetch(`${backendUrl}/api/tags`, {
|
|
40781
|
+
signal: AbortSignal.timeout(3e3)
|
|
40782
|
+
});
|
|
40783
|
+
if (resp.ok) {
|
|
40784
|
+
process.stdout.write(` ${c2.green("\u2714")} Ollama is running.
|
|
40702
40785
|
|
|
40703
40786
|
`);
|
|
40704
|
-
|
|
40787
|
+
return true;
|
|
40788
|
+
}
|
|
40789
|
+
} catch {
|
|
40790
|
+
}
|
|
40705
40791
|
}
|
|
40706
|
-
process.stdout.write(`
|
|
40707
|
-
${c2.cyan("\u25CF")} Pulling ${c2.bold(selectedVariant.tag)}... (this may take a while)
|
|
40708
|
-
`);
|
|
40709
|
-
try {
|
|
40710
|
-
pullModelWithAutoUpdate(selectedVariant.tag);
|
|
40711
|
-
process.stdout.write(`
|
|
40712
|
-
${c2.green("\u2714")} Model ${c2.bold(selectedVariant.tag)} pulled successfully.
|
|
40792
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Ollama started but not responding. Try ${c2.bold("ollama serve")} manually.
|
|
40713
40793
|
|
|
40714
40794
|
`);
|
|
40795
|
+
return false;
|
|
40796
|
+
}
|
|
40797
|
+
function pullModelWithAutoUpdate(tag) {
|
|
40798
|
+
try {
|
|
40799
|
+
execSync27(`ollama pull ${tag}`, {
|
|
40800
|
+
stdio: "inherit",
|
|
40801
|
+
timeout: 36e5
|
|
40802
|
+
// 1 hour max
|
|
40803
|
+
});
|
|
40715
40804
|
} catch (err) {
|
|
40716
|
-
|
|
40717
|
-
|
|
40718
|
-
|
|
40719
|
-
|
|
40720
|
-
|
|
40721
|
-
|
|
40722
|
-
const customName = `open-agents-${selectedVariant.tag.replace(":", "-").replace(".", "")}`;
|
|
40723
|
-
process.stdout.write(` ${c2.cyan("\u25CF")} Context window recommendation: ${c2.bold(ctx.label)} (${ctx.numCtx} tokens)
|
|
40805
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
40806
|
+
const stderr = err?.stderr?.toString?.() ?? errMsg;
|
|
40807
|
+
const combined = errMsg + "\n" + stderr;
|
|
40808
|
+
if (combined.includes("412") || combined.includes("newer version") || combined.includes("requires a newer version")) {
|
|
40809
|
+
process.stdout.write(`
|
|
40810
|
+
${c2.yellow("\u26A0")} Ollama needs to be updated for this model.
|
|
40724
40811
|
`);
|
|
40725
|
-
|
|
40812
|
+
if (!ensureCurl()) {
|
|
40813
|
+
throw new Error("curl is required to update Ollama but could not be installed.");
|
|
40814
|
+
}
|
|
40815
|
+
process.stdout.write(` ${c2.cyan("\u25CF")} Updating Ollama via official install script...
|
|
40726
40816
|
|
|
40727
40817
|
`);
|
|
40728
|
-
const createModelfile = await ask(rl, ` Create optimized model "${c2.bold(customName)}" with ${ctx.label} context? (Y/n) `);
|
|
40729
|
-
if (createModelfile.toLowerCase() !== "n") {
|
|
40730
40818
|
try {
|
|
40731
|
-
|
|
40732
|
-
|
|
40733
|
-
|
|
40734
|
-
|
|
40735
|
-
`PARAMETER temperature 0`,
|
|
40736
|
-
`PARAMETER num_predict ${numPredict}`,
|
|
40737
|
-
`PARAMETER stop "<|endoftext|>"`
|
|
40738
|
-
].join("\n");
|
|
40739
|
-
const modelDir2 = join54(homedir13(), ".open-agents", "models");
|
|
40740
|
-
mkdirSync15(modelDir2, { recursive: true });
|
|
40741
|
-
const modelfilePath = join54(modelDir2, `Modelfile.${customName}`);
|
|
40742
|
-
writeFileSync16(modelfilePath, modelfileContent + "\n", "utf8");
|
|
40743
|
-
process.stdout.write(` ${c2.dim("Creating model...")} `);
|
|
40744
|
-
execSync27(`ollama create ${customName} -f ${modelfilePath}`, {
|
|
40745
|
-
stdio: "pipe",
|
|
40746
|
-
timeout: 12e4
|
|
40819
|
+
execSync27("curl -fsSL https://ollama.com/install.sh | sh", {
|
|
40820
|
+
stdio: "inherit",
|
|
40821
|
+
timeout: 3e5
|
|
40822
|
+
// 5 min max for install
|
|
40747
40823
|
});
|
|
40748
|
-
process.stdout.write(`${c2.green("\u2714")}
|
|
40749
|
-
`);
|
|
40750
|
-
setConfigValue("model", customName);
|
|
40751
40824
|
process.stdout.write(`
|
|
40752
|
-
${c2.green("\u2714")}
|
|
40825
|
+
${c2.green("\u2714")} Ollama updated successfully.
|
|
40753
40826
|
`);
|
|
40754
|
-
process.stdout.write(` ${c2.
|
|
40827
|
+
process.stdout.write(` ${c2.cyan("\u25CF")} Retrying pull of ${c2.bold(tag)}...
|
|
40755
40828
|
|
|
40756
40829
|
`);
|
|
40757
|
-
|
|
40758
|
-
|
|
40759
|
-
|
|
40760
|
-
|
|
40830
|
+
execSync27(`ollama pull ${tag}`, {
|
|
40831
|
+
stdio: "inherit",
|
|
40832
|
+
timeout: 36e5
|
|
40833
|
+
});
|
|
40834
|
+
} catch (updateErr) {
|
|
40835
|
+
const updateMsg = updateErr instanceof Error ? updateErr.message : String(updateErr);
|
|
40836
|
+
throw new Error(`Failed to update Ollama and retry pull: ${updateMsg}
|
|
40837
|
+
Try manually:
|
|
40838
|
+
curl -fsSL https://ollama.com/install.sh | sh
|
|
40839
|
+
ollama pull ${tag}`);
|
|
40761
40840
|
}
|
|
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
40841
|
} else {
|
|
40953
|
-
|
|
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
|
-
}
|
|
40842
|
+
throw err;
|
|
41003
40843
|
}
|
|
41004
40844
|
}
|
|
41005
|
-
|
|
41006
|
-
|
|
41007
|
-
|
|
41008
|
-
|
|
41009
|
-
|
|
41010
|
-
|
|
41011
|
-
|
|
41012
|
-
|
|
41013
|
-
|
|
41014
|
-
|
|
41015
|
-
|
|
41016
|
-
|
|
40845
|
+
}
|
|
40846
|
+
function computeInferenceScore(specs) {
|
|
40847
|
+
const effectiveGB = Math.max(specs.gpuVramGB, specs.availableRamGB);
|
|
40848
|
+
const memoryScore = Math.min(100, Math.round(effectiveGB <= 4 ? effectiveGB * 2.5 : effectiveGB <= 16 ? 10 + (effectiveGB - 4) * 2.5 : effectiveGB <= 48 ? 40 + (effectiveGB - 16) * 0.94 : effectiveGB <= 128 ? 70 + (effectiveGB - 48) * 0.375 : 100));
|
|
40849
|
+
let computeScore;
|
|
40850
|
+
if (specs.gpuVramGB >= 24)
|
|
40851
|
+
computeScore = 90;
|
|
40852
|
+
else if (specs.gpuVramGB >= 12)
|
|
40853
|
+
computeScore = 70;
|
|
40854
|
+
else if (specs.gpuVramGB >= 8)
|
|
40855
|
+
computeScore = 55;
|
|
40856
|
+
else if (specs.gpuVramGB >= 4)
|
|
40857
|
+
computeScore = 35;
|
|
40858
|
+
else if (specs.totalRamGB >= 32)
|
|
40859
|
+
computeScore = 25;
|
|
40860
|
+
else
|
|
40861
|
+
computeScore = Math.max(5, Math.round(specs.totalRamGB * 1.5));
|
|
40862
|
+
let speedScore;
|
|
40863
|
+
if (specs.gpuVramGB >= 48)
|
|
40864
|
+
speedScore = 95;
|
|
40865
|
+
else if (specs.gpuVramGB >= 24)
|
|
40866
|
+
speedScore = 80;
|
|
40867
|
+
else if (specs.gpuVramGB >= 12)
|
|
40868
|
+
speedScore = 65;
|
|
40869
|
+
else if (specs.gpuVramGB >= 8)
|
|
40870
|
+
speedScore = 50;
|
|
40871
|
+
else if (specs.gpuVramGB > 0)
|
|
40872
|
+
speedScore = 35;
|
|
40873
|
+
else
|
|
40874
|
+
speedScore = Math.min(30, Math.round(specs.totalRamGB * 0.6));
|
|
40875
|
+
const overall = Math.round(memoryScore * 0.45 + computeScore * 0.35 + speedScore * 0.2);
|
|
40876
|
+
const modelCompat = QWEN_VARIANTS.filter((v) => !v.cloud).map((v) => {
|
|
40877
|
+
const budget = effectiveGB * 0.8;
|
|
40878
|
+
const fits = v.sizeGB <= budget;
|
|
40879
|
+
let note;
|
|
40880
|
+
if (fits) {
|
|
40881
|
+
const headroom = budget - v.sizeGB;
|
|
40882
|
+
note = headroom > v.sizeGB * 0.5 ? "comfortable" : headroom > v.sizeGB * 0.15 ? "good fit" : "tight fit";
|
|
40883
|
+
if (specs.gpuVramGB > 0 && v.sizeGB <= specs.gpuVramGB * 0.8) {
|
|
40884
|
+
note += " (GPU accelerated)";
|
|
41017
40885
|
}
|
|
40886
|
+
} else {
|
|
40887
|
+
const overshoot = v.sizeGB - budget;
|
|
40888
|
+
note = `need ${overshoot.toFixed(0)} GB more`;
|
|
41018
40889
|
}
|
|
40890
|
+
return { tag: v.tag, fits, note };
|
|
40891
|
+
});
|
|
40892
|
+
let summary;
|
|
40893
|
+
if (overall >= 80)
|
|
40894
|
+
summary = "Excellent \u2014 can run large models with full context windows";
|
|
40895
|
+
else if (overall >= 60)
|
|
40896
|
+
summary = "Strong \u2014 medium to large models with good context";
|
|
40897
|
+
else if (overall >= 40)
|
|
40898
|
+
summary = "Moderate \u2014 small to medium models, reasonable performance";
|
|
40899
|
+
else if (overall >= 20)
|
|
40900
|
+
summary = "Basic \u2014 small models only, limited context";
|
|
40901
|
+
else
|
|
40902
|
+
summary = "Minimal \u2014 very small models or cloud inference recommended";
|
|
40903
|
+
return { overall, memory: memoryScore, compute: computeScore, speed: speedScore, summary, modelCompat };
|
|
40904
|
+
}
|
|
40905
|
+
function renderScoreBar(score, width = 20) {
|
|
40906
|
+
const filled = Math.round(score / 100 * width);
|
|
40907
|
+
const empty = width - filled;
|
|
40908
|
+
const color = score >= 70 ? c2.green : score >= 40 ? c2.yellow : c2.red;
|
|
40909
|
+
return color("\u2588".repeat(filled)) + c2.dim("\u2591".repeat(empty));
|
|
40910
|
+
}
|
|
40911
|
+
function ensurePython3() {
|
|
40912
|
+
process.stdout.write(` ${c2.cyan("\u25CF")} Installing Python3...
|
|
40913
|
+
`);
|
|
40914
|
+
const plat = platform2();
|
|
40915
|
+
if (plat === "win32") {
|
|
40916
|
+
process.stdout.write(` ${c2.dim("Download Python from https://python.org/downloads/")}
|
|
40917
|
+
|
|
40918
|
+
`);
|
|
40919
|
+
return;
|
|
41019
40920
|
}
|
|
41020
|
-
|
|
41021
|
-
|
|
41022
|
-
|
|
41023
|
-
|
|
41024
|
-
|
|
41025
|
-
|
|
41026
|
-
|
|
41027
|
-
|
|
41028
|
-
|
|
41029
|
-
|
|
41030
|
-
|
|
41031
|
-
|
|
41032
|
-
|
|
41033
|
-
|
|
41034
|
-
|
|
41035
|
-
|
|
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.");
|
|
40921
|
+
const strategies = [
|
|
40922
|
+
{ check: "apt-get", install: "sudo apt-get update -qq && sudo apt-get install -y -qq python3 python3-venv python3-pip", label: "apt" },
|
|
40923
|
+
{ check: "dnf", install: "sudo dnf install -y -q python3 python3-pip", label: "dnf" },
|
|
40924
|
+
{ check: "yum", install: "sudo yum install -y -q python3 python3-pip", label: "yum" },
|
|
40925
|
+
{ check: "pacman", install: "sudo pacman -S --noconfirm python python-pip", label: "pacman" },
|
|
40926
|
+
{ check: "apk", install: "sudo apk add --quiet python3 py3-pip", label: "apk" },
|
|
40927
|
+
{ check: "zypper", install: "sudo zypper install -y python3 python3-pip", label: "zypper" }
|
|
40928
|
+
];
|
|
40929
|
+
if (plat === "darwin") {
|
|
40930
|
+
if (hasCmd("brew")) {
|
|
40931
|
+
try {
|
|
40932
|
+
execSync27("brew install python3", { stdio: "inherit", timeout: 3e5 });
|
|
40933
|
+
if (hasCmd("python3")) {
|
|
40934
|
+
process.stdout.write(` ${c2.green("\u2714")} Python3 installed via Homebrew.
|
|
40935
|
+
`);
|
|
40936
|
+
return;
|
|
41040
40937
|
}
|
|
40938
|
+
} catch {
|
|
41041
40939
|
}
|
|
41042
40940
|
}
|
|
41043
40941
|
}
|
|
41044
|
-
const
|
|
41045
|
-
|
|
41046
|
-
|
|
41047
|
-
|
|
41048
|
-
|
|
41049
|
-
|
|
41050
|
-
|
|
41051
|
-
|
|
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.");
|
|
40942
|
+
for (const s of strategies) {
|
|
40943
|
+
if (hasCmd(s.check)) {
|
|
40944
|
+
try {
|
|
40945
|
+
execSync27(s.install, { stdio: "inherit", timeout: 12e4 });
|
|
40946
|
+
if (hasCmd("python3") || hasCmd("python")) {
|
|
40947
|
+
process.stdout.write(` ${c2.green("\u2714")} Python3 installed via ${s.label}.
|
|
40948
|
+
`);
|
|
40949
|
+
return;
|
|
41063
40950
|
}
|
|
40951
|
+
} catch {
|
|
41064
40952
|
}
|
|
41065
|
-
} catch (err) {
|
|
41066
|
-
log(`moondream-station install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
41067
40953
|
}
|
|
41068
40954
|
}
|
|
41069
|
-
|
|
41070
|
-
|
|
41071
|
-
|
|
41072
|
-
|
|
40955
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Could not install Python3 automatically. Install manually.
|
|
40956
|
+
|
|
40957
|
+
`);
|
|
40958
|
+
}
|
|
40959
|
+
function checkPythonVenv() {
|
|
40960
|
+
try {
|
|
40961
|
+
execSync27("python3 -m venv --help", { stdio: "pipe", timeout: 5e3 });
|
|
40962
|
+
return true;
|
|
40963
|
+
} catch {
|
|
41073
40964
|
try {
|
|
41074
|
-
execSync27(
|
|
41075
|
-
|
|
40965
|
+
execSync27("python -m venv --help", { stdio: "pipe", timeout: 5e3 });
|
|
40966
|
+
return true;
|
|
41076
40967
|
} catch {
|
|
40968
|
+
return false;
|
|
41077
40969
|
}
|
|
41078
|
-
|
|
41079
|
-
|
|
41080
|
-
|
|
40970
|
+
}
|
|
40971
|
+
}
|
|
40972
|
+
function ensurePythonVenv() {
|
|
40973
|
+
process.stdout.write(` ${c2.cyan("\u25CF")} Installing python3-venv...
|
|
40974
|
+
`);
|
|
40975
|
+
const strategies = [
|
|
40976
|
+
{ check: "apt-get", install: "sudo apt-get update -qq && sudo apt-get install -y -qq python3-venv", label: "apt" },
|
|
40977
|
+
{ check: "dnf", install: "sudo dnf install -y -q python3-libs", label: "dnf" },
|
|
40978
|
+
{ check: "pacman", install: "sudo pacman -S --noconfirm python", label: "pacman" },
|
|
40979
|
+
{ check: "apk", install: "sudo apk add --quiet python3", label: "apk" }
|
|
40980
|
+
];
|
|
40981
|
+
for (const s of strategies) {
|
|
40982
|
+
if (hasCmd(s.check)) {
|
|
41081
40983
|
try {
|
|
41082
|
-
execSync27(
|
|
41083
|
-
|
|
41084
|
-
|
|
41085
|
-
|
|
41086
|
-
|
|
41087
|
-
log("OCR Python stack install completed but import verification failed.");
|
|
40984
|
+
execSync27(s.install, { stdio: "inherit", timeout: 12e4 });
|
|
40985
|
+
if (checkPythonVenv()) {
|
|
40986
|
+
process.stdout.write(` ${c2.green("\u2714")} python3-venv installed via ${s.label}.
|
|
40987
|
+
`);
|
|
40988
|
+
return;
|
|
41088
40989
|
}
|
|
41089
|
-
} catch
|
|
41090
|
-
log(`OCR Python stack install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
40990
|
+
} catch {
|
|
41091
40991
|
}
|
|
41092
40992
|
}
|
|
41093
|
-
} else {
|
|
41094
|
-
log("Python venv unavailable \u2014 advanced OCR pipeline will fall back to basic tesseract.");
|
|
41095
40993
|
}
|
|
40994
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Could not install python3-venv. Install manually: sudo apt install python3-venv
|
|
40995
|
+
|
|
40996
|
+
`);
|
|
41096
40997
|
}
|
|
41097
|
-
function
|
|
41098
|
-
|
|
41099
|
-
|
|
41100
|
-
|
|
41101
|
-
|
|
41102
|
-
return;
|
|
41103
|
-
}
|
|
41104
|
-
const log = onInfo ?? (() => {
|
|
40998
|
+
async function runSetupWizard(config) {
|
|
40999
|
+
const rl = readline.createInterface({
|
|
41000
|
+
input: process.stdin,
|
|
41001
|
+
output: process.stdout,
|
|
41002
|
+
terminal: true
|
|
41105
41003
|
});
|
|
41106
|
-
|
|
41107
|
-
|
|
41108
|
-
|
|
41109
|
-
|
|
41110
|
-
|
|
41111
|
-
|
|
41112
|
-
|
|
41004
|
+
try {
|
|
41005
|
+
return await doSetup(config, rl);
|
|
41006
|
+
} finally {
|
|
41007
|
+
rl.close();
|
|
41008
|
+
}
|
|
41009
|
+
}
|
|
41010
|
+
async function promptForCustomEndpoint(config, rl) {
|
|
41011
|
+
process.stdout.write(`
|
|
41012
|
+
${c2.cyan("\u25CF")} Enter an OpenAI-compatible inference endpoint.
|
|
41013
|
+
`);
|
|
41014
|
+
process.stdout.write(` ${c2.dim("Examples:")}
|
|
41015
|
+
`);
|
|
41016
|
+
process.stdout.write(` ${c2.dim(" https://chutes.ai/v1")}
|
|
41017
|
+
`);
|
|
41018
|
+
process.stdout.write(` ${c2.dim(" http://10.0.0.5:11434")}
|
|
41019
|
+
`);
|
|
41020
|
+
process.stdout.write(` ${c2.dim(" https://api.together.xyz/v1")}
|
|
41021
|
+
|
|
41022
|
+
`);
|
|
41023
|
+
const endpoint = await ask(rl, ` ${c2.bold("Endpoint URL:")} `);
|
|
41024
|
+
if (!endpoint) {
|
|
41025
|
+
process.stdout.write(` ${c2.dim("No endpoint entered.")}
|
|
41026
|
+
`);
|
|
41027
|
+
const startAnyway = await ask(rl, `
|
|
41028
|
+
${c2.bold("Start anyway without a backend?")} (y/n) `);
|
|
41029
|
+
if (startAnyway.toLowerCase() === "y" || startAnyway.toLowerCase() === "yes") {
|
|
41030
|
+
return config.model;
|
|
41113
41031
|
}
|
|
41114
|
-
|
|
41115
|
-
|
|
41116
|
-
|
|
41117
|
-
|
|
41118
|
-
|
|
41119
|
-
|
|
41120
|
-
|
|
41121
|
-
|
|
41122
|
-
|
|
41123
|
-
|
|
41124
|
-
|
|
41125
|
-
|
|
41126
|
-
|
|
41127
|
-
|
|
41128
|
-
|
|
41129
|
-
|
|
41130
|
-
|
|
41131
|
-
|
|
41132
|
-
|
|
41032
|
+
return config.model;
|
|
41033
|
+
}
|
|
41034
|
+
const cleanUrl = normalizeBaseUrl(endpoint);
|
|
41035
|
+
const needsKey = await ask(rl, `
|
|
41036
|
+
${c2.bold("Does this endpoint require an API key?")} (y/n) `);
|
|
41037
|
+
let apiKey = "";
|
|
41038
|
+
if (needsKey.toLowerCase() === "y" || needsKey.toLowerCase() === "yes") {
|
|
41039
|
+
apiKey = await askSecret(rl, ` ${c2.bold("API key:")} `);
|
|
41040
|
+
}
|
|
41041
|
+
process.stdout.write(`
|
|
41042
|
+
${c2.cyan("\u25CF")} Enter the model name for this endpoint.
|
|
41043
|
+
`);
|
|
41044
|
+
process.stdout.write(` ${c2.dim("Examples: qwen3.5:122b, meta-llama/Llama-3.3-70B, etc.")}
|
|
41045
|
+
|
|
41046
|
+
`);
|
|
41047
|
+
const modelName = await ask(rl, ` ${c2.bold("Model name")} (Enter for ${c2.dim(config.model)}): `);
|
|
41048
|
+
const chosenModel = modelName || config.model;
|
|
41049
|
+
const provider = detectProvider(endpoint);
|
|
41050
|
+
process.stdout.write(`
|
|
41051
|
+
${c2.cyan("\u25CF")} Detected provider: ${c2.bold(provider.label)}
|
|
41052
|
+
`);
|
|
41053
|
+
process.stdout.write(` ${c2.cyan("\u25CF")} Testing endpoint ${c2.bold(cleanUrl)}...
|
|
41054
|
+
`);
|
|
41055
|
+
let testOk = false;
|
|
41056
|
+
try {
|
|
41057
|
+
const testUrl = `${cleanUrl}${provider.modelsPath}`;
|
|
41058
|
+
const headers = { "Content-Type": "application/json" };
|
|
41059
|
+
if (apiKey) {
|
|
41060
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
41061
|
+
}
|
|
41062
|
+
const resp = await fetch(testUrl, { headers, signal: AbortSignal.timeout(1e4) });
|
|
41063
|
+
if (resp.ok) {
|
|
41064
|
+
process.stdout.write(` ${c2.green("\u2714")} Endpoint reachable.
|
|
41065
|
+
`);
|
|
41066
|
+
testOk = true;
|
|
41067
|
+
} else {
|
|
41068
|
+
if (provider.id !== "ollama") {
|
|
41069
|
+
try {
|
|
41070
|
+
const ollamaResp = await fetch(`${cleanUrl}/api/tags`, { signal: AbortSignal.timeout(1e4) });
|
|
41071
|
+
if (ollamaResp.ok) {
|
|
41072
|
+
process.stdout.write(` ${c2.green("\u2714")} Ollama endpoint detected.
|
|
41073
|
+
`);
|
|
41074
|
+
testOk = true;
|
|
41075
|
+
}
|
|
41076
|
+
} catch {
|
|
41133
41077
|
}
|
|
41134
|
-
} catch {
|
|
41135
41078
|
}
|
|
41136
|
-
|
|
41137
|
-
|
|
41138
|
-
|
|
41139
|
-
if (hasCmd("cloudflared")) {
|
|
41140
|
-
log("cloudflared installed via Homebrew.");
|
|
41141
|
-
return true;
|
|
41142
|
-
}
|
|
41143
|
-
} catch {
|
|
41079
|
+
if (!testOk) {
|
|
41080
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Endpoint returned HTTP ${resp.status}
|
|
41081
|
+
`);
|
|
41144
41082
|
}
|
|
41145
41083
|
}
|
|
41146
|
-
|
|
41147
|
-
|
|
41148
|
-
|
|
41149
|
-
}
|
|
41150
|
-
function expandedModelName(baseModel) {
|
|
41151
|
-
return `open-agents-${baseModel.replace(":", "-").replace(/\./g, "")}`;
|
|
41152
|
-
}
|
|
41153
|
-
async function checkExpandedVariant(modelName, backendUrl) {
|
|
41154
|
-
if (modelName.startsWith("open-agents-"))
|
|
41155
|
-
return null;
|
|
41156
|
-
const target = expandedModelName(modelName);
|
|
41157
|
-
try {
|
|
41158
|
-
const models = await fetchOllamaModels(backendUrl);
|
|
41159
|
-
const found = models.find((m) => m.name === target || m.name.startsWith(target + ":"));
|
|
41160
|
-
return found ? found.name : false;
|
|
41161
|
-
} catch {
|
|
41162
|
-
return false;
|
|
41084
|
+
} catch (err) {
|
|
41085
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Could not reach endpoint: ${err instanceof Error ? err.message : String(err)}
|
|
41086
|
+
`);
|
|
41163
41087
|
}
|
|
41164
|
-
|
|
41165
|
-
|
|
41166
|
-
|
|
41167
|
-
|
|
41168
|
-
|
|
41169
|
-
|
|
41170
|
-
|
|
41171
|
-
|
|
41172
|
-
|
|
41173
|
-
|
|
41174
|
-
|
|
41175
|
-
|
|
41176
|
-
|
|
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 };
|
|
41199
|
-
} catch {
|
|
41200
|
-
return null;
|
|
41088
|
+
if (!testOk) {
|
|
41089
|
+
if (provider.authRequired && !apiKey) {
|
|
41090
|
+
process.stdout.write(` ${c2.dim(`${provider.label} typically requires an API key.`)}
|
|
41091
|
+
`);
|
|
41092
|
+
}
|
|
41093
|
+
const startAnyway = await ask(rl, `
|
|
41094
|
+
${c2.bold("Endpoint unreachable. Start anyway?")} (y/n) `);
|
|
41095
|
+
if (startAnyway.toLowerCase() !== "y" && startAnyway.toLowerCase() !== "yes") {
|
|
41096
|
+
process.stdout.write(` ${c2.dim("You can configure the endpoint later with /endpoint")}
|
|
41097
|
+
|
|
41098
|
+
`);
|
|
41099
|
+
return config.model;
|
|
41100
|
+
}
|
|
41201
41101
|
}
|
|
41202
|
-
|
|
41203
|
-
|
|
41204
|
-
|
|
41205
|
-
|
|
41206
|
-
try {
|
|
41207
|
-
const numPredict = Math.min(16384, Math.max(2048, Math.floor(ctx.numCtx * 0.25)));
|
|
41208
|
-
const modelfileContent = [
|
|
41209
|
-
`FROM ${baseModel}`,
|
|
41210
|
-
`PARAMETER num_ctx ${ctx.numCtx}`,
|
|
41211
|
-
`PARAMETER temperature 0`,
|
|
41212
|
-
`PARAMETER num_predict ${numPredict}`,
|
|
41213
|
-
`PARAMETER stop "<|endoftext|>"`
|
|
41214
|
-
].join("\n");
|
|
41215
|
-
const modelDir2 = join54(homedir13(), ".open-agents", "models");
|
|
41216
|
-
mkdirSync15(modelDir2, { recursive: true });
|
|
41217
|
-
const modelfilePath = join54(modelDir2, `Modelfile.${customName}`);
|
|
41218
|
-
writeFileSync16(modelfilePath, modelfileContent + "\n", "utf8");
|
|
41219
|
-
await execAsync(`ollama create ${customName} -f ${modelfilePath}`, {
|
|
41220
|
-
timeout: 12e4
|
|
41221
|
-
});
|
|
41222
|
-
return customName;
|
|
41223
|
-
} catch {
|
|
41224
|
-
return null;
|
|
41102
|
+
setConfigValue("backendUrl", cleanUrl);
|
|
41103
|
+
setConfigValue("model", chosenModel);
|
|
41104
|
+
if (apiKey) {
|
|
41105
|
+
setConfigValue("apiKey", apiKey);
|
|
41225
41106
|
}
|
|
41107
|
+
const backendType = provider.id === "ollama" ? "ollama" : "vllm";
|
|
41108
|
+
setConfigValue("backendType", backendType);
|
|
41109
|
+
process.stdout.write(`
|
|
41110
|
+
${c2.green("\u2714")} Configured: ${c2.bold(chosenModel)} at ${c2.bold(cleanUrl)}
|
|
41111
|
+
`);
|
|
41112
|
+
process.stdout.write(` ${c2.green("\u2714")} Provider: ${c2.bold(provider.label)}
|
|
41113
|
+
`);
|
|
41114
|
+
if (apiKey)
|
|
41115
|
+
process.stdout.write(` ${c2.green("\u2714")} API key saved.
|
|
41116
|
+
`);
|
|
41117
|
+
process.stdout.write(` ${c2.green("\u2714")} Backend type: ${c2.bold(backendType)}
|
|
41118
|
+
|
|
41119
|
+
`);
|
|
41120
|
+
return chosenModel;
|
|
41226
41121
|
}
|
|
41227
|
-
async function
|
|
41228
|
-
|
|
41229
|
-
|
|
41122
|
+
async function doSetup(config, rl) {
|
|
41123
|
+
process.stdout.write(`
|
|
41124
|
+
${c2.bold(c2.cyan("open-agents"))}
|
|
41125
|
+
`);
|
|
41126
|
+
process.stdout.write(` ${c2.dim("\u2500".repeat(60))}
|
|
41127
|
+
`);
|
|
41128
|
+
process.stdout.write(` ${c2.bold("First-run setup")}
|
|
41129
|
+
|
|
41130
|
+
`);
|
|
41131
|
+
process.stdout.write(` ${c2.cyan("\u25CF")} Detecting system specs...
|
|
41132
|
+
`);
|
|
41133
|
+
const specs = detectSystemSpecs();
|
|
41134
|
+
process.stdout.write(` ${c2.dim(" RAM:")} ${specs.totalRamGB.toFixed(1)} GB total, ${specs.availableRamGB.toFixed(1)} GB available
|
|
41135
|
+
`);
|
|
41136
|
+
if (specs.gpuVramGB > 0) {
|
|
41137
|
+
process.stdout.write(` ${c2.dim(" GPU:")} ${specs.gpuName || "NVIDIA"} \u2014 ${specs.gpuVramGB.toFixed(1)} GB VRAM
|
|
41138
|
+
`);
|
|
41139
|
+
} else {
|
|
41140
|
+
process.stdout.write(` ${c2.dim(" GPU:")} No NVIDIA GPU detected (CPU inference)
|
|
41141
|
+
`);
|
|
41230
41142
|
}
|
|
41231
|
-
const
|
|
41232
|
-
const
|
|
41233
|
-
|
|
41143
|
+
const score = computeInferenceScore(specs);
|
|
41144
|
+
const w = 24;
|
|
41145
|
+
const bw = 30;
|
|
41146
|
+
process.stdout.write(`
|
|
41147
|
+
${c2.dim("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510")}
|
|
41148
|
+
`);
|
|
41149
|
+
process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold(" Inference Capability")} ${c2.dim("\u2502")}
|
|
41150
|
+
`);
|
|
41151
|
+
process.stdout.write(` ${c2.dim("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524")}
|
|
41152
|
+
`);
|
|
41153
|
+
process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold("Overall")} ${c2.dim("\u2502")}
|
|
41154
|
+
`);
|
|
41155
|
+
process.stdout.write(` ${c2.dim("\u2502")} ${renderScoreBar(score.overall, bw)} ${c2.bold(String(score.overall).padStart(3))}${c2.dim("/100")} ${c2.dim("\u2502")}
|
|
41156
|
+
`);
|
|
41157
|
+
process.stdout.write(` ${c2.dim("\u2502")} ${c2.dim(score.summary.padEnd(46))} ${c2.dim("\u2502")}
|
|
41158
|
+
`);
|
|
41159
|
+
process.stdout.write(` ${c2.dim("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524")}
|
|
41160
|
+
`);
|
|
41161
|
+
process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold("Memory")} ${c2.dim(`${specs.totalRamGB.toFixed(0)} GB RAM` + (specs.gpuVramGB > 0 ? ` + ${specs.gpuVramGB.toFixed(0)} GB VRAM` : ""))}${" ".repeat(Math.max(0, 27 - (`${specs.totalRamGB.toFixed(0)} GB RAM` + (specs.gpuVramGB > 0 ? ` + ${specs.gpuVramGB.toFixed(0)} GB VRAM` : "")).length))} ${c2.dim("\u2502")}
|
|
41162
|
+
`);
|
|
41163
|
+
process.stdout.write(` ${c2.dim("\u2502")} ${renderScoreBar(score.memory, w)} ${String(score.memory).padStart(3)} ${c2.dim("\u2502")}
|
|
41164
|
+
`);
|
|
41165
|
+
process.stdout.write(` ${c2.dim("\u2502")} ${c2.dim("\u2502")}
|
|
41166
|
+
`);
|
|
41167
|
+
process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold("Compute")} ${c2.dim(specs.gpuVramGB > 0 ? specs.gpuName || "NVIDIA GPU" : "CPU only")}${" ".repeat(Math.max(0, 28 - (specs.gpuVramGB > 0 ? specs.gpuName || "NVIDIA GPU" : "CPU only").length))} ${c2.dim("\u2502")}
|
|
41168
|
+
`);
|
|
41169
|
+
process.stdout.write(` ${c2.dim("\u2502")} ${renderScoreBar(score.compute, w)} ${String(score.compute).padStart(3)} ${c2.dim("\u2502")}
|
|
41170
|
+
`);
|
|
41171
|
+
process.stdout.write(` ${c2.dim("\u2502")} ${c2.dim("\u2502")}
|
|
41172
|
+
`);
|
|
41173
|
+
process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold("Speed")} ${c2.dim(specs.gpuVramGB > 0 ? "GPU accelerated" : "CPU inference")}${" ".repeat(Math.max(0, 28 - (specs.gpuVramGB > 0 ? "GPU accelerated" : "CPU inference").length))} ${c2.dim("\u2502")}
|
|
41174
|
+
`);
|
|
41175
|
+
process.stdout.write(` ${c2.dim("\u2502")} ${renderScoreBar(score.speed, w)} ${String(score.speed).padStart(3)} ${c2.dim("\u2502")}
|
|
41176
|
+
`);
|
|
41177
|
+
process.stdout.write(` ${c2.dim("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524")}
|
|
41178
|
+
`);
|
|
41179
|
+
process.stdout.write(` ${c2.dim("\u2502")} ${c2.bold(" Model Compatibility")} ${c2.dim("\u2502")}
|
|
41180
|
+
`);
|
|
41181
|
+
for (const compat of score.modelCompat) {
|
|
41182
|
+
const icon = compat.fits ? c2.green("\u2714") : c2.red("\u2716");
|
|
41183
|
+
const tag = compat.fits ? compat.tag : c2.dim(compat.tag);
|
|
41184
|
+
const note = c2.dim(compat.note);
|
|
41185
|
+
const visTag = compat.tag;
|
|
41186
|
+
const visNote = compat.note;
|
|
41187
|
+
const pad = Math.max(0, 44 - 2 - visTag.length - 1 - visNote.length);
|
|
41188
|
+
process.stdout.write(` ${c2.dim("\u2502")} ${icon} ${tag} ${" ".repeat(pad)}${note} ${c2.dim("\u2502")}
|
|
41189
|
+
`);
|
|
41190
|
+
}
|
|
41191
|
+
process.stdout.write(` ${c2.dim("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518")}
|
|
41192
|
+
|
|
41193
|
+
`);
|
|
41194
|
+
let hasPython = hasCmd("python3") || hasCmd("python");
|
|
41195
|
+
if (!hasPython) {
|
|
41196
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Python3 not found (needed for vision, OCR, browser automation).
|
|
41197
|
+
`);
|
|
41198
|
+
const installPy = await ask(rl, ` ${c2.bold("Install Python3?")} (Y/n) `);
|
|
41199
|
+
if (installPy.toLowerCase() !== "n") {
|
|
41200
|
+
ensurePython3();
|
|
41201
|
+
hasPython = hasCmd("python3") || hasCmd("python");
|
|
41202
|
+
}
|
|
41203
|
+
}
|
|
41204
|
+
if (hasPython) {
|
|
41205
|
+
const hasVenv = checkPythonVenv();
|
|
41206
|
+
if (!hasVenv) {
|
|
41207
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Python3 venv module not found (needed for Moondream, OCR, browser automation).
|
|
41208
|
+
`);
|
|
41209
|
+
const installVenv = await ask(rl, ` ${c2.bold("Install python3-venv?")} (Y/n) `);
|
|
41210
|
+
if (installVenv.toLowerCase() !== "n") {
|
|
41211
|
+
ensurePythonVenv();
|
|
41212
|
+
}
|
|
41213
|
+
}
|
|
41214
|
+
}
|
|
41215
|
+
let models = [];
|
|
41216
|
+
let usingCustomEndpoint = false;
|
|
41234
41217
|
try {
|
|
41235
|
-
|
|
41236
|
-
sizeGB = modelSizeGB(models, modelName);
|
|
41218
|
+
models = await fetchOllamaModels(config.backendUrl);
|
|
41237
41219
|
} catch {
|
|
41238
|
-
}
|
|
41239
|
-
|
|
41240
|
-
|
|
41241
|
-
|
|
41242
|
-
|
|
41243
|
-
const
|
|
41244
|
-
|
|
41245
|
-
|
|
41246
|
-
|
|
41247
|
-
|
|
41248
|
-
|
|
41249
|
-
|
|
41250
|
-
|
|
41251
|
-
|
|
41252
|
-
|
|
41253
|
-
|
|
41254
|
-
|
|
41255
|
-
const
|
|
41256
|
-
|
|
41257
|
-
|
|
41220
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Cannot reach Ollama at ${c2.bold(config.backendUrl)}
|
|
41221
|
+
|
|
41222
|
+
`);
|
|
41223
|
+
const useCustom = await ask(rl, ` ${c2.bold("Use a custom inference endpoint instead?")} (Y/n) `);
|
|
41224
|
+
if (useCustom.toLowerCase() === "y" || useCustom.toLowerCase() === "yes") {
|
|
41225
|
+
const endpointResult = await promptForCustomEndpoint(config, rl);
|
|
41226
|
+
if (endpointResult) {
|
|
41227
|
+
return endpointResult;
|
|
41228
|
+
}
|
|
41229
|
+
usingCustomEndpoint = true;
|
|
41230
|
+
} else {
|
|
41231
|
+
const ollamaInstalled = hasCmd("ollama");
|
|
41232
|
+
if (ollamaInstalled) {
|
|
41233
|
+
process.stdout.write(`
|
|
41234
|
+
${c2.cyan("\u25CF")} Ollama is installed but not running. Starting automatically...
|
|
41235
|
+
`);
|
|
41236
|
+
try {
|
|
41237
|
+
const child = spawn19("ollama", ["serve"], { stdio: "ignore", detached: true });
|
|
41238
|
+
child.unref();
|
|
41239
|
+
await new Promise((resolve36) => setTimeout(resolve36, 3e3));
|
|
41240
|
+
try {
|
|
41241
|
+
models = await fetchOllamaModels(config.backendUrl);
|
|
41242
|
+
process.stdout.write(` ${c2.green("\u2714")} Ollama is running.
|
|
41243
|
+
|
|
41244
|
+
`);
|
|
41245
|
+
} catch {
|
|
41246
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Ollama started but not responding yet. It may need a moment.
|
|
41247
|
+
|
|
41248
|
+
`);
|
|
41249
|
+
}
|
|
41250
|
+
} catch {
|
|
41251
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Could not start Ollama. Try running ${c2.bold("ollama serve")} manually.
|
|
41252
|
+
|
|
41253
|
+
`);
|
|
41254
|
+
}
|
|
41255
|
+
} else {
|
|
41256
|
+
const installOllama = await ask(rl, `
|
|
41257
|
+
${c2.bold("Install Ollama for local inference?")} (Y/n) `);
|
|
41258
|
+
if (installOllama.toLowerCase() !== "n") {
|
|
41259
|
+
const installed = await autoInstallOllama(rl);
|
|
41260
|
+
if (installed) {
|
|
41261
|
+
process.stdout.write(`
|
|
41262
|
+
${c2.cyan("\u25CF")} Starting ollama serve...
|
|
41263
|
+
`);
|
|
41264
|
+
try {
|
|
41265
|
+
const child = spawn19("ollama", ["serve"], { stdio: "ignore", detached: true });
|
|
41266
|
+
child.unref();
|
|
41267
|
+
await new Promise((resolve36) => setTimeout(resolve36, 3e3));
|
|
41268
|
+
try {
|
|
41269
|
+
models = await fetchOllamaModels(config.backendUrl);
|
|
41270
|
+
process.stdout.write(` ${c2.green("\u2714")} Ollama is running.
|
|
41271
|
+
|
|
41272
|
+
`);
|
|
41273
|
+
} catch {
|
|
41274
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Ollama installed but not responding yet. Try ${c2.bold("ollama serve")} manually.
|
|
41275
|
+
|
|
41276
|
+
`);
|
|
41277
|
+
}
|
|
41278
|
+
} catch {
|
|
41279
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Ollama installed. Start it with: ${c2.bold("ollama serve")}
|
|
41280
|
+
|
|
41281
|
+
`);
|
|
41282
|
+
}
|
|
41258
41283
|
}
|
|
41284
|
+
} else {
|
|
41285
|
+
process.stdout.write(`
|
|
41286
|
+
${c2.dim("You'll need to configure an inference endpoint.")}
|
|
41287
|
+
`);
|
|
41288
|
+
process.stdout.write(` ${c2.dim("Use:")} ${c2.bold("/endpoint <url> --auth <key>")} ${c2.dim("once inside the TUI.")}
|
|
41289
|
+
|
|
41290
|
+
`);
|
|
41291
|
+
return config.model;
|
|
41259
41292
|
}
|
|
41260
41293
|
}
|
|
41261
|
-
} catch {
|
|
41262
41294
|
}
|
|
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 };
|
|
41271
41295
|
}
|
|
41272
|
-
|
|
41273
|
-
|
|
41274
|
-
return { model: created, created: true, contextLabel: ctx.label, numCtx: ctx.numCtx };
|
|
41296
|
+
if (usingCustomEndpoint) {
|
|
41297
|
+
return config.model;
|
|
41275
41298
|
}
|
|
41276
|
-
|
|
41277
|
-
|
|
41278
|
-
|
|
41279
|
-
|
|
41280
|
-
|
|
41281
|
-
|
|
41282
|
-
stdio: "pipe",
|
|
41283
|
-
timeout: 5e3
|
|
41284
|
-
}).trim();
|
|
41285
|
-
if (nvimPath)
|
|
41286
|
-
return nvimPath;
|
|
41287
|
-
} catch {
|
|
41299
|
+
const currentModel = findModel(models, config.model);
|
|
41300
|
+
if (currentModel) {
|
|
41301
|
+
process.stdout.write(` ${c2.green("\u2714")} Model ${c2.bold(currentModel.name)} is available.
|
|
41302
|
+
|
|
41303
|
+
`);
|
|
41304
|
+
return currentModel.name;
|
|
41288
41305
|
}
|
|
41289
|
-
|
|
41290
|
-
|
|
41291
|
-
|
|
41292
|
-
|
|
41293
|
-
const
|
|
41294
|
-
|
|
41295
|
-
|
|
41296
|
-
|
|
41297
|
-
}
|
|
41298
|
-
const
|
|
41299
|
-
|
|
41300
|
-
|
|
41301
|
-
|
|
41302
|
-
|
|
41303
|
-
|
|
41304
|
-
|
|
41305
|
-
|
|
41306
|
-
|
|
41307
|
-
}
|
|
41308
|
-
|
|
41309
|
-
const
|
|
41310
|
-
|
|
41311
|
-
|
|
41312
|
-
|
|
41313
|
-
|
|
41314
|
-
|
|
41315
|
-
|
|
41306
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Default model ${c2.bold(config.model)} is not available.
|
|
41307
|
+
|
|
41308
|
+
`);
|
|
41309
|
+
if (models.length > 0) {
|
|
41310
|
+
const backendUrl = config.backendUrl || "http://localhost:11434";
|
|
41311
|
+
const modelChecks = await Promise.all(models.slice(0, 15).map(async (m) => ({
|
|
41312
|
+
...m,
|
|
41313
|
+
hasTools: await checkToolSupport(m.name, backendUrl)
|
|
41314
|
+
})));
|
|
41315
|
+
const modelItems = modelChecks.map((m) => {
|
|
41316
|
+
const toolTag = m.hasTools ? "tools \u2713" : "text mode";
|
|
41317
|
+
return { key: m.name, label: m.name, detail: `${m.size} ${toolTag}` };
|
|
41318
|
+
});
|
|
41319
|
+
modelItems.push({ key: "__pull_new__", label: "Pull a new qwen3.5 model instead", detail: "" });
|
|
41320
|
+
const modelResult = await tuiSelect({
|
|
41321
|
+
items: modelItems,
|
|
41322
|
+
title: "Select a Model",
|
|
41323
|
+
rl
|
|
41324
|
+
});
|
|
41325
|
+
if (modelResult.confirmed && modelResult.key && modelResult.key !== "__pull_new__") {
|
|
41326
|
+
const selected = modelChecks.find((m) => m.name === modelResult.key);
|
|
41327
|
+
setConfigValue("model", selected.name);
|
|
41328
|
+
const mode = selected.hasTools ? "native tool calling" : "text mode (instruct-and-parse)";
|
|
41329
|
+
process.stdout.write(`
|
|
41330
|
+
${c2.green("\u2714")} Selected ${c2.bold(selected.name)} \u2014 ${mode}. Saved to config.
|
|
41331
|
+
|
|
41332
|
+
`);
|
|
41333
|
+
return selected.name;
|
|
41316
41334
|
}
|
|
41317
|
-
|
|
41318
|
-
|
|
41335
|
+
} else {
|
|
41336
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} No models found on this system.
|
|
41337
|
+
|
|
41338
|
+
`);
|
|
41319
41339
|
}
|
|
41320
|
-
|
|
41321
|
-
|
|
41322
|
-
|
|
41323
|
-
|
|
41324
|
-
|
|
41325
|
-
|
|
41326
|
-
|
|
41327
|
-
|
|
41328
|
-
|
|
41329
|
-
|
|
41330
|
-
|
|
41331
|
-
|
|
41332
|
-
|
|
41333
|
-
|
|
41340
|
+
const recommended = recommendModel(specs);
|
|
41341
|
+
const localVariants = QWEN_VARIANTS.filter((v) => !v.cloud);
|
|
41342
|
+
const pullItems = localVariants.map((v) => {
|
|
41343
|
+
const fits = v.sizeGB <= Math.max(specs.gpuVramGB, specs.availableRamGB) * 0.8;
|
|
41344
|
+
const isRec = v.tag === recommended.tag;
|
|
41345
|
+
const suffix = isRec ? " \u2190 recommended" : !fits ? " (exceeds memory)" : "";
|
|
41346
|
+
return { key: v.tag, label: v.tag, detail: `${v.label}${suffix}` };
|
|
41347
|
+
});
|
|
41348
|
+
pullItems.push({ key: "qwen3.5:cloud", label: "qwen3.5:cloud", detail: "Ollama Cloud" });
|
|
41349
|
+
pullItems.push({ key: "qwen3.5:397b-cloud", label: "qwen3.5:397b-cloud", detail: "397B Ollama Cloud" });
|
|
41350
|
+
const pullResult = await tuiSelect({
|
|
41351
|
+
items: pullItems,
|
|
41352
|
+
title: "Select a Model to Pull",
|
|
41353
|
+
activeKey: recommended.tag,
|
|
41354
|
+
rl
|
|
41355
|
+
});
|
|
41356
|
+
let selectedVariant;
|
|
41357
|
+
if (!pullResult.confirmed || !pullResult.key) {
|
|
41358
|
+
process.stdout.write(`
|
|
41359
|
+
${c2.dim("Skipping model pull. You can pull manually with: ollama pull <model>")}
|
|
41360
|
+
|
|
41361
|
+
`);
|
|
41362
|
+
return config.model;
|
|
41334
41363
|
}
|
|
41335
|
-
|
|
41336
|
-
|
|
41337
|
-
|
|
41338
|
-
|
|
41339
|
-
|
|
41340
|
-
|
|
41341
|
-
|
|
41342
|
-
|
|
41343
|
-
|
|
41344
|
-
|
|
41345
|
-
|
|
41346
|
-
|
|
41364
|
+
selectedVariant = QWEN_VARIANTS.find((v) => v.tag === pullResult.key) ?? recommended;
|
|
41365
|
+
process.stdout.write(`
|
|
41366
|
+
${c2.cyan("\u25CF")} Pulling ${c2.bold(selectedVariant.tag)}... (this may take a while)
|
|
41367
|
+
`);
|
|
41368
|
+
try {
|
|
41369
|
+
pullModelWithAutoUpdate(selectedVariant.tag);
|
|
41370
|
+
process.stdout.write(`
|
|
41371
|
+
${c2.green("\u2714")} Model ${c2.bold(selectedVariant.tag)} pulled successfully.
|
|
41372
|
+
|
|
41373
|
+
`);
|
|
41374
|
+
} catch (err) {
|
|
41375
|
+
renderError(`Failed to pull model: ${err instanceof Error ? err.message : String(err)}`);
|
|
41376
|
+
renderInfo("Try manually: ollama pull " + selectedVariant.tag);
|
|
41377
|
+
return config.model;
|
|
41378
|
+
}
|
|
41379
|
+
if (!selectedVariant.cloud) {
|
|
41380
|
+
const ctx = calculateContextWindow(specs, selectedVariant.sizeGB);
|
|
41381
|
+
const customName = `open-agents-${selectedVariant.tag.replace(":", "-").replace(".", "")}`;
|
|
41382
|
+
process.stdout.write(` ${c2.cyan("\u25CF")} Context window recommendation: ${c2.bold(ctx.label)} (${ctx.numCtx} tokens)
|
|
41383
|
+
`);
|
|
41384
|
+
process.stdout.write(` ${c2.dim(`Based on ${specs.totalRamGB.toFixed(0)} GB RAM, ${selectedVariant.sizeGB} GB model`)}
|
|
41385
|
+
|
|
41386
|
+
`);
|
|
41387
|
+
const createModelfile = await ask(rl, ` Create optimized model "${c2.bold(customName)}" with ${ctx.label} context? (Y/n) `);
|
|
41388
|
+
if (createModelfile.toLowerCase() !== "n") {
|
|
41347
41389
|
try {
|
|
41348
|
-
|
|
41349
|
-
|
|
41390
|
+
const numPredict = Math.min(16384, Math.max(2048, Math.floor(ctx.numCtx * 0.25)));
|
|
41391
|
+
const modelfileContent = [
|
|
41392
|
+
`FROM ${selectedVariant.tag}`,
|
|
41393
|
+
`PARAMETER num_ctx ${ctx.numCtx}`,
|
|
41394
|
+
`PARAMETER temperature 0`,
|
|
41395
|
+
`PARAMETER num_predict ${numPredict}`,
|
|
41396
|
+
`PARAMETER stop "<|endoftext|>"`
|
|
41397
|
+
].join("\n");
|
|
41398
|
+
const modelDir2 = join54(homedir13(), ".open-agents", "models");
|
|
41399
|
+
mkdirSync15(modelDir2, { recursive: true });
|
|
41400
|
+
const modelfilePath = join54(modelDir2, `Modelfile.${customName}`);
|
|
41401
|
+
writeFileSync16(modelfilePath, modelfileContent + "\n", "utf8");
|
|
41402
|
+
process.stdout.write(` ${c2.dim("Creating model...")} `);
|
|
41403
|
+
execSync27(`ollama create ${customName} -f ${modelfilePath}`, {
|
|
41404
|
+
stdio: "pipe",
|
|
41350
41405
|
timeout: 12e4
|
|
41351
41406
|
});
|
|
41352
|
-
|
|
41353
|
-
|
|
41354
|
-
|
|
41407
|
+
process.stdout.write(`${c2.green("\u2714")}
|
|
41408
|
+
`);
|
|
41409
|
+
setConfigValue("model", customName);
|
|
41410
|
+
process.stdout.write(`
|
|
41411
|
+
${c2.green("\u2714")} Model ${c2.bold(customName)} created with ${ctx.label} context.
|
|
41412
|
+
`);
|
|
41413
|
+
process.stdout.write(` ${c2.green("\u2714")} Saved as default model in config.
|
|
41414
|
+
|
|
41415
|
+
`);
|
|
41416
|
+
return customName;
|
|
41417
|
+
} catch (err) {
|
|
41418
|
+
renderWarning(`Could not create custom model: ${err instanceof Error ? err.message : String(err)}`);
|
|
41419
|
+
renderInfo(`Using base model ${selectedVariant.tag} instead.`);
|
|
41355
41420
|
}
|
|
41356
41421
|
}
|
|
41357
|
-
|
|
41358
|
-
|
|
41422
|
+
setConfigValue("model", selectedVariant.tag);
|
|
41423
|
+
process.stdout.write(`
|
|
41424
|
+
${c2.green("\u2714")} Saved ${c2.bold(selectedVariant.tag)} as default model.
|
|
41425
|
+
|
|
41426
|
+
`);
|
|
41427
|
+
return selectedVariant.tag;
|
|
41359
41428
|
}
|
|
41360
|
-
|
|
41429
|
+
setConfigValue("model", selectedVariant.tag);
|
|
41430
|
+
process.stdout.write(`
|
|
41431
|
+
${c2.green("\u2714")} Saved ${c2.bold(selectedVariant.tag)} as default model.
|
|
41432
|
+
|
|
41433
|
+
`);
|
|
41434
|
+
return selectedVariant.tag;
|
|
41361
41435
|
}
|
|
41362
|
-
function
|
|
41363
|
-
const shell = process.env.SHELL ?? "";
|
|
41364
|
-
const rcFile = shell.includes("zsh") ? join54(homedir13(), ".zshrc") : join54(homedir13(), ".bashrc");
|
|
41436
|
+
async function isModelAvailable(config) {
|
|
41365
41437
|
try {
|
|
41366
|
-
const
|
|
41367
|
-
|
|
41368
|
-
return;
|
|
41369
|
-
const exportLine = `
|
|
41370
|
-
export PATH="${binDir}:$PATH" # Added by open-agents for nvim
|
|
41371
|
-
`;
|
|
41372
|
-
appendFileSync2(rcFile, exportLine, "utf8");
|
|
41373
|
-
console.log(` Added ${binDir} to ${rcFile}`);
|
|
41438
|
+
const models = await fetchModels(config.backendUrl, config.apiKey);
|
|
41439
|
+
return !!findModel(models, config.model);
|
|
41374
41440
|
} catch {
|
|
41441
|
+
return false;
|
|
41375
41442
|
}
|
|
41376
41443
|
}
|
|
41377
|
-
|
|
41378
|
-
|
|
41379
|
-
|
|
41380
|
-
|
|
41381
|
-
|
|
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);
|
|
41444
|
+
function isFirstRun() {
|
|
41445
|
+
try {
|
|
41446
|
+
return !existsSync37(join54(homedir13(), ".open-agents", "config.json"));
|
|
41447
|
+
} catch {
|
|
41448
|
+
return true;
|
|
41421
41449
|
}
|
|
41422
|
-
return process.stdout.write(data);
|
|
41423
41450
|
}
|
|
41424
|
-
function
|
|
41425
|
-
|
|
41451
|
+
function hasCmd(cmd) {
|
|
41452
|
+
try {
|
|
41453
|
+
const whichCmd = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
41454
|
+
execSync27(whichCmd, { stdio: "pipe", timeout: 3e3 });
|
|
41455
|
+
return true;
|
|
41456
|
+
} catch {
|
|
41426
41457
|
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
41458
|
}
|
|
41451
41459
|
}
|
|
41452
|
-
function
|
|
41453
|
-
|
|
41454
|
-
if (
|
|
41455
|
-
|
|
41456
|
-
|
|
41457
|
-
|
|
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
|
-
}
|
|
41460
|
+
function detectPkgManager() {
|
|
41461
|
+
const plat = process.platform;
|
|
41462
|
+
if (plat === "win32") {
|
|
41463
|
+
if (hasCmd("choco"))
|
|
41464
|
+
return "choco";
|
|
41465
|
+
return null;
|
|
41479
41466
|
}
|
|
41480
|
-
|
|
41481
|
-
|
|
41482
|
-
|
|
41483
|
-
|
|
41484
|
-
"use strict";
|
|
41485
|
-
_overlayActive = false;
|
|
41486
|
-
_depth = 0;
|
|
41487
|
-
_buffer = [];
|
|
41488
|
-
_origStdoutWrite = null;
|
|
41489
|
-
_origStderrWrite = null;
|
|
41490
|
-
_onLeaveCallback = null;
|
|
41467
|
+
if (plat === "darwin") {
|
|
41468
|
+
if (hasCmd("brew"))
|
|
41469
|
+
return "brew";
|
|
41470
|
+
return null;
|
|
41491
41471
|
}
|
|
41492
|
-
|
|
41493
|
-
|
|
41494
|
-
|
|
41495
|
-
|
|
41496
|
-
|
|
41497
|
-
|
|
41498
|
-
|
|
41499
|
-
|
|
41500
|
-
|
|
41501
|
-
function stripAnsi(s) {
|
|
41502
|
-
return s.replace(/\x1B\[[0-9;]*m/g, "");
|
|
41472
|
+
if (hasCmd("apt-get"))
|
|
41473
|
+
return "apt";
|
|
41474
|
+
if (hasCmd("dnf"))
|
|
41475
|
+
return "dnf";
|
|
41476
|
+
if (hasCmd("pacman"))
|
|
41477
|
+
return "pacman";
|
|
41478
|
+
if (hasCmd("brew"))
|
|
41479
|
+
return "brew";
|
|
41480
|
+
return null;
|
|
41503
41481
|
}
|
|
41504
|
-
function
|
|
41505
|
-
|
|
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}`;
|
|
41482
|
+
function getVenvDir() {
|
|
41483
|
+
return join54(homedir13(), ".open-agents", "venv");
|
|
41509
41484
|
}
|
|
41510
|
-
function
|
|
41511
|
-
|
|
41512
|
-
|
|
41485
|
+
function hasVenvModule() {
|
|
41486
|
+
try {
|
|
41487
|
+
execSync27("python3 -m venv --help", { stdio: "pipe", timeout: 5e3 });
|
|
41488
|
+
return true;
|
|
41489
|
+
} catch {
|
|
41490
|
+
return false;
|
|
41513
41491
|
}
|
|
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
41492
|
}
|
|
41519
|
-
function
|
|
41520
|
-
const
|
|
41521
|
-
const
|
|
41522
|
-
const
|
|
41523
|
-
const
|
|
41524
|
-
if (
|
|
41525
|
-
return
|
|
41493
|
+
function ensureVenv(log) {
|
|
41494
|
+
const venvDir = getVenvDir();
|
|
41495
|
+
const isWin2 = process.platform === "win32";
|
|
41496
|
+
const pipPath = isWin2 ? join54(venvDir, "Scripts", "pip.exe") : join54(venvDir, "bin", "pip");
|
|
41497
|
+
const pythonCmd = isWin2 ? "python" : "python3";
|
|
41498
|
+
if (existsSync37(pipPath))
|
|
41499
|
+
return venvDir;
|
|
41500
|
+
log("Creating Python venv for vision deps...");
|
|
41501
|
+
if (!hasCmd(pythonCmd) && !hasCmd("python3")) {
|
|
41502
|
+
log(`${pythonCmd} not found \u2014 cannot create venv.`);
|
|
41503
|
+
return null;
|
|
41526
41504
|
}
|
|
41527
|
-
|
|
41528
|
-
|
|
41529
|
-
|
|
41530
|
-
|
|
41531
|
-
|
|
41532
|
-
|
|
41533
|
-
|
|
41534
|
-
|
|
41535
|
-
}
|
|
41536
|
-
|
|
41537
|
-
|
|
41538
|
-
|
|
41539
|
-
|
|
41540
|
-
|
|
41541
|
-
|
|
41542
|
-
|
|
41543
|
-
|
|
41544
|
-
|
|
41545
|
-
|
|
41546
|
-
|
|
41547
|
-
|
|
41505
|
+
if (!isWin2 && !hasVenvModule()) {
|
|
41506
|
+
log("python3 venv module not available \u2014 venv creation skipped.");
|
|
41507
|
+
return null;
|
|
41508
|
+
}
|
|
41509
|
+
try {
|
|
41510
|
+
mkdirSync15(join54(homedir13(), ".open-agents"), { recursive: true });
|
|
41511
|
+
const pyCmd = hasCmd(pythonCmd) ? pythonCmd : "python3";
|
|
41512
|
+
execSync27(`${pyCmd} -m venv "${venvDir}"`, { stdio: "pipe", timeout: 3e4 });
|
|
41513
|
+
execSync27(`"${pipPath}" install --upgrade pip`, {
|
|
41514
|
+
stdio: "pipe",
|
|
41515
|
+
timeout: 6e4
|
|
41516
|
+
});
|
|
41517
|
+
log("Python venv created at ~/.open-agents/venv");
|
|
41518
|
+
return venvDir;
|
|
41519
|
+
} catch (err) {
|
|
41520
|
+
log(`Failed to create venv: ${err instanceof Error ? err.message : String(err)}`);
|
|
41521
|
+
return null;
|
|
41522
|
+
}
|
|
41523
|
+
}
|
|
41524
|
+
function trySudoPasswordless(cmd, timeoutMs = 12e4) {
|
|
41525
|
+
try {
|
|
41526
|
+
execSync27(`sudo -n ${cmd}`, {
|
|
41527
|
+
stdio: "pipe",
|
|
41528
|
+
timeout: timeoutMs,
|
|
41529
|
+
env: { ...process.env, DEBIAN_FRONTEND: "noninteractive" }
|
|
41530
|
+
});
|
|
41531
|
+
return true;
|
|
41532
|
+
} catch {
|
|
41533
|
+
return false;
|
|
41548
41534
|
}
|
|
41549
|
-
|
|
41550
|
-
|
|
41551
|
-
|
|
41552
|
-
|
|
41553
|
-
|
|
41554
|
-
|
|
41555
|
-
|
|
41535
|
+
}
|
|
41536
|
+
function runWithSudo(cmd, password, timeoutMs = 12e4) {
|
|
41537
|
+
try {
|
|
41538
|
+
const escaped = cmd.replace(/'/g, "'\\''");
|
|
41539
|
+
execSync27(`sudo -S bash -c '${escaped}'`, {
|
|
41540
|
+
input: password + "\n",
|
|
41541
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
41542
|
+
timeout: timeoutMs,
|
|
41543
|
+
env: { ...process.env, DEBIAN_FRONTEND: "noninteractive" }
|
|
41544
|
+
});
|
|
41545
|
+
return "ok";
|
|
41546
|
+
} catch (err) {
|
|
41547
|
+
const stderr = err?.stderr?.toString() ?? "";
|
|
41548
|
+
if (/incorrect password|authentication failure|Sorry, try again|not in the sudoers/i.test(stderr)) {
|
|
41549
|
+
return "auth_fail";
|
|
41556
41550
|
}
|
|
41557
|
-
return
|
|
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;
|
|
41551
|
+
return "cmd_fail";
|
|
41563
41552
|
}
|
|
41564
|
-
|
|
41565
|
-
|
|
41566
|
-
|
|
41567
|
-
|
|
41568
|
-
|
|
41569
|
-
|
|
41570
|
-
|
|
41571
|
-
|
|
41572
|
-
|
|
41573
|
-
|
|
41574
|
-
|
|
41575
|
-
|
|
41576
|
-
|
|
41577
|
-
|
|
41578
|
-
|
|
41579
|
-
|
|
41580
|
-
|
|
41581
|
-
|
|
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));
|
|
41553
|
+
}
|
|
41554
|
+
function validateSudoPassword(password) {
|
|
41555
|
+
return runWithSudo("true", password, 1e4) === "ok";
|
|
41556
|
+
}
|
|
41557
|
+
async function acquireSudoPassword(getSudoPassword, log, cachedPasswordRef) {
|
|
41558
|
+
if (cachedPasswordRef.value && validateSudoPassword(cachedPasswordRef.value)) {
|
|
41559
|
+
return cachedPasswordRef.value;
|
|
41560
|
+
}
|
|
41561
|
+
if (trySudoPasswordless("true", 1e4)) {
|
|
41562
|
+
return "";
|
|
41563
|
+
}
|
|
41564
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
41565
|
+
if (attempt > 0)
|
|
41566
|
+
log("Authentication failed \u2014 please re-enter password.");
|
|
41567
|
+
const pw = await getSudoPassword();
|
|
41568
|
+
if (!pw)
|
|
41569
|
+
return null;
|
|
41570
|
+
if (validateSudoPassword(pw)) {
|
|
41571
|
+
cachedPasswordRef.value = pw;
|
|
41572
|
+
return pw;
|
|
41603
41573
|
}
|
|
41604
|
-
|
|
41605
|
-
|
|
41606
|
-
|
|
41607
|
-
|
|
41608
|
-
|
|
41609
|
-
|
|
41610
|
-
|
|
41611
|
-
|
|
41612
|
-
|
|
41613
|
-
|
|
41614
|
-
|
|
41615
|
-
|
|
41616
|
-
|
|
41617
|
-
|
|
41618
|
-
|
|
41619
|
-
|
|
41620
|
-
|
|
41621
|
-
|
|
41574
|
+
}
|
|
41575
|
+
return null;
|
|
41576
|
+
}
|
|
41577
|
+
async function sudoInstall(cmd, getSudoPassword, log, cachedPasswordRef, timeoutMs = 12e4) {
|
|
41578
|
+
if (trySudoPasswordless(cmd, timeoutMs))
|
|
41579
|
+
return true;
|
|
41580
|
+
const pw = await acquireSudoPassword(getSudoPassword, log, cachedPasswordRef);
|
|
41581
|
+
if (pw === null)
|
|
41582
|
+
return false;
|
|
41583
|
+
if (pw === "")
|
|
41584
|
+
return false;
|
|
41585
|
+
const result = runWithSudo(cmd, pw, timeoutMs);
|
|
41586
|
+
if (result === "ok")
|
|
41587
|
+
return true;
|
|
41588
|
+
if (result === "cmd_fail") {
|
|
41589
|
+
log(`Install command failed (not an auth issue) \u2014 package may not be available for this OS.`);
|
|
41590
|
+
}
|
|
41591
|
+
return false;
|
|
41592
|
+
}
|
|
41593
|
+
async function ensureVisionDeps(onInfo, getSudoPassword) {
|
|
41594
|
+
const log = onInfo ?? (() => {
|
|
41595
|
+
});
|
|
41596
|
+
const cachedPasswordRef = { value: null };
|
|
41597
|
+
const getPassword = getSudoPassword ?? (() => Promise.resolve(null));
|
|
41598
|
+
const allDeps = [
|
|
41599
|
+
{ binary: "tesseract", label: "tesseract-ocr", pkgs: { apt: "tesseract-ocr", dnf: "tesseract", pacman: "tesseract", brew: "tesseract", choco: "tesseract" } },
|
|
41600
|
+
{ binary: "pdftotext", label: "poppler-utils", pkgs: { apt: "poppler-utils", dnf: "poppler-utils", pacman: "poppler", brew: "poppler", choco: "poppler" } },
|
|
41601
|
+
{ binary: "gs", label: "ghostscript", pkgs: { apt: "ghostscript", dnf: "ghostscript", pacman: "ghostscript", brew: "ghostscript", choco: "ghostscript" } },
|
|
41602
|
+
{ binary: "ocrmypdf", label: "ocrmypdf", pkgs: { apt: "ocrmypdf", dnf: "ocrmypdf", pacman: "ocrmypdf", brew: "ocrmypdf", choco: "ocrmypdf" } }
|
|
41603
|
+
];
|
|
41604
|
+
{
|
|
41605
|
+
const pm2 = detectPkgManager();
|
|
41606
|
+
const missing = allDeps.filter((d) => !hasCmd(d.binary));
|
|
41607
|
+
if (missing.length === 0) {
|
|
41608
|
+
} else if (!pm2) {
|
|
41609
|
+
for (const d of missing)
|
|
41610
|
+
log(`No supported package manager \u2014 ${d.label} unavailable.`);
|
|
41611
|
+
} else {
|
|
41612
|
+
const pkgNames = missing.map((d) => d.pkgs[pm2]).filter(Boolean);
|
|
41613
|
+
if (pkgNames.length === 0) {
|
|
41614
|
+
for (const d of missing)
|
|
41615
|
+
log(`${d.label} not available for ${pm2}.`);
|
|
41622
41616
|
} else {
|
|
41623
|
-
|
|
41624
|
-
|
|
41625
|
-
|
|
41626
|
-
|
|
41627
|
-
|
|
41628
|
-
|
|
41629
|
-
|
|
41630
|
-
|
|
41631
|
-
|
|
41632
|
-
|
|
41633
|
-
|
|
41634
|
-
|
|
41635
|
-
|
|
41636
|
-
|
|
41637
|
-
|
|
41638
|
-
|
|
41639
|
-
|
|
41617
|
+
const labels = missing.map((d) => d.label).join(", ");
|
|
41618
|
+
log(`Installing ${labels}...`);
|
|
41619
|
+
const needsSudo = pm2 !== "brew" && pm2 !== "choco";
|
|
41620
|
+
let batchCmd;
|
|
41621
|
+
switch (pm2) {
|
|
41622
|
+
case "apt":
|
|
41623
|
+
batchCmd = `apt-get update -qq && apt-get install -y -qq ${pkgNames.join(" ")}`;
|
|
41624
|
+
break;
|
|
41625
|
+
case "dnf":
|
|
41626
|
+
batchCmd = `dnf install -y -q ${pkgNames.join(" ")}`;
|
|
41627
|
+
break;
|
|
41628
|
+
case "pacman":
|
|
41629
|
+
batchCmd = `pacman -S --noconfirm ${pkgNames.join(" ")}`;
|
|
41630
|
+
break;
|
|
41631
|
+
case "brew":
|
|
41632
|
+
batchCmd = `brew install ${pkgNames.join(" ")}`;
|
|
41633
|
+
break;
|
|
41634
|
+
case "choco":
|
|
41635
|
+
batchCmd = `choco install -y ${pkgNames.join(" ")}`;
|
|
41636
|
+
break;
|
|
41637
|
+
default:
|
|
41638
|
+
batchCmd = `echo "unsupported pm: ${pm2}" && exit 1`;
|
|
41640
41639
|
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
41640
|
}
|
|
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));
|
|
41641
|
+
let ok = false;
|
|
41642
|
+
if (needsSudo) {
|
|
41643
|
+
ok = await sudoInstall(batchCmd, getPassword, log, cachedPasswordRef, 18e4);
|
|
41669
41644
|
} 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();
|
|
41645
|
+
try {
|
|
41646
|
+
execSync27(batchCmd, { stdio: "pipe", timeout: 18e4 });
|
|
41647
|
+
ok = true;
|
|
41648
|
+
} catch {
|
|
41649
|
+
ok = false;
|
|
41801
41650
|
}
|
|
41802
41651
|
}
|
|
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
|
-
});
|
|
41652
|
+
for (const d of missing) {
|
|
41653
|
+
if (hasCmd(d.binary)) {
|
|
41654
|
+
log(`${d.label} installed.`);
|
|
41655
|
+
} else if (!ok) {
|
|
41656
|
+
log(`${d.label} could not be installed \u2014 features will be limited.`);
|
|
41837
41657
|
} else {
|
|
41838
|
-
|
|
41839
|
-
deleteConfirmSel = false;
|
|
41840
|
-
render();
|
|
41658
|
+
log(`${d.label} install completed but binary not found.`);
|
|
41841
41659
|
}
|
|
41842
|
-
} else if (seq === "\x1B" || seq === "\x1B\x1B" || seq === "") {
|
|
41843
|
-
deleteConfirmIdx = -1;
|
|
41844
|
-
deleteConfirmSel = false;
|
|
41845
|
-
render();
|
|
41846
41660
|
}
|
|
41847
|
-
return;
|
|
41848
41661
|
}
|
|
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();
|
|
41904
|
-
}
|
|
41905
|
-
} else if (seq === " " && opts.onAction) {
|
|
41906
|
-
if (!isSkippable(cursor) && matchSet.has(cursor)) {
|
|
41907
|
-
if (opts.onAction(items[cursor], "space"))
|
|
41908
|
-
render();
|
|
41662
|
+
}
|
|
41663
|
+
}
|
|
41664
|
+
const pm = detectPkgManager();
|
|
41665
|
+
if (!hasCmd("pip3") && !hasCmd("pip") && pm) {
|
|
41666
|
+
const pipCmds = {
|
|
41667
|
+
apt: "apt-get install -y python3-pip",
|
|
41668
|
+
dnf: "dnf install -y python3-pip"
|
|
41669
|
+
};
|
|
41670
|
+
const pipCmd = pipCmds[pm];
|
|
41671
|
+
if (pipCmd) {
|
|
41672
|
+
log("Installing python3-pip...");
|
|
41673
|
+
const ok = await sudoInstall(pipCmd, getPassword, log, cachedPasswordRef);
|
|
41674
|
+
if (!ok) {
|
|
41675
|
+
log("python3-pip could not be installed \u2014 moondream-station may be unavailable.");
|
|
41676
|
+
}
|
|
41677
|
+
}
|
|
41678
|
+
}
|
|
41679
|
+
if (hasCmd("python3") && !hasVenvModule() && pm) {
|
|
41680
|
+
const venvCmds = {
|
|
41681
|
+
apt: () => {
|
|
41682
|
+
try {
|
|
41683
|
+
const pyVer = execSync27(`python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 }).trim();
|
|
41684
|
+
return `apt-get install -y python3-venv python${pyVer}-venv`;
|
|
41685
|
+
} catch {
|
|
41686
|
+
return "apt-get install -y python3-venv";
|
|
41909
41687
|
}
|
|
41910
|
-
}
|
|
41911
|
-
|
|
41912
|
-
|
|
41913
|
-
|
|
41914
|
-
|
|
41915
|
-
|
|
41916
|
-
|
|
41917
|
-
|
|
41918
|
-
|
|
41919
|
-
|
|
41920
|
-
|
|
41921
|
-
|
|
41922
|
-
|
|
41923
|
-
|
|
41924
|
-
|
|
41925
|
-
|
|
41926
|
-
|
|
41927
|
-
|
|
41688
|
+
},
|
|
41689
|
+
dnf: () => "dnf install -y python3-venv"
|
|
41690
|
+
};
|
|
41691
|
+
const cmdFn = venvCmds[pm];
|
|
41692
|
+
if (cmdFn) {
|
|
41693
|
+
const cmd = cmdFn();
|
|
41694
|
+
if (cmd) {
|
|
41695
|
+
log("Installing python3-venv...");
|
|
41696
|
+
const ok = await sudoInstall(cmd, getPassword, log, cachedPasswordRef);
|
|
41697
|
+
if (!ok) {
|
|
41698
|
+
log("python3-venv could not be installed \u2014 moondream-station may be unavailable.");
|
|
41699
|
+
}
|
|
41700
|
+
}
|
|
41701
|
+
}
|
|
41702
|
+
}
|
|
41703
|
+
const venvDir = getVenvDir();
|
|
41704
|
+
const venvBin = join54(venvDir, "bin");
|
|
41705
|
+
const venvMoondream = join54(venvBin, "moondream-station");
|
|
41706
|
+
const venv = ensureVenv(log);
|
|
41707
|
+
if (venv && !hasCmd("moondream-station") && !existsSync37(venvMoondream)) {
|
|
41708
|
+
const venvPip2 = join54(venvBin, "pip");
|
|
41709
|
+
log("Installing moondream-station in ~/.open-agents/venv...");
|
|
41710
|
+
try {
|
|
41711
|
+
execSync27(`"${venvPip2}" install moondream-station`, { stdio: "pipe", timeout: 3e5 });
|
|
41712
|
+
if (existsSync37(venvMoondream)) {
|
|
41713
|
+
log("moondream-station installed successfully.");
|
|
41714
|
+
} else {
|
|
41715
|
+
try {
|
|
41716
|
+
const check = execSync27(`"${venvPip2}" show moondream-station`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 });
|
|
41717
|
+
if (check.includes("moondream")) {
|
|
41718
|
+
log("moondream-station package installed.");
|
|
41928
41719
|
}
|
|
41929
|
-
|
|
41930
|
-
|
|
41720
|
+
} catch {
|
|
41721
|
+
log("moondream-station install completed.");
|
|
41931
41722
|
}
|
|
41932
|
-
}
|
|
41933
|
-
|
|
41934
|
-
|
|
41935
|
-
|
|
41936
|
-
|
|
41937
|
-
|
|
41938
|
-
|
|
41939
|
-
|
|
41940
|
-
|
|
41941
|
-
|
|
41942
|
-
|
|
41943
|
-
|
|
41944
|
-
|
|
41945
|
-
|
|
41946
|
-
|
|
41723
|
+
}
|
|
41724
|
+
} catch (err) {
|
|
41725
|
+
log(`moondream-station install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
41726
|
+
}
|
|
41727
|
+
}
|
|
41728
|
+
if (venv) {
|
|
41729
|
+
const venvPython2 = join54(venvBin, "python");
|
|
41730
|
+
const venvPip2 = join54(venvBin, "pip");
|
|
41731
|
+
let ocrStackInstalled = false;
|
|
41732
|
+
try {
|
|
41733
|
+
execSync27(`"${venvPython2}" -c "import cv2, pytesseract, numpy, PIL"`, { stdio: "pipe", timeout: 1e4 });
|
|
41734
|
+
ocrStackInstalled = true;
|
|
41735
|
+
} catch {
|
|
41736
|
+
}
|
|
41737
|
+
if (!ocrStackInstalled) {
|
|
41738
|
+
const ocrPackages = "pytesseract Pillow opencv-python-headless numpy";
|
|
41739
|
+
log("Installing OCR Python stack (pytesseract, OpenCV, Pillow, numpy)...");
|
|
41740
|
+
try {
|
|
41741
|
+
execSync27(`"${venvPip2}" install ${ocrPackages}`, { stdio: "pipe", timeout: 3e5 });
|
|
41742
|
+
try {
|
|
41743
|
+
execSync27(`"${venvPython2}" -c "import cv2, pytesseract, numpy, PIL"`, { stdio: "pipe", timeout: 1e4 });
|
|
41744
|
+
log("OCR Python stack installed successfully.");
|
|
41745
|
+
} catch {
|
|
41746
|
+
log("OCR Python stack install completed but import verification failed.");
|
|
41947
41747
|
}
|
|
41948
|
-
}
|
|
41949
|
-
|
|
41950
|
-
|
|
41951
|
-
|
|
41952
|
-
|
|
41953
|
-
|
|
41954
|
-
|
|
41955
|
-
|
|
41956
|
-
|
|
41957
|
-
|
|
41958
|
-
|
|
41959
|
-
|
|
41960
|
-
|
|
41961
|
-
|
|
41962
|
-
|
|
41963
|
-
|
|
41964
|
-
|
|
41748
|
+
} catch (err) {
|
|
41749
|
+
log(`OCR Python stack install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
41750
|
+
}
|
|
41751
|
+
}
|
|
41752
|
+
} else {
|
|
41753
|
+
log("Python venv unavailable \u2014 advanced OCR pipeline will fall back to basic tesseract.");
|
|
41754
|
+
}
|
|
41755
|
+
}
|
|
41756
|
+
function ensureCloudflaredBackground(onInfo) {
|
|
41757
|
+
if (_cloudflaredInstallPromise)
|
|
41758
|
+
return;
|
|
41759
|
+
if (hasCmd("cloudflared")) {
|
|
41760
|
+
_cloudflaredInstallPromise = Promise.resolve(true);
|
|
41761
|
+
return;
|
|
41762
|
+
}
|
|
41763
|
+
const log = onInfo ?? (() => {
|
|
41764
|
+
});
|
|
41765
|
+
log("Installing cloudflared for live voice sessions...");
|
|
41766
|
+
_cloudflaredInstallPromise = (async () => {
|
|
41767
|
+
const arch2 = process.arch;
|
|
41768
|
+
const os = platform2();
|
|
41769
|
+
if (os !== "win32" && !ensureCurl()) {
|
|
41770
|
+
log("curl not available \u2014 cannot install cloudflared.");
|
|
41771
|
+
return false;
|
|
41772
|
+
}
|
|
41773
|
+
if (os === "linux") {
|
|
41774
|
+
const archMap = { x64: "amd64", arm64: "arm64", arm: "arm" };
|
|
41775
|
+
const cfArch = archMap[arch2] ?? "amd64";
|
|
41776
|
+
try {
|
|
41777
|
+
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 });
|
|
41778
|
+
if (!process.env.PATH?.includes(`${homedir13()}/.local/bin`)) {
|
|
41779
|
+
process.env.PATH = `${homedir13()}/.local/bin:${process.env.PATH}`;
|
|
41965
41780
|
}
|
|
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;
|
|
41781
|
+
if (hasCmd("cloudflared")) {
|
|
41782
|
+
log("cloudflared installed.");
|
|
41783
|
+
return true;
|
|
41983
41784
|
}
|
|
41984
|
-
|
|
41985
|
-
|
|
41986
|
-
|
|
41987
|
-
|
|
41988
|
-
|
|
41989
|
-
|
|
41785
|
+
} catch {
|
|
41786
|
+
}
|
|
41787
|
+
try {
|
|
41788
|
+
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 });
|
|
41789
|
+
if (hasCmd("cloudflared")) {
|
|
41790
|
+
log("cloudflared installed.");
|
|
41791
|
+
return true;
|
|
41990
41792
|
}
|
|
41991
|
-
|
|
41992
|
-
render();
|
|
41793
|
+
} catch {
|
|
41993
41794
|
}
|
|
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");
|
|
41795
|
+
} else if (os === "darwin") {
|
|
41796
|
+
try {
|
|
41797
|
+
execSync27("brew install cloudflared", { stdio: "pipe", timeout: 12e4 });
|
|
41798
|
+
if (hasCmd("cloudflared")) {
|
|
41799
|
+
log("cloudflared installed via Homebrew.");
|
|
41800
|
+
return true;
|
|
42013
41801
|
}
|
|
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
|
-
|
|
41802
|
+
} catch {
|
|
41803
|
+
}
|
|
41804
|
+
}
|
|
41805
|
+
log("cloudflared not available \u2014 live voice sessions disabled.");
|
|
41806
|
+
return false;
|
|
41807
|
+
})();
|
|
41808
|
+
}
|
|
41809
|
+
function expandedModelName(baseModel) {
|
|
41810
|
+
return `open-agents-${baseModel.replace(":", "-").replace(/\./g, "")}`;
|
|
41811
|
+
}
|
|
41812
|
+
async function checkExpandedVariant(modelName, backendUrl) {
|
|
41813
|
+
if (modelName.startsWith("open-agents-"))
|
|
41814
|
+
return null;
|
|
41815
|
+
const target = expandedModelName(modelName);
|
|
41816
|
+
try {
|
|
41817
|
+
const models = await fetchOllamaModels(backendUrl);
|
|
41818
|
+
const found = models.find((m) => m.name === target || m.name.startsWith(target + ":"));
|
|
41819
|
+
return found ? found.name : false;
|
|
41820
|
+
} catch {
|
|
41821
|
+
return false;
|
|
41822
|
+
}
|
|
41823
|
+
}
|
|
41824
|
+
function modelSizeGB(models, modelName) {
|
|
41825
|
+
const m = findModel(models, modelName);
|
|
41826
|
+
if (m)
|
|
41827
|
+
return m.sizeBytes / 1024 ** 3;
|
|
41828
|
+
const known = QWEN_VARIANTS.find((v) => modelName.includes(v.tag.split(":")[1] ?? ""));
|
|
41829
|
+
return known?.sizeGB ?? 4;
|
|
41830
|
+
}
|
|
41831
|
+
async function queryModelKVInfo(backendUrl, modelName) {
|
|
41832
|
+
try {
|
|
41833
|
+
const normalized = backendUrl.replace(/\/+$/, "");
|
|
41834
|
+
const res = await fetch(`${normalized}/api/show`, {
|
|
41835
|
+
method: "POST",
|
|
41836
|
+
headers: { "Content-Type": "application/json" },
|
|
41837
|
+
body: JSON.stringify({ name: modelName }),
|
|
41838
|
+
signal: AbortSignal.timeout(5e3)
|
|
41839
|
+
});
|
|
41840
|
+
if (!res.ok)
|
|
41841
|
+
return null;
|
|
41842
|
+
const data = await res.json();
|
|
41843
|
+
if (!data.model_info)
|
|
41844
|
+
return null;
|
|
41845
|
+
const info = data.model_info;
|
|
41846
|
+
const arch2 = info["general.architecture"];
|
|
41847
|
+
if (!arch2)
|
|
41848
|
+
return null;
|
|
41849
|
+
const nLayers = info[`${arch2}.block_count`];
|
|
41850
|
+
const nKVHeads = info[`${arch2}.attention.head_count_kv`] ?? info[`${arch2}.attention.head_count`];
|
|
41851
|
+
const keyDim = info[`${arch2}.attention.key_length`];
|
|
41852
|
+
const valDim = info[`${arch2}.attention.value_length`] ?? keyDim;
|
|
41853
|
+
const archMax = info[`${arch2}.context_length`];
|
|
41854
|
+
if (!nLayers || !nKVHeads || !keyDim || !valDim || !archMax)
|
|
41855
|
+
return null;
|
|
41856
|
+
const kvBytesPerToken = nLayers * nKVHeads * (keyDim + valDim) * 2;
|
|
41857
|
+
return { kvBytesPerToken, archMax };
|
|
41858
|
+
} catch {
|
|
41859
|
+
return null;
|
|
41860
|
+
}
|
|
41861
|
+
}
|
|
41862
|
+
async function createExpandedVariantAsync(baseModel, specs, sizeGB, kvBytesPerToken, archMax) {
|
|
41863
|
+
const customName = expandedModelName(baseModel);
|
|
41864
|
+
const ctx = calculateContextWindow(specs, sizeGB, kvBytesPerToken, archMax);
|
|
41865
|
+
try {
|
|
41866
|
+
const numPredict = Math.min(16384, Math.max(2048, Math.floor(ctx.numCtx * 0.25)));
|
|
41867
|
+
const modelfileContent = [
|
|
41868
|
+
`FROM ${baseModel}`,
|
|
41869
|
+
`PARAMETER num_ctx ${ctx.numCtx}`,
|
|
41870
|
+
`PARAMETER temperature 0`,
|
|
41871
|
+
`PARAMETER num_predict ${numPredict}`,
|
|
41872
|
+
`PARAMETER stop "<|endoftext|>"`
|
|
41873
|
+
].join("\n");
|
|
41874
|
+
const modelDir2 = join54(homedir13(), ".open-agents", "models");
|
|
41875
|
+
mkdirSync15(modelDir2, { recursive: true });
|
|
41876
|
+
const modelfilePath = join54(modelDir2, `Modelfile.${customName}`);
|
|
41877
|
+
writeFileSync16(modelfilePath, modelfileContent + "\n", "utf8");
|
|
41878
|
+
await execAsync(`ollama create ${customName} -f ${modelfilePath}`, {
|
|
41879
|
+
timeout: 12e4
|
|
41880
|
+
});
|
|
41881
|
+
return customName;
|
|
41882
|
+
} catch {
|
|
41883
|
+
return null;
|
|
41884
|
+
}
|
|
41885
|
+
}
|
|
41886
|
+
async function ensureExpandedContext(modelName, backendUrl) {
|
|
41887
|
+
if (modelName.includes("cloud") || modelName.includes(":cloud")) {
|
|
41888
|
+
return { model: modelName, created: false, contextLabel: "remote", numCtx: 0 };
|
|
41889
|
+
}
|
|
41890
|
+
const specs = await detectSystemSpecsAsync();
|
|
41891
|
+
const kvInfo = await queryModelKVInfo(backendUrl, modelName);
|
|
41892
|
+
let sizeGB = 4;
|
|
41893
|
+
try {
|
|
41894
|
+
const models = await fetchOllamaModels(backendUrl);
|
|
41895
|
+
sizeGB = modelSizeGB(models, modelName);
|
|
41896
|
+
} catch {
|
|
41897
|
+
}
|
|
41898
|
+
const ctx = calculateContextWindow(specs, sizeGB, kvInfo?.kvBytesPerToken, kvInfo?.archMax);
|
|
41899
|
+
if (modelName.startsWith("open-agents-")) {
|
|
41900
|
+
try {
|
|
41901
|
+
const normalized = backendUrl.replace(/\/+$/, "");
|
|
41902
|
+
const showRes = await fetch(`${normalized}/api/show`, {
|
|
41903
|
+
method: "POST",
|
|
41904
|
+
headers: { "Content-Type": "application/json" },
|
|
41905
|
+
body: JSON.stringify({ name: modelName }),
|
|
41906
|
+
signal: AbortSignal.timeout(5e3)
|
|
41907
|
+
});
|
|
41908
|
+
if (showRes.ok) {
|
|
41909
|
+
const showData = await showRes.json();
|
|
41910
|
+
const numCtxMatch = showData.parameters?.match(/num_ctx\s+(\d+)/);
|
|
41911
|
+
const currentNumCtx = numCtxMatch ? parseInt(numCtxMatch[1], 10) : 0;
|
|
41912
|
+
if (currentNumCtx !== ctx.numCtx) {
|
|
41913
|
+
const fromMatch = showData.modelfile?.match(/^FROM\s+(.+)$/m);
|
|
41914
|
+
const baseModel = fromMatch?.[1]?.trim();
|
|
41915
|
+
if (baseModel && !baseModel.startsWith("open-agents-")) {
|
|
41916
|
+
await createExpandedVariantAsync(baseModel, specs, sizeGB, kvInfo?.kvBytesPerToken, kvInfo?.archMax);
|
|
42054
41917
|
}
|
|
42055
41918
|
}
|
|
42056
|
-
|
|
42057
|
-
|
|
42058
|
-
renderInput();
|
|
42059
|
-
});
|
|
41919
|
+
}
|
|
41920
|
+
} catch {
|
|
42060
41921
|
}
|
|
42061
|
-
|
|
42062
|
-
|
|
42063
|
-
|
|
42064
|
-
|
|
42065
|
-
|
|
41922
|
+
return { model: modelName, created: false, contextLabel: ctx.label, numCtx: ctx.numCtx };
|
|
41923
|
+
}
|
|
41924
|
+
const existing = await checkExpandedVariant(modelName, backendUrl);
|
|
41925
|
+
if (existing === null) {
|
|
41926
|
+
return { model: modelName, created: false, contextLabel: "", numCtx: 0 };
|
|
41927
|
+
}
|
|
41928
|
+
if (typeof existing === "string") {
|
|
41929
|
+
return { model: existing, created: false, contextLabel: ctx.label, numCtx: ctx.numCtx };
|
|
41930
|
+
}
|
|
41931
|
+
const created = await createExpandedVariantAsync(modelName, specs, sizeGB, kvInfo?.kvBytesPerToken, kvInfo?.archMax);
|
|
41932
|
+
if (created) {
|
|
41933
|
+
return { model: created, created: true, contextLabel: ctx.label, numCtx: ctx.numCtx };
|
|
41934
|
+
}
|
|
41935
|
+
return { model: modelName, created: false, contextLabel: ctx.label, numCtx: ctx.numCtx };
|
|
42066
41936
|
}
|
|
42067
|
-
|
|
42068
|
-
|
|
42069
|
-
|
|
41937
|
+
async function ensureNeovim() {
|
|
41938
|
+
try {
|
|
41939
|
+
const nvimPath = execSync27("which nvim 2>/dev/null || where nvim 2>nul", {
|
|
41940
|
+
encoding: "utf8",
|
|
41941
|
+
stdio: "pipe",
|
|
41942
|
+
timeout: 5e3
|
|
41943
|
+
}).trim();
|
|
41944
|
+
if (nvimPath)
|
|
41945
|
+
return nvimPath;
|
|
41946
|
+
} catch {
|
|
41947
|
+
}
|
|
41948
|
+
const platform6 = process.platform;
|
|
41949
|
+
const arch2 = process.arch;
|
|
41950
|
+
if (platform6 === "linux") {
|
|
41951
|
+
const binDir = join54(homedir13(), ".local", "bin");
|
|
41952
|
+
const nvimDest = join54(binDir, "nvim");
|
|
41953
|
+
try {
|
|
41954
|
+
mkdirSync15(binDir, { recursive: true });
|
|
41955
|
+
} catch {
|
|
41956
|
+
}
|
|
41957
|
+
const appImageName = arch2 === "arm64" ? "nvim-linux-arm64.appimage" : "nvim-linux-x86_64.appimage";
|
|
41958
|
+
const url = `https://github.com/neovim/neovim/releases/latest/download/${appImageName}`;
|
|
41959
|
+
console.log(` Downloading Neovim (${appImageName})...`);
|
|
41960
|
+
try {
|
|
41961
|
+
execSync27(`curl -fsSL "${url}" -o "${nvimDest}"`, { stdio: "pipe", timeout: 6e4 });
|
|
41962
|
+
execSync27(`chmod +x "${nvimDest}"`, { stdio: "pipe", timeout: 3e3 });
|
|
41963
|
+
} catch (err) {
|
|
41964
|
+
console.log(` Failed to download Neovim: ${err instanceof Error ? err.message : String(err)}`);
|
|
41965
|
+
return null;
|
|
41966
|
+
}
|
|
41967
|
+
try {
|
|
41968
|
+
const ver = execSync27(`"${nvimDest}" --version`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 }).split("\n")[0];
|
|
41969
|
+
console.log(` Installed: ${ver}`);
|
|
41970
|
+
} catch {
|
|
41971
|
+
console.log(" Warning: nvim binary downloaded but may not work (missing FUSE? Try: nvim --appimage-extract)");
|
|
41972
|
+
}
|
|
41973
|
+
if (!process.env.PATH?.includes(binDir)) {
|
|
41974
|
+
process.env.PATH = `${binDir}:${process.env.PATH}`;
|
|
41975
|
+
}
|
|
41976
|
+
ensurePathInShellRc(binDir);
|
|
41977
|
+
return nvimDest;
|
|
41978
|
+
}
|
|
41979
|
+
if (platform6 === "darwin") {
|
|
41980
|
+
if (hasCmd("brew")) {
|
|
41981
|
+
console.log(" Installing Neovim via Homebrew...");
|
|
41982
|
+
try {
|
|
41983
|
+
execSync27("brew install neovim", { stdio: "inherit", timeout: 12e4 });
|
|
41984
|
+
const nvimPath = execSync27("which nvim", { encoding: "utf8", stdio: "pipe", timeout: 3e3 }).trim();
|
|
41985
|
+
return nvimPath || null;
|
|
41986
|
+
} catch {
|
|
41987
|
+
console.log(" brew install neovim failed.");
|
|
41988
|
+
return null;
|
|
41989
|
+
}
|
|
41990
|
+
}
|
|
41991
|
+
console.log(" Homebrew not found. Install Neovim: brew install neovim");
|
|
41992
|
+
return null;
|
|
41993
|
+
}
|
|
41994
|
+
if (platform6 === "win32") {
|
|
41995
|
+
if (hasCmd("choco")) {
|
|
41996
|
+
console.log(" Installing Neovim via Chocolatey...");
|
|
41997
|
+
try {
|
|
41998
|
+
execSync27("choco install neovim -y", { stdio: "inherit", timeout: 12e4 });
|
|
41999
|
+
return "nvim";
|
|
42000
|
+
} catch {
|
|
42001
|
+
console.log(" choco install neovim failed.");
|
|
42002
|
+
}
|
|
42003
|
+
}
|
|
42004
|
+
if (hasCmd("winget")) {
|
|
42005
|
+
console.log(" Installing Neovim via winget...");
|
|
42006
|
+
try {
|
|
42007
|
+
execSync27("winget install Neovim.Neovim --accept-source-agreements --accept-package-agreements", {
|
|
42008
|
+
stdio: "inherit",
|
|
42009
|
+
timeout: 12e4
|
|
42010
|
+
});
|
|
42011
|
+
return "nvim";
|
|
42012
|
+
} catch {
|
|
42013
|
+
console.log(" winget install neovim failed.");
|
|
42014
|
+
}
|
|
42015
|
+
}
|
|
42016
|
+
console.log(" Install Neovim manually: https://neovim.io");
|
|
42017
|
+
return null;
|
|
42018
|
+
}
|
|
42019
|
+
return null;
|
|
42020
|
+
}
|
|
42021
|
+
function ensurePathInShellRc(binDir) {
|
|
42022
|
+
const shell = process.env.SHELL ?? "";
|
|
42023
|
+
const rcFile = shell.includes("zsh") ? join54(homedir13(), ".zshrc") : join54(homedir13(), ".bashrc");
|
|
42024
|
+
try {
|
|
42025
|
+
const rcContent = existsSync37(rcFile) ? readFileSync28(rcFile, "utf8") : "";
|
|
42026
|
+
if (rcContent.includes(binDir))
|
|
42027
|
+
return;
|
|
42028
|
+
const exportLine = `
|
|
42029
|
+
export PATH="${binDir}:$PATH" # Added by open-agents for nvim
|
|
42030
|
+
`;
|
|
42031
|
+
appendFileSync2(rcFile, exportLine, "utf8");
|
|
42032
|
+
console.log(` Added ${binDir} to ${rcFile}`);
|
|
42033
|
+
} catch {
|
|
42034
|
+
}
|
|
42035
|
+
}
|
|
42036
|
+
var execAsync, QWEN_VARIANTS, _toolSupportCache, _cloudflaredInstallPromise;
|
|
42037
|
+
var init_setup = __esm({
|
|
42038
|
+
"packages/cli/dist/tui/setup.js"() {
|
|
42070
42039
|
"use strict";
|
|
42071
|
-
|
|
42072
|
-
|
|
42073
|
-
|
|
42074
|
-
|
|
42075
|
-
|
|
42076
|
-
|
|
42077
|
-
|
|
42078
|
-
|
|
42079
|
-
|
|
42080
|
-
|
|
42081
|
-
|
|
42082
|
-
|
|
42083
|
-
|
|
42040
|
+
init_model_picker();
|
|
42041
|
+
init_render();
|
|
42042
|
+
init_config();
|
|
42043
|
+
init_dist();
|
|
42044
|
+
init_tui_select();
|
|
42045
|
+
execAsync = promisify6(exec2);
|
|
42046
|
+
QWEN_VARIANTS = [
|
|
42047
|
+
{ tag: "qwen3.5:0.8b", sizeGB: 1, label: "0.8B params (1.0 GB)", cloud: false },
|
|
42048
|
+
{ tag: "qwen3.5:2b", sizeGB: 2.7, label: "2B params (2.7 GB)", cloud: false },
|
|
42049
|
+
{ tag: "qwen3.5:4b", sizeGB: 3.4, label: "4B params (3.4 GB)", cloud: false },
|
|
42050
|
+
{ tag: "qwen3.5:9b", sizeGB: 6.6, label: "9B params (6.6 GB) \u2014 recommended minimum", cloud: false },
|
|
42051
|
+
{ tag: "qwen3.5:27b", sizeGB: 17, label: "27B params (17 GB)", cloud: false },
|
|
42052
|
+
{ tag: "qwen3.5:35b", sizeGB: 24, label: "35B params (24 GB)", cloud: false },
|
|
42053
|
+
{ tag: "qwen3.5:122b", sizeGB: 81, label: "122B params (81 GB) \u2014 best local", cloud: false },
|
|
42054
|
+
{ tag: "qwen3.5:cloud", sizeGB: 0, label: "Cloud (Ollama Cloud)", cloud: true },
|
|
42055
|
+
{ tag: "qwen3.5:397b-cloud", sizeGB: 0, label: "397B Cloud (Ollama Cloud)", cloud: true }
|
|
42056
|
+
];
|
|
42057
|
+
_toolSupportCache = /* @__PURE__ */ new Map();
|
|
42058
|
+
_cloudflaredInstallPromise = null;
|
|
42084
42059
|
}
|
|
42085
42060
|
});
|
|
42086
42061
|
|
|
@@ -50633,11 +50608,11 @@ async function handleUpdate(subcommand, ctx) {
|
|
|
50633
50608
|
}
|
|
50634
50609
|
let currentVersion = "0.0.0";
|
|
50635
50610
|
try {
|
|
50636
|
-
const { createRequire:
|
|
50611
|
+
const { createRequire: createRequire6 } = await import("node:module");
|
|
50637
50612
|
const { fileURLToPath: fileURLToPath15 } = await import("node:url");
|
|
50638
50613
|
const { dirname: dirname23, join: join76 } = await import("node:path");
|
|
50639
50614
|
const { existsSync: existsSync56 } = await import("node:fs");
|
|
50640
|
-
const req =
|
|
50615
|
+
const req = createRequire6(import.meta.url);
|
|
50641
50616
|
const thisDir = dirname23(fileURLToPath15(import.meta.url));
|
|
50642
50617
|
const candidates = [
|
|
50643
50618
|
join76(thisDir, "..", "package.json"),
|
|
@@ -51817,8 +51792,20 @@ var init_project_context = __esm({
|
|
|
51817
51792
|
});
|
|
51818
51793
|
|
|
51819
51794
|
// packages/memory/dist/db.js
|
|
51820
|
-
import
|
|
51795
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
51796
|
+
function getDatabase() {
|
|
51797
|
+
if (_Database !== null)
|
|
51798
|
+
return _Database;
|
|
51799
|
+
try {
|
|
51800
|
+
const req = createRequire2(import.meta.url);
|
|
51801
|
+
_Database = req("better-sqlite3");
|
|
51802
|
+
} catch {
|
|
51803
|
+
throw new Error("better-sqlite3 is not available (native module build may have failed).\nTry: npm rebuild better-sqlite3\nMemory features (SQLite) are disabled until this is resolved.");
|
|
51804
|
+
}
|
|
51805
|
+
return _Database;
|
|
51806
|
+
}
|
|
51821
51807
|
function initDb(dbPath) {
|
|
51808
|
+
const Database = getDatabase();
|
|
51822
51809
|
const db = new Database(dbPath);
|
|
51823
51810
|
db.pragma("journal_mode = WAL");
|
|
51824
51811
|
db.pragma("foreign_keys = ON");
|
|
@@ -52040,9 +52027,11 @@ function runMigrations(db) {
|
|
|
52040
52027
|
ON memory_link (target_id);
|
|
52041
52028
|
`);
|
|
52042
52029
|
}
|
|
52030
|
+
var _Database;
|
|
52043
52031
|
var init_db = __esm({
|
|
52044
52032
|
"packages/memory/dist/db.js"() {
|
|
52045
52033
|
"use strict";
|
|
52034
|
+
_Database = null;
|
|
52046
52035
|
}
|
|
52047
52036
|
});
|
|
52048
52037
|
|
|
@@ -62102,7 +62091,7 @@ __export(serve_exports, {
|
|
|
62102
62091
|
});
|
|
62103
62092
|
import * as http from "node:http";
|
|
62104
62093
|
import * as https from "node:https";
|
|
62105
|
-
import { createRequire as
|
|
62094
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
62106
62095
|
import { fileURLToPath as fileURLToPath12 } from "node:url";
|
|
62107
62096
|
import { dirname as dirname20, join as join69, resolve as resolve31 } from "node:path";
|
|
62108
62097
|
import { spawn as spawn20 } from "node:child_process";
|
|
@@ -62110,7 +62099,7 @@ import { mkdirSync as mkdirSync26, writeFileSync as writeFileSync25, readFileSyn
|
|
|
62110
62099
|
import { randomBytes as randomBytes16 } from "node:crypto";
|
|
62111
62100
|
function getVersion3() {
|
|
62112
62101
|
try {
|
|
62113
|
-
const require2 =
|
|
62102
|
+
const require2 = createRequire3(import.meta.url);
|
|
62114
62103
|
const thisDir = dirname20(fileURLToPath12(import.meta.url));
|
|
62115
62104
|
const candidates = [
|
|
62116
62105
|
join69(thisDir, "..", "package.json"),
|
|
@@ -63530,7 +63519,7 @@ import * as readline2 from "node:readline";
|
|
|
63530
63519
|
import { Writable } from "node:stream";
|
|
63531
63520
|
import { cwd } from "node:process";
|
|
63532
63521
|
import { resolve as resolve32, join as join70, dirname as dirname21, extname as extname11 } from "node:path";
|
|
63533
|
-
import { createRequire as
|
|
63522
|
+
import { createRequire as createRequire4 } from "node:module";
|
|
63534
63523
|
import { fileURLToPath as fileURLToPath13 } from "node:url";
|
|
63535
63524
|
import { readFileSync as readFileSync42, writeFileSync as writeFileSync26, appendFileSync as appendFileSync4, rmSync as rmSync3, readdirSync as readdirSync21, mkdirSync as mkdirSync27 } from "node:fs";
|
|
63536
63525
|
import { existsSync as existsSync53 } from "node:fs";
|
|
@@ -63551,7 +63540,7 @@ function formatTimeAgo(date) {
|
|
|
63551
63540
|
}
|
|
63552
63541
|
function getVersion4() {
|
|
63553
63542
|
try {
|
|
63554
|
-
const require2 =
|
|
63543
|
+
const require2 = createRequire4(import.meta.url);
|
|
63555
63544
|
const thisDir = dirname21(fileURLToPath13(import.meta.url));
|
|
63556
63545
|
const candidates = [
|
|
63557
63546
|
join70(thisDir, "..", "package.json"),
|
|
@@ -64881,7 +64870,7 @@ async function startInteractive(config, repoPath) {
|
|
|
64881
64870
|
initOaDirectory(repoRoot);
|
|
64882
64871
|
const savedSettings = resolveSettings(repoRoot);
|
|
64883
64872
|
if (process.stdout.isTTY) {
|
|
64884
|
-
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
|
|
64873
|
+
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");
|
|
64885
64874
|
const restoreScreen = () => {
|
|
64886
64875
|
process.stdout.write("\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?1015l\x1B[?25h\x1B[?1049l");
|
|
64887
64876
|
};
|
|
@@ -65403,6 +65392,9 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
|
|
|
65403
65392
|
statusBar.handlePointerEvent(type, col, row);
|
|
65404
65393
|
});
|
|
65405
65394
|
process.stdin.pipe(mouseFilter);
|
|
65395
|
+
if (process.stdout.isTTY) {
|
|
65396
|
+
process.stdout.write("\x1B[?1002h\x1B[?1006h");
|
|
65397
|
+
}
|
|
65406
65398
|
process.stdin.on("error", (err) => {
|
|
65407
65399
|
if (err.code === "EIO" || err.code === "ENOTTY")
|
|
65408
65400
|
return;
|
|
@@ -69314,7 +69306,7 @@ init_config();
|
|
|
69314
69306
|
init_output();
|
|
69315
69307
|
init_updater();
|
|
69316
69308
|
import { parseArgs as nodeParseArgs2 } from "node:util";
|
|
69317
|
-
import { createRequire as
|
|
69309
|
+
import { createRequire as createRequire5 } from "node:module";
|
|
69318
69310
|
import { fileURLToPath as fileURLToPath14 } from "node:url";
|
|
69319
69311
|
import { dirname as dirname22, join as join75 } from "node:path";
|
|
69320
69312
|
|
|
@@ -69422,7 +69414,7 @@ init_spinner();
|
|
|
69422
69414
|
init_output();
|
|
69423
69415
|
function getVersion5() {
|
|
69424
69416
|
try {
|
|
69425
|
-
const require2 =
|
|
69417
|
+
const require2 = createRequire5(import.meta.url);
|
|
69426
69418
|
const pkgPath = join75(dirname22(fileURLToPath14(import.meta.url)), "..", "package.json");
|
|
69427
69419
|
const pkg = require2(pkgPath);
|
|
69428
69420
|
return pkg.version;
|