@tablation/crew 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.js +165 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -213,8 +213,8 @@ crew inbox [--member NAME] your tickets across every workspace
|
|
|
213
213
|
crew connect WS[/PROJECT] resolve a workspace/project's ids into the state tree
|
|
214
214
|
crew repos add [ROUTE] [NAME] [PATH] attach a local checkout to a route in crew.yaml, creating its Repos row if missing (--dry-run previews)
|
|
215
215
|
crew repos list ROUTE the checkouts a route has, and the tracker Repos row each matches
|
|
216
|
-
crew install
|
|
217
|
-
crew uninstall
|
|
216
|
+
crew install [--replace] write and load this platform's scheduler unit (macOS: clears a crew installed from another path)
|
|
217
|
+
crew uninstall [--all] unload and remove it (--all: every crew launchd unit, whichever path it came from)
|
|
218
218
|
```
|
|
219
219
|
|
|
220
220
|
Every command that could change something takes `--dry-run`.
|
package/dist/cli.js
CHANGED
|
@@ -1930,7 +1930,7 @@ function describeRelease(o, route) {
|
|
|
1930
1930
|
// src/install.ts
|
|
1931
1931
|
import { dirname as dirname3, join as join5, extname } from "path";
|
|
1932
1932
|
import { homedir as homedir2 } from "os";
|
|
1933
|
-
import { accessSync, constants, existsSync as existsSync5, mkdirSync as mkdirSync3, statSync as statSync2, writeFileSync as writeFileSync2, unlinkSync, realpathSync } from "fs";
|
|
1933
|
+
import { accessSync, constants, existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync5, statSync as statSync2, writeFileSync as writeFileSync2, unlinkSync, realpathSync } from "fs";
|
|
1934
1934
|
import { spawnSync } from "child_process";
|
|
1935
1935
|
import { createHash } from "crypto";
|
|
1936
1936
|
|
|
@@ -2164,7 +2164,8 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
2164
2164
|
mechanism: "launchd",
|
|
2165
2165
|
unitPaths: [unitPath],
|
|
2166
2166
|
crontabLine: null,
|
|
2167
|
-
unloadCommands: [["launchctl", "unload", unitPath]]
|
|
2167
|
+
unloadCommands: [["launchctl", "unload", unitPath]],
|
|
2168
|
+
removeLabel: label
|
|
2168
2169
|
};
|
|
2169
2170
|
}
|
|
2170
2171
|
if (hostShip === "linux" && hasSystemd) {
|
|
@@ -2282,6 +2283,15 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
2282
2283
|
if (!ok) log.warn("unload command failed \u2014 it may not have been loaded; continuing to remove files");
|
|
2283
2284
|
}
|
|
2284
2285
|
}
|
|
2286
|
+
if (plan.removeLabel) {
|
|
2287
|
+
if (dryRun) {
|
|
2288
|
+
log.emit(`would run: launchctl remove ${plan.removeLabel}`);
|
|
2289
|
+
} else {
|
|
2290
|
+
if (spawnSync("launchctl", ["remove", plan.removeLabel], { encoding: "utf8" }).status === 0) {
|
|
2291
|
+
log.emit(`removed loaded job ${plan.removeLabel}`);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2285
2295
|
for (const path of plan.unitPaths) {
|
|
2286
2296
|
if (!existsSync5(path)) continue;
|
|
2287
2297
|
if (dryRun) {
|
|
@@ -2300,6 +2310,91 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
2300
2310
|
}
|
|
2301
2311
|
}
|
|
2302
2312
|
}
|
|
2313
|
+
var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
|
|
2314
|
+
function launchAgentsDir() {
|
|
2315
|
+
return join5(homedir2(), "Library", "LaunchAgents");
|
|
2316
|
+
}
|
|
2317
|
+
function xmlUnescape(s) {
|
|
2318
|
+
return s.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
2319
|
+
}
|
|
2320
|
+
function programFromPlist(text) {
|
|
2321
|
+
const block = /<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/.exec(text);
|
|
2322
|
+
if (!block) return null;
|
|
2323
|
+
const args = [...block[1].matchAll(/<string>([\s\S]*?)<\/string>/g)].map((m) => xmlUnescape(m[1]));
|
|
2324
|
+
return args.find((a) => a.endsWith("/bin/crew")) ?? args[0] ?? null;
|
|
2325
|
+
}
|
|
2326
|
+
function listLaunchdUnits(dir = launchAgentsDir()) {
|
|
2327
|
+
if (!existsSync5(dir)) return [];
|
|
2328
|
+
return readdirSync(dir).filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
|
|
2329
|
+
const plistPath = join5(dir, f);
|
|
2330
|
+
let program = null;
|
|
2331
|
+
try {
|
|
2332
|
+
program = programFromPlist(readFileSync5(plistPath, "utf8"));
|
|
2333
|
+
} catch {
|
|
2334
|
+
}
|
|
2335
|
+
return {
|
|
2336
|
+
label: f.slice(0, -".plist".length),
|
|
2337
|
+
plistPath,
|
|
2338
|
+
program,
|
|
2339
|
+
programExists: program !== null && existsSync5(program)
|
|
2340
|
+
};
|
|
2341
|
+
});
|
|
2342
|
+
}
|
|
2343
|
+
function foreignLaunchdUnits(crewHome, dir) {
|
|
2344
|
+
const own = new Set(["run", "release", "passengers"].map((job) => labelFor(crewHome, job)));
|
|
2345
|
+
return listLaunchdUnits(dir).filter((u) => !own.has(u.label));
|
|
2346
|
+
}
|
|
2347
|
+
function removeLaunchdUnit(unit, dryRun, log) {
|
|
2348
|
+
if (dryRun) {
|
|
2349
|
+
log.emit(`would unload and remove ${unit.label}`);
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
if (existsSync5(unit.plistPath)) spawnSync("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
|
|
2353
|
+
spawnSync("launchctl", ["remove", unit.label], { encoding: "utf8" });
|
|
2354
|
+
if (existsSync5(unit.plistPath)) unlinkSync(unit.plistPath);
|
|
2355
|
+
log.emit(`removed ${unit.label} (${unit.plistPath})`);
|
|
2356
|
+
}
|
|
2357
|
+
function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir) {
|
|
2358
|
+
const foreign = foreignLaunchdUnits(crewHome, dir);
|
|
2359
|
+
const blocking = foreign.filter((u) => u.program === null || u.programExists);
|
|
2360
|
+
if (blocking.length > 0 && !replace) {
|
|
2361
|
+
log.warn(
|
|
2362
|
+
`two crew installs target this machine: ${blocking.map((u) => `${u.label} runs ${u.program ?? "an unreadable ProgramArguments"}`).join("; ")}. Run \`crew uninstall\` from the other one, or pass --replace.`
|
|
2363
|
+
);
|
|
2364
|
+
return false;
|
|
2365
|
+
}
|
|
2366
|
+
for (const unit of foreign) {
|
|
2367
|
+
const why = unit.program === null ? "replacing a unit with no readable ProgramArguments" : unit.programExists ? "replacing a live other install" : "removing a stale unit whose program is gone";
|
|
2368
|
+
log.emit(`${why}: ${unit.label}`);
|
|
2369
|
+
removeLaunchdUnit(unit, dryRun, log);
|
|
2370
|
+
}
|
|
2371
|
+
return true;
|
|
2372
|
+
}
|
|
2373
|
+
function removeAllLaunchdUnits(dryRun, log, dir) {
|
|
2374
|
+
for (const unit of listLaunchdUnits(dir)) removeLaunchdUnit(unit, dryRun, log);
|
|
2375
|
+
}
|
|
2376
|
+
function parseLaunchctlList(text) {
|
|
2377
|
+
const jobs = /* @__PURE__ */ new Map();
|
|
2378
|
+
for (const line of text.split("\n")) {
|
|
2379
|
+
const cols = line.split(" ");
|
|
2380
|
+
if (cols.length !== 3 || cols[2] === "Label") continue;
|
|
2381
|
+
jobs.set(cols[2], { pid: cols[0], lastExit: cols[1] });
|
|
2382
|
+
}
|
|
2383
|
+
return jobs;
|
|
2384
|
+
}
|
|
2385
|
+
function describeForeignLaunchdUnits(crewHome, dir) {
|
|
2386
|
+
const foreign = foreignLaunchdUnits(crewHome, dir);
|
|
2387
|
+
if (foreign.length === 0) return [];
|
|
2388
|
+
const list = spawnSync("launchctl", ["list"], { encoding: "utf8" });
|
|
2389
|
+
const jobs = parseLaunchctlList(list.status === 0 ? list.stdout ?? "" : "");
|
|
2390
|
+
return foreign.map((u) => {
|
|
2391
|
+
const status2 = jobs.get(u.label);
|
|
2392
|
+
const loaded = status2 ? `loaded, last exit ${status2.lastExit}` : "not loaded";
|
|
2393
|
+
const program = u.program ? `${u.program} (${u.programExists ? "present" : "GONE"})` : "no readable ProgramArguments";
|
|
2394
|
+
const stale = status2 && !u.programExists ? " \u2014 stale: `crew install` removes it" : "";
|
|
2395
|
+
return ` ${u.label}: ${loaded}; runs ${program}${stale}`;
|
|
2396
|
+
});
|
|
2397
|
+
}
|
|
2303
2398
|
function readCrontab() {
|
|
2304
2399
|
const res = spawnSync("crontab", ["-l"], { encoding: "utf8" });
|
|
2305
2400
|
return res.status === 0 ? res.stdout ?? "" : "";
|
|
@@ -4511,7 +4606,7 @@ var alreadyReported = (ticket, g, comments) => comments.some((c) => c.ticket_id
|
|
|
4511
4606
|
|
|
4512
4607
|
// src/agent.ts
|
|
4513
4608
|
import { spawn as spawn2 } from "child_process";
|
|
4514
|
-
import { readFileSync as
|
|
4609
|
+
import { readFileSync as readFileSync6, existsSync as existsSync7, statSync as statSync4, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4, createWriteStream } from "fs";
|
|
4515
4610
|
import { finished } from "stream/promises";
|
|
4516
4611
|
import { join as join7, dirname as dirname5 } from "path";
|
|
4517
4612
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -4954,10 +5049,10 @@ function planAgentRun(o) {
|
|
|
4954
5049
|
let ageSeconds;
|
|
4955
5050
|
if (existsSync7(digestPath2)) {
|
|
4956
5051
|
ageSeconds = Math.floor((now() - statSync4(digestPath2).mtimeMs) / 1e3);
|
|
4957
|
-
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest =
|
|
5052
|
+
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync6(digestPath2, "utf8");
|
|
4958
5053
|
}
|
|
4959
|
-
const common =
|
|
4960
|
-
const brief =
|
|
5054
|
+
const common = readFileSync6(commonPath, "utf8");
|
|
5055
|
+
const brief = readFileSync6(briefPath, "utf8");
|
|
4961
5056
|
const effectiveCommon = o.divergedPrompt ? "" : common;
|
|
4962
5057
|
const effectiveBrief = o.divergedPrompt ?? brief;
|
|
4963
5058
|
const prompt = assemblePrompt({
|
|
@@ -5337,7 +5432,7 @@ Wrote ${o.configPath}.
|
|
|
5337
5432
|
}
|
|
5338
5433
|
|
|
5339
5434
|
// src/release-run.ts
|
|
5340
|
-
import { readFileSync as
|
|
5435
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync9 } from "fs";
|
|
5341
5436
|
import { join as join9 } from "path";
|
|
5342
5437
|
|
|
5343
5438
|
// src/release.ts
|
|
@@ -5802,7 +5897,7 @@ async function bump(o, size) {
|
|
|
5802
5897
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
5803
5898
|
return null;
|
|
5804
5899
|
}
|
|
5805
|
-
const current = JSON.parse(
|
|
5900
|
+
const current = JSON.parse(readFileSync7(first, "utf8")).version;
|
|
5806
5901
|
if (!current) {
|
|
5807
5902
|
o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
|
|
5808
5903
|
return null;
|
|
@@ -5815,7 +5910,7 @@ async function bump(o, size) {
|
|
|
5815
5910
|
return version;
|
|
5816
5911
|
}
|
|
5817
5912
|
for (const f of files) {
|
|
5818
|
-
const src =
|
|
5913
|
+
const src = readFileSync7(f, "utf8");
|
|
5819
5914
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
5820
5915
|
if (out === src) {
|
|
5821
5916
|
o.emit.error(`no version field in ${f}`);
|
|
@@ -5834,7 +5929,7 @@ function writeChangelog(o, version, entries) {
|
|
|
5834
5929
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
5835
5930
|
return;
|
|
5836
5931
|
}
|
|
5837
|
-
const existing = existsSync9(path) ?
|
|
5932
|
+
const existing = existsSync9(path) ? readFileSync7(path, "utf8") : "# Changelog\n";
|
|
5838
5933
|
writeFileSync6(path, insertChangelogSection(existing, section));
|
|
5839
5934
|
}
|
|
5840
5935
|
async function confirm(o, expected) {
|
|
@@ -6682,7 +6777,7 @@ function nextRoles(decision, ran) {
|
|
|
6682
6777
|
}
|
|
6683
6778
|
|
|
6684
6779
|
// src/agents.ts
|
|
6685
|
-
import { readFileSync as
|
|
6780
|
+
import { readFileSync as readFileSync8, existsSync as existsSync12 } from "fs";
|
|
6686
6781
|
import { join as join11 } from "path";
|
|
6687
6782
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError6, TablationApiError } from "@tablation/client";
|
|
6688
6783
|
var PERSONA_NAME = {
|
|
@@ -6699,10 +6794,10 @@ var AgentsSyncError = class extends Error {
|
|
|
6699
6794
|
function personaDefaultPrompt(promptsDir, role) {
|
|
6700
6795
|
const briefPath = join11(promptsDir, "personas", `lane-${role}.md`);
|
|
6701
6796
|
if (!existsSync12(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
|
|
6702
|
-
if (role === "pair") return
|
|
6797
|
+
if (role === "pair") return readFileSync8(briefPath, "utf8");
|
|
6703
6798
|
const commonPath = join11(promptsDir, "personas", "common.md");
|
|
6704
6799
|
if (!existsSync12(commonPath)) throw new AgentsSyncError(`no shared policy at ${commonPath}`);
|
|
6705
|
-
return `${
|
|
6800
|
+
return `${readFileSync8(commonPath, "utf8")}${readFileSync8(briefPath, "utf8")}`;
|
|
6706
6801
|
}
|
|
6707
6802
|
async function resolveAgentId(route, role, opts) {
|
|
6708
6803
|
const cached = route.resolved?.agentPersonas?.[role]?.agentId;
|
|
@@ -7323,14 +7418,14 @@ import {
|
|
|
7323
7418
|
} from "@tablation/client";
|
|
7324
7419
|
|
|
7325
7420
|
// src/skills.ts
|
|
7326
|
-
import { readFileSync as
|
|
7421
|
+
import { readFileSync as readFileSync9, readdirSync as readdirSync2, existsSync as existsSync13 } from "fs";
|
|
7327
7422
|
import { join as join12 } from "path";
|
|
7328
7423
|
import { parse as parseYaml } from "yaml";
|
|
7329
7424
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError7, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
7330
7425
|
var SkillSyncError = class extends Error {
|
|
7331
7426
|
};
|
|
7332
7427
|
function parseSkillFile(path) {
|
|
7333
|
-
const text =
|
|
7428
|
+
const text = readFileSync9(path, "utf8");
|
|
7334
7429
|
const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text);
|
|
7335
7430
|
if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
|
|
7336
7431
|
const frontmatter = match[1];
|
|
@@ -7345,7 +7440,7 @@ function parseSkillFile(path) {
|
|
|
7345
7440
|
function listLocalSkills(promptsDir) {
|
|
7346
7441
|
const dir = join12(promptsDir, "skills");
|
|
7347
7442
|
if (!existsSync13(dir)) return [];
|
|
7348
|
-
return
|
|
7443
|
+
return readdirSync2(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join12(dir, f)));
|
|
7349
7444
|
}
|
|
7350
7445
|
async function syncSkills(route, opts) {
|
|
7351
7446
|
if (!route.resolved) {
|
|
@@ -7670,7 +7765,7 @@ function resolveRepoProject(projects, opt, routeAreaId) {
|
|
|
7670
7765
|
|
|
7671
7766
|
// src/claude-mcp.ts
|
|
7672
7767
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
7673
|
-
import { readFileSync as
|
|
7768
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
7674
7769
|
import { homedir as homedir4 } from "os";
|
|
7675
7770
|
import { join as join13 } from "path";
|
|
7676
7771
|
var MCP_SERVER_NAME = "tablation";
|
|
@@ -7685,7 +7780,7 @@ function runClaude(bin, args) {
|
|
|
7685
7780
|
}
|
|
7686
7781
|
function userScopeEntry(home) {
|
|
7687
7782
|
try {
|
|
7688
|
-
const j = JSON.parse(
|
|
7783
|
+
const j = JSON.parse(readFileSync10(join13(home, ".claude.json"), "utf8"));
|
|
7689
7784
|
return j.mcpServers?.[MCP_SERVER_NAME];
|
|
7690
7785
|
} catch {
|
|
7691
7786
|
return void 0;
|
|
@@ -7884,14 +7979,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
7884
7979
|
}
|
|
7885
7980
|
|
|
7886
7981
|
// src/stream-sweep.ts
|
|
7887
|
-
import { existsSync as existsSync15, readdirSync as
|
|
7982
|
+
import { existsSync as existsSync15, readdirSync as readdirSync3, rmSync as rmSync2, statSync as statSync6 } from "fs";
|
|
7888
7983
|
import { join as join14 } from "path";
|
|
7889
7984
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
7890
7985
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
7891
7986
|
const dir = join14(stateDir, "streams");
|
|
7892
7987
|
if (!existsSync15(dir)) return [];
|
|
7893
7988
|
const cutoff = now - retentionDays * DAY_MS;
|
|
7894
|
-
return
|
|
7989
|
+
return readdirSync3(dir).map((name) => join14(dir, name)).filter((path) => {
|
|
7895
7990
|
try {
|
|
7896
7991
|
return statSync6(path).mtimeMs < cutoff;
|
|
7897
7992
|
} catch {
|
|
@@ -7918,13 +8013,13 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
7918
8013
|
}
|
|
7919
8014
|
|
|
7920
8015
|
// src/cli.ts
|
|
7921
|
-
import { readFileSync as
|
|
8016
|
+
import { readFileSync as readFileSync14, existsSync as existsSync19, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9, unlinkSync as unlinkSync5, copyFileSync, renameSync } from "fs";
|
|
7922
8017
|
import { createInterface } from "readline/promises";
|
|
7923
8018
|
import { dirname as dirOf, resolve as resolvePath } from "path";
|
|
7924
8019
|
|
|
7925
8020
|
// src/ssh-keys.ts
|
|
7926
8021
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
7927
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as
|
|
8022
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync11, chmodSync } from "fs";
|
|
7928
8023
|
import { dirname as dirname8, join as join15 } from "path";
|
|
7929
8024
|
var SshKeyError = class extends Error {
|
|
7930
8025
|
};
|
|
@@ -7958,7 +8053,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
7958
8053
|
if (!existsSync16(publicKeyPath)) {
|
|
7959
8054
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
7960
8055
|
}
|
|
7961
|
-
const publicKey =
|
|
8056
|
+
const publicKey = readFileSync11(publicKeyPath, "utf8").trim();
|
|
7962
8057
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
7963
8058
|
}
|
|
7964
8059
|
function sshKeygenAvailable() {
|
|
@@ -8172,7 +8267,7 @@ function syncAllPassengerCheckouts(cfg) {
|
|
|
8172
8267
|
|
|
8173
8268
|
// src/tunnel.ts
|
|
8174
8269
|
import { spawn as spawn3 } from "child_process";
|
|
8175
|
-
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as
|
|
8270
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3, readdirSync as readdirSync4 } from "fs";
|
|
8176
8271
|
import { randomInt } from "crypto";
|
|
8177
8272
|
import { join as join16 } from "path";
|
|
8178
8273
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
@@ -8247,7 +8342,7 @@ function readPersistedTunnel(stateDir, workspaceId) {
|
|
|
8247
8342
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
8248
8343
|
if (!existsSync17(path)) return void 0;
|
|
8249
8344
|
try {
|
|
8250
|
-
return JSON.parse(
|
|
8345
|
+
return JSON.parse(readFileSync12(path, "utf8"));
|
|
8251
8346
|
} catch {
|
|
8252
8347
|
return void 0;
|
|
8253
8348
|
}
|
|
@@ -8354,7 +8449,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
8354
8449
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
8355
8450
|
const dir = join16(stateDir, "passengers");
|
|
8356
8451
|
if (!existsSync17(dir)) return [];
|
|
8357
|
-
return
|
|
8452
|
+
return readdirSync4(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
8358
8453
|
}
|
|
8359
8454
|
|
|
8360
8455
|
// src/mcp-endpoint-registry.ts
|
|
@@ -8474,7 +8569,7 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
8474
8569
|
import { createServer as createHttpServer } from "http";
|
|
8475
8570
|
import { spawn as spawn4 } from "child_process";
|
|
8476
8571
|
import { randomBytes } from "crypto";
|
|
8477
|
-
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as
|
|
8572
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync13, writeFileSync as writeFileSync8, unlinkSync as unlinkSync4, readdirSync as readdirSync5 } from "fs";
|
|
8478
8573
|
import { join as join17 } from "path";
|
|
8479
8574
|
|
|
8480
8575
|
// src/secret-compare.ts
|
|
@@ -8570,7 +8665,7 @@ function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
|
8570
8665
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
8571
8666
|
if (!existsSync18(path)) return void 0;
|
|
8572
8667
|
try {
|
|
8573
|
-
return JSON.parse(
|
|
8668
|
+
return JSON.parse(readFileSync13(path, "utf8"));
|
|
8574
8669
|
} catch {
|
|
8575
8670
|
return void 0;
|
|
8576
8671
|
}
|
|
@@ -8601,7 +8696,7 @@ function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
|
8601
8696
|
const dir = join17(stateDir, "passengers");
|
|
8602
8697
|
if (!existsSync18(dir)) return [];
|
|
8603
8698
|
const suffix = "-sync.json";
|
|
8604
|
-
return
|
|
8699
|
+
return readdirSync5(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
8605
8700
|
}
|
|
8606
8701
|
function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
8607
8702
|
const alive = deps.isPidAlive ?? isPidAlive;
|
|
@@ -8656,7 +8751,7 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
|
8656
8751
|
}
|
|
8657
8752
|
|
|
8658
8753
|
// src/daemon.ts
|
|
8659
|
-
import { readdirSync as
|
|
8754
|
+
import { readdirSync as readdirSync6, statSync as statSync7 } from "fs";
|
|
8660
8755
|
import { join as join18 } from "path";
|
|
8661
8756
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
8662
8757
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
@@ -8695,7 +8790,7 @@ function latestMtime(path) {
|
|
|
8695
8790
|
if (stat.isFile()) return stat.mtimeMs;
|
|
8696
8791
|
if (!stat.isDirectory()) return 0;
|
|
8697
8792
|
let latest = stat.mtimeMs;
|
|
8698
|
-
for (const entry of
|
|
8793
|
+
for (const entry of readdirSync6(path, { withFileTypes: true })) {
|
|
8699
8794
|
latest = Math.max(latest, latestMtime(join18(path, entry.name)));
|
|
8700
8795
|
}
|
|
8701
8796
|
return latest;
|
|
@@ -9374,12 +9469,21 @@ if (command === "passenger-sync-daemon") {
|
|
|
9374
9469
|
const trimmed = typeof raw === "string" ? raw.trim() : "";
|
|
9375
9470
|
return trimmed ? `${trimmed.toLowerCase()}-` : void 0;
|
|
9376
9471
|
}
|
|
9377
|
-
async function installScheduler() {
|
|
9472
|
+
async function installScheduler(opts = {}) {
|
|
9378
9473
|
const host = hostPlatform();
|
|
9379
9474
|
if (host === "windows") {
|
|
9380
9475
|
process.stderr.write("crew install: no scheduler support yet for Windows (Task Scheduler is planned, not built)\n");
|
|
9381
9476
|
process.exit(2);
|
|
9382
9477
|
}
|
|
9478
|
+
if (host === "macos") {
|
|
9479
|
+
const ok = clearForeignLaunchdUnits(CREW_HOME, !!opts.replace, dryRun, {
|
|
9480
|
+
emit: (m) => process.stdout.write(`${m}
|
|
9481
|
+
`),
|
|
9482
|
+
warn: (m) => process.stderr.write(`crew install: ${m}
|
|
9483
|
+
`)
|
|
9484
|
+
});
|
|
9485
|
+
if (!ok) return false;
|
|
9486
|
+
}
|
|
9383
9487
|
const installPathProblem = cfg.routes.some((r) => r.enabled && r.hostPassengers) ? dockerPathProblem(cfg.ship) : void 0;
|
|
9384
9488
|
if (installPathProblem) {
|
|
9385
9489
|
process.stderr.write(`crew install: Host Passengers is on but ${installPathProblem}
|
|
@@ -9431,6 +9535,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
9431
9535
|
}
|
|
9432
9536
|
}
|
|
9433
9537
|
}
|
|
9538
|
+
return true;
|
|
9434
9539
|
}
|
|
9435
9540
|
async function routeProblems(c) {
|
|
9436
9541
|
const host = hostPlatform();
|
|
@@ -9477,14 +9582,13 @@ if (command === "passenger-sync-daemon") {
|
|
|
9477
9582
|
for (const a of plan.actions) {
|
|
9478
9583
|
if (a.kind === "enable") {
|
|
9479
9584
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
9480
|
-
writeFileSync9(cfg.configFile, planEnable(
|
|
9585
|
+
writeFileSync9(cfg.configFile, planEnable(readFileSync14(cfg.configFile, "utf8"), a.route));
|
|
9481
9586
|
enabledSound.add(a.route);
|
|
9482
9587
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
9483
9588
|
} else {
|
|
9484
9589
|
if (enabledSound.size === 0) continue;
|
|
9485
9590
|
if (prompter && !await prompter.confirm("No scheduler is installed for this ship \u2014 run `crew install` now?", false)) continue;
|
|
9486
|
-
await installScheduler();
|
|
9487
|
-
changed2.push("ran `crew install` (scheduler units written and loaded)");
|
|
9591
|
+
if (await installScheduler()) changed2.push("ran `crew install` (scheduler units written and loaded)");
|
|
9488
9592
|
}
|
|
9489
9593
|
}
|
|
9490
9594
|
} finally {
|
|
@@ -10376,7 +10480,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
10376
10480
|
`);
|
|
10377
10481
|
if (!dryRun) {
|
|
10378
10482
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
10379
|
-
const raw = existsSync19(resolvedPath) ? JSON.parse(
|
|
10483
|
+
const raw = existsSync19(resolvedPath) ? JSON.parse(readFileSync14(resolvedPath, "utf8")) : {};
|
|
10380
10484
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
10381
10485
|
raw.agentSkills = skillResult.agentSkills;
|
|
10382
10486
|
mkdirSync10(dirOf(resolvedPath), { recursive: true });
|
|
@@ -10425,7 +10529,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
10425
10529
|
`);
|
|
10426
10530
|
if (!dryRun) {
|
|
10427
10531
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
10428
|
-
const raw = existsSync19(resolvedPath) ? JSON.parse(
|
|
10532
|
+
const raw = existsSync19(resolvedPath) ? JSON.parse(readFileSync14(resolvedPath, "utf8")) : {};
|
|
10429
10533
|
raw.agentSkills = result.agentSkills;
|
|
10430
10534
|
mkdirSync10(dirOf(resolvedPath), { recursive: true });
|
|
10431
10535
|
writeFileSync9(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
@@ -10441,7 +10545,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
10441
10545
|
process.exit(2);
|
|
10442
10546
|
};
|
|
10443
10547
|
if (sub !== "add" && sub !== "list") usage2();
|
|
10444
|
-
const configText =
|
|
10548
|
+
const configText = readFileSync14(cfg.configFile, "utf8");
|
|
10445
10549
|
let routeName = positional[2];
|
|
10446
10550
|
let addPath = ".";
|
|
10447
10551
|
let addName;
|
|
@@ -10465,7 +10569,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
10465
10569
|
let resolvedRemotes;
|
|
10466
10570
|
if (existsSync19(resolvedFile)) {
|
|
10467
10571
|
try {
|
|
10468
|
-
resolvedRemotes = JSON.parse(
|
|
10572
|
+
resolvedRemotes = JSON.parse(readFileSync14(resolvedFile, "utf8"));
|
|
10469
10573
|
} catch {
|
|
10470
10574
|
}
|
|
10471
10575
|
}
|
|
@@ -10536,7 +10640,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
10536
10640
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
10537
10641
|
`);
|
|
10538
10642
|
if (existsSync19(resolvedFile)) {
|
|
10539
|
-
const raw = JSON.parse(
|
|
10643
|
+
const raw = JSON.parse(readFileSync14(resolvedFile, "utf8"));
|
|
10540
10644
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
10541
10645
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
10542
10646
|
writeFileSync9(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
@@ -10771,7 +10875,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
10771
10875
|
const hadPreviousFile = existsSync19(resolvedPath);
|
|
10772
10876
|
if (hadPreviousFile) {
|
|
10773
10877
|
try {
|
|
10774
|
-
previous = JSON.parse(
|
|
10878
|
+
previous = JSON.parse(readFileSync14(resolvedPath, "utf8"));
|
|
10775
10879
|
} catch {
|
|
10776
10880
|
}
|
|
10777
10881
|
}
|
|
@@ -11274,7 +11378,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
11274
11378
|
break;
|
|
11275
11379
|
}
|
|
11276
11380
|
case "install": {
|
|
11277
|
-
await installScheduler();
|
|
11381
|
+
if (!await installScheduler({ replace: flag("replace") })) process.exit(2);
|
|
11278
11382
|
break;
|
|
11279
11383
|
}
|
|
11280
11384
|
case "uninstall": {
|
|
@@ -11291,6 +11395,16 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
11291
11395
|
emit: (m) => process.stdout.write(`${m}
|
|
11292
11396
|
`),
|
|
11293
11397
|
warn: (m) => process.stderr.write(`crew uninstall: ${m}
|
|
11398
|
+
`)
|
|
11399
|
+
});
|
|
11400
|
+
}
|
|
11401
|
+
if (flag("all") && host === "macos") {
|
|
11402
|
+
process.stdout.write(`uninstalling every crew launchd unit${dryRun ? " (dry run)" : ""}
|
|
11403
|
+
`);
|
|
11404
|
+
removeAllLaunchdUnits(dryRun, {
|
|
11405
|
+
emit: (m) => process.stdout.write(`${m}
|
|
11406
|
+
`),
|
|
11407
|
+
warn: (m) => process.stderr.write(`crew uninstall: ${m}
|
|
11294
11408
|
`)
|
|
11295
11409
|
});
|
|
11296
11410
|
}
|
|
@@ -11302,7 +11416,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
11302
11416
|
}
|
|
11303
11417
|
case "log": {
|
|
11304
11418
|
try {
|
|
11305
|
-
const text =
|
|
11419
|
+
const text = readFileSync14(cfg.ship.logFile, "utf8").split("\n");
|
|
11306
11420
|
process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
11307
11421
|
`);
|
|
11308
11422
|
} catch {
|
|
@@ -11557,6 +11671,14 @@ git: ${gitPath ? `OK (${gitPath})` : "NOT FOUND on the scheduler P
|
|
|
11557
11671
|
process.stdout.write(`attention: ${i.kind} \u2014 ${i.message} (since ${i.since})
|
|
11558
11672
|
`);
|
|
11559
11673
|
}
|
|
11674
|
+
if (host === "macos") {
|
|
11675
|
+
const foreign = describeForeignLaunchdUnits(CREW_HOME);
|
|
11676
|
+
if (foreign.length > 0) {
|
|
11677
|
+
process.stdout.write(`foreign launchd units (not this checkout's \u2014 \`crew install\` or \`crew uninstall --all\` clears them):
|
|
11678
|
+
${foreign.join("\n")}
|
|
11679
|
+
`);
|
|
11680
|
+
}
|
|
11681
|
+
}
|
|
11560
11682
|
const passengerRoutes = cfg.routes.filter((r) => r.enabled && r.hostPassengers);
|
|
11561
11683
|
if (passengerRoutes.length === 0) {
|
|
11562
11684
|
process.stdout.write("host passengers: off (no enabled route sets hostPassengers: true)\n");
|
package/package.json
CHANGED