@tablation/crew 0.121.0 → 0.122.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 +676 -434
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -262,6 +262,9 @@ function intelFlavor(script) {
|
|
|
262
262
|
const w = script?.trim();
|
|
263
263
|
return w && Object.prototype.hasOwnProperty.call(INTEL_FLAVORS, w) ? w : void 0;
|
|
264
264
|
}
|
|
265
|
+
function wantsCodeBridge(script) {
|
|
266
|
+
return !!script?.trim() && intelFlavor(script) !== "typescript-lsp";
|
|
267
|
+
}
|
|
265
268
|
var INTEL_MINIMUM_TOOLS = [
|
|
266
269
|
"find symbols by name",
|
|
267
270
|
"references to a symbol",
|
|
@@ -1309,13 +1312,17 @@ var claudeAdapter = {
|
|
|
1309
1312
|
// so the resolved key is never written to an argv string or a config
|
|
1310
1313
|
// file on disk. Skipped when there's no key to send: a route `crew
|
|
1311
1314
|
// connect` hasn't resolved yet has nothing to authenticate with.
|
|
1312
|
-
...o.mcp ? ["--mcp-config", JSON.stringify({
|
|
1315
|
+
...o.mcp || o.code ? ["--mcp-config", JSON.stringify({
|
|
1313
1316
|
mcpServers: {
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1317
|
+
...o.mcp ? {
|
|
1318
|
+
tablation: {
|
|
1319
|
+
type: "http",
|
|
1320
|
+
url: o.mcp.url,
|
|
1321
|
+
headers: { Authorization: `Bearer \${${o.mcp.keyVar}}` }
|
|
1322
|
+
}
|
|
1323
|
+
} : {},
|
|
1324
|
+
// CREW-2912: the bridge to the worktree's symbol service, attached once `crew worktree` has started it.
|
|
1325
|
+
...o.code ? { [INTEL_SERVER_NAME]: { type: "stdio", command: o.code.command, args: o.code.args } } : {}
|
|
1319
1326
|
}
|
|
1320
1327
|
})] : [],
|
|
1321
1328
|
// A crew-generated language-server plugin (the `typescript-lsp` intel flavor). Not under oMLX,
|
|
@@ -1664,6 +1671,14 @@ var codexAdapter = {
|
|
|
1664
1671
|
...cfg(`mcp_servers.${SERVER}.bearer_token_env_var`, o.mcp.keyVar),
|
|
1665
1672
|
...cfg(`mcp_servers.${SERVER}.default_tools_approval_mode`, "approve")
|
|
1666
1673
|
] : [],
|
|
1674
|
+
// CREW-2912: the bridge to the worktree's symbol service, scoped to this run like the tracker entry above.
|
|
1675
|
+
...o.code ? [
|
|
1676
|
+
"-c",
|
|
1677
|
+
`mcp_servers.${INTEL_SERVER_NAME}.command=${tomlString(o.code.command)}`,
|
|
1678
|
+
"-c",
|
|
1679
|
+
`mcp_servers.${INTEL_SERVER_NAME}.args=[${o.code.args.map(tomlString).join(", ")}]`,
|
|
1680
|
+
...cfg(`mcp_servers.${INTEL_SERVER_NAME}.default_tools_approval_mode`, "approve")
|
|
1681
|
+
] : [],
|
|
1667
1682
|
...spec.args,
|
|
1668
1683
|
// Prompt on stdin.
|
|
1669
1684
|
"-"
|
|
@@ -2045,7 +2060,8 @@ var geminiAdapter = {
|
|
|
2045
2060
|
];
|
|
2046
2061
|
return wrapInvocation(spec, { model: o.model, args });
|
|
2047
2062
|
},
|
|
2048
|
-
|
|
2063
|
+
// CREW-2912: Gemini has no per-run server list, so the bridge is declared in a per-run system settings file the planner wrote.
|
|
2064
|
+
runEnv: (spec, o) => ({ ...spec.env, ...o?.code?.settingsFile ? { GEMINI_CLI_SYSTEM_SETTINGS_PATH: o.code.settingsFile } : {} }),
|
|
2049
2065
|
provider: () => "google",
|
|
2050
2066
|
unsetEnv: BILLING_VARS_TO_UNSET2,
|
|
2051
2067
|
mapStreamLine: mapGeminiLine,
|
|
@@ -6189,8 +6205,8 @@ function attentionProblems(items, already) {
|
|
|
6189
6205
|
import { createHash as createHash6 } from "crypto";
|
|
6190
6206
|
import { spawn as spawn5 } from "child_process";
|
|
6191
6207
|
import { execFileSync as execFileSync8 } from "child_process";
|
|
6192
|
-
import { mkdirSync as
|
|
6193
|
-
import { dirname as
|
|
6208
|
+
import { mkdirSync as mkdirSync19, readFileSync as readFileSync26, renameSync as renameSync3, writeFileSync as writeFileSync20, rmSync as rmSync15 } from "fs";
|
|
6209
|
+
import { dirname as dirname14, join as join31 } from "path";
|
|
6194
6210
|
import { tmpdir as tmpdir3 } from "os";
|
|
6195
6211
|
|
|
6196
6212
|
// src/passenger-containers.ts
|
|
@@ -6203,18 +6219,18 @@ import { promisify as promisify2 } from "util";
|
|
|
6203
6219
|
|
|
6204
6220
|
// src/deprovision-host.ts
|
|
6205
6221
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
6206
|
-
import { existsSync as
|
|
6222
|
+
import { existsSync as existsSync29, readdirSync as readdirSync11 } from "fs";
|
|
6207
6223
|
|
|
6208
6224
|
// src/interrupted.ts
|
|
6209
|
-
import { existsSync as
|
|
6210
|
-
import { join as
|
|
6225
|
+
import { existsSync as existsSync27, readdirSync as readdirSync9, readFileSync as readFileSync25 } from "fs";
|
|
6226
|
+
import { join as join29 } from "path";
|
|
6211
6227
|
|
|
6212
6228
|
// src/agent.ts
|
|
6213
6229
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
6214
|
-
import { readFileSync as
|
|
6230
|
+
import { readFileSync as readFileSync24, existsSync as existsSync26, statSync as statSync8, mkdirSync as mkdirSync18, writeFileSync as writeFileSync19, createWriteStream, openSync as openSync4, closeSync as closeSync4, readSync } from "fs";
|
|
6215
6231
|
import { StringDecoder } from "string_decoder";
|
|
6216
6232
|
import { finished } from "stream/promises";
|
|
6217
|
-
import { join as
|
|
6233
|
+
import { join as join28, dirname as dirname13, basename as basename9 } from "path";
|
|
6218
6234
|
import { createHash as createHash4 } from "crypto";
|
|
6219
6235
|
|
|
6220
6236
|
// src/state.ts
|
|
@@ -7100,6 +7116,143 @@ function renderEnvironment(i) {
|
|
|
7100
7116
|
return lines2.join("\n");
|
|
7101
7117
|
}
|
|
7102
7118
|
|
|
7119
|
+
// src/intel-bridge.ts
|
|
7120
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync19, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "fs";
|
|
7121
|
+
import { dirname as dirname10, join as join21 } from "path";
|
|
7122
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
7123
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
7124
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7125
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7126
|
+
import {
|
|
7127
|
+
CallToolRequestSchema,
|
|
7128
|
+
ListToolsRequestSchema
|
|
7129
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
7130
|
+
var STATUS_TOOL = "code_status";
|
|
7131
|
+
var STATUS_WAITING = "The symbol service is not available yet: the worktree has not been cut. Run `crew worktree` for the ticket; the symbol tools appear in this server afterwards.";
|
|
7132
|
+
var STATUS_LOST = "The symbol service stopped answering, so its tools are unavailable. They return if the service comes back; until then use text search.";
|
|
7133
|
+
function intelDir(stateDir, route2, ticket) {
|
|
7134
|
+
const safe = (s) => s.replace(/[^A-Za-z0-9._-]+/g, "_");
|
|
7135
|
+
return join21(stateDir, "intel", safe(route2), safe(ticket));
|
|
7136
|
+
}
|
|
7137
|
+
var attachFilePath = (stateDir, route2, ticket) => join21(intelDir(stateDir, route2, ticket), "url");
|
|
7138
|
+
function writeAttachFile(file, url) {
|
|
7139
|
+
mkdirSync14(dirname10(file), { recursive: true });
|
|
7140
|
+
writeFileSync15(file, `${url}
|
|
7141
|
+
`);
|
|
7142
|
+
}
|
|
7143
|
+
function readAttachFile(file) {
|
|
7144
|
+
try {
|
|
7145
|
+
const url = readFileSync19(file, "utf8").trim();
|
|
7146
|
+
return /^https?:\/\/\S+$/i.test(url) ? url : void 0;
|
|
7147
|
+
} catch {
|
|
7148
|
+
return void 0;
|
|
7149
|
+
}
|
|
7150
|
+
}
|
|
7151
|
+
var statusTool = {
|
|
7152
|
+
name: STATUS_TOOL,
|
|
7153
|
+
description: "Reports whether this repository's symbol service (find symbols, references, definitions, file outlines) is attached. Before `crew worktree` has been run for the ticket it is not; the symbol tools replace this one once it is.",
|
|
7154
|
+
inputSchema: { type: "object", properties: {} }
|
|
7155
|
+
};
|
|
7156
|
+
var defaultConnect = async (url) => {
|
|
7157
|
+
const client2 = new Client({ name: "crew-intel-bridge", version: "0" }, { capabilities: {} });
|
|
7158
|
+
await client2.connect(new StreamableHTTPClientTransport(new URL(url)));
|
|
7159
|
+
return client2;
|
|
7160
|
+
};
|
|
7161
|
+
function createBridge(o) {
|
|
7162
|
+
const connect2 = o.connectUpstream ?? defaultConnect;
|
|
7163
|
+
const server = new Server(
|
|
7164
|
+
{ name: INTEL_SERVER_NAME, version: o.version ?? "0" },
|
|
7165
|
+
{ capabilities: { tools: { listChanged: true } } }
|
|
7166
|
+
);
|
|
7167
|
+
let upstream;
|
|
7168
|
+
let lost = false;
|
|
7169
|
+
let connecting = false;
|
|
7170
|
+
let timer;
|
|
7171
|
+
const notify2 = () => {
|
|
7172
|
+
void server.sendToolListChanged().catch(() => {
|
|
7173
|
+
});
|
|
7174
|
+
};
|
|
7175
|
+
const drop = () => {
|
|
7176
|
+
const gone = upstream;
|
|
7177
|
+
upstream = void 0;
|
|
7178
|
+
lost = true;
|
|
7179
|
+
if (gone) void gone.close().catch(() => {
|
|
7180
|
+
});
|
|
7181
|
+
notify2();
|
|
7182
|
+
};
|
|
7183
|
+
const status2 = () => ({ content: [{ type: "text", text: lost ? STATUS_LOST : STATUS_WAITING }] });
|
|
7184
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
7185
|
+
if (!upstream) return { tools: [statusTool] };
|
|
7186
|
+
try {
|
|
7187
|
+
return await upstream.listTools();
|
|
7188
|
+
} catch {
|
|
7189
|
+
drop();
|
|
7190
|
+
return { tools: [statusTool] };
|
|
7191
|
+
}
|
|
7192
|
+
});
|
|
7193
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
7194
|
+
if (!upstream) return status2();
|
|
7195
|
+
try {
|
|
7196
|
+
return await upstream.callTool({ name: req.params.name, arguments: req.params.arguments });
|
|
7197
|
+
} catch {
|
|
7198
|
+
drop();
|
|
7199
|
+
return status2();
|
|
7200
|
+
}
|
|
7201
|
+
});
|
|
7202
|
+
const tryAttach = async () => {
|
|
7203
|
+
if (upstream || connecting) return false;
|
|
7204
|
+
const url = readAttachFile(o.attachFile);
|
|
7205
|
+
if (!url) return false;
|
|
7206
|
+
connecting = true;
|
|
7207
|
+
try {
|
|
7208
|
+
const client2 = await connect2(url);
|
|
7209
|
+
client2.onclose = () => {
|
|
7210
|
+
if (upstream === client2) drop();
|
|
7211
|
+
};
|
|
7212
|
+
upstream = client2;
|
|
7213
|
+
lost = false;
|
|
7214
|
+
notify2();
|
|
7215
|
+
return true;
|
|
7216
|
+
} catch {
|
|
7217
|
+
return false;
|
|
7218
|
+
} finally {
|
|
7219
|
+
connecting = false;
|
|
7220
|
+
}
|
|
7221
|
+
};
|
|
7222
|
+
return {
|
|
7223
|
+
server,
|
|
7224
|
+
attached: () => upstream !== void 0,
|
|
7225
|
+
tryAttach,
|
|
7226
|
+
async start(transport) {
|
|
7227
|
+
await server.connect(transport);
|
|
7228
|
+
if (existsSync20(o.attachFile)) await tryAttach();
|
|
7229
|
+
timer = setInterval(() => {
|
|
7230
|
+
void tryAttach();
|
|
7231
|
+
}, o.pollMs ?? 1e3);
|
|
7232
|
+
timer.unref?.();
|
|
7233
|
+
},
|
|
7234
|
+
async stop() {
|
|
7235
|
+
if (timer) clearInterval(timer);
|
|
7236
|
+
const gone = upstream;
|
|
7237
|
+
upstream = void 0;
|
|
7238
|
+
await gone?.close().catch(() => {
|
|
7239
|
+
});
|
|
7240
|
+
await server.close().catch(() => {
|
|
7241
|
+
});
|
|
7242
|
+
}
|
|
7243
|
+
};
|
|
7244
|
+
}
|
|
7245
|
+
async function runBridge(stateDir, route2, ticket) {
|
|
7246
|
+
const bridge = createBridge({ attachFile: attachFilePath(stateDir, route2, ticket) });
|
|
7247
|
+
const closed = new Promise((resolve8) => {
|
|
7248
|
+
bridge.server.onclose = resolve8;
|
|
7249
|
+
process.stdin.on("end", resolve8);
|
|
7250
|
+
});
|
|
7251
|
+
await bridge.start(new StdioServerTransport());
|
|
7252
|
+
await closed;
|
|
7253
|
+
await bridge.stop();
|
|
7254
|
+
}
|
|
7255
|
+
|
|
7103
7256
|
// src/contract.ts
|
|
7104
7257
|
var DEFAULT_CONTRACT = {
|
|
7105
7258
|
columns: {
|
|
@@ -7329,8 +7482,8 @@ var BeforeFirstEditTracker = class {
|
|
|
7329
7482
|
};
|
|
7330
7483
|
|
|
7331
7484
|
// src/agent-log.ts
|
|
7332
|
-
import { existsSync as
|
|
7333
|
-
import { join as
|
|
7485
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync15, readdirSync as readdirSync6, readFileSync as readFileSync20, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "fs";
|
|
7486
|
+
import { join as join22 } from "path";
|
|
7334
7487
|
|
|
7335
7488
|
// src/pending-backoff.ts
|
|
7336
7489
|
var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
|
|
@@ -7533,20 +7686,20 @@ async function addRunToTicket(target, t, recordId, run3) {
|
|
|
7533
7686
|
};
|
|
7534
7687
|
}
|
|
7535
7688
|
}
|
|
7536
|
-
var queueDir = (stateDir) =>
|
|
7689
|
+
var queueDir = (stateDir) => join22(stateDir, "pending-agent-logs");
|
|
7537
7690
|
function queueAgentRun(stateDir, name, q2) {
|
|
7538
|
-
|
|
7539
|
-
|
|
7691
|
+
mkdirSync15(queueDir(stateDir), { recursive: true });
|
|
7692
|
+
writeFileSync16(join22(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
7540
7693
|
}
|
|
7541
7694
|
async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
7542
7695
|
const dir = queueDir(stateDir);
|
|
7543
|
-
if (!
|
|
7696
|
+
if (!existsSync21(dir)) return 0;
|
|
7544
7697
|
let sent = 0;
|
|
7545
7698
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
7546
|
-
const path =
|
|
7699
|
+
const path = join22(dir, f);
|
|
7547
7700
|
let q2;
|
|
7548
7701
|
try {
|
|
7549
|
-
q2 = JSON.parse(
|
|
7702
|
+
q2 = JSON.parse(readFileSync20(path, "utf8"));
|
|
7550
7703
|
} catch {
|
|
7551
7704
|
continue;
|
|
7552
7705
|
}
|
|
@@ -7565,17 +7718,17 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
7565
7718
|
attempts: 0,
|
|
7566
7719
|
nextAttemptAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7567
7720
|
});
|
|
7568
|
-
|
|
7721
|
+
rmSync12(path, { force: true });
|
|
7569
7722
|
sent += 1;
|
|
7570
7723
|
log.emit(`sent a queued Agent Log report (${f})`);
|
|
7571
7724
|
} catch (e) {
|
|
7572
7725
|
if (!(e instanceof AgentLogUnreachableError)) {
|
|
7573
|
-
|
|
7726
|
+
rmSync12(path, { force: true });
|
|
7574
7727
|
log.warn(`dropped a queued Agent Log report (${f}): ${e.message}`);
|
|
7575
7728
|
continue;
|
|
7576
7729
|
}
|
|
7577
7730
|
const attempts = q2.attempts + 1;
|
|
7578
|
-
|
|
7731
|
+
writeFileSync16(path, JSON.stringify({
|
|
7579
7732
|
...q2,
|
|
7580
7733
|
attempts,
|
|
7581
7734
|
warned: true,
|
|
@@ -7586,20 +7739,20 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
7586
7739
|
}
|
|
7587
7740
|
return sent;
|
|
7588
7741
|
}
|
|
7589
|
-
var countersDir = (stateDir) =>
|
|
7742
|
+
var countersDir = (stateDir) => join22(stateDir, "pending-ticket-counters");
|
|
7590
7743
|
function queueTicketCounters(stateDir, name, q2) {
|
|
7591
|
-
|
|
7592
|
-
|
|
7744
|
+
mkdirSync15(countersDir(stateDir), { recursive: true });
|
|
7745
|
+
writeFileSync16(join22(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
7593
7746
|
}
|
|
7594
7747
|
async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
7595
7748
|
const dir = countersDir(stateDir);
|
|
7596
|
-
if (!
|
|
7749
|
+
if (!existsSync21(dir)) return 0;
|
|
7597
7750
|
let sent = 0;
|
|
7598
7751
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
7599
|
-
const path =
|
|
7752
|
+
const path = join22(dir, f);
|
|
7600
7753
|
let q2;
|
|
7601
7754
|
try {
|
|
7602
|
-
q2 = JSON.parse(
|
|
7755
|
+
q2 = JSON.parse(readFileSync20(path, "utf8"));
|
|
7603
7756
|
} catch {
|
|
7604
7757
|
continue;
|
|
7605
7758
|
}
|
|
@@ -7608,17 +7761,17 @@ async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /
|
|
|
7608
7761
|
const apiKey = await keyFor(q2.role);
|
|
7609
7762
|
if (!apiKey) continue;
|
|
7610
7763
|
await writeCounters({ ...q2.target, apiKey }, q2.write);
|
|
7611
|
-
|
|
7764
|
+
rmSync12(path, { force: true });
|
|
7612
7765
|
sent += 1;
|
|
7613
7766
|
log.emit(`added a queued run's spend to ${q2.write.ticketReference ?? q2.write.recordId} (${f})`);
|
|
7614
7767
|
} catch (e) {
|
|
7615
7768
|
if (!isRetryable(e)) {
|
|
7616
|
-
|
|
7769
|
+
rmSync12(path, { force: true });
|
|
7617
7770
|
log.warn(`dropped a queued ticket counter update (${f}): ${e.message}`);
|
|
7618
7771
|
continue;
|
|
7619
7772
|
}
|
|
7620
7773
|
const attempts = q2.attempts + 1;
|
|
7621
|
-
|
|
7774
|
+
writeFileSync16(path, JSON.stringify({
|
|
7622
7775
|
...q2,
|
|
7623
7776
|
attempts,
|
|
7624
7777
|
warned: true,
|
|
@@ -7821,8 +7974,8 @@ var HandoffTranscriptTracker = class {
|
|
|
7821
7974
|
};
|
|
7822
7975
|
|
|
7823
7976
|
// src/pending-handoff.ts
|
|
7824
|
-
import { existsSync as
|
|
7825
|
-
import { join as
|
|
7977
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync16, readdirSync as readdirSync7, readFileSync as readFileSync21, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "fs";
|
|
7978
|
+
import { join as join24 } from "path";
|
|
7826
7979
|
|
|
7827
7980
|
// src/ticket-branch.ts
|
|
7828
7981
|
import { basename as basename7 } from "path";
|
|
@@ -8988,8 +9141,8 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
|
|
|
8988
9141
|
}
|
|
8989
9142
|
|
|
8990
9143
|
// src/run-worktree.ts
|
|
8991
|
-
import { existsSync as
|
|
8992
|
-
import { dirname as
|
|
9144
|
+
import { existsSync as existsSync22 } from "fs";
|
|
9145
|
+
import { dirname as dirname11, join as join23 } from "path";
|
|
8993
9146
|
var describeRunWorktreePlan = (p) => {
|
|
8994
9147
|
switch (p.action) {
|
|
8995
9148
|
case "cut-remote":
|
|
@@ -9030,7 +9183,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
9030
9183
|
const existing = worktreeForNumber(dir, num);
|
|
9031
9184
|
const located = locateBranchForTicket(dir, cfg, t, role);
|
|
9032
9185
|
const baseRef = hasRemote ? `${remote}/${base}` : base;
|
|
9033
|
-
if (existing && !existing.prunable &&
|
|
9186
|
+
if (existing && !existing.prunable && existsSync22(existing.path)) {
|
|
9034
9187
|
const branch = existing.branch;
|
|
9035
9188
|
if (status(existing.path).length > 0) {
|
|
9036
9189
|
return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
|
|
@@ -9046,7 +9199,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
9046
9199
|
if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
|
|
9047
9200
|
return { action: "recut", path: existing.path, branch, base: baseRef };
|
|
9048
9201
|
}
|
|
9049
|
-
const path =
|
|
9202
|
+
const path = join23(dirname11(dir), worktreeForTicket(dir, cfg, t, role));
|
|
9050
9203
|
if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
|
|
9051
9204
|
return { action: "cut-remote", path, branch: located.branch };
|
|
9052
9205
|
}
|
|
@@ -9100,7 +9253,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
9100
9253
|
if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
|
|
9101
9254
|
if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
|
|
9102
9255
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
9103
|
-
if (wt &&
|
|
9256
|
+
if (wt && existsSync22(wt.path)) {
|
|
9104
9257
|
if (status(wt.path).length > 0) return false;
|
|
9105
9258
|
if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
|
|
9106
9259
|
}
|
|
@@ -9108,7 +9261,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
9108
9261
|
}
|
|
9109
9262
|
async function finishRunWorktree(dir, cfg, branch) {
|
|
9110
9263
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
9111
|
-
if (!wt || !
|
|
9264
|
+
if (!wt || !existsSync22(wt.path)) return { kind: "none" };
|
|
9112
9265
|
if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
|
|
9113
9266
|
if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
|
|
9114
9267
|
if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
|
|
@@ -9120,7 +9273,7 @@ async function finishRunWorktree(dir, cfg, branch) {
|
|
|
9120
9273
|
if (pids.length) await killGently(pids);
|
|
9121
9274
|
}
|
|
9122
9275
|
gitOk(dir, ["worktree", "remove", wt.path]);
|
|
9123
|
-
return
|
|
9276
|
+
return existsSync22(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
|
|
9124
9277
|
}
|
|
9125
9278
|
|
|
9126
9279
|
// src/handoff-guard.ts
|
|
@@ -9503,20 +9656,20 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
|
|
|
9503
9656
|
|
|
9504
9657
|
// src/pending-handoff.ts
|
|
9505
9658
|
var PENDING_MAX_AGE_MS = 48 * 36e5;
|
|
9506
|
-
var pendingDirFor = (stateDir) =>
|
|
9507
|
-
var stateDirOfStream = (streamPath) =>
|
|
9659
|
+
var pendingDirFor = (stateDir) => join24(stateDir, "pending-handoffs");
|
|
9660
|
+
var stateDirOfStream = (streamPath) => join24(streamPath, "..", "..");
|
|
9508
9661
|
function writePendingHandoff(stateDir, rec) {
|
|
9509
9662
|
const dir = pendingDirFor(stateDir);
|
|
9510
|
-
|
|
9511
|
-
|
|
9663
|
+
mkdirSync16(dir, { recursive: true });
|
|
9664
|
+
writeFileSync17(join24(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
|
|
9512
9665
|
}
|
|
9513
9666
|
function listPendingHandoffs(stateDir, route2) {
|
|
9514
9667
|
const dir = pendingDirFor(stateDir);
|
|
9515
|
-
if (!
|
|
9668
|
+
if (!existsSync23(dir)) return [];
|
|
9516
9669
|
const out = [];
|
|
9517
9670
|
for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
9518
9671
|
try {
|
|
9519
|
-
const rec = JSON.parse(
|
|
9672
|
+
const rec = JSON.parse(readFileSync21(join24(dir, f), "utf8"));
|
|
9520
9673
|
if (rec.version === 1 && rec.ticketRecordId && (!route2 || rec.route === route2)) out.push(rec);
|
|
9521
9674
|
} catch {
|
|
9522
9675
|
}
|
|
@@ -9524,7 +9677,7 @@ function listPendingHandoffs(stateDir, route2) {
|
|
|
9524
9677
|
return out;
|
|
9525
9678
|
}
|
|
9526
9679
|
function removePendingHandoff(stateDir, ticketRecordId) {
|
|
9527
|
-
|
|
9680
|
+
rmSync13(join24(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
|
|
9528
9681
|
}
|
|
9529
9682
|
function pendingHandoffFor(stateDir, ticketRecordId) {
|
|
9530
9683
|
return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
|
|
@@ -9634,8 +9787,8 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
|
|
|
9634
9787
|
}
|
|
9635
9788
|
|
|
9636
9789
|
// src/skills.ts
|
|
9637
|
-
import { readFileSync as
|
|
9638
|
-
import { join as
|
|
9790
|
+
import { readFileSync as readFileSync23, readdirSync as readdirSync8, existsSync as existsSync25 } from "fs";
|
|
9791
|
+
import { join as join26 } from "path";
|
|
9639
9792
|
import { parse as parseYaml } from "yaml";
|
|
9640
9793
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
9641
9794
|
|
|
@@ -9651,8 +9804,8 @@ ${additions}`;
|
|
|
9651
9804
|
}
|
|
9652
9805
|
|
|
9653
9806
|
// src/agents.ts
|
|
9654
|
-
import { readFileSync as
|
|
9655
|
-
import { join as
|
|
9807
|
+
import { readFileSync as readFileSync22, existsSync as existsSync24 } from "fs";
|
|
9808
|
+
import { join as join25 } from "path";
|
|
9656
9809
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
|
|
9657
9810
|
var PERSONA_NAME = {
|
|
9658
9811
|
dev: "Developer",
|
|
@@ -9666,12 +9819,12 @@ var CREW_AGENT_COLUMN = "agent_id";
|
|
|
9666
9819
|
var AgentsSyncError = class extends Error {
|
|
9667
9820
|
};
|
|
9668
9821
|
function personaDefaultPrompt(promptsDir, role) {
|
|
9669
|
-
const briefPath =
|
|
9670
|
-
if (!
|
|
9671
|
-
if (SELF_CONTAINED_ROLES.has(role)) return
|
|
9672
|
-
const commonPath =
|
|
9673
|
-
if (!
|
|
9674
|
-
return `${
|
|
9822
|
+
const briefPath = join25(promptsDir, "personas", `lane-${role}.md`);
|
|
9823
|
+
if (!existsSync24(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
|
|
9824
|
+
if (SELF_CONTAINED_ROLES.has(role)) return readFileSync22(briefPath, "utf8");
|
|
9825
|
+
const commonPath = join25(promptsDir, "personas", "common.md");
|
|
9826
|
+
if (!existsSync24(commonPath)) return readFileSync22(briefPath, "utf8");
|
|
9827
|
+
return `${readFileSync22(commonPath, "utf8")}${readFileSync22(briefPath, "utf8")}`;
|
|
9675
9828
|
}
|
|
9676
9829
|
async function resolveAgentId(route2, role, opts) {
|
|
9677
9830
|
const cached = route2.resolved?.agentPersonas?.[role]?.agentId;
|
|
@@ -10118,7 +10271,7 @@ function describeCrewLink(o) {
|
|
|
10118
10271
|
var SkillSyncError = class extends Error {
|
|
10119
10272
|
};
|
|
10120
10273
|
function parseSkillFile(path) {
|
|
10121
|
-
const text2 =
|
|
10274
|
+
const text2 = readFileSync23(path, "utf8");
|
|
10122
10275
|
const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text2);
|
|
10123
10276
|
if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
|
|
10124
10277
|
const frontmatter = match[1];
|
|
@@ -10131,9 +10284,9 @@ function parseSkillFile(path) {
|
|
|
10131
10284
|
return { name: meta.name, description: meta.description, prompt: body, file: path };
|
|
10132
10285
|
}
|
|
10133
10286
|
function listLocalSkills(promptsDir) {
|
|
10134
|
-
const dir =
|
|
10135
|
-
if (!
|
|
10136
|
-
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(
|
|
10287
|
+
const dir = join26(promptsDir, "skills");
|
|
10288
|
+
if (!existsSync25(dir)) return [];
|
|
10289
|
+
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join26(dir, f)));
|
|
10137
10290
|
}
|
|
10138
10291
|
async function syncSkills(route2, opts) {
|
|
10139
10292
|
if (!route2.resolved) {
|
|
@@ -10318,9 +10471,9 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
|
|
|
10318
10471
|
|
|
10319
10472
|
// src/runner.ts
|
|
10320
10473
|
import { spawn as spawn3, execFile, execFileSync as execFileSync6 } from "child_process";
|
|
10321
|
-
import { mkdtempSync, mkdirSync as
|
|
10474
|
+
import { mkdtempSync, mkdirSync as mkdirSync17, writeFileSync as writeFileSync18, rmSync as rmSync14 } from "fs";
|
|
10322
10475
|
import { tmpdir as tmpdir2 } from "os";
|
|
10323
|
-
import { basename as basename8, dirname as
|
|
10476
|
+
import { basename as basename8, dirname as dirname12, join as join27 } from "path";
|
|
10324
10477
|
import { promisify } from "util";
|
|
10325
10478
|
|
|
10326
10479
|
// src/run-limit.ts
|
|
@@ -10449,8 +10602,8 @@ var SandboxRunner = class {
|
|
|
10449
10602
|
throw new Error(`cannot generate the sandbox policy: ${e.message}`);
|
|
10450
10603
|
}
|
|
10451
10604
|
policyFile = policyPathFor(plan.streamPath);
|
|
10452
|
-
|
|
10453
|
-
|
|
10605
|
+
mkdirSync17(dirname12(policyFile), { recursive: true });
|
|
10606
|
+
writeFileSync18(policyFile, generated.yaml, { mode: 384 });
|
|
10454
10607
|
}
|
|
10455
10608
|
const create2 = ["sandbox", "create", "--name", name, "--detach"];
|
|
10456
10609
|
if (policyFile) create2.push("--policy", policyFile);
|
|
@@ -10493,13 +10646,13 @@ var SandboxRunner = class {
|
|
|
10493
10646
|
const harness = cfg.agentBin ?? basename8(plan.bin);
|
|
10494
10647
|
const viaUpload = Buffer.byteLength(plan.prompt) > SANDBOX_STDIN_LIMIT;
|
|
10495
10648
|
if (viaUpload) {
|
|
10496
|
-
const tmp = mkdtempSync(
|
|
10649
|
+
const tmp = mkdtempSync(join27(tmpdir2(), "crew-prompt-"));
|
|
10497
10650
|
try {
|
|
10498
|
-
const file =
|
|
10499
|
-
|
|
10651
|
+
const file = join27(tmp, "prompt.txt");
|
|
10652
|
+
writeFileSync18(file, plan.prompt, { mode: 384 });
|
|
10500
10653
|
await this.os(["sandbox", "upload", name, file, SANDBOX_PROMPT_PATH]);
|
|
10501
10654
|
} finally {
|
|
10502
|
-
|
|
10655
|
+
rmSync14(tmp, { recursive: true, force: true });
|
|
10503
10656
|
}
|
|
10504
10657
|
}
|
|
10505
10658
|
const withStubs = `PATH=${shQuote2(SANDBOX_STUB_DIR)}:"$PATH"; export PATH; `;
|
|
@@ -10649,7 +10802,7 @@ var ALLOWANCE_REFUSAL_MARKER = "AI_ALLOWANCE_EXCEEDED";
|
|
|
10649
10802
|
function streamShowsAllowanceRefusal(streamPath) {
|
|
10650
10803
|
let raw;
|
|
10651
10804
|
try {
|
|
10652
|
-
raw =
|
|
10805
|
+
raw = readFileSync24(streamPath, "utf8");
|
|
10653
10806
|
} catch {
|
|
10654
10807
|
return false;
|
|
10655
10808
|
}
|
|
@@ -10672,26 +10825,49 @@ function streamShowsAllowanceRefusal(streamPath) {
|
|
|
10672
10825
|
}
|
|
10673
10826
|
return false;
|
|
10674
10827
|
}
|
|
10675
|
-
var digestPathFor = (stateDir, routeLabel, role, runId) =>
|
|
10828
|
+
var digestPathFor = (stateDir, routeLabel, role, runId) => join28(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
10829
|
+
function codeBridgeFor(o, harness) {
|
|
10830
|
+
if (o.ship.runner === "sandbox" || !o.ticket) return void 0;
|
|
10831
|
+
const number = /(\d+)$/.exec(o.ticket)?.[1];
|
|
10832
|
+
if (!number) return void 0;
|
|
10833
|
+
let intel;
|
|
10834
|
+
try {
|
|
10835
|
+
const repos = reposOf(o.route);
|
|
10836
|
+
const target = repos.find((r) => r.name === o.repo) ?? (repos.length === 1 ? repos[0] : void 0);
|
|
10837
|
+
intel = target ? loadRepoConfig(target.dir)?.hooks?.intel : void 0;
|
|
10838
|
+
} catch {
|
|
10839
|
+
}
|
|
10840
|
+
if (!wantsCodeBridge(intel)) return void 0;
|
|
10841
|
+
const args = [...process.execArgv, process.argv[1] ?? "crew", "intel", "bridge", o.route.route, number, "--state-dir", o.stateDir];
|
|
10842
|
+
const bridge = { command: process.execPath, args };
|
|
10843
|
+
if (harness === "gemini") {
|
|
10844
|
+
const file = join28(intelDir(o.stateDir, o.route.route, number), "gemini-settings.json");
|
|
10845
|
+
mkdirSync18(dirname13(file), { recursive: true });
|
|
10846
|
+
writeFileSync19(file, `${JSON.stringify({ mcpServers: { [INTEL_SERVER_NAME]: { command: bridge.command, args } } }, null, 2)}
|
|
10847
|
+
`);
|
|
10848
|
+
bridge.settingsFile = file;
|
|
10849
|
+
}
|
|
10850
|
+
return bridge;
|
|
10851
|
+
}
|
|
10676
10852
|
function planAgentRun(o) {
|
|
10677
|
-
const briefPath =
|
|
10678
|
-
if (!
|
|
10853
|
+
const briefPath = join28(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
10854
|
+
if (!existsSync26(briefPath)) {
|
|
10679
10855
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
10680
10856
|
}
|
|
10681
|
-
const commonPath =
|
|
10682
|
-
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) &&
|
|
10857
|
+
const commonPath = join28(o.route.promptsDir, "personas", "common.md");
|
|
10858
|
+
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync26(commonPath);
|
|
10683
10859
|
const now = o.now ?? (() => Date.now());
|
|
10684
10860
|
const routeLabel = routeSlug(o.route.route);
|
|
10685
10861
|
const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
|
|
10686
|
-
const digestPath2 = ownDigest &&
|
|
10862
|
+
const digestPath2 = ownDigest && existsSync26(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
|
|
10687
10863
|
let digest;
|
|
10688
10864
|
let ageSeconds;
|
|
10689
|
-
if (
|
|
10865
|
+
if (existsSync26(digestPath2)) {
|
|
10690
10866
|
ageSeconds = Math.floor((now() - statSync8(digestPath2).mtimeMs) / 1e3);
|
|
10691
|
-
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest =
|
|
10867
|
+
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync24(digestPath2, "utf8");
|
|
10692
10868
|
}
|
|
10693
|
-
const common = usesCommon ?
|
|
10694
|
-
const brief =
|
|
10869
|
+
const common = usesCommon ? readFileSync24(commonPath, "utf8") : "";
|
|
10870
|
+
const brief = readFileSync24(briefPath, "utf8");
|
|
10695
10871
|
const effectiveCommon = o.divergedPrompt ? "" : common;
|
|
10696
10872
|
const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
|
|
10697
10873
|
const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
|
|
@@ -10714,7 +10890,7 @@ function planAgentRun(o) {
|
|
|
10714
10890
|
digest
|
|
10715
10891
|
});
|
|
10716
10892
|
const promptSha = createHash4("sha256").update(expanded.text).digest("hex");
|
|
10717
|
-
const streamsDir =
|
|
10893
|
+
const streamsDir = join28(o.stateDir, "streams");
|
|
10718
10894
|
const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
|
|
10719
10895
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
10720
10896
|
const { model } = seat;
|
|
@@ -10729,7 +10905,9 @@ function planAgentRun(o) {
|
|
|
10729
10905
|
toolchain = { ...toolchain, inference: { baseUrl: inferenceBaseUrl(o.route.baseUrl, o.route.resolved.workspaceId), token: o.apiKey } };
|
|
10730
10906
|
}
|
|
10731
10907
|
const adapter = adapterFor(toolchain.harness);
|
|
10908
|
+
const code = codeBridgeFor(o, adapter.harness);
|
|
10732
10909
|
const invocation = adapter.invocation(toolchain, {
|
|
10910
|
+
code,
|
|
10733
10911
|
role: o.role,
|
|
10734
10912
|
model,
|
|
10735
10913
|
pluginDirs: adapter.harness === "claude" ? lspPluginDirsFor(o.route, o.repo, o.stateDir) : void 0,
|
|
@@ -10788,7 +10966,7 @@ function planAgentRun(o) {
|
|
|
10788
10966
|
unsetEnv: adapter.unsetEnv,
|
|
10789
10967
|
setEnv: {
|
|
10790
10968
|
...o.apiKey ? { [API_KEY_VAR]: o.apiKey } : {},
|
|
10791
|
-
...adapter.runEnv(toolchain),
|
|
10969
|
+
...adapter.runEnv(toolchain, { code }),
|
|
10792
10970
|
// The operator's global SessionStart hook (pair-context-hook.sh) injects
|
|
10793
10971
|
// the interactive Pair persona's prompt for any session it can't tell
|
|
10794
10972
|
// apart from a person's own terminal. Every headless lane run this
|
|
@@ -10805,8 +10983,8 @@ function planAgentRun(o) {
|
|
|
10805
10983
|
digestAttached: digest !== void 0,
|
|
10806
10984
|
digestAgeSeconds: ageSeconds,
|
|
10807
10985
|
model,
|
|
10808
|
-
streamPath:
|
|
10809
|
-
eventsPath:
|
|
10986
|
+
streamPath: join28(streamsDir, `${base}.jsonl`),
|
|
10987
|
+
eventsPath: join28(streamsDir, `${base}.events.jsonl`),
|
|
10810
10988
|
ticket: o.ticket,
|
|
10811
10989
|
ticketRecordId: o.ticketRecordId ?? null,
|
|
10812
10990
|
repo: o.repo ?? null,
|
|
@@ -10837,7 +11015,7 @@ function describePlan(p) {
|
|
|
10837
11015
|
var pidPathFor = (streamPath) => `${streamPath}.pid`;
|
|
10838
11016
|
function readRunSidecar(streamPath) {
|
|
10839
11017
|
try {
|
|
10840
|
-
const sc = JSON.parse(
|
|
11018
|
+
const sc = JSON.parse(readFileSync24(pidPathFor(streamPath), "utf8"));
|
|
10841
11019
|
return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
|
|
10842
11020
|
} catch {
|
|
10843
11021
|
return null;
|
|
@@ -10884,7 +11062,7 @@ async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMi
|
|
|
10884
11062
|
var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
|
|
10885
11063
|
function openSink(path) {
|
|
10886
11064
|
try {
|
|
10887
|
-
|
|
11065
|
+
mkdirSync18(dirname13(path), { recursive: true });
|
|
10888
11066
|
return createWriteStream(path, { flags: "a" });
|
|
10889
11067
|
} catch {
|
|
10890
11068
|
return void 0;
|
|
@@ -11070,7 +11248,7 @@ var RunObserver = class {
|
|
|
11070
11248
|
};
|
|
11071
11249
|
function writeRunMeta(id, startedAt, liveTicket, extra) {
|
|
11072
11250
|
try {
|
|
11073
|
-
|
|
11251
|
+
writeFileSync19(`${id.streamPath}.meta.json`, JSON.stringify({
|
|
11074
11252
|
route: id.route,
|
|
11075
11253
|
role: id.role,
|
|
11076
11254
|
hint: id.ticket,
|
|
@@ -11236,7 +11414,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
11236
11414
|
const id = identityOf(plan, adapter.harness);
|
|
11237
11415
|
let streamFd;
|
|
11238
11416
|
try {
|
|
11239
|
-
|
|
11417
|
+
mkdirSync18(dirname13(plan.streamPath), { recursive: true });
|
|
11240
11418
|
streamFd = openSync4(plan.streamPath, "a");
|
|
11241
11419
|
} catch (e) {
|
|
11242
11420
|
void endSink(eventsSink);
|
|
@@ -11294,7 +11472,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
11294
11472
|
promptSha: plan.promptSha
|
|
11295
11473
|
};
|
|
11296
11474
|
try {
|
|
11297
|
-
|
|
11475
|
+
writeFileSync19(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
|
|
11298
11476
|
} catch {
|
|
11299
11477
|
}
|
|
11300
11478
|
}
|
|
@@ -11408,21 +11586,21 @@ var pidAlive = (pid) => {
|
|
|
11408
11586
|
};
|
|
11409
11587
|
function runFinished(streamPath) {
|
|
11410
11588
|
const path = `${streamPath}.meta.json`;
|
|
11411
|
-
if (!
|
|
11589
|
+
if (!existsSync27(path)) return false;
|
|
11412
11590
|
try {
|
|
11413
|
-
return JSON.parse(
|
|
11591
|
+
return JSON.parse(readFileSync25(path, "utf8")).running !== true;
|
|
11414
11592
|
} catch {
|
|
11415
11593
|
return true;
|
|
11416
11594
|
}
|
|
11417
11595
|
}
|
|
11418
11596
|
function orphanedStreams(o) {
|
|
11419
|
-
const dir =
|
|
11420
|
-
if (!
|
|
11597
|
+
const dir = join29(o.stateDir, "streams");
|
|
11598
|
+
if (!existsSync27(dir)) return [];
|
|
11421
11599
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
11422
11600
|
const out = [];
|
|
11423
11601
|
for (const name of readdirSync9(dir)) {
|
|
11424
11602
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
11425
|
-
const streamPath =
|
|
11603
|
+
const streamPath = join29(dir, name);
|
|
11426
11604
|
if (runFinished(streamPath)) continue;
|
|
11427
11605
|
const sidecar = readRunSidecar(streamPath);
|
|
11428
11606
|
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
@@ -11475,15 +11653,15 @@ async function reconcileRuns(o) {
|
|
|
11475
11653
|
}
|
|
11476
11654
|
|
|
11477
11655
|
// src/live-runs.ts
|
|
11478
|
-
import { existsSync as
|
|
11479
|
-
import { join as
|
|
11656
|
+
import { existsSync as existsSync28, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
|
|
11657
|
+
import { join as join30 } from "path";
|
|
11480
11658
|
function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
11481
|
-
const dir =
|
|
11482
|
-
if (!
|
|
11659
|
+
const dir = join30(stateDir, "streams");
|
|
11660
|
+
if (!existsSync28(dir)) return [];
|
|
11483
11661
|
const out = [];
|
|
11484
11662
|
for (const name of readdirSync10(dir)) {
|
|
11485
11663
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
11486
|
-
const streamPath =
|
|
11664
|
+
const streamPath = join30(dir, name);
|
|
11487
11665
|
if (runFinished(streamPath)) continue;
|
|
11488
11666
|
const sidecar = readRunSidecar(streamPath);
|
|
11489
11667
|
if (!sidecar || !isAlive(sidecar)) continue;
|
|
@@ -11493,12 +11671,12 @@ function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
|
11493
11671
|
}
|
|
11494
11672
|
function runActivity(stateDir, isAlive = runIsAlive) {
|
|
11495
11673
|
const activeRuns = liveRuns(stateDir, isAlive).length;
|
|
11496
|
-
const dir =
|
|
11674
|
+
const dir = join30(stateDir, "streams");
|
|
11497
11675
|
let newest = 0;
|
|
11498
|
-
if (
|
|
11676
|
+
if (existsSync28(dir)) {
|
|
11499
11677
|
for (const name of readdirSync10(dir)) {
|
|
11500
11678
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
11501
|
-
const streamPath =
|
|
11679
|
+
const streamPath = join30(dir, name);
|
|
11502
11680
|
if (!runFinished(streamPath)) continue;
|
|
11503
11681
|
try {
|
|
11504
11682
|
newest = Math.max(newest, statSync9(`${streamPath}.meta.json`).mtimeMs);
|
|
@@ -11869,7 +12047,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
11869
12047
|
if (host === "windows") return [];
|
|
11870
12048
|
const ctx = installContext();
|
|
11871
12049
|
const systemd = detectSystemd();
|
|
11872
|
-
return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) =>
|
|
12050
|
+
return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync29(p));
|
|
11873
12051
|
};
|
|
11874
12052
|
return {
|
|
11875
12053
|
liveRuns: () => liveRuns(stateDir).map((r) => ({ role: r.role, route: r.route, pid: r.pid, ticketRecordId: r.ticketRecordId ?? void 0, streamPath: r.streamPath })),
|
|
@@ -11930,7 +12108,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
11930
12108
|
},
|
|
11931
12109
|
remaining() {
|
|
11932
12110
|
const left = [];
|
|
11933
|
-
if (
|
|
12111
|
+
if (existsSync29(cfg.configFile)) left.push(`config ${cfg.configFile}`);
|
|
11934
12112
|
for (const h of forgeHosts()) if (hasForgeToken(cfg.ship.forges, h, stateDir)) left.push(`forge token for ${h}`);
|
|
11935
12113
|
try {
|
|
11936
12114
|
for (const k of readdirSync11(keysDir(stateDir))) left.push(`seat key ${k}`);
|
|
@@ -12538,19 +12716,19 @@ function planSandbox(repo, decl, warm = {}) {
|
|
|
12538
12716
|
const layer = repoLayer(repo, decl, crew, warm);
|
|
12539
12717
|
return { repo, tag: layer.tag, claude: SANDBOX_CLAUDE_VERSION, layers: [base, crew, layer] };
|
|
12540
12718
|
}
|
|
12541
|
-
var sandboxStatePath = (stateDir) =>
|
|
12719
|
+
var sandboxStatePath = (stateDir) => join31(stateDir, "sandbox-images.json");
|
|
12542
12720
|
function readSandboxState(stateDir) {
|
|
12543
12721
|
try {
|
|
12544
|
-
const v = JSON.parse(
|
|
12722
|
+
const v = JSON.parse(readFileSync26(sandboxStatePath(stateDir), "utf8"));
|
|
12545
12723
|
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
12546
12724
|
} catch {
|
|
12547
12725
|
return {};
|
|
12548
12726
|
}
|
|
12549
12727
|
}
|
|
12550
12728
|
function writeSandboxState(stateDir, state) {
|
|
12551
|
-
|
|
12729
|
+
mkdirSync19(stateDir, { recursive: true });
|
|
12552
12730
|
const file = sandboxStatePath(stateDir);
|
|
12553
|
-
|
|
12731
|
+
writeFileSync20(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
|
|
12554
12732
|
`);
|
|
12555
12733
|
renameSync3(`${file}.tmp`, file);
|
|
12556
12734
|
}
|
|
@@ -12570,7 +12748,7 @@ var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((re
|
|
|
12570
12748
|
child.stdout.on("data", (d) => chunks.push(d));
|
|
12571
12749
|
child.stderr.on("data", (d) => chunks.push(d));
|
|
12572
12750
|
const done = (code, extra = "") => {
|
|
12573
|
-
|
|
12751
|
+
writeFileSync20(logPath, `$ ${engine} ${args.join(" ")}
|
|
12574
12752
|
${Buffer.concat(chunks).toString("utf8")}${extra}`, { flag: "a" });
|
|
12575
12753
|
resolve8(code);
|
|
12576
12754
|
};
|
|
@@ -12592,7 +12770,7 @@ var engineRemoveImage = (engine = "docker", exec = realExec2) => (tag) => {
|
|
|
12592
12770
|
} catch {
|
|
12593
12771
|
}
|
|
12594
12772
|
};
|
|
12595
|
-
var sandboxLogDir = (stateDir) =>
|
|
12773
|
+
var sandboxLogDir = (stateDir) => join31(stateDir, "sandbox-logs");
|
|
12596
12774
|
async function buildSandboxImage(plan, deps) {
|
|
12597
12775
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
12598
12776
|
const state = readSandboxState(deps.stateDir);
|
|
@@ -12601,20 +12779,20 @@ async function buildSandboxImage(plan, deps) {
|
|
|
12601
12779
|
return { kind: "reused", tag: plan.tag };
|
|
12602
12780
|
}
|
|
12603
12781
|
const logDir = sandboxLogDir(deps.stateDir);
|
|
12604
|
-
|
|
12605
|
-
const logPath =
|
|
12782
|
+
mkdirSync19(logDir, { recursive: true });
|
|
12783
|
+
const logPath = join31(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
|
|
12606
12784
|
const builtLayers = [];
|
|
12607
12785
|
for (const layer of plan.layers) {
|
|
12608
12786
|
if (deps.hasImage(layer.tag)) continue;
|
|
12609
|
-
const ctx =
|
|
12610
|
-
|
|
12787
|
+
const ctx = join31(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
|
|
12788
|
+
mkdirSync19(ctx, { recursive: true });
|
|
12611
12789
|
try {
|
|
12612
|
-
|
|
12790
|
+
writeFileSync20(join31(ctx, "Dockerfile"), layer.dockerfile);
|
|
12613
12791
|
for (const [name, content] of Object.entries(layer.files)) {
|
|
12614
|
-
|
|
12615
|
-
|
|
12792
|
+
mkdirSync19(dirname14(join31(ctx, name)), { recursive: true });
|
|
12793
|
+
writeFileSync20(join31(ctx, name), content);
|
|
12616
12794
|
}
|
|
12617
|
-
const code = await deps.run(["build", "--tag", layer.tag, "--file",
|
|
12795
|
+
const code = await deps.run(["build", "--tag", layer.tag, "--file", join31(ctx, "Dockerfile"), ctx], logPath);
|
|
12618
12796
|
if (code !== 0) {
|
|
12619
12797
|
state[plan.repo] = { ...entry, failure: { tag: plan.tag, at: now().toISOString(), logPath } };
|
|
12620
12798
|
writeSandboxState(deps.stateDir, state);
|
|
@@ -12622,7 +12800,7 @@ async function buildSandboxImage(plan, deps) {
|
|
|
12622
12800
|
}
|
|
12623
12801
|
builtLayers.push(layer.name);
|
|
12624
12802
|
} finally {
|
|
12625
|
-
|
|
12803
|
+
rmSync15(ctx, { recursive: true, force: true });
|
|
12626
12804
|
}
|
|
12627
12805
|
}
|
|
12628
12806
|
const tags = [{ tag: plan.tag, builtAt: now().toISOString(), claude: plan.claude }, ...entry.tags.filter((t) => t.tag !== plan.tag)];
|
|
@@ -12803,9 +12981,9 @@ function managedRuntimeWarnings(kind, ship) {
|
|
|
12803
12981
|
|
|
12804
12982
|
// src/selftest.ts
|
|
12805
12983
|
import { spawn as spawn6 } from "child_process";
|
|
12806
|
-
import { existsSync as
|
|
12984
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync2, readFileSync as readFileSync27, rmSync as rmSync16, writeFileSync as writeFileSync21 } from "fs";
|
|
12807
12985
|
import { tmpdir as tmpdir4 } from "os";
|
|
12808
|
-
import { join as
|
|
12986
|
+
import { join as join32 } from "path";
|
|
12809
12987
|
var SELFTEST_TIMEOUT_MS = 12e4;
|
|
12810
12988
|
var KEY_VAR = "CREW_API_KEY";
|
|
12811
12989
|
var SELFTEST_PROMPT = "This is a read-only connectivity check. Use the tracker MCP tool to read this workspace (the workspaces find-one tool, using the workspace id below) and reply with the workspace's name alone, on one line. Do not write anything, do not create files, and do not use any other tool.\n\nWorkspace id: ";
|
|
@@ -12966,7 +13144,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
12966
13144
|
...adapter.runEnv(spec),
|
|
12967
13145
|
[KEY_VAR]: i.apiKey
|
|
12968
13146
|
};
|
|
12969
|
-
const cwd = mkdtempSync2(
|
|
13147
|
+
const cwd = mkdtempSync2(join32(tmpdir4(), "crew-selftest-"));
|
|
12970
13148
|
try {
|
|
12971
13149
|
const run3 = await deps.run({
|
|
12972
13150
|
bin: invocation.bin,
|
|
@@ -12988,7 +13166,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
12988
13166
|
return finish({ ok: false, failure, detail, ...usage2 });
|
|
12989
13167
|
} finally {
|
|
12990
13168
|
try {
|
|
12991
|
-
|
|
13169
|
+
rmSync16(cwd, { recursive: true, force: true });
|
|
12992
13170
|
} catch {
|
|
12993
13171
|
}
|
|
12994
13172
|
}
|
|
@@ -12999,11 +13177,11 @@ function formatResult(r) {
|
|
|
12999
13177
|
const secs = `${(r.ms / 1e3).toFixed(1)}s`;
|
|
13000
13178
|
return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
|
|
13001
13179
|
}
|
|
13002
|
-
var fileFor2 = (stateDir) =>
|
|
13180
|
+
var fileFor2 = (stateDir) => join32(stateDir, "selftest.json");
|
|
13003
13181
|
function readSelftest(stateDir) {
|
|
13004
13182
|
try {
|
|
13005
13183
|
const p = fileFor2(stateDir);
|
|
13006
|
-
return
|
|
13184
|
+
return existsSync30(p) ? JSON.parse(readFileSync27(p, "utf8")) : {};
|
|
13007
13185
|
} catch {
|
|
13008
13186
|
return {};
|
|
13009
13187
|
}
|
|
@@ -13011,8 +13189,8 @@ function readSelftest(stateDir) {
|
|
|
13011
13189
|
function recordSelftest(stateDir, results) {
|
|
13012
13190
|
const all = readSelftest(stateDir);
|
|
13013
13191
|
for (const r of results) all[r.toolchain] = r;
|
|
13014
|
-
|
|
13015
|
-
|
|
13192
|
+
mkdirSync20(stateDir, { recursive: true });
|
|
13193
|
+
writeFileSync21(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
|
|
13016
13194
|
`);
|
|
13017
13195
|
}
|
|
13018
13196
|
function doctorSelftestLines(stored, toolchains) {
|
|
@@ -13024,7 +13202,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
13024
13202
|
}
|
|
13025
13203
|
|
|
13026
13204
|
// src/cli.ts
|
|
13027
|
-
import { resolve as resolve7, dirname as
|
|
13205
|
+
import { resolve as resolve7, dirname as dirname33, join as join54, basename as basename16 } from "path";
|
|
13028
13206
|
import { fileURLToPath } from "url";
|
|
13029
13207
|
import { hostname as hostname4, homedir as homedir11 } from "os";
|
|
13030
13208
|
import { spawnSync as spawnSync11 } from "child_process";
|
|
@@ -13032,6 +13210,7 @@ import { parse as parseYaml2 } from "yaml";
|
|
|
13032
13210
|
|
|
13033
13211
|
// src/argv.ts
|
|
13034
13212
|
var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
13213
|
+
"state-dir",
|
|
13035
13214
|
"agent-bin",
|
|
13036
13215
|
"area",
|
|
13037
13216
|
"base-url",
|
|
@@ -13221,14 +13400,14 @@ function rosterMarkdown(roster, meId) {
|
|
|
13221
13400
|
}
|
|
13222
13401
|
|
|
13223
13402
|
// src/connect-resume.ts
|
|
13224
|
-
import { mkdirSync as
|
|
13225
|
-
import { dirname as
|
|
13403
|
+
import { mkdirSync as mkdirSync21, readFileSync as readFileSync28, rmSync as rmSync17, writeFileSync as writeFileSync22 } from "fs";
|
|
13404
|
+
import { dirname as dirname15, join as join33 } from "path";
|
|
13226
13405
|
function lastSessionFile(stateDir) {
|
|
13227
|
-
return
|
|
13406
|
+
return join33(stateDir, "connect-session.json");
|
|
13228
13407
|
}
|
|
13229
13408
|
function readPointers(stateDir) {
|
|
13230
13409
|
try {
|
|
13231
|
-
const parsed = JSON.parse(
|
|
13410
|
+
const parsed = JSON.parse(readFileSync28(lastSessionFile(stateDir), "utf8"));
|
|
13232
13411
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
13233
13412
|
const out = {};
|
|
13234
13413
|
for (const [host, v] of Object.entries(parsed)) {
|
|
@@ -13242,8 +13421,8 @@ function readPointers(stateDir) {
|
|
|
13242
13421
|
}
|
|
13243
13422
|
function writePointers(stateDir, pointers) {
|
|
13244
13423
|
const file = lastSessionFile(stateDir);
|
|
13245
|
-
|
|
13246
|
-
|
|
13424
|
+
mkdirSync21(dirname15(file), { recursive: true });
|
|
13425
|
+
writeFileSync22(file, `${JSON.stringify(pointers, null, 2)}
|
|
13247
13426
|
`, { mode: 384 });
|
|
13248
13427
|
}
|
|
13249
13428
|
function rememberSession(stateDir, host, workspaceSlug) {
|
|
@@ -13260,7 +13439,7 @@ function forgetWorkspace(stateDir, host, workspaceSlug) {
|
|
|
13260
13439
|
const { [host]: _old, ...rest } = all;
|
|
13261
13440
|
const next = left.length ? { ...rest, [host]: left } : rest;
|
|
13262
13441
|
if (Object.keys(next).length) writePointers(stateDir, next);
|
|
13263
|
-
else
|
|
13442
|
+
else rmSync17(lastSessionFile(stateDir), { force: true });
|
|
13264
13443
|
} catch {
|
|
13265
13444
|
}
|
|
13266
13445
|
}
|
|
@@ -13300,7 +13479,7 @@ function forgetSession(stateDir, host) {
|
|
|
13300
13479
|
try {
|
|
13301
13480
|
const { [host]: _gone, ...rest } = readPointers(stateDir);
|
|
13302
13481
|
if (Object.keys(rest).length) writePointers(stateDir, rest);
|
|
13303
|
-
else
|
|
13482
|
+
else rmSync17(lastSessionFile(stateDir), { force: true });
|
|
13304
13483
|
} catch {
|
|
13305
13484
|
}
|
|
13306
13485
|
}
|
|
@@ -13328,15 +13507,15 @@ async function signOutSessions(o) {
|
|
|
13328
13507
|
}
|
|
13329
13508
|
|
|
13330
13509
|
// src/reset.ts
|
|
13331
|
-
import { existsSync as
|
|
13332
|
-
import { dirname as
|
|
13510
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync22, readFileSync as readFileSync29, readdirSync as readdirSync12, rmSync as rmSync18, statSync as statSync10, writeFileSync as writeFileSync23 } from "fs";
|
|
13511
|
+
import { dirname as dirname16, join as join34 } from "path";
|
|
13333
13512
|
import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
|
|
13334
13513
|
function clonedCheckoutsFile(stateDir) {
|
|
13335
|
-
return
|
|
13514
|
+
return join34(stateDir, "cloned-checkouts.json");
|
|
13336
13515
|
}
|
|
13337
13516
|
function readClonedCheckouts(stateDir) {
|
|
13338
13517
|
try {
|
|
13339
|
-
const parsed = JSON.parse(
|
|
13518
|
+
const parsed = JSON.parse(readFileSync29(clonedCheckoutsFile(stateDir), "utf8"));
|
|
13340
13519
|
return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
|
|
13341
13520
|
} catch {
|
|
13342
13521
|
return [];
|
|
@@ -13346,8 +13525,8 @@ function recordClonedCheckout(stateDir, dir) {
|
|
|
13346
13525
|
const known = readClonedCheckouts(stateDir);
|
|
13347
13526
|
if (known.includes(dir)) return;
|
|
13348
13527
|
const file = clonedCheckoutsFile(stateDir);
|
|
13349
|
-
|
|
13350
|
-
|
|
13528
|
+
mkdirSync22(dirname16(file), { recursive: true });
|
|
13529
|
+
writeFileSync23(file, `${JSON.stringify([...known, dir], null, 2)}
|
|
13351
13530
|
`);
|
|
13352
13531
|
}
|
|
13353
13532
|
var mark = (dryRun) => dryRun ? "would-remove" : "removed";
|
|
@@ -13401,8 +13580,8 @@ async function runReset(o, fx) {
|
|
|
13401
13580
|
}
|
|
13402
13581
|
if (o.beforeState) steps.push(...await o.beforeState());
|
|
13403
13582
|
steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
|
|
13404
|
-
if (
|
|
13405
|
-
if (!o.dryRun)
|
|
13583
|
+
if (existsSync31(o.configFile)) {
|
|
13584
|
+
if (!o.dryRun) rmSync18(o.configFile, { force: true });
|
|
13406
13585
|
steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
|
|
13407
13586
|
} else {
|
|
13408
13587
|
steps.push({ step: "config", target: o.configFile, action: "absent" });
|
|
@@ -13410,7 +13589,7 @@ async function runReset(o, fx) {
|
|
|
13410
13589
|
return steps;
|
|
13411
13590
|
}
|
|
13412
13591
|
function removeCheckout(dir, dryRun, fx) {
|
|
13413
|
-
if (!
|
|
13592
|
+
if (!existsSync31(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
|
|
13414
13593
|
const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
|
|
13415
13594
|
if (dirty.length) {
|
|
13416
13595
|
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" : ""})` };
|
|
@@ -13418,21 +13597,21 @@ function removeCheckout(dir, dryRun, fx) {
|
|
|
13418
13597
|
if (linkedWorktrees.length) {
|
|
13419
13598
|
return { step: "checkout", target: dir, action: "kept", detail: `has linked worktrees (${linkedWorktrees.join(", ")}); remove them first` };
|
|
13420
13599
|
}
|
|
13421
|
-
if (!dryRun)
|
|
13600
|
+
if (!dryRun) rmSync18(dir, { recursive: true, force: true });
|
|
13422
13601
|
return { step: "checkout", target: dir, action: mark(dryRun) };
|
|
13423
13602
|
}
|
|
13424
13603
|
function removeState(stateDir, dryRun) {
|
|
13425
13604
|
const out = [];
|
|
13426
|
-
const targets = [
|
|
13605
|
+
const targets = [join34(stateDir, "resolved"), join34(stateDir, "keys"), clonedCheckoutsFile(stateDir), join34(stateDir, "connect-session.json"), join34(stateDir, "reload-requested"), join34(stateDir, "daemon.started")];
|
|
13427
13606
|
try {
|
|
13428
13607
|
for (const name of readdirSync12(stateDir)) {
|
|
13429
|
-
if (name.startsWith(".") && statSync10(
|
|
13608
|
+
if (name.startsWith(".") && statSync10(join34(stateDir, name)).isFile()) targets.push(join34(stateDir, name));
|
|
13430
13609
|
}
|
|
13431
13610
|
} catch {
|
|
13432
13611
|
}
|
|
13433
13612
|
for (const t of targets) {
|
|
13434
|
-
if (!
|
|
13435
|
-
if (!dryRun)
|
|
13613
|
+
if (!existsSync31(t)) continue;
|
|
13614
|
+
if (!dryRun) rmSync18(t, { recursive: true, force: true });
|
|
13436
13615
|
out.push({ step: "state", target: t, action: mark(dryRun) });
|
|
13437
13616
|
}
|
|
13438
13617
|
if (!out.length) out.push({ step: "state", target: stateDir, action: "absent", detail: "nothing to remove" });
|
|
@@ -13446,7 +13625,7 @@ function wipeStateDir(stateDir, dryRun) {
|
|
|
13446
13625
|
return [{ step: "state", target: stateDir, action: "absent", detail: "no state directory" }];
|
|
13447
13626
|
}
|
|
13448
13627
|
if (!names.length) return [{ step: "state", target: stateDir, action: "absent", detail: "already empty" }];
|
|
13449
|
-
if (!dryRun) for (const n of names)
|
|
13628
|
+
if (!dryRun) for (const n of names) rmSync18(join34(stateDir, n), { recursive: true, force: true });
|
|
13450
13629
|
return [{ step: "state", target: stateDir, action: mark(dryRun), detail: `${names.length} entr${names.length === 1 ? "y" : "ies"}` }];
|
|
13451
13630
|
}
|
|
13452
13631
|
function describeResetStep(s) {
|
|
@@ -13527,9 +13706,9 @@ async function runDeprovision(o, fx, deps) {
|
|
|
13527
13706
|
}
|
|
13528
13707
|
|
|
13529
13708
|
// src/pool-member.ts
|
|
13530
|
-
import { mkdirSync as
|
|
13709
|
+
import { mkdirSync as mkdirSync23, readFileSync as readFileSync30, renameSync as renameSync4, writeFileSync as writeFileSync24 } from "fs";
|
|
13531
13710
|
import { homedir as homedir8 } from "os";
|
|
13532
|
-
import { dirname as
|
|
13711
|
+
import { dirname as dirname17, join as join35 } from "path";
|
|
13533
13712
|
var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
|
|
13534
13713
|
function backoffMs(attempt) {
|
|
13535
13714
|
return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
|
|
@@ -13760,22 +13939,22 @@ async function runPoolMember(deps) {
|
|
|
13760
13939
|
}
|
|
13761
13940
|
}
|
|
13762
13941
|
function defaultMemberDir(env = process.env) {
|
|
13763
|
-
return env.CREW_POOL_DIR ??
|
|
13942
|
+
return env.CREW_POOL_DIR ?? join35(env.XDG_STATE_HOME ?? join35(homedir8(), ".local", "state"), "crew-pool-member");
|
|
13764
13943
|
}
|
|
13765
13944
|
function fileMemberStore(dir) {
|
|
13766
|
-
const file =
|
|
13945
|
+
const file = join35(dir, "member.json");
|
|
13767
13946
|
return {
|
|
13768
13947
|
read() {
|
|
13769
13948
|
try {
|
|
13770
|
-
return JSON.parse(
|
|
13949
|
+
return JSON.parse(readFileSync30(file, "utf8"));
|
|
13771
13950
|
} catch {
|
|
13772
13951
|
return {};
|
|
13773
13952
|
}
|
|
13774
13953
|
},
|
|
13775
13954
|
write(f) {
|
|
13776
|
-
|
|
13955
|
+
mkdirSync23(dirname17(file), { recursive: true, mode: 448 });
|
|
13777
13956
|
const tmp = `${file}.tmp`;
|
|
13778
|
-
|
|
13957
|
+
writeFileSync24(tmp, `${JSON.stringify(f)}
|
|
13779
13958
|
`, { mode: 384 });
|
|
13780
13959
|
renameSync4(tmp, file);
|
|
13781
13960
|
}
|
|
@@ -13827,16 +14006,16 @@ function hostArchitecture(arch = process.arch) {
|
|
|
13827
14006
|
|
|
13828
14007
|
// src/pool-member-host.ts
|
|
13829
14008
|
import "child_process";
|
|
13830
|
-
import { existsSync as
|
|
14009
|
+
import { existsSync as existsSync35 } from "fs";
|
|
13831
14010
|
import { hostname as hostname3 } from "os";
|
|
13832
14011
|
|
|
13833
14012
|
// src/machine-id.ts
|
|
13834
14013
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
13835
14014
|
import { createHash as createHash7 } from "crypto";
|
|
13836
|
-
import { existsSync as
|
|
13837
|
-
import { dirname as
|
|
14015
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync24, readFileSync as readFileSync31, writeFileSync as writeFileSync25 } from "fs";
|
|
14016
|
+
import { dirname as dirname18, join as join36 } from "path";
|
|
13838
14017
|
function machineIdPathFor(stateDir) {
|
|
13839
|
-
return
|
|
14018
|
+
return join36(stateDir, "machine-id");
|
|
13840
14019
|
}
|
|
13841
14020
|
var sha256 = (s) => createHash7("sha256").update(s).digest("hex");
|
|
13842
14021
|
function readRawMachineId(platform2 = process.platform) {
|
|
@@ -13849,8 +14028,8 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
13849
14028
|
return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
|
|
13850
14029
|
}
|
|
13851
14030
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
13852
|
-
if (
|
|
13853
|
-
const id =
|
|
14031
|
+
if (existsSync32(p)) {
|
|
14032
|
+
const id = readFileSync31(p, "utf8").trim();
|
|
13854
14033
|
if (id) return id;
|
|
13855
14034
|
}
|
|
13856
14035
|
}
|
|
@@ -13861,15 +14040,15 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
13861
14040
|
function ensureMachineId(stateDir, opts = {}) {
|
|
13862
14041
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
13863
14042
|
const path = machineIdPathFor(stateDir);
|
|
13864
|
-
if (
|
|
13865
|
-
const cached =
|
|
14043
|
+
if (existsSync32(path)) {
|
|
14044
|
+
const cached = readFileSync31(path, "utf8").trim();
|
|
13866
14045
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
13867
14046
|
}
|
|
13868
14047
|
const raw = (opts.read ?? readRawMachineId)();
|
|
13869
14048
|
if (!raw) return void 0;
|
|
13870
14049
|
const hashed = sha256(raw);
|
|
13871
|
-
|
|
13872
|
-
|
|
14050
|
+
mkdirSync24(dirname18(path), { recursive: true });
|
|
14051
|
+
writeFileSync25(path, `${hashed}
|
|
13873
14052
|
`, { mode: 384 });
|
|
13874
14053
|
return hashed;
|
|
13875
14054
|
}
|
|
@@ -13882,7 +14061,7 @@ function matchShipRows(ships, shipName2, machineId) {
|
|
|
13882
14061
|
|
|
13883
14062
|
// src/provision.ts
|
|
13884
14063
|
import { spawnSync as spawnSync8 } from "child_process";
|
|
13885
|
-
import { existsSync as
|
|
14064
|
+
import { existsSync as existsSync33, readFileSync as readFileSync32, writeFileSync as writeFileSync26 } from "fs";
|
|
13886
14065
|
import { hostname as hostname2 } from "os";
|
|
13887
14066
|
import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
|
|
13888
14067
|
|
|
@@ -13891,7 +14070,7 @@ import { parseDocument as parseDocument2 } from "yaml";
|
|
|
13891
14070
|
|
|
13892
14071
|
// src/repos-cmd.ts
|
|
13893
14072
|
import { homedir as homedir9 } from "os";
|
|
13894
|
-
import { basename as basename10, join as
|
|
14073
|
+
import { basename as basename10, join as join37, resolve as resolve4 } from "path";
|
|
13895
14074
|
import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
|
|
13896
14075
|
var ReposError = class extends Error {
|
|
13897
14076
|
};
|
|
@@ -14169,7 +14348,7 @@ function planRepoClone(i) {
|
|
|
14169
14348
|
}
|
|
14170
14349
|
const ship = doc.get("ship", true);
|
|
14171
14350
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
14172
|
-
const target = i.dir ? expandDir(i.dir, i.base) :
|
|
14351
|
+
const target = i.dir ? expandDir(i.dir, i.base) : join37(expandDir(basePath, i.base), i.route.split("/")[0], name);
|
|
14173
14352
|
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
|
|
14174
14353
|
const forgeHost = i.resolved?.repoForgeHosts?.[id];
|
|
14175
14354
|
return { ...add, remote, target, ...forgeHost ? { forgeHost } : {} };
|
|
@@ -14476,7 +14655,7 @@ function realProvisionDeps(crewHome) {
|
|
|
14476
14655
|
};
|
|
14477
14656
|
const configFile = () => {
|
|
14478
14657
|
const f = findConfigFile(crewHome);
|
|
14479
|
-
return
|
|
14658
|
+
return existsSync33(f) ? f : null;
|
|
14480
14659
|
};
|
|
14481
14660
|
return {
|
|
14482
14661
|
crew: spawnCrew,
|
|
@@ -14484,13 +14663,13 @@ function realProvisionDeps(crewHome) {
|
|
|
14484
14663
|
enableRoute(route2) {
|
|
14485
14664
|
const file = configFile();
|
|
14486
14665
|
if (!file) return false;
|
|
14487
|
-
const text2 =
|
|
14666
|
+
const text2 = readFileSync32(file, "utf8");
|
|
14488
14667
|
const doc = parseDocument3(text2);
|
|
14489
14668
|
const routes = doc.get("routes");
|
|
14490
14669
|
const entry = routes?.items?.find((r) => r.get("route") === route2);
|
|
14491
14670
|
if (!entry) throw new Error(`route ${route2} is not in ${file}`);
|
|
14492
14671
|
if (entry.get("enabled") === true) return false;
|
|
14493
|
-
|
|
14672
|
+
writeFileSync26(file, planEnable(text2, route2));
|
|
14494
14673
|
return true;
|
|
14495
14674
|
},
|
|
14496
14675
|
installState() {
|
|
@@ -14510,14 +14689,14 @@ function realProvisionDeps(crewHome) {
|
|
|
14510
14689
|
continue;
|
|
14511
14690
|
}
|
|
14512
14691
|
for (const p of plan.unitPaths) {
|
|
14513
|
-
if (!
|
|
14692
|
+
if (!existsSync33(p) || readFileSync32(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
|
|
14514
14693
|
}
|
|
14515
14694
|
}
|
|
14516
14695
|
const label = labelFor(crewHome, "run");
|
|
14517
14696
|
let running = false;
|
|
14518
14697
|
let lingering = true;
|
|
14519
14698
|
if (mechanism === "systemd") {
|
|
14520
|
-
const system = ctx.root ||
|
|
14699
|
+
const system = ctx.root || existsSync33(systemUnitPathFor(label));
|
|
14521
14700
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
14522
14701
|
running = spawnSync8(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
|
|
14523
14702
|
if (!system) {
|
|
@@ -14534,18 +14713,18 @@ function realProvisionDeps(crewHome) {
|
|
|
14534
14713
|
setShipSetting(key, value2) {
|
|
14535
14714
|
const file = configFile();
|
|
14536
14715
|
if (!file) return false;
|
|
14537
|
-
const doc = parseDocument3(
|
|
14716
|
+
const doc = parseDocument3(readFileSync32(file, "utf8"));
|
|
14538
14717
|
if (doc.getIn(["ship", key]) === value2) return false;
|
|
14539
14718
|
doc.setIn(["ship", key], value2);
|
|
14540
|
-
|
|
14719
|
+
writeFileSync26(file, doc.toString());
|
|
14541
14720
|
return true;
|
|
14542
14721
|
},
|
|
14543
14722
|
applyManagedDefaults() {
|
|
14544
14723
|
const file = configFile();
|
|
14545
14724
|
if (!file) return [];
|
|
14546
|
-
const doc = parseDocument3(
|
|
14725
|
+
const doc = parseDocument3(readFileSync32(file, "utf8"));
|
|
14547
14726
|
const wrote = applyManagedDefaultsToDoc(doc);
|
|
14548
|
-
if (wrote.length)
|
|
14727
|
+
if (wrote.length) writeFileSync26(file, doc.toString());
|
|
14549
14728
|
return wrote;
|
|
14550
14729
|
},
|
|
14551
14730
|
storedToken(host) {
|
|
@@ -14562,9 +14741,9 @@ host=${host}
|
|
|
14562
14741
|
|
|
14563
14742
|
// src/tunnel.ts
|
|
14564
14743
|
import { spawn as spawn7 } from "child_process";
|
|
14565
|
-
import { existsSync as
|
|
14744
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync25, readFileSync as readFileSync33, writeFileSync as writeFileSync27, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
|
|
14566
14745
|
import { randomInt } from "crypto";
|
|
14567
|
-
import { join as
|
|
14746
|
+
import { join as join38 } from "path";
|
|
14568
14747
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
14569
14748
|
var defaultSpawn = (cmd, args) => spawn7(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
14570
14749
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -14636,21 +14815,21 @@ function startTunnel(opts) {
|
|
|
14636
14815
|
};
|
|
14637
14816
|
}
|
|
14638
14817
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
14639
|
-
return
|
|
14818
|
+
return join38(stateDir, "passengers", `${workspaceId}.json`);
|
|
14640
14819
|
}
|
|
14641
14820
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
14642
14821
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
14643
|
-
if (!
|
|
14822
|
+
if (!existsSync34(path)) return void 0;
|
|
14644
14823
|
try {
|
|
14645
|
-
return JSON.parse(
|
|
14824
|
+
return JSON.parse(readFileSync33(path, "utf8"));
|
|
14646
14825
|
} catch {
|
|
14647
14826
|
return void 0;
|
|
14648
14827
|
}
|
|
14649
14828
|
}
|
|
14650
14829
|
function writePersistedTunnel(stateDir, state) {
|
|
14651
14830
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
14652
|
-
|
|
14653
|
-
|
|
14831
|
+
mkdirSync25(join38(stateDir, "passengers"), { recursive: true });
|
|
14832
|
+
writeFileSync27(path, `${JSON.stringify(state, null, 2)}
|
|
14654
14833
|
`);
|
|
14655
14834
|
}
|
|
14656
14835
|
function updatePersistedTunnel(stateDir, workspaceId, patch2) {
|
|
@@ -14752,8 +14931,8 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
14752
14931
|
}
|
|
14753
14932
|
}
|
|
14754
14933
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
14755
|
-
const dir =
|
|
14756
|
-
if (!
|
|
14934
|
+
const dir = join38(stateDir, "passengers");
|
|
14935
|
+
if (!existsSync34(dir)) return [];
|
|
14757
14936
|
return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
14758
14937
|
}
|
|
14759
14938
|
|
|
@@ -14778,7 +14957,7 @@ function realPoolMemberDeps(o) {
|
|
|
14778
14957
|
crewVersion: crewVersion(o.crewHome),
|
|
14779
14958
|
hostname: hostname3()
|
|
14780
14959
|
},
|
|
14781
|
-
hasShipConfig: () =>
|
|
14960
|
+
hasShipConfig: () => existsSync35(findConfigFile(o.crewHome)),
|
|
14782
14961
|
async provision(lease, instanceClass, shipKind) {
|
|
14783
14962
|
const hosts = lease.forges.map((f) => f.host.toLowerCase());
|
|
14784
14963
|
const lines2 = [];
|
|
@@ -15654,8 +15833,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
|
|
|
15654
15833
|
}
|
|
15655
15834
|
|
|
15656
15835
|
// src/poll.ts
|
|
15657
|
-
import { writeFileSync as
|
|
15658
|
-
import { join as
|
|
15836
|
+
import { writeFileSync as writeFileSync28 } from "fs";
|
|
15837
|
+
import { join as join39 } from "path";
|
|
15659
15838
|
|
|
15660
15839
|
// src/stalled.ts
|
|
15661
15840
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -15962,7 +16141,7 @@ async function decideCycle(o) {
|
|
|
15962
16141
|
}
|
|
15963
16142
|
return decision;
|
|
15964
16143
|
}
|
|
15965
|
-
var digestPath = (stateDir, route2, role, runId) =>
|
|
16144
|
+
var digestPath = (stateDir, route2, role, runId) => join39(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
15966
16145
|
function otherRunsRemoved(tickets, own) {
|
|
15967
16146
|
if (!own) return tickets;
|
|
15968
16147
|
return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
|
|
@@ -16040,7 +16219,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
|
|
|
16040
16219
|
|
|
16041
16220
|
${body}` : body;
|
|
16042
16221
|
const path = digestPath(stateDir, routeSlug(o.route.route), role, runId);
|
|
16043
|
-
|
|
16222
|
+
writeFileSync28(path, text2);
|
|
16044
16223
|
o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text2)} bytes)`, {
|
|
16045
16224
|
data: { role, bytes: Buffer.byteLength(text2) }
|
|
16046
16225
|
});
|
|
@@ -16148,8 +16327,8 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
|
|
|
16148
16327
|
}
|
|
16149
16328
|
|
|
16150
16329
|
// src/repo-refresh.ts
|
|
16151
|
-
import { existsSync as
|
|
16152
|
-
import { dirname as
|
|
16330
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as readFileSync34, writeFileSync as writeFileSync29 } from "fs";
|
|
16331
|
+
import { dirname as dirname20 } from "path";
|
|
16153
16332
|
var REFRESH_COOLDOWN_MS = 2 * 6e4;
|
|
16154
16333
|
var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
|
|
16155
16334
|
var lastAsked = /* @__PURE__ */ new Map();
|
|
@@ -16178,11 +16357,11 @@ function applyRepoRows(resolved, rows) {
|
|
|
16178
16357
|
}
|
|
16179
16358
|
function persistRepoRows(stateDir, routeName, rows) {
|
|
16180
16359
|
const path = resolvedPathFor(stateDir, routeName);
|
|
16181
|
-
if (!
|
|
16182
|
-
const raw = JSON.parse(
|
|
16360
|
+
if (!existsSync36(path)) return;
|
|
16361
|
+
const raw = JSON.parse(readFileSync34(path, "utf8"));
|
|
16183
16362
|
applyRepoRows(raw, rows);
|
|
16184
|
-
|
|
16185
|
-
|
|
16363
|
+
mkdirSync26(dirname20(path), { recursive: true });
|
|
16364
|
+
writeFileSync29(path, `${JSON.stringify(raw, null, 2)}
|
|
16186
16365
|
`);
|
|
16187
16366
|
}
|
|
16188
16367
|
async function refreshUnknownRepos(o) {
|
|
@@ -16632,13 +16811,13 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
|
|
|
16632
16811
|
var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
|
|
16633
16812
|
|
|
16634
16813
|
// src/connect-wizard.ts
|
|
16635
|
-
import { delimiter, dirname as
|
|
16636
|
-
import { accessSync as accessSync3, constants as constants3, existsSync as
|
|
16814
|
+
import { delimiter, dirname as dirname21, join as join40 } from "path";
|
|
16815
|
+
import { accessSync as accessSync3, constants as constants3, existsSync as existsSync37, mkdirSync as mkdirSync27, writeFileSync as writeFileSync30 } from "fs";
|
|
16637
16816
|
import { stringify as stringify3 } from "yaml";
|
|
16638
16817
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
16639
16818
|
function shouldRunWizard(opts) {
|
|
16640
16819
|
if (!opts.isTTY) return false;
|
|
16641
|
-
const exists = opts.configExists ??
|
|
16820
|
+
const exists = opts.configExists ?? existsSync37;
|
|
16642
16821
|
return !configSearchPath(opts.crewHome).some((p) => exists(p));
|
|
16643
16822
|
}
|
|
16644
16823
|
function firstRunConfigPath() {
|
|
@@ -16655,7 +16834,7 @@ function defaultIsExecutable(p) {
|
|
|
16655
16834
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
16656
16835
|
for (const dir of pathEnv.split(delimiter)) {
|
|
16657
16836
|
if (!dir) continue;
|
|
16658
|
-
const candidate =
|
|
16837
|
+
const candidate = join40(dir, name);
|
|
16659
16838
|
if (isExecutable(candidate)) return candidate;
|
|
16660
16839
|
}
|
|
16661
16840
|
return void 0;
|
|
@@ -16681,9 +16860,9 @@ async function runWizard(p, env) {
|
|
|
16681
16860
|
if (entered && isExec(entered)) agentBin = entered;
|
|
16682
16861
|
else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
|
|
16683
16862
|
}
|
|
16684
|
-
const nodeDir =
|
|
16863
|
+
const nodeDir = dirname21(env.nodePath);
|
|
16685
16864
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
16686
|
-
const baseDirs = [nodeDir, pnpmPath ?
|
|
16865
|
+
const baseDirs = [nodeDir, pnpmPath ? dirname21(pnpmPath) : void 0];
|
|
16687
16866
|
p.say(
|
|
16688
16867
|
`
|
|
16689
16868
|
Host Passengers lets this machine run the agent sessions that other people's
|
|
@@ -16694,7 +16873,7 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
|
|
|
16694
16873
|
p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
|
|
16695
16874
|
}
|
|
16696
16875
|
const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
16697
|
-
const extraPath = deriveExtraPath([...baseDirs, dockerPath ?
|
|
16876
|
+
const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname21(dockerPath) : void 0]);
|
|
16698
16877
|
if (extraPath) {
|
|
16699
16878
|
p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
|
|
16700
16879
|
}
|
|
@@ -16716,9 +16895,9 @@ routes:
|
|
|
16716
16895
|
${routeBlock}`;
|
|
16717
16896
|
}
|
|
16718
16897
|
function writeNewConfig(path, text2) {
|
|
16719
|
-
|
|
16898
|
+
mkdirSync27(dirname21(path), { recursive: true });
|
|
16720
16899
|
try {
|
|
16721
|
-
|
|
16900
|
+
writeFileSync30(path, text2, { mode: 384, flag: "wx" });
|
|
16722
16901
|
return true;
|
|
16723
16902
|
} catch (e) {
|
|
16724
16903
|
if (e.code === "EEXIST") return false;
|
|
@@ -16752,7 +16931,7 @@ Wrote ${o.configPath}.
|
|
|
16752
16931
|
// src/connect-json.ts
|
|
16753
16932
|
import { accessSync as accessSync4, constants as constants4 } from "fs";
|
|
16754
16933
|
import { homedir as homedir10 } from "os";
|
|
16755
|
-
import { dirname as
|
|
16934
|
+
import { dirname as dirname22, join as join41 } from "path";
|
|
16756
16935
|
import { delimiter as delimiter2 } from "path";
|
|
16757
16936
|
var CHOICE_EXIT_CODE = 3;
|
|
16758
16937
|
function createEmitter(json2, write) {
|
|
@@ -16791,12 +16970,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
|
|
|
16791
16970
|
const dirs = [
|
|
16792
16971
|
...pathEnv.split(delimiter2),
|
|
16793
16972
|
...(extraPath ?? "").split(delimiter2),
|
|
16794
|
-
|
|
16795
|
-
|
|
16973
|
+
join41(home, ".local/bin"),
|
|
16974
|
+
join41(home, ".claude/local"),
|
|
16796
16975
|
"/opt/homebrew/bin",
|
|
16797
16976
|
"/usr/local/bin",
|
|
16798
|
-
|
|
16799
|
-
|
|
16977
|
+
join41(home, ".npm-global/bin"),
|
|
16978
|
+
join41(home, ".volta/bin")
|
|
16800
16979
|
];
|
|
16801
16980
|
return [...new Set(dirs.filter(Boolean))];
|
|
16802
16981
|
}
|
|
@@ -16818,7 +16997,7 @@ function flagWizardAnswers(f, env) {
|
|
|
16818
16997
|
const agentBin = f.agentBin ?? found?.bin;
|
|
16819
16998
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
16820
16999
|
const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
16821
|
-
const extraPath = f.extraPath ?? deriveExtraPath([
|
|
17000
|
+
const extraPath = f.extraPath ?? deriveExtraPath([dirname22(env.nodePath), pnpmPath ? dirname22(pnpmPath) : void 0, dockerPath ? dirname22(dockerPath) : void 0]);
|
|
16822
17001
|
return {
|
|
16823
17002
|
answers: {
|
|
16824
17003
|
name: f.shipName?.trim() || env.hostname,
|
|
@@ -16832,8 +17011,8 @@ function flagWizardAnswers(f, env) {
|
|
|
16832
17011
|
}
|
|
16833
17012
|
|
|
16834
17013
|
// src/release-run.ts
|
|
16835
|
-
import { readFileSync as
|
|
16836
|
-
import { dirname as
|
|
17014
|
+
import { readFileSync as readFileSync35, writeFileSync as writeFileSync31, existsSync as existsSync38, copyFileSync, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync3, rmSync as rmSync19 } from "fs";
|
|
17015
|
+
import { dirname as dirname23, join as join42 } from "path";
|
|
16837
17016
|
import { tmpdir as tmpdir5 } from "os";
|
|
16838
17017
|
|
|
16839
17018
|
// src/release.ts
|
|
@@ -17574,13 +17753,13 @@ async function bump(o, size) {
|
|
|
17574
17753
|
}
|
|
17575
17754
|
return printed;
|
|
17576
17755
|
}
|
|
17577
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
17756
|
+
const files = o.repo.release.versionFiles.map((f) => join42(o.cwd, f));
|
|
17578
17757
|
const first = files[0];
|
|
17579
|
-
if (!
|
|
17758
|
+
if (!existsSync38(first)) {
|
|
17580
17759
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
17581
17760
|
return null;
|
|
17582
17761
|
}
|
|
17583
|
-
const current = JSON.parse(
|
|
17762
|
+
const current = JSON.parse(readFileSync35(first, "utf8")).version;
|
|
17584
17763
|
if (!current) {
|
|
17585
17764
|
o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
|
|
17586
17765
|
return null;
|
|
@@ -17593,27 +17772,27 @@ async function bump(o, size) {
|
|
|
17593
17772
|
return version;
|
|
17594
17773
|
}
|
|
17595
17774
|
for (const f of files) {
|
|
17596
|
-
const src =
|
|
17775
|
+
const src = readFileSync35(f, "utf8");
|
|
17597
17776
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
17598
17777
|
if (out === src) {
|
|
17599
17778
|
o.emit.error(`no version field in ${f}`);
|
|
17600
17779
|
return null;
|
|
17601
17780
|
}
|
|
17602
|
-
|
|
17781
|
+
writeFileSync31(f, out);
|
|
17603
17782
|
}
|
|
17604
17783
|
return version;
|
|
17605
17784
|
}
|
|
17606
17785
|
function writeChangelog(o, version, entries) {
|
|
17607
17786
|
const file = o.repo.release.changelog;
|
|
17608
17787
|
if (!file || entries.length === 0) return;
|
|
17609
|
-
const path =
|
|
17788
|
+
const path = join42(o.cwd, file);
|
|
17610
17789
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
17611
17790
|
if (o.dryRun) {
|
|
17612
17791
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
17613
17792
|
return;
|
|
17614
17793
|
}
|
|
17615
|
-
const existing =
|
|
17616
|
-
|
|
17794
|
+
const existing = existsSync38(path) ? readFileSync35(path, "utf8") : "# Changelog\n";
|
|
17795
|
+
writeFileSync31(path, insertChangelogSection(existing, section));
|
|
17617
17796
|
}
|
|
17618
17797
|
async function confirm(o, expected) {
|
|
17619
17798
|
if (!o.repo.hooks.released) return void 0;
|
|
@@ -17696,7 +17875,7 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
|
|
|
17696
17875
|
run3(cwd, ["worktree", "remove", "--force", dir]);
|
|
17697
17876
|
} finally {
|
|
17698
17877
|
try {
|
|
17699
|
-
|
|
17878
|
+
rmSync19(root, { recursive: true, force: true });
|
|
17700
17879
|
} finally {
|
|
17701
17880
|
run3(cwd, ["worktree", "prune"]);
|
|
17702
17881
|
run3(cwd, ["branch", "-D", branch]);
|
|
@@ -17706,8 +17885,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
|
|
|
17706
17885
|
function openReleaseCheckout(o) {
|
|
17707
17886
|
const { remote, base } = o.repo.branch;
|
|
17708
17887
|
const branch = `crew/release-${Date.now()}`;
|
|
17709
|
-
const root = mkdtempSync3(
|
|
17710
|
-
const dir =
|
|
17888
|
+
const root = mkdtempSync3(join42(tmpdir5(), "crew-release-"));
|
|
17889
|
+
const dir = join42(root, "checkout");
|
|
17711
17890
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
17712
17891
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
17713
17892
|
try {
|
|
@@ -17717,10 +17896,10 @@ function openReleaseCheckout(o) {
|
|
|
17717
17896
|
throw e;
|
|
17718
17897
|
}
|
|
17719
17898
|
for (const f of o.repo.worktrees.copy) {
|
|
17720
|
-
const src =
|
|
17721
|
-
if (!
|
|
17722
|
-
|
|
17723
|
-
copyFileSync(src,
|
|
17899
|
+
const src = join42(o.cwd, f);
|
|
17900
|
+
if (!existsSync38(src)) continue;
|
|
17901
|
+
mkdirSync28(dirname23(join42(dir, f)), { recursive: true });
|
|
17902
|
+
copyFileSync(src, join42(dir, f));
|
|
17724
17903
|
}
|
|
17725
17904
|
return { dir, branch, close };
|
|
17726
17905
|
}
|
|
@@ -18742,7 +18921,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
|
|
|
18742
18921
|
}
|
|
18743
18922
|
|
|
18744
18923
|
// src/watch.ts
|
|
18745
|
-
import { watch as watchFile, existsSync as
|
|
18924
|
+
import { watch as watchFile, existsSync as existsSync39, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
|
|
18746
18925
|
var E = "\x1B[";
|
|
18747
18926
|
var dim = (s) => `${E}2m${s}${E}0m`;
|
|
18748
18927
|
var bold = (s) => `${E}1m${s}${E}0m`;
|
|
@@ -18843,7 +19022,7 @@ function renderHeaders(states, now = Date.now()) {
|
|
|
18843
19022
|
return states.map((s) => renderHeader(s, now)).join("");
|
|
18844
19023
|
}
|
|
18845
19024
|
function readFrom(file, from) {
|
|
18846
|
-
if (!
|
|
19025
|
+
if (!existsSync39(file)) return { events: [], offset: from };
|
|
18847
19026
|
const size = statSync11(file).size;
|
|
18848
19027
|
if (size < from) return readFrom(file, 0);
|
|
18849
19028
|
if (size === from) return { events: [], offset: from };
|
|
@@ -19086,8 +19265,8 @@ function nextRoles(decision, ran) {
|
|
|
19086
19265
|
}
|
|
19087
19266
|
|
|
19088
19267
|
// src/ship-name.ts
|
|
19089
|
-
import { existsSync as
|
|
19090
|
-
import { dirname as
|
|
19268
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync29, readFileSync as readFileSync36, renameSync as renameSync5, writeFileSync as writeFileSync32 } from "fs";
|
|
19269
|
+
import { dirname as dirname24 } from "path";
|
|
19091
19270
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
19092
19271
|
var ShipNameError = class extends Error {
|
|
19093
19272
|
};
|
|
@@ -19148,34 +19327,34 @@ async function syncRouteName(i) {
|
|
|
19148
19327
|
return { status: "ok", ...base, action, name: i.local };
|
|
19149
19328
|
}
|
|
19150
19329
|
function readSyncedName(resolvedFile) {
|
|
19151
|
-
if (!
|
|
19330
|
+
if (!existsSync40(resolvedFile)) return void 0;
|
|
19152
19331
|
try {
|
|
19153
|
-
const v = JSON.parse(
|
|
19332
|
+
const v = JSON.parse(readFileSync36(resolvedFile, "utf8")).shipName;
|
|
19154
19333
|
return typeof v === "string" ? v : void 0;
|
|
19155
19334
|
} catch {
|
|
19156
19335
|
return void 0;
|
|
19157
19336
|
}
|
|
19158
19337
|
}
|
|
19159
19338
|
function writeSyncedName(resolvedFile, name) {
|
|
19160
|
-
if (!
|
|
19339
|
+
if (!existsSync40(resolvedFile)) return;
|
|
19161
19340
|
let raw;
|
|
19162
19341
|
try {
|
|
19163
|
-
raw = JSON.parse(
|
|
19342
|
+
raw = JSON.parse(readFileSync36(resolvedFile, "utf8"));
|
|
19164
19343
|
} catch {
|
|
19165
19344
|
return;
|
|
19166
19345
|
}
|
|
19167
19346
|
if (raw.shipName === name) return;
|
|
19168
19347
|
raw.shipName = name;
|
|
19169
|
-
|
|
19348
|
+
mkdirSync29(dirname24(resolvedFile), { recursive: true });
|
|
19170
19349
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
19171
|
-
|
|
19350
|
+
writeFileSync32(tmp, `${JSON.stringify(raw, null, 2)}
|
|
19172
19351
|
`);
|
|
19173
19352
|
renameSync5(tmp, resolvedFile);
|
|
19174
19353
|
}
|
|
19175
19354
|
async function reconcileShipName(i) {
|
|
19176
19355
|
let local = i.ship.name;
|
|
19177
19356
|
try {
|
|
19178
|
-
local = shipNameOfYaml(
|
|
19357
|
+
local = shipNameOfYaml(readFileSync36(i.configFile, "utf8")) ?? local;
|
|
19179
19358
|
} catch {
|
|
19180
19359
|
}
|
|
19181
19360
|
const resolvedFile = resolvedPathFor(i.stateDir, i.route);
|
|
@@ -19199,8 +19378,8 @@ async function reconcileShipName(i) {
|
|
|
19199
19378
|
}
|
|
19200
19379
|
if (result.status !== "ok") return result;
|
|
19201
19380
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
19202
|
-
const text2 =
|
|
19203
|
-
|
|
19381
|
+
const text2 = readFileSync36(i.configFile, "utf8");
|
|
19382
|
+
writeFileSync32(i.configFile, setShipNameInYaml(text2, result.name));
|
|
19204
19383
|
i.ship.name = result.name;
|
|
19205
19384
|
if (result.action === "row-wins") {
|
|
19206
19385
|
i.emit?.warn(
|
|
@@ -19859,8 +20038,8 @@ function describeCapability(i) {
|
|
|
19859
20038
|
}
|
|
19860
20039
|
|
|
19861
20040
|
// src/agents-sync-run.ts
|
|
19862
|
-
import { existsSync as
|
|
19863
|
-
import { dirname as
|
|
20041
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync30, readFileSync as readFileSync37, writeFileSync as writeFileSync33 } from "fs";
|
|
20042
|
+
import { dirname as dirname25 } from "path";
|
|
19864
20043
|
async function runAgentsSync(target, o) {
|
|
19865
20044
|
const dryRun = o.dryRun ?? false;
|
|
19866
20045
|
const force = o.force ?? false;
|
|
@@ -19893,12 +20072,12 @@ async function runAgentsSync(target, o) {
|
|
|
19893
20072
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
19894
20073
|
if (!dryRun) {
|
|
19895
20074
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
19896
|
-
const raw =
|
|
20075
|
+
const raw = existsSync41(resolvedPath) ? JSON.parse(readFileSync37(resolvedPath, "utf8")) : {};
|
|
19897
20076
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
19898
20077
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
19899
20078
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
19900
|
-
|
|
19901
|
-
|
|
20079
|
+
mkdirSync30(dirname25(resolvedPath), { recursive: true });
|
|
20080
|
+
writeFileSync33(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
19902
20081
|
`);
|
|
19903
20082
|
if (target.resolved) {
|
|
19904
20083
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -20015,7 +20194,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
20015
20194
|
|
|
20016
20195
|
// src/repos-check.ts
|
|
20017
20196
|
import { spawnSync as spawnSync10 } from "child_process";
|
|
20018
|
-
import { readFileSync as
|
|
20197
|
+
import { readFileSync as readFileSync38 } from "fs";
|
|
20019
20198
|
import { basename as basename11 } from "path";
|
|
20020
20199
|
var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
|
|
20021
20200
|
function checkHookSyntax(script, shell) {
|
|
@@ -20056,7 +20235,7 @@ function checkRepoSpec(dir, opts = {}) {
|
|
|
20056
20235
|
}
|
|
20057
20236
|
let parsed;
|
|
20058
20237
|
try {
|
|
20059
|
-
parsed = parseRepoConfig(
|
|
20238
|
+
parsed = parseRepoConfig(readFileSync38(file, "utf8"), file);
|
|
20060
20239
|
} catch (e) {
|
|
20061
20240
|
if (!(e instanceof RepoConfigError)) throw e;
|
|
20062
20241
|
return { ...empty, file, error: e.message, exitCode: 1 };
|
|
@@ -20513,7 +20692,7 @@ ${run3.err}` });
|
|
|
20513
20692
|
}
|
|
20514
20693
|
|
|
20515
20694
|
// src/sample-ticket.ts
|
|
20516
|
-
import { basename as basename12, dirname as
|
|
20695
|
+
import { basename as basename12, dirname as dirname26, join as join43 } from "path";
|
|
20517
20696
|
var SAMPLE_REPO = "crew-sample";
|
|
20518
20697
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
20519
20698
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -20536,8 +20715,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
20536
20715
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
20537
20716
|
`;
|
|
20538
20717
|
function sampleDir(existing, reposBase, workspace) {
|
|
20539
|
-
if (existing.length > 0) return
|
|
20540
|
-
return
|
|
20718
|
+
if (existing.length > 0) return join43(dirname26(existing[0]), SAMPLE_REPO);
|
|
20719
|
+
return join43(reposBase, workspace, SAMPLE_REPO);
|
|
20541
20720
|
}
|
|
20542
20721
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
20543
20722
|
function sampleDescription() {
|
|
@@ -20581,8 +20760,8 @@ function planSampleTicket(i) {
|
|
|
20581
20760
|
}
|
|
20582
20761
|
|
|
20583
20762
|
// src/worktree-sweep.ts
|
|
20584
|
-
import { existsSync as
|
|
20585
|
-
import { basename as basename13, dirname as
|
|
20763
|
+
import { existsSync as existsSync42, readFileSync as readFileSync39, readdirSync as readdirSync14 } from "fs";
|
|
20764
|
+
import { basename as basename13, dirname as dirname27, join as join44, resolve as resolve5 } from "path";
|
|
20586
20765
|
function planWorktreeSweep(target, tickets, contract) {
|
|
20587
20766
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
20588
20767
|
const verified = contract.statuses.verified;
|
|
@@ -20646,7 +20825,7 @@ function worktreeTicketNumbers(target) {
|
|
|
20646
20825
|
return [...nums];
|
|
20647
20826
|
}
|
|
20648
20827
|
function siblingWorktreeDirs(target) {
|
|
20649
|
-
const parent =
|
|
20828
|
+
const parent = dirname27(target.dir.replace(/[/\\]+$/, ""));
|
|
20650
20829
|
const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
|
|
20651
20830
|
const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
|
|
20652
20831
|
let names;
|
|
@@ -20659,7 +20838,7 @@ function siblingWorktreeDirs(target) {
|
|
|
20659
20838
|
for (const name of names) {
|
|
20660
20839
|
if (!name.startsWith(stem)) continue;
|
|
20661
20840
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
20662
|
-
const path =
|
|
20841
|
+
const path = join44(parent, name);
|
|
20663
20842
|
if (m && !registered.has(resolve5(path))) out.push({ path, number: m[1] });
|
|
20664
20843
|
}
|
|
20665
20844
|
return out;
|
|
@@ -20694,7 +20873,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20694
20873
|
let removed = 0;
|
|
20695
20874
|
let keptBranches = 0;
|
|
20696
20875
|
for (const a of actions) {
|
|
20697
|
-
if (!dryRun && base && !a.keepBranch &&
|
|
20876
|
+
if (!dryRun && base && !a.keepBranch && existsSync42(a.path) && isHandBackMerge(a.path, base)) {
|
|
20698
20877
|
if (gitOk(a.path, ["merge", "--abort"]) !== null) {
|
|
20699
20878
|
log.emit(
|
|
20700
20879
|
`abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
|
|
@@ -20702,7 +20881,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20702
20881
|
);
|
|
20703
20882
|
}
|
|
20704
20883
|
}
|
|
20705
|
-
if (!dryRun &&
|
|
20884
|
+
if (!dryRun && existsSync42(a.path)) {
|
|
20706
20885
|
let dirty = false;
|
|
20707
20886
|
try {
|
|
20708
20887
|
dirty = status(a.path).length > 0;
|
|
@@ -20713,7 +20892,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20713
20892
|
continue;
|
|
20714
20893
|
}
|
|
20715
20894
|
}
|
|
20716
|
-
if (!dryRun && a.requirePushed && a.branch &&
|
|
20895
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync42(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
20717
20896
|
refuse2(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
|
|
20718
20897
|
continue;
|
|
20719
20898
|
}
|
|
@@ -20762,7 +20941,7 @@ function isHandBackMerge(path, base) {
|
|
|
20762
20941
|
if (!msgPath) return false;
|
|
20763
20942
|
let first;
|
|
20764
20943
|
try {
|
|
20765
|
-
first =
|
|
20944
|
+
first = readFileSync39(resolve5(path, msgPath), "utf8").split("\n")[0] ?? "";
|
|
20766
20945
|
} catch {
|
|
20767
20946
|
return false;
|
|
20768
20947
|
}
|
|
@@ -20867,14 +21046,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
20867
21046
|
}
|
|
20868
21047
|
|
|
20869
21048
|
// src/stream-sweep.ts
|
|
20870
|
-
import { existsSync as
|
|
20871
|
-
import { join as
|
|
21049
|
+
import { existsSync as existsSync43, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
|
|
21050
|
+
import { join as join45 } from "path";
|
|
20872
21051
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
20873
21052
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
20874
|
-
const dir =
|
|
20875
|
-
if (!
|
|
21053
|
+
const dir = join45(stateDir, "streams");
|
|
21054
|
+
if (!existsSync43(dir)) return [];
|
|
20876
21055
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
20877
|
-
return readdirSync15(dir).map((name) =>
|
|
21056
|
+
return readdirSync15(dir).map((name) => join45(dir, name)).filter((path) => {
|
|
20878
21057
|
try {
|
|
20879
21058
|
return statSync12(path).mtimeMs < cutoff;
|
|
20880
21059
|
} catch {
|
|
@@ -20890,7 +21069,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
20890
21069
|
continue;
|
|
20891
21070
|
}
|
|
20892
21071
|
try {
|
|
20893
|
-
|
|
21072
|
+
rmSync20(path, { force: true });
|
|
20894
21073
|
removed++;
|
|
20895
21074
|
log.emit(`removed stream artifact ${path}`, { step: "worktree" });
|
|
20896
21075
|
} catch (e) {
|
|
@@ -20900,9 +21079,9 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
20900
21079
|
return removed;
|
|
20901
21080
|
}
|
|
20902
21081
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
20903
|
-
if (!
|
|
21082
|
+
if (!existsSync43(stateDir)) return [];
|
|
20904
21083
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
20905
|
-
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) =>
|
|
21084
|
+
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join45(stateDir, name)).filter((path) => {
|
|
20906
21085
|
try {
|
|
20907
21086
|
return statSync12(path).mtimeMs < cutoff;
|
|
20908
21087
|
} catch {
|
|
@@ -20912,12 +21091,12 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
20912
21091
|
}
|
|
20913
21092
|
|
|
20914
21093
|
// src/cli.ts
|
|
20915
|
-
import { realpathSync as realpathSync3, readFileSync as
|
|
21094
|
+
import { realpathSync as realpathSync3, readFileSync as readFileSync45, existsSync as existsSync50, mkdirSync as mkdirSync38, writeFileSync as writeFileSync38, rmSync as rmSync25, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
|
|
20916
21095
|
import { createInterface } from "readline/promises";
|
|
20917
21096
|
|
|
20918
21097
|
// src/periscope-cmd.ts
|
|
20919
|
-
import { existsSync as
|
|
20920
|
-
import { join as
|
|
21098
|
+
import { existsSync as existsSync44, mkdirSync as mkdirSync31, readFileSync as readFileSync40, rmSync as rmSync21, writeFileSync as writeFileSync34 } from "fs";
|
|
21099
|
+
import { join as join46 } from "path";
|
|
20921
21100
|
var PeriscopeError = class extends Error {
|
|
20922
21101
|
};
|
|
20923
21102
|
var LivePeriscopeError = class extends PeriscopeError {
|
|
@@ -20927,22 +21106,22 @@ var LivePeriscopeError = class extends PeriscopeError {
|
|
|
20927
21106
|
this.data = { refused: "live_periscope_exists", ...live };
|
|
20928
21107
|
}
|
|
20929
21108
|
};
|
|
20930
|
-
var priorDir = (stateDir) =>
|
|
20931
|
-
var priorPath = (stateDir, ticketRecordId) =>
|
|
21109
|
+
var priorDir = (stateDir) => join46(stateDir, "periscope-priors");
|
|
21110
|
+
var priorPath = (stateDir, ticketRecordId) => join46(priorDir(stateDir), `${ticketRecordId}.json`);
|
|
20932
21111
|
function removePeriscopePrior(stateDir, ticketRecordId) {
|
|
20933
|
-
|
|
21112
|
+
rmSync21(priorPath(stateDir, ticketRecordId), { force: true });
|
|
20934
21113
|
}
|
|
20935
21114
|
function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
20936
21115
|
try {
|
|
20937
|
-
|
|
20938
|
-
|
|
21116
|
+
mkdirSync31(priorDir(stateDir), { recursive: true });
|
|
21117
|
+
writeFileSync34(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
|
|
20939
21118
|
} catch {
|
|
20940
21119
|
}
|
|
20941
21120
|
}
|
|
20942
21121
|
function readPeriscopePrior(stateDir, ticketRecordId) {
|
|
20943
21122
|
try {
|
|
20944
21123
|
const p = priorPath(stateDir, ticketRecordId);
|
|
20945
|
-
return
|
|
21124
|
+
return existsSync44(p) ? JSON.parse(readFileSync40(p, "utf8")) : void 0;
|
|
20946
21125
|
} catch {
|
|
20947
21126
|
return void 0;
|
|
20948
21127
|
}
|
|
@@ -21396,7 +21575,7 @@ ${tail2(preview.output)}`);
|
|
|
21396
21575
|
// src/periscope-idle.ts
|
|
21397
21576
|
import { execFileSync as execFileSync12 } from "child_process";
|
|
21398
21577
|
import { statSync as statSync13 } from "fs";
|
|
21399
|
-
import { join as
|
|
21578
|
+
import { join as join47 } from "path";
|
|
21400
21579
|
var MAX_SCANNED_FILES = 2e4;
|
|
21401
21580
|
function newestWorktreeMtime(root) {
|
|
21402
21581
|
const a = newestTrackedMtime(root);
|
|
@@ -21421,7 +21600,7 @@ function newestTrackedMtime(root) {
|
|
|
21421
21600
|
if (!rel) continue;
|
|
21422
21601
|
if (++scanned > MAX_SCANNED_FILES) break;
|
|
21423
21602
|
try {
|
|
21424
|
-
const m = statSync13(
|
|
21603
|
+
const m = statSync13(join47(root, rel)).mtimeMs;
|
|
21425
21604
|
if (newest === null || m > newest) newest = m;
|
|
21426
21605
|
} catch {
|
|
21427
21606
|
}
|
|
@@ -21497,18 +21676,18 @@ import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
|
21497
21676
|
|
|
21498
21677
|
// src/ssh-keys.ts
|
|
21499
21678
|
import { execFileSync as execFileSync13 } from "child_process";
|
|
21500
|
-
import { existsSync as
|
|
21501
|
-
import { dirname as
|
|
21679
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync32, readFileSync as readFileSync41, chmodSync as chmodSync3 } from "fs";
|
|
21680
|
+
import { dirname as dirname28, join as join48 } from "path";
|
|
21502
21681
|
var SshKeyError = class extends Error {
|
|
21503
21682
|
};
|
|
21504
21683
|
function shipSshKeyPathFor(stateDir) {
|
|
21505
|
-
return
|
|
21684
|
+
return join48(stateDir, "ssh", "id_ed25519");
|
|
21506
21685
|
}
|
|
21507
21686
|
function ensureShipSshKeypair(stateDir) {
|
|
21508
21687
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
21509
21688
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
21510
|
-
if (!
|
|
21511
|
-
|
|
21689
|
+
if (!existsSync45(privateKeyPath)) {
|
|
21690
|
+
mkdirSync32(dirname28(privateKeyPath), { recursive: true });
|
|
21512
21691
|
try {
|
|
21513
21692
|
execFileSync13("ssh-keygen", [
|
|
21514
21693
|
"-t",
|
|
@@ -21528,10 +21707,10 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
21528
21707
|
}
|
|
21529
21708
|
chmodSync3(privateKeyPath, 384);
|
|
21530
21709
|
}
|
|
21531
|
-
if (!
|
|
21710
|
+
if (!existsSync45(publicKeyPath)) {
|
|
21532
21711
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
21533
21712
|
}
|
|
21534
|
-
const publicKey =
|
|
21713
|
+
const publicKey = readFileSync41(publicKeyPath, "utf8").trim();
|
|
21535
21714
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
21536
21715
|
}
|
|
21537
21716
|
function sshKeygenAvailable() {
|
|
@@ -21695,12 +21874,12 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
21695
21874
|
import { createServer as createHttpServer } from "http";
|
|
21696
21875
|
import { spawn as spawn9 } from "child_process";
|
|
21697
21876
|
import { randomBytes } from "crypto";
|
|
21698
|
-
import { existsSync as
|
|
21699
|
-
import { join as
|
|
21877
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync34, readFileSync as readFileSync42, writeFileSync as writeFileSync35, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
|
|
21878
|
+
import { join as join49 } from "path";
|
|
21700
21879
|
|
|
21701
21880
|
// src/passenger-mirror.ts
|
|
21702
|
-
import { mkdirSync as
|
|
21703
|
-
import { dirname as
|
|
21881
|
+
import { mkdirSync as mkdirSync33 } from "fs";
|
|
21882
|
+
import { dirname as dirname29 } from "path";
|
|
21704
21883
|
function mirrorRepoOf(spec) {
|
|
21705
21884
|
const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
|
|
21706
21885
|
if (fromOrigin) return fromOrigin;
|
|
@@ -21718,9 +21897,9 @@ async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCa
|
|
|
21718
21897
|
continue;
|
|
21719
21898
|
}
|
|
21720
21899
|
try {
|
|
21721
|
-
const parent =
|
|
21722
|
-
|
|
21723
|
-
|
|
21900
|
+
const parent = dirname29(spec.dir);
|
|
21901
|
+
mkdirSync33(parent, { recursive: true });
|
|
21902
|
+
mkdirSync33(spec.dir, { recursive: true });
|
|
21724
21903
|
const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
|
|
21725
21904
|
out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
|
|
21726
21905
|
} catch (e) {
|
|
@@ -21826,21 +22005,21 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
21826
22005
|
return child;
|
|
21827
22006
|
};
|
|
21828
22007
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
21829
|
-
return
|
|
22008
|
+
return join49(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
21830
22009
|
}
|
|
21831
22010
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
21832
22011
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
21833
|
-
if (!
|
|
22012
|
+
if (!existsSync46(path)) return void 0;
|
|
21834
22013
|
try {
|
|
21835
|
-
return JSON.parse(
|
|
22014
|
+
return JSON.parse(readFileSync42(path, "utf8"));
|
|
21836
22015
|
} catch {
|
|
21837
22016
|
return void 0;
|
|
21838
22017
|
}
|
|
21839
22018
|
}
|
|
21840
22019
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
21841
22020
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
21842
|
-
|
|
21843
|
-
|
|
22021
|
+
mkdirSync34(join49(stateDir, "passengers"), { recursive: true });
|
|
22022
|
+
writeFileSync35(path, `${JSON.stringify(state, null, 2)}
|
|
21844
22023
|
`);
|
|
21845
22024
|
}
|
|
21846
22025
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -21860,8 +22039,8 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
21860
22039
|
}
|
|
21861
22040
|
}
|
|
21862
22041
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
21863
|
-
const dir =
|
|
21864
|
-
if (!
|
|
22042
|
+
const dir = join49(stateDir, "passengers");
|
|
22043
|
+
if (!existsSync46(dir)) return [];
|
|
21865
22044
|
const suffix = "-sync.json";
|
|
21866
22045
|
return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
21867
22046
|
}
|
|
@@ -21919,12 +22098,12 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
|
21919
22098
|
|
|
21920
22099
|
// src/periscope-proxy.ts
|
|
21921
22100
|
import { spawn as spawn10 } from "child_process";
|
|
21922
|
-
import { appendFileSync as appendFileSync3, mkdirSync as
|
|
22101
|
+
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync35 } from "fs";
|
|
21923
22102
|
import { createServer, request as httpRequest } from "http";
|
|
21924
22103
|
import { request as httpsRequest } from "https";
|
|
21925
22104
|
import { connect as netConnect } from "net";
|
|
21926
22105
|
import { connect as tlsConnect } from "tls";
|
|
21927
|
-
import { dirname as
|
|
22106
|
+
import { dirname as dirname30, isAbsolute as isAbsolute4 } from "path";
|
|
21928
22107
|
import { brotliDecompressSync, gunzipSync, inflateSync } from "zlib";
|
|
21929
22108
|
|
|
21930
22109
|
// src/periscope-inspector.ts
|
|
@@ -22038,7 +22217,7 @@ function parsePointedBody(raw) {
|
|
|
22038
22217
|
return entry;
|
|
22039
22218
|
}
|
|
22040
22219
|
function appendPointed(file, entry, now = /* @__PURE__ */ new Date()) {
|
|
22041
|
-
|
|
22220
|
+
mkdirSync35(dirname30(file), { recursive: true });
|
|
22042
22221
|
appendFileSync3(file, `${JSON.stringify({ ...entry, at: now.toISOString() })}
|
|
22043
22222
|
`);
|
|
22044
22223
|
}
|
|
@@ -22299,6 +22478,33 @@ async function launchProxy(invocation, worktree, previewUrl, number, o) {
|
|
|
22299
22478
|
throw new Error(`the proxy did not start listening on ${o.host}:${port}`);
|
|
22300
22479
|
}
|
|
22301
22480
|
|
|
22481
|
+
// src/intel-worktree.ts
|
|
22482
|
+
async function startWorktreeIntel(d) {
|
|
22483
|
+
const file = attachFilePath(d.stateDir, d.route, d.number);
|
|
22484
|
+
const probe = d.probe ?? probeIntel;
|
|
22485
|
+
let outcome;
|
|
22486
|
+
try {
|
|
22487
|
+
const known = readAttachFile(file);
|
|
22488
|
+
outcome = known ? await probe({ runHook: async () => ({ code: 0, output: known }) }, {}, 3) : { state: "absent" };
|
|
22489
|
+
if (outcome.state !== "ready") {
|
|
22490
|
+
const prepared = await d.prepare(d.worktree);
|
|
22491
|
+
if (prepared && prepared.code !== 0) {
|
|
22492
|
+
outcome = { state: "failed", reason: `setup exited ${prepared.code}` };
|
|
22493
|
+
} else {
|
|
22494
|
+
const env = { CREW_TICKET: d.ticketTag, CREW_BRANCH: d.branch };
|
|
22495
|
+
const ports = await d.runHook("ports", d.worktree, env);
|
|
22496
|
+
if (ports) Object.assign(env, parseEnvLines(ports.output));
|
|
22497
|
+
outcome = await probe({ runHook: (e) => d.runHook("intel", d.worktree, e) }, env, d.startupSeconds);
|
|
22498
|
+
}
|
|
22499
|
+
}
|
|
22500
|
+
if (outcome.state === "ready") writeAttachFile(file, outcome.url);
|
|
22501
|
+
} catch (e) {
|
|
22502
|
+
outcome = { state: "failed", reason: e.message.slice(0, 200) };
|
|
22503
|
+
}
|
|
22504
|
+
const tail3 = outcome.state === "ready" ? " The session's `code` server attaches to it within a second; until it lists these tools it still shows only `code_status`." : "";
|
|
22505
|
+
return `${codeToolsParagraph(outcome)}${tail3}`;
|
|
22506
|
+
}
|
|
22507
|
+
|
|
22302
22508
|
// src/outage.ts
|
|
22303
22509
|
var TRANSIENT_BACKOFF_MS = [15e3, 3e4, 6e4];
|
|
22304
22510
|
var OUTAGE_ESCALATE_AFTER = 5;
|
|
@@ -22331,8 +22537,8 @@ function describeCause(err3) {
|
|
|
22331
22537
|
}
|
|
22332
22538
|
|
|
22333
22539
|
// src/daemon.ts
|
|
22334
|
-
import { existsSync as
|
|
22335
|
-
import { join as
|
|
22540
|
+
import { existsSync as existsSync47, mkdirSync as mkdirSync36, readFileSync as readFileSync43, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync36 } from "fs";
|
|
22541
|
+
import { join as join50 } from "path";
|
|
22336
22542
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
22337
22543
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
22338
22544
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
@@ -22420,7 +22626,7 @@ function latestMtime(path) {
|
|
|
22420
22626
|
if (!stat2.isDirectory()) return 0;
|
|
22421
22627
|
let latest = stat2.mtimeMs;
|
|
22422
22628
|
for (const entry of readdirSync17(path, { withFileTypes: true })) {
|
|
22423
|
-
latest = Math.max(latest, latestMtime(
|
|
22629
|
+
latest = Math.max(latest, latestMtime(join50(path, entry.name)));
|
|
22424
22630
|
}
|
|
22425
22631
|
return latest;
|
|
22426
22632
|
}
|
|
@@ -22429,10 +22635,10 @@ function computeTreeSignature(paths) {
|
|
|
22429
22635
|
}
|
|
22430
22636
|
function makeStaleChecker(paths, opts = {}) {
|
|
22431
22637
|
const initial = computeTreeSignature(paths);
|
|
22432
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
22638
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync47(opts.reloadSentinel);
|
|
22433
22639
|
}
|
|
22434
22640
|
function reloadSentinelFor(stateDir) {
|
|
22435
|
-
return
|
|
22641
|
+
return join50(stateDir, "reload-requested");
|
|
22436
22642
|
}
|
|
22437
22643
|
function readReloadRequestedAt(stateDir) {
|
|
22438
22644
|
try {
|
|
@@ -22442,17 +22648,17 @@ function readReloadRequestedAt(stateDir) {
|
|
|
22442
22648
|
}
|
|
22443
22649
|
}
|
|
22444
22650
|
function daemonStampFor(stateDir) {
|
|
22445
|
-
return
|
|
22651
|
+
return join50(stateDir, "daemon.started");
|
|
22446
22652
|
}
|
|
22447
22653
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
22448
|
-
|
|
22449
|
-
|
|
22450
|
-
|
|
22654
|
+
mkdirSync36(stateDir, { recursive: true });
|
|
22655
|
+
rmSync22(reloadSentinelFor(stateDir), { force: true });
|
|
22656
|
+
writeFileSync36(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
22451
22657
|
`);
|
|
22452
22658
|
}
|
|
22453
22659
|
function readDaemonStarted(stateDir) {
|
|
22454
22660
|
try {
|
|
22455
|
-
const v = JSON.parse(
|
|
22661
|
+
const v = JSON.parse(readFileSync43(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
22456
22662
|
return typeof v === "number" ? v : null;
|
|
22457
22663
|
} catch {
|
|
22458
22664
|
return null;
|
|
@@ -23018,18 +23224,18 @@ import { createHash as createHash10 } from "crypto";
|
|
|
23018
23224
|
import {
|
|
23019
23225
|
chmodSync as chmodSync4,
|
|
23020
23226
|
closeSync as closeSync6,
|
|
23021
|
-
existsSync as
|
|
23227
|
+
existsSync as existsSync48,
|
|
23022
23228
|
fstatSync,
|
|
23023
23229
|
openSync as openSync6,
|
|
23024
23230
|
readSync as readSync3,
|
|
23025
23231
|
readdirSync as readdirSync18,
|
|
23026
|
-
rmSync as
|
|
23232
|
+
rmSync as rmSync23,
|
|
23027
23233
|
statSync as statSync15,
|
|
23028
23234
|
watch
|
|
23029
23235
|
} from "fs";
|
|
23030
23236
|
import { createConnection } from "net";
|
|
23031
23237
|
import { createServer as createServer2 } from "http";
|
|
23032
|
-
import { basename as basename14, dirname as
|
|
23238
|
+
import { basename as basename14, dirname as dirname31, join as join51 } from "path";
|
|
23033
23239
|
import { execFile as execFile3, spawn as spawn12 } from "child_process";
|
|
23034
23240
|
|
|
23035
23241
|
// src/periscope-control.ts
|
|
@@ -23258,7 +23464,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
|
23258
23464
|
// src/control-api.ts
|
|
23259
23465
|
var PAUSABLE = ["dev", "design", "qa"];
|
|
23260
23466
|
var CONTROL_SOCKET_NAME = "control.sock";
|
|
23261
|
-
var controlSocketFor = (stateDir) =>
|
|
23467
|
+
var controlSocketFor = (stateDir) => join51(stateDir, CONTROL_SOCKET_NAME);
|
|
23262
23468
|
var MAX_SOCKET_PATH = 100;
|
|
23263
23469
|
var MAX_TAIL_BYTES = 1024 * 1024;
|
|
23264
23470
|
var MAX_WAIT_SECONDS = 30;
|
|
@@ -23345,7 +23551,7 @@ function candidates(live, rotated) {
|
|
|
23345
23551
|
const out = [live];
|
|
23346
23552
|
if (!rotated) return out;
|
|
23347
23553
|
try {
|
|
23348
|
-
for (const n of readdirSync18(
|
|
23554
|
+
for (const n of readdirSync18(dirname31(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join51(dirname31(live), n));
|
|
23349
23555
|
} catch {
|
|
23350
23556
|
}
|
|
23351
23557
|
return out;
|
|
@@ -23389,7 +23595,7 @@ function lastLines2(path, n) {
|
|
|
23389
23595
|
}
|
|
23390
23596
|
function readTail(live, opts) {
|
|
23391
23597
|
const max = opts.maxBytes ?? MAX_TAIL_BYTES;
|
|
23392
|
-
if (!
|
|
23598
|
+
if (!existsSync48(live)) {
|
|
23393
23599
|
return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
|
|
23394
23600
|
}
|
|
23395
23601
|
const cur = opts.after ? decodeCursor(opts.after) : null;
|
|
@@ -23505,7 +23711,7 @@ async function tailReply(path, q2, rotated, abort) {
|
|
|
23505
23711
|
const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
|
|
23506
23712
|
let r = readTail(path, { after, tail: tail3, rotated });
|
|
23507
23713
|
while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
|
|
23508
|
-
await waitForChange(
|
|
23714
|
+
await waitForChange(dirname31(path), deadline - Date.now(), abort);
|
|
23509
23715
|
r = readTail(path, { after: r.cursor, rotated });
|
|
23510
23716
|
}
|
|
23511
23717
|
return reply2(200, r);
|
|
@@ -23545,8 +23751,8 @@ async function route(deps, method, url, req, abort) {
|
|
|
23545
23751
|
if (p.startsWith("/streams/") && get2) {
|
|
23546
23752
|
const run3 = decodeURIComponent(p.slice("/streams/".length));
|
|
23547
23753
|
if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
|
|
23548
|
-
const file =
|
|
23549
|
-
if (!
|
|
23754
|
+
const file = join51(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
|
|
23755
|
+
if (!existsSync48(file)) return err2(404, "no such run");
|
|
23550
23756
|
return tailReply(file, url.searchParams, false, abort);
|
|
23551
23757
|
}
|
|
23552
23758
|
if ((p === "/pause" || p === "/resume") && post3) {
|
|
@@ -23590,7 +23796,7 @@ function send(res, r) {
|
|
|
23590
23796
|
res.end(body);
|
|
23591
23797
|
}
|
|
23592
23798
|
async function socketIsStale(path) {
|
|
23593
|
-
if (!
|
|
23799
|
+
if (!existsSync48(path)) return false;
|
|
23594
23800
|
return new Promise((resolve8) => {
|
|
23595
23801
|
const c = createConnection(path);
|
|
23596
23802
|
c.once("connect", () => {
|
|
@@ -23608,8 +23814,8 @@ async function startControlServer(deps) {
|
|
|
23608
23814
|
log(`control API not started: socket path is too long for a unix socket (${path})`);
|
|
23609
23815
|
return null;
|
|
23610
23816
|
}
|
|
23611
|
-
if (await socketIsStale(path))
|
|
23612
|
-
else if (
|
|
23817
|
+
if (await socketIsStale(path)) rmSync23(path, { force: true });
|
|
23818
|
+
else if (existsSync48(path)) {
|
|
23613
23819
|
log(`control API not started: ${path} is in use by another process`);
|
|
23614
23820
|
return null;
|
|
23615
23821
|
}
|
|
@@ -23655,7 +23861,7 @@ async function startControlServer(deps) {
|
|
|
23655
23861
|
path,
|
|
23656
23862
|
close: () => new Promise((done) => {
|
|
23657
23863
|
server.close(() => {
|
|
23658
|
-
|
|
23864
|
+
rmSync23(path, { force: true });
|
|
23659
23865
|
done();
|
|
23660
23866
|
});
|
|
23661
23867
|
})
|
|
@@ -23695,57 +23901,57 @@ function crewJson(args, timeoutMs = 6e4) {
|
|
|
23695
23901
|
|
|
23696
23902
|
// src/periscope-mcp.ts
|
|
23697
23903
|
import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
|
|
23698
|
-
import { basename as basename15, dirname as
|
|
23904
|
+
import { basename as basename15, dirname as dirname32, isAbsolute as isAbsolute5, join as join53, relative as relative2, resolve as resolve6, sep } from "path";
|
|
23699
23905
|
import { z } from "zod";
|
|
23700
23906
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
23701
23907
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
23702
23908
|
|
|
23703
23909
|
// src/periscope-endpoints.ts
|
|
23704
23910
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
23705
|
-
import { chmodSync as chmodSync5, existsSync as
|
|
23706
|
-
import { join as
|
|
23707
|
-
var secretDir = (stateDir) =>
|
|
23708
|
-
var secretFile = (stateDir, id) =>
|
|
23709
|
-
var endpointDir = (stateDir) =>
|
|
23710
|
-
var endpointFile = (stateDir, id) =>
|
|
23911
|
+
import { chmodSync as chmodSync5, existsSync as existsSync49, mkdirSync as mkdirSync37, readdirSync as readdirSync19, readFileSync as readFileSync44, rmSync as rmSync24, writeFileSync as writeFileSync37 } from "fs";
|
|
23912
|
+
import { join as join52 } from "path";
|
|
23913
|
+
var secretDir = (stateDir) => join52(stateDir, "periscope-mcp");
|
|
23914
|
+
var secretFile = (stateDir, id) => join52(secretDir(stateDir), `${id}.json`);
|
|
23915
|
+
var endpointDir = (stateDir) => join52(stateDir, "periscope-endpoints");
|
|
23916
|
+
var endpointFile = (stateDir, id) => join52(endpointDir(stateDir), `${id}.json`);
|
|
23711
23917
|
function mintPeriscopeSecret(stateDir, ticketRecordId) {
|
|
23712
|
-
|
|
23918
|
+
mkdirSync37(secretDir(stateDir), { recursive: true, mode: 448 });
|
|
23713
23919
|
const secret = randomBytes3(32).toString("base64url");
|
|
23714
23920
|
const file = secretFile(stateDir, ticketRecordId);
|
|
23715
|
-
|
|
23921
|
+
writeFileSync37(file, `${JSON.stringify({ secret })}
|
|
23716
23922
|
`, { mode: 384 });
|
|
23717
23923
|
chmodSync5(file, 384);
|
|
23718
23924
|
return secret;
|
|
23719
23925
|
}
|
|
23720
23926
|
function readPeriscopeSecret(stateDir, ticketRecordId) {
|
|
23721
23927
|
try {
|
|
23722
|
-
const v = JSON.parse(
|
|
23928
|
+
const v = JSON.parse(readFileSync44(secretFile(stateDir, ticketRecordId), "utf8"));
|
|
23723
23929
|
return typeof v.secret === "string" && v.secret ? v.secret : void 0;
|
|
23724
23930
|
} catch {
|
|
23725
23931
|
return void 0;
|
|
23726
23932
|
}
|
|
23727
23933
|
}
|
|
23728
23934
|
function removePeriscopeSecret(stateDir, ticketRecordId) {
|
|
23729
|
-
|
|
23935
|
+
rmSync24(secretFile(stateDir, ticketRecordId), { force: true });
|
|
23730
23936
|
}
|
|
23731
23937
|
function readPeriscopeEndpoint(stateDir, ticketRecordId) {
|
|
23732
23938
|
try {
|
|
23733
|
-
return JSON.parse(
|
|
23939
|
+
return JSON.parse(readFileSync44(endpointFile(stateDir, ticketRecordId), "utf8"));
|
|
23734
23940
|
} catch {
|
|
23735
23941
|
return void 0;
|
|
23736
23942
|
}
|
|
23737
23943
|
}
|
|
23738
23944
|
function writePeriscopeEndpoint(stateDir, rec) {
|
|
23739
|
-
|
|
23740
|
-
|
|
23945
|
+
mkdirSync37(endpointDir(stateDir), { recursive: true });
|
|
23946
|
+
writeFileSync37(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
|
|
23741
23947
|
`);
|
|
23742
23948
|
}
|
|
23743
23949
|
function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
23744
|
-
|
|
23950
|
+
rmSync24(endpointFile(stateDir, ticketRecordId), { force: true });
|
|
23745
23951
|
}
|
|
23746
23952
|
function listPeriscopeEndpoints(stateDir) {
|
|
23747
23953
|
const dir = endpointDir(stateDir);
|
|
23748
|
-
if (!
|
|
23954
|
+
if (!existsSync49(dir)) return [];
|
|
23749
23955
|
const out = [];
|
|
23750
23956
|
for (const name of readdirSync19(dir)) {
|
|
23751
23957
|
if (!name.endsWith(".json")) continue;
|
|
@@ -23833,10 +24039,10 @@ async function realAncestor(p) {
|
|
|
23833
24039
|
}
|
|
23834
24040
|
if (isLink) {
|
|
23835
24041
|
if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
|
|
23836
|
-
cur = resolve6(
|
|
24042
|
+
cur = resolve6(dirname32(cur), await readlink(cur));
|
|
23837
24043
|
continue;
|
|
23838
24044
|
}
|
|
23839
|
-
const parent =
|
|
24045
|
+
const parent = dirname32(cur);
|
|
23840
24046
|
if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
|
|
23841
24047
|
tail3.push(basename15(cur));
|
|
23842
24048
|
cur = parent;
|
|
@@ -23848,13 +24054,13 @@ async function resolveInWorktree(root, requested) {
|
|
|
23848
24054
|
let abs;
|
|
23849
24055
|
if (isAbsolute5(requested)) {
|
|
23850
24056
|
abs = resolve6(requested);
|
|
23851
|
-
if (isInside(root, abs)) abs =
|
|
24057
|
+
if (isInside(root, abs)) abs = join53(realRoot, relative2(root, abs));
|
|
23852
24058
|
} else {
|
|
23853
24059
|
abs = resolve6(realRoot, requested);
|
|
23854
24060
|
}
|
|
23855
24061
|
if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
23856
24062
|
const { real, rest } = await realAncestor(abs);
|
|
23857
|
-
const final = rest ?
|
|
24063
|
+
const final = rest ? join53(real, rest) : real;
|
|
23858
24064
|
if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
23859
24065
|
if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
|
|
23860
24066
|
return final;
|
|
@@ -23922,7 +24128,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
23922
24128
|
const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
|
|
23923
24129
|
const lines2 = await Promise.all(entries.map(async (e) => {
|
|
23924
24130
|
if (e.isDirectory()) return `[DIR] ${e.name}`;
|
|
23925
|
-
const size = (await lstat(
|
|
24131
|
+
const size = (await lstat(join53(abs, e.name))).size;
|
|
23926
24132
|
return `[FILE] ${e.name} (${size} bytes)`;
|
|
23927
24133
|
}));
|
|
23928
24134
|
return text(lines2.join("\n") || "(empty)");
|
|
@@ -23947,7 +24153,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
23947
24153
|
if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
|
|
23948
24154
|
const abs = await inTree(path);
|
|
23949
24155
|
if (abs === await realpath(root)) return text("path must name a file", true);
|
|
23950
|
-
await mkdir(
|
|
24156
|
+
await mkdir(dirname32(abs), { recursive: true });
|
|
23951
24157
|
await inTree(path);
|
|
23952
24158
|
await writeFile(abs, content);
|
|
23953
24159
|
return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
|
|
@@ -23987,8 +24193,8 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
23987
24193
|
inputSchema: { path: z.string() },
|
|
23988
24194
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
23989
24195
|
}, async ({ path }) => {
|
|
23990
|
-
const parent = await inTree(
|
|
23991
|
-
const abs =
|
|
24196
|
+
const parent = await inTree(dirname32(path) === "" ? "." : dirname32(path));
|
|
24197
|
+
const abs = join53(parent, basename15(path));
|
|
23992
24198
|
if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
|
|
23993
24199
|
const st = await lstat(abs).catch(() => void 0);
|
|
23994
24200
|
if (!st) return text(`${path} does not exist`, true);
|
|
@@ -24109,7 +24315,7 @@ function findPeriscope(sidecars, segment) {
|
|
|
24109
24315
|
}
|
|
24110
24316
|
|
|
24111
24317
|
// src/cli.ts
|
|
24112
|
-
var CREW_HOME = isCompiledBinary(import.meta.url) ?
|
|
24318
|
+
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname33(process.execPath) : resolve7(dirname33(fileURLToPath(import.meta.url)), "..");
|
|
24113
24319
|
var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
|
|
24114
24320
|
var CYCLE_SECONDS2 = 120;
|
|
24115
24321
|
var STALE_CYCLES = 3;
|
|
@@ -24367,6 +24573,7 @@ if (!command) usage(argv.length ? `unknown option "${argv[0]}"` : void 0);
|
|
|
24367
24573
|
var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
24368
24574
|
"passenger-sync-daemon",
|
|
24369
24575
|
"periscope-proxy",
|
|
24576
|
+
"intel",
|
|
24370
24577
|
"agents",
|
|
24371
24578
|
"connect",
|
|
24372
24579
|
"credential",
|
|
@@ -24478,7 +24685,7 @@ if (command === "provision") {
|
|
|
24478
24685
|
process.exit(outcome.code);
|
|
24479
24686
|
}
|
|
24480
24687
|
if (command === "pool-member") {
|
|
24481
|
-
const pool = poolConfigFrom(argv, process.env, () =>
|
|
24688
|
+
const pool = poolConfigFrom(argv, process.env, () => readFileSync45(0, "utf8"));
|
|
24482
24689
|
if (typeof pool === "string") {
|
|
24483
24690
|
process.stderr.write(`crew pool-member: ${pool}
|
|
24484
24691
|
`);
|
|
@@ -24512,6 +24719,14 @@ if (command === "passenger-sync-daemon") {
|
|
|
24512
24719
|
process.exit(2);
|
|
24513
24720
|
}
|
|
24514
24721
|
runProxyFromEnv(dropFile);
|
|
24722
|
+
} else if (command === "intel") {
|
|
24723
|
+
const [, sub, bridgeRoute, bridgeTicket] = positional;
|
|
24724
|
+
const stateDir = value("state-dir");
|
|
24725
|
+
if (sub !== "bridge" || !bridgeRoute || !bridgeTicket || !stateDir) {
|
|
24726
|
+
process.stderr.write("usage: crew intel bridge <route> <ticket number> --state-dir DIR (registered by a lane run, not run by hand)\n");
|
|
24727
|
+
process.exit(2);
|
|
24728
|
+
}
|
|
24729
|
+
await runBridge(stateDir, bridgeRoute, bridgeTicket);
|
|
24515
24730
|
} else {
|
|
24516
24731
|
let requireArmed = function(what) {
|
|
24517
24732
|
if (dryRun || route2.enabled) return;
|
|
@@ -24713,7 +24928,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
24713
24928
|
} else {
|
|
24714
24929
|
const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
|
|
24715
24930
|
const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
|
|
24716
|
-
if (configless && !
|
|
24931
|
+
if (configless && !existsSync50(findConfigFile(CREW_HOME))) {
|
|
24717
24932
|
cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
|
|
24718
24933
|
} else {
|
|
24719
24934
|
cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
|
|
@@ -24728,7 +24943,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
24728
24943
|
throw e;
|
|
24729
24944
|
}
|
|
24730
24945
|
if (command === "credential") {
|
|
24731
|
-
const r = runCredential(positional[1],
|
|
24946
|
+
const r = runCredential(positional[1], readFileSync45(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
|
|
24732
24947
|
process.stdout.write(r.out);
|
|
24733
24948
|
process.stderr.write(r.err);
|
|
24734
24949
|
process.exit(r.code);
|
|
@@ -24740,9 +24955,9 @@ if (command === "passenger-sync-daemon") {
|
|
|
24740
24955
|
forges: cfg.ship.forges,
|
|
24741
24956
|
stateDir: cfg.ship.stateDir,
|
|
24742
24957
|
configFile: cfg.configFile,
|
|
24743
|
-
readFile: (p) =>
|
|
24744
|
-
writeFile: (p, t) =>
|
|
24745
|
-
stdin: () =>
|
|
24958
|
+
readFile: (p) => readFileSync45(p, "utf8"),
|
|
24959
|
+
writeFile: (p, t) => writeFileSync38(p, t),
|
|
24960
|
+
stdin: () => readFileSync45(0, "utf8"),
|
|
24746
24961
|
env: process.env
|
|
24747
24962
|
}
|
|
24748
24963
|
);
|
|
@@ -25035,8 +25250,8 @@ if (command === "passenger-sync-daemon") {
|
|
|
25035
25250
|
}
|
|
25036
25251
|
if (host === "linux" && hasSystemd) {
|
|
25037
25252
|
const label = labelFor(CREW_HOME, "run");
|
|
25038
|
-
const staleTimer =
|
|
25039
|
-
if (
|
|
25253
|
+
const staleTimer = join54(homedir11(), ".config", "systemd", "user", `${label}.timer`);
|
|
25254
|
+
if (existsSync50(staleTimer)) {
|
|
25040
25255
|
process.stdout.write(
|
|
25041
25256
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
25042
25257
|
`
|
|
@@ -25063,7 +25278,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25063
25278
|
const tracker = new Tracker(c, cfg.ship);
|
|
25064
25279
|
problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
|
|
25065
25280
|
for (const r of await resolvedRepos(c)) {
|
|
25066
|
-
if (!
|
|
25281
|
+
if (!existsSync50(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
25067
25282
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
25068
25283
|
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
25069
25284
|
}
|
|
@@ -25090,7 +25305,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25090
25305
|
}
|
|
25091
25306
|
async function doctorFix(o) {
|
|
25092
25307
|
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))));
|
|
25093
|
-
const unitExists = (p) =>
|
|
25308
|
+
const unitExists = (p) => existsSync50(p);
|
|
25094
25309
|
const crontab = o.host === "linux" ? spawnSync11("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
25095
25310
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
|
|
25096
25311
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -25107,7 +25322,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
25107
25322
|
for (const a of plan.actions) {
|
|
25108
25323
|
if (a.kind === "enable") {
|
|
25109
25324
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
25110
|
-
|
|
25325
|
+
writeFileSync38(cfg.configFile, planEnable(readFileSync45(cfg.configFile, "utf8"), a.route));
|
|
25111
25326
|
enabledSound.add(a.route);
|
|
25112
25327
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
25113
25328
|
} else {
|
|
@@ -26246,7 +26461,7 @@ ${cu.tail.trimEnd()}
|
|
|
26246
26461
|
process.exit(1);
|
|
26247
26462
|
}
|
|
26248
26463
|
requireArmed("run the daemon loop");
|
|
26249
|
-
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [
|
|
26464
|
+
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join54(CREW_HOME, "src"), join54(CREW_HOME, "dist"), join54(CREW_HOME, "bin")];
|
|
26250
26465
|
const configPaths = daemonConfigPaths(cfg);
|
|
26251
26466
|
watchPaths.push(...configPaths);
|
|
26252
26467
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -26551,10 +26766,10 @@ ${cu.tail.trimEnd()}
|
|
|
26551
26766
|
`);
|
|
26552
26767
|
if (!dryRun) {
|
|
26553
26768
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
26554
|
-
const raw =
|
|
26769
|
+
const raw = existsSync50(resolvedPath) ? JSON.parse(readFileSync45(resolvedPath, "utf8")) : {};
|
|
26555
26770
|
raw.agentSkills = result.agentSkills;
|
|
26556
|
-
|
|
26557
|
-
|
|
26771
|
+
mkdirSync38(dirOf2(resolvedPath), { recursive: true });
|
|
26772
|
+
writeFileSync38(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
26558
26773
|
`);
|
|
26559
26774
|
}
|
|
26560
26775
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -26577,7 +26792,7 @@ ${cu.tail.trimEnd()}
|
|
|
26577
26792
|
throw e;
|
|
26578
26793
|
}
|
|
26579
26794
|
const oldName = cfg.ship.name;
|
|
26580
|
-
|
|
26795
|
+
writeFileSync38(cfg.configFile, setShipNameInYaml(readFileSync45(cfg.configFile, "utf8"), newName));
|
|
26581
26796
|
cfg.ship.name = newName;
|
|
26582
26797
|
let renameMachineId;
|
|
26583
26798
|
try {
|
|
@@ -26624,14 +26839,14 @@ ${cu.tail.trimEnd()}
|
|
|
26624
26839
|
process.exit(2);
|
|
26625
26840
|
};
|
|
26626
26841
|
if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks" && sub !== "check") usage2();
|
|
26627
|
-
const configText =
|
|
26842
|
+
const configText = readFileSync45(cfg.configFile, "utf8");
|
|
26628
26843
|
if (sub === "check") {
|
|
26629
26844
|
try {
|
|
26630
26845
|
const routeArg = value("route") ?? positional[2];
|
|
26631
26846
|
const found = cfg.routes.find((r) => r.route === routeArg);
|
|
26632
26847
|
if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
|
|
26633
26848
|
const named2 = value("route") ? void 0 : positional[3];
|
|
26634
|
-
const dir = named2 ? repoDirOf(configText,
|
|
26849
|
+
const dir = named2 ? repoDirOf(configText, dirname33(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
|
|
26635
26850
|
const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
|
|
26636
26851
|
const o = target ? found.repoOverrides[target.name] : void 0;
|
|
26637
26852
|
runReposCheck(dir, {
|
|
@@ -26654,9 +26869,9 @@ ${cu.tail.trimEnd()}
|
|
|
26654
26869
|
if (sub === "hooks") {
|
|
26655
26870
|
try {
|
|
26656
26871
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
26657
|
-
const dir = repoDirOf(configText,
|
|
26658
|
-
if (!
|
|
26659
|
-
const files = { exists: (r) =>
|
|
26872
|
+
const dir = repoDirOf(configText, dirname33(cfg.configFile), a.route, a.name);
|
|
26873
|
+
if (!existsSync50(dir)) throw new ReposError(`${dir} does not exist on disk`);
|
|
26874
|
+
const files = { exists: (r) => existsSync50(resolvePath(dir, r)), list: (r) => {
|
|
26660
26875
|
try {
|
|
26661
26876
|
return readdirSync20(resolvePath(dir, r));
|
|
26662
26877
|
} catch {
|
|
@@ -26664,7 +26879,7 @@ ${cu.tail.trimEnd()}
|
|
|
26664
26879
|
}
|
|
26665
26880
|
}, read: (r) => {
|
|
26666
26881
|
try {
|
|
26667
|
-
return
|
|
26882
|
+
return readFileSync45(resolvePath(dir, r), "utf8");
|
|
26668
26883
|
} catch {
|
|
26669
26884
|
return void 0;
|
|
26670
26885
|
}
|
|
@@ -26706,19 +26921,19 @@ ${cu.tail.trimEnd()}
|
|
|
26706
26921
|
let cleared;
|
|
26707
26922
|
if (clear2) {
|
|
26708
26923
|
const plan = clearRouteHooks(configText, a.route, a.name);
|
|
26709
|
-
if (plan.removed.length > 0)
|
|
26924
|
+
if (plan.removed.length > 0) writeFileSync38(cfg.configFile, plan.text);
|
|
26710
26925
|
cleared = { file: cfg.configFile, removed: plan.removed };
|
|
26711
26926
|
}
|
|
26712
26927
|
if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
26713
26928
|
if (write === "route") {
|
|
26714
26929
|
const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
|
|
26715
|
-
|
|
26930
|
+
writeFileSync38(cfg.configFile, plan.text);
|
|
26716
26931
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
|
|
26717
26932
|
} else if (write === "repo") {
|
|
26718
26933
|
const have = existingRepoConfig(files);
|
|
26719
26934
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
26720
26935
|
const file = resolvePath(dir, ".crew.yaml");
|
|
26721
|
-
|
|
26936
|
+
writeFileSync38(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
|
|
26722
26937
|
written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
|
|
26723
26938
|
}
|
|
26724
26939
|
if (flag("json")) {
|
|
@@ -26774,7 +26989,7 @@ ${cu.tail.trimEnd()}
|
|
|
26774
26989
|
if (sub === "list" && !routeName) usage2();
|
|
26775
26990
|
if (sub === "add") {
|
|
26776
26991
|
try {
|
|
26777
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
26992
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync50(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
|
|
26778
26993
|
routeName = a.route;
|
|
26779
26994
|
addPath = a.path;
|
|
26780
26995
|
addName = a.name;
|
|
@@ -26804,7 +27019,7 @@ ${cu.tail.trimEnd()}
|
|
|
26804
27019
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
26805
27020
|
if (sub === "list" || sub === "add") {
|
|
26806
27021
|
const liveRoute = cfg.routes.find((r) => r.route === routeName);
|
|
26807
|
-
if (liveRoute?.resolved?.reposModelId &&
|
|
27022
|
+
if (liveRoute?.resolved?.reposModelId && existsSync50(resolvedFile) && !dryRun) {
|
|
26808
27023
|
try {
|
|
26809
27024
|
await hydrateApiKeys([liveRoute]);
|
|
26810
27025
|
persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
|
|
@@ -26813,16 +27028,16 @@ ${cu.tail.trimEnd()}
|
|
|
26813
27028
|
}
|
|
26814
27029
|
}
|
|
26815
27030
|
let resolvedRemotes;
|
|
26816
|
-
if (
|
|
27031
|
+
if (existsSync50(resolvedFile)) {
|
|
26817
27032
|
try {
|
|
26818
|
-
resolvedRemotes = JSON.parse(
|
|
27033
|
+
resolvedRemotes = JSON.parse(readFileSync45(resolvedFile, "utf8"));
|
|
26819
27034
|
} catch {
|
|
26820
27035
|
}
|
|
26821
27036
|
}
|
|
26822
27037
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
26823
27038
|
const text2 = configText;
|
|
26824
|
-
const base =
|
|
26825
|
-
const isCheckout = (d) =>
|
|
27039
|
+
const base = dirname33(cfg.configFile);
|
|
27040
|
+
const isCheckout = (d) => existsSync50(`${d}/.git`);
|
|
26826
27041
|
try {
|
|
26827
27042
|
if (sub === "list") {
|
|
26828
27043
|
if (flag("json")) {
|
|
@@ -26832,11 +27047,11 @@ ${cu.tail.trimEnd()}
|
|
|
26832
27047
|
base,
|
|
26833
27048
|
resolved: resolvedRemotes,
|
|
26834
27049
|
originOf,
|
|
26835
|
-
exists: (d) =>
|
|
27050
|
+
exists: (d) => existsSync50(d),
|
|
26836
27051
|
isCheckout,
|
|
26837
27052
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
26838
27053
|
try {
|
|
26839
|
-
return Object.keys(parseRepoConfig(
|
|
27054
|
+
return Object.keys(parseRepoConfig(readFileSync45(resolvePath(d, f), "utf8"), f).hooks).length > 0;
|
|
26840
27055
|
} catch {
|
|
26841
27056
|
return false;
|
|
26842
27057
|
}
|
|
@@ -26853,7 +27068,7 @@ ${cu.tail.trimEnd()}
|
|
|
26853
27068
|
for (const r of repos) {
|
|
26854
27069
|
const origin2 = originOf(r.dir);
|
|
26855
27070
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
26856
|
-
const note = !
|
|
27071
|
+
const note = !existsSync50(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
|
|
26857
27072
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
26858
27073
|
`);
|
|
26859
27074
|
}
|
|
@@ -26865,9 +27080,9 @@ ${cu.tail.trimEnd()}
|
|
|
26865
27080
|
` : `${human}
|
|
26866
27081
|
`);
|
|
26867
27082
|
const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
|
|
26868
|
-
if (
|
|
27083
|
+
if (existsSync50(plan2.target) && (existsSync50(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
|
|
26869
27084
|
throw new ReposError(
|
|
26870
|
-
|
|
27085
|
+
existsSync50(`${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`
|
|
26871
27086
|
);
|
|
26872
27087
|
}
|
|
26873
27088
|
if (dryRun) {
|
|
@@ -26885,14 +27100,14 @@ ${cu.tail.trimEnd()}
|
|
|
26885
27100
|
throw new ReposError(`${e.message}${authHint}
|
|
26886
27101
|
crew.yaml was not changed`);
|
|
26887
27102
|
}
|
|
26888
|
-
|
|
27103
|
+
writeFileSync38(cfg.configFile, plan2.text);
|
|
26889
27104
|
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:
|
|
26890
27105
|
`);
|
|
26891
27106
|
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}`);
|
|
26892
27107
|
break;
|
|
26893
27108
|
}
|
|
26894
27109
|
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
|
|
26895
|
-
const top =
|
|
27110
|
+
const top = existsSync50(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
26896
27111
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
26897
27112
|
const origin = originOf(top);
|
|
26898
27113
|
const plan = planRepoAdd({
|
|
@@ -26944,15 +27159,15 @@ ${cu.tail.trimEnd()}
|
|
|
26944
27159
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
26945
27160
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
26946
27161
|
`);
|
|
26947
|
-
if (
|
|
26948
|
-
const raw = JSON.parse(
|
|
27162
|
+
if (existsSync50(resolvedFile)) {
|
|
27163
|
+
const raw = JSON.parse(readFileSync45(resolvedFile, "utf8"));
|
|
26949
27164
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
26950
27165
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
26951
|
-
|
|
27166
|
+
writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
26952
27167
|
`);
|
|
26953
27168
|
}
|
|
26954
27169
|
}
|
|
26955
|
-
|
|
27170
|
+
writeFileSync38(cfg.configFile, plan.text);
|
|
26956
27171
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
26957
27172
|
`);
|
|
26958
27173
|
} catch (e) {
|
|
@@ -27323,7 +27538,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27323
27538
|
process.exit(2);
|
|
27324
27539
|
}
|
|
27325
27540
|
if (found.shipNameAdopted && !dryRun) {
|
|
27326
|
-
|
|
27541
|
+
writeFileSync38(cfg.configFile, setShipNameInYaml(readFileSync45(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
27327
27542
|
cfg.ship.name = found.shipNameAdopted;
|
|
27328
27543
|
}
|
|
27329
27544
|
if (found.shipRow) {
|
|
@@ -27366,10 +27581,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27366
27581
|
}
|
|
27367
27582
|
}
|
|
27368
27583
|
let previous;
|
|
27369
|
-
const hadPreviousFile =
|
|
27584
|
+
const hadPreviousFile = existsSync50(resolvedPath);
|
|
27370
27585
|
if (hadPreviousFile) {
|
|
27371
27586
|
try {
|
|
27372
|
-
previous = JSON.parse(
|
|
27587
|
+
previous = JSON.parse(readFileSync45(resolvedPath, "utf8"));
|
|
27373
27588
|
} catch {
|
|
27374
27589
|
}
|
|
27375
27590
|
}
|
|
@@ -27499,10 +27714,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27499
27714
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
27500
27715
|
`);
|
|
27501
27716
|
} else {
|
|
27502
|
-
|
|
27717
|
+
mkdirSync38(dirname33(resolvedPath), { recursive: true });
|
|
27503
27718
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
27504
27719
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
27505
|
-
|
|
27720
|
+
writeFileSync38(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
27506
27721
|
`);
|
|
27507
27722
|
renameSync6(tmpPath, resolvedPath);
|
|
27508
27723
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
@@ -27548,8 +27763,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27548
27763
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
27549
27764
|
`);
|
|
27550
27765
|
} else {
|
|
27551
|
-
|
|
27552
|
-
|
|
27766
|
+
mkdirSync38(dirname33(apiKeyFile), { recursive: true });
|
|
27767
|
+
writeFileSync38(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
27553
27768
|
`, { mode: 384 });
|
|
27554
27769
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
27555
27770
|
`);
|
|
@@ -28231,6 +28446,33 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28231
28446
|
}
|
|
28232
28447
|
const pathWt = applyRunWorktree(repoWt.dir, repoWt.config, planWt);
|
|
28233
28448
|
emit.emit(describeRunWorktreePlan(planWt));
|
|
28449
|
+
if (pathWt && planIsUsable(planWt) && repoWt.config.hooks.intel) {
|
|
28450
|
+
const shellWt = resolveShell(repoWt.config.shell ?? cfg.ship.shell);
|
|
28451
|
+
const paragraph = await startWorktreeIntel({
|
|
28452
|
+
worktree: pathWt,
|
|
28453
|
+
route: routeWt.route,
|
|
28454
|
+
number: String(nWt),
|
|
28455
|
+
stateDir: cfg.ship.stateDir,
|
|
28456
|
+
ticketTag: String(ticketWt.issue_tag ?? ticketWt.issue_id ?? `ISSUE-${nWt}`),
|
|
28457
|
+
branch: planWt.branch ?? "",
|
|
28458
|
+
startupSeconds: cfg.ship.intelStartupSeconds,
|
|
28459
|
+
prepare: async (path) => {
|
|
28460
|
+
for (const f of repoWt.config.worktrees.copy) {
|
|
28461
|
+
const src = join54(repoWt.dir, f);
|
|
28462
|
+
if (!existsSync50(src) || existsSync50(join54(path, f))) continue;
|
|
28463
|
+
mkdirSync38(dirOf2(join54(path, f)), { recursive: true });
|
|
28464
|
+
copyFileSync2(src, join54(path, f));
|
|
28465
|
+
}
|
|
28466
|
+
const setup = repoWt.config.hooks.setup;
|
|
28467
|
+
return setup ? runScript(setup, { cwd: path, shell: shellWt }) : null;
|
|
28468
|
+
},
|
|
28469
|
+
runHook: async (name, cwd, env) => {
|
|
28470
|
+
const script = repoWt.config.hooks[name];
|
|
28471
|
+
return script ? runScript(script, { cwd, env, shell: shellWt, timeoutMs: name === "intel" ? 6e4 : void 0 }) : null;
|
|
28472
|
+
}
|
|
28473
|
+
});
|
|
28474
|
+
emit.emit(paragraph);
|
|
28475
|
+
}
|
|
28234
28476
|
if (pathWt) process.stdout.write(`${pathWt}
|
|
28235
28477
|
`);
|
|
28236
28478
|
if (!planIsUsable(planWt)) process.exitCode = 1;
|
|
@@ -28430,7 +28672,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
28430
28672
|
const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
|
|
28431
28673
|
const resumed = await runPeriscopeResume({
|
|
28432
28674
|
find: async () => hit && { row: hit.row, tag: hit.tag },
|
|
28433
|
-
worktreeExists: (p) =>
|
|
28675
|
+
worktreeExists: (p) => existsSync50(p),
|
|
28434
28676
|
prepareDropFile,
|
|
28435
28677
|
startProxy: (wt, url, n) => launchProxy(crewInvocation(CREW_HOME), wt, url, Number(n), { host: hit.row.host || "localhost", inject: hit.repo.config.periscope?.inspector !== "repo" }),
|
|
28436
28678
|
caps: periscopeCaps(cfg.ship),
|
|
@@ -28526,10 +28768,10 @@ ${where.join("\n")}`);
|
|
|
28526
28768
|
},
|
|
28527
28769
|
prepare: async (path) => {
|
|
28528
28770
|
for (const f of repoP.config.worktrees.copy) {
|
|
28529
|
-
const src =
|
|
28530
|
-
if (!
|
|
28531
|
-
|
|
28532
|
-
copyFileSync2(src,
|
|
28771
|
+
const src = join54(repoP.dir, f);
|
|
28772
|
+
if (!existsSync50(src)) continue;
|
|
28773
|
+
mkdirSync38(dirOf2(join54(path, f)), { recursive: true });
|
|
28774
|
+
copyFileSync2(src, join54(path, f));
|
|
28533
28775
|
}
|
|
28534
28776
|
const setup = repoP.config.hooks.setup;
|
|
28535
28777
|
return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
|
|
@@ -28839,7 +29081,7 @@ ${where.join("\n")}`);
|
|
|
28839
29081
|
};
|
|
28840
29082
|
const droppedRoutes = [];
|
|
28841
29083
|
try {
|
|
28842
|
-
const rawCfg = parseYaml2(
|
|
29084
|
+
const rawCfg = parseYaml2(readFileSync45(cfg.configFile, "utf8"));
|
|
28843
29085
|
for (const r of rawCfg?.routes ?? []) {
|
|
28844
29086
|
if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
|
|
28845
29087
|
try {
|
|
@@ -28892,8 +29134,8 @@ ${where.join("\n")}`);
|
|
|
28892
29134
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
28893
29135
|
break;
|
|
28894
29136
|
}
|
|
28895
|
-
|
|
28896
|
-
|
|
29137
|
+
mkdirSync38(cfg.ship.stateDir, { recursive: true });
|
|
29138
|
+
writeFileSync38(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
28897
29139
|
`);
|
|
28898
29140
|
process.stdout.write(
|
|
28899
29141
|
"crew reload: requested \u2014 the daemon relaunches with the new config once its running agents end (it starts no new work meanwhile); if it is busy, `crew reload --now` forces the relaunch and interrupts them\n"
|
|
@@ -28958,7 +29200,7 @@ ${where.join("\n")}`);
|
|
|
28958
29200
|
}
|
|
28959
29201
|
case "log": {
|
|
28960
29202
|
try {
|
|
28961
|
-
const text2 =
|
|
29203
|
+
const text2 = readFileSync45(cfg.ship.logFile, "utf8").split("\n");
|
|
28962
29204
|
process.stdout.write(`${text2.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
28963
29205
|
`);
|
|
28964
29206
|
} catch {
|
|
@@ -28981,8 +29223,8 @@ ${where.join("\n")}`);
|
|
|
28981
29223
|
};
|
|
28982
29224
|
if (!route2.resolved) fail2(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
|
|
28983
29225
|
const tracker = new Tracker(route2, cfg.ship);
|
|
28984
|
-
const configText =
|
|
28985
|
-
const cfgBase =
|
|
29226
|
+
const configText = readFileSync45(cfg.configFile, "utf8");
|
|
29227
|
+
const cfgBase = dirname33(cfg.configFile);
|
|
28986
29228
|
const configured2 = configuredRepos(configText, route2.route, cfgBase);
|
|
28987
29229
|
const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
|
|
28988
29230
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
|
|
@@ -29010,7 +29252,7 @@ ${where.join("\n")}`);
|
|
|
29010
29252
|
}
|
|
29011
29253
|
if (configured2.some((r) => r.dir === dir)) {
|
|
29012
29254
|
try {
|
|
29013
|
-
|
|
29255
|
+
writeFileSync38(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
|
|
29014
29256
|
} catch (e) {
|
|
29015
29257
|
if (e instanceof ReposError) fail2(e.message);
|
|
29016
29258
|
throw e;
|
|
@@ -29018,29 +29260,29 @@ ${where.join("\n")}`);
|
|
|
29018
29260
|
}
|
|
29019
29261
|
if (row) {
|
|
29020
29262
|
await tracker.deleteRepoRow(row.id);
|
|
29021
|
-
if (
|
|
29022
|
-
const raw = JSON.parse(
|
|
29263
|
+
if (existsSync50(resolvedFile)) {
|
|
29264
|
+
const raw = JSON.parse(readFileSync45(resolvedFile, "utf8"));
|
|
29023
29265
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
29024
29266
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
29025
|
-
|
|
29267
|
+
writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
29026
29268
|
`);
|
|
29027
29269
|
}
|
|
29028
29270
|
}
|
|
29029
|
-
if (
|
|
29271
|
+
if (existsSync50(join54(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
|
|
29030
29272
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
29031
29273
|
break;
|
|
29032
29274
|
}
|
|
29033
29275
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
29034
|
-
const fresh = !
|
|
29035
|
-
if (
|
|
29276
|
+
const fresh = !existsSync50(join54(dir, ".git"));
|
|
29277
|
+
if (existsSync50(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
29036
29278
|
if (dryRun) {
|
|
29037
29279
|
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`);
|
|
29038
29280
|
break;
|
|
29039
29281
|
}
|
|
29040
29282
|
if (fresh) {
|
|
29041
|
-
|
|
29042
|
-
|
|
29043
|
-
|
|
29283
|
+
mkdirSync38(dir, { recursive: true });
|
|
29284
|
+
writeFileSync38(join54(dir, "README.md"), SAMPLE_README);
|
|
29285
|
+
writeFileSync38(join54(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
29044
29286
|
const g = (...a) => {
|
|
29045
29287
|
const r = spawnSync11("git", a, { cwd: dir, encoding: "utf8" });
|
|
29046
29288
|
if (r.status !== 0) fail2(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -29058,17 +29300,17 @@ ${where.join("\n")}`);
|
|
|
29058
29300
|
spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
|
|
29059
29301
|
});
|
|
29060
29302
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
29061
|
-
if (
|
|
29062
|
-
const raw = JSON.parse(
|
|
29303
|
+
if (existsSync50(resolvedFile)) {
|
|
29304
|
+
const raw = JSON.parse(readFileSync45(resolvedFile, "utf8"));
|
|
29063
29305
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
29064
29306
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
29065
|
-
|
|
29307
|
+
writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
29066
29308
|
`);
|
|
29067
29309
|
}
|
|
29068
29310
|
}
|
|
29069
29311
|
if (!configured2.some((r) => r.dir === dir)) {
|
|
29070
29312
|
try {
|
|
29071
|
-
|
|
29313
|
+
writeFileSync38(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
|
|
29072
29314
|
} catch (e) {
|
|
29073
29315
|
if (e instanceof ReposError) fail2(e.message);
|
|
29074
29316
|
throw e;
|
|
@@ -29414,7 +29656,7 @@ watermark: ${state.watermark()}
|
|
|
29414
29656
|
}
|
|
29415
29657
|
for (const r of repos) {
|
|
29416
29658
|
const remote = r.config.branch.remote;
|
|
29417
|
-
if (
|
|
29659
|
+
if (existsSync50(r.dir)) {
|
|
29418
29660
|
pushAuth.push({
|
|
29419
29661
|
route: c.route,
|
|
29420
29662
|
repo: r.name,
|
|
@@ -29509,7 +29751,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
29509
29751
|
}
|
|
29510
29752
|
try {
|
|
29511
29753
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
29512
|
-
const evSize =
|
|
29754
|
+
const evSize = existsSync50(evFile) ? statSync16(evFile).size : 0;
|
|
29513
29755
|
process.stdout.write(
|
|
29514
29756
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
29515
29757
|
` : "events log: ok\n"
|
|
@@ -29520,7 +29762,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
29520
29762
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run", installContext());
|
|
29521
29763
|
const abandons = unitAbandonsAgents(unitPlan, (p) => {
|
|
29522
29764
|
try {
|
|
29523
|
-
return
|
|
29765
|
+
return readFileSync45(p, "utf8");
|
|
29524
29766
|
} catch {
|
|
29525
29767
|
return null;
|
|
29526
29768
|
}
|