@tablation/crew 0.114.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 +825 -735
- 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;
|
|
@@ -3178,14 +3255,14 @@ function mergeRouteRelease(route2, override) {
|
|
|
3178
3255
|
function expand(p, base) {
|
|
3179
3256
|
let out = p.startsWith("~") ? p.replace(/^~/, homedir6()) : p;
|
|
3180
3257
|
out = out.replace(/\$\{?HOME\}?/g, homedir6());
|
|
3181
|
-
return
|
|
3258
|
+
return isAbsolute2(out) ? out : resolve2(base, out);
|
|
3182
3259
|
}
|
|
3183
3260
|
function resolvePromptsDir(promptSet, crewHome, base) {
|
|
3184
|
-
if (!promptSet) return
|
|
3261
|
+
if (!promptSet) return join13(crewHome, "prompts", "default");
|
|
3185
3262
|
if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
|
|
3186
3263
|
return expand(promptSet, base);
|
|
3187
3264
|
}
|
|
3188
|
-
return
|
|
3265
|
+
return join13(crewHome, "prompts", promptSet);
|
|
3189
3266
|
}
|
|
3190
3267
|
var Missing = class {
|
|
3191
3268
|
paths = [];
|
|
@@ -3209,36 +3286,36 @@ var Missing = class {
|
|
|
3209
3286
|
};
|
|
3210
3287
|
function crewVersion(crewHome) {
|
|
3211
3288
|
try {
|
|
3212
|
-
const pkg = JSON.parse(
|
|
3289
|
+
const pkg = JSON.parse(readFileSync12(join13(crewHome, "package.json"), "utf8"));
|
|
3213
3290
|
return pkg.version ?? "0.0.0";
|
|
3214
3291
|
} catch {
|
|
3215
3292
|
return "0.0.0";
|
|
3216
3293
|
}
|
|
3217
3294
|
}
|
|
3218
3295
|
function configSearchPath(crewHome) {
|
|
3219
|
-
if (process.env.CREW_CONFIG) return [
|
|
3296
|
+
if (process.env.CREW_CONFIG) return [resolve2(process.env.CREW_CONFIG)];
|
|
3220
3297
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
3221
3298
|
return [
|
|
3222
|
-
...xdg ? [
|
|
3223
|
-
|
|
3224
|
-
|
|
3299
|
+
...xdg ? [join13(xdg, "crew", "crew.yaml")] : [],
|
|
3300
|
+
join13(homedir6(), ".config", "crew", "crew.yaml"),
|
|
3301
|
+
resolve2(crewHome, "crew.yaml")
|
|
3225
3302
|
];
|
|
3226
3303
|
}
|
|
3227
3304
|
function findConfigFile(crewHome) {
|
|
3228
3305
|
const candidates2 = configSearchPath(crewHome);
|
|
3229
|
-
return candidates2.find((c) =>
|
|
3306
|
+
return candidates2.find((c) => existsSync11(c)) ?? candidates2[candidates2.length - 1];
|
|
3230
3307
|
}
|
|
3231
|
-
var defaultConfigPath = () =>
|
|
3308
|
+
var defaultConfigPath = () => join13(process.env.XDG_CONFIG_HOME ?? join13(homedir6(), ".config"), "crew", "crew.yaml");
|
|
3232
3309
|
function routeSlug(route2) {
|
|
3233
3310
|
return route2.replace(/\//g, "-");
|
|
3234
3311
|
}
|
|
3235
3312
|
function resolvedPathFor(stateDir, route2) {
|
|
3236
3313
|
const [workspace, project] = splitRoute(route2);
|
|
3237
|
-
return
|
|
3314
|
+
return join13(stateDir, "resolved", workspace, `${project}.json`);
|
|
3238
3315
|
}
|
|
3239
3316
|
function apiKeyPathFor(stateDir, route2) {
|
|
3240
3317
|
const [workspace, project] = splitRoute(route2);
|
|
3241
|
-
return
|
|
3318
|
+
return join13(stateDir, "keys", workspace, `${project}.env`);
|
|
3242
3319
|
}
|
|
3243
3320
|
function splitRoute(route2) {
|
|
3244
3321
|
const parts = route2.split("/");
|
|
@@ -3249,9 +3326,9 @@ function splitRoute(route2) {
|
|
|
3249
3326
|
}
|
|
3250
3327
|
function readResolvedFile(stateDir, route2) {
|
|
3251
3328
|
const path = resolvedPathFor(stateDir, route2);
|
|
3252
|
-
if (!
|
|
3329
|
+
if (!existsSync11(path)) return void 0;
|
|
3253
3330
|
try {
|
|
3254
|
-
return JSON.parse(
|
|
3331
|
+
return JSON.parse(readFileSync12(path, "utf8"));
|
|
3255
3332
|
} catch (e) {
|
|
3256
3333
|
throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
|
|
3257
3334
|
}
|
|
@@ -3312,23 +3389,23 @@ function parseResolved(raw, m, where) {
|
|
|
3312
3389
|
}
|
|
3313
3390
|
function loadConfig(crewHome, configFile, opts = {}) {
|
|
3314
3391
|
const file = configFile ?? findConfigFile(crewHome);
|
|
3315
|
-
if (opts.text === void 0 && !
|
|
3392
|
+
if (opts.text === void 0 && !existsSync11(file)) {
|
|
3316
3393
|
throw new ConfigError(
|
|
3317
3394
|
`no config found. Looked in:
|
|
3318
3395
|
` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
|
|
3319
3396
|
|
|
3320
3397
|
Create the first of those:
|
|
3321
|
-
mkdir -p ${
|
|
3398
|
+
mkdir -p ${dirname5(defaultConfigPath())} && cp ${join13(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
|
|
3322
3399
|
);
|
|
3323
3400
|
}
|
|
3324
3401
|
let raw;
|
|
3325
3402
|
try {
|
|
3326
|
-
raw = parse2(opts.text ??
|
|
3403
|
+
raw = parse2(opts.text ?? readFileSync12(file, "utf8"));
|
|
3327
3404
|
} catch (e) {
|
|
3328
3405
|
throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
|
|
3329
3406
|
}
|
|
3330
3407
|
if (!raw || typeof raw !== "object") throw new ConfigError(`${file} is empty or not a mapping`);
|
|
3331
|
-
const base =
|
|
3408
|
+
const base = dirname5(file);
|
|
3332
3409
|
const missing = new Missing();
|
|
3333
3410
|
rejectUnknownKeys(raw, /* @__PURE__ */ new Set(["ship", "routes"]), "top-level", file);
|
|
3334
3411
|
const shipRaw = raw.ship ?? {};
|
|
@@ -3442,7 +3519,7 @@ Create the first of those:
|
|
|
3442
3519
|
useNvm: shipRaw.useNvm !== false,
|
|
3443
3520
|
nvmSh: shipRaw.nvmSh,
|
|
3444
3521
|
stateDir,
|
|
3445
|
-
logFile: expand(shipRaw.logFile ??
|
|
3522
|
+
logFile: expand(shipRaw.logFile ?? join13(tmpdir(), "crew.log"), base),
|
|
3446
3523
|
userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
|
|
3447
3524
|
maxConcurrentAgents,
|
|
3448
3525
|
maxConcurrentAgentsSet,
|
|
@@ -3622,7 +3699,7 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
|
|
|
3622
3699
|
const hasRepos = Object.keys(repoDirs).length > 0;
|
|
3623
3700
|
const reposBasePath = expand(String(c?.reposBasePath ?? raw.ship?.reposBasePath ?? DEFAULT_REPOS_BASE_PATH), base);
|
|
3624
3701
|
const dirIsCloneBase = !c?.dir && !hasRepos && !Array.isArray(c?.repos);
|
|
3625
|
-
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]) : "";
|
|
3626
3703
|
const weight = c?.weight === void 0 ? void 0 : Number(c.weight);
|
|
3627
3704
|
if (weight !== void 0 && !(Number.isFinite(weight) && weight > 0)) {
|
|
3628
3705
|
missing.add(`${where}.weight must be a positive number (got "${c?.weight}")`);
|
|
@@ -3671,21 +3748,21 @@ function routeForDir(cfg, dir) {
|
|
|
3671
3748
|
}
|
|
3672
3749
|
function locateDir(cfg, dir, commonDirOf) {
|
|
3673
3750
|
const within = (base, target2) => {
|
|
3674
|
-
const b =
|
|
3751
|
+
const b = resolve2(base);
|
|
3675
3752
|
return target2 === b || target2.startsWith(`${b}/`);
|
|
3676
3753
|
};
|
|
3677
3754
|
const find = (target2) => {
|
|
3678
3755
|
for (const route2 of cfg.routes) {
|
|
3679
3756
|
const repo = reposOf(route2).find((x) => within(x.dir, target2));
|
|
3680
|
-
if (repo) return { route: route2, repo, checkout:
|
|
3757
|
+
if (repo) return { route: route2, repo, checkout: resolve2(repo.dir) };
|
|
3681
3758
|
}
|
|
3682
3759
|
return void 0;
|
|
3683
3760
|
};
|
|
3684
|
-
const target =
|
|
3761
|
+
const target = resolve2(dir);
|
|
3685
3762
|
const direct = find(target);
|
|
3686
3763
|
if (direct) return direct;
|
|
3687
3764
|
const common = commonDirOf?.(target);
|
|
3688
|
-
return common ? find(
|
|
3765
|
+
return common ? find(dirname5(resolve2(target, common))) : void 0;
|
|
3689
3766
|
}
|
|
3690
3767
|
function findRoute(cfg, route2) {
|
|
3691
3768
|
if (!route2) {
|
|
@@ -3720,8 +3797,8 @@ function repoIdForName(r, name) {
|
|
|
3720
3797
|
}
|
|
3721
3798
|
function resolveApiKey(r) {
|
|
3722
3799
|
if (r.apiKey) return r.apiKey;
|
|
3723
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
3724
|
-
for (const line of
|
|
3800
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync11(r.apiKeyFile)) {
|
|
3801
|
+
for (const line of readFileSync12(r.apiKeyFile, "utf8").split("\n")) {
|
|
3725
3802
|
const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
|
|
3726
3803
|
if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
3727
3804
|
}
|
|
@@ -3734,9 +3811,9 @@ function resolveApiKey(r) {
|
|
|
3734
3811
|
}
|
|
3735
3812
|
function credentialSource(r) {
|
|
3736
3813
|
if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
|
|
3737
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
3814
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync11(r.apiKeyFile)) {
|
|
3738
3815
|
const re = new RegExp(`^${r.apiKeyVar}=`);
|
|
3739
|
-
if (
|
|
3816
|
+
if (readFileSync12(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
|
|
3740
3817
|
}
|
|
3741
3818
|
return process.env.CREW_API_KEY?.trim() ? "CREW_API_KEY" : "none";
|
|
3742
3819
|
}
|
|
@@ -3792,7 +3869,7 @@ function configuredMembers(rt) {
|
|
|
3792
3869
|
return out;
|
|
3793
3870
|
}
|
|
3794
3871
|
function defaultRepoDir(basePath, workspace, repoName) {
|
|
3795
|
-
return
|
|
3872
|
+
return join13(basePath, workspace, repoName);
|
|
3796
3873
|
}
|
|
3797
3874
|
function reposOf(r) {
|
|
3798
3875
|
const named = Object.entries(r.repos);
|
|
@@ -3831,10 +3908,10 @@ function passengerRepoTargets(r) {
|
|
|
3831
3908
|
return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
|
|
3832
3909
|
}
|
|
3833
3910
|
function passengerCheckoutDir(basePath, workspace, repoName) {
|
|
3834
|
-
return
|
|
3911
|
+
return join13(basePath, workspace, ".passenger-checkouts", repoName);
|
|
3835
3912
|
}
|
|
3836
3913
|
function passengerMirrorArea(ship) {
|
|
3837
|
-
return ship.runner === "sandbox" ?
|
|
3914
|
+
return ship.runner === "sandbox" ? join13(ship.stateDir, "passenger-sources") : void 0;
|
|
3838
3915
|
}
|
|
3839
3916
|
function passengerCheckoutTargets(r, mirrorArea) {
|
|
3840
3917
|
if (!r.hostPassengers) return [];
|
|
@@ -3845,7 +3922,7 @@ function passengerCheckoutTargets(r, mirrorArea) {
|
|
|
3845
3922
|
const forgeHost = t.forgeHost ?? (id ? r.resolved?.repoForgeHosts?.[id] : void 0);
|
|
3846
3923
|
return {
|
|
3847
3924
|
name: t.name,
|
|
3848
|
-
dir: mirrorArea ?
|
|
3925
|
+
dir: mirrorArea ? join13(mirrorArea, workspace, t.name) : passengerCheckoutDir(r.reposBasePath, workspace, t.name),
|
|
3849
3926
|
remote: t.remote ?? (id ? remotes[id] : void 0),
|
|
3850
3927
|
...forgeHost ? { forgeHost } : {}
|
|
3851
3928
|
};
|
|
@@ -4123,9 +4200,9 @@ function recoveredFailureHeadlines(o, route2) {
|
|
|
4123
4200
|
}
|
|
4124
4201
|
|
|
4125
4202
|
// src/install.ts
|
|
4126
|
-
import { dirname as
|
|
4203
|
+
import { dirname as dirname6, join as join14, extname } from "path";
|
|
4127
4204
|
import { homedir as homedir7, userInfo } from "os";
|
|
4128
|
-
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";
|
|
4129
4206
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
4130
4207
|
import { createHash as createHash3 } from "crypto";
|
|
4131
4208
|
|
|
@@ -4137,7 +4214,7 @@ function isCompiledBinary(moduleUrl) {
|
|
|
4137
4214
|
// src/install.ts
|
|
4138
4215
|
function crewInvocation(crewHome) {
|
|
4139
4216
|
if (isCompiledBinary(import.meta.url)) return [process.execPath];
|
|
4140
|
-
return [process.execPath,
|
|
4217
|
+
return [process.execPath, join14(crewHome, "bin", "crew")];
|
|
4141
4218
|
}
|
|
4142
4219
|
var INTERVAL_SECONDS = 120;
|
|
4143
4220
|
function labelFor(crewHome, job) {
|
|
@@ -4168,29 +4245,29 @@ function schedulerLogFor(crewLog, job) {
|
|
|
4168
4245
|
return `${stem}.${suffix}${ext || ".log"}`;
|
|
4169
4246
|
}
|
|
4170
4247
|
function pathFor(ship) {
|
|
4171
|
-
const nodeBinDir =
|
|
4248
|
+
const nodeBinDir = dirname6(process.execPath);
|
|
4172
4249
|
const parts = [nodeBinDir];
|
|
4173
4250
|
if (ship.extraPath) parts.push(ship.extraPath);
|
|
4174
4251
|
parts.push("/usr/bin", "/bin", "/usr/sbin", "/sbin");
|
|
4175
4252
|
return parts.join(":");
|
|
4176
4253
|
}
|
|
4177
4254
|
function findOnPath(bin, searchPath) {
|
|
4178
|
-
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));
|
|
4179
4256
|
for (const candidate of candidates2) {
|
|
4180
4257
|
try {
|
|
4181
4258
|
accessSync2(candidate, constants2.X_OK);
|
|
4182
|
-
if (
|
|
4259
|
+
if (statSync5(candidate).isFile()) return candidate;
|
|
4183
4260
|
} catch {
|
|
4184
4261
|
}
|
|
4185
4262
|
}
|
|
4186
4263
|
return void 0;
|
|
4187
4264
|
}
|
|
4188
|
-
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(":");
|
|
4189
4266
|
function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
|
|
4190
4267
|
const schedulerPath = pathFor(ship);
|
|
4191
4268
|
if (findOnPath("docker", schedulerPath)) return void 0;
|
|
4192
4269
|
const inShell = findOnPath("docker", shellPath);
|
|
4193
|
-
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\``;
|
|
4194
4271
|
}
|
|
4195
4272
|
var USER_INSTALL = { root: false, user: "" };
|
|
4196
4273
|
function installContext(env = process.env) {
|
|
@@ -4200,7 +4277,7 @@ function installContext(env = process.env) {
|
|
|
4200
4277
|
}
|
|
4201
4278
|
var SYSTEM_UNIT_DIR = "/etc/systemd/system";
|
|
4202
4279
|
function systemUnitPathFor(label, suffix = "service") {
|
|
4203
|
-
return
|
|
4280
|
+
return join14(SYSTEM_UNIT_DIR, `${label}.${suffix}`);
|
|
4204
4281
|
}
|
|
4205
4282
|
function unitEnvironment(ship) {
|
|
4206
4283
|
return [["PATH", pathFor(ship)], ...Object.entries(ship.env ?? {})];
|
|
@@ -4221,7 +4298,7 @@ function xmlEscape(s) {
|
|
|
4221
4298
|
}
|
|
4222
4299
|
function planLaunchd(ship, crewHome, job) {
|
|
4223
4300
|
const label = labelFor(crewHome, job);
|
|
4224
|
-
const unitPath =
|
|
4301
|
+
const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4225
4302
|
const crewLog = ship.logFile;
|
|
4226
4303
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4227
4304
|
const persistent = job === "run";
|
|
@@ -4275,7 +4352,7 @@ ${scheduleKeys}
|
|
|
4275
4352
|
}
|
|
4276
4353
|
function planSystemd(ship, crewHome, job, ctx) {
|
|
4277
4354
|
const label = labelFor(crewHome, job);
|
|
4278
|
-
const unitDir = ctx.root ? SYSTEM_UNIT_DIR :
|
|
4355
|
+
const unitDir = ctx.root ? SYSTEM_UNIT_DIR : join14(homedir7(), ".config", "systemd", "user");
|
|
4279
4356
|
const ctl = ctx.root ? ["systemctl"] : ["systemctl", "--user"];
|
|
4280
4357
|
const userLine = ctx.root && ctx.user && ctx.user !== "root" ? `User=${ctx.user}
|
|
4281
4358
|
` : "";
|
|
@@ -4284,7 +4361,7 @@ function planSystemd(ship, crewHome, job, ctx) {
|
|
|
4284
4361
|
lingerCommand: ["loginctl", "enable-linger", ctx.user],
|
|
4285
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`
|
|
4286
4363
|
};
|
|
4287
|
-
const servicePath =
|
|
4364
|
+
const servicePath = join14(unitDir, `${label}.service`);
|
|
4288
4365
|
const crewLog = ship.logFile;
|
|
4289
4366
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4290
4367
|
const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
|
|
@@ -4325,7 +4402,7 @@ WantedBy=${wantedBy}
|
|
|
4325
4402
|
...linger
|
|
4326
4403
|
};
|
|
4327
4404
|
}
|
|
4328
|
-
const timerPath =
|
|
4405
|
+
const timerPath = join14(unitDir, `${label}.timer`);
|
|
4329
4406
|
const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
|
|
4330
4407
|
[Unit]
|
|
4331
4408
|
Description=${description}
|
|
@@ -4400,7 +4477,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run", ctx = US
|
|
|
4400
4477
|
function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
4401
4478
|
const label = labelFor(crewHome, job);
|
|
4402
4479
|
if (hostShip === "macos") {
|
|
4403
|
-
const unitPath =
|
|
4480
|
+
const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4404
4481
|
return {
|
|
4405
4482
|
mechanism: "launchd",
|
|
4406
4483
|
unitPaths: [unitPath],
|
|
@@ -4410,20 +4487,20 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4410
4487
|
};
|
|
4411
4488
|
}
|
|
4412
4489
|
if (hostShip === "linux" && hasSystemd) {
|
|
4413
|
-
const system =
|
|
4414
|
-
const unitDir = system ? SYSTEM_UNIT_DIR :
|
|
4490
|
+
const system = existsSync12(systemUnitPathFor(label));
|
|
4491
|
+
const unitDir = system ? SYSTEM_UNIT_DIR : join14(homedir7(), ".config", "systemd", "user");
|
|
4415
4492
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
4416
4493
|
if (job === "run") {
|
|
4417
4494
|
return {
|
|
4418
4495
|
mechanism: "systemd",
|
|
4419
|
-
unitPaths: [
|
|
4496
|
+
unitPaths: [join14(unitDir, `${label}.service`)],
|
|
4420
4497
|
crontabLine: null,
|
|
4421
4498
|
unloadCommands: [[...ctl, "disable", "--now", `${label}.service`]]
|
|
4422
4499
|
};
|
|
4423
4500
|
}
|
|
4424
4501
|
return {
|
|
4425
4502
|
mechanism: "systemd",
|
|
4426
|
-
unitPaths: [
|
|
4503
|
+
unitPaths: [join14(unitDir, `${label}.service`), join14(unitDir, `${label}.timer`)],
|
|
4427
4504
|
crontabLine: null,
|
|
4428
4505
|
unloadCommands: [[...ctl, "disable", "--now", `${label}.timer`]]
|
|
4429
4506
|
};
|
|
@@ -4441,7 +4518,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4441
4518
|
function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
4442
4519
|
const label = labelFor(crewHome, "run");
|
|
4443
4520
|
if (hostShip === "macos") {
|
|
4444
|
-
const unitPath =
|
|
4521
|
+
const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4445
4522
|
return {
|
|
4446
4523
|
mechanism: "launchd",
|
|
4447
4524
|
startCommand: ["launchctl", "load", unitPath],
|
|
@@ -4451,7 +4528,7 @@ function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
|
4451
4528
|
}
|
|
4452
4529
|
if (hostShip === "linux" && hasSystemd) {
|
|
4453
4530
|
const unit = `${label}.service`;
|
|
4454
|
-
const ctl =
|
|
4531
|
+
const ctl = existsSync12(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
|
|
4455
4532
|
return {
|
|
4456
4533
|
mechanism: "systemd",
|
|
4457
4534
|
startCommand: [...ctl, "start", unit],
|
|
@@ -4483,10 +4560,10 @@ async function applyInstall(plan, crewHome, dryRun, log) {
|
|
|
4483
4560
|
log.emit(`would write ${path}`);
|
|
4484
4561
|
continue;
|
|
4485
4562
|
}
|
|
4486
|
-
const before =
|
|
4563
|
+
const before = existsSync12(path) ? readFileSync13(path, "utf8") : null;
|
|
4487
4564
|
if (before !== plan.unitContent[path]) changed2 = true;
|
|
4488
|
-
|
|
4489
|
-
|
|
4565
|
+
mkdirSync8(dirname6(path), { recursive: true });
|
|
4566
|
+
writeFileSync9(path, plan.unitContent[path]);
|
|
4490
4567
|
log.emit(`wrote ${path}`);
|
|
4491
4568
|
}
|
|
4492
4569
|
if (plan.crontabLine) {
|
|
@@ -4550,7 +4627,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
4550
4627
|
}
|
|
4551
4628
|
}
|
|
4552
4629
|
for (const path of plan.unitPaths) {
|
|
4553
|
-
if (!
|
|
4630
|
+
if (!existsSync12(path)) continue;
|
|
4554
4631
|
if (dryRun) {
|
|
4555
4632
|
log.emit(`would remove ${path}`);
|
|
4556
4633
|
continue;
|
|
@@ -4569,7 +4646,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
4569
4646
|
}
|
|
4570
4647
|
var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
|
|
4571
4648
|
function launchAgentsDir() {
|
|
4572
|
-
return
|
|
4649
|
+
return join14(homedir7(), "Library", "LaunchAgents");
|
|
4573
4650
|
}
|
|
4574
4651
|
function xmlUnescape(s) {
|
|
4575
4652
|
return s.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
@@ -4586,25 +4663,25 @@ function loadedCrewLabels() {
|
|
|
4586
4663
|
return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
|
|
4587
4664
|
}
|
|
4588
4665
|
function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
4589
|
-
const files =
|
|
4666
|
+
const files = existsSync12(dir) ? readdirSync3(dir) : [];
|
|
4590
4667
|
const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
|
|
4591
|
-
const plistPath =
|
|
4668
|
+
const plistPath = join14(dir, f);
|
|
4592
4669
|
let program = null;
|
|
4593
4670
|
try {
|
|
4594
|
-
program = programFromPlist(
|
|
4671
|
+
program = programFromPlist(readFileSync13(plistPath, "utf8"));
|
|
4595
4672
|
} catch {
|
|
4596
4673
|
}
|
|
4597
4674
|
return {
|
|
4598
4675
|
label: f.slice(0, -".plist".length),
|
|
4599
4676
|
plistPath,
|
|
4600
4677
|
program,
|
|
4601
|
-
programExists: program !== null &&
|
|
4678
|
+
programExists: program !== null && existsSync12(program)
|
|
4602
4679
|
};
|
|
4603
4680
|
});
|
|
4604
4681
|
const seen = new Set(units.map((u) => u.label));
|
|
4605
4682
|
for (const label of [...loaded].sort()) {
|
|
4606
4683
|
if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
|
|
4607
|
-
units.push({ label, plistPath:
|
|
4684
|
+
units.push({ label, plistPath: join14(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
|
|
4608
4685
|
}
|
|
4609
4686
|
return units;
|
|
4610
4687
|
}
|
|
@@ -4617,9 +4694,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
|
|
|
4617
4694
|
log.emit(`would unload and remove ${unit.label}`);
|
|
4618
4695
|
return;
|
|
4619
4696
|
}
|
|
4620
|
-
if (
|
|
4697
|
+
if (existsSync12(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
|
|
4621
4698
|
spawnSync4("launchctl", ["remove", unit.label], { encoding: "utf8" });
|
|
4622
|
-
if (
|
|
4699
|
+
if (existsSync12(unit.plistPath)) unlinkSync(unit.plistPath);
|
|
4623
4700
|
log.emit(`removed ${unit.label} (${unit.plistPath})`);
|
|
4624
4701
|
}
|
|
4625
4702
|
function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
|
|
@@ -4697,24 +4774,24 @@ function detectSystemd() {
|
|
|
4697
4774
|
}
|
|
4698
4775
|
|
|
4699
4776
|
// src/ship-attention.ts
|
|
4700
|
-
import { existsSync as
|
|
4777
|
+
import { existsSync as existsSync14, statfsSync } from "fs";
|
|
4701
4778
|
|
|
4702
4779
|
// src/git.ts
|
|
4703
|
-
import { execFileSync as
|
|
4780
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
4704
4781
|
import {
|
|
4705
|
-
existsSync as
|
|
4706
|
-
mkdirSync as
|
|
4782
|
+
existsSync as existsSync13,
|
|
4783
|
+
mkdirSync as mkdirSync9,
|
|
4707
4784
|
openSync as openSync2,
|
|
4708
4785
|
closeSync as closeSync2,
|
|
4709
4786
|
writeSync,
|
|
4710
|
-
statSync as
|
|
4787
|
+
statSync as statSync6,
|
|
4711
4788
|
unlinkSync as unlinkSync2,
|
|
4712
|
-
readFileSync as
|
|
4713
|
-
writeFileSync as
|
|
4789
|
+
readFileSync as readFileSync14,
|
|
4790
|
+
writeFileSync as writeFileSync10,
|
|
4714
4791
|
readdirSync as readdirSync4,
|
|
4715
4792
|
rmSync as rmSync8
|
|
4716
4793
|
} from "fs";
|
|
4717
|
-
import { dirname as
|
|
4794
|
+
import { dirname as dirname7, basename as basename5, join as join15 } from "path";
|
|
4718
4795
|
var GitError = class extends Error {
|
|
4719
4796
|
};
|
|
4720
4797
|
function streamText(x) {
|
|
@@ -4722,7 +4799,7 @@ function streamText(x) {
|
|
|
4722
4799
|
}
|
|
4723
4800
|
function gitRaw(cwd, args) {
|
|
4724
4801
|
try {
|
|
4725
|
-
return
|
|
4802
|
+
return execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
4726
4803
|
} catch (e) {
|
|
4727
4804
|
const err3 = e;
|
|
4728
4805
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -4731,7 +4808,7 @@ function gitRaw(cwd, args) {
|
|
|
4731
4808
|
}
|
|
4732
4809
|
function git(cwd, args) {
|
|
4733
4810
|
try {
|
|
4734
|
-
return
|
|
4811
|
+
return execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
4735
4812
|
} catch (e) {
|
|
4736
4813
|
const err3 = e;
|
|
4737
4814
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -4861,7 +4938,7 @@ function worktreeForNumber(cwd, num) {
|
|
|
4861
4938
|
return worktrees(cwd).find((w) => re.test(basename5(w.path))) ?? null;
|
|
4862
4939
|
}
|
|
4863
4940
|
var commitBodies = (cwd, range) => gitOk(cwd, ["log", range, "--format=%B"]) ?? "";
|
|
4864
|
-
var
|
|
4941
|
+
var resolve3 = (cwd, ref) => gitOk(cwd, ["rev-parse", "-q", "--verify", `${ref}^{commit}`]);
|
|
4865
4942
|
var countCommits = (cwd, range) => Number.parseInt(gitOk(cwd, ["rev-list", "--count", range]) ?? "0", 10);
|
|
4866
4943
|
function latestReleaseTag(cwd, pattern = "v*", ref = "HEAD") {
|
|
4867
4944
|
return gitOk(cwd, ["describe", "--tags", "--abbrev=0", "--match", pattern, ref]);
|
|
@@ -4870,7 +4947,7 @@ function firstReleaseTagContaining(cwd, sha, pattern = "v*") {
|
|
|
4870
4947
|
const out = gitOk(cwd, ["tag", "--contains", sha, "--list", pattern, "--sort=creatordate"]);
|
|
4871
4948
|
return out?.split("\n").map((l) => l.trim()).find(Boolean) ?? null;
|
|
4872
4949
|
}
|
|
4873
|
-
var tagCommit = (cwd, tag) =>
|
|
4950
|
+
var tagCommit = (cwd, tag) => resolve3(cwd, tag);
|
|
4874
4951
|
function createReleaseTag(cwd, tag, sha, message) {
|
|
4875
4952
|
git(cwd, ["tag", "-a", tag, sha, "-m", message]);
|
|
4876
4953
|
}
|
|
@@ -4902,9 +4979,9 @@ function keyCommitsInRange(cwd, key, range) {
|
|
|
4902
4979
|
}
|
|
4903
4980
|
function patchIdOf(cwd, from, to) {
|
|
4904
4981
|
try {
|
|
4905
|
-
const diff =
|
|
4982
|
+
const diff = execFileSync3("git", ["diff", from, to], { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
|
|
4906
4983
|
if (!diff) return null;
|
|
4907
|
-
const out =
|
|
4984
|
+
const out = execFileSync3("git", ["patch-id", "--stable"], { cwd, encoding: "utf8", input: diff });
|
|
4908
4985
|
return out.split(" ")[0]?.trim() || null;
|
|
4909
4986
|
} catch {
|
|
4910
4987
|
return null;
|
|
@@ -5094,7 +5171,7 @@ function recoverStrandedReleaseCheckouts(cwd) {
|
|
|
5094
5171
|
if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
|
|
5095
5172
|
else if (line.startsWith("branch refs/heads/crew/release-") && path) {
|
|
5096
5173
|
gitOk(cwd, ["worktree", "remove", "--force", path]);
|
|
5097
|
-
const root =
|
|
5174
|
+
const root = dirname7(path);
|
|
5098
5175
|
if (basename5(root).startsWith("crew-release-")) rmSync8(root, { recursive: true, force: true });
|
|
5099
5176
|
}
|
|
5100
5177
|
}
|
|
@@ -5112,11 +5189,11 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
|
|
|
5112
5189
|
}
|
|
5113
5190
|
for (const name of names) {
|
|
5114
5191
|
if (!name.startsWith("crew-release-")) continue;
|
|
5115
|
-
const root =
|
|
5192
|
+
const root = join15(tmp, name);
|
|
5116
5193
|
try {
|
|
5117
|
-
const st =
|
|
5194
|
+
const st = statSync6(root);
|
|
5118
5195
|
if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
|
|
5119
|
-
if (
|
|
5196
|
+
if (existsSync13(join15(root, "checkout"))) continue;
|
|
5120
5197
|
rmSync8(root, { recursive: true, force: true });
|
|
5121
5198
|
removed.push(name);
|
|
5122
5199
|
} catch {
|
|
@@ -5168,16 +5245,16 @@ function originUrlOf(dir, remoteName) {
|
|
|
5168
5245
|
}
|
|
5169
5246
|
}
|
|
5170
5247
|
function ensureRepoCheckout(dir, remote, opts = {}) {
|
|
5171
|
-
if (
|
|
5248
|
+
if (existsSync13(`${dir}/.git`)) return false;
|
|
5172
5249
|
if (!remote) {
|
|
5173
5250
|
throw new GitError(
|
|
5174
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`
|
|
5175
5252
|
);
|
|
5176
5253
|
}
|
|
5177
|
-
|
|
5254
|
+
mkdirSync9(dirname7(dir), { recursive: true });
|
|
5178
5255
|
const url = cloneUrlFor(remote, opts);
|
|
5179
5256
|
try {
|
|
5180
|
-
|
|
5257
|
+
execFileSync3("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
5181
5258
|
} catch (e) {
|
|
5182
5259
|
const err3 = e;
|
|
5183
5260
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -5199,7 +5276,7 @@ function sleepSync(ms3) {
|
|
|
5199
5276
|
}
|
|
5200
5277
|
function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
5201
5278
|
const lockPath = checkoutLockPath(dir);
|
|
5202
|
-
|
|
5279
|
+
mkdirSync9(dirname7(lockPath), { recursive: true });
|
|
5203
5280
|
const deadline = Date.now() + timeoutMs;
|
|
5204
5281
|
for (; ; ) {
|
|
5205
5282
|
try {
|
|
@@ -5217,7 +5294,7 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
|
5217
5294
|
const err3 = e;
|
|
5218
5295
|
if (err3.code !== "EEXIST") throw err3;
|
|
5219
5296
|
try {
|
|
5220
|
-
if (Date.now() -
|
|
5297
|
+
if (Date.now() - statSync6(lockPath).mtimeMs > CHECKOUT_LOCK_STALE_MS) {
|
|
5221
5298
|
try {
|
|
5222
5299
|
unlinkSync2(lockPath);
|
|
5223
5300
|
} catch {
|
|
@@ -5236,17 +5313,17 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
|
5236
5313
|
function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {}) {
|
|
5237
5314
|
const release = acquireCheckoutLock(dir);
|
|
5238
5315
|
try {
|
|
5239
|
-
if (!
|
|
5316
|
+
if (!existsSync13(`${dir}/.git`) && githubRemote) {
|
|
5240
5317
|
const url = cloneUrlFor(githubRemote, clone);
|
|
5241
5318
|
const marker = cloneFailureMarker(dir);
|
|
5242
|
-
if (
|
|
5243
|
-
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 };
|
|
5244
5321
|
}
|
|
5245
5322
|
try {
|
|
5246
5323
|
ensureRepoCheckout(dir, githubRemote, clone);
|
|
5247
5324
|
} catch (e) {
|
|
5248
5325
|
if (!(e instanceof GitError)) throw e;
|
|
5249
|
-
|
|
5326
|
+
writeFileSync10(marker, `${url}
|
|
5250
5327
|
${e.message}`);
|
|
5251
5328
|
return { action: "clone-failed", detail: e.message, repeat: false };
|
|
5252
5329
|
}
|
|
@@ -5472,7 +5549,7 @@ function noteHeldWorktree(state, h, nowMs = Date.now()) {
|
|
|
5472
5549
|
function reconcileHeldWorktrees(state, repo, log, stillRefused) {
|
|
5473
5550
|
for (const i of state.shipAttention().previous()) {
|
|
5474
5551
|
if (i.kind !== "worktree_held" || !i.held || i.held.repo !== repo) continue;
|
|
5475
|
-
const gone = !
|
|
5552
|
+
const gone = !existsSync14(i.held.path);
|
|
5476
5553
|
if (!gone && !(stillRefused && !stillRefused.has(i.held.path))) continue;
|
|
5477
5554
|
clearShipAttention(state, i.key);
|
|
5478
5555
|
log.emit(
|
|
@@ -5600,13 +5677,13 @@ function attentionProblems(items, already) {
|
|
|
5600
5677
|
// src/sandbox-image.ts
|
|
5601
5678
|
import { createHash as createHash6 } from "crypto";
|
|
5602
5679
|
import { spawn as spawn4 } from "child_process";
|
|
5603
|
-
import { execFileSync as
|
|
5604
|
-
import { mkdirSync as
|
|
5605
|
-
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";
|
|
5606
5683
|
import { tmpdir as tmpdir3 } from "os";
|
|
5607
5684
|
|
|
5608
5685
|
// src/passenger-containers.ts
|
|
5609
|
-
import { execFileSync as
|
|
5686
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
5610
5687
|
import { createHash as createHash5 } from "crypto";
|
|
5611
5688
|
|
|
5612
5689
|
// src/sandbox-cache.ts
|
|
@@ -5615,32 +5692,32 @@ import { promisify as promisify2 } from "util";
|
|
|
5615
5692
|
|
|
5616
5693
|
// src/deprovision-host.ts
|
|
5617
5694
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
5618
|
-
import { existsSync as
|
|
5695
|
+
import { existsSync as existsSync25, readdirSync as readdirSync11 } from "fs";
|
|
5619
5696
|
|
|
5620
5697
|
// src/interrupted.ts
|
|
5621
|
-
import { existsSync as
|
|
5622
|
-
import { join as
|
|
5698
|
+
import { existsSync as existsSync23, readdirSync as readdirSync9, readFileSync as readFileSync21 } from "fs";
|
|
5699
|
+
import { join as join25 } from "path";
|
|
5623
5700
|
|
|
5624
5701
|
// src/agent.ts
|
|
5625
5702
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
5626
|
-
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";
|
|
5627
5704
|
import { StringDecoder } from "string_decoder";
|
|
5628
5705
|
import { finished } from "stream/promises";
|
|
5629
|
-
import { join as
|
|
5706
|
+
import { join as join24, dirname as dirname10, basename as basename9 } from "path";
|
|
5630
5707
|
import { createHash as createHash4 } from "crypto";
|
|
5631
5708
|
|
|
5632
5709
|
// src/state.ts
|
|
5633
|
-
import { readFileSync as
|
|
5634
|
-
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";
|
|
5635
5712
|
var EPOCH = "1970-01-01T00:00:00.000Z";
|
|
5636
5713
|
var State = class _State {
|
|
5637
5714
|
dir;
|
|
5638
5715
|
constructor(dir) {
|
|
5639
5716
|
this.dir = dir;
|
|
5640
|
-
|
|
5717
|
+
mkdirSync10(dir, { recursive: true });
|
|
5641
5718
|
}
|
|
5642
5719
|
path(...parts) {
|
|
5643
|
-
return
|
|
5720
|
+
return join16(this.dir, ...parts);
|
|
5644
5721
|
}
|
|
5645
5722
|
/**
|
|
5646
5723
|
* The timestamp every "new since last poll" check is measured against.
|
|
@@ -5651,14 +5728,14 @@ var State = class _State {
|
|
|
5651
5728
|
*/
|
|
5652
5729
|
watermark() {
|
|
5653
5730
|
try {
|
|
5654
|
-
const v =
|
|
5731
|
+
const v = readFileSync15(this.path(".poll-watermark"), "utf8").trim();
|
|
5655
5732
|
return v || EPOCH;
|
|
5656
5733
|
} catch {
|
|
5657
5734
|
return EPOCH;
|
|
5658
5735
|
}
|
|
5659
5736
|
}
|
|
5660
5737
|
setWatermark(at) {
|
|
5661
|
-
|
|
5738
|
+
writeFileSync11(this.path(".poll-watermark"), `${at}
|
|
5662
5739
|
`);
|
|
5663
5740
|
}
|
|
5664
5741
|
/**
|
|
@@ -5676,16 +5753,16 @@ var State = class _State {
|
|
|
5676
5753
|
}
|
|
5677
5754
|
/** Whole-crew pause, and the per-role sentinels one level down. */
|
|
5678
5755
|
isPaused() {
|
|
5679
|
-
return
|
|
5756
|
+
return existsSync15(this.path(".crew-paused"));
|
|
5680
5757
|
}
|
|
5681
5758
|
isRolePaused(role) {
|
|
5682
|
-
return
|
|
5759
|
+
return existsSync15(this.path(`.role-paused-${role}`));
|
|
5683
5760
|
}
|
|
5684
5761
|
pausedRoles(roles) {
|
|
5685
5762
|
return new Set(roles.filter((r) => this.isRolePaused(r)));
|
|
5686
5763
|
}
|
|
5687
5764
|
pause(role) {
|
|
5688
|
-
|
|
5765
|
+
writeFileSync11(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
|
|
5689
5766
|
}
|
|
5690
5767
|
resume(role) {
|
|
5691
5768
|
rmSync9(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), { force: true });
|
|
@@ -5696,10 +5773,10 @@ var State = class _State {
|
|
|
5696
5773
|
* while agents keep working.
|
|
5697
5774
|
*/
|
|
5698
5775
|
isReleasePaused() {
|
|
5699
|
-
return
|
|
5776
|
+
return existsSync15(this.path(".release-paused"));
|
|
5700
5777
|
}
|
|
5701
5778
|
setReleasePaused(paused) {
|
|
5702
|
-
if (paused)
|
|
5779
|
+
if (paused) writeFileSync11(this.path(".release-paused"), "");
|
|
5703
5780
|
else rmSync9(this.path(".release-paused"), { force: true });
|
|
5704
5781
|
}
|
|
5705
5782
|
/**
|
|
@@ -5737,7 +5814,7 @@ var State = class _State {
|
|
|
5737
5814
|
const name = _State.safe(rawName);
|
|
5738
5815
|
const file = this.path(`.${name}.lock`);
|
|
5739
5816
|
try {
|
|
5740
|
-
const existing = Number.parseInt(
|
|
5817
|
+
const existing = Number.parseInt(readFileSync15(file, "utf8").trim(), 10);
|
|
5741
5818
|
if (Number.isFinite(existing) && existing > 0) {
|
|
5742
5819
|
try {
|
|
5743
5820
|
process.kill(existing, 0);
|
|
@@ -5747,7 +5824,7 @@ var State = class _State {
|
|
|
5747
5824
|
}
|
|
5748
5825
|
} catch {
|
|
5749
5826
|
}
|
|
5750
|
-
|
|
5827
|
+
writeFileSync11(file, `${process.pid}
|
|
5751
5828
|
`);
|
|
5752
5829
|
return {
|
|
5753
5830
|
ok: true,
|
|
@@ -5775,7 +5852,7 @@ var State = class _State {
|
|
|
5775
5852
|
if (!/^\.release-.+\.lock$/.test(n)) continue;
|
|
5776
5853
|
let pid = 0;
|
|
5777
5854
|
try {
|
|
5778
|
-
pid = Number.parseInt(
|
|
5855
|
+
pid = Number.parseInt(readFileSync15(this.path(n), "utf8").trim(), 10);
|
|
5779
5856
|
} catch {
|
|
5780
5857
|
continue;
|
|
5781
5858
|
}
|
|
@@ -5861,7 +5938,7 @@ var State = class _State {
|
|
|
5861
5938
|
ticketInFlight(recordId) {
|
|
5862
5939
|
const file = this.path(`.${_State.safe(`crew-ticket-${recordId}`)}.lock`);
|
|
5863
5940
|
try {
|
|
5864
|
-
const pid = Number.parseInt(
|
|
5941
|
+
const pid = Number.parseInt(readFileSync15(file, "utf8").trim(), 10);
|
|
5865
5942
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
5866
5943
|
process.kill(pid, 0);
|
|
5867
5944
|
return true;
|
|
@@ -5895,7 +5972,7 @@ var State = class _State {
|
|
|
5895
5972
|
const heldReported = `.release-held-${suffix}`;
|
|
5896
5973
|
const read = (f) => {
|
|
5897
5974
|
try {
|
|
5898
|
-
return
|
|
5975
|
+
return readFileSync15(this.path(f), "utf8").trim() || null;
|
|
5899
5976
|
} catch {
|
|
5900
5977
|
return null;
|
|
5901
5978
|
}
|
|
@@ -5904,7 +5981,7 @@ var State = class _State {
|
|
|
5904
5981
|
blockedCount: () => Number.parseInt(read(blocked) ?? "", 10) || 0,
|
|
5905
5982
|
noteBlocked: () => {
|
|
5906
5983
|
const n = (Number.parseInt(read(blocked) ?? "", 10) || 0) + 1;
|
|
5907
|
-
|
|
5984
|
+
writeFileSync11(this.path(blocked), `${n}
|
|
5908
5985
|
`);
|
|
5909
5986
|
return n;
|
|
5910
5987
|
},
|
|
@@ -5921,7 +5998,7 @@ var State = class _State {
|
|
|
5921
5998
|
*/
|
|
5922
5999
|
deployFailedSha: () => read(failed),
|
|
5923
6000
|
noteDeployFailed: (sha) => {
|
|
5924
|
-
|
|
6001
|
+
writeFileSync11(this.path(failed), `${sha}
|
|
5925
6002
|
`);
|
|
5926
6003
|
},
|
|
5927
6004
|
clearDeployFailed: () => {
|
|
@@ -5935,7 +6012,7 @@ var State = class _State {
|
|
|
5935
6012
|
noteTestGateFailed: (sha) => {
|
|
5936
6013
|
const [prev, n] = (read(gateFailed) ?? "").split(" ");
|
|
5937
6014
|
const next = prev === sha ? (Number.parseInt(n ?? "", 10) || 0) + 1 : 1;
|
|
5938
|
-
|
|
6015
|
+
writeFileSync11(this.path(gateFailed), `${sha} ${next}
|
|
5939
6016
|
`);
|
|
5940
6017
|
return next;
|
|
5941
6018
|
},
|
|
@@ -5945,13 +6022,13 @@ var State = class _State {
|
|
|
5945
6022
|
/** Whether the board was already told about this head's red gate. */
|
|
5946
6023
|
heldReported: () => {
|
|
5947
6024
|
try {
|
|
5948
|
-
return
|
|
6025
|
+
return readFileSync15(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
|
|
5949
6026
|
} catch {
|
|
5950
6027
|
return null;
|
|
5951
6028
|
}
|
|
5952
6029
|
},
|
|
5953
6030
|
noteHeldReported: (signature) => {
|
|
5954
|
-
|
|
6031
|
+
writeFileSync11(this.path(heldReported), `${signature}
|
|
5955
6032
|
`);
|
|
5956
6033
|
},
|
|
5957
6034
|
clearHeldReported: () => {
|
|
@@ -5959,7 +6036,7 @@ var State = class _State {
|
|
|
5959
6036
|
},
|
|
5960
6037
|
testGateReported: (sha) => read(gateReported) === sha,
|
|
5961
6038
|
noteTestGateReported: (sha) => {
|
|
5962
|
-
|
|
6039
|
+
writeFileSync11(this.path(gateReported), `${sha}
|
|
5963
6040
|
`);
|
|
5964
6041
|
}
|
|
5965
6042
|
};
|
|
@@ -5982,7 +6059,7 @@ var State = class _State {
|
|
|
5982
6059
|
const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
|
|
5983
6060
|
const read = (f) => {
|
|
5984
6061
|
try {
|
|
5985
|
-
return
|
|
6062
|
+
return readFileSync15(this.path(f), "utf8").trim() || null;
|
|
5986
6063
|
} catch {
|
|
5987
6064
|
return null;
|
|
5988
6065
|
}
|
|
@@ -6015,18 +6092,18 @@ var State = class _State {
|
|
|
6015
6092
|
clear2();
|
|
6016
6093
|
return;
|
|
6017
6094
|
}
|
|
6018
|
-
|
|
6095
|
+
writeFileSync11(this.path(streakFile), `${this.readInt(streakFile) + 1}
|
|
6019
6096
|
`);
|
|
6020
6097
|
const prior = read(ticketFile);
|
|
6021
6098
|
if (topTicket && topTicket === prior) {
|
|
6022
|
-
|
|
6099
|
+
writeFileSync11(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
|
|
6023
6100
|
`);
|
|
6024
6101
|
} else if (topTicket) {
|
|
6025
|
-
|
|
6102
|
+
writeFileSync11(this.path(ticketFile), `${topTicket}
|
|
6026
6103
|
`);
|
|
6027
|
-
|
|
6104
|
+
writeFileSync11(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
6028
6105
|
`);
|
|
6029
|
-
|
|
6106
|
+
writeFileSync11(this.path(ticketStreakFile), "1\n");
|
|
6030
6107
|
}
|
|
6031
6108
|
}
|
|
6032
6109
|
};
|
|
@@ -6047,14 +6124,14 @@ var State = class _State {
|
|
|
6047
6124
|
return {
|
|
6048
6125
|
previous: () => {
|
|
6049
6126
|
try {
|
|
6050
|
-
const parsed = JSON.parse(
|
|
6127
|
+
const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
|
|
6051
6128
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
6052
6129
|
} catch {
|
|
6053
6130
|
return {};
|
|
6054
6131
|
}
|
|
6055
6132
|
},
|
|
6056
6133
|
persist: (next) => {
|
|
6057
|
-
|
|
6134
|
+
writeFileSync11(this.path(file), JSON.stringify(next));
|
|
6058
6135
|
}
|
|
6059
6136
|
};
|
|
6060
6137
|
}
|
|
@@ -6070,14 +6147,14 @@ var State = class _State {
|
|
|
6070
6147
|
return {
|
|
6071
6148
|
previous: () => {
|
|
6072
6149
|
try {
|
|
6073
|
-
const parsed = JSON.parse(
|
|
6150
|
+
const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
|
|
6074
6151
|
return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : [];
|
|
6075
6152
|
} catch {
|
|
6076
6153
|
return [];
|
|
6077
6154
|
}
|
|
6078
6155
|
},
|
|
6079
6156
|
persist: (next) => {
|
|
6080
|
-
|
|
6157
|
+
writeFileSync11(this.path(file), JSON.stringify(next));
|
|
6081
6158
|
}
|
|
6082
6159
|
};
|
|
6083
6160
|
}
|
|
@@ -6091,14 +6168,14 @@ var State = class _State {
|
|
|
6091
6168
|
return {
|
|
6092
6169
|
previous: () => {
|
|
6093
6170
|
try {
|
|
6094
|
-
const parsed = JSON.parse(
|
|
6171
|
+
const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
|
|
6095
6172
|
return Array.isArray(parsed) ? parsed : [];
|
|
6096
6173
|
} catch {
|
|
6097
6174
|
return [];
|
|
6098
6175
|
}
|
|
6099
6176
|
},
|
|
6100
6177
|
persist: (next) => {
|
|
6101
|
-
|
|
6178
|
+
writeFileSync11(this.path(file), JSON.stringify(next));
|
|
6102
6179
|
}
|
|
6103
6180
|
};
|
|
6104
6181
|
}
|
|
@@ -6111,11 +6188,11 @@ var State = class _State {
|
|
|
6111
6188
|
const file = ".release-last-run";
|
|
6112
6189
|
return {
|
|
6113
6190
|
touch: () => {
|
|
6114
|
-
|
|
6191
|
+
writeFileSync11(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
|
|
6115
6192
|
},
|
|
6116
6193
|
at: () => {
|
|
6117
6194
|
try {
|
|
6118
|
-
return
|
|
6195
|
+
return statSync7(this.path(file)).mtimeMs;
|
|
6119
6196
|
} catch {
|
|
6120
6197
|
return null;
|
|
6121
6198
|
}
|
|
@@ -6124,7 +6201,7 @@ var State = class _State {
|
|
|
6124
6201
|
}
|
|
6125
6202
|
readInt(file) {
|
|
6126
6203
|
try {
|
|
6127
|
-
return Number.parseInt(
|
|
6204
|
+
return Number.parseInt(readFileSync15(this.path(file), "utf8").trim(), 10) || 0;
|
|
6128
6205
|
} catch {
|
|
6129
6206
|
return 0;
|
|
6130
6207
|
}
|
|
@@ -6665,8 +6742,8 @@ var PERISCOPE_COLUMNS = [
|
|
|
6665
6742
|
];
|
|
6666
6743
|
|
|
6667
6744
|
// src/agent-log.ts
|
|
6668
|
-
import { existsSync as
|
|
6669
|
-
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";
|
|
6670
6747
|
|
|
6671
6748
|
// src/pending-backoff.ts
|
|
6672
6749
|
var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
|
|
@@ -6838,20 +6915,20 @@ async function addRunToTicket(target, t, recordId, run3) {
|
|
|
6838
6915
|
};
|
|
6839
6916
|
}
|
|
6840
6917
|
}
|
|
6841
|
-
var queueDir = (stateDir) =>
|
|
6918
|
+
var queueDir = (stateDir) => join17(stateDir, "pending-agent-logs");
|
|
6842
6919
|
function queueAgentRun(stateDir, name, q2) {
|
|
6843
|
-
|
|
6844
|
-
|
|
6920
|
+
mkdirSync11(queueDir(stateDir), { recursive: true });
|
|
6921
|
+
writeFileSync12(join17(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
6845
6922
|
}
|
|
6846
6923
|
async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
6847
6924
|
const dir = queueDir(stateDir);
|
|
6848
|
-
if (!
|
|
6925
|
+
if (!existsSync16(dir)) return 0;
|
|
6849
6926
|
let sent = 0;
|
|
6850
6927
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
6851
|
-
const path =
|
|
6928
|
+
const path = join17(dir, f);
|
|
6852
6929
|
let q2;
|
|
6853
6930
|
try {
|
|
6854
|
-
q2 = JSON.parse(
|
|
6931
|
+
q2 = JSON.parse(readFileSync16(path, "utf8"));
|
|
6855
6932
|
} catch {
|
|
6856
6933
|
continue;
|
|
6857
6934
|
}
|
|
@@ -6880,7 +6957,7 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
6880
6957
|
continue;
|
|
6881
6958
|
}
|
|
6882
6959
|
const attempts = q2.attempts + 1;
|
|
6883
|
-
|
|
6960
|
+
writeFileSync12(path, JSON.stringify({
|
|
6884
6961
|
...q2,
|
|
6885
6962
|
attempts,
|
|
6886
6963
|
warned: true,
|
|
@@ -6891,20 +6968,20 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
6891
6968
|
}
|
|
6892
6969
|
return sent;
|
|
6893
6970
|
}
|
|
6894
|
-
var countersDir = (stateDir) =>
|
|
6971
|
+
var countersDir = (stateDir) => join17(stateDir, "pending-ticket-counters");
|
|
6895
6972
|
function queueTicketCounters(stateDir, name, q2) {
|
|
6896
|
-
|
|
6897
|
-
|
|
6973
|
+
mkdirSync11(countersDir(stateDir), { recursive: true });
|
|
6974
|
+
writeFileSync12(join17(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
6898
6975
|
}
|
|
6899
6976
|
async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
6900
6977
|
const dir = countersDir(stateDir);
|
|
6901
|
-
if (!
|
|
6978
|
+
if (!existsSync16(dir)) return 0;
|
|
6902
6979
|
let sent = 0;
|
|
6903
6980
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
6904
|
-
const path =
|
|
6981
|
+
const path = join17(dir, f);
|
|
6905
6982
|
let q2;
|
|
6906
6983
|
try {
|
|
6907
|
-
q2 = JSON.parse(
|
|
6984
|
+
q2 = JSON.parse(readFileSync16(path, "utf8"));
|
|
6908
6985
|
} catch {
|
|
6909
6986
|
continue;
|
|
6910
6987
|
}
|
|
@@ -6923,7 +7000,7 @@ async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /
|
|
|
6923
7000
|
continue;
|
|
6924
7001
|
}
|
|
6925
7002
|
const attempts = q2.attempts + 1;
|
|
6926
|
-
|
|
7003
|
+
writeFileSync12(path, JSON.stringify({
|
|
6927
7004
|
...q2,
|
|
6928
7005
|
attempts,
|
|
6929
7006
|
warned: true,
|
|
@@ -7126,8 +7203,8 @@ var HandoffTranscriptTracker = class {
|
|
|
7126
7203
|
};
|
|
7127
7204
|
|
|
7128
7205
|
// src/pending-handoff.ts
|
|
7129
|
-
import { existsSync as
|
|
7130
|
-
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";
|
|
7131
7208
|
|
|
7132
7209
|
// src/ticket-branch.ts
|
|
7133
7210
|
import { basename as basename7 } from "path";
|
|
@@ -8293,16 +8370,16 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
|
|
|
8293
8370
|
}
|
|
8294
8371
|
|
|
8295
8372
|
// src/run-worktree.ts
|
|
8296
|
-
import { existsSync as
|
|
8297
|
-
import { dirname as
|
|
8373
|
+
import { existsSync as existsSync18 } from "fs";
|
|
8374
|
+
import { dirname as dirname8, join as join19 } from "path";
|
|
8298
8375
|
|
|
8299
8376
|
// src/ports.ts
|
|
8300
|
-
import { execFileSync as
|
|
8301
|
-
import { existsSync as
|
|
8302
|
-
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";
|
|
8303
8380
|
var run = (cmd, args) => {
|
|
8304
8381
|
try {
|
|
8305
|
-
return
|
|
8382
|
+
return execFileSync4(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
8306
8383
|
} catch {
|
|
8307
8384
|
return "";
|
|
8308
8385
|
}
|
|
@@ -8344,7 +8421,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
|
|
|
8344
8421
|
}
|
|
8345
8422
|
function worktreeExists(parent, prefix, n) {
|
|
8346
8423
|
for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
|
|
8347
|
-
if (
|
|
8424
|
+
if (existsSync17(join18(parent, name, ".git"))) return true;
|
|
8348
8425
|
}
|
|
8349
8426
|
return false;
|
|
8350
8427
|
}
|
|
@@ -8427,7 +8504,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
8427
8504
|
const existing = worktreeForNumber(dir, num);
|
|
8428
8505
|
const located = locateBranchForTicket(dir, cfg, t, role);
|
|
8429
8506
|
const baseRef = hasRemote ? `${remote}/${base}` : base;
|
|
8430
|
-
if (existing && !existing.prunable &&
|
|
8507
|
+
if (existing && !existing.prunable && existsSync18(existing.path)) {
|
|
8431
8508
|
const branch = existing.branch;
|
|
8432
8509
|
if (status(existing.path).length > 0) {
|
|
8433
8510
|
return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
|
|
@@ -8443,7 +8520,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
8443
8520
|
if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
|
|
8444
8521
|
return { action: "recut", path: existing.path, branch, base: baseRef };
|
|
8445
8522
|
}
|
|
8446
|
-
const path =
|
|
8523
|
+
const path = join19(dirname8(dir), worktreeForTicket(dir, cfg, t, role));
|
|
8447
8524
|
if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
|
|
8448
8525
|
return { action: "cut-remote", path, branch: located.branch };
|
|
8449
8526
|
}
|
|
@@ -8497,7 +8574,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
8497
8574
|
if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
|
|
8498
8575
|
if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
|
|
8499
8576
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
8500
|
-
if (wt &&
|
|
8577
|
+
if (wt && existsSync18(wt.path)) {
|
|
8501
8578
|
if (status(wt.path).length > 0) return false;
|
|
8502
8579
|
if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
|
|
8503
8580
|
}
|
|
@@ -8505,7 +8582,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
8505
8582
|
}
|
|
8506
8583
|
async function finishRunWorktree(dir, cfg, branch) {
|
|
8507
8584
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
8508
|
-
if (!wt || !
|
|
8585
|
+
if (!wt || !existsSync18(wt.path)) return { kind: "none" };
|
|
8509
8586
|
if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
|
|
8510
8587
|
if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
|
|
8511
8588
|
if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
|
|
@@ -8517,7 +8594,7 @@ async function finishRunWorktree(dir, cfg, branch) {
|
|
|
8517
8594
|
if (pids.length) await killGently(pids);
|
|
8518
8595
|
}
|
|
8519
8596
|
gitOk(dir, ["worktree", "remove", wt.path]);
|
|
8520
|
-
return
|
|
8597
|
+
return existsSync18(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
|
|
8521
8598
|
}
|
|
8522
8599
|
|
|
8523
8600
|
// src/handoff-guard.ts
|
|
@@ -8900,20 +8977,20 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
|
|
|
8900
8977
|
|
|
8901
8978
|
// src/pending-handoff.ts
|
|
8902
8979
|
var PENDING_MAX_AGE_MS = 48 * 36e5;
|
|
8903
|
-
var pendingDirFor = (stateDir) =>
|
|
8904
|
-
var stateDirOfStream = (streamPath) =>
|
|
8980
|
+
var pendingDirFor = (stateDir) => join20(stateDir, "pending-handoffs");
|
|
8981
|
+
var stateDirOfStream = (streamPath) => join20(streamPath, "..", "..");
|
|
8905
8982
|
function writePendingHandoff(stateDir, rec) {
|
|
8906
8983
|
const dir = pendingDirFor(stateDir);
|
|
8907
|
-
|
|
8908
|
-
|
|
8984
|
+
mkdirSync12(dir, { recursive: true });
|
|
8985
|
+
writeFileSync13(join20(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
|
|
8909
8986
|
}
|
|
8910
8987
|
function listPendingHandoffs(stateDir, route2) {
|
|
8911
8988
|
const dir = pendingDirFor(stateDir);
|
|
8912
|
-
if (!
|
|
8989
|
+
if (!existsSync19(dir)) return [];
|
|
8913
8990
|
const out = [];
|
|
8914
8991
|
for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
8915
8992
|
try {
|
|
8916
|
-
const rec = JSON.parse(
|
|
8993
|
+
const rec = JSON.parse(readFileSync17(join20(dir, f), "utf8"));
|
|
8917
8994
|
if (rec.version === 1 && rec.ticketRecordId && (!route2 || rec.route === route2)) out.push(rec);
|
|
8918
8995
|
} catch {
|
|
8919
8996
|
}
|
|
@@ -8921,7 +8998,7 @@ function listPendingHandoffs(stateDir, route2) {
|
|
|
8921
8998
|
return out;
|
|
8922
8999
|
}
|
|
8923
9000
|
function removePendingHandoff(stateDir, ticketRecordId) {
|
|
8924
|
-
rmSync11(
|
|
9001
|
+
rmSync11(join20(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
|
|
8925
9002
|
}
|
|
8926
9003
|
function pendingHandoffFor(stateDir, ticketRecordId) {
|
|
8927
9004
|
return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
|
|
@@ -9031,8 +9108,8 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
|
|
|
9031
9108
|
}
|
|
9032
9109
|
|
|
9033
9110
|
// src/skills.ts
|
|
9034
|
-
import { readFileSync as
|
|
9035
|
-
import { join as
|
|
9111
|
+
import { readFileSync as readFileSync19, readdirSync as readdirSync8, existsSync as existsSync21 } from "fs";
|
|
9112
|
+
import { join as join22 } from "path";
|
|
9036
9113
|
import { parse as parseYaml } from "yaml";
|
|
9037
9114
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
9038
9115
|
|
|
@@ -9048,8 +9125,8 @@ ${additions}`;
|
|
|
9048
9125
|
}
|
|
9049
9126
|
|
|
9050
9127
|
// src/agents.ts
|
|
9051
|
-
import { readFileSync as
|
|
9052
|
-
import { join as
|
|
9128
|
+
import { readFileSync as readFileSync18, existsSync as existsSync20 } from "fs";
|
|
9129
|
+
import { join as join21 } from "path";
|
|
9053
9130
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
|
|
9054
9131
|
var PERSONA_NAME = {
|
|
9055
9132
|
dev: "Developer",
|
|
@@ -9063,12 +9140,12 @@ var CREW_AGENT_COLUMN = "agent_id";
|
|
|
9063
9140
|
var AgentsSyncError = class extends Error {
|
|
9064
9141
|
};
|
|
9065
9142
|
function personaDefaultPrompt(promptsDir, role) {
|
|
9066
|
-
const briefPath =
|
|
9067
|
-
if (!
|
|
9068
|
-
if (SELF_CONTAINED_ROLES.has(role)) return
|
|
9069
|
-
const commonPath =
|
|
9070
|
-
if (!
|
|
9071
|
-
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")}`;
|
|
9072
9149
|
}
|
|
9073
9150
|
async function resolveAgentId(route2, role, opts) {
|
|
9074
9151
|
const cached = route2.resolved?.agentPersonas?.[role]?.agentId;
|
|
@@ -9515,7 +9592,7 @@ function describeCrewLink(o) {
|
|
|
9515
9592
|
var SkillSyncError = class extends Error {
|
|
9516
9593
|
};
|
|
9517
9594
|
function parseSkillFile(path) {
|
|
9518
|
-
const text2 =
|
|
9595
|
+
const text2 = readFileSync19(path, "utf8");
|
|
9519
9596
|
const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text2);
|
|
9520
9597
|
if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
|
|
9521
9598
|
const frontmatter = match[1];
|
|
@@ -9528,9 +9605,9 @@ function parseSkillFile(path) {
|
|
|
9528
9605
|
return { name: meta.name, description: meta.description, prompt: body, file: path };
|
|
9529
9606
|
}
|
|
9530
9607
|
function listLocalSkills(promptsDir) {
|
|
9531
|
-
const dir =
|
|
9532
|
-
if (!
|
|
9533
|
-
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)));
|
|
9534
9611
|
}
|
|
9535
9612
|
async function syncSkills(route2, opts) {
|
|
9536
9613
|
if (!route2.resolved) {
|
|
@@ -9714,10 +9791,10 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
|
|
|
9714
9791
|
}
|
|
9715
9792
|
|
|
9716
9793
|
// src/runner.ts
|
|
9717
|
-
import { spawn as spawn2, execFile, execFileSync as
|
|
9718
|
-
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";
|
|
9719
9796
|
import { tmpdir as tmpdir2 } from "os";
|
|
9720
|
-
import { basename as basename8, dirname as
|
|
9797
|
+
import { basename as basename8, dirname as dirname9, join as join23 } from "path";
|
|
9721
9798
|
import { promisify } from "util";
|
|
9722
9799
|
|
|
9723
9800
|
// src/run-limit.ts
|
|
@@ -9846,8 +9923,8 @@ var SandboxRunner = class {
|
|
|
9846
9923
|
throw new Error(`cannot generate the sandbox policy: ${e.message}`);
|
|
9847
9924
|
}
|
|
9848
9925
|
policyFile = policyPathFor(plan.streamPath);
|
|
9849
|
-
|
|
9850
|
-
|
|
9926
|
+
mkdirSync13(dirname9(policyFile), { recursive: true });
|
|
9927
|
+
writeFileSync14(policyFile, generated.yaml, { mode: 384 });
|
|
9851
9928
|
}
|
|
9852
9929
|
const create2 = ["sandbox", "create", "--name", name, "--detach"];
|
|
9853
9930
|
if (policyFile) create2.push("--policy", policyFile);
|
|
@@ -9890,10 +9967,10 @@ var SandboxRunner = class {
|
|
|
9890
9967
|
const harness = cfg.agentBin ?? basename8(plan.bin);
|
|
9891
9968
|
const viaUpload = Buffer.byteLength(plan.prompt) > SANDBOX_STDIN_LIMIT;
|
|
9892
9969
|
if (viaUpload) {
|
|
9893
|
-
const tmp = mkdtempSync(
|
|
9970
|
+
const tmp = mkdtempSync(join23(tmpdir2(), "crew-prompt-"));
|
|
9894
9971
|
try {
|
|
9895
|
-
const file =
|
|
9896
|
-
|
|
9972
|
+
const file = join23(tmp, "prompt.txt");
|
|
9973
|
+
writeFileSync14(file, plan.prompt, { mode: 384 });
|
|
9897
9974
|
await this.os(["sandbox", "upload", name, file, SANDBOX_PROMPT_PATH]);
|
|
9898
9975
|
} finally {
|
|
9899
9976
|
rmSync12(tmp, { recursive: true, force: true });
|
|
@@ -9959,7 +10036,7 @@ function describeExecError(e) {
|
|
|
9959
10036
|
}
|
|
9960
10037
|
function gitOrigin(cwd) {
|
|
9961
10038
|
try {
|
|
9962
|
-
return
|
|
10039
|
+
return execFileSync5("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
9963
10040
|
} catch {
|
|
9964
10041
|
return null;
|
|
9965
10042
|
}
|
|
@@ -9970,7 +10047,7 @@ function runnerFor(kind, sandbox) {
|
|
|
9970
10047
|
function runnerOfSidecar(sc) {
|
|
9971
10048
|
return sc.runner === "sandbox" ? new SandboxRunner(sc.sandboxBin ? { bin: sc.sandboxBin } : {}) : new HostRunner();
|
|
9972
10049
|
}
|
|
9973
|
-
function checkSandboxRunner(cfg = {}, run3 = (b, a) =>
|
|
10050
|
+
function checkSandboxRunner(cfg = {}, run3 = (b, a) => execFileSync5(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 })) {
|
|
9974
10051
|
const bin = cfg.bin ?? DEFAULT_SANDBOX_CONFIG.bin;
|
|
9975
10052
|
let version;
|
|
9976
10053
|
try {
|
|
@@ -10037,7 +10114,7 @@ var ALLOWANCE_REFUSAL_MARKER = "AI_ALLOWANCE_EXCEEDED";
|
|
|
10037
10114
|
function streamShowsAllowanceRefusal(streamPath) {
|
|
10038
10115
|
let raw;
|
|
10039
10116
|
try {
|
|
10040
|
-
raw =
|
|
10117
|
+
raw = readFileSync20(streamPath, "utf8");
|
|
10041
10118
|
} catch {
|
|
10042
10119
|
return false;
|
|
10043
10120
|
}
|
|
@@ -10060,26 +10137,26 @@ function streamShowsAllowanceRefusal(streamPath) {
|
|
|
10060
10137
|
}
|
|
10061
10138
|
return false;
|
|
10062
10139
|
}
|
|
10063
|
-
var digestPathFor = (stateDir, routeLabel, role, runId) =>
|
|
10140
|
+
var digestPathFor = (stateDir, routeLabel, role, runId) => join24(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
10064
10141
|
function planAgentRun(o) {
|
|
10065
|
-
const briefPath =
|
|
10066
|
-
if (!
|
|
10142
|
+
const briefPath = join24(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
10143
|
+
if (!existsSync22(briefPath)) {
|
|
10067
10144
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
10068
10145
|
}
|
|
10069
|
-
const commonPath =
|
|
10070
|
-
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);
|
|
10071
10148
|
const now = o.now ?? (() => Date.now());
|
|
10072
10149
|
const routeLabel = routeSlug(o.route.route);
|
|
10073
10150
|
const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
|
|
10074
|
-
const digestPath2 = ownDigest &&
|
|
10151
|
+
const digestPath2 = ownDigest && existsSync22(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
|
|
10075
10152
|
let digest;
|
|
10076
10153
|
let ageSeconds;
|
|
10077
|
-
if (
|
|
10078
|
-
ageSeconds = Math.floor((now() -
|
|
10079
|
-
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");
|
|
10080
10157
|
}
|
|
10081
|
-
const common = usesCommon ?
|
|
10082
|
-
const brief =
|
|
10158
|
+
const common = usesCommon ? readFileSync20(commonPath, "utf8") : "";
|
|
10159
|
+
const brief = readFileSync20(briefPath, "utf8");
|
|
10083
10160
|
const effectiveCommon = o.divergedPrompt ? "" : common;
|
|
10084
10161
|
const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
|
|
10085
10162
|
const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
|
|
@@ -10102,7 +10179,7 @@ function planAgentRun(o) {
|
|
|
10102
10179
|
digest
|
|
10103
10180
|
});
|
|
10104
10181
|
const promptSha = createHash4("sha256").update(expanded.text).digest("hex");
|
|
10105
|
-
const streamsDir =
|
|
10182
|
+
const streamsDir = join24(o.stateDir, "streams");
|
|
10106
10183
|
const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
|
|
10107
10184
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
10108
10185
|
const { model } = seat;
|
|
@@ -10192,8 +10269,8 @@ function planAgentRun(o) {
|
|
|
10192
10269
|
digestAttached: digest !== void 0,
|
|
10193
10270
|
digestAgeSeconds: ageSeconds,
|
|
10194
10271
|
model,
|
|
10195
|
-
streamPath:
|
|
10196
|
-
eventsPath:
|
|
10272
|
+
streamPath: join24(streamsDir, `${base}.jsonl`),
|
|
10273
|
+
eventsPath: join24(streamsDir, `${base}.events.jsonl`),
|
|
10197
10274
|
ticket: o.ticket,
|
|
10198
10275
|
ticketRecordId: o.ticketRecordId ?? null,
|
|
10199
10276
|
repo: o.repo ?? null,
|
|
@@ -10224,7 +10301,7 @@ function describePlan(p) {
|
|
|
10224
10301
|
var pidPathFor = (streamPath) => `${streamPath}.pid`;
|
|
10225
10302
|
function readRunSidecar(streamPath) {
|
|
10226
10303
|
try {
|
|
10227
|
-
const sc = JSON.parse(
|
|
10304
|
+
const sc = JSON.parse(readFileSync20(pidPathFor(streamPath), "utf8"));
|
|
10228
10305
|
return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
|
|
10229
10306
|
} catch {
|
|
10230
10307
|
return null;
|
|
@@ -10271,7 +10348,7 @@ async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMi
|
|
|
10271
10348
|
var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
|
|
10272
10349
|
function openSink(path) {
|
|
10273
10350
|
try {
|
|
10274
|
-
|
|
10351
|
+
mkdirSync14(dirname10(path), { recursive: true });
|
|
10275
10352
|
return createWriteStream(path, { flags: "a" });
|
|
10276
10353
|
} catch {
|
|
10277
10354
|
return void 0;
|
|
@@ -10453,7 +10530,7 @@ var RunObserver = class {
|
|
|
10453
10530
|
};
|
|
10454
10531
|
function writeRunMeta(id, startedAt, liveTicket, extra) {
|
|
10455
10532
|
try {
|
|
10456
|
-
|
|
10533
|
+
writeFileSync15(`${id.streamPath}.meta.json`, JSON.stringify({
|
|
10457
10534
|
route: id.route,
|
|
10458
10535
|
role: id.role,
|
|
10459
10536
|
hint: id.ticket,
|
|
@@ -10613,7 +10690,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
10613
10690
|
const id = identityOf(plan, adapter.harness);
|
|
10614
10691
|
let streamFd;
|
|
10615
10692
|
try {
|
|
10616
|
-
|
|
10693
|
+
mkdirSync14(dirname10(plan.streamPath), { recursive: true });
|
|
10617
10694
|
streamFd = openSync3(plan.streamPath, "a");
|
|
10618
10695
|
} catch (e) {
|
|
10619
10696
|
void endSink(eventsSink);
|
|
@@ -10645,7 +10722,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
10645
10722
|
closeSync3(streamFd);
|
|
10646
10723
|
return supervise(begun);
|
|
10647
10724
|
function supervise({ child, handle, promptDelivered }) {
|
|
10648
|
-
return new Promise((
|
|
10725
|
+
return new Promise((resolve8, reject) => {
|
|
10649
10726
|
const spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10650
10727
|
const deadline = deadlineFor(spawnedAt, plan.maxRunMinutes);
|
|
10651
10728
|
if (child.pid !== void 0) {
|
|
@@ -10671,7 +10748,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
10671
10748
|
promptSha: plan.promptSha
|
|
10672
10749
|
};
|
|
10673
10750
|
try {
|
|
10674
|
-
|
|
10751
|
+
writeFileSync15(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
|
|
10675
10752
|
} catch {
|
|
10676
10753
|
}
|
|
10677
10754
|
}
|
|
@@ -10715,7 +10792,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
10715
10792
|
plan.agentLog?.apiKey,
|
|
10716
10793
|
hintMoved
|
|
10717
10794
|
);
|
|
10718
|
-
|
|
10795
|
+
resolve8({ code: code ?? 1, ms: ms3, ...handoffPending ? { handoffPending } : {}, ...killed ? { killed } : {} });
|
|
10719
10796
|
});
|
|
10720
10797
|
child.stdin?.on("error", (err3) => {
|
|
10721
10798
|
emit.warn(`agent closed stdin before the prompt was delivered: ${err3.message}`, { step: "agent" });
|
|
@@ -10785,21 +10862,21 @@ var pidAlive = (pid) => {
|
|
|
10785
10862
|
};
|
|
10786
10863
|
function runFinished(streamPath) {
|
|
10787
10864
|
const path = `${streamPath}.meta.json`;
|
|
10788
|
-
if (!
|
|
10865
|
+
if (!existsSync23(path)) return false;
|
|
10789
10866
|
try {
|
|
10790
|
-
return JSON.parse(
|
|
10867
|
+
return JSON.parse(readFileSync21(path, "utf8")).running !== true;
|
|
10791
10868
|
} catch {
|
|
10792
10869
|
return true;
|
|
10793
10870
|
}
|
|
10794
10871
|
}
|
|
10795
10872
|
function orphanedStreams(o) {
|
|
10796
|
-
const dir =
|
|
10797
|
-
if (!
|
|
10873
|
+
const dir = join25(o.stateDir, "streams");
|
|
10874
|
+
if (!existsSync23(dir)) return [];
|
|
10798
10875
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
10799
10876
|
const out = [];
|
|
10800
10877
|
for (const name of readdirSync9(dir)) {
|
|
10801
10878
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
10802
|
-
const streamPath =
|
|
10879
|
+
const streamPath = join25(dir, name);
|
|
10803
10880
|
if (runFinished(streamPath)) continue;
|
|
10804
10881
|
const sidecar = readRunSidecar(streamPath);
|
|
10805
10882
|
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
@@ -10852,15 +10929,15 @@ async function reconcileRuns(o) {
|
|
|
10852
10929
|
}
|
|
10853
10930
|
|
|
10854
10931
|
// src/live-runs.ts
|
|
10855
|
-
import { existsSync as
|
|
10856
|
-
import { join as
|
|
10932
|
+
import { existsSync as existsSync24, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
|
|
10933
|
+
import { join as join26 } from "path";
|
|
10857
10934
|
function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
10858
|
-
const dir =
|
|
10859
|
-
if (!
|
|
10935
|
+
const dir = join26(stateDir, "streams");
|
|
10936
|
+
if (!existsSync24(dir)) return [];
|
|
10860
10937
|
const out = [];
|
|
10861
10938
|
for (const name of readdirSync10(dir)) {
|
|
10862
10939
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
10863
|
-
const streamPath =
|
|
10940
|
+
const streamPath = join26(dir, name);
|
|
10864
10941
|
if (runFinished(streamPath)) continue;
|
|
10865
10942
|
const sidecar = readRunSidecar(streamPath);
|
|
10866
10943
|
if (!sidecar || !isAlive(sidecar)) continue;
|
|
@@ -10870,15 +10947,15 @@ function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
|
10870
10947
|
}
|
|
10871
10948
|
function runActivity(stateDir, isAlive = runIsAlive) {
|
|
10872
10949
|
const activeRuns = liveRuns(stateDir, isAlive).length;
|
|
10873
|
-
const dir =
|
|
10950
|
+
const dir = join26(stateDir, "streams");
|
|
10874
10951
|
let newest = 0;
|
|
10875
|
-
if (
|
|
10952
|
+
if (existsSync24(dir)) {
|
|
10876
10953
|
for (const name of readdirSync10(dir)) {
|
|
10877
10954
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
10878
|
-
const streamPath =
|
|
10955
|
+
const streamPath = join26(dir, name);
|
|
10879
10956
|
if (!runFinished(streamPath)) continue;
|
|
10880
10957
|
try {
|
|
10881
|
-
newest = Math.max(newest,
|
|
10958
|
+
newest = Math.max(newest, statSync9(`${streamPath}.meta.json`).mtimeMs);
|
|
10882
10959
|
} catch {
|
|
10883
10960
|
}
|
|
10884
10961
|
}
|
|
@@ -11246,7 +11323,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
11246
11323
|
if (host === "windows") return [];
|
|
11247
11324
|
const ctx = installContext();
|
|
11248
11325
|
const systemd = detectSystemd();
|
|
11249
|
-
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));
|
|
11250
11327
|
};
|
|
11251
11328
|
return {
|
|
11252
11329
|
liveRuns: () => liveRuns(stateDir).map((r) => ({ role: r.role, route: r.route, pid: r.pid, ticketRecordId: r.ticketRecordId ?? void 0, streamPath: r.streamPath })),
|
|
@@ -11307,7 +11384,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
11307
11384
|
},
|
|
11308
11385
|
remaining() {
|
|
11309
11386
|
const left = [];
|
|
11310
|
-
if (
|
|
11387
|
+
if (existsSync25(cfg.configFile)) left.push(`config ${cfg.configFile}`);
|
|
11311
11388
|
for (const h of forgeHosts()) if (hasForgeToken(cfg.ship.forges, h, stateDir)) left.push(`forge token for ${h}`);
|
|
11312
11389
|
try {
|
|
11313
11390
|
for (const k of readdirSync11(keysDir(stateDir))) left.push(`seat key ${k}`);
|
|
@@ -11582,7 +11659,7 @@ function makeCachePass(o) {
|
|
|
11582
11659
|
// src/passenger-containers.ts
|
|
11583
11660
|
var realExec2 = (cmd, args, opts) => {
|
|
11584
11661
|
try {
|
|
11585
|
-
return
|
|
11662
|
+
return execFileSync6(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
|
|
11586
11663
|
} catch (e) {
|
|
11587
11664
|
const err3 = e;
|
|
11588
11665
|
const why = [err3.stderr, err3.stdout].map((x) => (x ?? "").trim()).filter(Boolean).join(" / ");
|
|
@@ -11862,7 +11939,7 @@ var WARM_MAX_BYTES = 64 * 1024 * 1024;
|
|
|
11862
11939
|
var globToRegExp = (glob) => new RegExp(`^${glob.split("*").map((p) => p.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")}$`);
|
|
11863
11940
|
function readWarmFiles(dir, ref, patterns) {
|
|
11864
11941
|
if (patterns.length === 0) return {};
|
|
11865
|
-
const git2 = (args) =>
|
|
11942
|
+
const git2 = (args) => execFileSync7("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
|
|
11866
11943
|
const all = git2(["ls-tree", "-r", "--name-only", ref]).split("\n").filter(Boolean);
|
|
11867
11944
|
const res = patterns.map(globToRegExp);
|
|
11868
11945
|
const out = {};
|
|
@@ -11915,19 +11992,19 @@ function planSandbox(repo, decl, warm = {}) {
|
|
|
11915
11992
|
const layer = repoLayer(repo, decl, crew, warm);
|
|
11916
11993
|
return { repo, tag: layer.tag, claude: SANDBOX_CLAUDE_VERSION, layers: [base, crew, layer] };
|
|
11917
11994
|
}
|
|
11918
|
-
var sandboxStatePath = (stateDir) =>
|
|
11995
|
+
var sandboxStatePath = (stateDir) => join27(stateDir, "sandbox-images.json");
|
|
11919
11996
|
function readSandboxState(stateDir) {
|
|
11920
11997
|
try {
|
|
11921
|
-
const v = JSON.parse(
|
|
11998
|
+
const v = JSON.parse(readFileSync22(sandboxStatePath(stateDir), "utf8"));
|
|
11922
11999
|
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
11923
12000
|
} catch {
|
|
11924
12001
|
return {};
|
|
11925
12002
|
}
|
|
11926
12003
|
}
|
|
11927
12004
|
function writeSandboxState(stateDir, state) {
|
|
11928
|
-
|
|
12005
|
+
mkdirSync15(stateDir, { recursive: true });
|
|
11929
12006
|
const file = sandboxStatePath(stateDir);
|
|
11930
|
-
|
|
12007
|
+
writeFileSync16(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
|
|
11931
12008
|
`);
|
|
11932
12009
|
renameSync3(`${file}.tmp`, file);
|
|
11933
12010
|
}
|
|
@@ -11941,15 +12018,15 @@ function sandboxImageStatus(state, plan) {
|
|
|
11941
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})` };
|
|
11942
12019
|
return { status: "ok", detail: `${current.tag} built ${current.builtAt}, claude ${current.claude ?? "unknown"}` };
|
|
11943
12020
|
}
|
|
11944
|
-
var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((
|
|
12021
|
+
var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((resolve8) => {
|
|
11945
12022
|
const child = spawn4(engine, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
11946
12023
|
const chunks = [];
|
|
11947
12024
|
child.stdout.on("data", (d) => chunks.push(d));
|
|
11948
12025
|
child.stderr.on("data", (d) => chunks.push(d));
|
|
11949
12026
|
const done = (code, extra = "") => {
|
|
11950
|
-
|
|
12027
|
+
writeFileSync16(logPath, `$ ${engine} ${args.join(" ")}
|
|
11951
12028
|
${Buffer.concat(chunks).toString("utf8")}${extra}`, { flag: "a" });
|
|
11952
|
-
|
|
12029
|
+
resolve8(code);
|
|
11953
12030
|
};
|
|
11954
12031
|
child.on("error", (e) => done(127, `${e.message}
|
|
11955
12032
|
`));
|
|
@@ -11969,7 +12046,7 @@ var engineRemoveImage = (engine = "docker", exec = realExec2) => (tag) => {
|
|
|
11969
12046
|
} catch {
|
|
11970
12047
|
}
|
|
11971
12048
|
};
|
|
11972
|
-
var sandboxLogDir = (stateDir) =>
|
|
12049
|
+
var sandboxLogDir = (stateDir) => join27(stateDir, "sandbox-logs");
|
|
11973
12050
|
async function buildSandboxImage(plan, deps) {
|
|
11974
12051
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
11975
12052
|
const state = readSandboxState(deps.stateDir);
|
|
@@ -11978,20 +12055,20 @@ async function buildSandboxImage(plan, deps) {
|
|
|
11978
12055
|
return { kind: "reused", tag: plan.tag };
|
|
11979
12056
|
}
|
|
11980
12057
|
const logDir = sandboxLogDir(deps.stateDir);
|
|
11981
|
-
|
|
11982
|
-
const logPath =
|
|
12058
|
+
mkdirSync15(logDir, { recursive: true });
|
|
12059
|
+
const logPath = join27(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
|
|
11983
12060
|
const builtLayers = [];
|
|
11984
12061
|
for (const layer of plan.layers) {
|
|
11985
12062
|
if (deps.hasImage(layer.tag)) continue;
|
|
11986
|
-
const ctx =
|
|
11987
|
-
|
|
12063
|
+
const ctx = join27(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
|
|
12064
|
+
mkdirSync15(ctx, { recursive: true });
|
|
11988
12065
|
try {
|
|
11989
|
-
|
|
12066
|
+
writeFileSync16(join27(ctx, "Dockerfile"), layer.dockerfile);
|
|
11990
12067
|
for (const [name, content] of Object.entries(layer.files)) {
|
|
11991
|
-
|
|
11992
|
-
|
|
12068
|
+
mkdirSync15(dirname11(join27(ctx, name)), { recursive: true });
|
|
12069
|
+
writeFileSync16(join27(ctx, name), content);
|
|
11993
12070
|
}
|
|
11994
|
-
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);
|
|
11995
12072
|
if (code !== 0) {
|
|
11996
12073
|
state[plan.repo] = { ...entry, failure: { tag: plan.tag, at: now().toISOString(), logPath } };
|
|
11997
12074
|
writeSandboxState(deps.stateDir, state);
|
|
@@ -12044,10 +12121,10 @@ async function sandboxBuildTick(repos, deps, force = false) {
|
|
|
12044
12121
|
}
|
|
12045
12122
|
|
|
12046
12123
|
// src/doctor-checks.ts
|
|
12047
|
-
import { execFileSync as
|
|
12124
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
12048
12125
|
var PROBE_BRANCH = "crew-probe-branch";
|
|
12049
12126
|
var realGit = (cwd, args, env) => {
|
|
12050
|
-
|
|
12127
|
+
execFileSync8("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
12051
12128
|
};
|
|
12052
12129
|
function isSshRemote(url) {
|
|
12053
12130
|
return /^ssh:\/\//.test(url) || /^[\w.-]+@[\w.-]+:/.test(url);
|
|
@@ -12127,10 +12204,10 @@ function formatPushAuth(repo, r) {
|
|
|
12127
12204
|
return `${label}FAILED \u2014 ${r.detail}; ${r.hint}
|
|
12128
12205
|
`;
|
|
12129
12206
|
}
|
|
12130
|
-
async function settleRepos(items, nameOf,
|
|
12207
|
+
async function settleRepos(items, nameOf, resolve8) {
|
|
12131
12208
|
const out = await Promise.all(items.map(async (t) => {
|
|
12132
12209
|
try {
|
|
12133
|
-
return { ok: true, value: await
|
|
12210
|
+
return { ok: true, value: await resolve8(t) };
|
|
12134
12211
|
} catch (e) {
|
|
12135
12212
|
return { ok: false, repo: nameOf(t), detail: e.message };
|
|
12136
12213
|
}
|
|
@@ -12180,14 +12257,14 @@ function managedRuntimeWarnings(kind, ship) {
|
|
|
12180
12257
|
|
|
12181
12258
|
// src/selftest.ts
|
|
12182
12259
|
import { spawn as spawn5 } from "child_process";
|
|
12183
|
-
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";
|
|
12184
12261
|
import { tmpdir as tmpdir4 } from "os";
|
|
12185
|
-
import { join as
|
|
12262
|
+
import { join as join28 } from "path";
|
|
12186
12263
|
var SELFTEST_TIMEOUT_MS = 12e4;
|
|
12187
12264
|
var KEY_VAR = "CREW_API_KEY";
|
|
12188
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: ";
|
|
12189
12266
|
var realDeps = {
|
|
12190
|
-
run: (o) => new Promise((
|
|
12267
|
+
run: (o) => new Promise((resolve8) => {
|
|
12191
12268
|
let stdout = "";
|
|
12192
12269
|
let stderr = "";
|
|
12193
12270
|
let timedOut = false;
|
|
@@ -12195,7 +12272,7 @@ var realDeps = {
|
|
|
12195
12272
|
const done = (r) => {
|
|
12196
12273
|
if (!settled) {
|
|
12197
12274
|
settled = true;
|
|
12198
|
-
|
|
12275
|
+
resolve8(r);
|
|
12199
12276
|
}
|
|
12200
12277
|
};
|
|
12201
12278
|
const child = spawn5(o.bin, o.args, { cwd: o.cwd, env: o.env, stdio: ["pipe", "pipe", "pipe"], detached: true });
|
|
@@ -12343,7 +12420,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
12343
12420
|
...adapter.runEnv(spec),
|
|
12344
12421
|
[KEY_VAR]: i.apiKey
|
|
12345
12422
|
};
|
|
12346
|
-
const cwd = mkdtempSync2(
|
|
12423
|
+
const cwd = mkdtempSync2(join28(tmpdir4(), "crew-selftest-"));
|
|
12347
12424
|
try {
|
|
12348
12425
|
const run3 = await deps.run({
|
|
12349
12426
|
bin: invocation.bin,
|
|
@@ -12376,11 +12453,11 @@ function formatResult(r) {
|
|
|
12376
12453
|
const secs = `${(r.ms / 1e3).toFixed(1)}s`;
|
|
12377
12454
|
return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
|
|
12378
12455
|
}
|
|
12379
|
-
var fileFor2 = (stateDir) =>
|
|
12456
|
+
var fileFor2 = (stateDir) => join28(stateDir, "selftest.json");
|
|
12380
12457
|
function readSelftest(stateDir) {
|
|
12381
12458
|
try {
|
|
12382
12459
|
const p = fileFor2(stateDir);
|
|
12383
|
-
return
|
|
12460
|
+
return existsSync26(p) ? JSON.parse(readFileSync23(p, "utf8")) : {};
|
|
12384
12461
|
} catch {
|
|
12385
12462
|
return {};
|
|
12386
12463
|
}
|
|
@@ -12388,8 +12465,8 @@ function readSelftest(stateDir) {
|
|
|
12388
12465
|
function recordSelftest(stateDir, results) {
|
|
12389
12466
|
const all = readSelftest(stateDir);
|
|
12390
12467
|
for (const r of results) all[r.toolchain] = r;
|
|
12391
|
-
|
|
12392
|
-
|
|
12468
|
+
mkdirSync16(stateDir, { recursive: true });
|
|
12469
|
+
writeFileSync17(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
|
|
12393
12470
|
`);
|
|
12394
12471
|
}
|
|
12395
12472
|
function doctorSelftestLines(stored, toolchains) {
|
|
@@ -12401,7 +12478,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
12401
12478
|
}
|
|
12402
12479
|
|
|
12403
12480
|
// src/cli.ts
|
|
12404
|
-
import { resolve as
|
|
12481
|
+
import { resolve as resolve7, dirname as dirname29, join as join50, basename as basename16 } from "path";
|
|
12405
12482
|
import { fileURLToPath } from "url";
|
|
12406
12483
|
import { hostname as hostname4, homedir as homedir11 } from "os";
|
|
12407
12484
|
import { spawnSync as spawnSync10 } from "child_process";
|
|
@@ -12598,14 +12675,14 @@ function rosterMarkdown(roster, meId) {
|
|
|
12598
12675
|
}
|
|
12599
12676
|
|
|
12600
12677
|
// src/connect-resume.ts
|
|
12601
|
-
import { mkdirSync as
|
|
12602
|
-
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";
|
|
12603
12680
|
function lastSessionFile(stateDir) {
|
|
12604
|
-
return
|
|
12681
|
+
return join29(stateDir, "connect-session.json");
|
|
12605
12682
|
}
|
|
12606
12683
|
function readPointers(stateDir) {
|
|
12607
12684
|
try {
|
|
12608
|
-
const parsed = JSON.parse(
|
|
12685
|
+
const parsed = JSON.parse(readFileSync24(lastSessionFile(stateDir), "utf8"));
|
|
12609
12686
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
12610
12687
|
const out = {};
|
|
12611
12688
|
for (const [host, v] of Object.entries(parsed)) {
|
|
@@ -12619,8 +12696,8 @@ function readPointers(stateDir) {
|
|
|
12619
12696
|
}
|
|
12620
12697
|
function writePointers(stateDir, pointers) {
|
|
12621
12698
|
const file = lastSessionFile(stateDir);
|
|
12622
|
-
|
|
12623
|
-
|
|
12699
|
+
mkdirSync17(dirname12(file), { recursive: true });
|
|
12700
|
+
writeFileSync18(file, `${JSON.stringify(pointers, null, 2)}
|
|
12624
12701
|
`, { mode: 384 });
|
|
12625
12702
|
}
|
|
12626
12703
|
function rememberSession(stateDir, host, workspaceSlug) {
|
|
@@ -12705,15 +12782,15 @@ async function signOutSessions(o) {
|
|
|
12705
12782
|
}
|
|
12706
12783
|
|
|
12707
12784
|
// src/reset.ts
|
|
12708
|
-
import { existsSync as
|
|
12709
|
-
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";
|
|
12710
12787
|
import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
|
|
12711
12788
|
function clonedCheckoutsFile(stateDir) {
|
|
12712
|
-
return
|
|
12789
|
+
return join30(stateDir, "cloned-checkouts.json");
|
|
12713
12790
|
}
|
|
12714
12791
|
function readClonedCheckouts(stateDir) {
|
|
12715
12792
|
try {
|
|
12716
|
-
const parsed = JSON.parse(
|
|
12793
|
+
const parsed = JSON.parse(readFileSync25(clonedCheckoutsFile(stateDir), "utf8"));
|
|
12717
12794
|
return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
|
|
12718
12795
|
} catch {
|
|
12719
12796
|
return [];
|
|
@@ -12723,8 +12800,8 @@ function recordClonedCheckout(stateDir, dir) {
|
|
|
12723
12800
|
const known = readClonedCheckouts(stateDir);
|
|
12724
12801
|
if (known.includes(dir)) return;
|
|
12725
12802
|
const file = clonedCheckoutsFile(stateDir);
|
|
12726
|
-
|
|
12727
|
-
|
|
12803
|
+
mkdirSync18(dirname13(file), { recursive: true });
|
|
12804
|
+
writeFileSync19(file, `${JSON.stringify([...known, dir], null, 2)}
|
|
12728
12805
|
`);
|
|
12729
12806
|
}
|
|
12730
12807
|
var mark = (dryRun) => dryRun ? "would-remove" : "removed";
|
|
@@ -12778,7 +12855,7 @@ async function runReset(o, fx) {
|
|
|
12778
12855
|
}
|
|
12779
12856
|
if (o.beforeState) steps.push(...await o.beforeState());
|
|
12780
12857
|
steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
|
|
12781
|
-
if (
|
|
12858
|
+
if (existsSync27(o.configFile)) {
|
|
12782
12859
|
if (!o.dryRun) rmSync16(o.configFile, { force: true });
|
|
12783
12860
|
steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
|
|
12784
12861
|
} else {
|
|
@@ -12787,7 +12864,7 @@ async function runReset(o, fx) {
|
|
|
12787
12864
|
return steps;
|
|
12788
12865
|
}
|
|
12789
12866
|
function removeCheckout(dir, dryRun, fx) {
|
|
12790
|
-
if (!
|
|
12867
|
+
if (!existsSync27(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
|
|
12791
12868
|
const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
|
|
12792
12869
|
if (dirty.length) {
|
|
12793
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" : ""})` };
|
|
@@ -12800,15 +12877,15 @@ function removeCheckout(dir, dryRun, fx) {
|
|
|
12800
12877
|
}
|
|
12801
12878
|
function removeState(stateDir, dryRun) {
|
|
12802
12879
|
const out = [];
|
|
12803
|
-
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")];
|
|
12804
12881
|
try {
|
|
12805
12882
|
for (const name of readdirSync12(stateDir)) {
|
|
12806
|
-
if (name.startsWith(".") &&
|
|
12883
|
+
if (name.startsWith(".") && statSync10(join30(stateDir, name)).isFile()) targets.push(join30(stateDir, name));
|
|
12807
12884
|
}
|
|
12808
12885
|
} catch {
|
|
12809
12886
|
}
|
|
12810
12887
|
for (const t of targets) {
|
|
12811
|
-
if (!
|
|
12888
|
+
if (!existsSync27(t)) continue;
|
|
12812
12889
|
if (!dryRun) rmSync16(t, { recursive: true, force: true });
|
|
12813
12890
|
out.push({ step: "state", target: t, action: mark(dryRun) });
|
|
12814
12891
|
}
|
|
@@ -12823,7 +12900,7 @@ function wipeStateDir(stateDir, dryRun) {
|
|
|
12823
12900
|
return [{ step: "state", target: stateDir, action: "absent", detail: "no state directory" }];
|
|
12824
12901
|
}
|
|
12825
12902
|
if (!names.length) return [{ step: "state", target: stateDir, action: "absent", detail: "already empty" }];
|
|
12826
|
-
if (!dryRun) for (const n of names) rmSync16(
|
|
12903
|
+
if (!dryRun) for (const n of names) rmSync16(join30(stateDir, n), { recursive: true, force: true });
|
|
12827
12904
|
return [{ step: "state", target: stateDir, action: mark(dryRun), detail: `${names.length} entr${names.length === 1 ? "y" : "ies"}` }];
|
|
12828
12905
|
}
|
|
12829
12906
|
function describeResetStep(s) {
|
|
@@ -12904,9 +12981,9 @@ async function runDeprovision(o, fx, deps) {
|
|
|
12904
12981
|
}
|
|
12905
12982
|
|
|
12906
12983
|
// src/pool-member.ts
|
|
12907
|
-
import { mkdirSync as
|
|
12984
|
+
import { mkdirSync as mkdirSync19, readFileSync as readFileSync26, renameSync as renameSync4, writeFileSync as writeFileSync20 } from "fs";
|
|
12908
12985
|
import { homedir as homedir8 } from "os";
|
|
12909
|
-
import { dirname as
|
|
12986
|
+
import { dirname as dirname14, join as join31 } from "path";
|
|
12910
12987
|
var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
|
|
12911
12988
|
function backoffMs(attempt) {
|
|
12912
12989
|
return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
|
|
@@ -13137,22 +13214,22 @@ async function runPoolMember(deps) {
|
|
|
13137
13214
|
}
|
|
13138
13215
|
}
|
|
13139
13216
|
function defaultMemberDir(env = process.env) {
|
|
13140
|
-
return env.CREW_POOL_DIR ??
|
|
13217
|
+
return env.CREW_POOL_DIR ?? join31(env.XDG_STATE_HOME ?? join31(homedir8(), ".local", "state"), "crew-pool-member");
|
|
13141
13218
|
}
|
|
13142
13219
|
function fileMemberStore(dir) {
|
|
13143
|
-
const file =
|
|
13220
|
+
const file = join31(dir, "member.json");
|
|
13144
13221
|
return {
|
|
13145
13222
|
read() {
|
|
13146
13223
|
try {
|
|
13147
|
-
return JSON.parse(
|
|
13224
|
+
return JSON.parse(readFileSync26(file, "utf8"));
|
|
13148
13225
|
} catch {
|
|
13149
13226
|
return {};
|
|
13150
13227
|
}
|
|
13151
13228
|
},
|
|
13152
13229
|
write(f) {
|
|
13153
|
-
|
|
13230
|
+
mkdirSync19(dirname14(file), { recursive: true, mode: 448 });
|
|
13154
13231
|
const tmp = `${file}.tmp`;
|
|
13155
|
-
|
|
13232
|
+
writeFileSync20(tmp, `${JSON.stringify(f)}
|
|
13156
13233
|
`, { mode: 384 });
|
|
13157
13234
|
renameSync4(tmp, file);
|
|
13158
13235
|
}
|
|
@@ -13204,30 +13281,30 @@ function hostArchitecture(arch = process.arch) {
|
|
|
13204
13281
|
|
|
13205
13282
|
// src/pool-member-host.ts
|
|
13206
13283
|
import "child_process";
|
|
13207
|
-
import { existsSync as
|
|
13284
|
+
import { existsSync as existsSync31 } from "fs";
|
|
13208
13285
|
import { hostname as hostname3 } from "os";
|
|
13209
13286
|
|
|
13210
13287
|
// src/machine-id.ts
|
|
13211
|
-
import { execFileSync as
|
|
13288
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
13212
13289
|
import { createHash as createHash7 } from "crypto";
|
|
13213
|
-
import { existsSync as
|
|
13214
|
-
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";
|
|
13215
13292
|
function machineIdPathFor(stateDir) {
|
|
13216
|
-
return
|
|
13293
|
+
return join32(stateDir, "machine-id");
|
|
13217
13294
|
}
|
|
13218
13295
|
var sha256 = (s) => createHash7("sha256").update(s).digest("hex");
|
|
13219
13296
|
function readRawMachineId(platform2 = process.platform) {
|
|
13220
13297
|
try {
|
|
13221
13298
|
if (platform2 === "darwin") {
|
|
13222
|
-
const out =
|
|
13299
|
+
const out = execFileSync9("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
|
|
13223
13300
|
encoding: "utf8",
|
|
13224
13301
|
stdio: ["ignore", "pipe", "ignore"]
|
|
13225
13302
|
});
|
|
13226
13303
|
return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
|
|
13227
13304
|
}
|
|
13228
13305
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
13229
|
-
if (
|
|
13230
|
-
const id =
|
|
13306
|
+
if (existsSync28(p)) {
|
|
13307
|
+
const id = readFileSync27(p, "utf8").trim();
|
|
13231
13308
|
if (id) return id;
|
|
13232
13309
|
}
|
|
13233
13310
|
}
|
|
@@ -13238,15 +13315,15 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
13238
13315
|
function ensureMachineId(stateDir, opts = {}) {
|
|
13239
13316
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
13240
13317
|
const path = machineIdPathFor(stateDir);
|
|
13241
|
-
if (
|
|
13242
|
-
const cached =
|
|
13318
|
+
if (existsSync28(path)) {
|
|
13319
|
+
const cached = readFileSync27(path, "utf8").trim();
|
|
13243
13320
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
13244
13321
|
}
|
|
13245
13322
|
const raw = (opts.read ?? readRawMachineId)();
|
|
13246
13323
|
if (!raw) return void 0;
|
|
13247
13324
|
const hashed = sha256(raw);
|
|
13248
|
-
|
|
13249
|
-
|
|
13325
|
+
mkdirSync20(dirname15(path), { recursive: true });
|
|
13326
|
+
writeFileSync21(path, `${hashed}
|
|
13250
13327
|
`, { mode: 384 });
|
|
13251
13328
|
return hashed;
|
|
13252
13329
|
}
|
|
@@ -13259,7 +13336,7 @@ function matchShipRows(ships, shipName2, machineId) {
|
|
|
13259
13336
|
|
|
13260
13337
|
// src/provision.ts
|
|
13261
13338
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
13262
|
-
import { existsSync as
|
|
13339
|
+
import { existsSync as existsSync29, readFileSync as readFileSync28, writeFileSync as writeFileSync22 } from "fs";
|
|
13263
13340
|
import { hostname as hostname2 } from "os";
|
|
13264
13341
|
import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
|
|
13265
13342
|
|
|
@@ -13268,7 +13345,7 @@ import { parseDocument as parseDocument2 } from "yaml";
|
|
|
13268
13345
|
|
|
13269
13346
|
// src/repos-cmd.ts
|
|
13270
13347
|
import { homedir as homedir9 } from "os";
|
|
13271
|
-
import { basename as basename10, join as
|
|
13348
|
+
import { basename as basename10, join as join33, resolve as resolve4 } from "path";
|
|
13272
13349
|
import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
|
|
13273
13350
|
var ReposError = class extends Error {
|
|
13274
13351
|
};
|
|
@@ -13315,7 +13392,7 @@ function parseAddArgs(args, routes, isDir) {
|
|
|
13315
13392
|
}
|
|
13316
13393
|
function expandDir(p, base) {
|
|
13317
13394
|
const home = p === "~" ? homedir9() : p.startsWith("~/") ? `${homedir9()}/${p.slice(2)}` : p;
|
|
13318
|
-
return
|
|
13395
|
+
return resolve4(base, home);
|
|
13319
13396
|
}
|
|
13320
13397
|
function routeMapOf(doc, route2) {
|
|
13321
13398
|
const routes = doc.get("routes", true);
|
|
@@ -13546,7 +13623,7 @@ function planRepoClone(i) {
|
|
|
13546
13623
|
}
|
|
13547
13624
|
const ship = doc.get("ship", true);
|
|
13548
13625
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
13549
|
-
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);
|
|
13550
13627
|
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
|
|
13551
13628
|
const forgeHost = i.resolved?.repoForgeHosts?.[id];
|
|
13552
13629
|
return { ...add, remote, target, ...forgeHost ? { forgeHost } : {} };
|
|
@@ -13853,7 +13930,7 @@ function realProvisionDeps(crewHome) {
|
|
|
13853
13930
|
};
|
|
13854
13931
|
const configFile = () => {
|
|
13855
13932
|
const f = findConfigFile(crewHome);
|
|
13856
|
-
return
|
|
13933
|
+
return existsSync29(f) ? f : null;
|
|
13857
13934
|
};
|
|
13858
13935
|
return {
|
|
13859
13936
|
crew: spawnCrew,
|
|
@@ -13861,13 +13938,13 @@ function realProvisionDeps(crewHome) {
|
|
|
13861
13938
|
enableRoute(route2) {
|
|
13862
13939
|
const file = configFile();
|
|
13863
13940
|
if (!file) return false;
|
|
13864
|
-
const text2 =
|
|
13941
|
+
const text2 = readFileSync28(file, "utf8");
|
|
13865
13942
|
const doc = parseDocument3(text2);
|
|
13866
13943
|
const routes = doc.get("routes");
|
|
13867
13944
|
const entry = routes?.items?.find((r) => r.get("route") === route2);
|
|
13868
13945
|
if (!entry) throw new Error(`route ${route2} is not in ${file}`);
|
|
13869
13946
|
if (entry.get("enabled") === true) return false;
|
|
13870
|
-
|
|
13947
|
+
writeFileSync22(file, planEnable(text2, route2));
|
|
13871
13948
|
return true;
|
|
13872
13949
|
},
|
|
13873
13950
|
installState() {
|
|
@@ -13887,14 +13964,14 @@ function realProvisionDeps(crewHome) {
|
|
|
13887
13964
|
continue;
|
|
13888
13965
|
}
|
|
13889
13966
|
for (const p of plan.unitPaths) {
|
|
13890
|
-
if (!
|
|
13967
|
+
if (!existsSync29(p) || readFileSync28(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
|
|
13891
13968
|
}
|
|
13892
13969
|
}
|
|
13893
13970
|
const label = labelFor(crewHome, "run");
|
|
13894
13971
|
let running = false;
|
|
13895
13972
|
let lingering = true;
|
|
13896
13973
|
if (mechanism === "systemd") {
|
|
13897
|
-
const system = ctx.root ||
|
|
13974
|
+
const system = ctx.root || existsSync29(systemUnitPathFor(label));
|
|
13898
13975
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
13899
13976
|
running = spawnSync7(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
|
|
13900
13977
|
if (!system) {
|
|
@@ -13911,18 +13988,18 @@ function realProvisionDeps(crewHome) {
|
|
|
13911
13988
|
setShipSetting(key, value2) {
|
|
13912
13989
|
const file = configFile();
|
|
13913
13990
|
if (!file) return false;
|
|
13914
|
-
const doc = parseDocument3(
|
|
13991
|
+
const doc = parseDocument3(readFileSync28(file, "utf8"));
|
|
13915
13992
|
if (doc.getIn(["ship", key]) === value2) return false;
|
|
13916
13993
|
doc.setIn(["ship", key], value2);
|
|
13917
|
-
|
|
13994
|
+
writeFileSync22(file, doc.toString());
|
|
13918
13995
|
return true;
|
|
13919
13996
|
},
|
|
13920
13997
|
applyManagedDefaults() {
|
|
13921
13998
|
const file = configFile();
|
|
13922
13999
|
if (!file) return [];
|
|
13923
|
-
const doc = parseDocument3(
|
|
14000
|
+
const doc = parseDocument3(readFileSync28(file, "utf8"));
|
|
13924
14001
|
const wrote = applyManagedDefaultsToDoc(doc);
|
|
13925
|
-
if (wrote.length)
|
|
14002
|
+
if (wrote.length) writeFileSync22(file, doc.toString());
|
|
13926
14003
|
return wrote;
|
|
13927
14004
|
},
|
|
13928
14005
|
storedToken(host) {
|
|
@@ -13939,9 +14016,9 @@ host=${host}
|
|
|
13939
14016
|
|
|
13940
14017
|
// src/tunnel.ts
|
|
13941
14018
|
import { spawn as spawn6 } from "child_process";
|
|
13942
|
-
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";
|
|
13943
14020
|
import { randomInt } from "crypto";
|
|
13944
|
-
import { join as
|
|
14021
|
+
import { join as join34 } from "path";
|
|
13945
14022
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
13946
14023
|
var defaultSpawn = (cmd, args) => spawn6(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
13947
14024
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -14013,21 +14090,21 @@ function startTunnel(opts) {
|
|
|
14013
14090
|
};
|
|
14014
14091
|
}
|
|
14015
14092
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
14016
|
-
return
|
|
14093
|
+
return join34(stateDir, "passengers", `${workspaceId}.json`);
|
|
14017
14094
|
}
|
|
14018
14095
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
14019
14096
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
14020
|
-
if (!
|
|
14097
|
+
if (!existsSync30(path)) return void 0;
|
|
14021
14098
|
try {
|
|
14022
|
-
return JSON.parse(
|
|
14099
|
+
return JSON.parse(readFileSync29(path, "utf8"));
|
|
14023
14100
|
} catch {
|
|
14024
14101
|
return void 0;
|
|
14025
14102
|
}
|
|
14026
14103
|
}
|
|
14027
14104
|
function writePersistedTunnel(stateDir, state) {
|
|
14028
14105
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
14029
|
-
|
|
14030
|
-
|
|
14106
|
+
mkdirSync21(join34(stateDir, "passengers"), { recursive: true });
|
|
14107
|
+
writeFileSync23(path, `${JSON.stringify(state, null, 2)}
|
|
14031
14108
|
`);
|
|
14032
14109
|
}
|
|
14033
14110
|
function updatePersistedTunnel(stateDir, workspaceId, patch2) {
|
|
@@ -14129,8 +14206,8 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
14129
14206
|
}
|
|
14130
14207
|
}
|
|
14131
14208
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
14132
|
-
const dir =
|
|
14133
|
-
if (!
|
|
14209
|
+
const dir = join34(stateDir, "passengers");
|
|
14210
|
+
if (!existsSync30(dir)) return [];
|
|
14134
14211
|
return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
14135
14212
|
}
|
|
14136
14213
|
|
|
@@ -14155,7 +14232,7 @@ function realPoolMemberDeps(o) {
|
|
|
14155
14232
|
crewVersion: crewVersion(o.crewHome),
|
|
14156
14233
|
hostname: hostname3()
|
|
14157
14234
|
},
|
|
14158
|
-
hasShipConfig: () =>
|
|
14235
|
+
hasShipConfig: () => existsSync31(findConfigFile(o.crewHome)),
|
|
14159
14236
|
async provision(lease, instanceClass, shipKind) {
|
|
14160
14237
|
const hosts = lease.forges.map((f) => f.host.toLowerCase());
|
|
14161
14238
|
const lines2 = [];
|
|
@@ -15031,8 +15108,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
|
|
|
15031
15108
|
}
|
|
15032
15109
|
|
|
15033
15110
|
// src/poll.ts
|
|
15034
|
-
import { writeFileSync as
|
|
15035
|
-
import { join as
|
|
15111
|
+
import { writeFileSync as writeFileSync24 } from "fs";
|
|
15112
|
+
import { join as join35 } from "path";
|
|
15036
15113
|
|
|
15037
15114
|
// src/stalled.ts
|
|
15038
15115
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -15339,7 +15416,7 @@ async function decideCycle(o) {
|
|
|
15339
15416
|
}
|
|
15340
15417
|
return decision;
|
|
15341
15418
|
}
|
|
15342
|
-
var digestPath = (stateDir, route2, role, runId) =>
|
|
15419
|
+
var digestPath = (stateDir, route2, role, runId) => join35(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
15343
15420
|
function otherRunsRemoved(tickets, own) {
|
|
15344
15421
|
if (!own) return tickets;
|
|
15345
15422
|
return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
|
|
@@ -15417,7 +15494,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
|
|
|
15417
15494
|
|
|
15418
15495
|
${body}` : body;
|
|
15419
15496
|
const path = digestPath(stateDir, routeSlug(o.route.route), role, runId);
|
|
15420
|
-
|
|
15497
|
+
writeFileSync24(path, text2);
|
|
15421
15498
|
o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text2)} bytes)`, {
|
|
15422
15499
|
data: { role, bytes: Buffer.byteLength(text2) }
|
|
15423
15500
|
});
|
|
@@ -15525,8 +15602,8 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
|
|
|
15525
15602
|
}
|
|
15526
15603
|
|
|
15527
15604
|
// src/repo-refresh.ts
|
|
15528
|
-
import { existsSync as
|
|
15529
|
-
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";
|
|
15530
15607
|
var REFRESH_COOLDOWN_MS = 2 * 6e4;
|
|
15531
15608
|
var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
|
|
15532
15609
|
var lastAsked = /* @__PURE__ */ new Map();
|
|
@@ -15555,11 +15632,11 @@ function applyRepoRows(resolved, rows) {
|
|
|
15555
15632
|
}
|
|
15556
15633
|
function persistRepoRows(stateDir, routeName, rows) {
|
|
15557
15634
|
const path = resolvedPathFor(stateDir, routeName);
|
|
15558
|
-
if (!
|
|
15559
|
-
const raw = JSON.parse(
|
|
15635
|
+
if (!existsSync32(path)) return;
|
|
15636
|
+
const raw = JSON.parse(readFileSync30(path, "utf8"));
|
|
15560
15637
|
applyRepoRows(raw, rows);
|
|
15561
|
-
|
|
15562
|
-
|
|
15638
|
+
mkdirSync22(dirname17(path), { recursive: true });
|
|
15639
|
+
writeFileSync25(path, `${JSON.stringify(raw, null, 2)}
|
|
15563
15640
|
`);
|
|
15564
15641
|
}
|
|
15565
15642
|
async function refreshUnknownRepos(o) {
|
|
@@ -16009,13 +16086,13 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
|
|
|
16009
16086
|
var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
|
|
16010
16087
|
|
|
16011
16088
|
// src/connect-wizard.ts
|
|
16012
|
-
import { delimiter, dirname as
|
|
16013
|
-
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";
|
|
16014
16091
|
import { stringify as stringify3 } from "yaml";
|
|
16015
16092
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
16016
16093
|
function shouldRunWizard(opts) {
|
|
16017
16094
|
if (!opts.isTTY) return false;
|
|
16018
|
-
const exists = opts.configExists ??
|
|
16095
|
+
const exists = opts.configExists ?? existsSync33;
|
|
16019
16096
|
return !configSearchPath(opts.crewHome).some((p) => exists(p));
|
|
16020
16097
|
}
|
|
16021
16098
|
function firstRunConfigPath() {
|
|
@@ -16032,7 +16109,7 @@ function defaultIsExecutable(p) {
|
|
|
16032
16109
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
16033
16110
|
for (const dir of pathEnv.split(delimiter)) {
|
|
16034
16111
|
if (!dir) continue;
|
|
16035
|
-
const candidate =
|
|
16112
|
+
const candidate = join36(dir, name);
|
|
16036
16113
|
if (isExecutable(candidate)) return candidate;
|
|
16037
16114
|
}
|
|
16038
16115
|
return void 0;
|
|
@@ -16058,9 +16135,9 @@ async function runWizard(p, env) {
|
|
|
16058
16135
|
if (entered && isExec(entered)) agentBin = entered;
|
|
16059
16136
|
else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
|
|
16060
16137
|
}
|
|
16061
|
-
const nodeDir =
|
|
16138
|
+
const nodeDir = dirname18(env.nodePath);
|
|
16062
16139
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
16063
|
-
const baseDirs = [nodeDir, pnpmPath ?
|
|
16140
|
+
const baseDirs = [nodeDir, pnpmPath ? dirname18(pnpmPath) : void 0];
|
|
16064
16141
|
p.say(
|
|
16065
16142
|
`
|
|
16066
16143
|
Host Passengers lets this machine run the agent sessions that other people's
|
|
@@ -16071,7 +16148,7 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
|
|
|
16071
16148
|
p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
|
|
16072
16149
|
}
|
|
16073
16150
|
const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
16074
|
-
const extraPath = deriveExtraPath([...baseDirs, dockerPath ?
|
|
16151
|
+
const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname18(dockerPath) : void 0]);
|
|
16075
16152
|
if (extraPath) {
|
|
16076
16153
|
p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
|
|
16077
16154
|
}
|
|
@@ -16093,9 +16170,9 @@ routes:
|
|
|
16093
16170
|
${routeBlock}`;
|
|
16094
16171
|
}
|
|
16095
16172
|
function writeNewConfig(path, text2) {
|
|
16096
|
-
|
|
16173
|
+
mkdirSync23(dirname18(path), { recursive: true });
|
|
16097
16174
|
try {
|
|
16098
|
-
|
|
16175
|
+
writeFileSync26(path, text2, { mode: 384, flag: "wx" });
|
|
16099
16176
|
return true;
|
|
16100
16177
|
} catch (e) {
|
|
16101
16178
|
if (e.code === "EEXIST") return false;
|
|
@@ -16129,7 +16206,7 @@ Wrote ${o.configPath}.
|
|
|
16129
16206
|
// src/connect-json.ts
|
|
16130
16207
|
import { accessSync as accessSync4, constants as constants4 } from "fs";
|
|
16131
16208
|
import { homedir as homedir10 } from "os";
|
|
16132
|
-
import { dirname as
|
|
16209
|
+
import { dirname as dirname19, join as join37 } from "path";
|
|
16133
16210
|
import { delimiter as delimiter2 } from "path";
|
|
16134
16211
|
var CHOICE_EXIT_CODE = 3;
|
|
16135
16212
|
function createEmitter(json2, write) {
|
|
@@ -16168,12 +16245,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
|
|
|
16168
16245
|
const dirs = [
|
|
16169
16246
|
...pathEnv.split(delimiter2),
|
|
16170
16247
|
...(extraPath ?? "").split(delimiter2),
|
|
16171
|
-
|
|
16172
|
-
|
|
16248
|
+
join37(home, ".local/bin"),
|
|
16249
|
+
join37(home, ".claude/local"),
|
|
16173
16250
|
"/opt/homebrew/bin",
|
|
16174
16251
|
"/usr/local/bin",
|
|
16175
|
-
|
|
16176
|
-
|
|
16252
|
+
join37(home, ".npm-global/bin"),
|
|
16253
|
+
join37(home, ".volta/bin")
|
|
16177
16254
|
];
|
|
16178
16255
|
return [...new Set(dirs.filter(Boolean))];
|
|
16179
16256
|
}
|
|
@@ -16195,7 +16272,7 @@ function flagWizardAnswers(f, env) {
|
|
|
16195
16272
|
const agentBin = f.agentBin ?? found?.bin;
|
|
16196
16273
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
16197
16274
|
const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
16198
|
-
const extraPath = f.extraPath ?? deriveExtraPath([
|
|
16275
|
+
const extraPath = f.extraPath ?? deriveExtraPath([dirname19(env.nodePath), pnpmPath ? dirname19(pnpmPath) : void 0, dockerPath ? dirname19(dockerPath) : void 0]);
|
|
16199
16276
|
return {
|
|
16200
16277
|
answers: {
|
|
16201
16278
|
name: f.shipName?.trim() || env.hostname,
|
|
@@ -16209,8 +16286,8 @@ function flagWizardAnswers(f, env) {
|
|
|
16209
16286
|
}
|
|
16210
16287
|
|
|
16211
16288
|
// src/release-run.ts
|
|
16212
|
-
import { readFileSync as
|
|
16213
|
-
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";
|
|
16214
16291
|
import { tmpdir as tmpdir5 } from "os";
|
|
16215
16292
|
|
|
16216
16293
|
// src/release.ts
|
|
@@ -16429,7 +16506,7 @@ function decideRelease(cwd, tickets, contract, opts = {}) {
|
|
|
16429
16506
|
const block = opts.guard === false ? null : checkGuards(cwd, base);
|
|
16430
16507
|
const lastTag = latestReleaseTag(cwd, pattern, ref);
|
|
16431
16508
|
const lastReleased = lastTag ? tagCommit(cwd, lastTag) : null;
|
|
16432
|
-
const head =
|
|
16509
|
+
const head = resolve3(cwd, ref) ?? "";
|
|
16433
16510
|
const merges = block ? [] : planMerge(cwd, tickets, contract, lastReleased, opts.branchFor ?? ((t) => branchForIssue(cwd, t.issue_id)), opts.ref ?? base, opts.blockerInfo, ref);
|
|
16434
16511
|
const unreleasedCommits = lastReleased ? countCommits(cwd, `${lastReleased}..${ref}`) : 0;
|
|
16435
16512
|
const unreleasedWork = lastReleased ? (gitOk(cwd, ["log", "--format=%s", `${lastReleased}..${ref}`]) ?? "").split("\n").filter((l) => l && !RELEASE_COMMIT_SUBJECT.test(l)).length : 0;
|
|
@@ -16609,8 +16686,8 @@ function movedPast(r, t, branch, pushedAs) {
|
|
|
16609
16686
|
if (!r.columns.verifiedSha) return null;
|
|
16610
16687
|
const verified = columnValue(t, r.contract.columns.verifiedSha);
|
|
16611
16688
|
if (!verified) return null;
|
|
16612
|
-
const head =
|
|
16613
|
-
const remoteHead =
|
|
16689
|
+
const head = resolve3(r.cwd, branch);
|
|
16690
|
+
const remoteHead = resolve3(r.cwd, `${r.repo.branch.remote}/${pushedAs}`);
|
|
16614
16691
|
if (remoteHead && !isAncestor(r.cwd, remoteHead, verified) && remoteHead !== head) {
|
|
16615
16692
|
return { ticket: t, verified, head: remoteHead, branch: pushedAs };
|
|
16616
16693
|
}
|
|
@@ -16633,7 +16710,7 @@ async function runReview(r, verified, reviewing) {
|
|
|
16633
16710
|
out.requeues.push(moved);
|
|
16634
16711
|
continue;
|
|
16635
16712
|
}
|
|
16636
|
-
const head =
|
|
16713
|
+
const head = resolve3(r.cwd, branch);
|
|
16637
16714
|
if (!head) continue;
|
|
16638
16715
|
if (r.dryRun) {
|
|
16639
16716
|
r.emit.emit(`would push ${branch} to ${remote}/${pushedAs} for review`, { ticket: ticket.issue_id });
|
|
@@ -16951,13 +17028,13 @@ async function bump(o, size) {
|
|
|
16951
17028
|
}
|
|
16952
17029
|
return printed;
|
|
16953
17030
|
}
|
|
16954
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
17031
|
+
const files = o.repo.release.versionFiles.map((f) => join38(o.cwd, f));
|
|
16955
17032
|
const first = files[0];
|
|
16956
|
-
if (!
|
|
17033
|
+
if (!existsSync34(first)) {
|
|
16957
17034
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
16958
17035
|
return null;
|
|
16959
17036
|
}
|
|
16960
|
-
const current = JSON.parse(
|
|
17037
|
+
const current = JSON.parse(readFileSync31(first, "utf8")).version;
|
|
16961
17038
|
if (!current) {
|
|
16962
17039
|
o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
|
|
16963
17040
|
return null;
|
|
@@ -16970,27 +17047,27 @@ async function bump(o, size) {
|
|
|
16970
17047
|
return version;
|
|
16971
17048
|
}
|
|
16972
17049
|
for (const f of files) {
|
|
16973
|
-
const src =
|
|
17050
|
+
const src = readFileSync31(f, "utf8");
|
|
16974
17051
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
16975
17052
|
if (out === src) {
|
|
16976
17053
|
o.emit.error(`no version field in ${f}`);
|
|
16977
17054
|
return null;
|
|
16978
17055
|
}
|
|
16979
|
-
|
|
17056
|
+
writeFileSync27(f, out);
|
|
16980
17057
|
}
|
|
16981
17058
|
return version;
|
|
16982
17059
|
}
|
|
16983
17060
|
function writeChangelog(o, version, entries) {
|
|
16984
17061
|
const file = o.repo.release.changelog;
|
|
16985
17062
|
if (!file || entries.length === 0) return;
|
|
16986
|
-
const path =
|
|
17063
|
+
const path = join38(o.cwd, file);
|
|
16987
17064
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
16988
17065
|
if (o.dryRun) {
|
|
16989
17066
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
16990
17067
|
return;
|
|
16991
17068
|
}
|
|
16992
|
-
const existing =
|
|
16993
|
-
|
|
17069
|
+
const existing = existsSync34(path) ? readFileSync31(path, "utf8") : "# Changelog\n";
|
|
17070
|
+
writeFileSync27(path, insertChangelogSection(existing, section));
|
|
16994
17071
|
}
|
|
16995
17072
|
async function confirm(o, expected) {
|
|
16996
17073
|
if (!o.repo.hooks.released) return void 0;
|
|
@@ -17083,8 +17160,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
|
|
|
17083
17160
|
function openReleaseCheckout(o) {
|
|
17084
17161
|
const { remote, base } = o.repo.branch;
|
|
17085
17162
|
const branch = `crew/release-${Date.now()}`;
|
|
17086
|
-
const root = mkdtempSync3(
|
|
17087
|
-
const dir =
|
|
17163
|
+
const root = mkdtempSync3(join38(tmpdir5(), "crew-release-"));
|
|
17164
|
+
const dir = join38(root, "checkout");
|
|
17088
17165
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
17089
17166
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
17090
17167
|
try {
|
|
@@ -17094,10 +17171,10 @@ function openReleaseCheckout(o) {
|
|
|
17094
17171
|
throw e;
|
|
17095
17172
|
}
|
|
17096
17173
|
for (const f of o.repo.worktrees.copy) {
|
|
17097
|
-
const src =
|
|
17098
|
-
if (!
|
|
17099
|
-
|
|
17100
|
-
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));
|
|
17101
17178
|
}
|
|
17102
17179
|
return { dir, branch, close };
|
|
17103
17180
|
}
|
|
@@ -17520,7 +17597,7 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
|
|
|
17520
17597
|
branches: merged.filter((c) => c.branch).map((c) => ({
|
|
17521
17598
|
key: c.ticket.issue_tag ?? c.ticket.issue_id,
|
|
17522
17599
|
branch: c.branch,
|
|
17523
|
-
tip:
|
|
17600
|
+
tip: resolve3(o.cwd, c.branch) ?? ""
|
|
17524
17601
|
}))
|
|
17525
17602
|
};
|
|
17526
17603
|
const hookFailure2 = {
|
|
@@ -17583,7 +17660,7 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
|
|
|
17583
17660
|
if (releaseBranch && !o.dryRun) {
|
|
17584
17661
|
const { remote, base } = o.repo.branch;
|
|
17585
17662
|
if (fetchRemote(o.cwd, remote) && remoteBranchExists(o.cwd, remote, base)) {
|
|
17586
|
-
const moved =
|
|
17663
|
+
const moved = resolve3(o.cwd, `${remote}/${base}`);
|
|
17587
17664
|
if (moved && !isAncestor(o.cwd, moved, "HEAD")) {
|
|
17588
17665
|
o.emit.warn(`${remote}/${base} moved during the release \u2014 discarding it; the tickets stay verified and the next cycle retries`);
|
|
17589
17666
|
return { merged, conflicts, unbuildable, version, deployed: false, decision, stopped: "base branch moved during release" };
|
|
@@ -18076,7 +18153,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
|
|
|
18076
18153
|
}
|
|
18077
18154
|
|
|
18078
18155
|
// src/watch.ts
|
|
18079
|
-
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";
|
|
18080
18157
|
var E = "\x1B[";
|
|
18081
18158
|
var dim = (s) => `${E}2m${s}${E}0m`;
|
|
18082
18159
|
var bold = (s) => `${E}1m${s}${E}0m`;
|
|
@@ -18177,8 +18254,8 @@ function renderHeaders(states, now = Date.now()) {
|
|
|
18177
18254
|
return states.map((s) => renderHeader(s, now)).join("");
|
|
18178
18255
|
}
|
|
18179
18256
|
function readFrom(file, from) {
|
|
18180
|
-
if (!
|
|
18181
|
-
const size =
|
|
18257
|
+
if (!existsSync35(file)) return { events: [], offset: from };
|
|
18258
|
+
const size = statSync11(file).size;
|
|
18182
18259
|
if (size < from) return readFrom(file, 0);
|
|
18183
18260
|
if (size === from) return { events: [], offset: from };
|
|
18184
18261
|
const fd = openSync4(file, "r");
|
|
@@ -18420,8 +18497,8 @@ function nextRoles(decision, ran) {
|
|
|
18420
18497
|
}
|
|
18421
18498
|
|
|
18422
18499
|
// src/ship-name.ts
|
|
18423
|
-
import { existsSync as
|
|
18424
|
-
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";
|
|
18425
18502
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
18426
18503
|
var ShipNameError = class extends Error {
|
|
18427
18504
|
};
|
|
@@ -18482,34 +18559,34 @@ async function syncRouteName(i) {
|
|
|
18482
18559
|
return { status: "ok", ...base, action, name: i.local };
|
|
18483
18560
|
}
|
|
18484
18561
|
function readSyncedName(resolvedFile) {
|
|
18485
|
-
if (!
|
|
18562
|
+
if (!existsSync36(resolvedFile)) return void 0;
|
|
18486
18563
|
try {
|
|
18487
|
-
const v = JSON.parse(
|
|
18564
|
+
const v = JSON.parse(readFileSync32(resolvedFile, "utf8")).shipName;
|
|
18488
18565
|
return typeof v === "string" ? v : void 0;
|
|
18489
18566
|
} catch {
|
|
18490
18567
|
return void 0;
|
|
18491
18568
|
}
|
|
18492
18569
|
}
|
|
18493
18570
|
function writeSyncedName(resolvedFile, name) {
|
|
18494
|
-
if (!
|
|
18571
|
+
if (!existsSync36(resolvedFile)) return;
|
|
18495
18572
|
let raw;
|
|
18496
18573
|
try {
|
|
18497
|
-
raw = JSON.parse(
|
|
18574
|
+
raw = JSON.parse(readFileSync32(resolvedFile, "utf8"));
|
|
18498
18575
|
} catch {
|
|
18499
18576
|
return;
|
|
18500
18577
|
}
|
|
18501
18578
|
if (raw.shipName === name) return;
|
|
18502
18579
|
raw.shipName = name;
|
|
18503
|
-
|
|
18580
|
+
mkdirSync25(dirname21(resolvedFile), { recursive: true });
|
|
18504
18581
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
18505
|
-
|
|
18582
|
+
writeFileSync28(tmp, `${JSON.stringify(raw, null, 2)}
|
|
18506
18583
|
`);
|
|
18507
18584
|
renameSync5(tmp, resolvedFile);
|
|
18508
18585
|
}
|
|
18509
18586
|
async function reconcileShipName(i) {
|
|
18510
18587
|
let local = i.ship.name;
|
|
18511
18588
|
try {
|
|
18512
|
-
local = shipNameOfYaml(
|
|
18589
|
+
local = shipNameOfYaml(readFileSync32(i.configFile, "utf8")) ?? local;
|
|
18513
18590
|
} catch {
|
|
18514
18591
|
}
|
|
18515
18592
|
const resolvedFile = resolvedPathFor(i.stateDir, i.route);
|
|
@@ -18533,8 +18610,8 @@ async function reconcileShipName(i) {
|
|
|
18533
18610
|
}
|
|
18534
18611
|
if (result.status !== "ok") return result;
|
|
18535
18612
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
18536
|
-
const text2 =
|
|
18537
|
-
|
|
18613
|
+
const text2 = readFileSync32(i.configFile, "utf8");
|
|
18614
|
+
writeFileSync28(i.configFile, setShipNameInYaml(text2, result.name));
|
|
18538
18615
|
i.ship.name = result.name;
|
|
18539
18616
|
if (result.action === "row-wins") {
|
|
18540
18617
|
i.emit?.warn(
|
|
@@ -19193,8 +19270,8 @@ function describeCapability(i) {
|
|
|
19193
19270
|
}
|
|
19194
19271
|
|
|
19195
19272
|
// src/agents-sync-run.ts
|
|
19196
|
-
import { existsSync as
|
|
19197
|
-
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";
|
|
19198
19275
|
async function runAgentsSync(target, o) {
|
|
19199
19276
|
const dryRun = o.dryRun ?? false;
|
|
19200
19277
|
const force = o.force ?? false;
|
|
@@ -19227,12 +19304,12 @@ async function runAgentsSync(target, o) {
|
|
|
19227
19304
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
19228
19305
|
if (!dryRun) {
|
|
19229
19306
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
19230
|
-
const raw =
|
|
19307
|
+
const raw = existsSync37(resolvedPath) ? JSON.parse(readFileSync33(resolvedPath, "utf8")) : {};
|
|
19231
19308
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
19232
19309
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
19233
19310
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
19234
|
-
|
|
19235
|
-
|
|
19311
|
+
mkdirSync26(dirname22(resolvedPath), { recursive: true });
|
|
19312
|
+
writeFileSync29(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
19236
19313
|
`);
|
|
19237
19314
|
if (target.resolved) {
|
|
19238
19315
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -19336,7 +19413,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
19336
19413
|
|
|
19337
19414
|
// src/repos-check.ts
|
|
19338
19415
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
19339
|
-
import { readFileSync as
|
|
19416
|
+
import { readFileSync as readFileSync34 } from "fs";
|
|
19340
19417
|
import { basename as basename11 } from "path";
|
|
19341
19418
|
var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
|
|
19342
19419
|
function checkHookSyntax(script, shell) {
|
|
@@ -19377,7 +19454,7 @@ function checkRepoSpec(dir, opts = {}) {
|
|
|
19377
19454
|
}
|
|
19378
19455
|
let parsed;
|
|
19379
19456
|
try {
|
|
19380
|
-
parsed = parseRepoConfig(
|
|
19457
|
+
parsed = parseRepoConfig(readFileSync34(file, "utf8"), file);
|
|
19381
19458
|
} catch (e) {
|
|
19382
19459
|
if (!(e instanceof RepoConfigError)) throw e;
|
|
19383
19460
|
return { ...empty, file, error: e.message, exitCode: 1 };
|
|
@@ -19656,9 +19733,9 @@ function existingRepoConfig(f) {
|
|
|
19656
19733
|
}
|
|
19657
19734
|
|
|
19658
19735
|
// src/repos-hooks-agent.ts
|
|
19659
|
-
import { spawn as spawn7, execFileSync as
|
|
19736
|
+
import { spawn as spawn7, execFileSync as execFileSync10 } from "child_process";
|
|
19660
19737
|
import { realpathSync as realpathSync2 } from "fs";
|
|
19661
|
-
import { isAbsolute as
|
|
19738
|
+
import { isAbsolute as isAbsolute3, relative } from "path";
|
|
19662
19739
|
var AgentSuggestError = class extends Error {
|
|
19663
19740
|
reason;
|
|
19664
19741
|
constructor(reason, message) {
|
|
@@ -19727,7 +19804,7 @@ function parseAgentStream(out, dir) {
|
|
|
19727
19804
|
if (ev?.type === "assistant" && Array.isArray(ev.message?.content)) {
|
|
19728
19805
|
for (const b of ev.message.content) {
|
|
19729
19806
|
const p = b?.type === "tool_use" && b.name === "Read" ? b.input?.file_path : void 0;
|
|
19730
|
-
if (typeof p === "string") files.add(
|
|
19807
|
+
if (typeof p === "string") files.add(isAbsolute3(p) ? relative(base, p) : p);
|
|
19731
19808
|
}
|
|
19732
19809
|
} else if (ev?.type === "result") {
|
|
19733
19810
|
text2 = typeof ev.result === "string" ? ev.result : "";
|
|
@@ -19759,7 +19836,7 @@ function classifyFailure(o) {
|
|
|
19759
19836
|
}
|
|
19760
19837
|
function porcelain(dir) {
|
|
19761
19838
|
try {
|
|
19762
|
-
return
|
|
19839
|
+
return execFileSync10("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
|
|
19763
19840
|
} catch {
|
|
19764
19841
|
return void 0;
|
|
19765
19842
|
}
|
|
@@ -19774,7 +19851,7 @@ async function runAgentSuggest(o) {
|
|
|
19774
19851
|
const before = porcelain(o.dir);
|
|
19775
19852
|
const timeout = AbortSignal.timeout(o.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
19776
19853
|
const signal = o.signal ? AbortSignal.any([o.signal, timeout]) : timeout;
|
|
19777
|
-
const run3 = await new Promise((
|
|
19854
|
+
const run3 = await new Promise((resolve8) => {
|
|
19778
19855
|
let out = "";
|
|
19779
19856
|
let err3 = "";
|
|
19780
19857
|
const child = spawn7(bin, args, { cwd: o.dir, env, detached: true, stdio: ["pipe", "pipe", "pipe"] });
|
|
@@ -19783,7 +19860,7 @@ async function runAgentSuggest(o) {
|
|
|
19783
19860
|
if (settled) return;
|
|
19784
19861
|
settled = true;
|
|
19785
19862
|
signal.removeEventListener("abort", onAbort);
|
|
19786
|
-
|
|
19863
|
+
resolve8(r);
|
|
19787
19864
|
};
|
|
19788
19865
|
const onAbort = () => {
|
|
19789
19866
|
try {
|
|
@@ -19828,7 +19905,7 @@ ${run3.err}` });
|
|
|
19828
19905
|
}
|
|
19829
19906
|
|
|
19830
19907
|
// src/sample-ticket.ts
|
|
19831
|
-
import { basename as basename12, dirname as
|
|
19908
|
+
import { basename as basename12, dirname as dirname23, join as join39 } from "path";
|
|
19832
19909
|
var SAMPLE_REPO = "crew-sample";
|
|
19833
19910
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
19834
19911
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -19851,8 +19928,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
19851
19928
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
19852
19929
|
`;
|
|
19853
19930
|
function sampleDir(existing, reposBase, workspace) {
|
|
19854
|
-
if (existing.length > 0) return
|
|
19855
|
-
return
|
|
19931
|
+
if (existing.length > 0) return join39(dirname23(existing[0]), SAMPLE_REPO);
|
|
19932
|
+
return join39(reposBase, workspace, SAMPLE_REPO);
|
|
19856
19933
|
}
|
|
19857
19934
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
19858
19935
|
function sampleDescription() {
|
|
@@ -19896,8 +19973,8 @@ function planSampleTicket(i) {
|
|
|
19896
19973
|
}
|
|
19897
19974
|
|
|
19898
19975
|
// src/worktree-sweep.ts
|
|
19899
|
-
import { existsSync as
|
|
19900
|
-
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";
|
|
19901
19978
|
function planWorktreeSweep(target, tickets, contract) {
|
|
19902
19979
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
19903
19980
|
const verified = contract.statuses.verified;
|
|
@@ -19961,9 +20038,9 @@ function worktreeTicketNumbers(target) {
|
|
|
19961
20038
|
return [...nums];
|
|
19962
20039
|
}
|
|
19963
20040
|
function siblingWorktreeDirs(target) {
|
|
19964
|
-
const parent =
|
|
20041
|
+
const parent = dirname24(target.dir.replace(/[/\\]+$/, ""));
|
|
19965
20042
|
const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
|
|
19966
|
-
const registered = new Set(worktrees(target.dir).map((w) =>
|
|
20043
|
+
const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
|
|
19967
20044
|
let names;
|
|
19968
20045
|
try {
|
|
19969
20046
|
names = readdirSync14(parent);
|
|
@@ -19974,8 +20051,8 @@ function siblingWorktreeDirs(target) {
|
|
|
19974
20051
|
for (const name of names) {
|
|
19975
20052
|
if (!name.startsWith(stem)) continue;
|
|
19976
20053
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
19977
|
-
const path =
|
|
19978
|
-
if (m && !registered.has(
|
|
20054
|
+
const path = join40(parent, name);
|
|
20055
|
+
if (m && !registered.has(resolve5(path))) out.push({ path, number: m[1] });
|
|
19979
20056
|
}
|
|
19980
20057
|
return out;
|
|
19981
20058
|
}
|
|
@@ -20009,7 +20086,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20009
20086
|
let removed = 0;
|
|
20010
20087
|
let keptBranches = 0;
|
|
20011
20088
|
for (const a of actions) {
|
|
20012
|
-
if (!dryRun && base && !a.keepBranch &&
|
|
20089
|
+
if (!dryRun && base && !a.keepBranch && existsSync38(a.path) && isHandBackMerge(a.path, base)) {
|
|
20013
20090
|
if (gitOk(a.path, ["merge", "--abort"]) !== null) {
|
|
20014
20091
|
log.emit(
|
|
20015
20092
|
`abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
|
|
@@ -20017,7 +20094,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20017
20094
|
);
|
|
20018
20095
|
}
|
|
20019
20096
|
}
|
|
20020
|
-
if (!dryRun &&
|
|
20097
|
+
if (!dryRun && existsSync38(a.path)) {
|
|
20021
20098
|
let dirty = false;
|
|
20022
20099
|
try {
|
|
20023
20100
|
dirty = status(a.path).length > 0;
|
|
@@ -20028,7 +20105,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20028
20105
|
continue;
|
|
20029
20106
|
}
|
|
20030
20107
|
}
|
|
20031
|
-
if (!dryRun && a.requirePushed && a.branch &&
|
|
20108
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync38(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
20032
20109
|
refuse(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
|
|
20033
20110
|
continue;
|
|
20034
20111
|
}
|
|
@@ -20077,7 +20154,7 @@ function isHandBackMerge(path, base) {
|
|
|
20077
20154
|
if (!msgPath) return false;
|
|
20078
20155
|
let first;
|
|
20079
20156
|
try {
|
|
20080
|
-
first =
|
|
20157
|
+
first = readFileSync35(resolve5(path, msgPath), "utf8").split("\n")[0] ?? "";
|
|
20081
20158
|
} catch {
|
|
20082
20159
|
return false;
|
|
20083
20160
|
}
|
|
@@ -20182,16 +20259,16 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
20182
20259
|
}
|
|
20183
20260
|
|
|
20184
20261
|
// src/stream-sweep.ts
|
|
20185
|
-
import { existsSync as
|
|
20186
|
-
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";
|
|
20187
20264
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
20188
20265
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
20189
|
-
const dir =
|
|
20190
|
-
if (!
|
|
20266
|
+
const dir = join41(stateDir, "streams");
|
|
20267
|
+
if (!existsSync39(dir)) return [];
|
|
20191
20268
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
20192
|
-
return readdirSync15(dir).map((name) =>
|
|
20269
|
+
return readdirSync15(dir).map((name) => join41(dir, name)).filter((path) => {
|
|
20193
20270
|
try {
|
|
20194
|
-
return
|
|
20271
|
+
return statSync12(path).mtimeMs < cutoff;
|
|
20195
20272
|
} catch {
|
|
20196
20273
|
return false;
|
|
20197
20274
|
}
|
|
@@ -20215,11 +20292,11 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
20215
20292
|
return removed;
|
|
20216
20293
|
}
|
|
20217
20294
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
20218
|
-
if (!
|
|
20295
|
+
if (!existsSync39(stateDir)) return [];
|
|
20219
20296
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
20220
|
-
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) => {
|
|
20221
20298
|
try {
|
|
20222
|
-
return
|
|
20299
|
+
return statSync12(path).mtimeMs < cutoff;
|
|
20223
20300
|
} catch {
|
|
20224
20301
|
return false;
|
|
20225
20302
|
}
|
|
@@ -20227,12 +20304,12 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
20227
20304
|
}
|
|
20228
20305
|
|
|
20229
20306
|
// src/cli.ts
|
|
20230
|
-
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";
|
|
20231
20308
|
import { createInterface } from "readline/promises";
|
|
20232
20309
|
|
|
20233
20310
|
// src/periscope-cmd.ts
|
|
20234
|
-
import { existsSync as
|
|
20235
|
-
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";
|
|
20236
20313
|
var PeriscopeError = class extends Error {
|
|
20237
20314
|
};
|
|
20238
20315
|
var LivePeriscopeError = class extends PeriscopeError {
|
|
@@ -20242,22 +20319,22 @@ var LivePeriscopeError = class extends PeriscopeError {
|
|
|
20242
20319
|
this.data = { refused: "live_periscope_exists", ...live };
|
|
20243
20320
|
}
|
|
20244
20321
|
};
|
|
20245
|
-
var priorDir = (stateDir) =>
|
|
20246
|
-
var priorPath = (stateDir, ticketRecordId) =>
|
|
20322
|
+
var priorDir = (stateDir) => join42(stateDir, "periscope-priors");
|
|
20323
|
+
var priorPath = (stateDir, ticketRecordId) => join42(priorDir(stateDir), `${ticketRecordId}.json`);
|
|
20247
20324
|
function removePeriscopePrior(stateDir, ticketRecordId) {
|
|
20248
20325
|
rmSync19(priorPath(stateDir, ticketRecordId), { force: true });
|
|
20249
20326
|
}
|
|
20250
20327
|
function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
20251
20328
|
try {
|
|
20252
|
-
|
|
20253
|
-
|
|
20329
|
+
mkdirSync27(priorDir(stateDir), { recursive: true });
|
|
20330
|
+
writeFileSync30(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
|
|
20254
20331
|
} catch {
|
|
20255
20332
|
}
|
|
20256
20333
|
}
|
|
20257
20334
|
function readPeriscopePrior(stateDir, ticketRecordId) {
|
|
20258
20335
|
try {
|
|
20259
20336
|
const p = priorPath(stateDir, ticketRecordId);
|
|
20260
|
-
return
|
|
20337
|
+
return existsSync40(p) ? JSON.parse(readFileSync36(p, "utf8")) : void 0;
|
|
20261
20338
|
} catch {
|
|
20262
20339
|
return void 0;
|
|
20263
20340
|
}
|
|
@@ -20419,7 +20496,12 @@ ${tail(iso.output)}`);
|
|
|
20419
20496
|
const handoff = await d.runHook("handoff", wt.path, env);
|
|
20420
20497
|
if (handoff && handoff.code !== 0) return abandon(`handoff failed (exit ${handoff.code})
|
|
20421
20498
|
${tail(handoff.output)}`);
|
|
20422
|
-
|
|
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) });
|
|
20423
20505
|
if (!preview || preview.code !== 0) {
|
|
20424
20506
|
return abandon(`preview exited ${preview?.code ?? "without running"}
|
|
20425
20507
|
${tail(preview?.output ?? "")}`);
|
|
@@ -20634,7 +20716,8 @@ async function runPeriscopeResume(d, number) {
|
|
|
20634
20716
|
${tail2(iso.output)}`);
|
|
20635
20717
|
if (iso) Object.assign(env, parseEnvLines(iso.output));
|
|
20636
20718
|
}
|
|
20637
|
-
|
|
20719
|
+
d.prepareDropFile?.(row.worktree);
|
|
20720
|
+
const preview = await d.runHook("preview", row.worktree, { ...env, CREW_PREVIEW_HOST: row.host || "localhost", ...pointedAtEnv(row.worktree) });
|
|
20638
20721
|
if (!preview || preview.code !== 0) {
|
|
20639
20722
|
throw new PeriscopeError(`preview exited ${preview?.code ?? "without running"}
|
|
20640
20723
|
${tail2(preview?.output ?? "")}`);
|
|
@@ -20654,14 +20737,19 @@ ${tail2(preview.output)}`);
|
|
|
20654
20737
|
}
|
|
20655
20738
|
|
|
20656
20739
|
// src/periscope-idle.ts
|
|
20657
|
-
import { execFileSync as
|
|
20658
|
-
import { statSync as
|
|
20659
|
-
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";
|
|
20660
20743
|
var MAX_SCANNED_FILES = 2e4;
|
|
20661
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) {
|
|
20662
20750
|
let listing;
|
|
20663
20751
|
try {
|
|
20664
|
-
listing =
|
|
20752
|
+
listing = execFileSync11("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
20665
20753
|
cwd: root,
|
|
20666
20754
|
encoding: "utf8",
|
|
20667
20755
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -20676,7 +20764,7 @@ function newestWorktreeMtime(root) {
|
|
|
20676
20764
|
if (!rel) continue;
|
|
20677
20765
|
if (++scanned > MAX_SCANNED_FILES) break;
|
|
20678
20766
|
try {
|
|
20679
|
-
const m =
|
|
20767
|
+
const m = statSync13(join43(root, rel)).mtimeMs;
|
|
20680
20768
|
if (newest === null || m > newest) newest = m;
|
|
20681
20769
|
} catch {
|
|
20682
20770
|
}
|
|
@@ -20751,21 +20839,21 @@ async function runPeriscopeIdlePass(d) {
|
|
|
20751
20839
|
import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
20752
20840
|
|
|
20753
20841
|
// src/ssh-keys.ts
|
|
20754
|
-
import { execFileSync as
|
|
20755
|
-
import { existsSync as
|
|
20756
|
-
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";
|
|
20757
20845
|
var SshKeyError = class extends Error {
|
|
20758
20846
|
};
|
|
20759
20847
|
function shipSshKeyPathFor(stateDir) {
|
|
20760
|
-
return
|
|
20848
|
+
return join44(stateDir, "ssh", "id_ed25519");
|
|
20761
20849
|
}
|
|
20762
20850
|
function ensureShipSshKeypair(stateDir) {
|
|
20763
20851
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
20764
20852
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
20765
|
-
if (!
|
|
20766
|
-
|
|
20853
|
+
if (!existsSync41(privateKeyPath)) {
|
|
20854
|
+
mkdirSync28(dirname25(privateKeyPath), { recursive: true });
|
|
20767
20855
|
try {
|
|
20768
|
-
|
|
20856
|
+
execFileSync12("ssh-keygen", [
|
|
20769
20857
|
"-t",
|
|
20770
20858
|
"ed25519",
|
|
20771
20859
|
"-N",
|
|
@@ -20783,15 +20871,15 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
20783
20871
|
}
|
|
20784
20872
|
chmodSync3(privateKeyPath, 384);
|
|
20785
20873
|
}
|
|
20786
|
-
if (!
|
|
20874
|
+
if (!existsSync41(publicKeyPath)) {
|
|
20787
20875
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
20788
20876
|
}
|
|
20789
|
-
const publicKey =
|
|
20877
|
+
const publicKey = readFileSync37(publicKeyPath, "utf8").trim();
|
|
20790
20878
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
20791
20879
|
}
|
|
20792
20880
|
function sshKeygenAvailable() {
|
|
20793
20881
|
try {
|
|
20794
|
-
|
|
20882
|
+
execFileSync12("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
|
|
20795
20883
|
return true;
|
|
20796
20884
|
} catch (e) {
|
|
20797
20885
|
return e.code !== "ENOENT";
|
|
@@ -20950,12 +21038,12 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
20950
21038
|
import { createServer as createHttpServer } from "http";
|
|
20951
21039
|
import { spawn as spawn8 } from "child_process";
|
|
20952
21040
|
import { randomBytes } from "crypto";
|
|
20953
|
-
import { existsSync as
|
|
20954
|
-
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";
|
|
20955
21043
|
|
|
20956
21044
|
// src/passenger-mirror.ts
|
|
20957
|
-
import { mkdirSync as
|
|
20958
|
-
import { dirname as
|
|
21045
|
+
import { mkdirSync as mkdirSync29 } from "fs";
|
|
21046
|
+
import { dirname as dirname26 } from "path";
|
|
20959
21047
|
function mirrorRepoOf(spec) {
|
|
20960
21048
|
const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
|
|
20961
21049
|
if (fromOrigin) return fromOrigin;
|
|
@@ -20973,9 +21061,9 @@ async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCa
|
|
|
20973
21061
|
continue;
|
|
20974
21062
|
}
|
|
20975
21063
|
try {
|
|
20976
|
-
const parent =
|
|
20977
|
-
|
|
20978
|
-
|
|
21064
|
+
const parent = dirname26(spec.dir);
|
|
21065
|
+
mkdirSync29(parent, { recursive: true });
|
|
21066
|
+
mkdirSync29(spec.dir, { recursive: true });
|
|
20979
21067
|
const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
|
|
20980
21068
|
out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
|
|
20981
21069
|
} catch (e) {
|
|
@@ -21081,21 +21169,21 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
21081
21169
|
return child;
|
|
21082
21170
|
};
|
|
21083
21171
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
21084
|
-
return
|
|
21172
|
+
return join45(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
21085
21173
|
}
|
|
21086
21174
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
21087
21175
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
21088
|
-
if (!
|
|
21176
|
+
if (!existsSync42(path)) return void 0;
|
|
21089
21177
|
try {
|
|
21090
|
-
return JSON.parse(
|
|
21178
|
+
return JSON.parse(readFileSync38(path, "utf8"));
|
|
21091
21179
|
} catch {
|
|
21092
21180
|
return void 0;
|
|
21093
21181
|
}
|
|
21094
21182
|
}
|
|
21095
21183
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
21096
21184
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
21097
|
-
|
|
21098
|
-
|
|
21185
|
+
mkdirSync30(join45(stateDir, "passengers"), { recursive: true });
|
|
21186
|
+
writeFileSync31(path, `${JSON.stringify(state, null, 2)}
|
|
21099
21187
|
`);
|
|
21100
21188
|
}
|
|
21101
21189
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -21115,8 +21203,8 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
21115
21203
|
}
|
|
21116
21204
|
}
|
|
21117
21205
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
21118
|
-
const dir =
|
|
21119
|
-
if (!
|
|
21206
|
+
const dir = join45(stateDir, "passengers");
|
|
21207
|
+
if (!existsSync42(dir)) return [];
|
|
21120
21208
|
const suffix = "-sync.json";
|
|
21121
21209
|
return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
21122
21210
|
}
|
|
@@ -21204,14 +21292,14 @@ function describeCause(err3) {
|
|
|
21204
21292
|
}
|
|
21205
21293
|
|
|
21206
21294
|
// src/daemon.ts
|
|
21207
|
-
import { existsSync as
|
|
21208
|
-
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";
|
|
21209
21297
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
21210
21298
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
21211
21299
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
21212
21300
|
var ALLOWANCE_PAUSE_MS = 5 * 60 * 1e3;
|
|
21213
21301
|
var RELOAD_WARN_MS = 10 * 60 * 1e3;
|
|
21214
|
-
var defaultSleep = (ms3) => new Promise((
|
|
21302
|
+
var defaultSleep = (ms3) => new Promise((resolve8) => setTimeout(resolve8, ms3));
|
|
21215
21303
|
async function runDaemonLoop(o) {
|
|
21216
21304
|
const floor = o.floorMs ?? DEFAULT_FLOOR_MS;
|
|
21217
21305
|
const ceiling = o.ceilingMs ?? DEFAULT_CEILING_MS;
|
|
@@ -21285,7 +21373,7 @@ async function runDaemonLoop(o) {
|
|
|
21285
21373
|
function latestMtime(path) {
|
|
21286
21374
|
let stat2;
|
|
21287
21375
|
try {
|
|
21288
|
-
stat2 =
|
|
21376
|
+
stat2 = statSync14(path);
|
|
21289
21377
|
} catch {
|
|
21290
21378
|
return 0;
|
|
21291
21379
|
}
|
|
@@ -21293,7 +21381,7 @@ function latestMtime(path) {
|
|
|
21293
21381
|
if (!stat2.isDirectory()) return 0;
|
|
21294
21382
|
let latest = stat2.mtimeMs;
|
|
21295
21383
|
for (const entry of readdirSync17(path, { withFileTypes: true })) {
|
|
21296
|
-
latest = Math.max(latest, latestMtime(
|
|
21384
|
+
latest = Math.max(latest, latestMtime(join46(path, entry.name)));
|
|
21297
21385
|
}
|
|
21298
21386
|
return latest;
|
|
21299
21387
|
}
|
|
@@ -21302,30 +21390,30 @@ function computeTreeSignature(paths) {
|
|
|
21302
21390
|
}
|
|
21303
21391
|
function makeStaleChecker(paths, opts = {}) {
|
|
21304
21392
|
const initial = computeTreeSignature(paths);
|
|
21305
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
21393
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync43(opts.reloadSentinel);
|
|
21306
21394
|
}
|
|
21307
21395
|
function reloadSentinelFor(stateDir) {
|
|
21308
|
-
return
|
|
21396
|
+
return join46(stateDir, "reload-requested");
|
|
21309
21397
|
}
|
|
21310
21398
|
function readReloadRequestedAt(stateDir) {
|
|
21311
21399
|
try {
|
|
21312
|
-
return
|
|
21400
|
+
return statSync14(reloadSentinelFor(stateDir)).mtimeMs;
|
|
21313
21401
|
} catch {
|
|
21314
21402
|
return null;
|
|
21315
21403
|
}
|
|
21316
21404
|
}
|
|
21317
21405
|
function daemonStampFor(stateDir) {
|
|
21318
|
-
return
|
|
21406
|
+
return join46(stateDir, "daemon.started");
|
|
21319
21407
|
}
|
|
21320
21408
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
21321
|
-
|
|
21409
|
+
mkdirSync31(stateDir, { recursive: true });
|
|
21322
21410
|
rmSync20(reloadSentinelFor(stateDir), { force: true });
|
|
21323
|
-
|
|
21411
|
+
writeFileSync32(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
21324
21412
|
`);
|
|
21325
21413
|
}
|
|
21326
21414
|
function readDaemonStarted(stateDir) {
|
|
21327
21415
|
try {
|
|
21328
|
-
const v = JSON.parse(
|
|
21416
|
+
const v = JSON.parse(readFileSync39(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
21329
21417
|
return typeof v === "number" ? v : null;
|
|
21330
21418
|
} catch {
|
|
21331
21419
|
return null;
|
|
@@ -21334,7 +21422,7 @@ function readDaemonStarted(stateDir) {
|
|
|
21334
21422
|
function configChangedAfter(files, startedAt) {
|
|
21335
21423
|
for (const f of files) {
|
|
21336
21424
|
try {
|
|
21337
|
-
if (
|
|
21425
|
+
if (statSync14(f).mtimeMs > startedAt) return f;
|
|
21338
21426
|
} catch {
|
|
21339
21427
|
}
|
|
21340
21428
|
}
|
|
@@ -21497,8 +21585,8 @@ async function runOnePassUnguarded(o) {
|
|
|
21497
21585
|
}
|
|
21498
21586
|
let settle = () => {
|
|
21499
21587
|
};
|
|
21500
|
-
const settled = new Promise((
|
|
21501
|
-
settle =
|
|
21588
|
+
const settled = new Promise((resolve8) => {
|
|
21589
|
+
settle = resolve8;
|
|
21502
21590
|
});
|
|
21503
21591
|
const ready = (tookTicket2) => settle(tookTicket2);
|
|
21504
21592
|
const run3 = spawn11(role, decision, ctx, { ready }).then(() => {
|
|
@@ -21758,7 +21846,7 @@ async function runRoleAgentLocked(role, decision, ctx, hooks, held) {
|
|
|
21758
21846
|
ship.name,
|
|
21759
21847
|
(t) => {
|
|
21760
21848
|
const d = dirForRepo(route2, t.repo_id);
|
|
21761
|
-
return d ? originBranchSha(d, t,
|
|
21849
|
+
return d ? originBranchSha(d, t, resolve3) : null;
|
|
21762
21850
|
}
|
|
21763
21851
|
);
|
|
21764
21852
|
if (!result.ticket) {
|
|
@@ -21891,18 +21979,18 @@ import { createHash as createHash10 } from "crypto";
|
|
|
21891
21979
|
import {
|
|
21892
21980
|
chmodSync as chmodSync4,
|
|
21893
21981
|
closeSync as closeSync5,
|
|
21894
|
-
existsSync as
|
|
21982
|
+
existsSync as existsSync44,
|
|
21895
21983
|
fstatSync,
|
|
21896
21984
|
openSync as openSync5,
|
|
21897
21985
|
readSync as readSync3,
|
|
21898
21986
|
readdirSync as readdirSync18,
|
|
21899
21987
|
rmSync as rmSync21,
|
|
21900
|
-
statSync as
|
|
21988
|
+
statSync as statSync15,
|
|
21901
21989
|
watch
|
|
21902
21990
|
} from "fs";
|
|
21903
21991
|
import { createConnection } from "net";
|
|
21904
21992
|
import { createServer } from "http";
|
|
21905
|
-
import { basename as basename14, dirname as
|
|
21993
|
+
import { basename as basename14, dirname as dirname27, join as join47 } from "path";
|
|
21906
21994
|
import { execFile as execFile3, spawn as spawn10 } from "child_process";
|
|
21907
21995
|
|
|
21908
21996
|
// src/periscope-control.ts
|
|
@@ -21953,7 +22041,7 @@ function lastJsonLine(stdout) {
|
|
|
21953
22041
|
}
|
|
21954
22042
|
return null;
|
|
21955
22043
|
}
|
|
21956
|
-
function
|
|
22044
|
+
function parseLine2(line) {
|
|
21957
22045
|
try {
|
|
21958
22046
|
const v = JSON.parse(line);
|
|
21959
22047
|
return v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
@@ -21981,7 +22069,7 @@ async function execute(h, args, streaming, after) {
|
|
|
21981
22069
|
body: null,
|
|
21982
22070
|
stream: async (write) => {
|
|
21983
22071
|
const r = await h.run(args, (line) => {
|
|
21984
|
-
const ev =
|
|
22072
|
+
const ev = parseLine2(line);
|
|
21985
22073
|
if (ev) write(ev);
|
|
21986
22074
|
});
|
|
21987
22075
|
const f = await finish(r);
|
|
@@ -22096,7 +22184,7 @@ async function handlePeriscopes(h, q2) {
|
|
|
22096
22184
|
], q2.wantsStream, h.mcp ? async (done) => ({ ...done, ...await h.mcp.onUp(picked, done) }) : void 0);
|
|
22097
22185
|
}
|
|
22098
22186
|
function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
22099
|
-
return new Promise((
|
|
22187
|
+
return new Promise((resolve8) => {
|
|
22100
22188
|
const child = spawn9(process.execPath, [process.argv[1] ?? "", ...args], { stdio: ["ignore", "pipe", "pipe"] });
|
|
22101
22189
|
let stdout = "";
|
|
22102
22190
|
let stderr = "";
|
|
@@ -22118,12 +22206,12 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
|
22118
22206
|
const t = setTimeout(() => child.kill("SIGTERM"), timeoutMs);
|
|
22119
22207
|
child.on("error", (e) => {
|
|
22120
22208
|
clearTimeout(t);
|
|
22121
|
-
|
|
22209
|
+
resolve8({ code: 127, stdout, stderr: `${stderr}${e.message}
|
|
22122
22210
|
` });
|
|
22123
22211
|
});
|
|
22124
22212
|
child.on("close", (code) => {
|
|
22125
22213
|
clearTimeout(t);
|
|
22126
|
-
|
|
22214
|
+
resolve8({ code: code ?? 1, stdout, stderr });
|
|
22127
22215
|
});
|
|
22128
22216
|
});
|
|
22129
22217
|
}
|
|
@@ -22131,7 +22219,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
|
22131
22219
|
// src/control-api.ts
|
|
22132
22220
|
var PAUSABLE = ["dev", "design", "qa"];
|
|
22133
22221
|
var CONTROL_SOCKET_NAME = "control.sock";
|
|
22134
|
-
var controlSocketFor = (stateDir) =>
|
|
22222
|
+
var controlSocketFor = (stateDir) => join47(stateDir, CONTROL_SOCKET_NAME);
|
|
22135
22223
|
var MAX_SOCKET_PATH = 100;
|
|
22136
22224
|
var MAX_TAIL_BYTES = 1024 * 1024;
|
|
22137
22225
|
var MAX_WAIT_SECONDS = 30;
|
|
@@ -22218,7 +22306,7 @@ function candidates(live, rotated) {
|
|
|
22218
22306
|
const out = [live];
|
|
22219
22307
|
if (!rotated) return out;
|
|
22220
22308
|
try {
|
|
22221
|
-
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));
|
|
22222
22310
|
} catch {
|
|
22223
22311
|
}
|
|
22224
22312
|
return out;
|
|
@@ -22241,7 +22329,7 @@ function readRange(path, from, max) {
|
|
|
22241
22329
|
}
|
|
22242
22330
|
}
|
|
22243
22331
|
function lastLines2(path, n) {
|
|
22244
|
-
const st =
|
|
22332
|
+
const st = statSync15(path);
|
|
22245
22333
|
const cursorAt = (off) => encodeCursor({ dev: st.dev, ino: st.ino, off });
|
|
22246
22334
|
if (n <= 0 || st.size === 0) return { lines: [], cursor: cursorAt(st.size), reset: false };
|
|
22247
22335
|
let start = st.size;
|
|
@@ -22262,7 +22350,7 @@ function lastLines2(path, n) {
|
|
|
22262
22350
|
}
|
|
22263
22351
|
function readTail(live, opts) {
|
|
22264
22352
|
const max = opts.maxBytes ?? MAX_TAIL_BYTES;
|
|
22265
|
-
if (!
|
|
22353
|
+
if (!existsSync44(live)) {
|
|
22266
22354
|
return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
|
|
22267
22355
|
}
|
|
22268
22356
|
const cur = opts.after ? decodeCursor(opts.after) : null;
|
|
@@ -22275,7 +22363,7 @@ function readTail(live, opts) {
|
|
|
22275
22363
|
} else {
|
|
22276
22364
|
const found = candidates(live, opts.rotated ?? false).find((p) => {
|
|
22277
22365
|
try {
|
|
22278
|
-
const s =
|
|
22366
|
+
const s = statSync15(p);
|
|
22279
22367
|
return s.ino === cur.ino && s.dev === cur.dev;
|
|
22280
22368
|
} catch {
|
|
22281
22369
|
return false;
|
|
@@ -22286,7 +22374,7 @@ function readTail(live, opts) {
|
|
|
22286
22374
|
off = cur.off;
|
|
22287
22375
|
} else reset = true;
|
|
22288
22376
|
}
|
|
22289
|
-
let size =
|
|
22377
|
+
let size = statSync15(path).size;
|
|
22290
22378
|
if (off > size) {
|
|
22291
22379
|
off = 0;
|
|
22292
22380
|
reset = true;
|
|
@@ -22294,12 +22382,12 @@ function readTail(live, opts) {
|
|
|
22294
22382
|
if (off >= size && path !== live) {
|
|
22295
22383
|
path = live;
|
|
22296
22384
|
off = 0;
|
|
22297
|
-
size =
|
|
22385
|
+
size = statSync15(path).size;
|
|
22298
22386
|
}
|
|
22299
22387
|
const { text: text2 } = readRange(path, off, max);
|
|
22300
22388
|
const cut = text2.lastIndexOf("\n");
|
|
22301
22389
|
const whole = cut < 0 ? "" : text2.slice(0, cut + 1);
|
|
22302
|
-
const st =
|
|
22390
|
+
const st = statSync15(path);
|
|
22303
22391
|
return {
|
|
22304
22392
|
lines: whole.split("\n").filter((l) => l.length),
|
|
22305
22393
|
cursor: encodeCursor({ dev: st.dev, ino: st.ino, off: off + Buffer.byteLength(whole) }),
|
|
@@ -22307,7 +22395,7 @@ function readTail(live, opts) {
|
|
|
22307
22395
|
};
|
|
22308
22396
|
}
|
|
22309
22397
|
function waitForChange(dir, ms3, signal) {
|
|
22310
|
-
return new Promise((
|
|
22398
|
+
return new Promise((resolve8) => {
|
|
22311
22399
|
let w;
|
|
22312
22400
|
const done = () => {
|
|
22313
22401
|
clearTimeout(t);
|
|
@@ -22317,7 +22405,7 @@ function waitForChange(dir, ms3, signal) {
|
|
|
22317
22405
|
w?.close();
|
|
22318
22406
|
} catch {
|
|
22319
22407
|
}
|
|
22320
|
-
|
|
22408
|
+
resolve8();
|
|
22321
22409
|
};
|
|
22322
22410
|
const t = setTimeout(done, ms3);
|
|
22323
22411
|
const poll = setInterval(done, 500);
|
|
@@ -22378,7 +22466,7 @@ async function tailReply(path, q2, rotated, abort) {
|
|
|
22378
22466
|
const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
|
|
22379
22467
|
let r = readTail(path, { after, tail: tail3, rotated });
|
|
22380
22468
|
while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
|
|
22381
|
-
await waitForChange(
|
|
22469
|
+
await waitForChange(dirname27(path), deadline - Date.now(), abort);
|
|
22382
22470
|
r = readTail(path, { after: r.cursor, rotated });
|
|
22383
22471
|
}
|
|
22384
22472
|
return reply2(200, r);
|
|
@@ -22418,8 +22506,8 @@ async function route(deps, method, url, req, abort) {
|
|
|
22418
22506
|
if (p.startsWith("/streams/") && get2) {
|
|
22419
22507
|
const run3 = decodeURIComponent(p.slice("/streams/".length));
|
|
22420
22508
|
if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
|
|
22421
|
-
const file =
|
|
22422
|
-
if (!
|
|
22509
|
+
const file = join47(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
|
|
22510
|
+
if (!existsSync44(file)) return err2(404, "no such run");
|
|
22423
22511
|
return tailReply(file, url.searchParams, false, abort);
|
|
22424
22512
|
}
|
|
22425
22513
|
if ((p === "/pause" || p === "/resume") && post3) {
|
|
@@ -22463,14 +22551,14 @@ function send(res, r) {
|
|
|
22463
22551
|
res.end(body);
|
|
22464
22552
|
}
|
|
22465
22553
|
async function socketIsStale(path) {
|
|
22466
|
-
if (!
|
|
22467
|
-
return new Promise((
|
|
22554
|
+
if (!existsSync44(path)) return false;
|
|
22555
|
+
return new Promise((resolve8) => {
|
|
22468
22556
|
const c = createConnection(path);
|
|
22469
22557
|
c.once("connect", () => {
|
|
22470
22558
|
c.destroy();
|
|
22471
|
-
|
|
22559
|
+
resolve8(false);
|
|
22472
22560
|
});
|
|
22473
|
-
c.once("error", () =>
|
|
22561
|
+
c.once("error", () => resolve8(true));
|
|
22474
22562
|
});
|
|
22475
22563
|
}
|
|
22476
22564
|
async function startControlServer(deps) {
|
|
@@ -22482,7 +22570,7 @@ async function startControlServer(deps) {
|
|
|
22482
22570
|
return null;
|
|
22483
22571
|
}
|
|
22484
22572
|
if (await socketIsStale(path)) rmSync21(path, { force: true });
|
|
22485
|
-
else if (
|
|
22573
|
+
else if (existsSync44(path)) {
|
|
22486
22574
|
log(`control API not started: ${path} is in use by another process`);
|
|
22487
22575
|
return null;
|
|
22488
22576
|
}
|
|
@@ -22512,10 +22600,10 @@ async function startControlServer(deps) {
|
|
|
22512
22600
|
}
|
|
22513
22601
|
})();
|
|
22514
22602
|
});
|
|
22515
|
-
return new Promise((
|
|
22603
|
+
return new Promise((resolve8) => {
|
|
22516
22604
|
server.once("error", (e) => {
|
|
22517
22605
|
log(`control API not started: ${e.message}`);
|
|
22518
|
-
|
|
22606
|
+
resolve8(null);
|
|
22519
22607
|
});
|
|
22520
22608
|
server.listen(path, () => {
|
|
22521
22609
|
try {
|
|
@@ -22523,7 +22611,7 @@ async function startControlServer(deps) {
|
|
|
22523
22611
|
} catch {
|
|
22524
22612
|
}
|
|
22525
22613
|
server.on("error", (e) => log(`control API error: ${e.message}`));
|
|
22526
|
-
|
|
22614
|
+
resolve8({
|
|
22527
22615
|
server,
|
|
22528
22616
|
path,
|
|
22529
22617
|
close: () => new Promise((done) => {
|
|
@@ -22554,13 +22642,13 @@ function schedulerHandler(control, run3 = runDetached) {
|
|
|
22554
22642
|
};
|
|
22555
22643
|
}
|
|
22556
22644
|
function crewJson(args, timeoutMs = 6e4) {
|
|
22557
|
-
return new Promise((
|
|
22645
|
+
return new Promise((resolve8) => {
|
|
22558
22646
|
execFile3(process.execPath, [process.argv[1] ?? "", ...args], { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 }, (_e, stdout) => {
|
|
22559
22647
|
try {
|
|
22560
22648
|
const v = JSON.parse(String(stdout));
|
|
22561
|
-
|
|
22649
|
+
resolve8(v && typeof v === "object" && !Array.isArray(v) ? v : null);
|
|
22562
22650
|
} catch {
|
|
22563
|
-
|
|
22651
|
+
resolve8(null);
|
|
22564
22652
|
}
|
|
22565
22653
|
});
|
|
22566
22654
|
});
|
|
@@ -22568,31 +22656,31 @@ function crewJson(args, timeoutMs = 6e4) {
|
|
|
22568
22656
|
|
|
22569
22657
|
// src/periscope-mcp.ts
|
|
22570
22658
|
import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
|
|
22571
|
-
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";
|
|
22572
22660
|
import { z } from "zod";
|
|
22573
22661
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
22574
22662
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
22575
22663
|
|
|
22576
22664
|
// src/periscope-endpoints.ts
|
|
22577
22665
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
22578
|
-
import { chmodSync as chmodSync5, existsSync as
|
|
22579
|
-
import { join as
|
|
22580
|
-
var secretDir = (stateDir) =>
|
|
22581
|
-
var secretFile = (stateDir, id) =>
|
|
22582
|
-
var endpointDir = (stateDir) =>
|
|
22583
|
-
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`);
|
|
22584
22672
|
function mintPeriscopeSecret(stateDir, ticketRecordId) {
|
|
22585
|
-
|
|
22673
|
+
mkdirSync32(secretDir(stateDir), { recursive: true, mode: 448 });
|
|
22586
22674
|
const secret = randomBytes3(32).toString("base64url");
|
|
22587
22675
|
const file = secretFile(stateDir, ticketRecordId);
|
|
22588
|
-
|
|
22676
|
+
writeFileSync33(file, `${JSON.stringify({ secret })}
|
|
22589
22677
|
`, { mode: 384 });
|
|
22590
22678
|
chmodSync5(file, 384);
|
|
22591
22679
|
return secret;
|
|
22592
22680
|
}
|
|
22593
22681
|
function readPeriscopeSecret(stateDir, ticketRecordId) {
|
|
22594
22682
|
try {
|
|
22595
|
-
const v = JSON.parse(
|
|
22683
|
+
const v = JSON.parse(readFileSync40(secretFile(stateDir, ticketRecordId), "utf8"));
|
|
22596
22684
|
return typeof v.secret === "string" && v.secret ? v.secret : void 0;
|
|
22597
22685
|
} catch {
|
|
22598
22686
|
return void 0;
|
|
@@ -22603,14 +22691,14 @@ function removePeriscopeSecret(stateDir, ticketRecordId) {
|
|
|
22603
22691
|
}
|
|
22604
22692
|
function readPeriscopeEndpoint(stateDir, ticketRecordId) {
|
|
22605
22693
|
try {
|
|
22606
|
-
return JSON.parse(
|
|
22694
|
+
return JSON.parse(readFileSync40(endpointFile(stateDir, ticketRecordId), "utf8"));
|
|
22607
22695
|
} catch {
|
|
22608
22696
|
return void 0;
|
|
22609
22697
|
}
|
|
22610
22698
|
}
|
|
22611
22699
|
function writePeriscopeEndpoint(stateDir, rec) {
|
|
22612
|
-
|
|
22613
|
-
|
|
22700
|
+
mkdirSync32(endpointDir(stateDir), { recursive: true });
|
|
22701
|
+
writeFileSync33(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
|
|
22614
22702
|
`);
|
|
22615
22703
|
}
|
|
22616
22704
|
function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
@@ -22618,7 +22706,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
|
22618
22706
|
}
|
|
22619
22707
|
function listPeriscopeEndpoints(stateDir) {
|
|
22620
22708
|
const dir = endpointDir(stateDir);
|
|
22621
|
-
if (!
|
|
22709
|
+
if (!existsSync45(dir)) return [];
|
|
22622
22710
|
const out = [];
|
|
22623
22711
|
for (const name of readdirSync19(dir)) {
|
|
22624
22712
|
if (!name.endsWith(".json")) continue;
|
|
@@ -22687,7 +22775,7 @@ var PathRefusal = class extends Error {
|
|
|
22687
22775
|
};
|
|
22688
22776
|
var isInside = (root, p) => {
|
|
22689
22777
|
const rel = relative2(root, p);
|
|
22690
|
-
return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !
|
|
22778
|
+
return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute4(rel);
|
|
22691
22779
|
};
|
|
22692
22780
|
async function realAncestor(p) {
|
|
22693
22781
|
let cur = p;
|
|
@@ -22706,10 +22794,10 @@ async function realAncestor(p) {
|
|
|
22706
22794
|
}
|
|
22707
22795
|
if (isLink) {
|
|
22708
22796
|
if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
|
|
22709
|
-
cur =
|
|
22797
|
+
cur = resolve6(dirname28(cur), await readlink(cur));
|
|
22710
22798
|
continue;
|
|
22711
22799
|
}
|
|
22712
|
-
const parent =
|
|
22800
|
+
const parent = dirname28(cur);
|
|
22713
22801
|
if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
|
|
22714
22802
|
tail3.push(basename15(cur));
|
|
22715
22803
|
cur = parent;
|
|
@@ -22719,15 +22807,15 @@ async function resolveInWorktree(root, requested) {
|
|
|
22719
22807
|
if (!requested || requested.includes("\0")) throw new PathRefusal("path is required");
|
|
22720
22808
|
const realRoot = await realpath(root);
|
|
22721
22809
|
let abs;
|
|
22722
|
-
if (
|
|
22723
|
-
abs =
|
|
22724
|
-
if (isInside(root, abs)) abs =
|
|
22810
|
+
if (isAbsolute4(requested)) {
|
|
22811
|
+
abs = resolve6(requested);
|
|
22812
|
+
if (isInside(root, abs)) abs = join49(realRoot, relative2(root, abs));
|
|
22725
22813
|
} else {
|
|
22726
|
-
abs =
|
|
22814
|
+
abs = resolve6(realRoot, requested);
|
|
22727
22815
|
}
|
|
22728
22816
|
if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
22729
22817
|
const { real, rest } = await realAncestor(abs);
|
|
22730
|
-
const final = rest ?
|
|
22818
|
+
const final = rest ? join49(real, rest) : real;
|
|
22731
22819
|
if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
22732
22820
|
if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
|
|
22733
22821
|
return final;
|
|
@@ -22762,7 +22850,7 @@ async function hookEnv(d, sc, name) {
|
|
|
22762
22850
|
if (iso && iso.code === 0) Object.assign(env, parseEnvLines(iso.output));
|
|
22763
22851
|
}
|
|
22764
22852
|
}
|
|
22765
|
-
if (name === "preview") env
|
|
22853
|
+
if (name === "preview") Object.assign(env, { CREW_PREVIEW_HOST: sc.host || "localhost" }, pointedAtEnv(sc.worktree));
|
|
22766
22854
|
return env;
|
|
22767
22855
|
}
|
|
22768
22856
|
function createPeriscopeMcpServer(d, sc) {
|
|
@@ -22795,7 +22883,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22795
22883
|
const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
|
|
22796
22884
|
const lines2 = await Promise.all(entries.map(async (e) => {
|
|
22797
22885
|
if (e.isDirectory()) return `[DIR] ${e.name}`;
|
|
22798
|
-
const size = (await lstat(
|
|
22886
|
+
const size = (await lstat(join49(abs, e.name))).size;
|
|
22799
22887
|
return `[FILE] ${e.name} (${size} bytes)`;
|
|
22800
22888
|
}));
|
|
22801
22889
|
return text(lines2.join("\n") || "(empty)");
|
|
@@ -22820,7 +22908,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22820
22908
|
if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
|
|
22821
22909
|
const abs = await inTree(path);
|
|
22822
22910
|
if (abs === await realpath(root)) return text("path must name a file", true);
|
|
22823
|
-
await mkdir(
|
|
22911
|
+
await mkdir(dirname28(abs), { recursive: true });
|
|
22824
22912
|
await inTree(path);
|
|
22825
22913
|
await writeFile(abs, content);
|
|
22826
22914
|
return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
|
|
@@ -22860,8 +22948,8 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22860
22948
|
inputSchema: { path: z.string() },
|
|
22861
22949
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
22862
22950
|
}, async ({ path }) => {
|
|
22863
|
-
const parent = await inTree(
|
|
22864
|
-
const abs =
|
|
22951
|
+
const parent = await inTree(dirname28(path) === "" ? "." : dirname28(path));
|
|
22952
|
+
const abs = join49(parent, basename15(path));
|
|
22865
22953
|
if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
|
|
22866
22954
|
const st = await lstat(abs).catch(() => void 0);
|
|
22867
22955
|
if (!st) return text(`${path} does not exist`, true);
|
|
@@ -22982,7 +23070,7 @@ function findPeriscope(sidecars, segment) {
|
|
|
22982
23070
|
}
|
|
22983
23071
|
|
|
22984
23072
|
// src/cli.ts
|
|
22985
|
-
var CREW_HOME = isCompiledBinary(import.meta.url) ?
|
|
23073
|
+
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname29(process.execPath) : resolve7(dirname29(fileURLToPath(import.meta.url)), "..");
|
|
22986
23074
|
var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
|
|
22987
23075
|
var CYCLE_SECONDS2 = 120;
|
|
22988
23076
|
var STALE_CYCLES = 3;
|
|
@@ -23350,7 +23438,7 @@ if (command === "provision") {
|
|
|
23350
23438
|
process.exit(outcome.code);
|
|
23351
23439
|
}
|
|
23352
23440
|
if (command === "pool-member") {
|
|
23353
|
-
const pool = poolConfigFrom(argv, process.env, () =>
|
|
23441
|
+
const pool = poolConfigFrom(argv, process.env, () => readFileSync41(0, "utf8"));
|
|
23354
23442
|
if (typeof pool === "string") {
|
|
23355
23443
|
process.stderr.write(`crew pool-member: ${pool}
|
|
23356
23444
|
`);
|
|
@@ -23578,7 +23666,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23578
23666
|
} else {
|
|
23579
23667
|
const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
|
|
23580
23668
|
const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
|
|
23581
|
-
if (configless && !
|
|
23669
|
+
if (configless && !existsSync46(findConfigFile(CREW_HOME))) {
|
|
23582
23670
|
cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
|
|
23583
23671
|
} else {
|
|
23584
23672
|
cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
|
|
@@ -23593,7 +23681,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23593
23681
|
throw e;
|
|
23594
23682
|
}
|
|
23595
23683
|
if (command === "credential") {
|
|
23596
|
-
const r = runCredential(positional[1],
|
|
23684
|
+
const r = runCredential(positional[1], readFileSync41(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
|
|
23597
23685
|
process.stdout.write(r.out);
|
|
23598
23686
|
process.stderr.write(r.err);
|
|
23599
23687
|
process.exit(r.code);
|
|
@@ -23605,9 +23693,9 @@ if (command === "passenger-sync-daemon") {
|
|
|
23605
23693
|
forges: cfg.ship.forges,
|
|
23606
23694
|
stateDir: cfg.ship.stateDir,
|
|
23607
23695
|
configFile: cfg.configFile,
|
|
23608
|
-
readFile: (p) =>
|
|
23609
|
-
writeFile: (p, t) =>
|
|
23610
|
-
stdin: () =>
|
|
23696
|
+
readFile: (p) => readFileSync41(p, "utf8"),
|
|
23697
|
+
writeFile: (p, t) => writeFileSync34(p, t),
|
|
23698
|
+
stdin: () => readFileSync41(0, "utf8"),
|
|
23611
23699
|
env: process.env
|
|
23612
23700
|
}
|
|
23613
23701
|
);
|
|
@@ -23900,8 +23988,8 @@ if (command === "passenger-sync-daemon") {
|
|
|
23900
23988
|
}
|
|
23901
23989
|
if (host === "linux" && hasSystemd) {
|
|
23902
23990
|
const label = labelFor(CREW_HOME, "run");
|
|
23903
|
-
const staleTimer =
|
|
23904
|
-
if (
|
|
23991
|
+
const staleTimer = join50(homedir11(), ".config", "systemd", "user", `${label}.timer`);
|
|
23992
|
+
if (existsSync46(staleTimer)) {
|
|
23905
23993
|
process.stdout.write(
|
|
23906
23994
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
23907
23995
|
`
|
|
@@ -23928,7 +24016,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23928
24016
|
const tracker = new Tracker(c, cfg.ship);
|
|
23929
24017
|
problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
|
|
23930
24018
|
for (const r of await resolvedRepos(c)) {
|
|
23931
|
-
if (!
|
|
24019
|
+
if (!existsSync46(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
23932
24020
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
23933
24021
|
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
23934
24022
|
}
|
|
@@ -23955,7 +24043,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23955
24043
|
}
|
|
23956
24044
|
async function doctorFix(o) {
|
|
23957
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))));
|
|
23958
|
-
const unitExists = (p) =>
|
|
24046
|
+
const unitExists = (p) => existsSync46(p);
|
|
23959
24047
|
const crontab = o.host === "linux" ? spawnSync10("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
23960
24048
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
|
|
23961
24049
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -23972,7 +24060,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23972
24060
|
for (const a of plan.actions) {
|
|
23973
24061
|
if (a.kind === "enable") {
|
|
23974
24062
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
23975
|
-
|
|
24063
|
+
writeFileSync34(cfg.configFile, planEnable(readFileSync41(cfg.configFile, "utf8"), a.route));
|
|
23976
24064
|
enabledSound.add(a.route);
|
|
23977
24065
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
23978
24066
|
} else {
|
|
@@ -24713,7 +24801,7 @@ ${cu.tail.trimEnd()}
|
|
|
24713
24801
|
cfg.ship.name,
|
|
24714
24802
|
(t) => {
|
|
24715
24803
|
const d = dirForRepo(w.route, t.repo_id);
|
|
24716
|
-
return d ? originBranchSha(d, t,
|
|
24804
|
+
return d ? originBranchSha(d, t, resolve3) : null;
|
|
24717
24805
|
}
|
|
24718
24806
|
);
|
|
24719
24807
|
if (!result.ticket) {
|
|
@@ -24925,7 +25013,7 @@ ${cu.tail.trimEnd()}
|
|
|
24925
25013
|
cfg.ship.name,
|
|
24926
25014
|
(t) => {
|
|
24927
25015
|
const d = dirForRepo(route2, t.repo_id);
|
|
24928
|
-
return d ? originBranchSha(d, t,
|
|
25016
|
+
return d ? originBranchSha(d, t, resolve3) : null;
|
|
24929
25017
|
}
|
|
24930
25018
|
);
|
|
24931
25019
|
if (!result.ticket) {
|
|
@@ -25111,7 +25199,7 @@ ${cu.tail.trimEnd()}
|
|
|
25111
25199
|
process.exit(1);
|
|
25112
25200
|
}
|
|
25113
25201
|
requireArmed("run the daemon loop");
|
|
25114
|
-
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")];
|
|
25115
25203
|
const configPaths = daemonConfigPaths(cfg);
|
|
25116
25204
|
watchPaths.push(...configPaths);
|
|
25117
25205
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -25416,10 +25504,10 @@ ${cu.tail.trimEnd()}
|
|
|
25416
25504
|
`);
|
|
25417
25505
|
if (!dryRun) {
|
|
25418
25506
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
25419
|
-
const raw =
|
|
25507
|
+
const raw = existsSync46(resolvedPath) ? JSON.parse(readFileSync41(resolvedPath, "utf8")) : {};
|
|
25420
25508
|
raw.agentSkills = result.agentSkills;
|
|
25421
|
-
|
|
25422
|
-
|
|
25509
|
+
mkdirSync33(dirOf2(resolvedPath), { recursive: true });
|
|
25510
|
+
writeFileSync34(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
25423
25511
|
`);
|
|
25424
25512
|
}
|
|
25425
25513
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -25442,7 +25530,7 @@ ${cu.tail.trimEnd()}
|
|
|
25442
25530
|
throw e;
|
|
25443
25531
|
}
|
|
25444
25532
|
const oldName = cfg.ship.name;
|
|
25445
|
-
|
|
25533
|
+
writeFileSync34(cfg.configFile, setShipNameInYaml(readFileSync41(cfg.configFile, "utf8"), newName));
|
|
25446
25534
|
cfg.ship.name = newName;
|
|
25447
25535
|
let renameMachineId;
|
|
25448
25536
|
try {
|
|
@@ -25489,14 +25577,14 @@ ${cu.tail.trimEnd()}
|
|
|
25489
25577
|
process.exit(2);
|
|
25490
25578
|
};
|
|
25491
25579
|
if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks" && sub !== "check") usage2();
|
|
25492
|
-
const configText =
|
|
25580
|
+
const configText = readFileSync41(cfg.configFile, "utf8");
|
|
25493
25581
|
if (sub === "check") {
|
|
25494
25582
|
try {
|
|
25495
25583
|
const routeArg = value("route") ?? positional[2];
|
|
25496
25584
|
const found = cfg.routes.find((r) => r.route === routeArg);
|
|
25497
25585
|
if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
|
|
25498
25586
|
const named2 = value("route") ? void 0 : positional[3];
|
|
25499
|
-
const dir = named2 ? repoDirOf(configText,
|
|
25587
|
+
const dir = named2 ? repoDirOf(configText, dirname29(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
|
|
25500
25588
|
const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
|
|
25501
25589
|
const o = target ? found.repoOverrides[target.name] : void 0;
|
|
25502
25590
|
runReposCheck(dir, {
|
|
@@ -25519,9 +25607,9 @@ ${cu.tail.trimEnd()}
|
|
|
25519
25607
|
if (sub === "hooks") {
|
|
25520
25608
|
try {
|
|
25521
25609
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
25522
|
-
const dir = repoDirOf(configText,
|
|
25523
|
-
if (!
|
|
25524
|
-
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) => {
|
|
25525
25613
|
try {
|
|
25526
25614
|
return readdirSync20(resolvePath(dir, r));
|
|
25527
25615
|
} catch {
|
|
@@ -25529,7 +25617,7 @@ ${cu.tail.trimEnd()}
|
|
|
25529
25617
|
}
|
|
25530
25618
|
}, read: (r) => {
|
|
25531
25619
|
try {
|
|
25532
|
-
return
|
|
25620
|
+
return readFileSync41(resolvePath(dir, r), "utf8");
|
|
25533
25621
|
} catch {
|
|
25534
25622
|
return void 0;
|
|
25535
25623
|
}
|
|
@@ -25571,19 +25659,19 @@ ${cu.tail.trimEnd()}
|
|
|
25571
25659
|
let cleared;
|
|
25572
25660
|
if (clear2) {
|
|
25573
25661
|
const plan = clearRouteHooks(configText, a.route, a.name);
|
|
25574
|
-
if (plan.removed.length > 0)
|
|
25662
|
+
if (plan.removed.length > 0) writeFileSync34(cfg.configFile, plan.text);
|
|
25575
25663
|
cleared = { file: cfg.configFile, removed: plan.removed };
|
|
25576
25664
|
}
|
|
25577
25665
|
if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
25578
25666
|
if (write === "route") {
|
|
25579
25667
|
const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
|
|
25580
|
-
|
|
25668
|
+
writeFileSync34(cfg.configFile, plan.text);
|
|
25581
25669
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
|
|
25582
25670
|
} else if (write === "repo") {
|
|
25583
25671
|
const have = existingRepoConfig(files);
|
|
25584
25672
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
25585
25673
|
const file = resolvePath(dir, ".crew.yaml");
|
|
25586
|
-
|
|
25674
|
+
writeFileSync34(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
|
|
25587
25675
|
written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
|
|
25588
25676
|
}
|
|
25589
25677
|
if (flag("json")) {
|
|
@@ -25639,7 +25727,7 @@ ${cu.tail.trimEnd()}
|
|
|
25639
25727
|
if (sub === "list" && !routeName) usage2();
|
|
25640
25728
|
if (sub === "add") {
|
|
25641
25729
|
try {
|
|
25642
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
25730
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync46(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
|
|
25643
25731
|
routeName = a.route;
|
|
25644
25732
|
addPath = a.path;
|
|
25645
25733
|
addName = a.name;
|
|
@@ -25669,7 +25757,7 @@ ${cu.tail.trimEnd()}
|
|
|
25669
25757
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
25670
25758
|
if (sub === "list" || sub === "add") {
|
|
25671
25759
|
const liveRoute = cfg.routes.find((r) => r.route === routeName);
|
|
25672
|
-
if (liveRoute?.resolved?.reposModelId &&
|
|
25760
|
+
if (liveRoute?.resolved?.reposModelId && existsSync46(resolvedFile) && !dryRun) {
|
|
25673
25761
|
try {
|
|
25674
25762
|
await hydrateApiKeys([liveRoute]);
|
|
25675
25763
|
persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
|
|
@@ -25678,16 +25766,16 @@ ${cu.tail.trimEnd()}
|
|
|
25678
25766
|
}
|
|
25679
25767
|
}
|
|
25680
25768
|
let resolvedRemotes;
|
|
25681
|
-
if (
|
|
25769
|
+
if (existsSync46(resolvedFile)) {
|
|
25682
25770
|
try {
|
|
25683
|
-
resolvedRemotes = JSON.parse(
|
|
25771
|
+
resolvedRemotes = JSON.parse(readFileSync41(resolvedFile, "utf8"));
|
|
25684
25772
|
} catch {
|
|
25685
25773
|
}
|
|
25686
25774
|
}
|
|
25687
25775
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
25688
25776
|
const text2 = configText;
|
|
25689
|
-
const base =
|
|
25690
|
-
const isCheckout = (d) =>
|
|
25777
|
+
const base = dirname29(cfg.configFile);
|
|
25778
|
+
const isCheckout = (d) => existsSync46(`${d}/.git`);
|
|
25691
25779
|
try {
|
|
25692
25780
|
if (sub === "list") {
|
|
25693
25781
|
if (flag("json")) {
|
|
@@ -25697,11 +25785,11 @@ ${cu.tail.trimEnd()}
|
|
|
25697
25785
|
base,
|
|
25698
25786
|
resolved: resolvedRemotes,
|
|
25699
25787
|
originOf,
|
|
25700
|
-
exists: (d) =>
|
|
25788
|
+
exists: (d) => existsSync46(d),
|
|
25701
25789
|
isCheckout,
|
|
25702
25790
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
25703
25791
|
try {
|
|
25704
|
-
return Object.keys(parseRepoConfig(
|
|
25792
|
+
return Object.keys(parseRepoConfig(readFileSync41(resolvePath(d, f), "utf8"), f).hooks).length > 0;
|
|
25705
25793
|
} catch {
|
|
25706
25794
|
return false;
|
|
25707
25795
|
}
|
|
@@ -25718,7 +25806,7 @@ ${cu.tail.trimEnd()}
|
|
|
25718
25806
|
for (const r of repos) {
|
|
25719
25807
|
const origin2 = originOf(r.dir);
|
|
25720
25808
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
25721
|
-
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)"}`;
|
|
25722
25810
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
25723
25811
|
`);
|
|
25724
25812
|
}
|
|
@@ -25730,9 +25818,9 @@ ${cu.tail.trimEnd()}
|
|
|
25730
25818
|
` : `${human}
|
|
25731
25819
|
`);
|
|
25732
25820
|
const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
|
|
25733
|
-
if (
|
|
25821
|
+
if (existsSync46(plan2.target) && (existsSync46(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
|
|
25734
25822
|
throw new ReposError(
|
|
25735
|
-
|
|
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`
|
|
25736
25824
|
);
|
|
25737
25825
|
}
|
|
25738
25826
|
if (dryRun) {
|
|
@@ -25750,14 +25838,14 @@ ${cu.tail.trimEnd()}
|
|
|
25750
25838
|
throw new ReposError(`${e.message}${authHint}
|
|
25751
25839
|
crew.yaml was not changed`);
|
|
25752
25840
|
}
|
|
25753
|
-
|
|
25841
|
+
writeFileSync34(cfg.configFile, plan2.text);
|
|
25754
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:
|
|
25755
25843
|
`);
|
|
25756
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}`);
|
|
25757
25845
|
break;
|
|
25758
25846
|
}
|
|
25759
25847
|
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
|
|
25760
|
-
const top =
|
|
25848
|
+
const top = existsSync46(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
25761
25849
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
25762
25850
|
const origin = originOf(top);
|
|
25763
25851
|
const plan = planRepoAdd({
|
|
@@ -25809,15 +25897,15 @@ ${cu.tail.trimEnd()}
|
|
|
25809
25897
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
25810
25898
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
25811
25899
|
`);
|
|
25812
|
-
if (
|
|
25813
|
-
const raw = JSON.parse(
|
|
25900
|
+
if (existsSync46(resolvedFile)) {
|
|
25901
|
+
const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
|
|
25814
25902
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
25815
25903
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
25816
|
-
|
|
25904
|
+
writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
25817
25905
|
`);
|
|
25818
25906
|
}
|
|
25819
25907
|
}
|
|
25820
|
-
|
|
25908
|
+
writeFileSync34(cfg.configFile, plan.text);
|
|
25821
25909
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
25822
25910
|
`);
|
|
25823
25911
|
} catch (e) {
|
|
@@ -26186,7 +26274,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26186
26274
|
process.exit(2);
|
|
26187
26275
|
}
|
|
26188
26276
|
if (found.shipNameAdopted && !dryRun) {
|
|
26189
|
-
|
|
26277
|
+
writeFileSync34(cfg.configFile, setShipNameInYaml(readFileSync41(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
26190
26278
|
cfg.ship.name = found.shipNameAdopted;
|
|
26191
26279
|
}
|
|
26192
26280
|
if (found.shipRow) {
|
|
@@ -26229,10 +26317,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26229
26317
|
}
|
|
26230
26318
|
}
|
|
26231
26319
|
let previous;
|
|
26232
|
-
const hadPreviousFile =
|
|
26320
|
+
const hadPreviousFile = existsSync46(resolvedPath);
|
|
26233
26321
|
if (hadPreviousFile) {
|
|
26234
26322
|
try {
|
|
26235
|
-
previous = JSON.parse(
|
|
26323
|
+
previous = JSON.parse(readFileSync41(resolvedPath, "utf8"));
|
|
26236
26324
|
} catch {
|
|
26237
26325
|
}
|
|
26238
26326
|
}
|
|
@@ -26362,10 +26450,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26362
26450
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
26363
26451
|
`);
|
|
26364
26452
|
} else {
|
|
26365
|
-
|
|
26453
|
+
mkdirSync33(dirname29(resolvedPath), { recursive: true });
|
|
26366
26454
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
26367
26455
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
26368
|
-
|
|
26456
|
+
writeFileSync34(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
26369
26457
|
`);
|
|
26370
26458
|
renameSync6(tmpPath, resolvedPath);
|
|
26371
26459
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
@@ -26411,8 +26499,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26411
26499
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
26412
26500
|
`);
|
|
26413
26501
|
} else {
|
|
26414
|
-
|
|
26415
|
-
|
|
26502
|
+
mkdirSync33(dirname29(apiKeyFile), { recursive: true });
|
|
26503
|
+
writeFileSync34(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
26416
26504
|
`, { mode: 384 });
|
|
26417
26505
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
26418
26506
|
`);
|
|
@@ -27139,7 +27227,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27139
27227
|
};
|
|
27140
27228
|
if (subP === "list") {
|
|
27141
27229
|
const entries = periscopeListEntries(
|
|
27142
|
-
(await Promise.all(routesP.map(gather))).flat(),
|
|
27230
|
+
(await Promise.all(routesP.map(gather))).flat().map((r) => ({ ...r, pointedAt: readPointedAt(r.worktree) })),
|
|
27143
27231
|
/* @__PURE__ */ new Date(),
|
|
27144
27232
|
(r) => pidsInWorktree(r.worktree, Number(r.number)).length > 0
|
|
27145
27233
|
);
|
|
@@ -27287,7 +27375,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27287
27375
|
const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
|
|
27288
27376
|
const resumed = await runPeriscopeResume({
|
|
27289
27377
|
find: async () => hit && { row: hit.row, tag: hit.tag },
|
|
27290
|
-
worktreeExists: (p) =>
|
|
27378
|
+
worktreeExists: (p) => existsSync46(p),
|
|
27379
|
+
prepareDropFile,
|
|
27291
27380
|
caps: periscopeCaps(cfg.ship),
|
|
27292
27381
|
runHook: async (name, cwd, env) => {
|
|
27293
27382
|
const script = hit?.repo.config.hooks[name];
|
|
@@ -27381,10 +27470,10 @@ ${where.join("\n")}`);
|
|
|
27381
27470
|
},
|
|
27382
27471
|
prepare: async (path) => {
|
|
27383
27472
|
for (const f of repoP.config.worktrees.copy) {
|
|
27384
|
-
const src =
|
|
27385
|
-
if (!
|
|
27386
|
-
|
|
27387
|
-
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));
|
|
27388
27477
|
}
|
|
27389
27478
|
const setup = repoP.config.hooks.setup;
|
|
27390
27479
|
return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
|
|
@@ -27396,6 +27485,7 @@ ${where.join("\n")}`);
|
|
|
27396
27485
|
return script ? runScript(script, { cwd, env, shell: shellP, timeoutMs: name === "preview" ? 18e4 : void 0 }) : null;
|
|
27397
27486
|
},
|
|
27398
27487
|
createRow: (fields) => trackerP.createPeriscope(fields),
|
|
27488
|
+
prepareDropFile,
|
|
27399
27489
|
recordLocal: (sc) => {
|
|
27400
27490
|
writePeriscopeSidecar(cfg.ship.stateDir, { ...sc, route: routeP.route });
|
|
27401
27491
|
if (flag("serve-mcp")) mintPeriscopeSecret(cfg.ship.stateDir, sc.ticketRecordId);
|
|
@@ -27679,7 +27769,7 @@ ${where.join("\n")}`);
|
|
|
27679
27769
|
};
|
|
27680
27770
|
const droppedRoutes = [];
|
|
27681
27771
|
try {
|
|
27682
|
-
const rawCfg = parseYaml2(
|
|
27772
|
+
const rawCfg = parseYaml2(readFileSync41(cfg.configFile, "utf8"));
|
|
27683
27773
|
for (const r of rawCfg?.routes ?? []) {
|
|
27684
27774
|
if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
|
|
27685
27775
|
try {
|
|
@@ -27732,8 +27822,8 @@ ${where.join("\n")}`);
|
|
|
27732
27822
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
27733
27823
|
break;
|
|
27734
27824
|
}
|
|
27735
|
-
|
|
27736
|
-
|
|
27825
|
+
mkdirSync33(cfg.ship.stateDir, { recursive: true });
|
|
27826
|
+
writeFileSync34(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
27737
27827
|
`);
|
|
27738
27828
|
process.stdout.write(
|
|
27739
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"
|
|
@@ -27798,7 +27888,7 @@ ${where.join("\n")}`);
|
|
|
27798
27888
|
}
|
|
27799
27889
|
case "log": {
|
|
27800
27890
|
try {
|
|
27801
|
-
const text2 =
|
|
27891
|
+
const text2 = readFileSync41(cfg.ship.logFile, "utf8").split("\n");
|
|
27802
27892
|
process.stdout.write(`${text2.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
27803
27893
|
`);
|
|
27804
27894
|
} catch {
|
|
@@ -27821,8 +27911,8 @@ ${where.join("\n")}`);
|
|
|
27821
27911
|
};
|
|
27822
27912
|
if (!route2.resolved) fail(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
|
|
27823
27913
|
const tracker = new Tracker(route2, cfg.ship);
|
|
27824
|
-
const configText =
|
|
27825
|
-
const cfgBase =
|
|
27914
|
+
const configText = readFileSync41(cfg.configFile, "utf8");
|
|
27915
|
+
const cfgBase = dirname29(cfg.configFile);
|
|
27826
27916
|
const configured2 = configuredRepos(configText, route2.route, cfgBase);
|
|
27827
27917
|
const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
|
|
27828
27918
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
|
|
@@ -27850,7 +27940,7 @@ ${where.join("\n")}`);
|
|
|
27850
27940
|
}
|
|
27851
27941
|
if (configured2.some((r) => r.dir === dir)) {
|
|
27852
27942
|
try {
|
|
27853
|
-
|
|
27943
|
+
writeFileSync34(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
|
|
27854
27944
|
} catch (e) {
|
|
27855
27945
|
if (e instanceof ReposError) fail(e.message);
|
|
27856
27946
|
throw e;
|
|
@@ -27858,29 +27948,29 @@ ${where.join("\n")}`);
|
|
|
27858
27948
|
}
|
|
27859
27949
|
if (row) {
|
|
27860
27950
|
await tracker.deleteRepoRow(row.id);
|
|
27861
|
-
if (
|
|
27862
|
-
const raw = JSON.parse(
|
|
27951
|
+
if (existsSync46(resolvedFile)) {
|
|
27952
|
+
const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
|
|
27863
27953
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
27864
27954
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
27865
|
-
|
|
27955
|
+
writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
27866
27956
|
`);
|
|
27867
27957
|
}
|
|
27868
27958
|
}
|
|
27869
|
-
if (
|
|
27959
|
+
if (existsSync46(join50(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync23(dir, { recursive: true, force: true });
|
|
27870
27960
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
27871
27961
|
break;
|
|
27872
27962
|
}
|
|
27873
27963
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
27874
|
-
const fresh = !
|
|
27875
|
-
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`);
|
|
27876
27966
|
if (dryRun) {
|
|
27877
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`);
|
|
27878
27968
|
break;
|
|
27879
27969
|
}
|
|
27880
27970
|
if (fresh) {
|
|
27881
|
-
|
|
27882
|
-
|
|
27883
|
-
|
|
27971
|
+
mkdirSync33(dir, { recursive: true });
|
|
27972
|
+
writeFileSync34(join50(dir, "README.md"), SAMPLE_README);
|
|
27973
|
+
writeFileSync34(join50(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
27884
27974
|
const g = (...a) => {
|
|
27885
27975
|
const r = spawnSync10("git", a, { cwd: dir, encoding: "utf8" });
|
|
27886
27976
|
if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -27898,17 +27988,17 @@ ${where.join("\n")}`);
|
|
|
27898
27988
|
spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
|
|
27899
27989
|
});
|
|
27900
27990
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
27901
|
-
if (
|
|
27902
|
-
const raw = JSON.parse(
|
|
27991
|
+
if (existsSync46(resolvedFile)) {
|
|
27992
|
+
const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
|
|
27903
27993
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
27904
27994
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
27905
|
-
|
|
27995
|
+
writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
27906
27996
|
`);
|
|
27907
27997
|
}
|
|
27908
27998
|
}
|
|
27909
27999
|
if (!configured2.some((r) => r.dir === dir)) {
|
|
27910
28000
|
try {
|
|
27911
|
-
|
|
28001
|
+
writeFileSync34(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
|
|
27912
28002
|
} catch (e) {
|
|
27913
28003
|
if (e instanceof ReposError) fail(e.message);
|
|
27914
28004
|
throw e;
|
|
@@ -28254,7 +28344,7 @@ watermark: ${state.watermark()}
|
|
|
28254
28344
|
}
|
|
28255
28345
|
for (const r of repos) {
|
|
28256
28346
|
const remote = r.config.branch.remote;
|
|
28257
|
-
if (
|
|
28347
|
+
if (existsSync46(r.dir)) {
|
|
28258
28348
|
pushAuth.push({
|
|
28259
28349
|
route: c.route,
|
|
28260
28350
|
repo: r.name,
|
|
@@ -28343,7 +28433,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
28343
28433
|
}
|
|
28344
28434
|
try {
|
|
28345
28435
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
28346
|
-
const evSize =
|
|
28436
|
+
const evSize = existsSync46(evFile) ? statSync16(evFile).size : 0;
|
|
28347
28437
|
process.stdout.write(
|
|
28348
28438
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
28349
28439
|
` : "events log: ok\n"
|
|
@@ -28354,7 +28444,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
28354
28444
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run", installContext());
|
|
28355
28445
|
const abandons = unitAbandonsAgents(unitPlan, (p) => {
|
|
28356
28446
|
try {
|
|
28357
|
-
return
|
|
28447
|
+
return readFileSync41(p, "utf8");
|
|
28358
28448
|
} catch {
|
|
28359
28449
|
return null;
|
|
28360
28450
|
}
|
|
@@ -28377,7 +28467,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
28377
28467
|
const changed2 = configChangedAfter(daemonConfigPaths(cfg), daemonStartedAt);
|
|
28378
28468
|
if (changed2) {
|
|
28379
28469
|
process.stdout.write(
|
|
28380
|
-
`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\`
|
|
28381
28471
|
`
|
|
28382
28472
|
);
|
|
28383
28473
|
}
|