@tablation/crew 0.114.0 → 0.116.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 +898 -741
- 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
|
|
@@ -2305,6 +2382,7 @@ var HOOK_NAMES = [
|
|
|
2305
2382
|
"isolate",
|
|
2306
2383
|
"handoff",
|
|
2307
2384
|
"preview",
|
|
2385
|
+
"map",
|
|
2308
2386
|
"ci_status",
|
|
2309
2387
|
"version",
|
|
2310
2388
|
"bump",
|
|
@@ -2375,8 +2453,8 @@ var MODES = ["local", "integrate", "ci_manual", "ci_auto", "external"];
|
|
|
2375
2453
|
var PROVIDERS = ["github", "buildkite", "other", "none"];
|
|
2376
2454
|
function findRepoConfig(dir) {
|
|
2377
2455
|
for (const name of REPO_CONFIG_FILENAMES) {
|
|
2378
|
-
const p =
|
|
2379
|
-
if (
|
|
2456
|
+
const p = join12(dir, name);
|
|
2457
|
+
if (existsSync10(p)) return p;
|
|
2380
2458
|
}
|
|
2381
2459
|
return null;
|
|
2382
2460
|
}
|
|
@@ -2656,7 +2734,7 @@ function parseRepoConfig(text2, file) {
|
|
|
2656
2734
|
function loadRepoConfig(dir) {
|
|
2657
2735
|
const file = findRepoConfig(dir);
|
|
2658
2736
|
if (!file) return null;
|
|
2659
|
-
return parseRepoConfig(
|
|
2737
|
+
return parseRepoConfig(readFileSync11(file, "utf8"), file);
|
|
2660
2738
|
}
|
|
2661
2739
|
function hookLabel(cfg, hook2) {
|
|
2662
2740
|
return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
|
|
@@ -2937,6 +3015,7 @@ var ConfigError = class extends Error {
|
|
|
2937
3015
|
};
|
|
2938
3016
|
var DEFAULT_MAX_PERISCOPES = 3;
|
|
2939
3017
|
var DEFAULT_PERISCOPE_IDLE_MINUTES = 120;
|
|
3018
|
+
var DEFAULT_MAP_TOKEN_BUDGET = 1e3;
|
|
2940
3019
|
function parseCloneScheme(v) {
|
|
2941
3020
|
if (v === void 0 || v === null || v === "") return void 0;
|
|
2942
3021
|
if (v === "ssh" || v === "https") return v;
|
|
@@ -3029,6 +3108,7 @@ var SHIP_KEYS = /* @__PURE__ */ new Set([
|
|
|
3029
3108
|
"previewHost",
|
|
3030
3109
|
"maxPeriscopes",
|
|
3031
3110
|
"periscopeIdleMinutes",
|
|
3111
|
+
"mapTokenBudget",
|
|
3032
3112
|
"secretStore",
|
|
3033
3113
|
"env",
|
|
3034
3114
|
"runner",
|
|
@@ -3178,14 +3258,14 @@ function mergeRouteRelease(route2, override) {
|
|
|
3178
3258
|
function expand(p, base) {
|
|
3179
3259
|
let out = p.startsWith("~") ? p.replace(/^~/, homedir6()) : p;
|
|
3180
3260
|
out = out.replace(/\$\{?HOME\}?/g, homedir6());
|
|
3181
|
-
return
|
|
3261
|
+
return isAbsolute2(out) ? out : resolve2(base, out);
|
|
3182
3262
|
}
|
|
3183
3263
|
function resolvePromptsDir(promptSet, crewHome, base) {
|
|
3184
|
-
if (!promptSet) return
|
|
3264
|
+
if (!promptSet) return join13(crewHome, "prompts", "default");
|
|
3185
3265
|
if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
|
|
3186
3266
|
return expand(promptSet, base);
|
|
3187
3267
|
}
|
|
3188
|
-
return
|
|
3268
|
+
return join13(crewHome, "prompts", promptSet);
|
|
3189
3269
|
}
|
|
3190
3270
|
var Missing = class {
|
|
3191
3271
|
paths = [];
|
|
@@ -3209,36 +3289,36 @@ var Missing = class {
|
|
|
3209
3289
|
};
|
|
3210
3290
|
function crewVersion(crewHome) {
|
|
3211
3291
|
try {
|
|
3212
|
-
const pkg = JSON.parse(
|
|
3292
|
+
const pkg = JSON.parse(readFileSync12(join13(crewHome, "package.json"), "utf8"));
|
|
3213
3293
|
return pkg.version ?? "0.0.0";
|
|
3214
3294
|
} catch {
|
|
3215
3295
|
return "0.0.0";
|
|
3216
3296
|
}
|
|
3217
3297
|
}
|
|
3218
3298
|
function configSearchPath(crewHome) {
|
|
3219
|
-
if (process.env.CREW_CONFIG) return [
|
|
3299
|
+
if (process.env.CREW_CONFIG) return [resolve2(process.env.CREW_CONFIG)];
|
|
3220
3300
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
3221
3301
|
return [
|
|
3222
|
-
...xdg ? [
|
|
3223
|
-
|
|
3224
|
-
|
|
3302
|
+
...xdg ? [join13(xdg, "crew", "crew.yaml")] : [],
|
|
3303
|
+
join13(homedir6(), ".config", "crew", "crew.yaml"),
|
|
3304
|
+
resolve2(crewHome, "crew.yaml")
|
|
3225
3305
|
];
|
|
3226
3306
|
}
|
|
3227
3307
|
function findConfigFile(crewHome) {
|
|
3228
3308
|
const candidates2 = configSearchPath(crewHome);
|
|
3229
|
-
return candidates2.find((c) =>
|
|
3309
|
+
return candidates2.find((c) => existsSync11(c)) ?? candidates2[candidates2.length - 1];
|
|
3230
3310
|
}
|
|
3231
|
-
var defaultConfigPath = () =>
|
|
3311
|
+
var defaultConfigPath = () => join13(process.env.XDG_CONFIG_HOME ?? join13(homedir6(), ".config"), "crew", "crew.yaml");
|
|
3232
3312
|
function routeSlug(route2) {
|
|
3233
3313
|
return route2.replace(/\//g, "-");
|
|
3234
3314
|
}
|
|
3235
3315
|
function resolvedPathFor(stateDir, route2) {
|
|
3236
3316
|
const [workspace, project] = splitRoute(route2);
|
|
3237
|
-
return
|
|
3317
|
+
return join13(stateDir, "resolved", workspace, `${project}.json`);
|
|
3238
3318
|
}
|
|
3239
3319
|
function apiKeyPathFor(stateDir, route2) {
|
|
3240
3320
|
const [workspace, project] = splitRoute(route2);
|
|
3241
|
-
return
|
|
3321
|
+
return join13(stateDir, "keys", workspace, `${project}.env`);
|
|
3242
3322
|
}
|
|
3243
3323
|
function splitRoute(route2) {
|
|
3244
3324
|
const parts = route2.split("/");
|
|
@@ -3249,9 +3329,9 @@ function splitRoute(route2) {
|
|
|
3249
3329
|
}
|
|
3250
3330
|
function readResolvedFile(stateDir, route2) {
|
|
3251
3331
|
const path = resolvedPathFor(stateDir, route2);
|
|
3252
|
-
if (!
|
|
3332
|
+
if (!existsSync11(path)) return void 0;
|
|
3253
3333
|
try {
|
|
3254
|
-
return JSON.parse(
|
|
3334
|
+
return JSON.parse(readFileSync12(path, "utf8"));
|
|
3255
3335
|
} catch (e) {
|
|
3256
3336
|
throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
|
|
3257
3337
|
}
|
|
@@ -3312,23 +3392,23 @@ function parseResolved(raw, m, where) {
|
|
|
3312
3392
|
}
|
|
3313
3393
|
function loadConfig(crewHome, configFile, opts = {}) {
|
|
3314
3394
|
const file = configFile ?? findConfigFile(crewHome);
|
|
3315
|
-
if (opts.text === void 0 && !
|
|
3395
|
+
if (opts.text === void 0 && !existsSync11(file)) {
|
|
3316
3396
|
throw new ConfigError(
|
|
3317
3397
|
`no config found. Looked in:
|
|
3318
3398
|
` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
|
|
3319
3399
|
|
|
3320
3400
|
Create the first of those:
|
|
3321
|
-
mkdir -p ${
|
|
3401
|
+
mkdir -p ${dirname5(defaultConfigPath())} && cp ${join13(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
|
|
3322
3402
|
);
|
|
3323
3403
|
}
|
|
3324
3404
|
let raw;
|
|
3325
3405
|
try {
|
|
3326
|
-
raw = parse2(opts.text ??
|
|
3406
|
+
raw = parse2(opts.text ?? readFileSync12(file, "utf8"));
|
|
3327
3407
|
} catch (e) {
|
|
3328
3408
|
throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
|
|
3329
3409
|
}
|
|
3330
3410
|
if (!raw || typeof raw !== "object") throw new ConfigError(`${file} is empty or not a mapping`);
|
|
3331
|
-
const base =
|
|
3411
|
+
const base = dirname5(file);
|
|
3332
3412
|
const missing = new Missing();
|
|
3333
3413
|
rejectUnknownKeys(raw, /* @__PURE__ */ new Set(["ship", "routes"]), "top-level", file);
|
|
3334
3414
|
const shipRaw = raw.ship ?? {};
|
|
@@ -3377,6 +3457,10 @@ Create the first of those:
|
|
|
3377
3457
|
if (!Number.isInteger(periscopeIdleMinutes) || periscopeIdleMinutes < 1) {
|
|
3378
3458
|
missing.add(`ship.periscopeIdleMinutes must be a positive integer (got "${shipRaw.periscopeIdleMinutes}")`);
|
|
3379
3459
|
}
|
|
3460
|
+
const mapTokenBudget = shipRaw.mapTokenBudget === void 0 ? DEFAULT_MAP_TOKEN_BUDGET : Number(shipRaw.mapTokenBudget);
|
|
3461
|
+
if (!Number.isInteger(mapTokenBudget) || mapTokenBudget < 1) {
|
|
3462
|
+
missing.add(`ship.mapTokenBudget must be a positive integer (got "${shipRaw.mapTokenBudget}")`);
|
|
3463
|
+
}
|
|
3380
3464
|
const secretStoreRaw = shipRaw.secretStore === void 0 ? "auto" : shipRaw.secretStore;
|
|
3381
3465
|
if (!isSecretStoreMode(secretStoreRaw)) {
|
|
3382
3466
|
missing.add(`ship.secretStore must be one of ${SECRET_STORE_MODES.join(" | ")} (got "${String(shipRaw.secretStore)}")`);
|
|
@@ -3442,7 +3526,7 @@ Create the first of those:
|
|
|
3442
3526
|
useNvm: shipRaw.useNvm !== false,
|
|
3443
3527
|
nvmSh: shipRaw.nvmSh,
|
|
3444
3528
|
stateDir,
|
|
3445
|
-
logFile: expand(shipRaw.logFile ??
|
|
3529
|
+
logFile: expand(shipRaw.logFile ?? join13(tmpdir(), "crew.log"), base),
|
|
3446
3530
|
userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
|
|
3447
3531
|
maxConcurrentAgents,
|
|
3448
3532
|
maxConcurrentAgentsSet,
|
|
@@ -3460,6 +3544,7 @@ Create the first of those:
|
|
|
3460
3544
|
previewHost: shipRaw.previewHost ? String(shipRaw.previewHost).trim() || void 0 : void 0,
|
|
3461
3545
|
maxPeriscopes,
|
|
3462
3546
|
periscopeIdleMinutes,
|
|
3547
|
+
mapTokenBudget,
|
|
3463
3548
|
secretStore,
|
|
3464
3549
|
env: parseShipEnv(shipRaw.env)
|
|
3465
3550
|
},
|
|
@@ -3622,7 +3707,7 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
|
|
|
3622
3707
|
const hasRepos = Object.keys(repoDirs).length > 0;
|
|
3623
3708
|
const reposBasePath = expand(String(c?.reposBasePath ?? raw.ship?.reposBasePath ?? DEFAULT_REPOS_BASE_PATH), base);
|
|
3624
3709
|
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 ?
|
|
3710
|
+
const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join13(reposBasePath, splitRoute(route2)[0]) : "";
|
|
3626
3711
|
const weight = c?.weight === void 0 ? void 0 : Number(c.weight);
|
|
3627
3712
|
if (weight !== void 0 && !(Number.isFinite(weight) && weight > 0)) {
|
|
3628
3713
|
missing.add(`${where}.weight must be a positive number (got "${c?.weight}")`);
|
|
@@ -3671,21 +3756,21 @@ function routeForDir(cfg, dir) {
|
|
|
3671
3756
|
}
|
|
3672
3757
|
function locateDir(cfg, dir, commonDirOf) {
|
|
3673
3758
|
const within = (base, target2) => {
|
|
3674
|
-
const b =
|
|
3759
|
+
const b = resolve2(base);
|
|
3675
3760
|
return target2 === b || target2.startsWith(`${b}/`);
|
|
3676
3761
|
};
|
|
3677
3762
|
const find = (target2) => {
|
|
3678
3763
|
for (const route2 of cfg.routes) {
|
|
3679
3764
|
const repo = reposOf(route2).find((x) => within(x.dir, target2));
|
|
3680
|
-
if (repo) return { route: route2, repo, checkout:
|
|
3765
|
+
if (repo) return { route: route2, repo, checkout: resolve2(repo.dir) };
|
|
3681
3766
|
}
|
|
3682
3767
|
return void 0;
|
|
3683
3768
|
};
|
|
3684
|
-
const target =
|
|
3769
|
+
const target = resolve2(dir);
|
|
3685
3770
|
const direct = find(target);
|
|
3686
3771
|
if (direct) return direct;
|
|
3687
3772
|
const common = commonDirOf?.(target);
|
|
3688
|
-
return common ? find(
|
|
3773
|
+
return common ? find(dirname5(resolve2(target, common))) : void 0;
|
|
3689
3774
|
}
|
|
3690
3775
|
function findRoute(cfg, route2) {
|
|
3691
3776
|
if (!route2) {
|
|
@@ -3720,8 +3805,8 @@ function repoIdForName(r, name) {
|
|
|
3720
3805
|
}
|
|
3721
3806
|
function resolveApiKey(r) {
|
|
3722
3807
|
if (r.apiKey) return r.apiKey;
|
|
3723
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
3724
|
-
for (const line of
|
|
3808
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync11(r.apiKeyFile)) {
|
|
3809
|
+
for (const line of readFileSync12(r.apiKeyFile, "utf8").split("\n")) {
|
|
3725
3810
|
const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
|
|
3726
3811
|
if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
3727
3812
|
}
|
|
@@ -3734,9 +3819,9 @@ function resolveApiKey(r) {
|
|
|
3734
3819
|
}
|
|
3735
3820
|
function credentialSource(r) {
|
|
3736
3821
|
if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
|
|
3737
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
3822
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync11(r.apiKeyFile)) {
|
|
3738
3823
|
const re = new RegExp(`^${r.apiKeyVar}=`);
|
|
3739
|
-
if (
|
|
3824
|
+
if (readFileSync12(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
|
|
3740
3825
|
}
|
|
3741
3826
|
return process.env.CREW_API_KEY?.trim() ? "CREW_API_KEY" : "none";
|
|
3742
3827
|
}
|
|
@@ -3792,7 +3877,7 @@ function configuredMembers(rt) {
|
|
|
3792
3877
|
return out;
|
|
3793
3878
|
}
|
|
3794
3879
|
function defaultRepoDir(basePath, workspace, repoName) {
|
|
3795
|
-
return
|
|
3880
|
+
return join13(basePath, workspace, repoName);
|
|
3796
3881
|
}
|
|
3797
3882
|
function reposOf(r) {
|
|
3798
3883
|
const named = Object.entries(r.repos);
|
|
@@ -3831,10 +3916,10 @@ function passengerRepoTargets(r) {
|
|
|
3831
3916
|
return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
|
|
3832
3917
|
}
|
|
3833
3918
|
function passengerCheckoutDir(basePath, workspace, repoName) {
|
|
3834
|
-
return
|
|
3919
|
+
return join13(basePath, workspace, ".passenger-checkouts", repoName);
|
|
3835
3920
|
}
|
|
3836
3921
|
function passengerMirrorArea(ship) {
|
|
3837
|
-
return ship.runner === "sandbox" ?
|
|
3922
|
+
return ship.runner === "sandbox" ? join13(ship.stateDir, "passenger-sources") : void 0;
|
|
3838
3923
|
}
|
|
3839
3924
|
function passengerCheckoutTargets(r, mirrorArea) {
|
|
3840
3925
|
if (!r.hostPassengers) return [];
|
|
@@ -3845,7 +3930,7 @@ function passengerCheckoutTargets(r, mirrorArea) {
|
|
|
3845
3930
|
const forgeHost = t.forgeHost ?? (id ? r.resolved?.repoForgeHosts?.[id] : void 0);
|
|
3846
3931
|
return {
|
|
3847
3932
|
name: t.name,
|
|
3848
|
-
dir: mirrorArea ?
|
|
3933
|
+
dir: mirrorArea ? join13(mirrorArea, workspace, t.name) : passengerCheckoutDir(r.reposBasePath, workspace, t.name),
|
|
3849
3934
|
remote: t.remote ?? (id ? remotes[id] : void 0),
|
|
3850
3935
|
...forgeHost ? { forgeHost } : {}
|
|
3851
3936
|
};
|
|
@@ -3962,6 +4047,7 @@ function runScript(script, opts) {
|
|
|
3962
4047
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3963
4048
|
});
|
|
3964
4049
|
let output = "";
|
|
4050
|
+
let stdout = "";
|
|
3965
4051
|
let pending = "";
|
|
3966
4052
|
const absorb = (buf) => {
|
|
3967
4053
|
const text2 = buf.toString();
|
|
@@ -3972,18 +4058,21 @@ function runScript(script, opts) {
|
|
|
3972
4058
|
pending = lines2.pop() ?? "";
|
|
3973
4059
|
for (const l of lines2) opts.onLine(l);
|
|
3974
4060
|
};
|
|
3975
|
-
child.stdout.on("data",
|
|
4061
|
+
child.stdout.on("data", (buf) => {
|
|
4062
|
+
stdout += buf.toString();
|
|
4063
|
+
absorb(buf);
|
|
4064
|
+
});
|
|
3976
4065
|
child.stderr.on("data", absorb);
|
|
3977
4066
|
const timer = opts.timeoutMs ? setTimeout(() => child.kill("SIGKILL"), opts.timeoutMs) : void 0;
|
|
3978
4067
|
child.on("error", (err3) => {
|
|
3979
4068
|
if (timer) clearTimeout(timer);
|
|
3980
4069
|
resolvePromise({ code: 127, output: `${output}
|
|
3981
|
-
crew: cannot run ${shell.bin}: ${err3.message}
|
|
4070
|
+
crew: cannot run ${shell.bin}: ${err3.message}`, stdout });
|
|
3982
4071
|
});
|
|
3983
4072
|
child.on("close", (code) => {
|
|
3984
4073
|
if (timer) clearTimeout(timer);
|
|
3985
4074
|
if (pending && opts.onLine) opts.onLine(pending);
|
|
3986
|
-
resolvePromise({ code: code ?? 1, output });
|
|
4075
|
+
resolvePromise({ code: code ?? 1, output, stdout });
|
|
3987
4076
|
});
|
|
3988
4077
|
});
|
|
3989
4078
|
}
|
|
@@ -4123,9 +4212,9 @@ function recoveredFailureHeadlines(o, route2) {
|
|
|
4123
4212
|
}
|
|
4124
4213
|
|
|
4125
4214
|
// src/install.ts
|
|
4126
|
-
import { dirname as
|
|
4215
|
+
import { dirname as dirname6, join as join14, extname } from "path";
|
|
4127
4216
|
import { homedir as homedir7, userInfo } from "os";
|
|
4128
|
-
import { accessSync as accessSync2, constants as constants2, existsSync as
|
|
4217
|
+
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
4218
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
4130
4219
|
import { createHash as createHash3 } from "crypto";
|
|
4131
4220
|
|
|
@@ -4137,7 +4226,7 @@ function isCompiledBinary(moduleUrl) {
|
|
|
4137
4226
|
// src/install.ts
|
|
4138
4227
|
function crewInvocation(crewHome) {
|
|
4139
4228
|
if (isCompiledBinary(import.meta.url)) return [process.execPath];
|
|
4140
|
-
return [process.execPath,
|
|
4229
|
+
return [process.execPath, join14(crewHome, "bin", "crew")];
|
|
4141
4230
|
}
|
|
4142
4231
|
var INTERVAL_SECONDS = 120;
|
|
4143
4232
|
function labelFor(crewHome, job) {
|
|
@@ -4168,29 +4257,29 @@ function schedulerLogFor(crewLog, job) {
|
|
|
4168
4257
|
return `${stem}.${suffix}${ext || ".log"}`;
|
|
4169
4258
|
}
|
|
4170
4259
|
function pathFor(ship) {
|
|
4171
|
-
const nodeBinDir =
|
|
4260
|
+
const nodeBinDir = dirname6(process.execPath);
|
|
4172
4261
|
const parts = [nodeBinDir];
|
|
4173
4262
|
if (ship.extraPath) parts.push(ship.extraPath);
|
|
4174
4263
|
parts.push("/usr/bin", "/bin", "/usr/sbin", "/sbin");
|
|
4175
4264
|
return parts.join(":");
|
|
4176
4265
|
}
|
|
4177
4266
|
function findOnPath(bin, searchPath) {
|
|
4178
|
-
const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) =>
|
|
4267
|
+
const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join14(dir, bin));
|
|
4179
4268
|
for (const candidate of candidates2) {
|
|
4180
4269
|
try {
|
|
4181
4270
|
accessSync2(candidate, constants2.X_OK);
|
|
4182
|
-
if (
|
|
4271
|
+
if (statSync5(candidate).isFile()) return candidate;
|
|
4183
4272
|
} catch {
|
|
4184
4273
|
}
|
|
4185
4274
|
}
|
|
4186
4275
|
return void 0;
|
|
4187
4276
|
}
|
|
4188
|
-
var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin",
|
|
4277
|
+
var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join14(homedir7(), ".docker", "bin")].join(":");
|
|
4189
4278
|
function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
|
|
4190
4279
|
const schedulerPath = pathFor(ship);
|
|
4191
4280
|
if (findOnPath("docker", schedulerPath)) return void 0;
|
|
4192
4281
|
const inShell = findOnPath("docker", shellPath);
|
|
4193
|
-
return inShell ? `\`docker\` is not on the scheduler PATH (${schedulerPath}) but is at ${inShell} in your shell; add ${
|
|
4282
|
+
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
4283
|
}
|
|
4195
4284
|
var USER_INSTALL = { root: false, user: "" };
|
|
4196
4285
|
function installContext(env = process.env) {
|
|
@@ -4200,7 +4289,7 @@ function installContext(env = process.env) {
|
|
|
4200
4289
|
}
|
|
4201
4290
|
var SYSTEM_UNIT_DIR = "/etc/systemd/system";
|
|
4202
4291
|
function systemUnitPathFor(label, suffix = "service") {
|
|
4203
|
-
return
|
|
4292
|
+
return join14(SYSTEM_UNIT_DIR, `${label}.${suffix}`);
|
|
4204
4293
|
}
|
|
4205
4294
|
function unitEnvironment(ship) {
|
|
4206
4295
|
return [["PATH", pathFor(ship)], ...Object.entries(ship.env ?? {})];
|
|
@@ -4221,7 +4310,7 @@ function xmlEscape(s) {
|
|
|
4221
4310
|
}
|
|
4222
4311
|
function planLaunchd(ship, crewHome, job) {
|
|
4223
4312
|
const label = labelFor(crewHome, job);
|
|
4224
|
-
const unitPath =
|
|
4313
|
+
const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4225
4314
|
const crewLog = ship.logFile;
|
|
4226
4315
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4227
4316
|
const persistent = job === "run";
|
|
@@ -4275,7 +4364,7 @@ ${scheduleKeys}
|
|
|
4275
4364
|
}
|
|
4276
4365
|
function planSystemd(ship, crewHome, job, ctx) {
|
|
4277
4366
|
const label = labelFor(crewHome, job);
|
|
4278
|
-
const unitDir = ctx.root ? SYSTEM_UNIT_DIR :
|
|
4367
|
+
const unitDir = ctx.root ? SYSTEM_UNIT_DIR : join14(homedir7(), ".config", "systemd", "user");
|
|
4279
4368
|
const ctl = ctx.root ? ["systemctl"] : ["systemctl", "--user"];
|
|
4280
4369
|
const userLine = ctx.root && ctx.user && ctx.user !== "root" ? `User=${ctx.user}
|
|
4281
4370
|
` : "";
|
|
@@ -4284,7 +4373,7 @@ function planSystemd(ship, crewHome, job, ctx) {
|
|
|
4284
4373
|
lingerCommand: ["loginctl", "enable-linger", ctx.user],
|
|
4285
4374
|
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
4375
|
};
|
|
4287
|
-
const servicePath =
|
|
4376
|
+
const servicePath = join14(unitDir, `${label}.service`);
|
|
4288
4377
|
const crewLog = ship.logFile;
|
|
4289
4378
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4290
4379
|
const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
|
|
@@ -4325,7 +4414,7 @@ WantedBy=${wantedBy}
|
|
|
4325
4414
|
...linger
|
|
4326
4415
|
};
|
|
4327
4416
|
}
|
|
4328
|
-
const timerPath =
|
|
4417
|
+
const timerPath = join14(unitDir, `${label}.timer`);
|
|
4329
4418
|
const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
|
|
4330
4419
|
[Unit]
|
|
4331
4420
|
Description=${description}
|
|
@@ -4400,7 +4489,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run", ctx = US
|
|
|
4400
4489
|
function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
4401
4490
|
const label = labelFor(crewHome, job);
|
|
4402
4491
|
if (hostShip === "macos") {
|
|
4403
|
-
const unitPath =
|
|
4492
|
+
const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4404
4493
|
return {
|
|
4405
4494
|
mechanism: "launchd",
|
|
4406
4495
|
unitPaths: [unitPath],
|
|
@@ -4410,20 +4499,20 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4410
4499
|
};
|
|
4411
4500
|
}
|
|
4412
4501
|
if (hostShip === "linux" && hasSystemd) {
|
|
4413
|
-
const system =
|
|
4414
|
-
const unitDir = system ? SYSTEM_UNIT_DIR :
|
|
4502
|
+
const system = existsSync12(systemUnitPathFor(label));
|
|
4503
|
+
const unitDir = system ? SYSTEM_UNIT_DIR : join14(homedir7(), ".config", "systemd", "user");
|
|
4415
4504
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
4416
4505
|
if (job === "run") {
|
|
4417
4506
|
return {
|
|
4418
4507
|
mechanism: "systemd",
|
|
4419
|
-
unitPaths: [
|
|
4508
|
+
unitPaths: [join14(unitDir, `${label}.service`)],
|
|
4420
4509
|
crontabLine: null,
|
|
4421
4510
|
unloadCommands: [[...ctl, "disable", "--now", `${label}.service`]]
|
|
4422
4511
|
};
|
|
4423
4512
|
}
|
|
4424
4513
|
return {
|
|
4425
4514
|
mechanism: "systemd",
|
|
4426
|
-
unitPaths: [
|
|
4515
|
+
unitPaths: [join14(unitDir, `${label}.service`), join14(unitDir, `${label}.timer`)],
|
|
4427
4516
|
crontabLine: null,
|
|
4428
4517
|
unloadCommands: [[...ctl, "disable", "--now", `${label}.timer`]]
|
|
4429
4518
|
};
|
|
@@ -4441,7 +4530,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4441
4530
|
function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
4442
4531
|
const label = labelFor(crewHome, "run");
|
|
4443
4532
|
if (hostShip === "macos") {
|
|
4444
|
-
const unitPath =
|
|
4533
|
+
const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4445
4534
|
return {
|
|
4446
4535
|
mechanism: "launchd",
|
|
4447
4536
|
startCommand: ["launchctl", "load", unitPath],
|
|
@@ -4451,7 +4540,7 @@ function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
|
4451
4540
|
}
|
|
4452
4541
|
if (hostShip === "linux" && hasSystemd) {
|
|
4453
4542
|
const unit = `${label}.service`;
|
|
4454
|
-
const ctl =
|
|
4543
|
+
const ctl = existsSync12(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
|
|
4455
4544
|
return {
|
|
4456
4545
|
mechanism: "systemd",
|
|
4457
4546
|
startCommand: [...ctl, "start", unit],
|
|
@@ -4483,10 +4572,10 @@ async function applyInstall(plan, crewHome, dryRun, log) {
|
|
|
4483
4572
|
log.emit(`would write ${path}`);
|
|
4484
4573
|
continue;
|
|
4485
4574
|
}
|
|
4486
|
-
const before =
|
|
4575
|
+
const before = existsSync12(path) ? readFileSync13(path, "utf8") : null;
|
|
4487
4576
|
if (before !== plan.unitContent[path]) changed2 = true;
|
|
4488
|
-
|
|
4489
|
-
|
|
4577
|
+
mkdirSync8(dirname6(path), { recursive: true });
|
|
4578
|
+
writeFileSync9(path, plan.unitContent[path]);
|
|
4490
4579
|
log.emit(`wrote ${path}`);
|
|
4491
4580
|
}
|
|
4492
4581
|
if (plan.crontabLine) {
|
|
@@ -4550,7 +4639,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
4550
4639
|
}
|
|
4551
4640
|
}
|
|
4552
4641
|
for (const path of plan.unitPaths) {
|
|
4553
|
-
if (!
|
|
4642
|
+
if (!existsSync12(path)) continue;
|
|
4554
4643
|
if (dryRun) {
|
|
4555
4644
|
log.emit(`would remove ${path}`);
|
|
4556
4645
|
continue;
|
|
@@ -4569,7 +4658,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
4569
4658
|
}
|
|
4570
4659
|
var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
|
|
4571
4660
|
function launchAgentsDir() {
|
|
4572
|
-
return
|
|
4661
|
+
return join14(homedir7(), "Library", "LaunchAgents");
|
|
4573
4662
|
}
|
|
4574
4663
|
function xmlUnescape(s) {
|
|
4575
4664
|
return s.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
@@ -4586,25 +4675,25 @@ function loadedCrewLabels() {
|
|
|
4586
4675
|
return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
|
|
4587
4676
|
}
|
|
4588
4677
|
function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
4589
|
-
const files =
|
|
4678
|
+
const files = existsSync12(dir) ? readdirSync3(dir) : [];
|
|
4590
4679
|
const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
|
|
4591
|
-
const plistPath =
|
|
4680
|
+
const plistPath = join14(dir, f);
|
|
4592
4681
|
let program = null;
|
|
4593
4682
|
try {
|
|
4594
|
-
program = programFromPlist(
|
|
4683
|
+
program = programFromPlist(readFileSync13(plistPath, "utf8"));
|
|
4595
4684
|
} catch {
|
|
4596
4685
|
}
|
|
4597
4686
|
return {
|
|
4598
4687
|
label: f.slice(0, -".plist".length),
|
|
4599
4688
|
plistPath,
|
|
4600
4689
|
program,
|
|
4601
|
-
programExists: program !== null &&
|
|
4690
|
+
programExists: program !== null && existsSync12(program)
|
|
4602
4691
|
};
|
|
4603
4692
|
});
|
|
4604
4693
|
const seen = new Set(units.map((u) => u.label));
|
|
4605
4694
|
for (const label of [...loaded].sort()) {
|
|
4606
4695
|
if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
|
|
4607
|
-
units.push({ label, plistPath:
|
|
4696
|
+
units.push({ label, plistPath: join14(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
|
|
4608
4697
|
}
|
|
4609
4698
|
return units;
|
|
4610
4699
|
}
|
|
@@ -4617,9 +4706,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
|
|
|
4617
4706
|
log.emit(`would unload and remove ${unit.label}`);
|
|
4618
4707
|
return;
|
|
4619
4708
|
}
|
|
4620
|
-
if (
|
|
4709
|
+
if (existsSync12(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
|
|
4621
4710
|
spawnSync4("launchctl", ["remove", unit.label], { encoding: "utf8" });
|
|
4622
|
-
if (
|
|
4711
|
+
if (existsSync12(unit.plistPath)) unlinkSync(unit.plistPath);
|
|
4623
4712
|
log.emit(`removed ${unit.label} (${unit.plistPath})`);
|
|
4624
4713
|
}
|
|
4625
4714
|
function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
|
|
@@ -4697,24 +4786,24 @@ function detectSystemd() {
|
|
|
4697
4786
|
}
|
|
4698
4787
|
|
|
4699
4788
|
// src/ship-attention.ts
|
|
4700
|
-
import { existsSync as
|
|
4789
|
+
import { existsSync as existsSync14, statfsSync } from "fs";
|
|
4701
4790
|
|
|
4702
4791
|
// src/git.ts
|
|
4703
|
-
import { execFileSync as
|
|
4792
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
4704
4793
|
import {
|
|
4705
|
-
existsSync as
|
|
4706
|
-
mkdirSync as
|
|
4794
|
+
existsSync as existsSync13,
|
|
4795
|
+
mkdirSync as mkdirSync9,
|
|
4707
4796
|
openSync as openSync2,
|
|
4708
4797
|
closeSync as closeSync2,
|
|
4709
4798
|
writeSync,
|
|
4710
|
-
statSync as
|
|
4799
|
+
statSync as statSync6,
|
|
4711
4800
|
unlinkSync as unlinkSync2,
|
|
4712
|
-
readFileSync as
|
|
4713
|
-
writeFileSync as
|
|
4801
|
+
readFileSync as readFileSync14,
|
|
4802
|
+
writeFileSync as writeFileSync10,
|
|
4714
4803
|
readdirSync as readdirSync4,
|
|
4715
4804
|
rmSync as rmSync8
|
|
4716
4805
|
} from "fs";
|
|
4717
|
-
import { dirname as
|
|
4806
|
+
import { dirname as dirname7, basename as basename5, join as join15 } from "path";
|
|
4718
4807
|
var GitError = class extends Error {
|
|
4719
4808
|
};
|
|
4720
4809
|
function streamText(x) {
|
|
@@ -4722,7 +4811,7 @@ function streamText(x) {
|
|
|
4722
4811
|
}
|
|
4723
4812
|
function gitRaw(cwd, args) {
|
|
4724
4813
|
try {
|
|
4725
|
-
return
|
|
4814
|
+
return execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
4726
4815
|
} catch (e) {
|
|
4727
4816
|
const err3 = e;
|
|
4728
4817
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -4731,7 +4820,7 @@ function gitRaw(cwd, args) {
|
|
|
4731
4820
|
}
|
|
4732
4821
|
function git(cwd, args) {
|
|
4733
4822
|
try {
|
|
4734
|
-
return
|
|
4823
|
+
return execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
4735
4824
|
} catch (e) {
|
|
4736
4825
|
const err3 = e;
|
|
4737
4826
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -4861,7 +4950,7 @@ function worktreeForNumber(cwd, num) {
|
|
|
4861
4950
|
return worktrees(cwd).find((w) => re.test(basename5(w.path))) ?? null;
|
|
4862
4951
|
}
|
|
4863
4952
|
var commitBodies = (cwd, range) => gitOk(cwd, ["log", range, "--format=%B"]) ?? "";
|
|
4864
|
-
var
|
|
4953
|
+
var resolve3 = (cwd, ref) => gitOk(cwd, ["rev-parse", "-q", "--verify", `${ref}^{commit}`]);
|
|
4865
4954
|
var countCommits = (cwd, range) => Number.parseInt(gitOk(cwd, ["rev-list", "--count", range]) ?? "0", 10);
|
|
4866
4955
|
function latestReleaseTag(cwd, pattern = "v*", ref = "HEAD") {
|
|
4867
4956
|
return gitOk(cwd, ["describe", "--tags", "--abbrev=0", "--match", pattern, ref]);
|
|
@@ -4870,7 +4959,7 @@ function firstReleaseTagContaining(cwd, sha, pattern = "v*") {
|
|
|
4870
4959
|
const out = gitOk(cwd, ["tag", "--contains", sha, "--list", pattern, "--sort=creatordate"]);
|
|
4871
4960
|
return out?.split("\n").map((l) => l.trim()).find(Boolean) ?? null;
|
|
4872
4961
|
}
|
|
4873
|
-
var tagCommit = (cwd, tag) =>
|
|
4962
|
+
var tagCommit = (cwd, tag) => resolve3(cwd, tag);
|
|
4874
4963
|
function createReleaseTag(cwd, tag, sha, message) {
|
|
4875
4964
|
git(cwd, ["tag", "-a", tag, sha, "-m", message]);
|
|
4876
4965
|
}
|
|
@@ -4902,9 +4991,9 @@ function keyCommitsInRange(cwd, key, range) {
|
|
|
4902
4991
|
}
|
|
4903
4992
|
function patchIdOf(cwd, from, to) {
|
|
4904
4993
|
try {
|
|
4905
|
-
const diff =
|
|
4994
|
+
const diff = execFileSync3("git", ["diff", from, to], { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
|
|
4906
4995
|
if (!diff) return null;
|
|
4907
|
-
const out =
|
|
4996
|
+
const out = execFileSync3("git", ["patch-id", "--stable"], { cwd, encoding: "utf8", input: diff });
|
|
4908
4997
|
return out.split(" ")[0]?.trim() || null;
|
|
4909
4998
|
} catch {
|
|
4910
4999
|
return null;
|
|
@@ -5094,7 +5183,7 @@ function recoverStrandedReleaseCheckouts(cwd) {
|
|
|
5094
5183
|
if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
|
|
5095
5184
|
else if (line.startsWith("branch refs/heads/crew/release-") && path) {
|
|
5096
5185
|
gitOk(cwd, ["worktree", "remove", "--force", path]);
|
|
5097
|
-
const root =
|
|
5186
|
+
const root = dirname7(path);
|
|
5098
5187
|
if (basename5(root).startsWith("crew-release-")) rmSync8(root, { recursive: true, force: true });
|
|
5099
5188
|
}
|
|
5100
5189
|
}
|
|
@@ -5112,11 +5201,11 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
|
|
|
5112
5201
|
}
|
|
5113
5202
|
for (const name of names) {
|
|
5114
5203
|
if (!name.startsWith("crew-release-")) continue;
|
|
5115
|
-
const root =
|
|
5204
|
+
const root = join15(tmp, name);
|
|
5116
5205
|
try {
|
|
5117
|
-
const st =
|
|
5206
|
+
const st = statSync6(root);
|
|
5118
5207
|
if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
|
|
5119
|
-
if (
|
|
5208
|
+
if (existsSync13(join15(root, "checkout"))) continue;
|
|
5120
5209
|
rmSync8(root, { recursive: true, force: true });
|
|
5121
5210
|
removed.push(name);
|
|
5122
5211
|
} catch {
|
|
@@ -5168,16 +5257,16 @@ function originUrlOf(dir, remoteName) {
|
|
|
5168
5257
|
}
|
|
5169
5258
|
}
|
|
5170
5259
|
function ensureRepoCheckout(dir, remote, opts = {}) {
|
|
5171
|
-
if (
|
|
5260
|
+
if (existsSync13(`${dir}/.git`)) return false;
|
|
5172
5261
|
if (!remote) {
|
|
5173
5262
|
throw new GitError(
|
|
5174
5263
|
`${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
5264
|
);
|
|
5176
5265
|
}
|
|
5177
|
-
|
|
5266
|
+
mkdirSync9(dirname7(dir), { recursive: true });
|
|
5178
5267
|
const url = cloneUrlFor(remote, opts);
|
|
5179
5268
|
try {
|
|
5180
|
-
|
|
5269
|
+
execFileSync3("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
5181
5270
|
} catch (e) {
|
|
5182
5271
|
const err3 = e;
|
|
5183
5272
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -5199,7 +5288,7 @@ function sleepSync(ms3) {
|
|
|
5199
5288
|
}
|
|
5200
5289
|
function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
5201
5290
|
const lockPath = checkoutLockPath(dir);
|
|
5202
|
-
|
|
5291
|
+
mkdirSync9(dirname7(lockPath), { recursive: true });
|
|
5203
5292
|
const deadline = Date.now() + timeoutMs;
|
|
5204
5293
|
for (; ; ) {
|
|
5205
5294
|
try {
|
|
@@ -5217,7 +5306,7 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
|
5217
5306
|
const err3 = e;
|
|
5218
5307
|
if (err3.code !== "EEXIST") throw err3;
|
|
5219
5308
|
try {
|
|
5220
|
-
if (Date.now() -
|
|
5309
|
+
if (Date.now() - statSync6(lockPath).mtimeMs > CHECKOUT_LOCK_STALE_MS) {
|
|
5221
5310
|
try {
|
|
5222
5311
|
unlinkSync2(lockPath);
|
|
5223
5312
|
} catch {
|
|
@@ -5236,17 +5325,17 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
|
5236
5325
|
function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {}) {
|
|
5237
5326
|
const release = acquireCheckoutLock(dir);
|
|
5238
5327
|
try {
|
|
5239
|
-
if (!
|
|
5328
|
+
if (!existsSync13(`${dir}/.git`) && githubRemote) {
|
|
5240
5329
|
const url = cloneUrlFor(githubRemote, clone);
|
|
5241
5330
|
const marker = cloneFailureMarker(dir);
|
|
5242
|
-
if (
|
|
5243
|
-
return { action: "clone-failed", detail:
|
|
5331
|
+
if (existsSync13(marker) && readFileSync14(marker, "utf8").split("\n")[0] === url) {
|
|
5332
|
+
return { action: "clone-failed", detail: readFileSync14(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
|
|
5244
5333
|
}
|
|
5245
5334
|
try {
|
|
5246
5335
|
ensureRepoCheckout(dir, githubRemote, clone);
|
|
5247
5336
|
} catch (e) {
|
|
5248
5337
|
if (!(e instanceof GitError)) throw e;
|
|
5249
|
-
|
|
5338
|
+
writeFileSync10(marker, `${url}
|
|
5250
5339
|
${e.message}`);
|
|
5251
5340
|
return { action: "clone-failed", detail: e.message, repeat: false };
|
|
5252
5341
|
}
|
|
@@ -5472,7 +5561,7 @@ function noteHeldWorktree(state, h, nowMs = Date.now()) {
|
|
|
5472
5561
|
function reconcileHeldWorktrees(state, repo, log, stillRefused) {
|
|
5473
5562
|
for (const i of state.shipAttention().previous()) {
|
|
5474
5563
|
if (i.kind !== "worktree_held" || !i.held || i.held.repo !== repo) continue;
|
|
5475
|
-
const gone = !
|
|
5564
|
+
const gone = !existsSync14(i.held.path);
|
|
5476
5565
|
if (!gone && !(stillRefused && !stillRefused.has(i.held.path))) continue;
|
|
5477
5566
|
clearShipAttention(state, i.key);
|
|
5478
5567
|
log.emit(
|
|
@@ -5600,13 +5689,13 @@ function attentionProblems(items, already) {
|
|
|
5600
5689
|
// src/sandbox-image.ts
|
|
5601
5690
|
import { createHash as createHash6 } from "crypto";
|
|
5602
5691
|
import { spawn as spawn4 } from "child_process";
|
|
5603
|
-
import { execFileSync as
|
|
5604
|
-
import { mkdirSync as
|
|
5605
|
-
import { dirname as
|
|
5692
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
5693
|
+
import { mkdirSync as mkdirSync15, readFileSync as readFileSync22, renameSync as renameSync3, writeFileSync as writeFileSync16, rmSync as rmSync13 } from "fs";
|
|
5694
|
+
import { dirname as dirname11, join as join27 } from "path";
|
|
5606
5695
|
import { tmpdir as tmpdir3 } from "os";
|
|
5607
5696
|
|
|
5608
5697
|
// src/passenger-containers.ts
|
|
5609
|
-
import { execFileSync as
|
|
5698
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
5610
5699
|
import { createHash as createHash5 } from "crypto";
|
|
5611
5700
|
|
|
5612
5701
|
// src/sandbox-cache.ts
|
|
@@ -5615,32 +5704,32 @@ import { promisify as promisify2 } from "util";
|
|
|
5615
5704
|
|
|
5616
5705
|
// src/deprovision-host.ts
|
|
5617
5706
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
5618
|
-
import { existsSync as
|
|
5707
|
+
import { existsSync as existsSync25, readdirSync as readdirSync11 } from "fs";
|
|
5619
5708
|
|
|
5620
5709
|
// src/interrupted.ts
|
|
5621
|
-
import { existsSync as
|
|
5622
|
-
import { join as
|
|
5710
|
+
import { existsSync as existsSync23, readdirSync as readdirSync9, readFileSync as readFileSync21 } from "fs";
|
|
5711
|
+
import { join as join25 } from "path";
|
|
5623
5712
|
|
|
5624
5713
|
// src/agent.ts
|
|
5625
5714
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
5626
|
-
import { readFileSync as
|
|
5715
|
+
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
5716
|
import { StringDecoder } from "string_decoder";
|
|
5628
5717
|
import { finished } from "stream/promises";
|
|
5629
|
-
import { join as
|
|
5718
|
+
import { join as join24, dirname as dirname10, basename as basename9 } from "path";
|
|
5630
5719
|
import { createHash as createHash4 } from "crypto";
|
|
5631
5720
|
|
|
5632
5721
|
// src/state.ts
|
|
5633
|
-
import { readFileSync as
|
|
5634
|
-
import { join as
|
|
5722
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, existsSync as existsSync15, mkdirSync as mkdirSync10, rmSync as rmSync9, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
|
|
5723
|
+
import { join as join16 } from "path";
|
|
5635
5724
|
var EPOCH = "1970-01-01T00:00:00.000Z";
|
|
5636
5725
|
var State = class _State {
|
|
5637
5726
|
dir;
|
|
5638
5727
|
constructor(dir) {
|
|
5639
5728
|
this.dir = dir;
|
|
5640
|
-
|
|
5729
|
+
mkdirSync10(dir, { recursive: true });
|
|
5641
5730
|
}
|
|
5642
5731
|
path(...parts) {
|
|
5643
|
-
return
|
|
5732
|
+
return join16(this.dir, ...parts);
|
|
5644
5733
|
}
|
|
5645
5734
|
/**
|
|
5646
5735
|
* The timestamp every "new since last poll" check is measured against.
|
|
@@ -5651,14 +5740,14 @@ var State = class _State {
|
|
|
5651
5740
|
*/
|
|
5652
5741
|
watermark() {
|
|
5653
5742
|
try {
|
|
5654
|
-
const v =
|
|
5743
|
+
const v = readFileSync15(this.path(".poll-watermark"), "utf8").trim();
|
|
5655
5744
|
return v || EPOCH;
|
|
5656
5745
|
} catch {
|
|
5657
5746
|
return EPOCH;
|
|
5658
5747
|
}
|
|
5659
5748
|
}
|
|
5660
5749
|
setWatermark(at) {
|
|
5661
|
-
|
|
5750
|
+
writeFileSync11(this.path(".poll-watermark"), `${at}
|
|
5662
5751
|
`);
|
|
5663
5752
|
}
|
|
5664
5753
|
/**
|
|
@@ -5676,16 +5765,16 @@ var State = class _State {
|
|
|
5676
5765
|
}
|
|
5677
5766
|
/** Whole-crew pause, and the per-role sentinels one level down. */
|
|
5678
5767
|
isPaused() {
|
|
5679
|
-
return
|
|
5768
|
+
return existsSync15(this.path(".crew-paused"));
|
|
5680
5769
|
}
|
|
5681
5770
|
isRolePaused(role) {
|
|
5682
|
-
return
|
|
5771
|
+
return existsSync15(this.path(`.role-paused-${role}`));
|
|
5683
5772
|
}
|
|
5684
5773
|
pausedRoles(roles) {
|
|
5685
5774
|
return new Set(roles.filter((r) => this.isRolePaused(r)));
|
|
5686
5775
|
}
|
|
5687
5776
|
pause(role) {
|
|
5688
|
-
|
|
5777
|
+
writeFileSync11(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
|
|
5689
5778
|
}
|
|
5690
5779
|
resume(role) {
|
|
5691
5780
|
rmSync9(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), { force: true });
|
|
@@ -5696,10 +5785,10 @@ var State = class _State {
|
|
|
5696
5785
|
* while agents keep working.
|
|
5697
5786
|
*/
|
|
5698
5787
|
isReleasePaused() {
|
|
5699
|
-
return
|
|
5788
|
+
return existsSync15(this.path(".release-paused"));
|
|
5700
5789
|
}
|
|
5701
5790
|
setReleasePaused(paused) {
|
|
5702
|
-
if (paused)
|
|
5791
|
+
if (paused) writeFileSync11(this.path(".release-paused"), "");
|
|
5703
5792
|
else rmSync9(this.path(".release-paused"), { force: true });
|
|
5704
5793
|
}
|
|
5705
5794
|
/**
|
|
@@ -5737,7 +5826,7 @@ var State = class _State {
|
|
|
5737
5826
|
const name = _State.safe(rawName);
|
|
5738
5827
|
const file = this.path(`.${name}.lock`);
|
|
5739
5828
|
try {
|
|
5740
|
-
const existing = Number.parseInt(
|
|
5829
|
+
const existing = Number.parseInt(readFileSync15(file, "utf8").trim(), 10);
|
|
5741
5830
|
if (Number.isFinite(existing) && existing > 0) {
|
|
5742
5831
|
try {
|
|
5743
5832
|
process.kill(existing, 0);
|
|
@@ -5747,7 +5836,7 @@ var State = class _State {
|
|
|
5747
5836
|
}
|
|
5748
5837
|
} catch {
|
|
5749
5838
|
}
|
|
5750
|
-
|
|
5839
|
+
writeFileSync11(file, `${process.pid}
|
|
5751
5840
|
`);
|
|
5752
5841
|
return {
|
|
5753
5842
|
ok: true,
|
|
@@ -5775,7 +5864,7 @@ var State = class _State {
|
|
|
5775
5864
|
if (!/^\.release-.+\.lock$/.test(n)) continue;
|
|
5776
5865
|
let pid = 0;
|
|
5777
5866
|
try {
|
|
5778
|
-
pid = Number.parseInt(
|
|
5867
|
+
pid = Number.parseInt(readFileSync15(this.path(n), "utf8").trim(), 10);
|
|
5779
5868
|
} catch {
|
|
5780
5869
|
continue;
|
|
5781
5870
|
}
|
|
@@ -5861,7 +5950,7 @@ var State = class _State {
|
|
|
5861
5950
|
ticketInFlight(recordId) {
|
|
5862
5951
|
const file = this.path(`.${_State.safe(`crew-ticket-${recordId}`)}.lock`);
|
|
5863
5952
|
try {
|
|
5864
|
-
const pid = Number.parseInt(
|
|
5953
|
+
const pid = Number.parseInt(readFileSync15(file, "utf8").trim(), 10);
|
|
5865
5954
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
5866
5955
|
process.kill(pid, 0);
|
|
5867
5956
|
return true;
|
|
@@ -5895,7 +5984,7 @@ var State = class _State {
|
|
|
5895
5984
|
const heldReported = `.release-held-${suffix}`;
|
|
5896
5985
|
const read = (f) => {
|
|
5897
5986
|
try {
|
|
5898
|
-
return
|
|
5987
|
+
return readFileSync15(this.path(f), "utf8").trim() || null;
|
|
5899
5988
|
} catch {
|
|
5900
5989
|
return null;
|
|
5901
5990
|
}
|
|
@@ -5904,7 +5993,7 @@ var State = class _State {
|
|
|
5904
5993
|
blockedCount: () => Number.parseInt(read(blocked) ?? "", 10) || 0,
|
|
5905
5994
|
noteBlocked: () => {
|
|
5906
5995
|
const n = (Number.parseInt(read(blocked) ?? "", 10) || 0) + 1;
|
|
5907
|
-
|
|
5996
|
+
writeFileSync11(this.path(blocked), `${n}
|
|
5908
5997
|
`);
|
|
5909
5998
|
return n;
|
|
5910
5999
|
},
|
|
@@ -5921,7 +6010,7 @@ var State = class _State {
|
|
|
5921
6010
|
*/
|
|
5922
6011
|
deployFailedSha: () => read(failed),
|
|
5923
6012
|
noteDeployFailed: (sha) => {
|
|
5924
|
-
|
|
6013
|
+
writeFileSync11(this.path(failed), `${sha}
|
|
5925
6014
|
`);
|
|
5926
6015
|
},
|
|
5927
6016
|
clearDeployFailed: () => {
|
|
@@ -5935,7 +6024,7 @@ var State = class _State {
|
|
|
5935
6024
|
noteTestGateFailed: (sha) => {
|
|
5936
6025
|
const [prev, n] = (read(gateFailed) ?? "").split(" ");
|
|
5937
6026
|
const next = prev === sha ? (Number.parseInt(n ?? "", 10) || 0) + 1 : 1;
|
|
5938
|
-
|
|
6027
|
+
writeFileSync11(this.path(gateFailed), `${sha} ${next}
|
|
5939
6028
|
`);
|
|
5940
6029
|
return next;
|
|
5941
6030
|
},
|
|
@@ -5945,13 +6034,13 @@ var State = class _State {
|
|
|
5945
6034
|
/** Whether the board was already told about this head's red gate. */
|
|
5946
6035
|
heldReported: () => {
|
|
5947
6036
|
try {
|
|
5948
|
-
return
|
|
6037
|
+
return readFileSync15(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
|
|
5949
6038
|
} catch {
|
|
5950
6039
|
return null;
|
|
5951
6040
|
}
|
|
5952
6041
|
},
|
|
5953
6042
|
noteHeldReported: (signature) => {
|
|
5954
|
-
|
|
6043
|
+
writeFileSync11(this.path(heldReported), `${signature}
|
|
5955
6044
|
`);
|
|
5956
6045
|
},
|
|
5957
6046
|
clearHeldReported: () => {
|
|
@@ -5959,7 +6048,7 @@ var State = class _State {
|
|
|
5959
6048
|
},
|
|
5960
6049
|
testGateReported: (sha) => read(gateReported) === sha,
|
|
5961
6050
|
noteTestGateReported: (sha) => {
|
|
5962
|
-
|
|
6051
|
+
writeFileSync11(this.path(gateReported), `${sha}
|
|
5963
6052
|
`);
|
|
5964
6053
|
}
|
|
5965
6054
|
};
|
|
@@ -5982,7 +6071,7 @@ var State = class _State {
|
|
|
5982
6071
|
const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
|
|
5983
6072
|
const read = (f) => {
|
|
5984
6073
|
try {
|
|
5985
|
-
return
|
|
6074
|
+
return readFileSync15(this.path(f), "utf8").trim() || null;
|
|
5986
6075
|
} catch {
|
|
5987
6076
|
return null;
|
|
5988
6077
|
}
|
|
@@ -6015,18 +6104,18 @@ var State = class _State {
|
|
|
6015
6104
|
clear2();
|
|
6016
6105
|
return;
|
|
6017
6106
|
}
|
|
6018
|
-
|
|
6107
|
+
writeFileSync11(this.path(streakFile), `${this.readInt(streakFile) + 1}
|
|
6019
6108
|
`);
|
|
6020
6109
|
const prior = read(ticketFile);
|
|
6021
6110
|
if (topTicket && topTicket === prior) {
|
|
6022
|
-
|
|
6111
|
+
writeFileSync11(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
|
|
6023
6112
|
`);
|
|
6024
6113
|
} else if (topTicket) {
|
|
6025
|
-
|
|
6114
|
+
writeFileSync11(this.path(ticketFile), `${topTicket}
|
|
6026
6115
|
`);
|
|
6027
|
-
|
|
6116
|
+
writeFileSync11(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
6028
6117
|
`);
|
|
6029
|
-
|
|
6118
|
+
writeFileSync11(this.path(ticketStreakFile), "1\n");
|
|
6030
6119
|
}
|
|
6031
6120
|
}
|
|
6032
6121
|
};
|
|
@@ -6047,14 +6136,14 @@ var State = class _State {
|
|
|
6047
6136
|
return {
|
|
6048
6137
|
previous: () => {
|
|
6049
6138
|
try {
|
|
6050
|
-
const parsed = JSON.parse(
|
|
6139
|
+
const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
|
|
6051
6140
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
6052
6141
|
} catch {
|
|
6053
6142
|
return {};
|
|
6054
6143
|
}
|
|
6055
6144
|
},
|
|
6056
6145
|
persist: (next) => {
|
|
6057
|
-
|
|
6146
|
+
writeFileSync11(this.path(file), JSON.stringify(next));
|
|
6058
6147
|
}
|
|
6059
6148
|
};
|
|
6060
6149
|
}
|
|
@@ -6070,14 +6159,14 @@ var State = class _State {
|
|
|
6070
6159
|
return {
|
|
6071
6160
|
previous: () => {
|
|
6072
6161
|
try {
|
|
6073
|
-
const parsed = JSON.parse(
|
|
6162
|
+
const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
|
|
6074
6163
|
return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : [];
|
|
6075
6164
|
} catch {
|
|
6076
6165
|
return [];
|
|
6077
6166
|
}
|
|
6078
6167
|
},
|
|
6079
6168
|
persist: (next) => {
|
|
6080
|
-
|
|
6169
|
+
writeFileSync11(this.path(file), JSON.stringify(next));
|
|
6081
6170
|
}
|
|
6082
6171
|
};
|
|
6083
6172
|
}
|
|
@@ -6091,14 +6180,14 @@ var State = class _State {
|
|
|
6091
6180
|
return {
|
|
6092
6181
|
previous: () => {
|
|
6093
6182
|
try {
|
|
6094
|
-
const parsed = JSON.parse(
|
|
6183
|
+
const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
|
|
6095
6184
|
return Array.isArray(parsed) ? parsed : [];
|
|
6096
6185
|
} catch {
|
|
6097
6186
|
return [];
|
|
6098
6187
|
}
|
|
6099
6188
|
},
|
|
6100
6189
|
persist: (next) => {
|
|
6101
|
-
|
|
6190
|
+
writeFileSync11(this.path(file), JSON.stringify(next));
|
|
6102
6191
|
}
|
|
6103
6192
|
};
|
|
6104
6193
|
}
|
|
@@ -6111,11 +6200,11 @@ var State = class _State {
|
|
|
6111
6200
|
const file = ".release-last-run";
|
|
6112
6201
|
return {
|
|
6113
6202
|
touch: () => {
|
|
6114
|
-
|
|
6203
|
+
writeFileSync11(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
|
|
6115
6204
|
},
|
|
6116
6205
|
at: () => {
|
|
6117
6206
|
try {
|
|
6118
|
-
return
|
|
6207
|
+
return statSync7(this.path(file)).mtimeMs;
|
|
6119
6208
|
} catch {
|
|
6120
6209
|
return null;
|
|
6121
6210
|
}
|
|
@@ -6124,7 +6213,7 @@ var State = class _State {
|
|
|
6124
6213
|
}
|
|
6125
6214
|
readInt(file) {
|
|
6126
6215
|
try {
|
|
6127
|
-
return Number.parseInt(
|
|
6216
|
+
return Number.parseInt(readFileSync15(this.path(file), "utf8").trim(), 10) || 0;
|
|
6128
6217
|
} catch {
|
|
6129
6218
|
return 0;
|
|
6130
6219
|
}
|
|
@@ -6203,6 +6292,7 @@ var HOOK_PURPOSE = [
|
|
|
6203
6292
|
["ports", "this worktree's port assignments; prints KEY=value overrides to export"],
|
|
6204
6293
|
["handoff", "leave the operator able to open what you built; put its output in your progress comment"],
|
|
6205
6294
|
["preview", "start this worktree's dev server for a viewer, bound to CREW_PREVIEW_HOST, detached; its last stdout line is the URL"],
|
|
6295
|
+
["map", 'prints an outline of the repository; the crew places it under "Repository map" below'],
|
|
6206
6296
|
["test", "run the suite"],
|
|
6207
6297
|
["build", "build it"],
|
|
6208
6298
|
["ci_status", "after you push and before you hand off: exit 0 means CI is green for your branch; on failure fix and push again, and say in your progress comment if it is still pending"]
|
|
@@ -6299,6 +6389,13 @@ function repoSection(r, key, heading) {
|
|
|
6299
6389
|
}
|
|
6300
6390
|
return lines2;
|
|
6301
6391
|
}
|
|
6392
|
+
function mapSection(r, heading) {
|
|
6393
|
+
if (r.map) return ["", heading, "", r.map, ""];
|
|
6394
|
+
if (r.mapSkipped) {
|
|
6395
|
+
return ["", `Note: ${r.mapSkipped}. Say so in one line of your progress comment; it does not block the work.`, ""];
|
|
6396
|
+
}
|
|
6397
|
+
return [];
|
|
6398
|
+
}
|
|
6302
6399
|
function renderEnvironment(i) {
|
|
6303
6400
|
const { route: route2, contract } = i;
|
|
6304
6401
|
const repos = i.repos;
|
|
@@ -6490,12 +6587,16 @@ function renderEnvironment(i) {
|
|
|
6490
6587
|
for (const r of repos) {
|
|
6491
6588
|
lines2.push(...i.sandbox ? ["", `### ${r.name}`] : repoSection(r, key, `### ${r.name} \u2014 \`${r.dir}\``));
|
|
6492
6589
|
lines2.push("", `Hooks for ${r.name} \u2014 the only commands you should run here:`, "", hookTable(r.config));
|
|
6590
|
+
lines2.push(...mapSection(r, `#### Repository map \u2014 ${r.name}`));
|
|
6493
6591
|
}
|
|
6494
6592
|
}
|
|
6495
6593
|
if (i.repoDir) {
|
|
6496
6594
|
lines2.push("", `This ticket's work happens in \`${i.repoDir}\`.`);
|
|
6497
6595
|
}
|
|
6498
|
-
if (only)
|
|
6596
|
+
if (only) {
|
|
6597
|
+
lines2.push("", "## Hooks \u2014 the only commands you should run", "", hookTable(only.config));
|
|
6598
|
+
lines2.push(...mapSection(only, "## Repository map"));
|
|
6599
|
+
}
|
|
6499
6600
|
return lines2.join("\n");
|
|
6500
6601
|
}
|
|
6501
6602
|
|
|
@@ -6665,8 +6766,8 @@ var PERISCOPE_COLUMNS = [
|
|
|
6665
6766
|
];
|
|
6666
6767
|
|
|
6667
6768
|
// src/agent-log.ts
|
|
6668
|
-
import { existsSync as
|
|
6669
|
-
import { join as
|
|
6769
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync6, readFileSync as readFileSync16, rmSync as rmSync10, writeFileSync as writeFileSync12 } from "fs";
|
|
6770
|
+
import { join as join17 } from "path";
|
|
6670
6771
|
|
|
6671
6772
|
// src/pending-backoff.ts
|
|
6672
6773
|
var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
|
|
@@ -6838,20 +6939,20 @@ async function addRunToTicket(target, t, recordId, run3) {
|
|
|
6838
6939
|
};
|
|
6839
6940
|
}
|
|
6840
6941
|
}
|
|
6841
|
-
var queueDir = (stateDir) =>
|
|
6942
|
+
var queueDir = (stateDir) => join17(stateDir, "pending-agent-logs");
|
|
6842
6943
|
function queueAgentRun(stateDir, name, q2) {
|
|
6843
|
-
|
|
6844
|
-
|
|
6944
|
+
mkdirSync11(queueDir(stateDir), { recursive: true });
|
|
6945
|
+
writeFileSync12(join17(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
6845
6946
|
}
|
|
6846
6947
|
async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
6847
6948
|
const dir = queueDir(stateDir);
|
|
6848
|
-
if (!
|
|
6949
|
+
if (!existsSync16(dir)) return 0;
|
|
6849
6950
|
let sent = 0;
|
|
6850
6951
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
6851
|
-
const path =
|
|
6952
|
+
const path = join17(dir, f);
|
|
6852
6953
|
let q2;
|
|
6853
6954
|
try {
|
|
6854
|
-
q2 = JSON.parse(
|
|
6955
|
+
q2 = JSON.parse(readFileSync16(path, "utf8"));
|
|
6855
6956
|
} catch {
|
|
6856
6957
|
continue;
|
|
6857
6958
|
}
|
|
@@ -6880,7 +6981,7 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
6880
6981
|
continue;
|
|
6881
6982
|
}
|
|
6882
6983
|
const attempts = q2.attempts + 1;
|
|
6883
|
-
|
|
6984
|
+
writeFileSync12(path, JSON.stringify({
|
|
6884
6985
|
...q2,
|
|
6885
6986
|
attempts,
|
|
6886
6987
|
warned: true,
|
|
@@ -6891,20 +6992,20 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
6891
6992
|
}
|
|
6892
6993
|
return sent;
|
|
6893
6994
|
}
|
|
6894
|
-
var countersDir = (stateDir) =>
|
|
6995
|
+
var countersDir = (stateDir) => join17(stateDir, "pending-ticket-counters");
|
|
6895
6996
|
function queueTicketCounters(stateDir, name, q2) {
|
|
6896
|
-
|
|
6897
|
-
|
|
6997
|
+
mkdirSync11(countersDir(stateDir), { recursive: true });
|
|
6998
|
+
writeFileSync12(join17(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
6898
6999
|
}
|
|
6899
7000
|
async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
6900
7001
|
const dir = countersDir(stateDir);
|
|
6901
|
-
if (!
|
|
7002
|
+
if (!existsSync16(dir)) return 0;
|
|
6902
7003
|
let sent = 0;
|
|
6903
7004
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
6904
|
-
const path =
|
|
7005
|
+
const path = join17(dir, f);
|
|
6905
7006
|
let q2;
|
|
6906
7007
|
try {
|
|
6907
|
-
q2 = JSON.parse(
|
|
7008
|
+
q2 = JSON.parse(readFileSync16(path, "utf8"));
|
|
6908
7009
|
} catch {
|
|
6909
7010
|
continue;
|
|
6910
7011
|
}
|
|
@@ -6923,7 +7024,7 @@ async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /
|
|
|
6923
7024
|
continue;
|
|
6924
7025
|
}
|
|
6925
7026
|
const attempts = q2.attempts + 1;
|
|
6926
|
-
|
|
7027
|
+
writeFileSync12(path, JSON.stringify({
|
|
6927
7028
|
...q2,
|
|
6928
7029
|
attempts,
|
|
6929
7030
|
warned: true,
|
|
@@ -7126,8 +7227,8 @@ var HandoffTranscriptTracker = class {
|
|
|
7126
7227
|
};
|
|
7127
7228
|
|
|
7128
7229
|
// src/pending-handoff.ts
|
|
7129
|
-
import { existsSync as
|
|
7130
|
-
import { join as
|
|
7230
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync12, readdirSync as readdirSync7, readFileSync as readFileSync17, rmSync as rmSync11, writeFileSync as writeFileSync13 } from "fs";
|
|
7231
|
+
import { join as join20 } from "path";
|
|
7131
7232
|
|
|
7132
7233
|
// src/ticket-branch.ts
|
|
7133
7234
|
import { basename as basename7 } from "path";
|
|
@@ -8293,16 +8394,16 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
|
|
|
8293
8394
|
}
|
|
8294
8395
|
|
|
8295
8396
|
// src/run-worktree.ts
|
|
8296
|
-
import { existsSync as
|
|
8297
|
-
import { dirname as
|
|
8397
|
+
import { existsSync as existsSync18 } from "fs";
|
|
8398
|
+
import { dirname as dirname8, join as join19 } from "path";
|
|
8298
8399
|
|
|
8299
8400
|
// src/ports.ts
|
|
8300
|
-
import { execFileSync as
|
|
8301
|
-
import { existsSync as
|
|
8302
|
-
import { join as
|
|
8401
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
8402
|
+
import { existsSync as existsSync17 } from "fs";
|
|
8403
|
+
import { join as join18 } from "path";
|
|
8303
8404
|
var run = (cmd, args) => {
|
|
8304
8405
|
try {
|
|
8305
|
-
return
|
|
8406
|
+
return execFileSync4(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
8306
8407
|
} catch {
|
|
8307
8408
|
return "";
|
|
8308
8409
|
}
|
|
@@ -8344,7 +8445,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
|
|
|
8344
8445
|
}
|
|
8345
8446
|
function worktreeExists(parent, prefix, n) {
|
|
8346
8447
|
for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
|
|
8347
|
-
if (
|
|
8448
|
+
if (existsSync17(join18(parent, name, ".git"))) return true;
|
|
8348
8449
|
}
|
|
8349
8450
|
return false;
|
|
8350
8451
|
}
|
|
@@ -8427,7 +8528,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
8427
8528
|
const existing = worktreeForNumber(dir, num);
|
|
8428
8529
|
const located = locateBranchForTicket(dir, cfg, t, role);
|
|
8429
8530
|
const baseRef = hasRemote ? `${remote}/${base}` : base;
|
|
8430
|
-
if (existing && !existing.prunable &&
|
|
8531
|
+
if (existing && !existing.prunable && existsSync18(existing.path)) {
|
|
8431
8532
|
const branch = existing.branch;
|
|
8432
8533
|
if (status(existing.path).length > 0) {
|
|
8433
8534
|
return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
|
|
@@ -8443,7 +8544,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
8443
8544
|
if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
|
|
8444
8545
|
return { action: "recut", path: existing.path, branch, base: baseRef };
|
|
8445
8546
|
}
|
|
8446
|
-
const path =
|
|
8547
|
+
const path = join19(dirname8(dir), worktreeForTicket(dir, cfg, t, role));
|
|
8447
8548
|
if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
|
|
8448
8549
|
return { action: "cut-remote", path, branch: located.branch };
|
|
8449
8550
|
}
|
|
@@ -8497,7 +8598,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
8497
8598
|
if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
|
|
8498
8599
|
if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
|
|
8499
8600
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
8500
|
-
if (wt &&
|
|
8601
|
+
if (wt && existsSync18(wt.path)) {
|
|
8501
8602
|
if (status(wt.path).length > 0) return false;
|
|
8502
8603
|
if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
|
|
8503
8604
|
}
|
|
@@ -8505,7 +8606,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
8505
8606
|
}
|
|
8506
8607
|
async function finishRunWorktree(dir, cfg, branch) {
|
|
8507
8608
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
8508
|
-
if (!wt || !
|
|
8609
|
+
if (!wt || !existsSync18(wt.path)) return { kind: "none" };
|
|
8509
8610
|
if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
|
|
8510
8611
|
if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
|
|
8511
8612
|
if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
|
|
@@ -8517,7 +8618,7 @@ async function finishRunWorktree(dir, cfg, branch) {
|
|
|
8517
8618
|
if (pids.length) await killGently(pids);
|
|
8518
8619
|
}
|
|
8519
8620
|
gitOk(dir, ["worktree", "remove", wt.path]);
|
|
8520
|
-
return
|
|
8621
|
+
return existsSync18(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
|
|
8521
8622
|
}
|
|
8522
8623
|
|
|
8523
8624
|
// src/handoff-guard.ts
|
|
@@ -8900,20 +9001,20 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
|
|
|
8900
9001
|
|
|
8901
9002
|
// src/pending-handoff.ts
|
|
8902
9003
|
var PENDING_MAX_AGE_MS = 48 * 36e5;
|
|
8903
|
-
var pendingDirFor = (stateDir) =>
|
|
8904
|
-
var stateDirOfStream = (streamPath) =>
|
|
9004
|
+
var pendingDirFor = (stateDir) => join20(stateDir, "pending-handoffs");
|
|
9005
|
+
var stateDirOfStream = (streamPath) => join20(streamPath, "..", "..");
|
|
8905
9006
|
function writePendingHandoff(stateDir, rec) {
|
|
8906
9007
|
const dir = pendingDirFor(stateDir);
|
|
8907
|
-
|
|
8908
|
-
|
|
9008
|
+
mkdirSync12(dir, { recursive: true });
|
|
9009
|
+
writeFileSync13(join20(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
|
|
8909
9010
|
}
|
|
8910
9011
|
function listPendingHandoffs(stateDir, route2) {
|
|
8911
9012
|
const dir = pendingDirFor(stateDir);
|
|
8912
|
-
if (!
|
|
9013
|
+
if (!existsSync19(dir)) return [];
|
|
8913
9014
|
const out = [];
|
|
8914
9015
|
for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
8915
9016
|
try {
|
|
8916
|
-
const rec = JSON.parse(
|
|
9017
|
+
const rec = JSON.parse(readFileSync17(join20(dir, f), "utf8"));
|
|
8917
9018
|
if (rec.version === 1 && rec.ticketRecordId && (!route2 || rec.route === route2)) out.push(rec);
|
|
8918
9019
|
} catch {
|
|
8919
9020
|
}
|
|
@@ -8921,7 +9022,7 @@ function listPendingHandoffs(stateDir, route2) {
|
|
|
8921
9022
|
return out;
|
|
8922
9023
|
}
|
|
8923
9024
|
function removePendingHandoff(stateDir, ticketRecordId) {
|
|
8924
|
-
rmSync11(
|
|
9025
|
+
rmSync11(join20(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
|
|
8925
9026
|
}
|
|
8926
9027
|
function pendingHandoffFor(stateDir, ticketRecordId) {
|
|
8927
9028
|
return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
|
|
@@ -9031,8 +9132,8 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
|
|
|
9031
9132
|
}
|
|
9032
9133
|
|
|
9033
9134
|
// src/skills.ts
|
|
9034
|
-
import { readFileSync as
|
|
9035
|
-
import { join as
|
|
9135
|
+
import { readFileSync as readFileSync19, readdirSync as readdirSync8, existsSync as existsSync21 } from "fs";
|
|
9136
|
+
import { join as join22 } from "path";
|
|
9036
9137
|
import { parse as parseYaml } from "yaml";
|
|
9037
9138
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
9038
9139
|
|
|
@@ -9048,8 +9149,8 @@ ${additions}`;
|
|
|
9048
9149
|
}
|
|
9049
9150
|
|
|
9050
9151
|
// src/agents.ts
|
|
9051
|
-
import { readFileSync as
|
|
9052
|
-
import { join as
|
|
9152
|
+
import { readFileSync as readFileSync18, existsSync as existsSync20 } from "fs";
|
|
9153
|
+
import { join as join21 } from "path";
|
|
9053
9154
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
|
|
9054
9155
|
var PERSONA_NAME = {
|
|
9055
9156
|
dev: "Developer",
|
|
@@ -9063,12 +9164,12 @@ var CREW_AGENT_COLUMN = "agent_id";
|
|
|
9063
9164
|
var AgentsSyncError = class extends Error {
|
|
9064
9165
|
};
|
|
9065
9166
|
function personaDefaultPrompt(promptsDir, role) {
|
|
9066
|
-
const briefPath =
|
|
9067
|
-
if (!
|
|
9068
|
-
if (SELF_CONTAINED_ROLES.has(role)) return
|
|
9069
|
-
const commonPath =
|
|
9070
|
-
if (!
|
|
9071
|
-
return `${
|
|
9167
|
+
const briefPath = join21(promptsDir, "personas", `lane-${role}.md`);
|
|
9168
|
+
if (!existsSync20(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
|
|
9169
|
+
if (SELF_CONTAINED_ROLES.has(role)) return readFileSync18(briefPath, "utf8");
|
|
9170
|
+
const commonPath = join21(promptsDir, "personas", "common.md");
|
|
9171
|
+
if (!existsSync20(commonPath)) return readFileSync18(briefPath, "utf8");
|
|
9172
|
+
return `${readFileSync18(commonPath, "utf8")}${readFileSync18(briefPath, "utf8")}`;
|
|
9072
9173
|
}
|
|
9073
9174
|
async function resolveAgentId(route2, role, opts) {
|
|
9074
9175
|
const cached = route2.resolved?.agentPersonas?.[role]?.agentId;
|
|
@@ -9515,7 +9616,7 @@ function describeCrewLink(o) {
|
|
|
9515
9616
|
var SkillSyncError = class extends Error {
|
|
9516
9617
|
};
|
|
9517
9618
|
function parseSkillFile(path) {
|
|
9518
|
-
const text2 =
|
|
9619
|
+
const text2 = readFileSync19(path, "utf8");
|
|
9519
9620
|
const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text2);
|
|
9520
9621
|
if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
|
|
9521
9622
|
const frontmatter = match[1];
|
|
@@ -9528,9 +9629,9 @@ function parseSkillFile(path) {
|
|
|
9528
9629
|
return { name: meta.name, description: meta.description, prompt: body, file: path };
|
|
9529
9630
|
}
|
|
9530
9631
|
function listLocalSkills(promptsDir) {
|
|
9531
|
-
const dir =
|
|
9532
|
-
if (!
|
|
9533
|
-
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(
|
|
9632
|
+
const dir = join22(promptsDir, "skills");
|
|
9633
|
+
if (!existsSync21(dir)) return [];
|
|
9634
|
+
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join22(dir, f)));
|
|
9534
9635
|
}
|
|
9535
9636
|
async function syncSkills(route2, opts) {
|
|
9536
9637
|
if (!route2.resolved) {
|
|
@@ -9714,10 +9815,10 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
|
|
|
9714
9815
|
}
|
|
9715
9816
|
|
|
9716
9817
|
// src/runner.ts
|
|
9717
|
-
import { spawn as spawn2, execFile, execFileSync as
|
|
9718
|
-
import { mkdtempSync, mkdirSync as
|
|
9818
|
+
import { spawn as spawn2, execFile, execFileSync as execFileSync5 } from "child_process";
|
|
9819
|
+
import { mkdtempSync, mkdirSync as mkdirSync13, writeFileSync as writeFileSync14, rmSync as rmSync12 } from "fs";
|
|
9719
9820
|
import { tmpdir as tmpdir2 } from "os";
|
|
9720
|
-
import { basename as basename8, dirname as
|
|
9821
|
+
import { basename as basename8, dirname as dirname9, join as join23 } from "path";
|
|
9721
9822
|
import { promisify } from "util";
|
|
9722
9823
|
|
|
9723
9824
|
// src/run-limit.ts
|
|
@@ -9846,8 +9947,8 @@ var SandboxRunner = class {
|
|
|
9846
9947
|
throw new Error(`cannot generate the sandbox policy: ${e.message}`);
|
|
9847
9948
|
}
|
|
9848
9949
|
policyFile = policyPathFor(plan.streamPath);
|
|
9849
|
-
|
|
9850
|
-
|
|
9950
|
+
mkdirSync13(dirname9(policyFile), { recursive: true });
|
|
9951
|
+
writeFileSync14(policyFile, generated.yaml, { mode: 384 });
|
|
9851
9952
|
}
|
|
9852
9953
|
const create2 = ["sandbox", "create", "--name", name, "--detach"];
|
|
9853
9954
|
if (policyFile) create2.push("--policy", policyFile);
|
|
@@ -9890,10 +9991,10 @@ var SandboxRunner = class {
|
|
|
9890
9991
|
const harness = cfg.agentBin ?? basename8(plan.bin);
|
|
9891
9992
|
const viaUpload = Buffer.byteLength(plan.prompt) > SANDBOX_STDIN_LIMIT;
|
|
9892
9993
|
if (viaUpload) {
|
|
9893
|
-
const tmp = mkdtempSync(
|
|
9994
|
+
const tmp = mkdtempSync(join23(tmpdir2(), "crew-prompt-"));
|
|
9894
9995
|
try {
|
|
9895
|
-
const file =
|
|
9896
|
-
|
|
9996
|
+
const file = join23(tmp, "prompt.txt");
|
|
9997
|
+
writeFileSync14(file, plan.prompt, { mode: 384 });
|
|
9897
9998
|
await this.os(["sandbox", "upload", name, file, SANDBOX_PROMPT_PATH]);
|
|
9898
9999
|
} finally {
|
|
9899
10000
|
rmSync12(tmp, { recursive: true, force: true });
|
|
@@ -9959,7 +10060,7 @@ function describeExecError(e) {
|
|
|
9959
10060
|
}
|
|
9960
10061
|
function gitOrigin(cwd) {
|
|
9961
10062
|
try {
|
|
9962
|
-
return
|
|
10063
|
+
return execFileSync5("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
9963
10064
|
} catch {
|
|
9964
10065
|
return null;
|
|
9965
10066
|
}
|
|
@@ -9970,7 +10071,7 @@ function runnerFor(kind, sandbox) {
|
|
|
9970
10071
|
function runnerOfSidecar(sc) {
|
|
9971
10072
|
return sc.runner === "sandbox" ? new SandboxRunner(sc.sandboxBin ? { bin: sc.sandboxBin } : {}) : new HostRunner();
|
|
9972
10073
|
}
|
|
9973
|
-
function checkSandboxRunner(cfg = {}, run3 = (b, a) =>
|
|
10074
|
+
function checkSandboxRunner(cfg = {}, run3 = (b, a) => execFileSync5(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 })) {
|
|
9974
10075
|
const bin = cfg.bin ?? DEFAULT_SANDBOX_CONFIG.bin;
|
|
9975
10076
|
let version;
|
|
9976
10077
|
try {
|
|
@@ -10037,7 +10138,7 @@ var ALLOWANCE_REFUSAL_MARKER = "AI_ALLOWANCE_EXCEEDED";
|
|
|
10037
10138
|
function streamShowsAllowanceRefusal(streamPath) {
|
|
10038
10139
|
let raw;
|
|
10039
10140
|
try {
|
|
10040
|
-
raw =
|
|
10141
|
+
raw = readFileSync20(streamPath, "utf8");
|
|
10041
10142
|
} catch {
|
|
10042
10143
|
return false;
|
|
10043
10144
|
}
|
|
@@ -10060,26 +10161,26 @@ function streamShowsAllowanceRefusal(streamPath) {
|
|
|
10060
10161
|
}
|
|
10061
10162
|
return false;
|
|
10062
10163
|
}
|
|
10063
|
-
var digestPathFor = (stateDir, routeLabel, role, runId) =>
|
|
10164
|
+
var digestPathFor = (stateDir, routeLabel, role, runId) => join24(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
10064
10165
|
function planAgentRun(o) {
|
|
10065
|
-
const briefPath =
|
|
10066
|
-
if (!
|
|
10166
|
+
const briefPath = join24(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
10167
|
+
if (!existsSync22(briefPath)) {
|
|
10067
10168
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
10068
10169
|
}
|
|
10069
|
-
const commonPath =
|
|
10070
|
-
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) &&
|
|
10170
|
+
const commonPath = join24(o.route.promptsDir, "personas", "common.md");
|
|
10171
|
+
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync22(commonPath);
|
|
10071
10172
|
const now = o.now ?? (() => Date.now());
|
|
10072
10173
|
const routeLabel = routeSlug(o.route.route);
|
|
10073
10174
|
const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
|
|
10074
|
-
const digestPath2 = ownDigest &&
|
|
10175
|
+
const digestPath2 = ownDigest && existsSync22(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
|
|
10075
10176
|
let digest;
|
|
10076
10177
|
let ageSeconds;
|
|
10077
|
-
if (
|
|
10078
|
-
ageSeconds = Math.floor((now() -
|
|
10079
|
-
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest =
|
|
10178
|
+
if (existsSync22(digestPath2)) {
|
|
10179
|
+
ageSeconds = Math.floor((now() - statSync8(digestPath2).mtimeMs) / 1e3);
|
|
10180
|
+
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync20(digestPath2, "utf8");
|
|
10080
10181
|
}
|
|
10081
|
-
const common = usesCommon ?
|
|
10082
|
-
const brief =
|
|
10182
|
+
const common = usesCommon ? readFileSync20(commonPath, "utf8") : "";
|
|
10183
|
+
const brief = readFileSync20(briefPath, "utf8");
|
|
10083
10184
|
const effectiveCommon = o.divergedPrompt ? "" : common;
|
|
10084
10185
|
const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
|
|
10085
10186
|
const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
|
|
@@ -10102,7 +10203,7 @@ function planAgentRun(o) {
|
|
|
10102
10203
|
digest
|
|
10103
10204
|
});
|
|
10104
10205
|
const promptSha = createHash4("sha256").update(expanded.text).digest("hex");
|
|
10105
|
-
const streamsDir =
|
|
10206
|
+
const streamsDir = join24(o.stateDir, "streams");
|
|
10106
10207
|
const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
|
|
10107
10208
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
10108
10209
|
const { model } = seat;
|
|
@@ -10192,8 +10293,8 @@ function planAgentRun(o) {
|
|
|
10192
10293
|
digestAttached: digest !== void 0,
|
|
10193
10294
|
digestAgeSeconds: ageSeconds,
|
|
10194
10295
|
model,
|
|
10195
|
-
streamPath:
|
|
10196
|
-
eventsPath:
|
|
10296
|
+
streamPath: join24(streamsDir, `${base}.jsonl`),
|
|
10297
|
+
eventsPath: join24(streamsDir, `${base}.events.jsonl`),
|
|
10197
10298
|
ticket: o.ticket,
|
|
10198
10299
|
ticketRecordId: o.ticketRecordId ?? null,
|
|
10199
10300
|
repo: o.repo ?? null,
|
|
@@ -10224,7 +10325,7 @@ function describePlan(p) {
|
|
|
10224
10325
|
var pidPathFor = (streamPath) => `${streamPath}.pid`;
|
|
10225
10326
|
function readRunSidecar(streamPath) {
|
|
10226
10327
|
try {
|
|
10227
|
-
const sc = JSON.parse(
|
|
10328
|
+
const sc = JSON.parse(readFileSync20(pidPathFor(streamPath), "utf8"));
|
|
10228
10329
|
return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
|
|
10229
10330
|
} catch {
|
|
10230
10331
|
return null;
|
|
@@ -10271,7 +10372,7 @@ async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMi
|
|
|
10271
10372
|
var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
|
|
10272
10373
|
function openSink(path) {
|
|
10273
10374
|
try {
|
|
10274
|
-
|
|
10375
|
+
mkdirSync14(dirname10(path), { recursive: true });
|
|
10275
10376
|
return createWriteStream(path, { flags: "a" });
|
|
10276
10377
|
} catch {
|
|
10277
10378
|
return void 0;
|
|
@@ -10453,7 +10554,7 @@ var RunObserver = class {
|
|
|
10453
10554
|
};
|
|
10454
10555
|
function writeRunMeta(id, startedAt, liveTicket, extra) {
|
|
10455
10556
|
try {
|
|
10456
|
-
|
|
10557
|
+
writeFileSync15(`${id.streamPath}.meta.json`, JSON.stringify({
|
|
10457
10558
|
route: id.route,
|
|
10458
10559
|
role: id.role,
|
|
10459
10560
|
hint: id.ticket,
|
|
@@ -10613,7 +10714,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
10613
10714
|
const id = identityOf(plan, adapter.harness);
|
|
10614
10715
|
let streamFd;
|
|
10615
10716
|
try {
|
|
10616
|
-
|
|
10717
|
+
mkdirSync14(dirname10(plan.streamPath), { recursive: true });
|
|
10617
10718
|
streamFd = openSync3(plan.streamPath, "a");
|
|
10618
10719
|
} catch (e) {
|
|
10619
10720
|
void endSink(eventsSink);
|
|
@@ -10645,7 +10746,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
10645
10746
|
closeSync3(streamFd);
|
|
10646
10747
|
return supervise(begun);
|
|
10647
10748
|
function supervise({ child, handle, promptDelivered }) {
|
|
10648
|
-
return new Promise((
|
|
10749
|
+
return new Promise((resolve8, reject) => {
|
|
10649
10750
|
const spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10650
10751
|
const deadline = deadlineFor(spawnedAt, plan.maxRunMinutes);
|
|
10651
10752
|
if (child.pid !== void 0) {
|
|
@@ -10671,7 +10772,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
10671
10772
|
promptSha: plan.promptSha
|
|
10672
10773
|
};
|
|
10673
10774
|
try {
|
|
10674
|
-
|
|
10775
|
+
writeFileSync15(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
|
|
10675
10776
|
} catch {
|
|
10676
10777
|
}
|
|
10677
10778
|
}
|
|
@@ -10715,7 +10816,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
10715
10816
|
plan.agentLog?.apiKey,
|
|
10716
10817
|
hintMoved
|
|
10717
10818
|
);
|
|
10718
|
-
|
|
10819
|
+
resolve8({ code: code ?? 1, ms: ms3, ...handoffPending ? { handoffPending } : {}, ...killed ? { killed } : {} });
|
|
10719
10820
|
});
|
|
10720
10821
|
child.stdin?.on("error", (err3) => {
|
|
10721
10822
|
emit.warn(`agent closed stdin before the prompt was delivered: ${err3.message}`, { step: "agent" });
|
|
@@ -10785,21 +10886,21 @@ var pidAlive = (pid) => {
|
|
|
10785
10886
|
};
|
|
10786
10887
|
function runFinished(streamPath) {
|
|
10787
10888
|
const path = `${streamPath}.meta.json`;
|
|
10788
|
-
if (!
|
|
10889
|
+
if (!existsSync23(path)) return false;
|
|
10789
10890
|
try {
|
|
10790
|
-
return JSON.parse(
|
|
10891
|
+
return JSON.parse(readFileSync21(path, "utf8")).running !== true;
|
|
10791
10892
|
} catch {
|
|
10792
10893
|
return true;
|
|
10793
10894
|
}
|
|
10794
10895
|
}
|
|
10795
10896
|
function orphanedStreams(o) {
|
|
10796
|
-
const dir =
|
|
10797
|
-
if (!
|
|
10897
|
+
const dir = join25(o.stateDir, "streams");
|
|
10898
|
+
if (!existsSync23(dir)) return [];
|
|
10798
10899
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
10799
10900
|
const out = [];
|
|
10800
10901
|
for (const name of readdirSync9(dir)) {
|
|
10801
10902
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
10802
|
-
const streamPath =
|
|
10903
|
+
const streamPath = join25(dir, name);
|
|
10803
10904
|
if (runFinished(streamPath)) continue;
|
|
10804
10905
|
const sidecar = readRunSidecar(streamPath);
|
|
10805
10906
|
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
@@ -10852,15 +10953,15 @@ async function reconcileRuns(o) {
|
|
|
10852
10953
|
}
|
|
10853
10954
|
|
|
10854
10955
|
// src/live-runs.ts
|
|
10855
|
-
import { existsSync as
|
|
10856
|
-
import { join as
|
|
10956
|
+
import { existsSync as existsSync24, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
|
|
10957
|
+
import { join as join26 } from "path";
|
|
10857
10958
|
function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
10858
|
-
const dir =
|
|
10859
|
-
if (!
|
|
10959
|
+
const dir = join26(stateDir, "streams");
|
|
10960
|
+
if (!existsSync24(dir)) return [];
|
|
10860
10961
|
const out = [];
|
|
10861
10962
|
for (const name of readdirSync10(dir)) {
|
|
10862
10963
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
10863
|
-
const streamPath =
|
|
10964
|
+
const streamPath = join26(dir, name);
|
|
10864
10965
|
if (runFinished(streamPath)) continue;
|
|
10865
10966
|
const sidecar = readRunSidecar(streamPath);
|
|
10866
10967
|
if (!sidecar || !isAlive(sidecar)) continue;
|
|
@@ -10870,15 +10971,15 @@ function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
|
10870
10971
|
}
|
|
10871
10972
|
function runActivity(stateDir, isAlive = runIsAlive) {
|
|
10872
10973
|
const activeRuns = liveRuns(stateDir, isAlive).length;
|
|
10873
|
-
const dir =
|
|
10974
|
+
const dir = join26(stateDir, "streams");
|
|
10874
10975
|
let newest = 0;
|
|
10875
|
-
if (
|
|
10976
|
+
if (existsSync24(dir)) {
|
|
10876
10977
|
for (const name of readdirSync10(dir)) {
|
|
10877
10978
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
10878
|
-
const streamPath =
|
|
10979
|
+
const streamPath = join26(dir, name);
|
|
10879
10980
|
if (!runFinished(streamPath)) continue;
|
|
10880
10981
|
try {
|
|
10881
|
-
newest = Math.max(newest,
|
|
10982
|
+
newest = Math.max(newest, statSync9(`${streamPath}.meta.json`).mtimeMs);
|
|
10882
10983
|
} catch {
|
|
10883
10984
|
}
|
|
10884
10985
|
}
|
|
@@ -11246,7 +11347,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
11246
11347
|
if (host === "windows") return [];
|
|
11247
11348
|
const ctx = installContext();
|
|
11248
11349
|
const systemd = detectSystemd();
|
|
11249
|
-
return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) =>
|
|
11350
|
+
return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync25(p));
|
|
11250
11351
|
};
|
|
11251
11352
|
return {
|
|
11252
11353
|
liveRuns: () => liveRuns(stateDir).map((r) => ({ role: r.role, route: r.route, pid: r.pid, ticketRecordId: r.ticketRecordId ?? void 0, streamPath: r.streamPath })),
|
|
@@ -11307,7 +11408,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
11307
11408
|
},
|
|
11308
11409
|
remaining() {
|
|
11309
11410
|
const left = [];
|
|
11310
|
-
if (
|
|
11411
|
+
if (existsSync25(cfg.configFile)) left.push(`config ${cfg.configFile}`);
|
|
11311
11412
|
for (const h of forgeHosts()) if (hasForgeToken(cfg.ship.forges, h, stateDir)) left.push(`forge token for ${h}`);
|
|
11312
11413
|
try {
|
|
11313
11414
|
for (const k of readdirSync11(keysDir(stateDir))) left.push(`seat key ${k}`);
|
|
@@ -11582,7 +11683,7 @@ function makeCachePass(o) {
|
|
|
11582
11683
|
// src/passenger-containers.ts
|
|
11583
11684
|
var realExec2 = (cmd, args, opts) => {
|
|
11584
11685
|
try {
|
|
11585
|
-
return
|
|
11686
|
+
return execFileSync6(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
|
|
11586
11687
|
} catch (e) {
|
|
11587
11688
|
const err3 = e;
|
|
11588
11689
|
const why = [err3.stderr, err3.stdout].map((x) => (x ?? "").trim()).filter(Boolean).join(" / ");
|
|
@@ -11862,7 +11963,7 @@ var WARM_MAX_BYTES = 64 * 1024 * 1024;
|
|
|
11862
11963
|
var globToRegExp = (glob) => new RegExp(`^${glob.split("*").map((p) => p.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")}$`);
|
|
11863
11964
|
function readWarmFiles(dir, ref, patterns) {
|
|
11864
11965
|
if (patterns.length === 0) return {};
|
|
11865
|
-
const git2 = (args) =>
|
|
11966
|
+
const git2 = (args) => execFileSync7("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
|
|
11866
11967
|
const all = git2(["ls-tree", "-r", "--name-only", ref]).split("\n").filter(Boolean);
|
|
11867
11968
|
const res = patterns.map(globToRegExp);
|
|
11868
11969
|
const out = {};
|
|
@@ -11915,19 +12016,19 @@ function planSandbox(repo, decl, warm = {}) {
|
|
|
11915
12016
|
const layer = repoLayer(repo, decl, crew, warm);
|
|
11916
12017
|
return { repo, tag: layer.tag, claude: SANDBOX_CLAUDE_VERSION, layers: [base, crew, layer] };
|
|
11917
12018
|
}
|
|
11918
|
-
var sandboxStatePath = (stateDir) =>
|
|
12019
|
+
var sandboxStatePath = (stateDir) => join27(stateDir, "sandbox-images.json");
|
|
11919
12020
|
function readSandboxState(stateDir) {
|
|
11920
12021
|
try {
|
|
11921
|
-
const v = JSON.parse(
|
|
12022
|
+
const v = JSON.parse(readFileSync22(sandboxStatePath(stateDir), "utf8"));
|
|
11922
12023
|
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
11923
12024
|
} catch {
|
|
11924
12025
|
return {};
|
|
11925
12026
|
}
|
|
11926
12027
|
}
|
|
11927
12028
|
function writeSandboxState(stateDir, state) {
|
|
11928
|
-
|
|
12029
|
+
mkdirSync15(stateDir, { recursive: true });
|
|
11929
12030
|
const file = sandboxStatePath(stateDir);
|
|
11930
|
-
|
|
12031
|
+
writeFileSync16(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
|
|
11931
12032
|
`);
|
|
11932
12033
|
renameSync3(`${file}.tmp`, file);
|
|
11933
12034
|
}
|
|
@@ -11941,15 +12042,15 @@ function sandboxImageStatus(state, plan) {
|
|
|
11941
12042
|
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
12043
|
return { status: "ok", detail: `${current.tag} built ${current.builtAt}, claude ${current.claude ?? "unknown"}` };
|
|
11943
12044
|
}
|
|
11944
|
-
var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((
|
|
12045
|
+
var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((resolve8) => {
|
|
11945
12046
|
const child = spawn4(engine, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
11946
12047
|
const chunks = [];
|
|
11947
12048
|
child.stdout.on("data", (d) => chunks.push(d));
|
|
11948
12049
|
child.stderr.on("data", (d) => chunks.push(d));
|
|
11949
12050
|
const done = (code, extra = "") => {
|
|
11950
|
-
|
|
12051
|
+
writeFileSync16(logPath, `$ ${engine} ${args.join(" ")}
|
|
11951
12052
|
${Buffer.concat(chunks).toString("utf8")}${extra}`, { flag: "a" });
|
|
11952
|
-
|
|
12053
|
+
resolve8(code);
|
|
11953
12054
|
};
|
|
11954
12055
|
child.on("error", (e) => done(127, `${e.message}
|
|
11955
12056
|
`));
|
|
@@ -11969,7 +12070,7 @@ var engineRemoveImage = (engine = "docker", exec = realExec2) => (tag) => {
|
|
|
11969
12070
|
} catch {
|
|
11970
12071
|
}
|
|
11971
12072
|
};
|
|
11972
|
-
var sandboxLogDir = (stateDir) =>
|
|
12073
|
+
var sandboxLogDir = (stateDir) => join27(stateDir, "sandbox-logs");
|
|
11973
12074
|
async function buildSandboxImage(plan, deps) {
|
|
11974
12075
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
11975
12076
|
const state = readSandboxState(deps.stateDir);
|
|
@@ -11978,20 +12079,20 @@ async function buildSandboxImage(plan, deps) {
|
|
|
11978
12079
|
return { kind: "reused", tag: plan.tag };
|
|
11979
12080
|
}
|
|
11980
12081
|
const logDir = sandboxLogDir(deps.stateDir);
|
|
11981
|
-
|
|
11982
|
-
const logPath =
|
|
12082
|
+
mkdirSync15(logDir, { recursive: true });
|
|
12083
|
+
const logPath = join27(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
|
|
11983
12084
|
const builtLayers = [];
|
|
11984
12085
|
for (const layer of plan.layers) {
|
|
11985
12086
|
if (deps.hasImage(layer.tag)) continue;
|
|
11986
|
-
const ctx =
|
|
11987
|
-
|
|
12087
|
+
const ctx = join27(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
|
|
12088
|
+
mkdirSync15(ctx, { recursive: true });
|
|
11988
12089
|
try {
|
|
11989
|
-
|
|
12090
|
+
writeFileSync16(join27(ctx, "Dockerfile"), layer.dockerfile);
|
|
11990
12091
|
for (const [name, content] of Object.entries(layer.files)) {
|
|
11991
|
-
|
|
11992
|
-
|
|
12092
|
+
mkdirSync15(dirname11(join27(ctx, name)), { recursive: true });
|
|
12093
|
+
writeFileSync16(join27(ctx, name), content);
|
|
11993
12094
|
}
|
|
11994
|
-
const code = await deps.run(["build", "--tag", layer.tag, "--file",
|
|
12095
|
+
const code = await deps.run(["build", "--tag", layer.tag, "--file", join27(ctx, "Dockerfile"), ctx], logPath);
|
|
11995
12096
|
if (code !== 0) {
|
|
11996
12097
|
state[plan.repo] = { ...entry, failure: { tag: plan.tag, at: now().toISOString(), logPath } };
|
|
11997
12098
|
writeSandboxState(deps.stateDir, state);
|
|
@@ -12044,10 +12145,10 @@ async function sandboxBuildTick(repos, deps, force = false) {
|
|
|
12044
12145
|
}
|
|
12045
12146
|
|
|
12046
12147
|
// src/doctor-checks.ts
|
|
12047
|
-
import { execFileSync as
|
|
12148
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
12048
12149
|
var PROBE_BRANCH = "crew-probe-branch";
|
|
12049
12150
|
var realGit = (cwd, args, env) => {
|
|
12050
|
-
|
|
12151
|
+
execFileSync8("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
12051
12152
|
};
|
|
12052
12153
|
function isSshRemote(url) {
|
|
12053
12154
|
return /^ssh:\/\//.test(url) || /^[\w.-]+@[\w.-]+:/.test(url);
|
|
@@ -12127,10 +12228,10 @@ function formatPushAuth(repo, r) {
|
|
|
12127
12228
|
return `${label}FAILED \u2014 ${r.detail}; ${r.hint}
|
|
12128
12229
|
`;
|
|
12129
12230
|
}
|
|
12130
|
-
async function settleRepos(items, nameOf,
|
|
12231
|
+
async function settleRepos(items, nameOf, resolve8) {
|
|
12131
12232
|
const out = await Promise.all(items.map(async (t) => {
|
|
12132
12233
|
try {
|
|
12133
|
-
return { ok: true, value: await
|
|
12234
|
+
return { ok: true, value: await resolve8(t) };
|
|
12134
12235
|
} catch (e) {
|
|
12135
12236
|
return { ok: false, repo: nameOf(t), detail: e.message };
|
|
12136
12237
|
}
|
|
@@ -12180,14 +12281,14 @@ function managedRuntimeWarnings(kind, ship) {
|
|
|
12180
12281
|
|
|
12181
12282
|
// src/selftest.ts
|
|
12182
12283
|
import { spawn as spawn5 } from "child_process";
|
|
12183
|
-
import { existsSync as
|
|
12284
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync16, mkdtempSync as mkdtempSync2, readFileSync as readFileSync23, rmSync as rmSync14, writeFileSync as writeFileSync17 } from "fs";
|
|
12184
12285
|
import { tmpdir as tmpdir4 } from "os";
|
|
12185
|
-
import { join as
|
|
12286
|
+
import { join as join28 } from "path";
|
|
12186
12287
|
var SELFTEST_TIMEOUT_MS = 12e4;
|
|
12187
12288
|
var KEY_VAR = "CREW_API_KEY";
|
|
12188
12289
|
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
12290
|
var realDeps = {
|
|
12190
|
-
run: (o) => new Promise((
|
|
12291
|
+
run: (o) => new Promise((resolve8) => {
|
|
12191
12292
|
let stdout = "";
|
|
12192
12293
|
let stderr = "";
|
|
12193
12294
|
let timedOut = false;
|
|
@@ -12195,7 +12296,7 @@ var realDeps = {
|
|
|
12195
12296
|
const done = (r) => {
|
|
12196
12297
|
if (!settled) {
|
|
12197
12298
|
settled = true;
|
|
12198
|
-
|
|
12299
|
+
resolve8(r);
|
|
12199
12300
|
}
|
|
12200
12301
|
};
|
|
12201
12302
|
const child = spawn5(o.bin, o.args, { cwd: o.cwd, env: o.env, stdio: ["pipe", "pipe", "pipe"], detached: true });
|
|
@@ -12343,7 +12444,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
12343
12444
|
...adapter.runEnv(spec),
|
|
12344
12445
|
[KEY_VAR]: i.apiKey
|
|
12345
12446
|
};
|
|
12346
|
-
const cwd = mkdtempSync2(
|
|
12447
|
+
const cwd = mkdtempSync2(join28(tmpdir4(), "crew-selftest-"));
|
|
12347
12448
|
try {
|
|
12348
12449
|
const run3 = await deps.run({
|
|
12349
12450
|
bin: invocation.bin,
|
|
@@ -12376,11 +12477,11 @@ function formatResult(r) {
|
|
|
12376
12477
|
const secs = `${(r.ms / 1e3).toFixed(1)}s`;
|
|
12377
12478
|
return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
|
|
12378
12479
|
}
|
|
12379
|
-
var fileFor2 = (stateDir) =>
|
|
12480
|
+
var fileFor2 = (stateDir) => join28(stateDir, "selftest.json");
|
|
12380
12481
|
function readSelftest(stateDir) {
|
|
12381
12482
|
try {
|
|
12382
12483
|
const p = fileFor2(stateDir);
|
|
12383
|
-
return
|
|
12484
|
+
return existsSync26(p) ? JSON.parse(readFileSync23(p, "utf8")) : {};
|
|
12384
12485
|
} catch {
|
|
12385
12486
|
return {};
|
|
12386
12487
|
}
|
|
@@ -12388,8 +12489,8 @@ function readSelftest(stateDir) {
|
|
|
12388
12489
|
function recordSelftest(stateDir, results) {
|
|
12389
12490
|
const all = readSelftest(stateDir);
|
|
12390
12491
|
for (const r of results) all[r.toolchain] = r;
|
|
12391
|
-
|
|
12392
|
-
|
|
12492
|
+
mkdirSync16(stateDir, { recursive: true });
|
|
12493
|
+
writeFileSync17(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
|
|
12393
12494
|
`);
|
|
12394
12495
|
}
|
|
12395
12496
|
function doctorSelftestLines(stored, toolchains) {
|
|
@@ -12401,7 +12502,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
12401
12502
|
}
|
|
12402
12503
|
|
|
12403
12504
|
// src/cli.ts
|
|
12404
|
-
import { resolve as
|
|
12505
|
+
import { resolve as resolve7, dirname as dirname29, join as join50, basename as basename16 } from "path";
|
|
12405
12506
|
import { fileURLToPath } from "url";
|
|
12406
12507
|
import { hostname as hostname4, homedir as homedir11 } from "os";
|
|
12407
12508
|
import { spawnSync as spawnSync10 } from "child_process";
|
|
@@ -12598,14 +12699,14 @@ function rosterMarkdown(roster, meId) {
|
|
|
12598
12699
|
}
|
|
12599
12700
|
|
|
12600
12701
|
// src/connect-resume.ts
|
|
12601
|
-
import { mkdirSync as
|
|
12602
|
-
import { dirname as
|
|
12702
|
+
import { mkdirSync as mkdirSync17, readFileSync as readFileSync24, rmSync as rmSync15, writeFileSync as writeFileSync18 } from "fs";
|
|
12703
|
+
import { dirname as dirname12, join as join29 } from "path";
|
|
12603
12704
|
function lastSessionFile(stateDir) {
|
|
12604
|
-
return
|
|
12705
|
+
return join29(stateDir, "connect-session.json");
|
|
12605
12706
|
}
|
|
12606
12707
|
function readPointers(stateDir) {
|
|
12607
12708
|
try {
|
|
12608
|
-
const parsed = JSON.parse(
|
|
12709
|
+
const parsed = JSON.parse(readFileSync24(lastSessionFile(stateDir), "utf8"));
|
|
12609
12710
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
12610
12711
|
const out = {};
|
|
12611
12712
|
for (const [host, v] of Object.entries(parsed)) {
|
|
@@ -12619,8 +12720,8 @@ function readPointers(stateDir) {
|
|
|
12619
12720
|
}
|
|
12620
12721
|
function writePointers(stateDir, pointers) {
|
|
12621
12722
|
const file = lastSessionFile(stateDir);
|
|
12622
|
-
|
|
12623
|
-
|
|
12723
|
+
mkdirSync17(dirname12(file), { recursive: true });
|
|
12724
|
+
writeFileSync18(file, `${JSON.stringify(pointers, null, 2)}
|
|
12624
12725
|
`, { mode: 384 });
|
|
12625
12726
|
}
|
|
12626
12727
|
function rememberSession(stateDir, host, workspaceSlug) {
|
|
@@ -12705,15 +12806,15 @@ async function signOutSessions(o) {
|
|
|
12705
12806
|
}
|
|
12706
12807
|
|
|
12707
12808
|
// src/reset.ts
|
|
12708
|
-
import { existsSync as
|
|
12709
|
-
import { dirname as
|
|
12809
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync18, readFileSync as readFileSync25, readdirSync as readdirSync12, rmSync as rmSync16, statSync as statSync10, writeFileSync as writeFileSync19 } from "fs";
|
|
12810
|
+
import { dirname as dirname13, join as join30 } from "path";
|
|
12710
12811
|
import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
|
|
12711
12812
|
function clonedCheckoutsFile(stateDir) {
|
|
12712
|
-
return
|
|
12813
|
+
return join30(stateDir, "cloned-checkouts.json");
|
|
12713
12814
|
}
|
|
12714
12815
|
function readClonedCheckouts(stateDir) {
|
|
12715
12816
|
try {
|
|
12716
|
-
const parsed = JSON.parse(
|
|
12817
|
+
const parsed = JSON.parse(readFileSync25(clonedCheckoutsFile(stateDir), "utf8"));
|
|
12717
12818
|
return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
|
|
12718
12819
|
} catch {
|
|
12719
12820
|
return [];
|
|
@@ -12723,8 +12824,8 @@ function recordClonedCheckout(stateDir, dir) {
|
|
|
12723
12824
|
const known = readClonedCheckouts(stateDir);
|
|
12724
12825
|
if (known.includes(dir)) return;
|
|
12725
12826
|
const file = clonedCheckoutsFile(stateDir);
|
|
12726
|
-
|
|
12727
|
-
|
|
12827
|
+
mkdirSync18(dirname13(file), { recursive: true });
|
|
12828
|
+
writeFileSync19(file, `${JSON.stringify([...known, dir], null, 2)}
|
|
12728
12829
|
`);
|
|
12729
12830
|
}
|
|
12730
12831
|
var mark = (dryRun) => dryRun ? "would-remove" : "removed";
|
|
@@ -12778,7 +12879,7 @@ async function runReset(o, fx) {
|
|
|
12778
12879
|
}
|
|
12779
12880
|
if (o.beforeState) steps.push(...await o.beforeState());
|
|
12780
12881
|
steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
|
|
12781
|
-
if (
|
|
12882
|
+
if (existsSync27(o.configFile)) {
|
|
12782
12883
|
if (!o.dryRun) rmSync16(o.configFile, { force: true });
|
|
12783
12884
|
steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
|
|
12784
12885
|
} else {
|
|
@@ -12787,7 +12888,7 @@ async function runReset(o, fx) {
|
|
|
12787
12888
|
return steps;
|
|
12788
12889
|
}
|
|
12789
12890
|
function removeCheckout(dir, dryRun, fx) {
|
|
12790
|
-
if (!
|
|
12891
|
+
if (!existsSync27(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
|
|
12791
12892
|
const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
|
|
12792
12893
|
if (dirty.length) {
|
|
12793
12894
|
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 +12901,15 @@ function removeCheckout(dir, dryRun, fx) {
|
|
|
12800
12901
|
}
|
|
12801
12902
|
function removeState(stateDir, dryRun) {
|
|
12802
12903
|
const out = [];
|
|
12803
|
-
const targets = [
|
|
12904
|
+
const targets = [join30(stateDir, "resolved"), join30(stateDir, "keys"), clonedCheckoutsFile(stateDir), join30(stateDir, "connect-session.json"), join30(stateDir, "reload-requested"), join30(stateDir, "daemon.started")];
|
|
12804
12905
|
try {
|
|
12805
12906
|
for (const name of readdirSync12(stateDir)) {
|
|
12806
|
-
if (name.startsWith(".") &&
|
|
12907
|
+
if (name.startsWith(".") && statSync10(join30(stateDir, name)).isFile()) targets.push(join30(stateDir, name));
|
|
12807
12908
|
}
|
|
12808
12909
|
} catch {
|
|
12809
12910
|
}
|
|
12810
12911
|
for (const t of targets) {
|
|
12811
|
-
if (!
|
|
12912
|
+
if (!existsSync27(t)) continue;
|
|
12812
12913
|
if (!dryRun) rmSync16(t, { recursive: true, force: true });
|
|
12813
12914
|
out.push({ step: "state", target: t, action: mark(dryRun) });
|
|
12814
12915
|
}
|
|
@@ -12823,7 +12924,7 @@ function wipeStateDir(stateDir, dryRun) {
|
|
|
12823
12924
|
return [{ step: "state", target: stateDir, action: "absent", detail: "no state directory" }];
|
|
12824
12925
|
}
|
|
12825
12926
|
if (!names.length) return [{ step: "state", target: stateDir, action: "absent", detail: "already empty" }];
|
|
12826
|
-
if (!dryRun) for (const n of names) rmSync16(
|
|
12927
|
+
if (!dryRun) for (const n of names) rmSync16(join30(stateDir, n), { recursive: true, force: true });
|
|
12827
12928
|
return [{ step: "state", target: stateDir, action: mark(dryRun), detail: `${names.length} entr${names.length === 1 ? "y" : "ies"}` }];
|
|
12828
12929
|
}
|
|
12829
12930
|
function describeResetStep(s) {
|
|
@@ -12904,9 +13005,9 @@ async function runDeprovision(o, fx, deps) {
|
|
|
12904
13005
|
}
|
|
12905
13006
|
|
|
12906
13007
|
// src/pool-member.ts
|
|
12907
|
-
import { mkdirSync as
|
|
13008
|
+
import { mkdirSync as mkdirSync19, readFileSync as readFileSync26, renameSync as renameSync4, writeFileSync as writeFileSync20 } from "fs";
|
|
12908
13009
|
import { homedir as homedir8 } from "os";
|
|
12909
|
-
import { dirname as
|
|
13010
|
+
import { dirname as dirname14, join as join31 } from "path";
|
|
12910
13011
|
var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
|
|
12911
13012
|
function backoffMs(attempt) {
|
|
12912
13013
|
return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
|
|
@@ -13137,22 +13238,22 @@ async function runPoolMember(deps) {
|
|
|
13137
13238
|
}
|
|
13138
13239
|
}
|
|
13139
13240
|
function defaultMemberDir(env = process.env) {
|
|
13140
|
-
return env.CREW_POOL_DIR ??
|
|
13241
|
+
return env.CREW_POOL_DIR ?? join31(env.XDG_STATE_HOME ?? join31(homedir8(), ".local", "state"), "crew-pool-member");
|
|
13141
13242
|
}
|
|
13142
13243
|
function fileMemberStore(dir) {
|
|
13143
|
-
const file =
|
|
13244
|
+
const file = join31(dir, "member.json");
|
|
13144
13245
|
return {
|
|
13145
13246
|
read() {
|
|
13146
13247
|
try {
|
|
13147
|
-
return JSON.parse(
|
|
13248
|
+
return JSON.parse(readFileSync26(file, "utf8"));
|
|
13148
13249
|
} catch {
|
|
13149
13250
|
return {};
|
|
13150
13251
|
}
|
|
13151
13252
|
},
|
|
13152
13253
|
write(f) {
|
|
13153
|
-
|
|
13254
|
+
mkdirSync19(dirname14(file), { recursive: true, mode: 448 });
|
|
13154
13255
|
const tmp = `${file}.tmp`;
|
|
13155
|
-
|
|
13256
|
+
writeFileSync20(tmp, `${JSON.stringify(f)}
|
|
13156
13257
|
`, { mode: 384 });
|
|
13157
13258
|
renameSync4(tmp, file);
|
|
13158
13259
|
}
|
|
@@ -13204,30 +13305,30 @@ function hostArchitecture(arch = process.arch) {
|
|
|
13204
13305
|
|
|
13205
13306
|
// src/pool-member-host.ts
|
|
13206
13307
|
import "child_process";
|
|
13207
|
-
import { existsSync as
|
|
13308
|
+
import { existsSync as existsSync31 } from "fs";
|
|
13208
13309
|
import { hostname as hostname3 } from "os";
|
|
13209
13310
|
|
|
13210
13311
|
// src/machine-id.ts
|
|
13211
|
-
import { execFileSync as
|
|
13312
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
13212
13313
|
import { createHash as createHash7 } from "crypto";
|
|
13213
|
-
import { existsSync as
|
|
13214
|
-
import { dirname as
|
|
13314
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync20, readFileSync as readFileSync27, writeFileSync as writeFileSync21 } from "fs";
|
|
13315
|
+
import { dirname as dirname15, join as join32 } from "path";
|
|
13215
13316
|
function machineIdPathFor(stateDir) {
|
|
13216
|
-
return
|
|
13317
|
+
return join32(stateDir, "machine-id");
|
|
13217
13318
|
}
|
|
13218
13319
|
var sha256 = (s) => createHash7("sha256").update(s).digest("hex");
|
|
13219
13320
|
function readRawMachineId(platform2 = process.platform) {
|
|
13220
13321
|
try {
|
|
13221
13322
|
if (platform2 === "darwin") {
|
|
13222
|
-
const out =
|
|
13323
|
+
const out = execFileSync9("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
|
|
13223
13324
|
encoding: "utf8",
|
|
13224
13325
|
stdio: ["ignore", "pipe", "ignore"]
|
|
13225
13326
|
});
|
|
13226
13327
|
return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
|
|
13227
13328
|
}
|
|
13228
13329
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
13229
|
-
if (
|
|
13230
|
-
const id =
|
|
13330
|
+
if (existsSync28(p)) {
|
|
13331
|
+
const id = readFileSync27(p, "utf8").trim();
|
|
13231
13332
|
if (id) return id;
|
|
13232
13333
|
}
|
|
13233
13334
|
}
|
|
@@ -13238,15 +13339,15 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
13238
13339
|
function ensureMachineId(stateDir, opts = {}) {
|
|
13239
13340
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
13240
13341
|
const path = machineIdPathFor(stateDir);
|
|
13241
|
-
if (
|
|
13242
|
-
const cached =
|
|
13342
|
+
if (existsSync28(path)) {
|
|
13343
|
+
const cached = readFileSync27(path, "utf8").trim();
|
|
13243
13344
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
13244
13345
|
}
|
|
13245
13346
|
const raw = (opts.read ?? readRawMachineId)();
|
|
13246
13347
|
if (!raw) return void 0;
|
|
13247
13348
|
const hashed = sha256(raw);
|
|
13248
|
-
|
|
13249
|
-
|
|
13349
|
+
mkdirSync20(dirname15(path), { recursive: true });
|
|
13350
|
+
writeFileSync21(path, `${hashed}
|
|
13250
13351
|
`, { mode: 384 });
|
|
13251
13352
|
return hashed;
|
|
13252
13353
|
}
|
|
@@ -13259,7 +13360,7 @@ function matchShipRows(ships, shipName2, machineId) {
|
|
|
13259
13360
|
|
|
13260
13361
|
// src/provision.ts
|
|
13261
13362
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
13262
|
-
import { existsSync as
|
|
13363
|
+
import { existsSync as existsSync29, readFileSync as readFileSync28, writeFileSync as writeFileSync22 } from "fs";
|
|
13263
13364
|
import { hostname as hostname2 } from "os";
|
|
13264
13365
|
import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
|
|
13265
13366
|
|
|
@@ -13268,7 +13369,7 @@ import { parseDocument as parseDocument2 } from "yaml";
|
|
|
13268
13369
|
|
|
13269
13370
|
// src/repos-cmd.ts
|
|
13270
13371
|
import { homedir as homedir9 } from "os";
|
|
13271
|
-
import { basename as basename10, join as
|
|
13372
|
+
import { basename as basename10, join as join33, resolve as resolve4 } from "path";
|
|
13272
13373
|
import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
|
|
13273
13374
|
var ReposError = class extends Error {
|
|
13274
13375
|
};
|
|
@@ -13315,7 +13416,7 @@ function parseAddArgs(args, routes, isDir) {
|
|
|
13315
13416
|
}
|
|
13316
13417
|
function expandDir(p, base) {
|
|
13317
13418
|
const home = p === "~" ? homedir9() : p.startsWith("~/") ? `${homedir9()}/${p.slice(2)}` : p;
|
|
13318
|
-
return
|
|
13419
|
+
return resolve4(base, home);
|
|
13319
13420
|
}
|
|
13320
13421
|
function routeMapOf(doc, route2) {
|
|
13321
13422
|
const routes = doc.get("routes", true);
|
|
@@ -13546,7 +13647,7 @@ function planRepoClone(i) {
|
|
|
13546
13647
|
}
|
|
13547
13648
|
const ship = doc.get("ship", true);
|
|
13548
13649
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
13549
|
-
const target = i.dir ? expandDir(i.dir, i.base) :
|
|
13650
|
+
const target = i.dir ? expandDir(i.dir, i.base) : join33(expandDir(basePath, i.base), i.route.split("/")[0], name);
|
|
13550
13651
|
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
|
|
13551
13652
|
const forgeHost = i.resolved?.repoForgeHosts?.[id];
|
|
13552
13653
|
return { ...add, remote, target, ...forgeHost ? { forgeHost } : {} };
|
|
@@ -13853,7 +13954,7 @@ function realProvisionDeps(crewHome) {
|
|
|
13853
13954
|
};
|
|
13854
13955
|
const configFile = () => {
|
|
13855
13956
|
const f = findConfigFile(crewHome);
|
|
13856
|
-
return
|
|
13957
|
+
return existsSync29(f) ? f : null;
|
|
13857
13958
|
};
|
|
13858
13959
|
return {
|
|
13859
13960
|
crew: spawnCrew,
|
|
@@ -13861,13 +13962,13 @@ function realProvisionDeps(crewHome) {
|
|
|
13861
13962
|
enableRoute(route2) {
|
|
13862
13963
|
const file = configFile();
|
|
13863
13964
|
if (!file) return false;
|
|
13864
|
-
const text2 =
|
|
13965
|
+
const text2 = readFileSync28(file, "utf8");
|
|
13865
13966
|
const doc = parseDocument3(text2);
|
|
13866
13967
|
const routes = doc.get("routes");
|
|
13867
13968
|
const entry = routes?.items?.find((r) => r.get("route") === route2);
|
|
13868
13969
|
if (!entry) throw new Error(`route ${route2} is not in ${file}`);
|
|
13869
13970
|
if (entry.get("enabled") === true) return false;
|
|
13870
|
-
|
|
13971
|
+
writeFileSync22(file, planEnable(text2, route2));
|
|
13871
13972
|
return true;
|
|
13872
13973
|
},
|
|
13873
13974
|
installState() {
|
|
@@ -13887,14 +13988,14 @@ function realProvisionDeps(crewHome) {
|
|
|
13887
13988
|
continue;
|
|
13888
13989
|
}
|
|
13889
13990
|
for (const p of plan.unitPaths) {
|
|
13890
|
-
if (!
|
|
13991
|
+
if (!existsSync29(p) || readFileSync28(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
|
|
13891
13992
|
}
|
|
13892
13993
|
}
|
|
13893
13994
|
const label = labelFor(crewHome, "run");
|
|
13894
13995
|
let running = false;
|
|
13895
13996
|
let lingering = true;
|
|
13896
13997
|
if (mechanism === "systemd") {
|
|
13897
|
-
const system = ctx.root ||
|
|
13998
|
+
const system = ctx.root || existsSync29(systemUnitPathFor(label));
|
|
13898
13999
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
13899
14000
|
running = spawnSync7(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
|
|
13900
14001
|
if (!system) {
|
|
@@ -13911,18 +14012,18 @@ function realProvisionDeps(crewHome) {
|
|
|
13911
14012
|
setShipSetting(key, value2) {
|
|
13912
14013
|
const file = configFile();
|
|
13913
14014
|
if (!file) return false;
|
|
13914
|
-
const doc = parseDocument3(
|
|
14015
|
+
const doc = parseDocument3(readFileSync28(file, "utf8"));
|
|
13915
14016
|
if (doc.getIn(["ship", key]) === value2) return false;
|
|
13916
14017
|
doc.setIn(["ship", key], value2);
|
|
13917
|
-
|
|
14018
|
+
writeFileSync22(file, doc.toString());
|
|
13918
14019
|
return true;
|
|
13919
14020
|
},
|
|
13920
14021
|
applyManagedDefaults() {
|
|
13921
14022
|
const file = configFile();
|
|
13922
14023
|
if (!file) return [];
|
|
13923
|
-
const doc = parseDocument3(
|
|
14024
|
+
const doc = parseDocument3(readFileSync28(file, "utf8"));
|
|
13924
14025
|
const wrote = applyManagedDefaultsToDoc(doc);
|
|
13925
|
-
if (wrote.length)
|
|
14026
|
+
if (wrote.length) writeFileSync22(file, doc.toString());
|
|
13926
14027
|
return wrote;
|
|
13927
14028
|
},
|
|
13928
14029
|
storedToken(host) {
|
|
@@ -13939,9 +14040,9 @@ host=${host}
|
|
|
13939
14040
|
|
|
13940
14041
|
// src/tunnel.ts
|
|
13941
14042
|
import { spawn as spawn6 } from "child_process";
|
|
13942
|
-
import { existsSync as
|
|
14043
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync21, readFileSync as readFileSync29, writeFileSync as writeFileSync23, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
|
|
13943
14044
|
import { randomInt } from "crypto";
|
|
13944
|
-
import { join as
|
|
14045
|
+
import { join as join34 } from "path";
|
|
13945
14046
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
13946
14047
|
var defaultSpawn = (cmd, args) => spawn6(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
13947
14048
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -14013,21 +14114,21 @@ function startTunnel(opts) {
|
|
|
14013
14114
|
};
|
|
14014
14115
|
}
|
|
14015
14116
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
14016
|
-
return
|
|
14117
|
+
return join34(stateDir, "passengers", `${workspaceId}.json`);
|
|
14017
14118
|
}
|
|
14018
14119
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
14019
14120
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
14020
|
-
if (!
|
|
14121
|
+
if (!existsSync30(path)) return void 0;
|
|
14021
14122
|
try {
|
|
14022
|
-
return JSON.parse(
|
|
14123
|
+
return JSON.parse(readFileSync29(path, "utf8"));
|
|
14023
14124
|
} catch {
|
|
14024
14125
|
return void 0;
|
|
14025
14126
|
}
|
|
14026
14127
|
}
|
|
14027
14128
|
function writePersistedTunnel(stateDir, state) {
|
|
14028
14129
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
14029
|
-
|
|
14030
|
-
|
|
14130
|
+
mkdirSync21(join34(stateDir, "passengers"), { recursive: true });
|
|
14131
|
+
writeFileSync23(path, `${JSON.stringify(state, null, 2)}
|
|
14031
14132
|
`);
|
|
14032
14133
|
}
|
|
14033
14134
|
function updatePersistedTunnel(stateDir, workspaceId, patch2) {
|
|
@@ -14129,8 +14230,8 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
14129
14230
|
}
|
|
14130
14231
|
}
|
|
14131
14232
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
14132
|
-
const dir =
|
|
14133
|
-
if (!
|
|
14233
|
+
const dir = join34(stateDir, "passengers");
|
|
14234
|
+
if (!existsSync30(dir)) return [];
|
|
14134
14235
|
return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
14135
14236
|
}
|
|
14136
14237
|
|
|
@@ -14155,7 +14256,7 @@ function realPoolMemberDeps(o) {
|
|
|
14155
14256
|
crewVersion: crewVersion(o.crewHome),
|
|
14156
14257
|
hostname: hostname3()
|
|
14157
14258
|
},
|
|
14158
|
-
hasShipConfig: () =>
|
|
14259
|
+
hasShipConfig: () => existsSync31(findConfigFile(o.crewHome)),
|
|
14159
14260
|
async provision(lease, instanceClass, shipKind) {
|
|
14160
14261
|
const hosts = lease.forges.map((f) => f.host.toLowerCase());
|
|
14161
14262
|
const lines2 = [];
|
|
@@ -15031,8 +15132,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
|
|
|
15031
15132
|
}
|
|
15032
15133
|
|
|
15033
15134
|
// src/poll.ts
|
|
15034
|
-
import { writeFileSync as
|
|
15035
|
-
import { join as
|
|
15135
|
+
import { writeFileSync as writeFileSync24 } from "fs";
|
|
15136
|
+
import { join as join35 } from "path";
|
|
15036
15137
|
|
|
15037
15138
|
// src/stalled.ts
|
|
15038
15139
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -15339,7 +15440,7 @@ async function decideCycle(o) {
|
|
|
15339
15440
|
}
|
|
15340
15441
|
return decision;
|
|
15341
15442
|
}
|
|
15342
|
-
var digestPath = (stateDir, route2, role, runId) =>
|
|
15443
|
+
var digestPath = (stateDir, route2, role, runId) => join35(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
15343
15444
|
function otherRunsRemoved(tickets, own) {
|
|
15344
15445
|
if (!own) return tickets;
|
|
15345
15446
|
return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
|
|
@@ -15417,7 +15518,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
|
|
|
15417
15518
|
|
|
15418
15519
|
${body}` : body;
|
|
15419
15520
|
const path = digestPath(stateDir, routeSlug(o.route.route), role, runId);
|
|
15420
|
-
|
|
15521
|
+
writeFileSync24(path, text2);
|
|
15421
15522
|
o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text2)} bytes)`, {
|
|
15422
15523
|
data: { role, bytes: Buffer.byteLength(text2) }
|
|
15423
15524
|
});
|
|
@@ -15525,8 +15626,8 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
|
|
|
15525
15626
|
}
|
|
15526
15627
|
|
|
15527
15628
|
// src/repo-refresh.ts
|
|
15528
|
-
import { existsSync as
|
|
15529
|
-
import { dirname as
|
|
15629
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync22, readFileSync as readFileSync30, writeFileSync as writeFileSync25 } from "fs";
|
|
15630
|
+
import { dirname as dirname17 } from "path";
|
|
15530
15631
|
var REFRESH_COOLDOWN_MS = 2 * 6e4;
|
|
15531
15632
|
var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
|
|
15532
15633
|
var lastAsked = /* @__PURE__ */ new Map();
|
|
@@ -15555,11 +15656,11 @@ function applyRepoRows(resolved, rows) {
|
|
|
15555
15656
|
}
|
|
15556
15657
|
function persistRepoRows(stateDir, routeName, rows) {
|
|
15557
15658
|
const path = resolvedPathFor(stateDir, routeName);
|
|
15558
|
-
if (!
|
|
15559
|
-
const raw = JSON.parse(
|
|
15659
|
+
if (!existsSync32(path)) return;
|
|
15660
|
+
const raw = JSON.parse(readFileSync30(path, "utf8"));
|
|
15560
15661
|
applyRepoRows(raw, rows);
|
|
15561
|
-
|
|
15562
|
-
|
|
15662
|
+
mkdirSync22(dirname17(path), { recursive: true });
|
|
15663
|
+
writeFileSync25(path, `${JSON.stringify(raw, null, 2)}
|
|
15563
15664
|
`);
|
|
15564
15665
|
}
|
|
15565
15666
|
async function refreshUnknownRepos(o) {
|
|
@@ -16009,13 +16110,13 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
|
|
|
16009
16110
|
var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
|
|
16010
16111
|
|
|
16011
16112
|
// src/connect-wizard.ts
|
|
16012
|
-
import { delimiter, dirname as
|
|
16013
|
-
import { accessSync as accessSync3, constants as constants3, existsSync as
|
|
16113
|
+
import { delimiter, dirname as dirname18, join as join36 } from "path";
|
|
16114
|
+
import { accessSync as accessSync3, constants as constants3, existsSync as existsSync33, mkdirSync as mkdirSync23, writeFileSync as writeFileSync26 } from "fs";
|
|
16014
16115
|
import { stringify as stringify3 } from "yaml";
|
|
16015
16116
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
16016
16117
|
function shouldRunWizard(opts) {
|
|
16017
16118
|
if (!opts.isTTY) return false;
|
|
16018
|
-
const exists = opts.configExists ??
|
|
16119
|
+
const exists = opts.configExists ?? existsSync33;
|
|
16019
16120
|
return !configSearchPath(opts.crewHome).some((p) => exists(p));
|
|
16020
16121
|
}
|
|
16021
16122
|
function firstRunConfigPath() {
|
|
@@ -16032,7 +16133,7 @@ function defaultIsExecutable(p) {
|
|
|
16032
16133
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
16033
16134
|
for (const dir of pathEnv.split(delimiter)) {
|
|
16034
16135
|
if (!dir) continue;
|
|
16035
|
-
const candidate =
|
|
16136
|
+
const candidate = join36(dir, name);
|
|
16036
16137
|
if (isExecutable(candidate)) return candidate;
|
|
16037
16138
|
}
|
|
16038
16139
|
return void 0;
|
|
@@ -16058,9 +16159,9 @@ async function runWizard(p, env) {
|
|
|
16058
16159
|
if (entered && isExec(entered)) agentBin = entered;
|
|
16059
16160
|
else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
|
|
16060
16161
|
}
|
|
16061
|
-
const nodeDir =
|
|
16162
|
+
const nodeDir = dirname18(env.nodePath);
|
|
16062
16163
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
16063
|
-
const baseDirs = [nodeDir, pnpmPath ?
|
|
16164
|
+
const baseDirs = [nodeDir, pnpmPath ? dirname18(pnpmPath) : void 0];
|
|
16064
16165
|
p.say(
|
|
16065
16166
|
`
|
|
16066
16167
|
Host Passengers lets this machine run the agent sessions that other people's
|
|
@@ -16071,7 +16172,7 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
|
|
|
16071
16172
|
p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
|
|
16072
16173
|
}
|
|
16073
16174
|
const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
16074
|
-
const extraPath = deriveExtraPath([...baseDirs, dockerPath ?
|
|
16175
|
+
const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname18(dockerPath) : void 0]);
|
|
16075
16176
|
if (extraPath) {
|
|
16076
16177
|
p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
|
|
16077
16178
|
}
|
|
@@ -16093,9 +16194,9 @@ routes:
|
|
|
16093
16194
|
${routeBlock}`;
|
|
16094
16195
|
}
|
|
16095
16196
|
function writeNewConfig(path, text2) {
|
|
16096
|
-
|
|
16197
|
+
mkdirSync23(dirname18(path), { recursive: true });
|
|
16097
16198
|
try {
|
|
16098
|
-
|
|
16199
|
+
writeFileSync26(path, text2, { mode: 384, flag: "wx" });
|
|
16099
16200
|
return true;
|
|
16100
16201
|
} catch (e) {
|
|
16101
16202
|
if (e.code === "EEXIST") return false;
|
|
@@ -16129,7 +16230,7 @@ Wrote ${o.configPath}.
|
|
|
16129
16230
|
// src/connect-json.ts
|
|
16130
16231
|
import { accessSync as accessSync4, constants as constants4 } from "fs";
|
|
16131
16232
|
import { homedir as homedir10 } from "os";
|
|
16132
|
-
import { dirname as
|
|
16233
|
+
import { dirname as dirname19, join as join37 } from "path";
|
|
16133
16234
|
import { delimiter as delimiter2 } from "path";
|
|
16134
16235
|
var CHOICE_EXIT_CODE = 3;
|
|
16135
16236
|
function createEmitter(json2, write) {
|
|
@@ -16168,12 +16269,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
|
|
|
16168
16269
|
const dirs = [
|
|
16169
16270
|
...pathEnv.split(delimiter2),
|
|
16170
16271
|
...(extraPath ?? "").split(delimiter2),
|
|
16171
|
-
|
|
16172
|
-
|
|
16272
|
+
join37(home, ".local/bin"),
|
|
16273
|
+
join37(home, ".claude/local"),
|
|
16173
16274
|
"/opt/homebrew/bin",
|
|
16174
16275
|
"/usr/local/bin",
|
|
16175
|
-
|
|
16176
|
-
|
|
16276
|
+
join37(home, ".npm-global/bin"),
|
|
16277
|
+
join37(home, ".volta/bin")
|
|
16177
16278
|
];
|
|
16178
16279
|
return [...new Set(dirs.filter(Boolean))];
|
|
16179
16280
|
}
|
|
@@ -16195,7 +16296,7 @@ function flagWizardAnswers(f, env) {
|
|
|
16195
16296
|
const agentBin = f.agentBin ?? found?.bin;
|
|
16196
16297
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
16197
16298
|
const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
16198
|
-
const extraPath = f.extraPath ?? deriveExtraPath([
|
|
16299
|
+
const extraPath = f.extraPath ?? deriveExtraPath([dirname19(env.nodePath), pnpmPath ? dirname19(pnpmPath) : void 0, dockerPath ? dirname19(dockerPath) : void 0]);
|
|
16199
16300
|
return {
|
|
16200
16301
|
answers: {
|
|
16201
16302
|
name: f.shipName?.trim() || env.hostname,
|
|
@@ -16209,8 +16310,8 @@ function flagWizardAnswers(f, env) {
|
|
|
16209
16310
|
}
|
|
16210
16311
|
|
|
16211
16312
|
// src/release-run.ts
|
|
16212
|
-
import { readFileSync as
|
|
16213
|
-
import { dirname as
|
|
16313
|
+
import { readFileSync as readFileSync31, writeFileSync as writeFileSync27, existsSync as existsSync34, copyFileSync, mkdirSync as mkdirSync24, mkdtempSync as mkdtempSync3, rmSync as rmSync17 } from "fs";
|
|
16314
|
+
import { dirname as dirname20, join as join38 } from "path";
|
|
16214
16315
|
import { tmpdir as tmpdir5 } from "os";
|
|
16215
16316
|
|
|
16216
16317
|
// src/release.ts
|
|
@@ -16429,7 +16530,7 @@ function decideRelease(cwd, tickets, contract, opts = {}) {
|
|
|
16429
16530
|
const block = opts.guard === false ? null : checkGuards(cwd, base);
|
|
16430
16531
|
const lastTag = latestReleaseTag(cwd, pattern, ref);
|
|
16431
16532
|
const lastReleased = lastTag ? tagCommit(cwd, lastTag) : null;
|
|
16432
|
-
const head =
|
|
16533
|
+
const head = resolve3(cwd, ref) ?? "";
|
|
16433
16534
|
const merges = block ? [] : planMerge(cwd, tickets, contract, lastReleased, opts.branchFor ?? ((t) => branchForIssue(cwd, t.issue_id)), opts.ref ?? base, opts.blockerInfo, ref);
|
|
16434
16535
|
const unreleasedCommits = lastReleased ? countCommits(cwd, `${lastReleased}..${ref}`) : 0;
|
|
16435
16536
|
const unreleasedWork = lastReleased ? (gitOk(cwd, ["log", "--format=%s", `${lastReleased}..${ref}`]) ?? "").split("\n").filter((l) => l && !RELEASE_COMMIT_SUBJECT.test(l)).length : 0;
|
|
@@ -16609,8 +16710,8 @@ function movedPast(r, t, branch, pushedAs) {
|
|
|
16609
16710
|
if (!r.columns.verifiedSha) return null;
|
|
16610
16711
|
const verified = columnValue(t, r.contract.columns.verifiedSha);
|
|
16611
16712
|
if (!verified) return null;
|
|
16612
|
-
const head =
|
|
16613
|
-
const remoteHead =
|
|
16713
|
+
const head = resolve3(r.cwd, branch);
|
|
16714
|
+
const remoteHead = resolve3(r.cwd, `${r.repo.branch.remote}/${pushedAs}`);
|
|
16614
16715
|
if (remoteHead && !isAncestor(r.cwd, remoteHead, verified) && remoteHead !== head) {
|
|
16615
16716
|
return { ticket: t, verified, head: remoteHead, branch: pushedAs };
|
|
16616
16717
|
}
|
|
@@ -16633,7 +16734,7 @@ async function runReview(r, verified, reviewing) {
|
|
|
16633
16734
|
out.requeues.push(moved);
|
|
16634
16735
|
continue;
|
|
16635
16736
|
}
|
|
16636
|
-
const head =
|
|
16737
|
+
const head = resolve3(r.cwd, branch);
|
|
16637
16738
|
if (!head) continue;
|
|
16638
16739
|
if (r.dryRun) {
|
|
16639
16740
|
r.emit.emit(`would push ${branch} to ${remote}/${pushedAs} for review`, { ticket: ticket.issue_id });
|
|
@@ -16951,13 +17052,13 @@ async function bump(o, size) {
|
|
|
16951
17052
|
}
|
|
16952
17053
|
return printed;
|
|
16953
17054
|
}
|
|
16954
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
17055
|
+
const files = o.repo.release.versionFiles.map((f) => join38(o.cwd, f));
|
|
16955
17056
|
const first = files[0];
|
|
16956
|
-
if (!
|
|
17057
|
+
if (!existsSync34(first)) {
|
|
16957
17058
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
16958
17059
|
return null;
|
|
16959
17060
|
}
|
|
16960
|
-
const current = JSON.parse(
|
|
17061
|
+
const current = JSON.parse(readFileSync31(first, "utf8")).version;
|
|
16961
17062
|
if (!current) {
|
|
16962
17063
|
o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
|
|
16963
17064
|
return null;
|
|
@@ -16970,27 +17071,27 @@ async function bump(o, size) {
|
|
|
16970
17071
|
return version;
|
|
16971
17072
|
}
|
|
16972
17073
|
for (const f of files) {
|
|
16973
|
-
const src =
|
|
17074
|
+
const src = readFileSync31(f, "utf8");
|
|
16974
17075
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
16975
17076
|
if (out === src) {
|
|
16976
17077
|
o.emit.error(`no version field in ${f}`);
|
|
16977
17078
|
return null;
|
|
16978
17079
|
}
|
|
16979
|
-
|
|
17080
|
+
writeFileSync27(f, out);
|
|
16980
17081
|
}
|
|
16981
17082
|
return version;
|
|
16982
17083
|
}
|
|
16983
17084
|
function writeChangelog(o, version, entries) {
|
|
16984
17085
|
const file = o.repo.release.changelog;
|
|
16985
17086
|
if (!file || entries.length === 0) return;
|
|
16986
|
-
const path =
|
|
17087
|
+
const path = join38(o.cwd, file);
|
|
16987
17088
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
16988
17089
|
if (o.dryRun) {
|
|
16989
17090
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
16990
17091
|
return;
|
|
16991
17092
|
}
|
|
16992
|
-
const existing =
|
|
16993
|
-
|
|
17093
|
+
const existing = existsSync34(path) ? readFileSync31(path, "utf8") : "# Changelog\n";
|
|
17094
|
+
writeFileSync27(path, insertChangelogSection(existing, section));
|
|
16994
17095
|
}
|
|
16995
17096
|
async function confirm(o, expected) {
|
|
16996
17097
|
if (!o.repo.hooks.released) return void 0;
|
|
@@ -17083,8 +17184,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
|
|
|
17083
17184
|
function openReleaseCheckout(o) {
|
|
17084
17185
|
const { remote, base } = o.repo.branch;
|
|
17085
17186
|
const branch = `crew/release-${Date.now()}`;
|
|
17086
|
-
const root = mkdtempSync3(
|
|
17087
|
-
const dir =
|
|
17187
|
+
const root = mkdtempSync3(join38(tmpdir5(), "crew-release-"));
|
|
17188
|
+
const dir = join38(root, "checkout");
|
|
17088
17189
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
17089
17190
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
17090
17191
|
try {
|
|
@@ -17094,10 +17195,10 @@ function openReleaseCheckout(o) {
|
|
|
17094
17195
|
throw e;
|
|
17095
17196
|
}
|
|
17096
17197
|
for (const f of o.repo.worktrees.copy) {
|
|
17097
|
-
const src =
|
|
17098
|
-
if (!
|
|
17099
|
-
|
|
17100
|
-
copyFileSync(src,
|
|
17198
|
+
const src = join38(o.cwd, f);
|
|
17199
|
+
if (!existsSync34(src)) continue;
|
|
17200
|
+
mkdirSync24(dirname20(join38(dir, f)), { recursive: true });
|
|
17201
|
+
copyFileSync(src, join38(dir, f));
|
|
17101
17202
|
}
|
|
17102
17203
|
return { dir, branch, close };
|
|
17103
17204
|
}
|
|
@@ -17520,7 +17621,7 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
|
|
|
17520
17621
|
branches: merged.filter((c) => c.branch).map((c) => ({
|
|
17521
17622
|
key: c.ticket.issue_tag ?? c.ticket.issue_id,
|
|
17522
17623
|
branch: c.branch,
|
|
17523
|
-
tip:
|
|
17624
|
+
tip: resolve3(o.cwd, c.branch) ?? ""
|
|
17524
17625
|
}))
|
|
17525
17626
|
};
|
|
17526
17627
|
const hookFailure2 = {
|
|
@@ -17583,7 +17684,7 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
|
|
|
17583
17684
|
if (releaseBranch && !o.dryRun) {
|
|
17584
17685
|
const { remote, base } = o.repo.branch;
|
|
17585
17686
|
if (fetchRemote(o.cwd, remote) && remoteBranchExists(o.cwd, remote, base)) {
|
|
17586
|
-
const moved =
|
|
17687
|
+
const moved = resolve3(o.cwd, `${remote}/${base}`);
|
|
17587
17688
|
if (moved && !isAncestor(o.cwd, moved, "HEAD")) {
|
|
17588
17689
|
o.emit.warn(`${remote}/${base} moved during the release \u2014 discarding it; the tickets stay verified and the next cycle retries`);
|
|
17589
17690
|
return { merged, conflicts, unbuildable, version, deployed: false, decision, stopped: "base branch moved during release" };
|
|
@@ -17687,6 +17788,49 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
|
|
|
17687
17788
|
return { merged, conflicts, unbuildable, version, tag, deployed, integrated, confirmed, decision };
|
|
17688
17789
|
}
|
|
17689
17790
|
|
|
17791
|
+
// src/repo-map.ts
|
|
17792
|
+
var CHARS_PER_TOKEN = 4;
|
|
17793
|
+
var MAP_TIMEOUT_MS = 6e4;
|
|
17794
|
+
function budgetMap(text2, tokenBudget) {
|
|
17795
|
+
const body = text2.replace(/\s+$/, "");
|
|
17796
|
+
const limit = tokenBudget * CHARS_PER_TOKEN;
|
|
17797
|
+
if (body.length <= limit) return body;
|
|
17798
|
+
const lines2 = body.split("\n");
|
|
17799
|
+
const noteFor = (kept2) => `\u2026 ${lines2.length - kept2} of ${lines2.length} lines cut to fit ship.mapTokenBudget (${tokenBudget} tokens)`;
|
|
17800
|
+
let kept = lines2.length;
|
|
17801
|
+
let size = body.length;
|
|
17802
|
+
while (kept > 0 && size + 1 + noteFor(kept).length > limit) {
|
|
17803
|
+
kept--;
|
|
17804
|
+
size -= lines2[kept].length + 1;
|
|
17805
|
+
}
|
|
17806
|
+
return [...lines2.slice(0, kept), noteFor(kept)].join("\n");
|
|
17807
|
+
}
|
|
17808
|
+
function loadRepoMap(repo, tokenBudget, shellFallback) {
|
|
17809
|
+
const script = repo.config.hooks.map;
|
|
17810
|
+
if (!script) return Promise.resolve({ skipped: "" });
|
|
17811
|
+
return runScript(script, {
|
|
17812
|
+
cwd: repo.dir,
|
|
17813
|
+
shell: resolveShell(repo.config.shell ?? shellFallback),
|
|
17814
|
+
env: { CREW_REPO: repo.name },
|
|
17815
|
+
timeoutMs: MAP_TIMEOUT_MS
|
|
17816
|
+
}).then(
|
|
17817
|
+
(r) => {
|
|
17818
|
+
if (r.code !== 0) return { skipped: `the \`map\` hook for ${repo.name} exited ${r.code}, so the brief has no repository map` };
|
|
17819
|
+
if (!r.stdout.trim()) return { skipped: `the \`map\` hook for ${repo.name} printed nothing, so the brief has no repository map` };
|
|
17820
|
+
return { text: budgetMap(r.stdout, tokenBudget) };
|
|
17821
|
+
},
|
|
17822
|
+
(e) => ({ skipped: `the \`map\` hook for ${repo.name} could not run (${e instanceof Error ? e.message : String(e)}), so the brief has no repository map` })
|
|
17823
|
+
);
|
|
17824
|
+
}
|
|
17825
|
+
async function withRepoMaps(repos, tokenBudget, shellFallback) {
|
|
17826
|
+
return Promise.all(repos.map(async (r) => {
|
|
17827
|
+
if (!r.config.hooks.map) return r;
|
|
17828
|
+
const out = await loadRepoMap(r, tokenBudget, shellFallback);
|
|
17829
|
+
if ("skipped" in out) return out.skipped ? { ...r, mapSkipped: out.skipped } : r;
|
|
17830
|
+
return { ...r, map: out.text };
|
|
17831
|
+
}));
|
|
17832
|
+
}
|
|
17833
|
+
|
|
17690
17834
|
// src/failure-alert.ts
|
|
17691
17835
|
var FAILURE_ALERT_MARKER = calloutHeader("CAUTION", "Failure Alert");
|
|
17692
17836
|
function findOpenFailureAlert(tickets, headline) {
|
|
@@ -18076,7 +18220,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
|
|
|
18076
18220
|
}
|
|
18077
18221
|
|
|
18078
18222
|
// src/watch.ts
|
|
18079
|
-
import { watch as watchFile, existsSync as
|
|
18223
|
+
import { watch as watchFile, existsSync as existsSync35, statSync as statSync11, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
|
|
18080
18224
|
var E = "\x1B[";
|
|
18081
18225
|
var dim = (s) => `${E}2m${s}${E}0m`;
|
|
18082
18226
|
var bold = (s) => `${E}1m${s}${E}0m`;
|
|
@@ -18177,8 +18321,8 @@ function renderHeaders(states, now = Date.now()) {
|
|
|
18177
18321
|
return states.map((s) => renderHeader(s, now)).join("");
|
|
18178
18322
|
}
|
|
18179
18323
|
function readFrom(file, from) {
|
|
18180
|
-
if (!
|
|
18181
|
-
const size =
|
|
18324
|
+
if (!existsSync35(file)) return { events: [], offset: from };
|
|
18325
|
+
const size = statSync11(file).size;
|
|
18182
18326
|
if (size < from) return readFrom(file, 0);
|
|
18183
18327
|
if (size === from) return { events: [], offset: from };
|
|
18184
18328
|
const fd = openSync4(file, "r");
|
|
@@ -18420,8 +18564,8 @@ function nextRoles(decision, ran) {
|
|
|
18420
18564
|
}
|
|
18421
18565
|
|
|
18422
18566
|
// src/ship-name.ts
|
|
18423
|
-
import { existsSync as
|
|
18424
|
-
import { dirname as
|
|
18567
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync25, readFileSync as readFileSync32, renameSync as renameSync5, writeFileSync as writeFileSync28 } from "fs";
|
|
18568
|
+
import { dirname as dirname21 } from "path";
|
|
18425
18569
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
18426
18570
|
var ShipNameError = class extends Error {
|
|
18427
18571
|
};
|
|
@@ -18482,34 +18626,34 @@ async function syncRouteName(i) {
|
|
|
18482
18626
|
return { status: "ok", ...base, action, name: i.local };
|
|
18483
18627
|
}
|
|
18484
18628
|
function readSyncedName(resolvedFile) {
|
|
18485
|
-
if (!
|
|
18629
|
+
if (!existsSync36(resolvedFile)) return void 0;
|
|
18486
18630
|
try {
|
|
18487
|
-
const v = JSON.parse(
|
|
18631
|
+
const v = JSON.parse(readFileSync32(resolvedFile, "utf8")).shipName;
|
|
18488
18632
|
return typeof v === "string" ? v : void 0;
|
|
18489
18633
|
} catch {
|
|
18490
18634
|
return void 0;
|
|
18491
18635
|
}
|
|
18492
18636
|
}
|
|
18493
18637
|
function writeSyncedName(resolvedFile, name) {
|
|
18494
|
-
if (!
|
|
18638
|
+
if (!existsSync36(resolvedFile)) return;
|
|
18495
18639
|
let raw;
|
|
18496
18640
|
try {
|
|
18497
|
-
raw = JSON.parse(
|
|
18641
|
+
raw = JSON.parse(readFileSync32(resolvedFile, "utf8"));
|
|
18498
18642
|
} catch {
|
|
18499
18643
|
return;
|
|
18500
18644
|
}
|
|
18501
18645
|
if (raw.shipName === name) return;
|
|
18502
18646
|
raw.shipName = name;
|
|
18503
|
-
|
|
18647
|
+
mkdirSync25(dirname21(resolvedFile), { recursive: true });
|
|
18504
18648
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
18505
|
-
|
|
18649
|
+
writeFileSync28(tmp, `${JSON.stringify(raw, null, 2)}
|
|
18506
18650
|
`);
|
|
18507
18651
|
renameSync5(tmp, resolvedFile);
|
|
18508
18652
|
}
|
|
18509
18653
|
async function reconcileShipName(i) {
|
|
18510
18654
|
let local = i.ship.name;
|
|
18511
18655
|
try {
|
|
18512
|
-
local = shipNameOfYaml(
|
|
18656
|
+
local = shipNameOfYaml(readFileSync32(i.configFile, "utf8")) ?? local;
|
|
18513
18657
|
} catch {
|
|
18514
18658
|
}
|
|
18515
18659
|
const resolvedFile = resolvedPathFor(i.stateDir, i.route);
|
|
@@ -18533,8 +18677,8 @@ async function reconcileShipName(i) {
|
|
|
18533
18677
|
}
|
|
18534
18678
|
if (result.status !== "ok") return result;
|
|
18535
18679
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
18536
|
-
const text2 =
|
|
18537
|
-
|
|
18680
|
+
const text2 = readFileSync32(i.configFile, "utf8");
|
|
18681
|
+
writeFileSync28(i.configFile, setShipNameInYaml(text2, result.name));
|
|
18538
18682
|
i.ship.name = result.name;
|
|
18539
18683
|
if (result.action === "row-wins") {
|
|
18540
18684
|
i.emit?.warn(
|
|
@@ -19193,8 +19337,8 @@ function describeCapability(i) {
|
|
|
19193
19337
|
}
|
|
19194
19338
|
|
|
19195
19339
|
// src/agents-sync-run.ts
|
|
19196
|
-
import { existsSync as
|
|
19197
|
-
import { dirname as
|
|
19340
|
+
import { existsSync as existsSync37, mkdirSync as mkdirSync26, readFileSync as readFileSync33, writeFileSync as writeFileSync29 } from "fs";
|
|
19341
|
+
import { dirname as dirname22 } from "path";
|
|
19198
19342
|
async function runAgentsSync(target, o) {
|
|
19199
19343
|
const dryRun = o.dryRun ?? false;
|
|
19200
19344
|
const force = o.force ?? false;
|
|
@@ -19227,12 +19371,12 @@ async function runAgentsSync(target, o) {
|
|
|
19227
19371
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
19228
19372
|
if (!dryRun) {
|
|
19229
19373
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
19230
|
-
const raw =
|
|
19374
|
+
const raw = existsSync37(resolvedPath) ? JSON.parse(readFileSync33(resolvedPath, "utf8")) : {};
|
|
19231
19375
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
19232
19376
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
19233
19377
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
19234
|
-
|
|
19235
|
-
|
|
19378
|
+
mkdirSync26(dirname22(resolvedPath), { recursive: true });
|
|
19379
|
+
writeFileSync29(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
19236
19380
|
`);
|
|
19237
19381
|
if (target.resolved) {
|
|
19238
19382
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -19336,7 +19480,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
19336
19480
|
|
|
19337
19481
|
// src/repos-check.ts
|
|
19338
19482
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
19339
|
-
import { readFileSync as
|
|
19483
|
+
import { readFileSync as readFileSync34 } from "fs";
|
|
19340
19484
|
import { basename as basename11 } from "path";
|
|
19341
19485
|
var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
|
|
19342
19486
|
function checkHookSyntax(script, shell) {
|
|
@@ -19377,7 +19521,7 @@ function checkRepoSpec(dir, opts = {}) {
|
|
|
19377
19521
|
}
|
|
19378
19522
|
let parsed;
|
|
19379
19523
|
try {
|
|
19380
|
-
parsed = parseRepoConfig(
|
|
19524
|
+
parsed = parseRepoConfig(readFileSync34(file, "utf8"), file);
|
|
19381
19525
|
} catch (e) {
|
|
19382
19526
|
if (!(e instanceof RepoConfigError)) throw e;
|
|
19383
19527
|
return { ...empty, file, error: e.message, exitCode: 1 };
|
|
@@ -19656,9 +19800,9 @@ function existingRepoConfig(f) {
|
|
|
19656
19800
|
}
|
|
19657
19801
|
|
|
19658
19802
|
// src/repos-hooks-agent.ts
|
|
19659
|
-
import { spawn as spawn7, execFileSync as
|
|
19803
|
+
import { spawn as spawn7, execFileSync as execFileSync10 } from "child_process";
|
|
19660
19804
|
import { realpathSync as realpathSync2 } from "fs";
|
|
19661
|
-
import { isAbsolute as
|
|
19805
|
+
import { isAbsolute as isAbsolute3, relative } from "path";
|
|
19662
19806
|
var AgentSuggestError = class extends Error {
|
|
19663
19807
|
reason;
|
|
19664
19808
|
constructor(reason, message) {
|
|
@@ -19727,7 +19871,7 @@ function parseAgentStream(out, dir) {
|
|
|
19727
19871
|
if (ev?.type === "assistant" && Array.isArray(ev.message?.content)) {
|
|
19728
19872
|
for (const b of ev.message.content) {
|
|
19729
19873
|
const p = b?.type === "tool_use" && b.name === "Read" ? b.input?.file_path : void 0;
|
|
19730
|
-
if (typeof p === "string") files.add(
|
|
19874
|
+
if (typeof p === "string") files.add(isAbsolute3(p) ? relative(base, p) : p);
|
|
19731
19875
|
}
|
|
19732
19876
|
} else if (ev?.type === "result") {
|
|
19733
19877
|
text2 = typeof ev.result === "string" ? ev.result : "";
|
|
@@ -19759,7 +19903,7 @@ function classifyFailure(o) {
|
|
|
19759
19903
|
}
|
|
19760
19904
|
function porcelain(dir) {
|
|
19761
19905
|
try {
|
|
19762
|
-
return
|
|
19906
|
+
return execFileSync10("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
|
|
19763
19907
|
} catch {
|
|
19764
19908
|
return void 0;
|
|
19765
19909
|
}
|
|
@@ -19774,7 +19918,7 @@ async function runAgentSuggest(o) {
|
|
|
19774
19918
|
const before = porcelain(o.dir);
|
|
19775
19919
|
const timeout = AbortSignal.timeout(o.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
19776
19920
|
const signal = o.signal ? AbortSignal.any([o.signal, timeout]) : timeout;
|
|
19777
|
-
const run3 = await new Promise((
|
|
19921
|
+
const run3 = await new Promise((resolve8) => {
|
|
19778
19922
|
let out = "";
|
|
19779
19923
|
let err3 = "";
|
|
19780
19924
|
const child = spawn7(bin, args, { cwd: o.dir, env, detached: true, stdio: ["pipe", "pipe", "pipe"] });
|
|
@@ -19783,7 +19927,7 @@ async function runAgentSuggest(o) {
|
|
|
19783
19927
|
if (settled) return;
|
|
19784
19928
|
settled = true;
|
|
19785
19929
|
signal.removeEventListener("abort", onAbort);
|
|
19786
|
-
|
|
19930
|
+
resolve8(r);
|
|
19787
19931
|
};
|
|
19788
19932
|
const onAbort = () => {
|
|
19789
19933
|
try {
|
|
@@ -19828,7 +19972,7 @@ ${run3.err}` });
|
|
|
19828
19972
|
}
|
|
19829
19973
|
|
|
19830
19974
|
// src/sample-ticket.ts
|
|
19831
|
-
import { basename as basename12, dirname as
|
|
19975
|
+
import { basename as basename12, dirname as dirname23, join as join39 } from "path";
|
|
19832
19976
|
var SAMPLE_REPO = "crew-sample";
|
|
19833
19977
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
19834
19978
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -19851,8 +19995,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
19851
19995
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
19852
19996
|
`;
|
|
19853
19997
|
function sampleDir(existing, reposBase, workspace) {
|
|
19854
|
-
if (existing.length > 0) return
|
|
19855
|
-
return
|
|
19998
|
+
if (existing.length > 0) return join39(dirname23(existing[0]), SAMPLE_REPO);
|
|
19999
|
+
return join39(reposBase, workspace, SAMPLE_REPO);
|
|
19856
20000
|
}
|
|
19857
20001
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
19858
20002
|
function sampleDescription() {
|
|
@@ -19896,8 +20040,8 @@ function planSampleTicket(i) {
|
|
|
19896
20040
|
}
|
|
19897
20041
|
|
|
19898
20042
|
// src/worktree-sweep.ts
|
|
19899
|
-
import { existsSync as
|
|
19900
|
-
import { basename as basename13, dirname as
|
|
20043
|
+
import { existsSync as existsSync38, readFileSync as readFileSync35, readdirSync as readdirSync14 } from "fs";
|
|
20044
|
+
import { basename as basename13, dirname as dirname24, join as join40, resolve as resolve5 } from "path";
|
|
19901
20045
|
function planWorktreeSweep(target, tickets, contract) {
|
|
19902
20046
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
19903
20047
|
const verified = contract.statuses.verified;
|
|
@@ -19961,9 +20105,9 @@ function worktreeTicketNumbers(target) {
|
|
|
19961
20105
|
return [...nums];
|
|
19962
20106
|
}
|
|
19963
20107
|
function siblingWorktreeDirs(target) {
|
|
19964
|
-
const parent =
|
|
20108
|
+
const parent = dirname24(target.dir.replace(/[/\\]+$/, ""));
|
|
19965
20109
|
const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
|
|
19966
|
-
const registered = new Set(worktrees(target.dir).map((w) =>
|
|
20110
|
+
const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
|
|
19967
20111
|
let names;
|
|
19968
20112
|
try {
|
|
19969
20113
|
names = readdirSync14(parent);
|
|
@@ -19974,8 +20118,8 @@ function siblingWorktreeDirs(target) {
|
|
|
19974
20118
|
for (const name of names) {
|
|
19975
20119
|
if (!name.startsWith(stem)) continue;
|
|
19976
20120
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
19977
|
-
const path =
|
|
19978
|
-
if (m && !registered.has(
|
|
20121
|
+
const path = join40(parent, name);
|
|
20122
|
+
if (m && !registered.has(resolve5(path))) out.push({ path, number: m[1] });
|
|
19979
20123
|
}
|
|
19980
20124
|
return out;
|
|
19981
20125
|
}
|
|
@@ -20009,7 +20153,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20009
20153
|
let removed = 0;
|
|
20010
20154
|
let keptBranches = 0;
|
|
20011
20155
|
for (const a of actions) {
|
|
20012
|
-
if (!dryRun && base && !a.keepBranch &&
|
|
20156
|
+
if (!dryRun && base && !a.keepBranch && existsSync38(a.path) && isHandBackMerge(a.path, base)) {
|
|
20013
20157
|
if (gitOk(a.path, ["merge", "--abort"]) !== null) {
|
|
20014
20158
|
log.emit(
|
|
20015
20159
|
`abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
|
|
@@ -20017,7 +20161,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20017
20161
|
);
|
|
20018
20162
|
}
|
|
20019
20163
|
}
|
|
20020
|
-
if (!dryRun &&
|
|
20164
|
+
if (!dryRun && existsSync38(a.path)) {
|
|
20021
20165
|
let dirty = false;
|
|
20022
20166
|
try {
|
|
20023
20167
|
dirty = status(a.path).length > 0;
|
|
@@ -20028,7 +20172,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20028
20172
|
continue;
|
|
20029
20173
|
}
|
|
20030
20174
|
}
|
|
20031
|
-
if (!dryRun && a.requirePushed && a.branch &&
|
|
20175
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync38(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
20032
20176
|
refuse(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
|
|
20033
20177
|
continue;
|
|
20034
20178
|
}
|
|
@@ -20077,7 +20221,7 @@ function isHandBackMerge(path, base) {
|
|
|
20077
20221
|
if (!msgPath) return false;
|
|
20078
20222
|
let first;
|
|
20079
20223
|
try {
|
|
20080
|
-
first =
|
|
20224
|
+
first = readFileSync35(resolve5(path, msgPath), "utf8").split("\n")[0] ?? "";
|
|
20081
20225
|
} catch {
|
|
20082
20226
|
return false;
|
|
20083
20227
|
}
|
|
@@ -20182,16 +20326,16 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
20182
20326
|
}
|
|
20183
20327
|
|
|
20184
20328
|
// src/stream-sweep.ts
|
|
20185
|
-
import { existsSync as
|
|
20186
|
-
import { join as
|
|
20329
|
+
import { existsSync as existsSync39, readdirSync as readdirSync15, rmSync as rmSync18, statSync as statSync12 } from "fs";
|
|
20330
|
+
import { join as join41 } from "path";
|
|
20187
20331
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
20188
20332
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
20189
|
-
const dir =
|
|
20190
|
-
if (!
|
|
20333
|
+
const dir = join41(stateDir, "streams");
|
|
20334
|
+
if (!existsSync39(dir)) return [];
|
|
20191
20335
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
20192
|
-
return readdirSync15(dir).map((name) =>
|
|
20336
|
+
return readdirSync15(dir).map((name) => join41(dir, name)).filter((path) => {
|
|
20193
20337
|
try {
|
|
20194
|
-
return
|
|
20338
|
+
return statSync12(path).mtimeMs < cutoff;
|
|
20195
20339
|
} catch {
|
|
20196
20340
|
return false;
|
|
20197
20341
|
}
|
|
@@ -20215,11 +20359,11 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
20215
20359
|
return removed;
|
|
20216
20360
|
}
|
|
20217
20361
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
20218
|
-
if (!
|
|
20362
|
+
if (!existsSync39(stateDir)) return [];
|
|
20219
20363
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
20220
|
-
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) =>
|
|
20364
|
+
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join41(stateDir, name)).filter((path) => {
|
|
20221
20365
|
try {
|
|
20222
|
-
return
|
|
20366
|
+
return statSync12(path).mtimeMs < cutoff;
|
|
20223
20367
|
} catch {
|
|
20224
20368
|
return false;
|
|
20225
20369
|
}
|
|
@@ -20227,12 +20371,12 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
20227
20371
|
}
|
|
20228
20372
|
|
|
20229
20373
|
// src/cli.ts
|
|
20230
|
-
import { realpathSync as realpathSync3, readFileSync as
|
|
20374
|
+
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
20375
|
import { createInterface } from "readline/promises";
|
|
20232
20376
|
|
|
20233
20377
|
// src/periscope-cmd.ts
|
|
20234
|
-
import { existsSync as
|
|
20235
|
-
import { join as
|
|
20378
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync27, readFileSync as readFileSync36, rmSync as rmSync19, writeFileSync as writeFileSync30 } from "fs";
|
|
20379
|
+
import { join as join42 } from "path";
|
|
20236
20380
|
var PeriscopeError = class extends Error {
|
|
20237
20381
|
};
|
|
20238
20382
|
var LivePeriscopeError = class extends PeriscopeError {
|
|
@@ -20242,22 +20386,22 @@ var LivePeriscopeError = class extends PeriscopeError {
|
|
|
20242
20386
|
this.data = { refused: "live_periscope_exists", ...live };
|
|
20243
20387
|
}
|
|
20244
20388
|
};
|
|
20245
|
-
var priorDir = (stateDir) =>
|
|
20246
|
-
var priorPath = (stateDir, ticketRecordId) =>
|
|
20389
|
+
var priorDir = (stateDir) => join42(stateDir, "periscope-priors");
|
|
20390
|
+
var priorPath = (stateDir, ticketRecordId) => join42(priorDir(stateDir), `${ticketRecordId}.json`);
|
|
20247
20391
|
function removePeriscopePrior(stateDir, ticketRecordId) {
|
|
20248
20392
|
rmSync19(priorPath(stateDir, ticketRecordId), { force: true });
|
|
20249
20393
|
}
|
|
20250
20394
|
function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
20251
20395
|
try {
|
|
20252
|
-
|
|
20253
|
-
|
|
20396
|
+
mkdirSync27(priorDir(stateDir), { recursive: true });
|
|
20397
|
+
writeFileSync30(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
|
|
20254
20398
|
} catch {
|
|
20255
20399
|
}
|
|
20256
20400
|
}
|
|
20257
20401
|
function readPeriscopePrior(stateDir, ticketRecordId) {
|
|
20258
20402
|
try {
|
|
20259
20403
|
const p = priorPath(stateDir, ticketRecordId);
|
|
20260
|
-
return
|
|
20404
|
+
return existsSync40(p) ? JSON.parse(readFileSync36(p, "utf8")) : void 0;
|
|
20261
20405
|
} catch {
|
|
20262
20406
|
return void 0;
|
|
20263
20407
|
}
|
|
@@ -20419,7 +20563,12 @@ ${tail(iso.output)}`);
|
|
|
20419
20563
|
const handoff = await d.runHook("handoff", wt.path, env);
|
|
20420
20564
|
if (handoff && handoff.code !== 0) return abandon(`handoff failed (exit ${handoff.code})
|
|
20421
20565
|
${tail(handoff.output)}`);
|
|
20422
|
-
|
|
20566
|
+
try {
|
|
20567
|
+
d.prepareDropFile(wt.path);
|
|
20568
|
+
} catch (e) {
|
|
20569
|
+
return abandon(`preparing the drop file failed: ${e.message}`);
|
|
20570
|
+
}
|
|
20571
|
+
const preview = await d.runHook("preview", wt.path, { ...env, CREW_PREVIEW_HOST: d.host, ...pointedAtEnv(wt.path) });
|
|
20423
20572
|
if (!preview || preview.code !== 0) {
|
|
20424
20573
|
return abandon(`preview exited ${preview?.code ?? "without running"}
|
|
20425
20574
|
${tail(preview?.output ?? "")}`);
|
|
@@ -20634,7 +20783,8 @@ async function runPeriscopeResume(d, number) {
|
|
|
20634
20783
|
${tail2(iso.output)}`);
|
|
20635
20784
|
if (iso) Object.assign(env, parseEnvLines(iso.output));
|
|
20636
20785
|
}
|
|
20637
|
-
|
|
20786
|
+
d.prepareDropFile?.(row.worktree);
|
|
20787
|
+
const preview = await d.runHook("preview", row.worktree, { ...env, CREW_PREVIEW_HOST: row.host || "localhost", ...pointedAtEnv(row.worktree) });
|
|
20638
20788
|
if (!preview || preview.code !== 0) {
|
|
20639
20789
|
throw new PeriscopeError(`preview exited ${preview?.code ?? "without running"}
|
|
20640
20790
|
${tail2(preview?.output ?? "")}`);
|
|
@@ -20654,14 +20804,19 @@ ${tail2(preview.output)}`);
|
|
|
20654
20804
|
}
|
|
20655
20805
|
|
|
20656
20806
|
// src/periscope-idle.ts
|
|
20657
|
-
import { execFileSync as
|
|
20658
|
-
import { statSync as
|
|
20659
|
-
import { join as
|
|
20807
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
20808
|
+
import { statSync as statSync13 } from "fs";
|
|
20809
|
+
import { join as join43 } from "path";
|
|
20660
20810
|
var MAX_SCANNED_FILES = 2e4;
|
|
20661
20811
|
function newestWorktreeMtime(root) {
|
|
20812
|
+
const a = newestTrackedMtime(root);
|
|
20813
|
+
const b = dropFileMtime(root);
|
|
20814
|
+
return a === null ? b : b === null ? a : Math.max(a, b);
|
|
20815
|
+
}
|
|
20816
|
+
function newestTrackedMtime(root) {
|
|
20662
20817
|
let listing;
|
|
20663
20818
|
try {
|
|
20664
|
-
listing =
|
|
20819
|
+
listing = execFileSync11("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
20665
20820
|
cwd: root,
|
|
20666
20821
|
encoding: "utf8",
|
|
20667
20822
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -20676,7 +20831,7 @@ function newestWorktreeMtime(root) {
|
|
|
20676
20831
|
if (!rel) continue;
|
|
20677
20832
|
if (++scanned > MAX_SCANNED_FILES) break;
|
|
20678
20833
|
try {
|
|
20679
|
-
const m =
|
|
20834
|
+
const m = statSync13(join43(root, rel)).mtimeMs;
|
|
20680
20835
|
if (newest === null || m > newest) newest = m;
|
|
20681
20836
|
} catch {
|
|
20682
20837
|
}
|
|
@@ -20751,21 +20906,21 @@ async function runPeriscopeIdlePass(d) {
|
|
|
20751
20906
|
import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
20752
20907
|
|
|
20753
20908
|
// src/ssh-keys.ts
|
|
20754
|
-
import { execFileSync as
|
|
20755
|
-
import { existsSync as
|
|
20756
|
-
import { dirname as
|
|
20909
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
20910
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync28, readFileSync as readFileSync37, chmodSync as chmodSync3 } from "fs";
|
|
20911
|
+
import { dirname as dirname25, join as join44 } from "path";
|
|
20757
20912
|
var SshKeyError = class extends Error {
|
|
20758
20913
|
};
|
|
20759
20914
|
function shipSshKeyPathFor(stateDir) {
|
|
20760
|
-
return
|
|
20915
|
+
return join44(stateDir, "ssh", "id_ed25519");
|
|
20761
20916
|
}
|
|
20762
20917
|
function ensureShipSshKeypair(stateDir) {
|
|
20763
20918
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
20764
20919
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
20765
|
-
if (!
|
|
20766
|
-
|
|
20920
|
+
if (!existsSync41(privateKeyPath)) {
|
|
20921
|
+
mkdirSync28(dirname25(privateKeyPath), { recursive: true });
|
|
20767
20922
|
try {
|
|
20768
|
-
|
|
20923
|
+
execFileSync12("ssh-keygen", [
|
|
20769
20924
|
"-t",
|
|
20770
20925
|
"ed25519",
|
|
20771
20926
|
"-N",
|
|
@@ -20783,15 +20938,15 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
20783
20938
|
}
|
|
20784
20939
|
chmodSync3(privateKeyPath, 384);
|
|
20785
20940
|
}
|
|
20786
|
-
if (!
|
|
20941
|
+
if (!existsSync41(publicKeyPath)) {
|
|
20787
20942
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
20788
20943
|
}
|
|
20789
|
-
const publicKey =
|
|
20944
|
+
const publicKey = readFileSync37(publicKeyPath, "utf8").trim();
|
|
20790
20945
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
20791
20946
|
}
|
|
20792
20947
|
function sshKeygenAvailable() {
|
|
20793
20948
|
try {
|
|
20794
|
-
|
|
20949
|
+
execFileSync12("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
|
|
20795
20950
|
return true;
|
|
20796
20951
|
} catch (e) {
|
|
20797
20952
|
return e.code !== "ENOENT";
|
|
@@ -20950,12 +21105,12 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
20950
21105
|
import { createServer as createHttpServer } from "http";
|
|
20951
21106
|
import { spawn as spawn8 } from "child_process";
|
|
20952
21107
|
import { randomBytes } from "crypto";
|
|
20953
|
-
import { existsSync as
|
|
20954
|
-
import { join as
|
|
21108
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, writeFileSync as writeFileSync31, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
|
|
21109
|
+
import { join as join45 } from "path";
|
|
20955
21110
|
|
|
20956
21111
|
// src/passenger-mirror.ts
|
|
20957
|
-
import { mkdirSync as
|
|
20958
|
-
import { dirname as
|
|
21112
|
+
import { mkdirSync as mkdirSync29 } from "fs";
|
|
21113
|
+
import { dirname as dirname26 } from "path";
|
|
20959
21114
|
function mirrorRepoOf(spec) {
|
|
20960
21115
|
const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
|
|
20961
21116
|
if (fromOrigin) return fromOrigin;
|
|
@@ -20973,9 +21128,9 @@ async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCa
|
|
|
20973
21128
|
continue;
|
|
20974
21129
|
}
|
|
20975
21130
|
try {
|
|
20976
|
-
const parent =
|
|
20977
|
-
|
|
20978
|
-
|
|
21131
|
+
const parent = dirname26(spec.dir);
|
|
21132
|
+
mkdirSync29(parent, { recursive: true });
|
|
21133
|
+
mkdirSync29(spec.dir, { recursive: true });
|
|
20979
21134
|
const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
|
|
20980
21135
|
out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
|
|
20981
21136
|
} catch (e) {
|
|
@@ -21081,21 +21236,21 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
21081
21236
|
return child;
|
|
21082
21237
|
};
|
|
21083
21238
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
21084
|
-
return
|
|
21239
|
+
return join45(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
21085
21240
|
}
|
|
21086
21241
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
21087
21242
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
21088
|
-
if (!
|
|
21243
|
+
if (!existsSync42(path)) return void 0;
|
|
21089
21244
|
try {
|
|
21090
|
-
return JSON.parse(
|
|
21245
|
+
return JSON.parse(readFileSync38(path, "utf8"));
|
|
21091
21246
|
} catch {
|
|
21092
21247
|
return void 0;
|
|
21093
21248
|
}
|
|
21094
21249
|
}
|
|
21095
21250
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
21096
21251
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
21097
|
-
|
|
21098
|
-
|
|
21252
|
+
mkdirSync30(join45(stateDir, "passengers"), { recursive: true });
|
|
21253
|
+
writeFileSync31(path, `${JSON.stringify(state, null, 2)}
|
|
21099
21254
|
`);
|
|
21100
21255
|
}
|
|
21101
21256
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -21115,8 +21270,8 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
21115
21270
|
}
|
|
21116
21271
|
}
|
|
21117
21272
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
21118
|
-
const dir =
|
|
21119
|
-
if (!
|
|
21273
|
+
const dir = join45(stateDir, "passengers");
|
|
21274
|
+
if (!existsSync42(dir)) return [];
|
|
21120
21275
|
const suffix = "-sync.json";
|
|
21121
21276
|
return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
21122
21277
|
}
|
|
@@ -21204,14 +21359,14 @@ function describeCause(err3) {
|
|
|
21204
21359
|
}
|
|
21205
21360
|
|
|
21206
21361
|
// src/daemon.ts
|
|
21207
|
-
import { existsSync as
|
|
21208
|
-
import { join as
|
|
21362
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync31, readFileSync as readFileSync39, readdirSync as readdirSync17, rmSync as rmSync20, statSync as statSync14, writeFileSync as writeFileSync32 } from "fs";
|
|
21363
|
+
import { join as join46 } from "path";
|
|
21209
21364
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
21210
21365
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
21211
21366
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
21212
21367
|
var ALLOWANCE_PAUSE_MS = 5 * 60 * 1e3;
|
|
21213
21368
|
var RELOAD_WARN_MS = 10 * 60 * 1e3;
|
|
21214
|
-
var defaultSleep = (ms3) => new Promise((
|
|
21369
|
+
var defaultSleep = (ms3) => new Promise((resolve8) => setTimeout(resolve8, ms3));
|
|
21215
21370
|
async function runDaemonLoop(o) {
|
|
21216
21371
|
const floor = o.floorMs ?? DEFAULT_FLOOR_MS;
|
|
21217
21372
|
const ceiling = o.ceilingMs ?? DEFAULT_CEILING_MS;
|
|
@@ -21285,7 +21440,7 @@ async function runDaemonLoop(o) {
|
|
|
21285
21440
|
function latestMtime(path) {
|
|
21286
21441
|
let stat2;
|
|
21287
21442
|
try {
|
|
21288
|
-
stat2 =
|
|
21443
|
+
stat2 = statSync14(path);
|
|
21289
21444
|
} catch {
|
|
21290
21445
|
return 0;
|
|
21291
21446
|
}
|
|
@@ -21293,7 +21448,7 @@ function latestMtime(path) {
|
|
|
21293
21448
|
if (!stat2.isDirectory()) return 0;
|
|
21294
21449
|
let latest = stat2.mtimeMs;
|
|
21295
21450
|
for (const entry of readdirSync17(path, { withFileTypes: true })) {
|
|
21296
|
-
latest = Math.max(latest, latestMtime(
|
|
21451
|
+
latest = Math.max(latest, latestMtime(join46(path, entry.name)));
|
|
21297
21452
|
}
|
|
21298
21453
|
return latest;
|
|
21299
21454
|
}
|
|
@@ -21302,30 +21457,30 @@ function computeTreeSignature(paths) {
|
|
|
21302
21457
|
}
|
|
21303
21458
|
function makeStaleChecker(paths, opts = {}) {
|
|
21304
21459
|
const initial = computeTreeSignature(paths);
|
|
21305
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
21460
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync43(opts.reloadSentinel);
|
|
21306
21461
|
}
|
|
21307
21462
|
function reloadSentinelFor(stateDir) {
|
|
21308
|
-
return
|
|
21463
|
+
return join46(stateDir, "reload-requested");
|
|
21309
21464
|
}
|
|
21310
21465
|
function readReloadRequestedAt(stateDir) {
|
|
21311
21466
|
try {
|
|
21312
|
-
return
|
|
21467
|
+
return statSync14(reloadSentinelFor(stateDir)).mtimeMs;
|
|
21313
21468
|
} catch {
|
|
21314
21469
|
return null;
|
|
21315
21470
|
}
|
|
21316
21471
|
}
|
|
21317
21472
|
function daemonStampFor(stateDir) {
|
|
21318
|
-
return
|
|
21473
|
+
return join46(stateDir, "daemon.started");
|
|
21319
21474
|
}
|
|
21320
21475
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
21321
|
-
|
|
21476
|
+
mkdirSync31(stateDir, { recursive: true });
|
|
21322
21477
|
rmSync20(reloadSentinelFor(stateDir), { force: true });
|
|
21323
|
-
|
|
21478
|
+
writeFileSync32(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
21324
21479
|
`);
|
|
21325
21480
|
}
|
|
21326
21481
|
function readDaemonStarted(stateDir) {
|
|
21327
21482
|
try {
|
|
21328
|
-
const v = JSON.parse(
|
|
21483
|
+
const v = JSON.parse(readFileSync39(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
21329
21484
|
return typeof v === "number" ? v : null;
|
|
21330
21485
|
} catch {
|
|
21331
21486
|
return null;
|
|
@@ -21334,7 +21489,7 @@ function readDaemonStarted(stateDir) {
|
|
|
21334
21489
|
function configChangedAfter(files, startedAt) {
|
|
21335
21490
|
for (const f of files) {
|
|
21336
21491
|
try {
|
|
21337
|
-
if (
|
|
21492
|
+
if (statSync14(f).mtimeMs > startedAt) return f;
|
|
21338
21493
|
} catch {
|
|
21339
21494
|
}
|
|
21340
21495
|
}
|
|
@@ -21497,8 +21652,8 @@ async function runOnePassUnguarded(o) {
|
|
|
21497
21652
|
}
|
|
21498
21653
|
let settle = () => {
|
|
21499
21654
|
};
|
|
21500
|
-
const settled = new Promise((
|
|
21501
|
-
settle =
|
|
21655
|
+
const settled = new Promise((resolve8) => {
|
|
21656
|
+
settle = resolve8;
|
|
21502
21657
|
});
|
|
21503
21658
|
const ready = (tookTicket2) => settle(tookTicket2);
|
|
21504
21659
|
const run3 = spawn11(role, decision, ctx, { ready }).then(() => {
|
|
@@ -21758,7 +21913,7 @@ async function runRoleAgentLocked(role, decision, ctx, hooks, held) {
|
|
|
21758
21913
|
ship.name,
|
|
21759
21914
|
(t) => {
|
|
21760
21915
|
const d = dirForRepo(route2, t.repo_id);
|
|
21761
|
-
return d ? originBranchSha(d, t,
|
|
21916
|
+
return d ? originBranchSha(d, t, resolve3) : null;
|
|
21762
21917
|
}
|
|
21763
21918
|
);
|
|
21764
21919
|
if (!result.ticket) {
|
|
@@ -21883,7 +22038,7 @@ async function buildEnvironment(route2, ship, ticket) {
|
|
|
21883
22038
|
});
|
|
21884
22039
|
const authorship = await tracker.authorshipProbe().catch(() => null);
|
|
21885
22040
|
const attachments = await tracker.attachmentsProbe().catch(() => null);
|
|
21886
|
-
return renderEnvironment({ route: route2, userAgent: ship.userAgent, repos, contract, sourceTicket: ticket, authorship, attachments, shipName: ship.name, ...sandboxEnv(ship) });
|
|
22041
|
+
return renderEnvironment({ route: route2, userAgent: ship.userAgent, repos: await withRepoMaps(repos, ship.mapTokenBudget, ship.shell), contract, sourceTicket: ticket, authorship, attachments, shipName: ship.name, ...sandboxEnv(ship) });
|
|
21887
22042
|
}
|
|
21888
22043
|
|
|
21889
22044
|
// src/control-api.ts
|
|
@@ -21891,18 +22046,18 @@ import { createHash as createHash10 } from "crypto";
|
|
|
21891
22046
|
import {
|
|
21892
22047
|
chmodSync as chmodSync4,
|
|
21893
22048
|
closeSync as closeSync5,
|
|
21894
|
-
existsSync as
|
|
22049
|
+
existsSync as existsSync44,
|
|
21895
22050
|
fstatSync,
|
|
21896
22051
|
openSync as openSync5,
|
|
21897
22052
|
readSync as readSync3,
|
|
21898
22053
|
readdirSync as readdirSync18,
|
|
21899
22054
|
rmSync as rmSync21,
|
|
21900
|
-
statSync as
|
|
22055
|
+
statSync as statSync15,
|
|
21901
22056
|
watch
|
|
21902
22057
|
} from "fs";
|
|
21903
22058
|
import { createConnection } from "net";
|
|
21904
22059
|
import { createServer } from "http";
|
|
21905
|
-
import { basename as basename14, dirname as
|
|
22060
|
+
import { basename as basename14, dirname as dirname27, join as join47 } from "path";
|
|
21906
22061
|
import { execFile as execFile3, spawn as spawn10 } from "child_process";
|
|
21907
22062
|
|
|
21908
22063
|
// src/periscope-control.ts
|
|
@@ -21953,7 +22108,7 @@ function lastJsonLine(stdout) {
|
|
|
21953
22108
|
}
|
|
21954
22109
|
return null;
|
|
21955
22110
|
}
|
|
21956
|
-
function
|
|
22111
|
+
function parseLine2(line) {
|
|
21957
22112
|
try {
|
|
21958
22113
|
const v = JSON.parse(line);
|
|
21959
22114
|
return v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
@@ -21981,7 +22136,7 @@ async function execute(h, args, streaming, after) {
|
|
|
21981
22136
|
body: null,
|
|
21982
22137
|
stream: async (write) => {
|
|
21983
22138
|
const r = await h.run(args, (line) => {
|
|
21984
|
-
const ev =
|
|
22139
|
+
const ev = parseLine2(line);
|
|
21985
22140
|
if (ev) write(ev);
|
|
21986
22141
|
});
|
|
21987
22142
|
const f = await finish(r);
|
|
@@ -22096,7 +22251,7 @@ async function handlePeriscopes(h, q2) {
|
|
|
22096
22251
|
], q2.wantsStream, h.mcp ? async (done) => ({ ...done, ...await h.mcp.onUp(picked, done) }) : void 0);
|
|
22097
22252
|
}
|
|
22098
22253
|
function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
22099
|
-
return new Promise((
|
|
22254
|
+
return new Promise((resolve8) => {
|
|
22100
22255
|
const child = spawn9(process.execPath, [process.argv[1] ?? "", ...args], { stdio: ["ignore", "pipe", "pipe"] });
|
|
22101
22256
|
let stdout = "";
|
|
22102
22257
|
let stderr = "";
|
|
@@ -22118,12 +22273,12 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
|
22118
22273
|
const t = setTimeout(() => child.kill("SIGTERM"), timeoutMs);
|
|
22119
22274
|
child.on("error", (e) => {
|
|
22120
22275
|
clearTimeout(t);
|
|
22121
|
-
|
|
22276
|
+
resolve8({ code: 127, stdout, stderr: `${stderr}${e.message}
|
|
22122
22277
|
` });
|
|
22123
22278
|
});
|
|
22124
22279
|
child.on("close", (code) => {
|
|
22125
22280
|
clearTimeout(t);
|
|
22126
|
-
|
|
22281
|
+
resolve8({ code: code ?? 1, stdout, stderr });
|
|
22127
22282
|
});
|
|
22128
22283
|
});
|
|
22129
22284
|
}
|
|
@@ -22131,7 +22286,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
|
22131
22286
|
// src/control-api.ts
|
|
22132
22287
|
var PAUSABLE = ["dev", "design", "qa"];
|
|
22133
22288
|
var CONTROL_SOCKET_NAME = "control.sock";
|
|
22134
|
-
var controlSocketFor = (stateDir) =>
|
|
22289
|
+
var controlSocketFor = (stateDir) => join47(stateDir, CONTROL_SOCKET_NAME);
|
|
22135
22290
|
var MAX_SOCKET_PATH = 100;
|
|
22136
22291
|
var MAX_TAIL_BYTES = 1024 * 1024;
|
|
22137
22292
|
var MAX_WAIT_SECONDS = 30;
|
|
@@ -22218,7 +22373,7 @@ function candidates(live, rotated) {
|
|
|
22218
22373
|
const out = [live];
|
|
22219
22374
|
if (!rotated) return out;
|
|
22220
22375
|
try {
|
|
22221
|
-
for (const n of readdirSync18(
|
|
22376
|
+
for (const n of readdirSync18(dirname27(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join47(dirname27(live), n));
|
|
22222
22377
|
} catch {
|
|
22223
22378
|
}
|
|
22224
22379
|
return out;
|
|
@@ -22241,7 +22396,7 @@ function readRange(path, from, max) {
|
|
|
22241
22396
|
}
|
|
22242
22397
|
}
|
|
22243
22398
|
function lastLines2(path, n) {
|
|
22244
|
-
const st =
|
|
22399
|
+
const st = statSync15(path);
|
|
22245
22400
|
const cursorAt = (off) => encodeCursor({ dev: st.dev, ino: st.ino, off });
|
|
22246
22401
|
if (n <= 0 || st.size === 0) return { lines: [], cursor: cursorAt(st.size), reset: false };
|
|
22247
22402
|
let start = st.size;
|
|
@@ -22262,7 +22417,7 @@ function lastLines2(path, n) {
|
|
|
22262
22417
|
}
|
|
22263
22418
|
function readTail(live, opts) {
|
|
22264
22419
|
const max = opts.maxBytes ?? MAX_TAIL_BYTES;
|
|
22265
|
-
if (!
|
|
22420
|
+
if (!existsSync44(live)) {
|
|
22266
22421
|
return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
|
|
22267
22422
|
}
|
|
22268
22423
|
const cur = opts.after ? decodeCursor(opts.after) : null;
|
|
@@ -22275,7 +22430,7 @@ function readTail(live, opts) {
|
|
|
22275
22430
|
} else {
|
|
22276
22431
|
const found = candidates(live, opts.rotated ?? false).find((p) => {
|
|
22277
22432
|
try {
|
|
22278
|
-
const s =
|
|
22433
|
+
const s = statSync15(p);
|
|
22279
22434
|
return s.ino === cur.ino && s.dev === cur.dev;
|
|
22280
22435
|
} catch {
|
|
22281
22436
|
return false;
|
|
@@ -22286,7 +22441,7 @@ function readTail(live, opts) {
|
|
|
22286
22441
|
off = cur.off;
|
|
22287
22442
|
} else reset = true;
|
|
22288
22443
|
}
|
|
22289
|
-
let size =
|
|
22444
|
+
let size = statSync15(path).size;
|
|
22290
22445
|
if (off > size) {
|
|
22291
22446
|
off = 0;
|
|
22292
22447
|
reset = true;
|
|
@@ -22294,12 +22449,12 @@ function readTail(live, opts) {
|
|
|
22294
22449
|
if (off >= size && path !== live) {
|
|
22295
22450
|
path = live;
|
|
22296
22451
|
off = 0;
|
|
22297
|
-
size =
|
|
22452
|
+
size = statSync15(path).size;
|
|
22298
22453
|
}
|
|
22299
22454
|
const { text: text2 } = readRange(path, off, max);
|
|
22300
22455
|
const cut = text2.lastIndexOf("\n");
|
|
22301
22456
|
const whole = cut < 0 ? "" : text2.slice(0, cut + 1);
|
|
22302
|
-
const st =
|
|
22457
|
+
const st = statSync15(path);
|
|
22303
22458
|
return {
|
|
22304
22459
|
lines: whole.split("\n").filter((l) => l.length),
|
|
22305
22460
|
cursor: encodeCursor({ dev: st.dev, ino: st.ino, off: off + Buffer.byteLength(whole) }),
|
|
@@ -22307,7 +22462,7 @@ function readTail(live, opts) {
|
|
|
22307
22462
|
};
|
|
22308
22463
|
}
|
|
22309
22464
|
function waitForChange(dir, ms3, signal) {
|
|
22310
|
-
return new Promise((
|
|
22465
|
+
return new Promise((resolve8) => {
|
|
22311
22466
|
let w;
|
|
22312
22467
|
const done = () => {
|
|
22313
22468
|
clearTimeout(t);
|
|
@@ -22317,7 +22472,7 @@ function waitForChange(dir, ms3, signal) {
|
|
|
22317
22472
|
w?.close();
|
|
22318
22473
|
} catch {
|
|
22319
22474
|
}
|
|
22320
|
-
|
|
22475
|
+
resolve8();
|
|
22321
22476
|
};
|
|
22322
22477
|
const t = setTimeout(done, ms3);
|
|
22323
22478
|
const poll = setInterval(done, 500);
|
|
@@ -22378,7 +22533,7 @@ async function tailReply(path, q2, rotated, abort) {
|
|
|
22378
22533
|
const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
|
|
22379
22534
|
let r = readTail(path, { after, tail: tail3, rotated });
|
|
22380
22535
|
while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
|
|
22381
|
-
await waitForChange(
|
|
22536
|
+
await waitForChange(dirname27(path), deadline - Date.now(), abort);
|
|
22382
22537
|
r = readTail(path, { after: r.cursor, rotated });
|
|
22383
22538
|
}
|
|
22384
22539
|
return reply2(200, r);
|
|
@@ -22418,8 +22573,8 @@ async function route(deps, method, url, req, abort) {
|
|
|
22418
22573
|
if (p.startsWith("/streams/") && get2) {
|
|
22419
22574
|
const run3 = decodeURIComponent(p.slice("/streams/".length));
|
|
22420
22575
|
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 (!
|
|
22576
|
+
const file = join47(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
|
|
22577
|
+
if (!existsSync44(file)) return err2(404, "no such run");
|
|
22423
22578
|
return tailReply(file, url.searchParams, false, abort);
|
|
22424
22579
|
}
|
|
22425
22580
|
if ((p === "/pause" || p === "/resume") && post3) {
|
|
@@ -22463,14 +22618,14 @@ function send(res, r) {
|
|
|
22463
22618
|
res.end(body);
|
|
22464
22619
|
}
|
|
22465
22620
|
async function socketIsStale(path) {
|
|
22466
|
-
if (!
|
|
22467
|
-
return new Promise((
|
|
22621
|
+
if (!existsSync44(path)) return false;
|
|
22622
|
+
return new Promise((resolve8) => {
|
|
22468
22623
|
const c = createConnection(path);
|
|
22469
22624
|
c.once("connect", () => {
|
|
22470
22625
|
c.destroy();
|
|
22471
|
-
|
|
22626
|
+
resolve8(false);
|
|
22472
22627
|
});
|
|
22473
|
-
c.once("error", () =>
|
|
22628
|
+
c.once("error", () => resolve8(true));
|
|
22474
22629
|
});
|
|
22475
22630
|
}
|
|
22476
22631
|
async function startControlServer(deps) {
|
|
@@ -22482,7 +22637,7 @@ async function startControlServer(deps) {
|
|
|
22482
22637
|
return null;
|
|
22483
22638
|
}
|
|
22484
22639
|
if (await socketIsStale(path)) rmSync21(path, { force: true });
|
|
22485
|
-
else if (
|
|
22640
|
+
else if (existsSync44(path)) {
|
|
22486
22641
|
log(`control API not started: ${path} is in use by another process`);
|
|
22487
22642
|
return null;
|
|
22488
22643
|
}
|
|
@@ -22512,10 +22667,10 @@ async function startControlServer(deps) {
|
|
|
22512
22667
|
}
|
|
22513
22668
|
})();
|
|
22514
22669
|
});
|
|
22515
|
-
return new Promise((
|
|
22670
|
+
return new Promise((resolve8) => {
|
|
22516
22671
|
server.once("error", (e) => {
|
|
22517
22672
|
log(`control API not started: ${e.message}`);
|
|
22518
|
-
|
|
22673
|
+
resolve8(null);
|
|
22519
22674
|
});
|
|
22520
22675
|
server.listen(path, () => {
|
|
22521
22676
|
try {
|
|
@@ -22523,7 +22678,7 @@ async function startControlServer(deps) {
|
|
|
22523
22678
|
} catch {
|
|
22524
22679
|
}
|
|
22525
22680
|
server.on("error", (e) => log(`control API error: ${e.message}`));
|
|
22526
|
-
|
|
22681
|
+
resolve8({
|
|
22527
22682
|
server,
|
|
22528
22683
|
path,
|
|
22529
22684
|
close: () => new Promise((done) => {
|
|
@@ -22554,13 +22709,13 @@ function schedulerHandler(control, run3 = runDetached) {
|
|
|
22554
22709
|
};
|
|
22555
22710
|
}
|
|
22556
22711
|
function crewJson(args, timeoutMs = 6e4) {
|
|
22557
|
-
return new Promise((
|
|
22712
|
+
return new Promise((resolve8) => {
|
|
22558
22713
|
execFile3(process.execPath, [process.argv[1] ?? "", ...args], { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 }, (_e, stdout) => {
|
|
22559
22714
|
try {
|
|
22560
22715
|
const v = JSON.parse(String(stdout));
|
|
22561
|
-
|
|
22716
|
+
resolve8(v && typeof v === "object" && !Array.isArray(v) ? v : null);
|
|
22562
22717
|
} catch {
|
|
22563
|
-
|
|
22718
|
+
resolve8(null);
|
|
22564
22719
|
}
|
|
22565
22720
|
});
|
|
22566
22721
|
});
|
|
@@ -22568,31 +22723,31 @@ function crewJson(args, timeoutMs = 6e4) {
|
|
|
22568
22723
|
|
|
22569
22724
|
// src/periscope-mcp.ts
|
|
22570
22725
|
import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
|
|
22571
|
-
import { basename as basename15, dirname as
|
|
22726
|
+
import { basename as basename15, dirname as dirname28, isAbsolute as isAbsolute4, join as join49, relative as relative2, resolve as resolve6, sep } from "path";
|
|
22572
22727
|
import { z } from "zod";
|
|
22573
22728
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
22574
22729
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
22575
22730
|
|
|
22576
22731
|
// src/periscope-endpoints.ts
|
|
22577
22732
|
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) =>
|
|
22733
|
+
import { chmodSync as chmodSync5, existsSync as existsSync45, mkdirSync as mkdirSync32, readdirSync as readdirSync19, readFileSync as readFileSync40, rmSync as rmSync22, writeFileSync as writeFileSync33 } from "fs";
|
|
22734
|
+
import { join as join48 } from "path";
|
|
22735
|
+
var secretDir = (stateDir) => join48(stateDir, "periscope-mcp");
|
|
22736
|
+
var secretFile = (stateDir, id) => join48(secretDir(stateDir), `${id}.json`);
|
|
22737
|
+
var endpointDir = (stateDir) => join48(stateDir, "periscope-endpoints");
|
|
22738
|
+
var endpointFile = (stateDir, id) => join48(endpointDir(stateDir), `${id}.json`);
|
|
22584
22739
|
function mintPeriscopeSecret(stateDir, ticketRecordId) {
|
|
22585
|
-
|
|
22740
|
+
mkdirSync32(secretDir(stateDir), { recursive: true, mode: 448 });
|
|
22586
22741
|
const secret = randomBytes3(32).toString("base64url");
|
|
22587
22742
|
const file = secretFile(stateDir, ticketRecordId);
|
|
22588
|
-
|
|
22743
|
+
writeFileSync33(file, `${JSON.stringify({ secret })}
|
|
22589
22744
|
`, { mode: 384 });
|
|
22590
22745
|
chmodSync5(file, 384);
|
|
22591
22746
|
return secret;
|
|
22592
22747
|
}
|
|
22593
22748
|
function readPeriscopeSecret(stateDir, ticketRecordId) {
|
|
22594
22749
|
try {
|
|
22595
|
-
const v = JSON.parse(
|
|
22750
|
+
const v = JSON.parse(readFileSync40(secretFile(stateDir, ticketRecordId), "utf8"));
|
|
22596
22751
|
return typeof v.secret === "string" && v.secret ? v.secret : void 0;
|
|
22597
22752
|
} catch {
|
|
22598
22753
|
return void 0;
|
|
@@ -22603,14 +22758,14 @@ function removePeriscopeSecret(stateDir, ticketRecordId) {
|
|
|
22603
22758
|
}
|
|
22604
22759
|
function readPeriscopeEndpoint(stateDir, ticketRecordId) {
|
|
22605
22760
|
try {
|
|
22606
|
-
return JSON.parse(
|
|
22761
|
+
return JSON.parse(readFileSync40(endpointFile(stateDir, ticketRecordId), "utf8"));
|
|
22607
22762
|
} catch {
|
|
22608
22763
|
return void 0;
|
|
22609
22764
|
}
|
|
22610
22765
|
}
|
|
22611
22766
|
function writePeriscopeEndpoint(stateDir, rec) {
|
|
22612
|
-
|
|
22613
|
-
|
|
22767
|
+
mkdirSync32(endpointDir(stateDir), { recursive: true });
|
|
22768
|
+
writeFileSync33(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
|
|
22614
22769
|
`);
|
|
22615
22770
|
}
|
|
22616
22771
|
function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
@@ -22618,7 +22773,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
|
22618
22773
|
}
|
|
22619
22774
|
function listPeriscopeEndpoints(stateDir) {
|
|
22620
22775
|
const dir = endpointDir(stateDir);
|
|
22621
|
-
if (!
|
|
22776
|
+
if (!existsSync45(dir)) return [];
|
|
22622
22777
|
const out = [];
|
|
22623
22778
|
for (const name of readdirSync19(dir)) {
|
|
22624
22779
|
if (!name.endsWith(".json")) continue;
|
|
@@ -22687,7 +22842,7 @@ var PathRefusal = class extends Error {
|
|
|
22687
22842
|
};
|
|
22688
22843
|
var isInside = (root, p) => {
|
|
22689
22844
|
const rel = relative2(root, p);
|
|
22690
|
-
return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !
|
|
22845
|
+
return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute4(rel);
|
|
22691
22846
|
};
|
|
22692
22847
|
async function realAncestor(p) {
|
|
22693
22848
|
let cur = p;
|
|
@@ -22706,10 +22861,10 @@ async function realAncestor(p) {
|
|
|
22706
22861
|
}
|
|
22707
22862
|
if (isLink) {
|
|
22708
22863
|
if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
|
|
22709
|
-
cur =
|
|
22864
|
+
cur = resolve6(dirname28(cur), await readlink(cur));
|
|
22710
22865
|
continue;
|
|
22711
22866
|
}
|
|
22712
|
-
const parent =
|
|
22867
|
+
const parent = dirname28(cur);
|
|
22713
22868
|
if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
|
|
22714
22869
|
tail3.push(basename15(cur));
|
|
22715
22870
|
cur = parent;
|
|
@@ -22719,15 +22874,15 @@ async function resolveInWorktree(root, requested) {
|
|
|
22719
22874
|
if (!requested || requested.includes("\0")) throw new PathRefusal("path is required");
|
|
22720
22875
|
const realRoot = await realpath(root);
|
|
22721
22876
|
let abs;
|
|
22722
|
-
if (
|
|
22723
|
-
abs =
|
|
22724
|
-
if (isInside(root, abs)) abs =
|
|
22877
|
+
if (isAbsolute4(requested)) {
|
|
22878
|
+
abs = resolve6(requested);
|
|
22879
|
+
if (isInside(root, abs)) abs = join49(realRoot, relative2(root, abs));
|
|
22725
22880
|
} else {
|
|
22726
|
-
abs =
|
|
22881
|
+
abs = resolve6(realRoot, requested);
|
|
22727
22882
|
}
|
|
22728
22883
|
if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
22729
22884
|
const { real, rest } = await realAncestor(abs);
|
|
22730
|
-
const final = rest ?
|
|
22885
|
+
const final = rest ? join49(real, rest) : real;
|
|
22731
22886
|
if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
22732
22887
|
if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
|
|
22733
22888
|
return final;
|
|
@@ -22762,7 +22917,7 @@ async function hookEnv(d, sc, name) {
|
|
|
22762
22917
|
if (iso && iso.code === 0) Object.assign(env, parseEnvLines(iso.output));
|
|
22763
22918
|
}
|
|
22764
22919
|
}
|
|
22765
|
-
if (name === "preview") env
|
|
22920
|
+
if (name === "preview") Object.assign(env, { CREW_PREVIEW_HOST: sc.host || "localhost" }, pointedAtEnv(sc.worktree));
|
|
22766
22921
|
return env;
|
|
22767
22922
|
}
|
|
22768
22923
|
function createPeriscopeMcpServer(d, sc) {
|
|
@@ -22795,7 +22950,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22795
22950
|
const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
|
|
22796
22951
|
const lines2 = await Promise.all(entries.map(async (e) => {
|
|
22797
22952
|
if (e.isDirectory()) return `[DIR] ${e.name}`;
|
|
22798
|
-
const size = (await lstat(
|
|
22953
|
+
const size = (await lstat(join49(abs, e.name))).size;
|
|
22799
22954
|
return `[FILE] ${e.name} (${size} bytes)`;
|
|
22800
22955
|
}));
|
|
22801
22956
|
return text(lines2.join("\n") || "(empty)");
|
|
@@ -22820,7 +22975,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22820
22975
|
if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
|
|
22821
22976
|
const abs = await inTree(path);
|
|
22822
22977
|
if (abs === await realpath(root)) return text("path must name a file", true);
|
|
22823
|
-
await mkdir(
|
|
22978
|
+
await mkdir(dirname28(abs), { recursive: true });
|
|
22824
22979
|
await inTree(path);
|
|
22825
22980
|
await writeFile(abs, content);
|
|
22826
22981
|
return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
|
|
@@ -22860,8 +23015,8 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22860
23015
|
inputSchema: { path: z.string() },
|
|
22861
23016
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
22862
23017
|
}, async ({ path }) => {
|
|
22863
|
-
const parent = await inTree(
|
|
22864
|
-
const abs =
|
|
23018
|
+
const parent = await inTree(dirname28(path) === "" ? "." : dirname28(path));
|
|
23019
|
+
const abs = join49(parent, basename15(path));
|
|
22865
23020
|
if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
|
|
22866
23021
|
const st = await lstat(abs).catch(() => void 0);
|
|
22867
23022
|
if (!st) return text(`${path} does not exist`, true);
|
|
@@ -22982,7 +23137,7 @@ function findPeriscope(sidecars, segment) {
|
|
|
22982
23137
|
}
|
|
22983
23138
|
|
|
22984
23139
|
// src/cli.ts
|
|
22985
|
-
var CREW_HOME = isCompiledBinary(import.meta.url) ?
|
|
23140
|
+
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname29(process.execPath) : resolve7(dirname29(fileURLToPath(import.meta.url)), "..");
|
|
22986
23141
|
var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
|
|
22987
23142
|
var CYCLE_SECONDS2 = 120;
|
|
22988
23143
|
var STALE_CYCLES = 3;
|
|
@@ -23350,7 +23505,7 @@ if (command === "provision") {
|
|
|
23350
23505
|
process.exit(outcome.code);
|
|
23351
23506
|
}
|
|
23352
23507
|
if (command === "pool-member") {
|
|
23353
|
-
const pool = poolConfigFrom(argv, process.env, () =>
|
|
23508
|
+
const pool = poolConfigFrom(argv, process.env, () => readFileSync41(0, "utf8"));
|
|
23354
23509
|
if (typeof pool === "string") {
|
|
23355
23510
|
process.stderr.write(`crew pool-member: ${pool}
|
|
23356
23511
|
`);
|
|
@@ -23578,7 +23733,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23578
23733
|
} else {
|
|
23579
23734
|
const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
|
|
23580
23735
|
const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
|
|
23581
|
-
if (configless && !
|
|
23736
|
+
if (configless && !existsSync46(findConfigFile(CREW_HOME))) {
|
|
23582
23737
|
cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
|
|
23583
23738
|
} else {
|
|
23584
23739
|
cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
|
|
@@ -23593,7 +23748,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23593
23748
|
throw e;
|
|
23594
23749
|
}
|
|
23595
23750
|
if (command === "credential") {
|
|
23596
|
-
const r = runCredential(positional[1],
|
|
23751
|
+
const r = runCredential(positional[1], readFileSync41(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
|
|
23597
23752
|
process.stdout.write(r.out);
|
|
23598
23753
|
process.stderr.write(r.err);
|
|
23599
23754
|
process.exit(r.code);
|
|
@@ -23605,9 +23760,9 @@ if (command === "passenger-sync-daemon") {
|
|
|
23605
23760
|
forges: cfg.ship.forges,
|
|
23606
23761
|
stateDir: cfg.ship.stateDir,
|
|
23607
23762
|
configFile: cfg.configFile,
|
|
23608
|
-
readFile: (p) =>
|
|
23609
|
-
writeFile: (p, t) =>
|
|
23610
|
-
stdin: () =>
|
|
23763
|
+
readFile: (p) => readFileSync41(p, "utf8"),
|
|
23764
|
+
writeFile: (p, t) => writeFileSync34(p, t),
|
|
23765
|
+
stdin: () => readFileSync41(0, "utf8"),
|
|
23611
23766
|
env: process.env
|
|
23612
23767
|
}
|
|
23613
23768
|
);
|
|
@@ -23900,8 +24055,8 @@ if (command === "passenger-sync-daemon") {
|
|
|
23900
24055
|
}
|
|
23901
24056
|
if (host === "linux" && hasSystemd) {
|
|
23902
24057
|
const label = labelFor(CREW_HOME, "run");
|
|
23903
|
-
const staleTimer =
|
|
23904
|
-
if (
|
|
24058
|
+
const staleTimer = join50(homedir11(), ".config", "systemd", "user", `${label}.timer`);
|
|
24059
|
+
if (existsSync46(staleTimer)) {
|
|
23905
24060
|
process.stdout.write(
|
|
23906
24061
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
23907
24062
|
`
|
|
@@ -23928,7 +24083,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23928
24083
|
const tracker = new Tracker(c, cfg.ship);
|
|
23929
24084
|
problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
|
|
23930
24085
|
for (const r of await resolvedRepos(c)) {
|
|
23931
|
-
if (!
|
|
24086
|
+
if (!existsSync46(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
23932
24087
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
23933
24088
|
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
23934
24089
|
}
|
|
@@ -23955,7 +24110,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23955
24110
|
}
|
|
23956
24111
|
async function doctorFix(o) {
|
|
23957
24112
|
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) =>
|
|
24113
|
+
const unitExists = (p) => existsSync46(p);
|
|
23959
24114
|
const crontab = o.host === "linux" ? spawnSync10("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
23960
24115
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
|
|
23961
24116
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -23972,7 +24127,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23972
24127
|
for (const a of plan.actions) {
|
|
23973
24128
|
if (a.kind === "enable") {
|
|
23974
24129
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
23975
|
-
|
|
24130
|
+
writeFileSync34(cfg.configFile, planEnable(readFileSync41(cfg.configFile, "utf8"), a.route));
|
|
23976
24131
|
enabledSound.add(a.route);
|
|
23977
24132
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
23978
24133
|
} else {
|
|
@@ -24049,7 +24204,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
24049
24204
|
return renderEnvironment({
|
|
24050
24205
|
route: c,
|
|
24051
24206
|
userAgent: cfg.ship.userAgent,
|
|
24052
|
-
repos: await resolvedRepos(c),
|
|
24207
|
+
repos: await withRepoMaps(await resolvedRepos(c), cfg.ship.mapTokenBudget, cfg.ship.shell),
|
|
24053
24208
|
contract,
|
|
24054
24209
|
sourceTicket: ticket,
|
|
24055
24210
|
authorship: await tracker.authorshipProbe().catch(() => null),
|
|
@@ -24713,7 +24868,7 @@ ${cu.tail.trimEnd()}
|
|
|
24713
24868
|
cfg.ship.name,
|
|
24714
24869
|
(t) => {
|
|
24715
24870
|
const d = dirForRepo(w.route, t.repo_id);
|
|
24716
|
-
return d ? originBranchSha(d, t,
|
|
24871
|
+
return d ? originBranchSha(d, t, resolve3) : null;
|
|
24717
24872
|
}
|
|
24718
24873
|
);
|
|
24719
24874
|
if (!result.ticket) {
|
|
@@ -24925,7 +25080,7 @@ ${cu.tail.trimEnd()}
|
|
|
24925
25080
|
cfg.ship.name,
|
|
24926
25081
|
(t) => {
|
|
24927
25082
|
const d = dirForRepo(route2, t.repo_id);
|
|
24928
|
-
return d ? originBranchSha(d, t,
|
|
25083
|
+
return d ? originBranchSha(d, t, resolve3) : null;
|
|
24929
25084
|
}
|
|
24930
25085
|
);
|
|
24931
25086
|
if (!result.ticket) {
|
|
@@ -25111,7 +25266,7 @@ ${cu.tail.trimEnd()}
|
|
|
25111
25266
|
process.exit(1);
|
|
25112
25267
|
}
|
|
25113
25268
|
requireArmed("run the daemon loop");
|
|
25114
|
-
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [
|
|
25269
|
+
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join50(CREW_HOME, "src"), join50(CREW_HOME, "dist"), join50(CREW_HOME, "bin")];
|
|
25115
25270
|
const configPaths = daemonConfigPaths(cfg);
|
|
25116
25271
|
watchPaths.push(...configPaths);
|
|
25117
25272
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -25416,10 +25571,10 @@ ${cu.tail.trimEnd()}
|
|
|
25416
25571
|
`);
|
|
25417
25572
|
if (!dryRun) {
|
|
25418
25573
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
25419
|
-
const raw =
|
|
25574
|
+
const raw = existsSync46(resolvedPath) ? JSON.parse(readFileSync41(resolvedPath, "utf8")) : {};
|
|
25420
25575
|
raw.agentSkills = result.agentSkills;
|
|
25421
|
-
|
|
25422
|
-
|
|
25576
|
+
mkdirSync33(dirOf2(resolvedPath), { recursive: true });
|
|
25577
|
+
writeFileSync34(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
25423
25578
|
`);
|
|
25424
25579
|
}
|
|
25425
25580
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -25442,7 +25597,7 @@ ${cu.tail.trimEnd()}
|
|
|
25442
25597
|
throw e;
|
|
25443
25598
|
}
|
|
25444
25599
|
const oldName = cfg.ship.name;
|
|
25445
|
-
|
|
25600
|
+
writeFileSync34(cfg.configFile, setShipNameInYaml(readFileSync41(cfg.configFile, "utf8"), newName));
|
|
25446
25601
|
cfg.ship.name = newName;
|
|
25447
25602
|
let renameMachineId;
|
|
25448
25603
|
try {
|
|
@@ -25489,14 +25644,14 @@ ${cu.tail.trimEnd()}
|
|
|
25489
25644
|
process.exit(2);
|
|
25490
25645
|
};
|
|
25491
25646
|
if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks" && sub !== "check") usage2();
|
|
25492
|
-
const configText =
|
|
25647
|
+
const configText = readFileSync41(cfg.configFile, "utf8");
|
|
25493
25648
|
if (sub === "check") {
|
|
25494
25649
|
try {
|
|
25495
25650
|
const routeArg = value("route") ?? positional[2];
|
|
25496
25651
|
const found = cfg.routes.find((r) => r.route === routeArg);
|
|
25497
25652
|
if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
|
|
25498
25653
|
const named2 = value("route") ? void 0 : positional[3];
|
|
25499
|
-
const dir = named2 ? repoDirOf(configText,
|
|
25654
|
+
const dir = named2 ? repoDirOf(configText, dirname29(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
|
|
25500
25655
|
const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
|
|
25501
25656
|
const o = target ? found.repoOverrides[target.name] : void 0;
|
|
25502
25657
|
runReposCheck(dir, {
|
|
@@ -25519,9 +25674,9 @@ ${cu.tail.trimEnd()}
|
|
|
25519
25674
|
if (sub === "hooks") {
|
|
25520
25675
|
try {
|
|
25521
25676
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
25522
|
-
const dir = repoDirOf(configText,
|
|
25523
|
-
if (!
|
|
25524
|
-
const files = { exists: (r) =>
|
|
25677
|
+
const dir = repoDirOf(configText, dirname29(cfg.configFile), a.route, a.name);
|
|
25678
|
+
if (!existsSync46(dir)) throw new ReposError(`${dir} does not exist on disk`);
|
|
25679
|
+
const files = { exists: (r) => existsSync46(resolvePath(dir, r)), list: (r) => {
|
|
25525
25680
|
try {
|
|
25526
25681
|
return readdirSync20(resolvePath(dir, r));
|
|
25527
25682
|
} catch {
|
|
@@ -25529,7 +25684,7 @@ ${cu.tail.trimEnd()}
|
|
|
25529
25684
|
}
|
|
25530
25685
|
}, read: (r) => {
|
|
25531
25686
|
try {
|
|
25532
|
-
return
|
|
25687
|
+
return readFileSync41(resolvePath(dir, r), "utf8");
|
|
25533
25688
|
} catch {
|
|
25534
25689
|
return void 0;
|
|
25535
25690
|
}
|
|
@@ -25571,19 +25726,19 @@ ${cu.tail.trimEnd()}
|
|
|
25571
25726
|
let cleared;
|
|
25572
25727
|
if (clear2) {
|
|
25573
25728
|
const plan = clearRouteHooks(configText, a.route, a.name);
|
|
25574
|
-
if (plan.removed.length > 0)
|
|
25729
|
+
if (plan.removed.length > 0) writeFileSync34(cfg.configFile, plan.text);
|
|
25575
25730
|
cleared = { file: cfg.configFile, removed: plan.removed };
|
|
25576
25731
|
}
|
|
25577
25732
|
if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
25578
25733
|
if (write === "route") {
|
|
25579
25734
|
const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
|
|
25580
|
-
|
|
25735
|
+
writeFileSync34(cfg.configFile, plan.text);
|
|
25581
25736
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
|
|
25582
25737
|
} else if (write === "repo") {
|
|
25583
25738
|
const have = existingRepoConfig(files);
|
|
25584
25739
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
25585
25740
|
const file = resolvePath(dir, ".crew.yaml");
|
|
25586
|
-
|
|
25741
|
+
writeFileSync34(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
|
|
25587
25742
|
written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
|
|
25588
25743
|
}
|
|
25589
25744
|
if (flag("json")) {
|
|
@@ -25639,7 +25794,7 @@ ${cu.tail.trimEnd()}
|
|
|
25639
25794
|
if (sub === "list" && !routeName) usage2();
|
|
25640
25795
|
if (sub === "add") {
|
|
25641
25796
|
try {
|
|
25642
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
25797
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync46(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
|
|
25643
25798
|
routeName = a.route;
|
|
25644
25799
|
addPath = a.path;
|
|
25645
25800
|
addName = a.name;
|
|
@@ -25669,7 +25824,7 @@ ${cu.tail.trimEnd()}
|
|
|
25669
25824
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
25670
25825
|
if (sub === "list" || sub === "add") {
|
|
25671
25826
|
const liveRoute = cfg.routes.find((r) => r.route === routeName);
|
|
25672
|
-
if (liveRoute?.resolved?.reposModelId &&
|
|
25827
|
+
if (liveRoute?.resolved?.reposModelId && existsSync46(resolvedFile) && !dryRun) {
|
|
25673
25828
|
try {
|
|
25674
25829
|
await hydrateApiKeys([liveRoute]);
|
|
25675
25830
|
persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
|
|
@@ -25678,16 +25833,16 @@ ${cu.tail.trimEnd()}
|
|
|
25678
25833
|
}
|
|
25679
25834
|
}
|
|
25680
25835
|
let resolvedRemotes;
|
|
25681
|
-
if (
|
|
25836
|
+
if (existsSync46(resolvedFile)) {
|
|
25682
25837
|
try {
|
|
25683
|
-
resolvedRemotes = JSON.parse(
|
|
25838
|
+
resolvedRemotes = JSON.parse(readFileSync41(resolvedFile, "utf8"));
|
|
25684
25839
|
} catch {
|
|
25685
25840
|
}
|
|
25686
25841
|
}
|
|
25687
25842
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
25688
25843
|
const text2 = configText;
|
|
25689
|
-
const base =
|
|
25690
|
-
const isCheckout = (d) =>
|
|
25844
|
+
const base = dirname29(cfg.configFile);
|
|
25845
|
+
const isCheckout = (d) => existsSync46(`${d}/.git`);
|
|
25691
25846
|
try {
|
|
25692
25847
|
if (sub === "list") {
|
|
25693
25848
|
if (flag("json")) {
|
|
@@ -25697,11 +25852,11 @@ ${cu.tail.trimEnd()}
|
|
|
25697
25852
|
base,
|
|
25698
25853
|
resolved: resolvedRemotes,
|
|
25699
25854
|
originOf,
|
|
25700
|
-
exists: (d) =>
|
|
25855
|
+
exists: (d) => existsSync46(d),
|
|
25701
25856
|
isCheckout,
|
|
25702
25857
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
25703
25858
|
try {
|
|
25704
|
-
return Object.keys(parseRepoConfig(
|
|
25859
|
+
return Object.keys(parseRepoConfig(readFileSync41(resolvePath(d, f), "utf8"), f).hooks).length > 0;
|
|
25705
25860
|
} catch {
|
|
25706
25861
|
return false;
|
|
25707
25862
|
}
|
|
@@ -25718,7 +25873,7 @@ ${cu.tail.trimEnd()}
|
|
|
25718
25873
|
for (const r of repos) {
|
|
25719
25874
|
const origin2 = originOf(r.dir);
|
|
25720
25875
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
25721
|
-
const note = !
|
|
25876
|
+
const note = !existsSync46(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
|
|
25722
25877
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
25723
25878
|
`);
|
|
25724
25879
|
}
|
|
@@ -25730,9 +25885,9 @@ ${cu.tail.trimEnd()}
|
|
|
25730
25885
|
` : `${human}
|
|
25731
25886
|
`);
|
|
25732
25887
|
const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
|
|
25733
|
-
if (
|
|
25888
|
+
if (existsSync46(plan2.target) && (existsSync46(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
|
|
25734
25889
|
throw new ReposError(
|
|
25735
|
-
|
|
25890
|
+
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
25891
|
);
|
|
25737
25892
|
}
|
|
25738
25893
|
if (dryRun) {
|
|
@@ -25750,14 +25905,14 @@ ${cu.tail.trimEnd()}
|
|
|
25750
25905
|
throw new ReposError(`${e.message}${authHint}
|
|
25751
25906
|
crew.yaml was not changed`);
|
|
25752
25907
|
}
|
|
25753
|
-
|
|
25908
|
+
writeFileSync34(cfg.configFile, plan2.text);
|
|
25754
25909
|
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
25910
|
`);
|
|
25756
25911
|
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
25912
|
break;
|
|
25758
25913
|
}
|
|
25759
25914
|
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
|
|
25760
|
-
const top =
|
|
25915
|
+
const top = existsSync46(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
25761
25916
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
25762
25917
|
const origin = originOf(top);
|
|
25763
25918
|
const plan = planRepoAdd({
|
|
@@ -25809,15 +25964,15 @@ ${cu.tail.trimEnd()}
|
|
|
25809
25964
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
25810
25965
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
25811
25966
|
`);
|
|
25812
|
-
if (
|
|
25813
|
-
const raw = JSON.parse(
|
|
25967
|
+
if (existsSync46(resolvedFile)) {
|
|
25968
|
+
const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
|
|
25814
25969
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
25815
25970
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
25816
|
-
|
|
25971
|
+
writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
25817
25972
|
`);
|
|
25818
25973
|
}
|
|
25819
25974
|
}
|
|
25820
|
-
|
|
25975
|
+
writeFileSync34(cfg.configFile, plan.text);
|
|
25821
25976
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
25822
25977
|
`);
|
|
25823
25978
|
} catch (e) {
|
|
@@ -26186,7 +26341,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26186
26341
|
process.exit(2);
|
|
26187
26342
|
}
|
|
26188
26343
|
if (found.shipNameAdopted && !dryRun) {
|
|
26189
|
-
|
|
26344
|
+
writeFileSync34(cfg.configFile, setShipNameInYaml(readFileSync41(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
26190
26345
|
cfg.ship.name = found.shipNameAdopted;
|
|
26191
26346
|
}
|
|
26192
26347
|
if (found.shipRow) {
|
|
@@ -26229,10 +26384,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26229
26384
|
}
|
|
26230
26385
|
}
|
|
26231
26386
|
let previous;
|
|
26232
|
-
const hadPreviousFile =
|
|
26387
|
+
const hadPreviousFile = existsSync46(resolvedPath);
|
|
26233
26388
|
if (hadPreviousFile) {
|
|
26234
26389
|
try {
|
|
26235
|
-
previous = JSON.parse(
|
|
26390
|
+
previous = JSON.parse(readFileSync41(resolvedPath, "utf8"));
|
|
26236
26391
|
} catch {
|
|
26237
26392
|
}
|
|
26238
26393
|
}
|
|
@@ -26362,10 +26517,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26362
26517
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
26363
26518
|
`);
|
|
26364
26519
|
} else {
|
|
26365
|
-
|
|
26520
|
+
mkdirSync33(dirname29(resolvedPath), { recursive: true });
|
|
26366
26521
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
26367
26522
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
26368
|
-
|
|
26523
|
+
writeFileSync34(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
26369
26524
|
`);
|
|
26370
26525
|
renameSync6(tmpPath, resolvedPath);
|
|
26371
26526
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
@@ -26411,8 +26566,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26411
26566
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
26412
26567
|
`);
|
|
26413
26568
|
} else {
|
|
26414
|
-
|
|
26415
|
-
|
|
26569
|
+
mkdirSync33(dirname29(apiKeyFile), { recursive: true });
|
|
26570
|
+
writeFileSync34(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
26416
26571
|
`, { mode: 384 });
|
|
26417
26572
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
26418
26573
|
`);
|
|
@@ -27139,7 +27294,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27139
27294
|
};
|
|
27140
27295
|
if (subP === "list") {
|
|
27141
27296
|
const entries = periscopeListEntries(
|
|
27142
|
-
(await Promise.all(routesP.map(gather))).flat(),
|
|
27297
|
+
(await Promise.all(routesP.map(gather))).flat().map((r) => ({ ...r, pointedAt: readPointedAt(r.worktree) })),
|
|
27143
27298
|
/* @__PURE__ */ new Date(),
|
|
27144
27299
|
(r) => pidsInWorktree(r.worktree, Number(r.number)).length > 0
|
|
27145
27300
|
);
|
|
@@ -27287,7 +27442,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27287
27442
|
const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
|
|
27288
27443
|
const resumed = await runPeriscopeResume({
|
|
27289
27444
|
find: async () => hit && { row: hit.row, tag: hit.tag },
|
|
27290
|
-
worktreeExists: (p) =>
|
|
27445
|
+
worktreeExists: (p) => existsSync46(p),
|
|
27446
|
+
prepareDropFile,
|
|
27291
27447
|
caps: periscopeCaps(cfg.ship),
|
|
27292
27448
|
runHook: async (name, cwd, env) => {
|
|
27293
27449
|
const script = hit?.repo.config.hooks[name];
|
|
@@ -27381,10 +27537,10 @@ ${where.join("\n")}`);
|
|
|
27381
27537
|
},
|
|
27382
27538
|
prepare: async (path) => {
|
|
27383
27539
|
for (const f of repoP.config.worktrees.copy) {
|
|
27384
|
-
const src =
|
|
27385
|
-
if (!
|
|
27386
|
-
|
|
27387
|
-
copyFileSync2(src,
|
|
27540
|
+
const src = join50(repoP.dir, f);
|
|
27541
|
+
if (!existsSync46(src)) continue;
|
|
27542
|
+
mkdirSync33(dirOf2(join50(path, f)), { recursive: true });
|
|
27543
|
+
copyFileSync2(src, join50(path, f));
|
|
27388
27544
|
}
|
|
27389
27545
|
const setup = repoP.config.hooks.setup;
|
|
27390
27546
|
return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
|
|
@@ -27396,6 +27552,7 @@ ${where.join("\n")}`);
|
|
|
27396
27552
|
return script ? runScript(script, { cwd, env, shell: shellP, timeoutMs: name === "preview" ? 18e4 : void 0 }) : null;
|
|
27397
27553
|
},
|
|
27398
27554
|
createRow: (fields) => trackerP.createPeriscope(fields),
|
|
27555
|
+
prepareDropFile,
|
|
27399
27556
|
recordLocal: (sc) => {
|
|
27400
27557
|
writePeriscopeSidecar(cfg.ship.stateDir, { ...sc, route: routeP.route });
|
|
27401
27558
|
if (flag("serve-mcp")) mintPeriscopeSecret(cfg.ship.stateDir, sc.ticketRecordId);
|
|
@@ -27679,7 +27836,7 @@ ${where.join("\n")}`);
|
|
|
27679
27836
|
};
|
|
27680
27837
|
const droppedRoutes = [];
|
|
27681
27838
|
try {
|
|
27682
|
-
const rawCfg = parseYaml2(
|
|
27839
|
+
const rawCfg = parseYaml2(readFileSync41(cfg.configFile, "utf8"));
|
|
27683
27840
|
for (const r of rawCfg?.routes ?? []) {
|
|
27684
27841
|
if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
|
|
27685
27842
|
try {
|
|
@@ -27732,8 +27889,8 @@ ${where.join("\n")}`);
|
|
|
27732
27889
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
27733
27890
|
break;
|
|
27734
27891
|
}
|
|
27735
|
-
|
|
27736
|
-
|
|
27892
|
+
mkdirSync33(cfg.ship.stateDir, { recursive: true });
|
|
27893
|
+
writeFileSync34(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
27737
27894
|
`);
|
|
27738
27895
|
process.stdout.write(
|
|
27739
27896
|
"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 +27955,7 @@ ${where.join("\n")}`);
|
|
|
27798
27955
|
}
|
|
27799
27956
|
case "log": {
|
|
27800
27957
|
try {
|
|
27801
|
-
const text2 =
|
|
27958
|
+
const text2 = readFileSync41(cfg.ship.logFile, "utf8").split("\n");
|
|
27802
27959
|
process.stdout.write(`${text2.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
27803
27960
|
`);
|
|
27804
27961
|
} catch {
|
|
@@ -27821,8 +27978,8 @@ ${where.join("\n")}`);
|
|
|
27821
27978
|
};
|
|
27822
27979
|
if (!route2.resolved) fail(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
|
|
27823
27980
|
const tracker = new Tracker(route2, cfg.ship);
|
|
27824
|
-
const configText =
|
|
27825
|
-
const cfgBase =
|
|
27981
|
+
const configText = readFileSync41(cfg.configFile, "utf8");
|
|
27982
|
+
const cfgBase = dirname29(cfg.configFile);
|
|
27826
27983
|
const configured2 = configuredRepos(configText, route2.route, cfgBase);
|
|
27827
27984
|
const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
|
|
27828
27985
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
|
|
@@ -27850,7 +28007,7 @@ ${where.join("\n")}`);
|
|
|
27850
28007
|
}
|
|
27851
28008
|
if (configured2.some((r) => r.dir === dir)) {
|
|
27852
28009
|
try {
|
|
27853
|
-
|
|
28010
|
+
writeFileSync34(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
|
|
27854
28011
|
} catch (e) {
|
|
27855
28012
|
if (e instanceof ReposError) fail(e.message);
|
|
27856
28013
|
throw e;
|
|
@@ -27858,29 +28015,29 @@ ${where.join("\n")}`);
|
|
|
27858
28015
|
}
|
|
27859
28016
|
if (row) {
|
|
27860
28017
|
await tracker.deleteRepoRow(row.id);
|
|
27861
|
-
if (
|
|
27862
|
-
const raw = JSON.parse(
|
|
28018
|
+
if (existsSync46(resolvedFile)) {
|
|
28019
|
+
const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
|
|
27863
28020
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
27864
28021
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
27865
|
-
|
|
28022
|
+
writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
27866
28023
|
`);
|
|
27867
28024
|
}
|
|
27868
28025
|
}
|
|
27869
|
-
if (
|
|
28026
|
+
if (existsSync46(join50(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync23(dir, { recursive: true, force: true });
|
|
27870
28027
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
27871
28028
|
break;
|
|
27872
28029
|
}
|
|
27873
28030
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
27874
|
-
const fresh = !
|
|
27875
|
-
if (
|
|
28031
|
+
const fresh = !existsSync46(join50(dir, ".git"));
|
|
28032
|
+
if (existsSync46(dir) && fresh && readdirSync20(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
27876
28033
|
if (dryRun) {
|
|
27877
28034
|
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
28035
|
break;
|
|
27879
28036
|
}
|
|
27880
28037
|
if (fresh) {
|
|
27881
|
-
|
|
27882
|
-
|
|
27883
|
-
|
|
28038
|
+
mkdirSync33(dir, { recursive: true });
|
|
28039
|
+
writeFileSync34(join50(dir, "README.md"), SAMPLE_README);
|
|
28040
|
+
writeFileSync34(join50(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
27884
28041
|
const g = (...a) => {
|
|
27885
28042
|
const r = spawnSync10("git", a, { cwd: dir, encoding: "utf8" });
|
|
27886
28043
|
if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -27898,17 +28055,17 @@ ${where.join("\n")}`);
|
|
|
27898
28055
|
spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
|
|
27899
28056
|
});
|
|
27900
28057
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
27901
|
-
if (
|
|
27902
|
-
const raw = JSON.parse(
|
|
28058
|
+
if (existsSync46(resolvedFile)) {
|
|
28059
|
+
const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
|
|
27903
28060
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
27904
28061
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
27905
|
-
|
|
28062
|
+
writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
27906
28063
|
`);
|
|
27907
28064
|
}
|
|
27908
28065
|
}
|
|
27909
28066
|
if (!configured2.some((r) => r.dir === dir)) {
|
|
27910
28067
|
try {
|
|
27911
|
-
|
|
28068
|
+
writeFileSync34(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
|
|
27912
28069
|
} catch (e) {
|
|
27913
28070
|
if (e instanceof ReposError) fail(e.message);
|
|
27914
28071
|
throw e;
|
|
@@ -28254,7 +28411,7 @@ watermark: ${state.watermark()}
|
|
|
28254
28411
|
}
|
|
28255
28412
|
for (const r of repos) {
|
|
28256
28413
|
const remote = r.config.branch.remote;
|
|
28257
|
-
if (
|
|
28414
|
+
if (existsSync46(r.dir)) {
|
|
28258
28415
|
pushAuth.push({
|
|
28259
28416
|
route: c.route,
|
|
28260
28417
|
repo: r.name,
|
|
@@ -28343,7 +28500,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
28343
28500
|
}
|
|
28344
28501
|
try {
|
|
28345
28502
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
28346
|
-
const evSize =
|
|
28503
|
+
const evSize = existsSync46(evFile) ? statSync16(evFile).size : 0;
|
|
28347
28504
|
process.stdout.write(
|
|
28348
28505
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
28349
28506
|
` : "events log: ok\n"
|
|
@@ -28354,7 +28511,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
28354
28511
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run", installContext());
|
|
28355
28512
|
const abandons = unitAbandonsAgents(unitPlan, (p) => {
|
|
28356
28513
|
try {
|
|
28357
|
-
return
|
|
28514
|
+
return readFileSync41(p, "utf8");
|
|
28358
28515
|
} catch {
|
|
28359
28516
|
return null;
|
|
28360
28517
|
}
|
|
@@ -28377,7 +28534,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
28377
28534
|
const changed2 = configChangedAfter(daemonConfigPaths(cfg), daemonStartedAt);
|
|
28378
28535
|
if (changed2) {
|
|
28379
28536
|
process.stdout.write(
|
|
28380
|
-
`daemon config: WARN ${changed2} changed at ${new Date(
|
|
28537
|
+
`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
28538
|
`
|
|
28382
28539
|
);
|
|
28383
28540
|
}
|