@tablation/crew 0.92.0 → 0.93.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +705 -512
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -223,8 +223,8 @@ function shellInitScript(shell) {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
// src/config.ts
|
|
226
|
-
import { readFileSync as
|
|
227
|
-
import { resolve, dirname as dirname4, isAbsolute, join as
|
|
226
|
+
import { readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
|
|
227
|
+
import { resolve, dirname as dirname4, isAbsolute, join as join12, basename as basename3 } from "path";
|
|
228
228
|
import { homedir as homedir6, tmpdir } from "os";
|
|
229
229
|
import { parse as parse2 } from "yaml";
|
|
230
230
|
|
|
@@ -1818,7 +1818,144 @@ function forgeOfRemote(forges, remoteUrl, hasToken) {
|
|
|
1818
1818
|
}
|
|
1819
1819
|
|
|
1820
1820
|
// src/config.ts
|
|
1821
|
-
import {
|
|
1821
|
+
import { hostFromUrl } from "@tablation/client";
|
|
1822
|
+
|
|
1823
|
+
// src/secret-store.ts
|
|
1824
|
+
import { createHash as createHash2 } from "crypto";
|
|
1825
|
+
import { accessSync, constants, existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync9, renameSync as renameSync2, rmSync as rmSync6, statSync as statSync3, writeFileSync as writeFileSync7, chmodSync as chmodSync2 } from "fs";
|
|
1826
|
+
import { join as join10 } from "path";
|
|
1827
|
+
import { getSessionStore as getOsSessionStore } from "@tablation/client";
|
|
1828
|
+
var SECRET_STORE_MODES = ["auto", "os", "file"];
|
|
1829
|
+
function isSecretStoreMode(v) {
|
|
1830
|
+
return typeof v === "string" && SECRET_STORE_MODES.includes(v);
|
|
1831
|
+
}
|
|
1832
|
+
var configured;
|
|
1833
|
+
function configureSecretStore(c) {
|
|
1834
|
+
configured = c;
|
|
1835
|
+
}
|
|
1836
|
+
function keysDir(stateDir) {
|
|
1837
|
+
return join10(stateDir, "keys");
|
|
1838
|
+
}
|
|
1839
|
+
function entryName(host, slug) {
|
|
1840
|
+
return `${createHash2("sha256").update(`${host}\0${slug}`).digest("hex")}.json`;
|
|
1841
|
+
}
|
|
1842
|
+
var FileSessionStore = class {
|
|
1843
|
+
dir;
|
|
1844
|
+
constructor(dir) {
|
|
1845
|
+
this.dir = dir;
|
|
1846
|
+
}
|
|
1847
|
+
ensureDir() {
|
|
1848
|
+
mkdirSync6(this.dir, { recursive: true, mode: 448 });
|
|
1849
|
+
if ((statSync3(this.dir).mode & 63) !== 0) chmodSync2(this.dir, 448);
|
|
1850
|
+
}
|
|
1851
|
+
async get(host, slug) {
|
|
1852
|
+
const file = join10(this.dir, entryName(host, slug));
|
|
1853
|
+
if (!existsSync8(this.dir)) return void 0;
|
|
1854
|
+
accessSync(this.dir, constants.R_OK | constants.X_OK);
|
|
1855
|
+
if (!existsSync8(file)) return void 0;
|
|
1856
|
+
return JSON.parse(readFileSync9(file, "utf8"));
|
|
1857
|
+
}
|
|
1858
|
+
async set(host, slug, session) {
|
|
1859
|
+
this.ensureDir();
|
|
1860
|
+
const file = join10(this.dir, entryName(host, slug));
|
|
1861
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
1862
|
+
writeFileSync7(tmp, JSON.stringify(session), { mode: 384 });
|
|
1863
|
+
renameSync2(tmp, file);
|
|
1864
|
+
}
|
|
1865
|
+
async clear(host, slug) {
|
|
1866
|
+
if (!existsSync8(this.dir)) return;
|
|
1867
|
+
rmSync6(join10(this.dir, entryName(host, slug)), { force: true });
|
|
1868
|
+
}
|
|
1869
|
+
/** Whether a key could be written here right now, without leaving anything behind. */
|
|
1870
|
+
writable() {
|
|
1871
|
+
try {
|
|
1872
|
+
this.ensureDir();
|
|
1873
|
+
accessSync(this.dir, constants.R_OK | constants.W_OK | constants.X_OK);
|
|
1874
|
+
const probe = join10(this.dir, `.probe.${process.pid}`);
|
|
1875
|
+
writeFileSync7(probe, "", { mode: 384 });
|
|
1876
|
+
rmSync6(probe, { force: true });
|
|
1877
|
+
return { ok: true };
|
|
1878
|
+
} catch (e) {
|
|
1879
|
+
return { ok: false, reason: e.code ?? e.message };
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
};
|
|
1883
|
+
function crewifyStoreError(e) {
|
|
1884
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1885
|
+
if (!/TABLATION_API_KEY/.test(msg)) return e instanceof Error ? e : new Error(msg);
|
|
1886
|
+
const out = new Error(
|
|
1887
|
+
msg.replace(
|
|
1888
|
+
/Set TABLATION_API_KEY instead of running `login`\./,
|
|
1889
|
+
"Set CREW_API_KEY for the ship key, or set `ship.secretStore: file` in crew.yaml to keep keys under the state directory."
|
|
1890
|
+
),
|
|
1891
|
+
{ cause: e }
|
|
1892
|
+
);
|
|
1893
|
+
return out;
|
|
1894
|
+
}
|
|
1895
|
+
var CrewHintedStore = class {
|
|
1896
|
+
inner;
|
|
1897
|
+
constructor(inner) {
|
|
1898
|
+
this.inner = inner;
|
|
1899
|
+
}
|
|
1900
|
+
async get(host, slug) {
|
|
1901
|
+
try {
|
|
1902
|
+
return await this.inner.get(host, slug);
|
|
1903
|
+
} catch (e) {
|
|
1904
|
+
throw crewifyStoreError(e);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
async set(host, slug, s) {
|
|
1908
|
+
try {
|
|
1909
|
+
await this.inner.set(host, slug, s);
|
|
1910
|
+
} catch (e) {
|
|
1911
|
+
throw crewifyStoreError(e);
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
async clear(host, slug) {
|
|
1915
|
+
try {
|
|
1916
|
+
await this.inner.clear(host, slug);
|
|
1917
|
+
} catch (e) {
|
|
1918
|
+
throw crewifyStoreError(e);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
};
|
|
1922
|
+
function osStoreReachable(env = process.env, platform2 = process.platform, hasBinary = onPath) {
|
|
1923
|
+
if (platform2 !== "linux") return true;
|
|
1924
|
+
if (!hasBinary("secret-tool")) return false;
|
|
1925
|
+
if (env.DBUS_SESSION_BUS_ADDRESS) return true;
|
|
1926
|
+
return !!env.XDG_RUNTIME_DIR && existsSync8(join10(env.XDG_RUNTIME_DIR, "bus"));
|
|
1927
|
+
}
|
|
1928
|
+
function onPath(name) {
|
|
1929
|
+
for (const dir of (process.env.PATH ?? "").split(":")) {
|
|
1930
|
+
if (dir && existsSync8(join10(dir, name))) return true;
|
|
1931
|
+
}
|
|
1932
|
+
return false;
|
|
1933
|
+
}
|
|
1934
|
+
function resolveStoreKind(mode, reachable = osStoreReachable()) {
|
|
1935
|
+
if (mode === "file") return "file";
|
|
1936
|
+
if (mode === "os") return "os";
|
|
1937
|
+
return reachable ? "os" : "file";
|
|
1938
|
+
}
|
|
1939
|
+
function describeSecretStore(c = configured) {
|
|
1940
|
+
const mode = c?.mode ?? "auto";
|
|
1941
|
+
const kind = c ? resolveStoreKind(mode) : "os";
|
|
1942
|
+
return kind === "file" && c ? { kind, mode, dir: keysDir(c.stateDir) } : { kind: "os", mode };
|
|
1943
|
+
}
|
|
1944
|
+
function getSessionStore(c = configured) {
|
|
1945
|
+
const r = describeSecretStore(c);
|
|
1946
|
+
if (r.kind === "file" && r.dir) return new FileSessionStore(r.dir);
|
|
1947
|
+
return new CrewHintedStore(getOsSessionStore());
|
|
1948
|
+
}
|
|
1949
|
+
function describeKeyStore(ship) {
|
|
1950
|
+
const c = { mode: ship.secretStore ?? "auto", stateDir: ship.stateDir };
|
|
1951
|
+
const r = describeSecretStore(c);
|
|
1952
|
+
const why = c.mode === "auto" ? " (auto: no desktop secret service reachable)" : ` (ship.secretStore: ${c.mode})`;
|
|
1953
|
+
if (r.kind === "file" && r.dir) {
|
|
1954
|
+
const w = new FileSessionStore(r.dir).writable();
|
|
1955
|
+
return `file ${r.dir}${why} \u2014 ${w.ok ? "writable" : `NOT writable (${w.reason})`}`;
|
|
1956
|
+
}
|
|
1957
|
+
return `OS secret store${c.mode === "auto" ? "" : why}`;
|
|
1958
|
+
}
|
|
1822
1959
|
|
|
1823
1960
|
// src/platform.ts
|
|
1824
1961
|
var SHIP_PLATFORMS = ["macos", "linux", "windows"];
|
|
@@ -1857,8 +1994,8 @@ function explain(ship, required) {
|
|
|
1857
1994
|
}
|
|
1858
1995
|
|
|
1859
1996
|
// src/repo-config.ts
|
|
1860
|
-
import { readFileSync as
|
|
1861
|
-
import { join as
|
|
1997
|
+
import { readFileSync as readFileSync10, existsSync as existsSync9 } from "fs";
|
|
1998
|
+
import { join as join11, basename as basename2 } from "path";
|
|
1862
1999
|
import { parse } from "yaml";
|
|
1863
2000
|
var SPEC_VERSION = 1;
|
|
1864
2001
|
var REPO_CONFIG_FILENAMES = [".crew.yaml", ".crew.yml", ".crew.json"];
|
|
@@ -1942,8 +2079,8 @@ var MODES = ["local", "integrate", "ci_manual", "ci_auto", "external"];
|
|
|
1942
2079
|
var PROVIDERS = ["github", "buildkite", "other", "none"];
|
|
1943
2080
|
function findRepoConfig(dir) {
|
|
1944
2081
|
for (const name of REPO_CONFIG_FILENAMES) {
|
|
1945
|
-
const p =
|
|
1946
|
-
if (
|
|
2082
|
+
const p = join11(dir, name);
|
|
2083
|
+
if (existsSync9(p)) return p;
|
|
1947
2084
|
}
|
|
1948
2085
|
return null;
|
|
1949
2086
|
}
|
|
@@ -2181,7 +2318,7 @@ function parseRepoConfig(text, file) {
|
|
|
2181
2318
|
function loadRepoConfig(dir) {
|
|
2182
2319
|
const file = findRepoConfig(dir);
|
|
2183
2320
|
if (!file) return null;
|
|
2184
|
-
return parseRepoConfig(
|
|
2321
|
+
return parseRepoConfig(readFileSync10(file, "utf8"), file);
|
|
2185
2322
|
}
|
|
2186
2323
|
function hookLabel(cfg, hook2) {
|
|
2187
2324
|
return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
|
|
@@ -2503,7 +2640,8 @@ var SHIP_KEYS = /* @__PURE__ */ new Set([
|
|
|
2503
2640
|
"forges",
|
|
2504
2641
|
"previewHost",
|
|
2505
2642
|
"maxPeriscopes",
|
|
2506
|
-
"periscopeIdleMinutes"
|
|
2643
|
+
"periscopeIdleMinutes",
|
|
2644
|
+
"secretStore"
|
|
2507
2645
|
]);
|
|
2508
2646
|
var ROUTE_KEYS = /* @__PURE__ */ new Set([
|
|
2509
2647
|
"route",
|
|
@@ -2651,11 +2789,11 @@ function expand(p, base) {
|
|
|
2651
2789
|
return isAbsolute(out) ? out : resolve(base, out);
|
|
2652
2790
|
}
|
|
2653
2791
|
function resolvePromptsDir(promptSet, crewHome, base) {
|
|
2654
|
-
if (!promptSet) return
|
|
2792
|
+
if (!promptSet) return join12(crewHome, "prompts", "default");
|
|
2655
2793
|
if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
|
|
2656
2794
|
return expand(promptSet, base);
|
|
2657
2795
|
}
|
|
2658
|
-
return
|
|
2796
|
+
return join12(crewHome, "prompts", promptSet);
|
|
2659
2797
|
}
|
|
2660
2798
|
var Missing = class {
|
|
2661
2799
|
paths = [];
|
|
@@ -2679,7 +2817,7 @@ var Missing = class {
|
|
|
2679
2817
|
};
|
|
2680
2818
|
function crewVersion(crewHome) {
|
|
2681
2819
|
try {
|
|
2682
|
-
const pkg = JSON.parse(
|
|
2820
|
+
const pkg = JSON.parse(readFileSync11(join12(crewHome, "package.json"), "utf8"));
|
|
2683
2821
|
return pkg.version ?? "0.0.0";
|
|
2684
2822
|
} catch {
|
|
2685
2823
|
return "0.0.0";
|
|
@@ -2689,26 +2827,26 @@ function configSearchPath(crewHome) {
|
|
|
2689
2827
|
if (process.env.CREW_CONFIG) return [resolve(process.env.CREW_CONFIG)];
|
|
2690
2828
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
2691
2829
|
return [
|
|
2692
|
-
...xdg ? [
|
|
2693
|
-
|
|
2830
|
+
...xdg ? [join12(xdg, "crew", "crew.yaml")] : [],
|
|
2831
|
+
join12(homedir6(), ".config", "crew", "crew.yaml"),
|
|
2694
2832
|
resolve(crewHome, "crew.yaml")
|
|
2695
2833
|
];
|
|
2696
2834
|
}
|
|
2697
2835
|
function findConfigFile(crewHome) {
|
|
2698
2836
|
const candidates2 = configSearchPath(crewHome);
|
|
2699
|
-
return candidates2.find((c) =>
|
|
2837
|
+
return candidates2.find((c) => existsSync10(c)) ?? candidates2[candidates2.length - 1];
|
|
2700
2838
|
}
|
|
2701
|
-
var defaultConfigPath = () =>
|
|
2839
|
+
var defaultConfigPath = () => join12(process.env.XDG_CONFIG_HOME ?? join12(homedir6(), ".config"), "crew", "crew.yaml");
|
|
2702
2840
|
function routeSlug(route2) {
|
|
2703
2841
|
return route2.replace(/\//g, "-");
|
|
2704
2842
|
}
|
|
2705
2843
|
function resolvedPathFor(stateDir, route2) {
|
|
2706
2844
|
const [workspace, project] = splitRoute(route2);
|
|
2707
|
-
return
|
|
2845
|
+
return join12(stateDir, "resolved", workspace, `${project}.json`);
|
|
2708
2846
|
}
|
|
2709
2847
|
function apiKeyPathFor(stateDir, route2) {
|
|
2710
2848
|
const [workspace, project] = splitRoute(route2);
|
|
2711
|
-
return
|
|
2849
|
+
return join12(stateDir, "keys", workspace, `${project}.env`);
|
|
2712
2850
|
}
|
|
2713
2851
|
function splitRoute(route2) {
|
|
2714
2852
|
const parts = route2.split("/");
|
|
@@ -2719,9 +2857,9 @@ function splitRoute(route2) {
|
|
|
2719
2857
|
}
|
|
2720
2858
|
function readResolvedFile(stateDir, route2) {
|
|
2721
2859
|
const path = resolvedPathFor(stateDir, route2);
|
|
2722
|
-
if (!
|
|
2860
|
+
if (!existsSync10(path)) return void 0;
|
|
2723
2861
|
try {
|
|
2724
|
-
return JSON.parse(
|
|
2862
|
+
return JSON.parse(readFileSync11(path, "utf8"));
|
|
2725
2863
|
} catch (e) {
|
|
2726
2864
|
throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
|
|
2727
2865
|
}
|
|
@@ -2781,18 +2919,18 @@ function parseResolved(raw, m, where) {
|
|
|
2781
2919
|
}
|
|
2782
2920
|
function loadConfig(crewHome, configFile, opts = {}) {
|
|
2783
2921
|
const file = configFile ?? findConfigFile(crewHome);
|
|
2784
|
-
if (opts.text === void 0 && !
|
|
2922
|
+
if (opts.text === void 0 && !existsSync10(file)) {
|
|
2785
2923
|
throw new ConfigError(
|
|
2786
2924
|
`no config found. Looked in:
|
|
2787
2925
|
` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
|
|
2788
2926
|
|
|
2789
2927
|
Create the first of those:
|
|
2790
|
-
mkdir -p ${dirname4(defaultConfigPath())} && cp ${
|
|
2928
|
+
mkdir -p ${dirname4(defaultConfigPath())} && cp ${join12(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
|
|
2791
2929
|
);
|
|
2792
2930
|
}
|
|
2793
2931
|
let raw;
|
|
2794
2932
|
try {
|
|
2795
|
-
raw = parse2(opts.text ??
|
|
2933
|
+
raw = parse2(opts.text ?? readFileSync11(file, "utf8"));
|
|
2796
2934
|
} catch (e) {
|
|
2797
2935
|
throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
|
|
2798
2936
|
}
|
|
@@ -2846,6 +2984,11 @@ Create the first of those:
|
|
|
2846
2984
|
if (!Number.isInteger(periscopeIdleMinutes) || periscopeIdleMinutes < 1) {
|
|
2847
2985
|
missing.add(`ship.periscopeIdleMinutes must be a positive integer (got "${shipRaw.periscopeIdleMinutes}")`);
|
|
2848
2986
|
}
|
|
2987
|
+
const secretStoreRaw = shipRaw.secretStore === void 0 ? "auto" : shipRaw.secretStore;
|
|
2988
|
+
if (!isSecretStoreMode(secretStoreRaw)) {
|
|
2989
|
+
missing.add(`ship.secretStore must be one of ${SECRET_STORE_MODES.join(" | ")} (got "${String(shipRaw.secretStore)}")`);
|
|
2990
|
+
}
|
|
2991
|
+
const secretStore = isSecretStoreMode(secretStoreRaw) ? secretStoreRaw : "auto";
|
|
2849
2992
|
const eventsRotateBytes = shipRaw.eventsRotateBytes === void 0 ? DEFAULT_EVENTS_ROTATE_BYTES : Number(shipRaw.eventsRotateBytes);
|
|
2850
2993
|
if (!Number.isInteger(eventsRotateBytes) || eventsRotateBytes < 1) {
|
|
2851
2994
|
missing.add(`ship.eventsRotateBytes must be a positive integer (got "${shipRaw.eventsRotateBytes}")`);
|
|
@@ -2885,6 +3028,7 @@ Create the first of those:
|
|
|
2885
3028
|
const dupe = seen.find((n, i) => n && seen.indexOf(n) !== i);
|
|
2886
3029
|
if (dupe) missing.add(`routes: duplicate route "${dupe}" \u2014 a route addresses itself, so it must be unique`);
|
|
2887
3030
|
missing.throwIfAny(file);
|
|
3031
|
+
configureSecretStore({ mode: secretStore, stateDir });
|
|
2888
3032
|
return {
|
|
2889
3033
|
ship: {
|
|
2890
3034
|
name: shipRaw.name ?? "this ship",
|
|
@@ -2902,7 +3046,7 @@ Create the first of those:
|
|
|
2902
3046
|
useNvm: shipRaw.useNvm !== false,
|
|
2903
3047
|
nvmSh: shipRaw.nvmSh,
|
|
2904
3048
|
stateDir,
|
|
2905
|
-
logFile: expand(shipRaw.logFile ??
|
|
3049
|
+
logFile: expand(shipRaw.logFile ?? join12(tmpdir(), "crew.log"), base),
|
|
2906
3050
|
userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
|
|
2907
3051
|
maxConcurrentAgents,
|
|
2908
3052
|
maxConcurrentAgentsSet,
|
|
@@ -2919,7 +3063,8 @@ Create the first of those:
|
|
|
2919
3063
|
forges: parseShipForges(shipRaw.forges),
|
|
2920
3064
|
previewHost: shipRaw.previewHost ? String(shipRaw.previewHost).trim() || void 0 : void 0,
|
|
2921
3065
|
maxPeriscopes,
|
|
2922
|
-
periscopeIdleMinutes
|
|
3066
|
+
periscopeIdleMinutes,
|
|
3067
|
+
secretStore
|
|
2923
3068
|
},
|
|
2924
3069
|
routes,
|
|
2925
3070
|
crewHome,
|
|
@@ -3063,7 +3208,7 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
|
|
|
3063
3208
|
const hasRepos = Object.keys(repoDirs).length > 0;
|
|
3064
3209
|
const reposBasePath = expand(String(c?.reposBasePath ?? raw.ship?.reposBasePath ?? DEFAULT_REPOS_BASE_PATH), base);
|
|
3065
3210
|
const dirIsCloneBase = !c?.dir && !hasRepos && !Array.isArray(c?.repos);
|
|
3066
|
-
const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ?
|
|
3211
|
+
const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join12(reposBasePath, splitRoute(route2)[0]) : "";
|
|
3067
3212
|
const weight = c?.weight === void 0 ? void 0 : Number(c.weight);
|
|
3068
3213
|
if (weight !== void 0 && !(Number.isFinite(weight) && weight > 0)) {
|
|
3069
3214
|
missing.add(`${where}.weight must be a positive number (got "${c?.weight}")`);
|
|
@@ -3160,8 +3305,8 @@ function repoIdForName(r, name) {
|
|
|
3160
3305
|
}
|
|
3161
3306
|
function resolveApiKey(r) {
|
|
3162
3307
|
if (r.apiKey) return r.apiKey;
|
|
3163
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
3164
|
-
for (const line of
|
|
3308
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync10(r.apiKeyFile)) {
|
|
3309
|
+
for (const line of readFileSync11(r.apiKeyFile, "utf8").split("\n")) {
|
|
3165
3310
|
const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
|
|
3166
3311
|
if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
3167
3312
|
}
|
|
@@ -3174,9 +3319,9 @@ function resolveApiKey(r) {
|
|
|
3174
3319
|
}
|
|
3175
3320
|
function credentialSource(r) {
|
|
3176
3321
|
if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
|
|
3177
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
3322
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync10(r.apiKeyFile)) {
|
|
3178
3323
|
const re = new RegExp(`^${r.apiKeyVar}=`);
|
|
3179
|
-
if (
|
|
3324
|
+
if (readFileSync11(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
|
|
3180
3325
|
}
|
|
3181
3326
|
return process.env.CREW_API_KEY?.trim() ? "CREW_API_KEY" : "none";
|
|
3182
3327
|
}
|
|
@@ -3232,7 +3377,7 @@ function configuredMembers(rt) {
|
|
|
3232
3377
|
return out;
|
|
3233
3378
|
}
|
|
3234
3379
|
function defaultRepoDir(basePath, workspace, repoName) {
|
|
3235
|
-
return
|
|
3380
|
+
return join12(basePath, workspace, repoName);
|
|
3236
3381
|
}
|
|
3237
3382
|
function reposOf(r) {
|
|
3238
3383
|
const named = Object.entries(r.repos);
|
|
@@ -3265,7 +3410,7 @@ function passengerRepoTargets(r) {
|
|
|
3265
3410
|
return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
|
|
3266
3411
|
}
|
|
3267
3412
|
function passengerCheckoutDir(basePath, workspace, repoName) {
|
|
3268
|
-
return
|
|
3413
|
+
return join12(basePath, workspace, ".passenger-checkouts", repoName);
|
|
3269
3414
|
}
|
|
3270
3415
|
function passengerCheckoutTargets(r) {
|
|
3271
3416
|
if (!r.hostPassengers) return [];
|
|
@@ -3372,10 +3517,10 @@ import { platform } from "process";
|
|
|
3372
3517
|
function defaultShell() {
|
|
3373
3518
|
return platform === "win32" ? { bin: "powershell", args: ["-NoProfile", "-NonInteractive", "-Command"] } : { bin: "bash", args: ["-c"] };
|
|
3374
3519
|
}
|
|
3375
|
-
function resolveShell(
|
|
3376
|
-
if (!
|
|
3377
|
-
if (typeof
|
|
3378
|
-
const bin =
|
|
3520
|
+
function resolveShell(configured2) {
|
|
3521
|
+
if (!configured2) return defaultShell();
|
|
3522
|
+
if (typeof configured2 !== "string") return configured2;
|
|
3523
|
+
const bin = configured2.trim();
|
|
3379
3524
|
if (/pwsh|powershell/i.test(bin)) {
|
|
3380
3525
|
return { bin, args: ["-NoProfile", "-NonInteractive", "-Command"] };
|
|
3381
3526
|
}
|
|
@@ -3552,11 +3697,11 @@ function recoveredFailureHeadlines(o, route2) {
|
|
|
3552
3697
|
}
|
|
3553
3698
|
|
|
3554
3699
|
// src/install.ts
|
|
3555
|
-
import { dirname as dirname5, join as
|
|
3700
|
+
import { dirname as dirname5, join as join13, extname } from "path";
|
|
3556
3701
|
import { homedir as homedir7 } from "os";
|
|
3557
|
-
import { accessSync, constants, existsSync as
|
|
3702
|
+
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync11, mkdirSync as mkdirSync7, readdirSync as readdirSync3, readFileSync as readFileSync12, statSync as statSync4, writeFileSync as writeFileSync8, unlinkSync, realpathSync } from "fs";
|
|
3558
3703
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
3559
|
-
import { createHash as
|
|
3704
|
+
import { createHash as createHash3 } from "crypto";
|
|
3560
3705
|
|
|
3561
3706
|
// src/runtime-info.ts
|
|
3562
3707
|
function isCompiledBinary(moduleUrl) {
|
|
@@ -3566,7 +3711,7 @@ function isCompiledBinary(moduleUrl) {
|
|
|
3566
3711
|
// src/install.ts
|
|
3567
3712
|
function crewInvocation(crewHome) {
|
|
3568
3713
|
if (isCompiledBinary(import.meta.url)) return [process.execPath];
|
|
3569
|
-
return [process.execPath,
|
|
3714
|
+
return [process.execPath, join13(crewHome, "bin", "crew")];
|
|
3570
3715
|
}
|
|
3571
3716
|
var INTERVAL_SECONDS = 120;
|
|
3572
3717
|
function labelFor(crewHome, job) {
|
|
@@ -3577,7 +3722,7 @@ function labelFor(crewHome, job) {
|
|
|
3577
3722
|
return crewHome;
|
|
3578
3723
|
}
|
|
3579
3724
|
})();
|
|
3580
|
-
const hash =
|
|
3725
|
+
const hash = createHash3("sha1").update(real).digest("hex").slice(0, 8);
|
|
3581
3726
|
const stem = job === "release" ? "com.tablation.crew-release" : job === "passengers" ? "com.tablation.crew-passengers" : "com.tablation.crew";
|
|
3582
3727
|
return `${stem}.${hash}`;
|
|
3583
3728
|
}
|
|
@@ -3604,17 +3749,17 @@ function pathFor(ship) {
|
|
|
3604
3749
|
return parts.join(":");
|
|
3605
3750
|
}
|
|
3606
3751
|
function findOnPath(bin, searchPath) {
|
|
3607
|
-
const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) =>
|
|
3752
|
+
const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join13(dir, bin));
|
|
3608
3753
|
for (const candidate of candidates2) {
|
|
3609
3754
|
try {
|
|
3610
|
-
|
|
3611
|
-
if (
|
|
3755
|
+
accessSync2(candidate, constants2.X_OK);
|
|
3756
|
+
if (statSync4(candidate).isFile()) return candidate;
|
|
3612
3757
|
} catch {
|
|
3613
3758
|
}
|
|
3614
3759
|
}
|
|
3615
3760
|
return void 0;
|
|
3616
3761
|
}
|
|
3617
|
-
var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin",
|
|
3762
|
+
var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join13(homedir7(), ".docker", "bin")].join(":");
|
|
3618
3763
|
function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
|
|
3619
3764
|
const schedulerPath = pathFor(ship);
|
|
3620
3765
|
if (findOnPath("docker", schedulerPath)) return void 0;
|
|
@@ -3626,7 +3771,7 @@ function xmlEscape(s) {
|
|
|
3626
3771
|
}
|
|
3627
3772
|
function planLaunchd(ship, crewHome, job) {
|
|
3628
3773
|
const label = labelFor(crewHome, job);
|
|
3629
|
-
const unitPath =
|
|
3774
|
+
const unitPath = join13(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
3630
3775
|
const crewLog = ship.logFile;
|
|
3631
3776
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
3632
3777
|
const persistent = job === "run";
|
|
@@ -3680,8 +3825,8 @@ ${scheduleKeys}
|
|
|
3680
3825
|
}
|
|
3681
3826
|
function planSystemd(ship, crewHome, job) {
|
|
3682
3827
|
const label = labelFor(crewHome, job);
|
|
3683
|
-
const unitDir =
|
|
3684
|
-
const servicePath =
|
|
3828
|
+
const unitDir = join13(homedir7(), ".config", "systemd", "user");
|
|
3829
|
+
const servicePath = join13(unitDir, `${label}.service`);
|
|
3685
3830
|
const crewLog = ship.logFile;
|
|
3686
3831
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
3687
3832
|
const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
|
|
@@ -3721,7 +3866,7 @@ WantedBy=default.target
|
|
|
3721
3866
|
schedulerLog
|
|
3722
3867
|
};
|
|
3723
3868
|
}
|
|
3724
|
-
const timerPath =
|
|
3869
|
+
const timerPath = join13(unitDir, `${label}.timer`);
|
|
3725
3870
|
const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
|
|
3726
3871
|
[Unit]
|
|
3727
3872
|
Description=${description}
|
|
@@ -3795,7 +3940,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
3795
3940
|
function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
3796
3941
|
const label = labelFor(crewHome, job);
|
|
3797
3942
|
if (hostShip === "macos") {
|
|
3798
|
-
const unitPath =
|
|
3943
|
+
const unitPath = join13(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
3799
3944
|
return {
|
|
3800
3945
|
mechanism: "launchd",
|
|
3801
3946
|
unitPaths: [unitPath],
|
|
@@ -3805,18 +3950,18 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
3805
3950
|
};
|
|
3806
3951
|
}
|
|
3807
3952
|
if (hostShip === "linux" && hasSystemd) {
|
|
3808
|
-
const unitDir =
|
|
3953
|
+
const unitDir = join13(homedir7(), ".config", "systemd", "user");
|
|
3809
3954
|
if (job === "run") {
|
|
3810
3955
|
return {
|
|
3811
3956
|
mechanism: "systemd",
|
|
3812
|
-
unitPaths: [
|
|
3957
|
+
unitPaths: [join13(unitDir, `${label}.service`)],
|
|
3813
3958
|
crontabLine: null,
|
|
3814
3959
|
unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.service`]]
|
|
3815
3960
|
};
|
|
3816
3961
|
}
|
|
3817
3962
|
return {
|
|
3818
3963
|
mechanism: "systemd",
|
|
3819
|
-
unitPaths: [
|
|
3964
|
+
unitPaths: [join13(unitDir, `${label}.service`), join13(unitDir, `${label}.timer`)],
|
|
3820
3965
|
crontabLine: null,
|
|
3821
3966
|
unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.timer`]]
|
|
3822
3967
|
};
|
|
@@ -3834,7 +3979,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
3834
3979
|
function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
3835
3980
|
const label = labelFor(crewHome, "run");
|
|
3836
3981
|
if (hostShip === "macos") {
|
|
3837
|
-
const unitPath =
|
|
3982
|
+
const unitPath = join13(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
3838
3983
|
return {
|
|
3839
3984
|
mechanism: "launchd",
|
|
3840
3985
|
startCommand: ["launchctl", "load", unitPath],
|
|
@@ -3874,8 +4019,8 @@ async function applyInstall(plan, crewHome, dryRun, log) {
|
|
|
3874
4019
|
log.emit(`would write ${path}`);
|
|
3875
4020
|
continue;
|
|
3876
4021
|
}
|
|
3877
|
-
|
|
3878
|
-
|
|
4022
|
+
mkdirSync7(dirname5(path), { recursive: true });
|
|
4023
|
+
writeFileSync8(path, plan.unitContent[path]);
|
|
3879
4024
|
log.emit(`wrote ${path}`);
|
|
3880
4025
|
}
|
|
3881
4026
|
if (plan.crontabLine) {
|
|
@@ -3929,7 +4074,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
3929
4074
|
}
|
|
3930
4075
|
}
|
|
3931
4076
|
for (const path of plan.unitPaths) {
|
|
3932
|
-
if (!
|
|
4077
|
+
if (!existsSync11(path)) continue;
|
|
3933
4078
|
if (dryRun) {
|
|
3934
4079
|
log.emit(`would remove ${path}`);
|
|
3935
4080
|
continue;
|
|
@@ -3948,7 +4093,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
3948
4093
|
}
|
|
3949
4094
|
var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
|
|
3950
4095
|
function launchAgentsDir() {
|
|
3951
|
-
return
|
|
4096
|
+
return join13(homedir7(), "Library", "LaunchAgents");
|
|
3952
4097
|
}
|
|
3953
4098
|
function xmlUnescape(s) {
|
|
3954
4099
|
return s.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
@@ -3965,25 +4110,25 @@ function loadedCrewLabels() {
|
|
|
3965
4110
|
return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
|
|
3966
4111
|
}
|
|
3967
4112
|
function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
3968
|
-
const files =
|
|
4113
|
+
const files = existsSync11(dir) ? readdirSync3(dir) : [];
|
|
3969
4114
|
const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
|
|
3970
|
-
const plistPath =
|
|
4115
|
+
const plistPath = join13(dir, f);
|
|
3971
4116
|
let program = null;
|
|
3972
4117
|
try {
|
|
3973
|
-
program = programFromPlist(
|
|
4118
|
+
program = programFromPlist(readFileSync12(plistPath, "utf8"));
|
|
3974
4119
|
} catch {
|
|
3975
4120
|
}
|
|
3976
4121
|
return {
|
|
3977
4122
|
label: f.slice(0, -".plist".length),
|
|
3978
4123
|
plistPath,
|
|
3979
4124
|
program,
|
|
3980
|
-
programExists: program !== null &&
|
|
4125
|
+
programExists: program !== null && existsSync11(program)
|
|
3981
4126
|
};
|
|
3982
4127
|
});
|
|
3983
4128
|
const seen = new Set(units.map((u) => u.label));
|
|
3984
4129
|
for (const label of [...loaded].sort()) {
|
|
3985
4130
|
if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
|
|
3986
|
-
units.push({ label, plistPath:
|
|
4131
|
+
units.push({ label, plistPath: join13(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
|
|
3987
4132
|
}
|
|
3988
4133
|
return units;
|
|
3989
4134
|
}
|
|
@@ -3996,9 +4141,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
|
|
|
3996
4141
|
log.emit(`would unload and remove ${unit.label}`);
|
|
3997
4142
|
return;
|
|
3998
4143
|
}
|
|
3999
|
-
if (
|
|
4144
|
+
if (existsSync11(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
|
|
4000
4145
|
spawnSync4("launchctl", ["remove", unit.label], { encoding: "utf8" });
|
|
4001
|
-
if (
|
|
4146
|
+
if (existsSync11(unit.plistPath)) unlinkSync(unit.plistPath);
|
|
4002
4147
|
log.emit(`removed ${unit.label} (${unit.plistPath})`);
|
|
4003
4148
|
}
|
|
4004
4149
|
function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
|
|
@@ -4076,24 +4221,24 @@ function detectSystemd() {
|
|
|
4076
4221
|
}
|
|
4077
4222
|
|
|
4078
4223
|
// src/ship-attention.ts
|
|
4079
|
-
import { existsSync as
|
|
4224
|
+
import { existsSync as existsSync13, statfsSync } from "fs";
|
|
4080
4225
|
|
|
4081
4226
|
// src/git.ts
|
|
4082
4227
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
4083
4228
|
import {
|
|
4084
|
-
existsSync as
|
|
4085
|
-
mkdirSync as
|
|
4229
|
+
existsSync as existsSync12,
|
|
4230
|
+
mkdirSync as mkdirSync8,
|
|
4086
4231
|
openSync as openSync2,
|
|
4087
4232
|
closeSync as closeSync2,
|
|
4088
4233
|
writeSync,
|
|
4089
|
-
statSync as
|
|
4234
|
+
statSync as statSync5,
|
|
4090
4235
|
unlinkSync as unlinkSync2,
|
|
4091
|
-
readFileSync as
|
|
4092
|
-
writeFileSync as
|
|
4236
|
+
readFileSync as readFileSync13,
|
|
4237
|
+
writeFileSync as writeFileSync9,
|
|
4093
4238
|
readdirSync as readdirSync4,
|
|
4094
|
-
rmSync as
|
|
4239
|
+
rmSync as rmSync7
|
|
4095
4240
|
} from "fs";
|
|
4096
|
-
import { dirname as dirname6, basename as basename4, join as
|
|
4241
|
+
import { dirname as dirname6, basename as basename4, join as join14 } from "path";
|
|
4097
4242
|
var GitError = class extends Error {
|
|
4098
4243
|
};
|
|
4099
4244
|
function streamText(x) {
|
|
@@ -4474,7 +4619,7 @@ function recoverStrandedReleaseCheckouts(cwd) {
|
|
|
4474
4619
|
else if (line.startsWith("branch refs/heads/crew/release-") && path) {
|
|
4475
4620
|
gitOk(cwd, ["worktree", "remove", "--force", path]);
|
|
4476
4621
|
const root = dirname6(path);
|
|
4477
|
-
if (basename4(root).startsWith("crew-release-"))
|
|
4622
|
+
if (basename4(root).startsWith("crew-release-")) rmSync7(root, { recursive: true, force: true });
|
|
4478
4623
|
}
|
|
4479
4624
|
}
|
|
4480
4625
|
const stranded = (gitOk(cwd, ["for-each-ref", "--format=%(refname:short)", "refs/heads/crew/release-*"]) ?? "").split("\n").filter(Boolean);
|
|
@@ -4491,12 +4636,12 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
|
|
|
4491
4636
|
}
|
|
4492
4637
|
for (const name of names) {
|
|
4493
4638
|
if (!name.startsWith("crew-release-")) continue;
|
|
4494
|
-
const root =
|
|
4639
|
+
const root = join14(tmp, name);
|
|
4495
4640
|
try {
|
|
4496
|
-
const st =
|
|
4641
|
+
const st = statSync5(root);
|
|
4497
4642
|
if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
|
|
4498
|
-
if (
|
|
4499
|
-
|
|
4643
|
+
if (existsSync12(join14(root, "checkout"))) continue;
|
|
4644
|
+
rmSync7(root, { recursive: true, force: true });
|
|
4500
4645
|
removed.push(name);
|
|
4501
4646
|
} catch {
|
|
4502
4647
|
}
|
|
@@ -4545,13 +4690,13 @@ function originUrlOf(dir, remoteName) {
|
|
|
4545
4690
|
}
|
|
4546
4691
|
}
|
|
4547
4692
|
function ensureRepoCheckout(dir, remote, opts = {}) {
|
|
4548
|
-
if (
|
|
4693
|
+
if (existsSync12(`${dir}/.git`)) return false;
|
|
4549
4694
|
if (!remote) {
|
|
4550
4695
|
throw new GitError(
|
|
4551
4696
|
`${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`
|
|
4552
4697
|
);
|
|
4553
4698
|
}
|
|
4554
|
-
|
|
4699
|
+
mkdirSync8(dirname6(dir), { recursive: true });
|
|
4555
4700
|
const url = cloneUrlFor(remote, opts);
|
|
4556
4701
|
try {
|
|
4557
4702
|
execFileSync2("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -4576,7 +4721,7 @@ function sleepSync(ms3) {
|
|
|
4576
4721
|
}
|
|
4577
4722
|
function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
4578
4723
|
const lockPath = checkoutLockPath(dir);
|
|
4579
|
-
|
|
4724
|
+
mkdirSync8(dirname6(lockPath), { recursive: true });
|
|
4580
4725
|
const deadline = Date.now() + timeoutMs;
|
|
4581
4726
|
for (; ; ) {
|
|
4582
4727
|
try {
|
|
@@ -4594,7 +4739,7 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
|
4594
4739
|
const err2 = e;
|
|
4595
4740
|
if (err2.code !== "EEXIST") throw err2;
|
|
4596
4741
|
try {
|
|
4597
|
-
if (Date.now() -
|
|
4742
|
+
if (Date.now() - statSync5(lockPath).mtimeMs > CHECKOUT_LOCK_STALE_MS) {
|
|
4598
4743
|
try {
|
|
4599
4744
|
unlinkSync2(lockPath);
|
|
4600
4745
|
} catch {
|
|
@@ -4613,17 +4758,17 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
|
4613
4758
|
function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {}) {
|
|
4614
4759
|
const release = acquireCheckoutLock(dir);
|
|
4615
4760
|
try {
|
|
4616
|
-
if (!
|
|
4761
|
+
if (!existsSync12(`${dir}/.git`) && githubRemote) {
|
|
4617
4762
|
const url = cloneUrlFor(githubRemote, clone);
|
|
4618
4763
|
const marker = cloneFailureMarker(dir);
|
|
4619
|
-
if (
|
|
4620
|
-
return { action: "clone-failed", detail:
|
|
4764
|
+
if (existsSync12(marker) && readFileSync13(marker, "utf8").split("\n")[0] === url) {
|
|
4765
|
+
return { action: "clone-failed", detail: readFileSync13(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
|
|
4621
4766
|
}
|
|
4622
4767
|
try {
|
|
4623
4768
|
ensureRepoCheckout(dir, githubRemote, clone);
|
|
4624
4769
|
} catch (e) {
|
|
4625
4770
|
if (!(e instanceof GitError)) throw e;
|
|
4626
|
-
|
|
4771
|
+
writeFileSync9(marker, `${url}
|
|
4627
4772
|
${e.message}`);
|
|
4628
4773
|
return { action: "clone-failed", detail: e.message, repeat: false };
|
|
4629
4774
|
}
|
|
@@ -4689,6 +4834,15 @@ function baseAttention(repo, outcome, remote, base, nowIso = (/* @__PURE__ */ ne
|
|
|
4689
4834
|
if (outcome.action === "level" || outcome.action === "fast-forwarded") return { clear: baseUnsafeKey(repo) };
|
|
4690
4835
|
return null;
|
|
4691
4836
|
}
|
|
4837
|
+
var seatKeyUnreadableKey = (route2, seat) => `seat_key_unreadable:${route2}:${seat}`;
|
|
4838
|
+
function seatKeyUnreadableItem(route2, seat, storeError, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
4839
|
+
return {
|
|
4840
|
+
kind: "seat_key_unreadable",
|
|
4841
|
+
key: seatKeyUnreadableKey(route2, seat),
|
|
4842
|
+
since: nowIso,
|
|
4843
|
+
message: `${route2}: the ${seat} seat's agent key cannot be read (${storeError}) \u2014 its runs are refused until the key store is readable. Resumes automatically once it is; \`crew doctor\` names the store in use`
|
|
4844
|
+
};
|
|
4845
|
+
}
|
|
4692
4846
|
var agentLogRefusedKey = (route2) => `agent_log_refused:${route2}`;
|
|
4693
4847
|
function agentLogRefusedItem(route2, role, status2, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
4694
4848
|
return {
|
|
@@ -4756,7 +4910,7 @@ function repoStopFor(items, route2) {
|
|
|
4756
4910
|
};
|
|
4757
4911
|
}
|
|
4758
4912
|
function recheckEnvironmentStops(state, route2, ship, deps = {}) {
|
|
4759
|
-
const
|
|
4913
|
+
const onPath2 = deps.onPath ?? findOnPath;
|
|
4760
4914
|
const dockerProblem = deps.dockerProblem ?? dockerPathProblem;
|
|
4761
4915
|
const cleared = [];
|
|
4762
4916
|
for (const item of state.shipAttention().previous()) {
|
|
@@ -4779,7 +4933,7 @@ function recheckEnvironmentStops(state, route2, ship, deps = {}) {
|
|
|
4779
4933
|
}
|
|
4780
4934
|
const script = cfg.hooks[h.hook];
|
|
4781
4935
|
const cmd = script ? hookCommand(script) : null;
|
|
4782
|
-
if (!cmd ||
|
|
4936
|
+
if (!cmd || onPath2(cmd, pathFor(ship))) {
|
|
4783
4937
|
clearShipAttention(state, item.key);
|
|
4784
4938
|
cleared.push(`${h.repo}/${h.hook}`);
|
|
4785
4939
|
}
|
|
@@ -4830,7 +4984,7 @@ function noteHeldWorktree(state, h, nowMs = Date.now()) {
|
|
|
4830
4984
|
function reconcileHeldWorktrees(state, repo, log, stillRefused) {
|
|
4831
4985
|
for (const i of state.shipAttention().previous()) {
|
|
4832
4986
|
if (i.kind !== "worktree_held" || !i.held || i.held.repo !== repo) continue;
|
|
4833
|
-
const gone = !
|
|
4987
|
+
const gone = !existsSync13(i.held.path);
|
|
4834
4988
|
if (!gone && !(stillRefused && !stillRefused.has(i.held.path))) continue;
|
|
4835
4989
|
clearShipAttention(state, i.key);
|
|
4836
4990
|
log.emit(
|
|
@@ -5081,30 +5235,30 @@ function roleCapWarnings(ship) {
|
|
|
5081
5235
|
|
|
5082
5236
|
// src/selftest.ts
|
|
5083
5237
|
import { spawn as spawn3 } from "child_process";
|
|
5084
|
-
import { existsSync as
|
|
5238
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync13, mkdtempSync, readFileSync as readFileSync20, rmSync as rmSync11, writeFileSync as writeFileSync14 } from "fs";
|
|
5085
5239
|
import { tmpdir as tmpdir2 } from "os";
|
|
5086
|
-
import { join as
|
|
5240
|
+
import { join as join23 } from "path";
|
|
5087
5241
|
|
|
5088
5242
|
// src/agent.ts
|
|
5089
5243
|
import { spawn as spawn2, spawnSync as spawnSync5 } from "child_process";
|
|
5090
|
-
import { readFileSync as
|
|
5244
|
+
import { readFileSync as readFileSync19, existsSync as existsSync21, statSync as statSync7, mkdirSync as mkdirSync12, writeFileSync as writeFileSync13, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
|
|
5091
5245
|
import { StringDecoder } from "string_decoder";
|
|
5092
5246
|
import { finished } from "stream/promises";
|
|
5093
|
-
import { join as
|
|
5094
|
-
import { createHash as
|
|
5247
|
+
import { join as join22, dirname as dirname8, basename as basename7 } from "path";
|
|
5248
|
+
import { createHash as createHash4 } from "crypto";
|
|
5095
5249
|
|
|
5096
5250
|
// src/state.ts
|
|
5097
|
-
import { readFileSync as
|
|
5098
|
-
import { join as
|
|
5251
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync14, mkdirSync as mkdirSync9, rmSync as rmSync8, statSync as statSync6, readdirSync as readdirSync5 } from "fs";
|
|
5252
|
+
import { join as join15 } from "path";
|
|
5099
5253
|
var EPOCH = "1970-01-01T00:00:00.000Z";
|
|
5100
5254
|
var State = class _State {
|
|
5101
5255
|
dir;
|
|
5102
5256
|
constructor(dir) {
|
|
5103
5257
|
this.dir = dir;
|
|
5104
|
-
|
|
5258
|
+
mkdirSync9(dir, { recursive: true });
|
|
5105
5259
|
}
|
|
5106
5260
|
path(...parts) {
|
|
5107
|
-
return
|
|
5261
|
+
return join15(this.dir, ...parts);
|
|
5108
5262
|
}
|
|
5109
5263
|
/**
|
|
5110
5264
|
* The timestamp every "new since last poll" check is measured against.
|
|
@@ -5115,14 +5269,14 @@ var State = class _State {
|
|
|
5115
5269
|
*/
|
|
5116
5270
|
watermark() {
|
|
5117
5271
|
try {
|
|
5118
|
-
const v =
|
|
5272
|
+
const v = readFileSync14(this.path(".poll-watermark"), "utf8").trim();
|
|
5119
5273
|
return v || EPOCH;
|
|
5120
5274
|
} catch {
|
|
5121
5275
|
return EPOCH;
|
|
5122
5276
|
}
|
|
5123
5277
|
}
|
|
5124
5278
|
setWatermark(at) {
|
|
5125
|
-
|
|
5279
|
+
writeFileSync10(this.path(".poll-watermark"), `${at}
|
|
5126
5280
|
`);
|
|
5127
5281
|
}
|
|
5128
5282
|
/**
|
|
@@ -5140,19 +5294,19 @@ var State = class _State {
|
|
|
5140
5294
|
}
|
|
5141
5295
|
/** Whole-crew pause, and the per-role sentinels one level down. */
|
|
5142
5296
|
isPaused() {
|
|
5143
|
-
return
|
|
5297
|
+
return existsSync14(this.path(".crew-paused"));
|
|
5144
5298
|
}
|
|
5145
5299
|
isRolePaused(role) {
|
|
5146
|
-
return
|
|
5300
|
+
return existsSync14(this.path(`.role-paused-${role}`));
|
|
5147
5301
|
}
|
|
5148
5302
|
pausedRoles(roles) {
|
|
5149
5303
|
return new Set(roles.filter((r) => this.isRolePaused(r)));
|
|
5150
5304
|
}
|
|
5151
5305
|
pause(role) {
|
|
5152
|
-
|
|
5306
|
+
writeFileSync10(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
|
|
5153
5307
|
}
|
|
5154
5308
|
resume(role) {
|
|
5155
|
-
|
|
5309
|
+
rmSync8(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), { force: true });
|
|
5156
5310
|
}
|
|
5157
5311
|
/**
|
|
5158
5312
|
* The release phase (merge, deploy, release) has its own sentinel, one
|
|
@@ -5160,11 +5314,11 @@ var State = class _State {
|
|
|
5160
5314
|
* while agents keep working.
|
|
5161
5315
|
*/
|
|
5162
5316
|
isReleasePaused() {
|
|
5163
|
-
return
|
|
5317
|
+
return existsSync14(this.path(".release-paused"));
|
|
5164
5318
|
}
|
|
5165
5319
|
setReleasePaused(paused) {
|
|
5166
|
-
if (paused)
|
|
5167
|
-
else
|
|
5320
|
+
if (paused) writeFileSync10(this.path(".release-paused"), "");
|
|
5321
|
+
else rmSync8(this.path(".release-paused"), { force: true });
|
|
5168
5322
|
}
|
|
5169
5323
|
/**
|
|
5170
5324
|
* Why a release cycle must not run right now, or null when it may. Every
|
|
@@ -5201,7 +5355,7 @@ var State = class _State {
|
|
|
5201
5355
|
const name = _State.safe(rawName);
|
|
5202
5356
|
const file = this.path(`.${name}.lock`);
|
|
5203
5357
|
try {
|
|
5204
|
-
const existing = Number.parseInt(
|
|
5358
|
+
const existing = Number.parseInt(readFileSync14(file, "utf8").trim(), 10);
|
|
5205
5359
|
if (Number.isFinite(existing) && existing > 0) {
|
|
5206
5360
|
try {
|
|
5207
5361
|
process.kill(existing, 0);
|
|
@@ -5211,13 +5365,13 @@ var State = class _State {
|
|
|
5211
5365
|
}
|
|
5212
5366
|
} catch {
|
|
5213
5367
|
}
|
|
5214
|
-
|
|
5368
|
+
writeFileSync10(file, `${process.pid}
|
|
5215
5369
|
`);
|
|
5216
5370
|
return {
|
|
5217
5371
|
ok: true,
|
|
5218
5372
|
release: () => {
|
|
5219
5373
|
try {
|
|
5220
|
-
|
|
5374
|
+
rmSync8(file, { force: true });
|
|
5221
5375
|
} catch {
|
|
5222
5376
|
}
|
|
5223
5377
|
}
|
|
@@ -5239,7 +5393,7 @@ var State = class _State {
|
|
|
5239
5393
|
if (!/^\.release-.+\.lock$/.test(n)) continue;
|
|
5240
5394
|
let pid = 0;
|
|
5241
5395
|
try {
|
|
5242
|
-
pid = Number.parseInt(
|
|
5396
|
+
pid = Number.parseInt(readFileSync14(this.path(n), "utf8").trim(), 10);
|
|
5243
5397
|
} catch {
|
|
5244
5398
|
continue;
|
|
5245
5399
|
}
|
|
@@ -5325,7 +5479,7 @@ var State = class _State {
|
|
|
5325
5479
|
ticketInFlight(recordId) {
|
|
5326
5480
|
const file = this.path(`.${_State.safe(`crew-ticket-${recordId}`)}.lock`);
|
|
5327
5481
|
try {
|
|
5328
|
-
const pid = Number.parseInt(
|
|
5482
|
+
const pid = Number.parseInt(readFileSync14(file, "utf8").trim(), 10);
|
|
5329
5483
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
5330
5484
|
process.kill(pid, 0);
|
|
5331
5485
|
return true;
|
|
@@ -5359,7 +5513,7 @@ var State = class _State {
|
|
|
5359
5513
|
const heldReported = `.release-held-${suffix}`;
|
|
5360
5514
|
const read = (f) => {
|
|
5361
5515
|
try {
|
|
5362
|
-
return
|
|
5516
|
+
return readFileSync14(this.path(f), "utf8").trim() || null;
|
|
5363
5517
|
} catch {
|
|
5364
5518
|
return null;
|
|
5365
5519
|
}
|
|
@@ -5368,12 +5522,12 @@ var State = class _State {
|
|
|
5368
5522
|
blockedCount: () => Number.parseInt(read(blocked) ?? "", 10) || 0,
|
|
5369
5523
|
noteBlocked: () => {
|
|
5370
5524
|
const n = (Number.parseInt(read(blocked) ?? "", 10) || 0) + 1;
|
|
5371
|
-
|
|
5525
|
+
writeFileSync10(this.path(blocked), `${n}
|
|
5372
5526
|
`);
|
|
5373
5527
|
return n;
|
|
5374
5528
|
},
|
|
5375
5529
|
clearBlocked: () => {
|
|
5376
|
-
|
|
5530
|
+
rmSync8(this.path(blocked), { force: true });
|
|
5377
5531
|
},
|
|
5378
5532
|
/**
|
|
5379
5533
|
* The commit a deploy last failed on.
|
|
@@ -5385,11 +5539,11 @@ var State = class _State {
|
|
|
5385
5539
|
*/
|
|
5386
5540
|
deployFailedSha: () => read(failed),
|
|
5387
5541
|
noteDeployFailed: (sha) => {
|
|
5388
|
-
|
|
5542
|
+
writeFileSync10(this.path(failed), `${sha}
|
|
5389
5543
|
`);
|
|
5390
5544
|
},
|
|
5391
5545
|
clearDeployFailed: () => {
|
|
5392
|
-
|
|
5546
|
+
rmSync8(this.path(failed), { force: true });
|
|
5393
5547
|
},
|
|
5394
5548
|
/**
|
|
5395
5549
|
* Consecutive test-gate failures on the same head (CREW-1368). A failure
|
|
@@ -5399,31 +5553,31 @@ var State = class _State {
|
|
|
5399
5553
|
noteTestGateFailed: (sha) => {
|
|
5400
5554
|
const [prev, n] = (read(gateFailed) ?? "").split(" ");
|
|
5401
5555
|
const next = prev === sha ? (Number.parseInt(n ?? "", 10) || 0) + 1 : 1;
|
|
5402
|
-
|
|
5556
|
+
writeFileSync10(this.path(gateFailed), `${sha} ${next}
|
|
5403
5557
|
`);
|
|
5404
5558
|
return next;
|
|
5405
5559
|
},
|
|
5406
5560
|
clearTestGateFailed: () => {
|
|
5407
|
-
|
|
5561
|
+
rmSync8(this.path(gateFailed), { force: true });
|
|
5408
5562
|
},
|
|
5409
5563
|
/** Whether the board was already told about this head's red gate. */
|
|
5410
5564
|
heldReported: () => {
|
|
5411
5565
|
try {
|
|
5412
|
-
return
|
|
5566
|
+
return readFileSync14(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
|
|
5413
5567
|
} catch {
|
|
5414
5568
|
return null;
|
|
5415
5569
|
}
|
|
5416
5570
|
},
|
|
5417
5571
|
noteHeldReported: (signature) => {
|
|
5418
|
-
|
|
5572
|
+
writeFileSync10(this.path(heldReported), `${signature}
|
|
5419
5573
|
`);
|
|
5420
5574
|
},
|
|
5421
5575
|
clearHeldReported: () => {
|
|
5422
|
-
|
|
5576
|
+
rmSync8(this.path(heldReported), { force: true });
|
|
5423
5577
|
},
|
|
5424
5578
|
testGateReported: (sha) => read(gateReported) === sha,
|
|
5425
5579
|
noteTestGateReported: (sha) => {
|
|
5426
|
-
|
|
5580
|
+
writeFileSync10(this.path(gateReported), `${sha}
|
|
5427
5581
|
`);
|
|
5428
5582
|
}
|
|
5429
5583
|
};
|
|
@@ -5446,13 +5600,13 @@ var State = class _State {
|
|
|
5446
5600
|
const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
|
|
5447
5601
|
const read = (f) => {
|
|
5448
5602
|
try {
|
|
5449
|
-
return
|
|
5603
|
+
return readFileSync14(this.path(f), "utf8").trim() || null;
|
|
5450
5604
|
} catch {
|
|
5451
5605
|
return null;
|
|
5452
5606
|
}
|
|
5453
5607
|
};
|
|
5454
5608
|
const clear2 = () => {
|
|
5455
|
-
for (const f of [streakFile, ticketFile, sinceFile, ticketStreakFile])
|
|
5609
|
+
for (const f of [streakFile, ticketFile, sinceFile, ticketStreakFile]) rmSync8(this.path(f), { force: true });
|
|
5456
5610
|
};
|
|
5457
5611
|
return {
|
|
5458
5612
|
/** Consecutive cycles this route had actionable work and did not win. */
|
|
@@ -5479,18 +5633,18 @@ var State = class _State {
|
|
|
5479
5633
|
clear2();
|
|
5480
5634
|
return;
|
|
5481
5635
|
}
|
|
5482
|
-
|
|
5636
|
+
writeFileSync10(this.path(streakFile), `${this.readInt(streakFile) + 1}
|
|
5483
5637
|
`);
|
|
5484
5638
|
const prior = read(ticketFile);
|
|
5485
5639
|
if (topTicket && topTicket === prior) {
|
|
5486
|
-
|
|
5640
|
+
writeFileSync10(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
|
|
5487
5641
|
`);
|
|
5488
5642
|
} else if (topTicket) {
|
|
5489
|
-
|
|
5643
|
+
writeFileSync10(this.path(ticketFile), `${topTicket}
|
|
5490
5644
|
`);
|
|
5491
|
-
|
|
5645
|
+
writeFileSync10(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
5492
5646
|
`);
|
|
5493
|
-
|
|
5647
|
+
writeFileSync10(this.path(ticketStreakFile), "1\n");
|
|
5494
5648
|
}
|
|
5495
5649
|
}
|
|
5496
5650
|
};
|
|
@@ -5511,14 +5665,14 @@ var State = class _State {
|
|
|
5511
5665
|
return {
|
|
5512
5666
|
previous: () => {
|
|
5513
5667
|
try {
|
|
5514
|
-
const parsed = JSON.parse(
|
|
5668
|
+
const parsed = JSON.parse(readFileSync14(this.path(file), "utf8"));
|
|
5515
5669
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
5516
5670
|
} catch {
|
|
5517
5671
|
return {};
|
|
5518
5672
|
}
|
|
5519
5673
|
},
|
|
5520
5674
|
persist: (next) => {
|
|
5521
|
-
|
|
5675
|
+
writeFileSync10(this.path(file), JSON.stringify(next));
|
|
5522
5676
|
}
|
|
5523
5677
|
};
|
|
5524
5678
|
}
|
|
@@ -5534,14 +5688,14 @@ var State = class _State {
|
|
|
5534
5688
|
return {
|
|
5535
5689
|
previous: () => {
|
|
5536
5690
|
try {
|
|
5537
|
-
const parsed = JSON.parse(
|
|
5691
|
+
const parsed = JSON.parse(readFileSync14(this.path(file), "utf8"));
|
|
5538
5692
|
return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : [];
|
|
5539
5693
|
} catch {
|
|
5540
5694
|
return [];
|
|
5541
5695
|
}
|
|
5542
5696
|
},
|
|
5543
5697
|
persist: (next) => {
|
|
5544
|
-
|
|
5698
|
+
writeFileSync10(this.path(file), JSON.stringify(next));
|
|
5545
5699
|
}
|
|
5546
5700
|
};
|
|
5547
5701
|
}
|
|
@@ -5555,14 +5709,14 @@ var State = class _State {
|
|
|
5555
5709
|
return {
|
|
5556
5710
|
previous: () => {
|
|
5557
5711
|
try {
|
|
5558
|
-
const parsed = JSON.parse(
|
|
5712
|
+
const parsed = JSON.parse(readFileSync14(this.path(file), "utf8"));
|
|
5559
5713
|
return Array.isArray(parsed) ? parsed : [];
|
|
5560
5714
|
} catch {
|
|
5561
5715
|
return [];
|
|
5562
5716
|
}
|
|
5563
5717
|
},
|
|
5564
5718
|
persist: (next) => {
|
|
5565
|
-
|
|
5719
|
+
writeFileSync10(this.path(file), JSON.stringify(next));
|
|
5566
5720
|
}
|
|
5567
5721
|
};
|
|
5568
5722
|
}
|
|
@@ -5575,11 +5729,11 @@ var State = class _State {
|
|
|
5575
5729
|
const file = ".release-last-run";
|
|
5576
5730
|
return {
|
|
5577
5731
|
touch: () => {
|
|
5578
|
-
|
|
5732
|
+
writeFileSync10(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
|
|
5579
5733
|
},
|
|
5580
5734
|
at: () => {
|
|
5581
5735
|
try {
|
|
5582
|
-
return
|
|
5736
|
+
return statSync6(this.path(file)).mtimeMs;
|
|
5583
5737
|
} catch {
|
|
5584
5738
|
return null;
|
|
5585
5739
|
}
|
|
@@ -5588,7 +5742,7 @@ var State = class _State {
|
|
|
5588
5742
|
}
|
|
5589
5743
|
readInt(file) {
|
|
5590
5744
|
try {
|
|
5591
|
-
return Number.parseInt(
|
|
5745
|
+
return Number.parseInt(readFileSync14(this.path(file), "utf8").trim(), 10) || 0;
|
|
5592
5746
|
} catch {
|
|
5593
5747
|
return 0;
|
|
5594
5748
|
}
|
|
@@ -6071,8 +6225,8 @@ var PERISCOPE_COLUMNS = [
|
|
|
6071
6225
|
];
|
|
6072
6226
|
|
|
6073
6227
|
// src/agent-log.ts
|
|
6074
|
-
import { existsSync as
|
|
6075
|
-
import { join as
|
|
6228
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync10, readdirSync as readdirSync6, readFileSync as readFileSync15, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "fs";
|
|
6229
|
+
import { join as join16 } from "path";
|
|
6076
6230
|
|
|
6077
6231
|
// src/pending-backoff.ts
|
|
6078
6232
|
var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
|
|
@@ -6244,20 +6398,20 @@ async function addRunToTicket(target, t, recordId, run2) {
|
|
|
6244
6398
|
};
|
|
6245
6399
|
}
|
|
6246
6400
|
}
|
|
6247
|
-
var queueDir = (stateDir) =>
|
|
6401
|
+
var queueDir = (stateDir) => join16(stateDir, "pending-agent-logs");
|
|
6248
6402
|
function queueAgentRun(stateDir, name, q) {
|
|
6249
|
-
|
|
6250
|
-
|
|
6403
|
+
mkdirSync10(queueDir(stateDir), { recursive: true });
|
|
6404
|
+
writeFileSync11(join16(queueDir(stateDir), `${name}.json`), JSON.stringify(q));
|
|
6251
6405
|
}
|
|
6252
6406
|
async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
6253
6407
|
const dir = queueDir(stateDir);
|
|
6254
|
-
if (!
|
|
6408
|
+
if (!existsSync15(dir)) return 0;
|
|
6255
6409
|
let sent = 0;
|
|
6256
6410
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
6257
|
-
const path =
|
|
6411
|
+
const path = join16(dir, f);
|
|
6258
6412
|
let q;
|
|
6259
6413
|
try {
|
|
6260
|
-
q = JSON.parse(
|
|
6414
|
+
q = JSON.parse(readFileSync15(path, "utf8"));
|
|
6261
6415
|
} catch {
|
|
6262
6416
|
continue;
|
|
6263
6417
|
}
|
|
@@ -6276,17 +6430,17 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
6276
6430
|
attempts: 0,
|
|
6277
6431
|
nextAttemptAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6278
6432
|
});
|
|
6279
|
-
|
|
6433
|
+
rmSync9(path, { force: true });
|
|
6280
6434
|
sent += 1;
|
|
6281
6435
|
log.emit(`sent a queued Agent Log report (${f})`);
|
|
6282
6436
|
} catch (e) {
|
|
6283
6437
|
if (!(e instanceof AgentLogUnreachableError)) {
|
|
6284
|
-
|
|
6438
|
+
rmSync9(path, { force: true });
|
|
6285
6439
|
log.warn(`dropped a queued Agent Log report (${f}): ${e.message}`);
|
|
6286
6440
|
continue;
|
|
6287
6441
|
}
|
|
6288
6442
|
const attempts = q.attempts + 1;
|
|
6289
|
-
|
|
6443
|
+
writeFileSync11(path, JSON.stringify({
|
|
6290
6444
|
...q,
|
|
6291
6445
|
attempts,
|
|
6292
6446
|
warned: true,
|
|
@@ -6297,20 +6451,20 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
6297
6451
|
}
|
|
6298
6452
|
return sent;
|
|
6299
6453
|
}
|
|
6300
|
-
var countersDir = (stateDir) =>
|
|
6454
|
+
var countersDir = (stateDir) => join16(stateDir, "pending-ticket-counters");
|
|
6301
6455
|
function queueTicketCounters(stateDir, name, q) {
|
|
6302
|
-
|
|
6303
|
-
|
|
6456
|
+
mkdirSync10(countersDir(stateDir), { recursive: true });
|
|
6457
|
+
writeFileSync11(join16(countersDir(stateDir), `${name}.json`), JSON.stringify(q));
|
|
6304
6458
|
}
|
|
6305
6459
|
async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
6306
6460
|
const dir = countersDir(stateDir);
|
|
6307
|
-
if (!
|
|
6461
|
+
if (!existsSync15(dir)) return 0;
|
|
6308
6462
|
let sent = 0;
|
|
6309
6463
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
6310
|
-
const path =
|
|
6464
|
+
const path = join16(dir, f);
|
|
6311
6465
|
let q;
|
|
6312
6466
|
try {
|
|
6313
|
-
q = JSON.parse(
|
|
6467
|
+
q = JSON.parse(readFileSync15(path, "utf8"));
|
|
6314
6468
|
} catch {
|
|
6315
6469
|
continue;
|
|
6316
6470
|
}
|
|
@@ -6319,17 +6473,17 @@ async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /
|
|
|
6319
6473
|
const apiKey = await keyFor(q.role);
|
|
6320
6474
|
if (!apiKey) continue;
|
|
6321
6475
|
await writeCounters({ ...q.target, apiKey }, q.write);
|
|
6322
|
-
|
|
6476
|
+
rmSync9(path, { force: true });
|
|
6323
6477
|
sent += 1;
|
|
6324
6478
|
log.emit(`added a queued run's spend to ${q.write.ticketReference ?? q.write.recordId} (${f})`);
|
|
6325
6479
|
} catch (e) {
|
|
6326
6480
|
if (!isRetryable(e)) {
|
|
6327
|
-
|
|
6481
|
+
rmSync9(path, { force: true });
|
|
6328
6482
|
log.warn(`dropped a queued ticket counter update (${f}): ${e.message}`);
|
|
6329
6483
|
continue;
|
|
6330
6484
|
}
|
|
6331
6485
|
const attempts = q.attempts + 1;
|
|
6332
|
-
|
|
6486
|
+
writeFileSync11(path, JSON.stringify({
|
|
6333
6487
|
...q,
|
|
6334
6488
|
attempts,
|
|
6335
6489
|
warned: true,
|
|
@@ -6532,8 +6686,8 @@ var HandoffTranscriptTracker = class {
|
|
|
6532
6686
|
};
|
|
6533
6687
|
|
|
6534
6688
|
// src/pending-handoff.ts
|
|
6535
|
-
import { existsSync as
|
|
6536
|
-
import { join as
|
|
6689
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, readdirSync as readdirSync7, readFileSync as readFileSync16, rmSync as rmSync10, writeFileSync as writeFileSync12 } from "fs";
|
|
6690
|
+
import { join as join19 } from "path";
|
|
6537
6691
|
|
|
6538
6692
|
// src/ticket-branch.ts
|
|
6539
6693
|
import { basename as basename6 } from "path";
|
|
@@ -7616,13 +7770,13 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
|
|
|
7616
7770
|
}
|
|
7617
7771
|
|
|
7618
7772
|
// src/run-worktree.ts
|
|
7619
|
-
import { existsSync as
|
|
7620
|
-
import { dirname as dirname7, join as
|
|
7773
|
+
import { existsSync as existsSync17 } from "fs";
|
|
7774
|
+
import { dirname as dirname7, join as join18 } from "path";
|
|
7621
7775
|
|
|
7622
7776
|
// src/ports.ts
|
|
7623
7777
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
7624
|
-
import { existsSync as
|
|
7625
|
-
import { join as
|
|
7778
|
+
import { existsSync as existsSync16 } from "fs";
|
|
7779
|
+
import { join as join17 } from "path";
|
|
7626
7780
|
var run = (cmd, args) => {
|
|
7627
7781
|
try {
|
|
7628
7782
|
return execFileSync4(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -7667,7 +7821,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
|
|
|
7667
7821
|
}
|
|
7668
7822
|
function worktreeExists(parent, prefix, n) {
|
|
7669
7823
|
for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
|
|
7670
|
-
if (
|
|
7824
|
+
if (existsSync16(join17(parent, name, ".git"))) return true;
|
|
7671
7825
|
}
|
|
7672
7826
|
return false;
|
|
7673
7827
|
}
|
|
@@ -7750,7 +7904,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
7750
7904
|
const existing = worktreeForNumber(dir, num);
|
|
7751
7905
|
const located = locateBranchForTicket(dir, cfg, t, role);
|
|
7752
7906
|
const baseRef = hasRemote ? `${remote}/${base}` : base;
|
|
7753
|
-
if (existing && !existing.prunable &&
|
|
7907
|
+
if (existing && !existing.prunable && existsSync17(existing.path)) {
|
|
7754
7908
|
const branch = existing.branch;
|
|
7755
7909
|
if (status(existing.path).length > 0) {
|
|
7756
7910
|
return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
|
|
@@ -7766,7 +7920,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
7766
7920
|
if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
|
|
7767
7921
|
return { action: "recut", path: existing.path, branch, base: baseRef };
|
|
7768
7922
|
}
|
|
7769
|
-
const path =
|
|
7923
|
+
const path = join18(dirname7(dir), worktreeForTicket(dir, cfg, t, role));
|
|
7770
7924
|
if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
|
|
7771
7925
|
return { action: "cut-remote", path, branch: located.branch };
|
|
7772
7926
|
}
|
|
@@ -7820,7 +7974,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
7820
7974
|
if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
|
|
7821
7975
|
if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
|
|
7822
7976
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
7823
|
-
if (wt &&
|
|
7977
|
+
if (wt && existsSync17(wt.path)) {
|
|
7824
7978
|
if (status(wt.path).length > 0) return false;
|
|
7825
7979
|
if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
|
|
7826
7980
|
}
|
|
@@ -7828,7 +7982,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
7828
7982
|
}
|
|
7829
7983
|
async function finishRunWorktree(dir, cfg, branch) {
|
|
7830
7984
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
7831
|
-
if (!wt || !
|
|
7985
|
+
if (!wt || !existsSync17(wt.path)) return { kind: "none" };
|
|
7832
7986
|
if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
|
|
7833
7987
|
if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
|
|
7834
7988
|
if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
|
|
@@ -7840,7 +7994,7 @@ async function finishRunWorktree(dir, cfg, branch) {
|
|
|
7840
7994
|
if (pids.length) await killGently(pids);
|
|
7841
7995
|
}
|
|
7842
7996
|
gitOk(dir, ["worktree", "remove", wt.path]);
|
|
7843
|
-
return
|
|
7997
|
+
return existsSync17(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
|
|
7844
7998
|
}
|
|
7845
7999
|
|
|
7846
8000
|
// src/handoff-guard.ts
|
|
@@ -8223,20 +8377,20 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
|
|
|
8223
8377
|
|
|
8224
8378
|
// src/pending-handoff.ts
|
|
8225
8379
|
var PENDING_MAX_AGE_MS = 48 * 36e5;
|
|
8226
|
-
var pendingDirFor = (stateDir) =>
|
|
8227
|
-
var stateDirOfStream = (streamPath) =>
|
|
8380
|
+
var pendingDirFor = (stateDir) => join19(stateDir, "pending-handoffs");
|
|
8381
|
+
var stateDirOfStream = (streamPath) => join19(streamPath, "..", "..");
|
|
8228
8382
|
function writePendingHandoff(stateDir, rec) {
|
|
8229
8383
|
const dir = pendingDirFor(stateDir);
|
|
8230
|
-
|
|
8231
|
-
|
|
8384
|
+
mkdirSync11(dir, { recursive: true });
|
|
8385
|
+
writeFileSync12(join19(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
|
|
8232
8386
|
}
|
|
8233
8387
|
function listPendingHandoffs(stateDir, route2) {
|
|
8234
8388
|
const dir = pendingDirFor(stateDir);
|
|
8235
|
-
if (!
|
|
8389
|
+
if (!existsSync18(dir)) return [];
|
|
8236
8390
|
const out = [];
|
|
8237
8391
|
for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
8238
8392
|
try {
|
|
8239
|
-
const rec = JSON.parse(
|
|
8393
|
+
const rec = JSON.parse(readFileSync16(join19(dir, f), "utf8"));
|
|
8240
8394
|
if (rec.version === 1 && rec.ticketRecordId && (!route2 || rec.route === route2)) out.push(rec);
|
|
8241
8395
|
} catch {
|
|
8242
8396
|
}
|
|
@@ -8244,7 +8398,7 @@ function listPendingHandoffs(stateDir, route2) {
|
|
|
8244
8398
|
return out;
|
|
8245
8399
|
}
|
|
8246
8400
|
function removePendingHandoff(stateDir, ticketRecordId) {
|
|
8247
|
-
|
|
8401
|
+
rmSync10(join19(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
|
|
8248
8402
|
}
|
|
8249
8403
|
function pendingHandoffFor(stateDir, ticketRecordId) {
|
|
8250
8404
|
return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
|
|
@@ -8354,8 +8508,8 @@ function expandSkillMarkers(text, lookup2, opts = {}) {
|
|
|
8354
8508
|
}
|
|
8355
8509
|
|
|
8356
8510
|
// src/skills.ts
|
|
8357
|
-
import { readFileSync as
|
|
8358
|
-
import { join as
|
|
8511
|
+
import { readFileSync as readFileSync18, readdirSync as readdirSync8, existsSync as existsSync20 } from "fs";
|
|
8512
|
+
import { join as join21 } from "path";
|
|
8359
8513
|
import { parse as parseYaml } from "yaml";
|
|
8360
8514
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
8361
8515
|
|
|
@@ -8371,8 +8525,8 @@ ${additions}`;
|
|
|
8371
8525
|
}
|
|
8372
8526
|
|
|
8373
8527
|
// src/agents.ts
|
|
8374
|
-
import { readFileSync as
|
|
8375
|
-
import { join as
|
|
8528
|
+
import { readFileSync as readFileSync17, existsSync as existsSync19 } from "fs";
|
|
8529
|
+
import { join as join20 } from "path";
|
|
8376
8530
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
|
|
8377
8531
|
var PERSONA_NAME = {
|
|
8378
8532
|
dev: "Developer",
|
|
@@ -8386,12 +8540,12 @@ var CREW_AGENT_COLUMN = "agent_id";
|
|
|
8386
8540
|
var AgentsSyncError = class extends Error {
|
|
8387
8541
|
};
|
|
8388
8542
|
function personaDefaultPrompt(promptsDir, role) {
|
|
8389
|
-
const briefPath =
|
|
8390
|
-
if (!
|
|
8391
|
-
if (SELF_CONTAINED_ROLES.has(role)) return
|
|
8392
|
-
const commonPath =
|
|
8393
|
-
if (!
|
|
8394
|
-
return `${
|
|
8543
|
+
const briefPath = join20(promptsDir, "personas", `lane-${role}.md`);
|
|
8544
|
+
if (!existsSync19(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
|
|
8545
|
+
if (SELF_CONTAINED_ROLES.has(role)) return readFileSync17(briefPath, "utf8");
|
|
8546
|
+
const commonPath = join20(promptsDir, "personas", "common.md");
|
|
8547
|
+
if (!existsSync19(commonPath)) return readFileSync17(briefPath, "utf8");
|
|
8548
|
+
return `${readFileSync17(commonPath, "utf8")}${readFileSync17(briefPath, "utf8")}`;
|
|
8395
8549
|
}
|
|
8396
8550
|
async function resolveAgentId(route2, role, opts) {
|
|
8397
8551
|
const cached = route2.resolved?.agentPersonas?.[role]?.agentId;
|
|
@@ -8838,7 +8992,7 @@ function describeCrewLink(o) {
|
|
|
8838
8992
|
var SkillSyncError = class extends Error {
|
|
8839
8993
|
};
|
|
8840
8994
|
function parseSkillFile(path) {
|
|
8841
|
-
const text =
|
|
8995
|
+
const text = readFileSync18(path, "utf8");
|
|
8842
8996
|
const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text);
|
|
8843
8997
|
if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
|
|
8844
8998
|
const frontmatter = match[1];
|
|
@@ -8851,9 +9005,9 @@ function parseSkillFile(path) {
|
|
|
8851
9005
|
return { name: meta.name, description: meta.description, prompt: body, file: path };
|
|
8852
9006
|
}
|
|
8853
9007
|
function listLocalSkills(promptsDir) {
|
|
8854
|
-
const dir =
|
|
8855
|
-
if (!
|
|
8856
|
-
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(
|
|
9008
|
+
const dir = join21(promptsDir, "skills");
|
|
9009
|
+
if (!existsSync20(dir)) return [];
|
|
9010
|
+
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join21(dir, f)));
|
|
8857
9011
|
}
|
|
8858
9012
|
async function syncSkills(route2, opts) {
|
|
8859
9013
|
if (!route2.resolved) {
|
|
@@ -9055,26 +9209,26 @@ var AgentError = class extends Error {
|
|
|
9055
9209
|
};
|
|
9056
9210
|
var AgentSpawnError = class extends AgentError {
|
|
9057
9211
|
};
|
|
9058
|
-
var digestPathFor = (stateDir, routeLabel, role, runId) =>
|
|
9212
|
+
var digestPathFor = (stateDir, routeLabel, role, runId) => join22(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
9059
9213
|
function planAgentRun(o) {
|
|
9060
|
-
const briefPath =
|
|
9061
|
-
if (!
|
|
9214
|
+
const briefPath = join22(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
9215
|
+
if (!existsSync21(briefPath)) {
|
|
9062
9216
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
9063
9217
|
}
|
|
9064
|
-
const commonPath =
|
|
9065
|
-
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) &&
|
|
9218
|
+
const commonPath = join22(o.route.promptsDir, "personas", "common.md");
|
|
9219
|
+
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync21(commonPath);
|
|
9066
9220
|
const now = o.now ?? (() => Date.now());
|
|
9067
9221
|
const routeLabel = routeSlug(o.route.route);
|
|
9068
9222
|
const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
|
|
9069
|
-
const digestPath2 = ownDigest &&
|
|
9223
|
+
const digestPath2 = ownDigest && existsSync21(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
|
|
9070
9224
|
let digest;
|
|
9071
9225
|
let ageSeconds;
|
|
9072
|
-
if (
|
|
9073
|
-
ageSeconds = Math.floor((now() -
|
|
9074
|
-
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest =
|
|
9226
|
+
if (existsSync21(digestPath2)) {
|
|
9227
|
+
ageSeconds = Math.floor((now() - statSync7(digestPath2).mtimeMs) / 1e3);
|
|
9228
|
+
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync19(digestPath2, "utf8");
|
|
9075
9229
|
}
|
|
9076
|
-
const common = usesCommon ?
|
|
9077
|
-
const brief =
|
|
9230
|
+
const common = usesCommon ? readFileSync19(commonPath, "utf8") : "";
|
|
9231
|
+
const brief = readFileSync19(briefPath, "utf8");
|
|
9078
9232
|
const effectiveCommon = o.divergedPrompt ? "" : common;
|
|
9079
9233
|
const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
|
|
9080
9234
|
const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
|
|
@@ -9095,8 +9249,8 @@ function planAgentRun(o) {
|
|
|
9095
9249
|
brief: expanded.text,
|
|
9096
9250
|
digest
|
|
9097
9251
|
});
|
|
9098
|
-
const promptSha =
|
|
9099
|
-
const streamsDir =
|
|
9252
|
+
const promptSha = createHash4("sha256").update(expanded.text).digest("hex");
|
|
9253
|
+
const streamsDir = join22(o.stateDir, "streams");
|
|
9100
9254
|
const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
|
|
9101
9255
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
9102
9256
|
const { toolchain, model } = seat;
|
|
@@ -9169,8 +9323,8 @@ function planAgentRun(o) {
|
|
|
9169
9323
|
digestAttached: digest !== void 0,
|
|
9170
9324
|
digestAgeSeconds: ageSeconds,
|
|
9171
9325
|
model,
|
|
9172
|
-
streamPath:
|
|
9173
|
-
eventsPath:
|
|
9326
|
+
streamPath: join22(streamsDir, `${base}.jsonl`),
|
|
9327
|
+
eventsPath: join22(streamsDir, `${base}.events.jsonl`),
|
|
9174
9328
|
ticket: o.ticket,
|
|
9175
9329
|
ticketRecordId: o.ticketRecordId ?? null,
|
|
9176
9330
|
repo: o.repo ?? null,
|
|
@@ -9201,7 +9355,7 @@ function describePlan(p) {
|
|
|
9201
9355
|
var pidPathFor = (streamPath) => `${streamPath}.pid`;
|
|
9202
9356
|
function readRunSidecar(streamPath) {
|
|
9203
9357
|
try {
|
|
9204
|
-
const sc = JSON.parse(
|
|
9358
|
+
const sc = JSON.parse(readFileSync19(pidPathFor(streamPath), "utf8"));
|
|
9205
9359
|
return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
|
|
9206
9360
|
} catch {
|
|
9207
9361
|
return null;
|
|
@@ -9237,7 +9391,7 @@ function identityOf(plan, adapterHarness) {
|
|
|
9237
9391
|
var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
|
|
9238
9392
|
function openSink(path) {
|
|
9239
9393
|
try {
|
|
9240
|
-
|
|
9394
|
+
mkdirSync12(dirname8(path), { recursive: true });
|
|
9241
9395
|
return createWriteStream(path, { flags: "a" });
|
|
9242
9396
|
} catch {
|
|
9243
9397
|
return void 0;
|
|
@@ -9419,7 +9573,7 @@ var RunObserver = class {
|
|
|
9419
9573
|
};
|
|
9420
9574
|
function writeRunMeta(id, startedAt, liveTicket, extra) {
|
|
9421
9575
|
try {
|
|
9422
|
-
|
|
9576
|
+
writeFileSync13(`${id.streamPath}.meta.json`, JSON.stringify({
|
|
9423
9577
|
route: id.route,
|
|
9424
9578
|
role: id.role,
|
|
9425
9579
|
hint: id.ticket,
|
|
@@ -9579,7 +9733,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
9579
9733
|
return new Promise((resolve6, reject) => {
|
|
9580
9734
|
let streamFd;
|
|
9581
9735
|
try {
|
|
9582
|
-
|
|
9736
|
+
mkdirSync12(dirname8(plan.streamPath), { recursive: true });
|
|
9583
9737
|
streamFd = openSync3(plan.streamPath, "a");
|
|
9584
9738
|
} catch (e) {
|
|
9585
9739
|
void endSink(eventsSink);
|
|
@@ -9614,7 +9768,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
9614
9768
|
promptSha: plan.promptSha
|
|
9615
9769
|
};
|
|
9616
9770
|
try {
|
|
9617
|
-
|
|
9771
|
+
writeFileSync13(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
|
|
9618
9772
|
} catch {
|
|
9619
9773
|
}
|
|
9620
9774
|
}
|
|
@@ -9680,7 +9834,7 @@ async function reattachRun(streamPath, sc, emit, o = {}) {
|
|
|
9680
9834
|
}
|
|
9681
9835
|
|
|
9682
9836
|
// src/seat-keys.ts
|
|
9683
|
-
import {
|
|
9837
|
+
import { hostFromUrl as hostFromUrl2 } from "@tablation/client";
|
|
9684
9838
|
var SEAT_KEY_ROLES = ROLE_NAMES.filter((r) => r !== "pair");
|
|
9685
9839
|
function keyIsAdmin(route2) {
|
|
9686
9840
|
const role = route2.resolved?.role;
|
|
@@ -9699,7 +9853,7 @@ function slugFor(route2, agentId) {
|
|
|
9699
9853
|
function seatAgentId(route2, role) {
|
|
9700
9854
|
return route2.resolved?.agentPersonas?.[role]?.agentId;
|
|
9701
9855
|
}
|
|
9702
|
-
async function resolveReleaseKey(route2, getStore =
|
|
9856
|
+
async function resolveReleaseKey(route2, getStore = getSessionStore) {
|
|
9703
9857
|
const ship = (reason) => ({ kind: "ship", apiKey: resolveApiKey(route2), reason });
|
|
9704
9858
|
const agentId = route2.resolved?.releaseAgentId;
|
|
9705
9859
|
if (!agentId) return ship(keyIsAdmin(route2) ? "release seat has no linked Agents row (run `crew agents sync`)" : `release seat ${MEMBER_NO_ROW}`);
|
|
@@ -9708,10 +9862,11 @@ async function resolveReleaseKey(route2, getStore = getSessionStore2) {
|
|
|
9708
9862
|
if (session?.apiKey) return { kind: "agent", apiKey: session.apiKey, agentId };
|
|
9709
9863
|
return ship(keyIsAdmin(route2) ? "no agent key stored for the release seat (run `crew agents sync`)" : MEMBER_NO_KEY);
|
|
9710
9864
|
} catch (e) {
|
|
9711
|
-
|
|
9865
|
+
const reason = describeReadError(e);
|
|
9866
|
+
return { kind: "ship", apiKey: resolveApiKey(route2), reason, storeError: reason };
|
|
9712
9867
|
}
|
|
9713
9868
|
}
|
|
9714
|
-
async function resolveSeatKey(route2, role, getStore =
|
|
9869
|
+
async function resolveSeatKey(route2, role, getStore = getSessionStore) {
|
|
9715
9870
|
const ship = (reason) => ({ kind: "ship", apiKey: resolveApiKey(route2), reason });
|
|
9716
9871
|
if (!SEAT_KEY_ROLES.includes(role)) return ship(`${role} always writes as the ship`);
|
|
9717
9872
|
const agentId = seatAgentId(route2, role);
|
|
@@ -9721,7 +9876,8 @@ async function resolveSeatKey(route2, role, getStore = getSessionStore2) {
|
|
|
9721
9876
|
if (session?.apiKey) return { kind: "agent", apiKey: session.apiKey, agentId };
|
|
9722
9877
|
return ship(keyIsAdmin(route2) ? "no agent key stored for this seat (run `crew agents sync`)" : MEMBER_NO_KEY);
|
|
9723
9878
|
} catch (e) {
|
|
9724
|
-
|
|
9879
|
+
const reason = describeReadError(e);
|
|
9880
|
+
return { kind: "ship", apiKey: resolveApiKey(route2), reason, storeError: reason };
|
|
9725
9881
|
}
|
|
9726
9882
|
}
|
|
9727
9883
|
function safeStderrLine(e) {
|
|
@@ -9735,6 +9891,8 @@ function safeStderrLine(e) {
|
|
|
9735
9891
|
return void 0;
|
|
9736
9892
|
}
|
|
9737
9893
|
function describeReadError(e) {
|
|
9894
|
+
const fsErr = e;
|
|
9895
|
+
if (typeof fsErr?.path === "string" && typeof fsErr.code === "string") return `key file store unreadable (${fsErr.code})`;
|
|
9738
9896
|
const code = e?.code;
|
|
9739
9897
|
const line = safeStderrLine(e);
|
|
9740
9898
|
const what = typeof code === "number" ? `security exited ${code}` : typeof code === "string" ? `security failed: ${code}` : void 0;
|
|
@@ -9763,7 +9921,7 @@ async function mintMissingSeatKeys(route2, o) {
|
|
|
9763
9921
|
];
|
|
9764
9922
|
let store;
|
|
9765
9923
|
try {
|
|
9766
|
-
store = (o.getStore ??
|
|
9924
|
+
store = (o.getStore ?? getSessionStore)();
|
|
9767
9925
|
} catch (e) {
|
|
9768
9926
|
return targets.map(({ role }) => ({
|
|
9769
9927
|
role,
|
|
@@ -9886,7 +10044,7 @@ async function revokeSeatKeys(route2, o) {
|
|
|
9886
10044
|
];
|
|
9887
10045
|
let store;
|
|
9888
10046
|
try {
|
|
9889
|
-
store = (o.getStore ??
|
|
10047
|
+
store = (o.getStore ?? getSessionStore)();
|
|
9890
10048
|
} catch (e) {
|
|
9891
10049
|
return targets.map(({ role }) => ({ role, action: "failed", detail: describeStoreError(e) }));
|
|
9892
10050
|
}
|
|
@@ -10117,7 +10275,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
10117
10275
|
...adapter.runEnv(spec),
|
|
10118
10276
|
[KEY_VAR]: i.apiKey
|
|
10119
10277
|
};
|
|
10120
|
-
const cwd = mkdtempSync(
|
|
10278
|
+
const cwd = mkdtempSync(join23(tmpdir2(), "crew-selftest-"));
|
|
10121
10279
|
try {
|
|
10122
10280
|
const run2 = await deps.run({
|
|
10123
10281
|
bin: invocation.bin,
|
|
@@ -10139,7 +10297,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
10139
10297
|
return finish({ ok: false, failure, detail, ...usage2 });
|
|
10140
10298
|
} finally {
|
|
10141
10299
|
try {
|
|
10142
|
-
|
|
10300
|
+
rmSync11(cwd, { recursive: true, force: true });
|
|
10143
10301
|
} catch {
|
|
10144
10302
|
}
|
|
10145
10303
|
}
|
|
@@ -10150,11 +10308,11 @@ function formatResult(r) {
|
|
|
10150
10308
|
const secs = `${(r.ms / 1e3).toFixed(1)}s`;
|
|
10151
10309
|
return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
|
|
10152
10310
|
}
|
|
10153
|
-
var fileFor2 = (stateDir) =>
|
|
10311
|
+
var fileFor2 = (stateDir) => join23(stateDir, "selftest.json");
|
|
10154
10312
|
function readSelftest(stateDir) {
|
|
10155
10313
|
try {
|
|
10156
10314
|
const p = fileFor2(stateDir);
|
|
10157
|
-
return
|
|
10315
|
+
return existsSync22(p) ? JSON.parse(readFileSync20(p, "utf8")) : {};
|
|
10158
10316
|
} catch {
|
|
10159
10317
|
return {};
|
|
10160
10318
|
}
|
|
@@ -10162,8 +10320,8 @@ function readSelftest(stateDir) {
|
|
|
10162
10320
|
function recordSelftest(stateDir, results) {
|
|
10163
10321
|
const all = readSelftest(stateDir);
|
|
10164
10322
|
for (const r of results) all[r.toolchain] = r;
|
|
10165
|
-
|
|
10166
|
-
|
|
10323
|
+
mkdirSync13(stateDir, { recursive: true });
|
|
10324
|
+
writeFileSync14(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
|
|
10167
10325
|
`);
|
|
10168
10326
|
}
|
|
10169
10327
|
function doctorSelftestLines(stored, toolchains) {
|
|
@@ -10175,7 +10333,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
10175
10333
|
}
|
|
10176
10334
|
|
|
10177
10335
|
// src/cli.ts
|
|
10178
|
-
import { resolve as resolve5, dirname as dirname22, join as
|
|
10336
|
+
import { resolve as resolve5, dirname as dirname22, join as join44, basename as basename13 } from "path";
|
|
10179
10337
|
import { fileURLToPath } from "url";
|
|
10180
10338
|
import { hostname as hostname2, homedir as homedir10 } from "os";
|
|
10181
10339
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
@@ -10296,10 +10454,10 @@ function seats(roster) {
|
|
|
10296
10454
|
function holds(roster) {
|
|
10297
10455
|
return [...roster.values()].filter((m) => m.kind === "hold");
|
|
10298
10456
|
}
|
|
10299
|
-
function buildRoster(
|
|
10457
|
+
function buildRoster(configured2, crewRows) {
|
|
10300
10458
|
const rows = new Map(crewRows.map((r) => [r.id, r]));
|
|
10301
10459
|
const roster = /* @__PURE__ */ new Map();
|
|
10302
|
-
for (const c of
|
|
10460
|
+
for (const c of configured2) {
|
|
10303
10461
|
if (!c.id) continue;
|
|
10304
10462
|
const row = rows.get(c.id);
|
|
10305
10463
|
const name = (row?.name ?? "").trim();
|
|
@@ -10365,14 +10523,14 @@ function rosterMarkdown(roster, meId) {
|
|
|
10365
10523
|
}
|
|
10366
10524
|
|
|
10367
10525
|
// src/connect-resume.ts
|
|
10368
|
-
import { mkdirSync as
|
|
10369
|
-
import { dirname as dirname9, join as
|
|
10526
|
+
import { mkdirSync as mkdirSync14, readFileSync as readFileSync21, rmSync as rmSync12, writeFileSync as writeFileSync15 } from "fs";
|
|
10527
|
+
import { dirname as dirname9, join as join24 } from "path";
|
|
10370
10528
|
function lastSessionFile(stateDir) {
|
|
10371
|
-
return
|
|
10529
|
+
return join24(stateDir, "connect-session.json");
|
|
10372
10530
|
}
|
|
10373
10531
|
function readPointers(stateDir) {
|
|
10374
10532
|
try {
|
|
10375
|
-
const parsed = JSON.parse(
|
|
10533
|
+
const parsed = JSON.parse(readFileSync21(lastSessionFile(stateDir), "utf8"));
|
|
10376
10534
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
10377
10535
|
const out = {};
|
|
10378
10536
|
for (const [host, v] of Object.entries(parsed)) {
|
|
@@ -10386,8 +10544,8 @@ function readPointers(stateDir) {
|
|
|
10386
10544
|
}
|
|
10387
10545
|
function writePointers(stateDir, pointers) {
|
|
10388
10546
|
const file = lastSessionFile(stateDir);
|
|
10389
|
-
|
|
10390
|
-
|
|
10547
|
+
mkdirSync14(dirname9(file), { recursive: true });
|
|
10548
|
+
writeFileSync15(file, `${JSON.stringify(pointers, null, 2)}
|
|
10391
10549
|
`, { mode: 384 });
|
|
10392
10550
|
}
|
|
10393
10551
|
function rememberSession(stateDir, host, workspaceSlug) {
|
|
@@ -10404,7 +10562,7 @@ function forgetWorkspace(stateDir, host, workspaceSlug) {
|
|
|
10404
10562
|
const { [host]: _old, ...rest } = all;
|
|
10405
10563
|
const next = left.length ? { ...rest, [host]: left } : rest;
|
|
10406
10564
|
if (Object.keys(next).length) writePointers(stateDir, next);
|
|
10407
|
-
else
|
|
10565
|
+
else rmSync12(lastSessionFile(stateDir), { force: true });
|
|
10408
10566
|
} catch {
|
|
10409
10567
|
}
|
|
10410
10568
|
}
|
|
@@ -10444,7 +10602,7 @@ function forgetSession(stateDir, host) {
|
|
|
10444
10602
|
try {
|
|
10445
10603
|
const { [host]: _gone, ...rest } = readPointers(stateDir);
|
|
10446
10604
|
if (Object.keys(rest).length) writePointers(stateDir, rest);
|
|
10447
|
-
else
|
|
10605
|
+
else rmSync12(lastSessionFile(stateDir), { force: true });
|
|
10448
10606
|
} catch {
|
|
10449
10607
|
}
|
|
10450
10608
|
}
|
|
@@ -10472,15 +10630,15 @@ async function signOutSessions(o) {
|
|
|
10472
10630
|
}
|
|
10473
10631
|
|
|
10474
10632
|
// src/reset.ts
|
|
10475
|
-
import { existsSync as
|
|
10476
|
-
import { dirname as dirname10, join as
|
|
10633
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync15, readFileSync as readFileSync22, readdirSync as readdirSync9, rmSync as rmSync13, statSync as statSync8, writeFileSync as writeFileSync16 } from "fs";
|
|
10634
|
+
import { dirname as dirname10, join as join25 } from "path";
|
|
10477
10635
|
import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
|
|
10478
10636
|
function clonedCheckoutsFile(stateDir) {
|
|
10479
|
-
return
|
|
10637
|
+
return join25(stateDir, "cloned-checkouts.json");
|
|
10480
10638
|
}
|
|
10481
10639
|
function readClonedCheckouts(stateDir) {
|
|
10482
10640
|
try {
|
|
10483
|
-
const parsed = JSON.parse(
|
|
10641
|
+
const parsed = JSON.parse(readFileSync22(clonedCheckoutsFile(stateDir), "utf8"));
|
|
10484
10642
|
return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
|
|
10485
10643
|
} catch {
|
|
10486
10644
|
return [];
|
|
@@ -10490,8 +10648,8 @@ function recordClonedCheckout(stateDir, dir) {
|
|
|
10490
10648
|
const known = readClonedCheckouts(stateDir);
|
|
10491
10649
|
if (known.includes(dir)) return;
|
|
10492
10650
|
const file = clonedCheckoutsFile(stateDir);
|
|
10493
|
-
|
|
10494
|
-
|
|
10651
|
+
mkdirSync15(dirname10(file), { recursive: true });
|
|
10652
|
+
writeFileSync16(file, `${JSON.stringify([...known, dir], null, 2)}
|
|
10495
10653
|
`);
|
|
10496
10654
|
}
|
|
10497
10655
|
var mark = (dryRun) => dryRun ? "would-remove" : "removed";
|
|
@@ -10543,8 +10701,8 @@ async function runReset(o, fx) {
|
|
|
10543
10701
|
for (const dir of readClonedCheckouts(o.stateDir)) steps.push(removeCheckout(dir, o.dryRun, fx));
|
|
10544
10702
|
}
|
|
10545
10703
|
steps.push(...removeState(o.stateDir, o.dryRun));
|
|
10546
|
-
if (
|
|
10547
|
-
if (!o.dryRun)
|
|
10704
|
+
if (existsSync23(o.configFile)) {
|
|
10705
|
+
if (!o.dryRun) rmSync13(o.configFile, { force: true });
|
|
10548
10706
|
steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
|
|
10549
10707
|
} else {
|
|
10550
10708
|
steps.push({ step: "config", target: o.configFile, action: "absent" });
|
|
@@ -10552,7 +10710,7 @@ async function runReset(o, fx) {
|
|
|
10552
10710
|
return steps;
|
|
10553
10711
|
}
|
|
10554
10712
|
function removeCheckout(dir, dryRun, fx) {
|
|
10555
|
-
if (!
|
|
10713
|
+
if (!existsSync23(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
|
|
10556
10714
|
const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
|
|
10557
10715
|
if (dirty.length) {
|
|
10558
10716
|
return { step: "checkout", target: dir, action: "kept", detail: `has uncommitted changes (${dirty.length} path${dirty.length === 1 ? "" : "s"}: ${dirty.slice(0, 5).join(", ")}${dirty.length > 5 ? ", \u2026" : ""})` };
|
|
@@ -10560,21 +10718,21 @@ function removeCheckout(dir, dryRun, fx) {
|
|
|
10560
10718
|
if (linkedWorktrees.length) {
|
|
10561
10719
|
return { step: "checkout", target: dir, action: "kept", detail: `has linked worktrees (${linkedWorktrees.join(", ")}); remove them first` };
|
|
10562
10720
|
}
|
|
10563
|
-
if (!dryRun)
|
|
10721
|
+
if (!dryRun) rmSync13(dir, { recursive: true, force: true });
|
|
10564
10722
|
return { step: "checkout", target: dir, action: mark(dryRun) };
|
|
10565
10723
|
}
|
|
10566
10724
|
function removeState(stateDir, dryRun) {
|
|
10567
10725
|
const out = [];
|
|
10568
|
-
const targets = [
|
|
10726
|
+
const targets = [join25(stateDir, "resolved"), join25(stateDir, "keys"), clonedCheckoutsFile(stateDir), join25(stateDir, "connect-session.json"), join25(stateDir, "reload-requested"), join25(stateDir, "daemon.started")];
|
|
10569
10727
|
try {
|
|
10570
10728
|
for (const name of readdirSync9(stateDir)) {
|
|
10571
|
-
if (name.startsWith(".") &&
|
|
10729
|
+
if (name.startsWith(".") && statSync8(join25(stateDir, name)).isFile()) targets.push(join25(stateDir, name));
|
|
10572
10730
|
}
|
|
10573
10731
|
} catch {
|
|
10574
10732
|
}
|
|
10575
10733
|
for (const t of targets) {
|
|
10576
|
-
if (!
|
|
10577
|
-
if (!dryRun)
|
|
10734
|
+
if (!existsSync23(t)) continue;
|
|
10735
|
+
if (!dryRun) rmSync13(t, { recursive: true, force: true });
|
|
10578
10736
|
out.push({ step: "state", target: t, action: mark(dryRun) });
|
|
10579
10737
|
}
|
|
10580
10738
|
if (!out.length) out.push({ step: "state", target: stateDir, action: "absent", detail: "nothing to remove" });
|
|
@@ -11361,8 +11519,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
|
|
|
11361
11519
|
}
|
|
11362
11520
|
|
|
11363
11521
|
// src/poll.ts
|
|
11364
|
-
import { writeFileSync as
|
|
11365
|
-
import { join as
|
|
11522
|
+
import { writeFileSync as writeFileSync17 } from "fs";
|
|
11523
|
+
import { join as join26 } from "path";
|
|
11366
11524
|
|
|
11367
11525
|
// src/stalled.ts
|
|
11368
11526
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -11669,7 +11827,7 @@ async function decideCycle(o) {
|
|
|
11669
11827
|
}
|
|
11670
11828
|
return decision;
|
|
11671
11829
|
}
|
|
11672
|
-
var digestPath = (stateDir, route2, role, runId) =>
|
|
11830
|
+
var digestPath = (stateDir, route2, role, runId) => join26(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
11673
11831
|
function otherRunsRemoved(tickets, own) {
|
|
11674
11832
|
if (!own) return tickets;
|
|
11675
11833
|
return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
|
|
@@ -11747,7 +11905,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
|
|
|
11747
11905
|
|
|
11748
11906
|
${body}` : body;
|
|
11749
11907
|
const path = digestPath(stateDir, routeSlug(o.route.route), role, runId);
|
|
11750
|
-
|
|
11908
|
+
writeFileSync17(path, text);
|
|
11751
11909
|
o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text)} bytes)`, {
|
|
11752
11910
|
data: { role, bytes: Buffer.byteLength(text) }
|
|
11753
11911
|
});
|
|
@@ -12230,13 +12388,13 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
|
|
|
12230
12388
|
var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
|
|
12231
12389
|
|
|
12232
12390
|
// src/connect-wizard.ts
|
|
12233
|
-
import { delimiter, dirname as dirname11, join as
|
|
12234
|
-
import { accessSync as
|
|
12391
|
+
import { delimiter, dirname as dirname11, join as join27 } from "path";
|
|
12392
|
+
import { accessSync as accessSync3, constants as constants3, existsSync as existsSync24, mkdirSync as mkdirSync16, writeFileSync as writeFileSync18 } from "fs";
|
|
12235
12393
|
import { stringify } from "yaml";
|
|
12236
12394
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
12237
12395
|
function shouldRunWizard(opts) {
|
|
12238
12396
|
if (!opts.isTTY) return false;
|
|
12239
|
-
const exists = opts.configExists ??
|
|
12397
|
+
const exists = opts.configExists ?? existsSync24;
|
|
12240
12398
|
return !configSearchPath(opts.crewHome).some((p) => exists(p));
|
|
12241
12399
|
}
|
|
12242
12400
|
function firstRunConfigPath() {
|
|
@@ -12244,7 +12402,7 @@ function firstRunConfigPath() {
|
|
|
12244
12402
|
}
|
|
12245
12403
|
function defaultIsExecutable(p) {
|
|
12246
12404
|
try {
|
|
12247
|
-
|
|
12405
|
+
accessSync3(p, constants3.X_OK);
|
|
12248
12406
|
return true;
|
|
12249
12407
|
} catch {
|
|
12250
12408
|
return false;
|
|
@@ -12253,7 +12411,7 @@ function defaultIsExecutable(p) {
|
|
|
12253
12411
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
12254
12412
|
for (const dir of pathEnv.split(delimiter)) {
|
|
12255
12413
|
if (!dir) continue;
|
|
12256
|
-
const candidate =
|
|
12414
|
+
const candidate = join27(dir, name);
|
|
12257
12415
|
if (isExecutable(candidate)) return candidate;
|
|
12258
12416
|
}
|
|
12259
12417
|
return void 0;
|
|
@@ -12314,9 +12472,9 @@ routes:
|
|
|
12314
12472
|
${routeBlock}`;
|
|
12315
12473
|
}
|
|
12316
12474
|
function writeNewConfig(path, text) {
|
|
12317
|
-
|
|
12475
|
+
mkdirSync16(dirname11(path), { recursive: true });
|
|
12318
12476
|
try {
|
|
12319
|
-
|
|
12477
|
+
writeFileSync18(path, text, { mode: 384, flag: "wx" });
|
|
12320
12478
|
return true;
|
|
12321
12479
|
} catch (e) {
|
|
12322
12480
|
if (e.code === "EEXIST") return false;
|
|
@@ -12348,9 +12506,9 @@ Wrote ${o.configPath}.
|
|
|
12348
12506
|
}
|
|
12349
12507
|
|
|
12350
12508
|
// src/connect-json.ts
|
|
12351
|
-
import { accessSync as
|
|
12509
|
+
import { accessSync as accessSync4, constants as constants4 } from "fs";
|
|
12352
12510
|
import { homedir as homedir8 } from "os";
|
|
12353
|
-
import { dirname as dirname12, join as
|
|
12511
|
+
import { dirname as dirname12, join as join28 } from "path";
|
|
12354
12512
|
import { delimiter as delimiter2 } from "path";
|
|
12355
12513
|
var CHOICE_EXIT_CODE = 3;
|
|
12356
12514
|
function createEmitter(json, write) {
|
|
@@ -12389,12 +12547,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
|
|
|
12389
12547
|
const dirs = [
|
|
12390
12548
|
...pathEnv.split(delimiter2),
|
|
12391
12549
|
...(extraPath ?? "").split(delimiter2),
|
|
12392
|
-
|
|
12393
|
-
|
|
12550
|
+
join28(home, ".local/bin"),
|
|
12551
|
+
join28(home, ".claude/local"),
|
|
12394
12552
|
"/opt/homebrew/bin",
|
|
12395
12553
|
"/usr/local/bin",
|
|
12396
|
-
|
|
12397
|
-
|
|
12554
|
+
join28(home, ".npm-global/bin"),
|
|
12555
|
+
join28(home, ".volta/bin")
|
|
12398
12556
|
];
|
|
12399
12557
|
return [...new Set(dirs.filter(Boolean))];
|
|
12400
12558
|
}
|
|
@@ -12405,7 +12563,7 @@ function findAgentBin(name, env, extraPath, isExec) {
|
|
|
12405
12563
|
function flagWizardAnswers(f, env) {
|
|
12406
12564
|
const isExec = env.isExecutable ?? ((p) => {
|
|
12407
12565
|
try {
|
|
12408
|
-
|
|
12566
|
+
accessSync4(p, constants4.X_OK);
|
|
12409
12567
|
return true;
|
|
12410
12568
|
} catch {
|
|
12411
12569
|
return false;
|
|
@@ -12430,8 +12588,8 @@ function flagWizardAnswers(f, env) {
|
|
|
12430
12588
|
}
|
|
12431
12589
|
|
|
12432
12590
|
// src/release-run.ts
|
|
12433
|
-
import { readFileSync as
|
|
12434
|
-
import { dirname as dirname13, join as
|
|
12591
|
+
import { readFileSync as readFileSync23, writeFileSync as writeFileSync19, existsSync as existsSync25, copyFileSync, mkdirSync as mkdirSync17, mkdtempSync as mkdtempSync2, rmSync as rmSync14 } from "fs";
|
|
12592
|
+
import { dirname as dirname13, join as join29 } from "path";
|
|
12435
12593
|
import { tmpdir as tmpdir3 } from "os";
|
|
12436
12594
|
|
|
12437
12595
|
// src/release.ts
|
|
@@ -13172,13 +13330,13 @@ async function bump(o, size) {
|
|
|
13172
13330
|
}
|
|
13173
13331
|
return printed;
|
|
13174
13332
|
}
|
|
13175
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
13333
|
+
const files = o.repo.release.versionFiles.map((f) => join29(o.cwd, f));
|
|
13176
13334
|
const first = files[0];
|
|
13177
|
-
if (!
|
|
13335
|
+
if (!existsSync25(first)) {
|
|
13178
13336
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
13179
13337
|
return null;
|
|
13180
13338
|
}
|
|
13181
|
-
const current = JSON.parse(
|
|
13339
|
+
const current = JSON.parse(readFileSync23(first, "utf8")).version;
|
|
13182
13340
|
if (!current) {
|
|
13183
13341
|
o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
|
|
13184
13342
|
return null;
|
|
@@ -13191,27 +13349,27 @@ async function bump(o, size) {
|
|
|
13191
13349
|
return version;
|
|
13192
13350
|
}
|
|
13193
13351
|
for (const f of files) {
|
|
13194
|
-
const src =
|
|
13352
|
+
const src = readFileSync23(f, "utf8");
|
|
13195
13353
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
13196
13354
|
if (out === src) {
|
|
13197
13355
|
o.emit.error(`no version field in ${f}`);
|
|
13198
13356
|
return null;
|
|
13199
13357
|
}
|
|
13200
|
-
|
|
13358
|
+
writeFileSync19(f, out);
|
|
13201
13359
|
}
|
|
13202
13360
|
return version;
|
|
13203
13361
|
}
|
|
13204
13362
|
function writeChangelog(o, version, entries) {
|
|
13205
13363
|
const file = o.repo.release.changelog;
|
|
13206
13364
|
if (!file || entries.length === 0) return;
|
|
13207
|
-
const path =
|
|
13365
|
+
const path = join29(o.cwd, file);
|
|
13208
13366
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
13209
13367
|
if (o.dryRun) {
|
|
13210
13368
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
13211
13369
|
return;
|
|
13212
13370
|
}
|
|
13213
|
-
const existing =
|
|
13214
|
-
|
|
13371
|
+
const existing = existsSync25(path) ? readFileSync23(path, "utf8") : "# Changelog\n";
|
|
13372
|
+
writeFileSync19(path, insertChangelogSection(existing, section));
|
|
13215
13373
|
}
|
|
13216
13374
|
async function confirm(o, expected) {
|
|
13217
13375
|
if (!o.repo.hooks.released) return void 0;
|
|
@@ -13294,7 +13452,7 @@ function closeReleaseCheckout(cwd, root, dir, branch, run2 = gitOk) {
|
|
|
13294
13452
|
run2(cwd, ["worktree", "remove", "--force", dir]);
|
|
13295
13453
|
} finally {
|
|
13296
13454
|
try {
|
|
13297
|
-
|
|
13455
|
+
rmSync14(root, { recursive: true, force: true });
|
|
13298
13456
|
} finally {
|
|
13299
13457
|
run2(cwd, ["worktree", "prune"]);
|
|
13300
13458
|
run2(cwd, ["branch", "-D", branch]);
|
|
@@ -13304,8 +13462,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run2 = gitOk) {
|
|
|
13304
13462
|
function openReleaseCheckout(o) {
|
|
13305
13463
|
const { remote, base } = o.repo.branch;
|
|
13306
13464
|
const branch = `crew/release-${Date.now()}`;
|
|
13307
|
-
const root = mkdtempSync2(
|
|
13308
|
-
const dir =
|
|
13465
|
+
const root = mkdtempSync2(join29(tmpdir3(), "crew-release-"));
|
|
13466
|
+
const dir = join29(root, "checkout");
|
|
13309
13467
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
13310
13468
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
13311
13469
|
try {
|
|
@@ -13315,10 +13473,10 @@ function openReleaseCheckout(o) {
|
|
|
13315
13473
|
throw e;
|
|
13316
13474
|
}
|
|
13317
13475
|
for (const f of o.repo.worktrees.copy) {
|
|
13318
|
-
const src =
|
|
13319
|
-
if (!
|
|
13320
|
-
|
|
13321
|
-
copyFileSync(src,
|
|
13476
|
+
const src = join29(o.cwd, f);
|
|
13477
|
+
if (!existsSync25(src)) continue;
|
|
13478
|
+
mkdirSync17(dirname13(join29(dir, f)), { recursive: true });
|
|
13479
|
+
copyFileSync(src, join29(dir, f));
|
|
13322
13480
|
}
|
|
13323
13481
|
return { dir, branch, close };
|
|
13324
13482
|
}
|
|
@@ -14019,12 +14177,12 @@ Passed: ${passed}.${willClose ? " Closing this alert." : ""}`,
|
|
|
14019
14177
|
}
|
|
14020
14178
|
|
|
14021
14179
|
// src/live-runs.ts
|
|
14022
|
-
import { existsSync as
|
|
14023
|
-
import { join as
|
|
14180
|
+
import { existsSync as existsSync27, readdirSync as readdirSync11 } from "fs";
|
|
14181
|
+
import { join as join31 } from "path";
|
|
14024
14182
|
|
|
14025
14183
|
// src/interrupted.ts
|
|
14026
|
-
import { existsSync as
|
|
14027
|
-
import { join as
|
|
14184
|
+
import { existsSync as existsSync26, readdirSync as readdirSync10, readFileSync as readFileSync24 } from "fs";
|
|
14185
|
+
import { join as join30 } from "path";
|
|
14028
14186
|
var INTERRUPTED_MARKER = calloutHeader("NOTE", "Interrupted");
|
|
14029
14187
|
function interruptedComment(b) {
|
|
14030
14188
|
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.";
|
|
@@ -14043,21 +14201,21 @@ var pidAlive = (pid) => {
|
|
|
14043
14201
|
};
|
|
14044
14202
|
function runFinished(streamPath) {
|
|
14045
14203
|
const path = `${streamPath}.meta.json`;
|
|
14046
|
-
if (!
|
|
14204
|
+
if (!existsSync26(path)) return false;
|
|
14047
14205
|
try {
|
|
14048
|
-
return JSON.parse(
|
|
14206
|
+
return JSON.parse(readFileSync24(path, "utf8")).running !== true;
|
|
14049
14207
|
} catch {
|
|
14050
14208
|
return true;
|
|
14051
14209
|
}
|
|
14052
14210
|
}
|
|
14053
14211
|
function orphanedStreams(o) {
|
|
14054
|
-
const dir =
|
|
14055
|
-
if (!
|
|
14212
|
+
const dir = join30(o.stateDir, "streams");
|
|
14213
|
+
if (!existsSync26(dir)) return [];
|
|
14056
14214
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
14057
14215
|
const out = [];
|
|
14058
14216
|
for (const name of readdirSync10(dir)) {
|
|
14059
14217
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
14060
|
-
const streamPath =
|
|
14218
|
+
const streamPath = join30(dir, name);
|
|
14061
14219
|
if (runFinished(streamPath)) continue;
|
|
14062
14220
|
const sidecar = readRunSidecar(streamPath);
|
|
14063
14221
|
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
@@ -14111,12 +14269,12 @@ async function reconcileRuns(o) {
|
|
|
14111
14269
|
|
|
14112
14270
|
// src/live-runs.ts
|
|
14113
14271
|
function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
14114
|
-
const dir =
|
|
14115
|
-
if (!
|
|
14272
|
+
const dir = join31(stateDir, "streams");
|
|
14273
|
+
if (!existsSync27(dir)) return [];
|
|
14116
14274
|
const out = [];
|
|
14117
14275
|
for (const name of readdirSync11(dir)) {
|
|
14118
14276
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
14119
|
-
const streamPath =
|
|
14277
|
+
const streamPath = join31(dir, name);
|
|
14120
14278
|
if (runFinished(streamPath)) continue;
|
|
14121
14279
|
const sidecar = readRunSidecar(streamPath);
|
|
14122
14280
|
if (!sidecar || !isAlive(sidecar)) continue;
|
|
@@ -14450,7 +14608,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
|
|
|
14450
14608
|
}
|
|
14451
14609
|
|
|
14452
14610
|
// src/watch.ts
|
|
14453
|
-
import { watch as watchFile, existsSync as
|
|
14611
|
+
import { watch as watchFile, existsSync as existsSync28, statSync as statSync9, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
|
|
14454
14612
|
var E = "\x1B[";
|
|
14455
14613
|
var dim = (s) => `${E}2m${s}${E}0m`;
|
|
14456
14614
|
var bold = (s) => `${E}1m${s}${E}0m`;
|
|
@@ -14551,8 +14709,8 @@ function renderHeaders(states, now = Date.now()) {
|
|
|
14551
14709
|
return states.map((s) => renderHeader(s, now)).join("");
|
|
14552
14710
|
}
|
|
14553
14711
|
function readFrom(file, from) {
|
|
14554
|
-
if (!
|
|
14555
|
-
const size =
|
|
14712
|
+
if (!existsSync28(file)) return { events: [], offset: from };
|
|
14713
|
+
const size = statSync9(file).size;
|
|
14556
14714
|
if (size < from) return readFrom(file, 0);
|
|
14557
14715
|
if (size === from) return { events: [], offset: from };
|
|
14558
14716
|
const fd = openSync4(file, "r");
|
|
@@ -14794,19 +14952,19 @@ function nextRoles(decision, ran) {
|
|
|
14794
14952
|
}
|
|
14795
14953
|
|
|
14796
14954
|
// src/ship-name.ts
|
|
14797
|
-
import { existsSync as
|
|
14955
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync19, readFileSync as readFileSync26, renameSync as renameSync3, writeFileSync as writeFileSync21 } from "fs";
|
|
14798
14956
|
import { dirname as dirname15 } from "path";
|
|
14799
14957
|
import { parseDocument as parseDocument2 } from "yaml";
|
|
14800
14958
|
|
|
14801
14959
|
// src/machine-id.ts
|
|
14802
14960
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
14803
|
-
import { createHash as
|
|
14804
|
-
import { existsSync as
|
|
14805
|
-
import { dirname as dirname14, join as
|
|
14961
|
+
import { createHash as createHash5 } from "crypto";
|
|
14962
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync18, readFileSync as readFileSync25, writeFileSync as writeFileSync20 } from "fs";
|
|
14963
|
+
import { dirname as dirname14, join as join32 } from "path";
|
|
14806
14964
|
function machineIdPathFor(stateDir) {
|
|
14807
|
-
return
|
|
14965
|
+
return join32(stateDir, "machine-id");
|
|
14808
14966
|
}
|
|
14809
|
-
var sha256 = (s) =>
|
|
14967
|
+
var sha256 = (s) => createHash5("sha256").update(s).digest("hex");
|
|
14810
14968
|
function readRawMachineId(platform2 = process.platform) {
|
|
14811
14969
|
try {
|
|
14812
14970
|
if (platform2 === "darwin") {
|
|
@@ -14817,8 +14975,8 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
14817
14975
|
return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
|
|
14818
14976
|
}
|
|
14819
14977
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
14820
|
-
if (
|
|
14821
|
-
const id =
|
|
14978
|
+
if (existsSync29(p)) {
|
|
14979
|
+
const id = readFileSync25(p, "utf8").trim();
|
|
14822
14980
|
if (id) return id;
|
|
14823
14981
|
}
|
|
14824
14982
|
}
|
|
@@ -14829,15 +14987,15 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
14829
14987
|
function ensureMachineId(stateDir, opts = {}) {
|
|
14830
14988
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
14831
14989
|
const path = machineIdPathFor(stateDir);
|
|
14832
|
-
if (
|
|
14833
|
-
const cached =
|
|
14990
|
+
if (existsSync29(path)) {
|
|
14991
|
+
const cached = readFileSync25(path, "utf8").trim();
|
|
14834
14992
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
14835
14993
|
}
|
|
14836
14994
|
const raw = (opts.read ?? readRawMachineId)();
|
|
14837
14995
|
if (!raw) return void 0;
|
|
14838
14996
|
const hashed = sha256(raw);
|
|
14839
|
-
|
|
14840
|
-
|
|
14997
|
+
mkdirSync18(dirname14(path), { recursive: true });
|
|
14998
|
+
writeFileSync20(path, `${hashed}
|
|
14841
14999
|
`, { mode: 384 });
|
|
14842
15000
|
return hashed;
|
|
14843
15001
|
}
|
|
@@ -14908,34 +15066,34 @@ async function syncRouteName(i) {
|
|
|
14908
15066
|
return { status: "ok", ...base, action, name: i.local };
|
|
14909
15067
|
}
|
|
14910
15068
|
function readSyncedName(resolvedFile) {
|
|
14911
|
-
if (!
|
|
15069
|
+
if (!existsSync30(resolvedFile)) return void 0;
|
|
14912
15070
|
try {
|
|
14913
|
-
const v = JSON.parse(
|
|
15071
|
+
const v = JSON.parse(readFileSync26(resolvedFile, "utf8")).shipName;
|
|
14914
15072
|
return typeof v === "string" ? v : void 0;
|
|
14915
15073
|
} catch {
|
|
14916
15074
|
return void 0;
|
|
14917
15075
|
}
|
|
14918
15076
|
}
|
|
14919
15077
|
function writeSyncedName(resolvedFile, name) {
|
|
14920
|
-
if (!
|
|
15078
|
+
if (!existsSync30(resolvedFile)) return;
|
|
14921
15079
|
let raw;
|
|
14922
15080
|
try {
|
|
14923
|
-
raw = JSON.parse(
|
|
15081
|
+
raw = JSON.parse(readFileSync26(resolvedFile, "utf8"));
|
|
14924
15082
|
} catch {
|
|
14925
15083
|
return;
|
|
14926
15084
|
}
|
|
14927
15085
|
if (raw.shipName === name) return;
|
|
14928
15086
|
raw.shipName = name;
|
|
14929
|
-
|
|
15087
|
+
mkdirSync19(dirname15(resolvedFile), { recursive: true });
|
|
14930
15088
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
14931
|
-
|
|
15089
|
+
writeFileSync21(tmp, `${JSON.stringify(raw, null, 2)}
|
|
14932
15090
|
`);
|
|
14933
|
-
|
|
15091
|
+
renameSync3(tmp, resolvedFile);
|
|
14934
15092
|
}
|
|
14935
15093
|
async function reconcileShipName(i) {
|
|
14936
15094
|
let local = i.ship.name;
|
|
14937
15095
|
try {
|
|
14938
|
-
local = shipNameOfYaml(
|
|
15096
|
+
local = shipNameOfYaml(readFileSync26(i.configFile, "utf8")) ?? local;
|
|
14939
15097
|
} catch {
|
|
14940
15098
|
}
|
|
14941
15099
|
const resolvedFile = resolvedPathFor(i.stateDir, i.route);
|
|
@@ -14959,8 +15117,8 @@ async function reconcileShipName(i) {
|
|
|
14959
15117
|
}
|
|
14960
15118
|
if (result.status !== "ok") return result;
|
|
14961
15119
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
14962
|
-
const text =
|
|
14963
|
-
|
|
15120
|
+
const text = readFileSync26(i.configFile, "utf8");
|
|
15121
|
+
writeFileSync21(i.configFile, setShipNameInYaml(text, result.name));
|
|
14964
15122
|
i.ship.name = result.name;
|
|
14965
15123
|
if (result.action === "row-wins") {
|
|
14966
15124
|
i.emit?.warn(
|
|
@@ -15558,7 +15716,6 @@ ${opts.area ? ` area: "${opts.area}"
|
|
|
15558
15716
|
|
|
15559
15717
|
// src/cli.ts
|
|
15560
15718
|
import {
|
|
15561
|
-
getSessionStore as getSessionStore3,
|
|
15562
15719
|
hostFromUrl as hostFromUrl4,
|
|
15563
15720
|
loginWithDeviceCode,
|
|
15564
15721
|
DeviceLoginError
|
|
@@ -15610,7 +15767,7 @@ function describeCapability(i) {
|
|
|
15610
15767
|
}
|
|
15611
15768
|
|
|
15612
15769
|
// src/agents-sync-run.ts
|
|
15613
|
-
import { existsSync as
|
|
15770
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync20, readFileSync as readFileSync27, writeFileSync as writeFileSync22 } from "fs";
|
|
15614
15771
|
import { dirname as dirname16 } from "path";
|
|
15615
15772
|
async function runAgentsSync(target, o) {
|
|
15616
15773
|
const dryRun = o.dryRun ?? false;
|
|
@@ -15644,12 +15801,12 @@ async function runAgentsSync(target, o) {
|
|
|
15644
15801
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
15645
15802
|
if (!dryRun) {
|
|
15646
15803
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
15647
|
-
const raw =
|
|
15804
|
+
const raw = existsSync31(resolvedPath) ? JSON.parse(readFileSync27(resolvedPath, "utf8")) : {};
|
|
15648
15805
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
15649
15806
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
15650
15807
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
15651
|
-
|
|
15652
|
-
|
|
15808
|
+
mkdirSync20(dirname16(resolvedPath), { recursive: true });
|
|
15809
|
+
writeFileSync22(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
15653
15810
|
`);
|
|
15654
15811
|
if (target.resolved) {
|
|
15655
15812
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -15664,7 +15821,7 @@ async function runAgentsSync(target, o) {
|
|
|
15664
15821
|
}
|
|
15665
15822
|
|
|
15666
15823
|
// src/logbook.ts
|
|
15667
|
-
import { createHash as
|
|
15824
|
+
import { createHash as createHash6 } from "crypto";
|
|
15668
15825
|
import { TablationClient as TablationClient4 } from "@tablation/client";
|
|
15669
15826
|
var LogbookError = class extends Error {
|
|
15670
15827
|
};
|
|
@@ -15737,7 +15894,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
15737
15894
|
};
|
|
15738
15895
|
}
|
|
15739
15896
|
const prompt = appendAdditions(match.prompt ?? "", match.prompt_additions);
|
|
15740
|
-
const actualSha =
|
|
15897
|
+
const actualSha = createHash6("sha256").update(prompt).digest("hex");
|
|
15741
15898
|
const exact = !!entry.prompt_sha && actualSha === entry.prompt_sha;
|
|
15742
15899
|
return {
|
|
15743
15900
|
entry,
|
|
@@ -15753,7 +15910,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
15753
15910
|
|
|
15754
15911
|
// src/repos-check.ts
|
|
15755
15912
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
15756
|
-
import { readFileSync as
|
|
15913
|
+
import { readFileSync as readFileSync28 } from "fs";
|
|
15757
15914
|
import { basename as basename8 } from "path";
|
|
15758
15915
|
var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
|
|
15759
15916
|
function checkHookSyntax(script, shell) {
|
|
@@ -15794,7 +15951,7 @@ function checkRepoSpec(dir, opts = {}) {
|
|
|
15794
15951
|
}
|
|
15795
15952
|
let parsed;
|
|
15796
15953
|
try {
|
|
15797
|
-
parsed = parseRepoConfig(
|
|
15954
|
+
parsed = parseRepoConfig(readFileSync28(file, "utf8"), file);
|
|
15798
15955
|
} catch (e) {
|
|
15799
15956
|
if (!(e instanceof RepoConfigError)) throw e;
|
|
15800
15957
|
return { ...empty, file, error: e.message, exitCode: 1 };
|
|
@@ -15851,7 +16008,7 @@ import { isMap as isMap2, parseDocument as parseDocument4, YAMLMap } from "yaml"
|
|
|
15851
16008
|
|
|
15852
16009
|
// src/repos-cmd.ts
|
|
15853
16010
|
import { homedir as homedir9 } from "os";
|
|
15854
|
-
import { basename as basename9, join as
|
|
16011
|
+
import { basename as basename9, join as join33, resolve as resolve3 } from "path";
|
|
15855
16012
|
import { isMap, isSeq, parseDocument as parseDocument3, stringify as stringify2 } from "yaml";
|
|
15856
16013
|
var ReposError = class extends Error {
|
|
15857
16014
|
};
|
|
@@ -16129,7 +16286,7 @@ function planRepoClone(i) {
|
|
|
16129
16286
|
}
|
|
16130
16287
|
const ship = doc.get("ship", true);
|
|
16131
16288
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
16132
|
-
const target = i.dir ? expandDir(i.dir, i.base) :
|
|
16289
|
+
const target = i.dir ? expandDir(i.dir, i.base) : join33(expandDir(basePath, i.base), i.route.split("/")[0], name);
|
|
16133
16290
|
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
|
|
16134
16291
|
return { ...add, remote, target };
|
|
16135
16292
|
}
|
|
@@ -16528,7 +16685,7 @@ ${run2.err}` });
|
|
|
16528
16685
|
}
|
|
16529
16686
|
|
|
16530
16687
|
// src/sample-ticket.ts
|
|
16531
|
-
import { basename as basename10, dirname as dirname18, join as
|
|
16688
|
+
import { basename as basename10, dirname as dirname18, join as join34 } from "path";
|
|
16532
16689
|
var SAMPLE_REPO = "crew-sample";
|
|
16533
16690
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
16534
16691
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -16551,8 +16708,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
16551
16708
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
16552
16709
|
`;
|
|
16553
16710
|
function sampleDir(existing, reposBase, workspace) {
|
|
16554
|
-
if (existing.length > 0) return
|
|
16555
|
-
return
|
|
16711
|
+
if (existing.length > 0) return join34(dirname18(existing[0]), SAMPLE_REPO);
|
|
16712
|
+
return join34(reposBase, workspace, SAMPLE_REPO);
|
|
16556
16713
|
}
|
|
16557
16714
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
16558
16715
|
function sampleDescription() {
|
|
@@ -16644,8 +16801,8 @@ function schedulerInstalled(plan, exists, crontab) {
|
|
|
16644
16801
|
}
|
|
16645
16802
|
|
|
16646
16803
|
// src/worktree-sweep.ts
|
|
16647
|
-
import { existsSync as
|
|
16648
|
-
import { basename as basename11, dirname as dirname19, join as
|
|
16804
|
+
import { existsSync as existsSync32, readFileSync as readFileSync29, readdirSync as readdirSync12 } from "fs";
|
|
16805
|
+
import { basename as basename11, dirname as dirname19, join as join35, resolve as resolve4 } from "path";
|
|
16649
16806
|
function planWorktreeSweep(target, tickets, contract) {
|
|
16650
16807
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
16651
16808
|
const verified = contract.statuses.verified;
|
|
@@ -16722,7 +16879,7 @@ function siblingWorktreeDirs(target) {
|
|
|
16722
16879
|
for (const name of names) {
|
|
16723
16880
|
if (!name.startsWith(stem)) continue;
|
|
16724
16881
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
16725
|
-
const path =
|
|
16882
|
+
const path = join35(parent, name);
|
|
16726
16883
|
if (m && !registered.has(resolve4(path))) out.push({ path, number: m[1] });
|
|
16727
16884
|
}
|
|
16728
16885
|
return out;
|
|
@@ -16757,7 +16914,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
16757
16914
|
let removed = 0;
|
|
16758
16915
|
let keptBranches = 0;
|
|
16759
16916
|
for (const a of actions) {
|
|
16760
|
-
if (!dryRun && base && !a.keepBranch &&
|
|
16917
|
+
if (!dryRun && base && !a.keepBranch && existsSync32(a.path) && isHandBackMerge(a.path, base)) {
|
|
16761
16918
|
if (gitOk(a.path, ["merge", "--abort"]) !== null) {
|
|
16762
16919
|
log.emit(
|
|
16763
16920
|
`abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
|
|
@@ -16765,7 +16922,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
16765
16922
|
);
|
|
16766
16923
|
}
|
|
16767
16924
|
}
|
|
16768
|
-
if (!dryRun &&
|
|
16925
|
+
if (!dryRun && existsSync32(a.path)) {
|
|
16769
16926
|
let dirty = false;
|
|
16770
16927
|
try {
|
|
16771
16928
|
dirty = status(a.path).length > 0;
|
|
@@ -16776,7 +16933,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
16776
16933
|
continue;
|
|
16777
16934
|
}
|
|
16778
16935
|
}
|
|
16779
|
-
if (!dryRun && a.requirePushed && a.branch &&
|
|
16936
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync32(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
16780
16937
|
refuse(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
|
|
16781
16938
|
continue;
|
|
16782
16939
|
}
|
|
@@ -16825,7 +16982,7 @@ function isHandBackMerge(path, base) {
|
|
|
16825
16982
|
if (!msgPath) return false;
|
|
16826
16983
|
let first;
|
|
16827
16984
|
try {
|
|
16828
|
-
first =
|
|
16985
|
+
first = readFileSync29(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
|
|
16829
16986
|
} catch {
|
|
16830
16987
|
return false;
|
|
16831
16988
|
}
|
|
@@ -16930,16 +17087,16 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
16930
17087
|
}
|
|
16931
17088
|
|
|
16932
17089
|
// src/stream-sweep.ts
|
|
16933
|
-
import { existsSync as
|
|
16934
|
-
import { join as
|
|
17090
|
+
import { existsSync as existsSync33, readdirSync as readdirSync13, rmSync as rmSync15, statSync as statSync10 } from "fs";
|
|
17091
|
+
import { join as join36 } from "path";
|
|
16935
17092
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
16936
17093
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
16937
|
-
const dir =
|
|
16938
|
-
if (!
|
|
17094
|
+
const dir = join36(stateDir, "streams");
|
|
17095
|
+
if (!existsSync33(dir)) return [];
|
|
16939
17096
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
16940
|
-
return readdirSync13(dir).map((name) =>
|
|
17097
|
+
return readdirSync13(dir).map((name) => join36(dir, name)).filter((path) => {
|
|
16941
17098
|
try {
|
|
16942
|
-
return
|
|
17099
|
+
return statSync10(path).mtimeMs < cutoff;
|
|
16943
17100
|
} catch {
|
|
16944
17101
|
return false;
|
|
16945
17102
|
}
|
|
@@ -16953,7 +17110,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
16953
17110
|
continue;
|
|
16954
17111
|
}
|
|
16955
17112
|
try {
|
|
16956
|
-
|
|
17113
|
+
rmSync15(path, { force: true });
|
|
16957
17114
|
removed++;
|
|
16958
17115
|
log.emit(`removed stream artifact ${path}`, { step: "worktree" });
|
|
16959
17116
|
} catch (e) {
|
|
@@ -16963,11 +17120,11 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
16963
17120
|
return removed;
|
|
16964
17121
|
}
|
|
16965
17122
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
16966
|
-
if (!
|
|
17123
|
+
if (!existsSync33(stateDir)) return [];
|
|
16967
17124
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
16968
|
-
return readdirSync13(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) =>
|
|
17125
|
+
return readdirSync13(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join36(stateDir, name)).filter((path) => {
|
|
16969
17126
|
try {
|
|
16970
|
-
return
|
|
17127
|
+
return statSync10(path).mtimeMs < cutoff;
|
|
16971
17128
|
} catch {
|
|
16972
17129
|
return false;
|
|
16973
17130
|
}
|
|
@@ -16975,30 +17132,30 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
16975
17132
|
}
|
|
16976
17133
|
|
|
16977
17134
|
// src/cli.ts
|
|
16978
|
-
import { realpathSync as realpathSync3, readFileSync as
|
|
17135
|
+
import { realpathSync as realpathSync3, readFileSync as readFileSync35, existsSync as existsSync40, mkdirSync as mkdirSync26, writeFileSync as writeFileSync27, rmSync as rmSync19, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync4, statSync as statSync14, readdirSync as readdirSync18 } from "fs";
|
|
16979
17136
|
import { createInterface } from "readline/promises";
|
|
16980
17137
|
|
|
16981
17138
|
// src/periscope-cmd.ts
|
|
16982
|
-
import { existsSync as
|
|
16983
|
-
import { join as
|
|
17139
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync21, readFileSync as readFileSync30, rmSync as rmSync16, writeFileSync as writeFileSync23 } from "fs";
|
|
17140
|
+
import { join as join37 } from "path";
|
|
16984
17141
|
var PeriscopeError = class extends Error {
|
|
16985
17142
|
};
|
|
16986
|
-
var priorDir = (stateDir) =>
|
|
16987
|
-
var priorPath = (stateDir, ticketRecordId) =>
|
|
17143
|
+
var priorDir = (stateDir) => join37(stateDir, "periscope-priors");
|
|
17144
|
+
var priorPath = (stateDir, ticketRecordId) => join37(priorDir(stateDir), `${ticketRecordId}.json`);
|
|
16988
17145
|
function removePeriscopePrior(stateDir, ticketRecordId) {
|
|
16989
|
-
|
|
17146
|
+
rmSync16(priorPath(stateDir, ticketRecordId), { force: true });
|
|
16990
17147
|
}
|
|
16991
17148
|
function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
16992
17149
|
try {
|
|
16993
|
-
|
|
16994
|
-
|
|
17150
|
+
mkdirSync21(priorDir(stateDir), { recursive: true });
|
|
17151
|
+
writeFileSync23(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
|
|
16995
17152
|
} catch {
|
|
16996
17153
|
}
|
|
16997
17154
|
}
|
|
16998
17155
|
function readPeriscopePrior(stateDir, ticketRecordId) {
|
|
16999
17156
|
try {
|
|
17000
17157
|
const p = priorPath(stateDir, ticketRecordId);
|
|
17001
|
-
return
|
|
17158
|
+
return existsSync34(p) ? JSON.parse(readFileSync30(p, "utf8")) : void 0;
|
|
17002
17159
|
} catch {
|
|
17003
17160
|
return void 0;
|
|
17004
17161
|
}
|
|
@@ -17390,8 +17547,8 @@ ${tail2(preview.output)}`);
|
|
|
17390
17547
|
|
|
17391
17548
|
// src/periscope-idle.ts
|
|
17392
17549
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
17393
|
-
import { statSync as
|
|
17394
|
-
import { join as
|
|
17550
|
+
import { statSync as statSync11 } from "fs";
|
|
17551
|
+
import { join as join38 } from "path";
|
|
17395
17552
|
var MAX_SCANNED_FILES = 2e4;
|
|
17396
17553
|
function newestWorktreeMtime(root) {
|
|
17397
17554
|
let listing;
|
|
@@ -17411,7 +17568,7 @@ function newestWorktreeMtime(root) {
|
|
|
17411
17568
|
if (!rel) continue;
|
|
17412
17569
|
if (++scanned > MAX_SCANNED_FILES) break;
|
|
17413
17570
|
try {
|
|
17414
|
-
const m =
|
|
17571
|
+
const m = statSync11(join38(root, rel)).mtimeMs;
|
|
17415
17572
|
if (newest === null || m > newest) newest = m;
|
|
17416
17573
|
} catch {
|
|
17417
17574
|
}
|
|
@@ -17487,18 +17644,18 @@ import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
|
17487
17644
|
|
|
17488
17645
|
// src/ssh-keys.ts
|
|
17489
17646
|
import { execFileSync as execFileSync8 } from "child_process";
|
|
17490
|
-
import { existsSync as
|
|
17491
|
-
import { dirname as dirname20, join as
|
|
17647
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync22, readFileSync as readFileSync31, chmodSync as chmodSync3 } from "fs";
|
|
17648
|
+
import { dirname as dirname20, join as join39 } from "path";
|
|
17492
17649
|
var SshKeyError = class extends Error {
|
|
17493
17650
|
};
|
|
17494
17651
|
function shipSshKeyPathFor(stateDir) {
|
|
17495
|
-
return
|
|
17652
|
+
return join39(stateDir, "ssh", "id_ed25519");
|
|
17496
17653
|
}
|
|
17497
17654
|
function ensureShipSshKeypair(stateDir) {
|
|
17498
17655
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
17499
17656
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
17500
|
-
if (!
|
|
17501
|
-
|
|
17657
|
+
if (!existsSync35(privateKeyPath)) {
|
|
17658
|
+
mkdirSync22(dirname20(privateKeyPath), { recursive: true });
|
|
17502
17659
|
try {
|
|
17503
17660
|
execFileSync8("ssh-keygen", [
|
|
17504
17661
|
"-t",
|
|
@@ -17516,12 +17673,12 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
17516
17673
|
const err2 = e;
|
|
17517
17674
|
throw new SshKeyError(`ssh-keygen failed: ${err2.stderr?.toString().trim() || err2.message}`);
|
|
17518
17675
|
}
|
|
17519
|
-
|
|
17676
|
+
chmodSync3(privateKeyPath, 384);
|
|
17520
17677
|
}
|
|
17521
|
-
if (!
|
|
17678
|
+
if (!existsSync35(publicKeyPath)) {
|
|
17522
17679
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
17523
17680
|
}
|
|
17524
|
-
const publicKey =
|
|
17681
|
+
const publicKey = readFileSync31(publicKeyPath, "utf8").trim();
|
|
17525
17682
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
17526
17683
|
}
|
|
17527
17684
|
function sshKeygenAvailable() {
|
|
@@ -17535,7 +17692,7 @@ function sshKeygenAvailable() {
|
|
|
17535
17692
|
|
|
17536
17693
|
// src/passenger-containers.ts
|
|
17537
17694
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
17538
|
-
import { createHash as
|
|
17695
|
+
import { createHash as createHash7 } from "crypto";
|
|
17539
17696
|
var realExec = (cmd, args, opts) => {
|
|
17540
17697
|
try {
|
|
17541
17698
|
return execFileSync9(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
|
|
@@ -17573,12 +17730,12 @@ function containerNameFor(workspaceId) {
|
|
|
17573
17730
|
return `crew-passenger-${workspaceId}`;
|
|
17574
17731
|
}
|
|
17575
17732
|
function portFor(workspaceId, base = 28765, span = 1e3) {
|
|
17576
|
-
const digest =
|
|
17733
|
+
const digest = createHash7("sha256").update(workspaceId).digest();
|
|
17577
17734
|
return base + digest.readUInt32BE(0) % span;
|
|
17578
17735
|
}
|
|
17579
17736
|
function mountsDigest(mounts, image) {
|
|
17580
17737
|
const sorted2 = [...mounts].sort((a, b) => a.containerPath.localeCompare(b.containerPath));
|
|
17581
|
-
return
|
|
17738
|
+
return createHash7("sha256").update(JSON.stringify({ mounts: sorted2, image })).digest("hex").slice(0, 16);
|
|
17582
17739
|
}
|
|
17583
17740
|
function planContainers(cfg) {
|
|
17584
17741
|
const byWorkspace = /* @__PURE__ */ new Map();
|
|
@@ -17738,9 +17895,9 @@ function syncAllPassengerCheckouts(cfg) {
|
|
|
17738
17895
|
|
|
17739
17896
|
// src/tunnel.ts
|
|
17740
17897
|
import { spawn as spawn5 } from "child_process";
|
|
17741
|
-
import { existsSync as
|
|
17898
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync23, readFileSync as readFileSync32, writeFileSync as writeFileSync24, unlinkSync as unlinkSync3, readdirSync as readdirSync14 } from "fs";
|
|
17742
17899
|
import { randomInt } from "crypto";
|
|
17743
|
-
import { join as
|
|
17900
|
+
import { join as join40 } from "path";
|
|
17744
17901
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
17745
17902
|
var defaultSpawn = (cmd, args) => spawn5(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
17746
17903
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -17807,21 +17964,21 @@ function startTunnel(opts) {
|
|
|
17807
17964
|
};
|
|
17808
17965
|
}
|
|
17809
17966
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
17810
|
-
return
|
|
17967
|
+
return join40(stateDir, "passengers", `${workspaceId}.json`);
|
|
17811
17968
|
}
|
|
17812
17969
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
17813
17970
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
17814
|
-
if (!
|
|
17971
|
+
if (!existsSync36(path)) return void 0;
|
|
17815
17972
|
try {
|
|
17816
|
-
return JSON.parse(
|
|
17973
|
+
return JSON.parse(readFileSync32(path, "utf8"));
|
|
17817
17974
|
} catch {
|
|
17818
17975
|
return void 0;
|
|
17819
17976
|
}
|
|
17820
17977
|
}
|
|
17821
17978
|
function writePersistedTunnel(stateDir, state) {
|
|
17822
17979
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
17823
|
-
|
|
17824
|
-
|
|
17980
|
+
mkdirSync23(join40(stateDir, "passengers"), { recursive: true });
|
|
17981
|
+
writeFileSync24(path, `${JSON.stringify(state, null, 2)}
|
|
17825
17982
|
`);
|
|
17826
17983
|
}
|
|
17827
17984
|
function updatePersistedTunnel(stateDir, workspaceId, patch2) {
|
|
@@ -17918,13 +18075,13 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
17918
18075
|
}
|
|
17919
18076
|
}
|
|
17920
18077
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
17921
|
-
const dir =
|
|
17922
|
-
if (!
|
|
18078
|
+
const dir = join40(stateDir, "passengers");
|
|
18079
|
+
if (!existsSync36(dir)) return [];
|
|
17923
18080
|
return readdirSync14(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
17924
18081
|
}
|
|
17925
18082
|
|
|
17926
18083
|
// src/mcp-endpoint-registry.ts
|
|
17927
|
-
import { createHash as
|
|
18084
|
+
import { createHash as createHash8 } from "crypto";
|
|
17928
18085
|
function endpointsUrl(api, workspaceId, id) {
|
|
17929
18086
|
const base = `${api.baseUrl.replace(/\/+$/, "")}/api/workspaces/${workspaceId}/mcp-endpoints`;
|
|
17930
18087
|
return id ? `${base}/${id}` : base;
|
|
@@ -17992,7 +18149,7 @@ async function probeEndpointAccess(api, workspaceId) {
|
|
|
17992
18149
|
}
|
|
17993
18150
|
var REGISTER_MCP_ENDPOINTS_HINT = "give this key's role the REGISTER_MCP_ENDPOINTS capability, e.g. via the group that holds the Crew project role";
|
|
17994
18151
|
function registrationKey(url, mcpSecret) {
|
|
17995
|
-
return
|
|
18152
|
+
return createHash8("sha256").update(`${url}
|
|
17996
18153
|
${mcpSecret ?? ""}`).digest("hex").slice(0, 16);
|
|
17997
18154
|
}
|
|
17998
18155
|
async function reconcileEndpoint(workspaceId, d) {
|
|
@@ -18040,8 +18197,8 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
18040
18197
|
import { createServer as createHttpServer } from "http";
|
|
18041
18198
|
import { spawn as spawn6 } from "child_process";
|
|
18042
18199
|
import { randomBytes } from "crypto";
|
|
18043
|
-
import { existsSync as
|
|
18044
|
-
import { join as
|
|
18200
|
+
import { existsSync as existsSync37, mkdirSync as mkdirSync24, readFileSync as readFileSync33, writeFileSync as writeFileSync25, unlinkSync as unlinkSync4, readdirSync as readdirSync15 } from "fs";
|
|
18201
|
+
import { join as join41 } from "path";
|
|
18045
18202
|
|
|
18046
18203
|
// src/secret-compare.ts
|
|
18047
18204
|
import { timingSafeEqual } from "crypto";
|
|
@@ -18130,21 +18287,21 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
18130
18287
|
return child;
|
|
18131
18288
|
};
|
|
18132
18289
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
18133
|
-
return
|
|
18290
|
+
return join41(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
18134
18291
|
}
|
|
18135
18292
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
18136
18293
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
18137
|
-
if (!
|
|
18294
|
+
if (!existsSync37(path)) return void 0;
|
|
18138
18295
|
try {
|
|
18139
|
-
return JSON.parse(
|
|
18296
|
+
return JSON.parse(readFileSync33(path, "utf8"));
|
|
18140
18297
|
} catch {
|
|
18141
18298
|
return void 0;
|
|
18142
18299
|
}
|
|
18143
18300
|
}
|
|
18144
18301
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
18145
18302
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
18146
|
-
|
|
18147
|
-
|
|
18303
|
+
mkdirSync24(join41(stateDir, "passengers"), { recursive: true });
|
|
18304
|
+
writeFileSync25(path, `${JSON.stringify(state, null, 2)}
|
|
18148
18305
|
`);
|
|
18149
18306
|
}
|
|
18150
18307
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -18164,8 +18321,8 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
18164
18321
|
}
|
|
18165
18322
|
}
|
|
18166
18323
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
18167
|
-
const dir =
|
|
18168
|
-
if (!
|
|
18324
|
+
const dir = join41(stateDir, "passengers");
|
|
18325
|
+
if (!existsSync37(dir)) return [];
|
|
18169
18326
|
const suffix = "-sync.json";
|
|
18170
18327
|
return readdirSync15(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
18171
18328
|
}
|
|
@@ -18253,8 +18410,8 @@ function describeCause(err2) {
|
|
|
18253
18410
|
}
|
|
18254
18411
|
|
|
18255
18412
|
// src/daemon.ts
|
|
18256
|
-
import { existsSync as
|
|
18257
|
-
import { join as
|
|
18413
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync25, readFileSync as readFileSync34, readdirSync as readdirSync16, rmSync as rmSync17, statSync as statSync12, writeFileSync as writeFileSync26 } from "fs";
|
|
18414
|
+
import { join as join42 } from "path";
|
|
18258
18415
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
18259
18416
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
18260
18417
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
@@ -18317,7 +18474,7 @@ async function runDaemonLoop(o) {
|
|
|
18317
18474
|
function latestMtime(path) {
|
|
18318
18475
|
let stat;
|
|
18319
18476
|
try {
|
|
18320
|
-
stat =
|
|
18477
|
+
stat = statSync12(path);
|
|
18321
18478
|
} catch {
|
|
18322
18479
|
return 0;
|
|
18323
18480
|
}
|
|
@@ -18325,7 +18482,7 @@ function latestMtime(path) {
|
|
|
18325
18482
|
if (!stat.isDirectory()) return 0;
|
|
18326
18483
|
let latest = stat.mtimeMs;
|
|
18327
18484
|
for (const entry of readdirSync16(path, { withFileTypes: true })) {
|
|
18328
|
-
latest = Math.max(latest, latestMtime(
|
|
18485
|
+
latest = Math.max(latest, latestMtime(join42(path, entry.name)));
|
|
18329
18486
|
}
|
|
18330
18487
|
return latest;
|
|
18331
18488
|
}
|
|
@@ -18334,23 +18491,23 @@ function computeTreeSignature(paths) {
|
|
|
18334
18491
|
}
|
|
18335
18492
|
function makeStaleChecker(paths, opts = {}) {
|
|
18336
18493
|
const initial = computeTreeSignature(paths);
|
|
18337
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
18494
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync38(opts.reloadSentinel);
|
|
18338
18495
|
}
|
|
18339
18496
|
function reloadSentinelFor(stateDir) {
|
|
18340
|
-
return
|
|
18497
|
+
return join42(stateDir, "reload-requested");
|
|
18341
18498
|
}
|
|
18342
18499
|
function daemonStampFor(stateDir) {
|
|
18343
|
-
return
|
|
18500
|
+
return join42(stateDir, "daemon.started");
|
|
18344
18501
|
}
|
|
18345
18502
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
18346
|
-
|
|
18347
|
-
|
|
18348
|
-
|
|
18503
|
+
mkdirSync25(stateDir, { recursive: true });
|
|
18504
|
+
rmSync17(reloadSentinelFor(stateDir), { force: true });
|
|
18505
|
+
writeFileSync26(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
18349
18506
|
`);
|
|
18350
18507
|
}
|
|
18351
18508
|
function readDaemonStarted(stateDir) {
|
|
18352
18509
|
try {
|
|
18353
|
-
const v = JSON.parse(
|
|
18510
|
+
const v = JSON.parse(readFileSync34(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
18354
18511
|
return typeof v === "number" ? v : null;
|
|
18355
18512
|
} catch {
|
|
18356
18513
|
return null;
|
|
@@ -18359,7 +18516,7 @@ function readDaemonStarted(stateDir) {
|
|
|
18359
18516
|
function configChangedAfter(files, startedAt) {
|
|
18360
18517
|
for (const f of files) {
|
|
18361
18518
|
try {
|
|
18362
|
-
if (
|
|
18519
|
+
if (statSync12(f).mtimeMs > startedAt) return f;
|
|
18363
18520
|
} catch {
|
|
18364
18521
|
}
|
|
18365
18522
|
}
|
|
@@ -18659,6 +18816,23 @@ async function runRoleAgentLocked(role, decision, ctx, hooks, held) {
|
|
|
18659
18816
|
const { route: route2, ship, emit } = ctx;
|
|
18660
18817
|
const runId = randomBytes2(3).toString("hex");
|
|
18661
18818
|
const identity = await resolveSeatKey(route2, role, ctx.getSessionStore);
|
|
18819
|
+
if (identity.kind === "ship" && identity.storeError && SEAT_KEY_ROLES.includes(role)) {
|
|
18820
|
+
emit.warn(`${role} run refused: its agent key cannot be read (${identity.storeError})`, { step: "agent", role });
|
|
18821
|
+
try {
|
|
18822
|
+
await raiseShipAttention(
|
|
18823
|
+
{ state: ctx.state, emit, route: route2, ship },
|
|
18824
|
+
seatKeyUnreadableItem(routeSlug(route2.route), role, identity.storeError)
|
|
18825
|
+
);
|
|
18826
|
+
} catch {
|
|
18827
|
+
}
|
|
18828
|
+
return;
|
|
18829
|
+
}
|
|
18830
|
+
if (SEAT_KEY_ROLES.includes(role)) {
|
|
18831
|
+
try {
|
|
18832
|
+
clearShipAttention(ctx.state, seatKeyUnreadableKey(routeSlug(route2.route), role));
|
|
18833
|
+
} catch {
|
|
18834
|
+
}
|
|
18835
|
+
}
|
|
18662
18836
|
if (identity.kind === "ship" && SEAT_KEY_ROLES.includes(role)) {
|
|
18663
18837
|
emit.warn(`${role} writes as the ship, not its own agent: ${identity.reason}`, { step: "agent", role });
|
|
18664
18838
|
} else if (identity.kind === "agent") {
|
|
@@ -18830,26 +19004,26 @@ async function buildEnvironment(route2, ship, ticket) {
|
|
|
18830
19004
|
}
|
|
18831
19005
|
|
|
18832
19006
|
// src/control-api.ts
|
|
18833
|
-
import { createHash as
|
|
19007
|
+
import { createHash as createHash9 } from "crypto";
|
|
18834
19008
|
import {
|
|
18835
|
-
chmodSync as
|
|
19009
|
+
chmodSync as chmodSync4,
|
|
18836
19010
|
closeSync as closeSync5,
|
|
18837
|
-
existsSync as
|
|
19011
|
+
existsSync as existsSync39,
|
|
18838
19012
|
fstatSync,
|
|
18839
19013
|
openSync as openSync5,
|
|
18840
19014
|
readSync as readSync3,
|
|
18841
19015
|
readdirSync as readdirSync17,
|
|
18842
|
-
rmSync as
|
|
18843
|
-
statSync as
|
|
19016
|
+
rmSync as rmSync18,
|
|
19017
|
+
statSync as statSync13,
|
|
18844
19018
|
watch
|
|
18845
19019
|
} from "fs";
|
|
18846
19020
|
import { createConnection } from "net";
|
|
18847
19021
|
import { createServer } from "http";
|
|
18848
|
-
import { basename as basename12, dirname as dirname21, join as
|
|
19022
|
+
import { basename as basename12, dirname as dirname21, join as join43 } from "path";
|
|
18849
19023
|
import { execFile, spawn as spawn7 } from "child_process";
|
|
18850
19024
|
var PAUSABLE = ["dev", "design", "qa"];
|
|
18851
19025
|
var CONTROL_SOCKET_NAME = "control.sock";
|
|
18852
|
-
var controlSocketFor = (stateDir) =>
|
|
19026
|
+
var controlSocketFor = (stateDir) => join43(stateDir, CONTROL_SOCKET_NAME);
|
|
18853
19027
|
var MAX_SOCKET_PATH = 100;
|
|
18854
19028
|
var MAX_TAIL_BYTES = 1024 * 1024;
|
|
18855
19029
|
var MAX_WAIT_SECONDS = 30;
|
|
@@ -18865,7 +19039,7 @@ var ControlAuth = class {
|
|
|
18865
19039
|
if (!token) return "unauthorized";
|
|
18866
19040
|
for (const k of this.o.shipKeys()) if (k && secretsMatch(token, k)) return "ok";
|
|
18867
19041
|
const now = (this.o.now ?? Date.now)();
|
|
18868
|
-
const id =
|
|
19042
|
+
const id = createHash9("sha256").update(token).digest("hex");
|
|
18869
19043
|
const hit = this.cache.get(id);
|
|
18870
19044
|
if (hit && now - hit.at < (this.o.cacheMs ?? 6e4)) return hit.verdict;
|
|
18871
19045
|
const verdict = await this.ask(token);
|
|
@@ -18920,7 +19094,7 @@ function candidates(live, rotated) {
|
|
|
18920
19094
|
const out = [live];
|
|
18921
19095
|
if (!rotated) return out;
|
|
18922
19096
|
try {
|
|
18923
|
-
for (const n of readdirSync17(dirname21(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(
|
|
19097
|
+
for (const n of readdirSync17(dirname21(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join43(dirname21(live), n));
|
|
18924
19098
|
} catch {
|
|
18925
19099
|
}
|
|
18926
19100
|
return out;
|
|
@@ -18943,7 +19117,7 @@ function readRange(path, from, max) {
|
|
|
18943
19117
|
}
|
|
18944
19118
|
}
|
|
18945
19119
|
function lastLines2(path, n) {
|
|
18946
|
-
const st =
|
|
19120
|
+
const st = statSync13(path);
|
|
18947
19121
|
const cursorAt = (off) => encodeCursor({ dev: st.dev, ino: st.ino, off });
|
|
18948
19122
|
if (n <= 0 || st.size === 0) return { lines: [], cursor: cursorAt(st.size), reset: false };
|
|
18949
19123
|
let start = st.size;
|
|
@@ -18964,7 +19138,7 @@ function lastLines2(path, n) {
|
|
|
18964
19138
|
}
|
|
18965
19139
|
function readTail(live, opts) {
|
|
18966
19140
|
const max = opts.maxBytes ?? MAX_TAIL_BYTES;
|
|
18967
|
-
if (!
|
|
19141
|
+
if (!existsSync39(live)) {
|
|
18968
19142
|
return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
|
|
18969
19143
|
}
|
|
18970
19144
|
const cur = opts.after ? decodeCursor(opts.after) : null;
|
|
@@ -18977,7 +19151,7 @@ function readTail(live, opts) {
|
|
|
18977
19151
|
} else {
|
|
18978
19152
|
const found = candidates(live, opts.rotated ?? false).find((p) => {
|
|
18979
19153
|
try {
|
|
18980
|
-
const s =
|
|
19154
|
+
const s = statSync13(p);
|
|
18981
19155
|
return s.ino === cur.ino && s.dev === cur.dev;
|
|
18982
19156
|
} catch {
|
|
18983
19157
|
return false;
|
|
@@ -18988,7 +19162,7 @@ function readTail(live, opts) {
|
|
|
18988
19162
|
off = cur.off;
|
|
18989
19163
|
} else reset = true;
|
|
18990
19164
|
}
|
|
18991
|
-
let size =
|
|
19165
|
+
let size = statSync13(path).size;
|
|
18992
19166
|
if (off > size) {
|
|
18993
19167
|
off = 0;
|
|
18994
19168
|
reset = true;
|
|
@@ -18996,12 +19170,12 @@ function readTail(live, opts) {
|
|
|
18996
19170
|
if (off >= size && path !== live) {
|
|
18997
19171
|
path = live;
|
|
18998
19172
|
off = 0;
|
|
18999
|
-
size =
|
|
19173
|
+
size = statSync13(path).size;
|
|
19000
19174
|
}
|
|
19001
19175
|
const { text } = readRange(path, off, max);
|
|
19002
19176
|
const cut = text.lastIndexOf("\n");
|
|
19003
19177
|
const whole = cut < 0 ? "" : text.slice(0, cut + 1);
|
|
19004
|
-
const st =
|
|
19178
|
+
const st = statSync13(path);
|
|
19005
19179
|
return {
|
|
19006
19180
|
lines: whole.split("\n").filter((l) => l.length),
|
|
19007
19181
|
cursor: encodeCursor({ dev: st.dev, ino: st.ino, off: off + Buffer.byteLength(whole) }),
|
|
@@ -19108,8 +19282,8 @@ async function route(deps, method, url, req, abort) {
|
|
|
19108
19282
|
if (p.startsWith("/streams/") && get2) {
|
|
19109
19283
|
const run2 = decodeURIComponent(p.slice("/streams/".length));
|
|
19110
19284
|
if (!RUN_NAME_RE.test(run2) || run2.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run2)) return err(400, "not a run name");
|
|
19111
|
-
const file =
|
|
19112
|
-
if (!
|
|
19285
|
+
const file = join43(deps.stateDir, "streams", run2.endsWith(".jsonl") ? run2 : `${run2}.jsonl`);
|
|
19286
|
+
if (!existsSync39(file)) return err(404, "no such run");
|
|
19113
19287
|
return tailReply(file, url.searchParams, false, abort);
|
|
19114
19288
|
}
|
|
19115
19289
|
if ((p === "/pause" || p === "/resume") && post3) {
|
|
@@ -19144,7 +19318,7 @@ function send(res, r) {
|
|
|
19144
19318
|
res.end(body);
|
|
19145
19319
|
}
|
|
19146
19320
|
async function socketIsStale(path) {
|
|
19147
|
-
if (!
|
|
19321
|
+
if (!existsSync39(path)) return false;
|
|
19148
19322
|
return new Promise((resolve6) => {
|
|
19149
19323
|
const c = createConnection(path);
|
|
19150
19324
|
c.once("connect", () => {
|
|
@@ -19162,8 +19336,8 @@ async function startControlServer(deps) {
|
|
|
19162
19336
|
log(`control API not started: socket path is too long for a unix socket (${path})`);
|
|
19163
19337
|
return null;
|
|
19164
19338
|
}
|
|
19165
|
-
if (await socketIsStale(path))
|
|
19166
|
-
else if (
|
|
19339
|
+
if (await socketIsStale(path)) rmSync18(path, { force: true });
|
|
19340
|
+
else if (existsSync39(path)) {
|
|
19167
19341
|
log(`control API not started: ${path} is in use by another process`);
|
|
19168
19342
|
return null;
|
|
19169
19343
|
}
|
|
@@ -19192,7 +19366,7 @@ async function startControlServer(deps) {
|
|
|
19192
19366
|
});
|
|
19193
19367
|
server.listen(path, () => {
|
|
19194
19368
|
try {
|
|
19195
|
-
|
|
19369
|
+
chmodSync4(path, 384);
|
|
19196
19370
|
} catch {
|
|
19197
19371
|
}
|
|
19198
19372
|
server.on("error", (e) => log(`control API error: ${e.message}`));
|
|
@@ -19201,7 +19375,7 @@ async function startControlServer(deps) {
|
|
|
19201
19375
|
path,
|
|
19202
19376
|
close: () => new Promise((done) => {
|
|
19203
19377
|
server.close(() => {
|
|
19204
|
-
|
|
19378
|
+
rmSync18(path, { force: true });
|
|
19205
19379
|
done();
|
|
19206
19380
|
});
|
|
19207
19381
|
})
|
|
@@ -19678,7 +19852,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
19678
19852
|
} else {
|
|
19679
19853
|
const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "connect" && flag("sign-out");
|
|
19680
19854
|
const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset");
|
|
19681
|
-
if (configless && !
|
|
19855
|
+
if (configless && !existsSync40(findConfigFile(CREW_HOME))) {
|
|
19682
19856
|
cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
|
|
19683
19857
|
} else {
|
|
19684
19858
|
cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
|
|
@@ -19693,7 +19867,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
19693
19867
|
throw e;
|
|
19694
19868
|
}
|
|
19695
19869
|
if (command === "credential") {
|
|
19696
|
-
const r = runCredential(positional[1],
|
|
19870
|
+
const r = runCredential(positional[1], readFileSync35(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
|
|
19697
19871
|
process.stdout.write(r.out);
|
|
19698
19872
|
process.stderr.write(r.err);
|
|
19699
19873
|
process.exit(r.code);
|
|
@@ -19705,9 +19879,9 @@ if (command === "passenger-sync-daemon") {
|
|
|
19705
19879
|
forges: cfg.ship.forges,
|
|
19706
19880
|
stateDir: cfg.ship.stateDir,
|
|
19707
19881
|
configFile: cfg.configFile,
|
|
19708
|
-
readFile: (p) =>
|
|
19709
|
-
writeFile: (p, t) =>
|
|
19710
|
-
stdin: () =>
|
|
19882
|
+
readFile: (p) => readFileSync35(p, "utf8"),
|
|
19883
|
+
writeFile: (p, t) => writeFileSync27(p, t),
|
|
19884
|
+
stdin: () => readFileSync35(0, "utf8"),
|
|
19711
19885
|
env: process.env
|
|
19712
19886
|
}
|
|
19713
19887
|
);
|
|
@@ -19943,8 +20117,8 @@ if (command === "passenger-sync-daemon") {
|
|
|
19943
20117
|
}
|
|
19944
20118
|
if (host === "linux" && hasSystemd) {
|
|
19945
20119
|
const label = labelFor(CREW_HOME, "run");
|
|
19946
|
-
const staleTimer =
|
|
19947
|
-
if (
|
|
20120
|
+
const staleTimer = join44(homedir10(), ".config", "systemd", "user", `${label}.timer`);
|
|
20121
|
+
if (existsSync40(staleTimer)) {
|
|
19948
20122
|
process.stdout.write(
|
|
19949
20123
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
19950
20124
|
`
|
|
@@ -19971,7 +20145,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
19971
20145
|
const tracker = new Tracker(c, cfg.ship);
|
|
19972
20146
|
problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
|
|
19973
20147
|
for (const r of await resolvedRepos(c)) {
|
|
19974
|
-
if (!
|
|
20148
|
+
if (!existsSync40(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
19975
20149
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
19976
20150
|
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
19977
20151
|
}
|
|
@@ -19989,7 +20163,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
19989
20163
|
}
|
|
19990
20164
|
async function doctorFix(o) {
|
|
19991
20165
|
const routes = await Promise.all(routesInScope(cfg.routes, named).map((c) => gatherHealth({ route: c.route, enabled: c.enabled, hasRepos: reposOf(c).length > 0 }, () => routeProblems(c))));
|
|
19992
|
-
const unitExists = (p) =>
|
|
20166
|
+
const unitExists = (p) => existsSync40(p);
|
|
19993
20167
|
const crontab = o.host === "linux" ? spawnSync7("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
19994
20168
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run"), unitExists, crontab) : true;
|
|
19995
20169
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -20006,7 +20180,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
20006
20180
|
for (const a of plan.actions) {
|
|
20007
20181
|
if (a.kind === "enable") {
|
|
20008
20182
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
20009
|
-
|
|
20183
|
+
writeFileSync27(cfg.configFile, planEnable(readFileSync35(cfg.configFile, "utf8"), a.route));
|
|
20010
20184
|
enabledSound.add(a.route);
|
|
20011
20185
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
20012
20186
|
} else {
|
|
@@ -20150,6 +20324,23 @@ ${changed2.map((c) => ` - ${c}
|
|
|
20150
20324
|
}
|
|
20151
20325
|
if (!dryRun && !c.enabled) return { scope, tests: "skipped", outcome: "skipped", detail: "route not enabled" };
|
|
20152
20326
|
const releaseIdentity = await resolveReleaseKey(c);
|
|
20327
|
+
const releaseRouteSlug = routeSlug(c.route);
|
|
20328
|
+
if (releaseIdentity.kind === "ship" && releaseIdentity.storeError) {
|
|
20329
|
+
const detail = `release refused: its agent key cannot be read (${releaseIdentity.storeError})`;
|
|
20330
|
+
remit.warn(`${scope}: ${detail}`, { step: "release" });
|
|
20331
|
+
try {
|
|
20332
|
+
await raiseShipAttention(
|
|
20333
|
+
{ state: new State(cfg.ship.stateDir), emit: remit, route: c, ship: cfg.ship },
|
|
20334
|
+
seatKeyUnreadableItem(releaseRouteSlug, "release", releaseIdentity.storeError)
|
|
20335
|
+
);
|
|
20336
|
+
} catch {
|
|
20337
|
+
}
|
|
20338
|
+
return { scope, tests: "skipped", outcome: "skipped", detail };
|
|
20339
|
+
}
|
|
20340
|
+
try {
|
|
20341
|
+
clearShipAttention(new State(cfg.ship.stateDir), seatKeyUnreadableKey(releaseRouteSlug, "release"));
|
|
20342
|
+
} catch {
|
|
20343
|
+
}
|
|
20153
20344
|
if (releaseIdentity.kind === "ship") remit.warn(`release writes as the ship, not its own agent: ${releaseIdentity.reason}`, { step: "release" });
|
|
20154
20345
|
const tracker = new Tracker(c, cfg.ship, { apiKey: releaseIdentity.apiKey });
|
|
20155
20346
|
if (!dryRun) {
|
|
@@ -21110,7 +21301,7 @@ ${cu.tail.trimEnd()}
|
|
|
21110
21301
|
process.exit(1);
|
|
21111
21302
|
}
|
|
21112
21303
|
requireArmed("run the daemon loop");
|
|
21113
|
-
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [
|
|
21304
|
+
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join44(CREW_HOME, "src"), join44(CREW_HOME, "dist"), join44(CREW_HOME, "bin")];
|
|
21114
21305
|
const configPaths = daemonConfigPaths(cfg);
|
|
21115
21306
|
watchPaths.push(...configPaths);
|
|
21116
21307
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -21362,10 +21553,10 @@ ${cu.tail.trimEnd()}
|
|
|
21362
21553
|
`);
|
|
21363
21554
|
if (!dryRun) {
|
|
21364
21555
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
21365
|
-
const raw =
|
|
21556
|
+
const raw = existsSync40(resolvedPath) ? JSON.parse(readFileSync35(resolvedPath, "utf8")) : {};
|
|
21366
21557
|
raw.agentSkills = result.agentSkills;
|
|
21367
|
-
|
|
21368
|
-
|
|
21558
|
+
mkdirSync26(dirOf2(resolvedPath), { recursive: true });
|
|
21559
|
+
writeFileSync27(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
21369
21560
|
`);
|
|
21370
21561
|
}
|
|
21371
21562
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -21388,7 +21579,7 @@ ${cu.tail.trimEnd()}
|
|
|
21388
21579
|
throw e;
|
|
21389
21580
|
}
|
|
21390
21581
|
const oldName = cfg.ship.name;
|
|
21391
|
-
|
|
21582
|
+
writeFileSync27(cfg.configFile, setShipNameInYaml(readFileSync35(cfg.configFile, "utf8"), newName));
|
|
21392
21583
|
cfg.ship.name = newName;
|
|
21393
21584
|
let renameMachineId;
|
|
21394
21585
|
try {
|
|
@@ -21435,7 +21626,7 @@ ${cu.tail.trimEnd()}
|
|
|
21435
21626
|
process.exit(2);
|
|
21436
21627
|
};
|
|
21437
21628
|
if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks" && sub !== "check") usage2();
|
|
21438
|
-
const configText =
|
|
21629
|
+
const configText = readFileSync35(cfg.configFile, "utf8");
|
|
21439
21630
|
if (sub === "check") {
|
|
21440
21631
|
try {
|
|
21441
21632
|
const routeArg = value("route") ?? positional[2];
|
|
@@ -21466,8 +21657,8 @@ ${cu.tail.trimEnd()}
|
|
|
21466
21657
|
try {
|
|
21467
21658
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
21468
21659
|
const dir = repoDirOf(configText, dirname22(cfg.configFile), a.route, a.name);
|
|
21469
|
-
if (!
|
|
21470
|
-
const files = { exists: (r) =>
|
|
21660
|
+
if (!existsSync40(dir)) throw new ReposError(`${dir} does not exist on disk`);
|
|
21661
|
+
const files = { exists: (r) => existsSync40(resolvePath(dir, r)), list: (r) => {
|
|
21471
21662
|
try {
|
|
21472
21663
|
return readdirSync18(resolvePath(dir, r));
|
|
21473
21664
|
} catch {
|
|
@@ -21475,7 +21666,7 @@ ${cu.tail.trimEnd()}
|
|
|
21475
21666
|
}
|
|
21476
21667
|
}, read: (r) => {
|
|
21477
21668
|
try {
|
|
21478
|
-
return
|
|
21669
|
+
return readFileSync35(resolvePath(dir, r), "utf8");
|
|
21479
21670
|
} catch {
|
|
21480
21671
|
return void 0;
|
|
21481
21672
|
}
|
|
@@ -21517,19 +21708,19 @@ ${cu.tail.trimEnd()}
|
|
|
21517
21708
|
let cleared;
|
|
21518
21709
|
if (clear2) {
|
|
21519
21710
|
const plan = clearRouteHooks(configText, a.route, a.name);
|
|
21520
|
-
if (plan.removed.length > 0)
|
|
21711
|
+
if (plan.removed.length > 0) writeFileSync27(cfg.configFile, plan.text);
|
|
21521
21712
|
cleared = { file: cfg.configFile, removed: plan.removed };
|
|
21522
21713
|
}
|
|
21523
21714
|
if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
21524
21715
|
if (write === "route") {
|
|
21525
21716
|
const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
|
|
21526
|
-
|
|
21717
|
+
writeFileSync27(cfg.configFile, plan.text);
|
|
21527
21718
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
|
|
21528
21719
|
} else if (write === "repo") {
|
|
21529
21720
|
const have = existingRepoConfig(files);
|
|
21530
21721
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
21531
21722
|
const file = resolvePath(dir, ".crew.yaml");
|
|
21532
|
-
|
|
21723
|
+
writeFileSync27(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
|
|
21533
21724
|
written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
|
|
21534
21725
|
}
|
|
21535
21726
|
if (flag("json")) {
|
|
@@ -21585,7 +21776,7 @@ ${cu.tail.trimEnd()}
|
|
|
21585
21776
|
if (sub === "list" && !routeName) usage2();
|
|
21586
21777
|
if (sub === "add") {
|
|
21587
21778
|
try {
|
|
21588
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
21779
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync40(resolvePath(p.replace(/^~(?=\/|$)/, homedir10()))));
|
|
21589
21780
|
routeName = a.route;
|
|
21590
21781
|
addPath = a.path;
|
|
21591
21782
|
addName = a.name;
|
|
@@ -21614,16 +21805,16 @@ ${cu.tail.trimEnd()}
|
|
|
21614
21805
|
}
|
|
21615
21806
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
21616
21807
|
let resolvedRemotes;
|
|
21617
|
-
if (
|
|
21808
|
+
if (existsSync40(resolvedFile)) {
|
|
21618
21809
|
try {
|
|
21619
|
-
resolvedRemotes = JSON.parse(
|
|
21810
|
+
resolvedRemotes = JSON.parse(readFileSync35(resolvedFile, "utf8"));
|
|
21620
21811
|
} catch {
|
|
21621
21812
|
}
|
|
21622
21813
|
}
|
|
21623
21814
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
21624
21815
|
const text = configText;
|
|
21625
21816
|
const base = dirname22(cfg.configFile);
|
|
21626
|
-
const isCheckout = (d) =>
|
|
21817
|
+
const isCheckout = (d) => existsSync40(`${d}/.git`);
|
|
21627
21818
|
try {
|
|
21628
21819
|
if (sub === "list") {
|
|
21629
21820
|
if (flag("json")) {
|
|
@@ -21633,11 +21824,11 @@ ${cu.tail.trimEnd()}
|
|
|
21633
21824
|
base,
|
|
21634
21825
|
resolved: resolvedRemotes,
|
|
21635
21826
|
originOf,
|
|
21636
|
-
exists: (d) =>
|
|
21827
|
+
exists: (d) => existsSync40(d),
|
|
21637
21828
|
isCheckout,
|
|
21638
21829
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
21639
21830
|
try {
|
|
21640
|
-
return Object.keys(parseRepoConfig(
|
|
21831
|
+
return Object.keys(parseRepoConfig(readFileSync35(resolvePath(d, f), "utf8"), f).hooks).length > 0;
|
|
21641
21832
|
} catch {
|
|
21642
21833
|
return false;
|
|
21643
21834
|
}
|
|
@@ -21654,7 +21845,7 @@ ${cu.tail.trimEnd()}
|
|
|
21654
21845
|
for (const r of repos) {
|
|
21655
21846
|
const origin2 = originOf(r.dir);
|
|
21656
21847
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
21657
|
-
const note = !
|
|
21848
|
+
const note = !existsSync40(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
|
|
21658
21849
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
21659
21850
|
`);
|
|
21660
21851
|
}
|
|
@@ -21666,9 +21857,9 @@ ${cu.tail.trimEnd()}
|
|
|
21666
21857
|
` : `${human}
|
|
21667
21858
|
`);
|
|
21668
21859
|
const plan2 = planRepoClone({ text, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
|
|
21669
|
-
if (
|
|
21860
|
+
if (existsSync40(plan2.target) && (existsSync40(`${plan2.target}/.git`) || readdirSync18(plan2.target).length > 0)) {
|
|
21670
21861
|
throw new ReposError(
|
|
21671
|
-
|
|
21862
|
+
existsSync40(`${plan2.target}/.git`) ? `${plan2.target} is already a checkout \u2014 attach it with \`crew repos add ${routeName} ${plan2.name} ${plan2.target}\`` : `${plan2.target} exists and is not empty \u2014 pass --dir to clone somewhere else`
|
|
21672
21863
|
);
|
|
21673
21864
|
}
|
|
21674
21865
|
if (dryRun) {
|
|
@@ -21686,14 +21877,14 @@ ${cu.tail.trimEnd()}
|
|
|
21686
21877
|
throw new ReposError(`${e.message}${authHint}
|
|
21687
21878
|
crew.yaml was not changed`);
|
|
21688
21879
|
}
|
|
21689
|
-
|
|
21880
|
+
writeFileSync27(cfg.configFile, plan2.text);
|
|
21690
21881
|
if (plan2.droppedDir) process.stderr.write(`crew repos: dropped dir: ${plan2.droppedDir} from route ${routeName} \u2014 it is not a checkout (the clone base); checkouts now live under repos:
|
|
21691
21882
|
`);
|
|
21692
21883
|
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}`);
|
|
21693
21884
|
break;
|
|
21694
21885
|
}
|
|
21695
21886
|
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir10()));
|
|
21696
|
-
const top =
|
|
21887
|
+
const top = existsSync40(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
21697
21888
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
21698
21889
|
const origin = originOf(top);
|
|
21699
21890
|
const plan = planRepoAdd({
|
|
@@ -21745,15 +21936,15 @@ ${cu.tail.trimEnd()}
|
|
|
21745
21936
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
21746
21937
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
21747
21938
|
`);
|
|
21748
|
-
if (
|
|
21749
|
-
const raw = JSON.parse(
|
|
21939
|
+
if (existsSync40(resolvedFile)) {
|
|
21940
|
+
const raw = JSON.parse(readFileSync35(resolvedFile, "utf8"));
|
|
21750
21941
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
21751
21942
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
21752
|
-
|
|
21943
|
+
writeFileSync27(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
21753
21944
|
`);
|
|
21754
21945
|
}
|
|
21755
21946
|
}
|
|
21756
|
-
|
|
21947
|
+
writeFileSync27(cfg.configFile, plan.text);
|
|
21757
21948
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
21758
21949
|
`);
|
|
21759
21950
|
} catch (e) {
|
|
@@ -21852,7 +22043,7 @@ ${reconstruction.prompt}
|
|
|
21852
22043
|
if (flag("sign-out")) {
|
|
21853
22044
|
let signOutStore;
|
|
21854
22045
|
try {
|
|
21855
|
-
signOutStore =
|
|
22046
|
+
signOutStore = getSessionStore();
|
|
21856
22047
|
} catch {
|
|
21857
22048
|
signOutStore = void 0;
|
|
21858
22049
|
}
|
|
@@ -21892,7 +22083,7 @@ ${reconstruction.prompt}
|
|
|
21892
22083
|
const givenKeySource = value("key") ?? process.env.CREW_CONNECT_KEY ? "flag" : "config";
|
|
21893
22084
|
let store;
|
|
21894
22085
|
try {
|
|
21895
|
-
store =
|
|
22086
|
+
store = getSessionStore();
|
|
21896
22087
|
} catch {
|
|
21897
22088
|
store = void 0;
|
|
21898
22089
|
}
|
|
@@ -22120,7 +22311,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
22120
22311
|
process.exit(2);
|
|
22121
22312
|
}
|
|
22122
22313
|
if (found.shipNameAdopted && !dryRun) {
|
|
22123
|
-
|
|
22314
|
+
writeFileSync27(cfg.configFile, setShipNameInYaml(readFileSync35(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
22124
22315
|
cfg.ship.name = found.shipNameAdopted;
|
|
22125
22316
|
}
|
|
22126
22317
|
if (found.shipRow) {
|
|
@@ -22139,8 +22330,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
22139
22330
|
}
|
|
22140
22331
|
const capabilityMisses = capabilities.filter((i) => i.status === "missing").map(missLine);
|
|
22141
22332
|
if (found.locksModelId && !dryRun) {
|
|
22142
|
-
const
|
|
22143
|
-
const repoNames = [...Object.values(found.repoNames ?? {}), ...
|
|
22333
|
+
const configured2 = cfg.routes.find((r) => r.route === route3);
|
|
22334
|
+
const repoNames = [...Object.values(found.repoNames ?? {}), ...configured2 ? reposOf(configured2).map((t) => t.name) : []].filter((n) => !!n);
|
|
22144
22335
|
try {
|
|
22145
22336
|
const made = await provisionReleaseLocks(authOpts, found.locksModelId, route3, repoNames);
|
|
22146
22337
|
for (const sc of made) process.stderr.write(`created release lock row ${sc}
|
|
@@ -22163,10 +22354,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
22163
22354
|
}
|
|
22164
22355
|
}
|
|
22165
22356
|
let previous;
|
|
22166
|
-
const hadPreviousFile =
|
|
22357
|
+
const hadPreviousFile = existsSync40(resolvedPath);
|
|
22167
22358
|
if (hadPreviousFile) {
|
|
22168
22359
|
try {
|
|
22169
|
-
previous = JSON.parse(
|
|
22360
|
+
previous = JSON.parse(readFileSync35(resolvedPath, "utf8"));
|
|
22170
22361
|
} catch {
|
|
22171
22362
|
}
|
|
22172
22363
|
}
|
|
@@ -22295,12 +22486,12 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
22295
22486
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
22296
22487
|
`);
|
|
22297
22488
|
} else {
|
|
22298
|
-
|
|
22489
|
+
mkdirSync26(dirname22(resolvedPath), { recursive: true });
|
|
22299
22490
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
22300
22491
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
22301
|
-
|
|
22492
|
+
writeFileSync27(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
22302
22493
|
`);
|
|
22303
|
-
|
|
22494
|
+
renameSync4(tmpPath, resolvedPath);
|
|
22304
22495
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
22305
22496
|
`);
|
|
22306
22497
|
}
|
|
@@ -22344,8 +22535,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
22344
22535
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
22345
22536
|
`);
|
|
22346
22537
|
} else {
|
|
22347
|
-
|
|
22348
|
-
|
|
22538
|
+
mkdirSync26(dirname22(apiKeyFile), { recursive: true });
|
|
22539
|
+
writeFileSync27(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
22349
22540
|
`, { mode: 384 });
|
|
22350
22541
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
22351
22542
|
`);
|
|
@@ -23079,7 +23270,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
23079
23270
|
const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
|
|
23080
23271
|
const resumed = await runPeriscopeResume({
|
|
23081
23272
|
find: async () => hit && { row: hit.row, tag: hit.tag },
|
|
23082
|
-
worktreeExists: (p) =>
|
|
23273
|
+
worktreeExists: (p) => existsSync40(p),
|
|
23083
23274
|
caps: periscopeCaps(cfg.ship),
|
|
23084
23275
|
runHook: async (name, cwd, env) => {
|
|
23085
23276
|
const script = hit?.repo.config.hooks[name];
|
|
@@ -23173,10 +23364,10 @@ ${where.join("\n")}`);
|
|
|
23173
23364
|
},
|
|
23174
23365
|
prepare: async (path) => {
|
|
23175
23366
|
for (const f of repoP.config.worktrees.copy) {
|
|
23176
|
-
const src =
|
|
23177
|
-
if (!
|
|
23178
|
-
|
|
23179
|
-
copyFileSync2(src,
|
|
23367
|
+
const src = join44(repoP.dir, f);
|
|
23368
|
+
if (!existsSync40(src)) continue;
|
|
23369
|
+
mkdirSync26(dirOf2(join44(path, f)), { recursive: true });
|
|
23370
|
+
copyFileSync2(src, join44(path, f));
|
|
23180
23371
|
}
|
|
23181
23372
|
const setup = repoP.config.hooks.setup;
|
|
23182
23373
|
return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
|
|
@@ -23402,7 +23593,7 @@ ${where.join("\n")}`);
|
|
|
23402
23593
|
const host = hostPlatform();
|
|
23403
23594
|
let resetStore;
|
|
23404
23595
|
try {
|
|
23405
|
-
resetStore =
|
|
23596
|
+
resetStore = getSessionStore();
|
|
23406
23597
|
} catch {
|
|
23407
23598
|
resetStore = void 0;
|
|
23408
23599
|
}
|
|
@@ -23451,7 +23642,7 @@ ${where.join("\n")}`);
|
|
|
23451
23642
|
};
|
|
23452
23643
|
const droppedRoutes = [];
|
|
23453
23644
|
try {
|
|
23454
|
-
const rawCfg = parseYaml2(
|
|
23645
|
+
const rawCfg = parseYaml2(readFileSync35(cfg.configFile, "utf8"));
|
|
23455
23646
|
for (const r of rawCfg?.routes ?? []) {
|
|
23456
23647
|
if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
|
|
23457
23648
|
try {
|
|
@@ -23493,8 +23684,8 @@ ${where.join("\n")}`);
|
|
|
23493
23684
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
23494
23685
|
break;
|
|
23495
23686
|
}
|
|
23496
|
-
|
|
23497
|
-
|
|
23687
|
+
mkdirSync26(cfg.ship.stateDir, { recursive: true });
|
|
23688
|
+
writeFileSync27(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
23498
23689
|
`);
|
|
23499
23690
|
process.stdout.write("crew reload: requested \u2014 the daemon finishes its in-flight pass, then relaunches with the new config\n");
|
|
23500
23691
|
if (flag("now") && control?.stopCommand && control.startCommand) {
|
|
@@ -23545,7 +23736,7 @@ ${where.join("\n")}`);
|
|
|
23545
23736
|
}
|
|
23546
23737
|
case "log": {
|
|
23547
23738
|
try {
|
|
23548
|
-
const text =
|
|
23739
|
+
const text = readFileSync35(cfg.ship.logFile, "utf8").split("\n");
|
|
23549
23740
|
process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
23550
23741
|
`);
|
|
23551
23742
|
} catch {
|
|
@@ -23568,10 +23759,10 @@ ${where.join("\n")}`);
|
|
|
23568
23759
|
};
|
|
23569
23760
|
if (!route2.resolved) fail(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
|
|
23570
23761
|
const tracker = new Tracker(route2, cfg.ship);
|
|
23571
|
-
const configText =
|
|
23762
|
+
const configText = readFileSync35(cfg.configFile, "utf8");
|
|
23572
23763
|
const cfgBase = dirname22(cfg.configFile);
|
|
23573
|
-
const
|
|
23574
|
-
const dir =
|
|
23764
|
+
const configured2 = configuredRepos(configText, route2.route, cfgBase);
|
|
23765
|
+
const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
|
|
23575
23766
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
|
|
23576
23767
|
const rows = await tracker.repoRows();
|
|
23577
23768
|
const row = rows.find((r) => r.name === SAMPLE_REPO);
|
|
@@ -23595,9 +23786,9 @@ ${where.join("\n")}`);
|
|
|
23595
23786
|
say("dry-run", { dir, row: row?.id ?? null, closed }, `would close ${closed.join(", ") || "no tickets"}, delete the Repos row, detach and delete ${dir}`);
|
|
23596
23787
|
break;
|
|
23597
23788
|
}
|
|
23598
|
-
if (
|
|
23789
|
+
if (configured2.some((r) => r.dir === dir)) {
|
|
23599
23790
|
try {
|
|
23600
|
-
|
|
23791
|
+
writeFileSync27(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
|
|
23601
23792
|
} catch (e) {
|
|
23602
23793
|
if (e instanceof ReposError) fail(e.message);
|
|
23603
23794
|
throw e;
|
|
@@ -23605,29 +23796,29 @@ ${where.join("\n")}`);
|
|
|
23605
23796
|
}
|
|
23606
23797
|
if (row) {
|
|
23607
23798
|
await tracker.deleteRepoRow(row.id);
|
|
23608
|
-
if (
|
|
23609
|
-
const raw = JSON.parse(
|
|
23799
|
+
if (existsSync40(resolvedFile)) {
|
|
23800
|
+
const raw = JSON.parse(readFileSync35(resolvedFile, "utf8"));
|
|
23610
23801
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
23611
23802
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
23612
|
-
|
|
23803
|
+
writeFileSync27(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
23613
23804
|
`);
|
|
23614
23805
|
}
|
|
23615
23806
|
}
|
|
23616
|
-
if (
|
|
23807
|
+
if (existsSync40(join44(dir, ".git")) && basename13(dir) === SAMPLE_REPO) rmSync19(dir, { recursive: true, force: true });
|
|
23617
23808
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
23618
23809
|
break;
|
|
23619
23810
|
}
|
|
23620
23811
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
23621
|
-
const fresh = !
|
|
23622
|
-
if (
|
|
23812
|
+
const fresh = !existsSync40(join44(dir, ".git"));
|
|
23813
|
+
if (existsSync40(dir) && fresh && readdirSync18(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
23623
23814
|
if (dryRun) {
|
|
23624
23815
|
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`);
|
|
23625
23816
|
break;
|
|
23626
23817
|
}
|
|
23627
23818
|
if (fresh) {
|
|
23628
|
-
|
|
23629
|
-
|
|
23630
|
-
|
|
23819
|
+
mkdirSync26(dir, { recursive: true });
|
|
23820
|
+
writeFileSync27(join44(dir, "README.md"), SAMPLE_README);
|
|
23821
|
+
writeFileSync27(join44(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
23631
23822
|
const g = (...a) => {
|
|
23632
23823
|
const r = spawnSync7("git", a, { cwd: dir, encoding: "utf8" });
|
|
23633
23824
|
if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -23645,17 +23836,17 @@ ${where.join("\n")}`);
|
|
|
23645
23836
|
spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
|
|
23646
23837
|
});
|
|
23647
23838
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
23648
|
-
if (
|
|
23649
|
-
const raw = JSON.parse(
|
|
23839
|
+
if (existsSync40(resolvedFile)) {
|
|
23840
|
+
const raw = JSON.parse(readFileSync35(resolvedFile, "utf8"));
|
|
23650
23841
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
23651
23842
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
23652
|
-
|
|
23843
|
+
writeFileSync27(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
23653
23844
|
`);
|
|
23654
23845
|
}
|
|
23655
23846
|
}
|
|
23656
|
-
if (!
|
|
23847
|
+
if (!configured2.some((r) => r.dir === dir)) {
|
|
23657
23848
|
try {
|
|
23658
|
-
|
|
23849
|
+
writeFileSync27(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
|
|
23659
23850
|
} catch (e) {
|
|
23660
23851
|
if (e instanceof ReposError) fail(e.message);
|
|
23661
23852
|
throw e;
|
|
@@ -23990,7 +24181,7 @@ watermark: ${state.watermark()}
|
|
|
23990
24181
|
}
|
|
23991
24182
|
for (const r of repos) {
|
|
23992
24183
|
const remote = r.config.branch.remote;
|
|
23993
|
-
if (
|
|
24184
|
+
if (existsSync40(r.dir)) {
|
|
23994
24185
|
pushAuth.push({
|
|
23995
24186
|
route: c.route,
|
|
23996
24187
|
repo: r.name,
|
|
@@ -24063,6 +24254,8 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
24063
24254
|
for (const w of roleCapWarnings(cfg.ship)) process.stdout.write(`role concurrency: WARN ${w}
|
|
24064
24255
|
`);
|
|
24065
24256
|
process.stdout.write(`periscopes: ${periscopeSummary(cfg)}
|
|
24257
|
+
`);
|
|
24258
|
+
process.stdout.write(`key store: ${describeKeyStore(cfg.ship)}
|
|
24066
24259
|
`);
|
|
24067
24260
|
const previewProblem = await previewHostProblem(cfg);
|
|
24068
24261
|
if (previewProblem) {
|
|
@@ -24073,7 +24266,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
24073
24266
|
}
|
|
24074
24267
|
try {
|
|
24075
24268
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
24076
|
-
const evSize =
|
|
24269
|
+
const evSize = existsSync40(evFile) ? statSync14(evFile).size : 0;
|
|
24077
24270
|
process.stdout.write(
|
|
24078
24271
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
24079
24272
|
` : "events log: ok\n"
|
|
@@ -24084,7 +24277,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
24084
24277
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run");
|
|
24085
24278
|
const abandons = unitAbandonsAgents(unitPlan, (p) => {
|
|
24086
24279
|
try {
|
|
24087
|
-
return
|
|
24280
|
+
return readFileSync35(p, "utf8");
|
|
24088
24281
|
} catch {
|
|
24089
24282
|
return null;
|
|
24090
24283
|
}
|
|
@@ -24100,7 +24293,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
24100
24293
|
const changed2 = configChangedAfter(daemonConfigPaths(cfg), daemonStartedAt);
|
|
24101
24294
|
if (changed2) {
|
|
24102
24295
|
process.stdout.write(
|
|
24103
|
-
`daemon config: WARN ${changed2} changed at ${new Date(
|
|
24296
|
+
`daemon config: WARN ${changed2} changed at ${new Date(statSync14(changed2).mtimeMs).toISOString()} but the daemon started at ${new Date(daemonStartedAt).toISOString()} \u2014 run \`crew reload\`
|
|
24104
24297
|
`
|
|
24105
24298
|
);
|
|
24106
24299
|
}
|