@tablation/crew 0.63.0 → 0.63.2
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 +223 -159
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5183,7 +5183,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
5183
5183
|
}
|
|
5184
5184
|
|
|
5185
5185
|
// src/cli.ts
|
|
5186
|
-
import { resolve as resolve5, dirname as
|
|
5186
|
+
import { resolve as resolve5, dirname as dirname19, join as join32, basename as basename10 } from "path";
|
|
5187
5187
|
import { fileURLToPath } from "url";
|
|
5188
5188
|
import { hostname as hostname2, homedir as homedir8 } from "os";
|
|
5189
5189
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
@@ -5762,15 +5762,52 @@ var State = class _State {
|
|
|
5762
5762
|
}
|
|
5763
5763
|
};
|
|
5764
5764
|
|
|
5765
|
-
// src/
|
|
5766
|
-
import {
|
|
5765
|
+
// src/connect-resume.ts
|
|
5766
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
|
|
5767
5767
|
import { dirname as dirname6, join as join14 } from "path";
|
|
5768
|
+
function lastSessionFile(stateDir) {
|
|
5769
|
+
return join14(stateDir, "connect-session.json");
|
|
5770
|
+
}
|
|
5771
|
+
function readPointers(stateDir) {
|
|
5772
|
+
try {
|
|
5773
|
+
const parsed = JSON.parse(readFileSync13(lastSessionFile(stateDir), "utf8"));
|
|
5774
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
5775
|
+
return Object.fromEntries(Object.entries(parsed).filter((e) => typeof e[1] === "string"));
|
|
5776
|
+
} catch {
|
|
5777
|
+
return {};
|
|
5778
|
+
}
|
|
5779
|
+
}
|
|
5780
|
+
function rememberSession(stateDir, host, workspaceSlug) {
|
|
5781
|
+
try {
|
|
5782
|
+
const file = lastSessionFile(stateDir);
|
|
5783
|
+
mkdirSync7(dirname6(file), { recursive: true });
|
|
5784
|
+
writeFileSync8(file, `${JSON.stringify({ ...readPointers(stateDir), [host]: workspaceSlug }, null, 2)}
|
|
5785
|
+
`, { mode: 384 });
|
|
5786
|
+
} catch {
|
|
5787
|
+
}
|
|
5788
|
+
}
|
|
5789
|
+
async function resumeKeychainSession(o) {
|
|
5790
|
+
const workspace = readPointers(o.stateDir)[o.host];
|
|
5791
|
+
if (!workspace) return void 0;
|
|
5792
|
+
try {
|
|
5793
|
+
const stored = await o.store.get(o.host, workspace);
|
|
5794
|
+
if (!stored?.apiKey) return void 0;
|
|
5795
|
+
if (!await o.verify(stored.apiKey)) return void 0;
|
|
5796
|
+
return { workspace, apiKey: stored.apiKey };
|
|
5797
|
+
} catch {
|
|
5798
|
+
return void 0;
|
|
5799
|
+
}
|
|
5800
|
+
}
|
|
5801
|
+
|
|
5802
|
+
// src/reset.ts
|
|
5803
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync14, readdirSync as readdirSync4, rmSync as rmSync7, statSync as statSync5, writeFileSync as writeFileSync9 } from "fs";
|
|
5804
|
+
import { dirname as dirname7, join as join15 } from "path";
|
|
5768
5805
|
function clonedCheckoutsFile(stateDir) {
|
|
5769
|
-
return
|
|
5806
|
+
return join15(stateDir, "cloned-checkouts.json");
|
|
5770
5807
|
}
|
|
5771
5808
|
function readClonedCheckouts(stateDir) {
|
|
5772
5809
|
try {
|
|
5773
|
-
const parsed = JSON.parse(
|
|
5810
|
+
const parsed = JSON.parse(readFileSync14(clonedCheckoutsFile(stateDir), "utf8"));
|
|
5774
5811
|
return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
|
|
5775
5812
|
} catch {
|
|
5776
5813
|
return [];
|
|
@@ -5780,8 +5817,8 @@ function recordClonedCheckout(stateDir, dir) {
|
|
|
5780
5817
|
const known = readClonedCheckouts(stateDir);
|
|
5781
5818
|
if (known.includes(dir)) return;
|
|
5782
5819
|
const file = clonedCheckoutsFile(stateDir);
|
|
5783
|
-
|
|
5784
|
-
|
|
5820
|
+
mkdirSync8(dirname7(file), { recursive: true });
|
|
5821
|
+
writeFileSync9(file, `${JSON.stringify([...known, dir], null, 2)}
|
|
5785
5822
|
`);
|
|
5786
5823
|
}
|
|
5787
5824
|
var mark = (dryRun) => dryRun ? "would-remove" : "removed";
|
|
@@ -5836,10 +5873,10 @@ function removeCheckout(dir, dryRun, fx) {
|
|
|
5836
5873
|
}
|
|
5837
5874
|
function removeState(stateDir, dryRun) {
|
|
5838
5875
|
const out = [];
|
|
5839
|
-
const targets = [
|
|
5876
|
+
const targets = [join15(stateDir, "resolved"), join15(stateDir, "keys"), clonedCheckoutsFile(stateDir), join15(stateDir, "connect-session.json"), join15(stateDir, "reload-requested"), join15(stateDir, "daemon.started")];
|
|
5840
5877
|
try {
|
|
5841
5878
|
for (const name of readdirSync4(stateDir)) {
|
|
5842
|
-
if (name.startsWith(".") && statSync5(
|
|
5879
|
+
if (name.startsWith(".") && statSync5(join15(stateDir, name)).isFile()) targets.push(join15(stateDir, name));
|
|
5843
5880
|
}
|
|
5844
5881
|
} catch {
|
|
5845
5882
|
}
|
|
@@ -6113,18 +6150,18 @@ import { existsSync as existsSync16, statfsSync } from "fs";
|
|
|
6113
6150
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
6114
6151
|
import {
|
|
6115
6152
|
existsSync as existsSync15,
|
|
6116
|
-
mkdirSync as
|
|
6153
|
+
mkdirSync as mkdirSync9,
|
|
6117
6154
|
openSync as openSync3,
|
|
6118
6155
|
closeSync as closeSync3,
|
|
6119
6156
|
writeSync,
|
|
6120
6157
|
statSync as statSync6,
|
|
6121
6158
|
unlinkSync as unlinkSync2,
|
|
6122
|
-
readFileSync as
|
|
6123
|
-
writeFileSync as
|
|
6159
|
+
readFileSync as readFileSync15,
|
|
6160
|
+
writeFileSync as writeFileSync10,
|
|
6124
6161
|
readdirSync as readdirSync5,
|
|
6125
6162
|
rmSync as rmSync8
|
|
6126
6163
|
} from "fs";
|
|
6127
|
-
import { dirname as
|
|
6164
|
+
import { dirname as dirname8, basename as basename5, join as join16 } from "path";
|
|
6128
6165
|
var GitError = class extends Error {
|
|
6129
6166
|
};
|
|
6130
6167
|
function streamText(x) {
|
|
@@ -6490,7 +6527,7 @@ function recoverStrandedReleaseCheckouts(cwd) {
|
|
|
6490
6527
|
if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
|
|
6491
6528
|
else if (line.startsWith("branch refs/heads/crew/release-") && path) {
|
|
6492
6529
|
gitOk(cwd, ["worktree", "remove", "--force", path]);
|
|
6493
|
-
const root =
|
|
6530
|
+
const root = dirname8(path);
|
|
6494
6531
|
if (basename5(root).startsWith("crew-release-")) rmSync8(root, { recursive: true, force: true });
|
|
6495
6532
|
}
|
|
6496
6533
|
}
|
|
@@ -6508,11 +6545,11 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
|
|
|
6508
6545
|
}
|
|
6509
6546
|
for (const name of names) {
|
|
6510
6547
|
if (!name.startsWith("crew-release-")) continue;
|
|
6511
|
-
const root =
|
|
6548
|
+
const root = join16(tmp, name);
|
|
6512
6549
|
try {
|
|
6513
6550
|
const st = statSync6(root);
|
|
6514
6551
|
if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
|
|
6515
|
-
if (existsSync15(
|
|
6552
|
+
if (existsSync15(join16(root, "checkout"))) continue;
|
|
6516
6553
|
rmSync8(root, { recursive: true, force: true });
|
|
6517
6554
|
removed.push(name);
|
|
6518
6555
|
} catch {
|
|
@@ -6561,7 +6598,7 @@ function ensureRepoCheckout(dir, remote, opts = {}) {
|
|
|
6561
6598
|
`${dir} does not exist and this repo has no discovered remote to clone from \u2014 re-run \`crew connect\`, or check out the repository yourself and point \`repos:\` at it`
|
|
6562
6599
|
);
|
|
6563
6600
|
}
|
|
6564
|
-
|
|
6601
|
+
mkdirSync9(dirname8(dir), { recursive: true });
|
|
6565
6602
|
const url = cloneUrlFor(remote, opts);
|
|
6566
6603
|
try {
|
|
6567
6604
|
execFileSync2("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -6586,7 +6623,7 @@ function sleepSync(ms2) {
|
|
|
6586
6623
|
}
|
|
6587
6624
|
function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
6588
6625
|
const lockPath = checkoutLockPath(dir);
|
|
6589
|
-
|
|
6626
|
+
mkdirSync9(dirname8(lockPath), { recursive: true });
|
|
6590
6627
|
const deadline = Date.now() + timeoutMs;
|
|
6591
6628
|
for (; ; ) {
|
|
6592
6629
|
try {
|
|
@@ -6626,14 +6663,14 @@ function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {
|
|
|
6626
6663
|
if (!existsSync15(`${dir}/.git`) && githubRemote) {
|
|
6627
6664
|
const url = cloneUrlFor(githubRemote, clone);
|
|
6628
6665
|
const marker = cloneFailureMarker(dir);
|
|
6629
|
-
if (existsSync15(marker) &&
|
|
6630
|
-
return { action: "clone-failed", detail:
|
|
6666
|
+
if (existsSync15(marker) && readFileSync15(marker, "utf8").split("\n")[0] === url) {
|
|
6667
|
+
return { action: "clone-failed", detail: readFileSync15(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
|
|
6631
6668
|
}
|
|
6632
6669
|
try {
|
|
6633
6670
|
ensureRepoCheckout(dir, githubRemote, clone);
|
|
6634
6671
|
} catch (e) {
|
|
6635
6672
|
if (!(e instanceof GitError)) throw e;
|
|
6636
|
-
|
|
6673
|
+
writeFileSync10(marker, `${url}
|
|
6637
6674
|
${e.message}`);
|
|
6638
6675
|
return { action: "clone-failed", detail: e.message, repeat: false };
|
|
6639
6676
|
}
|
|
@@ -8762,8 +8799,8 @@ function materializeBranchForTicket(dir, cfg, t, role, onProblem) {
|
|
|
8762
8799
|
}
|
|
8763
8800
|
|
|
8764
8801
|
// src/poll.ts
|
|
8765
|
-
import { writeFileSync as
|
|
8766
|
-
import { join as
|
|
8802
|
+
import { writeFileSync as writeFileSync11 } from "fs";
|
|
8803
|
+
import { join as join17 } from "path";
|
|
8767
8804
|
|
|
8768
8805
|
// src/stalled.ts
|
|
8769
8806
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -9054,7 +9091,7 @@ async function decideCycle(o) {
|
|
|
9054
9091
|
}
|
|
9055
9092
|
return decision;
|
|
9056
9093
|
}
|
|
9057
|
-
var digestPath = (stateDir, route, role) =>
|
|
9094
|
+
var digestPath = (stateDir, route, role) => join17(stateDir, `digest-${route}-${role}.md`);
|
|
9058
9095
|
function writeDigest(o, d, role, stateDir) {
|
|
9059
9096
|
const me = o.route.resolved?.seats[role];
|
|
9060
9097
|
if (!me) return false;
|
|
@@ -9118,7 +9155,7 @@ function writeDigest(o, d, role, stateDir) {
|
|
|
9118
9155
|
};
|
|
9119
9156
|
const text = role === "qa" ? qaDigest(input) : buildingDigest(input);
|
|
9120
9157
|
const path = digestPath(stateDir, routeSlug(o.route.route), role);
|
|
9121
|
-
|
|
9158
|
+
writeFileSync11(path, text);
|
|
9122
9159
|
o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text)} bytes)`, {
|
|
9123
9160
|
data: { role, bytes: Buffer.byteLength(text) }
|
|
9124
9161
|
});
|
|
@@ -9496,12 +9533,12 @@ async function applyStrandedVerified(writer, s, contract, operatorId, memberId,
|
|
|
9496
9533
|
|
|
9497
9534
|
// src/run-worktree.ts
|
|
9498
9535
|
import { existsSync as existsSync18 } from "fs";
|
|
9499
|
-
import { dirname as
|
|
9536
|
+
import { dirname as dirname9, join as join19 } from "path";
|
|
9500
9537
|
|
|
9501
9538
|
// src/ports.ts
|
|
9502
9539
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
9503
9540
|
import { existsSync as existsSync17 } from "fs";
|
|
9504
|
-
import { join as
|
|
9541
|
+
import { join as join18 } from "path";
|
|
9505
9542
|
var run = (cmd, args) => {
|
|
9506
9543
|
try {
|
|
9507
9544
|
return execFileSync3(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -9546,7 +9583,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
|
|
|
9546
9583
|
}
|
|
9547
9584
|
function worktreeExists(parent, prefix, n) {
|
|
9548
9585
|
for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
|
|
9549
|
-
if (existsSync17(
|
|
9586
|
+
if (existsSync17(join18(parent, name, ".git"))) return true;
|
|
9550
9587
|
}
|
|
9551
9588
|
return false;
|
|
9552
9589
|
}
|
|
@@ -9638,7 +9675,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
9638
9675
|
if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
|
|
9639
9676
|
return { action: "recut", path: existing.path, branch, base: baseRef };
|
|
9640
9677
|
}
|
|
9641
|
-
const path =
|
|
9678
|
+
const path = join19(dirname9(dir), worktreeForTicket(dir, cfg, t, role));
|
|
9642
9679
|
if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
|
|
9643
9680
|
return { action: "cut-remote", path, branch: located.branch };
|
|
9644
9681
|
}
|
|
@@ -9960,8 +9997,8 @@ async function guardRunStrays(route, ship, tracker, role, workingId, since2, log
|
|
|
9960
9997
|
}
|
|
9961
9998
|
|
|
9962
9999
|
// src/connect-wizard.ts
|
|
9963
|
-
import { delimiter, dirname as
|
|
9964
|
-
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync19, mkdirSync as
|
|
10000
|
+
import { delimiter, dirname as dirname10, join as join20 } from "path";
|
|
10001
|
+
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync19, mkdirSync as mkdirSync10, writeFileSync as writeFileSync12 } from "fs";
|
|
9965
10002
|
import { stringify } from "yaml";
|
|
9966
10003
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
9967
10004
|
function shouldRunWizard(opts) {
|
|
@@ -9983,7 +10020,7 @@ function defaultIsExecutable(p) {
|
|
|
9983
10020
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
9984
10021
|
for (const dir of pathEnv.split(delimiter)) {
|
|
9985
10022
|
if (!dir) continue;
|
|
9986
|
-
const candidate =
|
|
10023
|
+
const candidate = join20(dir, name);
|
|
9987
10024
|
if (isExecutable(candidate)) return candidate;
|
|
9988
10025
|
}
|
|
9989
10026
|
return void 0;
|
|
@@ -10009,9 +10046,9 @@ async function runWizard(p, env) {
|
|
|
10009
10046
|
if (entered && isExec(entered)) agentBin = entered;
|
|
10010
10047
|
else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
|
|
10011
10048
|
}
|
|
10012
|
-
const nodeDir =
|
|
10049
|
+
const nodeDir = dirname10(env.nodePath);
|
|
10013
10050
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
10014
|
-
const baseDirs = [nodeDir, pnpmPath ?
|
|
10051
|
+
const baseDirs = [nodeDir, pnpmPath ? dirname10(pnpmPath) : void 0];
|
|
10015
10052
|
p.say(
|
|
10016
10053
|
`
|
|
10017
10054
|
Host Passengers lets this machine run the agent sessions that other people's
|
|
@@ -10022,7 +10059,7 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
|
|
|
10022
10059
|
p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
|
|
10023
10060
|
}
|
|
10024
10061
|
const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
10025
|
-
const extraPath = deriveExtraPath([...baseDirs, dockerPath ?
|
|
10062
|
+
const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname10(dockerPath) : void 0]);
|
|
10026
10063
|
if (extraPath) {
|
|
10027
10064
|
p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
|
|
10028
10065
|
}
|
|
@@ -10044,9 +10081,9 @@ routes:
|
|
|
10044
10081
|
${routeBlock}`;
|
|
10045
10082
|
}
|
|
10046
10083
|
function writeNewConfig(path, text) {
|
|
10047
|
-
|
|
10084
|
+
mkdirSync10(dirname10(path), { recursive: true });
|
|
10048
10085
|
try {
|
|
10049
|
-
|
|
10086
|
+
writeFileSync12(path, text, { mode: 384, flag: "wx" });
|
|
10050
10087
|
return true;
|
|
10051
10088
|
} catch (e) {
|
|
10052
10089
|
if (e.code === "EEXIST") return false;
|
|
@@ -10079,7 +10116,7 @@ Wrote ${o.configPath}.
|
|
|
10079
10116
|
|
|
10080
10117
|
// src/connect-json.ts
|
|
10081
10118
|
import { accessSync as accessSync3, constants as constants3 } from "fs";
|
|
10082
|
-
import { dirname as
|
|
10119
|
+
import { dirname as dirname11 } from "path";
|
|
10083
10120
|
var CHOICE_EXIT_CODE = 3;
|
|
10084
10121
|
function createEmitter(json, write) {
|
|
10085
10122
|
return {
|
|
@@ -10127,7 +10164,7 @@ function flagWizardAnswers(f, env) {
|
|
|
10127
10164
|
if (f.agentBin && !isExec(f.agentBin)) return { error: `--agent-bin ${f.agentBin} is not an executable file` };
|
|
10128
10165
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
10129
10166
|
const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
10130
|
-
const extraPath = f.extraPath ?? deriveExtraPath([
|
|
10167
|
+
const extraPath = f.extraPath ?? deriveExtraPath([dirname11(env.nodePath), pnpmPath ? dirname11(pnpmPath) : void 0, dockerPath ? dirname11(dockerPath) : void 0]);
|
|
10131
10168
|
return {
|
|
10132
10169
|
answers: {
|
|
10133
10170
|
name: f.shipName?.trim() || env.hostname,
|
|
@@ -10140,8 +10177,8 @@ function flagWizardAnswers(f, env) {
|
|
|
10140
10177
|
}
|
|
10141
10178
|
|
|
10142
10179
|
// src/release-run.ts
|
|
10143
|
-
import { readFileSync as
|
|
10144
|
-
import { dirname as
|
|
10180
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync13, existsSync as existsSync20, copyFileSync, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync2, rmSync as rmSync9 } from "fs";
|
|
10181
|
+
import { dirname as dirname12, join as join21 } from "path";
|
|
10145
10182
|
import { tmpdir as tmpdir3 } from "os";
|
|
10146
10183
|
|
|
10147
10184
|
// src/release.ts
|
|
@@ -10829,13 +10866,13 @@ async function bump(o, size) {
|
|
|
10829
10866
|
}
|
|
10830
10867
|
return printed;
|
|
10831
10868
|
}
|
|
10832
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
10869
|
+
const files = o.repo.release.versionFiles.map((f) => join21(o.cwd, f));
|
|
10833
10870
|
const first = files[0];
|
|
10834
10871
|
if (!existsSync20(first)) {
|
|
10835
10872
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
10836
10873
|
return null;
|
|
10837
10874
|
}
|
|
10838
|
-
const current = JSON.parse(
|
|
10875
|
+
const current = JSON.parse(readFileSync16(first, "utf8")).version;
|
|
10839
10876
|
if (!current) {
|
|
10840
10877
|
o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
|
|
10841
10878
|
return null;
|
|
@@ -10848,27 +10885,27 @@ async function bump(o, size) {
|
|
|
10848
10885
|
return version;
|
|
10849
10886
|
}
|
|
10850
10887
|
for (const f of files) {
|
|
10851
|
-
const src =
|
|
10888
|
+
const src = readFileSync16(f, "utf8");
|
|
10852
10889
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
10853
10890
|
if (out === src) {
|
|
10854
10891
|
o.emit.error(`no version field in ${f}`);
|
|
10855
10892
|
return null;
|
|
10856
10893
|
}
|
|
10857
|
-
|
|
10894
|
+
writeFileSync13(f, out);
|
|
10858
10895
|
}
|
|
10859
10896
|
return version;
|
|
10860
10897
|
}
|
|
10861
10898
|
function writeChangelog(o, version, entries) {
|
|
10862
10899
|
const file = o.repo.release.changelog;
|
|
10863
10900
|
if (!file || entries.length === 0) return;
|
|
10864
|
-
const path =
|
|
10901
|
+
const path = join21(o.cwd, file);
|
|
10865
10902
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
10866
10903
|
if (o.dryRun) {
|
|
10867
10904
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
10868
10905
|
return;
|
|
10869
10906
|
}
|
|
10870
|
-
const existing = existsSync20(path) ?
|
|
10871
|
-
|
|
10907
|
+
const existing = existsSync20(path) ? readFileSync16(path, "utf8") : "# Changelog\n";
|
|
10908
|
+
writeFileSync13(path, insertChangelogSection(existing, section));
|
|
10872
10909
|
}
|
|
10873
10910
|
async function confirm(o, expected) {
|
|
10874
10911
|
if (!o.repo.hooks.released) return void 0;
|
|
@@ -10961,8 +10998,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run2 = gitOk) {
|
|
|
10961
10998
|
function openReleaseCheckout(o) {
|
|
10962
10999
|
const { remote, base } = o.repo.branch;
|
|
10963
11000
|
const branch = `crew/release-${Date.now()}`;
|
|
10964
|
-
const root = mkdtempSync2(
|
|
10965
|
-
const dir =
|
|
11001
|
+
const root = mkdtempSync2(join21(tmpdir3(), "crew-release-"));
|
|
11002
|
+
const dir = join21(root, "checkout");
|
|
10966
11003
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
10967
11004
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
10968
11005
|
try {
|
|
@@ -10972,10 +11009,10 @@ function openReleaseCheckout(o) {
|
|
|
10972
11009
|
throw e;
|
|
10973
11010
|
}
|
|
10974
11011
|
for (const f of o.repo.worktrees.copy) {
|
|
10975
|
-
const src =
|
|
11012
|
+
const src = join21(o.cwd, f);
|
|
10976
11013
|
if (!existsSync20(src)) continue;
|
|
10977
|
-
|
|
10978
|
-
copyFileSync(src,
|
|
11014
|
+
mkdirSync11(dirname12(join21(dir, f)), { recursive: true });
|
|
11015
|
+
copyFileSync(src, join21(dir, f));
|
|
10979
11016
|
}
|
|
10980
11017
|
return { dir, branch, close };
|
|
10981
11018
|
}
|
|
@@ -12769,8 +12806,8 @@ function describeCapability(i) {
|
|
|
12769
12806
|
}
|
|
12770
12807
|
|
|
12771
12808
|
// src/agents-sync-run.ts
|
|
12772
|
-
import { existsSync as existsSync22, mkdirSync as
|
|
12773
|
-
import { dirname as
|
|
12809
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync17, writeFileSync as writeFileSync14 } from "fs";
|
|
12810
|
+
import { dirname as dirname13 } from "path";
|
|
12774
12811
|
async function runAgentsSync(target, o) {
|
|
12775
12812
|
const dryRun = o.dryRun ?? false;
|
|
12776
12813
|
const force = o.force ?? false;
|
|
@@ -12803,12 +12840,12 @@ async function runAgentsSync(target, o) {
|
|
|
12803
12840
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
12804
12841
|
if (!dryRun) {
|
|
12805
12842
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
12806
|
-
const raw = existsSync22(resolvedPath) ? JSON.parse(
|
|
12843
|
+
const raw = existsSync22(resolvedPath) ? JSON.parse(readFileSync17(resolvedPath, "utf8")) : {};
|
|
12807
12844
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
12808
12845
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
12809
12846
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
12810
|
-
|
|
12811
|
-
|
|
12847
|
+
mkdirSync12(dirname13(resolvedPath), { recursive: true });
|
|
12848
|
+
writeFileSync14(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
12812
12849
|
`);
|
|
12813
12850
|
if (target.resolved) {
|
|
12814
12851
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -12915,7 +12952,7 @@ import { isMap as isMap2, parseDocument as parseDocument2, YAMLMap } from "yaml"
|
|
|
12915
12952
|
|
|
12916
12953
|
// src/repos-cmd.ts
|
|
12917
12954
|
import { homedir as homedir7 } from "os";
|
|
12918
|
-
import { basename as basename7, join as
|
|
12955
|
+
import { basename as basename7, join as join22, resolve as resolve3 } from "path";
|
|
12919
12956
|
import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
|
|
12920
12957
|
var ReposError = class extends Error {
|
|
12921
12958
|
};
|
|
@@ -13113,7 +13150,7 @@ function planRepoClone(i) {
|
|
|
13113
13150
|
}
|
|
13114
13151
|
const ship = doc.get("ship", true);
|
|
13115
13152
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
13116
|
-
const target = i.dir ? expandDir(i.dir, i.base) :
|
|
13153
|
+
const target = i.dir ? expandDir(i.dir, i.base) : join22(expandDir(basePath, i.base), i.route.split("/")[0], name);
|
|
13117
13154
|
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name });
|
|
13118
13155
|
return { ...add, remote, target };
|
|
13119
13156
|
}
|
|
@@ -13256,7 +13293,7 @@ function existingRepoConfig(f) {
|
|
|
13256
13293
|
}
|
|
13257
13294
|
|
|
13258
13295
|
// src/sample-ticket.ts
|
|
13259
|
-
import { basename as basename8, dirname as
|
|
13296
|
+
import { basename as basename8, dirname as dirname15, join as join23 } from "path";
|
|
13260
13297
|
var SAMPLE_REPO = "crew-sample";
|
|
13261
13298
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
13262
13299
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -13279,8 +13316,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
13279
13316
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
13280
13317
|
`;
|
|
13281
13318
|
function sampleDir(existing, reposBase, workspace) {
|
|
13282
|
-
if (existing.length > 0) return
|
|
13283
|
-
return
|
|
13319
|
+
if (existing.length > 0) return join23(dirname15(existing[0]), SAMPLE_REPO);
|
|
13320
|
+
return join23(reposBase, workspace, SAMPLE_REPO);
|
|
13284
13321
|
}
|
|
13285
13322
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
13286
13323
|
function sampleDescription() {
|
|
@@ -13361,8 +13398,8 @@ function schedulerInstalled(plan, exists, crontab) {
|
|
|
13361
13398
|
}
|
|
13362
13399
|
|
|
13363
13400
|
// src/worktree-sweep.ts
|
|
13364
|
-
import { existsSync as existsSync23, readFileSync as
|
|
13365
|
-
import { basename as basename9, dirname as
|
|
13401
|
+
import { existsSync as existsSync23, readFileSync as readFileSync18, readdirSync as readdirSync6 } from "fs";
|
|
13402
|
+
import { basename as basename9, dirname as dirname16, join as join24, resolve as resolve4 } from "path";
|
|
13366
13403
|
function planWorktreeSweep(target, tickets, contract) {
|
|
13367
13404
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
13368
13405
|
const verified = contract.statuses.verified;
|
|
@@ -13416,7 +13453,7 @@ function worktreeTicketNumbers(target) {
|
|
|
13416
13453
|
return [...nums];
|
|
13417
13454
|
}
|
|
13418
13455
|
function siblingWorktreeDirs(target) {
|
|
13419
|
-
const parent =
|
|
13456
|
+
const parent = dirname16(target.dir.replace(/[/\\]+$/, ""));
|
|
13420
13457
|
const stem = `${basename9(target.dir.replace(/[/\\]+$/, ""))}-`;
|
|
13421
13458
|
const registered = new Set(worktrees(target.dir).map((w) => resolve4(w.path)));
|
|
13422
13459
|
let names;
|
|
@@ -13429,7 +13466,7 @@ function siblingWorktreeDirs(target) {
|
|
|
13429
13466
|
for (const name of names) {
|
|
13430
13467
|
if (!name.startsWith(stem)) continue;
|
|
13431
13468
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
13432
|
-
const path =
|
|
13469
|
+
const path = join24(parent, name);
|
|
13433
13470
|
if (m && !registered.has(resolve4(path))) out.push({ path, number: m[1] });
|
|
13434
13471
|
}
|
|
13435
13472
|
return out;
|
|
@@ -13532,7 +13569,7 @@ function isHandBackMerge(path, base) {
|
|
|
13532
13569
|
if (!msgPath) return false;
|
|
13533
13570
|
let first;
|
|
13534
13571
|
try {
|
|
13535
|
-
first =
|
|
13572
|
+
first = readFileSync18(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
|
|
13536
13573
|
} catch {
|
|
13537
13574
|
return false;
|
|
13538
13575
|
}
|
|
@@ -13638,13 +13675,13 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
13638
13675
|
|
|
13639
13676
|
// src/stream-sweep.ts
|
|
13640
13677
|
import { existsSync as existsSync24, readdirSync as readdirSync7, rmSync as rmSync10, statSync as statSync8 } from "fs";
|
|
13641
|
-
import { join as
|
|
13678
|
+
import { join as join25 } from "path";
|
|
13642
13679
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
13643
13680
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
13644
|
-
const dir =
|
|
13681
|
+
const dir = join25(stateDir, "streams");
|
|
13645
13682
|
if (!existsSync24(dir)) return [];
|
|
13646
13683
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
13647
|
-
return readdirSync7(dir).map((name) =>
|
|
13684
|
+
return readdirSync7(dir).map((name) => join25(dir, name)).filter((path) => {
|
|
13648
13685
|
try {
|
|
13649
13686
|
return statSync8(path).mtimeMs < cutoff;
|
|
13650
13687
|
} catch {
|
|
@@ -13672,7 +13709,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
13672
13709
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
13673
13710
|
if (!existsSync24(stateDir)) return [];
|
|
13674
13711
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
13675
|
-
return readdirSync7(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) =>
|
|
13712
|
+
return readdirSync7(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join25(stateDir, name)).filter((path) => {
|
|
13676
13713
|
try {
|
|
13677
13714
|
return statSync8(path).mtimeMs < cutoff;
|
|
13678
13715
|
} catch {
|
|
@@ -13682,24 +13719,24 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
13682
13719
|
}
|
|
13683
13720
|
|
|
13684
13721
|
// src/cli.ts
|
|
13685
|
-
import { realpathSync as realpathSync2, readFileSync as
|
|
13722
|
+
import { realpathSync as realpathSync2, readFileSync as readFileSync25, existsSync as existsSync31, mkdirSync as mkdirSync18, writeFileSync as writeFileSync19, rmSync as rmSync12, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync2, statSync as statSync10, readdirSync as readdirSync12 } from "fs";
|
|
13686
13723
|
import { createInterface } from "readline/promises";
|
|
13687
13724
|
import { dirname as dirOf, resolve as resolvePath } from "path";
|
|
13688
13725
|
|
|
13689
13726
|
// src/ssh-keys.ts
|
|
13690
13727
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
13691
|
-
import { existsSync as existsSync25, mkdirSync as
|
|
13692
|
-
import { dirname as
|
|
13728
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync13, readFileSync as readFileSync19, chmodSync } from "fs";
|
|
13729
|
+
import { dirname as dirname17, join as join26 } from "path";
|
|
13693
13730
|
var SshKeyError = class extends Error {
|
|
13694
13731
|
};
|
|
13695
13732
|
function shipSshKeyPathFor(stateDir) {
|
|
13696
|
-
return
|
|
13733
|
+
return join26(stateDir, "ssh", "id_ed25519");
|
|
13697
13734
|
}
|
|
13698
13735
|
function ensureShipSshKeypair(stateDir) {
|
|
13699
13736
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
13700
13737
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
13701
13738
|
if (!existsSync25(privateKeyPath)) {
|
|
13702
|
-
|
|
13739
|
+
mkdirSync13(dirname17(privateKeyPath), { recursive: true });
|
|
13703
13740
|
try {
|
|
13704
13741
|
execFileSync4("ssh-keygen", [
|
|
13705
13742
|
"-t",
|
|
@@ -13722,7 +13759,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
13722
13759
|
if (!existsSync25(publicKeyPath)) {
|
|
13723
13760
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
13724
13761
|
}
|
|
13725
|
-
const publicKey =
|
|
13762
|
+
const publicKey = readFileSync19(publicKeyPath, "utf8").trim();
|
|
13726
13763
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
13727
13764
|
}
|
|
13728
13765
|
function sshKeygenAvailable() {
|
|
@@ -13737,10 +13774,10 @@ function sshKeygenAvailable() {
|
|
|
13737
13774
|
// src/machine-id.ts
|
|
13738
13775
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
13739
13776
|
import { createHash as createHash4 } from "crypto";
|
|
13740
|
-
import { existsSync as existsSync26, mkdirSync as
|
|
13741
|
-
import { dirname as
|
|
13777
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync14, readFileSync as readFileSync20, writeFileSync as writeFileSync15 } from "fs";
|
|
13778
|
+
import { dirname as dirname18, join as join27 } from "path";
|
|
13742
13779
|
function machineIdPathFor(stateDir) {
|
|
13743
|
-
return
|
|
13780
|
+
return join27(stateDir, "machine-id");
|
|
13744
13781
|
}
|
|
13745
13782
|
var sha256 = (s) => createHash4("sha256").update(s).digest("hex");
|
|
13746
13783
|
function readRawMachineId(platform2 = process.platform) {
|
|
@@ -13754,7 +13791,7 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
13754
13791
|
}
|
|
13755
13792
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
13756
13793
|
if (existsSync26(p)) {
|
|
13757
|
-
const id =
|
|
13794
|
+
const id = readFileSync20(p, "utf8").trim();
|
|
13758
13795
|
if (id) return id;
|
|
13759
13796
|
}
|
|
13760
13797
|
}
|
|
@@ -13766,14 +13803,14 @@ function ensureMachineId(stateDir, opts = {}) {
|
|
|
13766
13803
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
13767
13804
|
const path = machineIdPathFor(stateDir);
|
|
13768
13805
|
if (existsSync26(path)) {
|
|
13769
|
-
const cached =
|
|
13806
|
+
const cached = readFileSync20(path, "utf8").trim();
|
|
13770
13807
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
13771
13808
|
}
|
|
13772
13809
|
const raw = (opts.read ?? readRawMachineId)();
|
|
13773
13810
|
if (!raw) return void 0;
|
|
13774
13811
|
const hashed = sha256(raw);
|
|
13775
|
-
|
|
13776
|
-
|
|
13812
|
+
mkdirSync14(dirname18(path), { recursive: true });
|
|
13813
|
+
writeFileSync15(path, `${hashed}
|
|
13777
13814
|
`, { mode: 384 });
|
|
13778
13815
|
return hashed;
|
|
13779
13816
|
}
|
|
@@ -13988,9 +14025,9 @@ function syncAllPassengerCheckouts(cfg) {
|
|
|
13988
14025
|
|
|
13989
14026
|
// src/tunnel.ts
|
|
13990
14027
|
import { spawn as spawn4 } from "child_process";
|
|
13991
|
-
import { existsSync as existsSync27, mkdirSync as
|
|
14028
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync15, readFileSync as readFileSync21, writeFileSync as writeFileSync16, unlinkSync as unlinkSync3, readdirSync as readdirSync8 } from "fs";
|
|
13992
14029
|
import { randomInt } from "crypto";
|
|
13993
|
-
import { join as
|
|
14030
|
+
import { join as join28 } from "path";
|
|
13994
14031
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
13995
14032
|
var defaultSpawn = (cmd, args) => spawn4(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
13996
14033
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -14057,21 +14094,21 @@ function startTunnel(opts) {
|
|
|
14057
14094
|
};
|
|
14058
14095
|
}
|
|
14059
14096
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
14060
|
-
return
|
|
14097
|
+
return join28(stateDir, "passengers", `${workspaceId}.json`);
|
|
14061
14098
|
}
|
|
14062
14099
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
14063
14100
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
14064
14101
|
if (!existsSync27(path)) return void 0;
|
|
14065
14102
|
try {
|
|
14066
|
-
return JSON.parse(
|
|
14103
|
+
return JSON.parse(readFileSync21(path, "utf8"));
|
|
14067
14104
|
} catch {
|
|
14068
14105
|
return void 0;
|
|
14069
14106
|
}
|
|
14070
14107
|
}
|
|
14071
14108
|
function writePersistedTunnel(stateDir, state) {
|
|
14072
14109
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
14073
|
-
|
|
14074
|
-
|
|
14110
|
+
mkdirSync15(join28(stateDir, "passengers"), { recursive: true });
|
|
14111
|
+
writeFileSync16(path, `${JSON.stringify(state, null, 2)}
|
|
14075
14112
|
`);
|
|
14076
14113
|
}
|
|
14077
14114
|
function updatePersistedTunnel(stateDir, workspaceId, patch2) {
|
|
@@ -14168,7 +14205,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
14168
14205
|
}
|
|
14169
14206
|
}
|
|
14170
14207
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
14171
|
-
const dir =
|
|
14208
|
+
const dir = join28(stateDir, "passengers");
|
|
14172
14209
|
if (!existsSync27(dir)) return [];
|
|
14173
14210
|
return readdirSync8(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
14174
14211
|
}
|
|
@@ -14290,8 +14327,8 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
14290
14327
|
import { createServer as createHttpServer } from "http";
|
|
14291
14328
|
import { spawn as spawn5 } from "child_process";
|
|
14292
14329
|
import { randomBytes } from "crypto";
|
|
14293
|
-
import { existsSync as existsSync28, mkdirSync as
|
|
14294
|
-
import { join as
|
|
14330
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync16, readFileSync as readFileSync22, writeFileSync as writeFileSync17, unlinkSync as unlinkSync4, readdirSync as readdirSync9 } from "fs";
|
|
14331
|
+
import { join as join29 } from "path";
|
|
14295
14332
|
|
|
14296
14333
|
// src/secret-compare.ts
|
|
14297
14334
|
import { timingSafeEqual } from "crypto";
|
|
@@ -14380,21 +14417,21 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
14380
14417
|
return child;
|
|
14381
14418
|
};
|
|
14382
14419
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
14383
|
-
return
|
|
14420
|
+
return join29(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
14384
14421
|
}
|
|
14385
14422
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
14386
14423
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
14387
14424
|
if (!existsSync28(path)) return void 0;
|
|
14388
14425
|
try {
|
|
14389
|
-
return JSON.parse(
|
|
14426
|
+
return JSON.parse(readFileSync22(path, "utf8"));
|
|
14390
14427
|
} catch {
|
|
14391
14428
|
return void 0;
|
|
14392
14429
|
}
|
|
14393
14430
|
}
|
|
14394
14431
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
14395
14432
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
14396
|
-
|
|
14397
|
-
|
|
14433
|
+
mkdirSync16(join29(stateDir, "passengers"), { recursive: true });
|
|
14434
|
+
writeFileSync17(path, `${JSON.stringify(state, null, 2)}
|
|
14398
14435
|
`);
|
|
14399
14436
|
}
|
|
14400
14437
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -14414,7 +14451,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
14414
14451
|
}
|
|
14415
14452
|
}
|
|
14416
14453
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
14417
|
-
const dir =
|
|
14454
|
+
const dir = join29(stateDir, "passengers");
|
|
14418
14455
|
if (!existsSync28(dir)) return [];
|
|
14419
14456
|
const suffix = "-sync.json";
|
|
14420
14457
|
return readdirSync9(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
@@ -14472,12 +14509,12 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
|
14472
14509
|
}
|
|
14473
14510
|
|
|
14474
14511
|
// src/daemon.ts
|
|
14475
|
-
import { existsSync as existsSync30, mkdirSync as
|
|
14476
|
-
import { join as
|
|
14512
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync17, readFileSync as readFileSync24, readdirSync as readdirSync11, rmSync as rmSync11, statSync as statSync9, writeFileSync as writeFileSync18 } from "fs";
|
|
14513
|
+
import { join as join31 } from "path";
|
|
14477
14514
|
|
|
14478
14515
|
// src/interrupted.ts
|
|
14479
|
-
import { existsSync as existsSync29, readdirSync as readdirSync10, readFileSync as
|
|
14480
|
-
import { join as
|
|
14516
|
+
import { existsSync as existsSync29, readdirSync as readdirSync10, readFileSync as readFileSync23 } from "fs";
|
|
14517
|
+
import { join as join30 } from "path";
|
|
14481
14518
|
var INTERRUPTED_MARKER = calloutHeader("NOTE", "Interrupted");
|
|
14482
14519
|
function interruptedComment(b) {
|
|
14483
14520
|
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.";
|
|
@@ -14498,19 +14535,19 @@ function runFinished(streamPath) {
|
|
|
14498
14535
|
const path = `${streamPath}.meta.json`;
|
|
14499
14536
|
if (!existsSync29(path)) return false;
|
|
14500
14537
|
try {
|
|
14501
|
-
return JSON.parse(
|
|
14538
|
+
return JSON.parse(readFileSync23(path, "utf8")).running !== true;
|
|
14502
14539
|
} catch {
|
|
14503
14540
|
return true;
|
|
14504
14541
|
}
|
|
14505
14542
|
}
|
|
14506
14543
|
function orphanedStreams(o) {
|
|
14507
|
-
const dir =
|
|
14544
|
+
const dir = join30(o.stateDir, "streams");
|
|
14508
14545
|
if (!existsSync29(dir)) return [];
|
|
14509
14546
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
14510
14547
|
const out = [];
|
|
14511
14548
|
for (const name of readdirSync10(dir)) {
|
|
14512
14549
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
14513
|
-
const streamPath =
|
|
14550
|
+
const streamPath = join30(dir, name);
|
|
14514
14551
|
if (runFinished(streamPath)) continue;
|
|
14515
14552
|
const sidecar = readRunSidecar(streamPath);
|
|
14516
14553
|
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
@@ -14610,7 +14647,7 @@ function latestMtime(path) {
|
|
|
14610
14647
|
if (!stat.isDirectory()) return 0;
|
|
14611
14648
|
let latest = stat.mtimeMs;
|
|
14612
14649
|
for (const entry of readdirSync11(path, { withFileTypes: true })) {
|
|
14613
|
-
latest = Math.max(latest, latestMtime(
|
|
14650
|
+
latest = Math.max(latest, latestMtime(join31(path, entry.name)));
|
|
14614
14651
|
}
|
|
14615
14652
|
return latest;
|
|
14616
14653
|
}
|
|
@@ -14622,20 +14659,20 @@ function makeStaleChecker(paths, opts = {}) {
|
|
|
14622
14659
|
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync30(opts.reloadSentinel);
|
|
14623
14660
|
}
|
|
14624
14661
|
function reloadSentinelFor(stateDir) {
|
|
14625
|
-
return
|
|
14662
|
+
return join31(stateDir, "reload-requested");
|
|
14626
14663
|
}
|
|
14627
14664
|
function daemonStampFor(stateDir) {
|
|
14628
|
-
return
|
|
14665
|
+
return join31(stateDir, "daemon.started");
|
|
14629
14666
|
}
|
|
14630
14667
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
14631
|
-
|
|
14668
|
+
mkdirSync17(stateDir, { recursive: true });
|
|
14632
14669
|
rmSync11(reloadSentinelFor(stateDir), { force: true });
|
|
14633
|
-
|
|
14670
|
+
writeFileSync18(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
14634
14671
|
`);
|
|
14635
14672
|
}
|
|
14636
14673
|
function readDaemonStarted(stateDir) {
|
|
14637
14674
|
try {
|
|
14638
|
-
const v = JSON.parse(
|
|
14675
|
+
const v = JSON.parse(readFileSync24(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
14639
14676
|
return typeof v === "number" ? v : null;
|
|
14640
14677
|
} catch {
|
|
14641
14678
|
return null;
|
|
@@ -15001,7 +15038,7 @@ async function buildEnvironment(route, ship, ticket) {
|
|
|
15001
15038
|
}
|
|
15002
15039
|
|
|
15003
15040
|
// src/cli.ts
|
|
15004
|
-
var CREW_HOME = isCompiledBinary(import.meta.url) ?
|
|
15041
|
+
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname19(process.execPath) : resolve5(dirname19(fileURLToPath(import.meta.url)), "..");
|
|
15005
15042
|
var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
|
|
15006
15043
|
var CYCLE_SECONDS2 = 120;
|
|
15007
15044
|
var STALE_CYCLES = 3;
|
|
@@ -15147,7 +15184,7 @@ function usage() {
|
|
|
15147
15184
|
resume with no argument clears only the whole-crew pause; a role or release pause needs its own resume
|
|
15148
15185
|
crew log [route] tail the log
|
|
15149
15186
|
crew inbox [--member NAME] your tickets across every workspace (or a colleague's)
|
|
15150
|
-
crew connect resolve a workspace's ids into a crew.yaml block, then (admin key) sync personas/skills and mint seat keys (--no-agents-sync skips that; --no-mcp skips registering the Tablation MCP server with Claude Code; --mcp replace overwrites a different one)
|
|
15187
|
+
crew connect resolve a workspace's ids into a crew.yaml block, then (admin key) sync personas/skills and mint seat keys (--no-agents-sync skips that; --no-mcp skips registering the Tablation MCP server with Claude Code; --mcp replace overwrites a different one; --dry-run works with no crew.yaml and writes none: no file, no rows, no seat keys \u2014 the device approval and workspace credential behave as in a real run; with no workspace named it first reuses the keychain session a previous dry-run stored for the host, falling back to device approval when that is missing or rejected, and the signed_in event's session field says which)
|
|
15151
15188
|
crew disconnect remove what connect installed for Pair (Claude MCP; Codex/Gemini instructions block and MCP entry per ship.pair)
|
|
15152
15189
|
crew disconnect --reset [--remove-checkouts] [--dry-run] [--json] remove everything connect and install wrote on this machine: scheduler units, Pair/MCP entries, seat keys, keychain credential, resolved state, then crew.yaml (--remove-checkouts also deletes checkouts crew cloned, never one with local changes)
|
|
15153
15190
|
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)
|
|
@@ -15295,7 +15332,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
15295
15332
|
hostPassengers: flag("host-passengers"),
|
|
15296
15333
|
noWizard: flag("no-wizard")
|
|
15297
15334
|
};
|
|
15298
|
-
if (command === "connect" &&
|
|
15335
|
+
if (command === "connect" && usesFlagAnswers(wizardFlags, connectIsTTY) && shouldRunWizard({ isTTY: true, crewHome: CREW_HOME })) {
|
|
15299
15336
|
const flagged = flagWizardAnswers(wizardFlags, {
|
|
15300
15337
|
hostname: hostname2(),
|
|
15301
15338
|
platform: hostPlatform(),
|
|
@@ -15313,7 +15350,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
15313
15350
|
firstRun = { answers: flagged.answers, configPath };
|
|
15314
15351
|
firstRunFromFlags = true;
|
|
15315
15352
|
cfg = loadConfig(CREW_HOME, configPath, { text: renderShipBlock(flagged.answers), allowNoRoutes: true });
|
|
15316
|
-
} else if (command === "connect" &&
|
|
15353
|
+
} else if (command === "connect" && shouldRunWizard({
|
|
15317
15354
|
isTTY: connectIsTTY,
|
|
15318
15355
|
crewHome: CREW_HOME
|
|
15319
15356
|
})) {
|
|
@@ -15568,7 +15605,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
15568
15605
|
}
|
|
15569
15606
|
if (host === "linux" && hasSystemd) {
|
|
15570
15607
|
const label = labelFor(CREW_HOME, "run");
|
|
15571
|
-
const staleTimer =
|
|
15608
|
+
const staleTimer = join32(homedir8(), ".config", "systemd", "user", `${label}.timer`);
|
|
15572
15609
|
if (existsSync31(staleTimer)) {
|
|
15573
15610
|
process.stdout.write(
|
|
15574
15611
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
@@ -15630,7 +15667,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
15630
15667
|
for (const a of plan.actions) {
|
|
15631
15668
|
if (a.kind === "enable") {
|
|
15632
15669
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
15633
|
-
|
|
15670
|
+
writeFileSync19(cfg.configFile, planEnable(readFileSync25(cfg.configFile, "utf8"), a.route));
|
|
15634
15671
|
enabledSound.add(a.route);
|
|
15635
15672
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
15636
15673
|
} else {
|
|
@@ -16629,7 +16666,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
16629
16666
|
process.exit(1);
|
|
16630
16667
|
}
|
|
16631
16668
|
requireArmed("run the daemon loop");
|
|
16632
|
-
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [
|
|
16669
|
+
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join32(CREW_HOME, "src"), join32(CREW_HOME, "dist"), join32(CREW_HOME, "bin")];
|
|
16633
16670
|
const configPaths = daemonConfigPaths(cfg);
|
|
16634
16671
|
watchPaths.push(...configPaths);
|
|
16635
16672
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -16830,10 +16867,10 @@ ${changed2.map((c) => ` - ${c}
|
|
|
16830
16867
|
`);
|
|
16831
16868
|
if (!dryRun) {
|
|
16832
16869
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
16833
|
-
const raw = existsSync31(resolvedPath) ? JSON.parse(
|
|
16870
|
+
const raw = existsSync31(resolvedPath) ? JSON.parse(readFileSync25(resolvedPath, "utf8")) : {};
|
|
16834
16871
|
raw.agentSkills = result.agentSkills;
|
|
16835
|
-
|
|
16836
|
-
|
|
16872
|
+
mkdirSync18(dirOf(resolvedPath), { recursive: true });
|
|
16873
|
+
writeFileSync19(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
16837
16874
|
`);
|
|
16838
16875
|
}
|
|
16839
16876
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -16846,15 +16883,15 @@ ${changed2.map((c) => ` - ${c}
|
|
|
16846
16883
|
process.exit(2);
|
|
16847
16884
|
};
|
|
16848
16885
|
if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks") usage2();
|
|
16849
|
-
const configText =
|
|
16886
|
+
const configText = readFileSync25(cfg.configFile, "utf8");
|
|
16850
16887
|
if (sub === "hooks") {
|
|
16851
16888
|
try {
|
|
16852
16889
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
16853
|
-
const dir = repoDirOf(configText,
|
|
16890
|
+
const dir = repoDirOf(configText, dirname19(cfg.configFile), a.route, a.name);
|
|
16854
16891
|
if (!existsSync31(dir)) throw new ReposError(`${dir} does not exist on disk`);
|
|
16855
16892
|
const files = { exists: (r) => existsSync31(resolvePath(dir, r)), read: (r) => {
|
|
16856
16893
|
try {
|
|
16857
|
-
return
|
|
16894
|
+
return readFileSync25(resolvePath(dir, r), "utf8");
|
|
16858
16895
|
} catch {
|
|
16859
16896
|
return void 0;
|
|
16860
16897
|
}
|
|
@@ -16866,13 +16903,13 @@ ${changed2.map((c) => ` - ${c}
|
|
|
16866
16903
|
if (write && suggestion.source === null) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
16867
16904
|
if (write === "route") {
|
|
16868
16905
|
const plan = planRouteHooks(configText, a.route, a.name, suggestion.hooks);
|
|
16869
|
-
|
|
16906
|
+
writeFileSync19(cfg.configFile, plan.text);
|
|
16870
16907
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept };
|
|
16871
16908
|
} else if (write === "repo") {
|
|
16872
16909
|
const have = existingRepoConfig(files);
|
|
16873
16910
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
16874
16911
|
const file = resolvePath(dir, ".crew.yaml");
|
|
16875
|
-
|
|
16912
|
+
writeFileSync19(file, starterRepoConfig(suggestion.hooks), { flag: "wx" });
|
|
16876
16913
|
written = { target: "repo", file, hooks: Object.keys(suggestion.hooks), kept: [] };
|
|
16877
16914
|
}
|
|
16878
16915
|
if (flag("json")) {
|
|
@@ -16940,13 +16977,13 @@ ${changed2.map((c) => ` - ${c}
|
|
|
16940
16977
|
let resolvedRemotes;
|
|
16941
16978
|
if (existsSync31(resolvedFile)) {
|
|
16942
16979
|
try {
|
|
16943
|
-
resolvedRemotes = JSON.parse(
|
|
16980
|
+
resolvedRemotes = JSON.parse(readFileSync25(resolvedFile, "utf8"));
|
|
16944
16981
|
} catch {
|
|
16945
16982
|
}
|
|
16946
16983
|
}
|
|
16947
16984
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
16948
16985
|
const text = configText;
|
|
16949
|
-
const base =
|
|
16986
|
+
const base = dirname19(cfg.configFile);
|
|
16950
16987
|
try {
|
|
16951
16988
|
if (sub === "list") {
|
|
16952
16989
|
const repos = configuredRepos(text, routeName, base);
|
|
@@ -16987,7 +17024,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
16987
17024
|
throw new ReposError(`${e.message}${authHint}
|
|
16988
17025
|
crew.yaml was not changed`);
|
|
16989
17026
|
}
|
|
16990
|
-
|
|
17027
|
+
writeFileSync19(cfg.configFile, plan2.text);
|
|
16991
17028
|
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}`);
|
|
16992
17029
|
break;
|
|
16993
17030
|
}
|
|
@@ -17042,14 +17079,14 @@ ${changed2.map((c) => ` - ${c}
|
|
|
17042
17079
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
17043
17080
|
`);
|
|
17044
17081
|
if (existsSync31(resolvedFile)) {
|
|
17045
|
-
const raw = JSON.parse(
|
|
17082
|
+
const raw = JSON.parse(readFileSync25(resolvedFile, "utf8"));
|
|
17046
17083
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
17047
17084
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
17048
|
-
|
|
17085
|
+
writeFileSync19(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
17049
17086
|
`);
|
|
17050
17087
|
}
|
|
17051
17088
|
}
|
|
17052
|
-
|
|
17089
|
+
writeFileSync19(cfg.configFile, plan.text);
|
|
17053
17090
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
17054
17091
|
`);
|
|
17055
17092
|
} catch (e) {
|
|
@@ -17179,6 +17216,30 @@ ${reconstruction.prompt}
|
|
|
17179
17216
|
} catch {
|
|
17180
17217
|
}
|
|
17181
17218
|
}
|
|
17219
|
+
let signInSource = apiKey ? "key" : "device";
|
|
17220
|
+
if (!apiKey && !workspace && dryRun && store) {
|
|
17221
|
+
const resumed = await resumeKeychainSession({
|
|
17222
|
+
store,
|
|
17223
|
+
stateDir: cfg.ship.stateDir,
|
|
17224
|
+
host: hostFromUrl3(baseUrl),
|
|
17225
|
+
verify: async (k) => {
|
|
17226
|
+
try {
|
|
17227
|
+
await get({ baseUrl, apiKey: k, userAgent: cfg.ship.userAgent }, "/auth/me");
|
|
17228
|
+
return true;
|
|
17229
|
+
} catch {
|
|
17230
|
+
return false;
|
|
17231
|
+
}
|
|
17232
|
+
}
|
|
17233
|
+
});
|
|
17234
|
+
if (resumed) {
|
|
17235
|
+
apiKey = resumed.apiKey;
|
|
17236
|
+
workspace = resumed.workspace;
|
|
17237
|
+
keychainBacked = true;
|
|
17238
|
+
signInSource = "keychain";
|
|
17239
|
+
process.stderr.write(`Reusing the existing keychain session for ${hostFromUrl3(baseUrl)}/${workspace}.
|
|
17240
|
+
`);
|
|
17241
|
+
}
|
|
17242
|
+
}
|
|
17182
17243
|
let mintedFromDevice = false;
|
|
17183
17244
|
let signedInEmitted = false;
|
|
17184
17245
|
if (!apiKey) {
|
|
@@ -17231,7 +17292,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
17231
17292
|
signedInEmitted = true;
|
|
17232
17293
|
connectEvents.emit("signed_in", `signed in as ${result.identity.email}`, {
|
|
17233
17294
|
identity: { id: result.identity.id, email: result.identity.email },
|
|
17234
|
-
workspaces: [{ slug: result.workspace.slug, name: result.workspace.name }]
|
|
17295
|
+
workspaces: [{ slug: result.workspace.slug, name: result.workspace.name }],
|
|
17296
|
+
session: "device"
|
|
17235
17297
|
});
|
|
17236
17298
|
const deviceWorkspace = "path" in result.workspace && typeof result.workspace.path === "string" ? result.workspace.path : result.workspace.slug;
|
|
17237
17299
|
if (!workspace) workspace = deviceWorkspace;
|
|
@@ -17245,6 +17307,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
17245
17307
|
createdAt: result.apiKey.createdAt
|
|
17246
17308
|
});
|
|
17247
17309
|
keychainBacked = true;
|
|
17310
|
+
rememberSession(cfg.ship.stateDir, hostFromUrl3(baseUrl), deviceWorkspace);
|
|
17248
17311
|
process.stderr.write(
|
|
17249
17312
|
`API key stored in the OS keychain for ${hostFromUrl3(baseUrl)}/${deviceWorkspace}.
|
|
17250
17313
|
`
|
|
@@ -17290,7 +17353,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
17290
17353
|
signedInEmitted = true;
|
|
17291
17354
|
connectEvents.emit("signed_in", `key belongs to ${found.meEmail ?? found.meId}`, {
|
|
17292
17355
|
identity: { id: found.meId, email: found.meEmail, name: found.meName },
|
|
17293
|
-
workspaces: [{ slug: found.workspaceSlug, name: found.workspaceName, role: found.role }]
|
|
17356
|
+
workspaces: [{ slug: found.workspaceSlug, name: found.workspaceName, role: found.role }],
|
|
17357
|
+
session: signInSource
|
|
17294
17358
|
});
|
|
17295
17359
|
}
|
|
17296
17360
|
connectEvents.emit("workspace_resolved", `workspace ${found.workspaceSlug}`, {
|
|
@@ -17380,7 +17444,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
17380
17444
|
const hadPreviousFile = existsSync31(resolvedPath);
|
|
17381
17445
|
if (hadPreviousFile) {
|
|
17382
17446
|
try {
|
|
17383
|
-
previous = JSON.parse(
|
|
17447
|
+
previous = JSON.parse(readFileSync25(resolvedPath, "utf8"));
|
|
17384
17448
|
} catch {
|
|
17385
17449
|
}
|
|
17386
17450
|
}
|
|
@@ -17489,10 +17553,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
17489
17553
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
17490
17554
|
`);
|
|
17491
17555
|
} else {
|
|
17492
|
-
|
|
17556
|
+
mkdirSync18(dirname19(resolvedPath), { recursive: true });
|
|
17493
17557
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
17494
17558
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
17495
|
-
|
|
17559
|
+
writeFileSync19(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
17496
17560
|
`);
|
|
17497
17561
|
renameSync2(tmpPath, resolvedPath);
|
|
17498
17562
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
@@ -17527,8 +17591,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
17527
17591
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
17528
17592
|
`);
|
|
17529
17593
|
} else {
|
|
17530
|
-
|
|
17531
|
-
|
|
17594
|
+
mkdirSync18(dirname19(apiKeyFile), { recursive: true });
|
|
17595
|
+
writeFileSync19(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
17532
17596
|
`, { mode: 384 });
|
|
17533
17597
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
17534
17598
|
`);
|
|
@@ -17537,7 +17601,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
17537
17601
|
const keyPersisted = keychainBacked || apiKeyFile !== void 0;
|
|
17538
17602
|
const wizard = firstRun;
|
|
17539
17603
|
const writeFirstRun = wizard !== void 0 && !dryRun && keyPersisted;
|
|
17540
|
-
const routeDir = value("dir") ?? (writeFirstRun ?
|
|
17604
|
+
const routeDir = value("dir") ?? (writeFirstRun ? join32(DEFAULT_REPOS_BASE_PATH, found.workspaceSlug) : void 0);
|
|
17541
17605
|
const routeBlock = renderConnection(
|
|
17542
17606
|
found,
|
|
17543
17607
|
route2,
|
|
@@ -18181,7 +18245,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18181
18245
|
};
|
|
18182
18246
|
const droppedRoutes = [];
|
|
18183
18247
|
try {
|
|
18184
|
-
const rawCfg = parseYaml2(
|
|
18248
|
+
const rawCfg = parseYaml2(readFileSync25(cfg.configFile, "utf8"));
|
|
18185
18249
|
for (const r of rawCfg?.routes ?? []) {
|
|
18186
18250
|
if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
|
|
18187
18251
|
try {
|
|
@@ -18223,8 +18287,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18223
18287
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
18224
18288
|
break;
|
|
18225
18289
|
}
|
|
18226
|
-
|
|
18227
|
-
|
|
18290
|
+
mkdirSync18(cfg.ship.stateDir, { recursive: true });
|
|
18291
|
+
writeFileSync19(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
18228
18292
|
`);
|
|
18229
18293
|
process.stdout.write("crew reload: requested \u2014 the daemon finishes its in-flight pass, then relaunches with the new config\n");
|
|
18230
18294
|
if (flag("now") && control?.stopCommand && control.startCommand) {
|
|
@@ -18275,7 +18339,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18275
18339
|
}
|
|
18276
18340
|
case "log": {
|
|
18277
18341
|
try {
|
|
18278
|
-
const text =
|
|
18342
|
+
const text = readFileSync25(cfg.ship.logFile, "utf8").split("\n");
|
|
18279
18343
|
process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
18280
18344
|
`);
|
|
18281
18345
|
} catch {
|
|
@@ -18298,8 +18362,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18298
18362
|
};
|
|
18299
18363
|
if (!route.resolved) fail(`route ${route.route} is not connected \u2014 run \`crew connect\` first`);
|
|
18300
18364
|
const tracker = new Tracker(route, cfg.ship);
|
|
18301
|
-
const configText =
|
|
18302
|
-
const cfgBase =
|
|
18365
|
+
const configText = readFileSync25(cfg.configFile, "utf8");
|
|
18366
|
+
const cfgBase = dirname19(cfg.configFile);
|
|
18303
18367
|
const configured = configuredRepos(configText, route.route, cfgBase);
|
|
18304
18368
|
const dir = configured.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured.map((r) => r.dir), route.reposBasePath, route.route.split("/")[0]);
|
|
18305
18369
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route.route);
|
|
@@ -18327,7 +18391,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18327
18391
|
}
|
|
18328
18392
|
if (configured.some((r) => r.dir === dir)) {
|
|
18329
18393
|
try {
|
|
18330
|
-
|
|
18394
|
+
writeFileSync19(cfg.configFile, planRepoRemove(configText, cfgBase, route.route, dir).text);
|
|
18331
18395
|
} catch (e) {
|
|
18332
18396
|
if (e instanceof ReposError) fail(e.message);
|
|
18333
18397
|
throw e;
|
|
@@ -18336,28 +18400,28 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18336
18400
|
if (row) {
|
|
18337
18401
|
await tracker.deleteRepoRow(row.id);
|
|
18338
18402
|
if (existsSync31(resolvedFile)) {
|
|
18339
|
-
const raw = JSON.parse(
|
|
18403
|
+
const raw = JSON.parse(readFileSync25(resolvedFile, "utf8"));
|
|
18340
18404
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
18341
18405
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
18342
|
-
|
|
18406
|
+
writeFileSync19(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
18343
18407
|
`);
|
|
18344
18408
|
}
|
|
18345
18409
|
}
|
|
18346
|
-
if (existsSync31(
|
|
18410
|
+
if (existsSync31(join32(dir, ".git")) && basename10(dir) === SAMPLE_REPO) rmSync12(dir, { recursive: true, force: true });
|
|
18347
18411
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
18348
18412
|
break;
|
|
18349
18413
|
}
|
|
18350
18414
|
const projectId = route.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
18351
|
-
const fresh = !existsSync31(
|
|
18415
|
+
const fresh = !existsSync31(join32(dir, ".git"));
|
|
18352
18416
|
if (existsSync31(dir) && fresh && readdirSync12(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
18353
18417
|
if (dryRun) {
|
|
18354
18418
|
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`);
|
|
18355
18419
|
break;
|
|
18356
18420
|
}
|
|
18357
18421
|
if (fresh) {
|
|
18358
|
-
|
|
18359
|
-
|
|
18360
|
-
|
|
18422
|
+
mkdirSync18(dir, { recursive: true });
|
|
18423
|
+
writeFileSync19(join32(dir, "README.md"), SAMPLE_README);
|
|
18424
|
+
writeFileSync19(join32(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
18361
18425
|
const g = (...a) => {
|
|
18362
18426
|
const r = spawnSync6("git", a, { cwd: dir, encoding: "utf8" });
|
|
18363
18427
|
if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -18376,16 +18440,16 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18376
18440
|
});
|
|
18377
18441
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
18378
18442
|
if (existsSync31(resolvedFile)) {
|
|
18379
|
-
const raw = JSON.parse(
|
|
18443
|
+
const raw = JSON.parse(readFileSync25(resolvedFile, "utf8"));
|
|
18380
18444
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
18381
18445
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
18382
|
-
|
|
18446
|
+
writeFileSync19(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
18383
18447
|
`);
|
|
18384
18448
|
}
|
|
18385
18449
|
}
|
|
18386
18450
|
if (!configured.some((r) => r.dir === dir)) {
|
|
18387
18451
|
try {
|
|
18388
|
-
|
|
18452
|
+
writeFileSync19(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route.route, path: dir, name: SAMPLE_REPO }).text);
|
|
18389
18453
|
} catch (e) {
|
|
18390
18454
|
if (e instanceof ReposError) fail(e.message);
|
|
18391
18455
|
throw e;
|
|
@@ -18718,7 +18782,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
18718
18782
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run");
|
|
18719
18783
|
const abandons = unitAbandonsAgents(unitPlan, (p) => {
|
|
18720
18784
|
try {
|
|
18721
|
-
return
|
|
18785
|
+
return readFileSync25(p, "utf8");
|
|
18722
18786
|
} catch {
|
|
18723
18787
|
return null;
|
|
18724
18788
|
}
|
package/package.json
CHANGED