wholestack 0.4.0 → 0.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/{chunk-7DJJXUV4.js → chunk-TDSLCPQL.js} +1115 -54
- package/dist/cli.js +812 -24
- package/dist/index.d.ts +101 -5
- package/dist/index.js +1 -1
- package/package.json +2 -1
|
@@ -39,7 +39,22 @@ function renderTodos(todos) {
|
|
|
39
39
|
}
|
|
40
40
|
line();
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
var SHIMMER = [44, 44, 45, 80, 81, 117, 147, 176, 176, 147, 117, 81, 45, 44];
|
|
43
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
44
|
+
function shimmerRow(row, rowIdx, phase) {
|
|
45
|
+
let out = "";
|
|
46
|
+
for (let col = 0; col < row.length; col++) {
|
|
47
|
+
const ch = row[col];
|
|
48
|
+
if (ch === " ") {
|
|
49
|
+
out += ch;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const code = SHIMMER[(col + rowIdx + phase) % SHIMMER.length];
|
|
53
|
+
out += `\x1B[1;38;5;${code}m${ch}\x1B[0m`;
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
async function banner() {
|
|
43
58
|
const art = [
|
|
44
59
|
"\u2590\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u258C",
|
|
45
60
|
"\u2590 .----------------. \u258C",
|
|
@@ -62,13 +77,55 @@ function banner() {
|
|
|
62
77
|
if (i === 8) return " " + c.dim("/build for full stack apps");
|
|
63
78
|
return "";
|
|
64
79
|
};
|
|
80
|
+
const renderFinal = () => {
|
|
81
|
+
art.forEach((row, i) => {
|
|
82
|
+
const colored = zRows.has(i) ? c.bold(c.cyan(row)) : c.cyan(row);
|
|
83
|
+
line(" " + colored + side(i));
|
|
84
|
+
});
|
|
85
|
+
};
|
|
65
86
|
line();
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
87
|
+
const animate = TTY && useColor && !process.env.ZETA_NO_ANIM;
|
|
88
|
+
if (!animate) {
|
|
89
|
+
renderFinal();
|
|
90
|
+
line();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const FRAME_MS = 70;
|
|
94
|
+
const MAX_FRAMES = 900;
|
|
95
|
+
const stdin2 = process.stdin;
|
|
96
|
+
let stop = false;
|
|
97
|
+
const onKey = () => {
|
|
98
|
+
stop = true;
|
|
99
|
+
};
|
|
100
|
+
const rawCapable = typeof stdin2.setRawMode === "function";
|
|
101
|
+
if (rawCapable) stdin2.setRawMode(true);
|
|
102
|
+
stdin2.resume();
|
|
103
|
+
stdin2.once("data", onKey);
|
|
104
|
+
process.stdout.write("\x1B[?25l");
|
|
105
|
+
try {
|
|
106
|
+
for (let f = 0; f < MAX_FRAMES && !stop; f++) {
|
|
107
|
+
if (f > 0) process.stdout.write(`\x1B[${art.length}A`);
|
|
108
|
+
art.forEach((row, i) => {
|
|
109
|
+
process.stdout.write("\r " + shimmerRow(row, i, f) + "\x1B[K\n");
|
|
110
|
+
});
|
|
111
|
+
await sleep(FRAME_MS);
|
|
112
|
+
}
|
|
113
|
+
process.stdout.write(`\x1B[${art.length}A`);
|
|
114
|
+
art.forEach((row, i) => {
|
|
115
|
+
const colored = zRows.has(i) ? c.bold(c.cyan(row)) : c.cyan(row);
|
|
116
|
+
process.stdout.write("\r " + colored + side(i) + "\x1B[K\n");
|
|
117
|
+
});
|
|
118
|
+
} finally {
|
|
119
|
+
process.stdout.write("\x1B[?25h");
|
|
120
|
+
stdin2.removeListener("data", onKey);
|
|
121
|
+
if (rawCapable) stdin2.setRawMode(false);
|
|
122
|
+
stdin2.pause();
|
|
123
|
+
}
|
|
70
124
|
line();
|
|
71
125
|
}
|
|
126
|
+
function bell() {
|
|
127
|
+
if (TTY && !process.env.ZETA_NO_BELL) process.stderr.write("\x07");
|
|
128
|
+
}
|
|
72
129
|
function fmtTokens(n) {
|
|
73
130
|
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
74
131
|
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
@@ -112,26 +169,6 @@ function wrapPlain(text, width) {
|
|
|
112
169
|
}
|
|
113
170
|
return out;
|
|
114
171
|
}
|
|
115
|
-
function responseBox(text, title = "Zeta-G1.0") {
|
|
116
|
-
const inner = boxInnerWidth();
|
|
117
|
-
const textW = inner - 2;
|
|
118
|
-
const rows = wrapPlain(text, textW);
|
|
119
|
-
const label = ` ${title} `;
|
|
120
|
-
const rem = Math.max(0, inner - label.length);
|
|
121
|
-
const lft = Math.floor(rem / 2);
|
|
122
|
-
const top = "\u256D" + "\u2500".repeat(lft) + label + "\u2500".repeat(rem - lft) + "\u256E";
|
|
123
|
-
const blank = "\u2502" + " ".repeat(inner) + "\u2502";
|
|
124
|
-
const bottom = "\u2570" + "\u2500".repeat(inner) + "\u256F";
|
|
125
|
-
line();
|
|
126
|
-
line(" " + c.cyan(top));
|
|
127
|
-
line(" " + c.cyan(blank));
|
|
128
|
-
for (const r of rows) {
|
|
129
|
-
line(" " + c.cyan("\u2502") + " " + r.padEnd(textW) + " " + c.cyan("\u2502"));
|
|
130
|
-
}
|
|
131
|
-
line(" " + c.cyan(blank));
|
|
132
|
-
line(" " + c.cyan(bottom));
|
|
133
|
-
line();
|
|
134
|
-
}
|
|
135
172
|
function userBox(text) {
|
|
136
173
|
const inner = boxInnerWidth();
|
|
137
174
|
const textW = inner - 2;
|
|
@@ -158,23 +195,166 @@ function reasoningHeader() {
|
|
|
158
195
|
line();
|
|
159
196
|
line(" " + c.magenta("\u273B ") + c.dim(c.italic("thinking")));
|
|
160
197
|
}
|
|
161
|
-
var
|
|
162
|
-
|
|
163
|
-
|
|
198
|
+
var SWIPE = [
|
|
199
|
+
238,
|
|
200
|
+
239,
|
|
201
|
+
240,
|
|
202
|
+
242,
|
|
203
|
+
244,
|
|
204
|
+
246,
|
|
205
|
+
248,
|
|
206
|
+
250,
|
|
207
|
+
252,
|
|
208
|
+
254,
|
|
209
|
+
255,
|
|
210
|
+
87,
|
|
211
|
+
51,
|
|
212
|
+
45,
|
|
213
|
+
44,
|
|
214
|
+
45,
|
|
215
|
+
51,
|
|
216
|
+
87,
|
|
217
|
+
255,
|
|
218
|
+
254,
|
|
219
|
+
252,
|
|
220
|
+
250,
|
|
221
|
+
248,
|
|
222
|
+
246,
|
|
223
|
+
244,
|
|
224
|
+
242,
|
|
225
|
+
240,
|
|
226
|
+
239
|
|
227
|
+
];
|
|
228
|
+
var TRUECOLOR = /truecolor|24bit/i.test(process.env.COLORTERM ?? "");
|
|
229
|
+
function swipeRgb(t) {
|
|
230
|
+
const stops = [
|
|
231
|
+
[60, 64, 74],
|
|
232
|
+
[0, 200, 212],
|
|
233
|
+
[235, 242, 255],
|
|
234
|
+
[0, 200, 212],
|
|
235
|
+
[60, 64, 74]
|
|
236
|
+
];
|
|
237
|
+
const x = t % 1 * (stops.length - 1);
|
|
238
|
+
const i = Math.floor(x);
|
|
239
|
+
const f = x - i;
|
|
240
|
+
const a = stops[i];
|
|
241
|
+
const b = stops[Math.min(i + 1, stops.length - 1)];
|
|
242
|
+
return [
|
|
243
|
+
Math.round(a[0] + (b[0] - a[0]) * f),
|
|
244
|
+
Math.round(a[1] + (b[1] - a[1]) * f),
|
|
245
|
+
Math.round(a[2] + (b[2] - a[2]) * f)
|
|
246
|
+
];
|
|
247
|
+
}
|
|
248
|
+
function swipeAnsi(p) {
|
|
249
|
+
if (TRUECOLOR) {
|
|
250
|
+
const [r, g, b] = swipeRgb((p % SWIPE.length + SWIPE.length) % SWIPE.length / SWIPE.length);
|
|
251
|
+
return `\x1B[38;2;${r};${g};${b}m`;
|
|
252
|
+
}
|
|
253
|
+
return `\x1B[38;5;${SWIPE[(p % SWIPE.length + SWIPE.length) % SWIPE.length]}m`;
|
|
254
|
+
}
|
|
255
|
+
function shimmerText(text, phase) {
|
|
256
|
+
if (!useColor) return text;
|
|
257
|
+
let out = "";
|
|
258
|
+
for (let i = 0; i < text.length; i++) {
|
|
259
|
+
const ch = text[i];
|
|
260
|
+
if (ch === " ") {
|
|
261
|
+
out += ch;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
out += swipeAnsi(i + phase) + ch + "\x1B[0m";
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
268
|
+
var THINKING_WORDS = [
|
|
269
|
+
"thinking",
|
|
270
|
+
"cooking",
|
|
271
|
+
"vibin",
|
|
272
|
+
"whippin",
|
|
273
|
+
"stirring the pot",
|
|
274
|
+
"noodlin",
|
|
275
|
+
"scheming",
|
|
276
|
+
"wiring it up",
|
|
277
|
+
"summoning",
|
|
278
|
+
"locking in",
|
|
279
|
+
"manifesting",
|
|
280
|
+
"crunching",
|
|
281
|
+
"galaxy braining",
|
|
282
|
+
"percolating",
|
|
283
|
+
"marinating",
|
|
284
|
+
"conjuring",
|
|
285
|
+
"untangling",
|
|
286
|
+
"threading the needle",
|
|
287
|
+
"herding electrons",
|
|
288
|
+
"spelunking the stack",
|
|
289
|
+
"chiseling",
|
|
290
|
+
"brewing",
|
|
291
|
+
"plotting",
|
|
292
|
+
"tinkering",
|
|
293
|
+
"compiling thoughts",
|
|
294
|
+
"doing the thing"
|
|
295
|
+
];
|
|
296
|
+
var THINKING_WORDS_YOLO = [
|
|
297
|
+
"makin babies",
|
|
298
|
+
"stressin",
|
|
299
|
+
"sending it",
|
|
300
|
+
"full send",
|
|
301
|
+
"cooking with gas",
|
|
302
|
+
"going feral",
|
|
303
|
+
"yeetin code",
|
|
304
|
+
"gambling",
|
|
305
|
+
"raw-doggin prod",
|
|
306
|
+
"no cap buildin",
|
|
307
|
+
"speedrunnin",
|
|
308
|
+
"touchin grass",
|
|
309
|
+
"zootin",
|
|
310
|
+
"catchin a fade",
|
|
311
|
+
"standin on business"
|
|
312
|
+
];
|
|
313
|
+
function thinkingWords(yolo = false) {
|
|
314
|
+
return yolo ? [...THINKING_WORDS, ...THINKING_WORDS_YOLO] : THINKING_WORDS;
|
|
315
|
+
}
|
|
316
|
+
var Spinner = class {
|
|
317
|
+
phase = 0;
|
|
318
|
+
ticks = 0;
|
|
164
319
|
timer = null;
|
|
165
320
|
text = "";
|
|
166
|
-
|
|
321
|
+
phrases = null;
|
|
322
|
+
phraseIdx = 0;
|
|
323
|
+
hint = "";
|
|
324
|
+
startedAt = 0;
|
|
325
|
+
/**
|
|
326
|
+
* @param text fixed label (used when no `phrases`)
|
|
327
|
+
* @param opts.phrases rotate through these instead of a fixed label
|
|
328
|
+
* @param opts.hint dim suffix, e.g. "esc to interrupt"
|
|
329
|
+
*/
|
|
330
|
+
start(text, opts = {}) {
|
|
167
331
|
if (!TTY) return;
|
|
168
332
|
this.text = text;
|
|
169
|
-
this.
|
|
333
|
+
this.phrases = opts.phrases && opts.phrases.length ? opts.phrases : null;
|
|
334
|
+
this.hint = opts.hint ?? "";
|
|
335
|
+
this.phraseIdx = this.phrases ? Date.now() % this.phrases.length : 0;
|
|
336
|
+
this.startedAt = Date.now();
|
|
337
|
+
this.timer = setInterval(() => this.tick(), 80);
|
|
170
338
|
this.tick();
|
|
171
339
|
}
|
|
172
340
|
update(text) {
|
|
173
341
|
this.text = text;
|
|
174
342
|
}
|
|
343
|
+
label() {
|
|
344
|
+
if (this.phrases) return this.phrases[this.phraseIdx % this.phrases.length] + "\u2026";
|
|
345
|
+
return this.text;
|
|
346
|
+
}
|
|
175
347
|
tick() {
|
|
176
|
-
|
|
177
|
-
|
|
348
|
+
this.ticks++;
|
|
349
|
+
if (this.phrases && this.ticks % 30 === 0) {
|
|
350
|
+
this.phraseIdx = (this.phraseIdx + 1) % this.phrases.length;
|
|
351
|
+
}
|
|
352
|
+
this.phase = (this.phase + SWIPE.length - 1) % SWIPE.length;
|
|
353
|
+
const dot = swipeAnsi(this.phase) + "\u2726\x1B[0m";
|
|
354
|
+
const secs = Math.floor((Date.now() - this.startedAt) / 1e3);
|
|
355
|
+
const elapsed = secs >= 1 ? c.dim(` ${secs}s`) : "";
|
|
356
|
+
const tail2 = this.hint ? " " + c.dim(`(${this.hint})`) : "";
|
|
357
|
+
process.stderr.write(`\r ${dot} ${shimmerText(this.label(), this.phase)}${elapsed}${tail2}\x1B[K`);
|
|
178
358
|
}
|
|
179
359
|
stop() {
|
|
180
360
|
if (this.timer) {
|
|
@@ -721,10 +901,111 @@ function tail(s) {
|
|
|
721
901
|
return s.slice(-4e3);
|
|
722
902
|
}
|
|
723
903
|
|
|
904
|
+
// src/tasks.ts
|
|
905
|
+
import { spawn as spawn4 } from "child_process";
|
|
906
|
+
var MAX_OUTPUT_CHARS = 64 * 1024;
|
|
907
|
+
var TaskManager = class {
|
|
908
|
+
seq = 0;
|
|
909
|
+
map = /* @__PURE__ */ new Map();
|
|
910
|
+
/** Start a detached-from-the-REPL background command. Returns the task id. */
|
|
911
|
+
start(command, args, cwd) {
|
|
912
|
+
const id = `t${++this.seq}`;
|
|
913
|
+
const display = [command, ...args].join(" ");
|
|
914
|
+
const child = spawn4(command, args, {
|
|
915
|
+
cwd,
|
|
916
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
917
|
+
// Own process group so we can signal the whole tree on kill.
|
|
918
|
+
detached: false
|
|
919
|
+
});
|
|
920
|
+
const task = {
|
|
921
|
+
id,
|
|
922
|
+
display,
|
|
923
|
+
status: "running",
|
|
924
|
+
exitCode: null,
|
|
925
|
+
startedAt: Date.now(),
|
|
926
|
+
endedAt: null,
|
|
927
|
+
announced: false,
|
|
928
|
+
child,
|
|
929
|
+
buffer: ""
|
|
930
|
+
};
|
|
931
|
+
const append = (chunk) => {
|
|
932
|
+
task.buffer += chunk.toString();
|
|
933
|
+
if (task.buffer.length > MAX_OUTPUT_CHARS) {
|
|
934
|
+
task.buffer = task.buffer.slice(task.buffer.length - MAX_OUTPUT_CHARS);
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
child.stdout?.on("data", append);
|
|
938
|
+
child.stderr?.on("data", append);
|
|
939
|
+
child.on("error", (e) => {
|
|
940
|
+
task.buffer += `
|
|
941
|
+
[spawn error] ${e.message}
|
|
942
|
+
`;
|
|
943
|
+
task.status = "failed";
|
|
944
|
+
task.exitCode = task.exitCode ?? -1;
|
|
945
|
+
task.endedAt = Date.now();
|
|
946
|
+
});
|
|
947
|
+
child.on("close", (code) => {
|
|
948
|
+
task.exitCode = code;
|
|
949
|
+
task.status = code === 0 ? "exited" : "failed";
|
|
950
|
+
task.endedAt = Date.now();
|
|
951
|
+
});
|
|
952
|
+
this.map.set(id, task);
|
|
953
|
+
return this.toRecord(task);
|
|
954
|
+
}
|
|
955
|
+
toRecord(t) {
|
|
956
|
+
const { child: _child, buffer: _buffer, ...rec } = t;
|
|
957
|
+
return { ...rec };
|
|
958
|
+
}
|
|
959
|
+
get(id) {
|
|
960
|
+
const t = this.map.get(id);
|
|
961
|
+
if (!t) return null;
|
|
962
|
+
return { record: this.toRecord(t), output: t.buffer };
|
|
963
|
+
}
|
|
964
|
+
list() {
|
|
965
|
+
return [...this.map.values()].map((t) => this.toRecord(t));
|
|
966
|
+
}
|
|
967
|
+
/** Tasks that finished since the last call and haven't been announced yet. */
|
|
968
|
+
drainCompleted() {
|
|
969
|
+
const done = [];
|
|
970
|
+
for (const t of this.map.values()) {
|
|
971
|
+
if (t.status !== "running" && !t.announced) {
|
|
972
|
+
t.announced = true;
|
|
973
|
+
done.push(this.toRecord(t));
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return done;
|
|
977
|
+
}
|
|
978
|
+
kill(id) {
|
|
979
|
+
const t = this.map.get(id);
|
|
980
|
+
if (!t) return false;
|
|
981
|
+
if (t.status === "running") {
|
|
982
|
+
try {
|
|
983
|
+
t.child.kill("SIGTERM");
|
|
984
|
+
} catch {
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return true;
|
|
988
|
+
}
|
|
989
|
+
killAll() {
|
|
990
|
+
for (const t of this.map.values()) {
|
|
991
|
+
if (t.status === "running") {
|
|
992
|
+
try {
|
|
993
|
+
t.child.kill("SIGTERM");
|
|
994
|
+
} catch {
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
runtimeSeconds(t) {
|
|
1000
|
+
return Math.round(((t.endedAt ?? Date.now()) - t.startedAt) / 1e3);
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
var tasks = new TaskManager();
|
|
1004
|
+
|
|
724
1005
|
// src/tools.ts
|
|
725
1006
|
import { tool } from "ai";
|
|
726
1007
|
import { z } from "zod";
|
|
727
|
-
import { spawn as
|
|
1008
|
+
import { spawn as spawn5 } from "child_process";
|
|
728
1009
|
import { readFile as readFile2, writeFile, mkdir, readdir, stat } from "fs/promises";
|
|
729
1010
|
import { resolve as resolve2, dirname as dirname2, relative, join as join4, sep as sep2 } from "path";
|
|
730
1011
|
import fg from "fast-glob";
|
|
@@ -739,7 +1020,7 @@ function inside(ctx, p) {
|
|
|
739
1020
|
}
|
|
740
1021
|
function runShell(cmd, args, opts) {
|
|
741
1022
|
return new Promise((res) => {
|
|
742
|
-
const child =
|
|
1023
|
+
const child = spawn5(cmd, args, { cwd: opts.cwd, shell: false });
|
|
743
1024
|
let out = "";
|
|
744
1025
|
const timer = setTimeout(() => child.kill("SIGKILL"), opts.timeoutMs);
|
|
745
1026
|
const onAbort = () => child.kill("SIGKILL");
|
|
@@ -772,7 +1053,7 @@ async function search(ctx, pattern, searchPath, globPat, ignoreCase, maxResults,
|
|
|
772
1053
|
if (globPat) args.push("--glob", globPat);
|
|
773
1054
|
for (const ig of IGNORE) args.push("--glob", "!" + ig);
|
|
774
1055
|
args.push("-e", pattern, abs);
|
|
775
|
-
const child =
|
|
1056
|
+
const child = spawn5("rg", args, { cwd: ctx.cwd });
|
|
776
1057
|
const onAbort = () => child.kill("SIGKILL");
|
|
777
1058
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
778
1059
|
let out = "";
|
|
@@ -1237,19 +1518,77 @@ function buildTools(ctx) {
|
|
|
1237
1518
|
}
|
|
1238
1519
|
return { ok: false, error: r.error, log: r.log };
|
|
1239
1520
|
}
|
|
1521
|
+
}),
|
|
1522
|
+
run_background: tool({
|
|
1523
|
+
description: "Start a long-running command in the BACKGROUND (builds, test watchers, long scripts) and keep working. Returns a task id immediately \u2014 DO NOT wait. Poll it later with task_output, or just continue; finished tasks are reported back automatically. Use this instead of run_command when a job takes more than a few seconds or should run while you do other work. Gated by the permission layer like run_command.",
|
|
1524
|
+
inputSchema: z.object({
|
|
1525
|
+
command: z.string().describe("The executable, e.g. pnpm, npm, forge, node, vitest."),
|
|
1526
|
+
args: z.array(z.string()).default([])
|
|
1527
|
+
}),
|
|
1528
|
+
execute: async ({ command, args }) => {
|
|
1529
|
+
const display = [command, ...args].join(" ");
|
|
1530
|
+
if (await permissions.requestCommand(`(background) ${display}`) === "deny") {
|
|
1531
|
+
return {
|
|
1532
|
+
ok: false,
|
|
1533
|
+
declined: true,
|
|
1534
|
+
error: permissions.isPlan() ? permissions.planRefusal() : `declined: \`${display}\` \u2014 approve it, switch /mode, or re-run with --yes.`
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
const rec = tasks.start(command, args, ctx.cwd);
|
|
1538
|
+
toolLine("run_background", c.dim(`${rec.id} \xB7 ${display}`));
|
|
1539
|
+
return {
|
|
1540
|
+
ok: true,
|
|
1541
|
+
taskId: rec.id,
|
|
1542
|
+
status: rec.status,
|
|
1543
|
+
note: `started in background as ${rec.id}; poll with task_output or keep working`
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
}),
|
|
1547
|
+
task_output: tool({
|
|
1548
|
+
description: "Check a background task started with run_background: its status (running|exited|failed), exit code, runtime, and latest output. Pass no id to list all tasks. Use after starting a background job to see whether it finished and what it produced.",
|
|
1549
|
+
inputSchema: z.object({
|
|
1550
|
+
id: z.string().optional().describe("Task id (e.g. t1). Omit to list every task."),
|
|
1551
|
+
tail: z.number().int().min(200).max(2e4).default(4e3).describe("Max output chars to return.")
|
|
1552
|
+
}),
|
|
1553
|
+
execute: async ({ id, tail: tail2 }) => {
|
|
1554
|
+
if (!id) {
|
|
1555
|
+
return {
|
|
1556
|
+
ok: true,
|
|
1557
|
+
tasks: tasks.list().map((t) => ({
|
|
1558
|
+
id: t.id,
|
|
1559
|
+
status: t.status,
|
|
1560
|
+
exitCode: t.exitCode,
|
|
1561
|
+
seconds: tasks.runtimeSeconds(t),
|
|
1562
|
+
command: t.display
|
|
1563
|
+
}))
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
const found = tasks.get(id);
|
|
1567
|
+
if (!found) return { ok: false, error: `no task ${id}` };
|
|
1568
|
+
toolLine("task_output", c.dim(`${id} \xB7 ${found.record.status}`));
|
|
1569
|
+
return {
|
|
1570
|
+
ok: true,
|
|
1571
|
+
id,
|
|
1572
|
+
status: found.record.status,
|
|
1573
|
+
exitCode: found.record.exitCode,
|
|
1574
|
+
seconds: tasks.runtimeSeconds(found.record),
|
|
1575
|
+
command: found.record.display,
|
|
1576
|
+
output: found.output.slice(-tail2)
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1240
1579
|
})
|
|
1241
1580
|
};
|
|
1242
1581
|
}
|
|
1243
1582
|
|
|
1244
1583
|
// src/hooks.ts
|
|
1245
|
-
import { spawn as
|
|
1584
|
+
import { spawn as spawn6 } from "child_process";
|
|
1246
1585
|
import { readFileSync, existsSync as existsSync4 } from "fs";
|
|
1247
1586
|
import { homedir } from "os";
|
|
1248
1587
|
import { join as join5 } from "path";
|
|
1249
1588
|
var HOOK_TIMEOUT_MS = 15e3;
|
|
1250
1589
|
function runOne(def, event, payload, cwd) {
|
|
1251
1590
|
return new Promise((res) => {
|
|
1252
|
-
const child =
|
|
1591
|
+
const child = spawn6("sh", ["-c", def.command], { cwd });
|
|
1253
1592
|
let stdout2 = "";
|
|
1254
1593
|
let stderr = "";
|
|
1255
1594
|
const timer = setTimeout(() => child.kill("SIGKILL"), HOOK_TIMEOUT_MS);
|
|
@@ -1467,15 +1806,40 @@ ${transcript}`
|
|
|
1467
1806
|
|
|
1468
1807
|
// src/model.ts
|
|
1469
1808
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
var
|
|
1473
|
-
|
|
1474
|
-
|
|
1809
|
+
|
|
1810
|
+
// ../zeta-models/dist/index.js
|
|
1811
|
+
var readEnv = (k) => {
|
|
1812
|
+
const v = typeof process !== "undefined" ? process.env?.[k] : void 0;
|
|
1813
|
+
const t = v?.trim();
|
|
1814
|
+
return t && t.length > 0 ? t : void 0;
|
|
1815
|
+
};
|
|
1816
|
+
var PROVIDERS = {
|
|
1817
|
+
cerebras: { id: "cerebras", baseURL: "https://api.cerebras.ai/v1", keyEnv: "CEREBRAS_API_KEY" },
|
|
1818
|
+
openrouter: { id: "openrouter", baseURL: "https://openrouter.ai/api/v1", keyEnv: "OPENROUTER_API_KEY" }
|
|
1819
|
+
};
|
|
1820
|
+
var MODEL_IDS = {
|
|
1821
|
+
/** BUILD lane — writes ISL, generates the full stack. Cerebras. The 80%. */
|
|
1822
|
+
gptOss: readEnv("ZETA_GPTOSS_MODEL") || "gpt-oss-120b",
|
|
1823
|
+
/** BRAIN lane — frontend swarm, decisions, debugging, editing, planning. Cerebras. The 20%. */
|
|
1824
|
+
glm: readEnv("ZETA_GLM_MODEL") || "zai-glm-4.7",
|
|
1825
|
+
/** VISION lane — accepts image input. OpenRouter (Cerebras text lanes can't see). */
|
|
1826
|
+
vision: readEnv("ZETA_VISION_MODEL") || "anthropic/claude-sonnet-4.6"
|
|
1827
|
+
};
|
|
1828
|
+
|
|
1829
|
+
// src/model.ts
|
|
1830
|
+
var CEREBRAS_URL = PROVIDERS.cerebras.baseURL;
|
|
1831
|
+
var OPENROUTER_URL = PROVIDERS.openrouter.baseURL;
|
|
1832
|
+
var CEREBRAS_KEY = PROVIDERS.cerebras.keyEnv;
|
|
1833
|
+
var KEY_ENV = PROVIDERS.openrouter.keyEnv;
|
|
1834
|
+
var DEFAULT_CUSTOM_MODEL = MODEL_IDS.vision;
|
|
1835
|
+
var VISION_MODEL = MODEL_IDS.vision;
|
|
1475
1836
|
var MODELS = /* @__PURE__ */ new Map([
|
|
1476
|
-
["zeta-g1-lite", { modelId:
|
|
1477
|
-
["zeta-g1", { modelId:
|
|
1478
|
-
["zeta-g1-max", { modelId:
|
|
1837
|
+
["zeta-g1-lite", { modelId: MODEL_IDS.gptOss, label: "Zeta-G1.0 Lite", keyEnv: CEREBRAS_KEY, baseURL: CEREBRAS_URL, contextWindow: 128e3, thinking: "budget" }],
|
|
1838
|
+
["zeta-g1", { modelId: MODEL_IDS.glm, label: "Zeta-G1.0", keyEnv: CEREBRAS_KEY, baseURL: CEREBRAS_URL, contextWindow: 128e3, thinking: "budget" }],
|
|
1839
|
+
["zeta-g1-max", { modelId: MODEL_IDS.glm, label: "Zeta-G1.0 MAX", keyEnv: CEREBRAS_KEY, baseURL: CEREBRAS_URL, contextWindow: 128e3, thinking: "budget" }],
|
|
1840
|
+
// The vision tier — accepts image input (screenshots, mocks, diagrams). Routed
|
|
1841
|
+
// over OpenRouter because the Cerebras tiers are text-only.
|
|
1842
|
+
["zeta-g1-vision", { modelId: VISION_MODEL, label: "Zeta-G1.0 Vision", keyEnv: KEY_ENV, baseURL: OPENROUTER_URL, contextWindow: 2e5, thinking: null }]
|
|
1479
1843
|
]);
|
|
1480
1844
|
var MODEL_KEYS = [...MODELS.keys()];
|
|
1481
1845
|
function registerCustom(id) {
|
|
@@ -1510,6 +1874,9 @@ function resolveModelKey(raw) {
|
|
|
1510
1874
|
lite: "zeta-g1-lite",
|
|
1511
1875
|
max: "zeta-g1-max",
|
|
1512
1876
|
pro: "zeta-g1-max",
|
|
1877
|
+
vision: "zeta-g1-vision",
|
|
1878
|
+
vis: "zeta-g1-vision",
|
|
1879
|
+
image: "zeta-g1-vision",
|
|
1513
1880
|
// legacy convenience — resolve silently, but only the Zeta label is shown
|
|
1514
1881
|
opus: "zeta-g1-max",
|
|
1515
1882
|
sonnet: "zeta-g1",
|
|
@@ -1531,6 +1898,9 @@ function modelLabel(key) {
|
|
|
1531
1898
|
function modelId(key) {
|
|
1532
1899
|
return spec(key).modelId;
|
|
1533
1900
|
}
|
|
1901
|
+
function visionCapable(key) {
|
|
1902
|
+
return key === "zeta-g1-vision" || key.startsWith("custom:");
|
|
1903
|
+
}
|
|
1534
1904
|
function modelContextWindow(key) {
|
|
1535
1905
|
return spec(key).contextWindow;
|
|
1536
1906
|
}
|
|
@@ -1747,6 +2117,35 @@ var ROLE_PLANNER = [
|
|
|
1747
2117
|
"name the files and seams involved, and call out unknowns and risks up front.",
|
|
1748
2118
|
"Propose the smallest plan that fully covers the goal."
|
|
1749
2119
|
].join("\n");
|
|
2120
|
+
var ROLE_NZT_48 = [
|
|
2121
|
+
"ROLE \u2014 NZT-48 (web3 special operations):",
|
|
2122
|
+
"You are NZT-48 for this session: a web3 special-ops agent \u2014 sharper than an",
|
|
2123
|
+
"arrow, precise and tactical with every move. Voice: precise, tactical,",
|
|
2124
|
+
"economical. State the objective, the move, and the evidence \u2014 nothing else.",
|
|
2125
|
+
"",
|
|
2126
|
+
"Drive the CLI's web3 commands (audit / prove / verify) and the existing",
|
|
2127
|
+
"Foundry \xB7 Slither \xB7 Halmos \xB7 firewall harness \u2014 do not reinvent the prover.",
|
|
2128
|
+
"",
|
|
2129
|
+
"FOUR MISSIONS \u2014 run the one the request calls for, end to end:",
|
|
2130
|
+
"1. AUDIT & PROVE \u2014 discover every contract and ALL candidate invariants (never",
|
|
2131
|
+
" just the first); prove each with the real harness; fold verdicts honestly",
|
|
2132
|
+
" (proven / refuted / degraded); emit the signed bundle. Degraded is NOT a",
|
|
2133
|
+
" pass.",
|
|
2134
|
+
"2. EXPLOIT RECON (defensive) \u2014 map the attack surface (reentrancy, access",
|
|
2135
|
+
" control, overflow, oracle/price manipulation, unchecked calls, value",
|
|
2136
|
+
" extraction, proxy traps). Report severity + location + exploit path + fix.",
|
|
2137
|
+
" Hunt weaknesses to CLOSE them; never weaponize.",
|
|
2138
|
+
"3. BUILD & SHIP \u2014 idea \u2192 ISL \u2192 Solidity \u2192 proven \u2192 deploy-ready, inside the",
|
|
2139
|
+
" deterministic provable fragment. Run forge build/test, Slither, Halmos.",
|
|
2140
|
+
" Never weaken a gate to force green \u2014 self-heal the spec instead.",
|
|
2141
|
+
"4. MONITOR & RESPOND \u2014 re-prove only changed contracts on .sol PRs; report",
|
|
2142
|
+
" SHIP / NO_SHIP; surface anomalies with evidence, never act destructively.",
|
|
2143
|
+
"",
|
|
2144
|
+
"RULES OF ENGAGEMENT: truth over optics (a refuted invariant honestly reported",
|
|
2145
|
+
"is a win; fake-success is the one unforgivable error). One contract per proof",
|
|
2146
|
+
"file; honor non-vacuity. Defensive only \u2014 refuse mass targeting, live attacks,",
|
|
2147
|
+
"or anything outside an authorized engagement."
|
|
2148
|
+
].join("\n");
|
|
1750
2149
|
var DEFAULT_REGISTRY = (() => {
|
|
1751
2150
|
const r = new PromptRegistry();
|
|
1752
2151
|
r.registerSystem("zeta-g", ZETA_G_SYSTEM);
|
|
@@ -1754,6 +2153,7 @@ var DEFAULT_REGISTRY = (() => {
|
|
|
1754
2153
|
r.registerOverlay({ id: "verify", body: VERIFY_OVERLAY });
|
|
1755
2154
|
r.registerRole({ role: "coder", body: ROLE_CODER });
|
|
1756
2155
|
r.registerRole({ role: "planner", body: ROLE_PLANNER });
|
|
2156
|
+
r.registerRole({ role: "nzt-48", body: ROLE_NZT_48 });
|
|
1757
2157
|
return r;
|
|
1758
2158
|
})();
|
|
1759
2159
|
|
|
@@ -2377,6 +2777,208 @@ import {
|
|
|
2377
2777
|
streamText,
|
|
2378
2778
|
stepCountIs
|
|
2379
2779
|
} from "ai";
|
|
2780
|
+
|
|
2781
|
+
// src/markdown.ts
|
|
2782
|
+
var useColor2 = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
2783
|
+
var truecolor = /truecolor|24bit/i.test(process.env.COLORTERM ?? "");
|
|
2784
|
+
function fg2(r, g, b, c256) {
|
|
2785
|
+
if (!useColor2) return "";
|
|
2786
|
+
return truecolor ? `\x1B[38;2;${r};${g};${b}m` : `\x1B[38;5;${c256}m`;
|
|
2787
|
+
}
|
|
2788
|
+
var RESET = useColor2 ? "\x1B[0m" : "";
|
|
2789
|
+
var SY = {
|
|
2790
|
+
keyword: fg2(198, 120, 221, 176),
|
|
2791
|
+
// violet
|
|
2792
|
+
string: fg2(152, 195, 121, 114),
|
|
2793
|
+
// green
|
|
2794
|
+
number: fg2(209, 154, 102, 173),
|
|
2795
|
+
// orange
|
|
2796
|
+
comment: fg2(106, 115, 125, 244),
|
|
2797
|
+
// gray
|
|
2798
|
+
fn: fg2(97, 175, 239, 75),
|
|
2799
|
+
// blue
|
|
2800
|
+
punct: fg2(160, 168, 180, 247),
|
|
2801
|
+
type: fg2(229, 192, 123, 180)
|
|
2802
|
+
// yellow
|
|
2803
|
+
};
|
|
2804
|
+
var KEYWORDS = /\b(const|let|var|function|return|if|else|for|while|switch|case|break|continue|new|class|extends|import|export|from|default|async|await|try|catch|finally|throw|typeof|instanceof|in|of|this|super|yield|static|public|private|protected|readonly|interface|type|enum|namespace|implements|as|void|null|undefined|true|false|def|elif|lambda|pass|with|fn|let|mut|pub|use|struct|impl|match|where|select|insert|update|delete|create|table|primary|key|foreign|references|contract|mapping|address|uint256|require|emit|modifier|memory|storage|payable)\b/g;
|
|
2805
|
+
function highlightLine(src, _lang) {
|
|
2806
|
+
if (!useColor2) return src;
|
|
2807
|
+
const cm = src.match(/^(\s*)(\/\/.*|#.*)$/);
|
|
2808
|
+
if (cm) return cm[1] + SY.comment + cm[2] + RESET;
|
|
2809
|
+
let out = "";
|
|
2810
|
+
let i = 0;
|
|
2811
|
+
while (i < src.length) {
|
|
2812
|
+
const ch = src[i];
|
|
2813
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
2814
|
+
let j = i + 1;
|
|
2815
|
+
while (j < src.length && src[j] !== ch) {
|
|
2816
|
+
if (src[j] === "\\") j++;
|
|
2817
|
+
j++;
|
|
2818
|
+
}
|
|
2819
|
+
out += SY.string + src.slice(i, Math.min(j + 1, src.length)) + RESET;
|
|
2820
|
+
i = j + 1;
|
|
2821
|
+
continue;
|
|
2822
|
+
}
|
|
2823
|
+
if (/[0-9]/.test(ch) && (i === 0 || /[^A-Za-z0-9_]/.test(src[i - 1]))) {
|
|
2824
|
+
let j = i;
|
|
2825
|
+
while (j < src.length && /[0-9a-fx._]/i.test(src[j])) j++;
|
|
2826
|
+
out += SY.number + src.slice(i, j) + RESET;
|
|
2827
|
+
i = j;
|
|
2828
|
+
continue;
|
|
2829
|
+
}
|
|
2830
|
+
if (/[A-Za-z_$]/.test(ch)) {
|
|
2831
|
+
let j = i;
|
|
2832
|
+
while (j < src.length && /[A-Za-z0-9_$]/.test(src[j])) j++;
|
|
2833
|
+
const word = src.slice(i, j);
|
|
2834
|
+
KEYWORDS.lastIndex = 0;
|
|
2835
|
+
if (KEYWORDS.test(word)) out += SY.keyword + word + RESET;
|
|
2836
|
+
else if (src[j] === "(") out += SY.fn + word + RESET;
|
|
2837
|
+
else if (/^[A-Z]/.test(word)) out += SY.type + word + RESET;
|
|
2838
|
+
else out += word;
|
|
2839
|
+
i = j;
|
|
2840
|
+
continue;
|
|
2841
|
+
}
|
|
2842
|
+
out += ch;
|
|
2843
|
+
i++;
|
|
2844
|
+
}
|
|
2845
|
+
return out;
|
|
2846
|
+
}
|
|
2847
|
+
function inline(s) {
|
|
2848
|
+
if (!useColor2) return s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
|
|
2849
|
+
let out = s;
|
|
2850
|
+
out = out.replace(/`([^`]+)`/g, (_m, code) => fg2(229, 192, 123, 180) + code + RESET);
|
|
2851
|
+
out = out.replace(/\*\*([^*]+)\*\*/g, (_m, t) => "\x1B[1m" + t + "\x1B[22m");
|
|
2852
|
+
out = out.replace(/(^|[^*])\*([^*]+)\*/g, (_m, p, t) => p + "\x1B[3m" + t + "\x1B[23m");
|
|
2853
|
+
out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, t, u) => "\x1B[4m" + t + "\x1B[24m" + c.dim(` (${u})`));
|
|
2854
|
+
return out;
|
|
2855
|
+
}
|
|
2856
|
+
function renderMarkdown(md, width = 76) {
|
|
2857
|
+
const lines = md.replace(/\r/g, "").split("\n");
|
|
2858
|
+
const out = [];
|
|
2859
|
+
let inCode = false;
|
|
2860
|
+
let codeLang = "";
|
|
2861
|
+
for (let li = 0; li < lines.length; li++) {
|
|
2862
|
+
const raw = lines[li];
|
|
2863
|
+
if (!inCode && raw.includes("|") && li + 1 < lines.length && /^\s*\|?[\s:|-]*-{2,}[\s:|-]*\|?\s*$/.test(lines[li + 1]) && lines[li + 1].includes("-")) {
|
|
2864
|
+
const header = splitTableRow(raw);
|
|
2865
|
+
li++;
|
|
2866
|
+
const body = [];
|
|
2867
|
+
while (li + 1 < lines.length && lines[li + 1].includes("|") && lines[li + 1].trim()) {
|
|
2868
|
+
li++;
|
|
2869
|
+
body.push(splitTableRow(lines[li]));
|
|
2870
|
+
}
|
|
2871
|
+
for (const tl of renderTable(header, body, width)) out.push(tl);
|
|
2872
|
+
continue;
|
|
2873
|
+
}
|
|
2874
|
+
const fence = raw.match(/^\s*```(\w*)\s*$/);
|
|
2875
|
+
if (fence) {
|
|
2876
|
+
if (!inCode) {
|
|
2877
|
+
inCode = true;
|
|
2878
|
+
codeLang = fence[1] || "";
|
|
2879
|
+
out.push(c.dim(" \u250C" + (codeLang ? ` ${codeLang} ` : "\u2500").padEnd(Math.min(width, 40), "\u2500")));
|
|
2880
|
+
} else {
|
|
2881
|
+
inCode = false;
|
|
2882
|
+
out.push(c.dim(" \u2514" + "\u2500".repeat(Math.min(width, 38))));
|
|
2883
|
+
}
|
|
2884
|
+
continue;
|
|
2885
|
+
}
|
|
2886
|
+
if (inCode) {
|
|
2887
|
+
out.push(c.dim(" \u2502 ") + highlightLine(raw, codeLang));
|
|
2888
|
+
continue;
|
|
2889
|
+
}
|
|
2890
|
+
const h = raw.match(/^(#{1,6})\s+(.*)$/);
|
|
2891
|
+
if (h) {
|
|
2892
|
+
const txt = inline(h[2]);
|
|
2893
|
+
out.push("");
|
|
2894
|
+
out.push(" " + (useColor2 ? "\x1B[1m" : "") + (h[1].length <= 2 ? c.cyan(txt) : txt) + RESET);
|
|
2895
|
+
continue;
|
|
2896
|
+
}
|
|
2897
|
+
if (/^\s*([-*_])\1{2,}\s*$/.test(raw)) {
|
|
2898
|
+
out.push(" " + c.dim("\u2500".repeat(Math.min(width, 40))));
|
|
2899
|
+
continue;
|
|
2900
|
+
}
|
|
2901
|
+
const q = raw.match(/^\s*>\s?(.*)$/);
|
|
2902
|
+
if (q) {
|
|
2903
|
+
out.push(" " + c.dim("\u258F ") + c.italic(inline(q[1])));
|
|
2904
|
+
continue;
|
|
2905
|
+
}
|
|
2906
|
+
const ul = raw.match(/^(\s*)[-*+]\s+(.*)$/);
|
|
2907
|
+
const ol = raw.match(/^(\s*)(\d+)\.\s+(.*)$/);
|
|
2908
|
+
if (ul) {
|
|
2909
|
+
out.push(" " + ul[1] + c.cyan("\u2022 ") + inline(ul[2]));
|
|
2910
|
+
continue;
|
|
2911
|
+
}
|
|
2912
|
+
if (ol) {
|
|
2913
|
+
out.push(" " + ol[1] + c.cyan(ol[2] + ". ") + inline(ol[3]));
|
|
2914
|
+
continue;
|
|
2915
|
+
}
|
|
2916
|
+
if (!raw.trim()) {
|
|
2917
|
+
out.push("");
|
|
2918
|
+
continue;
|
|
2919
|
+
}
|
|
2920
|
+
for (const w of wrap2(inline(raw), width)) out.push(" " + w);
|
|
2921
|
+
}
|
|
2922
|
+
return out;
|
|
2923
|
+
}
|
|
2924
|
+
function vlen(s) {
|
|
2925
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
2926
|
+
}
|
|
2927
|
+
function splitTableRow(row) {
|
|
2928
|
+
let s = row.trim();
|
|
2929
|
+
if (s.startsWith("|")) s = s.slice(1);
|
|
2930
|
+
if (s.endsWith("|")) s = s.slice(0, -1);
|
|
2931
|
+
return s.split("|").map((cell) => inline(cell.trim()));
|
|
2932
|
+
}
|
|
2933
|
+
function renderTable(header, body, width) {
|
|
2934
|
+
const cols = Math.max(header.length, ...body.map((r) => r.length));
|
|
2935
|
+
const rows = [header, ...body].map((r) => {
|
|
2936
|
+
const cp = r.slice();
|
|
2937
|
+
while (cp.length < cols) cp.push("");
|
|
2938
|
+
return cp;
|
|
2939
|
+
});
|
|
2940
|
+
let w = Array.from({ length: cols }, (_, ci) => Math.max(...rows.map((r) => vlen(r[ci]))));
|
|
2941
|
+
const overhead = cols * 3 + 1;
|
|
2942
|
+
const budget = Math.max(cols * 3, width - overhead);
|
|
2943
|
+
const total = w.reduce((a, b) => a + b, 0);
|
|
2944
|
+
if (total > budget) w = w.map((x) => Math.max(3, Math.floor(x / total * budget)));
|
|
2945
|
+
const pad = (s, n) => {
|
|
2946
|
+
const v = vlen(s);
|
|
2947
|
+
if (v > n) {
|
|
2948
|
+
const plain = s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2949
|
+
return plain.slice(0, Math.max(0, n - 1)) + "\u2026";
|
|
2950
|
+
}
|
|
2951
|
+
return s + " ".repeat(n - v);
|
|
2952
|
+
};
|
|
2953
|
+
const bar = (l, m, r) => c.dim(" " + l + w.map((n) => "\u2500".repeat(n + 2)).join(m) + r);
|
|
2954
|
+
const rowLine = (r, head) => " " + c.dim("\u2502") + r.map((cell, ci) => " " + (head ? c.bold(c.cyan(pad(cell, w[ci]))) : pad(cell, w[ci])) + " ").join(c.dim("\u2502")) + c.dim("\u2502");
|
|
2955
|
+
const out = [bar("\u250C", "\u252C", "\u2510"), rowLine(rows[0], true), bar("\u251C", "\u253C", "\u2524")];
|
|
2956
|
+
for (const r of rows.slice(1)) out.push(rowLine(r, false));
|
|
2957
|
+
out.push(bar("\u2514", "\u2534", "\u2518"));
|
|
2958
|
+
return out;
|
|
2959
|
+
}
|
|
2960
|
+
function wrap2(s, width) {
|
|
2961
|
+
const words = s.split(/(\s+)/);
|
|
2962
|
+
const rows = [];
|
|
2963
|
+
let cur = "";
|
|
2964
|
+
let len = 0;
|
|
2965
|
+
const vlen2 = (x) => x.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
2966
|
+
for (const w of words) {
|
|
2967
|
+
const wl = vlen2(w);
|
|
2968
|
+
if (len + wl > width && cur) {
|
|
2969
|
+
rows.push(cur);
|
|
2970
|
+
cur = w.trimStart();
|
|
2971
|
+
len = vlen2(cur);
|
|
2972
|
+
} else {
|
|
2973
|
+
cur += w;
|
|
2974
|
+
len += wl;
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
if (cur.trim()) rows.push(cur);
|
|
2978
|
+
return rows.length ? rows : [""];
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
// src/agent.ts
|
|
2380
2982
|
var BASE_SYSTEM = `You are Zeta-G, a terminal coding agent for the ZETA engine.
|
|
2381
2983
|
|
|
2382
2984
|
ZETA turns a plain-language idea into a real, verified application: it writes
|
|
@@ -2528,6 +3130,10 @@ var Agent = class {
|
|
|
2528
3130
|
replaceHistory(messages) {
|
|
2529
3131
|
this.history = messages;
|
|
2530
3132
|
}
|
|
3133
|
+
/** A shallow copy of the current message history (for /export, inspection). */
|
|
3134
|
+
snapshot() {
|
|
3135
|
+
return [...this.history];
|
|
3136
|
+
}
|
|
2531
3137
|
/** % of the model's context window the current history occupies. */
|
|
2532
3138
|
contextPct() {
|
|
2533
3139
|
return Math.min(100, estimateTokens(this.history) / this.contextWindow * 100);
|
|
@@ -2536,7 +3142,7 @@ var Agent = class {
|
|
|
2536
3142
|
const native = this.toolNames.filter((t) => !isMcpTool(t));
|
|
2537
3143
|
const mcp = this.toolNames.filter((t) => isMcpTool(t));
|
|
2538
3144
|
const blocks = [
|
|
2539
|
-
DEFAULT_REGISTRY.composeSystem({ id: "zeta-g", overlayIds: [] }),
|
|
3145
|
+
DEFAULT_REGISTRY.composeSystem({ id: "zeta-g", role: this.opts.persona, overlayIds: [] }),
|
|
2540
3146
|
this.policy.authorityPreamble(),
|
|
2541
3147
|
`Tools available to you: ${native.join(", ")}.`
|
|
2542
3148
|
];
|
|
@@ -2582,7 +3188,7 @@ ${this.opts.memoryText}`);
|
|
|
2582
3188
|
return false;
|
|
2583
3189
|
}
|
|
2584
3190
|
/** Run one user turn end-to-end; streams to stdout, updates history. */
|
|
2585
|
-
async send(userInput, signal) {
|
|
3191
|
+
async send(userInput, signal, images) {
|
|
2586
3192
|
let input = userInput;
|
|
2587
3193
|
if (this.opts.hooks?.has("UserPromptSubmit")) {
|
|
2588
3194
|
const outcome = await this.opts.hooks.run("UserPromptSubmit", { prompt: userInput });
|
|
@@ -2598,11 +3204,22 @@ ${outcome.context.join("\n")}`;
|
|
|
2598
3204
|
}
|
|
2599
3205
|
}
|
|
2600
3206
|
this.session?.appendUser(userInput);
|
|
2601
|
-
|
|
3207
|
+
if (images && images.length > 0) {
|
|
3208
|
+
this.history.push({
|
|
3209
|
+
role: "user",
|
|
3210
|
+
content: [
|
|
3211
|
+
{ type: "text", text: input },
|
|
3212
|
+
...images.map((img) => ({ type: "image", image: img.dataUrl }))
|
|
3213
|
+
]
|
|
3214
|
+
});
|
|
3215
|
+
} else {
|
|
3216
|
+
this.history.push({ role: "user", content: input });
|
|
3217
|
+
}
|
|
2602
3218
|
await this.maybeCompact();
|
|
2603
3219
|
const providerOptions = buildProviderOptions(this.modelKey, this.thinking);
|
|
3220
|
+
const turnStart = Date.now();
|
|
2604
3221
|
const spinner = new Spinner();
|
|
2605
|
-
spinner.start("
|
|
3222
|
+
spinner.start("", { phrases: thinkingWords(this.opts.yolo), hint: "esc to interrupt" });
|
|
2606
3223
|
let spinning = true;
|
|
2607
3224
|
const stopSpin = () => {
|
|
2608
3225
|
if (spinning) {
|
|
@@ -2623,11 +3240,21 @@ ${outcome.context.join("\n")}`;
|
|
|
2623
3240
|
let aborted = false;
|
|
2624
3241
|
let textBuf = "";
|
|
2625
3242
|
let genFirstAt = 0;
|
|
3243
|
+
const toolStart = /* @__PURE__ */ new Map();
|
|
2626
3244
|
const flushText = () => {
|
|
2627
3245
|
const t = textBuf.trim();
|
|
2628
3246
|
textBuf = "";
|
|
2629
3247
|
phase = "none";
|
|
2630
|
-
if (t)
|
|
3248
|
+
if (!t) return;
|
|
3249
|
+
const inner = boxInnerWidth();
|
|
3250
|
+
const label = " Zeta-G1.0 ";
|
|
3251
|
+
const rem = Math.max(0, inner - label.length);
|
|
3252
|
+
const lft = Math.floor(rem / 2);
|
|
3253
|
+
line();
|
|
3254
|
+
line(" " + c.cyan("\u256D" + "\u2500".repeat(lft) + label + "\u2500".repeat(rem - lft) + "\u256E"));
|
|
3255
|
+
for (const ln of renderMarkdown(t, inner - 2)) line(ln);
|
|
3256
|
+
line(" " + c.cyan("\u2570" + "\u2500".repeat(inner) + "\u256F"));
|
|
3257
|
+
line();
|
|
2631
3258
|
};
|
|
2632
3259
|
try {
|
|
2633
3260
|
for await (const part of result.fullStream) {
|
|
@@ -2658,7 +3285,7 @@ ${outcome.context.join("\n")}`;
|
|
|
2658
3285
|
}
|
|
2659
3286
|
case "start-step":
|
|
2660
3287
|
if (phase !== "none" && !spinning) {
|
|
2661
|
-
spinner.start("
|
|
3288
|
+
spinner.start("", { phrases: thinkingWords(this.opts.yolo), hint: "esc to interrupt" });
|
|
2662
3289
|
spinning = true;
|
|
2663
3290
|
}
|
|
2664
3291
|
break;
|
|
@@ -2670,7 +3297,20 @@ ${outcome.context.join("\n")}`;
|
|
|
2670
3297
|
line();
|
|
2671
3298
|
phase = "none";
|
|
2672
3299
|
}
|
|
3300
|
+
{
|
|
3301
|
+
const id = part.toolCallId;
|
|
3302
|
+
if (id) toolStart.set(id, Date.now());
|
|
3303
|
+
}
|
|
3304
|
+
break;
|
|
3305
|
+
case "tool-result": {
|
|
3306
|
+
const id = part.toolCallId;
|
|
3307
|
+
const nm = String(part.toolName ?? "tool");
|
|
3308
|
+
const started = id ? toolStart.get(id) : void 0;
|
|
3309
|
+
const ms = started ? Date.now() - started : 0;
|
|
3310
|
+
if (ms >= 300) line(c.dim(` \u2713 ${nm} ${(ms / 1e3).toFixed(1)}s`));
|
|
3311
|
+
if (id) toolStart.delete(id);
|
|
2673
3312
|
break;
|
|
3313
|
+
}
|
|
2674
3314
|
case "tool-error": {
|
|
2675
3315
|
stopSpin();
|
|
2676
3316
|
if (phase === "text") flushText();
|
|
@@ -2756,6 +3396,7 @@ ${outcome.context.join("\n")}`;
|
|
|
2756
3396
|
} catch {
|
|
2757
3397
|
}
|
|
2758
3398
|
if (!aborted) await this.maybeCompact();
|
|
3399
|
+
if (!aborted && Date.now() - turnStart > 8e3) bell();
|
|
2759
3400
|
return { aborted, usage };
|
|
2760
3401
|
}
|
|
2761
3402
|
};
|
|
@@ -2773,12 +3414,14 @@ function isPermissionMode(v) {
|
|
|
2773
3414
|
}
|
|
2774
3415
|
var PLAN_REFUSAL = "refused: plan mode is read-only \u2014 present a plan for approval, don't act. The user can switch with /mode default or approve with /approve.";
|
|
2775
3416
|
var Permissions = class {
|
|
2776
|
-
constructor(mode, confirm) {
|
|
3417
|
+
constructor(mode, confirm, persistMode) {
|
|
2777
3418
|
this.mode = mode;
|
|
2778
3419
|
this.confirm = confirm;
|
|
3420
|
+
this.persistMode = persistMode;
|
|
2779
3421
|
}
|
|
2780
3422
|
mode;
|
|
2781
3423
|
confirm;
|
|
3424
|
+
persistMode;
|
|
2782
3425
|
alwaysCommands = /* @__PURE__ */ new Set();
|
|
2783
3426
|
acceptAllEdits = false;
|
|
2784
3427
|
isPlan() {
|
|
@@ -2801,8 +3444,15 @@ var Permissions = class {
|
|
|
2801
3444
|
const ans = await this.confirm(`apply this edit to ${c.bold(path)}?`, [
|
|
2802
3445
|
{ key: "y", label: "yes" },
|
|
2803
3446
|
{ key: "n", label: "no" },
|
|
2804
|
-
{ key: "a", label: "yes to all edits this session" }
|
|
3447
|
+
{ key: "a", label: "yes to all edits this session" },
|
|
3448
|
+
{ key: "r", label: "yes to all edits, every session (remember)" }
|
|
2805
3449
|
]);
|
|
3450
|
+
if (ans === "r") {
|
|
3451
|
+
this.acceptAllEdits = true;
|
|
3452
|
+
this.mode = "acceptEdits";
|
|
3453
|
+
this.persistMode?.("acceptEdits");
|
|
3454
|
+
return "allow";
|
|
3455
|
+
}
|
|
2806
3456
|
if (ans === "a") {
|
|
2807
3457
|
this.acceptAllEdits = true;
|
|
2808
3458
|
return "allow";
|
|
@@ -3023,6 +3673,139 @@ var Session = class _Session {
|
|
|
3023
3673
|
import { writeFileSync as writeFileSync2, existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
3024
3674
|
import { homedir as homedir4 } from "os";
|
|
3025
3675
|
import { join as join8 } from "path";
|
|
3676
|
+
|
|
3677
|
+
// src/git.ts
|
|
3678
|
+
import { execFileSync } from "child_process";
|
|
3679
|
+
function git(args, cwd) {
|
|
3680
|
+
return execFileSync("git", args, {
|
|
3681
|
+
cwd,
|
|
3682
|
+
encoding: "utf8",
|
|
3683
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
3684
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3685
|
+
}).trim();
|
|
3686
|
+
}
|
|
3687
|
+
function gitSafe(args, cwd) {
|
|
3688
|
+
try {
|
|
3689
|
+
return git(args, cwd);
|
|
3690
|
+
} catch {
|
|
3691
|
+
return null;
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
function isRepo(cwd) {
|
|
3695
|
+
return gitSafe(["rev-parse", "--is-inside-work-tree"], cwd) === "true";
|
|
3696
|
+
}
|
|
3697
|
+
var SECRET_HINTS = [
|
|
3698
|
+
{ re: /\bAKIA[0-9A-Z]{16}\b/, label: "AWS access key id" },
|
|
3699
|
+
{ re: /\bsk-[A-Za-z0-9]{20,}\b/, label: "OpenAI-style key" },
|
|
3700
|
+
{ re: /\bsk_live_[A-Za-z0-9]{20,}\b/, label: "Stripe live secret" },
|
|
3701
|
+
{ re: /\bghp_[A-Za-z0-9]{30,}\b/, label: "GitHub token" },
|
|
3702
|
+
{ re: /\bvbfl_[A-Za-z0-9]{8}_[A-Za-z0-9_-]{20,}\b/, label: "Wholestack PAT" },
|
|
3703
|
+
{ re: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/, label: "private key" }
|
|
3704
|
+
];
|
|
3705
|
+
function scanSecrets(diff) {
|
|
3706
|
+
const hits = /* @__PURE__ */ new Set();
|
|
3707
|
+
for (const { re, label } of SECRET_HINTS) {
|
|
3708
|
+
if (re.test(diff)) hits.add(label);
|
|
3709
|
+
}
|
|
3710
|
+
return [...hits];
|
|
3711
|
+
}
|
|
3712
|
+
var MAX_DIFF_CHARS = 12e3;
|
|
3713
|
+
var gitCommands = [
|
|
3714
|
+
{
|
|
3715
|
+
name: "diff",
|
|
3716
|
+
summary: "show working changes (/diff [--staged] [path])",
|
|
3717
|
+
source: "builtin",
|
|
3718
|
+
run: (ctx) => {
|
|
3719
|
+
if (!isRepo(ctx.cwd)) {
|
|
3720
|
+
ctx.print(" " + c.yellow("not a git repository."));
|
|
3721
|
+
return { type: "handled" };
|
|
3722
|
+
}
|
|
3723
|
+
const parts = ctx.args.split(/\s+/).filter(Boolean);
|
|
3724
|
+
const staged = parts.includes("--staged") || parts.includes("--cached");
|
|
3725
|
+
const paths = parts.filter((p) => p !== "--staged" && p !== "--cached");
|
|
3726
|
+
const args = ["--no-pager", "diff"];
|
|
3727
|
+
if (staged) args.push("--staged");
|
|
3728
|
+
if (paths.length) args.push("--", ...paths);
|
|
3729
|
+
const out = gitSafe(args, ctx.cwd) ?? "";
|
|
3730
|
+
if (!out) {
|
|
3731
|
+
ctx.print(" " + c.dim(staged ? "no staged changes." : "no unstaged changes."));
|
|
3732
|
+
return { type: "handled" };
|
|
3733
|
+
}
|
|
3734
|
+
const secrets = scanSecrets(out);
|
|
3735
|
+
if (secrets.length) {
|
|
3736
|
+
ctx.print(" " + c.red(`\u26A0 possible secret(s) in diff: ${secrets.join(", ")}`));
|
|
3737
|
+
}
|
|
3738
|
+
const shown = out.length > MAX_DIFF_CHARS ? out.slice(0, MAX_DIFF_CHARS) : out;
|
|
3739
|
+
ctx.print(shown);
|
|
3740
|
+
if (out.length > MAX_DIFF_CHARS) {
|
|
3741
|
+
ctx.print(" " + c.dim(`\u2026 (${out.length - MAX_DIFF_CHARS} more chars \u2014 run \`git diff\` for the rest)`));
|
|
3742
|
+
}
|
|
3743
|
+
return { type: "handled" };
|
|
3744
|
+
}
|
|
3745
|
+
},
|
|
3746
|
+
{
|
|
3747
|
+
name: "commit",
|
|
3748
|
+
summary: "commit changes (/commit [message] \u2014 omit to auto-write one)",
|
|
3749
|
+
source: "builtin",
|
|
3750
|
+
run: (ctx) => {
|
|
3751
|
+
if (!isRepo(ctx.cwd)) {
|
|
3752
|
+
ctx.print(" " + c.yellow("not a git repository. Run `git init` first."));
|
|
3753
|
+
return { type: "handled" };
|
|
3754
|
+
}
|
|
3755
|
+
const full = (gitSafe(["--no-pager", "diff", "HEAD"], ctx.cwd) ?? "") + (gitSafe(["--no-pager", "diff", "--staged"], ctx.cwd) ?? "");
|
|
3756
|
+
const secrets = scanSecrets(full);
|
|
3757
|
+
if (secrets.length) {
|
|
3758
|
+
ctx.print(" " + c.red(`\u2717 refusing to commit \u2014 possible secret(s): ${secrets.join(", ")}`));
|
|
3759
|
+
ctx.print(" " + c.dim("remove them (or add to .gitignore) and try again."));
|
|
3760
|
+
return { type: "handled" };
|
|
3761
|
+
}
|
|
3762
|
+
const msg = ctx.args.trim();
|
|
3763
|
+
if (msg) {
|
|
3764
|
+
try {
|
|
3765
|
+
git(["add", "-A"], ctx.cwd);
|
|
3766
|
+
const out = git(["commit", "-m", msg], ctx.cwd);
|
|
3767
|
+
ctx.print(" " + c.green("\u2713 committed"));
|
|
3768
|
+
ctx.print(" " + c.dim(out.split("\n")[0] ?? ""));
|
|
3769
|
+
} catch (e) {
|
|
3770
|
+
ctx.print(" " + c.red(e.message.split("\n")[0]));
|
|
3771
|
+
}
|
|
3772
|
+
return { type: "handled" };
|
|
3773
|
+
}
|
|
3774
|
+
const branch = gitSafe(["rev-parse", "--abbrev-ref", "HEAD"], ctx.cwd) ?? "?";
|
|
3775
|
+
return {
|
|
3776
|
+
type: "prompt",
|
|
3777
|
+
text: `Create a git commit for the current changes. Steps, using run_command:
|
|
3778
|
+
1. \`git status --porcelain\` and \`git --no-pager diff HEAD\` to see what changed.
|
|
3779
|
+
2. If nothing is staged, \`git add -A\` (but NEVER stage .env, secrets, or key files).
|
|
3780
|
+
3. Write ONE Conventional Commit message: \`type(scope): summary\` (\u226472 chars), a body only if the change needs it. Base it strictly on the actual diff \u2014 no invented changes.
|
|
3781
|
+
4. \`git commit -m "\u2026"\` (current branch: ${branch}). Show me the message you used.
|
|
3782
|
+
Do NOT push. Do NOT commit anything that looks like a credential.`
|
|
3783
|
+
};
|
|
3784
|
+
}
|
|
3785
|
+
},
|
|
3786
|
+
{
|
|
3787
|
+
name: "pr",
|
|
3788
|
+
summary: "push the branch and open a pull request (/pr [title])",
|
|
3789
|
+
source: "builtin",
|
|
3790
|
+
run: (ctx) => {
|
|
3791
|
+
if (!isRepo(ctx.cwd)) {
|
|
3792
|
+
ctx.print(" " + c.yellow("not a git repository."));
|
|
3793
|
+
return { type: "handled" };
|
|
3794
|
+
}
|
|
3795
|
+
const branch = gitSafe(["rev-parse", "--abbrev-ref", "HEAD"], ctx.cwd) ?? "?";
|
|
3796
|
+
const title = ctx.args.trim();
|
|
3797
|
+
return {
|
|
3798
|
+
type: "prompt",
|
|
3799
|
+
text: `Open a pull request for the current branch. Steps, using run_command:
|
|
3800
|
+
1. Confirm the branch (\`${branch}\`) is not the default; if it is, create a feature branch first.
|
|
3801
|
+
2. \`git push -u origin HEAD\`.
|
|
3802
|
+
3. \`gh pr create\` with a clear title` + (title ? ` (use: "${title}")` : " summarizing the change") + " and a concise body listing the key changes from the commit log/diff.\nIf `gh` is not installed or not authenticated, stop and tell me the manual PR URL instead."
|
|
3803
|
+
};
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
];
|
|
3807
|
+
|
|
3808
|
+
// src/commands.ts
|
|
3026
3809
|
var ZETA_MD_TEMPLATE = `# ZETA.md \u2014 project memory for Zeta-G
|
|
3027
3810
|
|
|
3028
3811
|
Tell the agent how this project works. It reads this file into its system prompt.
|
|
@@ -3359,6 +4142,39 @@ var BUILTINS = [
|
|
|
3359
4142
|
ctx.print(" " + c.dim("run ") + c.cyan("zeta-g login") + c.dim(" in your shell, then restart the session."));
|
|
3360
4143
|
return { type: "handled" };
|
|
3361
4144
|
}
|
|
4145
|
+
},
|
|
4146
|
+
...gitCommands,
|
|
4147
|
+
{
|
|
4148
|
+
name: "export",
|
|
4149
|
+
aliases: ["save"],
|
|
4150
|
+
summary: "write the transcript to markdown (/export [file.md])",
|
|
4151
|
+
source: "builtin",
|
|
4152
|
+
run: (ctx) => ({ type: "export", file: ctx.args.trim() || void 0 })
|
|
4153
|
+
},
|
|
4154
|
+
{
|
|
4155
|
+
name: "tasks",
|
|
4156
|
+
aliases: ["jobs"],
|
|
4157
|
+
summary: "list background tasks (/tasks \xB7 /tasks kill <id>)",
|
|
4158
|
+
source: "builtin",
|
|
4159
|
+
run: (ctx) => {
|
|
4160
|
+
const parts = ctx.args.split(/\s+/).filter(Boolean);
|
|
4161
|
+
if (parts[0] === "kill" && parts[1]) {
|
|
4162
|
+
const ok = tasks.kill(parts[1]);
|
|
4163
|
+
ctx.print(" " + (ok ? c.green(`signaled ${parts[1]}`) : c.red(`no task ${parts[1]}`)));
|
|
4164
|
+
return { type: "handled" };
|
|
4165
|
+
}
|
|
4166
|
+
const list = tasks.list();
|
|
4167
|
+
if (list.length === 0) {
|
|
4168
|
+
ctx.print(" " + c.dim("no background tasks. The agent starts them with run_background."));
|
|
4169
|
+
return { type: "handled" };
|
|
4170
|
+
}
|
|
4171
|
+
for (const t of list) {
|
|
4172
|
+
const dot = t.status === "running" ? c.yellow("\u25CF") : t.status === "exited" ? c.green("\u2713") : c.red("\u2717");
|
|
4173
|
+
const code = t.exitCode == null ? "" : c.dim(` (exit ${t.exitCode})`);
|
|
4174
|
+
ctx.print(` ${dot} ${c.cyan(t.id)} ${c.dim(`${tasks.runtimeSeconds(t)}s`)} ${t.display}${code}`);
|
|
4175
|
+
}
|
|
4176
|
+
return { type: "handled" };
|
|
4177
|
+
}
|
|
3362
4178
|
}
|
|
3363
4179
|
];
|
|
3364
4180
|
function parseCustom(name, body, source = "custom") {
|
|
@@ -3859,8 +4675,16 @@ var InputController = class {
|
|
|
3859
4675
|
}
|
|
3860
4676
|
return [[], line2];
|
|
3861
4677
|
}
|
|
3862
|
-
/** Read one logical line
|
|
4678
|
+
/** Read one logical line. On a TTY this renders a live bordered input box
|
|
4679
|
+
* (the box appears WHILE you type, not as an after-the-fact echo). On a
|
|
4680
|
+
* non-TTY / piped stdin it falls back to plain readline. */
|
|
3863
4681
|
async readLine(promptStr) {
|
|
4682
|
+
if (stdin.isTTY && !process.env.NO_COLOR) {
|
|
4683
|
+
const text = await this.readBoxed();
|
|
4684
|
+
const trimmed2 = text.trim();
|
|
4685
|
+
if (trimmed2) this.saveHistory(trimmed2);
|
|
4686
|
+
return trimmed2;
|
|
4687
|
+
}
|
|
3864
4688
|
let acc = "";
|
|
3865
4689
|
let prompt = promptStr;
|
|
3866
4690
|
for (; ; ) {
|
|
@@ -3877,6 +4701,230 @@ var InputController = class {
|
|
|
3877
4701
|
if (trimmed) this.saveHistory(trimmed);
|
|
3878
4702
|
return trimmed;
|
|
3879
4703
|
}
|
|
4704
|
+
/**
|
|
4705
|
+
* Live bordered input editor. Redraws a 4-side box on every keystroke with
|
|
4706
|
+
* the caret tracked inside it. Supports: insert/backspace/delete, ←/→,
|
|
4707
|
+
* Home/End (Ctrl-A/E), ↑/↓ history, Tab completion (/commands + @paths),
|
|
4708
|
+
* Shift/Alt-Enter or a trailing "\\" for a newline, Enter to submit,
|
|
4709
|
+
* Ctrl-C to quit, Ctrl-U to clear, Esc to clear the line.
|
|
4710
|
+
*/
|
|
4711
|
+
readBoxed() {
|
|
4712
|
+
const PROMPT = "\u203A ";
|
|
4713
|
+
const history = loadHistory();
|
|
4714
|
+
let histIdx = history.length;
|
|
4715
|
+
let draft = "";
|
|
4716
|
+
let buf = "";
|
|
4717
|
+
let cur = 0;
|
|
4718
|
+
let prevRows = 0;
|
|
4719
|
+
let F = Math.max(8, boxInnerWidth() - 2);
|
|
4720
|
+
let W = F + 2;
|
|
4721
|
+
const up = (n) => n > 0 ? `\x1B[${n}A` : "";
|
|
4722
|
+
const right = (n) => n > 0 ? `\x1B[${n}C` : "";
|
|
4723
|
+
const layout = (s) => {
|
|
4724
|
+
const rows = [""];
|
|
4725
|
+
const map = [];
|
|
4726
|
+
let r = 0;
|
|
4727
|
+
let col = 0;
|
|
4728
|
+
for (let i = 0; i < s.length; i++) {
|
|
4729
|
+
map[i] = { r, c: col };
|
|
4730
|
+
const ch = s[i];
|
|
4731
|
+
if (ch === "\n") {
|
|
4732
|
+
r++;
|
|
4733
|
+
col = 0;
|
|
4734
|
+
rows[r] = "";
|
|
4735
|
+
continue;
|
|
4736
|
+
}
|
|
4737
|
+
rows[r] += ch;
|
|
4738
|
+
col++;
|
|
4739
|
+
if (col >= F) {
|
|
4740
|
+
r++;
|
|
4741
|
+
col = 0;
|
|
4742
|
+
rows[r] = "";
|
|
4743
|
+
}
|
|
4744
|
+
}
|
|
4745
|
+
map[s.length] = { r, c: col };
|
|
4746
|
+
return { rows, map };
|
|
4747
|
+
};
|
|
4748
|
+
const render = () => {
|
|
4749
|
+
const S = PROMPT + buf;
|
|
4750
|
+
const { rows, map } = layout(S);
|
|
4751
|
+
const caret = map[PROMPT.length + cur];
|
|
4752
|
+
const contentRows = rows.length;
|
|
4753
|
+
let out = "";
|
|
4754
|
+
if (prevRows > 0) out += up(prevCaretR + 1) + "\r";
|
|
4755
|
+
out += "\x1B[J";
|
|
4756
|
+
out += "\r " + c.dim("\u256D" + "\u2500".repeat(W) + "\u256E") + "\n";
|
|
4757
|
+
rows.forEach((row, i) => {
|
|
4758
|
+
let cell = row;
|
|
4759
|
+
let body;
|
|
4760
|
+
if (i === 0 && cell.startsWith(PROMPT)) {
|
|
4761
|
+
body = c.cyan(PROMPT) + cell.slice(PROMPT.length).padEnd(F - PROMPT.length);
|
|
4762
|
+
} else {
|
|
4763
|
+
body = cell.padEnd(F);
|
|
4764
|
+
}
|
|
4765
|
+
out += "\r " + c.dim("\u2502") + " " + body + " " + c.dim("\u2502") + "\n";
|
|
4766
|
+
});
|
|
4767
|
+
out += "\r " + c.dim("\u2570" + "\u2500".repeat(W) + "\u256F");
|
|
4768
|
+
out += up(contentRows - caret.r) + "\r" + right(4 + caret.c);
|
|
4769
|
+
stdout.write(out);
|
|
4770
|
+
prevRows = contentRows;
|
|
4771
|
+
prevCaretR = caret.r;
|
|
4772
|
+
};
|
|
4773
|
+
let prevCaretR = 0;
|
|
4774
|
+
return new Promise((resolve3) => {
|
|
4775
|
+
emitKeypressEvents(stdin);
|
|
4776
|
+
const watching = this.activeWatch;
|
|
4777
|
+
if (watching) this.stopWatch();
|
|
4778
|
+
this.rl.pause();
|
|
4779
|
+
if (stdin.isTTY) stdin.setRawMode(true);
|
|
4780
|
+
stdin.resume();
|
|
4781
|
+
stdout.write("\x1B[?2004h");
|
|
4782
|
+
const onResize = () => {
|
|
4783
|
+
F = Math.max(8, boxInnerWidth() - 2);
|
|
4784
|
+
W = F + 2;
|
|
4785
|
+
prevRows = 0;
|
|
4786
|
+
render();
|
|
4787
|
+
};
|
|
4788
|
+
process.stdout.on("resize", onResize);
|
|
4789
|
+
const finish = (val) => {
|
|
4790
|
+
stdin.off("keypress", onKey);
|
|
4791
|
+
process.stdout.off("resize", onResize);
|
|
4792
|
+
stdout.write("\x1B[?2004l");
|
|
4793
|
+
if (stdin.isTTY) stdin.setRawMode(false);
|
|
4794
|
+
const down = prevRows - prevCaretR;
|
|
4795
|
+
stdout.write("\x1B[" + Math.max(1, down) + "B\r\n");
|
|
4796
|
+
this.rl.resume();
|
|
4797
|
+
if (watching) this.startWatch(watching);
|
|
4798
|
+
resolve3(val);
|
|
4799
|
+
};
|
|
4800
|
+
const replaceLine = (s) => {
|
|
4801
|
+
buf = s;
|
|
4802
|
+
cur = buf.length;
|
|
4803
|
+
};
|
|
4804
|
+
const onKey = (s, key) => {
|
|
4805
|
+
const name = key?.name;
|
|
4806
|
+
if (key?.ctrl && name === "c") {
|
|
4807
|
+
stdout.write("\n");
|
|
4808
|
+
this.opts.onExit();
|
|
4809
|
+
return;
|
|
4810
|
+
}
|
|
4811
|
+
if (key?.ctrl && name === "u") {
|
|
4812
|
+
buf = "";
|
|
4813
|
+
cur = 0;
|
|
4814
|
+
return render();
|
|
4815
|
+
}
|
|
4816
|
+
if (key?.ctrl && name === "w") {
|
|
4817
|
+
const before = buf.slice(0, cur).replace(/\s*\S+\s*$/, "");
|
|
4818
|
+
const after = buf.slice(cur);
|
|
4819
|
+
buf = before + after;
|
|
4820
|
+
cur = before.length;
|
|
4821
|
+
return render();
|
|
4822
|
+
}
|
|
4823
|
+
if (key?.ctrl && name === "k") {
|
|
4824
|
+
buf = buf.slice(0, cur);
|
|
4825
|
+
return render();
|
|
4826
|
+
}
|
|
4827
|
+
if (key?.meta && name === "left") {
|
|
4828
|
+
const m = buf.slice(0, cur).match(/\S+\s*$/);
|
|
4829
|
+
cur -= m ? m[0].length : cur;
|
|
4830
|
+
return render();
|
|
4831
|
+
}
|
|
4832
|
+
if (key?.meta && name === "right") {
|
|
4833
|
+
const m = buf.slice(cur).match(/^\s*\S+/);
|
|
4834
|
+
cur += m ? m[0].length : buf.length - cur;
|
|
4835
|
+
return render();
|
|
4836
|
+
}
|
|
4837
|
+
if (name === "return" || name === "enter") {
|
|
4838
|
+
if (key?.shift || key?.meta || buf.endsWith("\\")) {
|
|
4839
|
+
if (buf.endsWith("\\")) {
|
|
4840
|
+
buf = buf.slice(0, cur - 1) + "\n" + buf.slice(cur);
|
|
4841
|
+
} else {
|
|
4842
|
+
buf = buf.slice(0, cur) + "\n" + buf.slice(cur);
|
|
4843
|
+
}
|
|
4844
|
+
cur += 1;
|
|
4845
|
+
return render();
|
|
4846
|
+
}
|
|
4847
|
+
return finish(buf);
|
|
4848
|
+
}
|
|
4849
|
+
if (name === "backspace") {
|
|
4850
|
+
if (cur > 0) {
|
|
4851
|
+
buf = buf.slice(0, cur - 1) + buf.slice(cur);
|
|
4852
|
+
cur--;
|
|
4853
|
+
}
|
|
4854
|
+
return render();
|
|
4855
|
+
}
|
|
4856
|
+
if (name === "delete") {
|
|
4857
|
+
if (cur < buf.length) buf = buf.slice(0, cur) + buf.slice(cur + 1);
|
|
4858
|
+
return render();
|
|
4859
|
+
}
|
|
4860
|
+
if (name === "left") {
|
|
4861
|
+
if (cur > 0) cur--;
|
|
4862
|
+
return render();
|
|
4863
|
+
}
|
|
4864
|
+
if (name === "right") {
|
|
4865
|
+
if (cur < buf.length) cur++;
|
|
4866
|
+
return render();
|
|
4867
|
+
}
|
|
4868
|
+
if (name === "home" || key?.ctrl && name === "a") {
|
|
4869
|
+
cur = 0;
|
|
4870
|
+
return render();
|
|
4871
|
+
}
|
|
4872
|
+
if (name === "end" || key?.ctrl && name === "e") {
|
|
4873
|
+
cur = buf.length;
|
|
4874
|
+
return render();
|
|
4875
|
+
}
|
|
4876
|
+
if (name === "up") {
|
|
4877
|
+
if (histIdx === history.length) draft = buf;
|
|
4878
|
+
if (histIdx > 0) {
|
|
4879
|
+
histIdx--;
|
|
4880
|
+
replaceLine(history[histIdx]);
|
|
4881
|
+
}
|
|
4882
|
+
return render();
|
|
4883
|
+
}
|
|
4884
|
+
if (name === "down") {
|
|
4885
|
+
if (histIdx < history.length) {
|
|
4886
|
+
histIdx++;
|
|
4887
|
+
replaceLine(histIdx === history.length ? draft : history[histIdx]);
|
|
4888
|
+
}
|
|
4889
|
+
return render();
|
|
4890
|
+
}
|
|
4891
|
+
if (name === "escape") {
|
|
4892
|
+
buf = "";
|
|
4893
|
+
cur = 0;
|
|
4894
|
+
return render();
|
|
4895
|
+
}
|
|
4896
|
+
if (name === "tab") {
|
|
4897
|
+
const [hits] = this.complete(buf.slice(0, cur));
|
|
4898
|
+
if (hits.length) {
|
|
4899
|
+
const prefix = longestCommonPrefix(hits);
|
|
4900
|
+
const before = buf.slice(0, cur);
|
|
4901
|
+
const ws = Math.max(before.lastIndexOf(" "), before.lastIndexOf("\n"));
|
|
4902
|
+
const tokenStart = before.startsWith("/") && ws < 0 ? 0 : ws + 1;
|
|
4903
|
+
buf = buf.slice(0, tokenStart) + prefix + buf.slice(cur);
|
|
4904
|
+
cur = tokenStart + prefix.length;
|
|
4905
|
+
}
|
|
4906
|
+
return render();
|
|
4907
|
+
}
|
|
4908
|
+
const seq = key?.sequence ?? s ?? "";
|
|
4909
|
+
if (seq.includes("\x1B[200~") || seq.includes("\x1B[201~")) {
|
|
4910
|
+
const pasted = seq.replace(/\x1b\[20[01]~/g, "");
|
|
4911
|
+
buf = buf.slice(0, cur) + pasted + buf.slice(cur);
|
|
4912
|
+
cur += pasted.length;
|
|
4913
|
+
return render();
|
|
4914
|
+
}
|
|
4915
|
+
const ins = s && !key?.ctrl && !key?.meta ? s : "";
|
|
4916
|
+
if (ins && !ins.includes("\x1B")) {
|
|
4917
|
+
buf = buf.slice(0, cur) + ins + buf.slice(cur);
|
|
4918
|
+
cur += ins.length;
|
|
4919
|
+
return render();
|
|
4920
|
+
}
|
|
4921
|
+
};
|
|
4922
|
+
const bar = this.opts.contextBar?.();
|
|
4923
|
+
if (bar) stdout.write(" " + bar + "\n");
|
|
4924
|
+
stdin.on("keypress", onKey);
|
|
4925
|
+
render();
|
|
4926
|
+
});
|
|
4927
|
+
}
|
|
3880
4928
|
saveHistory(entry) {
|
|
3881
4929
|
try {
|
|
3882
4930
|
mkdirSync3(dirname5(HISTORY_FILE), { recursive: true });
|
|
@@ -3948,6 +4996,17 @@ var InputController = class {
|
|
|
3948
4996
|
this.rl.close();
|
|
3949
4997
|
}
|
|
3950
4998
|
};
|
|
4999
|
+
function longestCommonPrefix(items) {
|
|
5000
|
+
if (!items.length) return "";
|
|
5001
|
+
let prefix = items[0];
|
|
5002
|
+
for (const it of items.slice(1)) {
|
|
5003
|
+
let i = 0;
|
|
5004
|
+
while (i < prefix.length && i < it.length && prefix[i] === it[i]) i++;
|
|
5005
|
+
prefix = prefix.slice(0, i);
|
|
5006
|
+
if (!prefix) break;
|
|
5007
|
+
}
|
|
5008
|
+
return prefix;
|
|
5009
|
+
}
|
|
3951
5010
|
|
|
3952
5011
|
export {
|
|
3953
5012
|
c,
|
|
@@ -3961,6 +5020,7 @@ export {
|
|
|
3961
5020
|
PROPERTY_KINDS,
|
|
3962
5021
|
runProver,
|
|
3963
5022
|
killRunningApps,
|
|
5023
|
+
tasks,
|
|
3964
5024
|
buildTools,
|
|
3965
5025
|
HookRunner,
|
|
3966
5026
|
mergeHookSets,
|
|
@@ -3971,6 +5031,7 @@ export {
|
|
|
3971
5031
|
resolveModelKey,
|
|
3972
5032
|
modelLabel,
|
|
3973
5033
|
modelId,
|
|
5034
|
+
visionCapable,
|
|
3974
5035
|
modelContextWindow,
|
|
3975
5036
|
supportsThinking,
|
|
3976
5037
|
listModels,
|