@appchy/jarvis 0.1.63 → 0.1.65
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/bin.js +303 -126
- package/dist/bin.js.map +1 -1
- package/dist/ui/app.js +30 -30
- package/dist/ui/index.html +7 -0
- package/package.json +4 -4
package/dist/bin.js
CHANGED
|
@@ -197,19 +197,19 @@ function startScanner(options) {
|
|
|
197
197
|
const trimmed = rel.trim();
|
|
198
198
|
if (!trimmed) continue;
|
|
199
199
|
const abs = path3.join(repo.path, trimmed);
|
|
200
|
-
let
|
|
200
|
+
let stat4;
|
|
201
201
|
try {
|
|
202
|
-
|
|
202
|
+
stat4 = fs3.statSync(abs);
|
|
203
203
|
} catch {
|
|
204
204
|
continue;
|
|
205
205
|
}
|
|
206
|
-
if (!
|
|
206
|
+
if (!stat4.isFile()) continue;
|
|
207
207
|
const ext = path3.extname(trimmed).slice(1).toLowerCase() || null;
|
|
208
208
|
items.push({
|
|
209
209
|
repoPath: repo.path,
|
|
210
210
|
path: trimmed,
|
|
211
211
|
language: ext,
|
|
212
|
-
size:
|
|
212
|
+
size: stat4.size,
|
|
213
213
|
preview: readPreview(abs)
|
|
214
214
|
});
|
|
215
215
|
if (items.length >= BATCH_CAP) break;
|
|
@@ -401,20 +401,20 @@ function walkSessions(dir) {
|
|
|
401
401
|
const out = [];
|
|
402
402
|
for (const name of entries) {
|
|
403
403
|
const p = path3.join(dir, name);
|
|
404
|
-
let
|
|
404
|
+
let stat4;
|
|
405
405
|
try {
|
|
406
|
-
|
|
406
|
+
stat4 = fs3.statSync(p);
|
|
407
407
|
} catch {
|
|
408
408
|
continue;
|
|
409
409
|
}
|
|
410
|
-
if (
|
|
410
|
+
if (stat4.isDirectory()) {
|
|
411
411
|
out.push(...walkSessions(p));
|
|
412
412
|
continue;
|
|
413
413
|
}
|
|
414
414
|
if (!name.endsWith(".jsonl") && !name.endsWith(".json")) continue;
|
|
415
415
|
out.push({
|
|
416
416
|
filePath: p,
|
|
417
|
-
mtimeMs:
|
|
417
|
+
mtimeMs: stat4.mtimeMs,
|
|
418
418
|
messageCount: countLines(p)
|
|
419
419
|
});
|
|
420
420
|
}
|
|
@@ -748,7 +748,7 @@ function startIpcServer(socketPath, userId) {
|
|
|
748
748
|
};
|
|
749
749
|
}
|
|
750
750
|
async function callDaemonIpc(socketPath, method, params, timeoutMs = 5e3) {
|
|
751
|
-
return new Promise((
|
|
751
|
+
return new Promise((resolve11, reject) => {
|
|
752
752
|
const conn = net.connect(socketPath);
|
|
753
753
|
let buf = "";
|
|
754
754
|
let settled = false;
|
|
@@ -780,7 +780,7 @@ async function callDaemonIpc(socketPath, method, params, timeoutMs = 5e3) {
|
|
|
780
780
|
if (parsed.error) {
|
|
781
781
|
finish(() => reject(new Error(parsed.error?.message ?? "ipc error")));
|
|
782
782
|
} else {
|
|
783
|
-
finish(() =>
|
|
783
|
+
finish(() => resolve11(parsed.result));
|
|
784
784
|
}
|
|
785
785
|
} catch (err) {
|
|
786
786
|
finish(() => reject(err instanceof Error ? err : new Error(String(err))));
|
|
@@ -1215,10 +1215,10 @@ function humanizeAgo(iso) {
|
|
|
1215
1215
|
}
|
|
1216
1216
|
function readlineQuestion(req) {
|
|
1217
1217
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1218
|
-
return new Promise((
|
|
1218
|
+
return new Promise((resolve11) => {
|
|
1219
1219
|
rl.question(req.prompt, (answer) => {
|
|
1220
1220
|
rl.close();
|
|
1221
|
-
|
|
1221
|
+
resolve11(answer);
|
|
1222
1222
|
});
|
|
1223
1223
|
});
|
|
1224
1224
|
}
|
|
@@ -1242,8 +1242,8 @@ var TOOL_DIRS = [
|
|
|
1242
1242
|
"/opt/homebrew/bin",
|
|
1243
1243
|
"/usr/local/bin"
|
|
1244
1244
|
];
|
|
1245
|
-
function runnable(bin,
|
|
1246
|
-
const probe =
|
|
1245
|
+
function runnable(bin, spawn4) {
|
|
1246
|
+
const probe = spawn4(bin, ["--help"], { stdio: "ignore" });
|
|
1247
1247
|
return probe.error?.code !== "ENOENT";
|
|
1248
1248
|
}
|
|
1249
1249
|
function isExecutable(path22) {
|
|
@@ -1255,9 +1255,9 @@ function isExecutable(path22) {
|
|
|
1255
1255
|
}
|
|
1256
1256
|
}
|
|
1257
1257
|
function locate(bin, options) {
|
|
1258
|
-
const
|
|
1258
|
+
const spawn4 = options.spawn ?? spawnSync;
|
|
1259
1259
|
const executable = options.executable ?? isExecutable;
|
|
1260
|
-
if (runnable(bin,
|
|
1260
|
+
if (runnable(bin, spawn4)) return { command: bin, strayDir: null };
|
|
1261
1261
|
for (const dir of TOOL_DIRS) {
|
|
1262
1262
|
if (executable(join(dir, bin))) return { command: join(dir, bin), strayDir: dir };
|
|
1263
1263
|
}
|
|
@@ -1267,10 +1267,10 @@ function reachInto(dir) {
|
|
|
1267
1267
|
process.env.PATH = `${dir}${delimiter}${process.env.PATH ?? ""}`;
|
|
1268
1268
|
}
|
|
1269
1269
|
function resolveGraphify(options = {}) {
|
|
1270
|
-
const
|
|
1270
|
+
const tool2 = locate(BIN, options);
|
|
1271
1271
|
return {
|
|
1272
|
-
onPath:
|
|
1273
|
-
strayDir:
|
|
1272
|
+
onPath: tool2.command !== null && tool2.strayDir === null,
|
|
1273
|
+
strayDir: tool2.strayDir
|
|
1274
1274
|
};
|
|
1275
1275
|
}
|
|
1276
1276
|
function findUv(options = {}) {
|
|
@@ -1280,15 +1280,15 @@ async function confirmAtTerminal(req) {
|
|
|
1280
1280
|
if (!process.stdin.isTTY) return false;
|
|
1281
1281
|
const question = req.needsUv ? ` Install ${BIN}, and ${UV} (from ${UV_INSTALLER}) which installs it? [Y/n] ` : ` Install ${BIN} now with ${UV}? [Y/n] `;
|
|
1282
1282
|
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
1283
|
-
const answer = await new Promise((
|
|
1284
|
-
rl.question(question, (input) =>
|
|
1283
|
+
const answer = await new Promise((resolve11) => {
|
|
1284
|
+
rl.question(question, (input) => resolve11(input.trim().toLowerCase()));
|
|
1285
1285
|
});
|
|
1286
1286
|
rl.close();
|
|
1287
1287
|
return answer === "" || answer === "y" || answer === "yes";
|
|
1288
1288
|
}
|
|
1289
1289
|
function installGraphify(req, options = {}) {
|
|
1290
|
-
const
|
|
1291
|
-
const install =
|
|
1290
|
+
const spawn4 = options.spawn ?? spawnSync;
|
|
1291
|
+
const install = spawn4(req.uv, ["tool", "install", PACKAGE], { stdio: "inherit" });
|
|
1292
1292
|
if (install.status !== 0) {
|
|
1293
1293
|
return {
|
|
1294
1294
|
installed: false,
|
|
@@ -1307,16 +1307,16 @@ function installGraphify(req, options = {}) {
|
|
|
1307
1307
|
return { installed: true, strayDir: after.strayDir, problem: null };
|
|
1308
1308
|
}
|
|
1309
1309
|
function installUv(options = {}) {
|
|
1310
|
-
const
|
|
1311
|
-
if (!runnable("curl",
|
|
1310
|
+
const spawn4 = options.spawn ?? spawnSync;
|
|
1311
|
+
if (!runnable("curl", spawn4)) {
|
|
1312
1312
|
return { uv: null, problem: `curl is needed to fetch ${UV_INSTALLER} and is not installed.` };
|
|
1313
1313
|
}
|
|
1314
1314
|
const script = join(process.env.TMPDIR ?? "/tmp", `uv-install-${process.pid}.sh`);
|
|
1315
|
-
const fetched =
|
|
1315
|
+
const fetched = spawn4("curl", ["-LsSf", UV_INSTALLER, "-o", script], { stdio: "inherit" });
|
|
1316
1316
|
if (fetched.status !== 0) {
|
|
1317
1317
|
return { uv: null, problem: `could not download ${UV_INSTALLER}.` };
|
|
1318
1318
|
}
|
|
1319
|
-
const ran =
|
|
1319
|
+
const ran = spawn4("sh", [script], { stdio: "inherit" });
|
|
1320
1320
|
if (ran.status !== 0) {
|
|
1321
1321
|
return { uv: null, problem: `the uv installer did not succeed. See ${UV_INSTALLER}.` };
|
|
1322
1322
|
}
|
|
@@ -2510,10 +2510,10 @@ function rootsEqual(a, b) {
|
|
|
2510
2510
|
}
|
|
2511
2511
|
async function readlineQuestion2(question) {
|
|
2512
2512
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
2513
|
-
return new Promise((
|
|
2513
|
+
return new Promise((resolve11) => {
|
|
2514
2514
|
rl.question(question, (input) => {
|
|
2515
2515
|
rl.close();
|
|
2516
|
-
|
|
2516
|
+
resolve11(input);
|
|
2517
2517
|
});
|
|
2518
2518
|
});
|
|
2519
2519
|
}
|
|
@@ -2593,8 +2593,8 @@ function defaultPrompts() {
|
|
|
2593
2593
|
}
|
|
2594
2594
|
async function promptList(question, fallback) {
|
|
2595
2595
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
2596
|
-
const answer = await new Promise((
|
|
2597
|
-
rl.question(question, (input) =>
|
|
2596
|
+
const answer = await new Promise((resolve11) => {
|
|
2597
|
+
rl.question(question, (input) => resolve11(input.trim()));
|
|
2598
2598
|
});
|
|
2599
2599
|
rl.close();
|
|
2600
2600
|
const raw = !answer ? fallback : answer.split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -2748,8 +2748,8 @@ function register5(program) {
|
|
|
2748
2748
|
}
|
|
2749
2749
|
async function confirm(question) {
|
|
2750
2750
|
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
2751
|
-
const answer = await new Promise((
|
|
2752
|
-
rl.question(question, (input) =>
|
|
2751
|
+
const answer = await new Promise((resolve11) => {
|
|
2752
|
+
rl.question(question, (input) => resolve11(input.trim()));
|
|
2753
2753
|
});
|
|
2754
2754
|
rl.close();
|
|
2755
2755
|
return /^y(es)?$/i.test(answer);
|
|
@@ -3020,7 +3020,7 @@ async function idOf(term) {
|
|
|
3020
3020
|
} catch {
|
|
3021
3021
|
}
|
|
3022
3022
|
}
|
|
3023
|
-
await new Promise((
|
|
3023
|
+
await new Promise((resolve11) => setTimeout(resolve11, 250));
|
|
3024
3024
|
}
|
|
3025
3025
|
throw new Error("Claude Code started but never registered a session id");
|
|
3026
3026
|
}
|
|
@@ -3089,13 +3089,13 @@ async function live(req) {
|
|
|
3089
3089
|
else options.signal.addEventListener("abort", () => term.kill(), { once: true });
|
|
3090
3090
|
}
|
|
3091
3091
|
rest();
|
|
3092
|
-
await new Promise((
|
|
3092
|
+
await new Promise((resolve11) => {
|
|
3093
3093
|
term.onExit(({ exitCode, signal }) => {
|
|
3094
3094
|
if (idle) clearTimeout(idle);
|
|
3095
3095
|
if (closedForIdle) report2("stopped", "closed after sitting idle");
|
|
3096
3096
|
else if (exitCode === 0) report2("done");
|
|
3097
3097
|
else report2("failed", endedAs(exitCode, signal));
|
|
3098
|
-
|
|
3098
|
+
resolve11();
|
|
3099
3099
|
});
|
|
3100
3100
|
});
|
|
3101
3101
|
}
|
|
@@ -3608,13 +3608,13 @@ async function walkRoot(req) {
|
|
|
3608
3608
|
for (const entry of entries) {
|
|
3609
3609
|
if (entry.startsWith(".")) continue;
|
|
3610
3610
|
const projectDir = path15.join(req.root.dir, entry);
|
|
3611
|
-
let
|
|
3611
|
+
let stat4;
|
|
3612
3612
|
try {
|
|
3613
|
-
|
|
3613
|
+
stat4 = await fs14.stat(projectDir);
|
|
3614
3614
|
} catch {
|
|
3615
3615
|
continue;
|
|
3616
3616
|
}
|
|
3617
|
-
if (!
|
|
3617
|
+
if (!stat4.isDirectory()) continue;
|
|
3618
3618
|
let files2;
|
|
3619
3619
|
try {
|
|
3620
3620
|
files2 = await fs14.readdir(projectDir);
|
|
@@ -3655,18 +3655,18 @@ async function listLocalSessionIds(req = {}) {
|
|
|
3655
3655
|
async function getSessionFile(req) {
|
|
3656
3656
|
const provider2 = getSessionProvider({ path: req.path });
|
|
3657
3657
|
if (!provider2) return null;
|
|
3658
|
-
let
|
|
3658
|
+
let stat4;
|
|
3659
3659
|
try {
|
|
3660
|
-
|
|
3660
|
+
stat4 = await fs14.stat(req.path);
|
|
3661
3661
|
} catch {
|
|
3662
3662
|
return null;
|
|
3663
3663
|
}
|
|
3664
|
-
if (!
|
|
3664
|
+
if (!stat4.isFile()) return null;
|
|
3665
3665
|
return {
|
|
3666
3666
|
provider: provider2,
|
|
3667
3667
|
filePath: req.path,
|
|
3668
3668
|
projectDir: path15.dirname(req.path),
|
|
3669
|
-
mtimeMs:
|
|
3669
|
+
mtimeMs: stat4.mtimeMs
|
|
3670
3670
|
};
|
|
3671
3671
|
}
|
|
3672
3672
|
var defaultRepoCache = /* @__PURE__ */ new Map();
|
|
@@ -4498,45 +4498,10 @@ function describeAge(when) {
|
|
|
4498
4498
|
|
|
4499
4499
|
// ../../packages/board/src/board.git.ts
|
|
4500
4500
|
var run3 = promisify4(execFile4);
|
|
4501
|
-
var FORMAT = "%x1e%H%x1f%aI%x1f%an%x1f%B";
|
|
4502
4501
|
var SOURCED = "%x1e%H%x1f%S%x1f%aI%x1f%an%x1f%B";
|
|
4503
4502
|
var TRAILER = "^Work-Item: ";
|
|
4504
4503
|
var SEP = "";
|
|
4505
4504
|
var REC = "";
|
|
4506
|
-
async function boardCommits(root, id) {
|
|
4507
|
-
const out = await git2(root, [
|
|
4508
|
-
"log",
|
|
4509
|
-
"--all",
|
|
4510
|
-
"--reverse",
|
|
4511
|
-
`--format=${FORMAT}`,
|
|
4512
|
-
"-E",
|
|
4513
|
-
`--grep=^Work-Item: ${escapeGrep(id)}$`
|
|
4514
|
-
]);
|
|
4515
|
-
const commits = [];
|
|
4516
|
-
for (const record of out.split(REC)) {
|
|
4517
|
-
if (!record.trim()) continue;
|
|
4518
|
-
const [sha, when, author, message] = record.split(SEP);
|
|
4519
|
-
if (!message || !sha) continue;
|
|
4520
|
-
const branch = await branchOf(root, sha);
|
|
4521
|
-
commits.push({
|
|
4522
|
-
sha,
|
|
4523
|
-
when: when ?? "",
|
|
4524
|
-
by: author ?? "",
|
|
4525
|
-
message,
|
|
4526
|
-
branch,
|
|
4527
|
-
branchLastCommitAt: await lastCommitOn(root, branch)
|
|
4528
|
-
});
|
|
4529
|
-
}
|
|
4530
|
-
return commits;
|
|
4531
|
-
}
|
|
4532
|
-
async function lastCommitOn(root, branch) {
|
|
4533
|
-
return (await git2(root, ["log", "-1", "--format=%aI", branch || "HEAD"])).trim();
|
|
4534
|
-
}
|
|
4535
|
-
async function branchOf(root, sha) {
|
|
4536
|
-
const out = await git2(root, ["branch", "--all", "--format=%(refname:short)", "--contains", sha]);
|
|
4537
|
-
const first = out.split("\n").map((line) => line.trim()).filter(Boolean).filter((line) => !line.includes("HEAD ->"))[0];
|
|
4538
|
-
return first ?? "";
|
|
4539
|
-
}
|
|
4540
4505
|
async function git2(root, argv) {
|
|
4541
4506
|
try {
|
|
4542
4507
|
const { stdout } = await run3("git", argv, { cwd: root, timeout: 2e4 });
|
|
@@ -4545,10 +4510,19 @@ async function git2(root, argv) {
|
|
|
4545
4510
|
return "";
|
|
4546
4511
|
}
|
|
4547
4512
|
}
|
|
4548
|
-
function
|
|
4549
|
-
|
|
4513
|
+
function boardHistory(root) {
|
|
4514
|
+
const running = walking.get(root);
|
|
4515
|
+
if (running) return running;
|
|
4516
|
+
const walk2 = readHistory(root);
|
|
4517
|
+
walking.set(root, walk2);
|
|
4518
|
+
const done = () => {
|
|
4519
|
+
if (walking.get(root) === walk2) walking.delete(root);
|
|
4520
|
+
};
|
|
4521
|
+
void walk2.then(done, done);
|
|
4522
|
+
return walk2;
|
|
4550
4523
|
}
|
|
4551
|
-
|
|
4524
|
+
var walking = /* @__PURE__ */ new Map();
|
|
4525
|
+
async function readHistory(root) {
|
|
4552
4526
|
const [out, tips] = await Promise.all([
|
|
4553
4527
|
git2(root, ["log", "--all", "--source", "--reverse", `--format=${SOURCED}`, "-E", `--grep=${TRAILER}`]),
|
|
4554
4528
|
branchTips(root)
|
|
@@ -4591,7 +4565,8 @@ async function listHoldersByItem(root) {
|
|
|
4591
4565
|
return toHoldersByItem(await boardHistory(root));
|
|
4592
4566
|
}
|
|
4593
4567
|
async function listHolders(root, id) {
|
|
4594
|
-
|
|
4568
|
+
const byItem = await listHoldersByItem(root);
|
|
4569
|
+
return byItem.get(id) ?? [];
|
|
4595
4570
|
}
|
|
4596
4571
|
|
|
4597
4572
|
// ../../packages/board/src/board.tree.ts
|
|
@@ -4757,8 +4732,14 @@ async function listWorkItems(_ctx, req, options) {
|
|
|
4757
4732
|
return toPage(await scanTree(options.root), req);
|
|
4758
4733
|
}
|
|
4759
4734
|
async function findWorkItem(_ctx, req, options) {
|
|
4760
|
-
const
|
|
4761
|
-
|
|
4735
|
+
const work = join4(options.root, "work");
|
|
4736
|
+
for (const base of ["versions", "backlog"]) {
|
|
4737
|
+
for (const file of await walk(join4(work, base))) {
|
|
4738
|
+
const path22 = pathIn(work, file);
|
|
4739
|
+
if (path22.split("/").at(-2) === req.id) return readItem(work, file);
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4742
|
+
return null;
|
|
4762
4743
|
}
|
|
4763
4744
|
async function getWorkItem(ctx, req, options) {
|
|
4764
4745
|
const item2 = await findWorkItem(ctx, req, options);
|
|
@@ -4887,20 +4868,37 @@ function harnessMessage(err) {
|
|
|
4887
4868
|
const said = (e.stderr || e.stdout || "").trim();
|
|
4888
4869
|
return said || e.message || "the work harness failed";
|
|
4889
4870
|
}
|
|
4890
|
-
|
|
4871
|
+
function scanTree(root) {
|
|
4872
|
+
const running = scanning.get(root);
|
|
4873
|
+
if (running) return running;
|
|
4874
|
+
const scan2 = readTree(root);
|
|
4875
|
+
scanning.set(root, scan2);
|
|
4876
|
+
const done = () => {
|
|
4877
|
+
if (scanning.get(root) === scan2) scanning.delete(root);
|
|
4878
|
+
};
|
|
4879
|
+
void scan2.then(done, done);
|
|
4880
|
+
return scan2;
|
|
4881
|
+
}
|
|
4882
|
+
var scanning = /* @__PURE__ */ new Map();
|
|
4883
|
+
async function readTree(root) {
|
|
4891
4884
|
const work = join4(root, "work");
|
|
4892
4885
|
const items = [];
|
|
4893
4886
|
for (const base of ["versions", "backlog"]) {
|
|
4894
4887
|
for (const file of await walk(join4(work, base))) {
|
|
4895
|
-
const
|
|
4896
|
-
const changedAt = (await stat(file)).mtime.toISOString().slice(0, 10);
|
|
4897
|
-
const path22 = relative(work, file).split(sep).join("/");
|
|
4898
|
-
const item2 = toWorkItem({ path: path22, text, changedAt });
|
|
4888
|
+
const item2 = await readItem(work, file);
|
|
4899
4889
|
if (item2) items.push(item2);
|
|
4900
4890
|
}
|
|
4901
4891
|
}
|
|
4902
4892
|
return items;
|
|
4903
4893
|
}
|
|
4894
|
+
async function readItem(work, file) {
|
|
4895
|
+
const text = await readFile(file, "utf-8");
|
|
4896
|
+
const changedAt2 = (await stat(file)).mtime.toISOString().slice(0, 10);
|
|
4897
|
+
return toWorkItem({ path: pathIn(work, file), text, changedAt: changedAt2 });
|
|
4898
|
+
}
|
|
4899
|
+
function pathIn(work, file) {
|
|
4900
|
+
return relative(work, file).split(sep).join("/");
|
|
4901
|
+
}
|
|
4904
4902
|
async function walk(dir, depth = 0) {
|
|
4905
4903
|
if (depth > 6) return [];
|
|
4906
4904
|
let entries;
|
|
@@ -7135,8 +7133,8 @@ function notReadyMs() {
|
|
|
7135
7133
|
var PENDING = /* @__PURE__ */ Symbol("pending");
|
|
7136
7134
|
async function readyWithin(pending) {
|
|
7137
7135
|
let timer;
|
|
7138
|
-
const timeout = new Promise((
|
|
7139
|
-
timer = setTimeout(() =>
|
|
7136
|
+
const timeout = new Promise((resolve11) => {
|
|
7137
|
+
timer = setTimeout(() => resolve11(PENDING), notReadyMs());
|
|
7140
7138
|
timer.unref?.();
|
|
7141
7139
|
});
|
|
7142
7140
|
try {
|
|
@@ -9272,7 +9270,7 @@ function createServer(ctx, hosted = [], prompts = []) {
|
|
|
9272
9270
|
{
|
|
9273
9271
|
instructions: buildInstructions({
|
|
9274
9272
|
...typeof ctx === "function" ? {} : { ctx },
|
|
9275
|
-
served: [...TOOLS.map((
|
|
9273
|
+
served: [...TOOLS.map((tool2) => tool2.name), ...hosted.map((tool2) => tool2.name)]
|
|
9276
9274
|
})
|
|
9277
9275
|
}
|
|
9278
9276
|
);
|
|
@@ -9328,7 +9326,7 @@ function createServer(ctx, hosted = [], prompts = []) {
|
|
|
9328
9326
|
return server;
|
|
9329
9327
|
}
|
|
9330
9328
|
function buildInstructions(req = {}) {
|
|
9331
|
-
const served = req.served ?? TOOLS.map((
|
|
9329
|
+
const served = req.served ?? TOOLS.map((tool2) => tool2.name);
|
|
9332
9330
|
const has = (prefix) => served.some((name) => name.startsWith(prefix));
|
|
9333
9331
|
const lines = [
|
|
9334
9332
|
"This repo, through one connection: the work it has to do, and a map of what it knows."
|
|
@@ -9636,13 +9634,13 @@ async function readFileTree(req) {
|
|
|
9636
9634
|
if (entry && mtimeMs !== null && Date.now() - mtimeMs < TREE_FRESHNESS_MS) {
|
|
9637
9635
|
return { tree: entry.tree, root: entry.root };
|
|
9638
9636
|
}
|
|
9639
|
-
let
|
|
9637
|
+
let stat4;
|
|
9640
9638
|
try {
|
|
9641
|
-
|
|
9639
|
+
stat4 = await fs15.stat(req.workdir);
|
|
9642
9640
|
} catch {
|
|
9643
9641
|
return { tree: [], root: null };
|
|
9644
9642
|
}
|
|
9645
|
-
if (!
|
|
9643
|
+
if (!stat4.isDirectory()) return { tree: [], root: null };
|
|
9646
9644
|
const tree2 = await walkTree({ absPath: req.workdir, rootPath: req.workdir, depth: 0 });
|
|
9647
9645
|
await writeTreeCacheEntry({
|
|
9648
9646
|
workdir: req.workdir,
|
|
@@ -9683,15 +9681,15 @@ async function readFileContent(workdir, relPath) {
|
|
|
9683
9681
|
if (!absPath.startsWith(resolvedRoot + path16.sep) && absPath !== resolvedRoot) {
|
|
9684
9682
|
throw new FileReadError("path_outside_workdir", "path_outside_workdir");
|
|
9685
9683
|
}
|
|
9686
|
-
const
|
|
9687
|
-
if (!
|
|
9684
|
+
const stat4 = await fs15.stat(absPath);
|
|
9685
|
+
if (!stat4.isFile()) {
|
|
9688
9686
|
throw new FileReadError("not_a_file", "not_a_file");
|
|
9689
9687
|
}
|
|
9690
|
-
if (
|
|
9691
|
-
return { path: relPath, content: null, size:
|
|
9688
|
+
if (stat4.size > MAX_FILE_BYTES) {
|
|
9689
|
+
return { path: relPath, content: null, size: stat4.size, tooLarge: true };
|
|
9692
9690
|
}
|
|
9693
9691
|
const content = await fs15.readFile(absPath, "utf-8");
|
|
9694
|
-
return { path: relPath, content, size:
|
|
9692
|
+
return { path: relPath, content, size: stat4.size };
|
|
9695
9693
|
}
|
|
9696
9694
|
|
|
9697
9695
|
// src/workspace.ts
|
|
@@ -10117,7 +10115,7 @@ import { createRequire as createRequire2 } from "module";
|
|
|
10117
10115
|
var _require = createRequire2(import.meta.url);
|
|
10118
10116
|
var VERSION2 = _require("../package.json").version ?? "0.0.0";
|
|
10119
10117
|
var IS_DEV = String(_require("../package.json").name ?? "").endsWith("-dev");
|
|
10120
|
-
var SHA = "
|
|
10118
|
+
var SHA = "223ee7f";
|
|
10121
10119
|
var BUILT = "2026-09-09";
|
|
10122
10120
|
var BUILD = SHA ?? "source";
|
|
10123
10121
|
var BUILD_LABEL = `${SHA ? `${VERSION2} (${SHA}${BUILT ? ` ${BUILT}` : ""})` : `${VERSION2} (source)`}${IS_DEV ? " \u2014 development build" : ""}`;
|
|
@@ -10572,7 +10570,7 @@ async function startAgent(options) {
|
|
|
10572
10570
|
timestamp: Date.now()
|
|
10573
10571
|
};
|
|
10574
10572
|
publish(envCtx, "env.disconnected", { envId: options.envId, reason: "shutdown" });
|
|
10575
|
-
await new Promise((
|
|
10573
|
+
await new Promise((resolve11) => setImmediate(resolve11));
|
|
10576
10574
|
}
|
|
10577
10575
|
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
10578
10576
|
if (ipcServer) {
|
|
@@ -10849,10 +10847,10 @@ async function getHealth(req = {}) {
|
|
|
10849
10847
|
async function probeSocket(url, probeMs) {
|
|
10850
10848
|
const port = portOf(url);
|
|
10851
10849
|
if (port === null) return { url, reachable: false };
|
|
10852
|
-
const reachable = await new Promise((
|
|
10850
|
+
const reachable = await new Promise((resolve11) => {
|
|
10853
10851
|
const done = (answer) => {
|
|
10854
10852
|
socket.destroy();
|
|
10855
|
-
|
|
10853
|
+
resolve11(answer);
|
|
10856
10854
|
};
|
|
10857
10855
|
const socket = net2.connect({ host: new URL(url).hostname, port }).once("connect", () => done(true)).once("error", () => done(false));
|
|
10858
10856
|
socket.setTimeout(probeMs, () => done(false));
|
|
@@ -11025,10 +11023,10 @@ function removeDir(p) {
|
|
|
11025
11023
|
}
|
|
11026
11024
|
function confirm2(prompt) {
|
|
11027
11025
|
const rl = createInterface5({ input: process.stdin, output: process.stdout });
|
|
11028
|
-
return new Promise((
|
|
11026
|
+
return new Promise((resolve11) => {
|
|
11029
11027
|
rl.question(prompt, (answer) => {
|
|
11030
11028
|
rl.close();
|
|
11031
|
-
|
|
11029
|
+
resolve11(/^y(es)?$/i.test(answer.trim()));
|
|
11032
11030
|
});
|
|
11033
11031
|
});
|
|
11034
11032
|
}
|
|
@@ -11868,16 +11866,16 @@ function describeState(verb, response) {
|
|
|
11868
11866
|
|
|
11869
11867
|
// src/serve/hosted.ts
|
|
11870
11868
|
import crypto6 from "crypto";
|
|
11871
|
-
function toHostedTool(
|
|
11869
|
+
function toHostedTool(tool2) {
|
|
11872
11870
|
return {
|
|
11873
|
-
name:
|
|
11874
|
-
title:
|
|
11875
|
-
description:
|
|
11876
|
-
inputSchema: toShape(
|
|
11877
|
-
annotations: { readOnlyHint:
|
|
11871
|
+
name: tool2.name,
|
|
11872
|
+
title: tool2.title ?? tool2.name,
|
|
11873
|
+
description: tool2.description,
|
|
11874
|
+
inputSchema: toShape(tool2.input),
|
|
11875
|
+
annotations: { readOnlyHint: tool2.metadata?.readOnly === true },
|
|
11878
11876
|
call: async (input) => {
|
|
11879
|
-
const output = await
|
|
11880
|
-
return { ...output, summary: output.message ?? `${
|
|
11877
|
+
const output = await tool2.call(callerContext(), input);
|
|
11878
|
+
return { ...output, summary: output.message ?? `${tool2.name} ok` };
|
|
11881
11879
|
}
|
|
11882
11880
|
};
|
|
11883
11881
|
}
|
|
@@ -12181,7 +12179,7 @@ async function readMachine(req) {
|
|
|
12181
12179
|
}
|
|
12182
12180
|
|
|
12183
12181
|
// src/ui/server.ts
|
|
12184
|
-
var
|
|
12182
|
+
var SHIPPED = resolve9(dirname7(fileURLToPath4(import.meta.url)), "ui");
|
|
12185
12183
|
var TYPES = {
|
|
12186
12184
|
".html": "text/html; charset=utf-8",
|
|
12187
12185
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -12221,10 +12219,10 @@ async function item(repo, id, res) {
|
|
|
12221
12219
|
const read3 = await getWorkItem2(context(), { id }, { root: repo, harness: harness2() });
|
|
12222
12220
|
json(res, 200, { item: read3.item, brief: read3.brief });
|
|
12223
12221
|
}
|
|
12224
|
-
async function asset(repo, url, res) {
|
|
12222
|
+
async function asset(repo, assets, url, res) {
|
|
12225
12223
|
const wanted = url.pathname === "/" || !extname(url.pathname) ? "/index.html" : url.pathname;
|
|
12226
|
-
const path22 = join9(
|
|
12227
|
-
if (!path22.startsWith(
|
|
12224
|
+
const path22 = join9(assets, wanted);
|
|
12225
|
+
if (!path22.startsWith(assets)) {
|
|
12228
12226
|
res.writeHead(403).end("no");
|
|
12229
12227
|
return;
|
|
12230
12228
|
}
|
|
@@ -12249,7 +12247,9 @@ async function asset(repo, url, res) {
|
|
|
12249
12247
|
);
|
|
12250
12248
|
}
|
|
12251
12249
|
}
|
|
12252
|
-
function serveUi(repo, port) {
|
|
12250
|
+
function serveUi(repo, port, options = {}) {
|
|
12251
|
+
const assets = options.assets ?? SHIPPED;
|
|
12252
|
+
const watching = /* @__PURE__ */ new Set();
|
|
12253
12253
|
const server = createServer2((req, res) => {
|
|
12254
12254
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
12255
12255
|
void (async () => {
|
|
@@ -12270,7 +12270,17 @@ function serveUi(repo, port) {
|
|
|
12270
12270
|
json(res, 200, await readMachine({ root: repo }));
|
|
12271
12271
|
return;
|
|
12272
12272
|
}
|
|
12273
|
-
|
|
12273
|
+
if (url.pathname === "/api/v1/ui/reload") {
|
|
12274
|
+
res.writeHead(200, {
|
|
12275
|
+
"content-type": "text/event-stream",
|
|
12276
|
+
"cache-control": "no-store",
|
|
12277
|
+
connection: "keep-alive"
|
|
12278
|
+
});
|
|
12279
|
+
watching.add(res);
|
|
12280
|
+
res.on("close", () => watching.delete(res));
|
|
12281
|
+
return;
|
|
12282
|
+
}
|
|
12283
|
+
await asset(repo, assets, url, res);
|
|
12274
12284
|
} catch (err) {
|
|
12275
12285
|
const status2 = err instanceof ServiceError ? err.statusCode : 500;
|
|
12276
12286
|
json(res, status2, { error: err instanceof Error ? err.message : String(err) });
|
|
@@ -12282,21 +12292,167 @@ function serveUi(repo, port) {
|
|
|
12282
12292
|
server.listen(port, "127.0.0.1", () => {
|
|
12283
12293
|
ok({
|
|
12284
12294
|
url: `http://localhost:${port}`,
|
|
12285
|
-
|
|
12295
|
+
reload: () => {
|
|
12296
|
+
for (const page of watching) page.write("data: reload\n\n");
|
|
12297
|
+
},
|
|
12298
|
+
close: () => new Promise((done) => {
|
|
12299
|
+
for (const page of watching) page.end();
|
|
12300
|
+
watching.clear();
|
|
12301
|
+
server.close(() => done());
|
|
12302
|
+
})
|
|
12286
12303
|
});
|
|
12287
12304
|
});
|
|
12288
12305
|
});
|
|
12289
12306
|
}
|
|
12290
12307
|
|
|
12308
|
+
// src/ui/source.ts
|
|
12309
|
+
import { spawn as spawn3 } from "child_process";
|
|
12310
|
+
import { existsSync as existsSync6, readFileSync as readFileSync8, watch } from "fs";
|
|
12311
|
+
import { stat as stat3 } from "fs/promises";
|
|
12312
|
+
import { dirname as dirname8, join as join10, resolve as resolve10 } from "path";
|
|
12313
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
12314
|
+
var PAGE = join10("apps", "cli", "dist", "ui");
|
|
12315
|
+
var BUNDLE = "app.js";
|
|
12316
|
+
var WATCHED = [join10("apps", "cli", "src", "ui"), join10("packages", "ui", "src")];
|
|
12317
|
+
function fromSource() {
|
|
12318
|
+
return fileURLToPath5(import.meta.url).endsWith(".ts");
|
|
12319
|
+
}
|
|
12320
|
+
function sourceCheckout() {
|
|
12321
|
+
const named2 = process.env.JARVIS_SOURCE;
|
|
12322
|
+
if (named2) return usable(resolve10(named2));
|
|
12323
|
+
if (fromSource()) return usable(resolve10(dirname8(fileURLToPath5(import.meta.url)), "../../../.."));
|
|
12324
|
+
const built = builtFrom();
|
|
12325
|
+
return built ? usable(built) : null;
|
|
12326
|
+
}
|
|
12327
|
+
function builtFrom() {
|
|
12328
|
+
try {
|
|
12329
|
+
const manifest = join10(dirname8(fileURLToPath5(import.meta.url)), "..", "package.json");
|
|
12330
|
+
const read3 = JSON.parse(readFileSync8(manifest, "utf-8"));
|
|
12331
|
+
return typeof read3.jarvis?.source === "string" ? read3.jarvis.source : null;
|
|
12332
|
+
} catch {
|
|
12333
|
+
return null;
|
|
12334
|
+
}
|
|
12335
|
+
}
|
|
12336
|
+
function usable(root) {
|
|
12337
|
+
const files2 = [
|
|
12338
|
+
join10(root, "apps", "cli", "src", "ui", "browser", "App.tsx"),
|
|
12339
|
+
join10(root, "apps", "cli", "tsup.ui.config.ts")
|
|
12340
|
+
];
|
|
12341
|
+
const present = files2.every((path22) => existsSync6(path22)) && tool(root, "tsup") && tool(root, "tsx");
|
|
12342
|
+
return present ? root : null;
|
|
12343
|
+
}
|
|
12344
|
+
function tool(root, name) {
|
|
12345
|
+
const places = [
|
|
12346
|
+
join10(root, "apps", "cli", "node_modules", ".bin", name),
|
|
12347
|
+
join10(root, "node_modules", ".bin", name)
|
|
12348
|
+
];
|
|
12349
|
+
return places.find((path22) => existsSync6(path22)) ?? null;
|
|
12350
|
+
}
|
|
12351
|
+
function runFromCheckout(req) {
|
|
12352
|
+
const child = spawn3(
|
|
12353
|
+
tool(req.root, "tsx") ?? "tsx",
|
|
12354
|
+
[
|
|
12355
|
+
"watch",
|
|
12356
|
+
"--clear-screen=false",
|
|
12357
|
+
join10(req.root, "apps", "cli", "src", "bin.ts"),
|
|
12358
|
+
"ui",
|
|
12359
|
+
"--repo",
|
|
12360
|
+
req.repo,
|
|
12361
|
+
"--port",
|
|
12362
|
+
String(req.port),
|
|
12363
|
+
// The supervisor opens the browser, once, when the port answers. Left to the child it
|
|
12364
|
+
// would open a fresh tab on every restart `tsx watch` makes.
|
|
12365
|
+
"--no-open"
|
|
12366
|
+
],
|
|
12367
|
+
{ stdio: "inherit", cwd: req.root }
|
|
12368
|
+
);
|
|
12369
|
+
const relay = (signal) => () => {
|
|
12370
|
+
child.kill(signal);
|
|
12371
|
+
};
|
|
12372
|
+
process.once("SIGINT", relay("SIGINT"));
|
|
12373
|
+
process.once("SIGTERM", relay("SIGTERM"));
|
|
12374
|
+
child.once("exit", (code) => {
|
|
12375
|
+
process.exitCode = code ?? 0;
|
|
12376
|
+
});
|
|
12377
|
+
return child;
|
|
12378
|
+
}
|
|
12379
|
+
async function watchPage(root) {
|
|
12380
|
+
const dir = join10(root, PAGE);
|
|
12381
|
+
const before = await changedAt(join10(dir, BUNDLE));
|
|
12382
|
+
const build2 = spawn3(
|
|
12383
|
+
tool(root, "tsup") ?? "tsup",
|
|
12384
|
+
["--config", "tsup.ui.config.ts", ...WATCHED.flatMap((where) => ["--watch", join10(root, where)])],
|
|
12385
|
+
{
|
|
12386
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
12387
|
+
cwd: join10(root, "apps", "cli")
|
|
12388
|
+
}
|
|
12389
|
+
);
|
|
12390
|
+
const stop = () => {
|
|
12391
|
+
build2.kill("SIGTERM");
|
|
12392
|
+
};
|
|
12393
|
+
try {
|
|
12394
|
+
await landed(join10(dir, BUNDLE), before);
|
|
12395
|
+
} catch (err) {
|
|
12396
|
+
stop();
|
|
12397
|
+
throw err;
|
|
12398
|
+
}
|
|
12399
|
+
return { dir, stop };
|
|
12400
|
+
}
|
|
12401
|
+
var PATIENCE2 = 12e4;
|
|
12402
|
+
async function landed(file, before) {
|
|
12403
|
+
const until = Date.now() + PATIENCE2;
|
|
12404
|
+
while (Date.now() < until) {
|
|
12405
|
+
if (await changedAt(file) > before) return;
|
|
12406
|
+
await new Promise((done) => setTimeout(done, 100));
|
|
12407
|
+
}
|
|
12408
|
+
throw new Error(`the page did not build within ${PATIENCE2 / 1e3}s \u2014 run \`pnpm build\` there to see why`);
|
|
12409
|
+
}
|
|
12410
|
+
async function changedAt(file) {
|
|
12411
|
+
try {
|
|
12412
|
+
return (await stat3(file)).mtimeMs;
|
|
12413
|
+
} catch {
|
|
12414
|
+
return 0;
|
|
12415
|
+
}
|
|
12416
|
+
}
|
|
12417
|
+
function whenRebuilt(dir, reload) {
|
|
12418
|
+
let due = null;
|
|
12419
|
+
return watch(dir, (_event, name) => {
|
|
12420
|
+
if (name !== BUNDLE) return;
|
|
12421
|
+
if (due) clearTimeout(due);
|
|
12422
|
+
due = setTimeout(reload, 120);
|
|
12423
|
+
});
|
|
12424
|
+
}
|
|
12425
|
+
|
|
12291
12426
|
// src/commands/ui.ts
|
|
12292
12427
|
function register21(program) {
|
|
12293
|
-
program.command("ui").description("Open this repo's board in a browser \u2014 nothing to configure, nothing to join").option("--repo <path>", "The repo to show. Default: the working directory").option("--port <number>", "The port to listen on. Default: 3402").option("--no-open", "Print the address instead of opening a browser").action(async (opts) => {
|
|
12428
|
+
program.command("ui").description("Open this repo's board in a browser \u2014 nothing to configure, nothing to join").option("--repo <path>", "The repo to show. Default: the working directory").option("--port <number>", "The port to listen on. Default: 3402").option("--no-open", "Print the address instead of opening a browser").option("--packaged", "Serve the page this build shipped with, never a checkout's").action(async (opts) => {
|
|
12294
12429
|
const repo = resolveRepo(opts.repo);
|
|
12295
12430
|
const port = opts.port ? Number.parseInt(opts.port, 10) : 3402;
|
|
12431
|
+
const source2 = opts.packaged ? null : sourceCheckout();
|
|
12432
|
+
if (source2 && !fromSource()) {
|
|
12433
|
+
console.log(`${basename3(repo)} \u2192 serving ${source2}, from source`);
|
|
12434
|
+
runFromCheckout({ root: source2, repo, port });
|
|
12435
|
+
if (opts.open !== false) void openWhenUp(port);
|
|
12436
|
+
return;
|
|
12437
|
+
}
|
|
12438
|
+
let assets;
|
|
12439
|
+
let stopBuilding;
|
|
12440
|
+
if (source2) {
|
|
12441
|
+
console.log("building the board's page from this checkout\u2026");
|
|
12442
|
+
try {
|
|
12443
|
+
const page = await watchPage(source2);
|
|
12444
|
+
assets = page.dir;
|
|
12445
|
+
stopBuilding = page.stop;
|
|
12446
|
+
} catch (err) {
|
|
12447
|
+
console.error(`could not build the page from ${source2}: ${err instanceof Error ? err.message : String(err)}`);
|
|
12448
|
+
console.error("serving the page this build shipped with instead.");
|
|
12449
|
+
}
|
|
12450
|
+
}
|
|
12296
12451
|
let server;
|
|
12297
12452
|
try {
|
|
12298
|
-
server = await serveUi(repo, port);
|
|
12453
|
+
server = await serveUi(repo, port, assets ? { assets } : {});
|
|
12299
12454
|
} catch (err) {
|
|
12455
|
+
stopBuilding?.();
|
|
12300
12456
|
const busy = err.code === "EADDRINUSE";
|
|
12301
12457
|
console.error(
|
|
12302
12458
|
busy ? `port ${port} is already in use. Something else is on it \u2014 pass --port to pick another.` : `could not start: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -12304,16 +12460,37 @@ function register21(program) {
|
|
|
12304
12460
|
process.exitCode = 1;
|
|
12305
12461
|
return;
|
|
12306
12462
|
}
|
|
12463
|
+
const watching = assets ? whenRebuilt(assets, server.reload) : null;
|
|
12307
12464
|
console.log(`${basename3(repo)} \u2192 ${server.url}`);
|
|
12308
|
-
console.log(
|
|
12465
|
+
console.log(
|
|
12466
|
+
assets ? "Reading the board from this checkout, and rebuilding the page as you save it. Ctrl-C to stop." : "Reading the board from this checkout. Ctrl-C to stop."
|
|
12467
|
+
);
|
|
12309
12468
|
if (opts.open !== false) openBrowser({ url: server.url });
|
|
12310
12469
|
await new Promise((done) => {
|
|
12311
|
-
const stop = () =>
|
|
12470
|
+
const stop = () => {
|
|
12471
|
+
watching?.close();
|
|
12472
|
+
stopBuilding?.();
|
|
12473
|
+
void server.close().then(done);
|
|
12474
|
+
};
|
|
12312
12475
|
process.once("SIGINT", stop);
|
|
12313
12476
|
process.once("SIGTERM", stop);
|
|
12314
12477
|
});
|
|
12315
12478
|
});
|
|
12316
12479
|
}
|
|
12480
|
+
async function openWhenUp(port) {
|
|
12481
|
+
const until = Date.now() + 12e4;
|
|
12482
|
+
while (Date.now() < until) {
|
|
12483
|
+
try {
|
|
12484
|
+
const answered = await fetch(`http://localhost:${port}/api/v1/machine`);
|
|
12485
|
+
if (answered.ok) {
|
|
12486
|
+
openBrowser({ url: `http://localhost:${port}` });
|
|
12487
|
+
return;
|
|
12488
|
+
}
|
|
12489
|
+
} catch {
|
|
12490
|
+
}
|
|
12491
|
+
await new Promise((done) => setTimeout(done, 250));
|
|
12492
|
+
}
|
|
12493
|
+
}
|
|
12317
12494
|
|
|
12318
12495
|
// src/commands/work.ts
|
|
12319
12496
|
import { spawnSync as spawnSync3 } from "child_process";
|