@tablation/crew 0.73.4 → 0.74.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 +467 -427
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -128,176 +128,10 @@ function formatStaleOwnLock(scope, holder) {
|
|
|
128
128
|
`;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
// src/
|
|
132
|
-
import {
|
|
133
|
-
import {
|
|
134
|
-
import {
|
|
135
|
-
import { join as join19 } from "path";
|
|
136
|
-
|
|
137
|
-
// src/agent.ts
|
|
138
|
-
import { spawn as spawn2, spawnSync as spawnSync5 } from "child_process";
|
|
139
|
-
import { readFileSync as readFileSync15, existsSync as existsSync18, statSync as statSync5, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
|
|
140
|
-
import { StringDecoder } from "string_decoder";
|
|
141
|
-
import { finished } from "stream/promises";
|
|
142
|
-
import { join as join18, dirname as dirname7, basename as basename7 } from "path";
|
|
143
|
-
import { createHash as createHash3 } from "crypto";
|
|
144
|
-
|
|
145
|
-
// src/config.ts
|
|
146
|
-
import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
147
|
-
import { resolve, dirname as dirname3, isAbsolute, join as join8, basename as basename3 } from "path";
|
|
148
|
-
import { homedir as homedir5, tmpdir } from "os";
|
|
149
|
-
import { parse as parse2 } from "yaml";
|
|
150
|
-
|
|
151
|
-
// src/events.ts
|
|
152
|
-
import {
|
|
153
|
-
appendFileSync,
|
|
154
|
-
mkdirSync,
|
|
155
|
-
existsSync as existsSync2,
|
|
156
|
-
statSync,
|
|
157
|
-
renameSync,
|
|
158
|
-
openSync,
|
|
159
|
-
closeSync,
|
|
160
|
-
rmSync
|
|
161
|
-
} from "fs";
|
|
162
|
-
import { join, dirname } from "path";
|
|
163
|
-
function render(e) {
|
|
164
|
-
const scope = e.role ? `${e.step}[${e.role}]` : e.step;
|
|
165
|
-
const ticket = e.ticket ? ` ${e.ticket}` : "";
|
|
166
|
-
const level = e.level === "info" ? "" : `${e.level.toUpperCase()}: `;
|
|
167
|
-
return `${e.at} ${scope}:${ticket} ${level}${e.message}`;
|
|
168
|
-
}
|
|
169
|
-
var DEFAULT_EVENTS_ROTATE_BYTES = 64 * 1024 * 1024;
|
|
170
|
-
var ROTATED_EVENTS_RE = /^events\.\d{8}T\d{6}Z(?:-\d+)?\.jsonl$/;
|
|
171
|
-
var ROTATE_LOCK_STALE_MS = 6e4;
|
|
172
|
-
function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
|
|
173
|
-
try {
|
|
174
|
-
if (!existsSync2(file) || statSync(file).size <= maxBytes) return void 0;
|
|
175
|
-
} catch {
|
|
176
|
-
return void 0;
|
|
177
|
-
}
|
|
178
|
-
const lock = `${file}.rotate.lock`;
|
|
179
|
-
try {
|
|
180
|
-
try {
|
|
181
|
-
closeSync(openSync(lock, "wx"));
|
|
182
|
-
} catch {
|
|
183
|
-
try {
|
|
184
|
-
if (Date.now() - statSync(lock).mtimeMs < ROTATE_LOCK_STALE_MS) return void 0;
|
|
185
|
-
rmSync(lock, { force: true });
|
|
186
|
-
closeSync(openSync(lock, "wx"));
|
|
187
|
-
} catch {
|
|
188
|
-
return void 0;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
try {
|
|
192
|
-
if (statSync(file).size <= maxBytes) return void 0;
|
|
193
|
-
const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
|
|
194
|
-
let dest = join(dirname(file), `events.${stamp}.jsonl`);
|
|
195
|
-
for (let n = 1; existsSync2(dest); n++) dest = join(dirname(file), `events.${stamp}-${n}.jsonl`);
|
|
196
|
-
renameSync(file, dest);
|
|
197
|
-
return dest;
|
|
198
|
-
} finally {
|
|
199
|
-
rmSync(lock, { force: true });
|
|
200
|
-
}
|
|
201
|
-
} catch {
|
|
202
|
-
return void 0;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
var Emitter = class _Emitter {
|
|
206
|
-
cycle;
|
|
207
|
-
opts;
|
|
208
|
-
now;
|
|
209
|
-
step = "poll";
|
|
210
|
-
role;
|
|
211
|
-
constructor(opts) {
|
|
212
|
-
this.opts = opts;
|
|
213
|
-
this.now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
214
|
-
this.cycle = opts.cycleId ?? this.now().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
215
|
-
for (const f of [opts.eventFile, opts.logFile]) {
|
|
216
|
-
if (!f) continue;
|
|
217
|
-
try {
|
|
218
|
-
mkdirSync(dirname(f), { recursive: true });
|
|
219
|
-
} catch {
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
/** Everything emitted after this belongs to `step`, until the next one. */
|
|
224
|
-
enter(step, role) {
|
|
225
|
-
this.step = step;
|
|
226
|
-
this.role = role;
|
|
227
|
-
}
|
|
228
|
-
emit(message, extra = {}) {
|
|
229
|
-
const e = {
|
|
230
|
-
at: this.now().toISOString(),
|
|
231
|
-
cycle: this.cycle,
|
|
232
|
-
route: this.opts.route,
|
|
233
|
-
step: extra.step ?? this.step,
|
|
234
|
-
level: extra.level ?? "info",
|
|
235
|
-
message,
|
|
236
|
-
...extra.role ?? this.role ? { role: extra.role ?? this.role } : {},
|
|
237
|
-
...extra.ticket ? { ticket: extra.ticket } : {},
|
|
238
|
-
...extra.data ? { data: extra.data } : {}
|
|
239
|
-
};
|
|
240
|
-
const line = render(e);
|
|
241
|
-
try {
|
|
242
|
-
if (this.opts.eventFile && this.opts.rotateBytes) rotateEventsFile(this.opts.eventFile, this.opts.rotateBytes, this.now());
|
|
243
|
-
} catch {
|
|
244
|
-
}
|
|
245
|
-
try {
|
|
246
|
-
if (this.opts.eventFile) appendFileSync(this.opts.eventFile, `${JSON.stringify(e)}
|
|
247
|
-
`);
|
|
248
|
-
} catch {
|
|
249
|
-
}
|
|
250
|
-
try {
|
|
251
|
-
if (this.opts.logFile) appendFileSync(this.opts.logFile, `${line}
|
|
252
|
-
`);
|
|
253
|
-
} catch {
|
|
254
|
-
}
|
|
255
|
-
try {
|
|
256
|
-
(this.opts.console ?? ((l) => process.stderr.write(`${l}
|
|
257
|
-
`)))(line);
|
|
258
|
-
} catch {
|
|
259
|
-
}
|
|
260
|
-
return e;
|
|
261
|
-
}
|
|
262
|
-
warn(message, extra = {}) {
|
|
263
|
-
return this.emit(message, { ...extra, level: "warn" });
|
|
264
|
-
}
|
|
265
|
-
error(message, extra = {}) {
|
|
266
|
-
return this.emit(message, { ...extra, level: "error" });
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* A view of this emitter labelling every event with a different route,
|
|
270
|
-
* sharing the same sinks and cycle id. For a fleet-wide run, the top-level
|
|
271
|
-
* Emitter is stamped with just one route (see cli.ts) — a phase that
|
|
272
|
-
* actually knows which route it's working (release, per-repo poll) uses
|
|
273
|
-
* this instead, so its events aren't all attributed to `routes[0]`.
|
|
274
|
-
*/
|
|
275
|
-
forRoute(route) {
|
|
276
|
-
return new _Emitter({ ...this.opts, route, cycleId: this.cycle });
|
|
277
|
-
}
|
|
278
|
-
/** Times a step and emits its outcome, so a view can show duration. */
|
|
279
|
-
async timed(step, message, fn, role) {
|
|
280
|
-
this.enter(step, role);
|
|
281
|
-
const started = this.now().getTime();
|
|
282
|
-
try {
|
|
283
|
-
const result = await fn();
|
|
284
|
-
this.emit(message, { data: { ms: this.now().getTime() - started, ok: true } });
|
|
285
|
-
return result;
|
|
286
|
-
} catch (err) {
|
|
287
|
-
this.error(`${message} \u2014 ${err.message}`, {
|
|
288
|
-
data: { ms: this.now().getTime() - started, ok: false }
|
|
289
|
-
});
|
|
290
|
-
throw err;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
};
|
|
294
|
-
var eventFileFor = (stateDir) => join(stateDir, "events.jsonl");
|
|
295
|
-
|
|
296
|
-
// src/pair-install.ts
|
|
297
|
-
import { existsSync as existsSync5, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
298
|
-
import { spawnSync as spawnSync3 } from "child_process";
|
|
299
|
-
import { homedir as homedir4 } from "os";
|
|
300
|
-
import { join as join6 } from "path";
|
|
131
|
+
// src/adapters/claude.ts
|
|
132
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
133
|
+
import { homedir as homedir3 } from "os";
|
|
134
|
+
import { join as join4 } from "path";
|
|
301
135
|
|
|
302
136
|
// src/stream.ts
|
|
303
137
|
function toolTarget(name, input) {
|
|
@@ -349,19 +183,19 @@ function extractResult(line) {
|
|
|
349
183
|
|
|
350
184
|
// src/claude-mcp.ts
|
|
351
185
|
import { spawnSync } from "child_process";
|
|
352
|
-
import { readFileSync as
|
|
186
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
353
187
|
import { homedir } from "os";
|
|
354
|
-
import { join as
|
|
188
|
+
import { join as join2 } from "path";
|
|
355
189
|
|
|
356
190
|
// src/mcp-registrations.ts
|
|
357
191
|
import { createHash } from "crypto";
|
|
358
|
-
import { mkdirSync
|
|
359
|
-
import { join
|
|
360
|
-
var fileFor = (stateDir) =>
|
|
192
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
193
|
+
import { join } from "path";
|
|
194
|
+
var fileFor = (stateDir) => join(stateDir, "mcp-registrations.json");
|
|
361
195
|
var keyFingerprint = (key) => createHash("sha256").update(key).digest("hex").slice(0, 8);
|
|
362
196
|
function readMcpRegistrations(stateDir) {
|
|
363
197
|
try {
|
|
364
|
-
const j = JSON.parse(
|
|
198
|
+
const j = JSON.parse(readFileSync(fileFor(stateDir), "utf8"));
|
|
365
199
|
return Array.isArray(j) ? j : [];
|
|
366
200
|
} catch {
|
|
367
201
|
return [];
|
|
@@ -369,7 +203,7 @@ function readMcpRegistrations(stateDir) {
|
|
|
369
203
|
}
|
|
370
204
|
function recordMcpRegistration(stateDir, r, now = /* @__PURE__ */ new Date()) {
|
|
371
205
|
const rest = readMcpRegistrations(stateDir).filter((x) => !(x.harness === r.harness && x.scope === r.scope && x.name === r.name));
|
|
372
|
-
|
|
206
|
+
mkdirSync(stateDir, { recursive: true });
|
|
373
207
|
writeFileSync(fileFor(stateDir), `${JSON.stringify([...rest, { ...r, at: now.toISOString() }], null, 2)}
|
|
374
208
|
`, { mode: 384 });
|
|
375
209
|
}
|
|
@@ -393,7 +227,7 @@ function runClaude(bin, args) {
|
|
|
393
227
|
}
|
|
394
228
|
function userScopeEntry(home) {
|
|
395
229
|
try {
|
|
396
|
-
const j = JSON.parse(
|
|
230
|
+
const j = JSON.parse(readFileSync2(join2(home, ".claude.json"), "utf8"));
|
|
397
231
|
return j.mcpServers?.[MCP_SERVER_NAME];
|
|
398
232
|
} catch {
|
|
399
233
|
return void 0;
|
|
@@ -477,8 +311,8 @@ function removeClaudeMcp(o) {
|
|
|
477
311
|
}
|
|
478
312
|
|
|
479
313
|
// src/launcher.ts
|
|
480
|
-
import { basename, join as
|
|
481
|
-
import { existsSync as
|
|
314
|
+
import { basename, join as join3 } from "path";
|
|
315
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync } from "fs";
|
|
482
316
|
import { homedir as homedir2 } from "os";
|
|
483
317
|
function isOriLauncher(spec) {
|
|
484
318
|
return !!spec.launcher && basename(spec.launcher) === "ori";
|
|
@@ -487,7 +321,7 @@ function isOmlxLauncher(spec) {
|
|
|
487
321
|
return !!spec.launcher && basename(spec.launcher) === "omlx";
|
|
488
322
|
}
|
|
489
323
|
function defaultOmlxBin(home = homedir2()) {
|
|
490
|
-
return
|
|
324
|
+
return join3(home, ".omlx", "bin", "omlx");
|
|
491
325
|
}
|
|
492
326
|
function launcherExecutable(spec, home) {
|
|
493
327
|
if (!spec.launcher) return void 0;
|
|
@@ -540,8 +374,8 @@ function omlxModelPresent(model, dirs) {
|
|
|
540
374
|
});
|
|
541
375
|
}
|
|
542
376
|
function readOmlxSettings(home = homedir2()) {
|
|
543
|
-
const p =
|
|
544
|
-
return
|
|
377
|
+
const p = join3(home, ".omlx", "settings.json");
|
|
378
|
+
return existsSync2(p) ? parseOmlxSettings(readFileSync3(p, "utf8")) : void 0;
|
|
545
379
|
}
|
|
546
380
|
|
|
547
381
|
// src/capability.ts
|
|
@@ -592,6 +426,24 @@ var claudePair = {
|
|
|
592
426
|
},
|
|
593
427
|
removePair: (o) => removeClaudeMcp({ bin: o.bin ?? "claude", dryRun: o.dryRun })
|
|
594
428
|
};
|
|
429
|
+
function subscriptionSettings() {
|
|
430
|
+
return JSON.stringify({ env: Object.fromEntries(BILLING_VARS_TO_UNSET.map((v) => [v, ""])) });
|
|
431
|
+
}
|
|
432
|
+
function describeClaudeAuth(spec, env = process.env, home = homedir3()) {
|
|
433
|
+
if (spec.auth === "subscription") return "subscription (settings override)";
|
|
434
|
+
let fromSettings;
|
|
435
|
+
try {
|
|
436
|
+
const parsed = JSON.parse(readFileSync4(join4(home, ".claude", "settings.json"), "utf8"));
|
|
437
|
+
const e = parsed.env ?? {};
|
|
438
|
+
const on = (k) => e[k] !== void 0 && e[k] !== "" && e[k] !== "0";
|
|
439
|
+
if (on("CLAUDE_CODE_USE_VERTEX")) fromSettings = "vertex from ~/.claude/settings.json";
|
|
440
|
+
else if (on("ANTHROPIC_API_KEY") || on("ANTHROPIC_AUTH_TOKEN")) fromSettings = "API key from ~/.claude/settings.json";
|
|
441
|
+
} catch {
|
|
442
|
+
}
|
|
443
|
+
if (fromSettings) return `inherit: ${fromSettings}`;
|
|
444
|
+
const shell = BILLING_VARS_TO_UNSET.find((v) => v.startsWith("ANTHROPIC_API") && env[v]);
|
|
445
|
+
return shell ? `inherit: ${shell} in env (unset at exec)` : "inherit: subscription login";
|
|
446
|
+
}
|
|
595
447
|
var claudeAdapter = {
|
|
596
448
|
harness: "claude",
|
|
597
449
|
displayName: "Claude Code",
|
|
@@ -646,6 +498,7 @@ var claudeAdapter = {
|
|
|
646
498
|
}
|
|
647
499
|
}
|
|
648
500
|
})] : [],
|
|
501
|
+
...spec.auth === "subscription" ? ["--settings", subscriptionSettings()] : [],
|
|
649
502
|
...spec.args
|
|
650
503
|
];
|
|
651
504
|
return wrapInvocation(spec, { model: o.model, args });
|
|
@@ -672,14 +525,185 @@ var claudeAdapter = {
|
|
|
672
525
|
ensureMcpResult: ensureClaudeMcpResult
|
|
673
526
|
};
|
|
674
527
|
|
|
528
|
+
// src/selftest.ts
|
|
529
|
+
import { spawn as spawn3 } from "child_process";
|
|
530
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync10, mkdtempSync, readFileSync as readFileSync17, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "fs";
|
|
531
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
532
|
+
import { join as join20 } from "path";
|
|
533
|
+
|
|
534
|
+
// src/agent.ts
|
|
535
|
+
import { spawn as spawn2, spawnSync as spawnSync5 } from "child_process";
|
|
536
|
+
import { readFileSync as readFileSync16, existsSync as existsSync18, statSync as statSync5, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
|
|
537
|
+
import { StringDecoder } from "string_decoder";
|
|
538
|
+
import { finished } from "stream/promises";
|
|
539
|
+
import { join as join19, dirname as dirname7, basename as basename7 } from "path";
|
|
540
|
+
import { createHash as createHash3 } from "crypto";
|
|
541
|
+
|
|
542
|
+
// src/config.ts
|
|
543
|
+
import { readFileSync as readFileSync8, existsSync as existsSync7 } from "fs";
|
|
544
|
+
import { resolve, dirname as dirname3, isAbsolute, join as join9, basename as basename3 } from "path";
|
|
545
|
+
import { homedir as homedir6, tmpdir } from "os";
|
|
546
|
+
import { parse as parse2 } from "yaml";
|
|
547
|
+
|
|
548
|
+
// src/events.ts
|
|
549
|
+
import {
|
|
550
|
+
appendFileSync,
|
|
551
|
+
mkdirSync as mkdirSync2,
|
|
552
|
+
existsSync as existsSync3,
|
|
553
|
+
statSync,
|
|
554
|
+
renameSync,
|
|
555
|
+
openSync,
|
|
556
|
+
closeSync,
|
|
557
|
+
rmSync
|
|
558
|
+
} from "fs";
|
|
559
|
+
import { join as join5, dirname } from "path";
|
|
560
|
+
function render(e) {
|
|
561
|
+
const scope = e.role ? `${e.step}[${e.role}]` : e.step;
|
|
562
|
+
const ticket = e.ticket ? ` ${e.ticket}` : "";
|
|
563
|
+
const level = e.level === "info" ? "" : `${e.level.toUpperCase()}: `;
|
|
564
|
+
return `${e.at} ${scope}:${ticket} ${level}${e.message}`;
|
|
565
|
+
}
|
|
566
|
+
var DEFAULT_EVENTS_ROTATE_BYTES = 64 * 1024 * 1024;
|
|
567
|
+
var ROTATED_EVENTS_RE = /^events\.\d{8}T\d{6}Z(?:-\d+)?\.jsonl$/;
|
|
568
|
+
var ROTATE_LOCK_STALE_MS = 6e4;
|
|
569
|
+
function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
|
|
570
|
+
try {
|
|
571
|
+
if (!existsSync3(file) || statSync(file).size <= maxBytes) return void 0;
|
|
572
|
+
} catch {
|
|
573
|
+
return void 0;
|
|
574
|
+
}
|
|
575
|
+
const lock = `${file}.rotate.lock`;
|
|
576
|
+
try {
|
|
577
|
+
try {
|
|
578
|
+
closeSync(openSync(lock, "wx"));
|
|
579
|
+
} catch {
|
|
580
|
+
try {
|
|
581
|
+
if (Date.now() - statSync(lock).mtimeMs < ROTATE_LOCK_STALE_MS) return void 0;
|
|
582
|
+
rmSync(lock, { force: true });
|
|
583
|
+
closeSync(openSync(lock, "wx"));
|
|
584
|
+
} catch {
|
|
585
|
+
return void 0;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
try {
|
|
589
|
+
if (statSync(file).size <= maxBytes) return void 0;
|
|
590
|
+
const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
|
|
591
|
+
let dest = join5(dirname(file), `events.${stamp}.jsonl`);
|
|
592
|
+
for (let n = 1; existsSync3(dest); n++) dest = join5(dirname(file), `events.${stamp}-${n}.jsonl`);
|
|
593
|
+
renameSync(file, dest);
|
|
594
|
+
return dest;
|
|
595
|
+
} finally {
|
|
596
|
+
rmSync(lock, { force: true });
|
|
597
|
+
}
|
|
598
|
+
} catch {
|
|
599
|
+
return void 0;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
var Emitter = class _Emitter {
|
|
603
|
+
cycle;
|
|
604
|
+
opts;
|
|
605
|
+
now;
|
|
606
|
+
step = "poll";
|
|
607
|
+
role;
|
|
608
|
+
constructor(opts) {
|
|
609
|
+
this.opts = opts;
|
|
610
|
+
this.now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
611
|
+
this.cycle = opts.cycleId ?? this.now().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
612
|
+
for (const f of [opts.eventFile, opts.logFile]) {
|
|
613
|
+
if (!f) continue;
|
|
614
|
+
try {
|
|
615
|
+
mkdirSync2(dirname(f), { recursive: true });
|
|
616
|
+
} catch {
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
/** Everything emitted after this belongs to `step`, until the next one. */
|
|
621
|
+
enter(step, role) {
|
|
622
|
+
this.step = step;
|
|
623
|
+
this.role = role;
|
|
624
|
+
}
|
|
625
|
+
emit(message, extra = {}) {
|
|
626
|
+
const e = {
|
|
627
|
+
at: this.now().toISOString(),
|
|
628
|
+
cycle: this.cycle,
|
|
629
|
+
route: this.opts.route,
|
|
630
|
+
step: extra.step ?? this.step,
|
|
631
|
+
level: extra.level ?? "info",
|
|
632
|
+
message,
|
|
633
|
+
...extra.role ?? this.role ? { role: extra.role ?? this.role } : {},
|
|
634
|
+
...extra.ticket ? { ticket: extra.ticket } : {},
|
|
635
|
+
...extra.data ? { data: extra.data } : {}
|
|
636
|
+
};
|
|
637
|
+
const line = render(e);
|
|
638
|
+
try {
|
|
639
|
+
if (this.opts.eventFile && this.opts.rotateBytes) rotateEventsFile(this.opts.eventFile, this.opts.rotateBytes, this.now());
|
|
640
|
+
} catch {
|
|
641
|
+
}
|
|
642
|
+
try {
|
|
643
|
+
if (this.opts.eventFile) appendFileSync(this.opts.eventFile, `${JSON.stringify(e)}
|
|
644
|
+
`);
|
|
645
|
+
} catch {
|
|
646
|
+
}
|
|
647
|
+
try {
|
|
648
|
+
if (this.opts.logFile) appendFileSync(this.opts.logFile, `${line}
|
|
649
|
+
`);
|
|
650
|
+
} catch {
|
|
651
|
+
}
|
|
652
|
+
try {
|
|
653
|
+
(this.opts.console ?? ((l) => process.stderr.write(`${l}
|
|
654
|
+
`)))(line);
|
|
655
|
+
} catch {
|
|
656
|
+
}
|
|
657
|
+
return e;
|
|
658
|
+
}
|
|
659
|
+
warn(message, extra = {}) {
|
|
660
|
+
return this.emit(message, { ...extra, level: "warn" });
|
|
661
|
+
}
|
|
662
|
+
error(message, extra = {}) {
|
|
663
|
+
return this.emit(message, { ...extra, level: "error" });
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* A view of this emitter labelling every event with a different route,
|
|
667
|
+
* sharing the same sinks and cycle id. For a fleet-wide run, the top-level
|
|
668
|
+
* Emitter is stamped with just one route (see cli.ts) — a phase that
|
|
669
|
+
* actually knows which route it's working (release, per-repo poll) uses
|
|
670
|
+
* this instead, so its events aren't all attributed to `routes[0]`.
|
|
671
|
+
*/
|
|
672
|
+
forRoute(route) {
|
|
673
|
+
return new _Emitter({ ...this.opts, route, cycleId: this.cycle });
|
|
674
|
+
}
|
|
675
|
+
/** Times a step and emits its outcome, so a view can show duration. */
|
|
676
|
+
async timed(step, message, fn, role) {
|
|
677
|
+
this.enter(step, role);
|
|
678
|
+
const started = this.now().getTime();
|
|
679
|
+
try {
|
|
680
|
+
const result = await fn();
|
|
681
|
+
this.emit(message, { data: { ms: this.now().getTime() - started, ok: true } });
|
|
682
|
+
return result;
|
|
683
|
+
} catch (err) {
|
|
684
|
+
this.error(`${message} \u2014 ${err.message}`, {
|
|
685
|
+
data: { ms: this.now().getTime() - started, ok: false }
|
|
686
|
+
});
|
|
687
|
+
throw err;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
var eventFileFor = (stateDir) => join5(stateDir, "events.jsonl");
|
|
692
|
+
|
|
693
|
+
// src/pair-install.ts
|
|
694
|
+
import { existsSync as existsSync5, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
695
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
696
|
+
import { homedir as homedir5 } from "os";
|
|
697
|
+
import { join as join7 } from "path";
|
|
698
|
+
|
|
675
699
|
// src/adapters/codex.ts
|
|
676
700
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
677
|
-
import { homedir as
|
|
678
|
-
import { join as
|
|
701
|
+
import { homedir as homedir4 } from "os";
|
|
702
|
+
import { join as join6 } from "path";
|
|
679
703
|
import { rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
680
704
|
|
|
681
705
|
// src/pair-files.ts
|
|
682
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as
|
|
706
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync2, rmSync as rmSync2 } from "fs";
|
|
683
707
|
import { dirname as dirname2 } from "path";
|
|
684
708
|
var PAIR_BLOCK_START = "<!-- crew:pair start -->";
|
|
685
709
|
var PAIR_BLOCK_END = "<!-- crew:pair end -->";
|
|
@@ -720,7 +744,7 @@ function removePairBlock(text) {
|
|
|
720
744
|
return text.replace(new RegExp(`\\n*${BLOCK_RE.source}`), "\n").replace(/^\n+/, "").replace(/\n{3,}/g, "\n\n");
|
|
721
745
|
}
|
|
722
746
|
function readIfExists(path) {
|
|
723
|
-
return existsSync4(path) ?
|
|
747
|
+
return existsSync4(path) ? readFileSync6(path, "utf8") : void 0;
|
|
724
748
|
}
|
|
725
749
|
function writeFileMkdir(path, text) {
|
|
726
750
|
mkdirSync3(dirname2(path), { recursive: true });
|
|
@@ -867,8 +891,8 @@ function codexTable(url) {
|
|
|
867
891
|
return [`[mcp_servers.${SERVER}]`, `url = ${tomlString(url)}`, `bearer_token_env_var = ${tomlString(PAIR_KEY_VAR)}`];
|
|
868
892
|
}
|
|
869
893
|
var codexPaths = (home) => ({
|
|
870
|
-
instructions:
|
|
871
|
-
config:
|
|
894
|
+
instructions: join6(home, ".codex", "AGENTS.md"),
|
|
895
|
+
config: join6(home, ".codex", "config.toml")
|
|
872
896
|
});
|
|
873
897
|
function codexMcpState(config, url) {
|
|
874
898
|
const lines = config.split("\n");
|
|
@@ -884,7 +908,7 @@ var codexPair = {
|
|
|
884
908
|
harness: "codex",
|
|
885
909
|
displayName: "Codex",
|
|
886
910
|
installPair(o) {
|
|
887
|
-
const p = codexPaths(o.home ??
|
|
911
|
+
const p = codexPaths(o.home ?? homedir4());
|
|
888
912
|
const config = readIfExists(p.config) ?? "";
|
|
889
913
|
const st = codexMcpState(config, o.url);
|
|
890
914
|
if (st.state === "different" && !o.replace) {
|
|
@@ -901,7 +925,7 @@ var codexPair = {
|
|
|
901
925
|
return `pair(codex): ${o.dryRun ? "(dry run) would install" : "installed"} the Pair block in ${p.instructions} and ${SERVER} in ${p.config} (key read from $${PAIR_KEY_VAR})`;
|
|
902
926
|
},
|
|
903
927
|
verifyPair(o) {
|
|
904
|
-
const p = codexPaths(o.home ??
|
|
928
|
+
const p = codexPaths(o.home ?? homedir4());
|
|
905
929
|
const b = blockVerdict(p.instructions, "codex");
|
|
906
930
|
if (!b.ok) return b;
|
|
907
931
|
const st = codexMcpState(readIfExists(p.config) ?? "", o.url);
|
|
@@ -910,7 +934,7 @@ var codexPair = {
|
|
|
910
934
|
return { ok: true, detail: `${p.instructions}, ${p.config}` };
|
|
911
935
|
},
|
|
912
936
|
removePair(o) {
|
|
913
|
-
const p = codexPaths(o.home ??
|
|
937
|
+
const p = codexPaths(o.home ?? homedir4());
|
|
914
938
|
const hadBlock = removeBlockFile(p.instructions, o.dryRun);
|
|
915
939
|
const config = readIfExists(p.config);
|
|
916
940
|
const st = codexMcpState(config ?? "", o.url);
|
|
@@ -996,11 +1020,11 @@ var codexAdapter = {
|
|
|
996
1020
|
extractTurnTokens: extractCodexTurnTokens,
|
|
997
1021
|
inspectMcp(o) {
|
|
998
1022
|
if (binMissing(o.bin)) return { state: "no-binary" };
|
|
999
|
-
const st = codexMcpState(readIfExists(codexPaths(o.home ??
|
|
1023
|
+
const st = codexMcpState(readIfExists(codexPaths(o.home ?? homedir4()).config) ?? "", o.url);
|
|
1000
1024
|
return st.state === "registered" ? { state: "registered", url: o.url } : st;
|
|
1001
1025
|
},
|
|
1002
1026
|
ensureMcp(o) {
|
|
1003
|
-
const p = codexPaths(o.home ??
|
|
1027
|
+
const p = codexPaths(o.home ?? homedir4());
|
|
1004
1028
|
const config = readIfExists(p.config) ?? "";
|
|
1005
1029
|
const st = codexMcpState(config, o.url);
|
|
1006
1030
|
if (st.state === "registered") return `mcp: ${SERVER} already registered with Codex \u2192 ${o.url}`;
|
|
@@ -1015,8 +1039,8 @@ var codexAdapter = {
|
|
|
1015
1039
|
|
|
1016
1040
|
// src/pair-install.ts
|
|
1017
1041
|
var geminiPaths = (home) => ({
|
|
1018
|
-
instructions:
|
|
1019
|
-
config:
|
|
1042
|
+
instructions: join7(home, ".gemini", "GEMINI.md"),
|
|
1043
|
+
config: join7(home, ".gemini", "settings.json")
|
|
1020
1044
|
});
|
|
1021
1045
|
function readGeminiSettings(path) {
|
|
1022
1046
|
const text = readIfExists(path);
|
|
@@ -1037,14 +1061,14 @@ function geminiMcpState(s, url) {
|
|
|
1037
1061
|
return { state: "registered" };
|
|
1038
1062
|
}
|
|
1039
1063
|
function inspectGeminiMcp(o) {
|
|
1040
|
-
const p = geminiPaths(o.home ??
|
|
1064
|
+
const p = geminiPaths(o.home ?? homedir5());
|
|
1041
1065
|
const settings = readGeminiSettings(p.config);
|
|
1042
1066
|
if (!settings) return { state: "different", detail: `${p.config} is not a JSON object` };
|
|
1043
1067
|
const st = geminiMcpState(settings, o.url);
|
|
1044
1068
|
return st.state === "registered" ? { state: "registered", url: o.url } : st;
|
|
1045
1069
|
}
|
|
1046
1070
|
function ensureGeminiMcp(o) {
|
|
1047
|
-
const p = geminiPaths(o.home ??
|
|
1071
|
+
const p = geminiPaths(o.home ?? homedir5());
|
|
1048
1072
|
const settings = readGeminiSettings(p.config);
|
|
1049
1073
|
if (!settings) return `gemini: ${p.config} is not a JSON object \u2014 fix it by hand; nothing was written`;
|
|
1050
1074
|
const st = geminiMcpState(settings, o.url);
|
|
@@ -1062,7 +1086,7 @@ var geminiPair = {
|
|
|
1062
1086
|
harness: "gemini",
|
|
1063
1087
|
displayName: "Gemini CLI",
|
|
1064
1088
|
installPair(o) {
|
|
1065
|
-
const p = geminiPaths(o.home ??
|
|
1089
|
+
const p = geminiPaths(o.home ?? homedir5());
|
|
1066
1090
|
const settings = readGeminiSettings(p.config);
|
|
1067
1091
|
if (!settings) return `pair(gemini): ${p.config} is not a JSON object \u2014 fix it by hand; nothing was written`;
|
|
1068
1092
|
const st = geminiMcpState(settings, o.url);
|
|
@@ -1081,7 +1105,7 @@ var geminiPair = {
|
|
|
1081
1105
|
return `pair(gemini): ${o.dryRun ? "(dry run) would install" : "installed"} the Pair block in ${p.instructions} and ${SERVER} in ${p.config} (key read from $${PAIR_KEY_VAR})`;
|
|
1082
1106
|
},
|
|
1083
1107
|
verifyPair(o) {
|
|
1084
|
-
const p = geminiPaths(o.home ??
|
|
1108
|
+
const p = geminiPaths(o.home ?? homedir5());
|
|
1085
1109
|
const b = blockVerdict(p.instructions, "gemini");
|
|
1086
1110
|
if (!b.ok) return b;
|
|
1087
1111
|
const settings = readGeminiSettings(p.config);
|
|
@@ -1092,7 +1116,7 @@ var geminiPair = {
|
|
|
1092
1116
|
return { ok: true, detail: `${p.instructions}, ${p.config}` };
|
|
1093
1117
|
},
|
|
1094
1118
|
removePair(o) {
|
|
1095
|
-
const p = geminiPaths(o.home ??
|
|
1119
|
+
const p = geminiPaths(o.home ?? homedir5());
|
|
1096
1120
|
const hadBlock = removeBlockFile(p.instructions, o.dryRun);
|
|
1097
1121
|
const settings = readGeminiSettings(p.config);
|
|
1098
1122
|
const st = settings ? geminiMcpState(settings, o.url) : { state: "missing" };
|
|
@@ -1113,10 +1137,10 @@ var geminiPair = {
|
|
|
1113
1137
|
};
|
|
1114
1138
|
var VSCODE_FLAVOURS = ["Code", "Code - Insiders"];
|
|
1115
1139
|
function vscodeUserDirs(o) {
|
|
1116
|
-
const home = o.home ??
|
|
1140
|
+
const home = o.home ?? homedir5();
|
|
1117
1141
|
const platform2 = o.platform ?? process.platform;
|
|
1118
|
-
const root = platform2 === "darwin" ?
|
|
1119
|
-
const all = VSCODE_FLAVOURS.map((f) =>
|
|
1142
|
+
const root = platform2 === "darwin" ? join7(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join7(home, "AppData", "Roaming") : join7(home, ".config");
|
|
1143
|
+
const all = VSCODE_FLAVOURS.map((f) => join7(root, f, "User"));
|
|
1120
1144
|
const present = all.filter((d) => existsSync5(d));
|
|
1121
1145
|
return present.length > 0 ? present : [all[0]];
|
|
1122
1146
|
}
|
|
@@ -1164,8 +1188,8 @@ var copilotPair = {
|
|
|
1164
1188
|
installPair(o) {
|
|
1165
1189
|
const lines = [];
|
|
1166
1190
|
for (const dir of vscodeUserDirs(o)) {
|
|
1167
|
-
const mcpPath =
|
|
1168
|
-
const instrPath =
|
|
1191
|
+
const mcpPath = join7(dir, "mcp.json");
|
|
1192
|
+
const instrPath = join7(dir, "prompts", "crew-pair.instructions.md");
|
|
1169
1193
|
const settings = readVscodeMcp(mcpPath);
|
|
1170
1194
|
if (!settings) {
|
|
1171
1195
|
lines.push(`pair(copilot): ${mcpPath} is not a plain JSON object (comments are not supported) \u2014 fix it by hand; nothing was written there`);
|
|
@@ -1193,8 +1217,8 @@ var copilotPair = {
|
|
|
1193
1217
|
},
|
|
1194
1218
|
verifyPair(o) {
|
|
1195
1219
|
const verdicts = vscodeUserDirs(o).map((dir) => {
|
|
1196
|
-
const instrPath =
|
|
1197
|
-
const mcpPath =
|
|
1220
|
+
const instrPath = join7(dir, "prompts", "crew-pair.instructions.md");
|
|
1221
|
+
const mcpPath = join7(dir, "mcp.json");
|
|
1198
1222
|
const b = blockVerdict(instrPath, "copilot");
|
|
1199
1223
|
if (!b.ok) return b;
|
|
1200
1224
|
const settings = readVscodeMcp(mcpPath);
|
|
@@ -1209,8 +1233,8 @@ var copilotPair = {
|
|
|
1209
1233
|
removePair(o) {
|
|
1210
1234
|
const lines = [];
|
|
1211
1235
|
for (const dir of vscodeUserDirs(o)) {
|
|
1212
|
-
const mcpPath =
|
|
1213
|
-
const hadBlock = removeCopilotInstructions(
|
|
1236
|
+
const mcpPath = join7(dir, "mcp.json");
|
|
1237
|
+
const hadBlock = removeCopilotInstructions(join7(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
|
|
1214
1238
|
const settings = readVscodeMcp(mcpPath);
|
|
1215
1239
|
const st = settings ? copilotMcpState(settings, o.url) : { state: "missing" };
|
|
1216
1240
|
const hadMcp = st.state === "registered";
|
|
@@ -1509,8 +1533,8 @@ function explain(ship, required) {
|
|
|
1509
1533
|
}
|
|
1510
1534
|
|
|
1511
1535
|
// src/repo-config.ts
|
|
1512
|
-
import { readFileSync as
|
|
1513
|
-
import { join as
|
|
1536
|
+
import { readFileSync as readFileSync7, existsSync as existsSync6 } from "fs";
|
|
1537
|
+
import { join as join8, basename as basename2 } from "path";
|
|
1514
1538
|
import { parse } from "yaml";
|
|
1515
1539
|
var SPEC_VERSION = 1;
|
|
1516
1540
|
var REPO_CONFIG_FILENAMES = [".crew.yaml", ".crew.yml", ".crew.json"];
|
|
@@ -1593,7 +1617,7 @@ var MODES = ["local", "integrate", "ci_manual", "ci_auto", "external"];
|
|
|
1593
1617
|
var PROVIDERS = ["github", "buildkite", "other", "none"];
|
|
1594
1618
|
function findRepoConfig(dir) {
|
|
1595
1619
|
for (const name of REPO_CONFIG_FILENAMES) {
|
|
1596
|
-
const p =
|
|
1620
|
+
const p = join8(dir, name);
|
|
1597
1621
|
if (existsSync6(p)) return p;
|
|
1598
1622
|
}
|
|
1599
1623
|
return null;
|
|
@@ -1832,7 +1856,7 @@ function parseRepoConfig(text, file) {
|
|
|
1832
1856
|
function loadRepoConfig(dir) {
|
|
1833
1857
|
const file = findRepoConfig(dir);
|
|
1834
1858
|
if (!file) return null;
|
|
1835
|
-
return parseRepoConfig(
|
|
1859
|
+
return parseRepoConfig(readFileSync7(file, "utf8"), file);
|
|
1836
1860
|
}
|
|
1837
1861
|
function hookLabel(cfg, hook2) {
|
|
1838
1862
|
return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
|
|
@@ -2281,16 +2305,16 @@ function mergeRouteRelease(route, override) {
|
|
|
2281
2305
|
};
|
|
2282
2306
|
}
|
|
2283
2307
|
function expand(p, base) {
|
|
2284
|
-
let out = p.startsWith("~") ? p.replace(/^~/,
|
|
2285
|
-
out = out.replace(/\$\{?HOME\}?/g,
|
|
2308
|
+
let out = p.startsWith("~") ? p.replace(/^~/, homedir6()) : p;
|
|
2309
|
+
out = out.replace(/\$\{?HOME\}?/g, homedir6());
|
|
2286
2310
|
return isAbsolute(out) ? out : resolve(base, out);
|
|
2287
2311
|
}
|
|
2288
2312
|
function resolvePromptsDir(promptSet, crewHome, base) {
|
|
2289
|
-
if (!promptSet) return
|
|
2313
|
+
if (!promptSet) return join9(crewHome, "prompts", "default");
|
|
2290
2314
|
if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
|
|
2291
2315
|
return expand(promptSet, base);
|
|
2292
2316
|
}
|
|
2293
|
-
return
|
|
2317
|
+
return join9(crewHome, "prompts", promptSet);
|
|
2294
2318
|
}
|
|
2295
2319
|
var Missing = class {
|
|
2296
2320
|
paths = [];
|
|
@@ -2314,7 +2338,7 @@ var Missing = class {
|
|
|
2314
2338
|
};
|
|
2315
2339
|
function crewVersion(crewHome) {
|
|
2316
2340
|
try {
|
|
2317
|
-
const pkg = JSON.parse(
|
|
2341
|
+
const pkg = JSON.parse(readFileSync8(join9(crewHome, "package.json"), "utf8"));
|
|
2318
2342
|
return pkg.version ?? "0.0.0";
|
|
2319
2343
|
} catch {
|
|
2320
2344
|
return "0.0.0";
|
|
@@ -2324,8 +2348,8 @@ function configSearchPath(crewHome) {
|
|
|
2324
2348
|
if (process.env.CREW_CONFIG) return [resolve(process.env.CREW_CONFIG)];
|
|
2325
2349
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
2326
2350
|
return [
|
|
2327
|
-
...xdg ? [
|
|
2328
|
-
|
|
2351
|
+
...xdg ? [join9(xdg, "crew", "crew.yaml")] : [],
|
|
2352
|
+
join9(homedir6(), ".config", "crew", "crew.yaml"),
|
|
2329
2353
|
resolve(crewHome, "crew.yaml")
|
|
2330
2354
|
];
|
|
2331
2355
|
}
|
|
@@ -2333,17 +2357,17 @@ function findConfigFile(crewHome) {
|
|
|
2333
2357
|
const candidates = configSearchPath(crewHome);
|
|
2334
2358
|
return candidates.find((c) => existsSync7(c)) ?? candidates[candidates.length - 1];
|
|
2335
2359
|
}
|
|
2336
|
-
var defaultConfigPath = () =>
|
|
2360
|
+
var defaultConfigPath = () => join9(process.env.XDG_CONFIG_HOME ?? join9(homedir6(), ".config"), "crew", "crew.yaml");
|
|
2337
2361
|
function routeSlug(route) {
|
|
2338
2362
|
return route.replace(/\//g, "-");
|
|
2339
2363
|
}
|
|
2340
2364
|
function resolvedPathFor(stateDir, route) {
|
|
2341
2365
|
const [workspace, project] = splitRoute(route);
|
|
2342
|
-
return
|
|
2366
|
+
return join9(stateDir, "resolved", workspace, `${project}.json`);
|
|
2343
2367
|
}
|
|
2344
2368
|
function apiKeyPathFor(stateDir, route) {
|
|
2345
2369
|
const [workspace, project] = splitRoute(route);
|
|
2346
|
-
return
|
|
2370
|
+
return join9(stateDir, "keys", workspace, `${project}.env`);
|
|
2347
2371
|
}
|
|
2348
2372
|
function splitRoute(route) {
|
|
2349
2373
|
const parts = route.split("/");
|
|
@@ -2356,7 +2380,7 @@ function readResolvedFile(stateDir, route) {
|
|
|
2356
2380
|
const path = resolvedPathFor(stateDir, route);
|
|
2357
2381
|
if (!existsSync7(path)) return void 0;
|
|
2358
2382
|
try {
|
|
2359
|
-
return JSON.parse(
|
|
2383
|
+
return JSON.parse(readFileSync8(path, "utf8"));
|
|
2360
2384
|
} catch (e) {
|
|
2361
2385
|
throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
|
|
2362
2386
|
}
|
|
@@ -2421,12 +2445,12 @@ function loadConfig(crewHome, configFile, opts = {}) {
|
|
|
2421
2445
|
` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
|
|
2422
2446
|
|
|
2423
2447
|
Create the first of those:
|
|
2424
|
-
mkdir -p ${dirname3(defaultConfigPath())} && cp ${
|
|
2448
|
+
mkdir -p ${dirname3(defaultConfigPath())} && cp ${join9(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
|
|
2425
2449
|
);
|
|
2426
2450
|
}
|
|
2427
2451
|
let raw;
|
|
2428
2452
|
try {
|
|
2429
|
-
raw = parse2(opts.text ??
|
|
2453
|
+
raw = parse2(opts.text ?? readFileSync8(file, "utf8"));
|
|
2430
2454
|
} catch (e) {
|
|
2431
2455
|
throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
|
|
2432
2456
|
}
|
|
@@ -2504,7 +2528,7 @@ Create the first of those:
|
|
|
2504
2528
|
useNvm: shipRaw.useNvm !== false,
|
|
2505
2529
|
nvmSh: shipRaw.nvmSh,
|
|
2506
2530
|
stateDir,
|
|
2507
|
-
logFile: expand(shipRaw.logFile ??
|
|
2531
|
+
logFile: expand(shipRaw.logFile ?? join9(tmpdir(), "crew.log"), base),
|
|
2508
2532
|
userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
|
|
2509
2533
|
maxConcurrentAgents,
|
|
2510
2534
|
streamRetentionDays,
|
|
@@ -2526,7 +2550,7 @@ Create the first of those:
|
|
|
2526
2550
|
function expandCommand(cmd, base) {
|
|
2527
2551
|
return cmd.includes("/") || cmd.startsWith("~") ? expand(cmd, base) : cmd;
|
|
2528
2552
|
}
|
|
2529
|
-
var TOOLCHAIN_KEYS = /* @__PURE__ */ new Set(["harness", "bin", "launcher", "launcherBin", "model", "tiers", "args", "env", "maxThinkingTokens"]);
|
|
2553
|
+
var TOOLCHAIN_KEYS = /* @__PURE__ */ new Set(["harness", "bin", "launcher", "launcherBin", "model", "tiers", "args", "env", "maxThinkingTokens", "auth"]);
|
|
2530
2554
|
function parsePair(raw, missing) {
|
|
2531
2555
|
if (raw === void 0) return void 0;
|
|
2532
2556
|
if (!Array.isArray(raw)) {
|
|
@@ -2583,9 +2607,20 @@ function parseToolchains(raw, base, missing, file) {
|
|
|
2583
2607
|
if (!Number.isInteger(maxThinkingTokens) || maxThinkingTokens < 0) {
|
|
2584
2608
|
missing.add(`${where}.maxThinkingTokens must be a non-negative integer (got "${entryRaw.maxThinkingTokens}")`);
|
|
2585
2609
|
}
|
|
2610
|
+
let auth;
|
|
2611
|
+
if (entryRaw.auth !== void 0) {
|
|
2612
|
+
if (entryRaw.auth !== "subscription" && entryRaw.auth !== "inherit") {
|
|
2613
|
+
missing.add(`${where}.auth must be subscription|inherit (got "${entryRaw.auth}")`);
|
|
2614
|
+
} else if (harness !== "claude") {
|
|
2615
|
+
missing.add(`${where}.auth is only supported on the claude harness`);
|
|
2616
|
+
} else {
|
|
2617
|
+
auth = entryRaw.auth;
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2586
2620
|
out.push({
|
|
2587
2621
|
name,
|
|
2588
2622
|
harness,
|
|
2623
|
+
...auth ? { auth } : {},
|
|
2589
2624
|
bin: expandCommand(String(entryRaw.bin ?? adapter.defaultBin), base),
|
|
2590
2625
|
...entryRaw.launcher ? { launcher: expandCommand(String(entryRaw.launcher), base) } : {},
|
|
2591
2626
|
...entryRaw.launcher && entryRaw.launcherBin ? { launcherBin: expandCommand(String(entryRaw.launcherBin), base) } : {},
|
|
@@ -2645,7 +2680,7 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
|
|
|
2645
2680
|
const hasRepos = Object.keys(repoDirs).length > 0;
|
|
2646
2681
|
const reposBasePath = expand(String(c?.reposBasePath ?? raw.ship?.reposBasePath ?? DEFAULT_REPOS_BASE_PATH), base);
|
|
2647
2682
|
const dirIsCloneBase = !c?.dir && !hasRepos && c?.repos && typeof c.repos === "object" && !Array.isArray(c.repos);
|
|
2648
|
-
const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ?
|
|
2683
|
+
const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join9(reposBasePath, splitRoute(route)[0]) : missing.req(void 0, `${where}.dir (or ${where}.repos)`) ?? "";
|
|
2649
2684
|
const weight = c?.weight === void 0 ? void 0 : Number(c.weight);
|
|
2650
2685
|
if (weight !== void 0 && !(Number.isFinite(weight) && weight > 0)) {
|
|
2651
2686
|
missing.add(`${where}.weight must be a positive number (got "${c?.weight}")`);
|
|
@@ -2729,7 +2764,7 @@ function repoIdForName(r, name) {
|
|
|
2729
2764
|
function resolveApiKey(r) {
|
|
2730
2765
|
if (r.apiKey) return r.apiKey;
|
|
2731
2766
|
if (r.apiKeyFile && r.apiKeyVar && existsSync7(r.apiKeyFile)) {
|
|
2732
|
-
for (const line of
|
|
2767
|
+
for (const line of readFileSync8(r.apiKeyFile, "utf8").split("\n")) {
|
|
2733
2768
|
const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
|
|
2734
2769
|
if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
2735
2770
|
}
|
|
@@ -2744,7 +2779,7 @@ function credentialSource(r) {
|
|
|
2744
2779
|
if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
|
|
2745
2780
|
if (r.apiKeyFile && r.apiKeyVar && existsSync7(r.apiKeyFile)) {
|
|
2746
2781
|
const re = new RegExp(`^${r.apiKeyVar}=`);
|
|
2747
|
-
if (
|
|
2782
|
+
if (readFileSync8(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
|
|
2748
2783
|
}
|
|
2749
2784
|
return process.env.CREW_API_KEY?.trim() ? "CREW_API_KEY" : "none";
|
|
2750
2785
|
}
|
|
@@ -2800,7 +2835,7 @@ function configuredMembers(rt) {
|
|
|
2800
2835
|
return out;
|
|
2801
2836
|
}
|
|
2802
2837
|
function defaultRepoDir(basePath, workspace, repoName) {
|
|
2803
|
-
return
|
|
2838
|
+
return join9(basePath, workspace, repoName);
|
|
2804
2839
|
}
|
|
2805
2840
|
function reposOf(r) {
|
|
2806
2841
|
const named = Object.entries(r.repos);
|
|
@@ -2833,7 +2868,7 @@ function passengerRepoTargets(r) {
|
|
|
2833
2868
|
return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
|
|
2834
2869
|
}
|
|
2835
2870
|
function passengerCheckoutDir(basePath, workspace, repoName) {
|
|
2836
|
-
return
|
|
2871
|
+
return join9(basePath, workspace, ".passenger-checkouts", repoName);
|
|
2837
2872
|
}
|
|
2838
2873
|
function passengerCheckoutTargets(r) {
|
|
2839
2874
|
if (!r.hostPassengers) return [];
|
|
@@ -3181,8 +3216,8 @@ function renderEnvironment(i) {
|
|
|
3181
3216
|
}
|
|
3182
3217
|
|
|
3183
3218
|
// src/agent-log.ts
|
|
3184
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as
|
|
3185
|
-
import { join as
|
|
3219
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync9, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
3220
|
+
import { join as join10 } from "path";
|
|
3186
3221
|
|
|
3187
3222
|
// src/pending-backoff.ts
|
|
3188
3223
|
var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
|
|
@@ -3249,20 +3284,20 @@ async function reportAgentRun(target, run2) {
|
|
|
3249
3284
|
}
|
|
3250
3285
|
return { agentLogId: entry.id };
|
|
3251
3286
|
}
|
|
3252
|
-
var queueDir = (stateDir) =>
|
|
3287
|
+
var queueDir = (stateDir) => join10(stateDir, "pending-agent-logs");
|
|
3253
3288
|
function queueAgentRun(stateDir, name, q) {
|
|
3254
3289
|
mkdirSync4(queueDir(stateDir), { recursive: true });
|
|
3255
|
-
writeFileSync5(
|
|
3290
|
+
writeFileSync5(join10(queueDir(stateDir), `${name}.json`), JSON.stringify(q));
|
|
3256
3291
|
}
|
|
3257
3292
|
async function replayQueuedAgentRuns(stateDir, route, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
3258
3293
|
const dir = queueDir(stateDir);
|
|
3259
3294
|
if (!existsSync8(dir)) return 0;
|
|
3260
3295
|
let sent = 0;
|
|
3261
3296
|
for (const f of readdirSync2(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
3262
|
-
const path =
|
|
3297
|
+
const path = join10(dir, f);
|
|
3263
3298
|
let q;
|
|
3264
3299
|
try {
|
|
3265
|
-
q = JSON.parse(
|
|
3300
|
+
q = JSON.parse(readFileSync9(path, "utf8"));
|
|
3266
3301
|
} catch {
|
|
3267
3302
|
continue;
|
|
3268
3303
|
}
|
|
@@ -3462,8 +3497,8 @@ var HandoffTranscriptTracker = class {
|
|
|
3462
3497
|
};
|
|
3463
3498
|
|
|
3464
3499
|
// src/pending-handoff.ts
|
|
3465
|
-
import { existsSync as existsSync15, mkdirSync as mkdirSync8, readdirSync as readdirSync5, readFileSync as
|
|
3466
|
-
import { join as
|
|
3500
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync8, readdirSync as readdirSync5, readFileSync as readFileSync13, rmSync as rmSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
3501
|
+
import { join as join16 } from "path";
|
|
3467
3502
|
|
|
3468
3503
|
// src/git.ts
|
|
3469
3504
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
@@ -3475,12 +3510,12 @@ import {
|
|
|
3475
3510
|
writeSync,
|
|
3476
3511
|
statSync as statSync2,
|
|
3477
3512
|
unlinkSync,
|
|
3478
|
-
readFileSync as
|
|
3513
|
+
readFileSync as readFileSync10,
|
|
3479
3514
|
writeFileSync as writeFileSync6,
|
|
3480
3515
|
readdirSync as readdirSync3,
|
|
3481
3516
|
rmSync as rmSync6
|
|
3482
3517
|
} from "fs";
|
|
3483
|
-
import { dirname as dirname4, basename as basename5, join as
|
|
3518
|
+
import { dirname as dirname4, basename as basename5, join as join11 } from "path";
|
|
3484
3519
|
var GitError = class extends Error {
|
|
3485
3520
|
};
|
|
3486
3521
|
function streamText(x) {
|
|
@@ -3878,11 +3913,11 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
|
|
|
3878
3913
|
}
|
|
3879
3914
|
for (const name of names) {
|
|
3880
3915
|
if (!name.startsWith("crew-release-")) continue;
|
|
3881
|
-
const root =
|
|
3916
|
+
const root = join11(tmp, name);
|
|
3882
3917
|
try {
|
|
3883
3918
|
const st = statSync2(root);
|
|
3884
3919
|
if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
|
|
3885
|
-
if (existsSync9(
|
|
3920
|
+
if (existsSync9(join11(root, "checkout"))) continue;
|
|
3886
3921
|
rmSync6(root, { recursive: true, force: true });
|
|
3887
3922
|
removed.push(name);
|
|
3888
3923
|
} catch {
|
|
@@ -3996,8 +4031,8 @@ function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {
|
|
|
3996
4031
|
if (!existsSync9(`${dir}/.git`) && githubRemote) {
|
|
3997
4032
|
const url = cloneUrlFor(githubRemote, clone);
|
|
3998
4033
|
const marker = cloneFailureMarker(dir);
|
|
3999
|
-
if (existsSync9(marker) &&
|
|
4000
|
-
return { action: "clone-failed", detail:
|
|
4034
|
+
if (existsSync9(marker) && readFileSync10(marker, "utf8").split("\n")[0] === url) {
|
|
4035
|
+
return { action: "clone-failed", detail: readFileSync10(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
|
|
4001
4036
|
}
|
|
4002
4037
|
try {
|
|
4003
4038
|
ensureRepoCheckout(dir, githubRemote, clone);
|
|
@@ -4424,9 +4459,9 @@ function recoveredFailureHeadlines(o, route) {
|
|
|
4424
4459
|
}
|
|
4425
4460
|
|
|
4426
4461
|
// src/install.ts
|
|
4427
|
-
import { dirname as dirname5, join as
|
|
4428
|
-
import { homedir as
|
|
4429
|
-
import { accessSync, constants, existsSync as existsSync10, mkdirSync as mkdirSync6, readdirSync as readdirSync4, readFileSync as
|
|
4462
|
+
import { dirname as dirname5, join as join12, extname } from "path";
|
|
4463
|
+
import { homedir as homedir7 } from "os";
|
|
4464
|
+
import { accessSync, constants, existsSync as existsSync10, mkdirSync as mkdirSync6, readdirSync as readdirSync4, readFileSync as readFileSync11, statSync as statSync3, writeFileSync as writeFileSync7, unlinkSync as unlinkSync2, realpathSync } from "fs";
|
|
4430
4465
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
4431
4466
|
import { createHash as createHash2 } from "crypto";
|
|
4432
4467
|
|
|
@@ -4438,7 +4473,7 @@ function isCompiledBinary(moduleUrl) {
|
|
|
4438
4473
|
// src/install.ts
|
|
4439
4474
|
function crewInvocation(crewHome) {
|
|
4440
4475
|
if (isCompiledBinary(import.meta.url)) return [process.execPath];
|
|
4441
|
-
return [process.execPath,
|
|
4476
|
+
return [process.execPath, join12(crewHome, "bin", "crew")];
|
|
4442
4477
|
}
|
|
4443
4478
|
var INTERVAL_SECONDS = 120;
|
|
4444
4479
|
function labelFor(crewHome, job) {
|
|
@@ -4476,7 +4511,7 @@ function pathFor(ship) {
|
|
|
4476
4511
|
return parts.join(":");
|
|
4477
4512
|
}
|
|
4478
4513
|
function findOnPath(bin, searchPath) {
|
|
4479
|
-
const candidates = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) =>
|
|
4514
|
+
const candidates = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join12(dir, bin));
|
|
4480
4515
|
for (const candidate of candidates) {
|
|
4481
4516
|
try {
|
|
4482
4517
|
accessSync(candidate, constants.X_OK);
|
|
@@ -4486,7 +4521,7 @@ function findOnPath(bin, searchPath) {
|
|
|
4486
4521
|
}
|
|
4487
4522
|
return void 0;
|
|
4488
4523
|
}
|
|
4489
|
-
var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin",
|
|
4524
|
+
var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join12(homedir7(), ".docker", "bin")].join(":");
|
|
4490
4525
|
function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
|
|
4491
4526
|
const schedulerPath = pathFor(ship);
|
|
4492
4527
|
if (findOnPath("docker", schedulerPath)) return void 0;
|
|
@@ -4498,7 +4533,7 @@ function xmlEscape(s) {
|
|
|
4498
4533
|
}
|
|
4499
4534
|
function planLaunchd(ship, crewHome, job) {
|
|
4500
4535
|
const label = labelFor(crewHome, job);
|
|
4501
|
-
const unitPath =
|
|
4536
|
+
const unitPath = join12(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4502
4537
|
const crewLog = ship.logFile;
|
|
4503
4538
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4504
4539
|
const persistent = job === "run";
|
|
@@ -4552,8 +4587,8 @@ ${scheduleKeys}
|
|
|
4552
4587
|
}
|
|
4553
4588
|
function planSystemd(ship, crewHome, job) {
|
|
4554
4589
|
const label = labelFor(crewHome, job);
|
|
4555
|
-
const unitDir =
|
|
4556
|
-
const servicePath =
|
|
4590
|
+
const unitDir = join12(homedir7(), ".config", "systemd", "user");
|
|
4591
|
+
const servicePath = join12(unitDir, `${label}.service`);
|
|
4557
4592
|
const crewLog = ship.logFile;
|
|
4558
4593
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4559
4594
|
const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
|
|
@@ -4593,7 +4628,7 @@ WantedBy=default.target
|
|
|
4593
4628
|
schedulerLog
|
|
4594
4629
|
};
|
|
4595
4630
|
}
|
|
4596
|
-
const timerPath =
|
|
4631
|
+
const timerPath = join12(unitDir, `${label}.timer`);
|
|
4597
4632
|
const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
|
|
4598
4633
|
[Unit]
|
|
4599
4634
|
Description=${description}
|
|
@@ -4667,7 +4702,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4667
4702
|
function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
4668
4703
|
const label = labelFor(crewHome, job);
|
|
4669
4704
|
if (hostShip === "macos") {
|
|
4670
|
-
const unitPath =
|
|
4705
|
+
const unitPath = join12(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4671
4706
|
return {
|
|
4672
4707
|
mechanism: "launchd",
|
|
4673
4708
|
unitPaths: [unitPath],
|
|
@@ -4677,18 +4712,18 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4677
4712
|
};
|
|
4678
4713
|
}
|
|
4679
4714
|
if (hostShip === "linux" && hasSystemd) {
|
|
4680
|
-
const unitDir =
|
|
4715
|
+
const unitDir = join12(homedir7(), ".config", "systemd", "user");
|
|
4681
4716
|
if (job === "run") {
|
|
4682
4717
|
return {
|
|
4683
4718
|
mechanism: "systemd",
|
|
4684
|
-
unitPaths: [
|
|
4719
|
+
unitPaths: [join12(unitDir, `${label}.service`)],
|
|
4685
4720
|
crontabLine: null,
|
|
4686
4721
|
unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.service`]]
|
|
4687
4722
|
};
|
|
4688
4723
|
}
|
|
4689
4724
|
return {
|
|
4690
4725
|
mechanism: "systemd",
|
|
4691
|
-
unitPaths: [
|
|
4726
|
+
unitPaths: [join12(unitDir, `${label}.service`), join12(unitDir, `${label}.timer`)],
|
|
4692
4727
|
crontabLine: null,
|
|
4693
4728
|
unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.timer`]]
|
|
4694
4729
|
};
|
|
@@ -4706,7 +4741,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4706
4741
|
function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
4707
4742
|
const label = labelFor(crewHome, "run");
|
|
4708
4743
|
if (hostShip === "macos") {
|
|
4709
|
-
const unitPath =
|
|
4744
|
+
const unitPath = join12(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4710
4745
|
return {
|
|
4711
4746
|
mechanism: "launchd",
|
|
4712
4747
|
startCommand: ["launchctl", "load", unitPath],
|
|
@@ -4820,7 +4855,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
4820
4855
|
}
|
|
4821
4856
|
var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
|
|
4822
4857
|
function launchAgentsDir() {
|
|
4823
|
-
return
|
|
4858
|
+
return join12(homedir7(), "Library", "LaunchAgents");
|
|
4824
4859
|
}
|
|
4825
4860
|
function xmlUnescape(s) {
|
|
4826
4861
|
return s.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
@@ -4839,10 +4874,10 @@ function loadedCrewLabels() {
|
|
|
4839
4874
|
function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
4840
4875
|
const files = existsSync10(dir) ? readdirSync4(dir) : [];
|
|
4841
4876
|
const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
|
|
4842
|
-
const plistPath =
|
|
4877
|
+
const plistPath = join12(dir, f);
|
|
4843
4878
|
let program = null;
|
|
4844
4879
|
try {
|
|
4845
|
-
program = programFromPlist(
|
|
4880
|
+
program = programFromPlist(readFileSync11(plistPath, "utf8"));
|
|
4846
4881
|
} catch {
|
|
4847
4882
|
}
|
|
4848
4883
|
return {
|
|
@@ -4855,7 +4890,7 @@ function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
|
4855
4890
|
const seen = new Set(units.map((u) => u.label));
|
|
4856
4891
|
for (const label of [...loaded].sort()) {
|
|
4857
4892
|
if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
|
|
4858
|
-
units.push({ label, plistPath:
|
|
4893
|
+
units.push({ label, plistPath: join12(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
|
|
4859
4894
|
}
|
|
4860
4895
|
return units;
|
|
4861
4896
|
}
|
|
@@ -5201,8 +5236,8 @@ var SHELL_WORDS = /* @__PURE__ */ new Set([
|
|
|
5201
5236
|
]);
|
|
5202
5237
|
|
|
5203
5238
|
// src/state.ts
|
|
5204
|
-
import { readFileSync as
|
|
5205
|
-
import { join as
|
|
5239
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, existsSync as existsSync12, mkdirSync as mkdirSync7, rmSync as rmSync7, statSync as statSync4 } from "fs";
|
|
5240
|
+
import { join as join13 } from "path";
|
|
5206
5241
|
var EPOCH = "1970-01-01T00:00:00.000Z";
|
|
5207
5242
|
var State = class _State {
|
|
5208
5243
|
dir;
|
|
@@ -5211,7 +5246,7 @@ var State = class _State {
|
|
|
5211
5246
|
mkdirSync7(dir, { recursive: true });
|
|
5212
5247
|
}
|
|
5213
5248
|
path(...parts) {
|
|
5214
|
-
return
|
|
5249
|
+
return join13(this.dir, ...parts);
|
|
5215
5250
|
}
|
|
5216
5251
|
/**
|
|
5217
5252
|
* The timestamp every "new since last poll" check is measured against.
|
|
@@ -5222,7 +5257,7 @@ var State = class _State {
|
|
|
5222
5257
|
*/
|
|
5223
5258
|
watermark() {
|
|
5224
5259
|
try {
|
|
5225
|
-
const v =
|
|
5260
|
+
const v = readFileSync12(this.path(".poll-watermark"), "utf8").trim();
|
|
5226
5261
|
return v || EPOCH;
|
|
5227
5262
|
} catch {
|
|
5228
5263
|
return EPOCH;
|
|
@@ -5308,7 +5343,7 @@ var State = class _State {
|
|
|
5308
5343
|
const name = _State.safe(rawName);
|
|
5309
5344
|
const file = this.path(`.${name}.lock`);
|
|
5310
5345
|
try {
|
|
5311
|
-
const existing = Number.parseInt(
|
|
5346
|
+
const existing = Number.parseInt(readFileSync12(file, "utf8").trim(), 10);
|
|
5312
5347
|
if (Number.isFinite(existing) && existing > 0) {
|
|
5313
5348
|
try {
|
|
5314
5349
|
process.kill(existing, 0);
|
|
@@ -5402,7 +5437,7 @@ var State = class _State {
|
|
|
5402
5437
|
const heldReported = `.release-held-${suffix}`;
|
|
5403
5438
|
const read = (f) => {
|
|
5404
5439
|
try {
|
|
5405
|
-
return
|
|
5440
|
+
return readFileSync12(this.path(f), "utf8").trim() || null;
|
|
5406
5441
|
} catch {
|
|
5407
5442
|
return null;
|
|
5408
5443
|
}
|
|
@@ -5452,7 +5487,7 @@ var State = class _State {
|
|
|
5452
5487
|
/** Whether the board was already told about this head's red gate. */
|
|
5453
5488
|
heldReported: () => {
|
|
5454
5489
|
try {
|
|
5455
|
-
return
|
|
5490
|
+
return readFileSync12(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
|
|
5456
5491
|
} catch {
|
|
5457
5492
|
return null;
|
|
5458
5493
|
}
|
|
@@ -5489,7 +5524,7 @@ var State = class _State {
|
|
|
5489
5524
|
const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
|
|
5490
5525
|
const read = (f) => {
|
|
5491
5526
|
try {
|
|
5492
|
-
return
|
|
5527
|
+
return readFileSync12(this.path(f), "utf8").trim() || null;
|
|
5493
5528
|
} catch {
|
|
5494
5529
|
return null;
|
|
5495
5530
|
}
|
|
@@ -5554,7 +5589,7 @@ var State = class _State {
|
|
|
5554
5589
|
return {
|
|
5555
5590
|
previous: () => {
|
|
5556
5591
|
try {
|
|
5557
|
-
const parsed = JSON.parse(
|
|
5592
|
+
const parsed = JSON.parse(readFileSync12(this.path(file), "utf8"));
|
|
5558
5593
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
5559
5594
|
} catch {
|
|
5560
5595
|
return {};
|
|
@@ -5575,7 +5610,7 @@ var State = class _State {
|
|
|
5575
5610
|
return {
|
|
5576
5611
|
previous: () => {
|
|
5577
5612
|
try {
|
|
5578
|
-
const parsed = JSON.parse(
|
|
5613
|
+
const parsed = JSON.parse(readFileSync12(this.path(file), "utf8"));
|
|
5579
5614
|
return Array.isArray(parsed) ? parsed : [];
|
|
5580
5615
|
} catch {
|
|
5581
5616
|
return [];
|
|
@@ -5608,7 +5643,7 @@ var State = class _State {
|
|
|
5608
5643
|
}
|
|
5609
5644
|
readInt(file) {
|
|
5610
5645
|
try {
|
|
5611
|
-
return Number.parseInt(
|
|
5646
|
+
return Number.parseInt(readFileSync12(this.path(file), "utf8").trim(), 10) || 0;
|
|
5612
5647
|
} catch {
|
|
5613
5648
|
return 0;
|
|
5614
5649
|
}
|
|
@@ -6611,12 +6646,12 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
|
|
|
6611
6646
|
|
|
6612
6647
|
// src/run-worktree.ts
|
|
6613
6648
|
import { existsSync as existsSync14 } from "fs";
|
|
6614
|
-
import { dirname as dirname6, join as
|
|
6649
|
+
import { dirname as dirname6, join as join15 } from "path";
|
|
6615
6650
|
|
|
6616
6651
|
// src/ports.ts
|
|
6617
6652
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
6618
6653
|
import { existsSync as existsSync13 } from "fs";
|
|
6619
|
-
import { join as
|
|
6654
|
+
import { join as join14 } from "path";
|
|
6620
6655
|
var run = (cmd, args) => {
|
|
6621
6656
|
try {
|
|
6622
6657
|
return execFileSync3(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -6661,7 +6696,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
|
|
|
6661
6696
|
}
|
|
6662
6697
|
function worktreeExists(parent, prefix, n) {
|
|
6663
6698
|
for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
|
|
6664
|
-
if (existsSync13(
|
|
6699
|
+
if (existsSync13(join14(parent, name, ".git"))) return true;
|
|
6665
6700
|
}
|
|
6666
6701
|
return false;
|
|
6667
6702
|
}
|
|
@@ -6753,7 +6788,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
6753
6788
|
if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
|
|
6754
6789
|
return { action: "recut", path: existing.path, branch, base: baseRef };
|
|
6755
6790
|
}
|
|
6756
|
-
const path =
|
|
6791
|
+
const path = join15(dirname6(dir), worktreeForTicket(dir, cfg, t, role));
|
|
6757
6792
|
if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
|
|
6758
6793
|
return { action: "cut-remote", path, branch: located.branch };
|
|
6759
6794
|
}
|
|
@@ -7116,12 +7151,12 @@ async function guardRunStrays(route, ship, tracker, role, workingId, since2, log
|
|
|
7116
7151
|
|
|
7117
7152
|
// src/pending-handoff.ts
|
|
7118
7153
|
var PENDING_MAX_AGE_MS = 48 * 36e5;
|
|
7119
|
-
var pendingDirFor = (stateDir) =>
|
|
7120
|
-
var stateDirOfStream = (streamPath) =>
|
|
7154
|
+
var pendingDirFor = (stateDir) => join16(stateDir, "pending-handoffs");
|
|
7155
|
+
var stateDirOfStream = (streamPath) => join16(streamPath, "..", "..");
|
|
7121
7156
|
function writePendingHandoff(stateDir, rec) {
|
|
7122
7157
|
const dir = pendingDirFor(stateDir);
|
|
7123
7158
|
mkdirSync8(dir, { recursive: true });
|
|
7124
|
-
writeFileSync9(
|
|
7159
|
+
writeFileSync9(join16(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
|
|
7125
7160
|
}
|
|
7126
7161
|
function listPendingHandoffs(stateDir, route) {
|
|
7127
7162
|
const dir = pendingDirFor(stateDir);
|
|
@@ -7129,7 +7164,7 @@ function listPendingHandoffs(stateDir, route) {
|
|
|
7129
7164
|
const out = [];
|
|
7130
7165
|
for (const f of readdirSync5(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
7131
7166
|
try {
|
|
7132
|
-
const rec = JSON.parse(
|
|
7167
|
+
const rec = JSON.parse(readFileSync13(join16(dir, f), "utf8"));
|
|
7133
7168
|
if (rec.version === 1 && rec.ticketRecordId && (!route || rec.route === route)) out.push(rec);
|
|
7134
7169
|
} catch {
|
|
7135
7170
|
}
|
|
@@ -7137,7 +7172,7 @@ function listPendingHandoffs(stateDir, route) {
|
|
|
7137
7172
|
return out;
|
|
7138
7173
|
}
|
|
7139
7174
|
function removePendingHandoff(stateDir, ticketRecordId) {
|
|
7140
|
-
rmSync8(
|
|
7175
|
+
rmSync8(join16(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
|
|
7141
7176
|
}
|
|
7142
7177
|
function pendingHandoffFor(stateDir, ticketRecordId) {
|
|
7143
7178
|
return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
|
|
@@ -7247,8 +7282,8 @@ function expandSkillMarkers(text, lookup, opts = {}) {
|
|
|
7247
7282
|
}
|
|
7248
7283
|
|
|
7249
7284
|
// src/skills.ts
|
|
7250
|
-
import { readFileSync as
|
|
7251
|
-
import { join as
|
|
7285
|
+
import { readFileSync as readFileSync15, readdirSync as readdirSync6, existsSync as existsSync17 } from "fs";
|
|
7286
|
+
import { join as join18 } from "path";
|
|
7252
7287
|
import { parse as parseYaml } from "yaml";
|
|
7253
7288
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
7254
7289
|
|
|
@@ -7264,8 +7299,8 @@ ${additions}`;
|
|
|
7264
7299
|
}
|
|
7265
7300
|
|
|
7266
7301
|
// src/agents.ts
|
|
7267
|
-
import { readFileSync as
|
|
7268
|
-
import { join as
|
|
7302
|
+
import { readFileSync as readFileSync14, existsSync as existsSync16 } from "fs";
|
|
7303
|
+
import { join as join17 } from "path";
|
|
7269
7304
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
|
|
7270
7305
|
var PERSONA_NAME = {
|
|
7271
7306
|
dev: "Developer",
|
|
@@ -7279,12 +7314,12 @@ var CREW_AGENT_COLUMN = "agent_id";
|
|
|
7279
7314
|
var AgentsSyncError = class extends Error {
|
|
7280
7315
|
};
|
|
7281
7316
|
function personaDefaultPrompt(promptsDir, role) {
|
|
7282
|
-
const briefPath =
|
|
7317
|
+
const briefPath = join17(promptsDir, "personas", `lane-${role}.md`);
|
|
7283
7318
|
if (!existsSync16(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
|
|
7284
|
-
if (SELF_CONTAINED_ROLES.has(role)) return
|
|
7285
|
-
const commonPath =
|
|
7286
|
-
if (!existsSync16(commonPath)) return
|
|
7287
|
-
return `${
|
|
7319
|
+
if (SELF_CONTAINED_ROLES.has(role)) return readFileSync14(briefPath, "utf8");
|
|
7320
|
+
const commonPath = join17(promptsDir, "personas", "common.md");
|
|
7321
|
+
if (!existsSync16(commonPath)) return readFileSync14(briefPath, "utf8");
|
|
7322
|
+
return `${readFileSync14(commonPath, "utf8")}${readFileSync14(briefPath, "utf8")}`;
|
|
7288
7323
|
}
|
|
7289
7324
|
async function resolveAgentId(route, role, opts) {
|
|
7290
7325
|
const cached = route.resolved?.agentPersonas?.[role]?.agentId;
|
|
@@ -7731,7 +7766,7 @@ function describeCrewLink(o) {
|
|
|
7731
7766
|
var SkillSyncError = class extends Error {
|
|
7732
7767
|
};
|
|
7733
7768
|
function parseSkillFile(path) {
|
|
7734
|
-
const text =
|
|
7769
|
+
const text = readFileSync15(path, "utf8");
|
|
7735
7770
|
const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text);
|
|
7736
7771
|
if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
|
|
7737
7772
|
const frontmatter = match[1];
|
|
@@ -7744,9 +7779,9 @@ function parseSkillFile(path) {
|
|
|
7744
7779
|
return { name: meta.name, description: meta.description, prompt: body, file: path };
|
|
7745
7780
|
}
|
|
7746
7781
|
function listLocalSkills(promptsDir) {
|
|
7747
|
-
const dir =
|
|
7782
|
+
const dir = join18(promptsDir, "skills");
|
|
7748
7783
|
if (!existsSync17(dir)) return [];
|
|
7749
|
-
return readdirSync6(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(
|
|
7784
|
+
return readdirSync6(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join18(dir, f)));
|
|
7750
7785
|
}
|
|
7751
7786
|
async function syncSkills(route, opts) {
|
|
7752
7787
|
if (!route.resolved) {
|
|
@@ -7949,23 +7984,23 @@ var AgentError = class extends Error {
|
|
|
7949
7984
|
var AgentSpawnError = class extends AgentError {
|
|
7950
7985
|
};
|
|
7951
7986
|
function planAgentRun(o) {
|
|
7952
|
-
const briefPath =
|
|
7987
|
+
const briefPath = join19(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
7953
7988
|
if (!existsSync18(briefPath)) {
|
|
7954
7989
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
7955
7990
|
}
|
|
7956
|
-
const commonPath =
|
|
7991
|
+
const commonPath = join19(o.route.promptsDir, "personas", "common.md");
|
|
7957
7992
|
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync18(commonPath);
|
|
7958
7993
|
const now = o.now ?? (() => Date.now());
|
|
7959
7994
|
const routeLabel = routeSlug(o.route.route);
|
|
7960
|
-
const digestPath2 =
|
|
7995
|
+
const digestPath2 = join19(o.stateDir, `digest-${routeLabel}-${o.role}.md`);
|
|
7961
7996
|
let digest;
|
|
7962
7997
|
let ageSeconds;
|
|
7963
7998
|
if (existsSync18(digestPath2)) {
|
|
7964
7999
|
ageSeconds = Math.floor((now() - statSync5(digestPath2).mtimeMs) / 1e3);
|
|
7965
|
-
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest =
|
|
8000
|
+
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync16(digestPath2, "utf8");
|
|
7966
8001
|
}
|
|
7967
|
-
const common = usesCommon ?
|
|
7968
|
-
const brief =
|
|
8002
|
+
const common = usesCommon ? readFileSync16(commonPath, "utf8") : "";
|
|
8003
|
+
const brief = readFileSync16(briefPath, "utf8");
|
|
7969
8004
|
const effectiveCommon = o.divergedPrompt ? "" : common;
|
|
7970
8005
|
const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
|
|
7971
8006
|
const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
|
|
@@ -7987,7 +8022,7 @@ function planAgentRun(o) {
|
|
|
7987
8022
|
digest
|
|
7988
8023
|
});
|
|
7989
8024
|
const promptSha = createHash3("sha256").update(expanded.text).digest("hex");
|
|
7990
|
-
const streamsDir =
|
|
8025
|
+
const streamsDir = join19(o.stateDir, "streams");
|
|
7991
8026
|
const base = `${routeLabel}-${o.role}-${o.cycle}`;
|
|
7992
8027
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
7993
8028
|
const { toolchain, model } = seat;
|
|
@@ -8043,8 +8078,8 @@ function planAgentRun(o) {
|
|
|
8043
8078
|
digestAttached: digest !== void 0,
|
|
8044
8079
|
digestAgeSeconds: ageSeconds,
|
|
8045
8080
|
model,
|
|
8046
|
-
streamPath:
|
|
8047
|
-
eventsPath:
|
|
8081
|
+
streamPath: join19(streamsDir, `${base}.jsonl`),
|
|
8082
|
+
eventsPath: join19(streamsDir, `${base}.events.jsonl`),
|
|
8048
8083
|
ticket: o.ticket,
|
|
8049
8084
|
ticketRecordId: o.ticketRecordId ?? null,
|
|
8050
8085
|
agentLog,
|
|
@@ -8073,7 +8108,7 @@ function describePlan(p) {
|
|
|
8073
8108
|
var pidPathFor = (streamPath) => `${streamPath}.pid`;
|
|
8074
8109
|
function readRunSidecar(streamPath) {
|
|
8075
8110
|
try {
|
|
8076
|
-
const sc = JSON.parse(
|
|
8111
|
+
const sc = JSON.parse(readFileSync16(pidPathFor(streamPath), "utf8"));
|
|
8077
8112
|
return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
|
|
8078
8113
|
} catch {
|
|
8079
8114
|
return null;
|
|
@@ -8657,7 +8692,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
8657
8692
|
...adapter.runEnv(spec),
|
|
8658
8693
|
[KEY_VAR]: i.apiKey
|
|
8659
8694
|
};
|
|
8660
|
-
const cwd = mkdtempSync(
|
|
8695
|
+
const cwd = mkdtempSync(join20(tmpdir2(), "crew-selftest-"));
|
|
8661
8696
|
try {
|
|
8662
8697
|
const run2 = await deps.run({
|
|
8663
8698
|
bin: invocation.bin,
|
|
@@ -8672,7 +8707,8 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
8672
8707
|
const answer = [seen.finalText ?? "", ...seen.texts].join("\n");
|
|
8673
8708
|
const usage2 = { ...seen.tokens !== void 0 ? { tokens: seen.tokens } : {}, ...seen.costUsd !== void 0 ? { costUsd: seen.costUsd } : {} };
|
|
8674
8709
|
if (!run2.spawnError && !run2.timedOut && answer.toLowerCase().includes(i.workspaceName.toLowerCase())) {
|
|
8675
|
-
|
|
8710
|
+
const auth = spec.harness === "claude" ? ` on ${describeClaudeAuth(spec)}` : "";
|
|
8711
|
+
return finish({ ok: true, workspace: i.workspaceName, detail: `read workspace "${i.workspaceName}"${auth}`, ...usage2 });
|
|
8676
8712
|
}
|
|
8677
8713
|
const { failure, detail } = classify(spec, run2, seen);
|
|
8678
8714
|
return finish({ ok: false, failure, detail, ...usage2 });
|
|
@@ -8689,11 +8725,11 @@ function formatResult(r) {
|
|
|
8689
8725
|
const secs = `${(r.ms / 1e3).toFixed(1)}s`;
|
|
8690
8726
|
return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
|
|
8691
8727
|
}
|
|
8692
|
-
var fileFor2 = (stateDir) =>
|
|
8728
|
+
var fileFor2 = (stateDir) => join20(stateDir, "selftest.json");
|
|
8693
8729
|
function readSelftest(stateDir) {
|
|
8694
8730
|
try {
|
|
8695
8731
|
const p = fileFor2(stateDir);
|
|
8696
|
-
return existsSync19(p) ? JSON.parse(
|
|
8732
|
+
return existsSync19(p) ? JSON.parse(readFileSync17(p, "utf8")) : {};
|
|
8697
8733
|
} catch {
|
|
8698
8734
|
return {};
|
|
8699
8735
|
}
|
|
@@ -8714,9 +8750,9 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
8714
8750
|
}
|
|
8715
8751
|
|
|
8716
8752
|
// src/cli.ts
|
|
8717
|
-
import { resolve as resolve5, dirname as dirname20, join as
|
|
8753
|
+
import { resolve as resolve5, dirname as dirname20, join as join37, basename as basename11 } from "path";
|
|
8718
8754
|
import { fileURLToPath } from "url";
|
|
8719
|
-
import { hostname as hostname2, homedir as
|
|
8755
|
+
import { hostname as hostname2, homedir as homedir10 } from "os";
|
|
8720
8756
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
8721
8757
|
import { parse as parseYaml2 } from "yaml";
|
|
8722
8758
|
|
|
@@ -8894,14 +8930,14 @@ function rosterMarkdown(roster, meId) {
|
|
|
8894
8930
|
}
|
|
8895
8931
|
|
|
8896
8932
|
// src/connect-resume.ts
|
|
8897
|
-
import { mkdirSync as mkdirSync11, readFileSync as
|
|
8898
|
-
import { dirname as dirname8, join as
|
|
8933
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync18, rmSync as rmSync10, writeFileSync as writeFileSync12 } from "fs";
|
|
8934
|
+
import { dirname as dirname8, join as join21 } from "path";
|
|
8899
8935
|
function lastSessionFile(stateDir) {
|
|
8900
|
-
return
|
|
8936
|
+
return join21(stateDir, "connect-session.json");
|
|
8901
8937
|
}
|
|
8902
8938
|
function readPointers(stateDir) {
|
|
8903
8939
|
try {
|
|
8904
|
-
const parsed = JSON.parse(
|
|
8940
|
+
const parsed = JSON.parse(readFileSync18(lastSessionFile(stateDir), "utf8"));
|
|
8905
8941
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
8906
8942
|
const out = {};
|
|
8907
8943
|
for (const [host, v] of Object.entries(parsed)) {
|
|
@@ -8972,15 +9008,15 @@ async function signOutSessions(o) {
|
|
|
8972
9008
|
}
|
|
8973
9009
|
|
|
8974
9010
|
// src/reset.ts
|
|
8975
|
-
import { existsSync as existsSync20, mkdirSync as mkdirSync12, readFileSync as
|
|
8976
|
-
import { dirname as dirname9, join as
|
|
9011
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync12, readFileSync as readFileSync19, readdirSync as readdirSync7, rmSync as rmSync11, statSync as statSync6, writeFileSync as writeFileSync13 } from "fs";
|
|
9012
|
+
import { dirname as dirname9, join as join22 } from "path";
|
|
8977
9013
|
import { hostFromUrl as hostFromUrl2 } from "@tablation/client";
|
|
8978
9014
|
function clonedCheckoutsFile(stateDir) {
|
|
8979
|
-
return
|
|
9015
|
+
return join22(stateDir, "cloned-checkouts.json");
|
|
8980
9016
|
}
|
|
8981
9017
|
function readClonedCheckouts(stateDir) {
|
|
8982
9018
|
try {
|
|
8983
|
-
const parsed = JSON.parse(
|
|
9019
|
+
const parsed = JSON.parse(readFileSync19(clonedCheckoutsFile(stateDir), "utf8"));
|
|
8984
9020
|
return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
|
|
8985
9021
|
} catch {
|
|
8986
9022
|
return [];
|
|
@@ -9065,10 +9101,10 @@ function removeCheckout(dir, dryRun, fx) {
|
|
|
9065
9101
|
}
|
|
9066
9102
|
function removeState(stateDir, dryRun) {
|
|
9067
9103
|
const out = [];
|
|
9068
|
-
const targets = [
|
|
9104
|
+
const targets = [join22(stateDir, "resolved"), join22(stateDir, "keys"), clonedCheckoutsFile(stateDir), join22(stateDir, "connect-session.json"), join22(stateDir, "reload-requested"), join22(stateDir, "daemon.started")];
|
|
9069
9105
|
try {
|
|
9070
9106
|
for (const name of readdirSync7(stateDir)) {
|
|
9071
|
-
if (name.startsWith(".") && statSync6(
|
|
9107
|
+
if (name.startsWith(".") && statSync6(join22(stateDir, name)).isFile()) targets.push(join22(stateDir, name));
|
|
9072
9108
|
}
|
|
9073
9109
|
} catch {
|
|
9074
9110
|
}
|
|
@@ -9854,7 +9890,7 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
|
|
|
9854
9890
|
|
|
9855
9891
|
// src/poll.ts
|
|
9856
9892
|
import { writeFileSync as writeFileSync14 } from "fs";
|
|
9857
|
-
import { join as
|
|
9893
|
+
import { join as join23 } from "path";
|
|
9858
9894
|
|
|
9859
9895
|
// src/stalled.ts
|
|
9860
9896
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -10145,7 +10181,7 @@ async function decideCycle(o) {
|
|
|
10145
10181
|
}
|
|
10146
10182
|
return decision;
|
|
10147
10183
|
}
|
|
10148
|
-
var digestPath = (stateDir, route, role) =>
|
|
10184
|
+
var digestPath = (stateDir, route, role) => join23(stateDir, `digest-${route}-${role}.md`);
|
|
10149
10185
|
function writeDigest(o, d, role, stateDir) {
|
|
10150
10186
|
const me = o.route.resolved?.seats[role];
|
|
10151
10187
|
if (!me) return false;
|
|
@@ -10634,7 +10670,7 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
|
|
|
10634
10670
|
var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
|
|
10635
10671
|
|
|
10636
10672
|
// src/connect-wizard.ts
|
|
10637
|
-
import { delimiter, dirname as dirname10, join as
|
|
10673
|
+
import { delimiter, dirname as dirname10, join as join24 } from "path";
|
|
10638
10674
|
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync21, mkdirSync as mkdirSync13, writeFileSync as writeFileSync15 } from "fs";
|
|
10639
10675
|
import { stringify } from "yaml";
|
|
10640
10676
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
@@ -10657,7 +10693,7 @@ function defaultIsExecutable(p) {
|
|
|
10657
10693
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
10658
10694
|
for (const dir of pathEnv.split(delimiter)) {
|
|
10659
10695
|
if (!dir) continue;
|
|
10660
|
-
const candidate =
|
|
10696
|
+
const candidate = join24(dir, name);
|
|
10661
10697
|
if (isExecutable(candidate)) return candidate;
|
|
10662
10698
|
}
|
|
10663
10699
|
return void 0;
|
|
@@ -10753,8 +10789,8 @@ Wrote ${o.configPath}.
|
|
|
10753
10789
|
|
|
10754
10790
|
// src/connect-json.ts
|
|
10755
10791
|
import { accessSync as accessSync3, constants as constants3 } from "fs";
|
|
10756
|
-
import { homedir as
|
|
10757
|
-
import { dirname as dirname11, join as
|
|
10792
|
+
import { homedir as homedir8 } from "os";
|
|
10793
|
+
import { dirname as dirname11, join as join25 } from "path";
|
|
10758
10794
|
import { delimiter as delimiter2 } from "path";
|
|
10759
10795
|
var CHOICE_EXIT_CODE = 3;
|
|
10760
10796
|
function createEmitter(json, write) {
|
|
@@ -10793,17 +10829,17 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
|
|
|
10793
10829
|
const dirs = [
|
|
10794
10830
|
...pathEnv.split(delimiter2),
|
|
10795
10831
|
...(extraPath ?? "").split(delimiter2),
|
|
10796
|
-
|
|
10797
|
-
|
|
10832
|
+
join25(home, ".local/bin"),
|
|
10833
|
+
join25(home, ".claude/local"),
|
|
10798
10834
|
"/opt/homebrew/bin",
|
|
10799
10835
|
"/usr/local/bin",
|
|
10800
|
-
|
|
10801
|
-
|
|
10836
|
+
join25(home, ".npm-global/bin"),
|
|
10837
|
+
join25(home, ".volta/bin")
|
|
10802
10838
|
];
|
|
10803
10839
|
return [...new Set(dirs.filter(Boolean))];
|
|
10804
10840
|
}
|
|
10805
10841
|
function findAgentBin(name, env, extraPath, isExec) {
|
|
10806
|
-
const searched = agentBinSearchDirs(env.home ??
|
|
10842
|
+
const searched = agentBinSearchDirs(env.home ?? homedir8(), env.pathEnv, extraPath);
|
|
10807
10843
|
return { bin: findOnPath2(name, searched.join(delimiter2), isExec), searched };
|
|
10808
10844
|
}
|
|
10809
10845
|
function flagWizardAnswers(f, env) {
|
|
@@ -10834,8 +10870,8 @@ function flagWizardAnswers(f, env) {
|
|
|
10834
10870
|
}
|
|
10835
10871
|
|
|
10836
10872
|
// src/release-run.ts
|
|
10837
|
-
import { readFileSync as
|
|
10838
|
-
import { dirname as dirname12, join as
|
|
10873
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync16, existsSync as existsSync22, copyFileSync, mkdirSync as mkdirSync14, mkdtempSync as mkdtempSync2, rmSync as rmSync12 } from "fs";
|
|
10874
|
+
import { dirname as dirname12, join as join26 } from "path";
|
|
10839
10875
|
import { tmpdir as tmpdir3 } from "os";
|
|
10840
10876
|
|
|
10841
10877
|
// src/release.ts
|
|
@@ -11553,13 +11589,13 @@ async function bump(o, size) {
|
|
|
11553
11589
|
}
|
|
11554
11590
|
return printed;
|
|
11555
11591
|
}
|
|
11556
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
11592
|
+
const files = o.repo.release.versionFiles.map((f) => join26(o.cwd, f));
|
|
11557
11593
|
const first = files[0];
|
|
11558
11594
|
if (!existsSync22(first)) {
|
|
11559
11595
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
11560
11596
|
return null;
|
|
11561
11597
|
}
|
|
11562
|
-
const current = JSON.parse(
|
|
11598
|
+
const current = JSON.parse(readFileSync20(first, "utf8")).version;
|
|
11563
11599
|
if (!current) {
|
|
11564
11600
|
o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
|
|
11565
11601
|
return null;
|
|
@@ -11572,7 +11608,7 @@ async function bump(o, size) {
|
|
|
11572
11608
|
return version;
|
|
11573
11609
|
}
|
|
11574
11610
|
for (const f of files) {
|
|
11575
|
-
const src =
|
|
11611
|
+
const src = readFileSync20(f, "utf8");
|
|
11576
11612
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
11577
11613
|
if (out === src) {
|
|
11578
11614
|
o.emit.error(`no version field in ${f}`);
|
|
@@ -11585,13 +11621,13 @@ async function bump(o, size) {
|
|
|
11585
11621
|
function writeChangelog(o, version, entries) {
|
|
11586
11622
|
const file = o.repo.release.changelog;
|
|
11587
11623
|
if (!file || entries.length === 0) return;
|
|
11588
|
-
const path =
|
|
11624
|
+
const path = join26(o.cwd, file);
|
|
11589
11625
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
11590
11626
|
if (o.dryRun) {
|
|
11591
11627
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
11592
11628
|
return;
|
|
11593
11629
|
}
|
|
11594
|
-
const existing = existsSync22(path) ?
|
|
11630
|
+
const existing = existsSync22(path) ? readFileSync20(path, "utf8") : "# Changelog\n";
|
|
11595
11631
|
writeFileSync16(path, insertChangelogSection(existing, section));
|
|
11596
11632
|
}
|
|
11597
11633
|
async function confirm(o, expected) {
|
|
@@ -11685,8 +11721,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run2 = gitOk) {
|
|
|
11685
11721
|
function openReleaseCheckout(o) {
|
|
11686
11722
|
const { remote, base } = o.repo.branch;
|
|
11687
11723
|
const branch = `crew/release-${Date.now()}`;
|
|
11688
|
-
const root = mkdtempSync2(
|
|
11689
|
-
const dir =
|
|
11724
|
+
const root = mkdtempSync2(join26(tmpdir3(), "crew-release-"));
|
|
11725
|
+
const dir = join26(root, "checkout");
|
|
11690
11726
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
11691
11727
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
11692
11728
|
try {
|
|
@@ -11696,10 +11732,10 @@ function openReleaseCheckout(o) {
|
|
|
11696
11732
|
throw e;
|
|
11697
11733
|
}
|
|
11698
11734
|
for (const f of o.repo.worktrees.copy) {
|
|
11699
|
-
const src =
|
|
11735
|
+
const src = join26(o.cwd, f);
|
|
11700
11736
|
if (!existsSync22(src)) continue;
|
|
11701
|
-
mkdirSync14(dirname12(
|
|
11702
|
-
copyFileSync(src,
|
|
11737
|
+
mkdirSync14(dirname12(join26(dir, f)), { recursive: true });
|
|
11738
|
+
copyFileSync(src, join26(dir, f));
|
|
11703
11739
|
}
|
|
11704
11740
|
return { dir, branch, close };
|
|
11705
11741
|
}
|
|
@@ -12780,17 +12816,17 @@ function nextRoles(decision, ran) {
|
|
|
12780
12816
|
}
|
|
12781
12817
|
|
|
12782
12818
|
// src/ship-name.ts
|
|
12783
|
-
import { existsSync as existsSync25, mkdirSync as mkdirSync16, readFileSync as
|
|
12819
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync16, readFileSync as readFileSync22, renameSync as renameSync2, writeFileSync as writeFileSync18 } from "fs";
|
|
12784
12820
|
import { dirname as dirname14 } from "path";
|
|
12785
12821
|
import { parseDocument } from "yaml";
|
|
12786
12822
|
|
|
12787
12823
|
// src/machine-id.ts
|
|
12788
12824
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
12789
12825
|
import { createHash as createHash4 } from "crypto";
|
|
12790
|
-
import { existsSync as existsSync24, mkdirSync as mkdirSync15, readFileSync as
|
|
12791
|
-
import { dirname as dirname13, join as
|
|
12826
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync15, readFileSync as readFileSync21, writeFileSync as writeFileSync17 } from "fs";
|
|
12827
|
+
import { dirname as dirname13, join as join27 } from "path";
|
|
12792
12828
|
function machineIdPathFor(stateDir) {
|
|
12793
|
-
return
|
|
12829
|
+
return join27(stateDir, "machine-id");
|
|
12794
12830
|
}
|
|
12795
12831
|
var sha256 = (s) => createHash4("sha256").update(s).digest("hex");
|
|
12796
12832
|
function readRawMachineId(platform2 = process.platform) {
|
|
@@ -12804,7 +12840,7 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
12804
12840
|
}
|
|
12805
12841
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
12806
12842
|
if (existsSync24(p)) {
|
|
12807
|
-
const id =
|
|
12843
|
+
const id = readFileSync21(p, "utf8").trim();
|
|
12808
12844
|
if (id) return id;
|
|
12809
12845
|
}
|
|
12810
12846
|
}
|
|
@@ -12816,7 +12852,7 @@ function ensureMachineId(stateDir, opts = {}) {
|
|
|
12816
12852
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
12817
12853
|
const path = machineIdPathFor(stateDir);
|
|
12818
12854
|
if (existsSync24(path)) {
|
|
12819
|
-
const cached =
|
|
12855
|
+
const cached = readFileSync21(path, "utf8").trim();
|
|
12820
12856
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
12821
12857
|
}
|
|
12822
12858
|
const raw = (opts.read ?? readRawMachineId)();
|
|
@@ -12896,7 +12932,7 @@ async function syncRouteName(i) {
|
|
|
12896
12932
|
function readSyncedName(resolvedFile) {
|
|
12897
12933
|
if (!existsSync25(resolvedFile)) return void 0;
|
|
12898
12934
|
try {
|
|
12899
|
-
const v = JSON.parse(
|
|
12935
|
+
const v = JSON.parse(readFileSync22(resolvedFile, "utf8")).shipName;
|
|
12900
12936
|
return typeof v === "string" ? v : void 0;
|
|
12901
12937
|
} catch {
|
|
12902
12938
|
return void 0;
|
|
@@ -12906,7 +12942,7 @@ function writeSyncedName(resolvedFile, name) {
|
|
|
12906
12942
|
if (!existsSync25(resolvedFile)) return;
|
|
12907
12943
|
let raw;
|
|
12908
12944
|
try {
|
|
12909
|
-
raw = JSON.parse(
|
|
12945
|
+
raw = JSON.parse(readFileSync22(resolvedFile, "utf8"));
|
|
12910
12946
|
} catch {
|
|
12911
12947
|
return;
|
|
12912
12948
|
}
|
|
@@ -12921,7 +12957,7 @@ function writeSyncedName(resolvedFile, name) {
|
|
|
12921
12957
|
async function reconcileShipName(i) {
|
|
12922
12958
|
let local = i.ship.name;
|
|
12923
12959
|
try {
|
|
12924
|
-
local = shipNameOfYaml(
|
|
12960
|
+
local = shipNameOfYaml(readFileSync22(i.configFile, "utf8")) ?? local;
|
|
12925
12961
|
} catch {
|
|
12926
12962
|
}
|
|
12927
12963
|
const resolvedFile = resolvedPathFor(i.stateDir, i.route);
|
|
@@ -12945,7 +12981,7 @@ async function reconcileShipName(i) {
|
|
|
12945
12981
|
}
|
|
12946
12982
|
if (result.status !== "ok") return result;
|
|
12947
12983
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
12948
|
-
const text =
|
|
12984
|
+
const text = readFileSync22(i.configFile, "utf8");
|
|
12949
12985
|
writeFileSync18(i.configFile, setShipNameInYaml(text, result.name));
|
|
12950
12986
|
i.ship.name = result.name;
|
|
12951
12987
|
if (result.action === "row-wins") {
|
|
@@ -13854,7 +13890,7 @@ function describeCapability(i) {
|
|
|
13854
13890
|
}
|
|
13855
13891
|
|
|
13856
13892
|
// src/agents-sync-run.ts
|
|
13857
|
-
import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as
|
|
13893
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync23, writeFileSync as writeFileSync19 } from "fs";
|
|
13858
13894
|
import { dirname as dirname15 } from "path";
|
|
13859
13895
|
async function runAgentsSync(target, o) {
|
|
13860
13896
|
const dryRun = o.dryRun ?? false;
|
|
@@ -13888,7 +13924,7 @@ async function runAgentsSync(target, o) {
|
|
|
13888
13924
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
13889
13925
|
if (!dryRun) {
|
|
13890
13926
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
13891
|
-
const raw = existsSync26(resolvedPath) ? JSON.parse(
|
|
13927
|
+
const raw = existsSync26(resolvedPath) ? JSON.parse(readFileSync23(resolvedPath, "utf8")) : {};
|
|
13892
13928
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
13893
13929
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
13894
13930
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
@@ -13999,8 +14035,8 @@ async function showLogEntry(route, entryId, opts = {}) {
|
|
|
13999
14035
|
import { isMap as isMap2, parseDocument as parseDocument3, YAMLMap } from "yaml";
|
|
14000
14036
|
|
|
14001
14037
|
// src/repos-cmd.ts
|
|
14002
|
-
import { homedir as
|
|
14003
|
-
import { basename as basename8, join as
|
|
14038
|
+
import { homedir as homedir9 } from "os";
|
|
14039
|
+
import { basename as basename8, join as join28, resolve as resolve3 } from "path";
|
|
14004
14040
|
import { isMap, isSeq, parseDocument as parseDocument2, stringify as stringify2 } from "yaml";
|
|
14005
14041
|
var ReposError = class extends Error {
|
|
14006
14042
|
};
|
|
@@ -14046,7 +14082,7 @@ function parseAddArgs(args, routes, isDir) {
|
|
|
14046
14082
|
return { route, path: "." };
|
|
14047
14083
|
}
|
|
14048
14084
|
function expandDir(p, base) {
|
|
14049
|
-
const home = p === "~" ?
|
|
14085
|
+
const home = p === "~" ? homedir9() : p.startsWith("~/") ? `${homedir9()}/${p.slice(2)}` : p;
|
|
14050
14086
|
return resolve3(base, home);
|
|
14051
14087
|
}
|
|
14052
14088
|
function routeMapOf(doc, route) {
|
|
@@ -14278,7 +14314,7 @@ function planRepoClone(i) {
|
|
|
14278
14314
|
}
|
|
14279
14315
|
const ship = doc.get("ship", true);
|
|
14280
14316
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
14281
|
-
const target = i.dir ? expandDir(i.dir, i.base) :
|
|
14317
|
+
const target = i.dir ? expandDir(i.dir, i.base) : join28(expandDir(basePath, i.base), i.route.split("/")[0], name);
|
|
14282
14318
|
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
|
|
14283
14319
|
return { ...add, remote, target };
|
|
14284
14320
|
}
|
|
@@ -14677,7 +14713,7 @@ ${run2.err}` });
|
|
|
14677
14713
|
}
|
|
14678
14714
|
|
|
14679
14715
|
// src/sample-ticket.ts
|
|
14680
|
-
import { basename as basename9, dirname as dirname17, join as
|
|
14716
|
+
import { basename as basename9, dirname as dirname17, join as join29 } from "path";
|
|
14681
14717
|
var SAMPLE_REPO = "crew-sample";
|
|
14682
14718
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
14683
14719
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -14700,8 +14736,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
14700
14736
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
14701
14737
|
`;
|
|
14702
14738
|
function sampleDir(existing, reposBase, workspace) {
|
|
14703
|
-
if (existing.length > 0) return
|
|
14704
|
-
return
|
|
14739
|
+
if (existing.length > 0) return join29(dirname17(existing[0]), SAMPLE_REPO);
|
|
14740
|
+
return join29(reposBase, workspace, SAMPLE_REPO);
|
|
14705
14741
|
}
|
|
14706
14742
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
14707
14743
|
function sampleDescription() {
|
|
@@ -14793,8 +14829,8 @@ function schedulerInstalled(plan, exists, crontab) {
|
|
|
14793
14829
|
}
|
|
14794
14830
|
|
|
14795
14831
|
// src/worktree-sweep.ts
|
|
14796
|
-
import { existsSync as existsSync27, readFileSync as
|
|
14797
|
-
import { basename as basename10, dirname as dirname18, join as
|
|
14832
|
+
import { existsSync as existsSync27, readFileSync as readFileSync24, readdirSync as readdirSync8 } from "fs";
|
|
14833
|
+
import { basename as basename10, dirname as dirname18, join as join30, resolve as resolve4 } from "path";
|
|
14798
14834
|
function planWorktreeSweep(target, tickets, contract) {
|
|
14799
14835
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
14800
14836
|
const verified = contract.statuses.verified;
|
|
@@ -14861,7 +14897,7 @@ function siblingWorktreeDirs(target) {
|
|
|
14861
14897
|
for (const name of names) {
|
|
14862
14898
|
if (!name.startsWith(stem)) continue;
|
|
14863
14899
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
14864
|
-
const path =
|
|
14900
|
+
const path = join30(parent, name);
|
|
14865
14901
|
if (m && !registered.has(resolve4(path))) out.push({ path, number: m[1] });
|
|
14866
14902
|
}
|
|
14867
14903
|
return out;
|
|
@@ -14964,7 +15000,7 @@ function isHandBackMerge(path, base) {
|
|
|
14964
15000
|
if (!msgPath) return false;
|
|
14965
15001
|
let first;
|
|
14966
15002
|
try {
|
|
14967
|
-
first =
|
|
15003
|
+
first = readFileSync24(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
|
|
14968
15004
|
} catch {
|
|
14969
15005
|
return false;
|
|
14970
15006
|
}
|
|
@@ -15070,13 +15106,13 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
15070
15106
|
|
|
15071
15107
|
// src/stream-sweep.ts
|
|
15072
15108
|
import { existsSync as existsSync28, readdirSync as readdirSync9, rmSync as rmSync13, statSync as statSync8 } from "fs";
|
|
15073
|
-
import { join as
|
|
15109
|
+
import { join as join31 } from "path";
|
|
15074
15110
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
15075
15111
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
15076
|
-
const dir =
|
|
15112
|
+
const dir = join31(stateDir, "streams");
|
|
15077
15113
|
if (!existsSync28(dir)) return [];
|
|
15078
15114
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
15079
|
-
return readdirSync9(dir).map((name) =>
|
|
15115
|
+
return readdirSync9(dir).map((name) => join31(dir, name)).filter((path) => {
|
|
15080
15116
|
try {
|
|
15081
15117
|
return statSync8(path).mtimeMs < cutoff;
|
|
15082
15118
|
} catch {
|
|
@@ -15104,7 +15140,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
15104
15140
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
15105
15141
|
if (!existsSync28(stateDir)) return [];
|
|
15106
15142
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
15107
|
-
return readdirSync9(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) =>
|
|
15143
|
+
return readdirSync9(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join31(stateDir, name)).filter((path) => {
|
|
15108
15144
|
try {
|
|
15109
15145
|
return statSync8(path).mtimeMs < cutoff;
|
|
15110
15146
|
} catch {
|
|
@@ -15114,18 +15150,18 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
15114
15150
|
}
|
|
15115
15151
|
|
|
15116
15152
|
// src/cli.ts
|
|
15117
|
-
import { realpathSync as realpathSync3, readFileSync as
|
|
15153
|
+
import { realpathSync as realpathSync3, readFileSync as readFileSync30, existsSync as existsSync34, mkdirSync as mkdirSync22, writeFileSync as writeFileSync23, rmSync as rmSync15, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync3, statSync as statSync10, readdirSync as readdirSync14 } from "fs";
|
|
15118
15154
|
import { createInterface } from "readline/promises";
|
|
15119
15155
|
import { dirname as dirOf, resolve as resolvePath } from "path";
|
|
15120
15156
|
|
|
15121
15157
|
// src/ssh-keys.ts
|
|
15122
15158
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
15123
|
-
import { existsSync as existsSync29, mkdirSync as mkdirSync18, readFileSync as
|
|
15124
|
-
import { dirname as dirname19, join as
|
|
15159
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync18, readFileSync as readFileSync25, chmodSync } from "fs";
|
|
15160
|
+
import { dirname as dirname19, join as join32 } from "path";
|
|
15125
15161
|
var SshKeyError = class extends Error {
|
|
15126
15162
|
};
|
|
15127
15163
|
function shipSshKeyPathFor(stateDir) {
|
|
15128
|
-
return
|
|
15164
|
+
return join32(stateDir, "ssh", "id_ed25519");
|
|
15129
15165
|
}
|
|
15130
15166
|
function ensureShipSshKeypair(stateDir) {
|
|
15131
15167
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
@@ -15154,7 +15190,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
15154
15190
|
if (!existsSync29(publicKeyPath)) {
|
|
15155
15191
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
15156
15192
|
}
|
|
15157
|
-
const publicKey =
|
|
15193
|
+
const publicKey = readFileSync25(publicKeyPath, "utf8").trim();
|
|
15158
15194
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
15159
15195
|
}
|
|
15160
15196
|
function sshKeygenAvailable() {
|
|
@@ -15370,9 +15406,9 @@ function syncAllPassengerCheckouts(cfg) {
|
|
|
15370
15406
|
|
|
15371
15407
|
// src/tunnel.ts
|
|
15372
15408
|
import { spawn as spawn5 } from "child_process";
|
|
15373
|
-
import { existsSync as existsSync30, mkdirSync as mkdirSync19, readFileSync as
|
|
15409
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync19, readFileSync as readFileSync26, writeFileSync as writeFileSync20, unlinkSync as unlinkSync3, readdirSync as readdirSync10 } from "fs";
|
|
15374
15410
|
import { randomInt } from "crypto";
|
|
15375
|
-
import { join as
|
|
15411
|
+
import { join as join33 } from "path";
|
|
15376
15412
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
15377
15413
|
var defaultSpawn = (cmd, args) => spawn5(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
15378
15414
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -15439,20 +15475,20 @@ function startTunnel(opts) {
|
|
|
15439
15475
|
};
|
|
15440
15476
|
}
|
|
15441
15477
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
15442
|
-
return
|
|
15478
|
+
return join33(stateDir, "passengers", `${workspaceId}.json`);
|
|
15443
15479
|
}
|
|
15444
15480
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
15445
15481
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
15446
15482
|
if (!existsSync30(path)) return void 0;
|
|
15447
15483
|
try {
|
|
15448
|
-
return JSON.parse(
|
|
15484
|
+
return JSON.parse(readFileSync26(path, "utf8"));
|
|
15449
15485
|
} catch {
|
|
15450
15486
|
return void 0;
|
|
15451
15487
|
}
|
|
15452
15488
|
}
|
|
15453
15489
|
function writePersistedTunnel(stateDir, state) {
|
|
15454
15490
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
15455
|
-
mkdirSync19(
|
|
15491
|
+
mkdirSync19(join33(stateDir, "passengers"), { recursive: true });
|
|
15456
15492
|
writeFileSync20(path, `${JSON.stringify(state, null, 2)}
|
|
15457
15493
|
`);
|
|
15458
15494
|
}
|
|
@@ -15550,7 +15586,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
15550
15586
|
}
|
|
15551
15587
|
}
|
|
15552
15588
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
15553
|
-
const dir =
|
|
15589
|
+
const dir = join33(stateDir, "passengers");
|
|
15554
15590
|
if (!existsSync30(dir)) return [];
|
|
15555
15591
|
return readdirSync10(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
15556
15592
|
}
|
|
@@ -15672,8 +15708,8 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
15672
15708
|
import { createServer as createHttpServer } from "http";
|
|
15673
15709
|
import { spawn as spawn6 } from "child_process";
|
|
15674
15710
|
import { randomBytes } from "crypto";
|
|
15675
|
-
import { existsSync as existsSync31, mkdirSync as mkdirSync20, readFileSync as
|
|
15676
|
-
import { join as
|
|
15711
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync20, readFileSync as readFileSync27, writeFileSync as writeFileSync21, unlinkSync as unlinkSync4, readdirSync as readdirSync11 } from "fs";
|
|
15712
|
+
import { join as join34 } from "path";
|
|
15677
15713
|
|
|
15678
15714
|
// src/secret-compare.ts
|
|
15679
15715
|
import { timingSafeEqual } from "crypto";
|
|
@@ -15762,20 +15798,20 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
15762
15798
|
return child;
|
|
15763
15799
|
};
|
|
15764
15800
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
15765
|
-
return
|
|
15801
|
+
return join34(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
15766
15802
|
}
|
|
15767
15803
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
15768
15804
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
15769
15805
|
if (!existsSync31(path)) return void 0;
|
|
15770
15806
|
try {
|
|
15771
|
-
return JSON.parse(
|
|
15807
|
+
return JSON.parse(readFileSync27(path, "utf8"));
|
|
15772
15808
|
} catch {
|
|
15773
15809
|
return void 0;
|
|
15774
15810
|
}
|
|
15775
15811
|
}
|
|
15776
15812
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
15777
15813
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
15778
|
-
mkdirSync20(
|
|
15814
|
+
mkdirSync20(join34(stateDir, "passengers"), { recursive: true });
|
|
15779
15815
|
writeFileSync21(path, `${JSON.stringify(state, null, 2)}
|
|
15780
15816
|
`);
|
|
15781
15817
|
}
|
|
@@ -15796,7 +15832,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
15796
15832
|
}
|
|
15797
15833
|
}
|
|
15798
15834
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
15799
|
-
const dir =
|
|
15835
|
+
const dir = join34(stateDir, "passengers");
|
|
15800
15836
|
if (!existsSync31(dir)) return [];
|
|
15801
15837
|
const suffix = "-sync.json";
|
|
15802
15838
|
return readdirSync11(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
@@ -15885,12 +15921,12 @@ function describeCause(err) {
|
|
|
15885
15921
|
}
|
|
15886
15922
|
|
|
15887
15923
|
// src/daemon.ts
|
|
15888
|
-
import { existsSync as existsSync33, mkdirSync as mkdirSync21, readFileSync as
|
|
15889
|
-
import { join as
|
|
15924
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync21, readFileSync as readFileSync29, readdirSync as readdirSync13, rmSync as rmSync14, statSync as statSync9, writeFileSync as writeFileSync22 } from "fs";
|
|
15925
|
+
import { join as join36 } from "path";
|
|
15890
15926
|
|
|
15891
15927
|
// src/interrupted.ts
|
|
15892
|
-
import { existsSync as existsSync32, readdirSync as readdirSync12, readFileSync as
|
|
15893
|
-
import { join as
|
|
15928
|
+
import { existsSync as existsSync32, readdirSync as readdirSync12, readFileSync as readFileSync28 } from "fs";
|
|
15929
|
+
import { join as join35 } from "path";
|
|
15894
15930
|
var INTERRUPTED_MARKER = calloutHeader("NOTE", "Interrupted");
|
|
15895
15931
|
function interruptedComment(b) {
|
|
15896
15932
|
const tree = b.dirty === null ? "The worktree could not be checked." : b.dirty ? "The worktree has uncommitted changes \u2014 they are this seat's own, from that run." : "The worktree is clean.";
|
|
@@ -15911,19 +15947,19 @@ function runFinished(streamPath) {
|
|
|
15911
15947
|
const path = `${streamPath}.meta.json`;
|
|
15912
15948
|
if (!existsSync32(path)) return false;
|
|
15913
15949
|
try {
|
|
15914
|
-
return JSON.parse(
|
|
15950
|
+
return JSON.parse(readFileSync28(path, "utf8")).running !== true;
|
|
15915
15951
|
} catch {
|
|
15916
15952
|
return true;
|
|
15917
15953
|
}
|
|
15918
15954
|
}
|
|
15919
15955
|
function orphanedStreams(o) {
|
|
15920
|
-
const dir =
|
|
15956
|
+
const dir = join35(o.stateDir, "streams");
|
|
15921
15957
|
if (!existsSync32(dir)) return [];
|
|
15922
15958
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
15923
15959
|
const out = [];
|
|
15924
15960
|
for (const name of readdirSync12(dir)) {
|
|
15925
15961
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
15926
|
-
const streamPath =
|
|
15962
|
+
const streamPath = join35(dir, name);
|
|
15927
15963
|
if (runFinished(streamPath)) continue;
|
|
15928
15964
|
const sidecar = readRunSidecar(streamPath);
|
|
15929
15965
|
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
@@ -16043,7 +16079,7 @@ function latestMtime(path) {
|
|
|
16043
16079
|
if (!stat.isDirectory()) return 0;
|
|
16044
16080
|
let latest = stat.mtimeMs;
|
|
16045
16081
|
for (const entry of readdirSync13(path, { withFileTypes: true })) {
|
|
16046
|
-
latest = Math.max(latest, latestMtime(
|
|
16082
|
+
latest = Math.max(latest, latestMtime(join36(path, entry.name)));
|
|
16047
16083
|
}
|
|
16048
16084
|
return latest;
|
|
16049
16085
|
}
|
|
@@ -16055,10 +16091,10 @@ function makeStaleChecker(paths, opts = {}) {
|
|
|
16055
16091
|
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync33(opts.reloadSentinel);
|
|
16056
16092
|
}
|
|
16057
16093
|
function reloadSentinelFor(stateDir) {
|
|
16058
|
-
return
|
|
16094
|
+
return join36(stateDir, "reload-requested");
|
|
16059
16095
|
}
|
|
16060
16096
|
function daemonStampFor(stateDir) {
|
|
16061
|
-
return
|
|
16097
|
+
return join36(stateDir, "daemon.started");
|
|
16062
16098
|
}
|
|
16063
16099
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
16064
16100
|
mkdirSync21(stateDir, { recursive: true });
|
|
@@ -16068,7 +16104,7 @@ function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
|
16068
16104
|
}
|
|
16069
16105
|
function readDaemonStarted(stateDir) {
|
|
16070
16106
|
try {
|
|
16071
|
-
const v = JSON.parse(
|
|
16107
|
+
const v = JSON.parse(readFileSync29(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
16072
16108
|
return typeof v === "number" ? v : null;
|
|
16073
16109
|
} catch {
|
|
16074
16110
|
return null;
|
|
@@ -17089,7 +17125,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
17089
17125
|
}
|
|
17090
17126
|
if (host === "linux" && hasSystemd) {
|
|
17091
17127
|
const label = labelFor(CREW_HOME, "run");
|
|
17092
|
-
const staleTimer =
|
|
17128
|
+
const staleTimer = join37(homedir10(), ".config", "systemd", "user", `${label}.timer`);
|
|
17093
17129
|
if (existsSync34(staleTimer)) {
|
|
17094
17130
|
process.stdout.write(
|
|
17095
17131
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
@@ -17152,7 +17188,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
17152
17188
|
for (const a of plan.actions) {
|
|
17153
17189
|
if (a.kind === "enable") {
|
|
17154
17190
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
17155
|
-
writeFileSync23(cfg.configFile, planEnable(
|
|
17191
|
+
writeFileSync23(cfg.configFile, planEnable(readFileSync30(cfg.configFile, "utf8"), a.route));
|
|
17156
17192
|
enabledSound.add(a.route);
|
|
17157
17193
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
17158
17194
|
} else {
|
|
@@ -18174,7 +18210,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18174
18210
|
process.exit(1);
|
|
18175
18211
|
}
|
|
18176
18212
|
requireArmed("run the daemon loop");
|
|
18177
|
-
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [
|
|
18213
|
+
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join37(CREW_HOME, "src"), join37(CREW_HOME, "dist"), join37(CREW_HOME, "bin")];
|
|
18178
18214
|
const configPaths = daemonConfigPaths(cfg);
|
|
18179
18215
|
watchPaths.push(...configPaths);
|
|
18180
18216
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -18393,7 +18429,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18393
18429
|
`);
|
|
18394
18430
|
if (!dryRun) {
|
|
18395
18431
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
18396
|
-
const raw = existsSync34(resolvedPath) ? JSON.parse(
|
|
18432
|
+
const raw = existsSync34(resolvedPath) ? JSON.parse(readFileSync30(resolvedPath, "utf8")) : {};
|
|
18397
18433
|
raw.agentSkills = result.agentSkills;
|
|
18398
18434
|
mkdirSync22(dirOf(resolvedPath), { recursive: true });
|
|
18399
18435
|
writeFileSync23(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
@@ -18419,7 +18455,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18419
18455
|
throw e;
|
|
18420
18456
|
}
|
|
18421
18457
|
const oldName = cfg.ship.name;
|
|
18422
|
-
writeFileSync23(cfg.configFile, setShipNameInYaml(
|
|
18458
|
+
writeFileSync23(cfg.configFile, setShipNameInYaml(readFileSync30(cfg.configFile, "utf8"), newName));
|
|
18423
18459
|
cfg.ship.name = newName;
|
|
18424
18460
|
let renameMachineId;
|
|
18425
18461
|
try {
|
|
@@ -18466,7 +18502,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18466
18502
|
process.exit(2);
|
|
18467
18503
|
};
|
|
18468
18504
|
if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks") usage2();
|
|
18469
|
-
const configText =
|
|
18505
|
+
const configText = readFileSync30(cfg.configFile, "utf8");
|
|
18470
18506
|
if (sub === "hooks") {
|
|
18471
18507
|
try {
|
|
18472
18508
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
@@ -18480,7 +18516,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18480
18516
|
}
|
|
18481
18517
|
}, read: (r) => {
|
|
18482
18518
|
try {
|
|
18483
|
-
return
|
|
18519
|
+
return readFileSync30(resolvePath(dir, r), "utf8");
|
|
18484
18520
|
} catch {
|
|
18485
18521
|
return void 0;
|
|
18486
18522
|
}
|
|
@@ -18590,7 +18626,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18590
18626
|
if (sub === "list" && !routeName) usage2();
|
|
18591
18627
|
if (sub === "add") {
|
|
18592
18628
|
try {
|
|
18593
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync34(resolvePath(p.replace(/^~(?=\/|$)/,
|
|
18629
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync34(resolvePath(p.replace(/^~(?=\/|$)/, homedir10()))));
|
|
18594
18630
|
routeName = a.route;
|
|
18595
18631
|
addPath = a.path;
|
|
18596
18632
|
addName = a.name;
|
|
@@ -18621,7 +18657,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18621
18657
|
let resolvedRemotes;
|
|
18622
18658
|
if (existsSync34(resolvedFile)) {
|
|
18623
18659
|
try {
|
|
18624
|
-
resolvedRemotes = JSON.parse(
|
|
18660
|
+
resolvedRemotes = JSON.parse(readFileSync30(resolvedFile, "utf8"));
|
|
18625
18661
|
} catch {
|
|
18626
18662
|
}
|
|
18627
18663
|
}
|
|
@@ -18642,7 +18678,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18642
18678
|
isCheckout,
|
|
18643
18679
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
18644
18680
|
try {
|
|
18645
|
-
return Object.keys(parseRepoConfig(
|
|
18681
|
+
return Object.keys(parseRepoConfig(readFileSync30(resolvePath(d, f), "utf8"), f).hooks).length > 0;
|
|
18646
18682
|
} catch {
|
|
18647
18683
|
return false;
|
|
18648
18684
|
}
|
|
@@ -18697,7 +18733,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18697
18733
|
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}`);
|
|
18698
18734
|
break;
|
|
18699
18735
|
}
|
|
18700
|
-
const target = resolvePath(addPath.replace(/^~(?=\/|$)/,
|
|
18736
|
+
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir10()));
|
|
18701
18737
|
const top = existsSync34(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
18702
18738
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
18703
18739
|
const origin = originOf(top);
|
|
@@ -18751,7 +18787,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18751
18787
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
18752
18788
|
`);
|
|
18753
18789
|
if (existsSync34(resolvedFile)) {
|
|
18754
|
-
const raw = JSON.parse(
|
|
18790
|
+
const raw = JSON.parse(readFileSync30(resolvedFile, "utf8"));
|
|
18755
18791
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
18756
18792
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
18757
18793
|
writeFileSync23(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
@@ -19109,7 +19145,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19109
19145
|
process.exit(2);
|
|
19110
19146
|
}
|
|
19111
19147
|
if (found.shipNameAdopted && !dryRun) {
|
|
19112
|
-
writeFileSync23(cfg.configFile, setShipNameInYaml(
|
|
19148
|
+
writeFileSync23(cfg.configFile, setShipNameInYaml(readFileSync30(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
19113
19149
|
cfg.ship.name = found.shipNameAdopted;
|
|
19114
19150
|
}
|
|
19115
19151
|
if (found.shipRow) {
|
|
@@ -19155,7 +19191,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19155
19191
|
const hadPreviousFile = existsSync34(resolvedPath);
|
|
19156
19192
|
if (hadPreviousFile) {
|
|
19157
19193
|
try {
|
|
19158
|
-
previous = JSON.parse(
|
|
19194
|
+
previous = JSON.parse(readFileSync30(resolvedPath, "utf8"));
|
|
19159
19195
|
} catch {
|
|
19160
19196
|
}
|
|
19161
19197
|
}
|
|
@@ -20013,7 +20049,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
20013
20049
|
};
|
|
20014
20050
|
const droppedRoutes = [];
|
|
20015
20051
|
try {
|
|
20016
|
-
const rawCfg = parseYaml2(
|
|
20052
|
+
const rawCfg = parseYaml2(readFileSync30(cfg.configFile, "utf8"));
|
|
20017
20053
|
for (const r of rawCfg?.routes ?? []) {
|
|
20018
20054
|
if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
|
|
20019
20055
|
try {
|
|
@@ -20107,7 +20143,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
20107
20143
|
}
|
|
20108
20144
|
case "log": {
|
|
20109
20145
|
try {
|
|
20110
|
-
const text =
|
|
20146
|
+
const text = readFileSync30(cfg.ship.logFile, "utf8").split("\n");
|
|
20111
20147
|
process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
20112
20148
|
`);
|
|
20113
20149
|
} catch {
|
|
@@ -20130,7 +20166,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
20130
20166
|
};
|
|
20131
20167
|
if (!route.resolved) fail(`route ${route.route} is not connected \u2014 run \`crew connect\` first`);
|
|
20132
20168
|
const tracker = new Tracker(route, cfg.ship);
|
|
20133
|
-
const configText =
|
|
20169
|
+
const configText = readFileSync30(cfg.configFile, "utf8");
|
|
20134
20170
|
const cfgBase = dirname20(cfg.configFile);
|
|
20135
20171
|
const configured = configuredRepos(configText, route.route, cfgBase);
|
|
20136
20172
|
const dir = configured.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured.map((r) => r.dir), route.reposBasePath, route.route.split("/")[0]);
|
|
@@ -20168,19 +20204,19 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
20168
20204
|
if (row) {
|
|
20169
20205
|
await tracker.deleteRepoRow(row.id);
|
|
20170
20206
|
if (existsSync34(resolvedFile)) {
|
|
20171
|
-
const raw = JSON.parse(
|
|
20207
|
+
const raw = JSON.parse(readFileSync30(resolvedFile, "utf8"));
|
|
20172
20208
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
20173
20209
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
20174
20210
|
writeFileSync23(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
20175
20211
|
`);
|
|
20176
20212
|
}
|
|
20177
20213
|
}
|
|
20178
|
-
if (existsSync34(
|
|
20214
|
+
if (existsSync34(join37(dir, ".git")) && basename11(dir) === SAMPLE_REPO) rmSync15(dir, { recursive: true, force: true });
|
|
20179
20215
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
20180
20216
|
break;
|
|
20181
20217
|
}
|
|
20182
20218
|
const projectId = route.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
20183
|
-
const fresh = !existsSync34(
|
|
20219
|
+
const fresh = !existsSync34(join37(dir, ".git"));
|
|
20184
20220
|
if (existsSync34(dir) && fresh && readdirSync14(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
20185
20221
|
if (dryRun) {
|
|
20186
20222
|
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`);
|
|
@@ -20188,8 +20224,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
20188
20224
|
}
|
|
20189
20225
|
if (fresh) {
|
|
20190
20226
|
mkdirSync22(dir, { recursive: true });
|
|
20191
|
-
writeFileSync23(
|
|
20192
|
-
writeFileSync23(
|
|
20227
|
+
writeFileSync23(join37(dir, "README.md"), SAMPLE_README);
|
|
20228
|
+
writeFileSync23(join37(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
20193
20229
|
const g = (...a) => {
|
|
20194
20230
|
const r = spawnSync6("git", a, { cwd: dir, encoding: "utf8" });
|
|
20195
20231
|
if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -20208,7 +20244,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
20208
20244
|
});
|
|
20209
20245
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
20210
20246
|
if (existsSync34(resolvedFile)) {
|
|
20211
|
-
const raw = JSON.parse(
|
|
20247
|
+
const raw = JSON.parse(readFileSync30(resolvedFile, "utf8"));
|
|
20212
20248
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
20213
20249
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
20214
20250
|
writeFileSync23(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
@@ -20574,7 +20610,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
20574
20610
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run");
|
|
20575
20611
|
const abandons = unitAbandonsAgents(unitPlan, (p) => {
|
|
20576
20612
|
try {
|
|
20577
|
-
return
|
|
20613
|
+
return readFileSync30(p, "utf8");
|
|
20578
20614
|
} catch {
|
|
20579
20615
|
return null;
|
|
20580
20616
|
}
|
|
@@ -20962,6 +20998,10 @@ ${foreign.join("\n")}
|
|
|
20962
20998
|
}
|
|
20963
20999
|
for (const l of doctorSelftestLines(readSelftest(cfg.ship.stateDir), shipToolchains(cfg.ship).map((t) => t.name))) {
|
|
20964
21000
|
process.stdout.write(`${l}
|
|
21001
|
+
`);
|
|
21002
|
+
}
|
|
21003
|
+
for (const t of shipToolchains(cfg.ship).filter((t2) => t2.harness === "claude")) {
|
|
21004
|
+
process.stdout.write(`auth: ${t.name}: ${describeClaudeAuth(t)}
|
|
20965
21005
|
`);
|
|
20966
21006
|
}
|
|
20967
21007
|
const interactive = !!(process.stdin.isTTY && process.stdout.isTTY);
|