@tablation/crew 0.71.13 → 0.72.1
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 +478 -213
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5920,6 +5920,12 @@ var Tracker = class {
|
|
|
5920
5920
|
return [];
|
|
5921
5921
|
}
|
|
5922
5922
|
}
|
|
5923
|
+
/** Renames a Ships row (CREW-2495). Throws on a refused write (a member key may not edit the table). */
|
|
5924
|
+
async renameShipRow(rowId, name) {
|
|
5925
|
+
const model = this.route.resolved?.shipsModelId;
|
|
5926
|
+
if (!model) throw new Error("this workspace has no Ships table");
|
|
5927
|
+
await this.client.records.update(model, rowId, { name });
|
|
5928
|
+
}
|
|
5923
5929
|
/**
|
|
5924
5930
|
* This ship's own row on the `Ships` table, matched by name — the same
|
|
5925
5931
|
* exact-string match `doctor` has always used (see the callsign comment
|
|
@@ -8606,7 +8612,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
8606
8612
|
}
|
|
8607
8613
|
|
|
8608
8614
|
// src/cli.ts
|
|
8609
|
-
import { resolve as resolve5, dirname as
|
|
8615
|
+
import { resolve as resolve5, dirname as dirname20, join as join35, basename as basename11 } from "path";
|
|
8610
8616
|
import { fileURLToPath } from "url";
|
|
8611
8617
|
import { hostname as hostname2, homedir as homedir9 } from "os";
|
|
8612
8618
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
@@ -8792,40 +8798,47 @@ function readPointers(stateDir) {
|
|
|
8792
8798
|
try {
|
|
8793
8799
|
const parsed = JSON.parse(readFileSync16(lastSessionFile(stateDir), "utf8"));
|
|
8794
8800
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
8795
|
-
|
|
8801
|
+
const out = {};
|
|
8802
|
+
for (const [host, v] of Object.entries(parsed)) {
|
|
8803
|
+
const slugs = (Array.isArray(v) ? v : [v]).filter((x) => typeof x === "string" && x !== "");
|
|
8804
|
+
if (slugs.length) out[host] = slugs;
|
|
8805
|
+
}
|
|
8806
|
+
return out;
|
|
8796
8807
|
} catch {
|
|
8797
8808
|
return {};
|
|
8798
8809
|
}
|
|
8799
8810
|
}
|
|
8811
|
+
function writePointers(stateDir, pointers) {
|
|
8812
|
+
const file = lastSessionFile(stateDir);
|
|
8813
|
+
mkdirSync10(dirname8(file), { recursive: true });
|
|
8814
|
+
writeFileSync11(file, `${JSON.stringify(pointers, null, 2)}
|
|
8815
|
+
`, { mode: 384 });
|
|
8816
|
+
}
|
|
8800
8817
|
function rememberSession(stateDir, host, workspaceSlug) {
|
|
8801
8818
|
try {
|
|
8802
|
-
const
|
|
8803
|
-
|
|
8804
|
-
writeFileSync11(file, `${JSON.stringify({ ...readPointers(stateDir), [host]: workspaceSlug }, null, 2)}
|
|
8805
|
-
`, { mode: 384 });
|
|
8819
|
+
const all = readPointers(stateDir);
|
|
8820
|
+
writePointers(stateDir, { ...all, [host]: [...(all[host] ?? []).filter((w) => w !== workspaceSlug), workspaceSlug] });
|
|
8806
8821
|
} catch {
|
|
8807
8822
|
}
|
|
8808
8823
|
}
|
|
8809
|
-
async function
|
|
8810
|
-
const
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
} catch {
|
|
8818
|
-
return void 0;
|
|
8824
|
+
async function resumableSessions(o) {
|
|
8825
|
+
const out = [];
|
|
8826
|
+
for (const workspace of readPointers(o.stateDir)[o.host] ?? []) {
|
|
8827
|
+
try {
|
|
8828
|
+
const stored = await o.store.get(o.host, workspace);
|
|
8829
|
+
if (stored?.apiKey && await o.verify(stored.apiKey)) out.push({ workspace, apiKey: stored.apiKey });
|
|
8830
|
+
} catch {
|
|
8831
|
+
}
|
|
8819
8832
|
}
|
|
8833
|
+
return out;
|
|
8820
8834
|
}
|
|
8821
8835
|
function rememberedSessions(stateDir) {
|
|
8822
|
-
return Object.entries(readPointers(stateDir)).
|
|
8836
|
+
return Object.entries(readPointers(stateDir)).flatMap(([host, ws]) => ws.map((workspace) => ({ host, workspace })));
|
|
8823
8837
|
}
|
|
8824
8838
|
function forgetSession(stateDir, host) {
|
|
8825
8839
|
try {
|
|
8826
8840
|
const { [host]: _gone, ...rest } = readPointers(stateDir);
|
|
8827
|
-
if (Object.keys(rest).length)
|
|
8828
|
-
`, { mode: 384 });
|
|
8841
|
+
if (Object.keys(rest).length) writePointers(stateDir, rest);
|
|
8829
8842
|
else rmSync10(lastSessionFile(stateDir), { force: true });
|
|
8830
8843
|
} catch {
|
|
8831
8844
|
}
|
|
@@ -12636,6 +12649,217 @@ function nextRoles(decision, ran) {
|
|
|
12636
12649
|
return decision.selection.pending.filter((r) => !ran.includes(r)).sort((a, b) => (decision.selection.ranks[a] ?? 0) - (decision.selection.ranks[b] ?? 0));
|
|
12637
12650
|
}
|
|
12638
12651
|
|
|
12652
|
+
// src/ship-name.ts
|
|
12653
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync15, readFileSync as readFileSync20, renameSync as renameSync2, writeFileSync as writeFileSync17 } from "fs";
|
|
12654
|
+
import { dirname as dirname14 } from "path";
|
|
12655
|
+
import { parseDocument } from "yaml";
|
|
12656
|
+
|
|
12657
|
+
// src/machine-id.ts
|
|
12658
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
12659
|
+
import { createHash as createHash3 } from "crypto";
|
|
12660
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync14, readFileSync as readFileSync19, writeFileSync as writeFileSync16 } from "fs";
|
|
12661
|
+
import { dirname as dirname13, join as join25 } from "path";
|
|
12662
|
+
function machineIdPathFor(stateDir) {
|
|
12663
|
+
return join25(stateDir, "machine-id");
|
|
12664
|
+
}
|
|
12665
|
+
var sha256 = (s) => createHash3("sha256").update(s).digest("hex");
|
|
12666
|
+
function readRawMachineId(platform2 = process.platform) {
|
|
12667
|
+
try {
|
|
12668
|
+
if (platform2 === "darwin") {
|
|
12669
|
+
const out = execFileSync4("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
|
|
12670
|
+
encoding: "utf8",
|
|
12671
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
12672
|
+
});
|
|
12673
|
+
return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
|
|
12674
|
+
}
|
|
12675
|
+
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
12676
|
+
if (existsSync24(p)) {
|
|
12677
|
+
const id = readFileSync19(p, "utf8").trim();
|
|
12678
|
+
if (id) return id;
|
|
12679
|
+
}
|
|
12680
|
+
}
|
|
12681
|
+
} catch {
|
|
12682
|
+
}
|
|
12683
|
+
return void 0;
|
|
12684
|
+
}
|
|
12685
|
+
function ensureMachineId(stateDir, opts = {}) {
|
|
12686
|
+
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
12687
|
+
const path = machineIdPathFor(stateDir);
|
|
12688
|
+
if (existsSync24(path)) {
|
|
12689
|
+
const cached = readFileSync19(path, "utf8").trim();
|
|
12690
|
+
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
12691
|
+
}
|
|
12692
|
+
const raw = (opts.read ?? readRawMachineId)();
|
|
12693
|
+
if (!raw) return void 0;
|
|
12694
|
+
const hashed = sha256(raw);
|
|
12695
|
+
mkdirSync14(dirname13(path), { recursive: true });
|
|
12696
|
+
writeFileSync16(path, `${hashed}
|
|
12697
|
+
`, { mode: 384 });
|
|
12698
|
+
return hashed;
|
|
12699
|
+
}
|
|
12700
|
+
function matchShipRows(ships, shipName2, machineId) {
|
|
12701
|
+
const hasColumn = ships.some((r) => "machine_id" in r);
|
|
12702
|
+
const byId = hasColumn && machineId ? ships.filter((r) => r.machine_id === machineId) : [];
|
|
12703
|
+
if (byId.length > 0) return { rows: byId, by: "machine_id", hasColumn };
|
|
12704
|
+
return { rows: ships.filter((r) => (r.name ?? "").trim() === shipName2.trim()), by: "name", hasColumn };
|
|
12705
|
+
}
|
|
12706
|
+
|
|
12707
|
+
// src/ship-name.ts
|
|
12708
|
+
var ShipNameError = class extends Error {
|
|
12709
|
+
};
|
|
12710
|
+
function validateShipName(raw) {
|
|
12711
|
+
const name = raw.trim();
|
|
12712
|
+
if (!name) throw new ShipNameError("the ship name cannot be empty");
|
|
12713
|
+
if (/[\u0000-\u001f\u007f]/.test(name)) throw new ShipNameError("the ship name cannot contain control characters");
|
|
12714
|
+
return name;
|
|
12715
|
+
}
|
|
12716
|
+
function planNameSync(local, row, lastSynced) {
|
|
12717
|
+
if (local === row) return { drift: "none", action: "none" };
|
|
12718
|
+
const rowMoved = lastSynced === void 0 || row !== lastSynced;
|
|
12719
|
+
const localMoved = lastSynced === void 0 || local !== lastSynced;
|
|
12720
|
+
if (rowMoved && localMoved) return { drift: "both", action: "row-wins" };
|
|
12721
|
+
if (rowMoved) return { drift: "row", action: "adopt" };
|
|
12722
|
+
return { drift: "local", action: "push" };
|
|
12723
|
+
}
|
|
12724
|
+
function shipNameOfYaml(text) {
|
|
12725
|
+
const v = parseDocument(text).getIn(["ship", "name"]);
|
|
12726
|
+
return typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
12727
|
+
}
|
|
12728
|
+
function setShipNameInYaml(text, name) {
|
|
12729
|
+
const doc = parseDocument(text);
|
|
12730
|
+
doc.setIn(["ship", "name"], name);
|
|
12731
|
+
return doc.toString();
|
|
12732
|
+
}
|
|
12733
|
+
function pickShipRow(rows, machineId, byName = []) {
|
|
12734
|
+
const hasColumn = rows.some((r) => "machine_id" in r);
|
|
12735
|
+
if (!hasColumn || !machineId) return { kind: "unsupported" };
|
|
12736
|
+
const byId = rows.filter((r) => r.machine_id === machineId);
|
|
12737
|
+
if (byId.length === 1) return { kind: "row", row: byId[0], by: "machine_id" };
|
|
12738
|
+
if (byId.length > 1) return { kind: "ambiguous" };
|
|
12739
|
+
const names = new Set(byName.map((n) => n.trim()));
|
|
12740
|
+
const named = rows.filter((r) => !r.machine_id && names.has((r.name ?? "").trim()));
|
|
12741
|
+
if (named.length === 1) return { kind: "row", row: named[0], by: "name" };
|
|
12742
|
+
return { kind: named.length > 1 ? "ambiguous" : "none" };
|
|
12743
|
+
}
|
|
12744
|
+
async function syncRouteName(i) {
|
|
12745
|
+
const picked = pickShipRow(i.rows, i.machineId);
|
|
12746
|
+
if (picked.kind !== "row") {
|
|
12747
|
+
const status2 = picked.kind === "none" ? "no-row" : picked.kind;
|
|
12748
|
+
return { status: status2, drift: "none", action: "none", name: i.local };
|
|
12749
|
+
}
|
|
12750
|
+
const rowName = (picked.row.name ?? "").trim();
|
|
12751
|
+
if (!rowName) return { status: "no-row", drift: "none", action: "none", name: i.local };
|
|
12752
|
+
const { drift, action } = planNameSync(i.local, rowName, i.lastSynced);
|
|
12753
|
+
const base = { drift, rowId: picked.row.id, rowName };
|
|
12754
|
+
if (action === "push") {
|
|
12755
|
+
if (!i.rename) return { status: "ok", ...base, action, name: i.local };
|
|
12756
|
+
try {
|
|
12757
|
+
await i.rename(picked.row.id, i.local);
|
|
12758
|
+
} catch (e) {
|
|
12759
|
+
return { status: "refused", ...base, action: "none", name: i.local, error: e.message };
|
|
12760
|
+
}
|
|
12761
|
+
return { status: "ok", ...base, action, name: i.local };
|
|
12762
|
+
}
|
|
12763
|
+
if (action === "adopt" || action === "row-wins") return { status: "ok", ...base, action, name: rowName, from: i.local };
|
|
12764
|
+
return { status: "ok", ...base, action, name: i.local };
|
|
12765
|
+
}
|
|
12766
|
+
function readSyncedName(resolvedFile) {
|
|
12767
|
+
if (!existsSync25(resolvedFile)) return void 0;
|
|
12768
|
+
try {
|
|
12769
|
+
const v = JSON.parse(readFileSync20(resolvedFile, "utf8")).shipName;
|
|
12770
|
+
return typeof v === "string" ? v : void 0;
|
|
12771
|
+
} catch {
|
|
12772
|
+
return void 0;
|
|
12773
|
+
}
|
|
12774
|
+
}
|
|
12775
|
+
function writeSyncedName(resolvedFile, name) {
|
|
12776
|
+
if (!existsSync25(resolvedFile)) return;
|
|
12777
|
+
let raw;
|
|
12778
|
+
try {
|
|
12779
|
+
raw = JSON.parse(readFileSync20(resolvedFile, "utf8"));
|
|
12780
|
+
} catch {
|
|
12781
|
+
return;
|
|
12782
|
+
}
|
|
12783
|
+
if (raw.shipName === name) return;
|
|
12784
|
+
raw.shipName = name;
|
|
12785
|
+
mkdirSync15(dirname14(resolvedFile), { recursive: true });
|
|
12786
|
+
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
12787
|
+
writeFileSync17(tmp, `${JSON.stringify(raw, null, 2)}
|
|
12788
|
+
`);
|
|
12789
|
+
renameSync2(tmp, resolvedFile);
|
|
12790
|
+
}
|
|
12791
|
+
async function reconcileShipName(i) {
|
|
12792
|
+
let local = i.ship.name;
|
|
12793
|
+
try {
|
|
12794
|
+
local = shipNameOfYaml(readFileSync20(i.configFile, "utf8")) ?? local;
|
|
12795
|
+
} catch {
|
|
12796
|
+
}
|
|
12797
|
+
const resolvedFile = resolvedPathFor(i.stateDir, i.route);
|
|
12798
|
+
const lastSynced = readSyncedName(resolvedFile);
|
|
12799
|
+
let rows = [];
|
|
12800
|
+
try {
|
|
12801
|
+
rows = await i.rows();
|
|
12802
|
+
} catch {
|
|
12803
|
+
}
|
|
12804
|
+
const result = await syncRouteName({
|
|
12805
|
+
local,
|
|
12806
|
+
lastSynced,
|
|
12807
|
+
rows,
|
|
12808
|
+
machineId: i.machineId,
|
|
12809
|
+
rename: i.readOnly ? void 0 : i.rename
|
|
12810
|
+
});
|
|
12811
|
+
if (i.readOnly || result.status === "unsupported") return result;
|
|
12812
|
+
if (result.status === "refused") {
|
|
12813
|
+
i.emit?.warn(`could not rename the Ships row to "${local}": ${result.error}`, { step: "poll", data: { event: "ship_rename_refused", route: i.route, to: local } });
|
|
12814
|
+
return result;
|
|
12815
|
+
}
|
|
12816
|
+
if (result.status !== "ok") return result;
|
|
12817
|
+
if (result.action === "adopt" || result.action === "row-wins") {
|
|
12818
|
+
const text = readFileSync20(i.configFile, "utf8");
|
|
12819
|
+
writeFileSync17(i.configFile, setShipNameInYaml(text, result.name));
|
|
12820
|
+
i.ship.name = result.name;
|
|
12821
|
+
if (result.action === "row-wins") {
|
|
12822
|
+
i.emit?.warn(
|
|
12823
|
+
`ship name changed in both crew.yaml ("${result.from}") and the Ships table ("${result.name}"); the Ships row wins`,
|
|
12824
|
+
{ step: "poll", data: { event: "ship_renamed", route: i.route, from: result.from, to: result.name, source: "table", conflict: true } }
|
|
12825
|
+
);
|
|
12826
|
+
} else {
|
|
12827
|
+
i.emit?.emit(`ship renamed to "${result.name}" in the Ships table; crew.yaml updated`, {
|
|
12828
|
+
step: "poll",
|
|
12829
|
+
data: { event: "ship_renamed", route: i.route, from: result.from, to: result.name, source: "table" }
|
|
12830
|
+
});
|
|
12831
|
+
}
|
|
12832
|
+
} else if (result.action === "push") {
|
|
12833
|
+
i.emit?.emit(`ship name "${result.name}" pushed to the Ships table`, {
|
|
12834
|
+
step: "poll",
|
|
12835
|
+
data: { event: "ship_renamed", route: i.route, from: result.rowName, to: result.name, source: "crew.yaml" }
|
|
12836
|
+
});
|
|
12837
|
+
}
|
|
12838
|
+
writeSyncedName(resolvedFile, result.name);
|
|
12839
|
+
return result;
|
|
12840
|
+
}
|
|
12841
|
+
async function reconcileRouteShipName(o) {
|
|
12842
|
+
const fallback = { status: "unsupported", drift: "none", action: "none", name: o.ship.name };
|
|
12843
|
+
if (!o.route.resolved?.shipsModelId) return fallback;
|
|
12844
|
+
try {
|
|
12845
|
+
const machineId = o.machineId ?? ensureMachineId(o.ship.stateDir);
|
|
12846
|
+
const tracker = new Tracker(o.route, o.ship);
|
|
12847
|
+
return await reconcileShipName({
|
|
12848
|
+
route: o.route.route,
|
|
12849
|
+
configFile: o.configFile,
|
|
12850
|
+
stateDir: o.ship.stateDir,
|
|
12851
|
+
ship: o.ship,
|
|
12852
|
+
machineId,
|
|
12853
|
+
rows: () => tracker.shipRows(),
|
|
12854
|
+
rename: (id, name) => tracker.renameShipRow(id, name),
|
|
12855
|
+
emit: o.emit,
|
|
12856
|
+
readOnly: o.readOnly
|
|
12857
|
+
});
|
|
12858
|
+
} catch {
|
|
12859
|
+
return fallback;
|
|
12860
|
+
}
|
|
12861
|
+
}
|
|
12862
|
+
|
|
12639
12863
|
// src/connect.ts
|
|
12640
12864
|
var ConnectHttpError = class extends Error {
|
|
12641
12865
|
status;
|
|
@@ -12941,10 +13165,16 @@ async function discover(o) {
|
|
|
12941
13165
|
out.shipRow = { id: shipRowId, name: o.ship.name, action: "matched", matchedBy };
|
|
12942
13166
|
const patchBody = {};
|
|
12943
13167
|
if (matchedBy === "machine_id") {
|
|
12944
|
-
const
|
|
12945
|
-
|
|
12946
|
-
|
|
12947
|
-
|
|
13168
|
+
const rowName = (mine[0].name ?? "").trim();
|
|
13169
|
+
const route = `${out.workspaceSlug}/${out.projectSlug ?? out.projectId}`;
|
|
13170
|
+
const plan = planNameSync(o.ship.name.trim(), rowName, o.ship.lastSyncedName?.(route));
|
|
13171
|
+
if (plan.action === "push") {
|
|
13172
|
+
patchBody.name = o.ship.name.trim();
|
|
13173
|
+
provisioning.push(`renamed Ships row "${rowName}" to "${o.ship.name.trim()}" (crew.yaml changed)`);
|
|
13174
|
+
} else if (plan.action === "adopt" || plan.action === "row-wins") {
|
|
13175
|
+
out.shipRow.name = rowName;
|
|
13176
|
+
out.shipNameAdopted = rowName;
|
|
13177
|
+
provisioning.push(`matched Ships row "${rowName}" by machine id \u2014 this host's name becomes "${rowName}"${plan.action === "row-wins" ? " (crew.yaml also changed; the Ships row wins)" : ""}`);
|
|
12948
13178
|
}
|
|
12949
13179
|
patchBody.last_seen = (/* @__PURE__ */ new Date()).toISOString();
|
|
12950
13180
|
}
|
|
@@ -13494,8 +13724,8 @@ function describeCapability(i) {
|
|
|
13494
13724
|
}
|
|
13495
13725
|
|
|
13496
13726
|
// src/agents-sync-run.ts
|
|
13497
|
-
import { existsSync as
|
|
13498
|
-
import { dirname as
|
|
13727
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync16, readFileSync as readFileSync21, writeFileSync as writeFileSync18 } from "fs";
|
|
13728
|
+
import { dirname as dirname15 } from "path";
|
|
13499
13729
|
async function runAgentsSync(target, o) {
|
|
13500
13730
|
const dryRun = o.dryRun ?? false;
|
|
13501
13731
|
const force = o.force ?? false;
|
|
@@ -13528,12 +13758,12 @@ async function runAgentsSync(target, o) {
|
|
|
13528
13758
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
13529
13759
|
if (!dryRun) {
|
|
13530
13760
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
13531
|
-
const raw =
|
|
13761
|
+
const raw = existsSync26(resolvedPath) ? JSON.parse(readFileSync21(resolvedPath, "utf8")) : {};
|
|
13532
13762
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
13533
13763
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
13534
13764
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
13535
|
-
|
|
13536
|
-
|
|
13765
|
+
mkdirSync16(dirname15(resolvedPath), { recursive: true });
|
|
13766
|
+
writeFileSync18(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
13537
13767
|
`);
|
|
13538
13768
|
if (target.resolved) {
|
|
13539
13769
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -13548,7 +13778,7 @@ async function runAgentsSync(target, o) {
|
|
|
13548
13778
|
}
|
|
13549
13779
|
|
|
13550
13780
|
// src/logbook.ts
|
|
13551
|
-
import { createHash as
|
|
13781
|
+
import { createHash as createHash4 } from "crypto";
|
|
13552
13782
|
import { TablationClient as TablationClient4 } from "@tablation/client";
|
|
13553
13783
|
var LogbookError = class extends Error {
|
|
13554
13784
|
};
|
|
@@ -13621,7 +13851,7 @@ async function showLogEntry(route, entryId, opts = {}) {
|
|
|
13621
13851
|
};
|
|
13622
13852
|
}
|
|
13623
13853
|
const prompt = appendAdditions(match.prompt ?? "", match.prompt_additions);
|
|
13624
|
-
const actualSha =
|
|
13854
|
+
const actualSha = createHash4("sha256").update(prompt).digest("hex");
|
|
13625
13855
|
const exact = !!entry.prompt_sha && actualSha === entry.prompt_sha;
|
|
13626
13856
|
return {
|
|
13627
13857
|
entry,
|
|
@@ -13636,12 +13866,12 @@ async function showLogEntry(route, entryId, opts = {}) {
|
|
|
13636
13866
|
}
|
|
13637
13867
|
|
|
13638
13868
|
// src/repos-hooks.ts
|
|
13639
|
-
import { isMap as isMap2, parseDocument as
|
|
13869
|
+
import { isMap as isMap2, parseDocument as parseDocument3, YAMLMap } from "yaml";
|
|
13640
13870
|
|
|
13641
13871
|
// src/repos-cmd.ts
|
|
13642
13872
|
import { homedir as homedir8 } from "os";
|
|
13643
|
-
import { basename as basename8, join as
|
|
13644
|
-
import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
|
|
13873
|
+
import { basename as basename8, join as join26, resolve as resolve3 } from "path";
|
|
13874
|
+
import { isMap, isSeq, parseDocument as parseDocument2, stringify as stringify2 } from "yaml";
|
|
13645
13875
|
var ReposError = class extends Error {
|
|
13646
13876
|
};
|
|
13647
13877
|
function normalizeRemote(url) {
|
|
@@ -13665,7 +13895,7 @@ function matchRepoName(origin, resolved) {
|
|
|
13665
13895
|
return void 0;
|
|
13666
13896
|
}
|
|
13667
13897
|
function routeNamesOf(text) {
|
|
13668
|
-
const routes =
|
|
13898
|
+
const routes = parseDocument2(text).get("routes", true);
|
|
13669
13899
|
if (!isSeq(routes)) return [];
|
|
13670
13900
|
return routes.items.flatMap((i) => isMap(i) && typeof i.get("route") === "string" ? [i.get("route")] : []);
|
|
13671
13901
|
}
|
|
@@ -13700,7 +13930,7 @@ function routeMapOf(doc, route) {
|
|
|
13700
13930
|
throw new ReposError(`no route "${route}" in the config (have: ${have})`);
|
|
13701
13931
|
}
|
|
13702
13932
|
function configuredRepos(text, route, base) {
|
|
13703
|
-
const doc =
|
|
13933
|
+
const doc = parseDocument2(text);
|
|
13704
13934
|
const m = routeMapOf(doc, route);
|
|
13705
13935
|
const repos = m.get("repos");
|
|
13706
13936
|
if (isMap(repos)) {
|
|
@@ -13715,7 +13945,7 @@ function configuredRepos(text, route, base) {
|
|
|
13715
13945
|
return dir ? [{ name: basename8(String(dir)), dir: expandDir(String(dir), base) }] : [];
|
|
13716
13946
|
}
|
|
13717
13947
|
function routeSetsHooks(text, route, name) {
|
|
13718
|
-
const m = routeMapOf(
|
|
13948
|
+
const m = routeMapOf(parseDocument2(text), route);
|
|
13719
13949
|
const repos = m.get("repos", true);
|
|
13720
13950
|
let target = m;
|
|
13721
13951
|
if (isMap(repos)) target = repos.get(name, true);
|
|
@@ -13771,7 +14001,7 @@ function listRepoRows(i) {
|
|
|
13771
14001
|
return rows;
|
|
13772
14002
|
}
|
|
13773
14003
|
function planRepoAdd(i) {
|
|
13774
|
-
const doc =
|
|
14004
|
+
const doc = parseDocument2(i.text);
|
|
13775
14005
|
if (doc.errors.length > 0) throw new ReposError(`config is not valid YAML \u2014 ${doc.errors[0].message}`);
|
|
13776
14006
|
const m = routeMapOf(doc, i.route);
|
|
13777
14007
|
const existing = configuredRepos(i.text, i.route, i.base);
|
|
@@ -13808,7 +14038,7 @@ function planRepoAdd(i) {
|
|
|
13808
14038
|
return { text: String(doc), mode, name, block: renderBlock(m), warning, unmatched: !matched };
|
|
13809
14039
|
}
|
|
13810
14040
|
function planRepoRemove(text, base, route, path) {
|
|
13811
|
-
const doc =
|
|
14041
|
+
const doc = parseDocument2(text);
|
|
13812
14042
|
if (doc.errors.length > 0) throw new ReposError(`config is not valid YAML \u2014 ${doc.errors[0].message}`);
|
|
13813
14043
|
const m = routeMapOf(doc, route);
|
|
13814
14044
|
const target = expandDir(path, base);
|
|
@@ -13886,7 +14116,7 @@ function planRepoClone(i) {
|
|
|
13886
14116
|
`the Repos row "${name}" has no remote to clone from \u2014 attach an existing checkout with \`crew repos add ${i.route} ${name} <path>\`, or set the row's remote in the tracker and re-run \`crew connect\``
|
|
13887
14117
|
);
|
|
13888
14118
|
}
|
|
13889
|
-
const doc =
|
|
14119
|
+
const doc = parseDocument2(i.text);
|
|
13890
14120
|
const m = routeMapOf(doc, i.route);
|
|
13891
14121
|
const existing = configuredRepos(i.text, i.route, i.base);
|
|
13892
14122
|
if (existing.some((e) => e.name.toLowerCase() === name.toLowerCase())) {
|
|
@@ -13894,7 +14124,7 @@ function planRepoClone(i) {
|
|
|
13894
14124
|
}
|
|
13895
14125
|
const ship = doc.get("ship", true);
|
|
13896
14126
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
13897
|
-
const target = i.dir ? expandDir(i.dir, i.base) :
|
|
14127
|
+
const target = i.dir ? expandDir(i.dir, i.base) : join26(expandDir(basePath, i.base), i.route.split("/")[0], name);
|
|
13898
14128
|
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name });
|
|
13899
14129
|
return { ...add, remote, target };
|
|
13900
14130
|
}
|
|
@@ -14040,7 +14270,7 @@ function repoDirOf(text, base, route, name) {
|
|
|
14040
14270
|
return hit.dir;
|
|
14041
14271
|
}
|
|
14042
14272
|
function planRouteHooks(text, route, name, hooks) {
|
|
14043
|
-
const doc =
|
|
14273
|
+
const doc = parseDocument3(text);
|
|
14044
14274
|
if (doc.errors.length > 0) throw new ReposError(`config is not valid YAML \u2014 ${doc.errors[0].message}`);
|
|
14045
14275
|
const m = routeMapOf(doc, route);
|
|
14046
14276
|
const repos = m.get("repos", true);
|
|
@@ -14102,7 +14332,7 @@ function existingRepoConfig(f) {
|
|
|
14102
14332
|
}
|
|
14103
14333
|
|
|
14104
14334
|
// src/repos-hooks-agent.ts
|
|
14105
|
-
import { spawn as spawn4, execFileSync as
|
|
14335
|
+
import { spawn as spawn4, execFileSync as execFileSync5 } from "child_process";
|
|
14106
14336
|
import { realpathSync as realpathSync2 } from "fs";
|
|
14107
14337
|
import { isAbsolute as isAbsolute2, relative } from "path";
|
|
14108
14338
|
var AgentSuggestError = class extends Error {
|
|
@@ -14205,7 +14435,7 @@ function classifyFailure(o) {
|
|
|
14205
14435
|
}
|
|
14206
14436
|
function porcelain(dir) {
|
|
14207
14437
|
try {
|
|
14208
|
-
return
|
|
14438
|
+
return execFileSync5("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
|
|
14209
14439
|
} catch {
|
|
14210
14440
|
return void 0;
|
|
14211
14441
|
}
|
|
@@ -14274,7 +14504,7 @@ ${run2.err}` });
|
|
|
14274
14504
|
}
|
|
14275
14505
|
|
|
14276
14506
|
// src/sample-ticket.ts
|
|
14277
|
-
import { basename as basename9, dirname as
|
|
14507
|
+
import { basename as basename9, dirname as dirname17, join as join27 } from "path";
|
|
14278
14508
|
var SAMPLE_REPO = "crew-sample";
|
|
14279
14509
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
14280
14510
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -14297,8 +14527,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
14297
14527
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
14298
14528
|
`;
|
|
14299
14529
|
function sampleDir(existing, reposBase, workspace) {
|
|
14300
|
-
if (existing.length > 0) return
|
|
14301
|
-
return
|
|
14530
|
+
if (existing.length > 0) return join27(dirname17(existing[0]), SAMPLE_REPO);
|
|
14531
|
+
return join27(reposBase, workspace, SAMPLE_REPO);
|
|
14302
14532
|
}
|
|
14303
14533
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
14304
14534
|
function sampleDescription() {
|
|
@@ -14342,7 +14572,7 @@ function planSampleTicket(i) {
|
|
|
14342
14572
|
}
|
|
14343
14573
|
|
|
14344
14574
|
// src/doctor-fix.ts
|
|
14345
|
-
import { parseDocument as
|
|
14575
|
+
import { parseDocument as parseDocument4 } from "yaml";
|
|
14346
14576
|
var sound = (r) => r.hasRepos && r.problems.length === 0;
|
|
14347
14577
|
function planFix(f) {
|
|
14348
14578
|
const actions = [];
|
|
@@ -14369,7 +14599,7 @@ function routesInScope(routes, named) {
|
|
|
14369
14599
|
return named ? routes.filter((r) => r.route === named) : routes;
|
|
14370
14600
|
}
|
|
14371
14601
|
function planEnable(text, route) {
|
|
14372
|
-
const doc =
|
|
14602
|
+
const doc = parseDocument4(text);
|
|
14373
14603
|
routeMapOf(doc, route).set("enabled", true);
|
|
14374
14604
|
return String(doc);
|
|
14375
14605
|
}
|
|
@@ -14379,8 +14609,8 @@ function schedulerInstalled(plan, exists, crontab) {
|
|
|
14379
14609
|
}
|
|
14380
14610
|
|
|
14381
14611
|
// src/worktree-sweep.ts
|
|
14382
|
-
import { existsSync as
|
|
14383
|
-
import { basename as basename10, dirname as
|
|
14612
|
+
import { existsSync as existsSync27, readFileSync as readFileSync22, readdirSync as readdirSync8 } from "fs";
|
|
14613
|
+
import { basename as basename10, dirname as dirname18, join as join28, resolve as resolve4 } from "path";
|
|
14384
14614
|
function planWorktreeSweep(target, tickets, contract) {
|
|
14385
14615
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
14386
14616
|
const verified = contract.statuses.verified;
|
|
@@ -14434,7 +14664,7 @@ function worktreeTicketNumbers(target) {
|
|
|
14434
14664
|
return [...nums];
|
|
14435
14665
|
}
|
|
14436
14666
|
function siblingWorktreeDirs(target) {
|
|
14437
|
-
const parent =
|
|
14667
|
+
const parent = dirname18(target.dir.replace(/[/\\]+$/, ""));
|
|
14438
14668
|
const stem = `${basename10(target.dir.replace(/[/\\]+$/, ""))}-`;
|
|
14439
14669
|
const registered = new Set(worktrees(target.dir).map((w) => resolve4(w.path)));
|
|
14440
14670
|
let names;
|
|
@@ -14447,7 +14677,7 @@ function siblingWorktreeDirs(target) {
|
|
|
14447
14677
|
for (const name of names) {
|
|
14448
14678
|
if (!name.startsWith(stem)) continue;
|
|
14449
14679
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
14450
|
-
const path =
|
|
14680
|
+
const path = join28(parent, name);
|
|
14451
14681
|
if (m && !registered.has(resolve4(path))) out.push({ path, number: m[1] });
|
|
14452
14682
|
}
|
|
14453
14683
|
return out;
|
|
@@ -14482,7 +14712,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
14482
14712
|
let removed = 0;
|
|
14483
14713
|
let keptBranches = 0;
|
|
14484
14714
|
for (const a of actions) {
|
|
14485
|
-
if (!dryRun && base && !a.keepBranch &&
|
|
14715
|
+
if (!dryRun && base && !a.keepBranch && existsSync27(a.path) && isHandBackMerge(a.path, base)) {
|
|
14486
14716
|
if (gitOk(a.path, ["merge", "--abort"]) !== null) {
|
|
14487
14717
|
log.emit(
|
|
14488
14718
|
`abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
|
|
@@ -14490,7 +14720,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
14490
14720
|
);
|
|
14491
14721
|
}
|
|
14492
14722
|
}
|
|
14493
|
-
if (!dryRun &&
|
|
14723
|
+
if (!dryRun && existsSync27(a.path)) {
|
|
14494
14724
|
let dirty = false;
|
|
14495
14725
|
try {
|
|
14496
14726
|
dirty = status(a.path).length > 0;
|
|
@@ -14501,7 +14731,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
14501
14731
|
continue;
|
|
14502
14732
|
}
|
|
14503
14733
|
}
|
|
14504
|
-
if (!dryRun && a.requirePushed && a.branch &&
|
|
14734
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync27(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
14505
14735
|
refuse(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
|
|
14506
14736
|
continue;
|
|
14507
14737
|
}
|
|
@@ -14550,7 +14780,7 @@ function isHandBackMerge(path, base) {
|
|
|
14550
14780
|
if (!msgPath) return false;
|
|
14551
14781
|
let first;
|
|
14552
14782
|
try {
|
|
14553
|
-
first =
|
|
14783
|
+
first = readFileSync22(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
|
|
14554
14784
|
} catch {
|
|
14555
14785
|
return false;
|
|
14556
14786
|
}
|
|
@@ -14655,14 +14885,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
14655
14885
|
}
|
|
14656
14886
|
|
|
14657
14887
|
// src/stream-sweep.ts
|
|
14658
|
-
import { existsSync as
|
|
14659
|
-
import { join as
|
|
14888
|
+
import { existsSync as existsSync28, readdirSync as readdirSync9, rmSync as rmSync13, statSync as statSync8 } from "fs";
|
|
14889
|
+
import { join as join29 } from "path";
|
|
14660
14890
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
14661
14891
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
14662
|
-
const dir =
|
|
14663
|
-
if (!
|
|
14892
|
+
const dir = join29(stateDir, "streams");
|
|
14893
|
+
if (!existsSync28(dir)) return [];
|
|
14664
14894
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
14665
|
-
return readdirSync9(dir).map((name) =>
|
|
14895
|
+
return readdirSync9(dir).map((name) => join29(dir, name)).filter((path) => {
|
|
14666
14896
|
try {
|
|
14667
14897
|
return statSync8(path).mtimeMs < cutoff;
|
|
14668
14898
|
} catch {
|
|
@@ -14688,9 +14918,9 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
14688
14918
|
return removed;
|
|
14689
14919
|
}
|
|
14690
14920
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
14691
|
-
if (!
|
|
14921
|
+
if (!existsSync28(stateDir)) return [];
|
|
14692
14922
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
14693
|
-
return readdirSync9(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) =>
|
|
14923
|
+
return readdirSync9(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join29(stateDir, name)).filter((path) => {
|
|
14694
14924
|
try {
|
|
14695
14925
|
return statSync8(path).mtimeMs < cutoff;
|
|
14696
14926
|
} catch {
|
|
@@ -14700,26 +14930,26 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
14700
14930
|
}
|
|
14701
14931
|
|
|
14702
14932
|
// src/cli.ts
|
|
14703
|
-
import { realpathSync as realpathSync3, readFileSync as
|
|
14933
|
+
import { realpathSync as realpathSync3, readFileSync as readFileSync28, existsSync as existsSync34, mkdirSync as mkdirSync21, writeFileSync as writeFileSync22, rmSync as rmSync15, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync3, statSync as statSync10, readdirSync as readdirSync14 } from "fs";
|
|
14704
14934
|
import { createInterface } from "readline/promises";
|
|
14705
14935
|
import { dirname as dirOf, resolve as resolvePath } from "path";
|
|
14706
14936
|
|
|
14707
14937
|
// src/ssh-keys.ts
|
|
14708
|
-
import { execFileSync as
|
|
14709
|
-
import { existsSync as
|
|
14710
|
-
import { dirname as
|
|
14938
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
14939
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync17, readFileSync as readFileSync23, chmodSync } from "fs";
|
|
14940
|
+
import { dirname as dirname19, join as join30 } from "path";
|
|
14711
14941
|
var SshKeyError = class extends Error {
|
|
14712
14942
|
};
|
|
14713
14943
|
function shipSshKeyPathFor(stateDir) {
|
|
14714
|
-
return
|
|
14944
|
+
return join30(stateDir, "ssh", "id_ed25519");
|
|
14715
14945
|
}
|
|
14716
14946
|
function ensureShipSshKeypair(stateDir) {
|
|
14717
14947
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
14718
14948
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
14719
|
-
if (!
|
|
14720
|
-
|
|
14949
|
+
if (!existsSync29(privateKeyPath)) {
|
|
14950
|
+
mkdirSync17(dirname19(privateKeyPath), { recursive: true });
|
|
14721
14951
|
try {
|
|
14722
|
-
|
|
14952
|
+
execFileSync6("ssh-keygen", [
|
|
14723
14953
|
"-t",
|
|
14724
14954
|
"ed25519",
|
|
14725
14955
|
"-N",
|
|
@@ -14737,71 +14967,21 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
14737
14967
|
}
|
|
14738
14968
|
chmodSync(privateKeyPath, 384);
|
|
14739
14969
|
}
|
|
14740
|
-
if (!
|
|
14970
|
+
if (!existsSync29(publicKeyPath)) {
|
|
14741
14971
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
14742
14972
|
}
|
|
14743
|
-
const publicKey =
|
|
14973
|
+
const publicKey = readFileSync23(publicKeyPath, "utf8").trim();
|
|
14744
14974
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
14745
14975
|
}
|
|
14746
14976
|
function sshKeygenAvailable() {
|
|
14747
14977
|
try {
|
|
14748
|
-
|
|
14978
|
+
execFileSync6("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
|
|
14749
14979
|
return true;
|
|
14750
14980
|
} catch (e) {
|
|
14751
14981
|
return e.code !== "ENOENT";
|
|
14752
14982
|
}
|
|
14753
14983
|
}
|
|
14754
14984
|
|
|
14755
|
-
// src/machine-id.ts
|
|
14756
|
-
import { execFileSync as execFileSync6 } from "child_process";
|
|
14757
|
-
import { createHash as createHash4 } from "crypto";
|
|
14758
|
-
import { existsSync as existsSync28, mkdirSync as mkdirSync16, readFileSync as readFileSync22, writeFileSync as writeFileSync17 } from "fs";
|
|
14759
|
-
import { dirname as dirname18, join as join30 } from "path";
|
|
14760
|
-
function machineIdPathFor(stateDir) {
|
|
14761
|
-
return join30(stateDir, "machine-id");
|
|
14762
|
-
}
|
|
14763
|
-
var sha256 = (s) => createHash4("sha256").update(s).digest("hex");
|
|
14764
|
-
function readRawMachineId(platform2 = process.platform) {
|
|
14765
|
-
try {
|
|
14766
|
-
if (platform2 === "darwin") {
|
|
14767
|
-
const out = execFileSync6("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
|
|
14768
|
-
encoding: "utf8",
|
|
14769
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
14770
|
-
});
|
|
14771
|
-
return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
|
|
14772
|
-
}
|
|
14773
|
-
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
14774
|
-
if (existsSync28(p)) {
|
|
14775
|
-
const id = readFileSync22(p, "utf8").trim();
|
|
14776
|
-
if (id) return id;
|
|
14777
|
-
}
|
|
14778
|
-
}
|
|
14779
|
-
} catch {
|
|
14780
|
-
}
|
|
14781
|
-
return void 0;
|
|
14782
|
-
}
|
|
14783
|
-
function ensureMachineId(stateDir, opts = {}) {
|
|
14784
|
-
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
14785
|
-
const path = machineIdPathFor(stateDir);
|
|
14786
|
-
if (existsSync28(path)) {
|
|
14787
|
-
const cached = readFileSync22(path, "utf8").trim();
|
|
14788
|
-
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
14789
|
-
}
|
|
14790
|
-
const raw = (opts.read ?? readRawMachineId)();
|
|
14791
|
-
if (!raw) return void 0;
|
|
14792
|
-
const hashed = sha256(raw);
|
|
14793
|
-
mkdirSync16(dirname18(path), { recursive: true });
|
|
14794
|
-
writeFileSync17(path, `${hashed}
|
|
14795
|
-
`, { mode: 384 });
|
|
14796
|
-
return hashed;
|
|
14797
|
-
}
|
|
14798
|
-
function matchShipRows(ships, shipName2, machineId) {
|
|
14799
|
-
const hasColumn = ships.some((r) => "machine_id" in r);
|
|
14800
|
-
const byId = hasColumn && machineId ? ships.filter((r) => r.machine_id === machineId) : [];
|
|
14801
|
-
if (byId.length > 0) return { rows: byId, by: "machine_id", hasColumn };
|
|
14802
|
-
return { rows: ships.filter((r) => (r.name ?? "").trim() === shipName2.trim()), by: "name", hasColumn };
|
|
14803
|
-
}
|
|
14804
|
-
|
|
14805
14985
|
// src/passenger-containers.ts
|
|
14806
14986
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
14807
14987
|
import { createHash as createHash5 } from "crypto";
|
|
@@ -15006,7 +15186,7 @@ function syncAllPassengerCheckouts(cfg) {
|
|
|
15006
15186
|
|
|
15007
15187
|
// src/tunnel.ts
|
|
15008
15188
|
import { spawn as spawn5 } from "child_process";
|
|
15009
|
-
import { existsSync as
|
|
15189
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync18, readFileSync as readFileSync24, writeFileSync as writeFileSync19, unlinkSync as unlinkSync3, readdirSync as readdirSync10 } from "fs";
|
|
15010
15190
|
import { randomInt } from "crypto";
|
|
15011
15191
|
import { join as join31 } from "path";
|
|
15012
15192
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
@@ -15079,17 +15259,17 @@ function tunnelStatePath(stateDir, workspaceId) {
|
|
|
15079
15259
|
}
|
|
15080
15260
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
15081
15261
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
15082
|
-
if (!
|
|
15262
|
+
if (!existsSync30(path)) return void 0;
|
|
15083
15263
|
try {
|
|
15084
|
-
return JSON.parse(
|
|
15264
|
+
return JSON.parse(readFileSync24(path, "utf8"));
|
|
15085
15265
|
} catch {
|
|
15086
15266
|
return void 0;
|
|
15087
15267
|
}
|
|
15088
15268
|
}
|
|
15089
15269
|
function writePersistedTunnel(stateDir, state) {
|
|
15090
15270
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
15091
|
-
|
|
15092
|
-
|
|
15271
|
+
mkdirSync18(join31(stateDir, "passengers"), { recursive: true });
|
|
15272
|
+
writeFileSync19(path, `${JSON.stringify(state, null, 2)}
|
|
15093
15273
|
`);
|
|
15094
15274
|
}
|
|
15095
15275
|
function updatePersistedTunnel(stateDir, workspaceId, patch2) {
|
|
@@ -15187,7 +15367,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
15187
15367
|
}
|
|
15188
15368
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
15189
15369
|
const dir = join31(stateDir, "passengers");
|
|
15190
|
-
if (!
|
|
15370
|
+
if (!existsSync30(dir)) return [];
|
|
15191
15371
|
return readdirSync10(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
15192
15372
|
}
|
|
15193
15373
|
|
|
@@ -15308,7 +15488,7 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
15308
15488
|
import { createServer as createHttpServer } from "http";
|
|
15309
15489
|
import { spawn as spawn6 } from "child_process";
|
|
15310
15490
|
import { randomBytes } from "crypto";
|
|
15311
|
-
import { existsSync as
|
|
15491
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync19, readFileSync as readFileSync25, writeFileSync as writeFileSync20, unlinkSync as unlinkSync4, readdirSync as readdirSync11 } from "fs";
|
|
15312
15492
|
import { join as join32 } from "path";
|
|
15313
15493
|
|
|
15314
15494
|
// src/secret-compare.ts
|
|
@@ -15402,17 +15582,17 @@ function syncDaemonStatePath(stateDir, workspaceId) {
|
|
|
15402
15582
|
}
|
|
15403
15583
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
15404
15584
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
15405
|
-
if (!
|
|
15585
|
+
if (!existsSync31(path)) return void 0;
|
|
15406
15586
|
try {
|
|
15407
|
-
return JSON.parse(
|
|
15587
|
+
return JSON.parse(readFileSync25(path, "utf8"));
|
|
15408
15588
|
} catch {
|
|
15409
15589
|
return void 0;
|
|
15410
15590
|
}
|
|
15411
15591
|
}
|
|
15412
15592
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
15413
15593
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
15414
|
-
|
|
15415
|
-
|
|
15594
|
+
mkdirSync19(join32(stateDir, "passengers"), { recursive: true });
|
|
15595
|
+
writeFileSync20(path, `${JSON.stringify(state, null, 2)}
|
|
15416
15596
|
`);
|
|
15417
15597
|
}
|
|
15418
15598
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -15433,7 +15613,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
15433
15613
|
}
|
|
15434
15614
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
15435
15615
|
const dir = join32(stateDir, "passengers");
|
|
15436
|
-
if (!
|
|
15616
|
+
if (!existsSync31(dir)) return [];
|
|
15437
15617
|
const suffix = "-sync.json";
|
|
15438
15618
|
return readdirSync11(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
15439
15619
|
}
|
|
@@ -15521,11 +15701,11 @@ function describeCause(err) {
|
|
|
15521
15701
|
}
|
|
15522
15702
|
|
|
15523
15703
|
// src/daemon.ts
|
|
15524
|
-
import { existsSync as
|
|
15704
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync20, readFileSync as readFileSync27, readdirSync as readdirSync13, rmSync as rmSync14, statSync as statSync9, writeFileSync as writeFileSync21 } from "fs";
|
|
15525
15705
|
import { join as join34 } from "path";
|
|
15526
15706
|
|
|
15527
15707
|
// src/interrupted.ts
|
|
15528
|
-
import { existsSync as
|
|
15708
|
+
import { existsSync as existsSync32, readdirSync as readdirSync12, readFileSync as readFileSync26 } from "fs";
|
|
15529
15709
|
import { join as join33 } from "path";
|
|
15530
15710
|
var INTERRUPTED_MARKER = calloutHeader("NOTE", "Interrupted");
|
|
15531
15711
|
function interruptedComment(b) {
|
|
@@ -15545,16 +15725,16 @@ var pidAlive = (pid) => {
|
|
|
15545
15725
|
};
|
|
15546
15726
|
function runFinished(streamPath) {
|
|
15547
15727
|
const path = `${streamPath}.meta.json`;
|
|
15548
|
-
if (!
|
|
15728
|
+
if (!existsSync32(path)) return false;
|
|
15549
15729
|
try {
|
|
15550
|
-
return JSON.parse(
|
|
15730
|
+
return JSON.parse(readFileSync26(path, "utf8")).running !== true;
|
|
15551
15731
|
} catch {
|
|
15552
15732
|
return true;
|
|
15553
15733
|
}
|
|
15554
15734
|
}
|
|
15555
15735
|
function orphanedStreams(o) {
|
|
15556
15736
|
const dir = join33(o.stateDir, "streams");
|
|
15557
|
-
if (!
|
|
15737
|
+
if (!existsSync32(dir)) return [];
|
|
15558
15738
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
15559
15739
|
const out = [];
|
|
15560
15740
|
for (const name of readdirSync12(dir)) {
|
|
@@ -15688,7 +15868,7 @@ function computeTreeSignature(paths) {
|
|
|
15688
15868
|
}
|
|
15689
15869
|
function makeStaleChecker(paths, opts = {}) {
|
|
15690
15870
|
const initial = computeTreeSignature(paths);
|
|
15691
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
15871
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync33(opts.reloadSentinel);
|
|
15692
15872
|
}
|
|
15693
15873
|
function reloadSentinelFor(stateDir) {
|
|
15694
15874
|
return join34(stateDir, "reload-requested");
|
|
@@ -15697,14 +15877,14 @@ function daemonStampFor(stateDir) {
|
|
|
15697
15877
|
return join34(stateDir, "daemon.started");
|
|
15698
15878
|
}
|
|
15699
15879
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
15700
|
-
|
|
15880
|
+
mkdirSync20(stateDir, { recursive: true });
|
|
15701
15881
|
rmSync14(reloadSentinelFor(stateDir), { force: true });
|
|
15702
|
-
|
|
15882
|
+
writeFileSync21(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
15703
15883
|
`);
|
|
15704
15884
|
}
|
|
15705
15885
|
function readDaemonStarted(stateDir) {
|
|
15706
15886
|
try {
|
|
15707
|
-
const v = JSON.parse(
|
|
15887
|
+
const v = JSON.parse(readFileSync27(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
15708
15888
|
return typeof v === "number" ? v : null;
|
|
15709
15889
|
} catch {
|
|
15710
15890
|
return null;
|
|
@@ -15767,6 +15947,7 @@ async function runOnePassUnguarded(o) {
|
|
|
15767
15947
|
const spawn7 = o.spawnRoleAgent ?? runRoleAgent;
|
|
15768
15948
|
const breaker = o.spawnBreaker ?? new SpawnBreaker();
|
|
15769
15949
|
const ctx = { route: o.route, ship: o.ship, state: o.state, emit, getSessionStore: o.getSessionStore };
|
|
15950
|
+
if (o.configFile) await reconcileRouteShipName({ route: o.route, ship: o.ship, configFile: o.configFile, emit });
|
|
15770
15951
|
try {
|
|
15771
15952
|
await (o.reconcile ?? reconcileForRoute)(ctx, o);
|
|
15772
15953
|
} catch (e) {
|
|
@@ -16089,7 +16270,7 @@ async function buildEnvironment(route, ship, ticket) {
|
|
|
16089
16270
|
}
|
|
16090
16271
|
|
|
16091
16272
|
// src/cli.ts
|
|
16092
|
-
var CREW_HOME = isCompiledBinary(import.meta.url) ?
|
|
16273
|
+
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname20(process.execPath) : resolve5(dirname20(fileURLToPath(import.meta.url)), "..");
|
|
16093
16274
|
var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
|
|
16094
16275
|
var CYCLE_SECONDS2 = 120;
|
|
16095
16276
|
var STALE_CYCLES = 3;
|
|
@@ -16245,6 +16426,7 @@ function usage(problem, requested = false) {
|
|
|
16245
16426
|
crew agents sync [route] [--force] [--revoke-seat-keys] push crew's personas AND skill files into the workspace Agents/Agent Skills tables (--force overwrites even a diverged persona or skill; never one marked Customize prompt)
|
|
16246
16427
|
crew agents prompt R [route] print one persona's current prompt (e.g. R=pair, for a SessionStart hook; --harness H adds which CLI it serves)
|
|
16247
16428
|
crew skills sync [route] push only crew's skill files (e.g. grill-me) into the workspace Agent Skills table
|
|
16429
|
+
crew ship rename NAME [--json] rename this ship in crew.yaml and on every workspace's Ships row
|
|
16248
16430
|
crew repos add [ROUTE] [NAME] [PATH] attach a local checkout to a route in crew.yaml (--dry-run previews)
|
|
16249
16431
|
crew repos clone [ROUTE] NAME clone a Repos row's remote into the route directory and attach it (--dir PATH, --dry-run, --json)
|
|
16250
16432
|
crew repos hooks [ROUTE] NAME suggest test/build hooks for a checkout (--agent, --write route|repo, --recipes, --json)
|
|
@@ -16333,6 +16515,7 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
16333
16515
|
"run",
|
|
16334
16516
|
"sample-ticket",
|
|
16335
16517
|
"selftest",
|
|
16518
|
+
"ship",
|
|
16336
16519
|
"skills",
|
|
16337
16520
|
"status",
|
|
16338
16521
|
"sync",
|
|
@@ -16482,9 +16665,9 @@ if (command === "passenger-sync-daemon") {
|
|
|
16482
16665
|
firstRun = { answers, configPath };
|
|
16483
16666
|
cfg = loadConfig(CREW_HOME, configPath, { text: renderShipBlock(answers), allowNoRoutes: true });
|
|
16484
16667
|
} else {
|
|
16485
|
-
const toleratesNoRoutes = command === "repos" || command === "disconnect" && flag("reset") || command === "connect" && flag("sign-out");
|
|
16668
|
+
const toleratesNoRoutes = command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "connect" && flag("sign-out");
|
|
16486
16669
|
const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset");
|
|
16487
|
-
if (configless && !
|
|
16670
|
+
if (configless && !existsSync34(findConfigFile(CREW_HOME))) {
|
|
16488
16671
|
cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
|
|
16489
16672
|
} else {
|
|
16490
16673
|
cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
|
|
@@ -16506,7 +16689,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
16506
16689
|
const DAEMON_SUBCOMMANDS = /* @__PURE__ */ new Set(["start", "stop", "status", "restart"]);
|
|
16507
16690
|
const worktreeArgs = command === "worktree" ? splitWorktreeArgs(positional, cfg.routes.map((r) => r.route)) : void 0;
|
|
16508
16691
|
const named = worktreeArgs ? worktreeArgs.route : command === "daemon" && DAEMON_SUBCOMMANDS.has(positional[1] ?? "") ? positional[2] : positional[1];
|
|
16509
|
-
const fleetWide = command === "inbox" || command === "connect" || command === "disconnect" || command === "agents" || command === "skills" || command === "logbook" || command === "repos" || command === "projects" || command === "whoami" || releaseFleetWide || command === "pause" || command === "resume" || command === "reload" || FLEET_CAPABLE.has(command) && !named && cfg.routes.length > 1 || worktreeArgs !== void 0 && !named && cfg.routes.length > 1;
|
|
16692
|
+
const fleetWide = command === "inbox" || command === "connect" || command === "disconnect" || command === "agents" || command === "skills" || command === "logbook" || command === "repos" || command === "ship" || command === "projects" || command === "whoami" || releaseFleetWide || command === "pause" || command === "resume" || command === "reload" || FLEET_CAPABLE.has(command) && !named && cfg.routes.length > 1 || worktreeArgs !== void 0 && !named && cfg.routes.length > 1;
|
|
16510
16693
|
let route;
|
|
16511
16694
|
try {
|
|
16512
16695
|
route = fleetWide ? cfg.routes[0] : findRoute(cfg, named);
|
|
@@ -16723,7 +16906,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
16723
16906
|
if (host === "linux" && hasSystemd) {
|
|
16724
16907
|
const label = labelFor(CREW_HOME, "run");
|
|
16725
16908
|
const staleTimer = join35(homedir9(), ".config", "systemd", "user", `${label}.timer`);
|
|
16726
|
-
if (
|
|
16909
|
+
if (existsSync34(staleTimer)) {
|
|
16727
16910
|
process.stdout.write(
|
|
16728
16911
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
16729
16912
|
`
|
|
@@ -16749,7 +16932,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
16749
16932
|
const tracker = new Tracker(c, cfg.ship);
|
|
16750
16933
|
problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
|
|
16751
16934
|
for (const r of await resolvedRepos(c)) {
|
|
16752
|
-
if (!
|
|
16935
|
+
if (!existsSync34(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
16753
16936
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
16754
16937
|
problems.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
16755
16938
|
}
|
|
@@ -16767,7 +16950,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
16767
16950
|
}
|
|
16768
16951
|
async function doctorFix(o) {
|
|
16769
16952
|
const routes = await Promise.all(routesInScope(cfg.routes, named).map((c) => gatherHealth({ route: c.route, enabled: c.enabled, hasRepos: reposOf(c).length > 0 }, () => routeProblems(c))));
|
|
16770
|
-
const unitExists = (p) =>
|
|
16953
|
+
const unitExists = (p) => existsSync34(p);
|
|
16771
16954
|
const crontab = o.host === "linux" ? spawnSync6("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
16772
16955
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run"), unitExists, crontab) : true;
|
|
16773
16956
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -16784,7 +16967,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
16784
16967
|
for (const a of plan.actions) {
|
|
16785
16968
|
if (a.kind === "enable") {
|
|
16786
16969
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
16787
|
-
|
|
16970
|
+
writeFileSync22(cfg.configFile, planEnable(readFileSync28(cfg.configFile, "utf8"), a.route));
|
|
16788
16971
|
enabledSound.add(a.route);
|
|
16789
16972
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
16790
16973
|
} else {
|
|
@@ -17267,6 +17450,11 @@ ${changed2.map((c) => ` - ${c}
|
|
|
17267
17450
|
case "poll":
|
|
17268
17451
|
case "run": {
|
|
17269
17452
|
const skipInlineRelease = flag("no-release");
|
|
17453
|
+
if (!dryRun) {
|
|
17454
|
+
for (const r of fleetWide ? cfg.routes : [route]) {
|
|
17455
|
+
await reconcileRouteShipName({ route: r, ship: cfg.ship, configFile: cfg.configFile, emit: emit.forRoute(r.route) });
|
|
17456
|
+
}
|
|
17457
|
+
}
|
|
17270
17458
|
if (fleetWide) {
|
|
17271
17459
|
const fleet = await decideFleet({
|
|
17272
17460
|
routes: cfg.routes,
|
|
@@ -17800,6 +17988,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
17800
17988
|
state,
|
|
17801
17989
|
maxConcurrentAgents: cfg.ship.maxConcurrentAgents,
|
|
17802
17990
|
spawnBreaker,
|
|
17991
|
+
configFile: cfg.configFile,
|
|
17803
17992
|
newEmitter: () => new Emitter({
|
|
17804
17993
|
route: route.route,
|
|
17805
17994
|
eventFile: eventFileFor(cfg.ship.stateDir),
|
|
@@ -18006,15 +18195,72 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18006
18195
|
`);
|
|
18007
18196
|
if (!dryRun) {
|
|
18008
18197
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
18009
|
-
const raw =
|
|
18198
|
+
const raw = existsSync34(resolvedPath) ? JSON.parse(readFileSync28(resolvedPath, "utf8")) : {};
|
|
18010
18199
|
raw.agentSkills = result.agentSkills;
|
|
18011
|
-
|
|
18012
|
-
|
|
18200
|
+
mkdirSync21(dirOf(resolvedPath), { recursive: true });
|
|
18201
|
+
writeFileSync22(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
18013
18202
|
`);
|
|
18014
18203
|
}
|
|
18015
18204
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
18016
18205
|
break;
|
|
18017
18206
|
}
|
|
18207
|
+
case "ship": {
|
|
18208
|
+
if (positional[1] !== "rename" || positional.length !== 3) {
|
|
18209
|
+
process.stderr.write("crew ship rename NAME [--json]\n");
|
|
18210
|
+
process.exit(2);
|
|
18211
|
+
}
|
|
18212
|
+
let newName;
|
|
18213
|
+
try {
|
|
18214
|
+
newName = validateShipName(positional[2]);
|
|
18215
|
+
} catch (e) {
|
|
18216
|
+
if (e instanceof ShipNameError) {
|
|
18217
|
+
process.stderr.write(`crew ship: ${e.message}
|
|
18218
|
+
`);
|
|
18219
|
+
process.exit(2);
|
|
18220
|
+
}
|
|
18221
|
+
throw e;
|
|
18222
|
+
}
|
|
18223
|
+
const oldName = cfg.ship.name;
|
|
18224
|
+
writeFileSync22(cfg.configFile, setShipNameInYaml(readFileSync28(cfg.configFile, "utf8"), newName));
|
|
18225
|
+
cfg.ship.name = newName;
|
|
18226
|
+
let renameMachineId;
|
|
18227
|
+
try {
|
|
18228
|
+
renameMachineId = ensureMachineId(cfg.ship.stateDir);
|
|
18229
|
+
} catch {
|
|
18230
|
+
}
|
|
18231
|
+
const outcomes = [];
|
|
18232
|
+
for (const r of cfg.routes) {
|
|
18233
|
+
if (!r.resolved?.shipsModelId) {
|
|
18234
|
+
outcomes.push({ route: r.route, status: "no-ships-table" });
|
|
18235
|
+
continue;
|
|
18236
|
+
}
|
|
18237
|
+
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, r.route);
|
|
18238
|
+
const tracker = new Tracker(r, cfg.ship);
|
|
18239
|
+
const picked = pickShipRow(await tracker.shipRows(), renameMachineId, [oldName, readSyncedName(resolvedFile) ?? ""]);
|
|
18240
|
+
if (picked.kind !== "row") {
|
|
18241
|
+
outcomes.push({ route: r.route, status: picked.kind === "unsupported" ? "unsupported" : picked.kind === "none" ? "no-row" : "ambiguous" });
|
|
18242
|
+
continue;
|
|
18243
|
+
}
|
|
18244
|
+
try {
|
|
18245
|
+
await tracker.renameShipRow(picked.row.id, newName);
|
|
18246
|
+
writeSyncedName(resolvedFile, newName);
|
|
18247
|
+
outcomes.push({ route: r.route, status: "renamed", rowId: picked.row.id });
|
|
18248
|
+
} catch (e) {
|
|
18249
|
+
outcomes.push({ route: r.route, status: "refused", rowId: picked.row.id, error: e.message });
|
|
18250
|
+
}
|
|
18251
|
+
}
|
|
18252
|
+
if (flag("json")) {
|
|
18253
|
+
process.stdout.write(`${JSON.stringify({ name: newName, previous: oldName, configFile: cfg.configFile, routes: outcomes })}
|
|
18254
|
+
`);
|
|
18255
|
+
} else {
|
|
18256
|
+
process.stdout.write(`ship renamed "${oldName}" -> "${newName}" in ${cfg.configFile}
|
|
18257
|
+
`);
|
|
18258
|
+
for (const o of outcomes) process.stdout.write(` ${o.route}: ${o.status}${o.error ? ` (${o.error})` : ""}
|
|
18259
|
+
`);
|
|
18260
|
+
}
|
|
18261
|
+
if (outcomes.some((o) => o.status === "refused")) process.exitCode = 1;
|
|
18262
|
+
break;
|
|
18263
|
+
}
|
|
18018
18264
|
case "repos": {
|
|
18019
18265
|
const sub = positional[1];
|
|
18020
18266
|
const usage2 = () => {
|
|
@@ -18022,13 +18268,13 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18022
18268
|
process.exit(2);
|
|
18023
18269
|
};
|
|
18024
18270
|
if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks") usage2();
|
|
18025
|
-
const configText =
|
|
18271
|
+
const configText = readFileSync28(cfg.configFile, "utf8");
|
|
18026
18272
|
if (sub === "hooks") {
|
|
18027
18273
|
try {
|
|
18028
18274
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
18029
|
-
const dir = repoDirOf(configText,
|
|
18030
|
-
if (!
|
|
18031
|
-
const files = { exists: (r) =>
|
|
18275
|
+
const dir = repoDirOf(configText, dirname20(cfg.configFile), a.route, a.name);
|
|
18276
|
+
if (!existsSync34(dir)) throw new ReposError(`${dir} does not exist on disk`);
|
|
18277
|
+
const files = { exists: (r) => existsSync34(resolvePath(dir, r)), list: (r) => {
|
|
18032
18278
|
try {
|
|
18033
18279
|
return readdirSync14(resolvePath(dir, r));
|
|
18034
18280
|
} catch {
|
|
@@ -18036,7 +18282,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18036
18282
|
}
|
|
18037
18283
|
}, read: (r) => {
|
|
18038
18284
|
try {
|
|
18039
|
-
return
|
|
18285
|
+
return readFileSync28(resolvePath(dir, r), "utf8");
|
|
18040
18286
|
} catch {
|
|
18041
18287
|
return void 0;
|
|
18042
18288
|
}
|
|
@@ -18074,13 +18320,13 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18074
18320
|
if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
18075
18321
|
if (write === "route") {
|
|
18076
18322
|
const plan = planRouteHooks(configText, a.route, a.name, picked.hooks);
|
|
18077
|
-
|
|
18323
|
+
writeFileSync22(cfg.configFile, plan.text);
|
|
18078
18324
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept };
|
|
18079
18325
|
} else if (write === "repo") {
|
|
18080
18326
|
const have = existingRepoConfig(files);
|
|
18081
18327
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
18082
18328
|
const file = resolvePath(dir, ".crew.yaml");
|
|
18083
|
-
|
|
18329
|
+
writeFileSync22(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
|
|
18084
18330
|
written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [] };
|
|
18085
18331
|
}
|
|
18086
18332
|
if (flag("json")) {
|
|
@@ -18132,7 +18378,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18132
18378
|
if (sub === "list" && !routeName) usage2();
|
|
18133
18379
|
if (sub === "add") {
|
|
18134
18380
|
try {
|
|
18135
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
18381
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync34(resolvePath(p.replace(/^~(?=\/|$)/, homedir9()))));
|
|
18136
18382
|
routeName = a.route;
|
|
18137
18383
|
addPath = a.path;
|
|
18138
18384
|
addName = a.name;
|
|
@@ -18161,15 +18407,15 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18161
18407
|
}
|
|
18162
18408
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
18163
18409
|
let resolvedRemotes;
|
|
18164
|
-
if (
|
|
18410
|
+
if (existsSync34(resolvedFile)) {
|
|
18165
18411
|
try {
|
|
18166
|
-
resolvedRemotes = JSON.parse(
|
|
18412
|
+
resolvedRemotes = JSON.parse(readFileSync28(resolvedFile, "utf8"));
|
|
18167
18413
|
} catch {
|
|
18168
18414
|
}
|
|
18169
18415
|
}
|
|
18170
18416
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
18171
18417
|
const text = configText;
|
|
18172
|
-
const base =
|
|
18418
|
+
const base = dirname20(cfg.configFile);
|
|
18173
18419
|
try {
|
|
18174
18420
|
if (sub === "list") {
|
|
18175
18421
|
if (flag("json")) {
|
|
@@ -18179,10 +18425,10 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18179
18425
|
base,
|
|
18180
18426
|
resolved: resolvedRemotes,
|
|
18181
18427
|
originOf,
|
|
18182
|
-
exists: (d) =>
|
|
18428
|
+
exists: (d) => existsSync34(d),
|
|
18183
18429
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
18184
18430
|
try {
|
|
18185
|
-
return Object.keys(parseRepoConfig(
|
|
18431
|
+
return Object.keys(parseRepoConfig(readFileSync28(resolvePath(d, f), "utf8"), f).hooks).length > 0;
|
|
18186
18432
|
} catch {
|
|
18187
18433
|
return false;
|
|
18188
18434
|
}
|
|
@@ -18198,7 +18444,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18198
18444
|
for (const r of repos) {
|
|
18199
18445
|
const origin2 = originOf(r.dir);
|
|
18200
18446
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
18201
|
-
const note = !
|
|
18447
|
+
const note = !existsSync34(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
|
|
18202
18448
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
18203
18449
|
`);
|
|
18204
18450
|
}
|
|
@@ -18210,9 +18456,9 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18210
18456
|
` : `${human}
|
|
18211
18457
|
`);
|
|
18212
18458
|
const plan2 = planRepoClone({ text, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir") });
|
|
18213
|
-
if (
|
|
18459
|
+
if (existsSync34(plan2.target) && (existsSync34(`${plan2.target}/.git`) || readdirSync14(plan2.target).length > 0)) {
|
|
18214
18460
|
throw new ReposError(
|
|
18215
|
-
|
|
18461
|
+
existsSync34(`${plan2.target}/.git`) ? `${plan2.target} is already a checkout \u2014 attach it with \`crew repos add ${routeName} ${plan2.name} ${plan2.target}\`` : `${plan2.target} exists and is not empty \u2014 pass --dir to clone somewhere else`
|
|
18216
18462
|
);
|
|
18217
18463
|
}
|
|
18218
18464
|
if (dryRun) {
|
|
@@ -18230,12 +18476,12 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18230
18476
|
throw new ReposError(`${e.message}${authHint}
|
|
18231
18477
|
crew.yaml was not changed`);
|
|
18232
18478
|
}
|
|
18233
|
-
|
|
18479
|
+
writeFileSync22(cfg.configFile, plan2.text);
|
|
18234
18480
|
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}`);
|
|
18235
18481
|
break;
|
|
18236
18482
|
}
|
|
18237
18483
|
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir9()));
|
|
18238
|
-
const top =
|
|
18484
|
+
const top = existsSync34(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
18239
18485
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
18240
18486
|
const origin = originOf(top);
|
|
18241
18487
|
const plan = planRepoAdd({
|
|
@@ -18284,15 +18530,15 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18284
18530
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
18285
18531
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
18286
18532
|
`);
|
|
18287
|
-
if (
|
|
18288
|
-
const raw = JSON.parse(
|
|
18533
|
+
if (existsSync34(resolvedFile)) {
|
|
18534
|
+
const raw = JSON.parse(readFileSync28(resolvedFile, "utf8"));
|
|
18289
18535
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
18290
18536
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
18291
|
-
|
|
18537
|
+
writeFileSync22(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
18292
18538
|
`);
|
|
18293
18539
|
}
|
|
18294
18540
|
}
|
|
18295
|
-
|
|
18541
|
+
writeFileSync22(cfg.configFile, plan.text);
|
|
18296
18542
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
18297
18543
|
`);
|
|
18298
18544
|
} catch (e) {
|
|
@@ -18447,8 +18693,8 @@ ${reconstruction.prompt}
|
|
|
18447
18693
|
}
|
|
18448
18694
|
}
|
|
18449
18695
|
let signInSource = apiKey ? "key" : "device";
|
|
18450
|
-
if (!apiKey && !workspace &&
|
|
18451
|
-
const
|
|
18696
|
+
if (!apiKey && !workspace && store) {
|
|
18697
|
+
const resumable = await resumableSessions({
|
|
18452
18698
|
store,
|
|
18453
18699
|
stateDir: cfg.ship.stateDir,
|
|
18454
18700
|
host: hostFromUrl4(baseUrl),
|
|
@@ -18461,6 +18707,14 @@ ${reconstruction.prompt}
|
|
|
18461
18707
|
}
|
|
18462
18708
|
}
|
|
18463
18709
|
});
|
|
18710
|
+
if (resumable.length > 1 && connectEvents.json) {
|
|
18711
|
+
stopForChoice(
|
|
18712
|
+
"workspace",
|
|
18713
|
+
`${resumable.length} workspaces have a remembered sign-in on ${hostFromUrl4(baseUrl)} \u2014 which one?`,
|
|
18714
|
+
resumable.map((r) => ({ value: r.workspace, label: r.workspace }))
|
|
18715
|
+
);
|
|
18716
|
+
}
|
|
18717
|
+
const resumed = resumable.length === 1 ? resumable[0] : void 0;
|
|
18464
18718
|
if (resumed) {
|
|
18465
18719
|
apiKey = resumed.apiKey;
|
|
18466
18720
|
workspace = resumed.workspace;
|
|
@@ -18576,7 +18830,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18576
18830
|
platform: cfg.ship.platform,
|
|
18577
18831
|
machineId,
|
|
18578
18832
|
hostPassengers: firstRun?.answers.hostPassengers ?? existingRoute?.hostPassengers === true,
|
|
18579
|
-
sshPublicKey
|
|
18833
|
+
sshPublicKey,
|
|
18834
|
+
lastSyncedName: (r) => readSyncedName(resolvedPathFor(cfg.ship.stateDir, r))
|
|
18580
18835
|
};
|
|
18581
18836
|
const runConnect = async (ws, proj) => {
|
|
18582
18837
|
let found = await discover({ ...authOpts, workspace: ws, project: proj, area: value("area"), ship, dryRun, interactive: canAsk || connectEvents.json });
|
|
@@ -18633,6 +18888,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18633
18888
|
}
|
|
18634
18889
|
process.exit(2);
|
|
18635
18890
|
}
|
|
18891
|
+
if (found.shipNameAdopted && !dryRun) {
|
|
18892
|
+
writeFileSync22(cfg.configFile, setShipNameInYaml(readFileSync28(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
18893
|
+
cfg.ship.name = found.shipNameAdopted;
|
|
18894
|
+
}
|
|
18636
18895
|
if (found.shipRow) {
|
|
18637
18896
|
connectEvents.emit("ship_row", `Ships row "${found.shipRow.name}" ${found.shipRow.action}`, found.shipRow);
|
|
18638
18897
|
}
|
|
@@ -18673,10 +18932,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18673
18932
|
}
|
|
18674
18933
|
}
|
|
18675
18934
|
let previous;
|
|
18676
|
-
const hadPreviousFile =
|
|
18935
|
+
const hadPreviousFile = existsSync34(resolvedPath);
|
|
18677
18936
|
if (hadPreviousFile) {
|
|
18678
18937
|
try {
|
|
18679
|
-
previous = JSON.parse(
|
|
18938
|
+
previous = JSON.parse(readFileSync28(resolvedPath, "utf8"));
|
|
18680
18939
|
} catch {
|
|
18681
18940
|
}
|
|
18682
18941
|
}
|
|
@@ -18788,18 +19047,20 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18788
19047
|
// nothing about its prompt actually changed.
|
|
18789
19048
|
...previous?.agentPersonas ? { agentPersonas: previous.agentPersonas } : {},
|
|
18790
19049
|
...previous?.agentSkills ? { agentSkills: previous.agentSkills } : {},
|
|
18791
|
-
...previous?.releaseAgentId ? { releaseAgentId: previous.releaseAgentId } : {}
|
|
19050
|
+
...previous?.releaseAgentId ? { releaseAgentId: previous.releaseAgentId } : {},
|
|
19051
|
+
// CREW-2495: the ship name this route last saw agree with its Ships row.
|
|
19052
|
+
...found.shipRow ? { shipName: found.shipRow.name } : previous?.shipName ? { shipName: previous.shipName } : {}
|
|
18792
19053
|
};
|
|
18793
19054
|
if (dryRun) {
|
|
18794
19055
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
18795
19056
|
`);
|
|
18796
19057
|
} else {
|
|
18797
|
-
|
|
19058
|
+
mkdirSync21(dirname20(resolvedPath), { recursive: true });
|
|
18798
19059
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
18799
19060
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
18800
|
-
|
|
19061
|
+
writeFileSync22(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
18801
19062
|
`);
|
|
18802
|
-
|
|
19063
|
+
renameSync3(tmpPath, resolvedPath);
|
|
18803
19064
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
18804
19065
|
`);
|
|
18805
19066
|
}
|
|
@@ -18839,8 +19100,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18839
19100
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
18840
19101
|
`);
|
|
18841
19102
|
} else {
|
|
18842
|
-
|
|
18843
|
-
|
|
19103
|
+
mkdirSync21(dirname20(apiKeyFile), { recursive: true });
|
|
19104
|
+
writeFileSync22(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
18844
19105
|
`, { mode: 384 });
|
|
18845
19106
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
18846
19107
|
`);
|
|
@@ -19528,7 +19789,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19528
19789
|
};
|
|
19529
19790
|
const droppedRoutes = [];
|
|
19530
19791
|
try {
|
|
19531
|
-
const rawCfg = parseYaml2(
|
|
19792
|
+
const rawCfg = parseYaml2(readFileSync28(cfg.configFile, "utf8"));
|
|
19532
19793
|
for (const r of rawCfg?.routes ?? []) {
|
|
19533
19794
|
if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
|
|
19534
19795
|
try {
|
|
@@ -19570,8 +19831,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19570
19831
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
19571
19832
|
break;
|
|
19572
19833
|
}
|
|
19573
|
-
|
|
19574
|
-
|
|
19834
|
+
mkdirSync21(cfg.ship.stateDir, { recursive: true });
|
|
19835
|
+
writeFileSync22(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
19575
19836
|
`);
|
|
19576
19837
|
process.stdout.write("crew reload: requested \u2014 the daemon finishes its in-flight pass, then relaunches with the new config\n");
|
|
19577
19838
|
if (flag("now") && control?.stopCommand && control.startCommand) {
|
|
@@ -19622,7 +19883,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19622
19883
|
}
|
|
19623
19884
|
case "log": {
|
|
19624
19885
|
try {
|
|
19625
|
-
const text =
|
|
19886
|
+
const text = readFileSync28(cfg.ship.logFile, "utf8").split("\n");
|
|
19626
19887
|
process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
19627
19888
|
`);
|
|
19628
19889
|
} catch {
|
|
@@ -19645,8 +19906,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19645
19906
|
};
|
|
19646
19907
|
if (!route.resolved) fail(`route ${route.route} is not connected \u2014 run \`crew connect\` first`);
|
|
19647
19908
|
const tracker = new Tracker(route, cfg.ship);
|
|
19648
|
-
const configText =
|
|
19649
|
-
const cfgBase =
|
|
19909
|
+
const configText = readFileSync28(cfg.configFile, "utf8");
|
|
19910
|
+
const cfgBase = dirname20(cfg.configFile);
|
|
19650
19911
|
const configured = configuredRepos(configText, route.route, cfgBase);
|
|
19651
19912
|
const dir = configured.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured.map((r) => r.dir), route.reposBasePath, route.route.split("/")[0]);
|
|
19652
19913
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route.route);
|
|
@@ -19674,7 +19935,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19674
19935
|
}
|
|
19675
19936
|
if (configured.some((r) => r.dir === dir)) {
|
|
19676
19937
|
try {
|
|
19677
|
-
|
|
19938
|
+
writeFileSync22(cfg.configFile, planRepoRemove(configText, cfgBase, route.route, dir).text);
|
|
19678
19939
|
} catch (e) {
|
|
19679
19940
|
if (e instanceof ReposError) fail(e.message);
|
|
19680
19941
|
throw e;
|
|
@@ -19682,29 +19943,29 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19682
19943
|
}
|
|
19683
19944
|
if (row) {
|
|
19684
19945
|
await tracker.deleteRepoRow(row.id);
|
|
19685
|
-
if (
|
|
19686
|
-
const raw = JSON.parse(
|
|
19946
|
+
if (existsSync34(resolvedFile)) {
|
|
19947
|
+
const raw = JSON.parse(readFileSync28(resolvedFile, "utf8"));
|
|
19687
19948
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
19688
19949
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
19689
|
-
|
|
19950
|
+
writeFileSync22(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
19690
19951
|
`);
|
|
19691
19952
|
}
|
|
19692
19953
|
}
|
|
19693
|
-
if (
|
|
19954
|
+
if (existsSync34(join35(dir, ".git")) && basename11(dir) === SAMPLE_REPO) rmSync15(dir, { recursive: true, force: true });
|
|
19694
19955
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
19695
19956
|
break;
|
|
19696
19957
|
}
|
|
19697
19958
|
const projectId = route.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
19698
|
-
const fresh = !
|
|
19699
|
-
if (
|
|
19959
|
+
const fresh = !existsSync34(join35(dir, ".git"));
|
|
19960
|
+
if (existsSync34(dir) && fresh && readdirSync14(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
19700
19961
|
if (dryRun) {
|
|
19701
19962
|
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`);
|
|
19702
19963
|
break;
|
|
19703
19964
|
}
|
|
19704
19965
|
if (fresh) {
|
|
19705
|
-
|
|
19706
|
-
|
|
19707
|
-
|
|
19966
|
+
mkdirSync21(dir, { recursive: true });
|
|
19967
|
+
writeFileSync22(join35(dir, "README.md"), SAMPLE_README);
|
|
19968
|
+
writeFileSync22(join35(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
19708
19969
|
const g = (...a) => {
|
|
19709
19970
|
const r = spawnSync6("git", a, { cwd: dir, encoding: "utf8" });
|
|
19710
19971
|
if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -19722,17 +19983,17 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19722
19983
|
spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
|
|
19723
19984
|
});
|
|
19724
19985
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
19725
|
-
if (
|
|
19726
|
-
const raw = JSON.parse(
|
|
19986
|
+
if (existsSync34(resolvedFile)) {
|
|
19987
|
+
const raw = JSON.parse(readFileSync28(resolvedFile, "utf8"));
|
|
19727
19988
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
19728
19989
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
19729
|
-
|
|
19990
|
+
writeFileSync22(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
19730
19991
|
`);
|
|
19731
19992
|
}
|
|
19732
19993
|
}
|
|
19733
19994
|
if (!configured.some((r) => r.dir === dir)) {
|
|
19734
19995
|
try {
|
|
19735
|
-
|
|
19996
|
+
writeFileSync22(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route.route, path: dir, name: SAMPLE_REPO }).text);
|
|
19736
19997
|
} catch (e) {
|
|
19737
19998
|
if (e instanceof ReposError) fail(e.message);
|
|
19738
19999
|
throw e;
|
|
@@ -19853,6 +20114,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19853
20114
|
const projects = projectModeLabels(await statusTracker.projectRowsWithModes().catch(() => []));
|
|
19854
20115
|
const crewMinVersion = await readCrewFloor(route, cfg.ship) ?? null;
|
|
19855
20116
|
if (flag("json")) {
|
|
20117
|
+
const nameSync = await reconcileRouteShipName({ route, ship: cfg.ship, configFile: cfg.configFile, readOnly: true });
|
|
19856
20118
|
const repos = await resolvedRepos(route);
|
|
19857
20119
|
const seatIds = route.resolved?.seats ?? {};
|
|
19858
20120
|
const crewRows = Object.keys(seatIds).length ? await statusTracker.crewRows() : [];
|
|
@@ -19896,7 +20158,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19896
20158
|
eventFile: eventFileFor(cfg.ship.stateDir),
|
|
19897
20159
|
// Additive (CREW-1373): open ship-level items, including the
|
|
19898
20160
|
// derived `release_stale`. `version` stays 1.
|
|
19899
|
-
attention: openShipAttention(state)
|
|
20161
|
+
attention: openShipAttention(state),
|
|
20162
|
+
// Additive (CREW-2495): this ship's Ships-row name and whether it has drifted from `name`.
|
|
20163
|
+
rowName: nameSync.rowName ?? null,
|
|
20164
|
+
nameDrift: nameSync.drift
|
|
19900
20165
|
},
|
|
19901
20166
|
// Additive (CREW-1402): what the operator owes. `version` stays 1.
|
|
19902
20167
|
todo: todo.map((i) => ({ ticket: displayKey(i.ticket), title: i.ticket.title ?? null, reasons: i.reasons })),
|
|
@@ -20043,7 +20308,7 @@ watermark: ${state.watermark()}
|
|
|
20043
20308
|
}
|
|
20044
20309
|
for (const r of repos) {
|
|
20045
20310
|
const remote = r.config.branch.remote;
|
|
20046
|
-
if (
|
|
20311
|
+
if (existsSync34(r.dir)) {
|
|
20047
20312
|
pushAuth.push({
|
|
20048
20313
|
route: c.route,
|
|
20049
20314
|
repo: r.name,
|
|
@@ -20071,7 +20336,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
20071
20336
|
);
|
|
20072
20337
|
try {
|
|
20073
20338
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
20074
|
-
const evSize =
|
|
20339
|
+
const evSize = existsSync34(evFile) ? statSync10(evFile).size : 0;
|
|
20075
20340
|
process.stdout.write(
|
|
20076
20341
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
20077
20342
|
` : "events log: ok\n"
|
|
@@ -20082,7 +20347,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
20082
20347
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run");
|
|
20083
20348
|
const abandons = unitAbandonsAgents(unitPlan, (p) => {
|
|
20084
20349
|
try {
|
|
20085
|
-
return
|
|
20350
|
+
return readFileSync28(p, "utf8");
|
|
20086
20351
|
} catch {
|
|
20087
20352
|
return null;
|
|
20088
20353
|
}
|