@tablation/crew 0.113.0 → 0.115.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 +910 -743
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -45,12 +45,12 @@ function periscopeSummary(cfg) {
|
|
|
45
45
|
if (routes.length === 0) return `off (no enabled route sets hostPeriscopes: true; ${limits})`;
|
|
46
46
|
return `ON for ${routes.join(", ")} (host ${cfg.ship.previewHost ?? "NOT SET (ship.previewHost)"}, ${limits})`;
|
|
47
47
|
}
|
|
48
|
-
async function previewHostProblem(cfg,
|
|
48
|
+
async function previewHostProblem(cfg, resolve8 = (h) => lookup(h)) {
|
|
49
49
|
if (periscopeRoutes(cfg).length === 0) return void 0;
|
|
50
50
|
const host = cfg.ship.previewHost;
|
|
51
51
|
if (!host) return "ship.previewHost is not set \u2014 a route hosts periscopes, so Passengers have no host to be sent to";
|
|
52
52
|
try {
|
|
53
|
-
await
|
|
53
|
+
await resolve8(host);
|
|
54
54
|
} catch {
|
|
55
55
|
return `ship.previewHost "${host}" does not resolve from this machine`;
|
|
56
56
|
}
|
|
@@ -103,15 +103,92 @@ function duplicateLivePeriscopes(rows, tagOf, shipName2, route2) {
|
|
|
103
103
|
return out;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
// src/periscope-pointed.ts
|
|
107
|
+
import { execFileSync } from "child_process";
|
|
108
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
109
|
+
import { dirname, isAbsolute, join, resolve } from "path";
|
|
110
|
+
var POINTED_AT_DIR = ".crew";
|
|
111
|
+
var POINTED_AT_REL = `${POINTED_AT_DIR}/pointed-at.jsonl`;
|
|
112
|
+
var pointedAtFile = (worktree) => join(worktree, POINTED_AT_REL);
|
|
113
|
+
function pointedAtEnv(worktree) {
|
|
114
|
+
return { CREW_PERISCOPE: "1", CREW_POINTED_AT_FILE: pointedAtFile(worktree) };
|
|
115
|
+
}
|
|
116
|
+
function excludeFile(worktree) {
|
|
117
|
+
try {
|
|
118
|
+
const p = execFileSync("git", ["rev-parse", "--git-path", "info/exclude"], {
|
|
119
|
+
cwd: worktree,
|
|
120
|
+
encoding: "utf8",
|
|
121
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
122
|
+
}).trim();
|
|
123
|
+
return p ? isAbsolute(p) ? p : resolve(worktree, p) : void 0;
|
|
124
|
+
} catch {
|
|
125
|
+
return void 0;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function prepareDropFile(worktree) {
|
|
129
|
+
const file = pointedAtFile(worktree);
|
|
130
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
131
|
+
if (!existsSync2(file)) writeFileSync(file, "");
|
|
132
|
+
const ex = excludeFile(worktree);
|
|
133
|
+
if (ex) {
|
|
134
|
+
mkdirSync(dirname(ex), { recursive: true });
|
|
135
|
+
const have = existsSync2(ex) ? readFileSync(ex, "utf8") : "";
|
|
136
|
+
if (!have.split("\n").some((l) => l.trim() === `${POINTED_AT_DIR}/`)) {
|
|
137
|
+
appendFileSync(ex, `${have === "" || have.endsWith("\n") ? "" : "\n"}${POINTED_AT_DIR}/
|
|
138
|
+
`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return file;
|
|
142
|
+
}
|
|
143
|
+
function parseLine(raw) {
|
|
144
|
+
let v;
|
|
145
|
+
try {
|
|
146
|
+
v = JSON.parse(raw);
|
|
147
|
+
} catch {
|
|
148
|
+
return void 0;
|
|
149
|
+
}
|
|
150
|
+
if (!v || typeof v !== "object") return void 0;
|
|
151
|
+
const o = v;
|
|
152
|
+
if (typeof o.path !== "string" || o.path === "" || typeof o.line !== "number" || !Number.isInteger(o.line) || o.line < 1) return void 0;
|
|
153
|
+
if (typeof o.at !== "string") return void 0;
|
|
154
|
+
const out = { path: o.path, line: o.line, at: o.at };
|
|
155
|
+
if (typeof o.column === "number") out.column = o.column;
|
|
156
|
+
if (typeof o.component === "string") out.component = o.component;
|
|
157
|
+
if (typeof o.url === "string") out.url = o.url;
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
function readPointedAt(worktree) {
|
|
161
|
+
let text2;
|
|
162
|
+
try {
|
|
163
|
+
text2 = readFileSync(pointedAtFile(worktree), "utf8");
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const lines2 = text2.split("\n");
|
|
168
|
+
for (let i = lines2.length - 1; i >= 0; i--) {
|
|
169
|
+
if (lines2[i].trim() === "") continue;
|
|
170
|
+
const p = parseLine(lines2[i]);
|
|
171
|
+
if (p) return p;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
function dropFileMtime(worktree) {
|
|
176
|
+
try {
|
|
177
|
+
return statSync(pointedAtFile(worktree)).mtimeMs;
|
|
178
|
+
} catch {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
106
183
|
// src/periscope-state.ts
|
|
107
|
-
import { existsSync as
|
|
184
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
108
185
|
import { connect } from "net";
|
|
109
|
-
import { join } from "path";
|
|
110
|
-
var dirOf = (stateDir) =>
|
|
111
|
-
var fileOf = (stateDir, ticketRecordId) =>
|
|
186
|
+
import { join as join2 } from "path";
|
|
187
|
+
var dirOf = (stateDir) => join2(stateDir, "periscopes");
|
|
188
|
+
var fileOf = (stateDir, ticketRecordId) => join2(dirOf(stateDir), `${ticketRecordId}.json`);
|
|
112
189
|
function writePeriscopeSidecar(stateDir, sc) {
|
|
113
|
-
|
|
114
|
-
|
|
190
|
+
mkdirSync2(dirOf(stateDir), { recursive: true });
|
|
191
|
+
writeFileSync2(fileOf(stateDir, sc.ticketRecordId), `${JSON.stringify(sc, null, 2)}
|
|
115
192
|
`);
|
|
116
193
|
}
|
|
117
194
|
function removePeriscopeSidecar(stateDir, ticketRecordId) {
|
|
@@ -119,12 +196,12 @@ function removePeriscopeSidecar(stateDir, ticketRecordId) {
|
|
|
119
196
|
}
|
|
120
197
|
function readPeriscopeSidecars(stateDir) {
|
|
121
198
|
const dir = dirOf(stateDir);
|
|
122
|
-
if (!
|
|
199
|
+
if (!existsSync3(dir)) return [];
|
|
123
200
|
const out = [];
|
|
124
201
|
for (const name of readdirSync(dir)) {
|
|
125
202
|
if (!name.endsWith(".json")) continue;
|
|
126
203
|
try {
|
|
127
|
-
const sc = JSON.parse(
|
|
204
|
+
const sc = JSON.parse(readFileSync2(join2(dir, name), "utf8"));
|
|
128
205
|
if (sc && typeof sc.ticketRecordId === "string" && typeof sc.url === "string") out.push(sc);
|
|
129
206
|
} catch {
|
|
130
207
|
}
|
|
@@ -140,11 +217,11 @@ function urlPort(url) {
|
|
|
140
217
|
}
|
|
141
218
|
}
|
|
142
219
|
function portListening(port, timeoutMs = 500) {
|
|
143
|
-
return new Promise((
|
|
220
|
+
return new Promise((resolve8) => {
|
|
144
221
|
const s = connect({ port, host: "127.0.0.1" });
|
|
145
222
|
const done = (ok) => {
|
|
146
223
|
s.destroy();
|
|
147
|
-
|
|
224
|
+
resolve8(ok);
|
|
148
225
|
};
|
|
149
226
|
s.setTimeout(timeoutMs, () => done(false));
|
|
150
227
|
s.once("connect", () => done(true));
|
|
@@ -154,7 +231,7 @@ function portListening(port, timeoutMs = 500) {
|
|
|
154
231
|
async function periscopesForStatus(stateDir, probe = portListening) {
|
|
155
232
|
return Promise.all(readPeriscopeSidecars(stateDir).map(async (sc) => {
|
|
156
233
|
const port = urlPort(sc.url);
|
|
157
|
-
return { ...sc, serving: port === void 0 ? false : await probe(port) };
|
|
234
|
+
return { ...sc, serving: port === void 0 ? false : await probe(port), pointedAt: readPointedAt(sc.worktree) };
|
|
158
235
|
}));
|
|
159
236
|
}
|
|
160
237
|
|
|
@@ -242,23 +319,23 @@ function shellInitScript(shell) {
|
|
|
242
319
|
}
|
|
243
320
|
|
|
244
321
|
// src/config.ts
|
|
245
|
-
import { readFileSync as
|
|
246
|
-
import { resolve, dirname as
|
|
322
|
+
import { readFileSync as readFileSync12, existsSync as existsSync11 } from "fs";
|
|
323
|
+
import { resolve as resolve2, dirname as dirname5, isAbsolute as isAbsolute2, join as join13, basename as basename4 } from "path";
|
|
247
324
|
import { homedir as homedir6, tmpdir } from "os";
|
|
248
325
|
import { parse as parse2 } from "yaml";
|
|
249
326
|
|
|
250
327
|
// src/events.ts
|
|
251
328
|
import {
|
|
252
|
-
appendFileSync,
|
|
253
|
-
mkdirSync as
|
|
254
|
-
existsSync as
|
|
255
|
-
statSync,
|
|
329
|
+
appendFileSync as appendFileSync2,
|
|
330
|
+
mkdirSync as mkdirSync3,
|
|
331
|
+
existsSync as existsSync4,
|
|
332
|
+
statSync as statSync2,
|
|
256
333
|
renameSync,
|
|
257
334
|
openSync,
|
|
258
335
|
closeSync,
|
|
259
336
|
rmSync as rmSync2
|
|
260
337
|
} from "fs";
|
|
261
|
-
import { join as
|
|
338
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
262
339
|
function render(e) {
|
|
263
340
|
const scope = e.role ? `${e.step}[${e.role}]` : e.step;
|
|
264
341
|
const ticket = e.ticket ? ` ${e.ticket}` : "";
|
|
@@ -270,7 +347,7 @@ var ROTATED_EVENTS_RE = /^events\.\d{8}T\d{6}Z(?:-\d+)?\.jsonl$/;
|
|
|
270
347
|
var ROTATE_LOCK_STALE_MS = 6e4;
|
|
271
348
|
function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
|
|
272
349
|
try {
|
|
273
|
-
if (!
|
|
350
|
+
if (!existsSync4(file) || statSync2(file).size <= maxBytes) return void 0;
|
|
274
351
|
} catch {
|
|
275
352
|
return void 0;
|
|
276
353
|
}
|
|
@@ -280,7 +357,7 @@ function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
|
|
|
280
357
|
closeSync(openSync(lock, "wx"));
|
|
281
358
|
} catch {
|
|
282
359
|
try {
|
|
283
|
-
if (Date.now() -
|
|
360
|
+
if (Date.now() - statSync2(lock).mtimeMs < ROTATE_LOCK_STALE_MS) return void 0;
|
|
284
361
|
rmSync2(lock, { force: true });
|
|
285
362
|
closeSync(openSync(lock, "wx"));
|
|
286
363
|
} catch {
|
|
@@ -288,10 +365,10 @@ function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
|
|
|
288
365
|
}
|
|
289
366
|
}
|
|
290
367
|
try {
|
|
291
|
-
if (
|
|
368
|
+
if (statSync2(file).size <= maxBytes) return void 0;
|
|
292
369
|
const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
|
|
293
|
-
let dest =
|
|
294
|
-
for (let n = 1;
|
|
370
|
+
let dest = join3(dirname2(file), `events.${stamp}.jsonl`);
|
|
371
|
+
for (let n = 1; existsSync4(dest); n++) dest = join3(dirname2(file), `events.${stamp}-${n}.jsonl`);
|
|
295
372
|
renameSync(file, dest);
|
|
296
373
|
return dest;
|
|
297
374
|
} finally {
|
|
@@ -314,7 +391,7 @@ var Emitter = class _Emitter {
|
|
|
314
391
|
for (const f of [opts.eventFile, opts.logFile]) {
|
|
315
392
|
if (!f) continue;
|
|
316
393
|
try {
|
|
317
|
-
|
|
394
|
+
mkdirSync3(dirname2(f), { recursive: true });
|
|
318
395
|
} catch {
|
|
319
396
|
}
|
|
320
397
|
}
|
|
@@ -342,12 +419,12 @@ var Emitter = class _Emitter {
|
|
|
342
419
|
} catch {
|
|
343
420
|
}
|
|
344
421
|
try {
|
|
345
|
-
if (this.opts.eventFile)
|
|
422
|
+
if (this.opts.eventFile) appendFileSync2(this.opts.eventFile, `${JSON.stringify(e)}
|
|
346
423
|
`);
|
|
347
424
|
} catch {
|
|
348
425
|
}
|
|
349
426
|
try {
|
|
350
|
-
if (this.opts.logFile)
|
|
427
|
+
if (this.opts.logFile) appendFileSync2(this.opts.logFile, `${line}
|
|
351
428
|
`);
|
|
352
429
|
} catch {
|
|
353
430
|
}
|
|
@@ -390,18 +467,18 @@ var Emitter = class _Emitter {
|
|
|
390
467
|
}
|
|
391
468
|
}
|
|
392
469
|
};
|
|
393
|
-
var eventFileFor = (stateDir) =>
|
|
470
|
+
var eventFileFor = (stateDir) => join3(stateDir, "events.jsonl");
|
|
394
471
|
|
|
395
472
|
// src/pair-install.ts
|
|
396
|
-
import { existsSync as
|
|
473
|
+
import { existsSync as existsSync7, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
397
474
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
398
475
|
import { homedir as homedir5 } from "os";
|
|
399
|
-
import { join as
|
|
476
|
+
import { join as join9 } from "path";
|
|
400
477
|
|
|
401
478
|
// src/adapters/claude.ts
|
|
402
|
-
import { readFileSync as
|
|
479
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
403
480
|
import { homedir as homedir3 } from "os";
|
|
404
|
-
import { join as
|
|
481
|
+
import { join as join7 } from "path";
|
|
405
482
|
|
|
406
483
|
// src/stream.ts
|
|
407
484
|
function toolTarget(name, input) {
|
|
@@ -460,19 +537,19 @@ function extractResult(line) {
|
|
|
460
537
|
|
|
461
538
|
// src/claude-mcp.ts
|
|
462
539
|
import { spawnSync } from "child_process";
|
|
463
|
-
import { readFileSync as
|
|
540
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
464
541
|
import { homedir } from "os";
|
|
465
|
-
import { join as
|
|
542
|
+
import { join as join5 } from "path";
|
|
466
543
|
|
|
467
544
|
// src/mcp-registrations.ts
|
|
468
545
|
import { createHash } from "crypto";
|
|
469
|
-
import { mkdirSync as
|
|
470
|
-
import { join as
|
|
471
|
-
var fileFor = (stateDir) =>
|
|
546
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
547
|
+
import { join as join4 } from "path";
|
|
548
|
+
var fileFor = (stateDir) => join4(stateDir, "mcp-registrations.json");
|
|
472
549
|
var keyFingerprint = (key) => createHash("sha256").update(key).digest("hex").slice(0, 8);
|
|
473
550
|
function readMcpRegistrations(stateDir) {
|
|
474
551
|
try {
|
|
475
|
-
const j = JSON.parse(
|
|
552
|
+
const j = JSON.parse(readFileSync4(fileFor(stateDir), "utf8"));
|
|
476
553
|
return Array.isArray(j) ? j : [];
|
|
477
554
|
} catch {
|
|
478
555
|
return [];
|
|
@@ -480,8 +557,8 @@ function readMcpRegistrations(stateDir) {
|
|
|
480
557
|
}
|
|
481
558
|
function recordMcpRegistration(stateDir, r, now = /* @__PURE__ */ new Date()) {
|
|
482
559
|
const rest = readMcpRegistrations(stateDir).filter((x) => !(x.harness === r.harness && x.scope === r.scope && x.name === r.name));
|
|
483
|
-
|
|
484
|
-
|
|
560
|
+
mkdirSync4(stateDir, { recursive: true });
|
|
561
|
+
writeFileSync3(fileFor(stateDir), `${JSON.stringify([...rest, { ...r, at: now.toISOString() }], null, 2)}
|
|
485
562
|
`, { mode: 384 });
|
|
486
563
|
}
|
|
487
564
|
function findMcpRegistration(stateDir, r) {
|
|
@@ -504,7 +581,7 @@ function runClaude(bin, args) {
|
|
|
504
581
|
}
|
|
505
582
|
function userScopeEntry(home) {
|
|
506
583
|
try {
|
|
507
|
-
const j = JSON.parse(
|
|
584
|
+
const j = JSON.parse(readFileSync5(join5(home, ".claude.json"), "utf8"));
|
|
508
585
|
return j.mcpServers?.[MCP_SERVER_NAME];
|
|
509
586
|
} catch {
|
|
510
587
|
return void 0;
|
|
@@ -588,8 +665,8 @@ function removeClaudeMcp(o) {
|
|
|
588
665
|
}
|
|
589
666
|
|
|
590
667
|
// src/launcher.ts
|
|
591
|
-
import { basename, join as
|
|
592
|
-
import { existsSync as
|
|
668
|
+
import { basename, join as join6 } from "path";
|
|
669
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6, readdirSync as readdirSync2 } from "fs";
|
|
593
670
|
import { homedir as homedir2 } from "os";
|
|
594
671
|
function isOriLauncher(spec) {
|
|
595
672
|
return !!spec.launcher && basename(spec.launcher) === "ori";
|
|
@@ -598,7 +675,7 @@ function isOmlxLauncher(spec) {
|
|
|
598
675
|
return !!spec.launcher && basename(spec.launcher) === "omlx";
|
|
599
676
|
}
|
|
600
677
|
function defaultOmlxBin(home = homedir2()) {
|
|
601
|
-
return
|
|
678
|
+
return join6(home, ".omlx", "bin", "omlx");
|
|
602
679
|
}
|
|
603
680
|
function launcherExecutable(spec, home) {
|
|
604
681
|
if (!spec.launcher) return void 0;
|
|
@@ -651,8 +728,8 @@ function omlxModelPresent(model, dirs) {
|
|
|
651
728
|
});
|
|
652
729
|
}
|
|
653
730
|
function readOmlxSettings(home = homedir2()) {
|
|
654
|
-
const p =
|
|
655
|
-
return
|
|
731
|
+
const p = join6(home, ".omlx", "settings.json");
|
|
732
|
+
return existsSync5(p) ? parseOmlxSettings(readFileSync6(p, "utf8")) : void 0;
|
|
656
733
|
}
|
|
657
734
|
|
|
658
735
|
// src/capability.ts
|
|
@@ -716,7 +793,7 @@ function describeClaudeAuth(spec, env = process.env, home = homedir3()) {
|
|
|
716
793
|
if (spec.auth === "workspace") return `workspace${spec.inference ? ` (${new URL(spec.inference.baseUrl).host})` : " (endpoint resolved per route at run time)"}`;
|
|
717
794
|
let fromSettings;
|
|
718
795
|
try {
|
|
719
|
-
const parsed = JSON.parse(
|
|
796
|
+
const parsed = JSON.parse(readFileSync7(join7(home, ".claude", "settings.json"), "utf8"));
|
|
720
797
|
const e = parsed.env ?? {};
|
|
721
798
|
const on = (k) => e[k] !== void 0 && e[k] !== "" && e[k] !== "0";
|
|
722
799
|
if (on("CLAUDE_CODE_USE_VERTEX")) fromSettings = "vertex from ~/.claude/settings.json";
|
|
@@ -820,12 +897,12 @@ var claudeAdapter = {
|
|
|
820
897
|
// src/adapters/codex.ts
|
|
821
898
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
822
899
|
import { homedir as homedir4 } from "os";
|
|
823
|
-
import { join as
|
|
824
|
-
import { rmSync as rmSync4, writeFileSync as
|
|
900
|
+
import { join as join8 } from "path";
|
|
901
|
+
import { rmSync as rmSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
825
902
|
|
|
826
903
|
// src/pair-files.ts
|
|
827
|
-
import { existsSync as
|
|
828
|
-
import { dirname as
|
|
904
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync4, rmSync as rmSync3 } from "fs";
|
|
905
|
+
import { dirname as dirname3 } from "path";
|
|
829
906
|
var PAIR_BLOCK_START = "<!-- crew:pair start -->";
|
|
830
907
|
var PAIR_BLOCK_END = "<!-- crew:pair end -->";
|
|
831
908
|
var PAIR_KEY_VAR = "CREW_API_KEY";
|
|
@@ -865,11 +942,11 @@ function removePairBlock(text2) {
|
|
|
865
942
|
return text2.replace(new RegExp(`\\n*${BLOCK_RE.source}`), "\n").replace(/^\n+/, "").replace(/\n{3,}/g, "\n\n");
|
|
866
943
|
}
|
|
867
944
|
function readIfExists(path) {
|
|
868
|
-
return
|
|
945
|
+
return existsSync6(path) ? readFileSync8(path, "utf8") : void 0;
|
|
869
946
|
}
|
|
870
947
|
function writeFileMkdir(path, text2) {
|
|
871
|
-
|
|
872
|
-
|
|
948
|
+
mkdirSync5(dirname3(path), { recursive: true });
|
|
949
|
+
writeFileSync4(path, text2);
|
|
873
950
|
}
|
|
874
951
|
function installBlockFile(path, harness, dryRun) {
|
|
875
952
|
const before = readIfExists(path) ?? "";
|
|
@@ -885,7 +962,7 @@ function removeBlockFile(path, dryRun) {
|
|
|
885
962
|
if (dryRun) return true;
|
|
886
963
|
const after = removePairBlock(before);
|
|
887
964
|
if (after.trim() === "") rmSync3(path);
|
|
888
|
-
else
|
|
965
|
+
else writeFileSync4(path, after);
|
|
889
966
|
return true;
|
|
890
967
|
}
|
|
891
968
|
function blockVerdict(path, harness) {
|
|
@@ -1012,8 +1089,8 @@ function codexTable(url) {
|
|
|
1012
1089
|
return [`[mcp_servers.${SERVER}]`, `url = ${tomlString(url)}`, `bearer_token_env_var = ${tomlString(PAIR_KEY_VAR)}`];
|
|
1013
1090
|
}
|
|
1014
1091
|
var codexPaths = (home) => ({
|
|
1015
|
-
instructions:
|
|
1016
|
-
config:
|
|
1092
|
+
instructions: join8(home, ".codex", "AGENTS.md"),
|
|
1093
|
+
config: join8(home, ".codex", "config.toml")
|
|
1017
1094
|
});
|
|
1018
1095
|
function codexMcpState(config, url) {
|
|
1019
1096
|
const lines2 = config.split("\n");
|
|
@@ -1064,7 +1141,7 @@ var codexPair = {
|
|
|
1064
1141
|
if (hadMcp && !o.dryRun) {
|
|
1065
1142
|
const after = removeTomlTable(config, `mcp_servers.${SERVER}`);
|
|
1066
1143
|
if (after === "") rmSync4(p.config);
|
|
1067
|
-
else
|
|
1144
|
+
else writeFileSync5(p.config, after);
|
|
1068
1145
|
}
|
|
1069
1146
|
const what = hadBlock && hadMcp ? `the Pair block and ${SERVER} server` : hadBlock ? "the Pair block" : `the ${SERVER} server`;
|
|
1070
1147
|
return hadBlock || hadMcp ? `pair(codex): ${o.dryRun ? "(dry run) would remove" : "removed"} ${what}${kept}` : `pair(codex): nothing to remove${kept}`;
|
|
@@ -1160,8 +1237,8 @@ var codexAdapter = {
|
|
|
1160
1237
|
|
|
1161
1238
|
// src/pair-install.ts
|
|
1162
1239
|
var geminiPaths = (home) => ({
|
|
1163
|
-
instructions:
|
|
1164
|
-
config:
|
|
1240
|
+
instructions: join9(home, ".gemini", "GEMINI.md"),
|
|
1241
|
+
config: join9(home, ".gemini", "settings.json")
|
|
1165
1242
|
});
|
|
1166
1243
|
function readGeminiSettings(path) {
|
|
1167
1244
|
const text2 = readIfExists(path);
|
|
@@ -1249,7 +1326,7 @@ var geminiPair = {
|
|
|
1249
1326
|
if (Object.keys(rest).length > 0) next.mcpServers = rest;
|
|
1250
1327
|
else delete next.mcpServers;
|
|
1251
1328
|
if (Object.keys(next).length === 0) rmSync5(p.config);
|
|
1252
|
-
else
|
|
1329
|
+
else writeFileSync6(p.config, `${JSON.stringify(next, null, 2)}
|
|
1253
1330
|
`);
|
|
1254
1331
|
}
|
|
1255
1332
|
const what = hadBlock && hadMcp ? `the Pair block and ${SERVER} server` : hadBlock ? "the Pair block" : `the ${SERVER} server`;
|
|
@@ -1260,9 +1337,9 @@ var VSCODE_FLAVOURS = ["Code", "Code - Insiders"];
|
|
|
1260
1337
|
function vscodeUserDirs(o) {
|
|
1261
1338
|
const home = o.home ?? homedir5();
|
|
1262
1339
|
const platform2 = o.platform ?? process.platform;
|
|
1263
|
-
const root = platform2 === "darwin" ?
|
|
1264
|
-
const all = VSCODE_FLAVOURS.map((f) =>
|
|
1265
|
-
const present = all.filter((d) =>
|
|
1340
|
+
const root = platform2 === "darwin" ? join9(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join9(home, "AppData", "Roaming") : join9(home, ".config");
|
|
1341
|
+
const all = VSCODE_FLAVOURS.map((f) => join9(root, f, "User"));
|
|
1342
|
+
const present = all.filter((d) => existsSync7(d));
|
|
1266
1343
|
return present.length > 0 ? present : [all[0]];
|
|
1267
1344
|
}
|
|
1268
1345
|
var COPILOT_FRONTMATTER = '---\napplyTo: "**"\n---\n\n';
|
|
@@ -1300,7 +1377,7 @@ function removeCopilotInstructions(path, dryRun) {
|
|
|
1300
1377
|
if (dryRun) return true;
|
|
1301
1378
|
const after = removePairBlock(before);
|
|
1302
1379
|
if (after.trim() === "" || after.trim() === COPILOT_FRONTMATTER.trim()) rmSync5(path);
|
|
1303
|
-
else
|
|
1380
|
+
else writeFileSync6(path, after);
|
|
1304
1381
|
return true;
|
|
1305
1382
|
}
|
|
1306
1383
|
var copilotPair = {
|
|
@@ -1309,8 +1386,8 @@ var copilotPair = {
|
|
|
1309
1386
|
installPair(o) {
|
|
1310
1387
|
const lines2 = [];
|
|
1311
1388
|
for (const dir of vscodeUserDirs(o)) {
|
|
1312
|
-
const mcpPath =
|
|
1313
|
-
const instrPath =
|
|
1389
|
+
const mcpPath = join9(dir, "mcp.json");
|
|
1390
|
+
const instrPath = join9(dir, "prompts", "crew-pair.instructions.md");
|
|
1314
1391
|
const settings = readVscodeMcp(mcpPath);
|
|
1315
1392
|
if (!settings) {
|
|
1316
1393
|
lines2.push(`pair(copilot): ${mcpPath} is not a plain JSON object (comments are not supported) \u2014 fix it by hand; nothing was written there`);
|
|
@@ -1338,8 +1415,8 @@ var copilotPair = {
|
|
|
1338
1415
|
},
|
|
1339
1416
|
verifyPair(o) {
|
|
1340
1417
|
const verdicts = vscodeUserDirs(o).map((dir) => {
|
|
1341
|
-
const instrPath =
|
|
1342
|
-
const mcpPath =
|
|
1418
|
+
const instrPath = join9(dir, "prompts", "crew-pair.instructions.md");
|
|
1419
|
+
const mcpPath = join9(dir, "mcp.json");
|
|
1343
1420
|
const b = blockVerdict(instrPath, "copilot");
|
|
1344
1421
|
if (!b.ok) return b;
|
|
1345
1422
|
const settings = readVscodeMcp(mcpPath);
|
|
@@ -1354,8 +1431,8 @@ var copilotPair = {
|
|
|
1354
1431
|
removePair(o) {
|
|
1355
1432
|
const lines2 = [];
|
|
1356
1433
|
for (const dir of vscodeUserDirs(o)) {
|
|
1357
|
-
const mcpPath =
|
|
1358
|
-
const hadBlock = removeCopilotInstructions(
|
|
1434
|
+
const mcpPath = join9(dir, "mcp.json");
|
|
1435
|
+
const hadBlock = removeCopilotInstructions(join9(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
|
|
1359
1436
|
const settings = readVscodeMcp(mcpPath);
|
|
1360
1437
|
const st = settings ? copilotMcpState(settings, o.url) : { state: "missing" };
|
|
1361
1438
|
const hadMcp = st.state === "registered";
|
|
@@ -1366,7 +1443,7 @@ var copilotPair = {
|
|
|
1366
1443
|
if (Object.keys(rest).length > 0) next.servers = rest;
|
|
1367
1444
|
else delete next.servers;
|
|
1368
1445
|
if (Object.keys(next).length === 0) rmSync5(mcpPath);
|
|
1369
|
-
else
|
|
1446
|
+
else writeFileSync6(mcpPath, `${JSON.stringify(next, null, 2)}
|
|
1370
1447
|
`);
|
|
1371
1448
|
}
|
|
1372
1449
|
const what = hadBlock && hadMcp ? `the Pair instructions and ${SERVER} server` : hadBlock ? "the Pair instructions" : `the ${SERVER} server`;
|
|
@@ -1615,9 +1692,9 @@ function resolveSeat(ship, route2, role, persona = {}, repo) {
|
|
|
1615
1692
|
}
|
|
1616
1693
|
|
|
1617
1694
|
// src/forges.ts
|
|
1618
|
-
import { execFileSync } from "child_process";
|
|
1619
|
-
import { chmodSync, existsSync as
|
|
1620
|
-
import { dirname as
|
|
1695
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
1696
|
+
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync9, rmSync as rmSync6, statSync as statSync3, writeFileSync as writeFileSync7 } from "fs";
|
|
1697
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
1621
1698
|
var ForgeError = class extends Error {
|
|
1622
1699
|
};
|
|
1623
1700
|
var DEFAULT_FORGE_USERNAME = "x-access-token";
|
|
@@ -1645,7 +1722,7 @@ function forgeDescriptorFor(host) {
|
|
|
1645
1722
|
}
|
|
1646
1723
|
var DEFAULT_KEYCHAIN_SERVICE = "crew-forge";
|
|
1647
1724
|
function defaultTokenFilePath(stateDir, host) {
|
|
1648
|
-
return
|
|
1725
|
+
return join10(stateDir, "forges", `${host.replace(/[^a-z0-9.-]/gi, "_")}.token`);
|
|
1649
1726
|
}
|
|
1650
1727
|
var envSource = {
|
|
1651
1728
|
read: (s, c) => c.env[s.var]?.trim() || null,
|
|
@@ -1654,19 +1731,19 @@ var envSource = {
|
|
|
1654
1731
|
var fileSource = {
|
|
1655
1732
|
read(s, c) {
|
|
1656
1733
|
const path = s.path ?? defaultTokenFilePath(c.stateDir, c.host);
|
|
1657
|
-
if (!
|
|
1658
|
-
const mode =
|
|
1734
|
+
if (!existsSync8(path)) return null;
|
|
1735
|
+
const mode = statSync3(path).mode & 511;
|
|
1659
1736
|
if (mode & 63) {
|
|
1660
1737
|
throw new ForgeError(
|
|
1661
1738
|
`${path} is mode ${mode.toString(8).padStart(3, "0")}; a forge token file must be owner-only (chmod 600 ${path})`
|
|
1662
1739
|
);
|
|
1663
1740
|
}
|
|
1664
|
-
return
|
|
1741
|
+
return readFileSync9(path, "utf8").trim() || null;
|
|
1665
1742
|
},
|
|
1666
1743
|
write(s, token, c) {
|
|
1667
1744
|
const path = s.path ?? defaultTokenFilePath(c.stateDir, c.host);
|
|
1668
|
-
|
|
1669
|
-
|
|
1745
|
+
mkdirSync6(dirname4(path), { recursive: true, mode: 448 });
|
|
1746
|
+
writeFileSync7(path, `${token}
|
|
1670
1747
|
`, { mode: 384 });
|
|
1671
1748
|
chmodSync(path, 384);
|
|
1672
1749
|
},
|
|
@@ -1680,13 +1757,13 @@ var keychainSource = {
|
|
|
1680
1757
|
read(s, c) {
|
|
1681
1758
|
try {
|
|
1682
1759
|
if (process.platform === "darwin") {
|
|
1683
|
-
return
|
|
1760
|
+
return execFileSync2(
|
|
1684
1761
|
"security",
|
|
1685
1762
|
["find-generic-password", "-s", keychainService(s), "-a", c.host, "-w"],
|
|
1686
1763
|
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
1687
1764
|
).trim() || null;
|
|
1688
1765
|
}
|
|
1689
|
-
return
|
|
1766
|
+
return execFileSync2(
|
|
1690
1767
|
"secret-tool",
|
|
1691
1768
|
["lookup", "service", keychainService(s), "account", c.host],
|
|
1692
1769
|
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
@@ -1699,13 +1776,13 @@ var keychainSource = {
|
|
|
1699
1776
|
try {
|
|
1700
1777
|
if (process.platform === "darwin") {
|
|
1701
1778
|
const q2 = (v) => `"${v.replace(/(["\\])/g, "\\$1")}"`;
|
|
1702
|
-
|
|
1779
|
+
execFileSync2("security", ["-i"], {
|
|
1703
1780
|
input: `add-generic-password -U -s ${q2(keychainService(s))} -a ${q2(c.host)} -w ${q2(token)}
|
|
1704
1781
|
`,
|
|
1705
1782
|
stdio: ["pipe", "ignore", "pipe"]
|
|
1706
1783
|
});
|
|
1707
1784
|
} else {
|
|
1708
|
-
|
|
1785
|
+
execFileSync2(
|
|
1709
1786
|
"secret-tool",
|
|
1710
1787
|
["store", "--label", `crew ${c.host}`, "service", keychainService(s), "account", c.host],
|
|
1711
1788
|
{ input: token, stdio: ["pipe", "ignore", "pipe"] }
|
|
@@ -1718,9 +1795,9 @@ var keychainSource = {
|
|
|
1718
1795
|
erase(s, c) {
|
|
1719
1796
|
try {
|
|
1720
1797
|
if (process.platform === "darwin") {
|
|
1721
|
-
|
|
1798
|
+
execFileSync2("security", ["delete-generic-password", "-s", keychainService(s), "-a", c.host], { stdio: "ignore" });
|
|
1722
1799
|
} else {
|
|
1723
|
-
|
|
1800
|
+
execFileSync2("secret-tool", ["clear", "service", keychainService(s), "account", c.host], { stdio: "ignore" });
|
|
1724
1801
|
}
|
|
1725
1802
|
} catch {
|
|
1726
1803
|
}
|
|
@@ -1800,10 +1877,10 @@ function answerCredential(action, input, forges, stateDir, env = process.env) {
|
|
|
1800
1877
|
password=${token}
|
|
1801
1878
|
`;
|
|
1802
1879
|
}
|
|
1803
|
-
var missesPath = (stateDir) =>
|
|
1880
|
+
var missesPath = (stateDir) => join10(stateDir, "forge-misses.json");
|
|
1804
1881
|
function readForgeMisses(stateDir) {
|
|
1805
1882
|
try {
|
|
1806
|
-
const raw = JSON.parse(
|
|
1883
|
+
const raw = JSON.parse(readFileSync9(missesPath(stateDir), "utf8"));
|
|
1807
1884
|
return Object.entries(raw).filter(([, t]) => typeof t === "string").map(([host, lastMissAt]) => ({ host, lastMissAt })).sort((a, b) => a.host.localeCompare(b.host));
|
|
1808
1885
|
} catch {
|
|
1809
1886
|
return [];
|
|
@@ -1818,8 +1895,8 @@ function noteForgeMiss(stateDir, host, at) {
|
|
|
1818
1895
|
if (!(bare in cur)) return;
|
|
1819
1896
|
delete cur[bare];
|
|
1820
1897
|
} else cur[bare] = at;
|
|
1821
|
-
|
|
1822
|
-
|
|
1898
|
+
mkdirSync6(stateDir, { recursive: true });
|
|
1899
|
+
writeFileSync7(missesPath(stateDir), JSON.stringify(cur), { mode: 384 });
|
|
1823
1900
|
} catch {
|
|
1824
1901
|
}
|
|
1825
1902
|
}
|
|
@@ -1921,8 +1998,8 @@ import { hostFromUrl } from "@tablation/client";
|
|
|
1921
1998
|
|
|
1922
1999
|
// src/secret-store.ts
|
|
1923
2000
|
import { createHash as createHash2 } from "crypto";
|
|
1924
|
-
import { accessSync, constants, existsSync as
|
|
1925
|
-
import { join as
|
|
2001
|
+
import { accessSync, constants, existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync10, renameSync as renameSync2, rmSync as rmSync7, statSync as statSync4, writeFileSync as writeFileSync8, chmodSync as chmodSync2 } from "fs";
|
|
2002
|
+
import { join as join11 } from "path";
|
|
1926
2003
|
import { getSessionStore as getOsSessionStore } from "@tablation/client";
|
|
1927
2004
|
var SECRET_STORE_MODES = ["auto", "os", "file"];
|
|
1928
2005
|
function isSecretStoreMode(v) {
|
|
@@ -1933,7 +2010,7 @@ function configureSecretStore(c) {
|
|
|
1933
2010
|
configured = c;
|
|
1934
2011
|
}
|
|
1935
2012
|
function keysDir(stateDir) {
|
|
1936
|
-
return
|
|
2013
|
+
return join11(stateDir, "keys");
|
|
1937
2014
|
}
|
|
1938
2015
|
function entryName(host, slug2) {
|
|
1939
2016
|
return `${createHash2("sha256").update(`${host}\0${slug2}`).digest("hex")}.json`;
|
|
@@ -1944,34 +2021,34 @@ var FileSessionStore = class {
|
|
|
1944
2021
|
this.dir = dir;
|
|
1945
2022
|
}
|
|
1946
2023
|
ensureDir() {
|
|
1947
|
-
|
|
1948
|
-
if ((
|
|
2024
|
+
mkdirSync7(this.dir, { recursive: true, mode: 448 });
|
|
2025
|
+
if ((statSync4(this.dir).mode & 63) !== 0) chmodSync2(this.dir, 448);
|
|
1949
2026
|
}
|
|
1950
2027
|
async get(host, slug2) {
|
|
1951
|
-
const file =
|
|
1952
|
-
if (!
|
|
2028
|
+
const file = join11(this.dir, entryName(host, slug2));
|
|
2029
|
+
if (!existsSync9(this.dir)) return void 0;
|
|
1953
2030
|
accessSync(this.dir, constants.R_OK | constants.X_OK);
|
|
1954
|
-
if (!
|
|
1955
|
-
return JSON.parse(
|
|
2031
|
+
if (!existsSync9(file)) return void 0;
|
|
2032
|
+
return JSON.parse(readFileSync10(file, "utf8"));
|
|
1956
2033
|
}
|
|
1957
2034
|
async set(host, slug2, session) {
|
|
1958
2035
|
this.ensureDir();
|
|
1959
|
-
const file =
|
|
2036
|
+
const file = join11(this.dir, entryName(host, slug2));
|
|
1960
2037
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
1961
|
-
|
|
2038
|
+
writeFileSync8(tmp, JSON.stringify(session), { mode: 384 });
|
|
1962
2039
|
renameSync2(tmp, file);
|
|
1963
2040
|
}
|
|
1964
2041
|
async clear(host, slug2) {
|
|
1965
|
-
if (!
|
|
1966
|
-
rmSync7(
|
|
2042
|
+
if (!existsSync9(this.dir)) return;
|
|
2043
|
+
rmSync7(join11(this.dir, entryName(host, slug2)), { force: true });
|
|
1967
2044
|
}
|
|
1968
2045
|
/** Whether a key could be written here right now, without leaving anything behind. */
|
|
1969
2046
|
writable() {
|
|
1970
2047
|
try {
|
|
1971
2048
|
this.ensureDir();
|
|
1972
2049
|
accessSync(this.dir, constants.R_OK | constants.W_OK | constants.X_OK);
|
|
1973
|
-
const probe =
|
|
1974
|
-
|
|
2050
|
+
const probe = join11(this.dir, `.probe.${process.pid}`);
|
|
2051
|
+
writeFileSync8(probe, "", { mode: 384 });
|
|
1975
2052
|
rmSync7(probe, { force: true });
|
|
1976
2053
|
return { ok: true };
|
|
1977
2054
|
} catch (e) {
|
|
@@ -2022,11 +2099,11 @@ function osStoreReachable(env = process.env, platform2 = process.platform, hasBi
|
|
|
2022
2099
|
if (platform2 !== "linux") return true;
|
|
2023
2100
|
if (!hasBinary("secret-tool")) return false;
|
|
2024
2101
|
if (env.DBUS_SESSION_BUS_ADDRESS) return true;
|
|
2025
|
-
return !!env.XDG_RUNTIME_DIR &&
|
|
2102
|
+
return !!env.XDG_RUNTIME_DIR && existsSync9(join11(env.XDG_RUNTIME_DIR, "bus"));
|
|
2026
2103
|
}
|
|
2027
2104
|
function onPath(name) {
|
|
2028
2105
|
for (const dir of (process.env.PATH ?? "").split(":")) {
|
|
2029
|
-
if (dir &&
|
|
2106
|
+
if (dir && existsSync9(join11(dir, name))) return true;
|
|
2030
2107
|
}
|
|
2031
2108
|
return false;
|
|
2032
2109
|
}
|
|
@@ -2093,8 +2170,8 @@ function explain(ship, required) {
|
|
|
2093
2170
|
}
|
|
2094
2171
|
|
|
2095
2172
|
// src/repo-config.ts
|
|
2096
|
-
import { readFileSync as
|
|
2097
|
-
import { join as
|
|
2173
|
+
import { readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
|
|
2174
|
+
import { join as join12, basename as basename3 } from "path";
|
|
2098
2175
|
import { parse } from "yaml";
|
|
2099
2176
|
|
|
2100
2177
|
// src/sandbox-policy.ts
|
|
@@ -2375,8 +2452,8 @@ var MODES = ["local", "integrate", "ci_manual", "ci_auto", "external"];
|
|
|
2375
2452
|
var PROVIDERS = ["github", "buildkite", "other", "none"];
|
|
2376
2453
|
function findRepoConfig(dir) {
|
|
2377
2454
|
for (const name of REPO_CONFIG_FILENAMES) {
|
|
2378
|
-
const p =
|
|
2379
|
-
if (
|
|
2455
|
+
const p = join12(dir, name);
|
|
2456
|
+
if (existsSync10(p)) return p;
|
|
2380
2457
|
}
|
|
2381
2458
|
return null;
|
|
2382
2459
|
}
|
|
@@ -2656,7 +2733,7 @@ function parseRepoConfig(text2, file) {
|
|
|
2656
2733
|
function loadRepoConfig(dir) {
|
|
2657
2734
|
const file = findRepoConfig(dir);
|
|
2658
2735
|
if (!file) return null;
|
|
2659
|
-
return parseRepoConfig(
|
|
2736
|
+
return parseRepoConfig(readFileSync11(file, "utf8"), file);
|
|
2660
2737
|
}
|
|
2661
2738
|
function hookLabel(cfg, hook2) {
|
|
2662
2739
|
return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
|
|
@@ -3030,7 +3107,9 @@ var SHIP_KEYS = /* @__PURE__ */ new Set([
|
|
|
3030
3107
|
"maxPeriscopes",
|
|
3031
3108
|
"periscopeIdleMinutes",
|
|
3032
3109
|
"secretStore",
|
|
3033
|
-
"env"
|
|
3110
|
+
"env",
|
|
3111
|
+
"runner",
|
|
3112
|
+
"sandbox"
|
|
3034
3113
|
]);
|
|
3035
3114
|
var ROUTE_KEYS = /* @__PURE__ */ new Set([
|
|
3036
3115
|
"route",
|
|
@@ -3176,14 +3255,14 @@ function mergeRouteRelease(route2, override) {
|
|
|
3176
3255
|
function expand(p, base) {
|
|
3177
3256
|
let out = p.startsWith("~") ? p.replace(/^~/, homedir6()) : p;
|
|
3178
3257
|
out = out.replace(/\$\{?HOME\}?/g, homedir6());
|
|
3179
|
-
return
|
|
3258
|
+
return isAbsolute2(out) ? out : resolve2(base, out);
|
|
3180
3259
|
}
|
|
3181
3260
|
function resolvePromptsDir(promptSet, crewHome, base) {
|
|
3182
|
-
if (!promptSet) return
|
|
3261
|
+
if (!promptSet) return join13(crewHome, "prompts", "default");
|
|
3183
3262
|
if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
|
|
3184
3263
|
return expand(promptSet, base);
|
|
3185
3264
|
}
|
|
3186
|
-
return
|
|
3265
|
+
return join13(crewHome, "prompts", promptSet);
|
|
3187
3266
|
}
|
|
3188
3267
|
var Missing = class {
|
|
3189
3268
|
paths = [];
|
|
@@ -3207,36 +3286,36 @@ var Missing = class {
|
|
|
3207
3286
|
};
|
|
3208
3287
|
function crewVersion(crewHome) {
|
|
3209
3288
|
try {
|
|
3210
|
-
const pkg = JSON.parse(
|
|
3289
|
+
const pkg = JSON.parse(readFileSync12(join13(crewHome, "package.json"), "utf8"));
|
|
3211
3290
|
return pkg.version ?? "0.0.0";
|
|
3212
3291
|
} catch {
|
|
3213
3292
|
return "0.0.0";
|
|
3214
3293
|
}
|
|
3215
3294
|
}
|
|
3216
3295
|
function configSearchPath(crewHome) {
|
|
3217
|
-
if (process.env.CREW_CONFIG) return [
|
|
3296
|
+
if (process.env.CREW_CONFIG) return [resolve2(process.env.CREW_CONFIG)];
|
|
3218
3297
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
3219
3298
|
return [
|
|
3220
|
-
...xdg ? [
|
|
3221
|
-
|
|
3222
|
-
|
|
3299
|
+
...xdg ? [join13(xdg, "crew", "crew.yaml")] : [],
|
|
3300
|
+
join13(homedir6(), ".config", "crew", "crew.yaml"),
|
|
3301
|
+
resolve2(crewHome, "crew.yaml")
|
|
3223
3302
|
];
|
|
3224
3303
|
}
|
|
3225
3304
|
function findConfigFile(crewHome) {
|
|
3226
3305
|
const candidates2 = configSearchPath(crewHome);
|
|
3227
|
-
return candidates2.find((c) =>
|
|
3306
|
+
return candidates2.find((c) => existsSync11(c)) ?? candidates2[candidates2.length - 1];
|
|
3228
3307
|
}
|
|
3229
|
-
var defaultConfigPath = () =>
|
|
3308
|
+
var defaultConfigPath = () => join13(process.env.XDG_CONFIG_HOME ?? join13(homedir6(), ".config"), "crew", "crew.yaml");
|
|
3230
3309
|
function routeSlug(route2) {
|
|
3231
3310
|
return route2.replace(/\//g, "-");
|
|
3232
3311
|
}
|
|
3233
3312
|
function resolvedPathFor(stateDir, route2) {
|
|
3234
3313
|
const [workspace, project] = splitRoute(route2);
|
|
3235
|
-
return
|
|
3314
|
+
return join13(stateDir, "resolved", workspace, `${project}.json`);
|
|
3236
3315
|
}
|
|
3237
3316
|
function apiKeyPathFor(stateDir, route2) {
|
|
3238
3317
|
const [workspace, project] = splitRoute(route2);
|
|
3239
|
-
return
|
|
3318
|
+
return join13(stateDir, "keys", workspace, `${project}.env`);
|
|
3240
3319
|
}
|
|
3241
3320
|
function splitRoute(route2) {
|
|
3242
3321
|
const parts = route2.split("/");
|
|
@@ -3247,9 +3326,9 @@ function splitRoute(route2) {
|
|
|
3247
3326
|
}
|
|
3248
3327
|
function readResolvedFile(stateDir, route2) {
|
|
3249
3328
|
const path = resolvedPathFor(stateDir, route2);
|
|
3250
|
-
if (!
|
|
3329
|
+
if (!existsSync11(path)) return void 0;
|
|
3251
3330
|
try {
|
|
3252
|
-
return JSON.parse(
|
|
3331
|
+
return JSON.parse(readFileSync12(path, "utf8"));
|
|
3253
3332
|
} catch (e) {
|
|
3254
3333
|
throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
|
|
3255
3334
|
}
|
|
@@ -3310,23 +3389,23 @@ function parseResolved(raw, m, where) {
|
|
|
3310
3389
|
}
|
|
3311
3390
|
function loadConfig(crewHome, configFile, opts = {}) {
|
|
3312
3391
|
const file = configFile ?? findConfigFile(crewHome);
|
|
3313
|
-
if (opts.text === void 0 && !
|
|
3392
|
+
if (opts.text === void 0 && !existsSync11(file)) {
|
|
3314
3393
|
throw new ConfigError(
|
|
3315
3394
|
`no config found. Looked in:
|
|
3316
3395
|
` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
|
|
3317
3396
|
|
|
3318
3397
|
Create the first of those:
|
|
3319
|
-
mkdir -p ${
|
|
3398
|
+
mkdir -p ${dirname5(defaultConfigPath())} && cp ${join13(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
|
|
3320
3399
|
);
|
|
3321
3400
|
}
|
|
3322
3401
|
let raw;
|
|
3323
3402
|
try {
|
|
3324
|
-
raw = parse2(opts.text ??
|
|
3403
|
+
raw = parse2(opts.text ?? readFileSync12(file, "utf8"));
|
|
3325
3404
|
} catch (e) {
|
|
3326
3405
|
throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
|
|
3327
3406
|
}
|
|
3328
3407
|
if (!raw || typeof raw !== "object") throw new ConfigError(`${file} is empty or not a mapping`);
|
|
3329
|
-
const base =
|
|
3408
|
+
const base = dirname5(file);
|
|
3330
3409
|
const missing = new Missing();
|
|
3331
3410
|
rejectUnknownKeys(raw, /* @__PURE__ */ new Set(["ship", "routes"]), "top-level", file);
|
|
3332
3411
|
const shipRaw = raw.ship ?? {};
|
|
@@ -3440,7 +3519,7 @@ Create the first of those:
|
|
|
3440
3519
|
useNvm: shipRaw.useNvm !== false,
|
|
3441
3520
|
nvmSh: shipRaw.nvmSh,
|
|
3442
3521
|
stateDir,
|
|
3443
|
-
logFile: expand(shipRaw.logFile ??
|
|
3522
|
+
logFile: expand(shipRaw.logFile ?? join13(tmpdir(), "crew.log"), base),
|
|
3444
3523
|
userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
|
|
3445
3524
|
maxConcurrentAgents,
|
|
3446
3525
|
maxConcurrentAgentsSet,
|
|
@@ -3620,7 +3699,7 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
|
|
|
3620
3699
|
const hasRepos = Object.keys(repoDirs).length > 0;
|
|
3621
3700
|
const reposBasePath = expand(String(c?.reposBasePath ?? raw.ship?.reposBasePath ?? DEFAULT_REPOS_BASE_PATH), base);
|
|
3622
3701
|
const dirIsCloneBase = !c?.dir && !hasRepos && !Array.isArray(c?.repos);
|
|
3623
|
-
const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ?
|
|
3702
|
+
const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join13(reposBasePath, splitRoute(route2)[0]) : "";
|
|
3624
3703
|
const weight = c?.weight === void 0 ? void 0 : Number(c.weight);
|
|
3625
3704
|
if (weight !== void 0 && !(Number.isFinite(weight) && weight > 0)) {
|
|
3626
3705
|
missing.add(`${where}.weight must be a positive number (got "${c?.weight}")`);
|
|
@@ -3669,21 +3748,21 @@ function routeForDir(cfg, dir) {
|
|
|
3669
3748
|
}
|
|
3670
3749
|
function locateDir(cfg, dir, commonDirOf) {
|
|
3671
3750
|
const within = (base, target2) => {
|
|
3672
|
-
const b =
|
|
3751
|
+
const b = resolve2(base);
|
|
3673
3752
|
return target2 === b || target2.startsWith(`${b}/`);
|
|
3674
3753
|
};
|
|
3675
3754
|
const find = (target2) => {
|
|
3676
3755
|
for (const route2 of cfg.routes) {
|
|
3677
3756
|
const repo = reposOf(route2).find((x) => within(x.dir, target2));
|
|
3678
|
-
if (repo) return { route: route2, repo, checkout:
|
|
3757
|
+
if (repo) return { route: route2, repo, checkout: resolve2(repo.dir) };
|
|
3679
3758
|
}
|
|
3680
3759
|
return void 0;
|
|
3681
3760
|
};
|
|
3682
|
-
const target =
|
|
3761
|
+
const target = resolve2(dir);
|
|
3683
3762
|
const direct = find(target);
|
|
3684
3763
|
if (direct) return direct;
|
|
3685
3764
|
const common = commonDirOf?.(target);
|
|
3686
|
-
return common ? find(
|
|
3765
|
+
return common ? find(dirname5(resolve2(target, common))) : void 0;
|
|
3687
3766
|
}
|
|
3688
3767
|
function findRoute(cfg, route2) {
|
|
3689
3768
|
if (!route2) {
|
|
@@ -3718,8 +3797,8 @@ function repoIdForName(r, name) {
|
|
|
3718
3797
|
}
|
|
3719
3798
|
function resolveApiKey(r) {
|
|
3720
3799
|
if (r.apiKey) return r.apiKey;
|
|
3721
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
3722
|
-
for (const line of
|
|
3800
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync11(r.apiKeyFile)) {
|
|
3801
|
+
for (const line of readFileSync12(r.apiKeyFile, "utf8").split("\n")) {
|
|
3723
3802
|
const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
|
|
3724
3803
|
if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
3725
3804
|
}
|
|
@@ -3732,9 +3811,9 @@ function resolveApiKey(r) {
|
|
|
3732
3811
|
}
|
|
3733
3812
|
function credentialSource(r) {
|
|
3734
3813
|
if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
|
|
3735
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
3814
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync11(r.apiKeyFile)) {
|
|
3736
3815
|
const re = new RegExp(`^${r.apiKeyVar}=`);
|
|
3737
|
-
if (
|
|
3816
|
+
if (readFileSync12(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
|
|
3738
3817
|
}
|
|
3739
3818
|
return process.env.CREW_API_KEY?.trim() ? "CREW_API_KEY" : "none";
|
|
3740
3819
|
}
|
|
@@ -3790,7 +3869,7 @@ function configuredMembers(rt) {
|
|
|
3790
3869
|
return out;
|
|
3791
3870
|
}
|
|
3792
3871
|
function defaultRepoDir(basePath, workspace, repoName) {
|
|
3793
|
-
return
|
|
3872
|
+
return join13(basePath, workspace, repoName);
|
|
3794
3873
|
}
|
|
3795
3874
|
function reposOf(r) {
|
|
3796
3875
|
const named = Object.entries(r.repos);
|
|
@@ -3829,10 +3908,10 @@ function passengerRepoTargets(r) {
|
|
|
3829
3908
|
return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
|
|
3830
3909
|
}
|
|
3831
3910
|
function passengerCheckoutDir(basePath, workspace, repoName) {
|
|
3832
|
-
return
|
|
3911
|
+
return join13(basePath, workspace, ".passenger-checkouts", repoName);
|
|
3833
3912
|
}
|
|
3834
3913
|
function passengerMirrorArea(ship) {
|
|
3835
|
-
return ship.runner === "sandbox" ?
|
|
3914
|
+
return ship.runner === "sandbox" ? join13(ship.stateDir, "passenger-sources") : void 0;
|
|
3836
3915
|
}
|
|
3837
3916
|
function passengerCheckoutTargets(r, mirrorArea) {
|
|
3838
3917
|
if (!r.hostPassengers) return [];
|
|
@@ -3843,7 +3922,7 @@ function passengerCheckoutTargets(r, mirrorArea) {
|
|
|
3843
3922
|
const forgeHost = t.forgeHost ?? (id ? r.resolved?.repoForgeHosts?.[id] : void 0);
|
|
3844
3923
|
return {
|
|
3845
3924
|
name: t.name,
|
|
3846
|
-
dir: mirrorArea ?
|
|
3925
|
+
dir: mirrorArea ? join13(mirrorArea, workspace, t.name) : passengerCheckoutDir(r.reposBasePath, workspace, t.name),
|
|
3847
3926
|
remote: t.remote ?? (id ? remotes[id] : void 0),
|
|
3848
3927
|
...forgeHost ? { forgeHost } : {}
|
|
3849
3928
|
};
|
|
@@ -4121,9 +4200,9 @@ function recoveredFailureHeadlines(o, route2) {
|
|
|
4121
4200
|
}
|
|
4122
4201
|
|
|
4123
4202
|
// src/install.ts
|
|
4124
|
-
import { dirname as
|
|
4203
|
+
import { dirname as dirname6, join as join14, extname } from "path";
|
|
4125
4204
|
import { homedir as homedir7, userInfo } from "os";
|
|
4126
|
-
import { accessSync as accessSync2, constants as constants2, existsSync as
|
|
4205
|
+
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync12, mkdirSync as mkdirSync8, readdirSync as readdirSync3, readFileSync as readFileSync13, statSync as statSync5, writeFileSync as writeFileSync9, unlinkSync, realpathSync } from "fs";
|
|
4127
4206
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
4128
4207
|
import { createHash as createHash3 } from "crypto";
|
|
4129
4208
|
|
|
@@ -4135,7 +4214,7 @@ function isCompiledBinary(moduleUrl) {
|
|
|
4135
4214
|
// src/install.ts
|
|
4136
4215
|
function crewInvocation(crewHome) {
|
|
4137
4216
|
if (isCompiledBinary(import.meta.url)) return [process.execPath];
|
|
4138
|
-
return [process.execPath,
|
|
4217
|
+
return [process.execPath, join14(crewHome, "bin", "crew")];
|
|
4139
4218
|
}
|
|
4140
4219
|
var INTERVAL_SECONDS = 120;
|
|
4141
4220
|
function labelFor(crewHome, job) {
|
|
@@ -4166,29 +4245,29 @@ function schedulerLogFor(crewLog, job) {
|
|
|
4166
4245
|
return `${stem}.${suffix}${ext || ".log"}`;
|
|
4167
4246
|
}
|
|
4168
4247
|
function pathFor(ship) {
|
|
4169
|
-
const nodeBinDir =
|
|
4248
|
+
const nodeBinDir = dirname6(process.execPath);
|
|
4170
4249
|
const parts = [nodeBinDir];
|
|
4171
4250
|
if (ship.extraPath) parts.push(ship.extraPath);
|
|
4172
4251
|
parts.push("/usr/bin", "/bin", "/usr/sbin", "/sbin");
|
|
4173
4252
|
return parts.join(":");
|
|
4174
4253
|
}
|
|
4175
4254
|
function findOnPath(bin, searchPath) {
|
|
4176
|
-
const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) =>
|
|
4255
|
+
const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join14(dir, bin));
|
|
4177
4256
|
for (const candidate of candidates2) {
|
|
4178
4257
|
try {
|
|
4179
4258
|
accessSync2(candidate, constants2.X_OK);
|
|
4180
|
-
if (
|
|
4259
|
+
if (statSync5(candidate).isFile()) return candidate;
|
|
4181
4260
|
} catch {
|
|
4182
4261
|
}
|
|
4183
4262
|
}
|
|
4184
4263
|
return void 0;
|
|
4185
4264
|
}
|
|
4186
|
-
var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin",
|
|
4265
|
+
var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join14(homedir7(), ".docker", "bin")].join(":");
|
|
4187
4266
|
function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
|
|
4188
4267
|
const schedulerPath = pathFor(ship);
|
|
4189
4268
|
if (findOnPath("docker", schedulerPath)) return void 0;
|
|
4190
4269
|
const inShell = findOnPath("docker", shellPath);
|
|
4191
|
-
return inShell ? `\`docker\` is not on the scheduler PATH (${schedulerPath}) but is at ${inShell} in your shell; add ${
|
|
4270
|
+
return inShell ? `\`docker\` is not on the scheduler PATH (${schedulerPath}) but is at ${inShell} in your shell; add ${dirname6(inShell)} to ship.extraPath in crew.yaml and run \`crew install\`` : `\`docker\` is not on the scheduler PATH (${schedulerPath}) or your shell PATH; install Docker, then add its directory to ship.extraPath in crew.yaml and run \`crew install\``;
|
|
4192
4271
|
}
|
|
4193
4272
|
var USER_INSTALL = { root: false, user: "" };
|
|
4194
4273
|
function installContext(env = process.env) {
|
|
@@ -4198,7 +4277,7 @@ function installContext(env = process.env) {
|
|
|
4198
4277
|
}
|
|
4199
4278
|
var SYSTEM_UNIT_DIR = "/etc/systemd/system";
|
|
4200
4279
|
function systemUnitPathFor(label, suffix = "service") {
|
|
4201
|
-
return
|
|
4280
|
+
return join14(SYSTEM_UNIT_DIR, `${label}.${suffix}`);
|
|
4202
4281
|
}
|
|
4203
4282
|
function unitEnvironment(ship) {
|
|
4204
4283
|
return [["PATH", pathFor(ship)], ...Object.entries(ship.env ?? {})];
|
|
@@ -4219,7 +4298,7 @@ function xmlEscape(s) {
|
|
|
4219
4298
|
}
|
|
4220
4299
|
function planLaunchd(ship, crewHome, job) {
|
|
4221
4300
|
const label = labelFor(crewHome, job);
|
|
4222
|
-
const unitPath =
|
|
4301
|
+
const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4223
4302
|
const crewLog = ship.logFile;
|
|
4224
4303
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4225
4304
|
const persistent = job === "run";
|
|
@@ -4273,7 +4352,7 @@ ${scheduleKeys}
|
|
|
4273
4352
|
}
|
|
4274
4353
|
function planSystemd(ship, crewHome, job, ctx) {
|
|
4275
4354
|
const label = labelFor(crewHome, job);
|
|
4276
|
-
const unitDir = ctx.root ? SYSTEM_UNIT_DIR :
|
|
4355
|
+
const unitDir = ctx.root ? SYSTEM_UNIT_DIR : join14(homedir7(), ".config", "systemd", "user");
|
|
4277
4356
|
const ctl = ctx.root ? ["systemctl"] : ["systemctl", "--user"];
|
|
4278
4357
|
const userLine = ctx.root && ctx.user && ctx.user !== "root" ? `User=${ctx.user}
|
|
4279
4358
|
` : "";
|
|
@@ -4282,7 +4361,7 @@ function planSystemd(ship, crewHome, job, ctx) {
|
|
|
4282
4361
|
lingerCommand: ["loginctl", "enable-linger", ctx.user],
|
|
4283
4362
|
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`
|
|
4284
4363
|
};
|
|
4285
|
-
const servicePath =
|
|
4364
|
+
const servicePath = join14(unitDir, `${label}.service`);
|
|
4286
4365
|
const crewLog = ship.logFile;
|
|
4287
4366
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4288
4367
|
const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
|
|
@@ -4323,7 +4402,7 @@ WantedBy=${wantedBy}
|
|
|
4323
4402
|
...linger
|
|
4324
4403
|
};
|
|
4325
4404
|
}
|
|
4326
|
-
const timerPath =
|
|
4405
|
+
const timerPath = join14(unitDir, `${label}.timer`);
|
|
4327
4406
|
const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
|
|
4328
4407
|
[Unit]
|
|
4329
4408
|
Description=${description}
|
|
@@ -4398,7 +4477,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run", ctx = US
|
|
|
4398
4477
|
function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
4399
4478
|
const label = labelFor(crewHome, job);
|
|
4400
4479
|
if (hostShip === "macos") {
|
|
4401
|
-
const unitPath =
|
|
4480
|
+
const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4402
4481
|
return {
|
|
4403
4482
|
mechanism: "launchd",
|
|
4404
4483
|
unitPaths: [unitPath],
|
|
@@ -4408,20 +4487,20 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4408
4487
|
};
|
|
4409
4488
|
}
|
|
4410
4489
|
if (hostShip === "linux" && hasSystemd) {
|
|
4411
|
-
const system =
|
|
4412
|
-
const unitDir = system ? SYSTEM_UNIT_DIR :
|
|
4490
|
+
const system = existsSync12(systemUnitPathFor(label));
|
|
4491
|
+
const unitDir = system ? SYSTEM_UNIT_DIR : join14(homedir7(), ".config", "systemd", "user");
|
|
4413
4492
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
4414
4493
|
if (job === "run") {
|
|
4415
4494
|
return {
|
|
4416
4495
|
mechanism: "systemd",
|
|
4417
|
-
unitPaths: [
|
|
4496
|
+
unitPaths: [join14(unitDir, `${label}.service`)],
|
|
4418
4497
|
crontabLine: null,
|
|
4419
4498
|
unloadCommands: [[...ctl, "disable", "--now", `${label}.service`]]
|
|
4420
4499
|
};
|
|
4421
4500
|
}
|
|
4422
4501
|
return {
|
|
4423
4502
|
mechanism: "systemd",
|
|
4424
|
-
unitPaths: [
|
|
4503
|
+
unitPaths: [join14(unitDir, `${label}.service`), join14(unitDir, `${label}.timer`)],
|
|
4425
4504
|
crontabLine: null,
|
|
4426
4505
|
unloadCommands: [[...ctl, "disable", "--now", `${label}.timer`]]
|
|
4427
4506
|
};
|
|
@@ -4439,7 +4518,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4439
4518
|
function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
4440
4519
|
const label = labelFor(crewHome, "run");
|
|
4441
4520
|
if (hostShip === "macos") {
|
|
4442
|
-
const unitPath =
|
|
4521
|
+
const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4443
4522
|
return {
|
|
4444
4523
|
mechanism: "launchd",
|
|
4445
4524
|
startCommand: ["launchctl", "load", unitPath],
|
|
@@ -4449,7 +4528,7 @@ function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
|
4449
4528
|
}
|
|
4450
4529
|
if (hostShip === "linux" && hasSystemd) {
|
|
4451
4530
|
const unit = `${label}.service`;
|
|
4452
|
-
const ctl =
|
|
4531
|
+
const ctl = existsSync12(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
|
|
4453
4532
|
return {
|
|
4454
4533
|
mechanism: "systemd",
|
|
4455
4534
|
startCommand: [...ctl, "start", unit],
|
|
@@ -4481,10 +4560,10 @@ async function applyInstall(plan, crewHome, dryRun, log) {
|
|
|
4481
4560
|
log.emit(`would write ${path}`);
|
|
4482
4561
|
continue;
|
|
4483
4562
|
}
|
|
4484
|
-
const before =
|
|
4563
|
+
const before = existsSync12(path) ? readFileSync13(path, "utf8") : null;
|
|
4485
4564
|
if (before !== plan.unitContent[path]) changed2 = true;
|
|
4486
|
-
|
|
4487
|
-
|
|
4565
|
+
mkdirSync8(dirname6(path), { recursive: true });
|
|
4566
|
+
writeFileSync9(path, plan.unitContent[path]);
|
|
4488
4567
|
log.emit(`wrote ${path}`);
|
|
4489
4568
|
}
|
|
4490
4569
|
if (plan.crontabLine) {
|
|
@@ -4548,7 +4627,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
4548
4627
|
}
|
|
4549
4628
|
}
|
|
4550
4629
|
for (const path of plan.unitPaths) {
|
|
4551
|
-
if (!
|
|
4630
|
+
if (!existsSync12(path)) continue;
|
|
4552
4631
|
if (dryRun) {
|
|
4553
4632
|
log.emit(`would remove ${path}`);
|
|
4554
4633
|
continue;
|
|
@@ -4567,7 +4646,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
4567
4646
|
}
|
|
4568
4647
|
var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
|
|
4569
4648
|
function launchAgentsDir() {
|
|
4570
|
-
return
|
|
4649
|
+
return join14(homedir7(), "Library", "LaunchAgents");
|
|
4571
4650
|
}
|
|
4572
4651
|
function xmlUnescape(s) {
|
|
4573
4652
|
return s.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
@@ -4584,25 +4663,25 @@ function loadedCrewLabels() {
|
|
|
4584
4663
|
return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
|
|
4585
4664
|
}
|
|
4586
4665
|
function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
4587
|
-
const files =
|
|
4666
|
+
const files = existsSync12(dir) ? readdirSync3(dir) : [];
|
|
4588
4667
|
const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
|
|
4589
|
-
const plistPath =
|
|
4668
|
+
const plistPath = join14(dir, f);
|
|
4590
4669
|
let program = null;
|
|
4591
4670
|
try {
|
|
4592
|
-
program = programFromPlist(
|
|
4671
|
+
program = programFromPlist(readFileSync13(plistPath, "utf8"));
|
|
4593
4672
|
} catch {
|
|
4594
4673
|
}
|
|
4595
4674
|
return {
|
|
4596
4675
|
label: f.slice(0, -".plist".length),
|
|
4597
4676
|
plistPath,
|
|
4598
4677
|
program,
|
|
4599
|
-
programExists: program !== null &&
|
|
4678
|
+
programExists: program !== null && existsSync12(program)
|
|
4600
4679
|
};
|
|
4601
4680
|
});
|
|
4602
4681
|
const seen = new Set(units.map((u) => u.label));
|
|
4603
4682
|
for (const label of [...loaded].sort()) {
|
|
4604
4683
|
if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
|
|
4605
|
-
units.push({ label, plistPath:
|
|
4684
|
+
units.push({ label, plistPath: join14(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
|
|
4606
4685
|
}
|
|
4607
4686
|
return units;
|
|
4608
4687
|
}
|
|
@@ -4615,9 +4694,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
|
|
|
4615
4694
|
log.emit(`would unload and remove ${unit.label}`);
|
|
4616
4695
|
return;
|
|
4617
4696
|
}
|
|
4618
|
-
if (
|
|
4697
|
+
if (existsSync12(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
|
|
4619
4698
|
spawnSync4("launchctl", ["remove", unit.label], { encoding: "utf8" });
|
|
4620
|
-
if (
|
|
4699
|
+
if (existsSync12(unit.plistPath)) unlinkSync(unit.plistPath);
|
|
4621
4700
|
log.emit(`removed ${unit.label} (${unit.plistPath})`);
|
|
4622
4701
|
}
|
|
4623
4702
|
function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
|
|
@@ -4695,24 +4774,24 @@ function detectSystemd() {
|
|
|
4695
4774
|
}
|
|
4696
4775
|
|
|
4697
4776
|
// src/ship-attention.ts
|
|
4698
|
-
import { existsSync as
|
|
4777
|
+
import { existsSync as existsSync14, statfsSync } from "fs";
|
|
4699
4778
|
|
|
4700
4779
|
// src/git.ts
|
|
4701
|
-
import { execFileSync as
|
|
4780
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
4702
4781
|
import {
|
|
4703
|
-
existsSync as
|
|
4704
|
-
mkdirSync as
|
|
4782
|
+
existsSync as existsSync13,
|
|
4783
|
+
mkdirSync as mkdirSync9,
|
|
4705
4784
|
openSync as openSync2,
|
|
4706
4785
|
closeSync as closeSync2,
|
|
4707
4786
|
writeSync,
|
|
4708
|
-
statSync as
|
|
4787
|
+
statSync as statSync6,
|
|
4709
4788
|
unlinkSync as unlinkSync2,
|
|
4710
|
-
readFileSync as
|
|
4711
|
-
writeFileSync as
|
|
4789
|
+
readFileSync as readFileSync14,
|
|
4790
|
+
writeFileSync as writeFileSync10,
|
|
4712
4791
|
readdirSync as readdirSync4,
|
|
4713
4792
|
rmSync as rmSync8
|
|
4714
4793
|
} from "fs";
|
|
4715
|
-
import { dirname as
|
|
4794
|
+
import { dirname as dirname7, basename as basename5, join as join15 } from "path";
|
|
4716
4795
|
var GitError = class extends Error {
|
|
4717
4796
|
};
|
|
4718
4797
|
function streamText(x) {
|
|
@@ -4720,7 +4799,7 @@ function streamText(x) {
|
|
|
4720
4799
|
}
|
|
4721
4800
|
function gitRaw(cwd, args) {
|
|
4722
4801
|
try {
|
|
4723
|
-
return
|
|
4802
|
+
return execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
4724
4803
|
} catch (e) {
|
|
4725
4804
|
const err3 = e;
|
|
4726
4805
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -4729,7 +4808,7 @@ function gitRaw(cwd, args) {
|
|
|
4729
4808
|
}
|
|
4730
4809
|
function git(cwd, args) {
|
|
4731
4810
|
try {
|
|
4732
|
-
return
|
|
4811
|
+
return execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
4733
4812
|
} catch (e) {
|
|
4734
4813
|
const err3 = e;
|
|
4735
4814
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -4859,7 +4938,7 @@ function worktreeForNumber(cwd, num) {
|
|
|
4859
4938
|
return worktrees(cwd).find((w) => re.test(basename5(w.path))) ?? null;
|
|
4860
4939
|
}
|
|
4861
4940
|
var commitBodies = (cwd, range) => gitOk(cwd, ["log", range, "--format=%B"]) ?? "";
|
|
4862
|
-
var
|
|
4941
|
+
var resolve3 = (cwd, ref) => gitOk(cwd, ["rev-parse", "-q", "--verify", `${ref}^{commit}`]);
|
|
4863
4942
|
var countCommits = (cwd, range) => Number.parseInt(gitOk(cwd, ["rev-list", "--count", range]) ?? "0", 10);
|
|
4864
4943
|
function latestReleaseTag(cwd, pattern = "v*", ref = "HEAD") {
|
|
4865
4944
|
return gitOk(cwd, ["describe", "--tags", "--abbrev=0", "--match", pattern, ref]);
|
|
@@ -4868,7 +4947,7 @@ function firstReleaseTagContaining(cwd, sha, pattern = "v*") {
|
|
|
4868
4947
|
const out = gitOk(cwd, ["tag", "--contains", sha, "--list", pattern, "--sort=creatordate"]);
|
|
4869
4948
|
return out?.split("\n").map((l) => l.trim()).find(Boolean) ?? null;
|
|
4870
4949
|
}
|
|
4871
|
-
var tagCommit = (cwd, tag) =>
|
|
4950
|
+
var tagCommit = (cwd, tag) => resolve3(cwd, tag);
|
|
4872
4951
|
function createReleaseTag(cwd, tag, sha, message) {
|
|
4873
4952
|
git(cwd, ["tag", "-a", tag, sha, "-m", message]);
|
|
4874
4953
|
}
|
|
@@ -4900,9 +4979,9 @@ function keyCommitsInRange(cwd, key, range) {
|
|
|
4900
4979
|
}
|
|
4901
4980
|
function patchIdOf(cwd, from, to) {
|
|
4902
4981
|
try {
|
|
4903
|
-
const diff =
|
|
4982
|
+
const diff = execFileSync3("git", ["diff", from, to], { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
|
|
4904
4983
|
if (!diff) return null;
|
|
4905
|
-
const out =
|
|
4984
|
+
const out = execFileSync3("git", ["patch-id", "--stable"], { cwd, encoding: "utf8", input: diff });
|
|
4906
4985
|
return out.split(" ")[0]?.trim() || null;
|
|
4907
4986
|
} catch {
|
|
4908
4987
|
return null;
|
|
@@ -5092,7 +5171,7 @@ function recoverStrandedReleaseCheckouts(cwd) {
|
|
|
5092
5171
|
if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
|
|
5093
5172
|
else if (line.startsWith("branch refs/heads/crew/release-") && path) {
|
|
5094
5173
|
gitOk(cwd, ["worktree", "remove", "--force", path]);
|
|
5095
|
-
const root =
|
|
5174
|
+
const root = dirname7(path);
|
|
5096
5175
|
if (basename5(root).startsWith("crew-release-")) rmSync8(root, { recursive: true, force: true });
|
|
5097
5176
|
}
|
|
5098
5177
|
}
|
|
@@ -5110,11 +5189,11 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
|
|
|
5110
5189
|
}
|
|
5111
5190
|
for (const name of names) {
|
|
5112
5191
|
if (!name.startsWith("crew-release-")) continue;
|
|
5113
|
-
const root =
|
|
5192
|
+
const root = join15(tmp, name);
|
|
5114
5193
|
try {
|
|
5115
|
-
const st =
|
|
5194
|
+
const st = statSync6(root);
|
|
5116
5195
|
if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
|
|
5117
|
-
if (
|
|
5196
|
+
if (existsSync13(join15(root, "checkout"))) continue;
|
|
5118
5197
|
rmSync8(root, { recursive: true, force: true });
|
|
5119
5198
|
removed.push(name);
|
|
5120
5199
|
} catch {
|
|
@@ -5166,16 +5245,16 @@ function originUrlOf(dir, remoteName) {
|
|
|
5166
5245
|
}
|
|
5167
5246
|
}
|
|
5168
5247
|
function ensureRepoCheckout(dir, remote, opts = {}) {
|
|
5169
|
-
if (
|
|
5248
|
+
if (existsSync13(`${dir}/.git`)) return false;
|
|
5170
5249
|
if (!remote) {
|
|
5171
5250
|
throw new GitError(
|
|
5172
5251
|
`${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`
|
|
5173
5252
|
);
|
|
5174
5253
|
}
|
|
5175
|
-
|
|
5254
|
+
mkdirSync9(dirname7(dir), { recursive: true });
|
|
5176
5255
|
const url = cloneUrlFor(remote, opts);
|
|
5177
5256
|
try {
|
|
5178
|
-
|
|
5257
|
+
execFileSync3("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
5179
5258
|
} catch (e) {
|
|
5180
5259
|
const err3 = e;
|
|
5181
5260
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -5197,7 +5276,7 @@ function sleepSync(ms3) {
|
|
|
5197
5276
|
}
|
|
5198
5277
|
function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
5199
5278
|
const lockPath = checkoutLockPath(dir);
|
|
5200
|
-
|
|
5279
|
+
mkdirSync9(dirname7(lockPath), { recursive: true });
|
|
5201
5280
|
const deadline = Date.now() + timeoutMs;
|
|
5202
5281
|
for (; ; ) {
|
|
5203
5282
|
try {
|
|
@@ -5215,7 +5294,7 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
|
5215
5294
|
const err3 = e;
|
|
5216
5295
|
if (err3.code !== "EEXIST") throw err3;
|
|
5217
5296
|
try {
|
|
5218
|
-
if (Date.now() -
|
|
5297
|
+
if (Date.now() - statSync6(lockPath).mtimeMs > CHECKOUT_LOCK_STALE_MS) {
|
|
5219
5298
|
try {
|
|
5220
5299
|
unlinkSync2(lockPath);
|
|
5221
5300
|
} catch {
|
|
@@ -5234,17 +5313,17 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
|
5234
5313
|
function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {}) {
|
|
5235
5314
|
const release = acquireCheckoutLock(dir);
|
|
5236
5315
|
try {
|
|
5237
|
-
if (!
|
|
5316
|
+
if (!existsSync13(`${dir}/.git`) && githubRemote) {
|
|
5238
5317
|
const url = cloneUrlFor(githubRemote, clone);
|
|
5239
5318
|
const marker = cloneFailureMarker(dir);
|
|
5240
|
-
if (
|
|
5241
|
-
return { action: "clone-failed", detail:
|
|
5319
|
+
if (existsSync13(marker) && readFileSync14(marker, "utf8").split("\n")[0] === url) {
|
|
5320
|
+
return { action: "clone-failed", detail: readFileSync14(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
|
|
5242
5321
|
}
|
|
5243
5322
|
try {
|
|
5244
5323
|
ensureRepoCheckout(dir, githubRemote, clone);
|
|
5245
5324
|
} catch (e) {
|
|
5246
5325
|
if (!(e instanceof GitError)) throw e;
|
|
5247
|
-
|
|
5326
|
+
writeFileSync10(marker, `${url}
|
|
5248
5327
|
${e.message}`);
|
|
5249
5328
|
return { action: "clone-failed", detail: e.message, repeat: false };
|
|
5250
5329
|
}
|
|
@@ -5470,7 +5549,7 @@ function noteHeldWorktree(state, h, nowMs = Date.now()) {
|
|
|
5470
5549
|
function reconcileHeldWorktrees(state, repo, log, stillRefused) {
|
|
5471
5550
|
for (const i of state.shipAttention().previous()) {
|
|
5472
5551
|
if (i.kind !== "worktree_held" || !i.held || i.held.repo !== repo) continue;
|
|
5473
|
-
const gone = !
|
|
5552
|
+
const gone = !existsSync14(i.held.path);
|
|
5474
5553
|
if (!gone && !(stillRefused && !stillRefused.has(i.held.path))) continue;
|
|
5475
5554
|
clearShipAttention(state, i.key);
|
|
5476
5555
|
log.emit(
|
|
@@ -5598,13 +5677,13 @@ function attentionProblems(items, already) {
|
|
|
5598
5677
|
// src/sandbox-image.ts
|
|
5599
5678
|
import { createHash as createHash6 } from "crypto";
|
|
5600
5679
|
import { spawn as spawn4 } from "child_process";
|
|
5601
|
-
import { execFileSync as
|
|
5602
|
-
import { mkdirSync as
|
|
5603
|
-
import { dirname as
|
|
5680
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
5681
|
+
import { mkdirSync as mkdirSync15, readFileSync as readFileSync22, renameSync as renameSync3, writeFileSync as writeFileSync16, rmSync as rmSync13 } from "fs";
|
|
5682
|
+
import { dirname as dirname11, join as join27 } from "path";
|
|
5604
5683
|
import { tmpdir as tmpdir3 } from "os";
|
|
5605
5684
|
|
|
5606
5685
|
// src/passenger-containers.ts
|
|
5607
|
-
import { execFileSync as
|
|
5686
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
5608
5687
|
import { createHash as createHash5 } from "crypto";
|
|
5609
5688
|
|
|
5610
5689
|
// src/sandbox-cache.ts
|
|
@@ -5613,32 +5692,32 @@ import { promisify as promisify2 } from "util";
|
|
|
5613
5692
|
|
|
5614
5693
|
// src/deprovision-host.ts
|
|
5615
5694
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
5616
|
-
import { existsSync as
|
|
5695
|
+
import { existsSync as existsSync25, readdirSync as readdirSync11 } from "fs";
|
|
5617
5696
|
|
|
5618
5697
|
// src/interrupted.ts
|
|
5619
|
-
import { existsSync as
|
|
5620
|
-
import { join as
|
|
5698
|
+
import { existsSync as existsSync23, readdirSync as readdirSync9, readFileSync as readFileSync21 } from "fs";
|
|
5699
|
+
import { join as join25 } from "path";
|
|
5621
5700
|
|
|
5622
5701
|
// src/agent.ts
|
|
5623
5702
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
5624
|
-
import { readFileSync as
|
|
5703
|
+
import { readFileSync as readFileSync20, existsSync as existsSync22, statSync as statSync8, mkdirSync as mkdirSync14, writeFileSync as writeFileSync15, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
|
|
5625
5704
|
import { StringDecoder } from "string_decoder";
|
|
5626
5705
|
import { finished } from "stream/promises";
|
|
5627
|
-
import { join as
|
|
5706
|
+
import { join as join24, dirname as dirname10, basename as basename9 } from "path";
|
|
5628
5707
|
import { createHash as createHash4 } from "crypto";
|
|
5629
5708
|
|
|
5630
5709
|
// src/state.ts
|
|
5631
|
-
import { readFileSync as
|
|
5632
|
-
import { join as
|
|
5710
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, existsSync as existsSync15, mkdirSync as mkdirSync10, rmSync as rmSync9, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
|
|
5711
|
+
import { join as join16 } from "path";
|
|
5633
5712
|
var EPOCH = "1970-01-01T00:00:00.000Z";
|
|
5634
5713
|
var State = class _State {
|
|
5635
5714
|
dir;
|
|
5636
5715
|
constructor(dir) {
|
|
5637
5716
|
this.dir = dir;
|
|
5638
|
-
|
|
5717
|
+
mkdirSync10(dir, { recursive: true });
|
|
5639
5718
|
}
|
|
5640
5719
|
path(...parts) {
|
|
5641
|
-
return
|
|
5720
|
+
return join16(this.dir, ...parts);
|
|
5642
5721
|
}
|
|
5643
5722
|
/**
|
|
5644
5723
|
* The timestamp every "new since last poll" check is measured against.
|
|
@@ -5649,14 +5728,14 @@ var State = class _State {
|
|
|
5649
5728
|
*/
|
|
5650
5729
|
watermark() {
|
|
5651
5730
|
try {
|
|
5652
|
-
const v =
|
|
5731
|
+
const v = readFileSync15(this.path(".poll-watermark"), "utf8").trim();
|
|
5653
5732
|
return v || EPOCH;
|
|
5654
5733
|
} catch {
|
|
5655
5734
|
return EPOCH;
|
|
5656
5735
|
}
|
|
5657
5736
|
}
|
|
5658
5737
|
setWatermark(at) {
|
|
5659
|
-
|
|
5738
|
+
writeFileSync11(this.path(".poll-watermark"), `${at}
|
|
5660
5739
|
`);
|
|
5661
5740
|
}
|
|
5662
5741
|
/**
|
|
@@ -5674,16 +5753,16 @@ var State = class _State {
|
|
|
5674
5753
|
}
|
|
5675
5754
|
/** Whole-crew pause, and the per-role sentinels one level down. */
|
|
5676
5755
|
isPaused() {
|
|
5677
|
-
return
|
|
5756
|
+
return existsSync15(this.path(".crew-paused"));
|
|
5678
5757
|
}
|
|
5679
5758
|
isRolePaused(role) {
|
|
5680
|
-
return
|
|
5759
|
+
return existsSync15(this.path(`.role-paused-${role}`));
|
|
5681
5760
|
}
|
|
5682
5761
|
pausedRoles(roles) {
|
|
5683
5762
|
return new Set(roles.filter((r) => this.isRolePaused(r)));
|
|
5684
5763
|
}
|
|
5685
5764
|
pause(role) {
|
|
5686
|
-
|
|
5765
|
+
writeFileSync11(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
|
|
5687
5766
|
}
|
|
5688
5767
|
resume(role) {
|
|
5689
5768
|
rmSync9(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), { force: true });
|
|
@@ -5694,10 +5773,10 @@ var State = class _State {
|
|
|
5694
5773
|
* while agents keep working.
|
|
5695
5774
|
*/
|
|
5696
5775
|
isReleasePaused() {
|
|
5697
|
-
return
|
|
5776
|
+
return existsSync15(this.path(".release-paused"));
|
|
5698
5777
|
}
|
|
5699
5778
|
setReleasePaused(paused) {
|
|
5700
|
-
if (paused)
|
|
5779
|
+
if (paused) writeFileSync11(this.path(".release-paused"), "");
|
|
5701
5780
|
else rmSync9(this.path(".release-paused"), { force: true });
|
|
5702
5781
|
}
|
|
5703
5782
|
/**
|
|
@@ -5735,7 +5814,7 @@ var State = class _State {
|
|
|
5735
5814
|
const name = _State.safe(rawName);
|
|
5736
5815
|
const file = this.path(`.${name}.lock`);
|
|
5737
5816
|
try {
|
|
5738
|
-
const existing = Number.parseInt(
|
|
5817
|
+
const existing = Number.parseInt(readFileSync15(file, "utf8").trim(), 10);
|
|
5739
5818
|
if (Number.isFinite(existing) && existing > 0) {
|
|
5740
5819
|
try {
|
|
5741
5820
|
process.kill(existing, 0);
|
|
@@ -5745,7 +5824,7 @@ var State = class _State {
|
|
|
5745
5824
|
}
|
|
5746
5825
|
} catch {
|
|
5747
5826
|
}
|
|
5748
|
-
|
|
5827
|
+
writeFileSync11(file, `${process.pid}
|
|
5749
5828
|
`);
|
|
5750
5829
|
return {
|
|
5751
5830
|
ok: true,
|
|
@@ -5773,7 +5852,7 @@ var State = class _State {
|
|
|
5773
5852
|
if (!/^\.release-.+\.lock$/.test(n)) continue;
|
|
5774
5853
|
let pid = 0;
|
|
5775
5854
|
try {
|
|
5776
|
-
pid = Number.parseInt(
|
|
5855
|
+
pid = Number.parseInt(readFileSync15(this.path(n), "utf8").trim(), 10);
|
|
5777
5856
|
} catch {
|
|
5778
5857
|
continue;
|
|
5779
5858
|
}
|
|
@@ -5859,7 +5938,7 @@ var State = class _State {
|
|
|
5859
5938
|
ticketInFlight(recordId) {
|
|
5860
5939
|
const file = this.path(`.${_State.safe(`crew-ticket-${recordId}`)}.lock`);
|
|
5861
5940
|
try {
|
|
5862
|
-
const pid = Number.parseInt(
|
|
5941
|
+
const pid = Number.parseInt(readFileSync15(file, "utf8").trim(), 10);
|
|
5863
5942
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
5864
5943
|
process.kill(pid, 0);
|
|
5865
5944
|
return true;
|
|
@@ -5893,7 +5972,7 @@ var State = class _State {
|
|
|
5893
5972
|
const heldReported = `.release-held-${suffix}`;
|
|
5894
5973
|
const read = (f) => {
|
|
5895
5974
|
try {
|
|
5896
|
-
return
|
|
5975
|
+
return readFileSync15(this.path(f), "utf8").trim() || null;
|
|
5897
5976
|
} catch {
|
|
5898
5977
|
return null;
|
|
5899
5978
|
}
|
|
@@ -5902,7 +5981,7 @@ var State = class _State {
|
|
|
5902
5981
|
blockedCount: () => Number.parseInt(read(blocked) ?? "", 10) || 0,
|
|
5903
5982
|
noteBlocked: () => {
|
|
5904
5983
|
const n = (Number.parseInt(read(blocked) ?? "", 10) || 0) + 1;
|
|
5905
|
-
|
|
5984
|
+
writeFileSync11(this.path(blocked), `${n}
|
|
5906
5985
|
`);
|
|
5907
5986
|
return n;
|
|
5908
5987
|
},
|
|
@@ -5919,7 +5998,7 @@ var State = class _State {
|
|
|
5919
5998
|
*/
|
|
5920
5999
|
deployFailedSha: () => read(failed),
|
|
5921
6000
|
noteDeployFailed: (sha) => {
|
|
5922
|
-
|
|
6001
|
+
writeFileSync11(this.path(failed), `${sha}
|
|
5923
6002
|
`);
|
|
5924
6003
|
},
|
|
5925
6004
|
clearDeployFailed: () => {
|
|
@@ -5933,7 +6012,7 @@ var State = class _State {
|
|
|
5933
6012
|
noteTestGateFailed: (sha) => {
|
|
5934
6013
|
const [prev, n] = (read(gateFailed) ?? "").split(" ");
|
|
5935
6014
|
const next = prev === sha ? (Number.parseInt(n ?? "", 10) || 0) + 1 : 1;
|
|
5936
|
-
|
|
6015
|
+
writeFileSync11(this.path(gateFailed), `${sha} ${next}
|
|
5937
6016
|
`);
|
|
5938
6017
|
return next;
|
|
5939
6018
|
},
|
|
@@ -5943,13 +6022,13 @@ var State = class _State {
|
|
|
5943
6022
|
/** Whether the board was already told about this head's red gate. */
|
|
5944
6023
|
heldReported: () => {
|
|
5945
6024
|
try {
|
|
5946
|
-
return
|
|
6025
|
+
return readFileSync15(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
|
|
5947
6026
|
} catch {
|
|
5948
6027
|
return null;
|
|
5949
6028
|
}
|
|
5950
6029
|
},
|
|
5951
6030
|
noteHeldReported: (signature) => {
|
|
5952
|
-
|
|
6031
|
+
writeFileSync11(this.path(heldReported), `${signature}
|
|
5953
6032
|
`);
|
|
5954
6033
|
},
|
|
5955
6034
|
clearHeldReported: () => {
|
|
@@ -5957,7 +6036,7 @@ var State = class _State {
|
|
|
5957
6036
|
},
|
|
5958
6037
|
testGateReported: (sha) => read(gateReported) === sha,
|
|
5959
6038
|
noteTestGateReported: (sha) => {
|
|
5960
|
-
|
|
6039
|
+
writeFileSync11(this.path(gateReported), `${sha}
|
|
5961
6040
|
`);
|
|
5962
6041
|
}
|
|
5963
6042
|
};
|
|
@@ -5980,7 +6059,7 @@ var State = class _State {
|
|
|
5980
6059
|
const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
|
|
5981
6060
|
const read = (f) => {
|
|
5982
6061
|
try {
|
|
5983
|
-
return
|
|
6062
|
+
return readFileSync15(this.path(f), "utf8").trim() || null;
|
|
5984
6063
|
} catch {
|
|
5985
6064
|
return null;
|
|
5986
6065
|
}
|
|
@@ -6013,18 +6092,18 @@ var State = class _State {
|
|
|
6013
6092
|
clear2();
|
|
6014
6093
|
return;
|
|
6015
6094
|
}
|
|
6016
|
-
|
|
6095
|
+
writeFileSync11(this.path(streakFile), `${this.readInt(streakFile) + 1}
|
|
6017
6096
|
`);
|
|
6018
6097
|
const prior = read(ticketFile);
|
|
6019
6098
|
if (topTicket && topTicket === prior) {
|
|
6020
|
-
|
|
6099
|
+
writeFileSync11(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
|
|
6021
6100
|
`);
|
|
6022
6101
|
} else if (topTicket) {
|
|
6023
|
-
|
|
6102
|
+
writeFileSync11(this.path(ticketFile), `${topTicket}
|
|
6024
6103
|
`);
|
|
6025
|
-
|
|
6104
|
+
writeFileSync11(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
6026
6105
|
`);
|
|
6027
|
-
|
|
6106
|
+
writeFileSync11(this.path(ticketStreakFile), "1\n");
|
|
6028
6107
|
}
|
|
6029
6108
|
}
|
|
6030
6109
|
};
|
|
@@ -6045,14 +6124,14 @@ var State = class _State {
|
|
|
6045
6124
|
return {
|
|
6046
6125
|
previous: () => {
|
|
6047
6126
|
try {
|
|
6048
|
-
const parsed = JSON.parse(
|
|
6127
|
+
const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
|
|
6049
6128
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
6050
6129
|
} catch {
|
|
6051
6130
|
return {};
|
|
6052
6131
|
}
|
|
6053
6132
|
},
|
|
6054
6133
|
persist: (next) => {
|
|
6055
|
-
|
|
6134
|
+
writeFileSync11(this.path(file), JSON.stringify(next));
|
|
6056
6135
|
}
|
|
6057
6136
|
};
|
|
6058
6137
|
}
|
|
@@ -6068,14 +6147,14 @@ var State = class _State {
|
|
|
6068
6147
|
return {
|
|
6069
6148
|
previous: () => {
|
|
6070
6149
|
try {
|
|
6071
|
-
const parsed = JSON.parse(
|
|
6150
|
+
const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
|
|
6072
6151
|
return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : [];
|
|
6073
6152
|
} catch {
|
|
6074
6153
|
return [];
|
|
6075
6154
|
}
|
|
6076
6155
|
},
|
|
6077
6156
|
persist: (next) => {
|
|
6078
|
-
|
|
6157
|
+
writeFileSync11(this.path(file), JSON.stringify(next));
|
|
6079
6158
|
}
|
|
6080
6159
|
};
|
|
6081
6160
|
}
|
|
@@ -6089,14 +6168,14 @@ var State = class _State {
|
|
|
6089
6168
|
return {
|
|
6090
6169
|
previous: () => {
|
|
6091
6170
|
try {
|
|
6092
|
-
const parsed = JSON.parse(
|
|
6171
|
+
const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
|
|
6093
6172
|
return Array.isArray(parsed) ? parsed : [];
|
|
6094
6173
|
} catch {
|
|
6095
6174
|
return [];
|
|
6096
6175
|
}
|
|
6097
6176
|
},
|
|
6098
6177
|
persist: (next) => {
|
|
6099
|
-
|
|
6178
|
+
writeFileSync11(this.path(file), JSON.stringify(next));
|
|
6100
6179
|
}
|
|
6101
6180
|
};
|
|
6102
6181
|
}
|
|
@@ -6109,11 +6188,11 @@ var State = class _State {
|
|
|
6109
6188
|
const file = ".release-last-run";
|
|
6110
6189
|
return {
|
|
6111
6190
|
touch: () => {
|
|
6112
|
-
|
|
6191
|
+
writeFileSync11(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
|
|
6113
6192
|
},
|
|
6114
6193
|
at: () => {
|
|
6115
6194
|
try {
|
|
6116
|
-
return
|
|
6195
|
+
return statSync7(this.path(file)).mtimeMs;
|
|
6117
6196
|
} catch {
|
|
6118
6197
|
return null;
|
|
6119
6198
|
}
|
|
@@ -6122,7 +6201,7 @@ var State = class _State {
|
|
|
6122
6201
|
}
|
|
6123
6202
|
readInt(file) {
|
|
6124
6203
|
try {
|
|
6125
|
-
return Number.parseInt(
|
|
6204
|
+
return Number.parseInt(readFileSync15(this.path(file), "utf8").trim(), 10) || 0;
|
|
6126
6205
|
} catch {
|
|
6127
6206
|
return 0;
|
|
6128
6207
|
}
|
|
@@ -6663,8 +6742,8 @@ var PERISCOPE_COLUMNS = [
|
|
|
6663
6742
|
];
|
|
6664
6743
|
|
|
6665
6744
|
// src/agent-log.ts
|
|
6666
|
-
import { existsSync as
|
|
6667
|
-
import { join as
|
|
6745
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync6, readFileSync as readFileSync16, rmSync as rmSync10, writeFileSync as writeFileSync12 } from "fs";
|
|
6746
|
+
import { join as join17 } from "path";
|
|
6668
6747
|
|
|
6669
6748
|
// src/pending-backoff.ts
|
|
6670
6749
|
var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
|
|
@@ -6836,20 +6915,20 @@ async function addRunToTicket(target, t, recordId, run3) {
|
|
|
6836
6915
|
};
|
|
6837
6916
|
}
|
|
6838
6917
|
}
|
|
6839
|
-
var queueDir = (stateDir) =>
|
|
6918
|
+
var queueDir = (stateDir) => join17(stateDir, "pending-agent-logs");
|
|
6840
6919
|
function queueAgentRun(stateDir, name, q2) {
|
|
6841
|
-
|
|
6842
|
-
|
|
6920
|
+
mkdirSync11(queueDir(stateDir), { recursive: true });
|
|
6921
|
+
writeFileSync12(join17(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
6843
6922
|
}
|
|
6844
6923
|
async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
6845
6924
|
const dir = queueDir(stateDir);
|
|
6846
|
-
if (!
|
|
6925
|
+
if (!existsSync16(dir)) return 0;
|
|
6847
6926
|
let sent = 0;
|
|
6848
6927
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
6849
|
-
const path =
|
|
6928
|
+
const path = join17(dir, f);
|
|
6850
6929
|
let q2;
|
|
6851
6930
|
try {
|
|
6852
|
-
q2 = JSON.parse(
|
|
6931
|
+
q2 = JSON.parse(readFileSync16(path, "utf8"));
|
|
6853
6932
|
} catch {
|
|
6854
6933
|
continue;
|
|
6855
6934
|
}
|
|
@@ -6878,7 +6957,7 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
6878
6957
|
continue;
|
|
6879
6958
|
}
|
|
6880
6959
|
const attempts = q2.attempts + 1;
|
|
6881
|
-
|
|
6960
|
+
writeFileSync12(path, JSON.stringify({
|
|
6882
6961
|
...q2,
|
|
6883
6962
|
attempts,
|
|
6884
6963
|
warned: true,
|
|
@@ -6889,20 +6968,20 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
6889
6968
|
}
|
|
6890
6969
|
return sent;
|
|
6891
6970
|
}
|
|
6892
|
-
var countersDir = (stateDir) =>
|
|
6971
|
+
var countersDir = (stateDir) => join17(stateDir, "pending-ticket-counters");
|
|
6893
6972
|
function queueTicketCounters(stateDir, name, q2) {
|
|
6894
|
-
|
|
6895
|
-
|
|
6973
|
+
mkdirSync11(countersDir(stateDir), { recursive: true });
|
|
6974
|
+
writeFileSync12(join17(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
6896
6975
|
}
|
|
6897
6976
|
async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
6898
6977
|
const dir = countersDir(stateDir);
|
|
6899
|
-
if (!
|
|
6978
|
+
if (!existsSync16(dir)) return 0;
|
|
6900
6979
|
let sent = 0;
|
|
6901
6980
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
6902
|
-
const path =
|
|
6981
|
+
const path = join17(dir, f);
|
|
6903
6982
|
let q2;
|
|
6904
6983
|
try {
|
|
6905
|
-
q2 = JSON.parse(
|
|
6984
|
+
q2 = JSON.parse(readFileSync16(path, "utf8"));
|
|
6906
6985
|
} catch {
|
|
6907
6986
|
continue;
|
|
6908
6987
|
}
|
|
@@ -6921,7 +7000,7 @@ async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /
|
|
|
6921
7000
|
continue;
|
|
6922
7001
|
}
|
|
6923
7002
|
const attempts = q2.attempts + 1;
|
|
6924
|
-
|
|
7003
|
+
writeFileSync12(path, JSON.stringify({
|
|
6925
7004
|
...q2,
|
|
6926
7005
|
attempts,
|
|
6927
7006
|
warned: true,
|
|
@@ -7124,8 +7203,8 @@ var HandoffTranscriptTracker = class {
|
|
|
7124
7203
|
};
|
|
7125
7204
|
|
|
7126
7205
|
// src/pending-handoff.ts
|
|
7127
|
-
import { existsSync as
|
|
7128
|
-
import { join as
|
|
7206
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync12, readdirSync as readdirSync7, readFileSync as readFileSync17, rmSync as rmSync11, writeFileSync as writeFileSync13 } from "fs";
|
|
7207
|
+
import { join as join20 } from "path";
|
|
7129
7208
|
|
|
7130
7209
|
// src/ticket-branch.ts
|
|
7131
7210
|
import { basename as basename7 } from "path";
|
|
@@ -8291,16 +8370,16 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
|
|
|
8291
8370
|
}
|
|
8292
8371
|
|
|
8293
8372
|
// src/run-worktree.ts
|
|
8294
|
-
import { existsSync as
|
|
8295
|
-
import { dirname as
|
|
8373
|
+
import { existsSync as existsSync18 } from "fs";
|
|
8374
|
+
import { dirname as dirname8, join as join19 } from "path";
|
|
8296
8375
|
|
|
8297
8376
|
// src/ports.ts
|
|
8298
|
-
import { execFileSync as
|
|
8299
|
-
import { existsSync as
|
|
8300
|
-
import { join as
|
|
8377
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
8378
|
+
import { existsSync as existsSync17 } from "fs";
|
|
8379
|
+
import { join as join18 } from "path";
|
|
8301
8380
|
var run = (cmd, args) => {
|
|
8302
8381
|
try {
|
|
8303
|
-
return
|
|
8382
|
+
return execFileSync4(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
8304
8383
|
} catch {
|
|
8305
8384
|
return "";
|
|
8306
8385
|
}
|
|
@@ -8342,7 +8421,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
|
|
|
8342
8421
|
}
|
|
8343
8422
|
function worktreeExists(parent, prefix, n) {
|
|
8344
8423
|
for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
|
|
8345
|
-
if (
|
|
8424
|
+
if (existsSync17(join18(parent, name, ".git"))) return true;
|
|
8346
8425
|
}
|
|
8347
8426
|
return false;
|
|
8348
8427
|
}
|
|
@@ -8425,7 +8504,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
8425
8504
|
const existing = worktreeForNumber(dir, num);
|
|
8426
8505
|
const located = locateBranchForTicket(dir, cfg, t, role);
|
|
8427
8506
|
const baseRef = hasRemote ? `${remote}/${base}` : base;
|
|
8428
|
-
if (existing && !existing.prunable &&
|
|
8507
|
+
if (existing && !existing.prunable && existsSync18(existing.path)) {
|
|
8429
8508
|
const branch = existing.branch;
|
|
8430
8509
|
if (status(existing.path).length > 0) {
|
|
8431
8510
|
return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
|
|
@@ -8441,7 +8520,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
8441
8520
|
if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
|
|
8442
8521
|
return { action: "recut", path: existing.path, branch, base: baseRef };
|
|
8443
8522
|
}
|
|
8444
|
-
const path =
|
|
8523
|
+
const path = join19(dirname8(dir), worktreeForTicket(dir, cfg, t, role));
|
|
8445
8524
|
if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
|
|
8446
8525
|
return { action: "cut-remote", path, branch: located.branch };
|
|
8447
8526
|
}
|
|
@@ -8495,7 +8574,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
8495
8574
|
if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
|
|
8496
8575
|
if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
|
|
8497
8576
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
8498
|
-
if (wt &&
|
|
8577
|
+
if (wt && existsSync18(wt.path)) {
|
|
8499
8578
|
if (status(wt.path).length > 0) return false;
|
|
8500
8579
|
if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
|
|
8501
8580
|
}
|
|
@@ -8503,7 +8582,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
8503
8582
|
}
|
|
8504
8583
|
async function finishRunWorktree(dir, cfg, branch) {
|
|
8505
8584
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
8506
|
-
if (!wt || !
|
|
8585
|
+
if (!wt || !existsSync18(wt.path)) return { kind: "none" };
|
|
8507
8586
|
if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
|
|
8508
8587
|
if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
|
|
8509
8588
|
if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
|
|
@@ -8515,7 +8594,7 @@ async function finishRunWorktree(dir, cfg, branch) {
|
|
|
8515
8594
|
if (pids.length) await killGently(pids);
|
|
8516
8595
|
}
|
|
8517
8596
|
gitOk(dir, ["worktree", "remove", wt.path]);
|
|
8518
|
-
return
|
|
8597
|
+
return existsSync18(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
|
|
8519
8598
|
}
|
|
8520
8599
|
|
|
8521
8600
|
// src/handoff-guard.ts
|
|
@@ -8898,20 +8977,20 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
|
|
|
8898
8977
|
|
|
8899
8978
|
// src/pending-handoff.ts
|
|
8900
8979
|
var PENDING_MAX_AGE_MS = 48 * 36e5;
|
|
8901
|
-
var pendingDirFor = (stateDir) =>
|
|
8902
|
-
var stateDirOfStream = (streamPath) =>
|
|
8980
|
+
var pendingDirFor = (stateDir) => join20(stateDir, "pending-handoffs");
|
|
8981
|
+
var stateDirOfStream = (streamPath) => join20(streamPath, "..", "..");
|
|
8903
8982
|
function writePendingHandoff(stateDir, rec) {
|
|
8904
8983
|
const dir = pendingDirFor(stateDir);
|
|
8905
|
-
|
|
8906
|
-
|
|
8984
|
+
mkdirSync12(dir, { recursive: true });
|
|
8985
|
+
writeFileSync13(join20(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
|
|
8907
8986
|
}
|
|
8908
8987
|
function listPendingHandoffs(stateDir, route2) {
|
|
8909
8988
|
const dir = pendingDirFor(stateDir);
|
|
8910
|
-
if (!
|
|
8989
|
+
if (!existsSync19(dir)) return [];
|
|
8911
8990
|
const out = [];
|
|
8912
8991
|
for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
8913
8992
|
try {
|
|
8914
|
-
const rec = JSON.parse(
|
|
8993
|
+
const rec = JSON.parse(readFileSync17(join20(dir, f), "utf8"));
|
|
8915
8994
|
if (rec.version === 1 && rec.ticketRecordId && (!route2 || rec.route === route2)) out.push(rec);
|
|
8916
8995
|
} catch {
|
|
8917
8996
|
}
|
|
@@ -8919,7 +8998,7 @@ function listPendingHandoffs(stateDir, route2) {
|
|
|
8919
8998
|
return out;
|
|
8920
8999
|
}
|
|
8921
9000
|
function removePendingHandoff(stateDir, ticketRecordId) {
|
|
8922
|
-
rmSync11(
|
|
9001
|
+
rmSync11(join20(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
|
|
8923
9002
|
}
|
|
8924
9003
|
function pendingHandoffFor(stateDir, ticketRecordId) {
|
|
8925
9004
|
return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
|
|
@@ -9029,8 +9108,8 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
|
|
|
9029
9108
|
}
|
|
9030
9109
|
|
|
9031
9110
|
// src/skills.ts
|
|
9032
|
-
import { readFileSync as
|
|
9033
|
-
import { join as
|
|
9111
|
+
import { readFileSync as readFileSync19, readdirSync as readdirSync8, existsSync as existsSync21 } from "fs";
|
|
9112
|
+
import { join as join22 } from "path";
|
|
9034
9113
|
import { parse as parseYaml } from "yaml";
|
|
9035
9114
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
9036
9115
|
|
|
@@ -9046,8 +9125,8 @@ ${additions}`;
|
|
|
9046
9125
|
}
|
|
9047
9126
|
|
|
9048
9127
|
// src/agents.ts
|
|
9049
|
-
import { readFileSync as
|
|
9050
|
-
import { join as
|
|
9128
|
+
import { readFileSync as readFileSync18, existsSync as existsSync20 } from "fs";
|
|
9129
|
+
import { join as join21 } from "path";
|
|
9051
9130
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
|
|
9052
9131
|
var PERSONA_NAME = {
|
|
9053
9132
|
dev: "Developer",
|
|
@@ -9061,12 +9140,12 @@ var CREW_AGENT_COLUMN = "agent_id";
|
|
|
9061
9140
|
var AgentsSyncError = class extends Error {
|
|
9062
9141
|
};
|
|
9063
9142
|
function personaDefaultPrompt(promptsDir, role) {
|
|
9064
|
-
const briefPath =
|
|
9065
|
-
if (!
|
|
9066
|
-
if (SELF_CONTAINED_ROLES.has(role)) return
|
|
9067
|
-
const commonPath =
|
|
9068
|
-
if (!
|
|
9069
|
-
return `${
|
|
9143
|
+
const briefPath = join21(promptsDir, "personas", `lane-${role}.md`);
|
|
9144
|
+
if (!existsSync20(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
|
|
9145
|
+
if (SELF_CONTAINED_ROLES.has(role)) return readFileSync18(briefPath, "utf8");
|
|
9146
|
+
const commonPath = join21(promptsDir, "personas", "common.md");
|
|
9147
|
+
if (!existsSync20(commonPath)) return readFileSync18(briefPath, "utf8");
|
|
9148
|
+
return `${readFileSync18(commonPath, "utf8")}${readFileSync18(briefPath, "utf8")}`;
|
|
9070
9149
|
}
|
|
9071
9150
|
async function resolveAgentId(route2, role, opts) {
|
|
9072
9151
|
const cached = route2.resolved?.agentPersonas?.[role]?.agentId;
|
|
@@ -9513,7 +9592,7 @@ function describeCrewLink(o) {
|
|
|
9513
9592
|
var SkillSyncError = class extends Error {
|
|
9514
9593
|
};
|
|
9515
9594
|
function parseSkillFile(path) {
|
|
9516
|
-
const text2 =
|
|
9595
|
+
const text2 = readFileSync19(path, "utf8");
|
|
9517
9596
|
const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text2);
|
|
9518
9597
|
if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
|
|
9519
9598
|
const frontmatter = match[1];
|
|
@@ -9526,9 +9605,9 @@ function parseSkillFile(path) {
|
|
|
9526
9605
|
return { name: meta.name, description: meta.description, prompt: body, file: path };
|
|
9527
9606
|
}
|
|
9528
9607
|
function listLocalSkills(promptsDir) {
|
|
9529
|
-
const dir =
|
|
9530
|
-
if (!
|
|
9531
|
-
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(
|
|
9608
|
+
const dir = join22(promptsDir, "skills");
|
|
9609
|
+
if (!existsSync21(dir)) return [];
|
|
9610
|
+
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join22(dir, f)));
|
|
9532
9611
|
}
|
|
9533
9612
|
async function syncSkills(route2, opts) {
|
|
9534
9613
|
if (!route2.resolved) {
|
|
@@ -9712,10 +9791,10 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
|
|
|
9712
9791
|
}
|
|
9713
9792
|
|
|
9714
9793
|
// src/runner.ts
|
|
9715
|
-
import { spawn as spawn2, execFile, execFileSync as
|
|
9716
|
-
import { mkdtempSync, mkdirSync as
|
|
9794
|
+
import { spawn as spawn2, execFile, execFileSync as execFileSync5 } from "child_process";
|
|
9795
|
+
import { mkdtempSync, mkdirSync as mkdirSync13, writeFileSync as writeFileSync14, rmSync as rmSync12 } from "fs";
|
|
9717
9796
|
import { tmpdir as tmpdir2 } from "os";
|
|
9718
|
-
import { basename as basename8, dirname as
|
|
9797
|
+
import { basename as basename8, dirname as dirname9, join as join23 } from "path";
|
|
9719
9798
|
import { promisify } from "util";
|
|
9720
9799
|
|
|
9721
9800
|
// src/run-limit.ts
|
|
@@ -9844,8 +9923,8 @@ var SandboxRunner = class {
|
|
|
9844
9923
|
throw new Error(`cannot generate the sandbox policy: ${e.message}`);
|
|
9845
9924
|
}
|
|
9846
9925
|
policyFile = policyPathFor(plan.streamPath);
|
|
9847
|
-
|
|
9848
|
-
|
|
9926
|
+
mkdirSync13(dirname9(policyFile), { recursive: true });
|
|
9927
|
+
writeFileSync14(policyFile, generated.yaml, { mode: 384 });
|
|
9849
9928
|
}
|
|
9850
9929
|
const create2 = ["sandbox", "create", "--name", name, "--detach"];
|
|
9851
9930
|
if (policyFile) create2.push("--policy", policyFile);
|
|
@@ -9888,10 +9967,10 @@ var SandboxRunner = class {
|
|
|
9888
9967
|
const harness = cfg.agentBin ?? basename8(plan.bin);
|
|
9889
9968
|
const viaUpload = Buffer.byteLength(plan.prompt) > SANDBOX_STDIN_LIMIT;
|
|
9890
9969
|
if (viaUpload) {
|
|
9891
|
-
const tmp = mkdtempSync(
|
|
9970
|
+
const tmp = mkdtempSync(join23(tmpdir2(), "crew-prompt-"));
|
|
9892
9971
|
try {
|
|
9893
|
-
const file =
|
|
9894
|
-
|
|
9972
|
+
const file = join23(tmp, "prompt.txt");
|
|
9973
|
+
writeFileSync14(file, plan.prompt, { mode: 384 });
|
|
9895
9974
|
await this.os(["sandbox", "upload", name, file, SANDBOX_PROMPT_PATH]);
|
|
9896
9975
|
} finally {
|
|
9897
9976
|
rmSync12(tmp, { recursive: true, force: true });
|
|
@@ -9957,7 +10036,7 @@ function describeExecError(e) {
|
|
|
9957
10036
|
}
|
|
9958
10037
|
function gitOrigin(cwd) {
|
|
9959
10038
|
try {
|
|
9960
|
-
return
|
|
10039
|
+
return execFileSync5("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
9961
10040
|
} catch {
|
|
9962
10041
|
return null;
|
|
9963
10042
|
}
|
|
@@ -9968,7 +10047,7 @@ function runnerFor(kind, sandbox) {
|
|
|
9968
10047
|
function runnerOfSidecar(sc) {
|
|
9969
10048
|
return sc.runner === "sandbox" ? new SandboxRunner(sc.sandboxBin ? { bin: sc.sandboxBin } : {}) : new HostRunner();
|
|
9970
10049
|
}
|
|
9971
|
-
function checkSandboxRunner(cfg = {}, run3 = (b, a) =>
|
|
10050
|
+
function checkSandboxRunner(cfg = {}, run3 = (b, a) => execFileSync5(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 })) {
|
|
9972
10051
|
const bin = cfg.bin ?? DEFAULT_SANDBOX_CONFIG.bin;
|
|
9973
10052
|
let version;
|
|
9974
10053
|
try {
|
|
@@ -10035,7 +10114,7 @@ var ALLOWANCE_REFUSAL_MARKER = "AI_ALLOWANCE_EXCEEDED";
|
|
|
10035
10114
|
function streamShowsAllowanceRefusal(streamPath) {
|
|
10036
10115
|
let raw;
|
|
10037
10116
|
try {
|
|
10038
|
-
raw =
|
|
10117
|
+
raw = readFileSync20(streamPath, "utf8");
|
|
10039
10118
|
} catch {
|
|
10040
10119
|
return false;
|
|
10041
10120
|
}
|
|
@@ -10058,26 +10137,26 @@ function streamShowsAllowanceRefusal(streamPath) {
|
|
|
10058
10137
|
}
|
|
10059
10138
|
return false;
|
|
10060
10139
|
}
|
|
10061
|
-
var digestPathFor = (stateDir, routeLabel, role, runId) =>
|
|
10140
|
+
var digestPathFor = (stateDir, routeLabel, role, runId) => join24(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
10062
10141
|
function planAgentRun(o) {
|
|
10063
|
-
const briefPath =
|
|
10064
|
-
if (!
|
|
10142
|
+
const briefPath = join24(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
10143
|
+
if (!existsSync22(briefPath)) {
|
|
10065
10144
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
10066
10145
|
}
|
|
10067
|
-
const commonPath =
|
|
10068
|
-
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) &&
|
|
10146
|
+
const commonPath = join24(o.route.promptsDir, "personas", "common.md");
|
|
10147
|
+
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync22(commonPath);
|
|
10069
10148
|
const now = o.now ?? (() => Date.now());
|
|
10070
10149
|
const routeLabel = routeSlug(o.route.route);
|
|
10071
10150
|
const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
|
|
10072
|
-
const digestPath2 = ownDigest &&
|
|
10151
|
+
const digestPath2 = ownDigest && existsSync22(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
|
|
10073
10152
|
let digest;
|
|
10074
10153
|
let ageSeconds;
|
|
10075
|
-
if (
|
|
10076
|
-
ageSeconds = Math.floor((now() -
|
|
10077
|
-
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest =
|
|
10154
|
+
if (existsSync22(digestPath2)) {
|
|
10155
|
+
ageSeconds = Math.floor((now() - statSync8(digestPath2).mtimeMs) / 1e3);
|
|
10156
|
+
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync20(digestPath2, "utf8");
|
|
10078
10157
|
}
|
|
10079
|
-
const common = usesCommon ?
|
|
10080
|
-
const brief =
|
|
10158
|
+
const common = usesCommon ? readFileSync20(commonPath, "utf8") : "";
|
|
10159
|
+
const brief = readFileSync20(briefPath, "utf8");
|
|
10081
10160
|
const effectiveCommon = o.divergedPrompt ? "" : common;
|
|
10082
10161
|
const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
|
|
10083
10162
|
const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
|
|
@@ -10100,7 +10179,7 @@ function planAgentRun(o) {
|
|
|
10100
10179
|
digest
|
|
10101
10180
|
});
|
|
10102
10181
|
const promptSha = createHash4("sha256").update(expanded.text).digest("hex");
|
|
10103
|
-
const streamsDir =
|
|
10182
|
+
const streamsDir = join24(o.stateDir, "streams");
|
|
10104
10183
|
const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
|
|
10105
10184
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
10106
10185
|
const { model } = seat;
|
|
@@ -10190,8 +10269,8 @@ function planAgentRun(o) {
|
|
|
10190
10269
|
digestAttached: digest !== void 0,
|
|
10191
10270
|
digestAgeSeconds: ageSeconds,
|
|
10192
10271
|
model,
|
|
10193
|
-
streamPath:
|
|
10194
|
-
eventsPath:
|
|
10272
|
+
streamPath: join24(streamsDir, `${base}.jsonl`),
|
|
10273
|
+
eventsPath: join24(streamsDir, `${base}.events.jsonl`),
|
|
10195
10274
|
ticket: o.ticket,
|
|
10196
10275
|
ticketRecordId: o.ticketRecordId ?? null,
|
|
10197
10276
|
repo: o.repo ?? null,
|
|
@@ -10222,7 +10301,7 @@ function describePlan(p) {
|
|
|
10222
10301
|
var pidPathFor = (streamPath) => `${streamPath}.pid`;
|
|
10223
10302
|
function readRunSidecar(streamPath) {
|
|
10224
10303
|
try {
|
|
10225
|
-
const sc = JSON.parse(
|
|
10304
|
+
const sc = JSON.parse(readFileSync20(pidPathFor(streamPath), "utf8"));
|
|
10226
10305
|
return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
|
|
10227
10306
|
} catch {
|
|
10228
10307
|
return null;
|
|
@@ -10269,7 +10348,7 @@ async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMi
|
|
|
10269
10348
|
var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
|
|
10270
10349
|
function openSink(path) {
|
|
10271
10350
|
try {
|
|
10272
|
-
|
|
10351
|
+
mkdirSync14(dirname10(path), { recursive: true });
|
|
10273
10352
|
return createWriteStream(path, { flags: "a" });
|
|
10274
10353
|
} catch {
|
|
10275
10354
|
return void 0;
|
|
@@ -10451,7 +10530,7 @@ var RunObserver = class {
|
|
|
10451
10530
|
};
|
|
10452
10531
|
function writeRunMeta(id, startedAt, liveTicket, extra) {
|
|
10453
10532
|
try {
|
|
10454
|
-
|
|
10533
|
+
writeFileSync15(`${id.streamPath}.meta.json`, JSON.stringify({
|
|
10455
10534
|
route: id.route,
|
|
10456
10535
|
role: id.role,
|
|
10457
10536
|
hint: id.ticket,
|
|
@@ -10611,7 +10690,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
10611
10690
|
const id = identityOf(plan, adapter.harness);
|
|
10612
10691
|
let streamFd;
|
|
10613
10692
|
try {
|
|
10614
|
-
|
|
10693
|
+
mkdirSync14(dirname10(plan.streamPath), { recursive: true });
|
|
10615
10694
|
streamFd = openSync3(plan.streamPath, "a");
|
|
10616
10695
|
} catch (e) {
|
|
10617
10696
|
void endSink(eventsSink);
|
|
@@ -10643,7 +10722,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
10643
10722
|
closeSync3(streamFd);
|
|
10644
10723
|
return supervise(begun);
|
|
10645
10724
|
function supervise({ child, handle, promptDelivered }) {
|
|
10646
|
-
return new Promise((
|
|
10725
|
+
return new Promise((resolve8, reject) => {
|
|
10647
10726
|
const spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10648
10727
|
const deadline = deadlineFor(spawnedAt, plan.maxRunMinutes);
|
|
10649
10728
|
if (child.pid !== void 0) {
|
|
@@ -10669,7 +10748,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
10669
10748
|
promptSha: plan.promptSha
|
|
10670
10749
|
};
|
|
10671
10750
|
try {
|
|
10672
|
-
|
|
10751
|
+
writeFileSync15(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
|
|
10673
10752
|
} catch {
|
|
10674
10753
|
}
|
|
10675
10754
|
}
|
|
@@ -10713,7 +10792,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
10713
10792
|
plan.agentLog?.apiKey,
|
|
10714
10793
|
hintMoved
|
|
10715
10794
|
);
|
|
10716
|
-
|
|
10795
|
+
resolve8({ code: code ?? 1, ms: ms3, ...handoffPending ? { handoffPending } : {}, ...killed ? { killed } : {} });
|
|
10717
10796
|
});
|
|
10718
10797
|
child.stdin?.on("error", (err3) => {
|
|
10719
10798
|
emit.warn(`agent closed stdin before the prompt was delivered: ${err3.message}`, { step: "agent" });
|
|
@@ -10783,21 +10862,21 @@ var pidAlive = (pid) => {
|
|
|
10783
10862
|
};
|
|
10784
10863
|
function runFinished(streamPath) {
|
|
10785
10864
|
const path = `${streamPath}.meta.json`;
|
|
10786
|
-
if (!
|
|
10865
|
+
if (!existsSync23(path)) return false;
|
|
10787
10866
|
try {
|
|
10788
|
-
return JSON.parse(
|
|
10867
|
+
return JSON.parse(readFileSync21(path, "utf8")).running !== true;
|
|
10789
10868
|
} catch {
|
|
10790
10869
|
return true;
|
|
10791
10870
|
}
|
|
10792
10871
|
}
|
|
10793
10872
|
function orphanedStreams(o) {
|
|
10794
|
-
const dir =
|
|
10795
|
-
if (!
|
|
10873
|
+
const dir = join25(o.stateDir, "streams");
|
|
10874
|
+
if (!existsSync23(dir)) return [];
|
|
10796
10875
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
10797
10876
|
const out = [];
|
|
10798
10877
|
for (const name of readdirSync9(dir)) {
|
|
10799
10878
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
10800
|
-
const streamPath =
|
|
10879
|
+
const streamPath = join25(dir, name);
|
|
10801
10880
|
if (runFinished(streamPath)) continue;
|
|
10802
10881
|
const sidecar = readRunSidecar(streamPath);
|
|
10803
10882
|
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
@@ -10850,15 +10929,15 @@ async function reconcileRuns(o) {
|
|
|
10850
10929
|
}
|
|
10851
10930
|
|
|
10852
10931
|
// src/live-runs.ts
|
|
10853
|
-
import { existsSync as
|
|
10854
|
-
import { join as
|
|
10932
|
+
import { existsSync as existsSync24, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
|
|
10933
|
+
import { join as join26 } from "path";
|
|
10855
10934
|
function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
10856
|
-
const dir =
|
|
10857
|
-
if (!
|
|
10935
|
+
const dir = join26(stateDir, "streams");
|
|
10936
|
+
if (!existsSync24(dir)) return [];
|
|
10858
10937
|
const out = [];
|
|
10859
10938
|
for (const name of readdirSync10(dir)) {
|
|
10860
10939
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
10861
|
-
const streamPath =
|
|
10940
|
+
const streamPath = join26(dir, name);
|
|
10862
10941
|
if (runFinished(streamPath)) continue;
|
|
10863
10942
|
const sidecar = readRunSidecar(streamPath);
|
|
10864
10943
|
if (!sidecar || !isAlive(sidecar)) continue;
|
|
@@ -10868,15 +10947,15 @@ function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
|
10868
10947
|
}
|
|
10869
10948
|
function runActivity(stateDir, isAlive = runIsAlive) {
|
|
10870
10949
|
const activeRuns = liveRuns(stateDir, isAlive).length;
|
|
10871
|
-
const dir =
|
|
10950
|
+
const dir = join26(stateDir, "streams");
|
|
10872
10951
|
let newest = 0;
|
|
10873
|
-
if (
|
|
10952
|
+
if (existsSync24(dir)) {
|
|
10874
10953
|
for (const name of readdirSync10(dir)) {
|
|
10875
10954
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
10876
|
-
const streamPath =
|
|
10955
|
+
const streamPath = join26(dir, name);
|
|
10877
10956
|
if (!runFinished(streamPath)) continue;
|
|
10878
10957
|
try {
|
|
10879
|
-
newest = Math.max(newest,
|
|
10958
|
+
newest = Math.max(newest, statSync9(`${streamPath}.meta.json`).mtimeMs);
|
|
10880
10959
|
} catch {
|
|
10881
10960
|
}
|
|
10882
10961
|
}
|
|
@@ -11244,7 +11323,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
11244
11323
|
if (host === "windows") return [];
|
|
11245
11324
|
const ctx = installContext();
|
|
11246
11325
|
const systemd = detectSystemd();
|
|
11247
|
-
return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) =>
|
|
11326
|
+
return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync25(p));
|
|
11248
11327
|
};
|
|
11249
11328
|
return {
|
|
11250
11329
|
liveRuns: () => liveRuns(stateDir).map((r) => ({ role: r.role, route: r.route, pid: r.pid, ticketRecordId: r.ticketRecordId ?? void 0, streamPath: r.streamPath })),
|
|
@@ -11305,7 +11384,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
11305
11384
|
},
|
|
11306
11385
|
remaining() {
|
|
11307
11386
|
const left = [];
|
|
11308
|
-
if (
|
|
11387
|
+
if (existsSync25(cfg.configFile)) left.push(`config ${cfg.configFile}`);
|
|
11309
11388
|
for (const h of forgeHosts()) if (hasForgeToken(cfg.ship.forges, h, stateDir)) left.push(`forge token for ${h}`);
|
|
11310
11389
|
try {
|
|
11311
11390
|
for (const k of readdirSync11(keysDir(stateDir))) left.push(`seat key ${k}`);
|
|
@@ -11580,7 +11659,7 @@ function makeCachePass(o) {
|
|
|
11580
11659
|
// src/passenger-containers.ts
|
|
11581
11660
|
var realExec2 = (cmd, args, opts) => {
|
|
11582
11661
|
try {
|
|
11583
|
-
return
|
|
11662
|
+
return execFileSync6(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
|
|
11584
11663
|
} catch (e) {
|
|
11585
11664
|
const err3 = e;
|
|
11586
11665
|
const why = [err3.stderr, err3.stdout].map((x) => (x ?? "").trim()).filter(Boolean).join(" / ");
|
|
@@ -11860,7 +11939,7 @@ var WARM_MAX_BYTES = 64 * 1024 * 1024;
|
|
|
11860
11939
|
var globToRegExp = (glob) => new RegExp(`^${glob.split("*").map((p) => p.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")}$`);
|
|
11861
11940
|
function readWarmFiles(dir, ref, patterns) {
|
|
11862
11941
|
if (patterns.length === 0) return {};
|
|
11863
|
-
const git2 = (args) =>
|
|
11942
|
+
const git2 = (args) => execFileSync7("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
|
|
11864
11943
|
const all = git2(["ls-tree", "-r", "--name-only", ref]).split("\n").filter(Boolean);
|
|
11865
11944
|
const res = patterns.map(globToRegExp);
|
|
11866
11945
|
const out = {};
|
|
@@ -11913,19 +11992,19 @@ function planSandbox(repo, decl, warm = {}) {
|
|
|
11913
11992
|
const layer = repoLayer(repo, decl, crew, warm);
|
|
11914
11993
|
return { repo, tag: layer.tag, claude: SANDBOX_CLAUDE_VERSION, layers: [base, crew, layer] };
|
|
11915
11994
|
}
|
|
11916
|
-
var sandboxStatePath = (stateDir) =>
|
|
11995
|
+
var sandboxStatePath = (stateDir) => join27(stateDir, "sandbox-images.json");
|
|
11917
11996
|
function readSandboxState(stateDir) {
|
|
11918
11997
|
try {
|
|
11919
|
-
const v = JSON.parse(
|
|
11998
|
+
const v = JSON.parse(readFileSync22(sandboxStatePath(stateDir), "utf8"));
|
|
11920
11999
|
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
11921
12000
|
} catch {
|
|
11922
12001
|
return {};
|
|
11923
12002
|
}
|
|
11924
12003
|
}
|
|
11925
12004
|
function writeSandboxState(stateDir, state) {
|
|
11926
|
-
|
|
12005
|
+
mkdirSync15(stateDir, { recursive: true });
|
|
11927
12006
|
const file = sandboxStatePath(stateDir);
|
|
11928
|
-
|
|
12007
|
+
writeFileSync16(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
|
|
11929
12008
|
`);
|
|
11930
12009
|
renameSync3(`${file}.tmp`, file);
|
|
11931
12010
|
}
|
|
@@ -11939,15 +12018,15 @@ function sandboxImageStatus(state, plan) {
|
|
|
11939
12018
|
if (current.tag !== plan.tag) return { status: "stale", detail: `running ${current.tag} (claude ${current.claude ?? "unknown"}), declaration now builds ${plan.tag} (claude ${plan.claude})` };
|
|
11940
12019
|
return { status: "ok", detail: `${current.tag} built ${current.builtAt}, claude ${current.claude ?? "unknown"}` };
|
|
11941
12020
|
}
|
|
11942
|
-
var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((
|
|
12021
|
+
var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((resolve8) => {
|
|
11943
12022
|
const child = spawn4(engine, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
11944
12023
|
const chunks = [];
|
|
11945
12024
|
child.stdout.on("data", (d) => chunks.push(d));
|
|
11946
12025
|
child.stderr.on("data", (d) => chunks.push(d));
|
|
11947
12026
|
const done = (code, extra = "") => {
|
|
11948
|
-
|
|
12027
|
+
writeFileSync16(logPath, `$ ${engine} ${args.join(" ")}
|
|
11949
12028
|
${Buffer.concat(chunks).toString("utf8")}${extra}`, { flag: "a" });
|
|
11950
|
-
|
|
12029
|
+
resolve8(code);
|
|
11951
12030
|
};
|
|
11952
12031
|
child.on("error", (e) => done(127, `${e.message}
|
|
11953
12032
|
`));
|
|
@@ -11967,7 +12046,7 @@ var engineRemoveImage = (engine = "docker", exec = realExec2) => (tag) => {
|
|
|
11967
12046
|
} catch {
|
|
11968
12047
|
}
|
|
11969
12048
|
};
|
|
11970
|
-
var sandboxLogDir = (stateDir) =>
|
|
12049
|
+
var sandboxLogDir = (stateDir) => join27(stateDir, "sandbox-logs");
|
|
11971
12050
|
async function buildSandboxImage(plan, deps) {
|
|
11972
12051
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
11973
12052
|
const state = readSandboxState(deps.stateDir);
|
|
@@ -11976,20 +12055,20 @@ async function buildSandboxImage(plan, deps) {
|
|
|
11976
12055
|
return { kind: "reused", tag: plan.tag };
|
|
11977
12056
|
}
|
|
11978
12057
|
const logDir = sandboxLogDir(deps.stateDir);
|
|
11979
|
-
|
|
11980
|
-
const logPath =
|
|
12058
|
+
mkdirSync15(logDir, { recursive: true });
|
|
12059
|
+
const logPath = join27(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
|
|
11981
12060
|
const builtLayers = [];
|
|
11982
12061
|
for (const layer of plan.layers) {
|
|
11983
12062
|
if (deps.hasImage(layer.tag)) continue;
|
|
11984
|
-
const ctx =
|
|
11985
|
-
|
|
12063
|
+
const ctx = join27(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
|
|
12064
|
+
mkdirSync15(ctx, { recursive: true });
|
|
11986
12065
|
try {
|
|
11987
|
-
|
|
12066
|
+
writeFileSync16(join27(ctx, "Dockerfile"), layer.dockerfile);
|
|
11988
12067
|
for (const [name, content] of Object.entries(layer.files)) {
|
|
11989
|
-
|
|
11990
|
-
|
|
12068
|
+
mkdirSync15(dirname11(join27(ctx, name)), { recursive: true });
|
|
12069
|
+
writeFileSync16(join27(ctx, name), content);
|
|
11991
12070
|
}
|
|
11992
|
-
const code = await deps.run(["build", "--tag", layer.tag, "--file",
|
|
12071
|
+
const code = await deps.run(["build", "--tag", layer.tag, "--file", join27(ctx, "Dockerfile"), ctx], logPath);
|
|
11993
12072
|
if (code !== 0) {
|
|
11994
12073
|
state[plan.repo] = { ...entry, failure: { tag: plan.tag, at: now().toISOString(), logPath } };
|
|
11995
12074
|
writeSandboxState(deps.stateDir, state);
|
|
@@ -12042,10 +12121,10 @@ async function sandboxBuildTick(repos, deps, force = false) {
|
|
|
12042
12121
|
}
|
|
12043
12122
|
|
|
12044
12123
|
// src/doctor-checks.ts
|
|
12045
|
-
import { execFileSync as
|
|
12124
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
12046
12125
|
var PROBE_BRANCH = "crew-probe-branch";
|
|
12047
12126
|
var realGit = (cwd, args, env) => {
|
|
12048
|
-
|
|
12127
|
+
execFileSync8("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
12049
12128
|
};
|
|
12050
12129
|
function isSshRemote(url) {
|
|
12051
12130
|
return /^ssh:\/\//.test(url) || /^[\w.-]+@[\w.-]+:/.test(url);
|
|
@@ -12125,10 +12204,10 @@ function formatPushAuth(repo, r) {
|
|
|
12125
12204
|
return `${label}FAILED \u2014 ${r.detail}; ${r.hint}
|
|
12126
12205
|
`;
|
|
12127
12206
|
}
|
|
12128
|
-
async function settleRepos(items, nameOf,
|
|
12207
|
+
async function settleRepos(items, nameOf, resolve8) {
|
|
12129
12208
|
const out = await Promise.all(items.map(async (t) => {
|
|
12130
12209
|
try {
|
|
12131
|
-
return { ok: true, value: await
|
|
12210
|
+
return { ok: true, value: await resolve8(t) };
|
|
12132
12211
|
} catch (e) {
|
|
12133
12212
|
return { ok: false, repo: nameOf(t), detail: e.message };
|
|
12134
12213
|
}
|
|
@@ -12164,17 +12243,28 @@ function roleCapWarnings(ship) {
|
|
|
12164
12243
|
if (!ship.maxConcurrentAgentsSet) return [];
|
|
12165
12244
|
return Object.entries(ship.roleConcurrency).filter(([, n]) => (n ?? 0) > ship.maxConcurrentAgents).map(([role, n]) => `ship.roleConcurrency.${role} is ${n}, above ship.maxConcurrentAgents (${ship.maxConcurrentAgents}); the ship-wide cap wins`);
|
|
12166
12245
|
}
|
|
12246
|
+
function managedRuntimeWarnings(kind, ship) {
|
|
12247
|
+
if (kind !== "managed_instance" && kind !== "pooled_instance" && kind !== "pooled") return [];
|
|
12248
|
+
const out = [];
|
|
12249
|
+
const explicit = ship.toolchains ?? [];
|
|
12250
|
+
const effectiveToolchains = explicit.some((t) => t.name === "claude") ? explicit : [{ name: "claude", harness: "claude" }, ...explicit];
|
|
12251
|
+
if ((ship.runner ?? "host") !== "sandbox") out.push(`ship.runner is "${ship.runner ?? "host"}" on a ${kind} ship; a managed ship should run agents in the sandbox (set ship.runner: sandbox)`);
|
|
12252
|
+
for (const t of effectiveToolchains) {
|
|
12253
|
+
if (t.harness === "claude" && t.auth !== "workspace") out.push(`ship.toolchains.${t.name}.auth is "${t.auth ?? "inherit"}" on a ${kind} ship; a managed ship reaches a model only through the workspace endpoint (set auth: workspace)`);
|
|
12254
|
+
}
|
|
12255
|
+
return out;
|
|
12256
|
+
}
|
|
12167
12257
|
|
|
12168
12258
|
// src/selftest.ts
|
|
12169
12259
|
import { spawn as spawn5 } from "child_process";
|
|
12170
|
-
import { existsSync as
|
|
12260
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync16, mkdtempSync as mkdtempSync2, readFileSync as readFileSync23, rmSync as rmSync14, writeFileSync as writeFileSync17 } from "fs";
|
|
12171
12261
|
import { tmpdir as tmpdir4 } from "os";
|
|
12172
|
-
import { join as
|
|
12262
|
+
import { join as join28 } from "path";
|
|
12173
12263
|
var SELFTEST_TIMEOUT_MS = 12e4;
|
|
12174
12264
|
var KEY_VAR = "CREW_API_KEY";
|
|
12175
12265
|
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: ";
|
|
12176
12266
|
var realDeps = {
|
|
12177
|
-
run: (o) => new Promise((
|
|
12267
|
+
run: (o) => new Promise((resolve8) => {
|
|
12178
12268
|
let stdout = "";
|
|
12179
12269
|
let stderr = "";
|
|
12180
12270
|
let timedOut = false;
|
|
@@ -12182,7 +12272,7 @@ var realDeps = {
|
|
|
12182
12272
|
const done = (r) => {
|
|
12183
12273
|
if (!settled) {
|
|
12184
12274
|
settled = true;
|
|
12185
|
-
|
|
12275
|
+
resolve8(r);
|
|
12186
12276
|
}
|
|
12187
12277
|
};
|
|
12188
12278
|
const child = spawn5(o.bin, o.args, { cwd: o.cwd, env: o.env, stdio: ["pipe", "pipe", "pipe"], detached: true });
|
|
@@ -12330,7 +12420,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
12330
12420
|
...adapter.runEnv(spec),
|
|
12331
12421
|
[KEY_VAR]: i.apiKey
|
|
12332
12422
|
};
|
|
12333
|
-
const cwd = mkdtempSync2(
|
|
12423
|
+
const cwd = mkdtempSync2(join28(tmpdir4(), "crew-selftest-"));
|
|
12334
12424
|
try {
|
|
12335
12425
|
const run3 = await deps.run({
|
|
12336
12426
|
bin: invocation.bin,
|
|
@@ -12363,11 +12453,11 @@ function formatResult(r) {
|
|
|
12363
12453
|
const secs = `${(r.ms / 1e3).toFixed(1)}s`;
|
|
12364
12454
|
return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
|
|
12365
12455
|
}
|
|
12366
|
-
var fileFor2 = (stateDir) =>
|
|
12456
|
+
var fileFor2 = (stateDir) => join28(stateDir, "selftest.json");
|
|
12367
12457
|
function readSelftest(stateDir) {
|
|
12368
12458
|
try {
|
|
12369
12459
|
const p = fileFor2(stateDir);
|
|
12370
|
-
return
|
|
12460
|
+
return existsSync26(p) ? JSON.parse(readFileSync23(p, "utf8")) : {};
|
|
12371
12461
|
} catch {
|
|
12372
12462
|
return {};
|
|
12373
12463
|
}
|
|
@@ -12375,8 +12465,8 @@ function readSelftest(stateDir) {
|
|
|
12375
12465
|
function recordSelftest(stateDir, results) {
|
|
12376
12466
|
const all = readSelftest(stateDir);
|
|
12377
12467
|
for (const r of results) all[r.toolchain] = r;
|
|
12378
|
-
|
|
12379
|
-
|
|
12468
|
+
mkdirSync16(stateDir, { recursive: true });
|
|
12469
|
+
writeFileSync17(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
|
|
12380
12470
|
`);
|
|
12381
12471
|
}
|
|
12382
12472
|
function doctorSelftestLines(stored, toolchains) {
|
|
@@ -12388,7 +12478,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
12388
12478
|
}
|
|
12389
12479
|
|
|
12390
12480
|
// src/cli.ts
|
|
12391
|
-
import { resolve as
|
|
12481
|
+
import { resolve as resolve7, dirname as dirname29, join as join50, basename as basename16 } from "path";
|
|
12392
12482
|
import { fileURLToPath } from "url";
|
|
12393
12483
|
import { hostname as hostname4, homedir as homedir11 } from "os";
|
|
12394
12484
|
import { spawnSync as spawnSync10 } from "child_process";
|
|
@@ -12585,14 +12675,14 @@ function rosterMarkdown(roster, meId) {
|
|
|
12585
12675
|
}
|
|
12586
12676
|
|
|
12587
12677
|
// src/connect-resume.ts
|
|
12588
|
-
import { mkdirSync as
|
|
12589
|
-
import { dirname as
|
|
12678
|
+
import { mkdirSync as mkdirSync17, readFileSync as readFileSync24, rmSync as rmSync15, writeFileSync as writeFileSync18 } from "fs";
|
|
12679
|
+
import { dirname as dirname12, join as join29 } from "path";
|
|
12590
12680
|
function lastSessionFile(stateDir) {
|
|
12591
|
-
return
|
|
12681
|
+
return join29(stateDir, "connect-session.json");
|
|
12592
12682
|
}
|
|
12593
12683
|
function readPointers(stateDir) {
|
|
12594
12684
|
try {
|
|
12595
|
-
const parsed = JSON.parse(
|
|
12685
|
+
const parsed = JSON.parse(readFileSync24(lastSessionFile(stateDir), "utf8"));
|
|
12596
12686
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
12597
12687
|
const out = {};
|
|
12598
12688
|
for (const [host, v] of Object.entries(parsed)) {
|
|
@@ -12606,8 +12696,8 @@ function readPointers(stateDir) {
|
|
|
12606
12696
|
}
|
|
12607
12697
|
function writePointers(stateDir, pointers) {
|
|
12608
12698
|
const file = lastSessionFile(stateDir);
|
|
12609
|
-
|
|
12610
|
-
|
|
12699
|
+
mkdirSync17(dirname12(file), { recursive: true });
|
|
12700
|
+
writeFileSync18(file, `${JSON.stringify(pointers, null, 2)}
|
|
12611
12701
|
`, { mode: 384 });
|
|
12612
12702
|
}
|
|
12613
12703
|
function rememberSession(stateDir, host, workspaceSlug) {
|
|
@@ -12692,15 +12782,15 @@ async function signOutSessions(o) {
|
|
|
12692
12782
|
}
|
|
12693
12783
|
|
|
12694
12784
|
// src/reset.ts
|
|
12695
|
-
import { existsSync as
|
|
12696
|
-
import { dirname as
|
|
12785
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync18, readFileSync as readFileSync25, readdirSync as readdirSync12, rmSync as rmSync16, statSync as statSync10, writeFileSync as writeFileSync19 } from "fs";
|
|
12786
|
+
import { dirname as dirname13, join as join30 } from "path";
|
|
12697
12787
|
import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
|
|
12698
12788
|
function clonedCheckoutsFile(stateDir) {
|
|
12699
|
-
return
|
|
12789
|
+
return join30(stateDir, "cloned-checkouts.json");
|
|
12700
12790
|
}
|
|
12701
12791
|
function readClonedCheckouts(stateDir) {
|
|
12702
12792
|
try {
|
|
12703
|
-
const parsed = JSON.parse(
|
|
12793
|
+
const parsed = JSON.parse(readFileSync25(clonedCheckoutsFile(stateDir), "utf8"));
|
|
12704
12794
|
return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
|
|
12705
12795
|
} catch {
|
|
12706
12796
|
return [];
|
|
@@ -12710,8 +12800,8 @@ function recordClonedCheckout(stateDir, dir) {
|
|
|
12710
12800
|
const known = readClonedCheckouts(stateDir);
|
|
12711
12801
|
if (known.includes(dir)) return;
|
|
12712
12802
|
const file = clonedCheckoutsFile(stateDir);
|
|
12713
|
-
|
|
12714
|
-
|
|
12803
|
+
mkdirSync18(dirname13(file), { recursive: true });
|
|
12804
|
+
writeFileSync19(file, `${JSON.stringify([...known, dir], null, 2)}
|
|
12715
12805
|
`);
|
|
12716
12806
|
}
|
|
12717
12807
|
var mark = (dryRun) => dryRun ? "would-remove" : "removed";
|
|
@@ -12765,7 +12855,7 @@ async function runReset(o, fx) {
|
|
|
12765
12855
|
}
|
|
12766
12856
|
if (o.beforeState) steps.push(...await o.beforeState());
|
|
12767
12857
|
steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
|
|
12768
|
-
if (
|
|
12858
|
+
if (existsSync27(o.configFile)) {
|
|
12769
12859
|
if (!o.dryRun) rmSync16(o.configFile, { force: true });
|
|
12770
12860
|
steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
|
|
12771
12861
|
} else {
|
|
@@ -12774,7 +12864,7 @@ async function runReset(o, fx) {
|
|
|
12774
12864
|
return steps;
|
|
12775
12865
|
}
|
|
12776
12866
|
function removeCheckout(dir, dryRun, fx) {
|
|
12777
|
-
if (!
|
|
12867
|
+
if (!existsSync27(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
|
|
12778
12868
|
const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
|
|
12779
12869
|
if (dirty.length) {
|
|
12780
12870
|
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" : ""})` };
|
|
@@ -12787,15 +12877,15 @@ function removeCheckout(dir, dryRun, fx) {
|
|
|
12787
12877
|
}
|
|
12788
12878
|
function removeState(stateDir, dryRun) {
|
|
12789
12879
|
const out = [];
|
|
12790
|
-
const targets = [
|
|
12880
|
+
const targets = [join30(stateDir, "resolved"), join30(stateDir, "keys"), clonedCheckoutsFile(stateDir), join30(stateDir, "connect-session.json"), join30(stateDir, "reload-requested"), join30(stateDir, "daemon.started")];
|
|
12791
12881
|
try {
|
|
12792
12882
|
for (const name of readdirSync12(stateDir)) {
|
|
12793
|
-
if (name.startsWith(".") &&
|
|
12883
|
+
if (name.startsWith(".") && statSync10(join30(stateDir, name)).isFile()) targets.push(join30(stateDir, name));
|
|
12794
12884
|
}
|
|
12795
12885
|
} catch {
|
|
12796
12886
|
}
|
|
12797
12887
|
for (const t of targets) {
|
|
12798
|
-
if (!
|
|
12888
|
+
if (!existsSync27(t)) continue;
|
|
12799
12889
|
if (!dryRun) rmSync16(t, { recursive: true, force: true });
|
|
12800
12890
|
out.push({ step: "state", target: t, action: mark(dryRun) });
|
|
12801
12891
|
}
|
|
@@ -12810,7 +12900,7 @@ function wipeStateDir(stateDir, dryRun) {
|
|
|
12810
12900
|
return [{ step: "state", target: stateDir, action: "absent", detail: "no state directory" }];
|
|
12811
12901
|
}
|
|
12812
12902
|
if (!names.length) return [{ step: "state", target: stateDir, action: "absent", detail: "already empty" }];
|
|
12813
|
-
if (!dryRun) for (const n of names) rmSync16(
|
|
12903
|
+
if (!dryRun) for (const n of names) rmSync16(join30(stateDir, n), { recursive: true, force: true });
|
|
12814
12904
|
return [{ step: "state", target: stateDir, action: mark(dryRun), detail: `${names.length} entr${names.length === 1 ? "y" : "ies"}` }];
|
|
12815
12905
|
}
|
|
12816
12906
|
function describeResetStep(s) {
|
|
@@ -12891,9 +12981,9 @@ async function runDeprovision(o, fx, deps) {
|
|
|
12891
12981
|
}
|
|
12892
12982
|
|
|
12893
12983
|
// src/pool-member.ts
|
|
12894
|
-
import { mkdirSync as
|
|
12984
|
+
import { mkdirSync as mkdirSync19, readFileSync as readFileSync26, renameSync as renameSync4, writeFileSync as writeFileSync20 } from "fs";
|
|
12895
12985
|
import { homedir as homedir8 } from "os";
|
|
12896
|
-
import { dirname as
|
|
12986
|
+
import { dirname as dirname14, join as join31 } from "path";
|
|
12897
12987
|
var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
|
|
12898
12988
|
function backoffMs(attempt) {
|
|
12899
12989
|
return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
|
|
@@ -13124,22 +13214,22 @@ async function runPoolMember(deps) {
|
|
|
13124
13214
|
}
|
|
13125
13215
|
}
|
|
13126
13216
|
function defaultMemberDir(env = process.env) {
|
|
13127
|
-
return env.CREW_POOL_DIR ??
|
|
13217
|
+
return env.CREW_POOL_DIR ?? join31(env.XDG_STATE_HOME ?? join31(homedir8(), ".local", "state"), "crew-pool-member");
|
|
13128
13218
|
}
|
|
13129
13219
|
function fileMemberStore(dir) {
|
|
13130
|
-
const file =
|
|
13220
|
+
const file = join31(dir, "member.json");
|
|
13131
13221
|
return {
|
|
13132
13222
|
read() {
|
|
13133
13223
|
try {
|
|
13134
|
-
return JSON.parse(
|
|
13224
|
+
return JSON.parse(readFileSync26(file, "utf8"));
|
|
13135
13225
|
} catch {
|
|
13136
13226
|
return {};
|
|
13137
13227
|
}
|
|
13138
13228
|
},
|
|
13139
13229
|
write(f) {
|
|
13140
|
-
|
|
13230
|
+
mkdirSync19(dirname14(file), { recursive: true, mode: 448 });
|
|
13141
13231
|
const tmp = `${file}.tmp`;
|
|
13142
|
-
|
|
13232
|
+
writeFileSync20(tmp, `${JSON.stringify(f)}
|
|
13143
13233
|
`, { mode: 384 });
|
|
13144
13234
|
renameSync4(tmp, file);
|
|
13145
13235
|
}
|
|
@@ -13191,30 +13281,30 @@ function hostArchitecture(arch = process.arch) {
|
|
|
13191
13281
|
|
|
13192
13282
|
// src/pool-member-host.ts
|
|
13193
13283
|
import "child_process";
|
|
13194
|
-
import { existsSync as
|
|
13284
|
+
import { existsSync as existsSync31 } from "fs";
|
|
13195
13285
|
import { hostname as hostname3 } from "os";
|
|
13196
13286
|
|
|
13197
13287
|
// src/machine-id.ts
|
|
13198
|
-
import { execFileSync as
|
|
13288
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
13199
13289
|
import { createHash as createHash7 } from "crypto";
|
|
13200
|
-
import { existsSync as
|
|
13201
|
-
import { dirname as
|
|
13290
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync20, readFileSync as readFileSync27, writeFileSync as writeFileSync21 } from "fs";
|
|
13291
|
+
import { dirname as dirname15, join as join32 } from "path";
|
|
13202
13292
|
function machineIdPathFor(stateDir) {
|
|
13203
|
-
return
|
|
13293
|
+
return join32(stateDir, "machine-id");
|
|
13204
13294
|
}
|
|
13205
13295
|
var sha256 = (s) => createHash7("sha256").update(s).digest("hex");
|
|
13206
13296
|
function readRawMachineId(platform2 = process.platform) {
|
|
13207
13297
|
try {
|
|
13208
13298
|
if (platform2 === "darwin") {
|
|
13209
|
-
const out =
|
|
13299
|
+
const out = execFileSync9("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
|
|
13210
13300
|
encoding: "utf8",
|
|
13211
13301
|
stdio: ["ignore", "pipe", "ignore"]
|
|
13212
13302
|
});
|
|
13213
13303
|
return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
|
|
13214
13304
|
}
|
|
13215
13305
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
13216
|
-
if (
|
|
13217
|
-
const id =
|
|
13306
|
+
if (existsSync28(p)) {
|
|
13307
|
+
const id = readFileSync27(p, "utf8").trim();
|
|
13218
13308
|
if (id) return id;
|
|
13219
13309
|
}
|
|
13220
13310
|
}
|
|
@@ -13225,15 +13315,15 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
13225
13315
|
function ensureMachineId(stateDir, opts = {}) {
|
|
13226
13316
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
13227
13317
|
const path = machineIdPathFor(stateDir);
|
|
13228
|
-
if (
|
|
13229
|
-
const cached =
|
|
13318
|
+
if (existsSync28(path)) {
|
|
13319
|
+
const cached = readFileSync27(path, "utf8").trim();
|
|
13230
13320
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
13231
13321
|
}
|
|
13232
13322
|
const raw = (opts.read ?? readRawMachineId)();
|
|
13233
13323
|
if (!raw) return void 0;
|
|
13234
13324
|
const hashed = sha256(raw);
|
|
13235
|
-
|
|
13236
|
-
|
|
13325
|
+
mkdirSync20(dirname15(path), { recursive: true });
|
|
13326
|
+
writeFileSync21(path, `${hashed}
|
|
13237
13327
|
`, { mode: 384 });
|
|
13238
13328
|
return hashed;
|
|
13239
13329
|
}
|
|
@@ -13246,16 +13336,16 @@ function matchShipRows(ships, shipName2, machineId) {
|
|
|
13246
13336
|
|
|
13247
13337
|
// src/provision.ts
|
|
13248
13338
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
13249
|
-
import { existsSync as
|
|
13339
|
+
import { existsSync as existsSync29, readFileSync as readFileSync28, writeFileSync as writeFileSync22 } from "fs";
|
|
13250
13340
|
import { hostname as hostname2 } from "os";
|
|
13251
|
-
import { parseDocument as parseDocument3 } from "yaml";
|
|
13341
|
+
import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
|
|
13252
13342
|
|
|
13253
13343
|
// src/doctor-fix.ts
|
|
13254
13344
|
import { parseDocument as parseDocument2 } from "yaml";
|
|
13255
13345
|
|
|
13256
13346
|
// src/repos-cmd.ts
|
|
13257
13347
|
import { homedir as homedir9 } from "os";
|
|
13258
|
-
import { basename as basename10, join as
|
|
13348
|
+
import { basename as basename10, join as join33, resolve as resolve4 } from "path";
|
|
13259
13349
|
import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
|
|
13260
13350
|
var ReposError = class extends Error {
|
|
13261
13351
|
};
|
|
@@ -13302,7 +13392,7 @@ function parseAddArgs(args, routes, isDir) {
|
|
|
13302
13392
|
}
|
|
13303
13393
|
function expandDir(p, base) {
|
|
13304
13394
|
const home = p === "~" ? homedir9() : p.startsWith("~/") ? `${homedir9()}/${p.slice(2)}` : p;
|
|
13305
|
-
return
|
|
13395
|
+
return resolve4(base, home);
|
|
13306
13396
|
}
|
|
13307
13397
|
function routeMapOf(doc, route2) {
|
|
13308
13398
|
const routes = doc.get("routes", true);
|
|
@@ -13533,7 +13623,7 @@ function planRepoClone(i) {
|
|
|
13533
13623
|
}
|
|
13534
13624
|
const ship = doc.get("ship", true);
|
|
13535
13625
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
13536
|
-
const target = i.dir ? expandDir(i.dir, i.base) :
|
|
13626
|
+
const target = i.dir ? expandDir(i.dir, i.base) : join33(expandDir(basePath, i.base), i.route.split("/")[0], name);
|
|
13537
13627
|
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
|
|
13538
13628
|
const forgeHost = i.resolved?.repoForgeHosts?.[id];
|
|
13539
13629
|
return { ...add, remote, target, ...forgeHost ? { forgeHost } : {} };
|
|
@@ -13684,6 +13774,15 @@ async function runProvision(o, deps, write, human) {
|
|
|
13684
13774
|
emit("connect", `set ship.${key}`, { setting: key });
|
|
13685
13775
|
}
|
|
13686
13776
|
}
|
|
13777
|
+
if (o.runner === "host") {
|
|
13778
|
+
emit("connect", "runner: host chosen explicitly (--runner host); not applying the managed sandbox defaults", { setting: "ship.runner", value: "host" });
|
|
13779
|
+
if (deps.setShipSetting("runner", "host")) summary.changed = true;
|
|
13780
|
+
} else if (o.runner === "sandbox" || isManagedShipKind(o.shipKind)) {
|
|
13781
|
+
for (const key of deps.applyManagedDefaults()) {
|
|
13782
|
+
summary.changed = true;
|
|
13783
|
+
emit("connect", `set ${key}`, { setting: key });
|
|
13784
|
+
}
|
|
13785
|
+
}
|
|
13687
13786
|
for (const host of o.forgeHosts) {
|
|
13688
13787
|
const token = o.tokens[host];
|
|
13689
13788
|
const stored = deps.storedToken(host);
|
|
@@ -13727,6 +13826,38 @@ async function runProvision(o, deps, write, human) {
|
|
|
13727
13826
|
emit("done", summary.state, { summary });
|
|
13728
13827
|
return { code: PROVISION_EXIT.ok, summary };
|
|
13729
13828
|
}
|
|
13829
|
+
function isManagedShipKind(kind) {
|
|
13830
|
+
return kind === "managed_instance" || kind === "pooled_instance" || kind === "pooled";
|
|
13831
|
+
}
|
|
13832
|
+
var MANAGED_CACHE_VOLUME = "crew-cache";
|
|
13833
|
+
function applyManagedDefaultsToDoc(doc) {
|
|
13834
|
+
const wrote = [];
|
|
13835
|
+
if (doc.getIn(["ship", "runner"]) === void 0) {
|
|
13836
|
+
doc.setIn(["ship", "runner"], "sandbox");
|
|
13837
|
+
wrote.push("ship.runner");
|
|
13838
|
+
}
|
|
13839
|
+
if (doc.getIn(["ship", "sandbox", "cacheVolume"]) === void 0) {
|
|
13840
|
+
doc.setIn(["ship", "sandbox", "cacheVolume"], MANAGED_CACHE_VOLUME);
|
|
13841
|
+
wrote.push("ship.sandbox.cacheVolume");
|
|
13842
|
+
}
|
|
13843
|
+
const existing = doc.getIn(["ship", "toolchains"]);
|
|
13844
|
+
const hasClaude = isMap2(existing) && existing.items.some((i) => (isScalar(i.key) ? i.key.value : i.key) === "claude");
|
|
13845
|
+
if (!hasClaude) {
|
|
13846
|
+
doc.setIn(["ship", "toolchains", "claude", "harness"], "claude");
|
|
13847
|
+
for (const k of ["bin", "model", "maxThinkingTokens"]) {
|
|
13848
|
+
const v = doc.getIn(["ship", "agent", k]);
|
|
13849
|
+
if (v !== void 0) doc.setIn(["ship", "toolchains", "claude", k], v);
|
|
13850
|
+
}
|
|
13851
|
+
}
|
|
13852
|
+
const toolchains = doc.getIn(["ship", "toolchains"]);
|
|
13853
|
+
for (const name of isMap2(toolchains) ? toolchains.items.map((i) => String(isScalar(i.key) ? i.key.value : i.key)) : []) {
|
|
13854
|
+
if (doc.getIn(["ship", "toolchains", name, "harness"]) !== "claude") continue;
|
|
13855
|
+
if (doc.getIn(["ship", "toolchains", name, "auth"]) !== void 0) continue;
|
|
13856
|
+
doc.setIn(["ship", "toolchains", name, "auth"], "workspace");
|
|
13857
|
+
wrote.push(`ship.toolchains.${name}.auth`);
|
|
13858
|
+
}
|
|
13859
|
+
return wrote;
|
|
13860
|
+
}
|
|
13730
13861
|
function envKey(host) {
|
|
13731
13862
|
return host.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
13732
13863
|
}
|
|
@@ -13778,6 +13909,7 @@ function provisionOptionsFrom(argv2, env) {
|
|
|
13778
13909
|
relayHost: one("relay-host"),
|
|
13779
13910
|
maxConcurrentAgents: Number.isInteger(Number(one("max-concurrent-agents"))) && Number(one("max-concurrent-agents")) > 0 ? Number(one("max-concurrent-agents")) : void 0,
|
|
13780
13911
|
agentBin: one("agent-bin"),
|
|
13912
|
+
runner: one("runner") === "host" || one("runner") === "sandbox" ? one("runner") : void 0,
|
|
13781
13913
|
extraPath: one("extra-path"),
|
|
13782
13914
|
baseUrl: one("base-url"),
|
|
13783
13915
|
forgeHosts,
|
|
@@ -13798,7 +13930,7 @@ function realProvisionDeps(crewHome) {
|
|
|
13798
13930
|
};
|
|
13799
13931
|
const configFile = () => {
|
|
13800
13932
|
const f = findConfigFile(crewHome);
|
|
13801
|
-
return
|
|
13933
|
+
return existsSync29(f) ? f : null;
|
|
13802
13934
|
};
|
|
13803
13935
|
return {
|
|
13804
13936
|
crew: spawnCrew,
|
|
@@ -13806,13 +13938,13 @@ function realProvisionDeps(crewHome) {
|
|
|
13806
13938
|
enableRoute(route2) {
|
|
13807
13939
|
const file = configFile();
|
|
13808
13940
|
if (!file) return false;
|
|
13809
|
-
const text2 =
|
|
13941
|
+
const text2 = readFileSync28(file, "utf8");
|
|
13810
13942
|
const doc = parseDocument3(text2);
|
|
13811
13943
|
const routes = doc.get("routes");
|
|
13812
13944
|
const entry = routes?.items?.find((r) => r.get("route") === route2);
|
|
13813
13945
|
if (!entry) throw new Error(`route ${route2} is not in ${file}`);
|
|
13814
13946
|
if (entry.get("enabled") === true) return false;
|
|
13815
|
-
|
|
13947
|
+
writeFileSync22(file, planEnable(text2, route2));
|
|
13816
13948
|
return true;
|
|
13817
13949
|
},
|
|
13818
13950
|
installState() {
|
|
@@ -13832,14 +13964,14 @@ function realProvisionDeps(crewHome) {
|
|
|
13832
13964
|
continue;
|
|
13833
13965
|
}
|
|
13834
13966
|
for (const p of plan.unitPaths) {
|
|
13835
|
-
if (!
|
|
13967
|
+
if (!existsSync29(p) || readFileSync28(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
|
|
13836
13968
|
}
|
|
13837
13969
|
}
|
|
13838
13970
|
const label = labelFor(crewHome, "run");
|
|
13839
13971
|
let running = false;
|
|
13840
13972
|
let lingering = true;
|
|
13841
13973
|
if (mechanism === "systemd") {
|
|
13842
|
-
const system = ctx.root ||
|
|
13974
|
+
const system = ctx.root || existsSync29(systemUnitPathFor(label));
|
|
13843
13975
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
13844
13976
|
running = spawnSync7(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
|
|
13845
13977
|
if (!system) {
|
|
@@ -13856,12 +13988,20 @@ function realProvisionDeps(crewHome) {
|
|
|
13856
13988
|
setShipSetting(key, value2) {
|
|
13857
13989
|
const file = configFile();
|
|
13858
13990
|
if (!file) return false;
|
|
13859
|
-
const doc = parseDocument3(
|
|
13991
|
+
const doc = parseDocument3(readFileSync28(file, "utf8"));
|
|
13860
13992
|
if (doc.getIn(["ship", key]) === value2) return false;
|
|
13861
13993
|
doc.setIn(["ship", key], value2);
|
|
13862
|
-
|
|
13994
|
+
writeFileSync22(file, doc.toString());
|
|
13863
13995
|
return true;
|
|
13864
13996
|
},
|
|
13997
|
+
applyManagedDefaults() {
|
|
13998
|
+
const file = configFile();
|
|
13999
|
+
if (!file) return [];
|
|
14000
|
+
const doc = parseDocument3(readFileSync28(file, "utf8"));
|
|
14001
|
+
const wrote = applyManagedDefaultsToDoc(doc);
|
|
14002
|
+
if (wrote.length) writeFileSync22(file, doc.toString());
|
|
14003
|
+
return wrote;
|
|
14004
|
+
},
|
|
13865
14005
|
storedToken(host) {
|
|
13866
14006
|
const res = spawnCrew(["credential", "get"], { input: `protocol=https
|
|
13867
14007
|
host=${host}
|
|
@@ -13876,9 +14016,9 @@ host=${host}
|
|
|
13876
14016
|
|
|
13877
14017
|
// src/tunnel.ts
|
|
13878
14018
|
import { spawn as spawn6 } from "child_process";
|
|
13879
|
-
import { existsSync as
|
|
14019
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync21, readFileSync as readFileSync29, writeFileSync as writeFileSync23, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
|
|
13880
14020
|
import { randomInt } from "crypto";
|
|
13881
|
-
import { join as
|
|
14021
|
+
import { join as join34 } from "path";
|
|
13882
14022
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
13883
14023
|
var defaultSpawn = (cmd, args) => spawn6(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
13884
14024
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -13950,21 +14090,21 @@ function startTunnel(opts) {
|
|
|
13950
14090
|
};
|
|
13951
14091
|
}
|
|
13952
14092
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
13953
|
-
return
|
|
14093
|
+
return join34(stateDir, "passengers", `${workspaceId}.json`);
|
|
13954
14094
|
}
|
|
13955
14095
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
13956
14096
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
13957
|
-
if (!
|
|
14097
|
+
if (!existsSync30(path)) return void 0;
|
|
13958
14098
|
try {
|
|
13959
|
-
return JSON.parse(
|
|
14099
|
+
return JSON.parse(readFileSync29(path, "utf8"));
|
|
13960
14100
|
} catch {
|
|
13961
14101
|
return void 0;
|
|
13962
14102
|
}
|
|
13963
14103
|
}
|
|
13964
14104
|
function writePersistedTunnel(stateDir, state) {
|
|
13965
14105
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
13966
|
-
|
|
13967
|
-
|
|
14106
|
+
mkdirSync21(join34(stateDir, "passengers"), { recursive: true });
|
|
14107
|
+
writeFileSync23(path, `${JSON.stringify(state, null, 2)}
|
|
13968
14108
|
`);
|
|
13969
14109
|
}
|
|
13970
14110
|
function updatePersistedTunnel(stateDir, workspaceId, patch2) {
|
|
@@ -14066,8 +14206,8 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
14066
14206
|
}
|
|
14067
14207
|
}
|
|
14068
14208
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
14069
|
-
const dir =
|
|
14070
|
-
if (!
|
|
14209
|
+
const dir = join34(stateDir, "passengers");
|
|
14210
|
+
if (!existsSync30(dir)) return [];
|
|
14071
14211
|
return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
14072
14212
|
}
|
|
14073
14213
|
|
|
@@ -14092,7 +14232,7 @@ function realPoolMemberDeps(o) {
|
|
|
14092
14232
|
crewVersion: crewVersion(o.crewHome),
|
|
14093
14233
|
hostname: hostname3()
|
|
14094
14234
|
},
|
|
14095
|
-
hasShipConfig: () =>
|
|
14235
|
+
hasShipConfig: () => existsSync31(findConfigFile(o.crewHome)),
|
|
14096
14236
|
async provision(lease, instanceClass, shipKind) {
|
|
14097
14237
|
const hosts = lease.forges.map((f) => f.host.toLowerCase());
|
|
14098
14238
|
const lines2 = [];
|
|
@@ -14968,8 +15108,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
|
|
|
14968
15108
|
}
|
|
14969
15109
|
|
|
14970
15110
|
// src/poll.ts
|
|
14971
|
-
import { writeFileSync as
|
|
14972
|
-
import { join as
|
|
15111
|
+
import { writeFileSync as writeFileSync24 } from "fs";
|
|
15112
|
+
import { join as join35 } from "path";
|
|
14973
15113
|
|
|
14974
15114
|
// src/stalled.ts
|
|
14975
15115
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -15276,7 +15416,7 @@ async function decideCycle(o) {
|
|
|
15276
15416
|
}
|
|
15277
15417
|
return decision;
|
|
15278
15418
|
}
|
|
15279
|
-
var digestPath = (stateDir, route2, role, runId) =>
|
|
15419
|
+
var digestPath = (stateDir, route2, role, runId) => join35(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
15280
15420
|
function otherRunsRemoved(tickets, own) {
|
|
15281
15421
|
if (!own) return tickets;
|
|
15282
15422
|
return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
|
|
@@ -15354,7 +15494,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
|
|
|
15354
15494
|
|
|
15355
15495
|
${body}` : body;
|
|
15356
15496
|
const path = digestPath(stateDir, routeSlug(o.route.route), role, runId);
|
|
15357
|
-
|
|
15497
|
+
writeFileSync24(path, text2);
|
|
15358
15498
|
o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text2)} bytes)`, {
|
|
15359
15499
|
data: { role, bytes: Buffer.byteLength(text2) }
|
|
15360
15500
|
});
|
|
@@ -15462,8 +15602,8 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
|
|
|
15462
15602
|
}
|
|
15463
15603
|
|
|
15464
15604
|
// src/repo-refresh.ts
|
|
15465
|
-
import { existsSync as
|
|
15466
|
-
import { dirname as
|
|
15605
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync22, readFileSync as readFileSync30, writeFileSync as writeFileSync25 } from "fs";
|
|
15606
|
+
import { dirname as dirname17 } from "path";
|
|
15467
15607
|
var REFRESH_COOLDOWN_MS = 2 * 6e4;
|
|
15468
15608
|
var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
|
|
15469
15609
|
var lastAsked = /* @__PURE__ */ new Map();
|
|
@@ -15492,11 +15632,11 @@ function applyRepoRows(resolved, rows) {
|
|
|
15492
15632
|
}
|
|
15493
15633
|
function persistRepoRows(stateDir, routeName, rows) {
|
|
15494
15634
|
const path = resolvedPathFor(stateDir, routeName);
|
|
15495
|
-
if (!
|
|
15496
|
-
const raw = JSON.parse(
|
|
15635
|
+
if (!existsSync32(path)) return;
|
|
15636
|
+
const raw = JSON.parse(readFileSync30(path, "utf8"));
|
|
15497
15637
|
applyRepoRows(raw, rows);
|
|
15498
|
-
|
|
15499
|
-
|
|
15638
|
+
mkdirSync22(dirname17(path), { recursive: true });
|
|
15639
|
+
writeFileSync25(path, `${JSON.stringify(raw, null, 2)}
|
|
15500
15640
|
`);
|
|
15501
15641
|
}
|
|
15502
15642
|
async function refreshUnknownRepos(o) {
|
|
@@ -15946,13 +16086,13 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
|
|
|
15946
16086
|
var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
|
|
15947
16087
|
|
|
15948
16088
|
// src/connect-wizard.ts
|
|
15949
|
-
import { delimiter, dirname as
|
|
15950
|
-
import { accessSync as accessSync3, constants as constants3, existsSync as
|
|
16089
|
+
import { delimiter, dirname as dirname18, join as join36 } from "path";
|
|
16090
|
+
import { accessSync as accessSync3, constants as constants3, existsSync as existsSync33, mkdirSync as mkdirSync23, writeFileSync as writeFileSync26 } from "fs";
|
|
15951
16091
|
import { stringify as stringify3 } from "yaml";
|
|
15952
16092
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
15953
16093
|
function shouldRunWizard(opts) {
|
|
15954
16094
|
if (!opts.isTTY) return false;
|
|
15955
|
-
const exists = opts.configExists ??
|
|
16095
|
+
const exists = opts.configExists ?? existsSync33;
|
|
15956
16096
|
return !configSearchPath(opts.crewHome).some((p) => exists(p));
|
|
15957
16097
|
}
|
|
15958
16098
|
function firstRunConfigPath() {
|
|
@@ -15969,7 +16109,7 @@ function defaultIsExecutable(p) {
|
|
|
15969
16109
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
15970
16110
|
for (const dir of pathEnv.split(delimiter)) {
|
|
15971
16111
|
if (!dir) continue;
|
|
15972
|
-
const candidate =
|
|
16112
|
+
const candidate = join36(dir, name);
|
|
15973
16113
|
if (isExecutable(candidate)) return candidate;
|
|
15974
16114
|
}
|
|
15975
16115
|
return void 0;
|
|
@@ -15995,9 +16135,9 @@ async function runWizard(p, env) {
|
|
|
15995
16135
|
if (entered && isExec(entered)) agentBin = entered;
|
|
15996
16136
|
else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
|
|
15997
16137
|
}
|
|
15998
|
-
const nodeDir =
|
|
16138
|
+
const nodeDir = dirname18(env.nodePath);
|
|
15999
16139
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
16000
|
-
const baseDirs = [nodeDir, pnpmPath ?
|
|
16140
|
+
const baseDirs = [nodeDir, pnpmPath ? dirname18(pnpmPath) : void 0];
|
|
16001
16141
|
p.say(
|
|
16002
16142
|
`
|
|
16003
16143
|
Host Passengers lets this machine run the agent sessions that other people's
|
|
@@ -16008,7 +16148,7 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
|
|
|
16008
16148
|
p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
|
|
16009
16149
|
}
|
|
16010
16150
|
const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
16011
|
-
const extraPath = deriveExtraPath([...baseDirs, dockerPath ?
|
|
16151
|
+
const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname18(dockerPath) : void 0]);
|
|
16012
16152
|
if (extraPath) {
|
|
16013
16153
|
p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
|
|
16014
16154
|
}
|
|
@@ -16030,9 +16170,9 @@ routes:
|
|
|
16030
16170
|
${routeBlock}`;
|
|
16031
16171
|
}
|
|
16032
16172
|
function writeNewConfig(path, text2) {
|
|
16033
|
-
|
|
16173
|
+
mkdirSync23(dirname18(path), { recursive: true });
|
|
16034
16174
|
try {
|
|
16035
|
-
|
|
16175
|
+
writeFileSync26(path, text2, { mode: 384, flag: "wx" });
|
|
16036
16176
|
return true;
|
|
16037
16177
|
} catch (e) {
|
|
16038
16178
|
if (e.code === "EEXIST") return false;
|
|
@@ -16066,7 +16206,7 @@ Wrote ${o.configPath}.
|
|
|
16066
16206
|
// src/connect-json.ts
|
|
16067
16207
|
import { accessSync as accessSync4, constants as constants4 } from "fs";
|
|
16068
16208
|
import { homedir as homedir10 } from "os";
|
|
16069
|
-
import { dirname as
|
|
16209
|
+
import { dirname as dirname19, join as join37 } from "path";
|
|
16070
16210
|
import { delimiter as delimiter2 } from "path";
|
|
16071
16211
|
var CHOICE_EXIT_CODE = 3;
|
|
16072
16212
|
function createEmitter(json2, write) {
|
|
@@ -16105,12 +16245,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
|
|
|
16105
16245
|
const dirs = [
|
|
16106
16246
|
...pathEnv.split(delimiter2),
|
|
16107
16247
|
...(extraPath ?? "").split(delimiter2),
|
|
16108
|
-
|
|
16109
|
-
|
|
16248
|
+
join37(home, ".local/bin"),
|
|
16249
|
+
join37(home, ".claude/local"),
|
|
16110
16250
|
"/opt/homebrew/bin",
|
|
16111
16251
|
"/usr/local/bin",
|
|
16112
|
-
|
|
16113
|
-
|
|
16252
|
+
join37(home, ".npm-global/bin"),
|
|
16253
|
+
join37(home, ".volta/bin")
|
|
16114
16254
|
];
|
|
16115
16255
|
return [...new Set(dirs.filter(Boolean))];
|
|
16116
16256
|
}
|
|
@@ -16132,7 +16272,7 @@ function flagWizardAnswers(f, env) {
|
|
|
16132
16272
|
const agentBin = f.agentBin ?? found?.bin;
|
|
16133
16273
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
16134
16274
|
const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
16135
|
-
const extraPath = f.extraPath ?? deriveExtraPath([
|
|
16275
|
+
const extraPath = f.extraPath ?? deriveExtraPath([dirname19(env.nodePath), pnpmPath ? dirname19(pnpmPath) : void 0, dockerPath ? dirname19(dockerPath) : void 0]);
|
|
16136
16276
|
return {
|
|
16137
16277
|
answers: {
|
|
16138
16278
|
name: f.shipName?.trim() || env.hostname,
|
|
@@ -16146,8 +16286,8 @@ function flagWizardAnswers(f, env) {
|
|
|
16146
16286
|
}
|
|
16147
16287
|
|
|
16148
16288
|
// src/release-run.ts
|
|
16149
|
-
import { readFileSync as
|
|
16150
|
-
import { dirname as
|
|
16289
|
+
import { readFileSync as readFileSync31, writeFileSync as writeFileSync27, existsSync as existsSync34, copyFileSync, mkdirSync as mkdirSync24, mkdtempSync as mkdtempSync3, rmSync as rmSync17 } from "fs";
|
|
16290
|
+
import { dirname as dirname20, join as join38 } from "path";
|
|
16151
16291
|
import { tmpdir as tmpdir5 } from "os";
|
|
16152
16292
|
|
|
16153
16293
|
// src/release.ts
|
|
@@ -16366,7 +16506,7 @@ function decideRelease(cwd, tickets, contract, opts = {}) {
|
|
|
16366
16506
|
const block = opts.guard === false ? null : checkGuards(cwd, base);
|
|
16367
16507
|
const lastTag = latestReleaseTag(cwd, pattern, ref);
|
|
16368
16508
|
const lastReleased = lastTag ? tagCommit(cwd, lastTag) : null;
|
|
16369
|
-
const head =
|
|
16509
|
+
const head = resolve3(cwd, ref) ?? "";
|
|
16370
16510
|
const merges = block ? [] : planMerge(cwd, tickets, contract, lastReleased, opts.branchFor ?? ((t) => branchForIssue(cwd, t.issue_id)), opts.ref ?? base, opts.blockerInfo, ref);
|
|
16371
16511
|
const unreleasedCommits = lastReleased ? countCommits(cwd, `${lastReleased}..${ref}`) : 0;
|
|
16372
16512
|
const unreleasedWork = lastReleased ? (gitOk(cwd, ["log", "--format=%s", `${lastReleased}..${ref}`]) ?? "").split("\n").filter((l) => l && !RELEASE_COMMIT_SUBJECT.test(l)).length : 0;
|
|
@@ -16546,8 +16686,8 @@ function movedPast(r, t, branch, pushedAs) {
|
|
|
16546
16686
|
if (!r.columns.verifiedSha) return null;
|
|
16547
16687
|
const verified = columnValue(t, r.contract.columns.verifiedSha);
|
|
16548
16688
|
if (!verified) return null;
|
|
16549
|
-
const head =
|
|
16550
|
-
const remoteHead =
|
|
16689
|
+
const head = resolve3(r.cwd, branch);
|
|
16690
|
+
const remoteHead = resolve3(r.cwd, `${r.repo.branch.remote}/${pushedAs}`);
|
|
16551
16691
|
if (remoteHead && !isAncestor(r.cwd, remoteHead, verified) && remoteHead !== head) {
|
|
16552
16692
|
return { ticket: t, verified, head: remoteHead, branch: pushedAs };
|
|
16553
16693
|
}
|
|
@@ -16570,7 +16710,7 @@ async function runReview(r, verified, reviewing) {
|
|
|
16570
16710
|
out.requeues.push(moved);
|
|
16571
16711
|
continue;
|
|
16572
16712
|
}
|
|
16573
|
-
const head =
|
|
16713
|
+
const head = resolve3(r.cwd, branch);
|
|
16574
16714
|
if (!head) continue;
|
|
16575
16715
|
if (r.dryRun) {
|
|
16576
16716
|
r.emit.emit(`would push ${branch} to ${remote}/${pushedAs} for review`, { ticket: ticket.issue_id });
|
|
@@ -16888,13 +17028,13 @@ async function bump(o, size) {
|
|
|
16888
17028
|
}
|
|
16889
17029
|
return printed;
|
|
16890
17030
|
}
|
|
16891
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
17031
|
+
const files = o.repo.release.versionFiles.map((f) => join38(o.cwd, f));
|
|
16892
17032
|
const first = files[0];
|
|
16893
|
-
if (!
|
|
17033
|
+
if (!existsSync34(first)) {
|
|
16894
17034
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
16895
17035
|
return null;
|
|
16896
17036
|
}
|
|
16897
|
-
const current = JSON.parse(
|
|
17037
|
+
const current = JSON.parse(readFileSync31(first, "utf8")).version;
|
|
16898
17038
|
if (!current) {
|
|
16899
17039
|
o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
|
|
16900
17040
|
return null;
|
|
@@ -16907,27 +17047,27 @@ async function bump(o, size) {
|
|
|
16907
17047
|
return version;
|
|
16908
17048
|
}
|
|
16909
17049
|
for (const f of files) {
|
|
16910
|
-
const src =
|
|
17050
|
+
const src = readFileSync31(f, "utf8");
|
|
16911
17051
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
16912
17052
|
if (out === src) {
|
|
16913
17053
|
o.emit.error(`no version field in ${f}`);
|
|
16914
17054
|
return null;
|
|
16915
17055
|
}
|
|
16916
|
-
|
|
17056
|
+
writeFileSync27(f, out);
|
|
16917
17057
|
}
|
|
16918
17058
|
return version;
|
|
16919
17059
|
}
|
|
16920
17060
|
function writeChangelog(o, version, entries) {
|
|
16921
17061
|
const file = o.repo.release.changelog;
|
|
16922
17062
|
if (!file || entries.length === 0) return;
|
|
16923
|
-
const path =
|
|
17063
|
+
const path = join38(o.cwd, file);
|
|
16924
17064
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
16925
17065
|
if (o.dryRun) {
|
|
16926
17066
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
16927
17067
|
return;
|
|
16928
17068
|
}
|
|
16929
|
-
const existing =
|
|
16930
|
-
|
|
17069
|
+
const existing = existsSync34(path) ? readFileSync31(path, "utf8") : "# Changelog\n";
|
|
17070
|
+
writeFileSync27(path, insertChangelogSection(existing, section));
|
|
16931
17071
|
}
|
|
16932
17072
|
async function confirm(o, expected) {
|
|
16933
17073
|
if (!o.repo.hooks.released) return void 0;
|
|
@@ -17020,8 +17160,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
|
|
|
17020
17160
|
function openReleaseCheckout(o) {
|
|
17021
17161
|
const { remote, base } = o.repo.branch;
|
|
17022
17162
|
const branch = `crew/release-${Date.now()}`;
|
|
17023
|
-
const root = mkdtempSync3(
|
|
17024
|
-
const dir =
|
|
17163
|
+
const root = mkdtempSync3(join38(tmpdir5(), "crew-release-"));
|
|
17164
|
+
const dir = join38(root, "checkout");
|
|
17025
17165
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
17026
17166
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
17027
17167
|
try {
|
|
@@ -17031,10 +17171,10 @@ function openReleaseCheckout(o) {
|
|
|
17031
17171
|
throw e;
|
|
17032
17172
|
}
|
|
17033
17173
|
for (const f of o.repo.worktrees.copy) {
|
|
17034
|
-
const src =
|
|
17035
|
-
if (!
|
|
17036
|
-
|
|
17037
|
-
copyFileSync(src,
|
|
17174
|
+
const src = join38(o.cwd, f);
|
|
17175
|
+
if (!existsSync34(src)) continue;
|
|
17176
|
+
mkdirSync24(dirname20(join38(dir, f)), { recursive: true });
|
|
17177
|
+
copyFileSync(src, join38(dir, f));
|
|
17038
17178
|
}
|
|
17039
17179
|
return { dir, branch, close };
|
|
17040
17180
|
}
|
|
@@ -17457,7 +17597,7 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
|
|
|
17457
17597
|
branches: merged.filter((c) => c.branch).map((c) => ({
|
|
17458
17598
|
key: c.ticket.issue_tag ?? c.ticket.issue_id,
|
|
17459
17599
|
branch: c.branch,
|
|
17460
|
-
tip:
|
|
17600
|
+
tip: resolve3(o.cwd, c.branch) ?? ""
|
|
17461
17601
|
}))
|
|
17462
17602
|
};
|
|
17463
17603
|
const hookFailure2 = {
|
|
@@ -17520,7 +17660,7 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
|
|
|
17520
17660
|
if (releaseBranch && !o.dryRun) {
|
|
17521
17661
|
const { remote, base } = o.repo.branch;
|
|
17522
17662
|
if (fetchRemote(o.cwd, remote) && remoteBranchExists(o.cwd, remote, base)) {
|
|
17523
|
-
const moved =
|
|
17663
|
+
const moved = resolve3(o.cwd, `${remote}/${base}`);
|
|
17524
17664
|
if (moved && !isAncestor(o.cwd, moved, "HEAD")) {
|
|
17525
17665
|
o.emit.warn(`${remote}/${base} moved during the release \u2014 discarding it; the tickets stay verified and the next cycle retries`);
|
|
17526
17666
|
return { merged, conflicts, unbuildable, version, deployed: false, decision, stopped: "base branch moved during release" };
|
|
@@ -18013,7 +18153,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
|
|
|
18013
18153
|
}
|
|
18014
18154
|
|
|
18015
18155
|
// src/watch.ts
|
|
18016
|
-
import { watch as watchFile, existsSync as
|
|
18156
|
+
import { watch as watchFile, existsSync as existsSync35, statSync as statSync11, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
|
|
18017
18157
|
var E = "\x1B[";
|
|
18018
18158
|
var dim = (s) => `${E}2m${s}${E}0m`;
|
|
18019
18159
|
var bold = (s) => `${E}1m${s}${E}0m`;
|
|
@@ -18114,8 +18254,8 @@ function renderHeaders(states, now = Date.now()) {
|
|
|
18114
18254
|
return states.map((s) => renderHeader(s, now)).join("");
|
|
18115
18255
|
}
|
|
18116
18256
|
function readFrom(file, from) {
|
|
18117
|
-
if (!
|
|
18118
|
-
const size =
|
|
18257
|
+
if (!existsSync35(file)) return { events: [], offset: from };
|
|
18258
|
+
const size = statSync11(file).size;
|
|
18119
18259
|
if (size < from) return readFrom(file, 0);
|
|
18120
18260
|
if (size === from) return { events: [], offset: from };
|
|
18121
18261
|
const fd = openSync4(file, "r");
|
|
@@ -18357,8 +18497,8 @@ function nextRoles(decision, ran) {
|
|
|
18357
18497
|
}
|
|
18358
18498
|
|
|
18359
18499
|
// src/ship-name.ts
|
|
18360
|
-
import { existsSync as
|
|
18361
|
-
import { dirname as
|
|
18500
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync25, readFileSync as readFileSync32, renameSync as renameSync5, writeFileSync as writeFileSync28 } from "fs";
|
|
18501
|
+
import { dirname as dirname21 } from "path";
|
|
18362
18502
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
18363
18503
|
var ShipNameError = class extends Error {
|
|
18364
18504
|
};
|
|
@@ -18419,34 +18559,34 @@ async function syncRouteName(i) {
|
|
|
18419
18559
|
return { status: "ok", ...base, action, name: i.local };
|
|
18420
18560
|
}
|
|
18421
18561
|
function readSyncedName(resolvedFile) {
|
|
18422
|
-
if (!
|
|
18562
|
+
if (!existsSync36(resolvedFile)) return void 0;
|
|
18423
18563
|
try {
|
|
18424
|
-
const v = JSON.parse(
|
|
18564
|
+
const v = JSON.parse(readFileSync32(resolvedFile, "utf8")).shipName;
|
|
18425
18565
|
return typeof v === "string" ? v : void 0;
|
|
18426
18566
|
} catch {
|
|
18427
18567
|
return void 0;
|
|
18428
18568
|
}
|
|
18429
18569
|
}
|
|
18430
18570
|
function writeSyncedName(resolvedFile, name) {
|
|
18431
|
-
if (!
|
|
18571
|
+
if (!existsSync36(resolvedFile)) return;
|
|
18432
18572
|
let raw;
|
|
18433
18573
|
try {
|
|
18434
|
-
raw = JSON.parse(
|
|
18574
|
+
raw = JSON.parse(readFileSync32(resolvedFile, "utf8"));
|
|
18435
18575
|
} catch {
|
|
18436
18576
|
return;
|
|
18437
18577
|
}
|
|
18438
18578
|
if (raw.shipName === name) return;
|
|
18439
18579
|
raw.shipName = name;
|
|
18440
|
-
|
|
18580
|
+
mkdirSync25(dirname21(resolvedFile), { recursive: true });
|
|
18441
18581
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
18442
|
-
|
|
18582
|
+
writeFileSync28(tmp, `${JSON.stringify(raw, null, 2)}
|
|
18443
18583
|
`);
|
|
18444
18584
|
renameSync5(tmp, resolvedFile);
|
|
18445
18585
|
}
|
|
18446
18586
|
async function reconcileShipName(i) {
|
|
18447
18587
|
let local = i.ship.name;
|
|
18448
18588
|
try {
|
|
18449
|
-
local = shipNameOfYaml(
|
|
18589
|
+
local = shipNameOfYaml(readFileSync32(i.configFile, "utf8")) ?? local;
|
|
18450
18590
|
} catch {
|
|
18451
18591
|
}
|
|
18452
18592
|
const resolvedFile = resolvedPathFor(i.stateDir, i.route);
|
|
@@ -18470,8 +18610,8 @@ async function reconcileShipName(i) {
|
|
|
18470
18610
|
}
|
|
18471
18611
|
if (result.status !== "ok") return result;
|
|
18472
18612
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
18473
|
-
const text2 =
|
|
18474
|
-
|
|
18613
|
+
const text2 = readFileSync32(i.configFile, "utf8");
|
|
18614
|
+
writeFileSync28(i.configFile, setShipNameInYaml(text2, result.name));
|
|
18475
18615
|
i.ship.name = result.name;
|
|
18476
18616
|
if (result.action === "row-wins") {
|
|
18477
18617
|
i.emit?.warn(
|
|
@@ -19130,8 +19270,8 @@ function describeCapability(i) {
|
|
|
19130
19270
|
}
|
|
19131
19271
|
|
|
19132
19272
|
// src/agents-sync-run.ts
|
|
19133
|
-
import { existsSync as
|
|
19134
|
-
import { dirname as
|
|
19273
|
+
import { existsSync as existsSync37, mkdirSync as mkdirSync26, readFileSync as readFileSync33, writeFileSync as writeFileSync29 } from "fs";
|
|
19274
|
+
import { dirname as dirname22 } from "path";
|
|
19135
19275
|
async function runAgentsSync(target, o) {
|
|
19136
19276
|
const dryRun = o.dryRun ?? false;
|
|
19137
19277
|
const force = o.force ?? false;
|
|
@@ -19164,12 +19304,12 @@ async function runAgentsSync(target, o) {
|
|
|
19164
19304
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
19165
19305
|
if (!dryRun) {
|
|
19166
19306
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
19167
|
-
const raw =
|
|
19307
|
+
const raw = existsSync37(resolvedPath) ? JSON.parse(readFileSync33(resolvedPath, "utf8")) : {};
|
|
19168
19308
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
19169
19309
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
19170
19310
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
19171
|
-
|
|
19172
|
-
|
|
19311
|
+
mkdirSync26(dirname22(resolvedPath), { recursive: true });
|
|
19312
|
+
writeFileSync29(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
19173
19313
|
`);
|
|
19174
19314
|
if (target.resolved) {
|
|
19175
19315
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -19273,7 +19413,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
19273
19413
|
|
|
19274
19414
|
// src/repos-check.ts
|
|
19275
19415
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
19276
|
-
import { readFileSync as
|
|
19416
|
+
import { readFileSync as readFileSync34 } from "fs";
|
|
19277
19417
|
import { basename as basename11 } from "path";
|
|
19278
19418
|
var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
|
|
19279
19419
|
function checkHookSyntax(script, shell) {
|
|
@@ -19314,7 +19454,7 @@ function checkRepoSpec(dir, opts = {}) {
|
|
|
19314
19454
|
}
|
|
19315
19455
|
let parsed;
|
|
19316
19456
|
try {
|
|
19317
|
-
parsed = parseRepoConfig(
|
|
19457
|
+
parsed = parseRepoConfig(readFileSync34(file, "utf8"), file);
|
|
19318
19458
|
} catch (e) {
|
|
19319
19459
|
if (!(e instanceof RepoConfigError)) throw e;
|
|
19320
19460
|
return { ...empty, file, error: e.message, exitCode: 1 };
|
|
@@ -19371,7 +19511,7 @@ function renderRepoCheck(r) {
|
|
|
19371
19511
|
}
|
|
19372
19512
|
|
|
19373
19513
|
// src/repos-hooks.ts
|
|
19374
|
-
import { isMap as
|
|
19514
|
+
import { isMap as isMap3, parseDocument as parseDocument6, YAMLMap } from "yaml";
|
|
19375
19515
|
function packageManager(f) {
|
|
19376
19516
|
if (f.exists("pnpm-lock.yaml")) return "pnpm";
|
|
19377
19517
|
if (f.exists("yarn.lock")) return "yarn";
|
|
@@ -19517,9 +19657,9 @@ function planRouteHooks(text2, route2, name, hooks, opts = {}) {
|
|
|
19517
19657
|
const m = routeMapOf(doc, route2);
|
|
19518
19658
|
const repos = m.get("repos", true);
|
|
19519
19659
|
let target = m;
|
|
19520
|
-
if (
|
|
19660
|
+
if (isMap3(repos)) {
|
|
19521
19661
|
const entry = repos.get(name, true);
|
|
19522
|
-
if (
|
|
19662
|
+
if (isMap3(entry)) target = entry;
|
|
19523
19663
|
else {
|
|
19524
19664
|
const created = new YAMLMap();
|
|
19525
19665
|
created.set("dir", String(repos.get(name)));
|
|
@@ -19529,7 +19669,7 @@ function planRouteHooks(text2, route2, name, hooks, opts = {}) {
|
|
|
19529
19669
|
}
|
|
19530
19670
|
const current = target.get("hooks", true);
|
|
19531
19671
|
let hooksMap;
|
|
19532
|
-
if (
|
|
19672
|
+
if (isMap3(current)) hooksMap = current;
|
|
19533
19673
|
else {
|
|
19534
19674
|
hooksMap = new YAMLMap();
|
|
19535
19675
|
target.set("hooks", hooksMap);
|
|
@@ -19554,13 +19694,13 @@ function clearRouteHooks(text2, route2, name) {
|
|
|
19554
19694
|
const m = routeMapOf(doc, route2);
|
|
19555
19695
|
const repos = m.get("repos", true);
|
|
19556
19696
|
let target = m;
|
|
19557
|
-
if (
|
|
19697
|
+
if (isMap3(repos)) {
|
|
19558
19698
|
const entry = repos.get(name, true);
|
|
19559
|
-
if (!
|
|
19699
|
+
if (!isMap3(entry)) return { text: text2, removed: [] };
|
|
19560
19700
|
target = entry;
|
|
19561
19701
|
}
|
|
19562
19702
|
const current = target.get("hooks", true);
|
|
19563
|
-
if (!
|
|
19703
|
+
if (!isMap3(current)) return { text: text2, removed: [] };
|
|
19564
19704
|
const removed = current.items.map((i) => String(i.key.value ?? i.key));
|
|
19565
19705
|
target.delete("hooks");
|
|
19566
19706
|
return { text: String(doc), removed };
|
|
@@ -19593,9 +19733,9 @@ function existingRepoConfig(f) {
|
|
|
19593
19733
|
}
|
|
19594
19734
|
|
|
19595
19735
|
// src/repos-hooks-agent.ts
|
|
19596
|
-
import { spawn as spawn7, execFileSync as
|
|
19736
|
+
import { spawn as spawn7, execFileSync as execFileSync10 } from "child_process";
|
|
19597
19737
|
import { realpathSync as realpathSync2 } from "fs";
|
|
19598
|
-
import { isAbsolute as
|
|
19738
|
+
import { isAbsolute as isAbsolute3, relative } from "path";
|
|
19599
19739
|
var AgentSuggestError = class extends Error {
|
|
19600
19740
|
reason;
|
|
19601
19741
|
constructor(reason, message) {
|
|
@@ -19664,7 +19804,7 @@ function parseAgentStream(out, dir) {
|
|
|
19664
19804
|
if (ev?.type === "assistant" && Array.isArray(ev.message?.content)) {
|
|
19665
19805
|
for (const b of ev.message.content) {
|
|
19666
19806
|
const p = b?.type === "tool_use" && b.name === "Read" ? b.input?.file_path : void 0;
|
|
19667
|
-
if (typeof p === "string") files.add(
|
|
19807
|
+
if (typeof p === "string") files.add(isAbsolute3(p) ? relative(base, p) : p);
|
|
19668
19808
|
}
|
|
19669
19809
|
} else if (ev?.type === "result") {
|
|
19670
19810
|
text2 = typeof ev.result === "string" ? ev.result : "";
|
|
@@ -19696,7 +19836,7 @@ function classifyFailure(o) {
|
|
|
19696
19836
|
}
|
|
19697
19837
|
function porcelain(dir) {
|
|
19698
19838
|
try {
|
|
19699
|
-
return
|
|
19839
|
+
return execFileSync10("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
|
|
19700
19840
|
} catch {
|
|
19701
19841
|
return void 0;
|
|
19702
19842
|
}
|
|
@@ -19711,7 +19851,7 @@ async function runAgentSuggest(o) {
|
|
|
19711
19851
|
const before = porcelain(o.dir);
|
|
19712
19852
|
const timeout = AbortSignal.timeout(o.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
19713
19853
|
const signal = o.signal ? AbortSignal.any([o.signal, timeout]) : timeout;
|
|
19714
|
-
const run3 = await new Promise((
|
|
19854
|
+
const run3 = await new Promise((resolve8) => {
|
|
19715
19855
|
let out = "";
|
|
19716
19856
|
let err3 = "";
|
|
19717
19857
|
const child = spawn7(bin, args, { cwd: o.dir, env, detached: true, stdio: ["pipe", "pipe", "pipe"] });
|
|
@@ -19720,7 +19860,7 @@ async function runAgentSuggest(o) {
|
|
|
19720
19860
|
if (settled) return;
|
|
19721
19861
|
settled = true;
|
|
19722
19862
|
signal.removeEventListener("abort", onAbort);
|
|
19723
|
-
|
|
19863
|
+
resolve8(r);
|
|
19724
19864
|
};
|
|
19725
19865
|
const onAbort = () => {
|
|
19726
19866
|
try {
|
|
@@ -19765,7 +19905,7 @@ ${run3.err}` });
|
|
|
19765
19905
|
}
|
|
19766
19906
|
|
|
19767
19907
|
// src/sample-ticket.ts
|
|
19768
|
-
import { basename as basename12, dirname as
|
|
19908
|
+
import { basename as basename12, dirname as dirname23, join as join39 } from "path";
|
|
19769
19909
|
var SAMPLE_REPO = "crew-sample";
|
|
19770
19910
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
19771
19911
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -19788,8 +19928,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
19788
19928
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
19789
19929
|
`;
|
|
19790
19930
|
function sampleDir(existing, reposBase, workspace) {
|
|
19791
|
-
if (existing.length > 0) return
|
|
19792
|
-
return
|
|
19931
|
+
if (existing.length > 0) return join39(dirname23(existing[0]), SAMPLE_REPO);
|
|
19932
|
+
return join39(reposBase, workspace, SAMPLE_REPO);
|
|
19793
19933
|
}
|
|
19794
19934
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
19795
19935
|
function sampleDescription() {
|
|
@@ -19833,8 +19973,8 @@ function planSampleTicket(i) {
|
|
|
19833
19973
|
}
|
|
19834
19974
|
|
|
19835
19975
|
// src/worktree-sweep.ts
|
|
19836
|
-
import { existsSync as
|
|
19837
|
-
import { basename as basename13, dirname as
|
|
19976
|
+
import { existsSync as existsSync38, readFileSync as readFileSync35, readdirSync as readdirSync14 } from "fs";
|
|
19977
|
+
import { basename as basename13, dirname as dirname24, join as join40, resolve as resolve5 } from "path";
|
|
19838
19978
|
function planWorktreeSweep(target, tickets, contract) {
|
|
19839
19979
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
19840
19980
|
const verified = contract.statuses.verified;
|
|
@@ -19898,9 +20038,9 @@ function worktreeTicketNumbers(target) {
|
|
|
19898
20038
|
return [...nums];
|
|
19899
20039
|
}
|
|
19900
20040
|
function siblingWorktreeDirs(target) {
|
|
19901
|
-
const parent =
|
|
20041
|
+
const parent = dirname24(target.dir.replace(/[/\\]+$/, ""));
|
|
19902
20042
|
const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
|
|
19903
|
-
const registered = new Set(worktrees(target.dir).map((w) =>
|
|
20043
|
+
const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
|
|
19904
20044
|
let names;
|
|
19905
20045
|
try {
|
|
19906
20046
|
names = readdirSync14(parent);
|
|
@@ -19911,8 +20051,8 @@ function siblingWorktreeDirs(target) {
|
|
|
19911
20051
|
for (const name of names) {
|
|
19912
20052
|
if (!name.startsWith(stem)) continue;
|
|
19913
20053
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
19914
|
-
const path =
|
|
19915
|
-
if (m && !registered.has(
|
|
20054
|
+
const path = join40(parent, name);
|
|
20055
|
+
if (m && !registered.has(resolve5(path))) out.push({ path, number: m[1] });
|
|
19916
20056
|
}
|
|
19917
20057
|
return out;
|
|
19918
20058
|
}
|
|
@@ -19946,7 +20086,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
19946
20086
|
let removed = 0;
|
|
19947
20087
|
let keptBranches = 0;
|
|
19948
20088
|
for (const a of actions) {
|
|
19949
|
-
if (!dryRun && base && !a.keepBranch &&
|
|
20089
|
+
if (!dryRun && base && !a.keepBranch && existsSync38(a.path) && isHandBackMerge(a.path, base)) {
|
|
19950
20090
|
if (gitOk(a.path, ["merge", "--abort"]) !== null) {
|
|
19951
20091
|
log.emit(
|
|
19952
20092
|
`abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
|
|
@@ -19954,7 +20094,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
19954
20094
|
);
|
|
19955
20095
|
}
|
|
19956
20096
|
}
|
|
19957
|
-
if (!dryRun &&
|
|
20097
|
+
if (!dryRun && existsSync38(a.path)) {
|
|
19958
20098
|
let dirty = false;
|
|
19959
20099
|
try {
|
|
19960
20100
|
dirty = status(a.path).length > 0;
|
|
@@ -19965,7 +20105,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
19965
20105
|
continue;
|
|
19966
20106
|
}
|
|
19967
20107
|
}
|
|
19968
|
-
if (!dryRun && a.requirePushed && a.branch &&
|
|
20108
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync38(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
19969
20109
|
refuse(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
|
|
19970
20110
|
continue;
|
|
19971
20111
|
}
|
|
@@ -20014,7 +20154,7 @@ function isHandBackMerge(path, base) {
|
|
|
20014
20154
|
if (!msgPath) return false;
|
|
20015
20155
|
let first;
|
|
20016
20156
|
try {
|
|
20017
|
-
first =
|
|
20157
|
+
first = readFileSync35(resolve5(path, msgPath), "utf8").split("\n")[0] ?? "";
|
|
20018
20158
|
} catch {
|
|
20019
20159
|
return false;
|
|
20020
20160
|
}
|
|
@@ -20119,16 +20259,16 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
20119
20259
|
}
|
|
20120
20260
|
|
|
20121
20261
|
// src/stream-sweep.ts
|
|
20122
|
-
import { existsSync as
|
|
20123
|
-
import { join as
|
|
20262
|
+
import { existsSync as existsSync39, readdirSync as readdirSync15, rmSync as rmSync18, statSync as statSync12 } from "fs";
|
|
20263
|
+
import { join as join41 } from "path";
|
|
20124
20264
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
20125
20265
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
20126
|
-
const dir =
|
|
20127
|
-
if (!
|
|
20266
|
+
const dir = join41(stateDir, "streams");
|
|
20267
|
+
if (!existsSync39(dir)) return [];
|
|
20128
20268
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
20129
|
-
return readdirSync15(dir).map((name) =>
|
|
20269
|
+
return readdirSync15(dir).map((name) => join41(dir, name)).filter((path) => {
|
|
20130
20270
|
try {
|
|
20131
|
-
return
|
|
20271
|
+
return statSync12(path).mtimeMs < cutoff;
|
|
20132
20272
|
} catch {
|
|
20133
20273
|
return false;
|
|
20134
20274
|
}
|
|
@@ -20152,11 +20292,11 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
20152
20292
|
return removed;
|
|
20153
20293
|
}
|
|
20154
20294
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
20155
|
-
if (!
|
|
20295
|
+
if (!existsSync39(stateDir)) return [];
|
|
20156
20296
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
20157
|
-
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) =>
|
|
20297
|
+
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join41(stateDir, name)).filter((path) => {
|
|
20158
20298
|
try {
|
|
20159
|
-
return
|
|
20299
|
+
return statSync12(path).mtimeMs < cutoff;
|
|
20160
20300
|
} catch {
|
|
20161
20301
|
return false;
|
|
20162
20302
|
}
|
|
@@ -20164,12 +20304,12 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
20164
20304
|
}
|
|
20165
20305
|
|
|
20166
20306
|
// src/cli.ts
|
|
20167
|
-
import { realpathSync as realpathSync3, readFileSync as
|
|
20307
|
+
import { realpathSync as realpathSync3, readFileSync as readFileSync41, existsSync as existsSync46, mkdirSync as mkdirSync33, writeFileSync as writeFileSync34, rmSync as rmSync23, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
|
|
20168
20308
|
import { createInterface } from "readline/promises";
|
|
20169
20309
|
|
|
20170
20310
|
// src/periscope-cmd.ts
|
|
20171
|
-
import { existsSync as
|
|
20172
|
-
import { join as
|
|
20311
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync27, readFileSync as readFileSync36, rmSync as rmSync19, writeFileSync as writeFileSync30 } from "fs";
|
|
20312
|
+
import { join as join42 } from "path";
|
|
20173
20313
|
var PeriscopeError = class extends Error {
|
|
20174
20314
|
};
|
|
20175
20315
|
var LivePeriscopeError = class extends PeriscopeError {
|
|
@@ -20179,22 +20319,22 @@ var LivePeriscopeError = class extends PeriscopeError {
|
|
|
20179
20319
|
this.data = { refused: "live_periscope_exists", ...live };
|
|
20180
20320
|
}
|
|
20181
20321
|
};
|
|
20182
|
-
var priorDir = (stateDir) =>
|
|
20183
|
-
var priorPath = (stateDir, ticketRecordId) =>
|
|
20322
|
+
var priorDir = (stateDir) => join42(stateDir, "periscope-priors");
|
|
20323
|
+
var priorPath = (stateDir, ticketRecordId) => join42(priorDir(stateDir), `${ticketRecordId}.json`);
|
|
20184
20324
|
function removePeriscopePrior(stateDir, ticketRecordId) {
|
|
20185
20325
|
rmSync19(priorPath(stateDir, ticketRecordId), { force: true });
|
|
20186
20326
|
}
|
|
20187
20327
|
function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
20188
20328
|
try {
|
|
20189
|
-
|
|
20190
|
-
|
|
20329
|
+
mkdirSync27(priorDir(stateDir), { recursive: true });
|
|
20330
|
+
writeFileSync30(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
|
|
20191
20331
|
} catch {
|
|
20192
20332
|
}
|
|
20193
20333
|
}
|
|
20194
20334
|
function readPeriscopePrior(stateDir, ticketRecordId) {
|
|
20195
20335
|
try {
|
|
20196
20336
|
const p = priorPath(stateDir, ticketRecordId);
|
|
20197
|
-
return
|
|
20337
|
+
return existsSync40(p) ? JSON.parse(readFileSync36(p, "utf8")) : void 0;
|
|
20198
20338
|
} catch {
|
|
20199
20339
|
return void 0;
|
|
20200
20340
|
}
|
|
@@ -20356,7 +20496,12 @@ ${tail(iso.output)}`);
|
|
|
20356
20496
|
const handoff = await d.runHook("handoff", wt.path, env);
|
|
20357
20497
|
if (handoff && handoff.code !== 0) return abandon(`handoff failed (exit ${handoff.code})
|
|
20358
20498
|
${tail(handoff.output)}`);
|
|
20359
|
-
|
|
20499
|
+
try {
|
|
20500
|
+
d.prepareDropFile(wt.path);
|
|
20501
|
+
} catch (e) {
|
|
20502
|
+
return abandon(`preparing the drop file failed: ${e.message}`);
|
|
20503
|
+
}
|
|
20504
|
+
const preview = await d.runHook("preview", wt.path, { ...env, CREW_PREVIEW_HOST: d.host, ...pointedAtEnv(wt.path) });
|
|
20360
20505
|
if (!preview || preview.code !== 0) {
|
|
20361
20506
|
return abandon(`preview exited ${preview?.code ?? "without running"}
|
|
20362
20507
|
${tail(preview?.output ?? "")}`);
|
|
@@ -20571,7 +20716,8 @@ async function runPeriscopeResume(d, number) {
|
|
|
20571
20716
|
${tail2(iso.output)}`);
|
|
20572
20717
|
if (iso) Object.assign(env, parseEnvLines(iso.output));
|
|
20573
20718
|
}
|
|
20574
|
-
|
|
20719
|
+
d.prepareDropFile?.(row.worktree);
|
|
20720
|
+
const preview = await d.runHook("preview", row.worktree, { ...env, CREW_PREVIEW_HOST: row.host || "localhost", ...pointedAtEnv(row.worktree) });
|
|
20575
20721
|
if (!preview || preview.code !== 0) {
|
|
20576
20722
|
throw new PeriscopeError(`preview exited ${preview?.code ?? "without running"}
|
|
20577
20723
|
${tail2(preview?.output ?? "")}`);
|
|
@@ -20591,14 +20737,19 @@ ${tail2(preview.output)}`);
|
|
|
20591
20737
|
}
|
|
20592
20738
|
|
|
20593
20739
|
// src/periscope-idle.ts
|
|
20594
|
-
import { execFileSync as
|
|
20595
|
-
import { statSync as
|
|
20596
|
-
import { join as
|
|
20740
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
20741
|
+
import { statSync as statSync13 } from "fs";
|
|
20742
|
+
import { join as join43 } from "path";
|
|
20597
20743
|
var MAX_SCANNED_FILES = 2e4;
|
|
20598
20744
|
function newestWorktreeMtime(root) {
|
|
20745
|
+
const a = newestTrackedMtime(root);
|
|
20746
|
+
const b = dropFileMtime(root);
|
|
20747
|
+
return a === null ? b : b === null ? a : Math.max(a, b);
|
|
20748
|
+
}
|
|
20749
|
+
function newestTrackedMtime(root) {
|
|
20599
20750
|
let listing;
|
|
20600
20751
|
try {
|
|
20601
|
-
listing =
|
|
20752
|
+
listing = execFileSync11("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
20602
20753
|
cwd: root,
|
|
20603
20754
|
encoding: "utf8",
|
|
20604
20755
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -20613,7 +20764,7 @@ function newestWorktreeMtime(root) {
|
|
|
20613
20764
|
if (!rel) continue;
|
|
20614
20765
|
if (++scanned > MAX_SCANNED_FILES) break;
|
|
20615
20766
|
try {
|
|
20616
|
-
const m =
|
|
20767
|
+
const m = statSync13(join43(root, rel)).mtimeMs;
|
|
20617
20768
|
if (newest === null || m > newest) newest = m;
|
|
20618
20769
|
} catch {
|
|
20619
20770
|
}
|
|
@@ -20688,21 +20839,21 @@ async function runPeriscopeIdlePass(d) {
|
|
|
20688
20839
|
import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
20689
20840
|
|
|
20690
20841
|
// src/ssh-keys.ts
|
|
20691
|
-
import { execFileSync as
|
|
20692
|
-
import { existsSync as
|
|
20693
|
-
import { dirname as
|
|
20842
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
20843
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync28, readFileSync as readFileSync37, chmodSync as chmodSync3 } from "fs";
|
|
20844
|
+
import { dirname as dirname25, join as join44 } from "path";
|
|
20694
20845
|
var SshKeyError = class extends Error {
|
|
20695
20846
|
};
|
|
20696
20847
|
function shipSshKeyPathFor(stateDir) {
|
|
20697
|
-
return
|
|
20848
|
+
return join44(stateDir, "ssh", "id_ed25519");
|
|
20698
20849
|
}
|
|
20699
20850
|
function ensureShipSshKeypair(stateDir) {
|
|
20700
20851
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
20701
20852
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
20702
|
-
if (!
|
|
20703
|
-
|
|
20853
|
+
if (!existsSync41(privateKeyPath)) {
|
|
20854
|
+
mkdirSync28(dirname25(privateKeyPath), { recursive: true });
|
|
20704
20855
|
try {
|
|
20705
|
-
|
|
20856
|
+
execFileSync12("ssh-keygen", [
|
|
20706
20857
|
"-t",
|
|
20707
20858
|
"ed25519",
|
|
20708
20859
|
"-N",
|
|
@@ -20720,15 +20871,15 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
20720
20871
|
}
|
|
20721
20872
|
chmodSync3(privateKeyPath, 384);
|
|
20722
20873
|
}
|
|
20723
|
-
if (!
|
|
20874
|
+
if (!existsSync41(publicKeyPath)) {
|
|
20724
20875
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
20725
20876
|
}
|
|
20726
|
-
const publicKey =
|
|
20877
|
+
const publicKey = readFileSync37(publicKeyPath, "utf8").trim();
|
|
20727
20878
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
20728
20879
|
}
|
|
20729
20880
|
function sshKeygenAvailable() {
|
|
20730
20881
|
try {
|
|
20731
|
-
|
|
20882
|
+
execFileSync12("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
|
|
20732
20883
|
return true;
|
|
20733
20884
|
} catch (e) {
|
|
20734
20885
|
return e.code !== "ENOENT";
|
|
@@ -20887,12 +21038,12 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
20887
21038
|
import { createServer as createHttpServer } from "http";
|
|
20888
21039
|
import { spawn as spawn8 } from "child_process";
|
|
20889
21040
|
import { randomBytes } from "crypto";
|
|
20890
|
-
import { existsSync as
|
|
20891
|
-
import { join as
|
|
21041
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, writeFileSync as writeFileSync31, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
|
|
21042
|
+
import { join as join45 } from "path";
|
|
20892
21043
|
|
|
20893
21044
|
// src/passenger-mirror.ts
|
|
20894
|
-
import { mkdirSync as
|
|
20895
|
-
import { dirname as
|
|
21045
|
+
import { mkdirSync as mkdirSync29 } from "fs";
|
|
21046
|
+
import { dirname as dirname26 } from "path";
|
|
20896
21047
|
function mirrorRepoOf(spec) {
|
|
20897
21048
|
const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
|
|
20898
21049
|
if (fromOrigin) return fromOrigin;
|
|
@@ -20910,9 +21061,9 @@ async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCa
|
|
|
20910
21061
|
continue;
|
|
20911
21062
|
}
|
|
20912
21063
|
try {
|
|
20913
|
-
const parent =
|
|
20914
|
-
|
|
20915
|
-
|
|
21064
|
+
const parent = dirname26(spec.dir);
|
|
21065
|
+
mkdirSync29(parent, { recursive: true });
|
|
21066
|
+
mkdirSync29(spec.dir, { recursive: true });
|
|
20916
21067
|
const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
|
|
20917
21068
|
out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
|
|
20918
21069
|
} catch (e) {
|
|
@@ -21018,21 +21169,21 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
21018
21169
|
return child;
|
|
21019
21170
|
};
|
|
21020
21171
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
21021
|
-
return
|
|
21172
|
+
return join45(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
21022
21173
|
}
|
|
21023
21174
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
21024
21175
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
21025
|
-
if (!
|
|
21176
|
+
if (!existsSync42(path)) return void 0;
|
|
21026
21177
|
try {
|
|
21027
|
-
return JSON.parse(
|
|
21178
|
+
return JSON.parse(readFileSync38(path, "utf8"));
|
|
21028
21179
|
} catch {
|
|
21029
21180
|
return void 0;
|
|
21030
21181
|
}
|
|
21031
21182
|
}
|
|
21032
21183
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
21033
21184
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
21034
|
-
|
|
21035
|
-
|
|
21185
|
+
mkdirSync30(join45(stateDir, "passengers"), { recursive: true });
|
|
21186
|
+
writeFileSync31(path, `${JSON.stringify(state, null, 2)}
|
|
21036
21187
|
`);
|
|
21037
21188
|
}
|
|
21038
21189
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -21052,8 +21203,8 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
21052
21203
|
}
|
|
21053
21204
|
}
|
|
21054
21205
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
21055
|
-
const dir =
|
|
21056
|
-
if (!
|
|
21206
|
+
const dir = join45(stateDir, "passengers");
|
|
21207
|
+
if (!existsSync42(dir)) return [];
|
|
21057
21208
|
const suffix = "-sync.json";
|
|
21058
21209
|
return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
21059
21210
|
}
|
|
@@ -21141,14 +21292,14 @@ function describeCause(err3) {
|
|
|
21141
21292
|
}
|
|
21142
21293
|
|
|
21143
21294
|
// src/daemon.ts
|
|
21144
|
-
import { existsSync as
|
|
21145
|
-
import { join as
|
|
21295
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync31, readFileSync as readFileSync39, readdirSync as readdirSync17, rmSync as rmSync20, statSync as statSync14, writeFileSync as writeFileSync32 } from "fs";
|
|
21296
|
+
import { join as join46 } from "path";
|
|
21146
21297
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
21147
21298
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
21148
21299
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
21149
21300
|
var ALLOWANCE_PAUSE_MS = 5 * 60 * 1e3;
|
|
21150
21301
|
var RELOAD_WARN_MS = 10 * 60 * 1e3;
|
|
21151
|
-
var defaultSleep = (ms3) => new Promise((
|
|
21302
|
+
var defaultSleep = (ms3) => new Promise((resolve8) => setTimeout(resolve8, ms3));
|
|
21152
21303
|
async function runDaemonLoop(o) {
|
|
21153
21304
|
const floor = o.floorMs ?? DEFAULT_FLOOR_MS;
|
|
21154
21305
|
const ceiling = o.ceilingMs ?? DEFAULT_CEILING_MS;
|
|
@@ -21222,7 +21373,7 @@ async function runDaemonLoop(o) {
|
|
|
21222
21373
|
function latestMtime(path) {
|
|
21223
21374
|
let stat2;
|
|
21224
21375
|
try {
|
|
21225
|
-
stat2 =
|
|
21376
|
+
stat2 = statSync14(path);
|
|
21226
21377
|
} catch {
|
|
21227
21378
|
return 0;
|
|
21228
21379
|
}
|
|
@@ -21230,7 +21381,7 @@ function latestMtime(path) {
|
|
|
21230
21381
|
if (!stat2.isDirectory()) return 0;
|
|
21231
21382
|
let latest = stat2.mtimeMs;
|
|
21232
21383
|
for (const entry of readdirSync17(path, { withFileTypes: true })) {
|
|
21233
|
-
latest = Math.max(latest, latestMtime(
|
|
21384
|
+
latest = Math.max(latest, latestMtime(join46(path, entry.name)));
|
|
21234
21385
|
}
|
|
21235
21386
|
return latest;
|
|
21236
21387
|
}
|
|
@@ -21239,30 +21390,30 @@ function computeTreeSignature(paths) {
|
|
|
21239
21390
|
}
|
|
21240
21391
|
function makeStaleChecker(paths, opts = {}) {
|
|
21241
21392
|
const initial = computeTreeSignature(paths);
|
|
21242
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
21393
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync43(opts.reloadSentinel);
|
|
21243
21394
|
}
|
|
21244
21395
|
function reloadSentinelFor(stateDir) {
|
|
21245
|
-
return
|
|
21396
|
+
return join46(stateDir, "reload-requested");
|
|
21246
21397
|
}
|
|
21247
21398
|
function readReloadRequestedAt(stateDir) {
|
|
21248
21399
|
try {
|
|
21249
|
-
return
|
|
21400
|
+
return statSync14(reloadSentinelFor(stateDir)).mtimeMs;
|
|
21250
21401
|
} catch {
|
|
21251
21402
|
return null;
|
|
21252
21403
|
}
|
|
21253
21404
|
}
|
|
21254
21405
|
function daemonStampFor(stateDir) {
|
|
21255
|
-
return
|
|
21406
|
+
return join46(stateDir, "daemon.started");
|
|
21256
21407
|
}
|
|
21257
21408
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
21258
|
-
|
|
21409
|
+
mkdirSync31(stateDir, { recursive: true });
|
|
21259
21410
|
rmSync20(reloadSentinelFor(stateDir), { force: true });
|
|
21260
|
-
|
|
21411
|
+
writeFileSync32(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
21261
21412
|
`);
|
|
21262
21413
|
}
|
|
21263
21414
|
function readDaemonStarted(stateDir) {
|
|
21264
21415
|
try {
|
|
21265
|
-
const v = JSON.parse(
|
|
21416
|
+
const v = JSON.parse(readFileSync39(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
21266
21417
|
return typeof v === "number" ? v : null;
|
|
21267
21418
|
} catch {
|
|
21268
21419
|
return null;
|
|
@@ -21271,7 +21422,7 @@ function readDaemonStarted(stateDir) {
|
|
|
21271
21422
|
function configChangedAfter(files, startedAt) {
|
|
21272
21423
|
for (const f of files) {
|
|
21273
21424
|
try {
|
|
21274
|
-
if (
|
|
21425
|
+
if (statSync14(f).mtimeMs > startedAt) return f;
|
|
21275
21426
|
} catch {
|
|
21276
21427
|
}
|
|
21277
21428
|
}
|
|
@@ -21434,8 +21585,8 @@ async function runOnePassUnguarded(o) {
|
|
|
21434
21585
|
}
|
|
21435
21586
|
let settle = () => {
|
|
21436
21587
|
};
|
|
21437
|
-
const settled = new Promise((
|
|
21438
|
-
settle =
|
|
21588
|
+
const settled = new Promise((resolve8) => {
|
|
21589
|
+
settle = resolve8;
|
|
21439
21590
|
});
|
|
21440
21591
|
const ready = (tookTicket2) => settle(tookTicket2);
|
|
21441
21592
|
const run3 = spawn11(role, decision, ctx, { ready }).then(() => {
|
|
@@ -21695,7 +21846,7 @@ async function runRoleAgentLocked(role, decision, ctx, hooks, held) {
|
|
|
21695
21846
|
ship.name,
|
|
21696
21847
|
(t) => {
|
|
21697
21848
|
const d = dirForRepo(route2, t.repo_id);
|
|
21698
|
-
return d ? originBranchSha(d, t,
|
|
21849
|
+
return d ? originBranchSha(d, t, resolve3) : null;
|
|
21699
21850
|
}
|
|
21700
21851
|
);
|
|
21701
21852
|
if (!result.ticket) {
|
|
@@ -21828,18 +21979,18 @@ import { createHash as createHash10 } from "crypto";
|
|
|
21828
21979
|
import {
|
|
21829
21980
|
chmodSync as chmodSync4,
|
|
21830
21981
|
closeSync as closeSync5,
|
|
21831
|
-
existsSync as
|
|
21982
|
+
existsSync as existsSync44,
|
|
21832
21983
|
fstatSync,
|
|
21833
21984
|
openSync as openSync5,
|
|
21834
21985
|
readSync as readSync3,
|
|
21835
21986
|
readdirSync as readdirSync18,
|
|
21836
21987
|
rmSync as rmSync21,
|
|
21837
|
-
statSync as
|
|
21988
|
+
statSync as statSync15,
|
|
21838
21989
|
watch
|
|
21839
21990
|
} from "fs";
|
|
21840
21991
|
import { createConnection } from "net";
|
|
21841
21992
|
import { createServer } from "http";
|
|
21842
|
-
import { basename as basename14, dirname as
|
|
21993
|
+
import { basename as basename14, dirname as dirname27, join as join47 } from "path";
|
|
21843
21994
|
import { execFile as execFile3, spawn as spawn10 } from "child_process";
|
|
21844
21995
|
|
|
21845
21996
|
// src/periscope-control.ts
|
|
@@ -21890,7 +22041,7 @@ function lastJsonLine(stdout) {
|
|
|
21890
22041
|
}
|
|
21891
22042
|
return null;
|
|
21892
22043
|
}
|
|
21893
|
-
function
|
|
22044
|
+
function parseLine2(line) {
|
|
21894
22045
|
try {
|
|
21895
22046
|
const v = JSON.parse(line);
|
|
21896
22047
|
return v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
@@ -21918,7 +22069,7 @@ async function execute(h, args, streaming, after) {
|
|
|
21918
22069
|
body: null,
|
|
21919
22070
|
stream: async (write) => {
|
|
21920
22071
|
const r = await h.run(args, (line) => {
|
|
21921
|
-
const ev =
|
|
22072
|
+
const ev = parseLine2(line);
|
|
21922
22073
|
if (ev) write(ev);
|
|
21923
22074
|
});
|
|
21924
22075
|
const f = await finish(r);
|
|
@@ -22033,7 +22184,7 @@ async function handlePeriscopes(h, q2) {
|
|
|
22033
22184
|
], q2.wantsStream, h.mcp ? async (done) => ({ ...done, ...await h.mcp.onUp(picked, done) }) : void 0);
|
|
22034
22185
|
}
|
|
22035
22186
|
function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
22036
|
-
return new Promise((
|
|
22187
|
+
return new Promise((resolve8) => {
|
|
22037
22188
|
const child = spawn9(process.execPath, [process.argv[1] ?? "", ...args], { stdio: ["ignore", "pipe", "pipe"] });
|
|
22038
22189
|
let stdout = "";
|
|
22039
22190
|
let stderr = "";
|
|
@@ -22055,12 +22206,12 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
|
22055
22206
|
const t = setTimeout(() => child.kill("SIGTERM"), timeoutMs);
|
|
22056
22207
|
child.on("error", (e) => {
|
|
22057
22208
|
clearTimeout(t);
|
|
22058
|
-
|
|
22209
|
+
resolve8({ code: 127, stdout, stderr: `${stderr}${e.message}
|
|
22059
22210
|
` });
|
|
22060
22211
|
});
|
|
22061
22212
|
child.on("close", (code) => {
|
|
22062
22213
|
clearTimeout(t);
|
|
22063
|
-
|
|
22214
|
+
resolve8({ code: code ?? 1, stdout, stderr });
|
|
22064
22215
|
});
|
|
22065
22216
|
});
|
|
22066
22217
|
}
|
|
@@ -22068,7 +22219,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
|
22068
22219
|
// src/control-api.ts
|
|
22069
22220
|
var PAUSABLE = ["dev", "design", "qa"];
|
|
22070
22221
|
var CONTROL_SOCKET_NAME = "control.sock";
|
|
22071
|
-
var controlSocketFor = (stateDir) =>
|
|
22222
|
+
var controlSocketFor = (stateDir) => join47(stateDir, CONTROL_SOCKET_NAME);
|
|
22072
22223
|
var MAX_SOCKET_PATH = 100;
|
|
22073
22224
|
var MAX_TAIL_BYTES = 1024 * 1024;
|
|
22074
22225
|
var MAX_WAIT_SECONDS = 30;
|
|
@@ -22155,7 +22306,7 @@ function candidates(live, rotated) {
|
|
|
22155
22306
|
const out = [live];
|
|
22156
22307
|
if (!rotated) return out;
|
|
22157
22308
|
try {
|
|
22158
|
-
for (const n of readdirSync18(
|
|
22309
|
+
for (const n of readdirSync18(dirname27(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join47(dirname27(live), n));
|
|
22159
22310
|
} catch {
|
|
22160
22311
|
}
|
|
22161
22312
|
return out;
|
|
@@ -22178,7 +22329,7 @@ function readRange(path, from, max) {
|
|
|
22178
22329
|
}
|
|
22179
22330
|
}
|
|
22180
22331
|
function lastLines2(path, n) {
|
|
22181
|
-
const st =
|
|
22332
|
+
const st = statSync15(path);
|
|
22182
22333
|
const cursorAt = (off) => encodeCursor({ dev: st.dev, ino: st.ino, off });
|
|
22183
22334
|
if (n <= 0 || st.size === 0) return { lines: [], cursor: cursorAt(st.size), reset: false };
|
|
22184
22335
|
let start = st.size;
|
|
@@ -22199,7 +22350,7 @@ function lastLines2(path, n) {
|
|
|
22199
22350
|
}
|
|
22200
22351
|
function readTail(live, opts) {
|
|
22201
22352
|
const max = opts.maxBytes ?? MAX_TAIL_BYTES;
|
|
22202
|
-
if (!
|
|
22353
|
+
if (!existsSync44(live)) {
|
|
22203
22354
|
return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
|
|
22204
22355
|
}
|
|
22205
22356
|
const cur = opts.after ? decodeCursor(opts.after) : null;
|
|
@@ -22212,7 +22363,7 @@ function readTail(live, opts) {
|
|
|
22212
22363
|
} else {
|
|
22213
22364
|
const found = candidates(live, opts.rotated ?? false).find((p) => {
|
|
22214
22365
|
try {
|
|
22215
|
-
const s =
|
|
22366
|
+
const s = statSync15(p);
|
|
22216
22367
|
return s.ino === cur.ino && s.dev === cur.dev;
|
|
22217
22368
|
} catch {
|
|
22218
22369
|
return false;
|
|
@@ -22223,7 +22374,7 @@ function readTail(live, opts) {
|
|
|
22223
22374
|
off = cur.off;
|
|
22224
22375
|
} else reset = true;
|
|
22225
22376
|
}
|
|
22226
|
-
let size =
|
|
22377
|
+
let size = statSync15(path).size;
|
|
22227
22378
|
if (off > size) {
|
|
22228
22379
|
off = 0;
|
|
22229
22380
|
reset = true;
|
|
@@ -22231,12 +22382,12 @@ function readTail(live, opts) {
|
|
|
22231
22382
|
if (off >= size && path !== live) {
|
|
22232
22383
|
path = live;
|
|
22233
22384
|
off = 0;
|
|
22234
|
-
size =
|
|
22385
|
+
size = statSync15(path).size;
|
|
22235
22386
|
}
|
|
22236
22387
|
const { text: text2 } = readRange(path, off, max);
|
|
22237
22388
|
const cut = text2.lastIndexOf("\n");
|
|
22238
22389
|
const whole = cut < 0 ? "" : text2.slice(0, cut + 1);
|
|
22239
|
-
const st =
|
|
22390
|
+
const st = statSync15(path);
|
|
22240
22391
|
return {
|
|
22241
22392
|
lines: whole.split("\n").filter((l) => l.length),
|
|
22242
22393
|
cursor: encodeCursor({ dev: st.dev, ino: st.ino, off: off + Buffer.byteLength(whole) }),
|
|
@@ -22244,7 +22395,7 @@ function readTail(live, opts) {
|
|
|
22244
22395
|
};
|
|
22245
22396
|
}
|
|
22246
22397
|
function waitForChange(dir, ms3, signal) {
|
|
22247
|
-
return new Promise((
|
|
22398
|
+
return new Promise((resolve8) => {
|
|
22248
22399
|
let w;
|
|
22249
22400
|
const done = () => {
|
|
22250
22401
|
clearTimeout(t);
|
|
@@ -22254,7 +22405,7 @@ function waitForChange(dir, ms3, signal) {
|
|
|
22254
22405
|
w?.close();
|
|
22255
22406
|
} catch {
|
|
22256
22407
|
}
|
|
22257
|
-
|
|
22408
|
+
resolve8();
|
|
22258
22409
|
};
|
|
22259
22410
|
const t = setTimeout(done, ms3);
|
|
22260
22411
|
const poll = setInterval(done, 500);
|
|
@@ -22315,7 +22466,7 @@ async function tailReply(path, q2, rotated, abort) {
|
|
|
22315
22466
|
const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
|
|
22316
22467
|
let r = readTail(path, { after, tail: tail3, rotated });
|
|
22317
22468
|
while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
|
|
22318
|
-
await waitForChange(
|
|
22469
|
+
await waitForChange(dirname27(path), deadline - Date.now(), abort);
|
|
22319
22470
|
r = readTail(path, { after: r.cursor, rotated });
|
|
22320
22471
|
}
|
|
22321
22472
|
return reply2(200, r);
|
|
@@ -22355,8 +22506,8 @@ async function route(deps, method, url, req, abort) {
|
|
|
22355
22506
|
if (p.startsWith("/streams/") && get2) {
|
|
22356
22507
|
const run3 = decodeURIComponent(p.slice("/streams/".length));
|
|
22357
22508
|
if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
|
|
22358
|
-
const file =
|
|
22359
|
-
if (!
|
|
22509
|
+
const file = join47(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
|
|
22510
|
+
if (!existsSync44(file)) return err2(404, "no such run");
|
|
22360
22511
|
return tailReply(file, url.searchParams, false, abort);
|
|
22361
22512
|
}
|
|
22362
22513
|
if ((p === "/pause" || p === "/resume") && post3) {
|
|
@@ -22400,14 +22551,14 @@ function send(res, r) {
|
|
|
22400
22551
|
res.end(body);
|
|
22401
22552
|
}
|
|
22402
22553
|
async function socketIsStale(path) {
|
|
22403
|
-
if (!
|
|
22404
|
-
return new Promise((
|
|
22554
|
+
if (!existsSync44(path)) return false;
|
|
22555
|
+
return new Promise((resolve8) => {
|
|
22405
22556
|
const c = createConnection(path);
|
|
22406
22557
|
c.once("connect", () => {
|
|
22407
22558
|
c.destroy();
|
|
22408
|
-
|
|
22559
|
+
resolve8(false);
|
|
22409
22560
|
});
|
|
22410
|
-
c.once("error", () =>
|
|
22561
|
+
c.once("error", () => resolve8(true));
|
|
22411
22562
|
});
|
|
22412
22563
|
}
|
|
22413
22564
|
async function startControlServer(deps) {
|
|
@@ -22419,7 +22570,7 @@ async function startControlServer(deps) {
|
|
|
22419
22570
|
return null;
|
|
22420
22571
|
}
|
|
22421
22572
|
if (await socketIsStale(path)) rmSync21(path, { force: true });
|
|
22422
|
-
else if (
|
|
22573
|
+
else if (existsSync44(path)) {
|
|
22423
22574
|
log(`control API not started: ${path} is in use by another process`);
|
|
22424
22575
|
return null;
|
|
22425
22576
|
}
|
|
@@ -22449,10 +22600,10 @@ async function startControlServer(deps) {
|
|
|
22449
22600
|
}
|
|
22450
22601
|
})();
|
|
22451
22602
|
});
|
|
22452
|
-
return new Promise((
|
|
22603
|
+
return new Promise((resolve8) => {
|
|
22453
22604
|
server.once("error", (e) => {
|
|
22454
22605
|
log(`control API not started: ${e.message}`);
|
|
22455
|
-
|
|
22606
|
+
resolve8(null);
|
|
22456
22607
|
});
|
|
22457
22608
|
server.listen(path, () => {
|
|
22458
22609
|
try {
|
|
@@ -22460,7 +22611,7 @@ async function startControlServer(deps) {
|
|
|
22460
22611
|
} catch {
|
|
22461
22612
|
}
|
|
22462
22613
|
server.on("error", (e) => log(`control API error: ${e.message}`));
|
|
22463
|
-
|
|
22614
|
+
resolve8({
|
|
22464
22615
|
server,
|
|
22465
22616
|
path,
|
|
22466
22617
|
close: () => new Promise((done) => {
|
|
@@ -22491,13 +22642,13 @@ function schedulerHandler(control, run3 = runDetached) {
|
|
|
22491
22642
|
};
|
|
22492
22643
|
}
|
|
22493
22644
|
function crewJson(args, timeoutMs = 6e4) {
|
|
22494
|
-
return new Promise((
|
|
22645
|
+
return new Promise((resolve8) => {
|
|
22495
22646
|
execFile3(process.execPath, [process.argv[1] ?? "", ...args], { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 }, (_e, stdout) => {
|
|
22496
22647
|
try {
|
|
22497
22648
|
const v = JSON.parse(String(stdout));
|
|
22498
|
-
|
|
22649
|
+
resolve8(v && typeof v === "object" && !Array.isArray(v) ? v : null);
|
|
22499
22650
|
} catch {
|
|
22500
|
-
|
|
22651
|
+
resolve8(null);
|
|
22501
22652
|
}
|
|
22502
22653
|
});
|
|
22503
22654
|
});
|
|
@@ -22505,31 +22656,31 @@ function crewJson(args, timeoutMs = 6e4) {
|
|
|
22505
22656
|
|
|
22506
22657
|
// src/periscope-mcp.ts
|
|
22507
22658
|
import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
|
|
22508
|
-
import { basename as basename15, dirname as
|
|
22659
|
+
import { basename as basename15, dirname as dirname28, isAbsolute as isAbsolute4, join as join49, relative as relative2, resolve as resolve6, sep } from "path";
|
|
22509
22660
|
import { z } from "zod";
|
|
22510
22661
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
22511
22662
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
22512
22663
|
|
|
22513
22664
|
// src/periscope-endpoints.ts
|
|
22514
22665
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
22515
|
-
import { chmodSync as chmodSync5, existsSync as
|
|
22516
|
-
import { join as
|
|
22517
|
-
var secretDir = (stateDir) =>
|
|
22518
|
-
var secretFile = (stateDir, id) =>
|
|
22519
|
-
var endpointDir = (stateDir) =>
|
|
22520
|
-
var endpointFile = (stateDir, id) =>
|
|
22666
|
+
import { chmodSync as chmodSync5, existsSync as existsSync45, mkdirSync as mkdirSync32, readdirSync as readdirSync19, readFileSync as readFileSync40, rmSync as rmSync22, writeFileSync as writeFileSync33 } from "fs";
|
|
22667
|
+
import { join as join48 } from "path";
|
|
22668
|
+
var secretDir = (stateDir) => join48(stateDir, "periscope-mcp");
|
|
22669
|
+
var secretFile = (stateDir, id) => join48(secretDir(stateDir), `${id}.json`);
|
|
22670
|
+
var endpointDir = (stateDir) => join48(stateDir, "periscope-endpoints");
|
|
22671
|
+
var endpointFile = (stateDir, id) => join48(endpointDir(stateDir), `${id}.json`);
|
|
22521
22672
|
function mintPeriscopeSecret(stateDir, ticketRecordId) {
|
|
22522
|
-
|
|
22673
|
+
mkdirSync32(secretDir(stateDir), { recursive: true, mode: 448 });
|
|
22523
22674
|
const secret = randomBytes3(32).toString("base64url");
|
|
22524
22675
|
const file = secretFile(stateDir, ticketRecordId);
|
|
22525
|
-
|
|
22676
|
+
writeFileSync33(file, `${JSON.stringify({ secret })}
|
|
22526
22677
|
`, { mode: 384 });
|
|
22527
22678
|
chmodSync5(file, 384);
|
|
22528
22679
|
return secret;
|
|
22529
22680
|
}
|
|
22530
22681
|
function readPeriscopeSecret(stateDir, ticketRecordId) {
|
|
22531
22682
|
try {
|
|
22532
|
-
const v = JSON.parse(
|
|
22683
|
+
const v = JSON.parse(readFileSync40(secretFile(stateDir, ticketRecordId), "utf8"));
|
|
22533
22684
|
return typeof v.secret === "string" && v.secret ? v.secret : void 0;
|
|
22534
22685
|
} catch {
|
|
22535
22686
|
return void 0;
|
|
@@ -22540,14 +22691,14 @@ function removePeriscopeSecret(stateDir, ticketRecordId) {
|
|
|
22540
22691
|
}
|
|
22541
22692
|
function readPeriscopeEndpoint(stateDir, ticketRecordId) {
|
|
22542
22693
|
try {
|
|
22543
|
-
return JSON.parse(
|
|
22694
|
+
return JSON.parse(readFileSync40(endpointFile(stateDir, ticketRecordId), "utf8"));
|
|
22544
22695
|
} catch {
|
|
22545
22696
|
return void 0;
|
|
22546
22697
|
}
|
|
22547
22698
|
}
|
|
22548
22699
|
function writePeriscopeEndpoint(stateDir, rec) {
|
|
22549
|
-
|
|
22550
|
-
|
|
22700
|
+
mkdirSync32(endpointDir(stateDir), { recursive: true });
|
|
22701
|
+
writeFileSync33(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
|
|
22551
22702
|
`);
|
|
22552
22703
|
}
|
|
22553
22704
|
function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
@@ -22555,7 +22706,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
|
22555
22706
|
}
|
|
22556
22707
|
function listPeriscopeEndpoints(stateDir) {
|
|
22557
22708
|
const dir = endpointDir(stateDir);
|
|
22558
|
-
if (!
|
|
22709
|
+
if (!existsSync45(dir)) return [];
|
|
22559
22710
|
const out = [];
|
|
22560
22711
|
for (const name of readdirSync19(dir)) {
|
|
22561
22712
|
if (!name.endsWith(".json")) continue;
|
|
@@ -22624,7 +22775,7 @@ var PathRefusal = class extends Error {
|
|
|
22624
22775
|
};
|
|
22625
22776
|
var isInside = (root, p) => {
|
|
22626
22777
|
const rel = relative2(root, p);
|
|
22627
|
-
return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !
|
|
22778
|
+
return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute4(rel);
|
|
22628
22779
|
};
|
|
22629
22780
|
async function realAncestor(p) {
|
|
22630
22781
|
let cur = p;
|
|
@@ -22643,10 +22794,10 @@ async function realAncestor(p) {
|
|
|
22643
22794
|
}
|
|
22644
22795
|
if (isLink) {
|
|
22645
22796
|
if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
|
|
22646
|
-
cur =
|
|
22797
|
+
cur = resolve6(dirname28(cur), await readlink(cur));
|
|
22647
22798
|
continue;
|
|
22648
22799
|
}
|
|
22649
|
-
const parent =
|
|
22800
|
+
const parent = dirname28(cur);
|
|
22650
22801
|
if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
|
|
22651
22802
|
tail3.push(basename15(cur));
|
|
22652
22803
|
cur = parent;
|
|
@@ -22656,15 +22807,15 @@ async function resolveInWorktree(root, requested) {
|
|
|
22656
22807
|
if (!requested || requested.includes("\0")) throw new PathRefusal("path is required");
|
|
22657
22808
|
const realRoot = await realpath(root);
|
|
22658
22809
|
let abs;
|
|
22659
|
-
if (
|
|
22660
|
-
abs =
|
|
22661
|
-
if (isInside(root, abs)) abs =
|
|
22810
|
+
if (isAbsolute4(requested)) {
|
|
22811
|
+
abs = resolve6(requested);
|
|
22812
|
+
if (isInside(root, abs)) abs = join49(realRoot, relative2(root, abs));
|
|
22662
22813
|
} else {
|
|
22663
|
-
abs =
|
|
22814
|
+
abs = resolve6(realRoot, requested);
|
|
22664
22815
|
}
|
|
22665
22816
|
if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
22666
22817
|
const { real, rest } = await realAncestor(abs);
|
|
22667
|
-
const final = rest ?
|
|
22818
|
+
const final = rest ? join49(real, rest) : real;
|
|
22668
22819
|
if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
22669
22820
|
if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
|
|
22670
22821
|
return final;
|
|
@@ -22699,7 +22850,7 @@ async function hookEnv(d, sc, name) {
|
|
|
22699
22850
|
if (iso && iso.code === 0) Object.assign(env, parseEnvLines(iso.output));
|
|
22700
22851
|
}
|
|
22701
22852
|
}
|
|
22702
|
-
if (name === "preview") env
|
|
22853
|
+
if (name === "preview") Object.assign(env, { CREW_PREVIEW_HOST: sc.host || "localhost" }, pointedAtEnv(sc.worktree));
|
|
22703
22854
|
return env;
|
|
22704
22855
|
}
|
|
22705
22856
|
function createPeriscopeMcpServer(d, sc) {
|
|
@@ -22732,7 +22883,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22732
22883
|
const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
|
|
22733
22884
|
const lines2 = await Promise.all(entries.map(async (e) => {
|
|
22734
22885
|
if (e.isDirectory()) return `[DIR] ${e.name}`;
|
|
22735
|
-
const size = (await lstat(
|
|
22886
|
+
const size = (await lstat(join49(abs, e.name))).size;
|
|
22736
22887
|
return `[FILE] ${e.name} (${size} bytes)`;
|
|
22737
22888
|
}));
|
|
22738
22889
|
return text(lines2.join("\n") || "(empty)");
|
|
@@ -22757,7 +22908,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22757
22908
|
if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
|
|
22758
22909
|
const abs = await inTree(path);
|
|
22759
22910
|
if (abs === await realpath(root)) return text("path must name a file", true);
|
|
22760
|
-
await mkdir(
|
|
22911
|
+
await mkdir(dirname28(abs), { recursive: true });
|
|
22761
22912
|
await inTree(path);
|
|
22762
22913
|
await writeFile(abs, content);
|
|
22763
22914
|
return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
|
|
@@ -22797,8 +22948,8 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22797
22948
|
inputSchema: { path: z.string() },
|
|
22798
22949
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
22799
22950
|
}, async ({ path }) => {
|
|
22800
|
-
const parent = await inTree(
|
|
22801
|
-
const abs =
|
|
22951
|
+
const parent = await inTree(dirname28(path) === "" ? "." : dirname28(path));
|
|
22952
|
+
const abs = join49(parent, basename15(path));
|
|
22802
22953
|
if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
|
|
22803
22954
|
const st = await lstat(abs).catch(() => void 0);
|
|
22804
22955
|
if (!st) return text(`${path} does not exist`, true);
|
|
@@ -22919,7 +23070,7 @@ function findPeriscope(sidecars, segment) {
|
|
|
22919
23070
|
}
|
|
22920
23071
|
|
|
22921
23072
|
// src/cli.ts
|
|
22922
|
-
var CREW_HOME = isCompiledBinary(import.meta.url) ?
|
|
23073
|
+
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname29(process.execPath) : resolve7(dirname29(fileURLToPath(import.meta.url)), "..");
|
|
22923
23074
|
var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
|
|
22924
23075
|
var CYCLE_SECONDS2 = 120;
|
|
22925
23076
|
var STALE_CYCLES = 3;
|
|
@@ -23287,7 +23438,7 @@ if (command === "provision") {
|
|
|
23287
23438
|
process.exit(outcome.code);
|
|
23288
23439
|
}
|
|
23289
23440
|
if (command === "pool-member") {
|
|
23290
|
-
const pool = poolConfigFrom(argv, process.env, () =>
|
|
23441
|
+
const pool = poolConfigFrom(argv, process.env, () => readFileSync41(0, "utf8"));
|
|
23291
23442
|
if (typeof pool === "string") {
|
|
23292
23443
|
process.stderr.write(`crew pool-member: ${pool}
|
|
23293
23444
|
`);
|
|
@@ -23515,7 +23666,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23515
23666
|
} else {
|
|
23516
23667
|
const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
|
|
23517
23668
|
const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
|
|
23518
|
-
if (configless && !
|
|
23669
|
+
if (configless && !existsSync46(findConfigFile(CREW_HOME))) {
|
|
23519
23670
|
cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
|
|
23520
23671
|
} else {
|
|
23521
23672
|
cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
|
|
@@ -23530,7 +23681,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23530
23681
|
throw e;
|
|
23531
23682
|
}
|
|
23532
23683
|
if (command === "credential") {
|
|
23533
|
-
const r = runCredential(positional[1],
|
|
23684
|
+
const r = runCredential(positional[1], readFileSync41(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
|
|
23534
23685
|
process.stdout.write(r.out);
|
|
23535
23686
|
process.stderr.write(r.err);
|
|
23536
23687
|
process.exit(r.code);
|
|
@@ -23542,9 +23693,9 @@ if (command === "passenger-sync-daemon") {
|
|
|
23542
23693
|
forges: cfg.ship.forges,
|
|
23543
23694
|
stateDir: cfg.ship.stateDir,
|
|
23544
23695
|
configFile: cfg.configFile,
|
|
23545
|
-
readFile: (p) =>
|
|
23546
|
-
writeFile: (p, t) =>
|
|
23547
|
-
stdin: () =>
|
|
23696
|
+
readFile: (p) => readFileSync41(p, "utf8"),
|
|
23697
|
+
writeFile: (p, t) => writeFileSync34(p, t),
|
|
23698
|
+
stdin: () => readFileSync41(0, "utf8"),
|
|
23548
23699
|
env: process.env
|
|
23549
23700
|
}
|
|
23550
23701
|
);
|
|
@@ -23837,8 +23988,8 @@ if (command === "passenger-sync-daemon") {
|
|
|
23837
23988
|
}
|
|
23838
23989
|
if (host === "linux" && hasSystemd) {
|
|
23839
23990
|
const label = labelFor(CREW_HOME, "run");
|
|
23840
|
-
const staleTimer =
|
|
23841
|
-
if (
|
|
23991
|
+
const staleTimer = join50(homedir11(), ".config", "systemd", "user", `${label}.timer`);
|
|
23992
|
+
if (existsSync46(staleTimer)) {
|
|
23842
23993
|
process.stdout.write(
|
|
23843
23994
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
23844
23995
|
`
|
|
@@ -23865,7 +24016,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23865
24016
|
const tracker = new Tracker(c, cfg.ship);
|
|
23866
24017
|
problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
|
|
23867
24018
|
for (const r of await resolvedRepos(c)) {
|
|
23868
|
-
if (!
|
|
24019
|
+
if (!existsSync46(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
23869
24020
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
23870
24021
|
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
23871
24022
|
}
|
|
@@ -23881,9 +24032,18 @@ if (command === "passenger-sync-daemon") {
|
|
|
23881
24032
|
}
|
|
23882
24033
|
return { problems, readiness };
|
|
23883
24034
|
}
|
|
24035
|
+
async function doctorShipKind(route3, ship) {
|
|
24036
|
+
try {
|
|
24037
|
+
const rows = await new Tracker(route3, ship).shipRows();
|
|
24038
|
+
const mine = matchShipRows(rows, ship.name, doctorMachineId(ship.stateDir)).rows;
|
|
24039
|
+
return mine.length === 1 ? mine[0].kind ?? null : null;
|
|
24040
|
+
} catch {
|
|
24041
|
+
return null;
|
|
24042
|
+
}
|
|
24043
|
+
}
|
|
23884
24044
|
async function doctorFix(o) {
|
|
23885
24045
|
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))));
|
|
23886
|
-
const unitExists = (p) =>
|
|
24046
|
+
const unitExists = (p) => existsSync46(p);
|
|
23887
24047
|
const crontab = o.host === "linux" ? spawnSync10("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
23888
24048
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
|
|
23889
24049
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -23900,7 +24060,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23900
24060
|
for (const a of plan.actions) {
|
|
23901
24061
|
if (a.kind === "enable") {
|
|
23902
24062
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
23903
|
-
|
|
24063
|
+
writeFileSync34(cfg.configFile, planEnable(readFileSync41(cfg.configFile, "utf8"), a.route));
|
|
23904
24064
|
enabledSound.add(a.route);
|
|
23905
24065
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
23906
24066
|
} else {
|
|
@@ -24641,7 +24801,7 @@ ${cu.tail.trimEnd()}
|
|
|
24641
24801
|
cfg.ship.name,
|
|
24642
24802
|
(t) => {
|
|
24643
24803
|
const d = dirForRepo(w.route, t.repo_id);
|
|
24644
|
-
return d ? originBranchSha(d, t,
|
|
24804
|
+
return d ? originBranchSha(d, t, resolve3) : null;
|
|
24645
24805
|
}
|
|
24646
24806
|
);
|
|
24647
24807
|
if (!result.ticket) {
|
|
@@ -24853,7 +25013,7 @@ ${cu.tail.trimEnd()}
|
|
|
24853
25013
|
cfg.ship.name,
|
|
24854
25014
|
(t) => {
|
|
24855
25015
|
const d = dirForRepo(route2, t.repo_id);
|
|
24856
|
-
return d ? originBranchSha(d, t,
|
|
25016
|
+
return d ? originBranchSha(d, t, resolve3) : null;
|
|
24857
25017
|
}
|
|
24858
25018
|
);
|
|
24859
25019
|
if (!result.ticket) {
|
|
@@ -25039,7 +25199,7 @@ ${cu.tail.trimEnd()}
|
|
|
25039
25199
|
process.exit(1);
|
|
25040
25200
|
}
|
|
25041
25201
|
requireArmed("run the daemon loop");
|
|
25042
|
-
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [
|
|
25202
|
+
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join50(CREW_HOME, "src"), join50(CREW_HOME, "dist"), join50(CREW_HOME, "bin")];
|
|
25043
25203
|
const configPaths = daemonConfigPaths(cfg);
|
|
25044
25204
|
watchPaths.push(...configPaths);
|
|
25045
25205
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -25344,10 +25504,10 @@ ${cu.tail.trimEnd()}
|
|
|
25344
25504
|
`);
|
|
25345
25505
|
if (!dryRun) {
|
|
25346
25506
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
25347
|
-
const raw =
|
|
25507
|
+
const raw = existsSync46(resolvedPath) ? JSON.parse(readFileSync41(resolvedPath, "utf8")) : {};
|
|
25348
25508
|
raw.agentSkills = result.agentSkills;
|
|
25349
|
-
|
|
25350
|
-
|
|
25509
|
+
mkdirSync33(dirOf2(resolvedPath), { recursive: true });
|
|
25510
|
+
writeFileSync34(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
25351
25511
|
`);
|
|
25352
25512
|
}
|
|
25353
25513
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -25370,7 +25530,7 @@ ${cu.tail.trimEnd()}
|
|
|
25370
25530
|
throw e;
|
|
25371
25531
|
}
|
|
25372
25532
|
const oldName = cfg.ship.name;
|
|
25373
|
-
|
|
25533
|
+
writeFileSync34(cfg.configFile, setShipNameInYaml(readFileSync41(cfg.configFile, "utf8"), newName));
|
|
25374
25534
|
cfg.ship.name = newName;
|
|
25375
25535
|
let renameMachineId;
|
|
25376
25536
|
try {
|
|
@@ -25417,14 +25577,14 @@ ${cu.tail.trimEnd()}
|
|
|
25417
25577
|
process.exit(2);
|
|
25418
25578
|
};
|
|
25419
25579
|
if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks" && sub !== "check") usage2();
|
|
25420
|
-
const configText =
|
|
25580
|
+
const configText = readFileSync41(cfg.configFile, "utf8");
|
|
25421
25581
|
if (sub === "check") {
|
|
25422
25582
|
try {
|
|
25423
25583
|
const routeArg = value("route") ?? positional[2];
|
|
25424
25584
|
const found = cfg.routes.find((r) => r.route === routeArg);
|
|
25425
25585
|
if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
|
|
25426
25586
|
const named2 = value("route") ? void 0 : positional[3];
|
|
25427
|
-
const dir = named2 ? repoDirOf(configText,
|
|
25587
|
+
const dir = named2 ? repoDirOf(configText, dirname29(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
|
|
25428
25588
|
const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
|
|
25429
25589
|
const o = target ? found.repoOverrides[target.name] : void 0;
|
|
25430
25590
|
runReposCheck(dir, {
|
|
@@ -25447,9 +25607,9 @@ ${cu.tail.trimEnd()}
|
|
|
25447
25607
|
if (sub === "hooks") {
|
|
25448
25608
|
try {
|
|
25449
25609
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
25450
|
-
const dir = repoDirOf(configText,
|
|
25451
|
-
if (!
|
|
25452
|
-
const files = { exists: (r) =>
|
|
25610
|
+
const dir = repoDirOf(configText, dirname29(cfg.configFile), a.route, a.name);
|
|
25611
|
+
if (!existsSync46(dir)) throw new ReposError(`${dir} does not exist on disk`);
|
|
25612
|
+
const files = { exists: (r) => existsSync46(resolvePath(dir, r)), list: (r) => {
|
|
25453
25613
|
try {
|
|
25454
25614
|
return readdirSync20(resolvePath(dir, r));
|
|
25455
25615
|
} catch {
|
|
@@ -25457,7 +25617,7 @@ ${cu.tail.trimEnd()}
|
|
|
25457
25617
|
}
|
|
25458
25618
|
}, read: (r) => {
|
|
25459
25619
|
try {
|
|
25460
|
-
return
|
|
25620
|
+
return readFileSync41(resolvePath(dir, r), "utf8");
|
|
25461
25621
|
} catch {
|
|
25462
25622
|
return void 0;
|
|
25463
25623
|
}
|
|
@@ -25499,19 +25659,19 @@ ${cu.tail.trimEnd()}
|
|
|
25499
25659
|
let cleared;
|
|
25500
25660
|
if (clear2) {
|
|
25501
25661
|
const plan = clearRouteHooks(configText, a.route, a.name);
|
|
25502
|
-
if (plan.removed.length > 0)
|
|
25662
|
+
if (plan.removed.length > 0) writeFileSync34(cfg.configFile, plan.text);
|
|
25503
25663
|
cleared = { file: cfg.configFile, removed: plan.removed };
|
|
25504
25664
|
}
|
|
25505
25665
|
if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
25506
25666
|
if (write === "route") {
|
|
25507
25667
|
const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
|
|
25508
|
-
|
|
25668
|
+
writeFileSync34(cfg.configFile, plan.text);
|
|
25509
25669
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
|
|
25510
25670
|
} else if (write === "repo") {
|
|
25511
25671
|
const have = existingRepoConfig(files);
|
|
25512
25672
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
25513
25673
|
const file = resolvePath(dir, ".crew.yaml");
|
|
25514
|
-
|
|
25674
|
+
writeFileSync34(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
|
|
25515
25675
|
written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
|
|
25516
25676
|
}
|
|
25517
25677
|
if (flag("json")) {
|
|
@@ -25567,7 +25727,7 @@ ${cu.tail.trimEnd()}
|
|
|
25567
25727
|
if (sub === "list" && !routeName) usage2();
|
|
25568
25728
|
if (sub === "add") {
|
|
25569
25729
|
try {
|
|
25570
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
25730
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync46(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
|
|
25571
25731
|
routeName = a.route;
|
|
25572
25732
|
addPath = a.path;
|
|
25573
25733
|
addName = a.name;
|
|
@@ -25597,7 +25757,7 @@ ${cu.tail.trimEnd()}
|
|
|
25597
25757
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
25598
25758
|
if (sub === "list" || sub === "add") {
|
|
25599
25759
|
const liveRoute = cfg.routes.find((r) => r.route === routeName);
|
|
25600
|
-
if (liveRoute?.resolved?.reposModelId &&
|
|
25760
|
+
if (liveRoute?.resolved?.reposModelId && existsSync46(resolvedFile) && !dryRun) {
|
|
25601
25761
|
try {
|
|
25602
25762
|
await hydrateApiKeys([liveRoute]);
|
|
25603
25763
|
persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
|
|
@@ -25606,16 +25766,16 @@ ${cu.tail.trimEnd()}
|
|
|
25606
25766
|
}
|
|
25607
25767
|
}
|
|
25608
25768
|
let resolvedRemotes;
|
|
25609
|
-
if (
|
|
25769
|
+
if (existsSync46(resolvedFile)) {
|
|
25610
25770
|
try {
|
|
25611
|
-
resolvedRemotes = JSON.parse(
|
|
25771
|
+
resolvedRemotes = JSON.parse(readFileSync41(resolvedFile, "utf8"));
|
|
25612
25772
|
} catch {
|
|
25613
25773
|
}
|
|
25614
25774
|
}
|
|
25615
25775
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
25616
25776
|
const text2 = configText;
|
|
25617
|
-
const base =
|
|
25618
|
-
const isCheckout = (d) =>
|
|
25777
|
+
const base = dirname29(cfg.configFile);
|
|
25778
|
+
const isCheckout = (d) => existsSync46(`${d}/.git`);
|
|
25619
25779
|
try {
|
|
25620
25780
|
if (sub === "list") {
|
|
25621
25781
|
if (flag("json")) {
|
|
@@ -25625,11 +25785,11 @@ ${cu.tail.trimEnd()}
|
|
|
25625
25785
|
base,
|
|
25626
25786
|
resolved: resolvedRemotes,
|
|
25627
25787
|
originOf,
|
|
25628
|
-
exists: (d) =>
|
|
25788
|
+
exists: (d) => existsSync46(d),
|
|
25629
25789
|
isCheckout,
|
|
25630
25790
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
25631
25791
|
try {
|
|
25632
|
-
return Object.keys(parseRepoConfig(
|
|
25792
|
+
return Object.keys(parseRepoConfig(readFileSync41(resolvePath(d, f), "utf8"), f).hooks).length > 0;
|
|
25633
25793
|
} catch {
|
|
25634
25794
|
return false;
|
|
25635
25795
|
}
|
|
@@ -25646,7 +25806,7 @@ ${cu.tail.trimEnd()}
|
|
|
25646
25806
|
for (const r of repos) {
|
|
25647
25807
|
const origin2 = originOf(r.dir);
|
|
25648
25808
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
25649
|
-
const note = !
|
|
25809
|
+
const note = !existsSync46(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
|
|
25650
25810
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
25651
25811
|
`);
|
|
25652
25812
|
}
|
|
@@ -25658,9 +25818,9 @@ ${cu.tail.trimEnd()}
|
|
|
25658
25818
|
` : `${human}
|
|
25659
25819
|
`);
|
|
25660
25820
|
const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
|
|
25661
|
-
if (
|
|
25821
|
+
if (existsSync46(plan2.target) && (existsSync46(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
|
|
25662
25822
|
throw new ReposError(
|
|
25663
|
-
|
|
25823
|
+
existsSync46(`${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`
|
|
25664
25824
|
);
|
|
25665
25825
|
}
|
|
25666
25826
|
if (dryRun) {
|
|
@@ -25678,14 +25838,14 @@ ${cu.tail.trimEnd()}
|
|
|
25678
25838
|
throw new ReposError(`${e.message}${authHint}
|
|
25679
25839
|
crew.yaml was not changed`);
|
|
25680
25840
|
}
|
|
25681
|
-
|
|
25841
|
+
writeFileSync34(cfg.configFile, plan2.text);
|
|
25682
25842
|
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:
|
|
25683
25843
|
`);
|
|
25684
25844
|
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}`);
|
|
25685
25845
|
break;
|
|
25686
25846
|
}
|
|
25687
25847
|
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
|
|
25688
|
-
const top =
|
|
25848
|
+
const top = existsSync46(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
25689
25849
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
25690
25850
|
const origin = originOf(top);
|
|
25691
25851
|
const plan = planRepoAdd({
|
|
@@ -25737,15 +25897,15 @@ ${cu.tail.trimEnd()}
|
|
|
25737
25897
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
25738
25898
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
25739
25899
|
`);
|
|
25740
|
-
if (
|
|
25741
|
-
const raw = JSON.parse(
|
|
25900
|
+
if (existsSync46(resolvedFile)) {
|
|
25901
|
+
const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
|
|
25742
25902
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
25743
25903
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
25744
|
-
|
|
25904
|
+
writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
25745
25905
|
`);
|
|
25746
25906
|
}
|
|
25747
25907
|
}
|
|
25748
|
-
|
|
25908
|
+
writeFileSync34(cfg.configFile, plan.text);
|
|
25749
25909
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
25750
25910
|
`);
|
|
25751
25911
|
} catch (e) {
|
|
@@ -26114,7 +26274,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26114
26274
|
process.exit(2);
|
|
26115
26275
|
}
|
|
26116
26276
|
if (found.shipNameAdopted && !dryRun) {
|
|
26117
|
-
|
|
26277
|
+
writeFileSync34(cfg.configFile, setShipNameInYaml(readFileSync41(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
26118
26278
|
cfg.ship.name = found.shipNameAdopted;
|
|
26119
26279
|
}
|
|
26120
26280
|
if (found.shipRow) {
|
|
@@ -26157,10 +26317,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26157
26317
|
}
|
|
26158
26318
|
}
|
|
26159
26319
|
let previous;
|
|
26160
|
-
const hadPreviousFile =
|
|
26320
|
+
const hadPreviousFile = existsSync46(resolvedPath);
|
|
26161
26321
|
if (hadPreviousFile) {
|
|
26162
26322
|
try {
|
|
26163
|
-
previous = JSON.parse(
|
|
26323
|
+
previous = JSON.parse(readFileSync41(resolvedPath, "utf8"));
|
|
26164
26324
|
} catch {
|
|
26165
26325
|
}
|
|
26166
26326
|
}
|
|
@@ -26290,10 +26450,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26290
26450
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
26291
26451
|
`);
|
|
26292
26452
|
} else {
|
|
26293
|
-
|
|
26453
|
+
mkdirSync33(dirname29(resolvedPath), { recursive: true });
|
|
26294
26454
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
26295
26455
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
26296
|
-
|
|
26456
|
+
writeFileSync34(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
26297
26457
|
`);
|
|
26298
26458
|
renameSync6(tmpPath, resolvedPath);
|
|
26299
26459
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
@@ -26327,7 +26487,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26327
26487
|
}
|
|
26328
26488
|
let apiKeyFile;
|
|
26329
26489
|
const apiKeyVar = "CREW_API_KEY";
|
|
26330
|
-
if (firstRunFromFlags && agentBinSearched && !dryRun) {
|
|
26490
|
+
if (firstRunFromFlags && agentBinSearched && !dryRun && !isManagedShipKind(value("ship-kind"))) {
|
|
26331
26491
|
const msg = `no agent binary: "claude" was not found on PATH or in ${agentBinSearched.join(", ")} \u2014 pass --agent-bin /absolute/path`;
|
|
26332
26492
|
process.stderr.write(`crew connect: ${msg}
|
|
26333
26493
|
`);
|
|
@@ -26339,8 +26499,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26339
26499
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
26340
26500
|
`);
|
|
26341
26501
|
} else {
|
|
26342
|
-
|
|
26343
|
-
|
|
26502
|
+
mkdirSync33(dirname29(apiKeyFile), { recursive: true });
|
|
26503
|
+
writeFileSync34(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
26344
26504
|
`, { mode: 384 });
|
|
26345
26505
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
26346
26506
|
`);
|
|
@@ -27067,7 +27227,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27067
27227
|
};
|
|
27068
27228
|
if (subP === "list") {
|
|
27069
27229
|
const entries = periscopeListEntries(
|
|
27070
|
-
(await Promise.all(routesP.map(gather))).flat(),
|
|
27230
|
+
(await Promise.all(routesP.map(gather))).flat().map((r) => ({ ...r, pointedAt: readPointedAt(r.worktree) })),
|
|
27071
27231
|
/* @__PURE__ */ new Date(),
|
|
27072
27232
|
(r) => pidsInWorktree(r.worktree, Number(r.number)).length > 0
|
|
27073
27233
|
);
|
|
@@ -27215,7 +27375,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27215
27375
|
const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
|
|
27216
27376
|
const resumed = await runPeriscopeResume({
|
|
27217
27377
|
find: async () => hit && { row: hit.row, tag: hit.tag },
|
|
27218
|
-
worktreeExists: (p) =>
|
|
27378
|
+
worktreeExists: (p) => existsSync46(p),
|
|
27379
|
+
prepareDropFile,
|
|
27219
27380
|
caps: periscopeCaps(cfg.ship),
|
|
27220
27381
|
runHook: async (name, cwd, env) => {
|
|
27221
27382
|
const script = hit?.repo.config.hooks[name];
|
|
@@ -27309,10 +27470,10 @@ ${where.join("\n")}`);
|
|
|
27309
27470
|
},
|
|
27310
27471
|
prepare: async (path) => {
|
|
27311
27472
|
for (const f of repoP.config.worktrees.copy) {
|
|
27312
|
-
const src =
|
|
27313
|
-
if (!
|
|
27314
|
-
|
|
27315
|
-
copyFileSync2(src,
|
|
27473
|
+
const src = join50(repoP.dir, f);
|
|
27474
|
+
if (!existsSync46(src)) continue;
|
|
27475
|
+
mkdirSync33(dirOf2(join50(path, f)), { recursive: true });
|
|
27476
|
+
copyFileSync2(src, join50(path, f));
|
|
27316
27477
|
}
|
|
27317
27478
|
const setup = repoP.config.hooks.setup;
|
|
27318
27479
|
return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
|
|
@@ -27324,6 +27485,7 @@ ${where.join("\n")}`);
|
|
|
27324
27485
|
return script ? runScript(script, { cwd, env, shell: shellP, timeoutMs: name === "preview" ? 18e4 : void 0 }) : null;
|
|
27325
27486
|
},
|
|
27326
27487
|
createRow: (fields) => trackerP.createPeriscope(fields),
|
|
27488
|
+
prepareDropFile,
|
|
27327
27489
|
recordLocal: (sc) => {
|
|
27328
27490
|
writePeriscopeSidecar(cfg.ship.stateDir, { ...sc, route: routeP.route });
|
|
27329
27491
|
if (flag("serve-mcp")) mintPeriscopeSecret(cfg.ship.stateDir, sc.ticketRecordId);
|
|
@@ -27607,7 +27769,7 @@ ${where.join("\n")}`);
|
|
|
27607
27769
|
};
|
|
27608
27770
|
const droppedRoutes = [];
|
|
27609
27771
|
try {
|
|
27610
|
-
const rawCfg = parseYaml2(
|
|
27772
|
+
const rawCfg = parseYaml2(readFileSync41(cfg.configFile, "utf8"));
|
|
27611
27773
|
for (const r of rawCfg?.routes ?? []) {
|
|
27612
27774
|
if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
|
|
27613
27775
|
try {
|
|
@@ -27660,8 +27822,8 @@ ${where.join("\n")}`);
|
|
|
27660
27822
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
27661
27823
|
break;
|
|
27662
27824
|
}
|
|
27663
|
-
|
|
27664
|
-
|
|
27825
|
+
mkdirSync33(cfg.ship.stateDir, { recursive: true });
|
|
27826
|
+
writeFileSync34(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
27665
27827
|
`);
|
|
27666
27828
|
process.stdout.write(
|
|
27667
27829
|
"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"
|
|
@@ -27726,7 +27888,7 @@ ${where.join("\n")}`);
|
|
|
27726
27888
|
}
|
|
27727
27889
|
case "log": {
|
|
27728
27890
|
try {
|
|
27729
|
-
const text2 =
|
|
27891
|
+
const text2 = readFileSync41(cfg.ship.logFile, "utf8").split("\n");
|
|
27730
27892
|
process.stdout.write(`${text2.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
27731
27893
|
`);
|
|
27732
27894
|
} catch {
|
|
@@ -27749,8 +27911,8 @@ ${where.join("\n")}`);
|
|
|
27749
27911
|
};
|
|
27750
27912
|
if (!route2.resolved) fail(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
|
|
27751
27913
|
const tracker = new Tracker(route2, cfg.ship);
|
|
27752
|
-
const configText =
|
|
27753
|
-
const cfgBase =
|
|
27914
|
+
const configText = readFileSync41(cfg.configFile, "utf8");
|
|
27915
|
+
const cfgBase = dirname29(cfg.configFile);
|
|
27754
27916
|
const configured2 = configuredRepos(configText, route2.route, cfgBase);
|
|
27755
27917
|
const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
|
|
27756
27918
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
|
|
@@ -27778,7 +27940,7 @@ ${where.join("\n")}`);
|
|
|
27778
27940
|
}
|
|
27779
27941
|
if (configured2.some((r) => r.dir === dir)) {
|
|
27780
27942
|
try {
|
|
27781
|
-
|
|
27943
|
+
writeFileSync34(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
|
|
27782
27944
|
} catch (e) {
|
|
27783
27945
|
if (e instanceof ReposError) fail(e.message);
|
|
27784
27946
|
throw e;
|
|
@@ -27786,29 +27948,29 @@ ${where.join("\n")}`);
|
|
|
27786
27948
|
}
|
|
27787
27949
|
if (row) {
|
|
27788
27950
|
await tracker.deleteRepoRow(row.id);
|
|
27789
|
-
if (
|
|
27790
|
-
const raw = JSON.parse(
|
|
27951
|
+
if (existsSync46(resolvedFile)) {
|
|
27952
|
+
const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
|
|
27791
27953
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
27792
27954
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
27793
|
-
|
|
27955
|
+
writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
27794
27956
|
`);
|
|
27795
27957
|
}
|
|
27796
27958
|
}
|
|
27797
|
-
if (
|
|
27959
|
+
if (existsSync46(join50(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync23(dir, { recursive: true, force: true });
|
|
27798
27960
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
27799
27961
|
break;
|
|
27800
27962
|
}
|
|
27801
27963
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
27802
|
-
const fresh = !
|
|
27803
|
-
if (
|
|
27964
|
+
const fresh = !existsSync46(join50(dir, ".git"));
|
|
27965
|
+
if (existsSync46(dir) && fresh && readdirSync20(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
27804
27966
|
if (dryRun) {
|
|
27805
27967
|
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`);
|
|
27806
27968
|
break;
|
|
27807
27969
|
}
|
|
27808
27970
|
if (fresh) {
|
|
27809
|
-
|
|
27810
|
-
|
|
27811
|
-
|
|
27971
|
+
mkdirSync33(dir, { recursive: true });
|
|
27972
|
+
writeFileSync34(join50(dir, "README.md"), SAMPLE_README);
|
|
27973
|
+
writeFileSync34(join50(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
27812
27974
|
const g = (...a) => {
|
|
27813
27975
|
const r = spawnSync10("git", a, { cwd: dir, encoding: "utf8" });
|
|
27814
27976
|
if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -27826,17 +27988,17 @@ ${where.join("\n")}`);
|
|
|
27826
27988
|
spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
|
|
27827
27989
|
});
|
|
27828
27990
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
27829
|
-
if (
|
|
27830
|
-
const raw = JSON.parse(
|
|
27991
|
+
if (existsSync46(resolvedFile)) {
|
|
27992
|
+
const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
|
|
27831
27993
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
27832
27994
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
27833
|
-
|
|
27995
|
+
writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
27834
27996
|
`);
|
|
27835
27997
|
}
|
|
27836
27998
|
}
|
|
27837
27999
|
if (!configured2.some((r) => r.dir === dir)) {
|
|
27838
28000
|
try {
|
|
27839
|
-
|
|
28001
|
+
writeFileSync34(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
|
|
27840
28002
|
} catch (e) {
|
|
27841
28003
|
if (e instanceof ReposError) fail(e.message);
|
|
27842
28004
|
throw e;
|
|
@@ -28182,7 +28344,7 @@ watermark: ${state.watermark()}
|
|
|
28182
28344
|
}
|
|
28183
28345
|
for (const r of repos) {
|
|
28184
28346
|
const remote = r.config.branch.remote;
|
|
28185
|
-
if (
|
|
28347
|
+
if (existsSync46(r.dir)) {
|
|
28186
28348
|
pushAuth.push({
|
|
28187
28349
|
route: c.route,
|
|
28188
28350
|
repo: r.name,
|
|
@@ -28234,6 +28396,9 @@ watermark: ${state.watermark()}
|
|
|
28234
28396
|
}
|
|
28235
28397
|
}
|
|
28236
28398
|
for (const message of roleCapWarnings(cfg.ship)) problems.push({ severity: "note", kind: "role_cap_above_ship_cap", message });
|
|
28399
|
+
if (!flag("offline")) {
|
|
28400
|
+
for (const message of managedRuntimeWarnings(await doctorShipKind(route2, cfg.ship), cfg.ship)) problems.push({ severity: "attention", kind: "managed_runtime_default", message });
|
|
28401
|
+
}
|
|
28237
28402
|
const previewProblem2 = await previewHostProblem(cfg);
|
|
28238
28403
|
if (previewProblem2) problems.push(previewHostFinding(previewProblem2));
|
|
28239
28404
|
if (cfg.routes.some((x) => x.route === route2.route && x.enabled && x.hostPeriscopes)) {
|
|
@@ -28268,7 +28433,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
28268
28433
|
}
|
|
28269
28434
|
try {
|
|
28270
28435
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
28271
|
-
const evSize =
|
|
28436
|
+
const evSize = existsSync46(evFile) ? statSync16(evFile).size : 0;
|
|
28272
28437
|
process.stdout.write(
|
|
28273
28438
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
28274
28439
|
` : "events log: ok\n"
|
|
@@ -28279,7 +28444,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
28279
28444
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run", installContext());
|
|
28280
28445
|
const abandons = unitAbandonsAgents(unitPlan, (p) => {
|
|
28281
28446
|
try {
|
|
28282
|
-
return
|
|
28447
|
+
return readFileSync41(p, "utf8");
|
|
28283
28448
|
} catch {
|
|
28284
28449
|
return null;
|
|
28285
28450
|
}
|
|
@@ -28302,7 +28467,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
28302
28467
|
const changed2 = configChangedAfter(daemonConfigPaths(cfg), daemonStartedAt);
|
|
28303
28468
|
if (changed2) {
|
|
28304
28469
|
process.stdout.write(
|
|
28305
|
-
`daemon config: WARN ${changed2} changed at ${new Date(
|
|
28470
|
+
`daemon config: WARN ${changed2} changed at ${new Date(statSync16(changed2).mtimeMs).toISOString()} but the daemon started at ${new Date(daemonStartedAt).toISOString()} \u2014 run \`crew reload\`
|
|
28306
28471
|
`
|
|
28307
28472
|
);
|
|
28308
28473
|
}
|
|
@@ -28472,6 +28637,8 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
28472
28637
|
crew manifest: ${seats2.length ? seats2.map((c) => c.name).join(", ") : "none"}
|
|
28473
28638
|
`
|
|
28474
28639
|
);
|
|
28640
|
+
for (const w of managedRuntimeWarnings(row.kind, cfg.ship)) process.stdout.write(`managed runtime: WARN ${w}
|
|
28641
|
+
`);
|
|
28475
28642
|
}
|
|
28476
28643
|
}
|
|
28477
28644
|
{
|