beecork 2.4.2 → 2.5.1
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 +751 -277
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -135,9 +135,10 @@ var config = {
|
|
|
135
135
|
};
|
|
136
136
|
|
|
137
137
|
// src/update.ts
|
|
138
|
-
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
138
|
+
import { readFile, writeFile, mkdir, realpath } from "node:fs/promises";
|
|
139
139
|
import { homedir as homedir2 } from "node:os";
|
|
140
|
-
import { join, dirname } from "node:path";
|
|
140
|
+
import { join, dirname, basename, delimiter } from "node:path";
|
|
141
|
+
import { fileURLToPath } from "node:url";
|
|
141
142
|
import { spawn } from "node:child_process";
|
|
142
143
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
143
144
|
var cacheFile = () => join(homedir2(), ".beecork", "update-check.json");
|
|
@@ -149,6 +150,48 @@ async function currentVersion() {
|
|
|
149
150
|
return "0.0.0";
|
|
150
151
|
}
|
|
151
152
|
}
|
|
153
|
+
function runningPkgRoot() {
|
|
154
|
+
return fileURLToPath(new URL("..", import.meta.url));
|
|
155
|
+
}
|
|
156
|
+
function prefixFromPkgRoot(pkgRoot) {
|
|
157
|
+
const nm = dirname(pkgRoot);
|
|
158
|
+
if (basename(nm) !== "node_modules") return null;
|
|
159
|
+
const up = dirname(nm);
|
|
160
|
+
return basename(up) === "lib" ? dirname(up) : up;
|
|
161
|
+
}
|
|
162
|
+
function installPrefix() {
|
|
163
|
+
return prefixFromPkgRoot(runningPkgRoot());
|
|
164
|
+
}
|
|
165
|
+
async function findShadowingInstalls() {
|
|
166
|
+
let running;
|
|
167
|
+
try {
|
|
168
|
+
running = await realpath(runningPkgRoot());
|
|
169
|
+
} catch {
|
|
170
|
+
running = runningPkgRoot();
|
|
171
|
+
}
|
|
172
|
+
const names = process.platform === "win32" ? ["beecork.cmd", "beecork"] : ["beecork"];
|
|
173
|
+
const seenRoots = /* @__PURE__ */ new Set([running]);
|
|
174
|
+
const others = [];
|
|
175
|
+
for (const dir of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
176
|
+
for (const name of names) {
|
|
177
|
+
try {
|
|
178
|
+
const bin = join(dir, name);
|
|
179
|
+
const root = dirname(dirname(await realpath(bin)));
|
|
180
|
+
if (seenRoots.has(root)) continue;
|
|
181
|
+
seenRoots.add(root);
|
|
182
|
+
const pkg = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
|
|
183
|
+
if (String(pkg.name) === "beecork") others.push({ bin, version: String(pkg.version ?? "?").replace(/[^\w.+-]/g, "") });
|
|
184
|
+
} catch {
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return others;
|
|
189
|
+
}
|
|
190
|
+
async function shadowWarning() {
|
|
191
|
+
const others = await findShadowingInstalls();
|
|
192
|
+
if (!others.length) return null;
|
|
193
|
+
return "\u26A0 Another beecork is on your PATH and may shadow the version you just installed:\n" + others.map((o) => ` ${o.bin} (v${o.version})`).join("\n") + "\n Remove the stale one(s) so `beecork` always runs the newest install (may need sudo):\n" + others.map((o) => ` rm ${o.bin}`).join("\n");
|
|
194
|
+
}
|
|
152
195
|
function isNewer(a, b) {
|
|
153
196
|
const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
|
|
154
197
|
const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
|
|
@@ -195,8 +238,10 @@ async function checkForUpdate(current) {
|
|
|
195
238
|
function selfUpdate(timeoutMs = 12e4) {
|
|
196
239
|
return new Promise((resolve2) => {
|
|
197
240
|
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
198
|
-
const
|
|
199
|
-
|
|
241
|
+
const prefix = installPrefix();
|
|
242
|
+
const args = ["install", "-g", "beecork@latest", ...prefix ? ["--prefix", prefix] : []];
|
|
243
|
+
const child = spawn(npm, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
244
|
+
let out3 = "", done = false;
|
|
200
245
|
const finish = (r) => {
|
|
201
246
|
if (done) return;
|
|
202
247
|
done = true;
|
|
@@ -208,13 +253,13 @@ function selfUpdate(timeoutMs = 12e4) {
|
|
|
208
253
|
child.kill("SIGKILL");
|
|
209
254
|
} catch {
|
|
210
255
|
}
|
|
211
|
-
finish({ ok: false, output: `${
|
|
256
|
+
finish({ ok: false, output: `${out3.trim()}
|
|
212
257
|
(update timed out after ${timeoutMs}ms \u2014 run manually: npm install -g beecork@latest)`.trim() });
|
|
213
258
|
}, timeoutMs);
|
|
214
|
-
child.stdout?.on("data", (d) =>
|
|
215
|
-
child.stderr?.on("data", (d) =>
|
|
259
|
+
child.stdout?.on("data", (d) => out3 += d);
|
|
260
|
+
child.stderr?.on("data", (d) => out3 += d);
|
|
216
261
|
child.on("error", (e) => finish({ ok: false, output: e.message }));
|
|
217
|
-
child.on("close", (code) => finish({ ok: code === 0, output:
|
|
262
|
+
child.on("close", (code) => finish({ ok: code === 0, output: out3.trim() }));
|
|
218
263
|
});
|
|
219
264
|
}
|
|
220
265
|
|
|
@@ -256,27 +301,65 @@ function lineDiff(oldText, newText) {
|
|
|
256
301
|
lcs[i2][j2] = a[i2] === b[j2] ? lcs[i2 + 1][j2 + 1] + 1 : Math.max(lcs[i2 + 1][j2], lcs[i2][j2 + 1]);
|
|
257
302
|
}
|
|
258
303
|
}
|
|
259
|
-
const
|
|
304
|
+
const out3 = [];
|
|
260
305
|
let i = 0;
|
|
261
306
|
let j = 0;
|
|
262
307
|
while (i < m && j < n) {
|
|
263
308
|
if (a[i] === b[j]) {
|
|
264
|
-
|
|
309
|
+
out3.push(" " + a[i]);
|
|
265
310
|
i++;
|
|
266
311
|
j++;
|
|
267
312
|
} else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
|
|
268
|
-
|
|
313
|
+
out3.push("- " + a[i]);
|
|
269
314
|
i++;
|
|
270
315
|
} else {
|
|
271
|
-
|
|
316
|
+
out3.push("+ " + b[j]);
|
|
272
317
|
j++;
|
|
273
318
|
}
|
|
274
319
|
}
|
|
275
|
-
while (i < m)
|
|
276
|
-
while (j < n)
|
|
277
|
-
return
|
|
320
|
+
while (i < m) out3.push("- " + a[i++]);
|
|
321
|
+
while (j < n) out3.push("+ " + b[j++]);
|
|
322
|
+
return out3.join("\n");
|
|
278
323
|
}
|
|
279
324
|
|
|
325
|
+
// src/ansi.ts
|
|
326
|
+
var ESC = "\x1B";
|
|
327
|
+
var CSI = "\x1B[";
|
|
328
|
+
var ansi = {
|
|
329
|
+
// --- cursor visibility ---
|
|
330
|
+
hideCursor: CSI + "?25l",
|
|
331
|
+
showCursor: CSI + "?25h",
|
|
332
|
+
// --- save / restore cursor (position + attributes) ---
|
|
333
|
+
saveCursor: ESC + "7",
|
|
334
|
+
restoreCursor: ESC + "8",
|
|
335
|
+
// --- clearing ---
|
|
336
|
+
clearLine: CSI + "2K",
|
|
337
|
+
// entire current line (cursor row unchanged)
|
|
338
|
+
clearToEnd: CSI + "J",
|
|
339
|
+
// cursor → end of screen
|
|
340
|
+
clearScreen: CSI + "2J",
|
|
341
|
+
// whole viewport
|
|
342
|
+
clearScrollback: CSI + "3J",
|
|
343
|
+
// scrollback buffer (xterm extension)
|
|
344
|
+
cr: "\r",
|
|
345
|
+
// carriage return (column 1, same row)
|
|
346
|
+
home: CSI + "H",
|
|
347
|
+
// row 1, col 1
|
|
348
|
+
// --- motion (n omitted → terminal default of 1) ---
|
|
349
|
+
up: (n = 1) => CSI + n + "A",
|
|
350
|
+
forward: (n = 1) => CSI + n + "C",
|
|
351
|
+
moveTo: (row, col = 1) => CSI + row + ";" + col + "H",
|
|
352
|
+
// --- scroll region (DECSTBM); 1-based, inclusive. reset → full screen ---
|
|
353
|
+
setRegion: (top, bottom) => CSI + top + ";" + bottom + "r",
|
|
354
|
+
resetRegion: CSI + "r",
|
|
355
|
+
// --- bracketed paste mode ---
|
|
356
|
+
bracketedPasteOn: CSI + "?2004h",
|
|
357
|
+
bracketedPasteOff: CSI + "?2004l",
|
|
358
|
+
// --- reverse video (used to draw the pinned input's block cursor) ---
|
|
359
|
+
reverse: CSI + "7m",
|
|
360
|
+
reverseOff: CSI + "27m"
|
|
361
|
+
};
|
|
362
|
+
|
|
280
363
|
// src/ui.ts
|
|
281
364
|
var useColor = process.env.NO_COLOR ? false : process.env.FORCE_COLOR ? true : Boolean(process.stdout.isTTY);
|
|
282
365
|
var paint = (code) => (s) => useColor ? `\x1B[${code}m${s}\x1B[0m` : s;
|
|
@@ -294,12 +377,12 @@ var color = {
|
|
|
294
377
|
};
|
|
295
378
|
var isPrintableCodePoint = (c) => c >= 32 && c !== 127 && !(c >= 128 && c <= 159);
|
|
296
379
|
function stripControl(s) {
|
|
297
|
-
let
|
|
380
|
+
let out3 = "";
|
|
298
381
|
for (const ch of s) {
|
|
299
382
|
const c = ch.codePointAt(0);
|
|
300
|
-
if (c === 9 || c === 10 || isPrintableCodePoint(c))
|
|
383
|
+
if (c === 9 || c === 10 || isPrintableCodePoint(c)) out3 += ch;
|
|
301
384
|
}
|
|
302
|
-
return
|
|
385
|
+
return out3;
|
|
303
386
|
}
|
|
304
387
|
var stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
305
388
|
function charWidth(cp) {
|
|
@@ -418,19 +501,19 @@ function startSpinner(label) {
|
|
|
418
501
|
if (!process.stdout.isTTY || !useColor) return () => {
|
|
419
502
|
};
|
|
420
503
|
let i = 0;
|
|
421
|
-
const
|
|
504
|
+
const draw = () => {
|
|
422
505
|
if (steeringOnScreen) return;
|
|
423
|
-
process.stdout.write("
|
|
506
|
+
process.stdout.write(ansi.cr + " " + color.brand(SPINNER[i = (i + 1) % SPINNER.length]) + " " + color.dim(label));
|
|
424
507
|
};
|
|
425
|
-
process.stdout.write(
|
|
426
|
-
|
|
427
|
-
const timer = setInterval(
|
|
508
|
+
process.stdout.write(ansi.hideCursor);
|
|
509
|
+
draw();
|
|
510
|
+
const timer = setInterval(draw, 80);
|
|
428
511
|
let stopped = false;
|
|
429
512
|
return () => {
|
|
430
513
|
if (stopped) return;
|
|
431
514
|
stopped = true;
|
|
432
515
|
clearInterval(timer);
|
|
433
|
-
process.stdout.write(
|
|
516
|
+
process.stdout.write(ansi.cr + ansi.clearLine);
|
|
434
517
|
};
|
|
435
518
|
}
|
|
436
519
|
function markLines(width) {
|
|
@@ -456,8 +539,9 @@ function markLines(width) {
|
|
|
456
539
|
}
|
|
457
540
|
return lines.filter((l) => l.length > 0);
|
|
458
541
|
}
|
|
459
|
-
function printBanner(model, sources) {
|
|
542
|
+
function printBanner(model, version, sources) {
|
|
460
543
|
const safeModel = stripControl(model);
|
|
544
|
+
const safeVersion = stripControl(version);
|
|
461
545
|
const word = [
|
|
462
546
|
" _ _ ",
|
|
463
547
|
" | |__ ___ ___ ___ ___ _ __| | __",
|
|
@@ -465,21 +549,21 @@ function printBanner(model, sources) {
|
|
|
465
549
|
" | |_) | __/ __/ (_| (_) | | | < ",
|
|
466
550
|
" |_.__/ \\___|\\___|\\___\\___/|_| |_|\\_\\"
|
|
467
551
|
];
|
|
468
|
-
const
|
|
469
|
-
const markW = Math.max(0, ...
|
|
552
|
+
const mark2 = markLines(24);
|
|
553
|
+
const markW = Math.max(0, ...mark2.map((l) => l.length));
|
|
470
554
|
const wordW = Math.max(...word.map((l) => l.length));
|
|
471
|
-
const
|
|
555
|
+
const cols2 = process.stdout.columns || 80;
|
|
472
556
|
console.log();
|
|
473
|
-
if (
|
|
474
|
-
const h = Math.max(
|
|
475
|
-
const mPad = Math.floor((h -
|
|
557
|
+
if (cols2 >= markW + 3 + wordW + 2) {
|
|
558
|
+
const h = Math.max(mark2.length, word.length);
|
|
559
|
+
const mPad = Math.floor((h - mark2.length) / 2);
|
|
476
560
|
const wPad = Math.floor((h - word.length) / 2);
|
|
477
561
|
for (let i = 0; i < h; i++) {
|
|
478
|
-
const m = (
|
|
562
|
+
const m = (mark2[i - mPad] ?? "").padEnd(markW);
|
|
479
563
|
const w = word[i - wPad] ?? "";
|
|
480
564
|
console.log(" " + color.brand(`${m} ${w}`));
|
|
481
565
|
}
|
|
482
|
-
} else if (
|
|
566
|
+
} else if (cols2 >= wordW + 2) {
|
|
483
567
|
for (const w of word) console.log(" " + color.brand(w));
|
|
484
568
|
} else {
|
|
485
569
|
console.log(" " + color.brand("\u{1F41D} beecork"));
|
|
@@ -489,7 +573,7 @@ function printBanner(model, sources) {
|
|
|
489
573
|
const mem = sources.filter((s) => s.endsWith("memory.md"));
|
|
490
574
|
const cwd = tildify(process.cwd());
|
|
491
575
|
const rows2 = [
|
|
492
|
-
["",
|
|
576
|
+
["", `\u{1F41D} a tiny CLI coding agent \xB7 v${safeVersion}`],
|
|
493
577
|
["dir", cwd],
|
|
494
578
|
["model", safeModel],
|
|
495
579
|
["cork.md", cork.length ? cork.join(", ") : "none"],
|
|
@@ -500,7 +584,7 @@ function printBanner(model, sources) {
|
|
|
500
584
|
const plain = (l, v) => l ? l.padEnd(lw) + " " + v : v;
|
|
501
585
|
const bw = Math.max(...rows2.map(([l, v]) => plain(l, v).length));
|
|
502
586
|
const row = ([l, v]) => l ? color.bold(l.padEnd(lw)) + color.dim(" " + v) : color.dim(v);
|
|
503
|
-
if (
|
|
587
|
+
if (cols2 < bw + 6) {
|
|
504
588
|
for (const r of rows2) console.log(" " + row(r));
|
|
505
589
|
} else {
|
|
506
590
|
console.log(" " + color.dim("\u256D\u2500" + "\u2500".repeat(bw) + "\u2500\u256E"));
|
|
@@ -518,6 +602,39 @@ import { readFile as readFile4 } from "node:fs/promises";
|
|
|
518
602
|
|
|
519
603
|
// src/input.ts
|
|
520
604
|
import { emitKeypressEvents } from "node:readline";
|
|
605
|
+
|
|
606
|
+
// src/layout.ts
|
|
607
|
+
function rowColOf(s) {
|
|
608
|
+
return { row: (s.match(/\n/g) || []).length, col: s.length - (s.lastIndexOf("\n") + 1) };
|
|
609
|
+
}
|
|
610
|
+
function inputLayout(text, before, promptW, cols2) {
|
|
611
|
+
const c = Math.max(1, cols2);
|
|
612
|
+
const physRows = text.split("\n").map((l) => Math.max(1, Math.ceil((promptW + displayWidth(l)) / c)));
|
|
613
|
+
const totalPhys = physRows.reduce((a, b) => a + b, 0);
|
|
614
|
+
const curLogicalRow = (before.match(/\n/g) || []).length;
|
|
615
|
+
const curCol = promptW + displayWidth(before.slice(before.lastIndexOf("\n") + 1));
|
|
616
|
+
const physBefore = physRows.slice(0, curLogicalRow).reduce((a, b) => a + b, 0);
|
|
617
|
+
const curPhysRow = physBefore + Math.floor(curCol / c);
|
|
618
|
+
const curPhysCol = curCol % c;
|
|
619
|
+
return { totalPhys, curPhysRow, curPhysCol };
|
|
620
|
+
}
|
|
621
|
+
function moveVertIndex(buf2, cur2, dir) {
|
|
622
|
+
const lines = buf2.split("\n");
|
|
623
|
+
const { row, col } = rowColOf(buf2.slice(0, cur2));
|
|
624
|
+
const target = row + dir;
|
|
625
|
+
if (target < 0 || target >= lines.length) return null;
|
|
626
|
+
let idx = 0;
|
|
627
|
+
for (let i = 0; i < target; i++) idx += lines[i].length + 1;
|
|
628
|
+
return idx + Math.min(col, lines[target].length);
|
|
629
|
+
}
|
|
630
|
+
function windowStart(text, cur2, avail) {
|
|
631
|
+
let start = 0;
|
|
632
|
+
if (displayWidth(text.slice(0, cur2)) >= avail)
|
|
633
|
+
while (start < cur2 && displayWidth(text.slice(start, cur2)) >= avail) start++;
|
|
634
|
+
return start;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// src/input.ts
|
|
521
638
|
var out = (s) => process.stdout.write(s);
|
|
522
639
|
var active = null;
|
|
523
640
|
var started = false;
|
|
@@ -525,13 +642,13 @@ function initInput() {
|
|
|
525
642
|
if (started || !process.stdin.isTTY) return;
|
|
526
643
|
started = true;
|
|
527
644
|
process.stdin.setRawMode(true);
|
|
528
|
-
out(
|
|
645
|
+
out(ansi.bracketedPasteOff);
|
|
529
646
|
emitKeypressEvents(process.stdin);
|
|
530
647
|
process.stdin.on("keypress", (str, key) => active?.(str, key));
|
|
531
648
|
}
|
|
532
649
|
function teardownInput() {
|
|
533
650
|
if (started && process.stdin.isTTY) {
|
|
534
|
-
out(
|
|
651
|
+
out(ansi.bracketedPasteOn + ansi.showCursor);
|
|
535
652
|
process.stdin.setRawMode(false);
|
|
536
653
|
process.stdin.pause();
|
|
537
654
|
}
|
|
@@ -553,27 +670,27 @@ function readPrompt(opts) {
|
|
|
553
670
|
...(opts.skills ?? []).map((n) => ({ name: "/" + n, desc: "skill" }))
|
|
554
671
|
];
|
|
555
672
|
return new Promise((resolve2) => {
|
|
556
|
-
let
|
|
557
|
-
let
|
|
673
|
+
let buf2 = "";
|
|
674
|
+
let cur2 = 0;
|
|
558
675
|
let hist = history.length;
|
|
559
|
-
let
|
|
560
|
-
let
|
|
561
|
-
const
|
|
562
|
-
let
|
|
563
|
-
let
|
|
564
|
-
let
|
|
676
|
+
let sel2 = 0;
|
|
677
|
+
let menuHidden2 = false;
|
|
678
|
+
const BURST_IDLE_MS2 = 8;
|
|
679
|
+
let burstLen2 = 0;
|
|
680
|
+
let burstTimer2 = null;
|
|
681
|
+
let pendingRender2 = false;
|
|
565
682
|
const matches = () => {
|
|
566
683
|
if (opts.mask) return [];
|
|
567
|
-
const m =
|
|
684
|
+
const m = buf2.match(/^\/(\S*)$/);
|
|
568
685
|
if (!m) return [];
|
|
569
686
|
const pre = "/" + m[1];
|
|
570
687
|
return all.filter((c) => c.name.startsWith(pre)).slice(0, MENU_MAX);
|
|
571
688
|
};
|
|
572
|
-
const menu = () =>
|
|
689
|
+
const menu = () => menuHidden2 ? [] : matches();
|
|
573
690
|
const highlight = () => {
|
|
574
|
-
if (opts.mask) return "*".repeat(
|
|
575
|
-
const m =
|
|
576
|
-
if (!m) return
|
|
691
|
+
if (opts.mask) return "*".repeat(buf2.length);
|
|
692
|
+
const m = buf2.match(/^(\/\S*)([\s\S]*)$/);
|
|
693
|
+
if (!m) return buf2;
|
|
577
694
|
const [, token, rest] = m;
|
|
578
695
|
const known = all.some((c) => c.name === token);
|
|
579
696
|
const partial = all.some((c) => c.name.startsWith(token));
|
|
@@ -581,7 +698,6 @@ function readPrompt(opts) {
|
|
|
581
698
|
return styled + rest;
|
|
582
699
|
};
|
|
583
700
|
let lastCurRow = 0;
|
|
584
|
-
const rowColOf = (s) => ({ row: (s.match(/\n/g) || []).length, col: s.length - (s.lastIndexOf("\n") + 1) });
|
|
585
701
|
function drawBlock() {
|
|
586
702
|
const prompt = opts.promptString();
|
|
587
703
|
const promptW = stripAnsi(prompt).length;
|
|
@@ -591,199 +707,189 @@ function readPrompt(opts) {
|
|
|
591
707
|
for (let i = 1; i < lines.length; i++) out("\n" + indent + lines[i]);
|
|
592
708
|
return { promptW };
|
|
593
709
|
}
|
|
594
|
-
function
|
|
710
|
+
function render2() {
|
|
595
711
|
const mm = menu();
|
|
596
|
-
if (
|
|
597
|
-
out(
|
|
598
|
-
if (lastCurRow > 0) out(
|
|
599
|
-
out(
|
|
712
|
+
if (sel2 >= mm.length) sel2 = Math.max(0, mm.length - 1);
|
|
713
|
+
out(ansi.hideCursor);
|
|
714
|
+
if (lastCurRow > 0) out(ansi.up(lastCurRow));
|
|
715
|
+
out(ansi.cr + ansi.clearToEnd);
|
|
600
716
|
const { promptW } = drawBlock();
|
|
601
|
-
const
|
|
717
|
+
const cols2 = Math.max(1, process.stdout.columns || 80);
|
|
602
718
|
for (let i = 0; i < mm.length; i++) {
|
|
603
719
|
const m = mm[i];
|
|
604
720
|
const name = m.name.padEnd(10);
|
|
605
|
-
const maxDesc = Math.max(0,
|
|
721
|
+
const maxDesc = Math.max(0, cols2 - name.length - 4);
|
|
606
722
|
const desc = m.desc.length > maxDesc ? m.desc.slice(0, Math.max(0, maxDesc - 1)) + "\u2026" : m.desc;
|
|
607
|
-
out("\n" + (i ===
|
|
608
|
-
}
|
|
609
|
-
const text = opts.mask ? "*".repeat(
|
|
610
|
-
const
|
|
611
|
-
const
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
const physBefore = physRows.slice(0, curLogicalRow).reduce((a, b) => a + b, 0);
|
|
616
|
-
const curPhysRow = physBefore + Math.floor(curCol / cols);
|
|
617
|
-
const curPhysCol = curCol % cols;
|
|
618
|
-
const lastDrawnRow = totalInputPhys - 1 + mm.length;
|
|
619
|
-
if (lastDrawnRow > curPhysRow) out(`\x1B[${lastDrawnRow - curPhysRow}A`);
|
|
620
|
-
out("\r" + (curPhysCol > 0 ? `\x1B[${curPhysCol}C` : "") + "\x1B[?25h");
|
|
723
|
+
out("\n" + (i === sel2 ? color.green("\u203A " + name) + " " + color.dim(desc) : color.dim(" " + name + " " + desc)));
|
|
724
|
+
}
|
|
725
|
+
const text = opts.mask ? "*".repeat(buf2.length) : buf2;
|
|
726
|
+
const before = opts.mask ? text : buf2.slice(0, cur2);
|
|
727
|
+
const { totalPhys, curPhysRow, curPhysCol } = inputLayout(text, before, promptW, cols2);
|
|
728
|
+
const lastDrawnRow = totalPhys - 1 + mm.length;
|
|
729
|
+
if (lastDrawnRow > curPhysRow) out(ansi.up(lastDrawnRow - curPhysRow));
|
|
730
|
+
out(ansi.cr + (curPhysCol > 0 ? ansi.forward(curPhysCol) : "") + ansi.showCursor);
|
|
621
731
|
lastCurRow = curPhysRow;
|
|
622
732
|
}
|
|
623
733
|
function finish(result) {
|
|
624
734
|
restore();
|
|
625
|
-
if (
|
|
626
|
-
clearTimeout(
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
out(
|
|
631
|
-
if (lastCurRow > 0) out(
|
|
632
|
-
out(
|
|
735
|
+
if (burstTimer2) {
|
|
736
|
+
clearTimeout(burstTimer2);
|
|
737
|
+
burstTimer2 = null;
|
|
738
|
+
}
|
|
739
|
+
pendingRender2 = false;
|
|
740
|
+
out(ansi.hideCursor);
|
|
741
|
+
if (lastCurRow > 0) out(ansi.up(lastCurRow));
|
|
742
|
+
out(ansi.cr + ansi.clearToEnd);
|
|
633
743
|
drawBlock();
|
|
634
744
|
out("\n");
|
|
635
745
|
if (result.type === "line" && result.value.trim() && !opts.mask) history.push(result.value);
|
|
636
746
|
resolve2(result);
|
|
637
747
|
}
|
|
638
|
-
function
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
748
|
+
function insert2(s) {
|
|
749
|
+
buf2 = buf2.slice(0, cur2) + s + buf2.slice(cur2);
|
|
750
|
+
cur2 += s.length;
|
|
751
|
+
menuHidden2 = false;
|
|
752
|
+
sel2 = 0;
|
|
753
|
+
pendingRender2 = true;
|
|
644
754
|
}
|
|
645
|
-
function
|
|
755
|
+
function onKey2(str, key) {
|
|
646
756
|
const mm = menu();
|
|
647
|
-
|
|
648
|
-
if (
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
if (
|
|
653
|
-
|
|
654
|
-
|
|
757
|
+
burstLen2++;
|
|
758
|
+
if (burstTimer2) clearTimeout(burstTimer2);
|
|
759
|
+
burstTimer2 = setTimeout(() => {
|
|
760
|
+
burstTimer2 = null;
|
|
761
|
+
burstLen2 = 0;
|
|
762
|
+
if (pendingRender2) {
|
|
763
|
+
pendingRender2 = false;
|
|
764
|
+
render2();
|
|
655
765
|
}
|
|
656
|
-
},
|
|
766
|
+
}, BURST_IDLE_MS2);
|
|
657
767
|
if (isEnter(key)) {
|
|
658
|
-
if (key?.shift || key?.meta ||
|
|
659
|
-
|
|
768
|
+
if (key?.shift || key?.meta || burstLen2 > 1) {
|
|
769
|
+
insert2("\n");
|
|
660
770
|
return;
|
|
661
771
|
}
|
|
662
|
-
return finish({ type: "line", value:
|
|
772
|
+
return finish({ type: "line", value: buf2 });
|
|
663
773
|
}
|
|
664
774
|
if (key?.ctrl && key.name === "c") {
|
|
665
|
-
if (
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
775
|
+
if (buf2) {
|
|
776
|
+
buf2 = "";
|
|
777
|
+
cur2 = 0;
|
|
778
|
+
render2();
|
|
669
779
|
} else finish({ type: "quit" });
|
|
670
780
|
return;
|
|
671
781
|
}
|
|
672
782
|
if (key?.ctrl && key.name === "d") {
|
|
673
|
-
if (!
|
|
783
|
+
if (!buf2) finish({ type: "eof" });
|
|
674
784
|
return;
|
|
675
785
|
}
|
|
676
786
|
if (key?.name === "tab" && key.shift) {
|
|
677
787
|
opts.onShiftTab?.();
|
|
678
|
-
|
|
788
|
+
render2();
|
|
679
789
|
return;
|
|
680
790
|
}
|
|
681
791
|
if (key?.name === "tab") {
|
|
682
792
|
if (mm.length) {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
793
|
+
buf2 = mm[sel2].name + " ";
|
|
794
|
+
cur2 = buf2.length;
|
|
795
|
+
menuHidden2 = false;
|
|
796
|
+
render2();
|
|
687
797
|
}
|
|
688
798
|
return;
|
|
689
799
|
}
|
|
690
800
|
if (key?.name === "up") {
|
|
691
801
|
if (mm.length) {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
} else if (
|
|
802
|
+
sel2 = (sel2 - 1 + mm.length) % mm.length;
|
|
803
|
+
render2();
|
|
804
|
+
} else if (buf2.includes("\n")) moveVert(-1);
|
|
695
805
|
else histPrev();
|
|
696
806
|
return;
|
|
697
807
|
}
|
|
698
808
|
if (key?.name === "down") {
|
|
699
809
|
if (mm.length) {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
} else if (
|
|
810
|
+
sel2 = (sel2 + 1) % mm.length;
|
|
811
|
+
render2();
|
|
812
|
+
} else if (buf2.includes("\n")) moveVert(1);
|
|
703
813
|
else histNext();
|
|
704
814
|
return;
|
|
705
815
|
}
|
|
706
816
|
if (key?.name === "left") {
|
|
707
|
-
if (
|
|
708
|
-
|
|
817
|
+
if (cur2 > 0) cur2--;
|
|
818
|
+
render2();
|
|
709
819
|
return;
|
|
710
820
|
}
|
|
711
821
|
if (key?.name === "right") {
|
|
712
|
-
if (
|
|
713
|
-
|
|
822
|
+
if (cur2 < buf2.length) cur2++;
|
|
823
|
+
render2();
|
|
714
824
|
return;
|
|
715
825
|
}
|
|
716
826
|
if (key?.name === "home" || key?.ctrl && key.name === "a") {
|
|
717
|
-
|
|
718
|
-
|
|
827
|
+
cur2 = 0;
|
|
828
|
+
render2();
|
|
719
829
|
return;
|
|
720
830
|
}
|
|
721
831
|
if (key?.name === "end" || key?.ctrl && key.name === "e") {
|
|
722
|
-
|
|
723
|
-
|
|
832
|
+
cur2 = buf2.length;
|
|
833
|
+
render2();
|
|
724
834
|
return;
|
|
725
835
|
}
|
|
726
836
|
if (key?.ctrl && key.name === "u") {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
837
|
+
buf2 = buf2.slice(cur2);
|
|
838
|
+
cur2 = 0;
|
|
839
|
+
menuHidden2 = false;
|
|
840
|
+
render2();
|
|
731
841
|
return;
|
|
732
842
|
}
|
|
733
843
|
if (key?.name === "backspace") {
|
|
734
|
-
if (
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
844
|
+
if (cur2 > 0) {
|
|
845
|
+
buf2 = buf2.slice(0, cur2 - 1) + buf2.slice(cur2);
|
|
846
|
+
cur2--;
|
|
847
|
+
menuHidden2 = false;
|
|
848
|
+
render2();
|
|
739
849
|
}
|
|
740
850
|
return;
|
|
741
851
|
}
|
|
742
852
|
if (key?.name === "delete") {
|
|
743
|
-
if (
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
853
|
+
if (cur2 < buf2.length) {
|
|
854
|
+
buf2 = buf2.slice(0, cur2) + buf2.slice(cur2 + 1);
|
|
855
|
+
menuHidden2 = false;
|
|
856
|
+
render2();
|
|
747
857
|
}
|
|
748
858
|
return;
|
|
749
859
|
}
|
|
750
860
|
if (key?.name === "escape") {
|
|
751
861
|
if (mm.length) {
|
|
752
|
-
|
|
753
|
-
|
|
862
|
+
menuHidden2 = true;
|
|
863
|
+
render2();
|
|
754
864
|
}
|
|
755
865
|
return;
|
|
756
866
|
}
|
|
757
867
|
if (str && !key?.ctrl && !key?.meta && [...str].length === 1) {
|
|
758
|
-
if (isPrintableCodePoint(str.codePointAt(0)))
|
|
868
|
+
if (isPrintableCodePoint(str.codePointAt(0))) insert2(str);
|
|
759
869
|
}
|
|
760
870
|
}
|
|
761
871
|
function moveVert(dir) {
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
let idx = 0;
|
|
767
|
-
for (let i = 0; i < target; i++) idx += lines[i].length + 1;
|
|
768
|
-
cur = idx + Math.min(col, lines[target].length);
|
|
769
|
-
render();
|
|
872
|
+
const idx = moveVertIndex(buf2, cur2, dir);
|
|
873
|
+
if (idx === null) return;
|
|
874
|
+
cur2 = idx;
|
|
875
|
+
render2();
|
|
770
876
|
}
|
|
771
877
|
function histPrev() {
|
|
772
878
|
if (history.length === 0) return;
|
|
773
879
|
hist = Math.max(0, hist - 1);
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
880
|
+
buf2 = history[hist] ?? "";
|
|
881
|
+
cur2 = buf2.length;
|
|
882
|
+
render2();
|
|
777
883
|
}
|
|
778
884
|
function histNext() {
|
|
779
885
|
if (hist >= history.length) return;
|
|
780
886
|
hist += 1;
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
887
|
+
buf2 = hist === history.length ? "" : history[hist];
|
|
888
|
+
cur2 = buf2.length;
|
|
889
|
+
render2();
|
|
784
890
|
}
|
|
785
|
-
const restore = pushKeyHandler(
|
|
786
|
-
|
|
891
|
+
const restore = pushKeyHandler(onKey2);
|
|
892
|
+
render2();
|
|
787
893
|
});
|
|
788
894
|
}
|
|
789
895
|
function readChoice(prompt) {
|
|
@@ -804,36 +910,36 @@ function readChoice(prompt) {
|
|
|
804
910
|
function selectMenu(opts) {
|
|
805
911
|
if (!process.stdin.isTTY || opts.items.length === 0) return Promise.resolve(null);
|
|
806
912
|
return new Promise((resolve2) => {
|
|
807
|
-
let
|
|
913
|
+
let sel2 = Math.max(0, Math.min(opts.initial ?? 0, opts.items.length - 1));
|
|
808
914
|
let drawn = 0;
|
|
809
|
-
function
|
|
810
|
-
out(
|
|
811
|
-
if (drawn > 0) out(
|
|
812
|
-
out(
|
|
915
|
+
function render2() {
|
|
916
|
+
out(ansi.hideCursor);
|
|
917
|
+
if (drawn > 0) out(ansi.up(drawn));
|
|
918
|
+
out(ansi.cr + ansi.clearToEnd);
|
|
813
919
|
out(color.dim(opts.title) + "\n");
|
|
814
920
|
opts.items.forEach((it, i) => {
|
|
815
|
-
const row = i ===
|
|
921
|
+
const row = i === sel2 ? color.green("\u203A " + it.label) : " " + it.label;
|
|
816
922
|
out(row + (it.hint ? color.dim(" " + it.hint) : "") + "\n");
|
|
817
923
|
});
|
|
818
924
|
drawn = opts.items.length + 1;
|
|
819
925
|
}
|
|
820
926
|
function finish(v) {
|
|
821
927
|
restore();
|
|
822
|
-
if (drawn > 0) out(
|
|
823
|
-
out(
|
|
928
|
+
if (drawn > 0) out(ansi.up(drawn) + ansi.cr + ansi.clearToEnd);
|
|
929
|
+
out(ansi.showCursor);
|
|
824
930
|
resolve2(v);
|
|
825
931
|
}
|
|
826
932
|
const restore = pushKeyHandler((_str, key) => {
|
|
827
933
|
if (key?.name === "up") {
|
|
828
|
-
|
|
829
|
-
|
|
934
|
+
sel2 = (sel2 - 1 + opts.items.length) % opts.items.length;
|
|
935
|
+
render2();
|
|
830
936
|
} else if (key?.name === "down") {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
} else if (isEnter(key)) finish(opts.items[
|
|
937
|
+
sel2 = (sel2 + 1) % opts.items.length;
|
|
938
|
+
render2();
|
|
939
|
+
} else if (isEnter(key)) finish(opts.items[sel2].value);
|
|
834
940
|
else if (key?.name === "escape" || key?.name === "q" || key?.ctrl && key.name === "c") finish(null);
|
|
835
941
|
});
|
|
836
|
-
|
|
942
|
+
render2();
|
|
837
943
|
});
|
|
838
944
|
}
|
|
839
945
|
|
|
@@ -843,46 +949,46 @@ function renderFileBox(path, startLine, lines, hasMore) {
|
|
|
843
949
|
const p = stripControl(path);
|
|
844
950
|
const numW = String(startLine + Math.max(0, lines.length - 1)).length;
|
|
845
951
|
const header = startLine === 1 && !hasMore ? `${lines.length} line${lines.length === 1 ? "" : "s"}` : `lines ${startLine}\u2013${startLine + lines.length - 1}${hasMore ? " (+ more)" : ""}`;
|
|
846
|
-
const
|
|
847
|
-
lines.forEach((l, i) =>
|
|
848
|
-
if (hasMore)
|
|
849
|
-
|
|
850
|
-
return
|
|
952
|
+
const out3 = [color.dim("\u256D\u2500 ") + color.bold(p) + color.dim(` ${header}`)];
|
|
953
|
+
lines.forEach((l, i) => out3.push(color.dim("\u2502 ") + color.dim(String(startLine + i).padStart(numW)) + " " + stripControl(l)));
|
|
954
|
+
if (hasMore) out3.push(color.dim("\u2502 ") + color.dim(`\u2026 more (show ${p} offset ${startLine + lines.length})`));
|
|
955
|
+
out3.push(color.dim("\u2570\u2500"));
|
|
956
|
+
return out3.join("\n") + "\n";
|
|
851
957
|
}
|
|
852
958
|
function renderListing(path, names) {
|
|
853
959
|
const safe = names.map(stripControl);
|
|
854
|
-
const
|
|
960
|
+
const out3 = [color.dim("\u256D\u2500 ") + color.bold(stripControl(path)) + color.dim(` ${safe.length} entr${safe.length === 1 ? "y" : "ies"}`)];
|
|
855
961
|
if (safe.length === 0) {
|
|
856
|
-
|
|
962
|
+
out3.push(color.dim("\u2502 ") + color.dim("(empty)"));
|
|
857
963
|
} else {
|
|
858
964
|
const avail = BOX_W() - 2;
|
|
859
965
|
const colW = Math.min(Math.max(...safe.map((n) => n.length)) + 2, 40);
|
|
860
|
-
const
|
|
861
|
-
const rows2 = Math.ceil(safe.length /
|
|
966
|
+
const cols2 = Math.max(1, Math.floor(avail / colW));
|
|
967
|
+
const rows2 = Math.ceil(safe.length / cols2);
|
|
862
968
|
for (let r = 0; r < rows2; r++) {
|
|
863
969
|
let line = "";
|
|
864
|
-
for (let c = 0; c <
|
|
970
|
+
for (let c = 0; c < cols2; c++) {
|
|
865
971
|
const idx = c * rows2 + r;
|
|
866
972
|
if (idx >= safe.length) continue;
|
|
867
973
|
const n = safe[idx];
|
|
868
974
|
line += (n.endsWith("/") ? color.cyan(n) : n) + " ".repeat(Math.max(1, colW - n.length));
|
|
869
975
|
}
|
|
870
|
-
|
|
976
|
+
out3.push(color.dim("\u2502 ") + line.replace(/\s+$/, ""));
|
|
871
977
|
}
|
|
872
978
|
}
|
|
873
|
-
|
|
874
|
-
return
|
|
979
|
+
out3.push(color.dim("\u2570\u2500"));
|
|
980
|
+
return out3.join("\n") + "\n";
|
|
875
981
|
}
|
|
876
982
|
function renderTree(path, items, truncated) {
|
|
877
983
|
const dirs = items.filter((it) => it.isDir).length;
|
|
878
|
-
const
|
|
984
|
+
const out3 = [color.dim("\u256D\u2500 ") + color.bold(stripControl(path)) + color.dim(` ${items.length} entries \xB7 ${dirs} folders`)];
|
|
879
985
|
for (const it of items) {
|
|
880
986
|
const nm = stripControl(it.name);
|
|
881
|
-
|
|
987
|
+
out3.push(color.dim("\u2502 ") + color.dim(it.prefix) + (it.isDir ? color.cyan(nm) : nm));
|
|
882
988
|
}
|
|
883
|
-
if (truncated)
|
|
884
|
-
|
|
885
|
-
return
|
|
989
|
+
if (truncated) out3.push(color.dim("\u2502 ") + color.dim("\u2026 (truncated)"));
|
|
990
|
+
out3.push(color.dim("\u2570\u2500"));
|
|
991
|
+
return out3.join("\n") + "\n";
|
|
886
992
|
}
|
|
887
993
|
var SHOW_MARK = "";
|
|
888
994
|
var SHOW_KINDS = ["file", "dir", "tree"];
|
|
@@ -991,14 +1097,14 @@ function renderTable(rows2) {
|
|
|
991
1097
|
})
|
|
992
1098
|
);
|
|
993
1099
|
const width = Array.from({ length: ncol }, (_, c) => Math.max(...styled.map((r) => visLen(r[c]))));
|
|
994
|
-
const
|
|
1100
|
+
const out3 = styled.map((r) => {
|
|
995
1101
|
const cells = r.map((cell, c) => cell + " ".repeat(Math.max(0, width[c] - visLen(cell))));
|
|
996
1102
|
return " " + cells.join(color.dim(" \u2502 "));
|
|
997
1103
|
});
|
|
998
|
-
return
|
|
1104
|
+
return out3.join("\n") + "\n";
|
|
999
1105
|
}
|
|
1000
1106
|
function createMarkdownStream(write) {
|
|
1001
|
-
let
|
|
1107
|
+
let buf2 = "";
|
|
1002
1108
|
let scanFrom = 0;
|
|
1003
1109
|
let inCode = false;
|
|
1004
1110
|
let first = true;
|
|
@@ -1037,19 +1143,19 @@ function createMarkdownStream(write) {
|
|
|
1037
1143
|
}
|
|
1038
1144
|
return {
|
|
1039
1145
|
push(text) {
|
|
1040
|
-
|
|
1146
|
+
buf2 += text;
|
|
1041
1147
|
let i;
|
|
1042
|
-
while ((i =
|
|
1043
|
-
emit(
|
|
1044
|
-
|
|
1148
|
+
while ((i = buf2.indexOf("\n", scanFrom)) >= 0) {
|
|
1149
|
+
emit(buf2.slice(0, i));
|
|
1150
|
+
buf2 = buf2.slice(i + 1);
|
|
1045
1151
|
scanFrom = 0;
|
|
1046
1152
|
}
|
|
1047
|
-
scanFrom =
|
|
1153
|
+
scanFrom = buf2.length;
|
|
1048
1154
|
},
|
|
1049
1155
|
end() {
|
|
1050
|
-
if (
|
|
1051
|
-
emit(
|
|
1052
|
-
|
|
1156
|
+
if (buf2) {
|
|
1157
|
+
emit(buf2);
|
|
1158
|
+
buf2 = "";
|
|
1053
1159
|
}
|
|
1054
1160
|
flushTable();
|
|
1055
1161
|
if (inCode) {
|
|
@@ -1247,7 +1353,7 @@ async function runExplorer(task, focus, signal) {
|
|
|
1247
1353
|
|
|
1248
1354
|
// src/safety.ts
|
|
1249
1355
|
import { homedir as homedir3 } from "node:os";
|
|
1250
|
-
import { basename } from "node:path";
|
|
1356
|
+
import { basename as basename2 } from "node:path";
|
|
1251
1357
|
function pathGuard(args) {
|
|
1252
1358
|
const { abs, inRoot } = resolveInRoot(String(args.path ?? "."));
|
|
1253
1359
|
return inRoot ? {} : { needsApproval: true, reason: `path is outside the project root: ${abs}`, cacheKey: `path:${abs}` };
|
|
@@ -1255,7 +1361,7 @@ function pathGuard(args) {
|
|
|
1255
1361
|
var SECRET_FILE = /(^|\/)(\.env(\.[\w.-]+)?|[\w.-]*\.(env|pem|key|secret|pfx|p12|jks|keystore)|id_(rsa|ed25519|ecdsa|dsa)|credentials|\.git-credentials|\.pgpass|\.npmrc|\.netrc)$/i;
|
|
1256
1362
|
function isSecretPath(userPath) {
|
|
1257
1363
|
const { abs } = resolveInRoot(userPath);
|
|
1258
|
-
return SECRET_FILE.test(abs) || SECRET_FILE.test(
|
|
1364
|
+
return SECRET_FILE.test(abs) || SECRET_FILE.test(basename2(abs));
|
|
1259
1365
|
}
|
|
1260
1366
|
function secretGuard(args) {
|
|
1261
1367
|
const p = pathGuard(args);
|
|
@@ -1471,13 +1577,13 @@ async function atomicWrite(abs, content) {
|
|
|
1471
1577
|
}
|
|
1472
1578
|
var READ_PREFIX = /^ *\d+ {2}/;
|
|
1473
1579
|
function allIndexOf(hay, needle) {
|
|
1474
|
-
const
|
|
1580
|
+
const out3 = [];
|
|
1475
1581
|
let i = hay.indexOf(needle);
|
|
1476
1582
|
while (i !== -1) {
|
|
1477
|
-
|
|
1583
|
+
out3.push(i);
|
|
1478
1584
|
i = hay.indexOf(needle, i + 1);
|
|
1479
1585
|
}
|
|
1480
|
-
return
|
|
1586
|
+
return out3;
|
|
1481
1587
|
}
|
|
1482
1588
|
function stripReadPrefix(text) {
|
|
1483
1589
|
const lines = text.split("\n");
|
|
@@ -1972,11 +2078,11 @@ ${res.closest}` : `Re-read the file and copy the exact text (including whitespac
|
|
|
1972
2078
|
${stderr}` : "") || "(no output)";
|
|
1973
2079
|
} catch (err) {
|
|
1974
2080
|
const e = err;
|
|
1975
|
-
const
|
|
2081
|
+
const out3 = `${e.stdout ?? ""}${e.stderr ? `
|
|
1976
2082
|
[stderr]
|
|
1977
2083
|
${e.stderr}` : ""}`.trim();
|
|
1978
|
-
return `Error: command failed${
|
|
1979
|
-
${
|
|
2084
|
+
return `Error: command failed${out3 ? `:
|
|
2085
|
+
${out3}` : `: ${String(e.message ?? err)}`}`;
|
|
1980
2086
|
}
|
|
1981
2087
|
}
|
|
1982
2088
|
},
|
|
@@ -2221,14 +2327,14 @@ function validateArgs(tool, args) {
|
|
|
2221
2327
|
async function runVerify(signal) {
|
|
2222
2328
|
try {
|
|
2223
2329
|
const { stdout, stderr } = await runShell(config.verifyCommand, { timeout: config.verifyTimeoutMs, maxBuffer: config.maxToolBuffer, signal });
|
|
2224
|
-
const
|
|
2225
|
-
return `passed \u2713${
|
|
2226
|
-
${
|
|
2330
|
+
const out3 = `${stdout}${stderr}`.trim();
|
|
2331
|
+
return `passed \u2713${out3 ? `
|
|
2332
|
+
${out3.slice(-800)}` : ""}`;
|
|
2227
2333
|
} catch (err) {
|
|
2228
2334
|
const e = err;
|
|
2229
|
-
const
|
|
2335
|
+
const out3 = `${e.stdout ?? ""}${e.stderr ?? ""}`.trim() || String(e.message ?? err);
|
|
2230
2336
|
return `FAILED \u2717
|
|
2231
|
-
${
|
|
2337
|
+
${out3.slice(-1500)}`;
|
|
2232
2338
|
}
|
|
2233
2339
|
}
|
|
2234
2340
|
|
|
@@ -2261,12 +2367,12 @@ function parseSSELine(line) {
|
|
|
2261
2367
|
}
|
|
2262
2368
|
const delta = parsed.choices?.[0]?.delta;
|
|
2263
2369
|
if (!delta) return null;
|
|
2264
|
-
const
|
|
2265
|
-
if (delta.content)
|
|
2266
|
-
if (delta.tool_calls)
|
|
2267
|
-
if (typeof delta.reasoning === "string" && delta.reasoning)
|
|
2268
|
-
if (Array.isArray(delta.reasoning_details) && delta.reasoning_details.length)
|
|
2269
|
-
return
|
|
2370
|
+
const out3 = {};
|
|
2371
|
+
if (delta.content) out3.content = delta.content;
|
|
2372
|
+
if (delta.tool_calls) out3.toolCalls = delta.tool_calls;
|
|
2373
|
+
if (typeof delta.reasoning === "string" && delta.reasoning) out3.reasoning = delta.reasoning;
|
|
2374
|
+
if (Array.isArray(delta.reasoning_details) && delta.reasoning_details.length) out3.reasoningDetails = delta.reasoning_details;
|
|
2375
|
+
return out3;
|
|
2270
2376
|
}
|
|
2271
2377
|
function buildRequestBody(opts) {
|
|
2272
2378
|
const { model, messages, includeTools, effort, reasoningSupported, extra, tools } = opts;
|
|
@@ -2672,7 +2778,7 @@ async function saveSession(messages) {
|
|
|
2672
2778
|
}
|
|
2673
2779
|
function sanitizeSession(raw) {
|
|
2674
2780
|
if (!Array.isArray(raw)) return null;
|
|
2675
|
-
const
|
|
2781
|
+
const out3 = [];
|
|
2676
2782
|
for (const m of raw) {
|
|
2677
2783
|
if (!m || typeof m !== "object") return null;
|
|
2678
2784
|
const role = m.role;
|
|
@@ -2685,9 +2791,9 @@ function sanitizeSession(raw) {
|
|
|
2685
2791
|
if (Array.isArray(tc)) msg.tool_calls = tc;
|
|
2686
2792
|
const tcid = m.tool_call_id;
|
|
2687
2793
|
if (typeof tcid === "string") msg.tool_call_id = tcid;
|
|
2688
|
-
|
|
2794
|
+
out3.push(msg);
|
|
2689
2795
|
}
|
|
2690
|
-
return dropIncompleteToolTail(
|
|
2796
|
+
return dropIncompleteToolTail(out3);
|
|
2691
2797
|
}
|
|
2692
2798
|
function dropIncompleteToolTail(messages) {
|
|
2693
2799
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -2730,15 +2836,15 @@ async function loadLatestSession() {
|
|
|
2730
2836
|
async function listSessions() {
|
|
2731
2837
|
try {
|
|
2732
2838
|
const files = (await readdir2(sessionsDir())).filter((f) => f.endsWith(".json"));
|
|
2733
|
-
const
|
|
2839
|
+
const out3 = [];
|
|
2734
2840
|
for (const f of files) {
|
|
2735
2841
|
const msgs = await readSession(f);
|
|
2736
2842
|
if (!msgs || !msgs.length) continue;
|
|
2737
2843
|
const firstUser = msgs.find((m) => m.role === "user");
|
|
2738
2844
|
const preview = (firstUser?.content ?? "").replace(/\s+/g, " ").trim().slice(0, 60);
|
|
2739
|
-
|
|
2845
|
+
out3.push({ file: f, when: Number(f.replace(".json", "")) || 0, count: msgs.length, preview });
|
|
2740
2846
|
}
|
|
2741
|
-
return
|
|
2847
|
+
return out3.sort((a, b) => b.when - a.when);
|
|
2742
2848
|
} catch {
|
|
2743
2849
|
return [];
|
|
2744
2850
|
}
|
|
@@ -2978,7 +3084,7 @@ ${content}` }];
|
|
|
2978
3084
|
}
|
|
2979
3085
|
return [...messages, { role: "user", content }];
|
|
2980
3086
|
}
|
|
2981
|
-
async function runTurn(messages, userInput, ask, approvedTools, approvedGuardKeys, signal,
|
|
3087
|
+
async function runTurn(messages, userInput, ask, approvedTools, approvedGuardKeys, signal, steering2) {
|
|
2982
3088
|
messages.push({ role: "user", content: userInput });
|
|
2983
3089
|
const snapshot = messages.slice();
|
|
2984
3090
|
try {
|
|
@@ -2992,7 +3098,7 @@ async function runTurn(messages, userInput, ask, approvedTools, approvedGuardKey
|
|
|
2992
3098
|
console.error(color.red(`
|
|
2993
3099
|
[compaction failed: ${err.message} \u2014 continuing]`) + "\n");
|
|
2994
3100
|
}
|
|
2995
|
-
if (
|
|
3101
|
+
if (steering2?.length) messages = applySteering(messages, steering2.splice(0));
|
|
2996
3102
|
const message = await callModel(messages, true, signal);
|
|
2997
3103
|
messages.push(message);
|
|
2998
3104
|
if (!hasContent2(message)) {
|
|
@@ -3006,7 +3112,7 @@ async function runTurn(messages, userInput, ask, approvedTools, approvedGuardKey
|
|
|
3006
3112
|
await handleToolCall(call, messages, step, deps);
|
|
3007
3113
|
}
|
|
3008
3114
|
} else {
|
|
3009
|
-
answered = !
|
|
3115
|
+
answered = !steering2?.length;
|
|
3010
3116
|
}
|
|
3011
3117
|
}
|
|
3012
3118
|
if (signal?.aborted) {
|
|
@@ -3078,62 +3184,395 @@ async function runtimeContext() {
|
|
|
3078
3184
|
});
|
|
3079
3185
|
}
|
|
3080
3186
|
|
|
3081
|
-
// src/
|
|
3187
|
+
// src/chrome.ts
|
|
3082
3188
|
import { execFile as execFile2 } from "node:child_process";
|
|
3189
|
+
var out2 = (s) => process.stdout.write(s);
|
|
3190
|
+
var rows = () => process.stdout.rows ?? 24;
|
|
3191
|
+
var cols = () => process.stdout.columns ?? 80;
|
|
3083
3192
|
var active2 = false;
|
|
3084
|
-
var
|
|
3085
|
-
var
|
|
3193
|
+
var turnActive = false;
|
|
3194
|
+
var buf = "";
|
|
3195
|
+
var cur = 0;
|
|
3196
|
+
var allItems = [];
|
|
3197
|
+
var sel = 0;
|
|
3198
|
+
var menuHidden = false;
|
|
3199
|
+
var menuH = 0;
|
|
3200
|
+
var prevMenuH = 0;
|
|
3201
|
+
var lastRegionB = -1;
|
|
3202
|
+
var pickItems = null;
|
|
3203
|
+
var pickSel = 0;
|
|
3204
|
+
var pickTitle = "";
|
|
3205
|
+
var pickResolve = null;
|
|
3086
3206
|
var branch = "";
|
|
3087
|
-
var tokensOf =
|
|
3088
|
-
var
|
|
3207
|
+
var tokensOf = () => 0;
|
|
3208
|
+
var onResult = null;
|
|
3209
|
+
var onInterrupt = null;
|
|
3210
|
+
var steering = [];
|
|
3211
|
+
var restoreKeys = null;
|
|
3212
|
+
var gitTimer = null;
|
|
3213
|
+
var statusTimer = null;
|
|
3214
|
+
var lastRows = 0;
|
|
3215
|
+
var BURST_IDLE_MS = 8;
|
|
3216
|
+
var burstLen = 0;
|
|
3217
|
+
var burstTimer = null;
|
|
3218
|
+
var pendingRender = false;
|
|
3219
|
+
var statusRow = () => Math.max(1, rows());
|
|
3220
|
+
var borderBottomRow = () => Math.max(1, rows() - 1);
|
|
3221
|
+
var inputRow = () => Math.max(1, rows() - 2);
|
|
3222
|
+
var borderTopRow = () => Math.max(1, rows() - 3);
|
|
3223
|
+
var mark = () => color.green("\u203A ");
|
|
3224
|
+
var PW = 2;
|
|
3225
|
+
function modeSegment() {
|
|
3226
|
+
return state.mode === "auto" ? color.yellow("auto-approve") : state.mode === "readonly" ? color.cyan("read-only") : color.green("normal");
|
|
3227
|
+
}
|
|
3228
|
+
function statusText() {
|
|
3229
|
+
const model = state.model.split("/").pop() ?? state.model;
|
|
3230
|
+
const tok = tokensOf();
|
|
3231
|
+
const ctxK = Math.round(config.maxContextTokens / 1e3);
|
|
3232
|
+
const parts = [
|
|
3233
|
+
modeSegment(),
|
|
3234
|
+
color.cyan(model),
|
|
3235
|
+
color.dim(state.reasoningEffort),
|
|
3236
|
+
...branch ? [color.green(branch)] : [],
|
|
3237
|
+
color.dim(`~${Math.round(tok / 1e3)}k/${ctxK}k`)
|
|
3238
|
+
];
|
|
3239
|
+
const bg = runningTaskCount();
|
|
3240
|
+
if (bg > 0) parts.push(color.yellow(`${bg} task${bg === 1 ? "" : "s"}`));
|
|
3241
|
+
return parts.join(color.dim(" \xB7 "));
|
|
3242
|
+
}
|
|
3089
3243
|
function pollGit() {
|
|
3090
|
-
execFile2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: 1500, windowsHide: true }, (err,
|
|
3244
|
+
execFile2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: 1500, windowsHide: true }, (err, o) => {
|
|
3091
3245
|
if (err) {
|
|
3092
3246
|
branch = "";
|
|
3093
3247
|
return;
|
|
3094
3248
|
}
|
|
3095
|
-
const b = String(
|
|
3096
|
-
execFile2("git", ["status", "--porcelain"], { timeout: 1500, windowsHide: true }, (e2,
|
|
3097
|
-
branch = b + (!e2 && String(
|
|
3249
|
+
const b = String(o).trim();
|
|
3250
|
+
execFile2("git", ["status", "--porcelain"], { timeout: 1500, windowsHide: true }, (e2, o2) => {
|
|
3251
|
+
branch = b + (!e2 && String(o2).trim() ? "*" : "");
|
|
3098
3252
|
});
|
|
3099
3253
|
});
|
|
3100
3254
|
}
|
|
3101
|
-
function
|
|
3102
|
-
const
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
const bg = runningTaskCount();
|
|
3106
|
-
if (bg > 0) parts.push(`${bg} task${bg === 1 ? "" : "s"}`);
|
|
3107
|
-
return parts.join(" \xB7 ");
|
|
3255
|
+
function drawStatus() {
|
|
3256
|
+
const s = statusText();
|
|
3257
|
+
const vis = stripAnsi(s);
|
|
3258
|
+
out2(ansi.moveTo(statusRow()) + ansi.clearLine + (vis.length <= cols() ? s : stripAnsi(s).slice(0, cols() - 1)));
|
|
3108
3259
|
}
|
|
3109
|
-
function
|
|
3260
|
+
function drawBorder(row) {
|
|
3261
|
+
out2(ansi.moveTo(row) + ansi.clearLine + color.dim("\u2500".repeat(Math.max(1, cols()))));
|
|
3262
|
+
}
|
|
3263
|
+
var closePick = () => {
|
|
3264
|
+
pickItems = null;
|
|
3265
|
+
pickSel = 0;
|
|
3266
|
+
pickTitle = "";
|
|
3267
|
+
};
|
|
3268
|
+
function chromePick(items, initial = 0, title = "") {
|
|
3269
|
+
return new Promise((resolve2) => {
|
|
3270
|
+
if (!active2 || !items.length) {
|
|
3271
|
+
resolve2(null);
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
pickItems = items;
|
|
3275
|
+
pickSel = Math.min(Math.max(0, initial), items.length - 1);
|
|
3276
|
+
pickTitle = title;
|
|
3277
|
+
pickResolve = resolve2;
|
|
3278
|
+
render();
|
|
3279
|
+
});
|
|
3280
|
+
}
|
|
3281
|
+
var flat = (s) => s.replace(/\n/g, "\u23CE");
|
|
3282
|
+
function drawInput() {
|
|
3283
|
+
const disp = flat(buf);
|
|
3284
|
+
const start = windowStart(disp, cur, Math.max(1, cols() - PW));
|
|
3285
|
+
const shown = disp.slice(start);
|
|
3286
|
+
const ci = cur - start;
|
|
3287
|
+
let body;
|
|
3288
|
+
if (!buf) {
|
|
3289
|
+
body = ansi.reverse + " " + ansi.reverseOff + color.dim("type a message \xB7 / for commands \xB7 Shift+Tab mode");
|
|
3290
|
+
} else {
|
|
3291
|
+
const at = ci < shown.length ? shown[ci] : " ";
|
|
3292
|
+
body = shown.slice(0, ci) + ansi.reverse + at + ansi.reverseOff + (ci < shown.length ? shown.slice(ci + 1) : "");
|
|
3293
|
+
}
|
|
3294
|
+
out2(ansi.moveTo(inputRow()) + ansi.clearLine + mark() + body);
|
|
3295
|
+
}
|
|
3296
|
+
function currentMenu() {
|
|
3297
|
+
if (menuHidden || turnActive) return [];
|
|
3298
|
+
const m = buf.match(/^\/(\S*)$/);
|
|
3299
|
+
if (!m) return [];
|
|
3300
|
+
const pre = "/" + m[1];
|
|
3301
|
+
return allItems.filter((c) => c.name.startsWith(pre)).slice(0, 8);
|
|
3302
|
+
}
|
|
3303
|
+
function render() {
|
|
3110
3304
|
if (!active2) return;
|
|
3111
|
-
|
|
3305
|
+
const picker = pickItems;
|
|
3306
|
+
const list = picker ? picker.slice(0, 12).map((it, i) => ({ text: it.label + (it.hint ? " " + color.dim(it.hint) : ""), on: i === pickSel })) : currentMenu().map((it, i) => ({ text: it.name.padEnd(12) + " " + it.desc, on: i === sel }));
|
|
3307
|
+
if (!picker && sel >= list.length) sel = Math.max(0, list.length - 1);
|
|
3308
|
+
menuH = list.length;
|
|
3309
|
+
out2(ansi.hideCursor + ansi.saveCursor);
|
|
3310
|
+
const regionB = Math.max(1, rows() - 4 - menuH);
|
|
3311
|
+
if (regionB !== lastRegionB) {
|
|
3312
|
+
out2(ansi.setRegion(1, regionB));
|
|
3313
|
+
lastRegionB = regionB;
|
|
3314
|
+
}
|
|
3315
|
+
for (let r = borderTopRow() - prevMenuH; r < borderTopRow() - menuH; r++) out2(ansi.moveTo(r) + ansi.clearLine);
|
|
3316
|
+
prevMenuH = menuH;
|
|
3317
|
+
const base = borderTopRow() - menuH;
|
|
3318
|
+
for (let i = 0; i < list.length; i++) {
|
|
3319
|
+
out2(ansi.moveTo(base + i) + ansi.clearLine + (list[i].on ? color.green("\u203A ") + list[i].text : color.dim(" ") + list[i].text));
|
|
3320
|
+
}
|
|
3321
|
+
drawBorder(borderTopRow());
|
|
3322
|
+
if (picker) out2(ansi.moveTo(inputRow()) + ansi.clearLine + mark() + color.dim(pickTitle || "\u2191/\u2193 to choose \xB7 Enter to select \xB7 Esc to cancel"));
|
|
3323
|
+
else drawInput();
|
|
3324
|
+
drawBorder(borderBottomRow());
|
|
3325
|
+
drawStatus();
|
|
3326
|
+
out2(ansi.restoreCursor);
|
|
3327
|
+
lastRows = rows();
|
|
3328
|
+
pendingRender = false;
|
|
3112
3329
|
}
|
|
3113
3330
|
function onResize() {
|
|
3114
3331
|
if (!active2) return;
|
|
3115
|
-
|
|
3116
|
-
|
|
3332
|
+
const newRows = rows();
|
|
3333
|
+
if (lastRows > 0 && newRows > lastRows) {
|
|
3334
|
+
out2(ansi.saveCursor);
|
|
3335
|
+
const oldTop = Math.max(1, lastRows - 3 - prevMenuH);
|
|
3336
|
+
for (let r = oldTop; r <= lastRows; r++) out2(ansi.moveTo(r) + ansi.clearLine);
|
|
3337
|
+
out2(ansi.restoreCursor);
|
|
3338
|
+
}
|
|
3339
|
+
lastRegionB = -1;
|
|
3340
|
+
render();
|
|
3341
|
+
}
|
|
3342
|
+
function refreshChrome() {
|
|
3343
|
+
if (!active2) return;
|
|
3344
|
+
out2(ansi.hideCursor + ansi.saveCursor);
|
|
3345
|
+
drawStatus();
|
|
3346
|
+
out2(ansi.restoreCursor);
|
|
3347
|
+
}
|
|
3348
|
+
var edited = () => {
|
|
3349
|
+
menuHidden = false;
|
|
3350
|
+
sel = 0;
|
|
3351
|
+
};
|
|
3352
|
+
function insert(s) {
|
|
3353
|
+
buf = buf.slice(0, cur) + s + buf.slice(cur);
|
|
3354
|
+
cur += s.length;
|
|
3355
|
+
edited();
|
|
3356
|
+
pendingRender = true;
|
|
3357
|
+
}
|
|
3358
|
+
function resetBurst() {
|
|
3359
|
+
if (burstTimer) {
|
|
3360
|
+
clearTimeout(burstTimer);
|
|
3361
|
+
burstTimer = null;
|
|
3362
|
+
}
|
|
3363
|
+
burstLen = 0;
|
|
3364
|
+
pendingRender = false;
|
|
3365
|
+
}
|
|
3366
|
+
function onKey(str, key) {
|
|
3367
|
+
if (pickItems) {
|
|
3368
|
+
const n = pickItems.length;
|
|
3369
|
+
if (key?.name === "up") {
|
|
3370
|
+
pickSel = (pickSel - 1 + n) % n;
|
|
3371
|
+
render();
|
|
3372
|
+
return;
|
|
3373
|
+
}
|
|
3374
|
+
if (key?.name === "down") {
|
|
3375
|
+
pickSel = (pickSel + 1) % n;
|
|
3376
|
+
render();
|
|
3377
|
+
return;
|
|
3378
|
+
}
|
|
3379
|
+
if (key?.name === "return" || key?.name === "enter") {
|
|
3380
|
+
const v = pickItems[pickSel].value;
|
|
3381
|
+
closePick();
|
|
3382
|
+
render();
|
|
3383
|
+
pickResolve?.(v);
|
|
3384
|
+
pickResolve = null;
|
|
3385
|
+
return;
|
|
3386
|
+
}
|
|
3387
|
+
if (key?.name === "escape" || key?.ctrl && key.name === "c") {
|
|
3388
|
+
closePick();
|
|
3389
|
+
render();
|
|
3390
|
+
pickResolve?.(null);
|
|
3391
|
+
pickResolve = null;
|
|
3392
|
+
return;
|
|
3393
|
+
}
|
|
3394
|
+
return;
|
|
3395
|
+
}
|
|
3396
|
+
burstLen++;
|
|
3397
|
+
if (burstTimer) clearTimeout(burstTimer);
|
|
3398
|
+
burstTimer = setTimeout(() => {
|
|
3399
|
+
burstTimer = null;
|
|
3400
|
+
burstLen = 0;
|
|
3401
|
+
if (pendingRender) render();
|
|
3402
|
+
}, BURST_IDLE_MS);
|
|
3403
|
+
const mm = currentMenu();
|
|
3404
|
+
if (key?.ctrl && key.name === "c") {
|
|
3405
|
+
if (buf) {
|
|
3406
|
+
buf = "";
|
|
3407
|
+
cur = 0;
|
|
3408
|
+
edited();
|
|
3409
|
+
resetBurst();
|
|
3410
|
+
render();
|
|
3411
|
+
return;
|
|
3412
|
+
}
|
|
3413
|
+
if (turnActive) onInterrupt?.();
|
|
3414
|
+
else {
|
|
3415
|
+
const r = onResult;
|
|
3416
|
+
onResult = null;
|
|
3417
|
+
r?.({ type: "quit" });
|
|
3418
|
+
}
|
|
3419
|
+
return;
|
|
3420
|
+
}
|
|
3421
|
+
if (key?.name === "escape") {
|
|
3422
|
+
if (mm.length) {
|
|
3423
|
+
menuHidden = true;
|
|
3424
|
+
render();
|
|
3425
|
+
} else if (buf) {
|
|
3426
|
+
buf = "";
|
|
3427
|
+
cur = 0;
|
|
3428
|
+
render();
|
|
3429
|
+
}
|
|
3430
|
+
return;
|
|
3431
|
+
}
|
|
3432
|
+
if (key?.name === "tab" && key.shift) {
|
|
3433
|
+
state.mode = nextMode(state.mode);
|
|
3434
|
+
render();
|
|
3435
|
+
return;
|
|
3436
|
+
}
|
|
3437
|
+
if (key?.name === "tab") {
|
|
3438
|
+
if (mm.length) {
|
|
3439
|
+
buf = mm[sel].name + " ";
|
|
3440
|
+
cur = buf.length;
|
|
3441
|
+
edited();
|
|
3442
|
+
render();
|
|
3443
|
+
}
|
|
3444
|
+
return;
|
|
3445
|
+
}
|
|
3446
|
+
if (key?.name === "up") {
|
|
3447
|
+
if (mm.length) {
|
|
3448
|
+
sel = (sel - 1 + mm.length) % mm.length;
|
|
3449
|
+
render();
|
|
3450
|
+
}
|
|
3451
|
+
return;
|
|
3452
|
+
}
|
|
3453
|
+
if (key?.name === "down") {
|
|
3454
|
+
if (mm.length) {
|
|
3455
|
+
sel = (sel + 1) % mm.length;
|
|
3456
|
+
render();
|
|
3457
|
+
}
|
|
3458
|
+
return;
|
|
3459
|
+
}
|
|
3460
|
+
if (key?.name === "return" || key?.name === "enter") {
|
|
3461
|
+
if (key?.shift || key?.meta || burstLen > 1) {
|
|
3462
|
+
insert("\n");
|
|
3463
|
+
return;
|
|
3464
|
+
}
|
|
3465
|
+
resetBurst();
|
|
3466
|
+
const line = buf;
|
|
3467
|
+
buf = "";
|
|
3468
|
+
cur = 0;
|
|
3469
|
+
edited();
|
|
3470
|
+
render();
|
|
3471
|
+
if (turnActive) {
|
|
3472
|
+
if (line.trim()) steering.push(line.trim());
|
|
3473
|
+
return;
|
|
3474
|
+
}
|
|
3475
|
+
if (line.trim()) out2("\n" + mark() + line + "\n");
|
|
3476
|
+
const r = onResult;
|
|
3477
|
+
onResult = null;
|
|
3478
|
+
r?.({ type: "line", value: line });
|
|
3479
|
+
return;
|
|
3480
|
+
}
|
|
3481
|
+
if (key?.name === "backspace") {
|
|
3482
|
+
if (cur > 0) {
|
|
3483
|
+
buf = buf.slice(0, cur - 1) + buf.slice(cur);
|
|
3484
|
+
cur--;
|
|
3485
|
+
edited();
|
|
3486
|
+
render();
|
|
3487
|
+
}
|
|
3488
|
+
return;
|
|
3489
|
+
}
|
|
3490
|
+
if (key?.name === "delete") {
|
|
3491
|
+
if (cur < buf.length) {
|
|
3492
|
+
buf = buf.slice(0, cur) + buf.slice(cur + 1);
|
|
3493
|
+
edited();
|
|
3494
|
+
render();
|
|
3495
|
+
}
|
|
3496
|
+
return;
|
|
3497
|
+
}
|
|
3498
|
+
if (key?.name === "left") {
|
|
3499
|
+
if (cur > 0) {
|
|
3500
|
+
cur--;
|
|
3501
|
+
render();
|
|
3502
|
+
}
|
|
3503
|
+
return;
|
|
3504
|
+
}
|
|
3505
|
+
if (key?.name === "right") {
|
|
3506
|
+
if (cur < buf.length) {
|
|
3507
|
+
cur++;
|
|
3508
|
+
render();
|
|
3509
|
+
}
|
|
3510
|
+
return;
|
|
3511
|
+
}
|
|
3512
|
+
if (key && (key.name === "home" || key.ctrl && key.name === "a")) {
|
|
3513
|
+
cur = 0;
|
|
3514
|
+
render();
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3517
|
+
if (key && (key.name === "end" || key.ctrl && key.name === "e")) {
|
|
3518
|
+
cur = buf.length;
|
|
3519
|
+
render();
|
|
3520
|
+
return;
|
|
3521
|
+
}
|
|
3522
|
+
if (key?.ctrl && key.name === "u") {
|
|
3523
|
+
buf = buf.slice(cur);
|
|
3524
|
+
cur = 0;
|
|
3525
|
+
edited();
|
|
3526
|
+
render();
|
|
3527
|
+
return;
|
|
3528
|
+
}
|
|
3529
|
+
if (str && !key?.ctrl && !key?.meta && [...str].length === 1 && isPrintableCodePoint(str.codePointAt(0))) {
|
|
3530
|
+
insert(str);
|
|
3531
|
+
}
|
|
3117
3532
|
}
|
|
3118
|
-
function
|
|
3119
|
-
if (active2 || !process.stdout.isTTY
|
|
3533
|
+
function startChrome(opts) {
|
|
3534
|
+
if (active2 || !process.stdout.isTTY) return;
|
|
3120
3535
|
active2 = true;
|
|
3121
|
-
tokensOf = tokens;
|
|
3122
|
-
|
|
3536
|
+
tokensOf = opts.tokens;
|
|
3537
|
+
allItems = opts.items;
|
|
3538
|
+
onInterrupt = opts.onInterrupt;
|
|
3539
|
+
out2(ansi.bracketedPasteOff);
|
|
3123
3540
|
pollGit();
|
|
3124
|
-
|
|
3125
|
-
|
|
3541
|
+
restoreKeys = pushKeyHandler(onKey);
|
|
3542
|
+
render();
|
|
3543
|
+
statusTimer = setInterval(refreshChrome, config.statuslineRefreshMs);
|
|
3126
3544
|
gitTimer = setInterval(pollGit, 5e3);
|
|
3127
3545
|
process.stdout.on("resize", onResize);
|
|
3128
3546
|
}
|
|
3129
|
-
function
|
|
3547
|
+
function nextLine() {
|
|
3548
|
+
return new Promise((resolve2) => {
|
|
3549
|
+
turnActive = false;
|
|
3550
|
+
render();
|
|
3551
|
+
onResult = resolve2;
|
|
3552
|
+
});
|
|
3553
|
+
}
|
|
3554
|
+
function beginTurn() {
|
|
3555
|
+
steering.length = 0;
|
|
3556
|
+
turnActive = true;
|
|
3557
|
+
return steering;
|
|
3558
|
+
}
|
|
3559
|
+
function endTurn() {
|
|
3560
|
+
turnActive = false;
|
|
3561
|
+
render();
|
|
3562
|
+
}
|
|
3563
|
+
function stopChrome() {
|
|
3130
3564
|
if (!active2) return;
|
|
3131
3565
|
active2 = false;
|
|
3132
|
-
|
|
3133
|
-
|
|
3566
|
+
restoreKeys?.();
|
|
3567
|
+
restoreKeys = null;
|
|
3134
3568
|
process.stdout.removeListener("resize", onResize);
|
|
3135
|
-
|
|
3569
|
+
if (statusTimer) clearInterval(statusTimer);
|
|
3570
|
+
if (gitTimer) clearInterval(gitTimer);
|
|
3571
|
+
resetBurst();
|
|
3572
|
+
out2(ansi.resetRegion);
|
|
3573
|
+
for (const r of [statusRow(), borderBottomRow(), inputRow(), borderTopRow()]) out2(ansi.moveTo(r) + ansi.clearLine);
|
|
3136
3574
|
}
|
|
3575
|
+
var chromeEnabled = () => config.statuslineEnabled && !!process.stdout.isTTY;
|
|
3137
3576
|
|
|
3138
3577
|
// src/commands.ts
|
|
3139
3578
|
import { writeFile as writeFile4, mkdir as mkdir4, chmod as chmod3 } from "node:fs/promises";
|
|
@@ -3187,6 +3626,13 @@ ${extra}` : "");
|
|
|
3187
3626
|
}
|
|
3188
3627
|
|
|
3189
3628
|
// src/commands.ts
|
|
3629
|
+
async function pick(opts) {
|
|
3630
|
+
if (chromeEnabled()) {
|
|
3631
|
+
const v = await chromePick(opts.items.map((it) => ({ label: it.label, hint: it.hint, value: it.value })), opts.initial, opts.title);
|
|
3632
|
+
return v == null ? null : v;
|
|
3633
|
+
}
|
|
3634
|
+
return selectMenu(opts);
|
|
3635
|
+
}
|
|
3190
3636
|
var SLASH_COMMANDS = [
|
|
3191
3637
|
{ name: "/model", desc: "switch model (menu; /model <term> searches)" },
|
|
3192
3638
|
{ name: "/effort", desc: "set reasoning effort (off/low/medium/high/max)" },
|
|
@@ -3264,20 +3710,23 @@ async function handleCommand(input, messages) {
|
|
|
3264
3710
|
} else if (cmd === "/update") {
|
|
3265
3711
|
console.log(color.dim("updating beecork\u2026 (npm install -g beecork@latest)"));
|
|
3266
3712
|
const { ok, output } = await selfUpdate();
|
|
3267
|
-
if (ok)
|
|
3268
|
-
|
|
3713
|
+
if (ok) {
|
|
3714
|
+
console.log(color.green("\u2713 updated \u2014 restart beecork to use the new version.") + "\n");
|
|
3715
|
+
const w = await shadowWarning();
|
|
3716
|
+
if (w) console.log(color.yellow(w) + "\n");
|
|
3717
|
+
} else {
|
|
3269
3718
|
console.log(color.red("update failed: ") + output.split("\n").filter(Boolean).slice(-1)[0]);
|
|
3270
3719
|
console.log(color.dim(" run manually: npm install -g beecork (may need sudo / your version manager)") + "\n");
|
|
3271
3720
|
}
|
|
3272
3721
|
} else if (cmd === "/clear") {
|
|
3273
3722
|
messages.splice(1);
|
|
3274
|
-
if (process.stdout.isTTY) process.stdout.write(
|
|
3723
|
+
if (process.stdout.isTTY) process.stdout.write(ansi.clearScreen + ansi.clearScrollback + ansi.home);
|
|
3275
3724
|
console.log(color.dim("conversation cleared (kept the system prompt, your model + settings).") + "\n");
|
|
3276
3725
|
} else if (cmd === "/resume") {
|
|
3277
3726
|
const sessions = process.stdin.isTTY ? await listSessions() : [];
|
|
3278
3727
|
let restored = [];
|
|
3279
3728
|
if (sessions.length > 1) {
|
|
3280
|
-
const choice = await
|
|
3729
|
+
const choice = await pick({
|
|
3281
3730
|
title: "resume which session? \u2014 \u2191/\u2193 then Enter (Esc to cancel)",
|
|
3282
3731
|
items: sessions.map((s) => ({
|
|
3283
3732
|
label: s.preview || "(no first message)",
|
|
@@ -3330,7 +3779,7 @@ async function handleCommand(input, messages) {
|
|
|
3330
3779
|
}
|
|
3331
3780
|
async function pickModel() {
|
|
3332
3781
|
if (!process.stdin.isTTY) return showRecommended();
|
|
3333
|
-
const choice = await
|
|
3782
|
+
const choice = await pick({
|
|
3334
3783
|
title: "switch model \u2014 \u2191/\u2193 then Enter (Esc to cancel)",
|
|
3335
3784
|
initial: Math.max(0, RECOMMENDED_MODELS.findIndex((m) => m.slug === state.model)),
|
|
3336
3785
|
items: RECOMMENDED_MODELS.map((m) => ({
|
|
@@ -3353,7 +3802,7 @@ async function pickEffort() {
|
|
|
3353
3802
|
console.log(color.cyan(`reasoning effort: ${state.reasoningEffort}`) + color.dim(` (levels: ${EFFORTS.join(" / ")})`) + "\n");
|
|
3354
3803
|
return;
|
|
3355
3804
|
}
|
|
3356
|
-
const choice = await
|
|
3805
|
+
const choice = await pick({
|
|
3357
3806
|
title: "reasoning effort \u2014 \u2191/\u2193 then Enter (Esc to cancel)",
|
|
3358
3807
|
initial: Math.max(0, EFFORTS.indexOf(state.reasoningEffort)),
|
|
3359
3808
|
items: EFFORTS.map((e) => ({
|
|
@@ -3388,7 +3837,7 @@ async function searchModels(term) {
|
|
|
3388
3837
|
}
|
|
3389
3838
|
const priceOf = (m) => m.pricing?.prompt != null ? `$${(parseFloat(m.pricing.prompt) * 1e6).toFixed(2)}/1M` : "?";
|
|
3390
3839
|
if (process.stdin.isTTY) {
|
|
3391
|
-
const choice = await
|
|
3840
|
+
const choice = await pick({
|
|
3392
3841
|
title: `models matching "${term}" \u2014 \u2191/\u2193 then Enter (Esc to cancel)`,
|
|
3393
3842
|
items: matches.map((m) => ({
|
|
3394
3843
|
label: m.id,
|
|
@@ -3435,8 +3884,11 @@ async function main() {
|
|
|
3435
3884
|
if (process.argv[2] === "update") {
|
|
3436
3885
|
console.log("Updating beecork\u2026");
|
|
3437
3886
|
const { ok, output } = await selfUpdate();
|
|
3438
|
-
if (ok)
|
|
3439
|
-
|
|
3887
|
+
if (ok) {
|
|
3888
|
+
console.log(color.green("\u2713 beecork updated. Run `beecork` to use the new version."));
|
|
3889
|
+
const w = await shadowWarning();
|
|
3890
|
+
if (w) console.log("\n" + color.yellow(w));
|
|
3891
|
+
} else {
|
|
3440
3892
|
console.error(color.red("Update failed:") + "\n" + output);
|
|
3441
3893
|
console.error(color.dim("\nTry manually: npm install -g beecork (you may need sudo, or your Node version manager)."));
|
|
3442
3894
|
process.exitCode = 1;
|
|
@@ -3497,7 +3949,7 @@ ${instr.trusted}`;
|
|
|
3497
3949
|
};
|
|
3498
3950
|
const tty = !!process.stdin.isTTY;
|
|
3499
3951
|
process.on("exit", killAllTasks);
|
|
3500
|
-
process.on("exit",
|
|
3952
|
+
process.on("exit", stopChrome);
|
|
3501
3953
|
if (tty) {
|
|
3502
3954
|
initInput();
|
|
3503
3955
|
process.on("exit", teardownInput);
|
|
@@ -3516,12 +3968,13 @@ ${instr.trusted}`;
|
|
|
3516
3968
|
});
|
|
3517
3969
|
}
|
|
3518
3970
|
const rl = tty ? null : createInterface({ input: process.stdin, output: process.stdout, completer });
|
|
3519
|
-
|
|
3971
|
+
if (chromeEnabled()) process.stdout.write(ansi.clearScreen + ansi.clearScrollback + ansi.home);
|
|
3972
|
+
const version = await currentVersion();
|
|
3973
|
+
printBanner(state.model, version, instr.sources.map(tildify));
|
|
3520
3974
|
if (config.dangerouslySkipPermissions) {
|
|
3521
3975
|
console.log(color.red("\u26A0 --dangerously-skip-permissions is ON: the approval gate is OFF. Out-of-root paths and risky") + "\n" + color.red(" shell commands will RUN unprompted. Use only in a disposable sandbox. (read-only mode + catastrophic-") + "\n" + color.red(" command refusal still apply.)") + "\n");
|
|
3522
3976
|
}
|
|
3523
3977
|
if (tty) {
|
|
3524
|
-
const version = await currentVersion();
|
|
3525
3978
|
const newer = await checkForUpdate(version);
|
|
3526
3979
|
if (newer) console.log(color.dim(`\u25B8 beecork ${stripControl(newer)} is available (you have ${version}) \u2014 update: npm install -g beecork`) + "\n");
|
|
3527
3980
|
}
|
|
@@ -3565,10 +4018,19 @@ ${instr.trusted}`;
|
|
|
3565
4018
|
}
|
|
3566
4019
|
});
|
|
3567
4020
|
}
|
|
3568
|
-
|
|
4021
|
+
if (chromeEnabled())
|
|
4022
|
+
startChrome({
|
|
4023
|
+
tokens: () => estimateTokens(messages),
|
|
4024
|
+
items: [...SLASH_COMMANDS, ...skills.map((s) => ({ name: "/" + s.name, desc: "skill" }))],
|
|
4025
|
+
onInterrupt: () => activeTurn?.abort()
|
|
4026
|
+
});
|
|
3569
4027
|
while (true) {
|
|
3570
4028
|
let userInput;
|
|
3571
|
-
if (
|
|
4029
|
+
if (chromeEnabled()) {
|
|
4030
|
+
const r = await nextLine();
|
|
4031
|
+
if (r.type === "quit") break;
|
|
4032
|
+
userInput = r.value;
|
|
4033
|
+
} else if (tty) {
|
|
3572
4034
|
const r = await readPrompt({
|
|
3573
4035
|
promptString,
|
|
3574
4036
|
commands: SLASH_COMMANDS,
|
|
@@ -3606,16 +4068,27 @@ ${instr.trusted}`;
|
|
|
3606
4068
|
continue;
|
|
3607
4069
|
}
|
|
3608
4070
|
}
|
|
4071
|
+
if (chromeEnabled()) {
|
|
4072
|
+
activeTurn = new AbortController();
|
|
4073
|
+
const steering3 = beginTurn();
|
|
4074
|
+
try {
|
|
4075
|
+
messages = await runTurn(messages, userInput, ask, approvedTools, approvedGuardKeys, activeTurn.signal, steering3);
|
|
4076
|
+
} finally {
|
|
4077
|
+
activeTurn = null;
|
|
4078
|
+
endTurn();
|
|
4079
|
+
}
|
|
4080
|
+
continue;
|
|
4081
|
+
}
|
|
3609
4082
|
activeTurn = new AbortController();
|
|
3610
4083
|
let modeChangedMidTurn = false;
|
|
3611
|
-
const
|
|
4084
|
+
const steering2 = [];
|
|
3612
4085
|
let typed = "";
|
|
3613
|
-
const redrawSteer = () => process.stdout.write(
|
|
4086
|
+
const redrawSteer = () => process.stdout.write(ansi.cr + ansi.clearLine + color.cyan("\xBB ") + color.dim(typed));
|
|
3614
4087
|
const clearSteer = () => {
|
|
3615
|
-
process.stdout.write(
|
|
4088
|
+
process.stdout.write(ansi.cr + ansi.clearLine);
|
|
3616
4089
|
setSteeringActive(false);
|
|
3617
4090
|
};
|
|
3618
|
-
const
|
|
4091
|
+
const restoreKeys2 = tty ? pushKeyHandler((str, key) => {
|
|
3619
4092
|
if (key && (key.name === "escape" || key.ctrl && key.name === "c")) {
|
|
3620
4093
|
activeTurn?.abort();
|
|
3621
4094
|
return;
|
|
@@ -3630,7 +4103,7 @@ ${instr.trusted}`;
|
|
|
3630
4103
|
typed = "";
|
|
3631
4104
|
clearSteer();
|
|
3632
4105
|
if (note) {
|
|
3633
|
-
|
|
4106
|
+
steering2.push(note);
|
|
3634
4107
|
console.log(color.dim(`\xBB queued for next step: ${note}`));
|
|
3635
4108
|
}
|
|
3636
4109
|
return;
|
|
@@ -3650,9 +4123,9 @@ ${instr.trusted}`;
|
|
|
3650
4123
|
}) : () => {
|
|
3651
4124
|
};
|
|
3652
4125
|
try {
|
|
3653
|
-
messages = await runTurn(messages, userInput, ask, approvedTools, approvedGuardKeys, activeTurn.signal,
|
|
4126
|
+
messages = await runTurn(messages, userInput, ask, approvedTools, approvedGuardKeys, activeTurn.signal, steering2);
|
|
3654
4127
|
} finally {
|
|
3655
|
-
|
|
4128
|
+
restoreKeys2();
|
|
3656
4129
|
setSteeringActive(false);
|
|
3657
4130
|
activeTurn = null;
|
|
3658
4131
|
if (modeChangedMidTurn) console.log(color.yellow(`\u25B8 mode: ${modeLabel(state.mode)}`));
|
|
@@ -3660,6 +4133,7 @@ ${instr.trusted}`;
|
|
|
3660
4133
|
if (bg > 0) console.log(color.dim(`\u25B8 ${bg} background task${bg === 1 ? "" : "s"} running \u2014 check_task / stop_task`));
|
|
3661
4134
|
}
|
|
3662
4135
|
}
|
|
4136
|
+
stopChrome();
|
|
3663
4137
|
teardownInput();
|
|
3664
4138
|
rl?.close();
|
|
3665
4139
|
await persist();
|