@tablation/crew 0.120.0 → 0.121.0
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/cli.js +691 -617
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -183,10 +183,12 @@ function dropFileMtime(worktree) {
|
|
|
183
183
|
|
|
184
184
|
// src/intel-register.ts
|
|
185
185
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
186
|
-
import { existsSync as
|
|
187
|
-
import { dirname as dirname2, join as
|
|
186
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
187
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
188
188
|
|
|
189
189
|
// src/intel.ts
|
|
190
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
191
|
+
import { join as join2 } from "path";
|
|
190
192
|
var INTEL_FLAVORS = {
|
|
191
193
|
serena: {
|
|
192
194
|
summary: "crew runs a Serena symbol service per worktree, seeded from the main checkout's index",
|
|
@@ -197,11 +199,65 @@ var INTEL_FLAVORS = {
|
|
|
197
199
|
implemented: true
|
|
198
200
|
},
|
|
199
201
|
"typescript-lsp": {
|
|
200
|
-
summary: "
|
|
202
|
+
summary: "the Claude harness's own LSP tool serves the repository through a crew-generated language-server plugin (Claude only)",
|
|
203
|
+
// Checked against the main checkout's node_modules/.bin, then PATH, by `lspBinaryFound`.
|
|
201
204
|
prerequisites: [],
|
|
202
|
-
implemented:
|
|
205
|
+
implemented: true
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
var LSP_SERVERS = {
|
|
209
|
+
"typescript-lsp": {
|
|
210
|
+
bin: "typescript-language-server",
|
|
211
|
+
args: ["--stdio"],
|
|
212
|
+
install: "npm install -D typescript-language-server typescript",
|
|
213
|
+
extensionToLanguage: {
|
|
214
|
+
".ts": "typescript",
|
|
215
|
+
".tsx": "typescriptreact",
|
|
216
|
+
".js": "javascript",
|
|
217
|
+
".jsx": "javascriptreact",
|
|
218
|
+
".mts": "typescript",
|
|
219
|
+
".cts": "typescript",
|
|
220
|
+
".mjs": "javascript",
|
|
221
|
+
".cjs": "javascript"
|
|
222
|
+
}
|
|
203
223
|
}
|
|
204
224
|
};
|
|
225
|
+
function lspFlavor(script) {
|
|
226
|
+
const f = intelFlavor(script);
|
|
227
|
+
return f && Object.prototype.hasOwnProperty.call(LSP_SERVERS, f) ? f : void 0;
|
|
228
|
+
}
|
|
229
|
+
var lspLocalBin = (mainCheckout, flavor) => join2(mainCheckout, "node_modules", ".bin", LSP_SERVERS[flavor].bin);
|
|
230
|
+
function lspBinaryFound(flavor, mainCheckout, onPath2) {
|
|
231
|
+
return existsSync3(lspLocalBin(mainCheckout, flavor)) || onPath2(LSP_SERVERS[flavor].bin);
|
|
232
|
+
}
|
|
233
|
+
function ensureLspPlugin(stateDir, flavor, mainCheckout) {
|
|
234
|
+
const def = LSP_SERVERS[flavor];
|
|
235
|
+
const dir = join2(stateDir, "intel", "plugins", flavor);
|
|
236
|
+
const command2 = existsSync3(lspLocalBin(mainCheckout, flavor)) ? `\${CLAUDE_PROJECT_DIR}/node_modules/.bin/${def.bin}` : def.bin;
|
|
237
|
+
const files = [
|
|
238
|
+
[join2(".claude-plugin", "plugin.json"), { name: `crew-${flavor}`, version: "1.0.0", description: `Language server for the ${flavor} intel flavor, generated by crew` }],
|
|
239
|
+
[".lsp.json", { [def.bin]: { command: command2, args: [...def.args], extensionToLanguage: def.extensionToLanguage } }]
|
|
240
|
+
];
|
|
241
|
+
for (const [rel, doc] of files) {
|
|
242
|
+
const path = join2(dir, rel);
|
|
243
|
+
const text2 = `${JSON.stringify(doc, null, 2)}
|
|
244
|
+
`;
|
|
245
|
+
let old;
|
|
246
|
+
try {
|
|
247
|
+
old = readFileSync2(path, "utf8");
|
|
248
|
+
} catch {
|
|
249
|
+
}
|
|
250
|
+
if (old === text2) continue;
|
|
251
|
+
mkdirSync2(join2(path, ".."), { recursive: true });
|
|
252
|
+
writeFileSync2(path, text2);
|
|
253
|
+
}
|
|
254
|
+
return dir;
|
|
255
|
+
}
|
|
256
|
+
function lspToolsParagraph(harness, omlx = false) {
|
|
257
|
+
if (harness !== "claude") return "No symbol service is available for this harness: this repository's `intel` flavor serves only the Claude Code harness's own LSP tool.";
|
|
258
|
+
if (omlx) return "The harness's LSP tool is off for this launcher, so no symbol service is available for this run.";
|
|
259
|
+
return "The harness's `LSP` tool is active for this repository's TypeScript and JavaScript files (.ts .tsx .js .jsx .mts .cts .mjs .cjs). It offers definition, references, hover, file symbols, workspace symbol search, implementations and call hierarchy, read-only; diagnostics arrive after each edit.";
|
|
260
|
+
}
|
|
205
261
|
function intelFlavor(script) {
|
|
206
262
|
const w = script?.trim();
|
|
207
263
|
return w && Object.prototype.hasOwnProperty.call(INTEL_FLAVORS, w) ? w : void 0;
|
|
@@ -330,11 +386,11 @@ function registerCodeServer(worktree, url, server = INTEL_SERVER_NAME) {
|
|
|
330
386
|
[".gemini/settings.json", (old) => mergeJsonServer(old, { httpUrl: url }, server)]
|
|
331
387
|
];
|
|
332
388
|
for (const [rel, merge] of plans) {
|
|
333
|
-
const file =
|
|
389
|
+
const file = join3(worktree, rel);
|
|
334
390
|
try {
|
|
335
|
-
const next = merge(
|
|
336
|
-
|
|
337
|
-
|
|
391
|
+
const next = merge(existsSync4(file) ? readFileSync3(file, "utf8") : void 0);
|
|
392
|
+
mkdirSync3(dirname2(file), { recursive: true });
|
|
393
|
+
writeFileSync3(file, next);
|
|
338
394
|
if (tracked(worktree, rel)) execFileSync2("git", ["update-index", "--skip-worktree", "--", rel], { cwd: worktree, stdio: "ignore" });
|
|
339
395
|
else excludeFromGit(worktree, rel);
|
|
340
396
|
out.written.push(rel);
|
|
@@ -347,13 +403,13 @@ function registerCodeServer(worktree, url, server = INTEL_SERVER_NAME) {
|
|
|
347
403
|
|
|
348
404
|
// src/intel-serena.ts
|
|
349
405
|
import { spawn, spawnSync } from "child_process";
|
|
350
|
-
import { closeSync, cpSync, existsSync as
|
|
351
|
-
import { dirname as dirname3, join as
|
|
406
|
+
import { closeSync, cpSync, existsSync as existsSync6, mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync4 } from "fs";
|
|
407
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
352
408
|
|
|
353
409
|
// src/ports.ts
|
|
354
410
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
355
|
-
import { existsSync as
|
|
356
|
-
import { join as
|
|
411
|
+
import { existsSync as existsSync5 } from "fs";
|
|
412
|
+
import { join as join4 } from "path";
|
|
357
413
|
var run = (cmd, args) => {
|
|
358
414
|
try {
|
|
359
415
|
return execFileSync3(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -402,7 +458,7 @@ var intelPortFor = (n, s = DEFAULT_PORTS) => (s.intelBase ?? DEFAULT_PORTS.intel
|
|
|
402
458
|
var proxyPortFor = (n, s = DEFAULT_PORTS) => (s.proxyBase ?? DEFAULT_PORTS.proxyBase) + n;
|
|
403
459
|
function worktreeExists(parent, prefix, n) {
|
|
404
460
|
for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
|
|
405
|
-
if (
|
|
461
|
+
if (existsSync5(join4(parent, name, ".git"))) return true;
|
|
406
462
|
}
|
|
407
463
|
return false;
|
|
408
464
|
}
|
|
@@ -446,10 +502,10 @@ function pidsInWorktree(path, n, s = DEFAULT_PORTS) {
|
|
|
446
502
|
|
|
447
503
|
// src/intel-serena.ts
|
|
448
504
|
var serenaPaths = (stateDir, route2, ticket) => ({
|
|
449
|
-
home:
|
|
450
|
-
mainHome:
|
|
451
|
-
mainProject:
|
|
452
|
-
project:
|
|
505
|
+
home: join5(stateDir, "intel", route2, ticket, "home"),
|
|
506
|
+
mainHome: join5(stateDir, "intel", route2, "main", "home"),
|
|
507
|
+
mainProject: join5(stateDir, "intel", route2, "main"),
|
|
508
|
+
project: join5(stateDir, "intel", route2, ticket)
|
|
453
509
|
});
|
|
454
510
|
var serenaFolderFor = (stateDir, route2, ticket) => serenaPaths(stateDir, route2, ticket).project;
|
|
455
511
|
function serenaConfigYaml(projectFolder) {
|
|
@@ -493,20 +549,20 @@ function missingPrerequisites(flavor, onPath2) {
|
|
|
493
549
|
function prepareFolder(d, home, folder, checkout) {
|
|
494
550
|
d.mkdir(home);
|
|
495
551
|
d.mkdir(folder);
|
|
496
|
-
d.write(
|
|
497
|
-
d.write(
|
|
552
|
+
d.write(join5(home, "serena_config.yml"), serenaConfigYaml(folder));
|
|
553
|
+
d.write(join5(folder, "project.yml"), serenaProjectYaml(d.read(join5(checkout, ".serena", "project.yml"))));
|
|
498
554
|
}
|
|
499
555
|
function ensureMainIndex(d, s) {
|
|
500
556
|
const p = serenaPaths(s.stateDir, s.route, String(s.ticket));
|
|
501
557
|
const head = d.headOf(s.mainCheckout);
|
|
502
558
|
if (!head) return "unavailable";
|
|
503
|
-
const stamp =
|
|
559
|
+
const stamp = join5(p.mainProject, "indexed-commit");
|
|
504
560
|
if (d.read(stamp)?.trim() === head) return "fresh";
|
|
505
|
-
const pidFile =
|
|
561
|
+
const pidFile = join5(p.mainProject, "indexing.pid");
|
|
506
562
|
const running = Number(d.read(pidFile));
|
|
507
563
|
if (running > 0 && d.alive(running)) return "running";
|
|
508
564
|
prepareFolder(d, p.mainHome, p.mainProject, s.mainCheckout);
|
|
509
|
-
const pid = d.spawnIndex(serenaIndexArgs(s.mainCheckout), { SERENA_HOME: p.mainHome },
|
|
565
|
+
const pid = d.spawnIndex(serenaIndexArgs(s.mainCheckout), { SERENA_HOME: p.mainHome }, join5(p.mainProject, "index.log"), stamp, head);
|
|
510
566
|
d.write(pidFile, String(pid));
|
|
511
567
|
return "started";
|
|
512
568
|
}
|
|
@@ -516,15 +572,15 @@ function startSerena(d, s) {
|
|
|
516
572
|
const p = serenaPaths(s.stateDir, s.route, String(s.ticket));
|
|
517
573
|
try {
|
|
518
574
|
prepareFolder(d, p.home, p.project, s.worktree);
|
|
519
|
-
const mainCache =
|
|
520
|
-
const cache =
|
|
575
|
+
const mainCache = join5(p.mainProject, "cache");
|
|
576
|
+
const cache = join5(p.project, "cache");
|
|
521
577
|
if (!d.exists(cache) && d.exists(mainCache)) d.copyDir(mainCache, cache);
|
|
522
578
|
try {
|
|
523
579
|
ensureMainIndex(d, s);
|
|
524
580
|
} catch {
|
|
525
581
|
}
|
|
526
582
|
const port = intelPortFor(s.ticket);
|
|
527
|
-
d.spawnDetached("serena", serenaServeArgs(port, s.worktree), { SERENA_HOME: p.home },
|
|
583
|
+
d.spawnDetached("serena", serenaServeArgs(port, s.worktree), { SERENA_HOME: p.home }, join5(p.project, "serena.log"));
|
|
528
584
|
return { code: 0, output: `http://127.0.0.1:${port}/mcp
|
|
529
585
|
` };
|
|
530
586
|
} catch (e) {
|
|
@@ -533,7 +589,7 @@ function startSerena(d, s) {
|
|
|
533
589
|
}
|
|
534
590
|
function realSerenaDeps(onPath2, extraEnv = process.env) {
|
|
535
591
|
const detached = (bin, args, env, log) => {
|
|
536
|
-
|
|
592
|
+
mkdirSync4(dirname3(log), { recursive: true });
|
|
537
593
|
const fd = openSync(log, "a");
|
|
538
594
|
const child = spawn(bin, args, { detached: true, stdio: ["ignore", fd, fd], env: { ...extraEnv, ...env } });
|
|
539
595
|
child.unref();
|
|
@@ -543,20 +599,20 @@ function realSerenaDeps(onPath2, extraEnv = process.env) {
|
|
|
543
599
|
};
|
|
544
600
|
return {
|
|
545
601
|
onPath: onPath2,
|
|
546
|
-
exists:
|
|
602
|
+
exists: existsSync6,
|
|
547
603
|
mkdir: (p) => {
|
|
548
|
-
|
|
604
|
+
mkdirSync4(p, { recursive: true });
|
|
549
605
|
},
|
|
550
606
|
read: (p) => {
|
|
551
607
|
try {
|
|
552
|
-
return
|
|
608
|
+
return readFileSync4(p, "utf8");
|
|
553
609
|
} catch {
|
|
554
610
|
return void 0;
|
|
555
611
|
}
|
|
556
612
|
},
|
|
557
613
|
write: (p, text2) => {
|
|
558
|
-
|
|
559
|
-
|
|
614
|
+
mkdirSync4(dirname3(p), { recursive: true });
|
|
615
|
+
writeFileSync4(p, text2);
|
|
560
616
|
},
|
|
561
617
|
copyDir: (from, to) => cpSync(from, to, { recursive: true }),
|
|
562
618
|
headOf: (checkout) => {
|
|
@@ -582,14 +638,14 @@ function dropSerenaFolder(stateDir, route2, ticket) {
|
|
|
582
638
|
}
|
|
583
639
|
|
|
584
640
|
// src/periscope-state.ts
|
|
585
|
-
import { existsSync as
|
|
641
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readdirSync, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
586
642
|
import { connect } from "net";
|
|
587
|
-
import { join as
|
|
588
|
-
var dirOf = (stateDir) =>
|
|
589
|
-
var fileOf = (stateDir, ticketRecordId) =>
|
|
643
|
+
import { join as join6 } from "path";
|
|
644
|
+
var dirOf = (stateDir) => join6(stateDir, "periscopes");
|
|
645
|
+
var fileOf = (stateDir, ticketRecordId) => join6(dirOf(stateDir), `${ticketRecordId}.json`);
|
|
590
646
|
function writePeriscopeSidecar(stateDir, sc) {
|
|
591
|
-
|
|
592
|
-
|
|
647
|
+
mkdirSync5(dirOf(stateDir), { recursive: true });
|
|
648
|
+
writeFileSync5(fileOf(stateDir, sc.ticketRecordId), `${JSON.stringify(sc, null, 2)}
|
|
593
649
|
`);
|
|
594
650
|
}
|
|
595
651
|
function removePeriscopeSidecar(stateDir, ticketRecordId) {
|
|
@@ -597,12 +653,12 @@ function removePeriscopeSidecar(stateDir, ticketRecordId) {
|
|
|
597
653
|
}
|
|
598
654
|
function readPeriscopeSidecars(stateDir) {
|
|
599
655
|
const dir = dirOf(stateDir);
|
|
600
|
-
if (!
|
|
656
|
+
if (!existsSync7(dir)) return [];
|
|
601
657
|
const out = [];
|
|
602
658
|
for (const name of readdirSync(dir)) {
|
|
603
659
|
if (!name.endsWith(".json")) continue;
|
|
604
660
|
try {
|
|
605
|
-
const sc = JSON.parse(
|
|
661
|
+
const sc = JSON.parse(readFileSync5(join6(dir, name), "utf8"));
|
|
606
662
|
if (sc && typeof sc.ticketRecordId === "string" && typeof sc.url === "string") out.push(sc);
|
|
607
663
|
} catch {
|
|
608
664
|
}
|
|
@@ -720,23 +776,23 @@ function shellInitScript(shell) {
|
|
|
720
776
|
}
|
|
721
777
|
|
|
722
778
|
// src/config.ts
|
|
723
|
-
import { readFileSync as
|
|
724
|
-
import { resolve as resolve2, dirname as dirname7, isAbsolute as isAbsolute2, join as
|
|
779
|
+
import { readFileSync as readFileSync15, existsSync as existsSync15 } from "fs";
|
|
780
|
+
import { resolve as resolve2, dirname as dirname7, isAbsolute as isAbsolute2, join as join17, basename as basename4 } from "path";
|
|
725
781
|
import { homedir as homedir6, tmpdir } from "os";
|
|
726
782
|
import { parse as parse2 } from "yaml";
|
|
727
783
|
|
|
728
784
|
// src/events.ts
|
|
729
785
|
import {
|
|
730
786
|
appendFileSync as appendFileSync2,
|
|
731
|
-
mkdirSync as
|
|
732
|
-
existsSync as
|
|
787
|
+
mkdirSync as mkdirSync6,
|
|
788
|
+
existsSync as existsSync8,
|
|
733
789
|
statSync as statSync2,
|
|
734
790
|
renameSync,
|
|
735
791
|
openSync as openSync2,
|
|
736
792
|
closeSync as closeSync2,
|
|
737
793
|
rmSync as rmSync3
|
|
738
794
|
} from "fs";
|
|
739
|
-
import { join as
|
|
795
|
+
import { join as join7, dirname as dirname4 } from "path";
|
|
740
796
|
function render(e) {
|
|
741
797
|
const scope = e.role ? `${e.step}[${e.role}]` : e.step;
|
|
742
798
|
const ticket = e.ticket ? ` ${e.ticket}` : "";
|
|
@@ -748,7 +804,7 @@ var ROTATED_EVENTS_RE = /^events\.\d{8}T\d{6}Z(?:-\d+)?\.jsonl$/;
|
|
|
748
804
|
var ROTATE_LOCK_STALE_MS = 6e4;
|
|
749
805
|
function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
|
|
750
806
|
try {
|
|
751
|
-
if (!
|
|
807
|
+
if (!existsSync8(file) || statSync2(file).size <= maxBytes) return void 0;
|
|
752
808
|
} catch {
|
|
753
809
|
return void 0;
|
|
754
810
|
}
|
|
@@ -768,8 +824,8 @@ function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
|
|
|
768
824
|
try {
|
|
769
825
|
if (statSync2(file).size <= maxBytes) return void 0;
|
|
770
826
|
const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
|
|
771
|
-
let dest =
|
|
772
|
-
for (let n = 1;
|
|
827
|
+
let dest = join7(dirname4(file), `events.${stamp}.jsonl`);
|
|
828
|
+
for (let n = 1; existsSync8(dest); n++) dest = join7(dirname4(file), `events.${stamp}-${n}.jsonl`);
|
|
773
829
|
renameSync(file, dest);
|
|
774
830
|
return dest;
|
|
775
831
|
} finally {
|
|
@@ -792,7 +848,7 @@ var Emitter = class _Emitter {
|
|
|
792
848
|
for (const f of [opts.eventFile, opts.logFile]) {
|
|
793
849
|
if (!f) continue;
|
|
794
850
|
try {
|
|
795
|
-
|
|
851
|
+
mkdirSync6(dirname4(f), { recursive: true });
|
|
796
852
|
} catch {
|
|
797
853
|
}
|
|
798
854
|
}
|
|
@@ -868,18 +924,18 @@ var Emitter = class _Emitter {
|
|
|
868
924
|
}
|
|
869
925
|
}
|
|
870
926
|
};
|
|
871
|
-
var eventFileFor = (stateDir) =>
|
|
927
|
+
var eventFileFor = (stateDir) => join7(stateDir, "events.jsonl");
|
|
872
928
|
|
|
873
929
|
// src/pair-install.ts
|
|
874
|
-
import { existsSync as
|
|
930
|
+
import { existsSync as existsSync11, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
875
931
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
876
932
|
import { homedir as homedir5 } from "os";
|
|
877
|
-
import { join as
|
|
933
|
+
import { join as join13 } from "path";
|
|
878
934
|
|
|
879
935
|
// src/adapters/claude.ts
|
|
880
|
-
import { readFileSync as
|
|
936
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
881
937
|
import { homedir as homedir3 } from "os";
|
|
882
|
-
import { join as
|
|
938
|
+
import { join as join11 } from "path";
|
|
883
939
|
|
|
884
940
|
// src/stream.ts
|
|
885
941
|
function toolTarget(name, input) {
|
|
@@ -938,19 +994,19 @@ function extractResult(line) {
|
|
|
938
994
|
|
|
939
995
|
// src/claude-mcp.ts
|
|
940
996
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
941
|
-
import { readFileSync as
|
|
997
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
942
998
|
import { homedir } from "os";
|
|
943
|
-
import { join as
|
|
999
|
+
import { join as join9 } from "path";
|
|
944
1000
|
|
|
945
1001
|
// src/mcp-registrations.ts
|
|
946
1002
|
import { createHash } from "crypto";
|
|
947
|
-
import { mkdirSync as
|
|
948
|
-
import { join as
|
|
949
|
-
var fileFor = (stateDir) =>
|
|
1003
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
1004
|
+
import { join as join8 } from "path";
|
|
1005
|
+
var fileFor = (stateDir) => join8(stateDir, "mcp-registrations.json");
|
|
950
1006
|
var keyFingerprint = (key) => createHash("sha256").update(key).digest("hex").slice(0, 8);
|
|
951
1007
|
function readMcpRegistrations(stateDir) {
|
|
952
1008
|
try {
|
|
953
|
-
const j = JSON.parse(
|
|
1009
|
+
const j = JSON.parse(readFileSync7(fileFor(stateDir), "utf8"));
|
|
954
1010
|
return Array.isArray(j) ? j : [];
|
|
955
1011
|
} catch {
|
|
956
1012
|
return [];
|
|
@@ -958,8 +1014,8 @@ function readMcpRegistrations(stateDir) {
|
|
|
958
1014
|
}
|
|
959
1015
|
function recordMcpRegistration(stateDir, r, now = /* @__PURE__ */ new Date()) {
|
|
960
1016
|
const rest = readMcpRegistrations(stateDir).filter((x) => !(x.harness === r.harness && x.scope === r.scope && x.name === r.name));
|
|
961
|
-
|
|
962
|
-
|
|
1017
|
+
mkdirSync7(stateDir, { recursive: true });
|
|
1018
|
+
writeFileSync6(fileFor(stateDir), `${JSON.stringify([...rest, { ...r, at: now.toISOString() }], null, 2)}
|
|
963
1019
|
`, { mode: 384 });
|
|
964
1020
|
}
|
|
965
1021
|
function findMcpRegistration(stateDir, r) {
|
|
@@ -982,7 +1038,7 @@ function runClaude(bin, args) {
|
|
|
982
1038
|
}
|
|
983
1039
|
function userScopeEntry(home) {
|
|
984
1040
|
try {
|
|
985
|
-
const j = JSON.parse(
|
|
1041
|
+
const j = JSON.parse(readFileSync8(join9(home, ".claude.json"), "utf8"));
|
|
986
1042
|
return j.mcpServers?.[MCP_SERVER_NAME];
|
|
987
1043
|
} catch {
|
|
988
1044
|
return void 0;
|
|
@@ -1066,8 +1122,8 @@ function removeClaudeMcp(o) {
|
|
|
1066
1122
|
}
|
|
1067
1123
|
|
|
1068
1124
|
// src/launcher.ts
|
|
1069
|
-
import { basename, join as
|
|
1070
|
-
import { existsSync as
|
|
1125
|
+
import { basename, join as join10 } from "path";
|
|
1126
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9, readdirSync as readdirSync2 } from "fs";
|
|
1071
1127
|
import { homedir as homedir2 } from "os";
|
|
1072
1128
|
function isOriLauncher(spec) {
|
|
1073
1129
|
return !!spec.launcher && basename(spec.launcher) === "ori";
|
|
@@ -1076,7 +1132,7 @@ function isOmlxLauncher(spec) {
|
|
|
1076
1132
|
return !!spec.launcher && basename(spec.launcher) === "omlx";
|
|
1077
1133
|
}
|
|
1078
1134
|
function defaultOmlxBin(home = homedir2()) {
|
|
1079
|
-
return
|
|
1135
|
+
return join10(home, ".omlx", "bin", "omlx");
|
|
1080
1136
|
}
|
|
1081
1137
|
function launcherExecutable(spec, home) {
|
|
1082
1138
|
if (!spec.launcher) return void 0;
|
|
@@ -1129,8 +1185,8 @@ function omlxModelPresent(model, dirs) {
|
|
|
1129
1185
|
});
|
|
1130
1186
|
}
|
|
1131
1187
|
function readOmlxSettings(home = homedir2()) {
|
|
1132
|
-
const p =
|
|
1133
|
-
return
|
|
1188
|
+
const p = join10(home, ".omlx", "settings.json");
|
|
1189
|
+
return existsSync9(p) ? parseOmlxSettings(readFileSync9(p, "utf8")) : void 0;
|
|
1134
1190
|
}
|
|
1135
1191
|
|
|
1136
1192
|
// src/capability.ts
|
|
@@ -1194,7 +1250,7 @@ function describeClaudeAuth(spec, env = process.env, home = homedir3()) {
|
|
|
1194
1250
|
if (spec.auth === "workspace") return `workspace${spec.inference ? ` (${new URL(spec.inference.baseUrl).host})` : " (endpoint resolved per route at run time)"}`;
|
|
1195
1251
|
let fromSettings;
|
|
1196
1252
|
try {
|
|
1197
|
-
const parsed = JSON.parse(
|
|
1253
|
+
const parsed = JSON.parse(readFileSync10(join11(home, ".claude", "settings.json"), "utf8"));
|
|
1198
1254
|
const e = parsed.env ?? {};
|
|
1199
1255
|
const on = (k) => e[k] !== void 0 && e[k] !== "" && e[k] !== "0";
|
|
1200
1256
|
if (on("CLAUDE_CODE_USE_VERTEX")) fromSettings = "vertex from ~/.claude/settings.json";
|
|
@@ -1262,6 +1318,9 @@ var claudeAdapter = {
|
|
|
1262
1318
|
}
|
|
1263
1319
|
}
|
|
1264
1320
|
})] : [],
|
|
1321
|
+
// A crew-generated language-server plugin (the `typescript-lsp` intel flavor). Not under oMLX,
|
|
1322
|
+
// which denies the LSP tool above.
|
|
1323
|
+
...isOmlxLauncher(spec) ? [] : (o.pluginDirs ?? []).flatMap((d) => ["--plugin-dir", d]),
|
|
1265
1324
|
...spec.auth === "subscription" ? ["--settings", subscriptionSettings()] : [],
|
|
1266
1325
|
...spec.auth === "workspace" && spec.inference ? ["--settings", workspaceSettings(spec.inference.baseUrl)] : [],
|
|
1267
1326
|
...spec.args
|
|
@@ -1298,11 +1357,11 @@ var claudeAdapter = {
|
|
|
1298
1357
|
// src/adapters/codex.ts
|
|
1299
1358
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
1300
1359
|
import { homedir as homedir4 } from "os";
|
|
1301
|
-
import { join as
|
|
1302
|
-
import { rmSync as rmSync5, writeFileSync as
|
|
1360
|
+
import { join as join12 } from "path";
|
|
1361
|
+
import { rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
1303
1362
|
|
|
1304
1363
|
// src/pair-files.ts
|
|
1305
|
-
import { existsSync as
|
|
1364
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync7, rmSync as rmSync4 } from "fs";
|
|
1306
1365
|
import { dirname as dirname5 } from "path";
|
|
1307
1366
|
var PAIR_BLOCK_START = "<!-- crew:pair start -->";
|
|
1308
1367
|
var PAIR_BLOCK_END = "<!-- crew:pair end -->";
|
|
@@ -1343,11 +1402,11 @@ function removePairBlock(text2) {
|
|
|
1343
1402
|
return text2.replace(new RegExp(`\\n*${BLOCK_RE.source}`), "\n").replace(/^\n+/, "").replace(/\n{3,}/g, "\n\n");
|
|
1344
1403
|
}
|
|
1345
1404
|
function readIfExists(path) {
|
|
1346
|
-
return
|
|
1405
|
+
return existsSync10(path) ? readFileSync11(path, "utf8") : void 0;
|
|
1347
1406
|
}
|
|
1348
1407
|
function writeFileMkdir(path, text2) {
|
|
1349
|
-
|
|
1350
|
-
|
|
1408
|
+
mkdirSync8(dirname5(path), { recursive: true });
|
|
1409
|
+
writeFileSync7(path, text2);
|
|
1351
1410
|
}
|
|
1352
1411
|
function installBlockFile(path, harness, dryRun) {
|
|
1353
1412
|
const before = readIfExists(path) ?? "";
|
|
@@ -1363,7 +1422,7 @@ function removeBlockFile(path, dryRun) {
|
|
|
1363
1422
|
if (dryRun) return true;
|
|
1364
1423
|
const after = removePairBlock(before);
|
|
1365
1424
|
if (after.trim() === "") rmSync4(path);
|
|
1366
|
-
else
|
|
1425
|
+
else writeFileSync7(path, after);
|
|
1367
1426
|
return true;
|
|
1368
1427
|
}
|
|
1369
1428
|
function blockVerdict(path, harness) {
|
|
@@ -1490,8 +1549,8 @@ function codexTable(url) {
|
|
|
1490
1549
|
return [`[mcp_servers.${SERVER}]`, `url = ${tomlString(url)}`, `bearer_token_env_var = ${tomlString(PAIR_KEY_VAR)}`];
|
|
1491
1550
|
}
|
|
1492
1551
|
var codexPaths = (home) => ({
|
|
1493
|
-
instructions:
|
|
1494
|
-
config:
|
|
1552
|
+
instructions: join12(home, ".codex", "AGENTS.md"),
|
|
1553
|
+
config: join12(home, ".codex", "config.toml")
|
|
1495
1554
|
});
|
|
1496
1555
|
function codexMcpState(config, url) {
|
|
1497
1556
|
const lines2 = config.split("\n");
|
|
@@ -1542,7 +1601,7 @@ var codexPair = {
|
|
|
1542
1601
|
if (hadMcp && !o.dryRun) {
|
|
1543
1602
|
const after = removeTomlTable(config, `mcp_servers.${SERVER}`);
|
|
1544
1603
|
if (after === "") rmSync5(p.config);
|
|
1545
|
-
else
|
|
1604
|
+
else writeFileSync8(p.config, after);
|
|
1546
1605
|
}
|
|
1547
1606
|
const what = hadBlock && hadMcp ? `the Pair block and ${SERVER} server` : hadBlock ? "the Pair block" : `the ${SERVER} server`;
|
|
1548
1607
|
return hadBlock || hadMcp ? `pair(codex): ${o.dryRun ? "(dry run) would remove" : "removed"} ${what}${kept}` : `pair(codex): nothing to remove${kept}`;
|
|
@@ -1638,8 +1697,8 @@ var codexAdapter = {
|
|
|
1638
1697
|
|
|
1639
1698
|
// src/pair-install.ts
|
|
1640
1699
|
var geminiPaths = (home) => ({
|
|
1641
|
-
instructions:
|
|
1642
|
-
config:
|
|
1700
|
+
instructions: join13(home, ".gemini", "GEMINI.md"),
|
|
1701
|
+
config: join13(home, ".gemini", "settings.json")
|
|
1643
1702
|
});
|
|
1644
1703
|
function readGeminiSettings(path) {
|
|
1645
1704
|
const text2 = readIfExists(path);
|
|
@@ -1727,7 +1786,7 @@ var geminiPair = {
|
|
|
1727
1786
|
if (Object.keys(rest).length > 0) next.mcpServers = rest;
|
|
1728
1787
|
else delete next.mcpServers;
|
|
1729
1788
|
if (Object.keys(next).length === 0) rmSync6(p.config);
|
|
1730
|
-
else
|
|
1789
|
+
else writeFileSync9(p.config, `${JSON.stringify(next, null, 2)}
|
|
1731
1790
|
`);
|
|
1732
1791
|
}
|
|
1733
1792
|
const what = hadBlock && hadMcp ? `the Pair block and ${SERVER} server` : hadBlock ? "the Pair block" : `the ${SERVER} server`;
|
|
@@ -1738,9 +1797,9 @@ var VSCODE_FLAVOURS = ["Code", "Code - Insiders"];
|
|
|
1738
1797
|
function vscodeUserDirs(o) {
|
|
1739
1798
|
const home = o.home ?? homedir5();
|
|
1740
1799
|
const platform2 = o.platform ?? process.platform;
|
|
1741
|
-
const root = platform2 === "darwin" ?
|
|
1742
|
-
const all = VSCODE_FLAVOURS.map((f) =>
|
|
1743
|
-
const present = all.filter((d) =>
|
|
1800
|
+
const root = platform2 === "darwin" ? join13(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join13(home, "AppData", "Roaming") : join13(home, ".config");
|
|
1801
|
+
const all = VSCODE_FLAVOURS.map((f) => join13(root, f, "User"));
|
|
1802
|
+
const present = all.filter((d) => existsSync11(d));
|
|
1744
1803
|
return present.length > 0 ? present : [all[0]];
|
|
1745
1804
|
}
|
|
1746
1805
|
var COPILOT_FRONTMATTER = '---\napplyTo: "**"\n---\n\n';
|
|
@@ -1778,7 +1837,7 @@ function removeCopilotInstructions(path, dryRun) {
|
|
|
1778
1837
|
if (dryRun) return true;
|
|
1779
1838
|
const after = removePairBlock(before);
|
|
1780
1839
|
if (after.trim() === "" || after.trim() === COPILOT_FRONTMATTER.trim()) rmSync6(path);
|
|
1781
|
-
else
|
|
1840
|
+
else writeFileSync9(path, after);
|
|
1782
1841
|
return true;
|
|
1783
1842
|
}
|
|
1784
1843
|
var copilotPair = {
|
|
@@ -1787,8 +1846,8 @@ var copilotPair = {
|
|
|
1787
1846
|
installPair(o) {
|
|
1788
1847
|
const lines2 = [];
|
|
1789
1848
|
for (const dir of vscodeUserDirs(o)) {
|
|
1790
|
-
const mcpPath =
|
|
1791
|
-
const instrPath =
|
|
1849
|
+
const mcpPath = join13(dir, "mcp.json");
|
|
1850
|
+
const instrPath = join13(dir, "prompts", "crew-pair.instructions.md");
|
|
1792
1851
|
const settings = readVscodeMcp(mcpPath);
|
|
1793
1852
|
if (!settings) {
|
|
1794
1853
|
lines2.push(`pair(copilot): ${mcpPath} is not a plain JSON object (comments are not supported) \u2014 fix it by hand; nothing was written there`);
|
|
@@ -1816,8 +1875,8 @@ var copilotPair = {
|
|
|
1816
1875
|
},
|
|
1817
1876
|
verifyPair(o) {
|
|
1818
1877
|
const verdicts = vscodeUserDirs(o).map((dir) => {
|
|
1819
|
-
const instrPath =
|
|
1820
|
-
const mcpPath =
|
|
1878
|
+
const instrPath = join13(dir, "prompts", "crew-pair.instructions.md");
|
|
1879
|
+
const mcpPath = join13(dir, "mcp.json");
|
|
1821
1880
|
const b = blockVerdict(instrPath, "copilot");
|
|
1822
1881
|
if (!b.ok) return b;
|
|
1823
1882
|
const settings = readVscodeMcp(mcpPath);
|
|
@@ -1832,8 +1891,8 @@ var copilotPair = {
|
|
|
1832
1891
|
removePair(o) {
|
|
1833
1892
|
const lines2 = [];
|
|
1834
1893
|
for (const dir of vscodeUserDirs(o)) {
|
|
1835
|
-
const mcpPath =
|
|
1836
|
-
const hadBlock = removeCopilotInstructions(
|
|
1894
|
+
const mcpPath = join13(dir, "mcp.json");
|
|
1895
|
+
const hadBlock = removeCopilotInstructions(join13(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
|
|
1837
1896
|
const settings = readVscodeMcp(mcpPath);
|
|
1838
1897
|
const st = settings ? copilotMcpState(settings, o.url) : { state: "missing" };
|
|
1839
1898
|
const hadMcp = st.state === "registered";
|
|
@@ -1844,7 +1903,7 @@ var copilotPair = {
|
|
|
1844
1903
|
if (Object.keys(rest).length > 0) next.servers = rest;
|
|
1845
1904
|
else delete next.servers;
|
|
1846
1905
|
if (Object.keys(next).length === 0) rmSync6(mcpPath);
|
|
1847
|
-
else
|
|
1906
|
+
else writeFileSync9(mcpPath, `${JSON.stringify(next, null, 2)}
|
|
1848
1907
|
`);
|
|
1849
1908
|
}
|
|
1850
1909
|
const what = hadBlock && hadMcp ? `the Pair instructions and ${SERVER} server` : hadBlock ? "the Pair instructions" : `the ${SERVER} server`;
|
|
@@ -2094,8 +2153,8 @@ function resolveSeat(ship, route2, role, persona = {}, repo) {
|
|
|
2094
2153
|
|
|
2095
2154
|
// src/forges.ts
|
|
2096
2155
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
2097
|
-
import { chmodSync, existsSync as
|
|
2098
|
-
import { dirname as dirname6, join as
|
|
2156
|
+
import { chmodSync, existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync12, rmSync as rmSync7, statSync as statSync3, writeFileSync as writeFileSync10 } from "fs";
|
|
2157
|
+
import { dirname as dirname6, join as join14 } from "path";
|
|
2099
2158
|
var ForgeError = class extends Error {
|
|
2100
2159
|
};
|
|
2101
2160
|
var DEFAULT_FORGE_USERNAME = "x-access-token";
|
|
@@ -2123,7 +2182,7 @@ function forgeDescriptorFor(host) {
|
|
|
2123
2182
|
}
|
|
2124
2183
|
var DEFAULT_KEYCHAIN_SERVICE = "crew-forge";
|
|
2125
2184
|
function defaultTokenFilePath(stateDir, host) {
|
|
2126
|
-
return
|
|
2185
|
+
return join14(stateDir, "forges", `${host.replace(/[^a-z0-9.-]/gi, "_")}.token`);
|
|
2127
2186
|
}
|
|
2128
2187
|
var envSource = {
|
|
2129
2188
|
read: (s, c) => c.env[s.var]?.trim() || null,
|
|
@@ -2132,19 +2191,19 @@ var envSource = {
|
|
|
2132
2191
|
var fileSource = {
|
|
2133
2192
|
read(s, c) {
|
|
2134
2193
|
const path = s.path ?? defaultTokenFilePath(c.stateDir, c.host);
|
|
2135
|
-
if (!
|
|
2194
|
+
if (!existsSync12(path)) return null;
|
|
2136
2195
|
const mode = statSync3(path).mode & 511;
|
|
2137
2196
|
if (mode & 63) {
|
|
2138
2197
|
throw new ForgeError(
|
|
2139
2198
|
`${path} is mode ${mode.toString(8).padStart(3, "0")}; a forge token file must be owner-only (chmod 600 ${path})`
|
|
2140
2199
|
);
|
|
2141
2200
|
}
|
|
2142
|
-
return
|
|
2201
|
+
return readFileSync12(path, "utf8").trim() || null;
|
|
2143
2202
|
},
|
|
2144
2203
|
write(s, token, c) {
|
|
2145
2204
|
const path = s.path ?? defaultTokenFilePath(c.stateDir, c.host);
|
|
2146
|
-
|
|
2147
|
-
|
|
2205
|
+
mkdirSync9(dirname6(path), { recursive: true, mode: 448 });
|
|
2206
|
+
writeFileSync10(path, `${token}
|
|
2148
2207
|
`, { mode: 384 });
|
|
2149
2208
|
chmodSync(path, 384);
|
|
2150
2209
|
},
|
|
@@ -2278,10 +2337,10 @@ function answerCredential(action, input, forges, stateDir, env = process.env) {
|
|
|
2278
2337
|
password=${token}
|
|
2279
2338
|
`;
|
|
2280
2339
|
}
|
|
2281
|
-
var missesPath = (stateDir) =>
|
|
2340
|
+
var missesPath = (stateDir) => join14(stateDir, "forge-misses.json");
|
|
2282
2341
|
function readForgeMisses(stateDir) {
|
|
2283
2342
|
try {
|
|
2284
|
-
const raw = JSON.parse(
|
|
2343
|
+
const raw = JSON.parse(readFileSync12(missesPath(stateDir), "utf8"));
|
|
2285
2344
|
return Object.entries(raw).filter(([, t]) => typeof t === "string").map(([host, lastMissAt]) => ({ host, lastMissAt })).sort((a, b) => a.host.localeCompare(b.host));
|
|
2286
2345
|
} catch {
|
|
2287
2346
|
return [];
|
|
@@ -2296,8 +2355,8 @@ function noteForgeMiss(stateDir, host, at) {
|
|
|
2296
2355
|
if (!(bare in cur)) return;
|
|
2297
2356
|
delete cur[bare];
|
|
2298
2357
|
} else cur[bare] = at;
|
|
2299
|
-
|
|
2300
|
-
|
|
2358
|
+
mkdirSync9(stateDir, { recursive: true });
|
|
2359
|
+
writeFileSync10(missesPath(stateDir), JSON.stringify(cur), { mode: 384 });
|
|
2301
2360
|
} catch {
|
|
2302
2361
|
}
|
|
2303
2362
|
}
|
|
@@ -2399,8 +2458,8 @@ import { hostFromUrl } from "@tablation/client";
|
|
|
2399
2458
|
|
|
2400
2459
|
// src/secret-store.ts
|
|
2401
2460
|
import { createHash as createHash2 } from "crypto";
|
|
2402
|
-
import { accessSync, constants, existsSync as
|
|
2403
|
-
import { join as
|
|
2461
|
+
import { accessSync, constants, existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync13, renameSync as renameSync2, rmSync as rmSync8, statSync as statSync4, writeFileSync as writeFileSync11, chmodSync as chmodSync2 } from "fs";
|
|
2462
|
+
import { join as join15 } from "path";
|
|
2404
2463
|
import { getSessionStore as getOsSessionStore } from "@tablation/client";
|
|
2405
2464
|
var SECRET_STORE_MODES = ["auto", "os", "file"];
|
|
2406
2465
|
function isSecretStoreMode(v) {
|
|
@@ -2411,7 +2470,7 @@ function configureSecretStore(c) {
|
|
|
2411
2470
|
configured = c;
|
|
2412
2471
|
}
|
|
2413
2472
|
function keysDir(stateDir) {
|
|
2414
|
-
return
|
|
2473
|
+
return join15(stateDir, "keys");
|
|
2415
2474
|
}
|
|
2416
2475
|
function entryName(host, slug2) {
|
|
2417
2476
|
return `${createHash2("sha256").update(`${host}\0${slug2}`).digest("hex")}.json`;
|
|
@@ -2422,34 +2481,34 @@ var FileSessionStore = class {
|
|
|
2422
2481
|
this.dir = dir;
|
|
2423
2482
|
}
|
|
2424
2483
|
ensureDir() {
|
|
2425
|
-
|
|
2484
|
+
mkdirSync10(this.dir, { recursive: true, mode: 448 });
|
|
2426
2485
|
if ((statSync4(this.dir).mode & 63) !== 0) chmodSync2(this.dir, 448);
|
|
2427
2486
|
}
|
|
2428
2487
|
async get(host, slug2) {
|
|
2429
|
-
const file =
|
|
2430
|
-
if (!
|
|
2488
|
+
const file = join15(this.dir, entryName(host, slug2));
|
|
2489
|
+
if (!existsSync13(this.dir)) return void 0;
|
|
2431
2490
|
accessSync(this.dir, constants.R_OK | constants.X_OK);
|
|
2432
|
-
if (!
|
|
2433
|
-
return JSON.parse(
|
|
2491
|
+
if (!existsSync13(file)) return void 0;
|
|
2492
|
+
return JSON.parse(readFileSync13(file, "utf8"));
|
|
2434
2493
|
}
|
|
2435
2494
|
async set(host, slug2, session) {
|
|
2436
2495
|
this.ensureDir();
|
|
2437
|
-
const file =
|
|
2496
|
+
const file = join15(this.dir, entryName(host, slug2));
|
|
2438
2497
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
2439
|
-
|
|
2498
|
+
writeFileSync11(tmp, JSON.stringify(session), { mode: 384 });
|
|
2440
2499
|
renameSync2(tmp, file);
|
|
2441
2500
|
}
|
|
2442
2501
|
async clear(host, slug2) {
|
|
2443
|
-
if (!
|
|
2444
|
-
rmSync8(
|
|
2502
|
+
if (!existsSync13(this.dir)) return;
|
|
2503
|
+
rmSync8(join15(this.dir, entryName(host, slug2)), { force: true });
|
|
2445
2504
|
}
|
|
2446
2505
|
/** Whether a key could be written here right now, without leaving anything behind. */
|
|
2447
2506
|
writable() {
|
|
2448
2507
|
try {
|
|
2449
2508
|
this.ensureDir();
|
|
2450
2509
|
accessSync(this.dir, constants.R_OK | constants.W_OK | constants.X_OK);
|
|
2451
|
-
const probe =
|
|
2452
|
-
|
|
2510
|
+
const probe = join15(this.dir, `.probe.${process.pid}`);
|
|
2511
|
+
writeFileSync11(probe, "", { mode: 384 });
|
|
2453
2512
|
rmSync8(probe, { force: true });
|
|
2454
2513
|
return { ok: true };
|
|
2455
2514
|
} catch (e) {
|
|
@@ -2500,11 +2559,11 @@ function osStoreReachable(env = process.env, platform2 = process.platform, hasBi
|
|
|
2500
2559
|
if (platform2 !== "linux") return true;
|
|
2501
2560
|
if (!hasBinary("secret-tool")) return false;
|
|
2502
2561
|
if (env.DBUS_SESSION_BUS_ADDRESS) return true;
|
|
2503
|
-
return !!env.XDG_RUNTIME_DIR &&
|
|
2562
|
+
return !!env.XDG_RUNTIME_DIR && existsSync13(join15(env.XDG_RUNTIME_DIR, "bus"));
|
|
2504
2563
|
}
|
|
2505
2564
|
function onPath(name) {
|
|
2506
2565
|
for (const dir of (process.env.PATH ?? "").split(":")) {
|
|
2507
|
-
if (dir &&
|
|
2566
|
+
if (dir && existsSync13(join15(dir, name))) return true;
|
|
2508
2567
|
}
|
|
2509
2568
|
return false;
|
|
2510
2569
|
}
|
|
@@ -2571,8 +2630,8 @@ function explain(ship, required) {
|
|
|
2571
2630
|
}
|
|
2572
2631
|
|
|
2573
2632
|
// src/repo-config.ts
|
|
2574
|
-
import { readFileSync as
|
|
2575
|
-
import { join as
|
|
2633
|
+
import { readFileSync as readFileSync14, existsSync as existsSync14 } from "fs";
|
|
2634
|
+
import { join as join16, basename as basename3 } from "path";
|
|
2576
2635
|
import { parse } from "yaml";
|
|
2577
2636
|
|
|
2578
2637
|
// src/sandbox-policy.ts
|
|
@@ -2856,8 +2915,8 @@ var MODES = ["local", "integrate", "ci_manual", "ci_auto", "external"];
|
|
|
2856
2915
|
var PROVIDERS = ["github", "buildkite", "other", "none"];
|
|
2857
2916
|
function findRepoConfig(dir) {
|
|
2858
2917
|
for (const name of REPO_CONFIG_FILENAMES) {
|
|
2859
|
-
const p =
|
|
2860
|
-
if (
|
|
2918
|
+
const p = join16(dir, name);
|
|
2919
|
+
if (existsSync14(p)) return p;
|
|
2861
2920
|
}
|
|
2862
2921
|
return null;
|
|
2863
2922
|
}
|
|
@@ -3143,7 +3202,7 @@ function parseRepoConfig(text2, file) {
|
|
|
3143
3202
|
function loadRepoConfig(dir) {
|
|
3144
3203
|
const file = findRepoConfig(dir);
|
|
3145
3204
|
if (!file) return null;
|
|
3146
|
-
return parseRepoConfig(
|
|
3205
|
+
return parseRepoConfig(readFileSync14(file, "utf8"), file);
|
|
3147
3206
|
}
|
|
3148
3207
|
function hookLabel(cfg, hook2) {
|
|
3149
3208
|
return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
|
|
@@ -3672,11 +3731,11 @@ function expand(p, base) {
|
|
|
3672
3731
|
return isAbsolute2(out) ? out : resolve2(base, out);
|
|
3673
3732
|
}
|
|
3674
3733
|
function resolvePromptsDir(promptSet, crewHome, base) {
|
|
3675
|
-
if (!promptSet) return
|
|
3734
|
+
if (!promptSet) return join17(crewHome, "prompts", "default");
|
|
3676
3735
|
if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
|
|
3677
3736
|
return expand(promptSet, base);
|
|
3678
3737
|
}
|
|
3679
|
-
return
|
|
3738
|
+
return join17(crewHome, "prompts", promptSet);
|
|
3680
3739
|
}
|
|
3681
3740
|
var Missing = class {
|
|
3682
3741
|
paths = [];
|
|
@@ -3700,7 +3759,7 @@ var Missing = class {
|
|
|
3700
3759
|
};
|
|
3701
3760
|
function crewVersion(crewHome) {
|
|
3702
3761
|
try {
|
|
3703
|
-
const pkg = JSON.parse(
|
|
3762
|
+
const pkg = JSON.parse(readFileSync15(join17(crewHome, "package.json"), "utf8"));
|
|
3704
3763
|
return pkg.version ?? "0.0.0";
|
|
3705
3764
|
} catch {
|
|
3706
3765
|
return "0.0.0";
|
|
@@ -3710,26 +3769,26 @@ function configSearchPath(crewHome) {
|
|
|
3710
3769
|
if (process.env.CREW_CONFIG) return [resolve2(process.env.CREW_CONFIG)];
|
|
3711
3770
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
3712
3771
|
return [
|
|
3713
|
-
...xdg ? [
|
|
3714
|
-
|
|
3772
|
+
...xdg ? [join17(xdg, "crew", "crew.yaml")] : [],
|
|
3773
|
+
join17(homedir6(), ".config", "crew", "crew.yaml"),
|
|
3715
3774
|
resolve2(crewHome, "crew.yaml")
|
|
3716
3775
|
];
|
|
3717
3776
|
}
|
|
3718
3777
|
function findConfigFile(crewHome) {
|
|
3719
3778
|
const candidates2 = configSearchPath(crewHome);
|
|
3720
|
-
return candidates2.find((c) =>
|
|
3779
|
+
return candidates2.find((c) => existsSync15(c)) ?? candidates2[candidates2.length - 1];
|
|
3721
3780
|
}
|
|
3722
|
-
var defaultConfigPath = () =>
|
|
3781
|
+
var defaultConfigPath = () => join17(process.env.XDG_CONFIG_HOME ?? join17(homedir6(), ".config"), "crew", "crew.yaml");
|
|
3723
3782
|
function routeSlug(route2) {
|
|
3724
3783
|
return route2.replace(/\//g, "-");
|
|
3725
3784
|
}
|
|
3726
3785
|
function resolvedPathFor(stateDir, route2) {
|
|
3727
3786
|
const [workspace, project] = splitRoute(route2);
|
|
3728
|
-
return
|
|
3787
|
+
return join17(stateDir, "resolved", workspace, `${project}.json`);
|
|
3729
3788
|
}
|
|
3730
3789
|
function apiKeyPathFor(stateDir, route2) {
|
|
3731
3790
|
const [workspace, project] = splitRoute(route2);
|
|
3732
|
-
return
|
|
3791
|
+
return join17(stateDir, "keys", workspace, `${project}.env`);
|
|
3733
3792
|
}
|
|
3734
3793
|
function splitRoute(route2) {
|
|
3735
3794
|
const parts = route2.split("/");
|
|
@@ -3740,9 +3799,9 @@ function splitRoute(route2) {
|
|
|
3740
3799
|
}
|
|
3741
3800
|
function readResolvedFile(stateDir, route2) {
|
|
3742
3801
|
const path = resolvedPathFor(stateDir, route2);
|
|
3743
|
-
if (!
|
|
3802
|
+
if (!existsSync15(path)) return void 0;
|
|
3744
3803
|
try {
|
|
3745
|
-
return JSON.parse(
|
|
3804
|
+
return JSON.parse(readFileSync15(path, "utf8"));
|
|
3746
3805
|
} catch (e) {
|
|
3747
3806
|
throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
|
|
3748
3807
|
}
|
|
@@ -3803,18 +3862,18 @@ function parseResolved(raw, m, where) {
|
|
|
3803
3862
|
}
|
|
3804
3863
|
function loadConfig(crewHome, configFile, opts = {}) {
|
|
3805
3864
|
const file = configFile ?? findConfigFile(crewHome);
|
|
3806
|
-
if (opts.text === void 0 && !
|
|
3865
|
+
if (opts.text === void 0 && !existsSync15(file)) {
|
|
3807
3866
|
throw new ConfigError(
|
|
3808
3867
|
`no config found. Looked in:
|
|
3809
3868
|
` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
|
|
3810
3869
|
|
|
3811
3870
|
Create the first of those:
|
|
3812
|
-
mkdir -p ${dirname7(defaultConfigPath())} && cp ${
|
|
3871
|
+
mkdir -p ${dirname7(defaultConfigPath())} && cp ${join17(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
|
|
3813
3872
|
);
|
|
3814
3873
|
}
|
|
3815
3874
|
let raw;
|
|
3816
3875
|
try {
|
|
3817
|
-
raw = parse2(opts.text ??
|
|
3876
|
+
raw = parse2(opts.text ?? readFileSync15(file, "utf8"));
|
|
3818
3877
|
} catch (e) {
|
|
3819
3878
|
throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
|
|
3820
3879
|
}
|
|
@@ -3941,7 +4000,7 @@ Create the first of those:
|
|
|
3941
4000
|
useNvm: shipRaw.useNvm !== false,
|
|
3942
4001
|
nvmSh: shipRaw.nvmSh,
|
|
3943
4002
|
stateDir,
|
|
3944
|
-
logFile: expand(shipRaw.logFile ??
|
|
4003
|
+
logFile: expand(shipRaw.logFile ?? join17(tmpdir(), "crew.log"), base),
|
|
3945
4004
|
userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
|
|
3946
4005
|
maxConcurrentAgents,
|
|
3947
4006
|
maxConcurrentAgentsSet,
|
|
@@ -4123,7 +4182,7 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
|
|
|
4123
4182
|
const hasRepos = Object.keys(repoDirs).length > 0;
|
|
4124
4183
|
const reposBasePath = expand(String(c?.reposBasePath ?? raw.ship?.reposBasePath ?? DEFAULT_REPOS_BASE_PATH), base);
|
|
4125
4184
|
const dirIsCloneBase = !c?.dir && !hasRepos && !Array.isArray(c?.repos);
|
|
4126
|
-
const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ?
|
|
4185
|
+
const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join17(reposBasePath, splitRoute(route2)[0]) : "";
|
|
4127
4186
|
const weight = c?.weight === void 0 ? void 0 : Number(c.weight);
|
|
4128
4187
|
if (weight !== void 0 && !(Number.isFinite(weight) && weight > 0)) {
|
|
4129
4188
|
missing.add(`${where}.weight must be a positive number (got "${c?.weight}")`);
|
|
@@ -4221,8 +4280,8 @@ function repoIdForName(r, name) {
|
|
|
4221
4280
|
}
|
|
4222
4281
|
function resolveApiKey(r) {
|
|
4223
4282
|
if (r.apiKey) return r.apiKey;
|
|
4224
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
4225
|
-
for (const line of
|
|
4283
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync15(r.apiKeyFile)) {
|
|
4284
|
+
for (const line of readFileSync15(r.apiKeyFile, "utf8").split("\n")) {
|
|
4226
4285
|
const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
|
|
4227
4286
|
if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
4228
4287
|
}
|
|
@@ -4235,9 +4294,9 @@ function resolveApiKey(r) {
|
|
|
4235
4294
|
}
|
|
4236
4295
|
function credentialSource(r) {
|
|
4237
4296
|
if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
|
|
4238
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
4297
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync15(r.apiKeyFile)) {
|
|
4239
4298
|
const re = new RegExp(`^${r.apiKeyVar}=`);
|
|
4240
|
-
if (
|
|
4299
|
+
if (readFileSync15(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
|
|
4241
4300
|
}
|
|
4242
4301
|
return process.env.CREW_API_KEY?.trim() ? "CREW_API_KEY" : "none";
|
|
4243
4302
|
}
|
|
@@ -4293,7 +4352,7 @@ function configuredMembers(rt) {
|
|
|
4293
4352
|
return out;
|
|
4294
4353
|
}
|
|
4295
4354
|
function defaultRepoDir(basePath, workspace, repoName) {
|
|
4296
|
-
return
|
|
4355
|
+
return join17(basePath, workspace, repoName);
|
|
4297
4356
|
}
|
|
4298
4357
|
function reposOf(r) {
|
|
4299
4358
|
const named = Object.entries(r.repos);
|
|
@@ -4332,10 +4391,10 @@ function passengerRepoTargets(r) {
|
|
|
4332
4391
|
return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
|
|
4333
4392
|
}
|
|
4334
4393
|
function passengerCheckoutDir(basePath, workspace, repoName) {
|
|
4335
|
-
return
|
|
4394
|
+
return join17(basePath, workspace, ".passenger-checkouts", repoName);
|
|
4336
4395
|
}
|
|
4337
4396
|
function passengerMirrorArea(ship) {
|
|
4338
|
-
return ship.runner === "sandbox" ?
|
|
4397
|
+
return ship.runner === "sandbox" ? join17(ship.stateDir, "passenger-sources") : void 0;
|
|
4339
4398
|
}
|
|
4340
4399
|
function passengerCheckoutTargets(r, mirrorArea) {
|
|
4341
4400
|
if (!r.hostPassengers) return [];
|
|
@@ -4346,7 +4405,7 @@ function passengerCheckoutTargets(r, mirrorArea) {
|
|
|
4346
4405
|
const forgeHost = t.forgeHost ?? (id ? r.resolved?.repoForgeHosts?.[id] : void 0);
|
|
4347
4406
|
return {
|
|
4348
4407
|
name: t.name,
|
|
4349
|
-
dir: mirrorArea ?
|
|
4408
|
+
dir: mirrorArea ? join17(mirrorArea, workspace, t.name) : passengerCheckoutDir(r.reposBasePath, workspace, t.name),
|
|
4350
4409
|
remote: t.remote ?? (id ? remotes[id] : void 0),
|
|
4351
4410
|
...forgeHost ? { forgeHost } : {}
|
|
4352
4411
|
};
|
|
@@ -4628,9 +4687,9 @@ function recoveredFailureHeadlines(o, route2) {
|
|
|
4628
4687
|
}
|
|
4629
4688
|
|
|
4630
4689
|
// src/install.ts
|
|
4631
|
-
import { dirname as dirname8, join as
|
|
4690
|
+
import { dirname as dirname8, join as join18, extname } from "path";
|
|
4632
4691
|
import { homedir as homedir7, userInfo } from "os";
|
|
4633
|
-
import { accessSync as accessSync2, constants as constants2, existsSync as
|
|
4692
|
+
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync5, writeFileSync as writeFileSync12, unlinkSync, realpathSync } from "fs";
|
|
4634
4693
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
4635
4694
|
import { createHash as createHash3 } from "crypto";
|
|
4636
4695
|
|
|
@@ -4642,7 +4701,7 @@ function isCompiledBinary(moduleUrl) {
|
|
|
4642
4701
|
// src/install.ts
|
|
4643
4702
|
function crewInvocation(crewHome) {
|
|
4644
4703
|
if (isCompiledBinary(import.meta.url)) return [process.execPath];
|
|
4645
|
-
return [process.execPath,
|
|
4704
|
+
return [process.execPath, join18(crewHome, "bin", "crew")];
|
|
4646
4705
|
}
|
|
4647
4706
|
var INTERVAL_SECONDS = 120;
|
|
4648
4707
|
function labelFor(crewHome, job) {
|
|
@@ -4680,7 +4739,7 @@ function pathFor(ship) {
|
|
|
4680
4739
|
return parts.join(":");
|
|
4681
4740
|
}
|
|
4682
4741
|
function findOnPath(bin, searchPath) {
|
|
4683
|
-
const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) =>
|
|
4742
|
+
const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join18(dir, bin));
|
|
4684
4743
|
for (const candidate of candidates2) {
|
|
4685
4744
|
try {
|
|
4686
4745
|
accessSync2(candidate, constants2.X_OK);
|
|
@@ -4690,7 +4749,7 @@ function findOnPath(bin, searchPath) {
|
|
|
4690
4749
|
}
|
|
4691
4750
|
return void 0;
|
|
4692
4751
|
}
|
|
4693
|
-
var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin",
|
|
4752
|
+
var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join18(homedir7(), ".docker", "bin")].join(":");
|
|
4694
4753
|
function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
|
|
4695
4754
|
const schedulerPath = pathFor(ship);
|
|
4696
4755
|
if (findOnPath("docker", schedulerPath)) return void 0;
|
|
@@ -4705,7 +4764,7 @@ function installContext(env = process.env) {
|
|
|
4705
4764
|
}
|
|
4706
4765
|
var SYSTEM_UNIT_DIR = "/etc/systemd/system";
|
|
4707
4766
|
function systemUnitPathFor(label, suffix = "service") {
|
|
4708
|
-
return
|
|
4767
|
+
return join18(SYSTEM_UNIT_DIR, `${label}.${suffix}`);
|
|
4709
4768
|
}
|
|
4710
4769
|
function unitEnvironment(ship) {
|
|
4711
4770
|
return [["PATH", pathFor(ship)], ...Object.entries(ship.env ?? {})];
|
|
@@ -4726,7 +4785,7 @@ function xmlEscape(s) {
|
|
|
4726
4785
|
}
|
|
4727
4786
|
function planLaunchd(ship, crewHome, job) {
|
|
4728
4787
|
const label = labelFor(crewHome, job);
|
|
4729
|
-
const unitPath =
|
|
4788
|
+
const unitPath = join18(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4730
4789
|
const crewLog = ship.logFile;
|
|
4731
4790
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4732
4791
|
const persistent = job === "run";
|
|
@@ -4780,7 +4839,7 @@ ${scheduleKeys}
|
|
|
4780
4839
|
}
|
|
4781
4840
|
function planSystemd(ship, crewHome, job, ctx) {
|
|
4782
4841
|
const label = labelFor(crewHome, job);
|
|
4783
|
-
const unitDir = ctx.root ? SYSTEM_UNIT_DIR :
|
|
4842
|
+
const unitDir = ctx.root ? SYSTEM_UNIT_DIR : join18(homedir7(), ".config", "systemd", "user");
|
|
4784
4843
|
const ctl = ctx.root ? ["systemctl"] : ["systemctl", "--user"];
|
|
4785
4844
|
const userLine = ctx.root && ctx.user && ctx.user !== "root" ? `User=${ctx.user}
|
|
4786
4845
|
` : "";
|
|
@@ -4789,7 +4848,7 @@ function planSystemd(ship, crewHome, job, ctx) {
|
|
|
4789
4848
|
lingerCommand: ["loginctl", "enable-linger", ctx.user],
|
|
4790
4849
|
lingerHint: `the daemon will stop when ${ctx.user} logs out; run \`sudo loginctl enable-linger ${ctx.user}\` once, or run \`crew install\` as root for a system unit`
|
|
4791
4850
|
};
|
|
4792
|
-
const servicePath =
|
|
4851
|
+
const servicePath = join18(unitDir, `${label}.service`);
|
|
4793
4852
|
const crewLog = ship.logFile;
|
|
4794
4853
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4795
4854
|
const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
|
|
@@ -4830,7 +4889,7 @@ WantedBy=${wantedBy}
|
|
|
4830
4889
|
...linger
|
|
4831
4890
|
};
|
|
4832
4891
|
}
|
|
4833
|
-
const timerPath =
|
|
4892
|
+
const timerPath = join18(unitDir, `${label}.timer`);
|
|
4834
4893
|
const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
|
|
4835
4894
|
[Unit]
|
|
4836
4895
|
Description=${description}
|
|
@@ -4905,7 +4964,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run", ctx = US
|
|
|
4905
4964
|
function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
4906
4965
|
const label = labelFor(crewHome, job);
|
|
4907
4966
|
if (hostShip === "macos") {
|
|
4908
|
-
const unitPath =
|
|
4967
|
+
const unitPath = join18(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4909
4968
|
return {
|
|
4910
4969
|
mechanism: "launchd",
|
|
4911
4970
|
unitPaths: [unitPath],
|
|
@@ -4915,20 +4974,20 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4915
4974
|
};
|
|
4916
4975
|
}
|
|
4917
4976
|
if (hostShip === "linux" && hasSystemd) {
|
|
4918
|
-
const system =
|
|
4919
|
-
const unitDir = system ? SYSTEM_UNIT_DIR :
|
|
4977
|
+
const system = existsSync16(systemUnitPathFor(label));
|
|
4978
|
+
const unitDir = system ? SYSTEM_UNIT_DIR : join18(homedir7(), ".config", "systemd", "user");
|
|
4920
4979
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
4921
4980
|
if (job === "run") {
|
|
4922
4981
|
return {
|
|
4923
4982
|
mechanism: "systemd",
|
|
4924
|
-
unitPaths: [
|
|
4983
|
+
unitPaths: [join18(unitDir, `${label}.service`)],
|
|
4925
4984
|
crontabLine: null,
|
|
4926
4985
|
unloadCommands: [[...ctl, "disable", "--now", `${label}.service`]]
|
|
4927
4986
|
};
|
|
4928
4987
|
}
|
|
4929
4988
|
return {
|
|
4930
4989
|
mechanism: "systemd",
|
|
4931
|
-
unitPaths: [
|
|
4990
|
+
unitPaths: [join18(unitDir, `${label}.service`), join18(unitDir, `${label}.timer`)],
|
|
4932
4991
|
crontabLine: null,
|
|
4933
4992
|
unloadCommands: [[...ctl, "disable", "--now", `${label}.timer`]]
|
|
4934
4993
|
};
|
|
@@ -4946,7 +5005,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4946
5005
|
function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
4947
5006
|
const label = labelFor(crewHome, "run");
|
|
4948
5007
|
if (hostShip === "macos") {
|
|
4949
|
-
const unitPath =
|
|
5008
|
+
const unitPath = join18(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4950
5009
|
return {
|
|
4951
5010
|
mechanism: "launchd",
|
|
4952
5011
|
startCommand: ["launchctl", "load", unitPath],
|
|
@@ -4956,7 +5015,7 @@ function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
|
4956
5015
|
}
|
|
4957
5016
|
if (hostShip === "linux" && hasSystemd) {
|
|
4958
5017
|
const unit = `${label}.service`;
|
|
4959
|
-
const ctl =
|
|
5018
|
+
const ctl = existsSync16(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
|
|
4960
5019
|
return {
|
|
4961
5020
|
mechanism: "systemd",
|
|
4962
5021
|
startCommand: [...ctl, "start", unit],
|
|
@@ -4988,10 +5047,10 @@ async function applyInstall(plan, crewHome, dryRun, log) {
|
|
|
4988
5047
|
log.emit(`would write ${path}`);
|
|
4989
5048
|
continue;
|
|
4990
5049
|
}
|
|
4991
|
-
const before =
|
|
5050
|
+
const before = existsSync16(path) ? readFileSync16(path, "utf8") : null;
|
|
4992
5051
|
if (before !== plan.unitContent[path]) changed2 = true;
|
|
4993
|
-
|
|
4994
|
-
|
|
5052
|
+
mkdirSync11(dirname8(path), { recursive: true });
|
|
5053
|
+
writeFileSync12(path, plan.unitContent[path]);
|
|
4995
5054
|
log.emit(`wrote ${path}`);
|
|
4996
5055
|
}
|
|
4997
5056
|
if (plan.crontabLine) {
|
|
@@ -5055,7 +5114,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
5055
5114
|
}
|
|
5056
5115
|
}
|
|
5057
5116
|
for (const path of plan.unitPaths) {
|
|
5058
|
-
if (!
|
|
5117
|
+
if (!existsSync16(path)) continue;
|
|
5059
5118
|
if (dryRun) {
|
|
5060
5119
|
log.emit(`would remove ${path}`);
|
|
5061
5120
|
continue;
|
|
@@ -5074,7 +5133,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
5074
5133
|
}
|
|
5075
5134
|
var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
|
|
5076
5135
|
function launchAgentsDir() {
|
|
5077
|
-
return
|
|
5136
|
+
return join18(homedir7(), "Library", "LaunchAgents");
|
|
5078
5137
|
}
|
|
5079
5138
|
function xmlUnescape(s) {
|
|
5080
5139
|
return s.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
@@ -5091,25 +5150,25 @@ function loadedCrewLabels() {
|
|
|
5091
5150
|
return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
|
|
5092
5151
|
}
|
|
5093
5152
|
function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
5094
|
-
const files =
|
|
5153
|
+
const files = existsSync16(dir) ? readdirSync3(dir) : [];
|
|
5095
5154
|
const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
|
|
5096
|
-
const plistPath =
|
|
5155
|
+
const plistPath = join18(dir, f);
|
|
5097
5156
|
let program = null;
|
|
5098
5157
|
try {
|
|
5099
|
-
program = programFromPlist(
|
|
5158
|
+
program = programFromPlist(readFileSync16(plistPath, "utf8"));
|
|
5100
5159
|
} catch {
|
|
5101
5160
|
}
|
|
5102
5161
|
return {
|
|
5103
5162
|
label: f.slice(0, -".plist".length),
|
|
5104
5163
|
plistPath,
|
|
5105
5164
|
program,
|
|
5106
|
-
programExists: program !== null &&
|
|
5165
|
+
programExists: program !== null && existsSync16(program)
|
|
5107
5166
|
};
|
|
5108
5167
|
});
|
|
5109
5168
|
const seen = new Set(units.map((u) => u.label));
|
|
5110
5169
|
for (const label of [...loaded].sort()) {
|
|
5111
5170
|
if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
|
|
5112
|
-
units.push({ label, plistPath:
|
|
5171
|
+
units.push({ label, plistPath: join18(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
|
|
5113
5172
|
}
|
|
5114
5173
|
return units;
|
|
5115
5174
|
}
|
|
@@ -5122,9 +5181,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
|
|
|
5122
5181
|
log.emit(`would unload and remove ${unit.label}`);
|
|
5123
5182
|
return;
|
|
5124
5183
|
}
|
|
5125
|
-
if (
|
|
5184
|
+
if (existsSync16(unit.plistPath)) spawnSync5("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
|
|
5126
5185
|
spawnSync5("launchctl", ["remove", unit.label], { encoding: "utf8" });
|
|
5127
|
-
if (
|
|
5186
|
+
if (existsSync16(unit.plistPath)) unlinkSync(unit.plistPath);
|
|
5128
5187
|
log.emit(`removed ${unit.label} (${unit.plistPath})`);
|
|
5129
5188
|
}
|
|
5130
5189
|
function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
|
|
@@ -5202,24 +5261,24 @@ function detectSystemd() {
|
|
|
5202
5261
|
}
|
|
5203
5262
|
|
|
5204
5263
|
// src/ship-attention.ts
|
|
5205
|
-
import { existsSync as
|
|
5264
|
+
import { existsSync as existsSync18, statfsSync } from "fs";
|
|
5206
5265
|
|
|
5207
5266
|
// src/git.ts
|
|
5208
5267
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
5209
5268
|
import {
|
|
5210
|
-
existsSync as
|
|
5211
|
-
mkdirSync as
|
|
5269
|
+
existsSync as existsSync17,
|
|
5270
|
+
mkdirSync as mkdirSync12,
|
|
5212
5271
|
openSync as openSync3,
|
|
5213
5272
|
closeSync as closeSync3,
|
|
5214
5273
|
writeSync,
|
|
5215
5274
|
statSync as statSync6,
|
|
5216
5275
|
unlinkSync as unlinkSync2,
|
|
5217
|
-
readFileSync as
|
|
5218
|
-
writeFileSync as
|
|
5276
|
+
readFileSync as readFileSync17,
|
|
5277
|
+
writeFileSync as writeFileSync13,
|
|
5219
5278
|
readdirSync as readdirSync4,
|
|
5220
5279
|
rmSync as rmSync9
|
|
5221
5280
|
} from "fs";
|
|
5222
|
-
import { dirname as dirname9, basename as basename5, join as
|
|
5281
|
+
import { dirname as dirname9, basename as basename5, join as join19 } from "path";
|
|
5223
5282
|
var GitError = class extends Error {
|
|
5224
5283
|
};
|
|
5225
5284
|
function streamText(x) {
|
|
@@ -5617,11 +5676,11 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
|
|
|
5617
5676
|
}
|
|
5618
5677
|
for (const name of names) {
|
|
5619
5678
|
if (!name.startsWith("crew-release-")) continue;
|
|
5620
|
-
const root =
|
|
5679
|
+
const root = join19(tmp, name);
|
|
5621
5680
|
try {
|
|
5622
5681
|
const st = statSync6(root);
|
|
5623
5682
|
if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
|
|
5624
|
-
if (
|
|
5683
|
+
if (existsSync17(join19(root, "checkout"))) continue;
|
|
5625
5684
|
rmSync9(root, { recursive: true, force: true });
|
|
5626
5685
|
removed.push(name);
|
|
5627
5686
|
} catch {
|
|
@@ -5673,13 +5732,13 @@ function originUrlOf(dir, remoteName) {
|
|
|
5673
5732
|
}
|
|
5674
5733
|
}
|
|
5675
5734
|
function ensureRepoCheckout(dir, remote, opts = {}) {
|
|
5676
|
-
if (
|
|
5735
|
+
if (existsSync17(`${dir}/.git`)) return false;
|
|
5677
5736
|
if (!remote) {
|
|
5678
5737
|
throw new GitError(
|
|
5679
5738
|
`${dir} does not exist and this repo has no discovered remote to clone from \u2014 re-run \`crew connect\`, or check out the repository yourself and point \`repos:\` at it`
|
|
5680
5739
|
);
|
|
5681
5740
|
}
|
|
5682
|
-
|
|
5741
|
+
mkdirSync12(dirname9(dir), { recursive: true });
|
|
5683
5742
|
const url = cloneUrlFor(remote, opts);
|
|
5684
5743
|
try {
|
|
5685
5744
|
execFileSync5("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -5704,7 +5763,7 @@ function sleepSync(ms3) {
|
|
|
5704
5763
|
}
|
|
5705
5764
|
function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
5706
5765
|
const lockPath = checkoutLockPath(dir);
|
|
5707
|
-
|
|
5766
|
+
mkdirSync12(dirname9(lockPath), { recursive: true });
|
|
5708
5767
|
const deadline = Date.now() + timeoutMs;
|
|
5709
5768
|
for (; ; ) {
|
|
5710
5769
|
try {
|
|
@@ -5741,17 +5800,17 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
|
5741
5800
|
function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {}) {
|
|
5742
5801
|
const release = acquireCheckoutLock(dir);
|
|
5743
5802
|
try {
|
|
5744
|
-
if (!
|
|
5803
|
+
if (!existsSync17(`${dir}/.git`) && githubRemote) {
|
|
5745
5804
|
const url = cloneUrlFor(githubRemote, clone);
|
|
5746
5805
|
const marker = cloneFailureMarker(dir);
|
|
5747
|
-
if (
|
|
5748
|
-
return { action: "clone-failed", detail:
|
|
5806
|
+
if (existsSync17(marker) && readFileSync17(marker, "utf8").split("\n")[0] === url) {
|
|
5807
|
+
return { action: "clone-failed", detail: readFileSync17(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
|
|
5749
5808
|
}
|
|
5750
5809
|
try {
|
|
5751
5810
|
ensureRepoCheckout(dir, githubRemote, clone);
|
|
5752
5811
|
} catch (e) {
|
|
5753
5812
|
if (!(e instanceof GitError)) throw e;
|
|
5754
|
-
|
|
5813
|
+
writeFileSync13(marker, `${url}
|
|
5755
5814
|
${e.message}`);
|
|
5756
5815
|
return { action: "clone-failed", detail: e.message, repeat: false };
|
|
5757
5816
|
}
|
|
@@ -5977,7 +6036,7 @@ function noteHeldWorktree(state, h, nowMs = Date.now()) {
|
|
|
5977
6036
|
function reconcileHeldWorktrees(state, repo, log, stillRefused) {
|
|
5978
6037
|
for (const i of state.shipAttention().previous()) {
|
|
5979
6038
|
if (i.kind !== "worktree_held" || !i.held || i.held.repo !== repo) continue;
|
|
5980
|
-
const gone = !
|
|
6039
|
+
const gone = !existsSync18(i.held.path);
|
|
5981
6040
|
if (!gone && !(stillRefused && !stillRefused.has(i.held.path))) continue;
|
|
5982
6041
|
clearShipAttention(state, i.key);
|
|
5983
6042
|
log.emit(
|
|
@@ -6130,8 +6189,8 @@ function attentionProblems(items, already) {
|
|
|
6130
6189
|
import { createHash as createHash6 } from "crypto";
|
|
6131
6190
|
import { spawn as spawn5 } from "child_process";
|
|
6132
6191
|
import { execFileSync as execFileSync8 } from "child_process";
|
|
6133
|
-
import { mkdirSync as
|
|
6134
|
-
import { dirname as dirname13, join as
|
|
6192
|
+
import { mkdirSync as mkdirSync18, readFileSync as readFileSync25, renameSync as renameSync3, writeFileSync as writeFileSync19, rmSync as rmSync14 } from "fs";
|
|
6193
|
+
import { dirname as dirname13, join as join30 } from "path";
|
|
6135
6194
|
import { tmpdir as tmpdir3 } from "os";
|
|
6136
6195
|
|
|
6137
6196
|
// src/passenger-containers.ts
|
|
@@ -6144,32 +6203,32 @@ import { promisify as promisify2 } from "util";
|
|
|
6144
6203
|
|
|
6145
6204
|
// src/deprovision-host.ts
|
|
6146
6205
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
6147
|
-
import { existsSync as
|
|
6206
|
+
import { existsSync as existsSync28, readdirSync as readdirSync11 } from "fs";
|
|
6148
6207
|
|
|
6149
6208
|
// src/interrupted.ts
|
|
6150
|
-
import { existsSync as
|
|
6151
|
-
import { join as
|
|
6209
|
+
import { existsSync as existsSync26, readdirSync as readdirSync9, readFileSync as readFileSync24 } from "fs";
|
|
6210
|
+
import { join as join28 } from "path";
|
|
6152
6211
|
|
|
6153
6212
|
// src/agent.ts
|
|
6154
6213
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
6155
|
-
import { readFileSync as
|
|
6214
|
+
import { readFileSync as readFileSync23, existsSync as existsSync25, statSync as statSync8, mkdirSync as mkdirSync17, writeFileSync as writeFileSync18, createWriteStream, openSync as openSync4, closeSync as closeSync4, readSync } from "fs";
|
|
6156
6215
|
import { StringDecoder } from "string_decoder";
|
|
6157
6216
|
import { finished } from "stream/promises";
|
|
6158
|
-
import { join as
|
|
6217
|
+
import { join as join27, dirname as dirname12, basename as basename9 } from "path";
|
|
6159
6218
|
import { createHash as createHash4 } from "crypto";
|
|
6160
6219
|
|
|
6161
6220
|
// src/state.ts
|
|
6162
|
-
import { readFileSync as
|
|
6163
|
-
import { join as
|
|
6221
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync14, existsSync as existsSync19, mkdirSync as mkdirSync13, rmSync as rmSync10, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
|
|
6222
|
+
import { join as join20 } from "path";
|
|
6164
6223
|
var EPOCH = "1970-01-01T00:00:00.000Z";
|
|
6165
6224
|
var State = class _State {
|
|
6166
6225
|
dir;
|
|
6167
6226
|
constructor(dir) {
|
|
6168
6227
|
this.dir = dir;
|
|
6169
|
-
|
|
6228
|
+
mkdirSync13(dir, { recursive: true });
|
|
6170
6229
|
}
|
|
6171
6230
|
path(...parts) {
|
|
6172
|
-
return
|
|
6231
|
+
return join20(this.dir, ...parts);
|
|
6173
6232
|
}
|
|
6174
6233
|
/**
|
|
6175
6234
|
* The timestamp every "new since last poll" check is measured against.
|
|
@@ -6180,14 +6239,14 @@ var State = class _State {
|
|
|
6180
6239
|
*/
|
|
6181
6240
|
watermark() {
|
|
6182
6241
|
try {
|
|
6183
|
-
const v =
|
|
6242
|
+
const v = readFileSync18(this.path(".poll-watermark"), "utf8").trim();
|
|
6184
6243
|
return v || EPOCH;
|
|
6185
6244
|
} catch {
|
|
6186
6245
|
return EPOCH;
|
|
6187
6246
|
}
|
|
6188
6247
|
}
|
|
6189
6248
|
setWatermark(at) {
|
|
6190
|
-
|
|
6249
|
+
writeFileSync14(this.path(".poll-watermark"), `${at}
|
|
6191
6250
|
`);
|
|
6192
6251
|
}
|
|
6193
6252
|
/**
|
|
@@ -6205,16 +6264,16 @@ var State = class _State {
|
|
|
6205
6264
|
}
|
|
6206
6265
|
/** Whole-crew pause, and the per-role sentinels one level down. */
|
|
6207
6266
|
isPaused() {
|
|
6208
|
-
return
|
|
6267
|
+
return existsSync19(this.path(".crew-paused"));
|
|
6209
6268
|
}
|
|
6210
6269
|
isRolePaused(role) {
|
|
6211
|
-
return
|
|
6270
|
+
return existsSync19(this.path(`.role-paused-${role}`));
|
|
6212
6271
|
}
|
|
6213
6272
|
pausedRoles(roles) {
|
|
6214
6273
|
return new Set(roles.filter((r) => this.isRolePaused(r)));
|
|
6215
6274
|
}
|
|
6216
6275
|
pause(role) {
|
|
6217
|
-
|
|
6276
|
+
writeFileSync14(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
|
|
6218
6277
|
}
|
|
6219
6278
|
resume(role) {
|
|
6220
6279
|
rmSync10(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), { force: true });
|
|
@@ -6225,10 +6284,10 @@ var State = class _State {
|
|
|
6225
6284
|
* while agents keep working.
|
|
6226
6285
|
*/
|
|
6227
6286
|
isReleasePaused() {
|
|
6228
|
-
return
|
|
6287
|
+
return existsSync19(this.path(".release-paused"));
|
|
6229
6288
|
}
|
|
6230
6289
|
setReleasePaused(paused) {
|
|
6231
|
-
if (paused)
|
|
6290
|
+
if (paused) writeFileSync14(this.path(".release-paused"), "");
|
|
6232
6291
|
else rmSync10(this.path(".release-paused"), { force: true });
|
|
6233
6292
|
}
|
|
6234
6293
|
/**
|
|
@@ -6266,7 +6325,7 @@ var State = class _State {
|
|
|
6266
6325
|
const name = _State.safe(rawName);
|
|
6267
6326
|
const file = this.path(`.${name}.lock`);
|
|
6268
6327
|
try {
|
|
6269
|
-
const existing = Number.parseInt(
|
|
6328
|
+
const existing = Number.parseInt(readFileSync18(file, "utf8").trim(), 10);
|
|
6270
6329
|
if (Number.isFinite(existing) && existing > 0) {
|
|
6271
6330
|
try {
|
|
6272
6331
|
process.kill(existing, 0);
|
|
@@ -6276,7 +6335,7 @@ var State = class _State {
|
|
|
6276
6335
|
}
|
|
6277
6336
|
} catch {
|
|
6278
6337
|
}
|
|
6279
|
-
|
|
6338
|
+
writeFileSync14(file, `${process.pid}
|
|
6280
6339
|
`);
|
|
6281
6340
|
return {
|
|
6282
6341
|
ok: true,
|
|
@@ -6304,7 +6363,7 @@ var State = class _State {
|
|
|
6304
6363
|
if (!/^\.release-.+\.lock$/.test(n)) continue;
|
|
6305
6364
|
let pid = 0;
|
|
6306
6365
|
try {
|
|
6307
|
-
pid = Number.parseInt(
|
|
6366
|
+
pid = Number.parseInt(readFileSync18(this.path(n), "utf8").trim(), 10);
|
|
6308
6367
|
} catch {
|
|
6309
6368
|
continue;
|
|
6310
6369
|
}
|
|
@@ -6390,7 +6449,7 @@ var State = class _State {
|
|
|
6390
6449
|
ticketInFlight(recordId) {
|
|
6391
6450
|
const file = this.path(`.${_State.safe(`crew-ticket-${recordId}`)}.lock`);
|
|
6392
6451
|
try {
|
|
6393
|
-
const pid = Number.parseInt(
|
|
6452
|
+
const pid = Number.parseInt(readFileSync18(file, "utf8").trim(), 10);
|
|
6394
6453
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
6395
6454
|
process.kill(pid, 0);
|
|
6396
6455
|
return true;
|
|
@@ -6424,7 +6483,7 @@ var State = class _State {
|
|
|
6424
6483
|
const heldReported = `.release-held-${suffix}`;
|
|
6425
6484
|
const read = (f) => {
|
|
6426
6485
|
try {
|
|
6427
|
-
return
|
|
6486
|
+
return readFileSync18(this.path(f), "utf8").trim() || null;
|
|
6428
6487
|
} catch {
|
|
6429
6488
|
return null;
|
|
6430
6489
|
}
|
|
@@ -6433,7 +6492,7 @@ var State = class _State {
|
|
|
6433
6492
|
blockedCount: () => Number.parseInt(read(blocked) ?? "", 10) || 0,
|
|
6434
6493
|
noteBlocked: () => {
|
|
6435
6494
|
const n = (Number.parseInt(read(blocked) ?? "", 10) || 0) + 1;
|
|
6436
|
-
|
|
6495
|
+
writeFileSync14(this.path(blocked), `${n}
|
|
6437
6496
|
`);
|
|
6438
6497
|
return n;
|
|
6439
6498
|
},
|
|
@@ -6450,7 +6509,7 @@ var State = class _State {
|
|
|
6450
6509
|
*/
|
|
6451
6510
|
deployFailedSha: () => read(failed),
|
|
6452
6511
|
noteDeployFailed: (sha) => {
|
|
6453
|
-
|
|
6512
|
+
writeFileSync14(this.path(failed), `${sha}
|
|
6454
6513
|
`);
|
|
6455
6514
|
},
|
|
6456
6515
|
clearDeployFailed: () => {
|
|
@@ -6464,7 +6523,7 @@ var State = class _State {
|
|
|
6464
6523
|
noteTestGateFailed: (sha) => {
|
|
6465
6524
|
const [prev, n] = (read(gateFailed) ?? "").split(" ");
|
|
6466
6525
|
const next = prev === sha ? (Number.parseInt(n ?? "", 10) || 0) + 1 : 1;
|
|
6467
|
-
|
|
6526
|
+
writeFileSync14(this.path(gateFailed), `${sha} ${next}
|
|
6468
6527
|
`);
|
|
6469
6528
|
return next;
|
|
6470
6529
|
},
|
|
@@ -6474,13 +6533,13 @@ var State = class _State {
|
|
|
6474
6533
|
/** Whether the board was already told about this head's red gate. */
|
|
6475
6534
|
heldReported: () => {
|
|
6476
6535
|
try {
|
|
6477
|
-
return
|
|
6536
|
+
return readFileSync18(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
|
|
6478
6537
|
} catch {
|
|
6479
6538
|
return null;
|
|
6480
6539
|
}
|
|
6481
6540
|
},
|
|
6482
6541
|
noteHeldReported: (signature) => {
|
|
6483
|
-
|
|
6542
|
+
writeFileSync14(this.path(heldReported), `${signature}
|
|
6484
6543
|
`);
|
|
6485
6544
|
},
|
|
6486
6545
|
clearHeldReported: () => {
|
|
@@ -6488,7 +6547,7 @@ var State = class _State {
|
|
|
6488
6547
|
},
|
|
6489
6548
|
testGateReported: (sha) => read(gateReported) === sha,
|
|
6490
6549
|
noteTestGateReported: (sha) => {
|
|
6491
|
-
|
|
6550
|
+
writeFileSync14(this.path(gateReported), `${sha}
|
|
6492
6551
|
`);
|
|
6493
6552
|
}
|
|
6494
6553
|
};
|
|
@@ -6511,7 +6570,7 @@ var State = class _State {
|
|
|
6511
6570
|
const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
|
|
6512
6571
|
const read = (f) => {
|
|
6513
6572
|
try {
|
|
6514
|
-
return
|
|
6573
|
+
return readFileSync18(this.path(f), "utf8").trim() || null;
|
|
6515
6574
|
} catch {
|
|
6516
6575
|
return null;
|
|
6517
6576
|
}
|
|
@@ -6544,18 +6603,18 @@ var State = class _State {
|
|
|
6544
6603
|
clear2();
|
|
6545
6604
|
return;
|
|
6546
6605
|
}
|
|
6547
|
-
|
|
6606
|
+
writeFileSync14(this.path(streakFile), `${this.readInt(streakFile) + 1}
|
|
6548
6607
|
`);
|
|
6549
6608
|
const prior = read(ticketFile);
|
|
6550
6609
|
if (topTicket && topTicket === prior) {
|
|
6551
|
-
|
|
6610
|
+
writeFileSync14(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
|
|
6552
6611
|
`);
|
|
6553
6612
|
} else if (topTicket) {
|
|
6554
|
-
|
|
6613
|
+
writeFileSync14(this.path(ticketFile), `${topTicket}
|
|
6555
6614
|
`);
|
|
6556
|
-
|
|
6615
|
+
writeFileSync14(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
6557
6616
|
`);
|
|
6558
|
-
|
|
6617
|
+
writeFileSync14(this.path(ticketStreakFile), "1\n");
|
|
6559
6618
|
}
|
|
6560
6619
|
}
|
|
6561
6620
|
};
|
|
@@ -6576,14 +6635,14 @@ var State = class _State {
|
|
|
6576
6635
|
return {
|
|
6577
6636
|
previous: () => {
|
|
6578
6637
|
try {
|
|
6579
|
-
const parsed = JSON.parse(
|
|
6638
|
+
const parsed = JSON.parse(readFileSync18(this.path(file), "utf8"));
|
|
6580
6639
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
6581
6640
|
} catch {
|
|
6582
6641
|
return {};
|
|
6583
6642
|
}
|
|
6584
6643
|
},
|
|
6585
6644
|
persist: (next) => {
|
|
6586
|
-
|
|
6645
|
+
writeFileSync14(this.path(file), JSON.stringify(next));
|
|
6587
6646
|
}
|
|
6588
6647
|
};
|
|
6589
6648
|
}
|
|
@@ -6599,14 +6658,14 @@ var State = class _State {
|
|
|
6599
6658
|
return {
|
|
6600
6659
|
previous: () => {
|
|
6601
6660
|
try {
|
|
6602
|
-
const parsed = JSON.parse(
|
|
6661
|
+
const parsed = JSON.parse(readFileSync18(this.path(file), "utf8"));
|
|
6603
6662
|
return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : [];
|
|
6604
6663
|
} catch {
|
|
6605
6664
|
return [];
|
|
6606
6665
|
}
|
|
6607
6666
|
},
|
|
6608
6667
|
persist: (next) => {
|
|
6609
|
-
|
|
6668
|
+
writeFileSync14(this.path(file), JSON.stringify(next));
|
|
6610
6669
|
}
|
|
6611
6670
|
};
|
|
6612
6671
|
}
|
|
@@ -6620,14 +6679,14 @@ var State = class _State {
|
|
|
6620
6679
|
return {
|
|
6621
6680
|
previous: () => {
|
|
6622
6681
|
try {
|
|
6623
|
-
const parsed = JSON.parse(
|
|
6682
|
+
const parsed = JSON.parse(readFileSync18(this.path(file), "utf8"));
|
|
6624
6683
|
return Array.isArray(parsed) ? parsed : [];
|
|
6625
6684
|
} catch {
|
|
6626
6685
|
return [];
|
|
6627
6686
|
}
|
|
6628
6687
|
},
|
|
6629
6688
|
persist: (next) => {
|
|
6630
|
-
|
|
6689
|
+
writeFileSync14(this.path(file), JSON.stringify(next));
|
|
6631
6690
|
}
|
|
6632
6691
|
};
|
|
6633
6692
|
}
|
|
@@ -6640,7 +6699,7 @@ var State = class _State {
|
|
|
6640
6699
|
const file = ".release-last-run";
|
|
6641
6700
|
return {
|
|
6642
6701
|
touch: () => {
|
|
6643
|
-
|
|
6702
|
+
writeFileSync14(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
|
|
6644
6703
|
},
|
|
6645
6704
|
at: () => {
|
|
6646
6705
|
try {
|
|
@@ -6653,7 +6712,7 @@ var State = class _State {
|
|
|
6653
6712
|
}
|
|
6654
6713
|
readInt(file) {
|
|
6655
6714
|
try {
|
|
6656
|
-
return Number.parseInt(
|
|
6715
|
+
return Number.parseInt(readFileSync18(this.path(file), "utf8").trim(), 10) || 0;
|
|
6657
6716
|
} catch {
|
|
6658
6717
|
return 0;
|
|
6659
6718
|
}
|
|
@@ -7270,8 +7329,8 @@ var BeforeFirstEditTracker = class {
|
|
|
7270
7329
|
};
|
|
7271
7330
|
|
|
7272
7331
|
// src/agent-log.ts
|
|
7273
|
-
import { existsSync as
|
|
7274
|
-
import { join as
|
|
7332
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync14, readdirSync as readdirSync6, readFileSync as readFileSync19, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "fs";
|
|
7333
|
+
import { join as join21 } from "path";
|
|
7275
7334
|
|
|
7276
7335
|
// src/pending-backoff.ts
|
|
7277
7336
|
var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
|
|
@@ -7474,20 +7533,20 @@ async function addRunToTicket(target, t, recordId, run3) {
|
|
|
7474
7533
|
};
|
|
7475
7534
|
}
|
|
7476
7535
|
}
|
|
7477
|
-
var queueDir = (stateDir) =>
|
|
7536
|
+
var queueDir = (stateDir) => join21(stateDir, "pending-agent-logs");
|
|
7478
7537
|
function queueAgentRun(stateDir, name, q2) {
|
|
7479
|
-
|
|
7480
|
-
|
|
7538
|
+
mkdirSync14(queueDir(stateDir), { recursive: true });
|
|
7539
|
+
writeFileSync15(join21(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
7481
7540
|
}
|
|
7482
7541
|
async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
7483
7542
|
const dir = queueDir(stateDir);
|
|
7484
|
-
if (!
|
|
7543
|
+
if (!existsSync20(dir)) return 0;
|
|
7485
7544
|
let sent = 0;
|
|
7486
7545
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
7487
|
-
const path =
|
|
7546
|
+
const path = join21(dir, f);
|
|
7488
7547
|
let q2;
|
|
7489
7548
|
try {
|
|
7490
|
-
q2 = JSON.parse(
|
|
7549
|
+
q2 = JSON.parse(readFileSync19(path, "utf8"));
|
|
7491
7550
|
} catch {
|
|
7492
7551
|
continue;
|
|
7493
7552
|
}
|
|
@@ -7516,7 +7575,7 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
7516
7575
|
continue;
|
|
7517
7576
|
}
|
|
7518
7577
|
const attempts = q2.attempts + 1;
|
|
7519
|
-
|
|
7578
|
+
writeFileSync15(path, JSON.stringify({
|
|
7520
7579
|
...q2,
|
|
7521
7580
|
attempts,
|
|
7522
7581
|
warned: true,
|
|
@@ -7527,20 +7586,20 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
7527
7586
|
}
|
|
7528
7587
|
return sent;
|
|
7529
7588
|
}
|
|
7530
|
-
var countersDir = (stateDir) =>
|
|
7589
|
+
var countersDir = (stateDir) => join21(stateDir, "pending-ticket-counters");
|
|
7531
7590
|
function queueTicketCounters(stateDir, name, q2) {
|
|
7532
|
-
|
|
7533
|
-
|
|
7591
|
+
mkdirSync14(countersDir(stateDir), { recursive: true });
|
|
7592
|
+
writeFileSync15(join21(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
7534
7593
|
}
|
|
7535
7594
|
async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
7536
7595
|
const dir = countersDir(stateDir);
|
|
7537
|
-
if (!
|
|
7596
|
+
if (!existsSync20(dir)) return 0;
|
|
7538
7597
|
let sent = 0;
|
|
7539
7598
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
7540
|
-
const path =
|
|
7599
|
+
const path = join21(dir, f);
|
|
7541
7600
|
let q2;
|
|
7542
7601
|
try {
|
|
7543
|
-
q2 = JSON.parse(
|
|
7602
|
+
q2 = JSON.parse(readFileSync19(path, "utf8"));
|
|
7544
7603
|
} catch {
|
|
7545
7604
|
continue;
|
|
7546
7605
|
}
|
|
@@ -7559,7 +7618,7 @@ async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /
|
|
|
7559
7618
|
continue;
|
|
7560
7619
|
}
|
|
7561
7620
|
const attempts = q2.attempts + 1;
|
|
7562
|
-
|
|
7621
|
+
writeFileSync15(path, JSON.stringify({
|
|
7563
7622
|
...q2,
|
|
7564
7623
|
attempts,
|
|
7565
7624
|
warned: true,
|
|
@@ -7762,8 +7821,8 @@ var HandoffTranscriptTracker = class {
|
|
|
7762
7821
|
};
|
|
7763
7822
|
|
|
7764
7823
|
// src/pending-handoff.ts
|
|
7765
|
-
import { existsSync as
|
|
7766
|
-
import { join as
|
|
7824
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync15, readdirSync as readdirSync7, readFileSync as readFileSync20, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "fs";
|
|
7825
|
+
import { join as join23 } from "path";
|
|
7767
7826
|
|
|
7768
7827
|
// src/ticket-branch.ts
|
|
7769
7828
|
import { basename as basename7 } from "path";
|
|
@@ -8929,8 +8988,8 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
|
|
|
8929
8988
|
}
|
|
8930
8989
|
|
|
8931
8990
|
// src/run-worktree.ts
|
|
8932
|
-
import { existsSync as
|
|
8933
|
-
import { dirname as dirname10, join as
|
|
8991
|
+
import { existsSync as existsSync21 } from "fs";
|
|
8992
|
+
import { dirname as dirname10, join as join22 } from "path";
|
|
8934
8993
|
var describeRunWorktreePlan = (p) => {
|
|
8935
8994
|
switch (p.action) {
|
|
8936
8995
|
case "cut-remote":
|
|
@@ -8971,7 +9030,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
8971
9030
|
const existing = worktreeForNumber(dir, num);
|
|
8972
9031
|
const located = locateBranchForTicket(dir, cfg, t, role);
|
|
8973
9032
|
const baseRef = hasRemote ? `${remote}/${base}` : base;
|
|
8974
|
-
if (existing && !existing.prunable &&
|
|
9033
|
+
if (existing && !existing.prunable && existsSync21(existing.path)) {
|
|
8975
9034
|
const branch = existing.branch;
|
|
8976
9035
|
if (status(existing.path).length > 0) {
|
|
8977
9036
|
return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
|
|
@@ -8987,7 +9046,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
8987
9046
|
if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
|
|
8988
9047
|
return { action: "recut", path: existing.path, branch, base: baseRef };
|
|
8989
9048
|
}
|
|
8990
|
-
const path =
|
|
9049
|
+
const path = join22(dirname10(dir), worktreeForTicket(dir, cfg, t, role));
|
|
8991
9050
|
if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
|
|
8992
9051
|
return { action: "cut-remote", path, branch: located.branch };
|
|
8993
9052
|
}
|
|
@@ -9041,7 +9100,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
9041
9100
|
if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
|
|
9042
9101
|
if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
|
|
9043
9102
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
9044
|
-
if (wt &&
|
|
9103
|
+
if (wt && existsSync21(wt.path)) {
|
|
9045
9104
|
if (status(wt.path).length > 0) return false;
|
|
9046
9105
|
if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
|
|
9047
9106
|
}
|
|
@@ -9049,7 +9108,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
9049
9108
|
}
|
|
9050
9109
|
async function finishRunWorktree(dir, cfg, branch) {
|
|
9051
9110
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
9052
|
-
if (!wt || !
|
|
9111
|
+
if (!wt || !existsSync21(wt.path)) return { kind: "none" };
|
|
9053
9112
|
if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
|
|
9054
9113
|
if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
|
|
9055
9114
|
if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
|
|
@@ -9061,7 +9120,7 @@ async function finishRunWorktree(dir, cfg, branch) {
|
|
|
9061
9120
|
if (pids.length) await killGently(pids);
|
|
9062
9121
|
}
|
|
9063
9122
|
gitOk(dir, ["worktree", "remove", wt.path]);
|
|
9064
|
-
return
|
|
9123
|
+
return existsSync21(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
|
|
9065
9124
|
}
|
|
9066
9125
|
|
|
9067
9126
|
// src/handoff-guard.ts
|
|
@@ -9444,20 +9503,20 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
|
|
|
9444
9503
|
|
|
9445
9504
|
// src/pending-handoff.ts
|
|
9446
9505
|
var PENDING_MAX_AGE_MS = 48 * 36e5;
|
|
9447
|
-
var pendingDirFor = (stateDir) =>
|
|
9448
|
-
var stateDirOfStream = (streamPath) =>
|
|
9506
|
+
var pendingDirFor = (stateDir) => join23(stateDir, "pending-handoffs");
|
|
9507
|
+
var stateDirOfStream = (streamPath) => join23(streamPath, "..", "..");
|
|
9449
9508
|
function writePendingHandoff(stateDir, rec) {
|
|
9450
9509
|
const dir = pendingDirFor(stateDir);
|
|
9451
|
-
|
|
9452
|
-
|
|
9510
|
+
mkdirSync15(dir, { recursive: true });
|
|
9511
|
+
writeFileSync16(join23(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
|
|
9453
9512
|
}
|
|
9454
9513
|
function listPendingHandoffs(stateDir, route2) {
|
|
9455
9514
|
const dir = pendingDirFor(stateDir);
|
|
9456
|
-
if (!
|
|
9515
|
+
if (!existsSync22(dir)) return [];
|
|
9457
9516
|
const out = [];
|
|
9458
9517
|
for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
9459
9518
|
try {
|
|
9460
|
-
const rec = JSON.parse(
|
|
9519
|
+
const rec = JSON.parse(readFileSync20(join23(dir, f), "utf8"));
|
|
9461
9520
|
if (rec.version === 1 && rec.ticketRecordId && (!route2 || rec.route === route2)) out.push(rec);
|
|
9462
9521
|
} catch {
|
|
9463
9522
|
}
|
|
@@ -9465,7 +9524,7 @@ function listPendingHandoffs(stateDir, route2) {
|
|
|
9465
9524
|
return out;
|
|
9466
9525
|
}
|
|
9467
9526
|
function removePendingHandoff(stateDir, ticketRecordId) {
|
|
9468
|
-
rmSync12(
|
|
9527
|
+
rmSync12(join23(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
|
|
9469
9528
|
}
|
|
9470
9529
|
function pendingHandoffFor(stateDir, ticketRecordId) {
|
|
9471
9530
|
return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
|
|
@@ -9575,8 +9634,8 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
|
|
|
9575
9634
|
}
|
|
9576
9635
|
|
|
9577
9636
|
// src/skills.ts
|
|
9578
|
-
import { readFileSync as
|
|
9579
|
-
import { join as
|
|
9637
|
+
import { readFileSync as readFileSync22, readdirSync as readdirSync8, existsSync as existsSync24 } from "fs";
|
|
9638
|
+
import { join as join25 } from "path";
|
|
9580
9639
|
import { parse as parseYaml } from "yaml";
|
|
9581
9640
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
9582
9641
|
|
|
@@ -9592,8 +9651,8 @@ ${additions}`;
|
|
|
9592
9651
|
}
|
|
9593
9652
|
|
|
9594
9653
|
// src/agents.ts
|
|
9595
|
-
import { readFileSync as
|
|
9596
|
-
import { join as
|
|
9654
|
+
import { readFileSync as readFileSync21, existsSync as existsSync23 } from "fs";
|
|
9655
|
+
import { join as join24 } from "path";
|
|
9597
9656
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
|
|
9598
9657
|
var PERSONA_NAME = {
|
|
9599
9658
|
dev: "Developer",
|
|
@@ -9607,12 +9666,12 @@ var CREW_AGENT_COLUMN = "agent_id";
|
|
|
9607
9666
|
var AgentsSyncError = class extends Error {
|
|
9608
9667
|
};
|
|
9609
9668
|
function personaDefaultPrompt(promptsDir, role) {
|
|
9610
|
-
const briefPath =
|
|
9611
|
-
if (!
|
|
9612
|
-
if (SELF_CONTAINED_ROLES.has(role)) return
|
|
9613
|
-
const commonPath =
|
|
9614
|
-
if (!
|
|
9615
|
-
return `${
|
|
9669
|
+
const briefPath = join24(promptsDir, "personas", `lane-${role}.md`);
|
|
9670
|
+
if (!existsSync23(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
|
|
9671
|
+
if (SELF_CONTAINED_ROLES.has(role)) return readFileSync21(briefPath, "utf8");
|
|
9672
|
+
const commonPath = join24(promptsDir, "personas", "common.md");
|
|
9673
|
+
if (!existsSync23(commonPath)) return readFileSync21(briefPath, "utf8");
|
|
9674
|
+
return `${readFileSync21(commonPath, "utf8")}${readFileSync21(briefPath, "utf8")}`;
|
|
9616
9675
|
}
|
|
9617
9676
|
async function resolveAgentId(route2, role, opts) {
|
|
9618
9677
|
const cached = route2.resolved?.agentPersonas?.[role]?.agentId;
|
|
@@ -10059,7 +10118,7 @@ function describeCrewLink(o) {
|
|
|
10059
10118
|
var SkillSyncError = class extends Error {
|
|
10060
10119
|
};
|
|
10061
10120
|
function parseSkillFile(path) {
|
|
10062
|
-
const text2 =
|
|
10121
|
+
const text2 = readFileSync22(path, "utf8");
|
|
10063
10122
|
const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text2);
|
|
10064
10123
|
if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
|
|
10065
10124
|
const frontmatter = match[1];
|
|
@@ -10072,9 +10131,9 @@ function parseSkillFile(path) {
|
|
|
10072
10131
|
return { name: meta.name, description: meta.description, prompt: body, file: path };
|
|
10073
10132
|
}
|
|
10074
10133
|
function listLocalSkills(promptsDir) {
|
|
10075
|
-
const dir =
|
|
10076
|
-
if (!
|
|
10077
|
-
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(
|
|
10134
|
+
const dir = join25(promptsDir, "skills");
|
|
10135
|
+
if (!existsSync24(dir)) return [];
|
|
10136
|
+
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join25(dir, f)));
|
|
10078
10137
|
}
|
|
10079
10138
|
async function syncSkills(route2, opts) {
|
|
10080
10139
|
if (!route2.resolved) {
|
|
@@ -10259,9 +10318,9 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
|
|
|
10259
10318
|
|
|
10260
10319
|
// src/runner.ts
|
|
10261
10320
|
import { spawn as spawn3, execFile, execFileSync as execFileSync6 } from "child_process";
|
|
10262
|
-
import { mkdtempSync, mkdirSync as
|
|
10321
|
+
import { mkdtempSync, mkdirSync as mkdirSync16, writeFileSync as writeFileSync17, rmSync as rmSync13 } from "fs";
|
|
10263
10322
|
import { tmpdir as tmpdir2 } from "os";
|
|
10264
|
-
import { basename as basename8, dirname as dirname11, join as
|
|
10323
|
+
import { basename as basename8, dirname as dirname11, join as join26 } from "path";
|
|
10265
10324
|
import { promisify } from "util";
|
|
10266
10325
|
|
|
10267
10326
|
// src/run-limit.ts
|
|
@@ -10390,8 +10449,8 @@ var SandboxRunner = class {
|
|
|
10390
10449
|
throw new Error(`cannot generate the sandbox policy: ${e.message}`);
|
|
10391
10450
|
}
|
|
10392
10451
|
policyFile = policyPathFor(plan.streamPath);
|
|
10393
|
-
|
|
10394
|
-
|
|
10452
|
+
mkdirSync16(dirname11(policyFile), { recursive: true });
|
|
10453
|
+
writeFileSync17(policyFile, generated.yaml, { mode: 384 });
|
|
10395
10454
|
}
|
|
10396
10455
|
const create2 = ["sandbox", "create", "--name", name, "--detach"];
|
|
10397
10456
|
if (policyFile) create2.push("--policy", policyFile);
|
|
@@ -10434,10 +10493,10 @@ var SandboxRunner = class {
|
|
|
10434
10493
|
const harness = cfg.agentBin ?? basename8(plan.bin);
|
|
10435
10494
|
const viaUpload = Buffer.byteLength(plan.prompt) > SANDBOX_STDIN_LIMIT;
|
|
10436
10495
|
if (viaUpload) {
|
|
10437
|
-
const tmp = mkdtempSync(
|
|
10496
|
+
const tmp = mkdtempSync(join26(tmpdir2(), "crew-prompt-"));
|
|
10438
10497
|
try {
|
|
10439
|
-
const file =
|
|
10440
|
-
|
|
10498
|
+
const file = join26(tmp, "prompt.txt");
|
|
10499
|
+
writeFileSync17(file, plan.prompt, { mode: 384 });
|
|
10441
10500
|
await this.os(["sandbox", "upload", name, file, SANDBOX_PROMPT_PATH]);
|
|
10442
10501
|
} finally {
|
|
10443
10502
|
rmSync13(tmp, { recursive: true, force: true });
|
|
@@ -10571,6 +10630,15 @@ function sandboxPolicyInputFor(route2, toolchain, o) {
|
|
|
10571
10630
|
...o.sandbox?.workDir ? { workDir: o.sandbox.workDir } : {}
|
|
10572
10631
|
};
|
|
10573
10632
|
}
|
|
10633
|
+
function lspPluginDirsFor(route2, repo, stateDir) {
|
|
10634
|
+
try {
|
|
10635
|
+
const dir = reposOf(route2).find((r) => r.name === repo)?.dir ?? route2.dir;
|
|
10636
|
+
const flavor = lspFlavor(loadRepoConfig(dir)?.hooks.intel);
|
|
10637
|
+
return flavor ? [ensureLspPlugin(stateDir, flavor, dir)] : void 0;
|
|
10638
|
+
} catch {
|
|
10639
|
+
return void 0;
|
|
10640
|
+
}
|
|
10641
|
+
}
|
|
10574
10642
|
var AgentSpawnError = class extends AgentError {
|
|
10575
10643
|
};
|
|
10576
10644
|
var WorkspaceAuthRefused = class extends AgentSpawnError {
|
|
@@ -10581,7 +10649,7 @@ var ALLOWANCE_REFUSAL_MARKER = "AI_ALLOWANCE_EXCEEDED";
|
|
|
10581
10649
|
function streamShowsAllowanceRefusal(streamPath) {
|
|
10582
10650
|
let raw;
|
|
10583
10651
|
try {
|
|
10584
|
-
raw =
|
|
10652
|
+
raw = readFileSync23(streamPath, "utf8");
|
|
10585
10653
|
} catch {
|
|
10586
10654
|
return false;
|
|
10587
10655
|
}
|
|
@@ -10604,26 +10672,26 @@ function streamShowsAllowanceRefusal(streamPath) {
|
|
|
10604
10672
|
}
|
|
10605
10673
|
return false;
|
|
10606
10674
|
}
|
|
10607
|
-
var digestPathFor = (stateDir, routeLabel, role, runId) =>
|
|
10675
|
+
var digestPathFor = (stateDir, routeLabel, role, runId) => join27(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
10608
10676
|
function planAgentRun(o) {
|
|
10609
|
-
const briefPath =
|
|
10610
|
-
if (!
|
|
10677
|
+
const briefPath = join27(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
10678
|
+
if (!existsSync25(briefPath)) {
|
|
10611
10679
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
10612
10680
|
}
|
|
10613
|
-
const commonPath =
|
|
10614
|
-
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) &&
|
|
10681
|
+
const commonPath = join27(o.route.promptsDir, "personas", "common.md");
|
|
10682
|
+
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync25(commonPath);
|
|
10615
10683
|
const now = o.now ?? (() => Date.now());
|
|
10616
10684
|
const routeLabel = routeSlug(o.route.route);
|
|
10617
10685
|
const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
|
|
10618
|
-
const digestPath2 = ownDigest &&
|
|
10686
|
+
const digestPath2 = ownDigest && existsSync25(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
|
|
10619
10687
|
let digest;
|
|
10620
10688
|
let ageSeconds;
|
|
10621
|
-
if (
|
|
10689
|
+
if (existsSync25(digestPath2)) {
|
|
10622
10690
|
ageSeconds = Math.floor((now() - statSync8(digestPath2).mtimeMs) / 1e3);
|
|
10623
|
-
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest =
|
|
10691
|
+
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync23(digestPath2, "utf8");
|
|
10624
10692
|
}
|
|
10625
|
-
const common = usesCommon ?
|
|
10626
|
-
const brief =
|
|
10693
|
+
const common = usesCommon ? readFileSync23(commonPath, "utf8") : "";
|
|
10694
|
+
const brief = readFileSync23(briefPath, "utf8");
|
|
10627
10695
|
const effectiveCommon = o.divergedPrompt ? "" : common;
|
|
10628
10696
|
const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
|
|
10629
10697
|
const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
|
|
@@ -10646,7 +10714,7 @@ function planAgentRun(o) {
|
|
|
10646
10714
|
digest
|
|
10647
10715
|
});
|
|
10648
10716
|
const promptSha = createHash4("sha256").update(expanded.text).digest("hex");
|
|
10649
|
-
const streamsDir =
|
|
10717
|
+
const streamsDir = join27(o.stateDir, "streams");
|
|
10650
10718
|
const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
|
|
10651
10719
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
10652
10720
|
const { model } = seat;
|
|
@@ -10664,6 +10732,7 @@ function planAgentRun(o) {
|
|
|
10664
10732
|
const invocation = adapter.invocation(toolchain, {
|
|
10665
10733
|
role: o.role,
|
|
10666
10734
|
model,
|
|
10735
|
+
pluginDirs: adapter.harness === "claude" ? lspPluginDirsFor(o.route, o.repo, o.stateDir) : void 0,
|
|
10667
10736
|
// Skipped when there's no key to send: a route `crew connect` hasn't
|
|
10668
10737
|
// resolved yet has nothing to authenticate the MCP connection with either.
|
|
10669
10738
|
mcp: o.apiKey ? { url: `${o.route.baseUrl}/api/mcp`, keyVar: API_KEY_VAR } : void 0
|
|
@@ -10736,8 +10805,8 @@ function planAgentRun(o) {
|
|
|
10736
10805
|
digestAttached: digest !== void 0,
|
|
10737
10806
|
digestAgeSeconds: ageSeconds,
|
|
10738
10807
|
model,
|
|
10739
|
-
streamPath:
|
|
10740
|
-
eventsPath:
|
|
10808
|
+
streamPath: join27(streamsDir, `${base}.jsonl`),
|
|
10809
|
+
eventsPath: join27(streamsDir, `${base}.events.jsonl`),
|
|
10741
10810
|
ticket: o.ticket,
|
|
10742
10811
|
ticketRecordId: o.ticketRecordId ?? null,
|
|
10743
10812
|
repo: o.repo ?? null,
|
|
@@ -10768,7 +10837,7 @@ function describePlan(p) {
|
|
|
10768
10837
|
var pidPathFor = (streamPath) => `${streamPath}.pid`;
|
|
10769
10838
|
function readRunSidecar(streamPath) {
|
|
10770
10839
|
try {
|
|
10771
|
-
const sc = JSON.parse(
|
|
10840
|
+
const sc = JSON.parse(readFileSync23(pidPathFor(streamPath), "utf8"));
|
|
10772
10841
|
return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
|
|
10773
10842
|
} catch {
|
|
10774
10843
|
return null;
|
|
@@ -10815,7 +10884,7 @@ async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMi
|
|
|
10815
10884
|
var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
|
|
10816
10885
|
function openSink(path) {
|
|
10817
10886
|
try {
|
|
10818
|
-
|
|
10887
|
+
mkdirSync17(dirname12(path), { recursive: true });
|
|
10819
10888
|
return createWriteStream(path, { flags: "a" });
|
|
10820
10889
|
} catch {
|
|
10821
10890
|
return void 0;
|
|
@@ -11001,7 +11070,7 @@ var RunObserver = class {
|
|
|
11001
11070
|
};
|
|
11002
11071
|
function writeRunMeta(id, startedAt, liveTicket, extra) {
|
|
11003
11072
|
try {
|
|
11004
|
-
|
|
11073
|
+
writeFileSync18(`${id.streamPath}.meta.json`, JSON.stringify({
|
|
11005
11074
|
route: id.route,
|
|
11006
11075
|
role: id.role,
|
|
11007
11076
|
hint: id.ticket,
|
|
@@ -11167,7 +11236,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
11167
11236
|
const id = identityOf(plan, adapter.harness);
|
|
11168
11237
|
let streamFd;
|
|
11169
11238
|
try {
|
|
11170
|
-
|
|
11239
|
+
mkdirSync17(dirname12(plan.streamPath), { recursive: true });
|
|
11171
11240
|
streamFd = openSync4(plan.streamPath, "a");
|
|
11172
11241
|
} catch (e) {
|
|
11173
11242
|
void endSink(eventsSink);
|
|
@@ -11225,7 +11294,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
11225
11294
|
promptSha: plan.promptSha
|
|
11226
11295
|
};
|
|
11227
11296
|
try {
|
|
11228
|
-
|
|
11297
|
+
writeFileSync18(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
|
|
11229
11298
|
} catch {
|
|
11230
11299
|
}
|
|
11231
11300
|
}
|
|
@@ -11339,21 +11408,21 @@ var pidAlive = (pid) => {
|
|
|
11339
11408
|
};
|
|
11340
11409
|
function runFinished(streamPath) {
|
|
11341
11410
|
const path = `${streamPath}.meta.json`;
|
|
11342
|
-
if (!
|
|
11411
|
+
if (!existsSync26(path)) return false;
|
|
11343
11412
|
try {
|
|
11344
|
-
return JSON.parse(
|
|
11413
|
+
return JSON.parse(readFileSync24(path, "utf8")).running !== true;
|
|
11345
11414
|
} catch {
|
|
11346
11415
|
return true;
|
|
11347
11416
|
}
|
|
11348
11417
|
}
|
|
11349
11418
|
function orphanedStreams(o) {
|
|
11350
|
-
const dir =
|
|
11351
|
-
if (!
|
|
11419
|
+
const dir = join28(o.stateDir, "streams");
|
|
11420
|
+
if (!existsSync26(dir)) return [];
|
|
11352
11421
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
11353
11422
|
const out = [];
|
|
11354
11423
|
for (const name of readdirSync9(dir)) {
|
|
11355
11424
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
11356
|
-
const streamPath =
|
|
11425
|
+
const streamPath = join28(dir, name);
|
|
11357
11426
|
if (runFinished(streamPath)) continue;
|
|
11358
11427
|
const sidecar = readRunSidecar(streamPath);
|
|
11359
11428
|
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
@@ -11406,15 +11475,15 @@ async function reconcileRuns(o) {
|
|
|
11406
11475
|
}
|
|
11407
11476
|
|
|
11408
11477
|
// src/live-runs.ts
|
|
11409
|
-
import { existsSync as
|
|
11410
|
-
import { join as
|
|
11478
|
+
import { existsSync as existsSync27, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
|
|
11479
|
+
import { join as join29 } from "path";
|
|
11411
11480
|
function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
11412
|
-
const dir =
|
|
11413
|
-
if (!
|
|
11481
|
+
const dir = join29(stateDir, "streams");
|
|
11482
|
+
if (!existsSync27(dir)) return [];
|
|
11414
11483
|
const out = [];
|
|
11415
11484
|
for (const name of readdirSync10(dir)) {
|
|
11416
11485
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
11417
|
-
const streamPath =
|
|
11486
|
+
const streamPath = join29(dir, name);
|
|
11418
11487
|
if (runFinished(streamPath)) continue;
|
|
11419
11488
|
const sidecar = readRunSidecar(streamPath);
|
|
11420
11489
|
if (!sidecar || !isAlive(sidecar)) continue;
|
|
@@ -11424,12 +11493,12 @@ function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
|
11424
11493
|
}
|
|
11425
11494
|
function runActivity(stateDir, isAlive = runIsAlive) {
|
|
11426
11495
|
const activeRuns = liveRuns(stateDir, isAlive).length;
|
|
11427
|
-
const dir =
|
|
11496
|
+
const dir = join29(stateDir, "streams");
|
|
11428
11497
|
let newest = 0;
|
|
11429
|
-
if (
|
|
11498
|
+
if (existsSync27(dir)) {
|
|
11430
11499
|
for (const name of readdirSync10(dir)) {
|
|
11431
11500
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
11432
|
-
const streamPath =
|
|
11501
|
+
const streamPath = join29(dir, name);
|
|
11433
11502
|
if (!runFinished(streamPath)) continue;
|
|
11434
11503
|
try {
|
|
11435
11504
|
newest = Math.max(newest, statSync9(`${streamPath}.meta.json`).mtimeMs);
|
|
@@ -11800,7 +11869,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
11800
11869
|
if (host === "windows") return [];
|
|
11801
11870
|
const ctx = installContext();
|
|
11802
11871
|
const systemd = detectSystemd();
|
|
11803
|
-
return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) =>
|
|
11872
|
+
return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync28(p));
|
|
11804
11873
|
};
|
|
11805
11874
|
return {
|
|
11806
11875
|
liveRuns: () => liveRuns(stateDir).map((r) => ({ role: r.role, route: r.route, pid: r.pid, ticketRecordId: r.ticketRecordId ?? void 0, streamPath: r.streamPath })),
|
|
@@ -11861,7 +11930,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
11861
11930
|
},
|
|
11862
11931
|
remaining() {
|
|
11863
11932
|
const left = [];
|
|
11864
|
-
if (
|
|
11933
|
+
if (existsSync28(cfg.configFile)) left.push(`config ${cfg.configFile}`);
|
|
11865
11934
|
for (const h of forgeHosts()) if (hasForgeToken(cfg.ship.forges, h, stateDir)) left.push(`forge token for ${h}`);
|
|
11866
11935
|
try {
|
|
11867
11936
|
for (const k of readdirSync11(keysDir(stateDir))) left.push(`seat key ${k}`);
|
|
@@ -12469,19 +12538,19 @@ function planSandbox(repo, decl, warm = {}) {
|
|
|
12469
12538
|
const layer = repoLayer(repo, decl, crew, warm);
|
|
12470
12539
|
return { repo, tag: layer.tag, claude: SANDBOX_CLAUDE_VERSION, layers: [base, crew, layer] };
|
|
12471
12540
|
}
|
|
12472
|
-
var sandboxStatePath = (stateDir) =>
|
|
12541
|
+
var sandboxStatePath = (stateDir) => join30(stateDir, "sandbox-images.json");
|
|
12473
12542
|
function readSandboxState(stateDir) {
|
|
12474
12543
|
try {
|
|
12475
|
-
const v = JSON.parse(
|
|
12544
|
+
const v = JSON.parse(readFileSync25(sandboxStatePath(stateDir), "utf8"));
|
|
12476
12545
|
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
12477
12546
|
} catch {
|
|
12478
12547
|
return {};
|
|
12479
12548
|
}
|
|
12480
12549
|
}
|
|
12481
12550
|
function writeSandboxState(stateDir, state) {
|
|
12482
|
-
|
|
12551
|
+
mkdirSync18(stateDir, { recursive: true });
|
|
12483
12552
|
const file = sandboxStatePath(stateDir);
|
|
12484
|
-
|
|
12553
|
+
writeFileSync19(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
|
|
12485
12554
|
`);
|
|
12486
12555
|
renameSync3(`${file}.tmp`, file);
|
|
12487
12556
|
}
|
|
@@ -12501,7 +12570,7 @@ var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((re
|
|
|
12501
12570
|
child.stdout.on("data", (d) => chunks.push(d));
|
|
12502
12571
|
child.stderr.on("data", (d) => chunks.push(d));
|
|
12503
12572
|
const done = (code, extra = "") => {
|
|
12504
|
-
|
|
12573
|
+
writeFileSync19(logPath, `$ ${engine} ${args.join(" ")}
|
|
12505
12574
|
${Buffer.concat(chunks).toString("utf8")}${extra}`, { flag: "a" });
|
|
12506
12575
|
resolve8(code);
|
|
12507
12576
|
};
|
|
@@ -12523,7 +12592,7 @@ var engineRemoveImage = (engine = "docker", exec = realExec2) => (tag) => {
|
|
|
12523
12592
|
} catch {
|
|
12524
12593
|
}
|
|
12525
12594
|
};
|
|
12526
|
-
var sandboxLogDir = (stateDir) =>
|
|
12595
|
+
var sandboxLogDir = (stateDir) => join30(stateDir, "sandbox-logs");
|
|
12527
12596
|
async function buildSandboxImage(plan, deps) {
|
|
12528
12597
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
12529
12598
|
const state = readSandboxState(deps.stateDir);
|
|
@@ -12532,20 +12601,20 @@ async function buildSandboxImage(plan, deps) {
|
|
|
12532
12601
|
return { kind: "reused", tag: plan.tag };
|
|
12533
12602
|
}
|
|
12534
12603
|
const logDir = sandboxLogDir(deps.stateDir);
|
|
12535
|
-
|
|
12536
|
-
const logPath =
|
|
12604
|
+
mkdirSync18(logDir, { recursive: true });
|
|
12605
|
+
const logPath = join30(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
|
|
12537
12606
|
const builtLayers = [];
|
|
12538
12607
|
for (const layer of plan.layers) {
|
|
12539
12608
|
if (deps.hasImage(layer.tag)) continue;
|
|
12540
|
-
const ctx =
|
|
12541
|
-
|
|
12609
|
+
const ctx = join30(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
|
|
12610
|
+
mkdirSync18(ctx, { recursive: true });
|
|
12542
12611
|
try {
|
|
12543
|
-
|
|
12612
|
+
writeFileSync19(join30(ctx, "Dockerfile"), layer.dockerfile);
|
|
12544
12613
|
for (const [name, content] of Object.entries(layer.files)) {
|
|
12545
|
-
|
|
12546
|
-
|
|
12614
|
+
mkdirSync18(dirname13(join30(ctx, name)), { recursive: true });
|
|
12615
|
+
writeFileSync19(join30(ctx, name), content);
|
|
12547
12616
|
}
|
|
12548
|
-
const code = await deps.run(["build", "--tag", layer.tag, "--file",
|
|
12617
|
+
const code = await deps.run(["build", "--tag", layer.tag, "--file", join30(ctx, "Dockerfile"), ctx], logPath);
|
|
12549
12618
|
if (code !== 0) {
|
|
12550
12619
|
state[plan.repo] = { ...entry, failure: { tag: plan.tag, at: now().toISOString(), logPath } };
|
|
12551
12620
|
writeSandboxState(deps.stateDir, state);
|
|
@@ -12734,9 +12803,9 @@ function managedRuntimeWarnings(kind, ship) {
|
|
|
12734
12803
|
|
|
12735
12804
|
// src/selftest.ts
|
|
12736
12805
|
import { spawn as spawn6 } from "child_process";
|
|
12737
|
-
import { existsSync as
|
|
12806
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync19, mkdtempSync as mkdtempSync2, readFileSync as readFileSync26, rmSync as rmSync15, writeFileSync as writeFileSync20 } from "fs";
|
|
12738
12807
|
import { tmpdir as tmpdir4 } from "os";
|
|
12739
|
-
import { join as
|
|
12808
|
+
import { join as join31 } from "path";
|
|
12740
12809
|
var SELFTEST_TIMEOUT_MS = 12e4;
|
|
12741
12810
|
var KEY_VAR = "CREW_API_KEY";
|
|
12742
12811
|
var SELFTEST_PROMPT = "This is a read-only connectivity check. Use the tracker MCP tool to read this workspace (the workspaces find-one tool, using the workspace id below) and reply with the workspace's name alone, on one line. Do not write anything, do not create files, and do not use any other tool.\n\nWorkspace id: ";
|
|
@@ -12897,7 +12966,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
12897
12966
|
...adapter.runEnv(spec),
|
|
12898
12967
|
[KEY_VAR]: i.apiKey
|
|
12899
12968
|
};
|
|
12900
|
-
const cwd = mkdtempSync2(
|
|
12969
|
+
const cwd = mkdtempSync2(join31(tmpdir4(), "crew-selftest-"));
|
|
12901
12970
|
try {
|
|
12902
12971
|
const run3 = await deps.run({
|
|
12903
12972
|
bin: invocation.bin,
|
|
@@ -12930,11 +12999,11 @@ function formatResult(r) {
|
|
|
12930
12999
|
const secs = `${(r.ms / 1e3).toFixed(1)}s`;
|
|
12931
13000
|
return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
|
|
12932
13001
|
}
|
|
12933
|
-
var fileFor2 = (stateDir) =>
|
|
13002
|
+
var fileFor2 = (stateDir) => join31(stateDir, "selftest.json");
|
|
12934
13003
|
function readSelftest(stateDir) {
|
|
12935
13004
|
try {
|
|
12936
13005
|
const p = fileFor2(stateDir);
|
|
12937
|
-
return
|
|
13006
|
+
return existsSync29(p) ? JSON.parse(readFileSync26(p, "utf8")) : {};
|
|
12938
13007
|
} catch {
|
|
12939
13008
|
return {};
|
|
12940
13009
|
}
|
|
@@ -12942,8 +13011,8 @@ function readSelftest(stateDir) {
|
|
|
12942
13011
|
function recordSelftest(stateDir, results) {
|
|
12943
13012
|
const all = readSelftest(stateDir);
|
|
12944
13013
|
for (const r of results) all[r.toolchain] = r;
|
|
12945
|
-
|
|
12946
|
-
|
|
13014
|
+
mkdirSync19(stateDir, { recursive: true });
|
|
13015
|
+
writeFileSync20(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
|
|
12947
13016
|
`);
|
|
12948
13017
|
}
|
|
12949
13018
|
function doctorSelftestLines(stored, toolchains) {
|
|
@@ -12955,7 +13024,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
12955
13024
|
}
|
|
12956
13025
|
|
|
12957
13026
|
// src/cli.ts
|
|
12958
|
-
import { resolve as resolve7, dirname as dirname32, join as
|
|
13027
|
+
import { resolve as resolve7, dirname as dirname32, join as join53, basename as basename16 } from "path";
|
|
12959
13028
|
import { fileURLToPath } from "url";
|
|
12960
13029
|
import { hostname as hostname4, homedir as homedir11 } from "os";
|
|
12961
13030
|
import { spawnSync as spawnSync11 } from "child_process";
|
|
@@ -13152,14 +13221,14 @@ function rosterMarkdown(roster, meId) {
|
|
|
13152
13221
|
}
|
|
13153
13222
|
|
|
13154
13223
|
// src/connect-resume.ts
|
|
13155
|
-
import { mkdirSync as
|
|
13156
|
-
import { dirname as dirname14, join as
|
|
13224
|
+
import { mkdirSync as mkdirSync20, readFileSync as readFileSync27, rmSync as rmSync16, writeFileSync as writeFileSync21 } from "fs";
|
|
13225
|
+
import { dirname as dirname14, join as join32 } from "path";
|
|
13157
13226
|
function lastSessionFile(stateDir) {
|
|
13158
|
-
return
|
|
13227
|
+
return join32(stateDir, "connect-session.json");
|
|
13159
13228
|
}
|
|
13160
13229
|
function readPointers(stateDir) {
|
|
13161
13230
|
try {
|
|
13162
|
-
const parsed = JSON.parse(
|
|
13231
|
+
const parsed = JSON.parse(readFileSync27(lastSessionFile(stateDir), "utf8"));
|
|
13163
13232
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
13164
13233
|
const out = {};
|
|
13165
13234
|
for (const [host, v] of Object.entries(parsed)) {
|
|
@@ -13173,8 +13242,8 @@ function readPointers(stateDir) {
|
|
|
13173
13242
|
}
|
|
13174
13243
|
function writePointers(stateDir, pointers) {
|
|
13175
13244
|
const file = lastSessionFile(stateDir);
|
|
13176
|
-
|
|
13177
|
-
|
|
13245
|
+
mkdirSync20(dirname14(file), { recursive: true });
|
|
13246
|
+
writeFileSync21(file, `${JSON.stringify(pointers, null, 2)}
|
|
13178
13247
|
`, { mode: 384 });
|
|
13179
13248
|
}
|
|
13180
13249
|
function rememberSession(stateDir, host, workspaceSlug) {
|
|
@@ -13259,15 +13328,15 @@ async function signOutSessions(o) {
|
|
|
13259
13328
|
}
|
|
13260
13329
|
|
|
13261
13330
|
// src/reset.ts
|
|
13262
|
-
import { existsSync as
|
|
13263
|
-
import { dirname as dirname15, join as
|
|
13331
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync21, readFileSync as readFileSync28, readdirSync as readdirSync12, rmSync as rmSync17, statSync as statSync10, writeFileSync as writeFileSync22 } from "fs";
|
|
13332
|
+
import { dirname as dirname15, join as join33 } from "path";
|
|
13264
13333
|
import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
|
|
13265
13334
|
function clonedCheckoutsFile(stateDir) {
|
|
13266
|
-
return
|
|
13335
|
+
return join33(stateDir, "cloned-checkouts.json");
|
|
13267
13336
|
}
|
|
13268
13337
|
function readClonedCheckouts(stateDir) {
|
|
13269
13338
|
try {
|
|
13270
|
-
const parsed = JSON.parse(
|
|
13339
|
+
const parsed = JSON.parse(readFileSync28(clonedCheckoutsFile(stateDir), "utf8"));
|
|
13271
13340
|
return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
|
|
13272
13341
|
} catch {
|
|
13273
13342
|
return [];
|
|
@@ -13277,8 +13346,8 @@ function recordClonedCheckout(stateDir, dir) {
|
|
|
13277
13346
|
const known = readClonedCheckouts(stateDir);
|
|
13278
13347
|
if (known.includes(dir)) return;
|
|
13279
13348
|
const file = clonedCheckoutsFile(stateDir);
|
|
13280
|
-
|
|
13281
|
-
|
|
13349
|
+
mkdirSync21(dirname15(file), { recursive: true });
|
|
13350
|
+
writeFileSync22(file, `${JSON.stringify([...known, dir], null, 2)}
|
|
13282
13351
|
`);
|
|
13283
13352
|
}
|
|
13284
13353
|
var mark = (dryRun) => dryRun ? "would-remove" : "removed";
|
|
@@ -13332,7 +13401,7 @@ async function runReset(o, fx) {
|
|
|
13332
13401
|
}
|
|
13333
13402
|
if (o.beforeState) steps.push(...await o.beforeState());
|
|
13334
13403
|
steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
|
|
13335
|
-
if (
|
|
13404
|
+
if (existsSync30(o.configFile)) {
|
|
13336
13405
|
if (!o.dryRun) rmSync17(o.configFile, { force: true });
|
|
13337
13406
|
steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
|
|
13338
13407
|
} else {
|
|
@@ -13341,7 +13410,7 @@ async function runReset(o, fx) {
|
|
|
13341
13410
|
return steps;
|
|
13342
13411
|
}
|
|
13343
13412
|
function removeCheckout(dir, dryRun, fx) {
|
|
13344
|
-
if (!
|
|
13413
|
+
if (!existsSync30(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
|
|
13345
13414
|
const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
|
|
13346
13415
|
if (dirty.length) {
|
|
13347
13416
|
return { step: "checkout", target: dir, action: "kept", detail: `has uncommitted changes (${dirty.length} path${dirty.length === 1 ? "" : "s"}: ${dirty.slice(0, 5).join(", ")}${dirty.length > 5 ? ", \u2026" : ""})` };
|
|
@@ -13354,15 +13423,15 @@ function removeCheckout(dir, dryRun, fx) {
|
|
|
13354
13423
|
}
|
|
13355
13424
|
function removeState(stateDir, dryRun) {
|
|
13356
13425
|
const out = [];
|
|
13357
|
-
const targets = [
|
|
13426
|
+
const targets = [join33(stateDir, "resolved"), join33(stateDir, "keys"), clonedCheckoutsFile(stateDir), join33(stateDir, "connect-session.json"), join33(stateDir, "reload-requested"), join33(stateDir, "daemon.started")];
|
|
13358
13427
|
try {
|
|
13359
13428
|
for (const name of readdirSync12(stateDir)) {
|
|
13360
|
-
if (name.startsWith(".") && statSync10(
|
|
13429
|
+
if (name.startsWith(".") && statSync10(join33(stateDir, name)).isFile()) targets.push(join33(stateDir, name));
|
|
13361
13430
|
}
|
|
13362
13431
|
} catch {
|
|
13363
13432
|
}
|
|
13364
13433
|
for (const t of targets) {
|
|
13365
|
-
if (!
|
|
13434
|
+
if (!existsSync30(t)) continue;
|
|
13366
13435
|
if (!dryRun) rmSync17(t, { recursive: true, force: true });
|
|
13367
13436
|
out.push({ step: "state", target: t, action: mark(dryRun) });
|
|
13368
13437
|
}
|
|
@@ -13377,7 +13446,7 @@ function wipeStateDir(stateDir, dryRun) {
|
|
|
13377
13446
|
return [{ step: "state", target: stateDir, action: "absent", detail: "no state directory" }];
|
|
13378
13447
|
}
|
|
13379
13448
|
if (!names.length) return [{ step: "state", target: stateDir, action: "absent", detail: "already empty" }];
|
|
13380
|
-
if (!dryRun) for (const n of names) rmSync17(
|
|
13449
|
+
if (!dryRun) for (const n of names) rmSync17(join33(stateDir, n), { recursive: true, force: true });
|
|
13381
13450
|
return [{ step: "state", target: stateDir, action: mark(dryRun), detail: `${names.length} entr${names.length === 1 ? "y" : "ies"}` }];
|
|
13382
13451
|
}
|
|
13383
13452
|
function describeResetStep(s) {
|
|
@@ -13458,9 +13527,9 @@ async function runDeprovision(o, fx, deps) {
|
|
|
13458
13527
|
}
|
|
13459
13528
|
|
|
13460
13529
|
// src/pool-member.ts
|
|
13461
|
-
import { mkdirSync as
|
|
13530
|
+
import { mkdirSync as mkdirSync22, readFileSync as readFileSync29, renameSync as renameSync4, writeFileSync as writeFileSync23 } from "fs";
|
|
13462
13531
|
import { homedir as homedir8 } from "os";
|
|
13463
|
-
import { dirname as dirname16, join as
|
|
13532
|
+
import { dirname as dirname16, join as join34 } from "path";
|
|
13464
13533
|
var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
|
|
13465
13534
|
function backoffMs(attempt) {
|
|
13466
13535
|
return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
|
|
@@ -13691,22 +13760,22 @@ async function runPoolMember(deps) {
|
|
|
13691
13760
|
}
|
|
13692
13761
|
}
|
|
13693
13762
|
function defaultMemberDir(env = process.env) {
|
|
13694
|
-
return env.CREW_POOL_DIR ??
|
|
13763
|
+
return env.CREW_POOL_DIR ?? join34(env.XDG_STATE_HOME ?? join34(homedir8(), ".local", "state"), "crew-pool-member");
|
|
13695
13764
|
}
|
|
13696
13765
|
function fileMemberStore(dir) {
|
|
13697
|
-
const file =
|
|
13766
|
+
const file = join34(dir, "member.json");
|
|
13698
13767
|
return {
|
|
13699
13768
|
read() {
|
|
13700
13769
|
try {
|
|
13701
|
-
return JSON.parse(
|
|
13770
|
+
return JSON.parse(readFileSync29(file, "utf8"));
|
|
13702
13771
|
} catch {
|
|
13703
13772
|
return {};
|
|
13704
13773
|
}
|
|
13705
13774
|
},
|
|
13706
13775
|
write(f) {
|
|
13707
|
-
|
|
13776
|
+
mkdirSync22(dirname16(file), { recursive: true, mode: 448 });
|
|
13708
13777
|
const tmp = `${file}.tmp`;
|
|
13709
|
-
|
|
13778
|
+
writeFileSync23(tmp, `${JSON.stringify(f)}
|
|
13710
13779
|
`, { mode: 384 });
|
|
13711
13780
|
renameSync4(tmp, file);
|
|
13712
13781
|
}
|
|
@@ -13758,16 +13827,16 @@ function hostArchitecture(arch = process.arch) {
|
|
|
13758
13827
|
|
|
13759
13828
|
// src/pool-member-host.ts
|
|
13760
13829
|
import "child_process";
|
|
13761
|
-
import { existsSync as
|
|
13830
|
+
import { existsSync as existsSync34 } from "fs";
|
|
13762
13831
|
import { hostname as hostname3 } from "os";
|
|
13763
13832
|
|
|
13764
13833
|
// src/machine-id.ts
|
|
13765
13834
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
13766
13835
|
import { createHash as createHash7 } from "crypto";
|
|
13767
|
-
import { existsSync as
|
|
13768
|
-
import { dirname as dirname17, join as
|
|
13836
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync23, readFileSync as readFileSync30, writeFileSync as writeFileSync24 } from "fs";
|
|
13837
|
+
import { dirname as dirname17, join as join35 } from "path";
|
|
13769
13838
|
function machineIdPathFor(stateDir) {
|
|
13770
|
-
return
|
|
13839
|
+
return join35(stateDir, "machine-id");
|
|
13771
13840
|
}
|
|
13772
13841
|
var sha256 = (s) => createHash7("sha256").update(s).digest("hex");
|
|
13773
13842
|
function readRawMachineId(platform2 = process.platform) {
|
|
@@ -13780,8 +13849,8 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
13780
13849
|
return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
|
|
13781
13850
|
}
|
|
13782
13851
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
13783
|
-
if (
|
|
13784
|
-
const id =
|
|
13852
|
+
if (existsSync31(p)) {
|
|
13853
|
+
const id = readFileSync30(p, "utf8").trim();
|
|
13785
13854
|
if (id) return id;
|
|
13786
13855
|
}
|
|
13787
13856
|
}
|
|
@@ -13792,15 +13861,15 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
13792
13861
|
function ensureMachineId(stateDir, opts = {}) {
|
|
13793
13862
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
13794
13863
|
const path = machineIdPathFor(stateDir);
|
|
13795
|
-
if (
|
|
13796
|
-
const cached =
|
|
13864
|
+
if (existsSync31(path)) {
|
|
13865
|
+
const cached = readFileSync30(path, "utf8").trim();
|
|
13797
13866
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
13798
13867
|
}
|
|
13799
13868
|
const raw = (opts.read ?? readRawMachineId)();
|
|
13800
13869
|
if (!raw) return void 0;
|
|
13801
13870
|
const hashed = sha256(raw);
|
|
13802
|
-
|
|
13803
|
-
|
|
13871
|
+
mkdirSync23(dirname17(path), { recursive: true });
|
|
13872
|
+
writeFileSync24(path, `${hashed}
|
|
13804
13873
|
`, { mode: 384 });
|
|
13805
13874
|
return hashed;
|
|
13806
13875
|
}
|
|
@@ -13813,7 +13882,7 @@ function matchShipRows(ships, shipName2, machineId) {
|
|
|
13813
13882
|
|
|
13814
13883
|
// src/provision.ts
|
|
13815
13884
|
import { spawnSync as spawnSync8 } from "child_process";
|
|
13816
|
-
import { existsSync as
|
|
13885
|
+
import { existsSync as existsSync32, readFileSync as readFileSync31, writeFileSync as writeFileSync25 } from "fs";
|
|
13817
13886
|
import { hostname as hostname2 } from "os";
|
|
13818
13887
|
import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
|
|
13819
13888
|
|
|
@@ -13822,7 +13891,7 @@ import { parseDocument as parseDocument2 } from "yaml";
|
|
|
13822
13891
|
|
|
13823
13892
|
// src/repos-cmd.ts
|
|
13824
13893
|
import { homedir as homedir9 } from "os";
|
|
13825
|
-
import { basename as basename10, join as
|
|
13894
|
+
import { basename as basename10, join as join36, resolve as resolve4 } from "path";
|
|
13826
13895
|
import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
|
|
13827
13896
|
var ReposError = class extends Error {
|
|
13828
13897
|
};
|
|
@@ -14100,7 +14169,7 @@ function planRepoClone(i) {
|
|
|
14100
14169
|
}
|
|
14101
14170
|
const ship = doc.get("ship", true);
|
|
14102
14171
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
14103
|
-
const target = i.dir ? expandDir(i.dir, i.base) :
|
|
14172
|
+
const target = i.dir ? expandDir(i.dir, i.base) : join36(expandDir(basePath, i.base), i.route.split("/")[0], name);
|
|
14104
14173
|
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
|
|
14105
14174
|
const forgeHost = i.resolved?.repoForgeHosts?.[id];
|
|
14106
14175
|
return { ...add, remote, target, ...forgeHost ? { forgeHost } : {} };
|
|
@@ -14407,7 +14476,7 @@ function realProvisionDeps(crewHome) {
|
|
|
14407
14476
|
};
|
|
14408
14477
|
const configFile = () => {
|
|
14409
14478
|
const f = findConfigFile(crewHome);
|
|
14410
|
-
return
|
|
14479
|
+
return existsSync32(f) ? f : null;
|
|
14411
14480
|
};
|
|
14412
14481
|
return {
|
|
14413
14482
|
crew: spawnCrew,
|
|
@@ -14415,13 +14484,13 @@ function realProvisionDeps(crewHome) {
|
|
|
14415
14484
|
enableRoute(route2) {
|
|
14416
14485
|
const file = configFile();
|
|
14417
14486
|
if (!file) return false;
|
|
14418
|
-
const text2 =
|
|
14487
|
+
const text2 = readFileSync31(file, "utf8");
|
|
14419
14488
|
const doc = parseDocument3(text2);
|
|
14420
14489
|
const routes = doc.get("routes");
|
|
14421
14490
|
const entry = routes?.items?.find((r) => r.get("route") === route2);
|
|
14422
14491
|
if (!entry) throw new Error(`route ${route2} is not in ${file}`);
|
|
14423
14492
|
if (entry.get("enabled") === true) return false;
|
|
14424
|
-
|
|
14493
|
+
writeFileSync25(file, planEnable(text2, route2));
|
|
14425
14494
|
return true;
|
|
14426
14495
|
},
|
|
14427
14496
|
installState() {
|
|
@@ -14441,14 +14510,14 @@ function realProvisionDeps(crewHome) {
|
|
|
14441
14510
|
continue;
|
|
14442
14511
|
}
|
|
14443
14512
|
for (const p of plan.unitPaths) {
|
|
14444
|
-
if (!
|
|
14513
|
+
if (!existsSync32(p) || readFileSync31(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
|
|
14445
14514
|
}
|
|
14446
14515
|
}
|
|
14447
14516
|
const label = labelFor(crewHome, "run");
|
|
14448
14517
|
let running = false;
|
|
14449
14518
|
let lingering = true;
|
|
14450
14519
|
if (mechanism === "systemd") {
|
|
14451
|
-
const system = ctx.root ||
|
|
14520
|
+
const system = ctx.root || existsSync32(systemUnitPathFor(label));
|
|
14452
14521
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
14453
14522
|
running = spawnSync8(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
|
|
14454
14523
|
if (!system) {
|
|
@@ -14465,18 +14534,18 @@ function realProvisionDeps(crewHome) {
|
|
|
14465
14534
|
setShipSetting(key, value2) {
|
|
14466
14535
|
const file = configFile();
|
|
14467
14536
|
if (!file) return false;
|
|
14468
|
-
const doc = parseDocument3(
|
|
14537
|
+
const doc = parseDocument3(readFileSync31(file, "utf8"));
|
|
14469
14538
|
if (doc.getIn(["ship", key]) === value2) return false;
|
|
14470
14539
|
doc.setIn(["ship", key], value2);
|
|
14471
|
-
|
|
14540
|
+
writeFileSync25(file, doc.toString());
|
|
14472
14541
|
return true;
|
|
14473
14542
|
},
|
|
14474
14543
|
applyManagedDefaults() {
|
|
14475
14544
|
const file = configFile();
|
|
14476
14545
|
if (!file) return [];
|
|
14477
|
-
const doc = parseDocument3(
|
|
14546
|
+
const doc = parseDocument3(readFileSync31(file, "utf8"));
|
|
14478
14547
|
const wrote = applyManagedDefaultsToDoc(doc);
|
|
14479
|
-
if (wrote.length)
|
|
14548
|
+
if (wrote.length) writeFileSync25(file, doc.toString());
|
|
14480
14549
|
return wrote;
|
|
14481
14550
|
},
|
|
14482
14551
|
storedToken(host) {
|
|
@@ -14493,9 +14562,9 @@ host=${host}
|
|
|
14493
14562
|
|
|
14494
14563
|
// src/tunnel.ts
|
|
14495
14564
|
import { spawn as spawn7 } from "child_process";
|
|
14496
|
-
import { existsSync as
|
|
14565
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync24, readFileSync as readFileSync32, writeFileSync as writeFileSync26, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
|
|
14497
14566
|
import { randomInt } from "crypto";
|
|
14498
|
-
import { join as
|
|
14567
|
+
import { join as join37 } from "path";
|
|
14499
14568
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
14500
14569
|
var defaultSpawn = (cmd, args) => spawn7(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
14501
14570
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -14567,21 +14636,21 @@ function startTunnel(opts) {
|
|
|
14567
14636
|
};
|
|
14568
14637
|
}
|
|
14569
14638
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
14570
|
-
return
|
|
14639
|
+
return join37(stateDir, "passengers", `${workspaceId}.json`);
|
|
14571
14640
|
}
|
|
14572
14641
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
14573
14642
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
14574
|
-
if (!
|
|
14643
|
+
if (!existsSync33(path)) return void 0;
|
|
14575
14644
|
try {
|
|
14576
|
-
return JSON.parse(
|
|
14645
|
+
return JSON.parse(readFileSync32(path, "utf8"));
|
|
14577
14646
|
} catch {
|
|
14578
14647
|
return void 0;
|
|
14579
14648
|
}
|
|
14580
14649
|
}
|
|
14581
14650
|
function writePersistedTunnel(stateDir, state) {
|
|
14582
14651
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
14583
|
-
|
|
14584
|
-
|
|
14652
|
+
mkdirSync24(join37(stateDir, "passengers"), { recursive: true });
|
|
14653
|
+
writeFileSync26(path, `${JSON.stringify(state, null, 2)}
|
|
14585
14654
|
`);
|
|
14586
14655
|
}
|
|
14587
14656
|
function updatePersistedTunnel(stateDir, workspaceId, patch2) {
|
|
@@ -14683,8 +14752,8 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
14683
14752
|
}
|
|
14684
14753
|
}
|
|
14685
14754
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
14686
|
-
const dir =
|
|
14687
|
-
if (!
|
|
14755
|
+
const dir = join37(stateDir, "passengers");
|
|
14756
|
+
if (!existsSync33(dir)) return [];
|
|
14688
14757
|
return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
14689
14758
|
}
|
|
14690
14759
|
|
|
@@ -14709,7 +14778,7 @@ function realPoolMemberDeps(o) {
|
|
|
14709
14778
|
crewVersion: crewVersion(o.crewHome),
|
|
14710
14779
|
hostname: hostname3()
|
|
14711
14780
|
},
|
|
14712
|
-
hasShipConfig: () =>
|
|
14781
|
+
hasShipConfig: () => existsSync34(findConfigFile(o.crewHome)),
|
|
14713
14782
|
async provision(lease, instanceClass, shipKind) {
|
|
14714
14783
|
const hosts = lease.forges.map((f) => f.host.toLowerCase());
|
|
14715
14784
|
const lines2 = [];
|
|
@@ -15585,8 +15654,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
|
|
|
15585
15654
|
}
|
|
15586
15655
|
|
|
15587
15656
|
// src/poll.ts
|
|
15588
|
-
import { writeFileSync as
|
|
15589
|
-
import { join as
|
|
15657
|
+
import { writeFileSync as writeFileSync27 } from "fs";
|
|
15658
|
+
import { join as join38 } from "path";
|
|
15590
15659
|
|
|
15591
15660
|
// src/stalled.ts
|
|
15592
15661
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -15893,7 +15962,7 @@ async function decideCycle(o) {
|
|
|
15893
15962
|
}
|
|
15894
15963
|
return decision;
|
|
15895
15964
|
}
|
|
15896
|
-
var digestPath = (stateDir, route2, role, runId) =>
|
|
15965
|
+
var digestPath = (stateDir, route2, role, runId) => join38(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
15897
15966
|
function otherRunsRemoved(tickets, own) {
|
|
15898
15967
|
if (!own) return tickets;
|
|
15899
15968
|
return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
|
|
@@ -15971,7 +16040,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
|
|
|
15971
16040
|
|
|
15972
16041
|
${body}` : body;
|
|
15973
16042
|
const path = digestPath(stateDir, routeSlug(o.route.route), role, runId);
|
|
15974
|
-
|
|
16043
|
+
writeFileSync27(path, text2);
|
|
15975
16044
|
o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text2)} bytes)`, {
|
|
15976
16045
|
data: { role, bytes: Buffer.byteLength(text2) }
|
|
15977
16046
|
});
|
|
@@ -16079,7 +16148,7 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
|
|
|
16079
16148
|
}
|
|
16080
16149
|
|
|
16081
16150
|
// src/repo-refresh.ts
|
|
16082
|
-
import { existsSync as
|
|
16151
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync25, readFileSync as readFileSync33, writeFileSync as writeFileSync28 } from "fs";
|
|
16083
16152
|
import { dirname as dirname19 } from "path";
|
|
16084
16153
|
var REFRESH_COOLDOWN_MS = 2 * 6e4;
|
|
16085
16154
|
var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
|
|
@@ -16109,11 +16178,11 @@ function applyRepoRows(resolved, rows) {
|
|
|
16109
16178
|
}
|
|
16110
16179
|
function persistRepoRows(stateDir, routeName, rows) {
|
|
16111
16180
|
const path = resolvedPathFor(stateDir, routeName);
|
|
16112
|
-
if (!
|
|
16113
|
-
const raw = JSON.parse(
|
|
16181
|
+
if (!existsSync35(path)) return;
|
|
16182
|
+
const raw = JSON.parse(readFileSync33(path, "utf8"));
|
|
16114
16183
|
applyRepoRows(raw, rows);
|
|
16115
|
-
|
|
16116
|
-
|
|
16184
|
+
mkdirSync25(dirname19(path), { recursive: true });
|
|
16185
|
+
writeFileSync28(path, `${JSON.stringify(raw, null, 2)}
|
|
16117
16186
|
`);
|
|
16118
16187
|
}
|
|
16119
16188
|
async function refreshUnknownRepos(o) {
|
|
@@ -16563,13 +16632,13 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
|
|
|
16563
16632
|
var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
|
|
16564
16633
|
|
|
16565
16634
|
// src/connect-wizard.ts
|
|
16566
|
-
import { delimiter, dirname as dirname20, join as
|
|
16567
|
-
import { accessSync as accessSync3, constants as constants3, existsSync as
|
|
16635
|
+
import { delimiter, dirname as dirname20, join as join39 } from "path";
|
|
16636
|
+
import { accessSync as accessSync3, constants as constants3, existsSync as existsSync36, mkdirSync as mkdirSync26, writeFileSync as writeFileSync29 } from "fs";
|
|
16568
16637
|
import { stringify as stringify3 } from "yaml";
|
|
16569
16638
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
16570
16639
|
function shouldRunWizard(opts) {
|
|
16571
16640
|
if (!opts.isTTY) return false;
|
|
16572
|
-
const exists = opts.configExists ??
|
|
16641
|
+
const exists = opts.configExists ?? existsSync36;
|
|
16573
16642
|
return !configSearchPath(opts.crewHome).some((p) => exists(p));
|
|
16574
16643
|
}
|
|
16575
16644
|
function firstRunConfigPath() {
|
|
@@ -16586,7 +16655,7 @@ function defaultIsExecutable(p) {
|
|
|
16586
16655
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
16587
16656
|
for (const dir of pathEnv.split(delimiter)) {
|
|
16588
16657
|
if (!dir) continue;
|
|
16589
|
-
const candidate =
|
|
16658
|
+
const candidate = join39(dir, name);
|
|
16590
16659
|
if (isExecutable(candidate)) return candidate;
|
|
16591
16660
|
}
|
|
16592
16661
|
return void 0;
|
|
@@ -16647,9 +16716,9 @@ routes:
|
|
|
16647
16716
|
${routeBlock}`;
|
|
16648
16717
|
}
|
|
16649
16718
|
function writeNewConfig(path, text2) {
|
|
16650
|
-
|
|
16719
|
+
mkdirSync26(dirname20(path), { recursive: true });
|
|
16651
16720
|
try {
|
|
16652
|
-
|
|
16721
|
+
writeFileSync29(path, text2, { mode: 384, flag: "wx" });
|
|
16653
16722
|
return true;
|
|
16654
16723
|
} catch (e) {
|
|
16655
16724
|
if (e.code === "EEXIST") return false;
|
|
@@ -16683,7 +16752,7 @@ Wrote ${o.configPath}.
|
|
|
16683
16752
|
// src/connect-json.ts
|
|
16684
16753
|
import { accessSync as accessSync4, constants as constants4 } from "fs";
|
|
16685
16754
|
import { homedir as homedir10 } from "os";
|
|
16686
|
-
import { dirname as dirname21, join as
|
|
16755
|
+
import { dirname as dirname21, join as join40 } from "path";
|
|
16687
16756
|
import { delimiter as delimiter2 } from "path";
|
|
16688
16757
|
var CHOICE_EXIT_CODE = 3;
|
|
16689
16758
|
function createEmitter(json2, write) {
|
|
@@ -16722,12 +16791,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
|
|
|
16722
16791
|
const dirs = [
|
|
16723
16792
|
...pathEnv.split(delimiter2),
|
|
16724
16793
|
...(extraPath ?? "").split(delimiter2),
|
|
16725
|
-
|
|
16726
|
-
|
|
16794
|
+
join40(home, ".local/bin"),
|
|
16795
|
+
join40(home, ".claude/local"),
|
|
16727
16796
|
"/opt/homebrew/bin",
|
|
16728
16797
|
"/usr/local/bin",
|
|
16729
|
-
|
|
16730
|
-
|
|
16798
|
+
join40(home, ".npm-global/bin"),
|
|
16799
|
+
join40(home, ".volta/bin")
|
|
16731
16800
|
];
|
|
16732
16801
|
return [...new Set(dirs.filter(Boolean))];
|
|
16733
16802
|
}
|
|
@@ -16763,8 +16832,8 @@ function flagWizardAnswers(f, env) {
|
|
|
16763
16832
|
}
|
|
16764
16833
|
|
|
16765
16834
|
// src/release-run.ts
|
|
16766
|
-
import { readFileSync as
|
|
16767
|
-
import { dirname as dirname22, join as
|
|
16835
|
+
import { readFileSync as readFileSync34, writeFileSync as writeFileSync30, existsSync as existsSync37, copyFileSync, mkdirSync as mkdirSync27, mkdtempSync as mkdtempSync3, rmSync as rmSync18 } from "fs";
|
|
16836
|
+
import { dirname as dirname22, join as join41 } from "path";
|
|
16768
16837
|
import { tmpdir as tmpdir5 } from "os";
|
|
16769
16838
|
|
|
16770
16839
|
// src/release.ts
|
|
@@ -17505,13 +17574,13 @@ async function bump(o, size) {
|
|
|
17505
17574
|
}
|
|
17506
17575
|
return printed;
|
|
17507
17576
|
}
|
|
17508
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
17577
|
+
const files = o.repo.release.versionFiles.map((f) => join41(o.cwd, f));
|
|
17509
17578
|
const first = files[0];
|
|
17510
|
-
if (!
|
|
17579
|
+
if (!existsSync37(first)) {
|
|
17511
17580
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
17512
17581
|
return null;
|
|
17513
17582
|
}
|
|
17514
|
-
const current = JSON.parse(
|
|
17583
|
+
const current = JSON.parse(readFileSync34(first, "utf8")).version;
|
|
17515
17584
|
if (!current) {
|
|
17516
17585
|
o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
|
|
17517
17586
|
return null;
|
|
@@ -17524,27 +17593,27 @@ async function bump(o, size) {
|
|
|
17524
17593
|
return version;
|
|
17525
17594
|
}
|
|
17526
17595
|
for (const f of files) {
|
|
17527
|
-
const src =
|
|
17596
|
+
const src = readFileSync34(f, "utf8");
|
|
17528
17597
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
17529
17598
|
if (out === src) {
|
|
17530
17599
|
o.emit.error(`no version field in ${f}`);
|
|
17531
17600
|
return null;
|
|
17532
17601
|
}
|
|
17533
|
-
|
|
17602
|
+
writeFileSync30(f, out);
|
|
17534
17603
|
}
|
|
17535
17604
|
return version;
|
|
17536
17605
|
}
|
|
17537
17606
|
function writeChangelog(o, version, entries) {
|
|
17538
17607
|
const file = o.repo.release.changelog;
|
|
17539
17608
|
if (!file || entries.length === 0) return;
|
|
17540
|
-
const path =
|
|
17609
|
+
const path = join41(o.cwd, file);
|
|
17541
17610
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
17542
17611
|
if (o.dryRun) {
|
|
17543
17612
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
17544
17613
|
return;
|
|
17545
17614
|
}
|
|
17546
|
-
const existing =
|
|
17547
|
-
|
|
17615
|
+
const existing = existsSync37(path) ? readFileSync34(path, "utf8") : "# Changelog\n";
|
|
17616
|
+
writeFileSync30(path, insertChangelogSection(existing, section));
|
|
17548
17617
|
}
|
|
17549
17618
|
async function confirm(o, expected) {
|
|
17550
17619
|
if (!o.repo.hooks.released) return void 0;
|
|
@@ -17637,8 +17706,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
|
|
|
17637
17706
|
function openReleaseCheckout(o) {
|
|
17638
17707
|
const { remote, base } = o.repo.branch;
|
|
17639
17708
|
const branch = `crew/release-${Date.now()}`;
|
|
17640
|
-
const root = mkdtempSync3(
|
|
17641
|
-
const dir =
|
|
17709
|
+
const root = mkdtempSync3(join41(tmpdir5(), "crew-release-"));
|
|
17710
|
+
const dir = join41(root, "checkout");
|
|
17642
17711
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
17643
17712
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
17644
17713
|
try {
|
|
@@ -17648,10 +17717,10 @@ function openReleaseCheckout(o) {
|
|
|
17648
17717
|
throw e;
|
|
17649
17718
|
}
|
|
17650
17719
|
for (const f of o.repo.worktrees.copy) {
|
|
17651
|
-
const src =
|
|
17652
|
-
if (!
|
|
17653
|
-
|
|
17654
|
-
copyFileSync(src,
|
|
17720
|
+
const src = join41(o.cwd, f);
|
|
17721
|
+
if (!existsSync37(src)) continue;
|
|
17722
|
+
mkdirSync27(dirname22(join41(dir, f)), { recursive: true });
|
|
17723
|
+
copyFileSync(src, join41(dir, f));
|
|
17655
17724
|
}
|
|
17656
17725
|
return { dir, branch, close };
|
|
17657
17726
|
}
|
|
@@ -18673,7 +18742,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
|
|
|
18673
18742
|
}
|
|
18674
18743
|
|
|
18675
18744
|
// src/watch.ts
|
|
18676
|
-
import { watch as watchFile, existsSync as
|
|
18745
|
+
import { watch as watchFile, existsSync as existsSync38, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
|
|
18677
18746
|
var E = "\x1B[";
|
|
18678
18747
|
var dim = (s) => `${E}2m${s}${E}0m`;
|
|
18679
18748
|
var bold = (s) => `${E}1m${s}${E}0m`;
|
|
@@ -18774,7 +18843,7 @@ function renderHeaders(states, now = Date.now()) {
|
|
|
18774
18843
|
return states.map((s) => renderHeader(s, now)).join("");
|
|
18775
18844
|
}
|
|
18776
18845
|
function readFrom(file, from) {
|
|
18777
|
-
if (!
|
|
18846
|
+
if (!existsSync38(file)) return { events: [], offset: from };
|
|
18778
18847
|
const size = statSync11(file).size;
|
|
18779
18848
|
if (size < from) return readFrom(file, 0);
|
|
18780
18849
|
if (size === from) return { events: [], offset: from };
|
|
@@ -19017,7 +19086,7 @@ function nextRoles(decision, ran) {
|
|
|
19017
19086
|
}
|
|
19018
19087
|
|
|
19019
19088
|
// src/ship-name.ts
|
|
19020
|
-
import { existsSync as
|
|
19089
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync28, readFileSync as readFileSync35, renameSync as renameSync5, writeFileSync as writeFileSync31 } from "fs";
|
|
19021
19090
|
import { dirname as dirname23 } from "path";
|
|
19022
19091
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
19023
19092
|
var ShipNameError = class extends Error {
|
|
@@ -19079,34 +19148,34 @@ async function syncRouteName(i) {
|
|
|
19079
19148
|
return { status: "ok", ...base, action, name: i.local };
|
|
19080
19149
|
}
|
|
19081
19150
|
function readSyncedName(resolvedFile) {
|
|
19082
|
-
if (!
|
|
19151
|
+
if (!existsSync39(resolvedFile)) return void 0;
|
|
19083
19152
|
try {
|
|
19084
|
-
const v = JSON.parse(
|
|
19153
|
+
const v = JSON.parse(readFileSync35(resolvedFile, "utf8")).shipName;
|
|
19085
19154
|
return typeof v === "string" ? v : void 0;
|
|
19086
19155
|
} catch {
|
|
19087
19156
|
return void 0;
|
|
19088
19157
|
}
|
|
19089
19158
|
}
|
|
19090
19159
|
function writeSyncedName(resolvedFile, name) {
|
|
19091
|
-
if (!
|
|
19160
|
+
if (!existsSync39(resolvedFile)) return;
|
|
19092
19161
|
let raw;
|
|
19093
19162
|
try {
|
|
19094
|
-
raw = JSON.parse(
|
|
19163
|
+
raw = JSON.parse(readFileSync35(resolvedFile, "utf8"));
|
|
19095
19164
|
} catch {
|
|
19096
19165
|
return;
|
|
19097
19166
|
}
|
|
19098
19167
|
if (raw.shipName === name) return;
|
|
19099
19168
|
raw.shipName = name;
|
|
19100
|
-
|
|
19169
|
+
mkdirSync28(dirname23(resolvedFile), { recursive: true });
|
|
19101
19170
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
19102
|
-
|
|
19171
|
+
writeFileSync31(tmp, `${JSON.stringify(raw, null, 2)}
|
|
19103
19172
|
`);
|
|
19104
19173
|
renameSync5(tmp, resolvedFile);
|
|
19105
19174
|
}
|
|
19106
19175
|
async function reconcileShipName(i) {
|
|
19107
19176
|
let local = i.ship.name;
|
|
19108
19177
|
try {
|
|
19109
|
-
local = shipNameOfYaml(
|
|
19178
|
+
local = shipNameOfYaml(readFileSync35(i.configFile, "utf8")) ?? local;
|
|
19110
19179
|
} catch {
|
|
19111
19180
|
}
|
|
19112
19181
|
const resolvedFile = resolvedPathFor(i.stateDir, i.route);
|
|
@@ -19130,8 +19199,8 @@ async function reconcileShipName(i) {
|
|
|
19130
19199
|
}
|
|
19131
19200
|
if (result.status !== "ok") return result;
|
|
19132
19201
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
19133
|
-
const text2 =
|
|
19134
|
-
|
|
19202
|
+
const text2 = readFileSync35(i.configFile, "utf8");
|
|
19203
|
+
writeFileSync31(i.configFile, setShipNameInYaml(text2, result.name));
|
|
19135
19204
|
i.ship.name = result.name;
|
|
19136
19205
|
if (result.action === "row-wins") {
|
|
19137
19206
|
i.emit?.warn(
|
|
@@ -19790,7 +19859,7 @@ function describeCapability(i) {
|
|
|
19790
19859
|
}
|
|
19791
19860
|
|
|
19792
19861
|
// src/agents-sync-run.ts
|
|
19793
|
-
import { existsSync as
|
|
19862
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync29, readFileSync as readFileSync36, writeFileSync as writeFileSync32 } from "fs";
|
|
19794
19863
|
import { dirname as dirname24 } from "path";
|
|
19795
19864
|
async function runAgentsSync(target, o) {
|
|
19796
19865
|
const dryRun = o.dryRun ?? false;
|
|
@@ -19824,12 +19893,12 @@ async function runAgentsSync(target, o) {
|
|
|
19824
19893
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
19825
19894
|
if (!dryRun) {
|
|
19826
19895
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
19827
|
-
const raw =
|
|
19896
|
+
const raw = existsSync40(resolvedPath) ? JSON.parse(readFileSync36(resolvedPath, "utf8")) : {};
|
|
19828
19897
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
19829
19898
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
19830
19899
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
19831
|
-
|
|
19832
|
-
|
|
19900
|
+
mkdirSync29(dirname24(resolvedPath), { recursive: true });
|
|
19901
|
+
writeFileSync32(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
19833
19902
|
`);
|
|
19834
19903
|
if (target.resolved) {
|
|
19835
19904
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -19946,7 +20015,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
19946
20015
|
|
|
19947
20016
|
// src/repos-check.ts
|
|
19948
20017
|
import { spawnSync as spawnSync10 } from "child_process";
|
|
19949
|
-
import { readFileSync as
|
|
20018
|
+
import { readFileSync as readFileSync37 } from "fs";
|
|
19950
20019
|
import { basename as basename11 } from "path";
|
|
19951
20020
|
var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
|
|
19952
20021
|
function checkHookSyntax(script, shell) {
|
|
@@ -19987,7 +20056,7 @@ function checkRepoSpec(dir, opts = {}) {
|
|
|
19987
20056
|
}
|
|
19988
20057
|
let parsed;
|
|
19989
20058
|
try {
|
|
19990
|
-
parsed = parseRepoConfig(
|
|
20059
|
+
parsed = parseRepoConfig(readFileSync37(file, "utf8"), file);
|
|
19991
20060
|
} catch (e) {
|
|
19992
20061
|
if (!(e instanceof RepoConfigError)) throw e;
|
|
19993
20062
|
return { ...empty, file, error: e.message, exitCode: 1 };
|
|
@@ -20444,7 +20513,7 @@ ${run3.err}` });
|
|
|
20444
20513
|
}
|
|
20445
20514
|
|
|
20446
20515
|
// src/sample-ticket.ts
|
|
20447
|
-
import { basename as basename12, dirname as dirname25, join as
|
|
20516
|
+
import { basename as basename12, dirname as dirname25, join as join42 } from "path";
|
|
20448
20517
|
var SAMPLE_REPO = "crew-sample";
|
|
20449
20518
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
20450
20519
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -20467,8 +20536,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
20467
20536
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
20468
20537
|
`;
|
|
20469
20538
|
function sampleDir(existing, reposBase, workspace) {
|
|
20470
|
-
if (existing.length > 0) return
|
|
20471
|
-
return
|
|
20539
|
+
if (existing.length > 0) return join42(dirname25(existing[0]), SAMPLE_REPO);
|
|
20540
|
+
return join42(reposBase, workspace, SAMPLE_REPO);
|
|
20472
20541
|
}
|
|
20473
20542
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
20474
20543
|
function sampleDescription() {
|
|
@@ -20512,8 +20581,8 @@ function planSampleTicket(i) {
|
|
|
20512
20581
|
}
|
|
20513
20582
|
|
|
20514
20583
|
// src/worktree-sweep.ts
|
|
20515
|
-
import { existsSync as
|
|
20516
|
-
import { basename as basename13, dirname as dirname26, join as
|
|
20584
|
+
import { existsSync as existsSync41, readFileSync as readFileSync38, readdirSync as readdirSync14 } from "fs";
|
|
20585
|
+
import { basename as basename13, dirname as dirname26, join as join43, resolve as resolve5 } from "path";
|
|
20517
20586
|
function planWorktreeSweep(target, tickets, contract) {
|
|
20518
20587
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
20519
20588
|
const verified = contract.statuses.verified;
|
|
@@ -20590,7 +20659,7 @@ function siblingWorktreeDirs(target) {
|
|
|
20590
20659
|
for (const name of names) {
|
|
20591
20660
|
if (!name.startsWith(stem)) continue;
|
|
20592
20661
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
20593
|
-
const path =
|
|
20662
|
+
const path = join43(parent, name);
|
|
20594
20663
|
if (m && !registered.has(resolve5(path))) out.push({ path, number: m[1] });
|
|
20595
20664
|
}
|
|
20596
20665
|
return out;
|
|
@@ -20625,7 +20694,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20625
20694
|
let removed = 0;
|
|
20626
20695
|
let keptBranches = 0;
|
|
20627
20696
|
for (const a of actions) {
|
|
20628
|
-
if (!dryRun && base && !a.keepBranch &&
|
|
20697
|
+
if (!dryRun && base && !a.keepBranch && existsSync41(a.path) && isHandBackMerge(a.path, base)) {
|
|
20629
20698
|
if (gitOk(a.path, ["merge", "--abort"]) !== null) {
|
|
20630
20699
|
log.emit(
|
|
20631
20700
|
`abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
|
|
@@ -20633,7 +20702,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20633
20702
|
);
|
|
20634
20703
|
}
|
|
20635
20704
|
}
|
|
20636
|
-
if (!dryRun &&
|
|
20705
|
+
if (!dryRun && existsSync41(a.path)) {
|
|
20637
20706
|
let dirty = false;
|
|
20638
20707
|
try {
|
|
20639
20708
|
dirty = status(a.path).length > 0;
|
|
@@ -20644,7 +20713,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20644
20713
|
continue;
|
|
20645
20714
|
}
|
|
20646
20715
|
}
|
|
20647
|
-
if (!dryRun && a.requirePushed && a.branch &&
|
|
20716
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync41(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
20648
20717
|
refuse2(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
|
|
20649
20718
|
continue;
|
|
20650
20719
|
}
|
|
@@ -20693,7 +20762,7 @@ function isHandBackMerge(path, base) {
|
|
|
20693
20762
|
if (!msgPath) return false;
|
|
20694
20763
|
let first;
|
|
20695
20764
|
try {
|
|
20696
|
-
first =
|
|
20765
|
+
first = readFileSync38(resolve5(path, msgPath), "utf8").split("\n")[0] ?? "";
|
|
20697
20766
|
} catch {
|
|
20698
20767
|
return false;
|
|
20699
20768
|
}
|
|
@@ -20798,14 +20867,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
20798
20867
|
}
|
|
20799
20868
|
|
|
20800
20869
|
// src/stream-sweep.ts
|
|
20801
|
-
import { existsSync as
|
|
20802
|
-
import { join as
|
|
20870
|
+
import { existsSync as existsSync42, readdirSync as readdirSync15, rmSync as rmSync19, statSync as statSync12 } from "fs";
|
|
20871
|
+
import { join as join44 } from "path";
|
|
20803
20872
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
20804
20873
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
20805
|
-
const dir =
|
|
20806
|
-
if (!
|
|
20874
|
+
const dir = join44(stateDir, "streams");
|
|
20875
|
+
if (!existsSync42(dir)) return [];
|
|
20807
20876
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
20808
|
-
return readdirSync15(dir).map((name) =>
|
|
20877
|
+
return readdirSync15(dir).map((name) => join44(dir, name)).filter((path) => {
|
|
20809
20878
|
try {
|
|
20810
20879
|
return statSync12(path).mtimeMs < cutoff;
|
|
20811
20880
|
} catch {
|
|
@@ -20831,9 +20900,9 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
20831
20900
|
return removed;
|
|
20832
20901
|
}
|
|
20833
20902
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
20834
|
-
if (!
|
|
20903
|
+
if (!existsSync42(stateDir)) return [];
|
|
20835
20904
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
20836
|
-
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) =>
|
|
20905
|
+
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join44(stateDir, name)).filter((path) => {
|
|
20837
20906
|
try {
|
|
20838
20907
|
return statSync12(path).mtimeMs < cutoff;
|
|
20839
20908
|
} catch {
|
|
@@ -20843,12 +20912,12 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
20843
20912
|
}
|
|
20844
20913
|
|
|
20845
20914
|
// src/cli.ts
|
|
20846
|
-
import { realpathSync as realpathSync3, readFileSync as
|
|
20915
|
+
import { realpathSync as realpathSync3, readFileSync as readFileSync44, existsSync as existsSync49, mkdirSync as mkdirSync37, writeFileSync as writeFileSync37, rmSync as rmSync24, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
|
|
20847
20916
|
import { createInterface } from "readline/promises";
|
|
20848
20917
|
|
|
20849
20918
|
// src/periscope-cmd.ts
|
|
20850
|
-
import { existsSync as
|
|
20851
|
-
import { join as
|
|
20919
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync30, readFileSync as readFileSync39, rmSync as rmSync20, writeFileSync as writeFileSync33 } from "fs";
|
|
20920
|
+
import { join as join45 } from "path";
|
|
20852
20921
|
var PeriscopeError = class extends Error {
|
|
20853
20922
|
};
|
|
20854
20923
|
var LivePeriscopeError = class extends PeriscopeError {
|
|
@@ -20858,22 +20927,22 @@ var LivePeriscopeError = class extends PeriscopeError {
|
|
|
20858
20927
|
this.data = { refused: "live_periscope_exists", ...live };
|
|
20859
20928
|
}
|
|
20860
20929
|
};
|
|
20861
|
-
var priorDir = (stateDir) =>
|
|
20862
|
-
var priorPath = (stateDir, ticketRecordId) =>
|
|
20930
|
+
var priorDir = (stateDir) => join45(stateDir, "periscope-priors");
|
|
20931
|
+
var priorPath = (stateDir, ticketRecordId) => join45(priorDir(stateDir), `${ticketRecordId}.json`);
|
|
20863
20932
|
function removePeriscopePrior(stateDir, ticketRecordId) {
|
|
20864
20933
|
rmSync20(priorPath(stateDir, ticketRecordId), { force: true });
|
|
20865
20934
|
}
|
|
20866
20935
|
function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
20867
20936
|
try {
|
|
20868
|
-
|
|
20869
|
-
|
|
20937
|
+
mkdirSync30(priorDir(stateDir), { recursive: true });
|
|
20938
|
+
writeFileSync33(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
|
|
20870
20939
|
} catch {
|
|
20871
20940
|
}
|
|
20872
20941
|
}
|
|
20873
20942
|
function readPeriscopePrior(stateDir, ticketRecordId) {
|
|
20874
20943
|
try {
|
|
20875
20944
|
const p = priorPath(stateDir, ticketRecordId);
|
|
20876
|
-
return
|
|
20945
|
+
return existsSync43(p) ? JSON.parse(readFileSync39(p, "utf8")) : void 0;
|
|
20877
20946
|
} catch {
|
|
20878
20947
|
return void 0;
|
|
20879
20948
|
}
|
|
@@ -21062,7 +21131,7 @@ ${tail(preview.output)}`);
|
|
|
21062
21131
|
d.say("proxy", `proxy at ${url} in front of ${previewUrl}`, { url, previewUrl });
|
|
21063
21132
|
}
|
|
21064
21133
|
d.say("preview", `preview at ${url}`, { url });
|
|
21065
|
-
const intel = await probeIntel(
|
|
21134
|
+
const intel = d.lspPluginDir ? { state: "absent" } : await probeIntel(
|
|
21066
21135
|
{ runHook: (e) => d.runHook("intel", wt.path, e) },
|
|
21067
21136
|
env,
|
|
21068
21137
|
d.intelStartupSeconds ?? DEFAULT_INTEL_STARTUP_SECONDS2
|
|
@@ -21078,7 +21147,7 @@ ${tail(preview.output)}`);
|
|
|
21078
21147
|
d.say("warning", `symbol service not registered: ${e.message}`);
|
|
21079
21148
|
}
|
|
21080
21149
|
}
|
|
21081
|
-
const codeTools = codeToolsParagraph(codeOutcome);
|
|
21150
|
+
const codeTools = d.lspPluginDir ? `${lspToolsParagraph("claude")} Start the Claude session with \`--plugin-dir ${d.lspPluginDir}\` to load it.` : codeToolsParagraph(codeOutcome);
|
|
21082
21151
|
const now = d.now();
|
|
21083
21152
|
let row;
|
|
21084
21153
|
try {
|
|
@@ -21327,7 +21396,7 @@ ${tail2(preview.output)}`);
|
|
|
21327
21396
|
// src/periscope-idle.ts
|
|
21328
21397
|
import { execFileSync as execFileSync12 } from "child_process";
|
|
21329
21398
|
import { statSync as statSync13 } from "fs";
|
|
21330
|
-
import { join as
|
|
21399
|
+
import { join as join46 } from "path";
|
|
21331
21400
|
var MAX_SCANNED_FILES = 2e4;
|
|
21332
21401
|
function newestWorktreeMtime(root) {
|
|
21333
21402
|
const a = newestTrackedMtime(root);
|
|
@@ -21352,7 +21421,7 @@ function newestTrackedMtime(root) {
|
|
|
21352
21421
|
if (!rel) continue;
|
|
21353
21422
|
if (++scanned > MAX_SCANNED_FILES) break;
|
|
21354
21423
|
try {
|
|
21355
|
-
const m = statSync13(
|
|
21424
|
+
const m = statSync13(join46(root, rel)).mtimeMs;
|
|
21356
21425
|
if (newest === null || m > newest) newest = m;
|
|
21357
21426
|
} catch {
|
|
21358
21427
|
}
|
|
@@ -21428,18 +21497,18 @@ import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
|
21428
21497
|
|
|
21429
21498
|
// src/ssh-keys.ts
|
|
21430
21499
|
import { execFileSync as execFileSync13 } from "child_process";
|
|
21431
|
-
import { existsSync as
|
|
21432
|
-
import { dirname as dirname27, join as
|
|
21500
|
+
import { existsSync as existsSync44, mkdirSync as mkdirSync31, readFileSync as readFileSync40, chmodSync as chmodSync3 } from "fs";
|
|
21501
|
+
import { dirname as dirname27, join as join47 } from "path";
|
|
21433
21502
|
var SshKeyError = class extends Error {
|
|
21434
21503
|
};
|
|
21435
21504
|
function shipSshKeyPathFor(stateDir) {
|
|
21436
|
-
return
|
|
21505
|
+
return join47(stateDir, "ssh", "id_ed25519");
|
|
21437
21506
|
}
|
|
21438
21507
|
function ensureShipSshKeypair(stateDir) {
|
|
21439
21508
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
21440
21509
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
21441
|
-
if (!
|
|
21442
|
-
|
|
21510
|
+
if (!existsSync44(privateKeyPath)) {
|
|
21511
|
+
mkdirSync31(dirname27(privateKeyPath), { recursive: true });
|
|
21443
21512
|
try {
|
|
21444
21513
|
execFileSync13("ssh-keygen", [
|
|
21445
21514
|
"-t",
|
|
@@ -21459,10 +21528,10 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
21459
21528
|
}
|
|
21460
21529
|
chmodSync3(privateKeyPath, 384);
|
|
21461
21530
|
}
|
|
21462
|
-
if (!
|
|
21531
|
+
if (!existsSync44(publicKeyPath)) {
|
|
21463
21532
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
21464
21533
|
}
|
|
21465
|
-
const publicKey =
|
|
21534
|
+
const publicKey = readFileSync40(publicKeyPath, "utf8").trim();
|
|
21466
21535
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
21467
21536
|
}
|
|
21468
21537
|
function sshKeygenAvailable() {
|
|
@@ -21626,11 +21695,11 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
21626
21695
|
import { createServer as createHttpServer } from "http";
|
|
21627
21696
|
import { spawn as spawn9 } from "child_process";
|
|
21628
21697
|
import { randomBytes } from "crypto";
|
|
21629
|
-
import { existsSync as
|
|
21630
|
-
import { join as
|
|
21698
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync33, readFileSync as readFileSync41, writeFileSync as writeFileSync34, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
|
|
21699
|
+
import { join as join48 } from "path";
|
|
21631
21700
|
|
|
21632
21701
|
// src/passenger-mirror.ts
|
|
21633
|
-
import { mkdirSync as
|
|
21702
|
+
import { mkdirSync as mkdirSync32 } from "fs";
|
|
21634
21703
|
import { dirname as dirname28 } from "path";
|
|
21635
21704
|
function mirrorRepoOf(spec) {
|
|
21636
21705
|
const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
|
|
@@ -21650,8 +21719,8 @@ async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCa
|
|
|
21650
21719
|
}
|
|
21651
21720
|
try {
|
|
21652
21721
|
const parent = dirname28(spec.dir);
|
|
21653
|
-
|
|
21654
|
-
|
|
21722
|
+
mkdirSync32(parent, { recursive: true });
|
|
21723
|
+
mkdirSync32(spec.dir, { recursive: true });
|
|
21655
21724
|
const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
|
|
21656
21725
|
out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
|
|
21657
21726
|
} catch (e) {
|
|
@@ -21757,21 +21826,21 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
21757
21826
|
return child;
|
|
21758
21827
|
};
|
|
21759
21828
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
21760
|
-
return
|
|
21829
|
+
return join48(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
21761
21830
|
}
|
|
21762
21831
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
21763
21832
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
21764
|
-
if (!
|
|
21833
|
+
if (!existsSync45(path)) return void 0;
|
|
21765
21834
|
try {
|
|
21766
|
-
return JSON.parse(
|
|
21835
|
+
return JSON.parse(readFileSync41(path, "utf8"));
|
|
21767
21836
|
} catch {
|
|
21768
21837
|
return void 0;
|
|
21769
21838
|
}
|
|
21770
21839
|
}
|
|
21771
21840
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
21772
21841
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
21773
|
-
|
|
21774
|
-
|
|
21842
|
+
mkdirSync33(join48(stateDir, "passengers"), { recursive: true });
|
|
21843
|
+
writeFileSync34(path, `${JSON.stringify(state, null, 2)}
|
|
21775
21844
|
`);
|
|
21776
21845
|
}
|
|
21777
21846
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -21791,8 +21860,8 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
21791
21860
|
}
|
|
21792
21861
|
}
|
|
21793
21862
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
21794
|
-
const dir =
|
|
21795
|
-
if (!
|
|
21863
|
+
const dir = join48(stateDir, "passengers");
|
|
21864
|
+
if (!existsSync45(dir)) return [];
|
|
21796
21865
|
const suffix = "-sync.json";
|
|
21797
21866
|
return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
21798
21867
|
}
|
|
@@ -21850,7 +21919,7 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
|
21850
21919
|
|
|
21851
21920
|
// src/periscope-proxy.ts
|
|
21852
21921
|
import { spawn as spawn10 } from "child_process";
|
|
21853
|
-
import { appendFileSync as appendFileSync3, mkdirSync as
|
|
21922
|
+
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync34 } from "fs";
|
|
21854
21923
|
import { createServer, request as httpRequest } from "http";
|
|
21855
21924
|
import { request as httpsRequest } from "https";
|
|
21856
21925
|
import { connect as netConnect } from "net";
|
|
@@ -21969,7 +22038,7 @@ function parsePointedBody(raw) {
|
|
|
21969
22038
|
return entry;
|
|
21970
22039
|
}
|
|
21971
22040
|
function appendPointed(file, entry, now = /* @__PURE__ */ new Date()) {
|
|
21972
|
-
|
|
22041
|
+
mkdirSync34(dirname29(file), { recursive: true });
|
|
21973
22042
|
appendFileSync3(file, `${JSON.stringify({ ...entry, at: now.toISOString() })}
|
|
21974
22043
|
`);
|
|
21975
22044
|
}
|
|
@@ -22262,8 +22331,8 @@ function describeCause(err3) {
|
|
|
22262
22331
|
}
|
|
22263
22332
|
|
|
22264
22333
|
// src/daemon.ts
|
|
22265
|
-
import { existsSync as
|
|
22266
|
-
import { join as
|
|
22334
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync35, readFileSync as readFileSync42, readdirSync as readdirSync17, rmSync as rmSync21, statSync as statSync14, writeFileSync as writeFileSync35 } from "fs";
|
|
22335
|
+
import { join as join49 } from "path";
|
|
22267
22336
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
22268
22337
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
22269
22338
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
@@ -22351,7 +22420,7 @@ function latestMtime(path) {
|
|
|
22351
22420
|
if (!stat2.isDirectory()) return 0;
|
|
22352
22421
|
let latest = stat2.mtimeMs;
|
|
22353
22422
|
for (const entry of readdirSync17(path, { withFileTypes: true })) {
|
|
22354
|
-
latest = Math.max(latest, latestMtime(
|
|
22423
|
+
latest = Math.max(latest, latestMtime(join49(path, entry.name)));
|
|
22355
22424
|
}
|
|
22356
22425
|
return latest;
|
|
22357
22426
|
}
|
|
@@ -22360,10 +22429,10 @@ function computeTreeSignature(paths) {
|
|
|
22360
22429
|
}
|
|
22361
22430
|
function makeStaleChecker(paths, opts = {}) {
|
|
22362
22431
|
const initial = computeTreeSignature(paths);
|
|
22363
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
22432
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync46(opts.reloadSentinel);
|
|
22364
22433
|
}
|
|
22365
22434
|
function reloadSentinelFor(stateDir) {
|
|
22366
|
-
return
|
|
22435
|
+
return join49(stateDir, "reload-requested");
|
|
22367
22436
|
}
|
|
22368
22437
|
function readReloadRequestedAt(stateDir) {
|
|
22369
22438
|
try {
|
|
@@ -22373,17 +22442,17 @@ function readReloadRequestedAt(stateDir) {
|
|
|
22373
22442
|
}
|
|
22374
22443
|
}
|
|
22375
22444
|
function daemonStampFor(stateDir) {
|
|
22376
|
-
return
|
|
22445
|
+
return join49(stateDir, "daemon.started");
|
|
22377
22446
|
}
|
|
22378
22447
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
22379
|
-
|
|
22448
|
+
mkdirSync35(stateDir, { recursive: true });
|
|
22380
22449
|
rmSync21(reloadSentinelFor(stateDir), { force: true });
|
|
22381
|
-
|
|
22450
|
+
writeFileSync35(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
22382
22451
|
`);
|
|
22383
22452
|
}
|
|
22384
22453
|
function readDaemonStarted(stateDir) {
|
|
22385
22454
|
try {
|
|
22386
|
-
const v = JSON.parse(
|
|
22455
|
+
const v = JSON.parse(readFileSync42(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
22387
22456
|
return typeof v === "number" ? v : null;
|
|
22388
22457
|
} catch {
|
|
22389
22458
|
return null;
|
|
@@ -22949,7 +23018,7 @@ import { createHash as createHash10 } from "crypto";
|
|
|
22949
23018
|
import {
|
|
22950
23019
|
chmodSync as chmodSync4,
|
|
22951
23020
|
closeSync as closeSync6,
|
|
22952
|
-
existsSync as
|
|
23021
|
+
existsSync as existsSync47,
|
|
22953
23022
|
fstatSync,
|
|
22954
23023
|
openSync as openSync6,
|
|
22955
23024
|
readSync as readSync3,
|
|
@@ -22960,7 +23029,7 @@ import {
|
|
|
22960
23029
|
} from "fs";
|
|
22961
23030
|
import { createConnection } from "net";
|
|
22962
23031
|
import { createServer as createServer2 } from "http";
|
|
22963
|
-
import { basename as basename14, dirname as dirname30, join as
|
|
23032
|
+
import { basename as basename14, dirname as dirname30, join as join50 } from "path";
|
|
22964
23033
|
import { execFile as execFile3, spawn as spawn12 } from "child_process";
|
|
22965
23034
|
|
|
22966
23035
|
// src/periscope-control.ts
|
|
@@ -23189,7 +23258,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
|
23189
23258
|
// src/control-api.ts
|
|
23190
23259
|
var PAUSABLE = ["dev", "design", "qa"];
|
|
23191
23260
|
var CONTROL_SOCKET_NAME = "control.sock";
|
|
23192
|
-
var controlSocketFor = (stateDir) =>
|
|
23261
|
+
var controlSocketFor = (stateDir) => join50(stateDir, CONTROL_SOCKET_NAME);
|
|
23193
23262
|
var MAX_SOCKET_PATH = 100;
|
|
23194
23263
|
var MAX_TAIL_BYTES = 1024 * 1024;
|
|
23195
23264
|
var MAX_WAIT_SECONDS = 30;
|
|
@@ -23276,7 +23345,7 @@ function candidates(live, rotated) {
|
|
|
23276
23345
|
const out = [live];
|
|
23277
23346
|
if (!rotated) return out;
|
|
23278
23347
|
try {
|
|
23279
|
-
for (const n of readdirSync18(dirname30(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(
|
|
23348
|
+
for (const n of readdirSync18(dirname30(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join50(dirname30(live), n));
|
|
23280
23349
|
} catch {
|
|
23281
23350
|
}
|
|
23282
23351
|
return out;
|
|
@@ -23320,7 +23389,7 @@ function lastLines2(path, n) {
|
|
|
23320
23389
|
}
|
|
23321
23390
|
function readTail(live, opts) {
|
|
23322
23391
|
const max = opts.maxBytes ?? MAX_TAIL_BYTES;
|
|
23323
|
-
if (!
|
|
23392
|
+
if (!existsSync47(live)) {
|
|
23324
23393
|
return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
|
|
23325
23394
|
}
|
|
23326
23395
|
const cur = opts.after ? decodeCursor(opts.after) : null;
|
|
@@ -23476,8 +23545,8 @@ async function route(deps, method, url, req, abort) {
|
|
|
23476
23545
|
if (p.startsWith("/streams/") && get2) {
|
|
23477
23546
|
const run3 = decodeURIComponent(p.slice("/streams/".length));
|
|
23478
23547
|
if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
|
|
23479
|
-
const file =
|
|
23480
|
-
if (!
|
|
23548
|
+
const file = join50(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
|
|
23549
|
+
if (!existsSync47(file)) return err2(404, "no such run");
|
|
23481
23550
|
return tailReply(file, url.searchParams, false, abort);
|
|
23482
23551
|
}
|
|
23483
23552
|
if ((p === "/pause" || p === "/resume") && post3) {
|
|
@@ -23521,7 +23590,7 @@ function send(res, r) {
|
|
|
23521
23590
|
res.end(body);
|
|
23522
23591
|
}
|
|
23523
23592
|
async function socketIsStale(path) {
|
|
23524
|
-
if (!
|
|
23593
|
+
if (!existsSync47(path)) return false;
|
|
23525
23594
|
return new Promise((resolve8) => {
|
|
23526
23595
|
const c = createConnection(path);
|
|
23527
23596
|
c.once("connect", () => {
|
|
@@ -23540,7 +23609,7 @@ async function startControlServer(deps) {
|
|
|
23540
23609
|
return null;
|
|
23541
23610
|
}
|
|
23542
23611
|
if (await socketIsStale(path)) rmSync22(path, { force: true });
|
|
23543
|
-
else if (
|
|
23612
|
+
else if (existsSync47(path)) {
|
|
23544
23613
|
log(`control API not started: ${path} is in use by another process`);
|
|
23545
23614
|
return null;
|
|
23546
23615
|
}
|
|
@@ -23626,31 +23695,31 @@ function crewJson(args, timeoutMs = 6e4) {
|
|
|
23626
23695
|
|
|
23627
23696
|
// src/periscope-mcp.ts
|
|
23628
23697
|
import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
|
|
23629
|
-
import { basename as basename15, dirname as dirname31, isAbsolute as isAbsolute5, join as
|
|
23698
|
+
import { basename as basename15, dirname as dirname31, isAbsolute as isAbsolute5, join as join52, relative as relative2, resolve as resolve6, sep } from "path";
|
|
23630
23699
|
import { z } from "zod";
|
|
23631
23700
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
23632
23701
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
23633
23702
|
|
|
23634
23703
|
// src/periscope-endpoints.ts
|
|
23635
23704
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
23636
|
-
import { chmodSync as chmodSync5, existsSync as
|
|
23637
|
-
import { join as
|
|
23638
|
-
var secretDir = (stateDir) =>
|
|
23639
|
-
var secretFile = (stateDir, id) =>
|
|
23640
|
-
var endpointDir = (stateDir) =>
|
|
23641
|
-
var endpointFile = (stateDir, id) =>
|
|
23705
|
+
import { chmodSync as chmodSync5, existsSync as existsSync48, mkdirSync as mkdirSync36, readdirSync as readdirSync19, readFileSync as readFileSync43, rmSync as rmSync23, writeFileSync as writeFileSync36 } from "fs";
|
|
23706
|
+
import { join as join51 } from "path";
|
|
23707
|
+
var secretDir = (stateDir) => join51(stateDir, "periscope-mcp");
|
|
23708
|
+
var secretFile = (stateDir, id) => join51(secretDir(stateDir), `${id}.json`);
|
|
23709
|
+
var endpointDir = (stateDir) => join51(stateDir, "periscope-endpoints");
|
|
23710
|
+
var endpointFile = (stateDir, id) => join51(endpointDir(stateDir), `${id}.json`);
|
|
23642
23711
|
function mintPeriscopeSecret(stateDir, ticketRecordId) {
|
|
23643
|
-
|
|
23712
|
+
mkdirSync36(secretDir(stateDir), { recursive: true, mode: 448 });
|
|
23644
23713
|
const secret = randomBytes3(32).toString("base64url");
|
|
23645
23714
|
const file = secretFile(stateDir, ticketRecordId);
|
|
23646
|
-
|
|
23715
|
+
writeFileSync36(file, `${JSON.stringify({ secret })}
|
|
23647
23716
|
`, { mode: 384 });
|
|
23648
23717
|
chmodSync5(file, 384);
|
|
23649
23718
|
return secret;
|
|
23650
23719
|
}
|
|
23651
23720
|
function readPeriscopeSecret(stateDir, ticketRecordId) {
|
|
23652
23721
|
try {
|
|
23653
|
-
const v = JSON.parse(
|
|
23722
|
+
const v = JSON.parse(readFileSync43(secretFile(stateDir, ticketRecordId), "utf8"));
|
|
23654
23723
|
return typeof v.secret === "string" && v.secret ? v.secret : void 0;
|
|
23655
23724
|
} catch {
|
|
23656
23725
|
return void 0;
|
|
@@ -23661,14 +23730,14 @@ function removePeriscopeSecret(stateDir, ticketRecordId) {
|
|
|
23661
23730
|
}
|
|
23662
23731
|
function readPeriscopeEndpoint(stateDir, ticketRecordId) {
|
|
23663
23732
|
try {
|
|
23664
|
-
return JSON.parse(
|
|
23733
|
+
return JSON.parse(readFileSync43(endpointFile(stateDir, ticketRecordId), "utf8"));
|
|
23665
23734
|
} catch {
|
|
23666
23735
|
return void 0;
|
|
23667
23736
|
}
|
|
23668
23737
|
}
|
|
23669
23738
|
function writePeriscopeEndpoint(stateDir, rec) {
|
|
23670
|
-
|
|
23671
|
-
|
|
23739
|
+
mkdirSync36(endpointDir(stateDir), { recursive: true });
|
|
23740
|
+
writeFileSync36(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
|
|
23672
23741
|
`);
|
|
23673
23742
|
}
|
|
23674
23743
|
function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
@@ -23676,7 +23745,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
|
23676
23745
|
}
|
|
23677
23746
|
function listPeriscopeEndpoints(stateDir) {
|
|
23678
23747
|
const dir = endpointDir(stateDir);
|
|
23679
|
-
if (!
|
|
23748
|
+
if (!existsSync48(dir)) return [];
|
|
23680
23749
|
const out = [];
|
|
23681
23750
|
for (const name of readdirSync19(dir)) {
|
|
23682
23751
|
if (!name.endsWith(".json")) continue;
|
|
@@ -23779,13 +23848,13 @@ async function resolveInWorktree(root, requested) {
|
|
|
23779
23848
|
let abs;
|
|
23780
23849
|
if (isAbsolute5(requested)) {
|
|
23781
23850
|
abs = resolve6(requested);
|
|
23782
|
-
if (isInside(root, abs)) abs =
|
|
23851
|
+
if (isInside(root, abs)) abs = join52(realRoot, relative2(root, abs));
|
|
23783
23852
|
} else {
|
|
23784
23853
|
abs = resolve6(realRoot, requested);
|
|
23785
23854
|
}
|
|
23786
23855
|
if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
23787
23856
|
const { real, rest } = await realAncestor(abs);
|
|
23788
|
-
const final = rest ?
|
|
23857
|
+
const final = rest ? join52(real, rest) : real;
|
|
23789
23858
|
if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
23790
23859
|
if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
|
|
23791
23860
|
return final;
|
|
@@ -23853,7 +23922,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
23853
23922
|
const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
|
|
23854
23923
|
const lines2 = await Promise.all(entries.map(async (e) => {
|
|
23855
23924
|
if (e.isDirectory()) return `[DIR] ${e.name}`;
|
|
23856
|
-
const size = (await lstat(
|
|
23925
|
+
const size = (await lstat(join52(abs, e.name))).size;
|
|
23857
23926
|
return `[FILE] ${e.name} (${size} bytes)`;
|
|
23858
23927
|
}));
|
|
23859
23928
|
return text(lines2.join("\n") || "(empty)");
|
|
@@ -23919,7 +23988,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
23919
23988
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
23920
23989
|
}, async ({ path }) => {
|
|
23921
23990
|
const parent = await inTree(dirname31(path) === "" ? "." : dirname31(path));
|
|
23922
|
-
const abs =
|
|
23991
|
+
const abs = join52(parent, basename15(path));
|
|
23923
23992
|
if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
|
|
23924
23993
|
const st = await lstat(abs).catch(() => void 0);
|
|
23925
23994
|
if (!st) return text(`${path} does not exist`, true);
|
|
@@ -24409,7 +24478,7 @@ if (command === "provision") {
|
|
|
24409
24478
|
process.exit(outcome.code);
|
|
24410
24479
|
}
|
|
24411
24480
|
if (command === "pool-member") {
|
|
24412
|
-
const pool = poolConfigFrom(argv, process.env, () =>
|
|
24481
|
+
const pool = poolConfigFrom(argv, process.env, () => readFileSync44(0, "utf8"));
|
|
24413
24482
|
if (typeof pool === "string") {
|
|
24414
24483
|
process.stderr.write(`crew pool-member: ${pool}
|
|
24415
24484
|
`);
|
|
@@ -24644,7 +24713,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
24644
24713
|
} else {
|
|
24645
24714
|
const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
|
|
24646
24715
|
const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
|
|
24647
|
-
if (configless && !
|
|
24716
|
+
if (configless && !existsSync49(findConfigFile(CREW_HOME))) {
|
|
24648
24717
|
cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
|
|
24649
24718
|
} else {
|
|
24650
24719
|
cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
|
|
@@ -24659,7 +24728,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
24659
24728
|
throw e;
|
|
24660
24729
|
}
|
|
24661
24730
|
if (command === "credential") {
|
|
24662
|
-
const r = runCredential(positional[1],
|
|
24731
|
+
const r = runCredential(positional[1], readFileSync44(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
|
|
24663
24732
|
process.stdout.write(r.out);
|
|
24664
24733
|
process.stderr.write(r.err);
|
|
24665
24734
|
process.exit(r.code);
|
|
@@ -24671,9 +24740,9 @@ if (command === "passenger-sync-daemon") {
|
|
|
24671
24740
|
forges: cfg.ship.forges,
|
|
24672
24741
|
stateDir: cfg.ship.stateDir,
|
|
24673
24742
|
configFile: cfg.configFile,
|
|
24674
|
-
readFile: (p) =>
|
|
24675
|
-
writeFile: (p, t) =>
|
|
24676
|
-
stdin: () =>
|
|
24743
|
+
readFile: (p) => readFileSync44(p, "utf8"),
|
|
24744
|
+
writeFile: (p, t) => writeFileSync37(p, t),
|
|
24745
|
+
stdin: () => readFileSync44(0, "utf8"),
|
|
24677
24746
|
env: process.env
|
|
24678
24747
|
}
|
|
24679
24748
|
);
|
|
@@ -24966,8 +25035,8 @@ if (command === "passenger-sync-daemon") {
|
|
|
24966
25035
|
}
|
|
24967
25036
|
if (host === "linux" && hasSystemd) {
|
|
24968
25037
|
const label = labelFor(CREW_HOME, "run");
|
|
24969
|
-
const staleTimer =
|
|
24970
|
-
if (
|
|
25038
|
+
const staleTimer = join53(homedir11(), ".config", "systemd", "user", `${label}.timer`);
|
|
25039
|
+
if (existsSync49(staleTimer)) {
|
|
24971
25040
|
process.stdout.write(
|
|
24972
25041
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
24973
25042
|
`
|
|
@@ -24994,7 +25063,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
24994
25063
|
const tracker = new Tracker(c, cfg.ship);
|
|
24995
25064
|
problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
|
|
24996
25065
|
for (const r of await resolvedRepos(c)) {
|
|
24997
|
-
if (!
|
|
25066
|
+
if (!existsSync49(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
24998
25067
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
24999
25068
|
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
25000
25069
|
}
|
|
@@ -25021,7 +25090,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25021
25090
|
}
|
|
25022
25091
|
async function doctorFix(o) {
|
|
25023
25092
|
const routes = await Promise.all(routesInScope(cfg.routes, named).map((c) => gatherHealth({ route: c.route, enabled: c.enabled, hasRepos: reposOf(c).length > 0 }, () => routeProblems(c))));
|
|
25024
|
-
const unitExists = (p) =>
|
|
25093
|
+
const unitExists = (p) => existsSync49(p);
|
|
25025
25094
|
const crontab = o.host === "linux" ? spawnSync11("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
25026
25095
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
|
|
25027
25096
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -25038,7 +25107,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25038
25107
|
for (const a of plan.actions) {
|
|
25039
25108
|
if (a.kind === "enable") {
|
|
25040
25109
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
25041
|
-
|
|
25110
|
+
writeFileSync37(cfg.configFile, planEnable(readFileSync44(cfg.configFile, "utf8"), a.route));
|
|
25042
25111
|
enabledSound.add(a.route);
|
|
25043
25112
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
25044
25113
|
} else {
|
|
@@ -26177,7 +26246,7 @@ ${cu.tail.trimEnd()}
|
|
|
26177
26246
|
process.exit(1);
|
|
26178
26247
|
}
|
|
26179
26248
|
requireArmed("run the daemon loop");
|
|
26180
|
-
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [
|
|
26249
|
+
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join53(CREW_HOME, "src"), join53(CREW_HOME, "dist"), join53(CREW_HOME, "bin")];
|
|
26181
26250
|
const configPaths = daemonConfigPaths(cfg);
|
|
26182
26251
|
watchPaths.push(...configPaths);
|
|
26183
26252
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -26482,10 +26551,10 @@ ${cu.tail.trimEnd()}
|
|
|
26482
26551
|
`);
|
|
26483
26552
|
if (!dryRun) {
|
|
26484
26553
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
26485
|
-
const raw =
|
|
26554
|
+
const raw = existsSync49(resolvedPath) ? JSON.parse(readFileSync44(resolvedPath, "utf8")) : {};
|
|
26486
26555
|
raw.agentSkills = result.agentSkills;
|
|
26487
|
-
|
|
26488
|
-
|
|
26556
|
+
mkdirSync37(dirOf2(resolvedPath), { recursive: true });
|
|
26557
|
+
writeFileSync37(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
26489
26558
|
`);
|
|
26490
26559
|
}
|
|
26491
26560
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -26508,7 +26577,7 @@ ${cu.tail.trimEnd()}
|
|
|
26508
26577
|
throw e;
|
|
26509
26578
|
}
|
|
26510
26579
|
const oldName = cfg.ship.name;
|
|
26511
|
-
|
|
26580
|
+
writeFileSync37(cfg.configFile, setShipNameInYaml(readFileSync44(cfg.configFile, "utf8"), newName));
|
|
26512
26581
|
cfg.ship.name = newName;
|
|
26513
26582
|
let renameMachineId;
|
|
26514
26583
|
try {
|
|
@@ -26555,7 +26624,7 @@ ${cu.tail.trimEnd()}
|
|
|
26555
26624
|
process.exit(2);
|
|
26556
26625
|
};
|
|
26557
26626
|
if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks" && sub !== "check") usage2();
|
|
26558
|
-
const configText =
|
|
26627
|
+
const configText = readFileSync44(cfg.configFile, "utf8");
|
|
26559
26628
|
if (sub === "check") {
|
|
26560
26629
|
try {
|
|
26561
26630
|
const routeArg = value("route") ?? positional[2];
|
|
@@ -26586,8 +26655,8 @@ ${cu.tail.trimEnd()}
|
|
|
26586
26655
|
try {
|
|
26587
26656
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
26588
26657
|
const dir = repoDirOf(configText, dirname32(cfg.configFile), a.route, a.name);
|
|
26589
|
-
if (!
|
|
26590
|
-
const files = { exists: (r) =>
|
|
26658
|
+
if (!existsSync49(dir)) throw new ReposError(`${dir} does not exist on disk`);
|
|
26659
|
+
const files = { exists: (r) => existsSync49(resolvePath(dir, r)), list: (r) => {
|
|
26591
26660
|
try {
|
|
26592
26661
|
return readdirSync20(resolvePath(dir, r));
|
|
26593
26662
|
} catch {
|
|
@@ -26595,7 +26664,7 @@ ${cu.tail.trimEnd()}
|
|
|
26595
26664
|
}
|
|
26596
26665
|
}, read: (r) => {
|
|
26597
26666
|
try {
|
|
26598
|
-
return
|
|
26667
|
+
return readFileSync44(resolvePath(dir, r), "utf8");
|
|
26599
26668
|
} catch {
|
|
26600
26669
|
return void 0;
|
|
26601
26670
|
}
|
|
@@ -26637,19 +26706,19 @@ ${cu.tail.trimEnd()}
|
|
|
26637
26706
|
let cleared;
|
|
26638
26707
|
if (clear2) {
|
|
26639
26708
|
const plan = clearRouteHooks(configText, a.route, a.name);
|
|
26640
|
-
if (plan.removed.length > 0)
|
|
26709
|
+
if (plan.removed.length > 0) writeFileSync37(cfg.configFile, plan.text);
|
|
26641
26710
|
cleared = { file: cfg.configFile, removed: plan.removed };
|
|
26642
26711
|
}
|
|
26643
26712
|
if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
26644
26713
|
if (write === "route") {
|
|
26645
26714
|
const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
|
|
26646
|
-
|
|
26715
|
+
writeFileSync37(cfg.configFile, plan.text);
|
|
26647
26716
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
|
|
26648
26717
|
} else if (write === "repo") {
|
|
26649
26718
|
const have = existingRepoConfig(files);
|
|
26650
26719
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
26651
26720
|
const file = resolvePath(dir, ".crew.yaml");
|
|
26652
|
-
|
|
26721
|
+
writeFileSync37(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
|
|
26653
26722
|
written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
|
|
26654
26723
|
}
|
|
26655
26724
|
if (flag("json")) {
|
|
@@ -26705,7 +26774,7 @@ ${cu.tail.trimEnd()}
|
|
|
26705
26774
|
if (sub === "list" && !routeName) usage2();
|
|
26706
26775
|
if (sub === "add") {
|
|
26707
26776
|
try {
|
|
26708
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
26777
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync49(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
|
|
26709
26778
|
routeName = a.route;
|
|
26710
26779
|
addPath = a.path;
|
|
26711
26780
|
addName = a.name;
|
|
@@ -26735,7 +26804,7 @@ ${cu.tail.trimEnd()}
|
|
|
26735
26804
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
26736
26805
|
if (sub === "list" || sub === "add") {
|
|
26737
26806
|
const liveRoute = cfg.routes.find((r) => r.route === routeName);
|
|
26738
|
-
if (liveRoute?.resolved?.reposModelId &&
|
|
26807
|
+
if (liveRoute?.resolved?.reposModelId && existsSync49(resolvedFile) && !dryRun) {
|
|
26739
26808
|
try {
|
|
26740
26809
|
await hydrateApiKeys([liveRoute]);
|
|
26741
26810
|
persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
|
|
@@ -26744,16 +26813,16 @@ ${cu.tail.trimEnd()}
|
|
|
26744
26813
|
}
|
|
26745
26814
|
}
|
|
26746
26815
|
let resolvedRemotes;
|
|
26747
|
-
if (
|
|
26816
|
+
if (existsSync49(resolvedFile)) {
|
|
26748
26817
|
try {
|
|
26749
|
-
resolvedRemotes = JSON.parse(
|
|
26818
|
+
resolvedRemotes = JSON.parse(readFileSync44(resolvedFile, "utf8"));
|
|
26750
26819
|
} catch {
|
|
26751
26820
|
}
|
|
26752
26821
|
}
|
|
26753
26822
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
26754
26823
|
const text2 = configText;
|
|
26755
26824
|
const base = dirname32(cfg.configFile);
|
|
26756
|
-
const isCheckout = (d) =>
|
|
26825
|
+
const isCheckout = (d) => existsSync49(`${d}/.git`);
|
|
26757
26826
|
try {
|
|
26758
26827
|
if (sub === "list") {
|
|
26759
26828
|
if (flag("json")) {
|
|
@@ -26763,11 +26832,11 @@ ${cu.tail.trimEnd()}
|
|
|
26763
26832
|
base,
|
|
26764
26833
|
resolved: resolvedRemotes,
|
|
26765
26834
|
originOf,
|
|
26766
|
-
exists: (d) =>
|
|
26835
|
+
exists: (d) => existsSync49(d),
|
|
26767
26836
|
isCheckout,
|
|
26768
26837
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
26769
26838
|
try {
|
|
26770
|
-
return Object.keys(parseRepoConfig(
|
|
26839
|
+
return Object.keys(parseRepoConfig(readFileSync44(resolvePath(d, f), "utf8"), f).hooks).length > 0;
|
|
26771
26840
|
} catch {
|
|
26772
26841
|
return false;
|
|
26773
26842
|
}
|
|
@@ -26784,7 +26853,7 @@ ${cu.tail.trimEnd()}
|
|
|
26784
26853
|
for (const r of repos) {
|
|
26785
26854
|
const origin2 = originOf(r.dir);
|
|
26786
26855
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
26787
|
-
const note = !
|
|
26856
|
+
const note = !existsSync49(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
|
|
26788
26857
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
26789
26858
|
`);
|
|
26790
26859
|
}
|
|
@@ -26796,9 +26865,9 @@ ${cu.tail.trimEnd()}
|
|
|
26796
26865
|
` : `${human}
|
|
26797
26866
|
`);
|
|
26798
26867
|
const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
|
|
26799
|
-
if (
|
|
26868
|
+
if (existsSync49(plan2.target) && (existsSync49(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
|
|
26800
26869
|
throw new ReposError(
|
|
26801
|
-
|
|
26870
|
+
existsSync49(`${plan2.target}/.git`) ? `${plan2.target} is already a checkout \u2014 attach it with \`crew repos add ${routeName} ${plan2.name} ${plan2.target}\`` : `${plan2.target} exists and is not empty \u2014 pass --dir to clone somewhere else`
|
|
26802
26871
|
);
|
|
26803
26872
|
}
|
|
26804
26873
|
if (dryRun) {
|
|
@@ -26816,14 +26885,14 @@ ${cu.tail.trimEnd()}
|
|
|
26816
26885
|
throw new ReposError(`${e.message}${authHint}
|
|
26817
26886
|
crew.yaml was not changed`);
|
|
26818
26887
|
}
|
|
26819
|
-
|
|
26888
|
+
writeFileSync37(cfg.configFile, plan2.text);
|
|
26820
26889
|
if (plan2.droppedDir) process.stderr.write(`crew repos: dropped dir: ${plan2.droppedDir} from route ${routeName} \u2014 it is not a checkout (the clone base); checkouts now live under repos:
|
|
26821
26890
|
`);
|
|
26822
26891
|
say("attached", { route: routeName, name: plan2.name, dir: plan2.target, mode: plan2.mode }, `cloned and attached ${plan2.target} to ${routeName} as ${plan2.mode === "dir" ? "dir" : `repos.${plan2.name}`} in ${cfg.configFile}`);
|
|
26823
26892
|
break;
|
|
26824
26893
|
}
|
|
26825
26894
|
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
|
|
26826
|
-
const top =
|
|
26895
|
+
const top = existsSync49(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
26827
26896
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
26828
26897
|
const origin = originOf(top);
|
|
26829
26898
|
const plan = planRepoAdd({
|
|
@@ -26875,15 +26944,15 @@ ${cu.tail.trimEnd()}
|
|
|
26875
26944
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
26876
26945
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
26877
26946
|
`);
|
|
26878
|
-
if (
|
|
26879
|
-
const raw = JSON.parse(
|
|
26947
|
+
if (existsSync49(resolvedFile)) {
|
|
26948
|
+
const raw = JSON.parse(readFileSync44(resolvedFile, "utf8"));
|
|
26880
26949
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
26881
26950
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
26882
|
-
|
|
26951
|
+
writeFileSync37(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
26883
26952
|
`);
|
|
26884
26953
|
}
|
|
26885
26954
|
}
|
|
26886
|
-
|
|
26955
|
+
writeFileSync37(cfg.configFile, plan.text);
|
|
26887
26956
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
26888
26957
|
`);
|
|
26889
26958
|
} catch (e) {
|
|
@@ -27254,7 +27323,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27254
27323
|
process.exit(2);
|
|
27255
27324
|
}
|
|
27256
27325
|
if (found.shipNameAdopted && !dryRun) {
|
|
27257
|
-
|
|
27326
|
+
writeFileSync37(cfg.configFile, setShipNameInYaml(readFileSync44(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
27258
27327
|
cfg.ship.name = found.shipNameAdopted;
|
|
27259
27328
|
}
|
|
27260
27329
|
if (found.shipRow) {
|
|
@@ -27297,10 +27366,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27297
27366
|
}
|
|
27298
27367
|
}
|
|
27299
27368
|
let previous;
|
|
27300
|
-
const hadPreviousFile =
|
|
27369
|
+
const hadPreviousFile = existsSync49(resolvedPath);
|
|
27301
27370
|
if (hadPreviousFile) {
|
|
27302
27371
|
try {
|
|
27303
|
-
previous = JSON.parse(
|
|
27372
|
+
previous = JSON.parse(readFileSync44(resolvedPath, "utf8"));
|
|
27304
27373
|
} catch {
|
|
27305
27374
|
}
|
|
27306
27375
|
}
|
|
@@ -27430,10 +27499,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27430
27499
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
27431
27500
|
`);
|
|
27432
27501
|
} else {
|
|
27433
|
-
|
|
27502
|
+
mkdirSync37(dirname32(resolvedPath), { recursive: true });
|
|
27434
27503
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
27435
27504
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
27436
|
-
|
|
27505
|
+
writeFileSync37(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
27437
27506
|
`);
|
|
27438
27507
|
renameSync6(tmpPath, resolvedPath);
|
|
27439
27508
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
@@ -27479,8 +27548,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27479
27548
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
27480
27549
|
`);
|
|
27481
27550
|
} else {
|
|
27482
|
-
|
|
27483
|
-
|
|
27551
|
+
mkdirSync37(dirname32(apiKeyFile), { recursive: true });
|
|
27552
|
+
writeFileSync37(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
27484
27553
|
`, { mode: 384 });
|
|
27485
27554
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
27486
27555
|
`);
|
|
@@ -28361,7 +28430,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28361
28430
|
const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
|
|
28362
28431
|
const resumed = await runPeriscopeResume({
|
|
28363
28432
|
find: async () => hit && { row: hit.row, tag: hit.tag },
|
|
28364
|
-
worktreeExists: (p) =>
|
|
28433
|
+
worktreeExists: (p) => existsSync49(p),
|
|
28365
28434
|
prepareDropFile,
|
|
28366
28435
|
startProxy: (wt, url, n) => launchProxy(crewInvocation(CREW_HOME), wt, url, Number(n), { host: hit.row.host || "localhost", inject: hit.repo.config.periscope?.inspector !== "repo" }),
|
|
28367
28436
|
caps: periscopeCaps(cfg.ship),
|
|
@@ -28457,10 +28526,10 @@ ${where.join("\n")}`);
|
|
|
28457
28526
|
},
|
|
28458
28527
|
prepare: async (path) => {
|
|
28459
28528
|
for (const f of repoP.config.worktrees.copy) {
|
|
28460
|
-
const src =
|
|
28461
|
-
if (!
|
|
28462
|
-
|
|
28463
|
-
copyFileSync2(src,
|
|
28529
|
+
const src = join53(repoP.dir, f);
|
|
28530
|
+
if (!existsSync49(src)) continue;
|
|
28531
|
+
mkdirSync37(dirOf2(join53(path, f)), { recursive: true });
|
|
28532
|
+
copyFileSync2(src, join53(path, f));
|
|
28464
28533
|
}
|
|
28465
28534
|
const setup = repoP.config.hooks.setup;
|
|
28466
28535
|
return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
|
|
@@ -28478,7 +28547,7 @@ ${where.join("\n")}`);
|
|
|
28478
28547
|
mainCheckout: repoP.dir
|
|
28479
28548
|
});
|
|
28480
28549
|
}
|
|
28481
|
-
if (name === "intel" &&
|
|
28550
|
+
if (name === "intel" && lspFlavor(script)) return null;
|
|
28482
28551
|
return script ? runScript(script, { cwd, env, shell: shellP, timeoutMs: name === "preview" ? 18e4 : name === "intel" ? 6e4 : void 0 }) : null;
|
|
28483
28552
|
},
|
|
28484
28553
|
createRow: (fields) => trackerP.createPeriscope(fields),
|
|
@@ -28545,6 +28614,7 @@ ${where.join("\n")}`);
|
|
|
28545
28614
|
now: () => /* @__PURE__ */ new Date(),
|
|
28546
28615
|
host: a.host ?? "localhost",
|
|
28547
28616
|
intelStartupSeconds: cfg.ship.intelStartupSeconds,
|
|
28617
|
+
lspPluginDir: lspFlavor(repoP.config.hooks.intel) ? ensureLspPlugin(cfg.ship.stateDir, lspFlavor(repoP.config.hooks.intel), repoP.dir) : void 0,
|
|
28548
28618
|
say
|
|
28549
28619
|
};
|
|
28550
28620
|
if (dryRun) {
|
|
@@ -28769,7 +28839,7 @@ ${where.join("\n")}`);
|
|
|
28769
28839
|
};
|
|
28770
28840
|
const droppedRoutes = [];
|
|
28771
28841
|
try {
|
|
28772
|
-
const rawCfg = parseYaml2(
|
|
28842
|
+
const rawCfg = parseYaml2(readFileSync44(cfg.configFile, "utf8"));
|
|
28773
28843
|
for (const r of rawCfg?.routes ?? []) {
|
|
28774
28844
|
if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
|
|
28775
28845
|
try {
|
|
@@ -28822,8 +28892,8 @@ ${where.join("\n")}`);
|
|
|
28822
28892
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
28823
28893
|
break;
|
|
28824
28894
|
}
|
|
28825
|
-
|
|
28826
|
-
|
|
28895
|
+
mkdirSync37(cfg.ship.stateDir, { recursive: true });
|
|
28896
|
+
writeFileSync37(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
28827
28897
|
`);
|
|
28828
28898
|
process.stdout.write(
|
|
28829
28899
|
"crew reload: requested \u2014 the daemon relaunches with the new config once its running agents end (it starts no new work meanwhile); if it is busy, `crew reload --now` forces the relaunch and interrupts them\n"
|
|
@@ -28888,7 +28958,7 @@ ${where.join("\n")}`);
|
|
|
28888
28958
|
}
|
|
28889
28959
|
case "log": {
|
|
28890
28960
|
try {
|
|
28891
|
-
const text2 =
|
|
28961
|
+
const text2 = readFileSync44(cfg.ship.logFile, "utf8").split("\n");
|
|
28892
28962
|
process.stdout.write(`${text2.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
28893
28963
|
`);
|
|
28894
28964
|
} catch {
|
|
@@ -28911,7 +28981,7 @@ ${where.join("\n")}`);
|
|
|
28911
28981
|
};
|
|
28912
28982
|
if (!route2.resolved) fail2(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
|
|
28913
28983
|
const tracker = new Tracker(route2, cfg.ship);
|
|
28914
|
-
const configText =
|
|
28984
|
+
const configText = readFileSync44(cfg.configFile, "utf8");
|
|
28915
28985
|
const cfgBase = dirname32(cfg.configFile);
|
|
28916
28986
|
const configured2 = configuredRepos(configText, route2.route, cfgBase);
|
|
28917
28987
|
const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
|
|
@@ -28940,7 +29010,7 @@ ${where.join("\n")}`);
|
|
|
28940
29010
|
}
|
|
28941
29011
|
if (configured2.some((r) => r.dir === dir)) {
|
|
28942
29012
|
try {
|
|
28943
|
-
|
|
29013
|
+
writeFileSync37(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
|
|
28944
29014
|
} catch (e) {
|
|
28945
29015
|
if (e instanceof ReposError) fail2(e.message);
|
|
28946
29016
|
throw e;
|
|
@@ -28948,29 +29018,29 @@ ${where.join("\n")}`);
|
|
|
28948
29018
|
}
|
|
28949
29019
|
if (row) {
|
|
28950
29020
|
await tracker.deleteRepoRow(row.id);
|
|
28951
|
-
if (
|
|
28952
|
-
const raw = JSON.parse(
|
|
29021
|
+
if (existsSync49(resolvedFile)) {
|
|
29022
|
+
const raw = JSON.parse(readFileSync44(resolvedFile, "utf8"));
|
|
28953
29023
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
28954
29024
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
28955
|
-
|
|
29025
|
+
writeFileSync37(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
28956
29026
|
`);
|
|
28957
29027
|
}
|
|
28958
29028
|
}
|
|
28959
|
-
if (
|
|
29029
|
+
if (existsSync49(join53(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync24(dir, { recursive: true, force: true });
|
|
28960
29030
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
28961
29031
|
break;
|
|
28962
29032
|
}
|
|
28963
29033
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
28964
|
-
const fresh = !
|
|
28965
|
-
if (
|
|
29034
|
+
const fresh = !existsSync49(join53(dir, ".git"));
|
|
29035
|
+
if (existsSync49(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
28966
29036
|
if (dryRun) {
|
|
28967
29037
|
say("dry-run", { dir, newCheckout: fresh, repoRow: row?.id ?? null }, `would ${fresh ? "create" : "reuse"} ${dir}${row ? "" : ", create the Repos row"}, and file one ${tracker.contract.statuses.approved} ticket`);
|
|
28968
29038
|
break;
|
|
28969
29039
|
}
|
|
28970
29040
|
if (fresh) {
|
|
28971
|
-
|
|
28972
|
-
|
|
28973
|
-
|
|
29041
|
+
mkdirSync37(dir, { recursive: true });
|
|
29042
|
+
writeFileSync37(join53(dir, "README.md"), SAMPLE_README);
|
|
29043
|
+
writeFileSync37(join53(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
28974
29044
|
const g = (...a) => {
|
|
28975
29045
|
const r = spawnSync11("git", a, { cwd: dir, encoding: "utf8" });
|
|
28976
29046
|
if (r.status !== 0) fail2(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -28988,17 +29058,17 @@ ${where.join("\n")}`);
|
|
|
28988
29058
|
spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
|
|
28989
29059
|
});
|
|
28990
29060
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
28991
|
-
if (
|
|
28992
|
-
const raw = JSON.parse(
|
|
29061
|
+
if (existsSync49(resolvedFile)) {
|
|
29062
|
+
const raw = JSON.parse(readFileSync44(resolvedFile, "utf8"));
|
|
28993
29063
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
28994
29064
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
28995
|
-
|
|
29065
|
+
writeFileSync37(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
28996
29066
|
`);
|
|
28997
29067
|
}
|
|
28998
29068
|
}
|
|
28999
29069
|
if (!configured2.some((r) => r.dir === dir)) {
|
|
29000
29070
|
try {
|
|
29001
|
-
|
|
29071
|
+
writeFileSync37(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
|
|
29002
29072
|
} catch (e) {
|
|
29003
29073
|
if (e instanceof ReposError) fail2(e.message);
|
|
29004
29074
|
throw e;
|
|
@@ -29344,7 +29414,7 @@ watermark: ${state.watermark()}
|
|
|
29344
29414
|
}
|
|
29345
29415
|
for (const r of repos) {
|
|
29346
29416
|
const remote = r.config.branch.remote;
|
|
29347
|
-
if (
|
|
29417
|
+
if (existsSync49(r.dir)) {
|
|
29348
29418
|
pushAuth.push({
|
|
29349
29419
|
route: c.route,
|
|
29350
29420
|
repo: r.name,
|
|
@@ -29384,7 +29454,11 @@ watermark: ${state.watermark()}
|
|
|
29384
29454
|
hooks: r.config.hooks,
|
|
29385
29455
|
onPath: (cmd) => !!findOnPath(cmd, doctorSchedulerPath),
|
|
29386
29456
|
intelSyntax: r.config.hooks.intel && !intelFlavor(r.config.hooks.intel) ? checkHookSyntax(r.config.hooks.intel, r.config.shell) : void 0,
|
|
29387
|
-
intelFlavor: intelFlavor(r.config.hooks.intel) ? { name: intelFlavor(r.config.hooks.intel), missing:
|
|
29457
|
+
intelFlavor: intelFlavor(r.config.hooks.intel) ? { name: intelFlavor(r.config.hooks.intel), missing: [
|
|
29458
|
+
...missingPrerequisites(intelFlavor(r.config.hooks.intel), (bin) => !!findOnPath(bin, doctorSchedulerPath)),
|
|
29459
|
+
// CREW-2916: an `*-lsp` flavor needs the language server in the checkout or on PATH.
|
|
29460
|
+
...lspFlavor(r.config.hooks.intel) && !lspBinaryFound(lspFlavor(r.config.hooks.intel), r.dir, (bin) => !!findOnPath(bin, doctorSchedulerPath)) ? [`${LSP_SERVERS[lspFlavor(r.config.hooks.intel)].bin} (${LSP_SERVERS[lspFlavor(r.config.hooks.intel)].install})`] : []
|
|
29461
|
+
] } : void 0
|
|
29388
29462
|
});
|
|
29389
29463
|
problems.push(...f.problems);
|
|
29390
29464
|
passed += f.passed;
|
|
@@ -29435,7 +29509,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
29435
29509
|
}
|
|
29436
29510
|
try {
|
|
29437
29511
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
29438
|
-
const evSize =
|
|
29512
|
+
const evSize = existsSync49(evFile) ? statSync16(evFile).size : 0;
|
|
29439
29513
|
process.stdout.write(
|
|
29440
29514
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
29441
29515
|
` : "events log: ok\n"
|
|
@@ -29446,7 +29520,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
29446
29520
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run", installContext());
|
|
29447
29521
|
const abandons = unitAbandonsAgents(unitPlan, (p) => {
|
|
29448
29522
|
try {
|
|
29449
|
-
return
|
|
29523
|
+
return readFileSync44(p, "utf8");
|
|
29450
29524
|
} catch {
|
|
29451
29525
|
return null;
|
|
29452
29526
|
}
|