@fabricorg/cli 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fabric.js +1918 -707
- package/dist/fabric.js.map +1 -1
- package/dist/packages/cli/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/dist/fabric.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command as Command22 } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@fabricorg/cli",
|
|
9
|
-
version: "0.6.
|
|
9
|
+
version: "0.6.1",
|
|
10
10
|
description: "CLI for the Fabric AI platform",
|
|
11
11
|
keywords: [
|
|
12
12
|
"fabric",
|
|
@@ -235,10 +235,10 @@ var FabricHttpClient = class _FabricHttpClient {
|
|
|
235
235
|
* Inject default `org` / `personal` query params if the path doesn't already
|
|
236
236
|
* carry one. Resources that explicitly pass `?org=other` are untouched.
|
|
237
237
|
*/
|
|
238
|
-
buildUrl(
|
|
239
|
-
const url = `${this.baseUrl}/api/v1${
|
|
240
|
-
const hasOrg = /[?&]org=/.test(
|
|
241
|
-
const hasPersonal = /[?&]personal=/.test(
|
|
238
|
+
buildUrl(path30) {
|
|
239
|
+
const url = `${this.baseUrl}/api/v1${path30}`;
|
|
240
|
+
const hasOrg = /[?&]org=/.test(path30);
|
|
241
|
+
const hasPersonal = /[?&]personal=/.test(path30);
|
|
242
242
|
if (hasOrg || hasPersonal) {
|
|
243
243
|
return url;
|
|
244
244
|
}
|
|
@@ -251,11 +251,11 @@ var FabricHttpClient = class _FabricHttpClient {
|
|
|
251
251
|
if (additions.length === 0) {
|
|
252
252
|
return url;
|
|
253
253
|
}
|
|
254
|
-
const sep =
|
|
254
|
+
const sep = path30.includes("?") ? "&" : "?";
|
|
255
255
|
return `${url}${sep}${additions.join("&")}`;
|
|
256
256
|
}
|
|
257
|
-
async request(method,
|
|
258
|
-
const url = this.buildUrl(
|
|
257
|
+
async request(method, path30, options = {}) {
|
|
258
|
+
const url = this.buildUrl(path30);
|
|
259
259
|
const isMutating = MUTATING_METHODS.has(method);
|
|
260
260
|
const idempotencyKey = options.idempotencyKey ?? (isMutating ? randomId() : void 0);
|
|
261
261
|
const maxAttempts = (options.retry ?? this.retry).maxRetries + 1;
|
|
@@ -265,7 +265,7 @@ var FabricHttpClient = class _FabricHttpClient {
|
|
|
265
265
|
throw options.signal.reason;
|
|
266
266
|
}
|
|
267
267
|
const start = Date.now();
|
|
268
|
-
this.emit({ kind: "request", method, path:
|
|
268
|
+
this.emit({ kind: "request", method, path: path30, attempt: attempt2 });
|
|
269
269
|
try {
|
|
270
270
|
const result = await this.executeOnce(
|
|
271
271
|
method,
|
|
@@ -276,7 +276,7 @@ var FabricHttpClient = class _FabricHttpClient {
|
|
|
276
276
|
this.emit({
|
|
277
277
|
kind: "response",
|
|
278
278
|
method,
|
|
279
|
-
path:
|
|
279
|
+
path: path30,
|
|
280
280
|
attempt: attempt2,
|
|
281
281
|
status: 200,
|
|
282
282
|
durationMs: Date.now() - start
|
|
@@ -289,7 +289,7 @@ var FabricHttpClient = class _FabricHttpClient {
|
|
|
289
289
|
this.emit({
|
|
290
290
|
kind: retriable ? "retry" : "error",
|
|
291
291
|
method,
|
|
292
|
-
path:
|
|
292
|
+
path: path30,
|
|
293
293
|
attempt: attempt2,
|
|
294
294
|
status,
|
|
295
295
|
durationMs: Date.now() - start,
|
|
@@ -433,28 +433,28 @@ var FabricHttpClient = class _FabricHttpClient {
|
|
|
433
433
|
} catch {
|
|
434
434
|
}
|
|
435
435
|
}
|
|
436
|
-
get(
|
|
436
|
+
get(path30, options = {}) {
|
|
437
437
|
return this.request(
|
|
438
438
|
"GET",
|
|
439
|
-
|
|
439
|
+
path30,
|
|
440
440
|
options.signal ? { signal: options.signal } : {}
|
|
441
441
|
);
|
|
442
442
|
}
|
|
443
|
-
post(
|
|
444
|
-
return this.request("POST",
|
|
443
|
+
post(path30, body, options = {}) {
|
|
444
|
+
return this.request("POST", path30, {
|
|
445
445
|
body,
|
|
446
446
|
idempotencyKey: options.idempotencyKey,
|
|
447
447
|
retry: options.retry
|
|
448
448
|
});
|
|
449
449
|
}
|
|
450
|
-
patch(
|
|
451
|
-
return this.request("PATCH",
|
|
450
|
+
patch(path30, body, options = {}) {
|
|
451
|
+
return this.request("PATCH", path30, {
|
|
452
452
|
body,
|
|
453
453
|
idempotencyKey: options.idempotencyKey
|
|
454
454
|
});
|
|
455
455
|
}
|
|
456
|
-
put(
|
|
457
|
-
return this.request("PUT",
|
|
456
|
+
put(path30, body, options = {}) {
|
|
457
|
+
return this.request("PUT", path30, {
|
|
458
458
|
body,
|
|
459
459
|
idempotencyKey: options.idempotencyKey,
|
|
460
460
|
retry: options.retry
|
|
@@ -465,8 +465,8 @@ var FabricHttpClient = class _FabricHttpClient {
|
|
|
465
465
|
* delete names the path and the version in its body). Without `body`,
|
|
466
466
|
* nothing is sent, as before.
|
|
467
467
|
*/
|
|
468
|
-
delete(
|
|
469
|
-
return this.request("DELETE",
|
|
468
|
+
delete(path30, body, options = {}) {
|
|
469
|
+
return this.request("DELETE", path30, {
|
|
470
470
|
body,
|
|
471
471
|
idempotencyKey: options.idempotencyKey
|
|
472
472
|
});
|
|
@@ -1091,14 +1091,14 @@ function buildQuery3(opts) {
|
|
|
1091
1091
|
|
|
1092
1092
|
// ../sdk/src/resources/integrations.ts
|
|
1093
1093
|
var RESOURCE_BUILTINS = /* @__PURE__ */ new Set(["list", "constructor", "then"]);
|
|
1094
|
-
function makeDotPath(http, slug,
|
|
1095
|
-
const fn = (args) => http.post(`/integrations/${slug}/${
|
|
1094
|
+
function makeDotPath(http, slug, path30) {
|
|
1095
|
+
const fn = (args) => http.post(`/integrations/${slug}/${path30.join(".")}`, args ?? {});
|
|
1096
1096
|
return new Proxy(fn, {
|
|
1097
1097
|
get(target, prop) {
|
|
1098
1098
|
if (prop in target || typeof prop !== "string") {
|
|
1099
1099
|
return Reflect.get(target, prop);
|
|
1100
1100
|
}
|
|
1101
|
-
return makeDotPath(http, slug, [...
|
|
1101
|
+
return makeDotPath(http, slug, [...path30, prop]);
|
|
1102
1102
|
}
|
|
1103
1103
|
});
|
|
1104
1104
|
}
|
|
@@ -2238,19 +2238,23 @@ function pathAsCommandWord(file, platform = process.platform) {
|
|
|
2238
2238
|
}
|
|
2239
2239
|
return `"${normalised}"`;
|
|
2240
2240
|
}
|
|
2241
|
-
function launcherWords(
|
|
2241
|
+
function launcherWords(script) {
|
|
2242
2242
|
if (!isServedBundle()) {
|
|
2243
2243
|
return ["fabric"];
|
|
2244
2244
|
}
|
|
2245
2245
|
const tarball = bundleTarballPath();
|
|
2246
|
-
|
|
2247
|
-
|
|
2246
|
+
const packedFor = bakedOrigin();
|
|
2247
|
+
if (tarball !== void 0 && packedFor !== void 0) {
|
|
2248
|
+
return ["npx", "-y", `${packedFor}${tarball}`];
|
|
2248
2249
|
}
|
|
2249
2250
|
const word = pathAsCommandWord(script ?? bundleScriptPath());
|
|
2250
2251
|
return word === null ? ["fabric"] : ["node", word];
|
|
2251
2252
|
}
|
|
2252
|
-
function printableLauncherWords(
|
|
2253
|
-
|
|
2253
|
+
function printableLauncherWords() {
|
|
2254
|
+
if (launcherOrigin !== void 0 && launcherOrigin !== implicitOrigin() && !isSafeArgument(launcherOrigin)) {
|
|
2255
|
+
return null;
|
|
2256
|
+
}
|
|
2257
|
+
const words = launcherWords();
|
|
2254
2258
|
return words[0] === "npx" && !isSafeArgument(words[2] ?? "") ? null : words;
|
|
2255
2259
|
}
|
|
2256
2260
|
function launcherPathText() {
|
|
@@ -2262,7 +2266,7 @@ function implicitOrigin() {
|
|
|
2262
2266
|
}
|
|
2263
2267
|
function fabricCommand(args, origin = launcherOrigin) {
|
|
2264
2268
|
const deployment = origin ?? implicitOrigin();
|
|
2265
|
-
const words = printableLauncherWords(
|
|
2269
|
+
const words = printableLauncherWords();
|
|
2266
2270
|
const bound = deployment !== implicitOrigin() && !args.includes("--base-url") ? ` --base-url ${deployment}` : "";
|
|
2267
2271
|
if (words === null || bound !== "" && !isSafeArgument(deployment)) {
|
|
2268
2272
|
return NO_COMMAND;
|
|
@@ -2341,8 +2345,8 @@ function sameOriginEndpoint(value, field2, issuer) {
|
|
|
2341
2345
|
}
|
|
2342
2346
|
function authorizationServerMetadataUrl(issuer) {
|
|
2343
2347
|
const url = new URL(issuer);
|
|
2344
|
-
const
|
|
2345
|
-
return `${url.origin}/.well-known/oauth-authorization-server${
|
|
2348
|
+
const path30 = url.pathname.replace(/\/+$/, "");
|
|
2349
|
+
return `${url.origin}/.well-known/oauth-authorization-server${path30}`;
|
|
2346
2350
|
}
|
|
2347
2351
|
async function discoverAuthorizationServer(baseUrl, options = {}) {
|
|
2348
2352
|
const fetchImpl = options.fetch ?? ((input, init) => fetch(input, init));
|
|
@@ -2399,6 +2403,20 @@ async function discoverAuthorizationServer(baseUrl, options = {}) {
|
|
|
2399
2403
|
import { createServer } from "http";
|
|
2400
2404
|
var CALLBACK_PATH = "/callback";
|
|
2401
2405
|
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2406
|
+
var CallbackError = class extends Error {
|
|
2407
|
+
constructor(error, description) {
|
|
2408
|
+
super(
|
|
2409
|
+
error === "access_denied" ? "Sign-in was denied in the browser." : `Sign-in failed (${error}).`
|
|
2410
|
+
);
|
|
2411
|
+
this.error = error;
|
|
2412
|
+
this.description = description;
|
|
2413
|
+
this.name = "CallbackError";
|
|
2414
|
+
}
|
|
2415
|
+
};
|
|
2416
|
+
var FINAL_PAGE_HEADERS = {
|
|
2417
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
2418
|
+
Connection: "close"
|
|
2419
|
+
};
|
|
2402
2420
|
var PAGE = (title, body) => `<!doctype html><meta charset="utf-8"><title>${title}</title><body style="font-family:system-ui,sans-serif;max-width:32rem;margin:4rem auto;padding:0 1rem"><h1 style="font-weight:400">${title}</h1><p>${body}</p></body>`;
|
|
2403
2421
|
async function startLoopbackListener(options) {
|
|
2404
2422
|
const callbackPath = options.callbackPath ?? CALLBACK_PATH;
|
|
@@ -2415,9 +2433,9 @@ async function startLoopbackListener(options) {
|
|
|
2415
2433
|
});
|
|
2416
2434
|
result.catch(() => {
|
|
2417
2435
|
});
|
|
2418
|
-
const server = createServer((
|
|
2419
|
-
const url = new URL(
|
|
2420
|
-
if (
|
|
2436
|
+
const server = createServer((request2, response) => {
|
|
2437
|
+
const url = new URL(request2.url ?? "/", "http://127.0.0.1");
|
|
2438
|
+
if (request2.method !== "GET" || url.pathname !== callbackPath) {
|
|
2421
2439
|
response.writeHead(404).end();
|
|
2422
2440
|
return;
|
|
2423
2441
|
}
|
|
@@ -2434,17 +2452,17 @@ async function startLoopbackListener(options) {
|
|
|
2434
2452
|
return;
|
|
2435
2453
|
}
|
|
2436
2454
|
if (error) {
|
|
2437
|
-
|
|
2455
|
+
const failure = new CallbackError(
|
|
2456
|
+
error,
|
|
2457
|
+
url.searchParams.get("error_description")
|
|
2458
|
+
);
|
|
2459
|
+
response.writeHead(200, FINAL_PAGE_HEADERS).end(
|
|
2438
2460
|
PAGE(
|
|
2439
2461
|
"Sign-in cancelled",
|
|
2440
2462
|
"You can close this tab and return to your terminal."
|
|
2441
|
-
)
|
|
2463
|
+
),
|
|
2464
|
+
() => settle({ error: failure })
|
|
2442
2465
|
);
|
|
2443
|
-
settle({
|
|
2444
|
-
error: new Error(
|
|
2445
|
-
error === "access_denied" ? "Sign-in was denied in the browser." : `Sign-in failed (${error}).`
|
|
2446
|
-
)
|
|
2447
|
-
});
|
|
2448
2466
|
return;
|
|
2449
2467
|
}
|
|
2450
2468
|
if (!code) {
|
|
@@ -2456,13 +2474,13 @@ async function startLoopbackListener(options) {
|
|
|
2456
2474
|
);
|
|
2457
2475
|
return;
|
|
2458
2476
|
}
|
|
2459
|
-
response.writeHead(200,
|
|
2477
|
+
response.writeHead(200, FINAL_PAGE_HEADERS).end(
|
|
2460
2478
|
PAGE(
|
|
2461
2479
|
"Signed in",
|
|
2462
2480
|
"You can close this tab and return to your terminal."
|
|
2463
|
-
)
|
|
2481
|
+
),
|
|
2482
|
+
() => settle({ code, params: url.searchParams })
|
|
2464
2483
|
);
|
|
2465
|
-
settle({ code, params: url.searchParams });
|
|
2466
2484
|
});
|
|
2467
2485
|
const listen = (port2) => new Promise((resolve, reject) => {
|
|
2468
2486
|
server.once("error", reject);
|
|
@@ -2526,15 +2544,73 @@ function isProjectId(value) {
|
|
|
2526
2544
|
function projectResource(origin, audience, projectId) {
|
|
2527
2545
|
return `${origin.replace(/\/+$/, "")}${RESOURCE_BASE_PATH[audience]}/projects/${projectId}`;
|
|
2528
2546
|
}
|
|
2547
|
+
function normalizeServerUrl(value) {
|
|
2548
|
+
let parsed;
|
|
2549
|
+
try {
|
|
2550
|
+
parsed = new URL(value);
|
|
2551
|
+
} catch {
|
|
2552
|
+
return null;
|
|
2553
|
+
}
|
|
2554
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:" || parsed.username !== "" || parsed.password !== "") {
|
|
2555
|
+
return null;
|
|
2556
|
+
}
|
|
2557
|
+
return `${parsed.origin}${parsed.pathname.replace(/\/+$/, "")}${parsed.search}${parsed.hash}`;
|
|
2558
|
+
}
|
|
2529
2559
|
function isGatewayUrlOf(origin, url) {
|
|
2530
|
-
const gateway =
|
|
2531
|
-
|
|
2532
|
-
|
|
2560
|
+
const gateway = normalizeServerUrl(
|
|
2561
|
+
`${origin.replace(/\/+$/, "")}${RESOURCE_BASE_PATH.mcp}`
|
|
2562
|
+
);
|
|
2563
|
+
const candidate = normalizeServerUrl(url);
|
|
2564
|
+
if (gateway === null || candidate === null) {
|
|
2565
|
+
return false;
|
|
2566
|
+
}
|
|
2567
|
+
return candidate === gateway || new RegExp(`^${escaped(gateway)}/projects/[A-Za-z0-9_-]{1,64}$`).test(
|
|
2568
|
+
candidate
|
|
2569
|
+
);
|
|
2570
|
+
}
|
|
2571
|
+
function isOrgGatewayUrlOf(origin, url) {
|
|
2572
|
+
const gateway = normalizeServerUrl(
|
|
2573
|
+
`${origin.replace(/\/+$/, "")}${RESOURCE_BASE_PATH.mcp}`
|
|
2533
2574
|
);
|
|
2575
|
+
return gateway !== null && normalizeServerUrl(url) === gateway;
|
|
2534
2576
|
}
|
|
2535
2577
|
function escaped(text3) {
|
|
2536
2578
|
return text3.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2537
2579
|
}
|
|
2580
|
+
function originOf2(value) {
|
|
2581
|
+
const normal = normalizeServerUrl(value);
|
|
2582
|
+
return normal === null ? null : new URL(normal).origin;
|
|
2583
|
+
}
|
|
2584
|
+
function classifyGatewayUrl(origin, projectId, url) {
|
|
2585
|
+
const candidate = normalizeServerUrl(url);
|
|
2586
|
+
if (candidate === null || originOf2(url) !== originOf2(origin)) {
|
|
2587
|
+
return "foreign";
|
|
2588
|
+
}
|
|
2589
|
+
if (candidate === normalizeServerUrl(projectResource(origin, "mcp", projectId))) {
|
|
2590
|
+
return "this-project";
|
|
2591
|
+
}
|
|
2592
|
+
if (isOrgGatewayUrlOf(origin, url)) {
|
|
2593
|
+
return "org-wide";
|
|
2594
|
+
}
|
|
2595
|
+
return isGatewayUrlOf(origin, url) ? "other-project" : "unfamiliar";
|
|
2596
|
+
}
|
|
2597
|
+
function classifyGatewayResource(origin, projectId, resource) {
|
|
2598
|
+
const relation = classifyGatewayUrl(origin, projectId, resource);
|
|
2599
|
+
if (relation !== "unfamiliar") {
|
|
2600
|
+
return relation;
|
|
2601
|
+
}
|
|
2602
|
+
const api = normalizeServerUrl(
|
|
2603
|
+
`${origin.replace(/\/+$/, "")}${RESOURCE_BASE_PATH.api}`
|
|
2604
|
+
);
|
|
2605
|
+
const candidate = normalizeServerUrl(resource);
|
|
2606
|
+
const named = api === null || candidate === null ? null : new RegExp(
|
|
2607
|
+
`^${escaped(api)}/projects/([A-Za-z0-9_-]{1,64})$`
|
|
2608
|
+
).exec(candidate);
|
|
2609
|
+
if (named === null) {
|
|
2610
|
+
return "unfamiliar";
|
|
2611
|
+
}
|
|
2612
|
+
return named[1] === projectId ? "this-project" : "other-project";
|
|
2613
|
+
}
|
|
2538
2614
|
|
|
2539
2615
|
// src/lib/oauth/flow.ts
|
|
2540
2616
|
var REQUESTED_SCOPES = [
|
|
@@ -4111,7 +4187,7 @@ async function loginInBrowser(opts) {
|
|
|
4111
4187
|
if (origin === null) {
|
|
4112
4188
|
printError(BAD_BASE_URL_LINE, 2);
|
|
4113
4189
|
}
|
|
4114
|
-
const
|
|
4190
|
+
const forProject2 = opts.project === void 0 ? "" : ` for project ${opts.project}`;
|
|
4115
4191
|
let identity;
|
|
4116
4192
|
try {
|
|
4117
4193
|
identity = await signInWithBrowser({
|
|
@@ -4121,7 +4197,7 @@ async function loginInBrowser(opts) {
|
|
|
4121
4197
|
project: opts.project,
|
|
4122
4198
|
announce: (url) => {
|
|
4123
4199
|
process.stdout.write(
|
|
4124
|
-
`Opening your browser to sign in to ${effectiveBaseUrl}${
|
|
4200
|
+
`Opening your browser to sign in to ${effectiveBaseUrl}${forProject2}.
|
|
4125
4201
|
If it does not open, visit:
|
|
4126
4202
|
|
|
4127
4203
|
${url}
|
|
@@ -4137,7 +4213,7 @@ If it does not open, visit:
|
|
|
4137
4213
|
throw error;
|
|
4138
4214
|
}
|
|
4139
4215
|
printSuccess(
|
|
4140
|
-
`Authenticated as ${identity.name} (${identity.email})${
|
|
4216
|
+
`Authenticated as ${identity.name} (${identity.email})${forProject2}`
|
|
4141
4217
|
);
|
|
4142
4218
|
}
|
|
4143
4219
|
function buildLoginCommand() {
|
|
@@ -4649,16 +4725,47 @@ var AGENT_NAME = "Fabric";
|
|
|
4649
4725
|
function registrationPath() {
|
|
4650
4726
|
return join(dirname(getConfigPath()), "chatgpt-plan-host.json");
|
|
4651
4727
|
}
|
|
4652
|
-
async function
|
|
4728
|
+
async function loadRegistrationStore(path30 = registrationPath()) {
|
|
4729
|
+
let stored;
|
|
4653
4730
|
try {
|
|
4654
|
-
|
|
4731
|
+
stored = JSON.parse(await readFile(path30, "utf8"));
|
|
4655
4732
|
} catch {
|
|
4656
|
-
|
|
4733
|
+
stored = {};
|
|
4734
|
+
}
|
|
4735
|
+
const hostId = stored.hostId ?? `urn:uuid:${randomUUID2()}`;
|
|
4736
|
+
if (stored.accounts && typeof stored.accounts === "object") {
|
|
4737
|
+
return { hostId, accounts: stored.accounts };
|
|
4657
4738
|
}
|
|
4739
|
+
return {
|
|
4740
|
+
hostId,
|
|
4741
|
+
accounts: stored.subject && stored.clientId ? { [stored.subject]: { clientId: stored.clientId } } : {}
|
|
4742
|
+
};
|
|
4658
4743
|
}
|
|
4659
|
-
async function
|
|
4660
|
-
|
|
4661
|
-
|
|
4744
|
+
async function saveAccountRegistration(account, path30 = registrationPath()) {
|
|
4745
|
+
const store = await loadRegistrationStore(path30);
|
|
4746
|
+
const { hostId, subject, ...registration } = account;
|
|
4747
|
+
const next = {
|
|
4748
|
+
hostId,
|
|
4749
|
+
accounts: { ...store.accounts, [subject]: registration }
|
|
4750
|
+
};
|
|
4751
|
+
await mkdir(dirname(path30), { recursive: true, mode: 448 });
|
|
4752
|
+
await writeFile(path30, JSON.stringify(next), { mode: 384 });
|
|
4753
|
+
}
|
|
4754
|
+
function selectRegistration(store, account) {
|
|
4755
|
+
if (account === void 0) {
|
|
4756
|
+
return { hostId: store.hostId };
|
|
4757
|
+
}
|
|
4758
|
+
const email = account.toLowerCase();
|
|
4759
|
+
const match = Object.entries(store.accounts).find(
|
|
4760
|
+
([subject2, saved2]) => subject2 === account || saved2.email?.toLowerCase() === email
|
|
4761
|
+
);
|
|
4762
|
+
if (!match) {
|
|
4763
|
+
throw new Error(
|
|
4764
|
+
`No ChatGPT account ${account} is saved on this machine; run without --account to sign in with a new registration`
|
|
4765
|
+
);
|
|
4766
|
+
}
|
|
4767
|
+
const [subject, saved] = match;
|
|
4768
|
+
return { hostId: store.hostId, clientId: saved.clientId, subject };
|
|
4662
4769
|
}
|
|
4663
4770
|
function randomToken() {
|
|
4664
4771
|
return randomBytes2(24).toString("base64url");
|
|
@@ -4747,6 +4854,84 @@ function checkIdToken(idToken, expected) {
|
|
|
4747
4854
|
}
|
|
4748
4855
|
return { sub: claims.sub, email: claims.email };
|
|
4749
4856
|
}
|
|
4857
|
+
var WORKSPACE_DENIED_ERROR = "3p_login_workspace_scope_denied";
|
|
4858
|
+
function isWorkspaceDenied(err) {
|
|
4859
|
+
if (err instanceof CallbackError) {
|
|
4860
|
+
return err.error === WORKSPACE_DENIED_ERROR || /workspace/i.test(err.description ?? "");
|
|
4861
|
+
}
|
|
4862
|
+
return err instanceof Error && err.message.includes(WORKSPACE_DENIED_ERROR);
|
|
4863
|
+
}
|
|
4864
|
+
function explainDenial(err) {
|
|
4865
|
+
if (!isWorkspaceDenied(err)) {
|
|
4866
|
+
return err;
|
|
4867
|
+
}
|
|
4868
|
+
const description = err instanceof CallbackError && err.description ? ` ${err.description}` : "";
|
|
4869
|
+
return new Error(
|
|
4870
|
+
`${err.message}${description} ChatGPT refused it for that workspace: run \`fabric connect chatgpt\` again without --account (the same as --new-registration) and choose your personal Plus or Pro workspace when ChatGPT asks which one to sign in to.`
|
|
4871
|
+
);
|
|
4872
|
+
}
|
|
4873
|
+
async function attemptSignIn(registration, options) {
|
|
4874
|
+
const verifier = createCodeVerifier();
|
|
4875
|
+
const state = createState();
|
|
4876
|
+
const nonce = randomToken();
|
|
4877
|
+
const listener = await startLoopbackListener({
|
|
4878
|
+
state,
|
|
4879
|
+
callbackPath: CHATGPT_CALLBACK_PATH,
|
|
4880
|
+
preferredPort: options.preferredPort ?? CHATGPT_CALLBACK_PORT
|
|
4881
|
+
});
|
|
4882
|
+
try {
|
|
4883
|
+
options.open(
|
|
4884
|
+
buildAuthorizeUrl({
|
|
4885
|
+
clientId: registration.clientId ?? DYNAMIC_CLIENT_ID,
|
|
4886
|
+
hostId: registration.hostId,
|
|
4887
|
+
redirectUri: listener.redirectUri,
|
|
4888
|
+
state,
|
|
4889
|
+
nonce,
|
|
4890
|
+
codeChallenge: codeChallengeS256(verifier)
|
|
4891
|
+
})
|
|
4892
|
+
);
|
|
4893
|
+
const { code, params } = await listener.result;
|
|
4894
|
+
const clientId = issuedClientId(params, registration);
|
|
4895
|
+
const tokens = await exchangeCode(
|
|
4896
|
+
{
|
|
4897
|
+
clientId,
|
|
4898
|
+
code,
|
|
4899
|
+
codeVerifier: verifier,
|
|
4900
|
+
redirectUri: listener.redirectUri
|
|
4901
|
+
},
|
|
4902
|
+
options.fetchImpl
|
|
4903
|
+
);
|
|
4904
|
+
const { sub, email } = checkIdToken(tokens.id_token ?? "", {
|
|
4905
|
+
nonce,
|
|
4906
|
+
subject: registration.subject
|
|
4907
|
+
});
|
|
4908
|
+
await options.save?.({
|
|
4909
|
+
hostId: registration.hostId,
|
|
4910
|
+
subject: sub,
|
|
4911
|
+
clientId,
|
|
4912
|
+
...email !== void 0 && { email }
|
|
4913
|
+
});
|
|
4914
|
+
return { tokens, clientId, subject: sub, email };
|
|
4915
|
+
} finally {
|
|
4916
|
+
listener.close();
|
|
4917
|
+
}
|
|
4918
|
+
}
|
|
4919
|
+
async function signInWithChatGpt(options) {
|
|
4920
|
+
const { registration } = options;
|
|
4921
|
+
try {
|
|
4922
|
+
return await attemptSignIn(registration, options);
|
|
4923
|
+
} catch (err) {
|
|
4924
|
+
if (!registration.clientId || !isWorkspaceDenied(err)) {
|
|
4925
|
+
throw explainDenial(err);
|
|
4926
|
+
}
|
|
4927
|
+
}
|
|
4928
|
+
options.onRetry?.();
|
|
4929
|
+
try {
|
|
4930
|
+
return await attemptSignIn({ hostId: registration.hostId }, options);
|
|
4931
|
+
} catch (err) {
|
|
4932
|
+
throw explainDenial(err);
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4750
4935
|
function buildUploadPayload(tokens, registration) {
|
|
4751
4936
|
if (!tokens.refresh_token || !tokens.id_token) {
|
|
4752
4937
|
throw new Error("OpenAI's reply is missing the refresh or ID token");
|
|
@@ -4815,6 +5000,12 @@ function connectOptionsError(opts) {
|
|
|
4815
5000
|
if (opts.org && !opts.shared) {
|
|
4816
5001
|
return "--org is used only with --shared, to connect an account the organization shares.";
|
|
4817
5002
|
}
|
|
5003
|
+
if (opts.account !== void 0 && opts.newRegistration) {
|
|
5004
|
+
return "--account reuses a saved registration; --new-registration makes a new one. Use one of them.";
|
|
5005
|
+
}
|
|
5006
|
+
if (opts.account !== void 0 && opts.shared) {
|
|
5007
|
+
return "--account is for your own plan; a shared account always signs in with a new registration.";
|
|
5008
|
+
}
|
|
4818
5009
|
return null;
|
|
4819
5010
|
}
|
|
4820
5011
|
function open(url, what) {
|
|
@@ -4878,75 +5069,50 @@ async function connect(opts) {
|
|
|
4878
5069
|
printError(BAD_BASE_URL_LINE, 2);
|
|
4879
5070
|
}
|
|
4880
5071
|
const sharedOrganizationSlug = opts.shared ? opts.org : void 0;
|
|
5072
|
+
let registration;
|
|
5073
|
+
if (sharedOrganizationSlug) {
|
|
5074
|
+
registration = { hostId: `urn:uuid:${randomUUID3()}` };
|
|
5075
|
+
} else {
|
|
5076
|
+
try {
|
|
5077
|
+
registration = selectRegistration(
|
|
5078
|
+
await loadRegistrationStore(),
|
|
5079
|
+
opts.account
|
|
5080
|
+
);
|
|
5081
|
+
} catch (err) {
|
|
5082
|
+
printError(err.message, 2);
|
|
5083
|
+
}
|
|
5084
|
+
}
|
|
4881
5085
|
let ticket;
|
|
4882
5086
|
try {
|
|
4883
5087
|
ticket = await approveInFabric(origin, sharedOrganizationSlug);
|
|
4884
5088
|
} catch (err) {
|
|
4885
5089
|
printError(err.message, 1);
|
|
4886
5090
|
}
|
|
4887
|
-
const stored = await loadRegistration();
|
|
4888
|
-
let registration = stored;
|
|
4889
|
-
if (sharedOrganizationSlug) {
|
|
4890
|
-
registration = { hostId: `urn:uuid:${randomUUID3()}` };
|
|
4891
|
-
} else if (opts.newRegistration) {
|
|
4892
|
-
registration = { hostId: stored.hostId };
|
|
4893
|
-
}
|
|
4894
|
-
const verifier = createCodeVerifier();
|
|
4895
|
-
const state = createState();
|
|
4896
|
-
const nonce = randomToken();
|
|
4897
|
-
const listener = await startLoopbackListener({
|
|
4898
|
-
state,
|
|
4899
|
-
callbackPath: CHATGPT_CALLBACK_PATH,
|
|
4900
|
-
preferredPort: CHATGPT_CALLBACK_PORT
|
|
4901
|
-
});
|
|
4902
|
-
open(
|
|
4903
|
-
buildAuthorizeUrl({
|
|
4904
|
-
clientId: registration.clientId ?? DYNAMIC_CLIENT_ID,
|
|
4905
|
-
hostId: registration.hostId,
|
|
4906
|
-
redirectUri: listener.redirectUri,
|
|
4907
|
-
state,
|
|
4908
|
-
nonce,
|
|
4909
|
-
codeChallenge: codeChallengeS256(verifier)
|
|
4910
|
-
}),
|
|
4911
|
-
"sign in with ChatGPT"
|
|
4912
|
-
);
|
|
4913
5091
|
let result;
|
|
4914
|
-
let clientId;
|
|
4915
|
-
let subject;
|
|
4916
5092
|
try {
|
|
4917
|
-
const
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
code,
|
|
4925
|
-
codeVerifier: verifier,
|
|
4926
|
-
redirectUri: listener.redirectUri
|
|
5093
|
+
const signIn = await signInWithChatGpt({
|
|
5094
|
+
registration,
|
|
5095
|
+
open: (url) => open(url, "sign in with ChatGPT"),
|
|
5096
|
+
...!sharedOrganizationSlug && { save: saveAccountRegistration },
|
|
5097
|
+
onRetry: () => process.stdout.write(
|
|
5098
|
+
"ChatGPT refused this machine's saved registration; signing in again with a new one.\n"
|
|
5099
|
+
)
|
|
4927
5100
|
});
|
|
4928
|
-
subject = checkIdToken(tokens.id_token ?? "", {
|
|
4929
|
-
nonce,
|
|
4930
|
-
subject: registration.subject
|
|
4931
|
-
}).sub;
|
|
4932
5101
|
result = await uploadChatGptPlan({
|
|
4933
5102
|
origin,
|
|
4934
5103
|
ticket,
|
|
4935
|
-
payload: buildUploadPayload(tokens, {
|
|
4936
|
-
clientId,
|
|
5104
|
+
payload: buildUploadPayload(signIn.tokens, {
|
|
5105
|
+
clientId: signIn.clientId,
|
|
4937
5106
|
hostId: registration.hostId
|
|
4938
5107
|
})
|
|
4939
5108
|
});
|
|
4940
5109
|
} catch (err) {
|
|
4941
5110
|
printError(err.message, 1);
|
|
4942
|
-
} finally {
|
|
4943
|
-
listener.close();
|
|
4944
5111
|
}
|
|
4945
5112
|
if ("shared" in result) {
|
|
4946
5113
|
printSuccess(describeSharedAccount(result));
|
|
4947
5114
|
return;
|
|
4948
5115
|
}
|
|
4949
|
-
await saveRegistration({ hostId: registration.hostId, clientId, subject });
|
|
4950
5116
|
printSuccess(
|
|
4951
5117
|
`Connected your ChatGPT plan${result.email ? ` (${result.email})` : ""} to Fabric.`
|
|
4952
5118
|
);
|
|
@@ -4960,9 +5126,12 @@ function buildConnectCommand() {
|
|
|
4960
5126
|
connectCommand.addCommand(
|
|
4961
5127
|
new Command8("chatgpt").description(
|
|
4962
5128
|
"Approve in Fabric, sign in with ChatGPT and run your own Fabric AI work on your plan"
|
|
5129
|
+
).option(
|
|
5130
|
+
"--account <subject-or-email>",
|
|
5131
|
+
"Reuse the registration saved for this ChatGPT account instead of making a new one"
|
|
4963
5132
|
).option(
|
|
4964
5133
|
"--new-registration",
|
|
4965
|
-
"Register this machine with OpenAI again
|
|
5134
|
+
"Register this machine with OpenAI again (the default without --account)"
|
|
4966
5135
|
).option(
|
|
4967
5136
|
"--base-url <url>",
|
|
4968
5137
|
"Deployment to connect, such as https://example.com"
|
|
@@ -5582,23 +5751,23 @@ function normalizeContextSourcePath(input) {
|
|
|
5582
5751
|
if (slashed.startsWith("/") || DRIVE_LETTER_PREFIX.test(slashed)) {
|
|
5583
5752
|
return { ok: false, reason: "absolute" };
|
|
5584
5753
|
}
|
|
5585
|
-
let
|
|
5586
|
-
while (
|
|
5587
|
-
|
|
5754
|
+
let path30 = slashed.replace(/\/{2,}/g, "/");
|
|
5755
|
+
while (path30.startsWith("./")) {
|
|
5756
|
+
path30 = path30.slice(2);
|
|
5588
5757
|
}
|
|
5589
|
-
if (
|
|
5758
|
+
if (path30.length === 0) {
|
|
5590
5759
|
return { ok: false, reason: "empty" };
|
|
5591
5760
|
}
|
|
5592
|
-
if (
|
|
5761
|
+
if (path30.endsWith("/")) {
|
|
5593
5762
|
return { ok: false, reason: "trailing-slash" };
|
|
5594
5763
|
}
|
|
5595
|
-
if (
|
|
5764
|
+
if (path30.split("/").some((segment) => segment === "." || segment === "..")) {
|
|
5596
5765
|
return { ok: false, reason: "dot-segment" };
|
|
5597
5766
|
}
|
|
5598
|
-
if (
|
|
5767
|
+
if (path30.length > MAX_CONTEXT_SOURCE_PATH_LENGTH) {
|
|
5599
5768
|
return { ok: false, reason: "too-long" };
|
|
5600
5769
|
}
|
|
5601
|
-
return { ok: true, sourcePath:
|
|
5770
|
+
return { ok: true, sourcePath: path30 };
|
|
5602
5771
|
}
|
|
5603
5772
|
|
|
5604
5773
|
// src/lib/context-sync/lock.ts
|
|
@@ -5810,8 +5979,8 @@ async function walkContextDirectory(root, rules) {
|
|
|
5810
5979
|
const pending = [""];
|
|
5811
5980
|
while (pending.length > 0) {
|
|
5812
5981
|
const relativeDirectory = pending.pop();
|
|
5813
|
-
const
|
|
5814
|
-
for (const name of
|
|
5982
|
+
const names2 = await readdir(path5.join(root, relativeDirectory));
|
|
5983
|
+
for (const name of names2) {
|
|
5815
5984
|
const relative = relativeDirectory ? `${relativeDirectory}/${name}` : name;
|
|
5816
5985
|
let stats;
|
|
5817
5986
|
try {
|
|
@@ -5897,8 +6066,8 @@ function sharedPathProblem(entry, group2) {
|
|
|
5897
6066
|
if (others.length === 0) {
|
|
5898
6067
|
return null;
|
|
5899
6068
|
}
|
|
5900
|
-
const
|
|
5901
|
-
return others.every((other) => other.sourcePath === entry.sourcePath) ? `names the same source as ${
|
|
6069
|
+
const names2 = others.map((other) => other.diskPath).join(", ");
|
|
6070
|
+
return others.every((other) => other.sourcePath === entry.sourcePath) ? `names the same source as ${names2}` : `case-only collision with ${names2}`;
|
|
5902
6071
|
}
|
|
5903
6072
|
async function computeContextPlan(input) {
|
|
5904
6073
|
const { root, lock } = input;
|
|
@@ -6486,9 +6655,9 @@ async function pushContextPlan(input) {
|
|
|
6486
6655
|
}
|
|
6487
6656
|
};
|
|
6488
6657
|
const pruneTargetsSoFar = () => {
|
|
6489
|
-
const keptByMoves = results.map(moveKeptOldPath).filter((
|
|
6490
|
-
return [.../* @__PURE__ */ new Set([...input.plan.removed, ...keptByMoves])].filter((
|
|
6491
|
-
const locked = input.lock?.files[
|
|
6658
|
+
const keptByMoves = results.map(moveKeptOldPath).filter((path30) => path30 !== null);
|
|
6659
|
+
return [.../* @__PURE__ */ new Set([...input.plan.removed, ...keptByMoves])].filter((path30) => {
|
|
6660
|
+
const locked = input.lock?.files[path30];
|
|
6492
6661
|
return locked !== void 0 && locked.state !== "duplicate";
|
|
6493
6662
|
}).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
6494
6663
|
};
|
|
@@ -7126,7 +7295,7 @@ function pruneSummary(results, targets) {
|
|
|
7126
7295
|
(r) => r.status === "deleted" || r.status === "already-gone"
|
|
7127
7296
|
).map((r) => r.sourcePath)
|
|
7128
7297
|
);
|
|
7129
|
-
const notDeleted = targets.filter((
|
|
7298
|
+
const notDeleted = targets.filter((path30) => !settled.has(path30)).length;
|
|
7130
7299
|
let text3 = `--prune: ${count("deleted")} deleted, ${count("already-gone")} already gone, ${notDeleted} not deleted`;
|
|
7131
7300
|
const first = results.find((r) => r.status === "delete-failed");
|
|
7132
7301
|
if (first && first.status === "delete-failed") {
|
|
@@ -7510,7 +7679,7 @@ function buildFramesCommand() {
|
|
|
7510
7679
|
|
|
7511
7680
|
// src/commands/instructions/index.ts
|
|
7512
7681
|
import { readdir as readdir3 } from "fs/promises";
|
|
7513
|
-
import
|
|
7682
|
+
import path29 from "path";
|
|
7514
7683
|
import { Command as Command13 } from "commander";
|
|
7515
7684
|
|
|
7516
7685
|
// src/lib/environment.ts
|
|
@@ -7523,15 +7692,15 @@ function runningInCi(env2 = process.env) {
|
|
|
7523
7692
|
}
|
|
7524
7693
|
|
|
7525
7694
|
// src/lib/instructions/adoption.ts
|
|
7526
|
-
import
|
|
7695
|
+
import path11 from "path";
|
|
7527
7696
|
|
|
7528
7697
|
// src/lib/instructions/checkout.ts
|
|
7529
7698
|
import { realpath as realpath2 } from "fs/promises";
|
|
7530
|
-
import
|
|
7699
|
+
import path10 from "path";
|
|
7531
7700
|
|
|
7532
7701
|
// src/lib/instructions/git.ts
|
|
7533
|
-
import { lstat as
|
|
7534
|
-
import
|
|
7702
|
+
import { lstat as lstat5, stat } from "fs/promises";
|
|
7703
|
+
import path9 from "path";
|
|
7535
7704
|
|
|
7536
7705
|
// src/lib/instructions/git-literals.ts
|
|
7537
7706
|
var COMMIT_SHA = /^[0-9a-f]{40}$/;
|
|
@@ -7560,10 +7729,21 @@ function cloneableUrl(url) {
|
|
|
7560
7729
|
}
|
|
7561
7730
|
|
|
7562
7731
|
// src/lib/instructions/git-run.ts
|
|
7563
|
-
import { spawn as spawn2 } from "child_process";
|
|
7732
|
+
import { spawn as spawn2, spawnSync } from "child_process";
|
|
7733
|
+
|
|
7734
|
+
// src/lib/instructions/taskkill-path.ts
|
|
7735
|
+
import path7 from "path";
|
|
7736
|
+
function taskkillPath(env2 = process.env) {
|
|
7737
|
+
const root = env2.SystemRoot ?? env2.SYSTEMROOT ?? env2.windir ?? "C:\\Windows";
|
|
7738
|
+
return path7.win32.join(root, "System32", "taskkill.exe");
|
|
7739
|
+
}
|
|
7740
|
+
|
|
7741
|
+
// src/lib/instructions/git-run.ts
|
|
7564
7742
|
var STDOUT_CAP_BYTES = 64 * 1024;
|
|
7565
7743
|
var STDERR_CAP_BYTES = 8 * 1024;
|
|
7566
7744
|
var KILL_GRACE_MS = 1e3;
|
|
7745
|
+
var GROUP_KILL_GRACE_MS = 500;
|
|
7746
|
+
var BINARY_STDOUT_CAP_BYTES = 1024 * 1024;
|
|
7567
7747
|
var STRIPPED_VARIABLES = /* @__PURE__ */ new Set([
|
|
7568
7748
|
"GIT_DIR",
|
|
7569
7749
|
"GIT_WORK_TREE",
|
|
@@ -7657,6 +7837,8 @@ function runGit(cwd, args, deadline, options = {}) {
|
|
|
7657
7837
|
});
|
|
7658
7838
|
return;
|
|
7659
7839
|
}
|
|
7840
|
+
const grouped = Boolean(options.write && options.unattended);
|
|
7841
|
+
let escalate;
|
|
7660
7842
|
let settled = false;
|
|
7661
7843
|
const finish = (result) => {
|
|
7662
7844
|
if (!settled) {
|
|
@@ -7687,9 +7869,16 @@ function runGit(cwd, args, deadline, options = {}) {
|
|
|
7687
7869
|
...options.write && options.unattended ? hookWriteEnvironment(process.env) : gitEnvironment(process.env, options),
|
|
7688
7870
|
...gateway.env
|
|
7689
7871
|
},
|
|
7690
|
-
stdio: [
|
|
7872
|
+
stdio: [
|
|
7873
|
+
options.input === void 0 ? "ignore" : "pipe",
|
|
7874
|
+
"pipe",
|
|
7875
|
+
"pipe"
|
|
7876
|
+
],
|
|
7691
7877
|
shell: false,
|
|
7692
|
-
windowsHide: true
|
|
7878
|
+
windowsHide: true,
|
|
7879
|
+
// Its own process group, so the deadline can take the
|
|
7880
|
+
// transport children with it (`killTree`).
|
|
7881
|
+
detached: grouped && process.platform !== "win32"
|
|
7693
7882
|
}
|
|
7694
7883
|
);
|
|
7695
7884
|
} catch (error) {
|
|
@@ -7701,12 +7890,17 @@ function runGit(cwd, args, deadline, options = {}) {
|
|
|
7701
7890
|
let outOverflow = false;
|
|
7702
7891
|
const err = [];
|
|
7703
7892
|
let errBytes = 0;
|
|
7893
|
+
const stdoutCap = options.binary ? options.binaryCapBytes ?? BINARY_STDOUT_CAP_BYTES : STDOUT_CAP_BYTES;
|
|
7894
|
+
if (options.input !== void 0) {
|
|
7895
|
+
child.stdin?.on("error", () => void 0);
|
|
7896
|
+
child.stdin?.end(options.input);
|
|
7897
|
+
}
|
|
7704
7898
|
child.stdout?.on("data", (chunk) => {
|
|
7705
|
-
if (chunk.length >
|
|
7899
|
+
if (chunk.length > stdoutCap - outBytes) {
|
|
7706
7900
|
outOverflow = true;
|
|
7707
7901
|
}
|
|
7708
|
-
if (outBytes <
|
|
7709
|
-
const room =
|
|
7902
|
+
if (outBytes < stdoutCap) {
|
|
7903
|
+
const room = stdoutCap - outBytes;
|
|
7710
7904
|
out.push(chunk.subarray(0, room));
|
|
7711
7905
|
outBytes += Math.min(room, chunk.length);
|
|
7712
7906
|
}
|
|
@@ -7718,12 +7912,34 @@ function runGit(cwd, args, deadline, options = {}) {
|
|
|
7718
7912
|
errBytes += Math.min(room, chunk.length);
|
|
7719
7913
|
}
|
|
7720
7914
|
});
|
|
7721
|
-
let
|
|
7915
|
+
let stoppingGroup = false;
|
|
7916
|
+
const releaseGroup = () => {
|
|
7917
|
+
child.stdout?.destroy();
|
|
7918
|
+
child.stderr?.destroy();
|
|
7919
|
+
child.unref();
|
|
7920
|
+
};
|
|
7722
7921
|
const timer = setTimeout(() => {
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7922
|
+
if (grouped) {
|
|
7923
|
+
stopTree(child, "SIGTERM");
|
|
7924
|
+
if (process.platform !== "win32") {
|
|
7925
|
+
stoppingGroup = true;
|
|
7926
|
+
escalate = setTimeout(() => {
|
|
7927
|
+
stopTree(child, "SIGKILL");
|
|
7928
|
+
releaseGroup();
|
|
7929
|
+
finish({
|
|
7930
|
+
kind: "unavailable",
|
|
7931
|
+
reason: "git timed out"
|
|
7932
|
+
});
|
|
7933
|
+
}, GROUP_KILL_GRACE_MS);
|
|
7934
|
+
return;
|
|
7935
|
+
}
|
|
7936
|
+
releaseGroup();
|
|
7937
|
+
} else {
|
|
7938
|
+
child.kill("SIGTERM");
|
|
7939
|
+
escalate = setTimeout(() => {
|
|
7940
|
+
child.kill("SIGKILL");
|
|
7941
|
+
}, KILL_GRACE_MS);
|
|
7942
|
+
}
|
|
7727
7943
|
finish({ kind: "unavailable", reason: "git timed out" });
|
|
7728
7944
|
}, remaining);
|
|
7729
7945
|
timer.unref();
|
|
@@ -7731,6 +7947,9 @@ function runGit(cwd, args, deadline, options = {}) {
|
|
|
7731
7947
|
finish(spawnFailure(error));
|
|
7732
7948
|
});
|
|
7733
7949
|
child.on("close", (code) => {
|
|
7950
|
+
if (stoppingGroup) {
|
|
7951
|
+
return;
|
|
7952
|
+
}
|
|
7734
7953
|
clearTimeout(escalate);
|
|
7735
7954
|
if (outOverflow) {
|
|
7736
7955
|
finish({
|
|
@@ -7742,12 +7961,36 @@ function runGit(cwd, args, deadline, options = {}) {
|
|
|
7742
7961
|
finish({
|
|
7743
7962
|
kind: "exited",
|
|
7744
7963
|
code: code ?? -1,
|
|
7745
|
-
stdout: Buffer.concat(out).toString(
|
|
7964
|
+
stdout: Buffer.concat(out).toString(
|
|
7965
|
+
options.binary ? "latin1" : "utf8"
|
|
7966
|
+
),
|
|
7746
7967
|
stderr: Buffer.concat(err).toString("utf8")
|
|
7747
7968
|
});
|
|
7748
7969
|
});
|
|
7749
7970
|
});
|
|
7750
7971
|
}
|
|
7972
|
+
function stopTree(child, signal) {
|
|
7973
|
+
const pid = child.pid;
|
|
7974
|
+
if (pid === void 0) {
|
|
7975
|
+
child.kill(signal);
|
|
7976
|
+
return;
|
|
7977
|
+
}
|
|
7978
|
+
try {
|
|
7979
|
+
if (process.platform === "win32") {
|
|
7980
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
7981
|
+
spawnSync(taskkillPath(), ["/PID", String(pid), "/T", "/F"], {
|
|
7982
|
+
stdio: "ignore",
|
|
7983
|
+
windowsHide: true,
|
|
7984
|
+
timeout: 2e3
|
|
7985
|
+
});
|
|
7986
|
+
}
|
|
7987
|
+
} else {
|
|
7988
|
+
process.kill(-pid, signal);
|
|
7989
|
+
}
|
|
7990
|
+
} catch {
|
|
7991
|
+
}
|
|
7992
|
+
child.kill(signal);
|
|
7993
|
+
}
|
|
7751
7994
|
function spawnFailure(error) {
|
|
7752
7995
|
const code = error?.code;
|
|
7753
7996
|
if (code === "ENOENT") {
|
|
@@ -7831,7 +8074,12 @@ async function changedTrackedPaths(root, deadline) {
|
|
|
7831
8074
|
}
|
|
7832
8075
|
|
|
7833
8076
|
// src/lib/instructions/git-write.ts
|
|
8077
|
+
import { lstat as lstat4, readFile as readFile3 } from "fs/promises";
|
|
8078
|
+
import path8 from "path";
|
|
7834
8079
|
function fetchFailureOf(stderr) {
|
|
8080
|
+
if (/config-env/i.test(stderr) && /unknown option|usage:/i.test(stderr)) {
|
|
8081
|
+
return "old-git";
|
|
8082
|
+
}
|
|
7835
8083
|
if (/\(non-fast-forward\)|\[rejected\]|cannot fast-forward/i.test(stderr)) {
|
|
7836
8084
|
return "diverged";
|
|
7837
8085
|
}
|
|
@@ -7885,12 +8133,16 @@ function blockedFilesOf(stderr) {
|
|
|
7885
8133
|
}
|
|
7886
8134
|
return files;
|
|
7887
8135
|
}
|
|
7888
|
-
|
|
8136
|
+
var fetchHeadFlagWorks = true;
|
|
8137
|
+
function timedOut(reason) {
|
|
8138
|
+
return reason === "git timed out";
|
|
8139
|
+
}
|
|
8140
|
+
async function fetchInto(root, source, remote, ref, deadline, httpAuthorization) {
|
|
7889
8141
|
if (!isRemoteName(remote) || !isBranchLiteral(ref)) {
|
|
7890
8142
|
return { kind: "unavailable", reason: "not a branch this will fetch" };
|
|
7891
8143
|
}
|
|
7892
8144
|
const tracking = `refs/remotes/${remote}/${ref}`;
|
|
7893
|
-
const
|
|
8145
|
+
const fetchWith = (skipFetchHead) => runGit(
|
|
7894
8146
|
root,
|
|
7895
8147
|
[
|
|
7896
8148
|
"-c",
|
|
@@ -7899,15 +8151,26 @@ async function fetchRef(root, remote, ref, deadline) {
|
|
|
7899
8151
|
"maintenance.auto=false",
|
|
7900
8152
|
"fetch",
|
|
7901
8153
|
"--no-recurse-submodules",
|
|
8154
|
+
"--no-tags",
|
|
8155
|
+
...skipFetchHead ? ["--no-write-fetch-head"] : [],
|
|
7902
8156
|
"--",
|
|
7903
|
-
|
|
8157
|
+
source,
|
|
7904
8158
|
`refs/heads/${ref}:${tracking}`
|
|
7905
8159
|
],
|
|
7906
8160
|
deadline,
|
|
7907
|
-
{
|
|
8161
|
+
{
|
|
8162
|
+
write: true,
|
|
8163
|
+
unattended: true,
|
|
8164
|
+
...httpAuthorization === void 0 ? {} : { httpAuthorization }
|
|
8165
|
+
}
|
|
7908
8166
|
);
|
|
8167
|
+
let fetched = await fetchWith(fetchHeadFlagWorks);
|
|
8168
|
+
if (fetched.kind === "exited" && fetched.code !== 0 && fetchHeadFlagWorks && /no-write-fetch-head/i.test(fetched.stderr)) {
|
|
8169
|
+
fetchHeadFlagWorks = false;
|
|
8170
|
+
fetched = await fetchWith(false);
|
|
8171
|
+
}
|
|
7909
8172
|
if (fetched.kind === "unavailable") {
|
|
7910
|
-
return fetched.reason
|
|
8173
|
+
return timedOut(fetched.reason) ? { kind: "timed-out" } : { kind: "unavailable", reason: fetched.reason };
|
|
7911
8174
|
}
|
|
7912
8175
|
if (fetched.code !== 0) {
|
|
7913
8176
|
return { kind: "failed", reason: fetchFailureOf(fetched.stderr) };
|
|
@@ -7918,7 +8181,7 @@ async function fetchRef(root, remote, ref, deadline) {
|
|
|
7918
8181
|
deadline
|
|
7919
8182
|
);
|
|
7920
8183
|
if (tip.kind === "unavailable") {
|
|
7921
|
-
return tip.reason
|
|
8184
|
+
return timedOut(tip.reason) ? { kind: "timed-out" } : { kind: "unavailable", reason: tip.reason };
|
|
7922
8185
|
}
|
|
7923
8186
|
const [sha] = lines(tip.stdout);
|
|
7924
8187
|
if (tip.code !== 0 || !sha || !COMMIT_SHA.test(sha)) {
|
|
@@ -7926,59 +8189,29 @@ async function fetchRef(root, remote, ref, deadline) {
|
|
|
7926
8189
|
}
|
|
7927
8190
|
return { kind: "fetched", tip: sha };
|
|
7928
8191
|
}
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
8192
|
+
function fetchRef(root, remote, ref, deadline) {
|
|
8193
|
+
return fetchInto(root, remote, remote, ref, deadline);
|
|
8194
|
+
}
|
|
8195
|
+
function fetchRefFromUrl(root, url, remote, ref, deadline, httpAuthorization) {
|
|
8196
|
+
return fetchInto(root, url, remote, ref, deadline, httpAuthorization);
|
|
8197
|
+
}
|
|
8198
|
+
async function mergeOnce(root, sha, deadline) {
|
|
8199
|
+
const merged = await runGit(
|
|
7935
8200
|
root,
|
|
7936
8201
|
[
|
|
7937
8202
|
"-c",
|
|
7938
|
-
"
|
|
7939
|
-
"
|
|
7940
|
-
"
|
|
7941
|
-
"
|
|
7942
|
-
"--
|
|
7943
|
-
|
|
7944
|
-
url,
|
|
7945
|
-
`refs/heads/${ref}:${tracking}`
|
|
8203
|
+
"core.quotepath=false",
|
|
8204
|
+
"merge",
|
|
8205
|
+
"--ff-only",
|
|
8206
|
+
"--no-edit",
|
|
8207
|
+
"--quiet",
|
|
8208
|
+
sha
|
|
7946
8209
|
],
|
|
7947
8210
|
deadline,
|
|
7948
|
-
{ write: true, unattended: true, httpAuthorization }
|
|
7949
|
-
);
|
|
7950
|
-
if (fetched.kind === "unavailable") {
|
|
7951
|
-
return fetched.reason === "git timed out" ? { kind: "timed-out" } : { kind: "unavailable", reason: fetched.reason };
|
|
7952
|
-
}
|
|
7953
|
-
if (fetched.code !== 0) {
|
|
7954
|
-
return { kind: "failed", reason: fetchFailureOf(fetched.stderr) };
|
|
7955
|
-
}
|
|
7956
|
-
const tip = await runGit(
|
|
7957
|
-
root,
|
|
7958
|
-
["rev-parse", "--verify", "-q", `${tracking}^{commit}`],
|
|
7959
|
-
deadline
|
|
7960
|
-
);
|
|
7961
|
-
if (tip.kind === "unavailable") {
|
|
7962
|
-
return tip.reason === "git timed out" ? { kind: "timed-out" } : { kind: "unavailable", reason: tip.reason };
|
|
7963
|
-
}
|
|
7964
|
-
const [sha] = lines(tip.stdout);
|
|
7965
|
-
if (tip.code !== 0 || !sha || !COMMIT_SHA.test(sha)) {
|
|
7966
|
-
return { kind: "failed", reason: "missing-ref" };
|
|
7967
|
-
}
|
|
7968
|
-
return { kind: "fetched", tip: sha };
|
|
7969
|
-
}
|
|
7970
|
-
async function fastForwardTo(root, sha, deadline) {
|
|
7971
|
-
if (!COMMIT_SHA.test(sha)) {
|
|
7972
|
-
return { kind: "unavailable", reason: "not a commit name" };
|
|
7973
|
-
}
|
|
7974
|
-
const merged = await runGit(
|
|
7975
|
-
root,
|
|
7976
|
-
["merge", "--ff-only", "--no-edit", "--quiet", sha],
|
|
7977
|
-
deadline,
|
|
7978
8211
|
{ write: true, unattended: true }
|
|
7979
8212
|
);
|
|
7980
8213
|
if (merged.kind === "unavailable") {
|
|
7981
|
-
return merged.reason
|
|
8214
|
+
return timedOut(merged.reason) ? { kind: "timed-out" } : { kind: "unavailable", reason: merged.reason };
|
|
7982
8215
|
}
|
|
7983
8216
|
if (merged.code !== 0) {
|
|
7984
8217
|
const reason = mergeFailureOf(merged.stderr);
|
|
@@ -7990,55 +8223,294 @@ async function fastForwardTo(root, sha, deadline) {
|
|
|
7990
8223
|
deadline
|
|
7991
8224
|
);
|
|
7992
8225
|
if (head.kind === "unavailable") {
|
|
7993
|
-
return head.reason
|
|
8226
|
+
return timedOut(head.reason) ? { kind: "timed-out" } : { kind: "unavailable", reason: head.reason };
|
|
7994
8227
|
}
|
|
7995
8228
|
const [now] = lines(head.stdout);
|
|
7996
8229
|
return head.code === 0 && now === sha ? { kind: "merged", head: now } : { kind: "failed", reason: "other" };
|
|
7997
8230
|
}
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8231
|
+
function nulPaths(stdout) {
|
|
8232
|
+
return stdout.split("\0").filter((entry) => entry !== "");
|
|
8233
|
+
}
|
|
8234
|
+
var CONVERTING_ATTRIBUTES = ["filter", "ident", "working-tree-encoding"];
|
|
8235
|
+
var MAX_COMPARED_BYTES = 1024 * 1024;
|
|
8236
|
+
var CRLF = /\r\n/g;
|
|
8237
|
+
function nulFields(stdout) {
|
|
8238
|
+
const fields = stdout.split("\0");
|
|
8239
|
+
if (fields[fields.length - 1] === "") {
|
|
8240
|
+
fields.pop();
|
|
8241
|
+
}
|
|
8242
|
+
return fields;
|
|
8243
|
+
}
|
|
8244
|
+
async function readBlobs(root, ids, deadline) {
|
|
8245
|
+
const read = /* @__PURE__ */ new Map();
|
|
8246
|
+
const unique = [...new Set(ids)];
|
|
8247
|
+
if (unique.length === 0) {
|
|
8248
|
+
return read;
|
|
8249
|
+
}
|
|
8250
|
+
const answer = await runGit(root, ["cat-file", "--batch"], deadline, {
|
|
8251
|
+
binary: true,
|
|
8252
|
+
binaryCapBytes: 16 * MAX_COMPARED_BYTES,
|
|
8253
|
+
input: `${unique.join("\n")}
|
|
8254
|
+
`
|
|
8255
|
+
});
|
|
8256
|
+
if (answer.kind === "unavailable") {
|
|
8257
|
+
return timedOut(answer.reason) ? "ran-out-of-time" : void 0;
|
|
8024
8258
|
}
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8259
|
+
if (answer.code !== 0) {
|
|
8260
|
+
return void 0;
|
|
8261
|
+
}
|
|
8262
|
+
let at = 0;
|
|
8263
|
+
for (const id of unique) {
|
|
8264
|
+
const end = answer.stdout.indexOf("\n", at);
|
|
8265
|
+
if (end === -1) {
|
|
8266
|
+
return void 0;
|
|
8031
8267
|
}
|
|
8032
|
-
const
|
|
8033
|
-
if (
|
|
8034
|
-
|
|
8268
|
+
const header = answer.stdout.slice(at, end).split(" ");
|
|
8269
|
+
if (header[1] === "missing") {
|
|
8270
|
+
read.set(id, null);
|
|
8271
|
+
at = end + 1;
|
|
8272
|
+
continue;
|
|
8035
8273
|
}
|
|
8036
|
-
|
|
8274
|
+
const size = Number(header[2]);
|
|
8275
|
+
if (header[1] !== "blob" || !Number.isInteger(size)) {
|
|
8276
|
+
return void 0;
|
|
8277
|
+
}
|
|
8278
|
+
read.set(
|
|
8279
|
+
id,
|
|
8280
|
+
size > MAX_COMPARED_BYTES ? null : answer.stdout.slice(end + 1, end + 1 + size)
|
|
8281
|
+
);
|
|
8282
|
+
at = end + 1 + size + 1;
|
|
8037
8283
|
}
|
|
8284
|
+
return read;
|
|
8038
8285
|
}
|
|
8039
|
-
async function
|
|
8040
|
-
|
|
8041
|
-
|
|
8286
|
+
async function restorableBytes(root, file, entry, indexed, fileMode) {
|
|
8287
|
+
if (indexed === null || indexed === void 0 || entry.mode === "120000") {
|
|
8288
|
+
return null;
|
|
8289
|
+
}
|
|
8290
|
+
const absolute = path8.join(root, file);
|
|
8291
|
+
const info = await lstat4(absolute).catch(() => null);
|
|
8292
|
+
if (info === null || !info.isFile() || info.size > MAX_COMPARED_BYTES) {
|
|
8293
|
+
return null;
|
|
8294
|
+
}
|
|
8295
|
+
const executable = (info.mode & 73) !== 0;
|
|
8296
|
+
if (fileMode && executable !== (entry.mode === "100755")) {
|
|
8297
|
+
return null;
|
|
8298
|
+
}
|
|
8299
|
+
const local = await readFile3(absolute).catch(() => null);
|
|
8300
|
+
if (local === null) {
|
|
8301
|
+
return null;
|
|
8302
|
+
}
|
|
8303
|
+
const mine = local.toString("latin1");
|
|
8304
|
+
if (mine.includes("\0") || indexed.includes("\0")) {
|
|
8305
|
+
return null;
|
|
8306
|
+
}
|
|
8307
|
+
return mine.replace(CRLF, "\n") === indexed.replace(CRLF, "\n") ? local : null;
|
|
8308
|
+
}
|
|
8309
|
+
async function realBlockers(root, files, deadline) {
|
|
8310
|
+
const scope = ["--literal-pathspecs", "-c", "core.quotepath=false"];
|
|
8311
|
+
const [staged, unstaged, flags, stages, attributes, fileModeSetting] = await Promise.all([
|
|
8312
|
+
runGit(
|
|
8313
|
+
root,
|
|
8314
|
+
[
|
|
8315
|
+
...scope,
|
|
8316
|
+
"diff",
|
|
8317
|
+
"--cached",
|
|
8318
|
+
"--name-only",
|
|
8319
|
+
"-z",
|
|
8320
|
+
"--",
|
|
8321
|
+
...files
|
|
8322
|
+
],
|
|
8323
|
+
deadline
|
|
8324
|
+
),
|
|
8325
|
+
runGit(
|
|
8326
|
+
root,
|
|
8327
|
+
[...scope, "diff", "--name-only", "-z", "--", ...files],
|
|
8328
|
+
deadline
|
|
8329
|
+
),
|
|
8330
|
+
runGit(
|
|
8331
|
+
root,
|
|
8332
|
+
[...scope, "ls-files", "-v", "-z", "--", ...files],
|
|
8333
|
+
deadline
|
|
8334
|
+
),
|
|
8335
|
+
runGit(
|
|
8336
|
+
root,
|
|
8337
|
+
[...scope, "ls-files", "-s", "-z", "--", ...files],
|
|
8338
|
+
deadline
|
|
8339
|
+
),
|
|
8340
|
+
runGit(
|
|
8341
|
+
root,
|
|
8342
|
+
[
|
|
8343
|
+
...scope,
|
|
8344
|
+
"check-attr",
|
|
8345
|
+
"-z",
|
|
8346
|
+
...CONVERTING_ATTRIBUTES,
|
|
8347
|
+
"--",
|
|
8348
|
+
...files
|
|
8349
|
+
],
|
|
8350
|
+
deadline
|
|
8351
|
+
),
|
|
8352
|
+
runGit(
|
|
8353
|
+
root,
|
|
8354
|
+
["config", "--type=bool", "--get", "core.fileMode"],
|
|
8355
|
+
deadline
|
|
8356
|
+
)
|
|
8357
|
+
]);
|
|
8358
|
+
const answers = [
|
|
8359
|
+
staged,
|
|
8360
|
+
unstaged,
|
|
8361
|
+
flags,
|
|
8362
|
+
stages,
|
|
8363
|
+
attributes,
|
|
8364
|
+
fileModeSetting
|
|
8365
|
+
];
|
|
8366
|
+
if (answers.some((answer) => answer.kind === "unavailable")) {
|
|
8367
|
+
return answers.some(
|
|
8368
|
+
(answer) => answer.kind === "unavailable" && timedOut(answer.reason)
|
|
8369
|
+
) ? "ran-out-of-time" : null;
|
|
8370
|
+
}
|
|
8371
|
+
if (staged.kind !== "exited" || unstaged.kind !== "exited" || flags.kind !== "exited" || stages.kind !== "exited" || attributes.kind !== "exited" || fileModeSetting.kind !== "exited" || [staged, unstaged, flags, stages, attributes].some(
|
|
8372
|
+
(answer) => answer.kind === "exited" && answer.code !== 0
|
|
8373
|
+
)) {
|
|
8374
|
+
return null;
|
|
8375
|
+
}
|
|
8376
|
+
const fileMode = fileModeSetting.stdout.trim() !== "false";
|
|
8377
|
+
const changed = /* @__PURE__ */ new Set([
|
|
8378
|
+
...nulPaths(staged.stdout),
|
|
8379
|
+
...nulPaths(unstaged.stdout)
|
|
8380
|
+
]);
|
|
8381
|
+
const ordinary = new Set(
|
|
8382
|
+
nulPaths(flags.stdout).filter((entry) => entry.startsWith("H ")).map((entry) => entry.slice(2))
|
|
8383
|
+
);
|
|
8384
|
+
const entries = /* @__PURE__ */ new Map();
|
|
8385
|
+
for (const entry of nulPaths(stages.stdout)) {
|
|
8386
|
+
const match = /^(\d+) ([0-9a-f]{40,64}) (\d)\t([\s\S]*)$/.exec(entry);
|
|
8387
|
+
if (match?.[3] === "0" && match[1] !== void 0 && match[2] !== void 0 && match[4] !== void 0) {
|
|
8388
|
+
entries.set(match[4], { mode: match[1], blob: match[2] });
|
|
8389
|
+
}
|
|
8390
|
+
}
|
|
8391
|
+
const converted = /* @__PURE__ */ new Set();
|
|
8392
|
+
const fields = nulFields(attributes.stdout);
|
|
8393
|
+
for (let at = 0; at + 2 < fields.length; at += 3) {
|
|
8394
|
+
if (fields[at + 2] !== "unspecified") {
|
|
8395
|
+
converted.add(fields[at]);
|
|
8396
|
+
}
|
|
8397
|
+
}
|
|
8398
|
+
const candidates = files.filter(
|
|
8399
|
+
(file) => entries.has(file) && ordinary.has(file) && !converted.has(file) && !changed.has(file)
|
|
8400
|
+
);
|
|
8401
|
+
const blobs = await readBlobs(
|
|
8402
|
+
root,
|
|
8403
|
+
candidates.map((file) => entries.get(file)?.blob ?? ""),
|
|
8404
|
+
deadline
|
|
8405
|
+
);
|
|
8406
|
+
if (blobs === "ran-out-of-time" || blobs === void 0) {
|
|
8407
|
+
return blobs === void 0 ? null : blobs;
|
|
8408
|
+
}
|
|
8409
|
+
const real = [];
|
|
8410
|
+
const restorable = /* @__PURE__ */ new Map();
|
|
8411
|
+
for (const file of files) {
|
|
8412
|
+
const entry = entries.get(file);
|
|
8413
|
+
const bytes = entry === void 0 || !candidates.includes(file) ? null : await restorableBytes(
|
|
8414
|
+
root,
|
|
8415
|
+
file,
|
|
8416
|
+
entry,
|
|
8417
|
+
blobs.get(entry.blob),
|
|
8418
|
+
fileMode
|
|
8419
|
+
);
|
|
8420
|
+
if (bytes === null) {
|
|
8421
|
+
real.push(file);
|
|
8422
|
+
} else {
|
|
8423
|
+
restorable.set(file, bytes);
|
|
8424
|
+
}
|
|
8425
|
+
}
|
|
8426
|
+
return { real, restorable };
|
|
8427
|
+
}
|
|
8428
|
+
var RESTORE_AND_MERGE_FLOOR_MS = 1500;
|
|
8429
|
+
async function stillUntouched(root, restorable) {
|
|
8430
|
+
for (const [file, bytes] of restorable) {
|
|
8431
|
+
const now = await readFile3(path8.join(root, file)).catch(() => null);
|
|
8432
|
+
if (now === null || !now.equals(bytes)) {
|
|
8433
|
+
return false;
|
|
8434
|
+
}
|
|
8435
|
+
}
|
|
8436
|
+
return true;
|
|
8437
|
+
}
|
|
8438
|
+
async function fastForwardTo(root, sha, deadline) {
|
|
8439
|
+
if (!COMMIT_SHA.test(sha)) {
|
|
8440
|
+
return { kind: "unavailable", reason: "not a commit name" };
|
|
8441
|
+
}
|
|
8442
|
+
const first = await mergeOnce(root, sha, deadline);
|
|
8443
|
+
if (first.kind !== "failed" || first.reason !== "local-changes" || first.files.length === 0) {
|
|
8444
|
+
return first;
|
|
8445
|
+
}
|
|
8446
|
+
const judged = await realBlockers(root, first.files, deadline);
|
|
8447
|
+
if (judged === "ran-out-of-time") {
|
|
8448
|
+
return { kind: "timed-out" };
|
|
8449
|
+
}
|
|
8450
|
+
if (judged === null) {
|
|
8451
|
+
return first;
|
|
8452
|
+
}
|
|
8453
|
+
if (judged.real.length > 0) {
|
|
8454
|
+
return { kind: "failed", reason: "local-changes", files: judged.real };
|
|
8455
|
+
}
|
|
8456
|
+
if (deadline - Date.now() < RESTORE_AND_MERGE_FLOOR_MS || !await stillUntouched(root, judged.restorable)) {
|
|
8457
|
+
return first;
|
|
8458
|
+
}
|
|
8459
|
+
const restored = await runGit(
|
|
8460
|
+
root,
|
|
8461
|
+
["--literal-pathspecs", "checkout", "--", ...first.files],
|
|
8462
|
+
deadline,
|
|
8463
|
+
{ write: true, unattended: true }
|
|
8464
|
+
);
|
|
8465
|
+
if (restored.kind !== "exited" || restored.code !== 0) {
|
|
8466
|
+
return first;
|
|
8467
|
+
}
|
|
8468
|
+
return mergeOnce(root, sha, deadline);
|
|
8469
|
+
}
|
|
8470
|
+
|
|
8471
|
+
// src/lib/instructions/git.ts
|
|
8472
|
+
var OPERATION_MARKERS = [
|
|
8473
|
+
{ marker: "MERGE_HEAD", operation: "merge" },
|
|
8474
|
+
{ marker: "REBASE_HEAD", operation: "rebase" },
|
|
8475
|
+
{ marker: "rebase-merge", operation: "rebase" },
|
|
8476
|
+
{ marker: "rebase-apply", operation: "rebase" },
|
|
8477
|
+
{ marker: "CHERRY_PICK_HEAD", operation: "cherry-pick" },
|
|
8478
|
+
{ marker: "REVERT_HEAD", operation: "revert" },
|
|
8479
|
+
{ marker: "BISECT_LOG", operation: "bisect" },
|
|
8480
|
+
// Left by a multi-commit cherry-pick or revert between steps; the two
|
|
8481
|
+
// HEAD markers above name which one while a step is stopped.
|
|
8482
|
+
{ marker: "sequencer", operation: "cherry-pick" }
|
|
8483
|
+
];
|
|
8484
|
+
async function nearestExistingDirectory(dir) {
|
|
8485
|
+
let current = path9.resolve(dir);
|
|
8486
|
+
for (; ; ) {
|
|
8487
|
+
const found = await stat(current).catch(() => null);
|
|
8488
|
+
if (found?.isDirectory()) {
|
|
8489
|
+
return current;
|
|
8490
|
+
}
|
|
8491
|
+
const parent = path9.dirname(current);
|
|
8492
|
+
if (parent === current) {
|
|
8493
|
+
return null;
|
|
8494
|
+
}
|
|
8495
|
+
current = parent;
|
|
8496
|
+
}
|
|
8497
|
+
}
|
|
8498
|
+
async function hasGitEntryAbove(dir) {
|
|
8499
|
+
let current = dir;
|
|
8500
|
+
for (; ; ) {
|
|
8501
|
+
if (await lstat5(path9.join(current, ".git")).catch(() => null) !== null) {
|
|
8502
|
+
return true;
|
|
8503
|
+
}
|
|
8504
|
+
const parent = path9.dirname(current);
|
|
8505
|
+
if (parent === current) {
|
|
8506
|
+
return false;
|
|
8507
|
+
}
|
|
8508
|
+
current = parent;
|
|
8509
|
+
}
|
|
8510
|
+
}
|
|
8511
|
+
async function findWorkTree(dir, deadline) {
|
|
8512
|
+
const cwd = await nearestExistingDirectory(dir);
|
|
8513
|
+
if (cwd === null) {
|
|
8042
8514
|
return { kind: "absent" };
|
|
8043
8515
|
}
|
|
8044
8516
|
const result = await runGit(
|
|
@@ -8075,7 +8547,7 @@ async function findWorkTree(dir, deadline) {
|
|
|
8075
8547
|
kind: "ok",
|
|
8076
8548
|
value: {
|
|
8077
8549
|
toplevel,
|
|
8078
|
-
gitDir:
|
|
8550
|
+
gitDir: path9.resolve(cwd, gitDir),
|
|
8079
8551
|
shallow: shallow === "true",
|
|
8080
8552
|
superproject: superproject ? superproject : null
|
|
8081
8553
|
}
|
|
@@ -8174,7 +8646,7 @@ async function isAncestor(root, ancestorSha, descendantSha, deadline) {
|
|
|
8174
8646
|
}
|
|
8175
8647
|
return { kind: "unavailable", reason: "the commit is not in this clone" };
|
|
8176
8648
|
}
|
|
8177
|
-
async function
|
|
8649
|
+
async function hasNoTrackedContentChanges(root, deadline) {
|
|
8178
8650
|
for (const scope of [[], ["--cached"]]) {
|
|
8179
8651
|
const result = await simple(
|
|
8180
8652
|
root,
|
|
@@ -8201,7 +8673,7 @@ async function trackedPaths(root, relativePaths, deadline) {
|
|
|
8201
8673
|
return { kind: "ok", value: [] };
|
|
8202
8674
|
}
|
|
8203
8675
|
if (relativePaths.some(
|
|
8204
|
-
(relativePath) => relativePath === "" || relativePath.startsWith("-") ||
|
|
8676
|
+
(relativePath) => relativePath === "" || relativePath.startsWith("-") || path9.isAbsolute(relativePath) || path9.win32.isAbsolute(relativePath) || relativePath.split(/[\\/]/).some((part) => part === "..")
|
|
8205
8677
|
)) {
|
|
8206
8678
|
return {
|
|
8207
8679
|
kind: "unavailable",
|
|
@@ -8287,8 +8759,8 @@ async function operationInProgress(root, deadline) {
|
|
|
8287
8759
|
return { kind: "unavailable", reason: "git answered unexpectedly" };
|
|
8288
8760
|
}
|
|
8289
8761
|
for (const [index, { operation }] of OPERATION_MARKERS.entries()) {
|
|
8290
|
-
const marker =
|
|
8291
|
-
if (await
|
|
8762
|
+
const marker = path9.resolve(root, paths[index]);
|
|
8763
|
+
if (await lstat5(marker).catch(() => null) !== null) {
|
|
8292
8764
|
return { kind: "ok", value: operation };
|
|
8293
8765
|
}
|
|
8294
8766
|
}
|
|
@@ -8378,7 +8850,7 @@ async function lockFilesPresent(root, deadline) {
|
|
|
8378
8850
|
return { kind: "unavailable", reason: "git answered unexpectedly" };
|
|
8379
8851
|
}
|
|
8380
8852
|
for (const lock of paths) {
|
|
8381
|
-
if (await
|
|
8853
|
+
if (await lstat5(path9.resolve(root, lock)).catch(() => null) !== null) {
|
|
8382
8854
|
return { kind: "ok", value: true };
|
|
8383
8855
|
}
|
|
8384
8856
|
}
|
|
@@ -8426,7 +8898,7 @@ async function commonDir(root, deadline) {
|
|
|
8426
8898
|
if (result.value.code !== 0 || !dir) {
|
|
8427
8899
|
return { kind: "unavailable", reason: "git answered unexpectedly" };
|
|
8428
8900
|
}
|
|
8429
|
-
return { kind: "ok", value:
|
|
8901
|
+
return { kind: "ok", value: path9.resolve(root, dir) };
|
|
8430
8902
|
}
|
|
8431
8903
|
async function excludeFilePath(root, deadline) {
|
|
8432
8904
|
const result = await simple(
|
|
@@ -8441,7 +8913,7 @@ async function excludeFilePath(root, deadline) {
|
|
|
8441
8913
|
if (result.value.code !== 0 || !answer) {
|
|
8442
8914
|
return { kind: "unavailable", reason: "git answered unexpectedly" };
|
|
8443
8915
|
}
|
|
8444
|
-
return { kind: "ok", value:
|
|
8916
|
+
return { kind: "ok", value: path9.resolve(root, answer) };
|
|
8445
8917
|
}
|
|
8446
8918
|
async function isIgnored(root, relativePath, deadline) {
|
|
8447
8919
|
if (relativePath.startsWith("-") || relativePath === "") {
|
|
@@ -8470,6 +8942,9 @@ async function isIgnored(root, relativePath, deadline) {
|
|
|
8470
8942
|
};
|
|
8471
8943
|
}
|
|
8472
8944
|
function cloneFailureOf(stderr) {
|
|
8945
|
+
if (/config-env/i.test(stderr) && /unknown option|usage:/i.test(stderr)) {
|
|
8946
|
+
return "old-git";
|
|
8947
|
+
}
|
|
8473
8948
|
if (/already exists and is not an empty directory/i.test(stderr)) {
|
|
8474
8949
|
return "not-empty";
|
|
8475
8950
|
}
|
|
@@ -8550,6 +9025,12 @@ var TRAIT_TEXT = {
|
|
|
8550
9025
|
sparse: " (sparse checkout)",
|
|
8551
9026
|
superproject: " (inside a superproject)"
|
|
8552
9027
|
};
|
|
9028
|
+
function forProject(project, otherProjects) {
|
|
9029
|
+
return project !== void 0 && otherProjects === true ? ` for project ${project} (it is signed in for other projects)` : "";
|
|
9030
|
+
}
|
|
9031
|
+
function shown(value) {
|
|
9032
|
+
return sanitizeDisplayText(value, 255);
|
|
9033
|
+
}
|
|
8553
9034
|
var MAX_BLOCKED_FILES = 3;
|
|
8554
9035
|
function blockedFilesText(files) {
|
|
8555
9036
|
if (files.length === 0) {
|
|
@@ -8559,8 +9040,8 @@ function blockedFilesText(files) {
|
|
|
8559
9040
|
const more = files.length - MAX_BLOCKED_FILES;
|
|
8560
9041
|
return ` (${named}${more > 0 ? ` and ${more} more` : ""})`;
|
|
8561
9042
|
}
|
|
8562
|
-
function
|
|
8563
|
-
return
|
|
9043
|
+
function isUserActionableMergeFailure(reason) {
|
|
9044
|
+
return reason === "diverged" || reason === "local-changes";
|
|
8564
9045
|
}
|
|
8565
9046
|
var LAG_TEXT = {
|
|
8566
9047
|
refused: "a commit was refused by the secret scan \u2014 see the project's Coding Instructions tab",
|
|
@@ -8575,15 +9056,15 @@ var TOOL_LABEL = {
|
|
|
8575
9056
|
codex: "Codex"
|
|
8576
9057
|
};
|
|
8577
9058
|
function sessionStartClause(tools, applies) {
|
|
8578
|
-
const
|
|
8579
|
-
const single =
|
|
9059
|
+
const names2 = tools.map((tool) => TOOL_LABEL[tool]);
|
|
9060
|
+
const single = names2.length === 1;
|
|
8580
9061
|
const verb = applies ? `${single ? "applies" : "apply"} updates` : `${single ? "checks" : "check"} for updates`;
|
|
8581
|
-
return `${
|
|
9062
|
+
return `${names2.join(" and ")} ${verb} at every session start.`;
|
|
8582
9063
|
}
|
|
8583
9064
|
function fastForwardClause(tools, ref) {
|
|
8584
|
-
const
|
|
8585
|
-
const verb =
|
|
8586
|
-
return `${
|
|
9065
|
+
const names2 = tools.map((tool) => TOOL_LABEL[tool]);
|
|
9066
|
+
const verb = names2.length === 1 ? "fast-forwards" : "fast-forward";
|
|
9067
|
+
return `${names2.join(" and ")} ${verb} ${ref} at session start when safe.`;
|
|
8587
9068
|
}
|
|
8588
9069
|
function loginCommand(origin, project) {
|
|
8589
9070
|
if (!isSafeArgument(origin)) {
|
|
@@ -8594,6 +9075,22 @@ function loginCommand(origin, project) {
|
|
|
8594
9075
|
origin
|
|
8595
9076
|
);
|
|
8596
9077
|
}
|
|
9078
|
+
function behindAdvice(condition, ref) {
|
|
9079
|
+
switch (condition.kind) {
|
|
9080
|
+
case "clean":
|
|
9081
|
+
return ` \u2014 run: ${condition.pull}`;
|
|
9082
|
+
case "dirty":
|
|
9083
|
+
return " and has uncommitted changes \u2014 commit or stash, then pull.";
|
|
9084
|
+
case "operation":
|
|
9085
|
+
return `; a ${condition.operation} is in progress; nothing was changed.`;
|
|
9086
|
+
case "detached":
|
|
9087
|
+
return `; HEAD is detached \u2014 check out ${ref} and pull.`;
|
|
9088
|
+
case "other-branch":
|
|
9089
|
+
return `; you are on ${condition.branch} \u2014 pull ${ref} when you switch to it.`;
|
|
9090
|
+
default:
|
|
9091
|
+
return condition;
|
|
9092
|
+
}
|
|
9093
|
+
}
|
|
8597
9094
|
var LINES = {
|
|
8598
9095
|
"needs-project": ({ verb }) => `This folder is not a git checkout, so its project cannot be found. Run: ${fabricCommand(`instructions ${verb} --project <id>`)}`,
|
|
8599
9096
|
"no-remote": ({ verb }) => `This checkout has no remote, so its project cannot be found. Run: ${fabricCommand(`instructions ${verb} --project <id>`)}`,
|
|
@@ -8612,8 +9109,8 @@ var LINES = {
|
|
|
8612
9109
|
"bad-project-id": () => "--project must be a project id: letters, digits, '.', '_' or '-', starting with a letter or digit, at most 64 characters.",
|
|
8613
9110
|
"bad-org-slug": () => "--org must be an organization slug: letters, digits, '.', '_' or '-', starting with a letter or digit, at most 64 characters.",
|
|
8614
9111
|
"bad-remote-name": () => "--remote must be the name of a git remote: letters, digits, '.', '_', '-' and '/', starting with a letter, digit, '.' or '_'.",
|
|
8615
|
-
"not-signed-in": ({ origin, project }) => `Not signed in to ${origin}. Run: ${loginCommand(origin, project)}`,
|
|
8616
|
-
"hook-signed-out": ({ origin, project }) => `not signed in to ${origin} \u2014 run: ${loginCommand(origin, project)}`,
|
|
9112
|
+
"not-signed-in": ({ origin, project, otherProjects }) => `Not signed in to ${origin}${forProject(project, otherProjects)}. Run: ${loginCommand(origin, project)}`,
|
|
9113
|
+
"hook-signed-out": ({ origin, project, otherProjects }) => `not signed in to ${origin}${forProject(project, otherProjects)} \u2014 run: ${loginCommand(origin, project)}`,
|
|
8617
9114
|
"sign-in-failed": ({ origin, project }) => `Could not sign in to ${origin}. Run: ${loginCommand(origin, project)} to see why.`,
|
|
8618
9115
|
"unknown-tool": ({ tool }) => `Unsupported tool "${tool}". Use --tool claude-code or --tool codex.`,
|
|
8619
9116
|
"lessons-need-claude": () => "--lessons is not yet supported for codex; Codex hooks for lesson capture are not wired.",
|
|
@@ -8644,6 +9141,8 @@ var LINES = {
|
|
|
8644
9141
|
return `Could not clone ${repo}: it has no branch ${shown(ref)}. The Fabric MCP server was not registered.`;
|
|
8645
9142
|
case "checkout":
|
|
8646
9143
|
return `Could not finish checking out ${repo}. Fabric did not change the partial checkout or register its MCP server. Inspect it with git status, then repair or remove it yourself before trying again.`;
|
|
9144
|
+
case "old-git":
|
|
9145
|
+
return `Could not clone ${repo}: this git is older than 2.31, which Fabric's repository transport needs. The Fabric MCP server was not registered. Update git and try again.`;
|
|
8647
9146
|
case "other":
|
|
8648
9147
|
return `Could not clone ${repo}. The Fabric MCP server was not registered. Run: git clone -- ${url} to see why.`;
|
|
8649
9148
|
default:
|
|
@@ -8653,23 +9152,8 @@ var LINES = {
|
|
|
8653
9152
|
"no-clone-url": ({ repo }) => `This deployment did not say where to clone ${repo} from. Clone it yourself, then run: ${fabricCommand("instructions init")}`,
|
|
8654
9153
|
"lock-removed": ({ repo }) => `Removed .fabric/instructions.lock: this checkout follows ${repo} through git now.`,
|
|
8655
9154
|
"exclude-failed": ({ entries }) => `Could not update .git/info/exclude. Add these to your own ignore rules: ${entries.map(shown).join(" ")}`,
|
|
8656
|
-
behind: ({ version, sha7, ref, notFetched, traits, condition }) => {
|
|
8657
|
-
|
|
8658
|
-
switch (condition.kind) {
|
|
8659
|
-
case "clean":
|
|
8660
|
-
return `${state} \u2014 run: ${condition.pull}`;
|
|
8661
|
-
case "dirty":
|
|
8662
|
-
return `${state} and has uncommitted changes \u2014 commit or stash, then pull.`;
|
|
8663
|
-
case "operation":
|
|
8664
|
-
return `${state}; a ${condition.operation} is in progress; nothing was changed.`;
|
|
8665
|
-
case "detached":
|
|
8666
|
-
return `${state}; HEAD is detached \u2014 check out ${ref} and pull.`;
|
|
8667
|
-
case "other-branch":
|
|
8668
|
-
return `${state}; you are on ${condition.branch} \u2014 pull ${ref} when you switch to it.`;
|
|
8669
|
-
default:
|
|
8670
|
-
return condition;
|
|
8671
|
-
}
|
|
8672
|
-
},
|
|
9155
|
+
behind: ({ version, sha7, ref, notFetched, traits, condition }) => `${HOOK_PREFIX} v${version} (${sha7}) is on ${ref}; this checkout ${notFetched ? "has not fetched it yet" : "is behind"}${traits.map((trait) => TRAIT_TEXT[trait]).join("")}${behindAdvice(condition, ref)}`,
|
|
9156
|
+
"behind-direct": ({ repo, sha7, ref, traits, condition }) => `${HOOK_PREFIX}: read directly from ${repo} at ${sha7}; this checkout is behind${traits.map((trait) => TRAIT_TEXT[trait]).join("")}${behindAdvice(condition, ref)}`,
|
|
8673
9157
|
"earlier-source": ({ version, sourceRef, ref, repo }) => `${HOOK_PREFIX} v${version} was published from ${sourceRef}; the project now syncs ${ref} of ${repo} \u2014 pull ${ref} to pick up the next publication`,
|
|
8674
9158
|
"nothing-published": ({ repo }) => `${HOOK_PREFIX}: the project is repository-sourced but nothing has been published from ${repo} yet`,
|
|
8675
9159
|
"already-current": ({ version, sha7, ref, repo }) => `${HOOK_PREFIX} v${version}${sha7 === null ? "" : ` (${sha7})`} from ${ref} of ${repo} is already in this checkout's history; nothing to sync`,
|
|
@@ -8702,16 +9186,18 @@ var LINES = {
|
|
|
8702
9186
|
return `could not fetch ${repo}: it has no branch ${ref}; nothing was updated.`;
|
|
8703
9187
|
case "timeout":
|
|
8704
9188
|
return `could not fetch ${repo}: ${host} answered too slowly; nothing was updated.`;
|
|
9189
|
+
case "old-git":
|
|
9190
|
+
return `${HOOK_PREFIX}: could not fetch ${repo}: this git is older than 2.31, which Fabric's repository transport needs. Update git.`;
|
|
8705
9191
|
case "other":
|
|
8706
9192
|
return `could not fetch ${repo}. Run: ${commands.fetch} to see why.`;
|
|
8707
9193
|
default:
|
|
8708
9194
|
return reason;
|
|
8709
9195
|
}
|
|
8710
9196
|
},
|
|
8711
|
-
"ff-merge-failed": ({
|
|
8712
|
-
switch (reason) {
|
|
9197
|
+
"ff-merge-failed": ({ failure, ref, remote, commands }) => {
|
|
9198
|
+
switch (failure.reason) {
|
|
8713
9199
|
case "local-changes":
|
|
8714
|
-
return `${HOOK_PREFIX}: ${ref} is behind ${remote}/${ref}, but local changes would be overwritten${blockedFilesText(files
|
|
9200
|
+
return `${HOOK_PREFIX}: ${ref} is behind ${remote}/${ref}, but local changes would be overwritten${blockedFilesText(failure.files)}, so nothing was updated. Stash them or move them to another branch; the next session start updates this checkout on its own.`;
|
|
8715
9201
|
case "diverged":
|
|
8716
9202
|
return `${HOOK_PREFIX}: ${ref} and ${remote}/${ref} have diverged, so nothing was updated. Run: ${commands.rebase}, or merge ${remote}/${ref} yourself.`;
|
|
8717
9203
|
case "timeout":
|
|
@@ -8719,7 +9205,7 @@ var LINES = {
|
|
|
8719
9205
|
case "other":
|
|
8720
9206
|
return `git could not fast-forward ${ref}. Run: ${commands.pull} to see why.`;
|
|
8721
9207
|
default:
|
|
8722
|
-
return
|
|
9208
|
+
return failure;
|
|
8723
9209
|
}
|
|
8724
9210
|
},
|
|
8725
9211
|
"ff-locked": () => `${HOOK_PREFIX}: another fabric process is updating this checkout; nothing was changed.`,
|
|
@@ -8783,6 +9269,7 @@ var EXIT_CODES = {
|
|
|
8783
9269
|
"lock-removed": 0,
|
|
8784
9270
|
"exclude-failed": 0,
|
|
8785
9271
|
behind: 0,
|
|
9272
|
+
"behind-direct": 0,
|
|
8786
9273
|
"earlier-source": 0,
|
|
8787
9274
|
"nothing-published": 0,
|
|
8788
9275
|
"already-current": 0,
|
|
@@ -9056,7 +9543,7 @@ function downloadsIn(classification, manual) {
|
|
|
9056
9543
|
return manual && classification.class === "foreign";
|
|
9057
9544
|
}
|
|
9058
9545
|
function isSafeRootPath(rootPath) {
|
|
9059
|
-
if (
|
|
9546
|
+
if (path10.posix.isAbsolute(rootPath) || path10.win32.isAbsolute(rootPath) || rootPath.startsWith("~")) {
|
|
9060
9547
|
return false;
|
|
9061
9548
|
}
|
|
9062
9549
|
return !rootPath.split(/[\\/]/).some((segment) => segment === "..");
|
|
@@ -9120,15 +9607,15 @@ async function classify({
|
|
|
9120
9607
|
reason: "the project's branch name is not one this hook will use"
|
|
9121
9608
|
};
|
|
9122
9609
|
}
|
|
9123
|
-
const
|
|
9124
|
-
if (
|
|
9610
|
+
const names2 = await remotes(root, deadline);
|
|
9611
|
+
if (names2.kind !== "ok") {
|
|
9125
9612
|
return {
|
|
9126
9613
|
class: "unknown",
|
|
9127
|
-
reason:
|
|
9614
|
+
reason: names2.kind === "absent" ? "not a git checkout" : names2.reason
|
|
9128
9615
|
};
|
|
9129
9616
|
}
|
|
9130
9617
|
const matching = [];
|
|
9131
|
-
for (const name of remote === void 0 ?
|
|
9618
|
+
for (const name of remote === void 0 ? names2.value : names2.value.filter((candidate) => candidate === remote)) {
|
|
9132
9619
|
const url = await effectiveFetchUrl(root, name, deadline);
|
|
9133
9620
|
if (url.kind !== "ok") {
|
|
9134
9621
|
return {
|
|
@@ -9150,7 +9637,7 @@ async function classify({
|
|
|
9150
9637
|
if (matching.length > 1) {
|
|
9151
9638
|
return { class: "ambiguous", remotes: matching };
|
|
9152
9639
|
}
|
|
9153
|
-
const mapped =
|
|
9640
|
+
const mapped = path10.join(root, repository.rootPath);
|
|
9154
9641
|
if (!await sameDirectory(destination, mapped)) {
|
|
9155
9642
|
return {
|
|
9156
9643
|
class: "unmapped",
|
|
@@ -9176,34 +9663,29 @@ async function inspectMatchingCheckout(input) {
|
|
|
9176
9663
|
const { classification, repository, snapshot, deadline } = input;
|
|
9177
9664
|
const root = classification.toplevel;
|
|
9178
9665
|
const unknown = (reason) => reportFor(repository, { class: "unknown", reason });
|
|
9179
|
-
const branch = await
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
)
|
|
9184
|
-
|
|
9185
|
-
const
|
|
9186
|
-
|
|
9187
|
-
|
|
9188
|
-
|
|
9189
|
-
)
|
|
9190
|
-
|
|
9191
|
-
|
|
9192
|
-
|
|
9193
|
-
|
|
9194
|
-
clean.kind === "absent" ? "not a git checkout" : clean.reason
|
|
9195
|
-
);
|
|
9666
|
+
const [branch, head, operation, readClean] = await Promise.all([
|
|
9667
|
+
currentBranch(root, deadline),
|
|
9668
|
+
headSha(root, deadline),
|
|
9669
|
+
operationInProgress(root, deadline),
|
|
9670
|
+
input.contentChanges === "always" ? hasNoTrackedContentChanges(root, deadline) : Promise.resolve(null)
|
|
9671
|
+
]);
|
|
9672
|
+
for (const answer of [branch, head, operation, readClean]) {
|
|
9673
|
+
if (answer === null) {
|
|
9674
|
+
continue;
|
|
9675
|
+
}
|
|
9676
|
+
if (answer.kind !== "ok") {
|
|
9677
|
+
return unknown(
|
|
9678
|
+
answer.kind === "absent" ? "not a git checkout" : answer.reason
|
|
9679
|
+
);
|
|
9680
|
+
}
|
|
9196
9681
|
}
|
|
9197
|
-
|
|
9198
|
-
|
|
9199
|
-
return unknown(
|
|
9200
|
-
operation.kind === "absent" ? "not a git checkout" : operation.reason
|
|
9201
|
-
);
|
|
9682
|
+
if (branch.kind !== "ok" || head.kind !== "ok" || operation.kind !== "ok") {
|
|
9683
|
+
return unknown("the checkout could not be read");
|
|
9202
9684
|
}
|
|
9203
|
-
|
|
9685
|
+
let state = {
|
|
9204
9686
|
branch: branch.value,
|
|
9205
9687
|
head: head.value,
|
|
9206
|
-
clean:
|
|
9688
|
+
clean: readClean === null ? true : readClean.kind === "ok" && readClean.value,
|
|
9207
9689
|
operation: operation.value,
|
|
9208
9690
|
traits: classification.traits
|
|
9209
9691
|
};
|
|
@@ -9222,13 +9704,21 @@ async function inspectMatchingCheckout(input) {
|
|
|
9222
9704
|
contains = answer.kind === "ok" ? answer.value : null;
|
|
9223
9705
|
}
|
|
9224
9706
|
}
|
|
9225
|
-
const
|
|
9707
|
+
const report = () => matchingReport({
|
|
9226
9708
|
repository,
|
|
9227
9709
|
remote: classification.remote,
|
|
9228
9710
|
snapshot: snapshot ? { version: snapshot.version, source: snapshot.source } : null,
|
|
9229
9711
|
state,
|
|
9230
9712
|
contains
|
|
9231
9713
|
});
|
|
9714
|
+
let { kind: reportKind, line: line2 } = report();
|
|
9715
|
+
if (readClean === null && reportKind === "behind") {
|
|
9716
|
+
const content = await hasNoTrackedContentChanges(root, deadline);
|
|
9717
|
+
if (content.kind === "ok") {
|
|
9718
|
+
state = { ...state, clean: content.value };
|
|
9719
|
+
({ kind: reportKind, line: line2 } = report());
|
|
9720
|
+
}
|
|
9721
|
+
}
|
|
9232
9722
|
return {
|
|
9233
9723
|
classification,
|
|
9234
9724
|
state,
|
|
@@ -9302,30 +9792,6 @@ function matchingReport(input) {
|
|
|
9302
9792
|
if (contains === true) {
|
|
9303
9793
|
return { kind: "current", line: null };
|
|
9304
9794
|
}
|
|
9305
|
-
let condition;
|
|
9306
|
-
if (state.operation !== null) {
|
|
9307
|
-
condition = { kind: "operation", operation: state.operation };
|
|
9308
|
-
} else if (state.branch === null) {
|
|
9309
|
-
condition = { kind: "detached" };
|
|
9310
|
-
} else if (state.branch !== repository.ref) {
|
|
9311
|
-
condition = {
|
|
9312
|
-
kind: "other-branch",
|
|
9313
|
-
branch: show(state.branch, MAX.ref)
|
|
9314
|
-
};
|
|
9315
|
-
} else if (!state.clean) {
|
|
9316
|
-
condition = { kind: "dirty" };
|
|
9317
|
-
} else {
|
|
9318
|
-
condition = {
|
|
9319
|
-
kind: "clean",
|
|
9320
|
-
pull: [
|
|
9321
|
-
"git",
|
|
9322
|
-
"pull",
|
|
9323
|
-
"--ff-only",
|
|
9324
|
-
shellQuote(show(remote, MAX.remote)),
|
|
9325
|
-
shellQuote(ref)
|
|
9326
|
-
].join(" ")
|
|
9327
|
-
};
|
|
9328
|
-
}
|
|
9329
9795
|
return {
|
|
9330
9796
|
kind: "behind",
|
|
9331
9797
|
line: outcomeLine("behind", {
|
|
@@ -9334,10 +9800,43 @@ function matchingReport(input) {
|
|
|
9334
9800
|
ref,
|
|
9335
9801
|
notFetched: contains !== false,
|
|
9336
9802
|
traits: traitNames(state.traits),
|
|
9337
|
-
condition
|
|
9803
|
+
condition: behindCondition(state, repository.ref, remote)
|
|
9338
9804
|
})
|
|
9339
9805
|
};
|
|
9340
9806
|
}
|
|
9807
|
+
function behindCondition(state, branchRef, remote) {
|
|
9808
|
+
if (state.operation !== null) {
|
|
9809
|
+
return { kind: "operation", operation: state.operation };
|
|
9810
|
+
}
|
|
9811
|
+
if (state.branch === null) {
|
|
9812
|
+
return { kind: "detached" };
|
|
9813
|
+
}
|
|
9814
|
+
if (state.branch !== branchRef) {
|
|
9815
|
+
return { kind: "other-branch", branch: show(state.branch, MAX.ref) };
|
|
9816
|
+
}
|
|
9817
|
+
if (!state.clean) {
|
|
9818
|
+
return { kind: "dirty" };
|
|
9819
|
+
}
|
|
9820
|
+
return {
|
|
9821
|
+
kind: "clean",
|
|
9822
|
+
pull: [
|
|
9823
|
+
"git",
|
|
9824
|
+
"pull",
|
|
9825
|
+
"--ff-only",
|
|
9826
|
+
shellQuote(show(remote, MAX.remote)),
|
|
9827
|
+
shellQuote(show(branchRef, MAX.ref))
|
|
9828
|
+
].join(" ")
|
|
9829
|
+
};
|
|
9830
|
+
}
|
|
9831
|
+
function directBehindLine(input) {
|
|
9832
|
+
return outcomeLine("behind-direct", {
|
|
9833
|
+
repo: repositoryName(input.repository),
|
|
9834
|
+
sha7: show(input.commitSha.slice(0, 7), 7),
|
|
9835
|
+
ref: show(input.ref, MAX.ref),
|
|
9836
|
+
traits: traitNames(input.state.traits),
|
|
9837
|
+
condition: behindCondition(input.state, input.ref, input.remote)
|
|
9838
|
+
});
|
|
9839
|
+
}
|
|
9341
9840
|
function classLine(classification, repository) {
|
|
9342
9841
|
const name = repository ? repositoryName(repository) : "its repository";
|
|
9343
9842
|
switch (classification.class) {
|
|
@@ -9371,7 +9870,11 @@ function classLine(classification, repository) {
|
|
|
9371
9870
|
}
|
|
9372
9871
|
async function inspectCheckout(input) {
|
|
9373
9872
|
const classification = await classifyCheckout(input);
|
|
9374
|
-
return reportForClassification({
|
|
9873
|
+
return reportForClassification({
|
|
9874
|
+
contentChanges: "always",
|
|
9875
|
+
...input,
|
|
9876
|
+
classification
|
|
9877
|
+
});
|
|
9375
9878
|
}
|
|
9376
9879
|
async function reportForClassification(input) {
|
|
9377
9880
|
const { classification, repository } = input;
|
|
@@ -9384,6 +9887,7 @@ async function reportForClassification(input) {
|
|
|
9384
9887
|
try {
|
|
9385
9888
|
return await inspectMatchingCheckout({
|
|
9386
9889
|
...input,
|
|
9890
|
+
contentChanges: input.contentChanges ?? "always",
|
|
9387
9891
|
classification,
|
|
9388
9892
|
repository
|
|
9389
9893
|
});
|
|
@@ -9436,7 +9940,7 @@ function providerLoginCommand(provider, gitCommand) {
|
|
|
9436
9940
|
}
|
|
9437
9941
|
}
|
|
9438
9942
|
function folderForCommand(dest, relative) {
|
|
9439
|
-
const inside = relative.split(
|
|
9943
|
+
const inside = relative.split(path11.sep).join("/") || ".";
|
|
9440
9944
|
const typed = dest?.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
9441
9945
|
return shellQuote(
|
|
9442
9946
|
typed === void 0 || typed === "" ? inside : inside === "." ? typed : `${typed}/${inside}`
|
|
@@ -9478,9 +9982,9 @@ function adoptionFor(input) {
|
|
|
9478
9982
|
repo,
|
|
9479
9983
|
where: folderForCommand(
|
|
9480
9984
|
input.rerun.dest,
|
|
9481
|
-
|
|
9985
|
+
path11.relative(
|
|
9482
9986
|
input.destination,
|
|
9483
|
-
|
|
9987
|
+
path11.join(
|
|
9484
9988
|
classification.toplevel,
|
|
9485
9989
|
classification.rootPath
|
|
9486
9990
|
)
|
|
@@ -9512,8 +10016,9 @@ function adoptionFor(input) {
|
|
|
9512
10016
|
}
|
|
9513
10017
|
|
|
9514
10018
|
// src/lib/instructions/agent-mcp-config.ts
|
|
9515
|
-
import { readFile as
|
|
9516
|
-
import
|
|
10019
|
+
import { readFile as readFile4, stat as stat2 } from "fs/promises";
|
|
10020
|
+
import path12 from "path";
|
|
10021
|
+
var LEGACY_SERVER_NAME = "fabric";
|
|
9517
10022
|
var MAX_CONFIG_BYTES = 16 * 1024 * 1024;
|
|
9518
10023
|
async function readText(file) {
|
|
9519
10024
|
try {
|
|
@@ -9521,7 +10026,7 @@ async function readText(file) {
|
|
|
9521
10026
|
if (!info.isFile() || info.size > MAX_CONFIG_BYTES) {
|
|
9522
10027
|
return { kind: "unusable" };
|
|
9523
10028
|
}
|
|
9524
|
-
return { kind: "text", text: await
|
|
10029
|
+
return { kind: "text", text: await readFile4(file, "utf8") };
|
|
9525
10030
|
} catch (error) {
|
|
9526
10031
|
return error.code === "ENOENT" ? { kind: "absent" } : { kind: "unusable" };
|
|
9527
10032
|
}
|
|
@@ -9533,13 +10038,17 @@ function projectKey(folder, platform) {
|
|
|
9533
10038
|
const slashed = folder.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
9534
10039
|
return platform === "win32" ? slashed.toLowerCase() : slashed;
|
|
9535
10040
|
}
|
|
9536
|
-
function
|
|
9537
|
-
|
|
9538
|
-
|
|
9539
|
-
return void 0;
|
|
10041
|
+
function serversIn(servers) {
|
|
10042
|
+
if (typeof servers !== "object" || servers === null) {
|
|
10043
|
+
return [];
|
|
9540
10044
|
}
|
|
9541
|
-
|
|
9542
|
-
|
|
10045
|
+
return Object.entries(servers).flatMap(([name, server]) => {
|
|
10046
|
+
if (typeof server !== "object" || server === null) {
|
|
10047
|
+
return [];
|
|
10048
|
+
}
|
|
10049
|
+
const url = field(server, "url");
|
|
10050
|
+
return [{ name, url: typeof url === "string" ? url : null }];
|
|
10051
|
+
});
|
|
9543
10052
|
}
|
|
9544
10053
|
async function readClaudeServers(input) {
|
|
9545
10054
|
const directory = input.env.CLAUDE_CONFIG_DIR ?? input.home;
|
|
@@ -9547,7 +10056,7 @@ async function readClaudeServers(input) {
|
|
|
9547
10056
|
return { state: "unlocated" };
|
|
9548
10057
|
}
|
|
9549
10058
|
const servers = [];
|
|
9550
|
-
const config2 = await readText(
|
|
10059
|
+
const config2 = await readText(path12.join(directory, ".claude.json"));
|
|
9551
10060
|
if (config2.kind === "unusable") {
|
|
9552
10061
|
return { state: "unreadable" };
|
|
9553
10062
|
}
|
|
@@ -9563,29 +10072,24 @@ async function readClaudeServers(input) {
|
|
|
9563
10072
|
if (typeof projects === "object" && projects !== null) {
|
|
9564
10073
|
for (const [key, project] of Object.entries(projects)) {
|
|
9565
10074
|
if (projectKey(key, input.platform) === wanted) {
|
|
9566
|
-
const
|
|
9567
|
-
field(project, "mcpServers")
|
|
9568
|
-
|
|
9569
|
-
|
|
9570
|
-
if (found2 !== void 0) {
|
|
9571
|
-
servers.push({ scope: "local", ...found2 });
|
|
10075
|
+
for (const found of serversIn(
|
|
10076
|
+
field(project, "mcpServers")
|
|
10077
|
+
)) {
|
|
10078
|
+
servers.push({ scope: "local", ...found });
|
|
9572
10079
|
}
|
|
9573
10080
|
}
|
|
9574
10081
|
}
|
|
9575
10082
|
}
|
|
9576
|
-
const found
|
|
9577
|
-
if (found !== void 0) {
|
|
10083
|
+
for (const found of serversIn(field(parsed, "mcpServers"))) {
|
|
9578
10084
|
servers.push({ scope: "user", ...found });
|
|
9579
10085
|
}
|
|
9580
10086
|
}
|
|
9581
|
-
const shared = await readText(
|
|
10087
|
+
const shared = await readText(path12.join(input.cwd, ".mcp.json"));
|
|
9582
10088
|
if (shared.kind === "text") {
|
|
9583
10089
|
try {
|
|
9584
|
-
const found
|
|
9585
|
-
field(JSON.parse(shared.text), "mcpServers")
|
|
9586
|
-
|
|
9587
|
-
);
|
|
9588
|
-
if (found !== void 0) {
|
|
10090
|
+
for (const found of serversIn(
|
|
10091
|
+
field(JSON.parse(shared.text), "mcpServers")
|
|
10092
|
+
)) {
|
|
9589
10093
|
servers.push({ scope: "project", ...found });
|
|
9590
10094
|
}
|
|
9591
10095
|
} catch {
|
|
@@ -9599,11 +10103,11 @@ var CODEX_SERVER_SUBTABLE = /^\[mcp_servers\.(?:"[^"]+"|'[^']+'|[A-Za-z0-9_-]+)\
|
|
|
9599
10103
|
var CODEX_OTHER_HEADER = /^\[{1,2}\s*mcp_servers\b/;
|
|
9600
10104
|
var CODEX_OTHER_KEY = /^mcp_servers\b/;
|
|
9601
10105
|
async function readCodexServers(input) {
|
|
9602
|
-
const directory = input.env.CODEX_HOME ?? (input.home === null ? void 0 :
|
|
10106
|
+
const directory = input.env.CODEX_HOME ?? (input.home === null ? void 0 : path12.join(input.home, ".codex"));
|
|
9603
10107
|
if (directory === void 0) {
|
|
9604
10108
|
return { state: "unlocated" };
|
|
9605
10109
|
}
|
|
9606
|
-
const config2 = await readText(
|
|
10110
|
+
const config2 = await readText(path12.join(directory, "config.toml"));
|
|
9607
10111
|
if (config2.kind === "absent") {
|
|
9608
10112
|
return { state: "read", servers: [] };
|
|
9609
10113
|
}
|
|
@@ -9639,32 +10143,122 @@ async function readCodexServers(input) {
|
|
|
9639
10143
|
servers: [...servers].map(([name, url]) => ({ name, url }))
|
|
9640
10144
|
};
|
|
9641
10145
|
}
|
|
9642
|
-
|
|
9643
|
-
|
|
9644
|
-
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
10146
|
+
|
|
10147
|
+
// src/lib/instructions/agent-mcp-servers.ts
|
|
10148
|
+
var MAX_PROBES = 4;
|
|
10149
|
+
var SCOPE_PRECEDENCE = ["local", "project", "user"];
|
|
10150
|
+
var PLAIN_NAME = /^[A-Za-z0-9_-]{1,64}$/;
|
|
10151
|
+
function shownName(name) {
|
|
10152
|
+
return PLAIN_NAME.test(name) ? name : "(a name with unusual characters)";
|
|
10153
|
+
}
|
|
10154
|
+
function describeTarget(url) {
|
|
10155
|
+
if (url === null) {
|
|
10156
|
+
return "a local command";
|
|
10157
|
+
}
|
|
10158
|
+
try {
|
|
10159
|
+
const { host } = new URL(url);
|
|
10160
|
+
return host !== "" && isSafeArgument(host) ? host : "another address";
|
|
10161
|
+
} catch {
|
|
10162
|
+
return "another address";
|
|
10163
|
+
}
|
|
10164
|
+
}
|
|
10165
|
+
function effectiveFlags(servers) {
|
|
10166
|
+
const winners = /* @__PURE__ */ new Map();
|
|
10167
|
+
for (const server of servers) {
|
|
10168
|
+
const current = winners.get(server.name);
|
|
10169
|
+
if (server.scope !== null && (current === void 0 || SCOPE_PRECEDENCE.indexOf(server.scope) < SCOPE_PRECEDENCE.indexOf(current))) {
|
|
10170
|
+
winners.set(server.name, server.scope);
|
|
9650
10171
|
}
|
|
9651
|
-
return read2.servers.some((server) => server.name === input.name) ? "elsewhere" : "missing";
|
|
9652
10172
|
}
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
10173
|
+
return servers.map(
|
|
10174
|
+
(server) => server.scope === null || winners.get(server.name) === server.scope
|
|
10175
|
+
);
|
|
10176
|
+
}
|
|
10177
|
+
async function classifyServers(servers, context) {
|
|
10178
|
+
const flags = effectiveFlags(servers);
|
|
10179
|
+
const probed = /* @__PURE__ */ new Map();
|
|
10180
|
+
const classified = [];
|
|
10181
|
+
for (const [index, server] of servers.entries()) {
|
|
10182
|
+
const points = describeTarget(server.url);
|
|
10183
|
+
const effective = flags[index] === true;
|
|
10184
|
+
const base = {
|
|
10185
|
+
name: server.name,
|
|
10186
|
+
scope: server.scope,
|
|
10187
|
+
points,
|
|
10188
|
+
effective
|
|
10189
|
+
};
|
|
10190
|
+
if (server.url === null) {
|
|
10191
|
+
classified.push({ ...base, kind: "command" });
|
|
10192
|
+
continue;
|
|
10193
|
+
}
|
|
10194
|
+
const relation = classifyGatewayUrl(
|
|
10195
|
+
context.origin,
|
|
10196
|
+
context.projectId,
|
|
10197
|
+
server.url
|
|
10198
|
+
);
|
|
10199
|
+
if (relation !== "unfamiliar") {
|
|
10200
|
+
classified.push({ ...base, kind: relation });
|
|
10201
|
+
continue;
|
|
10202
|
+
}
|
|
10203
|
+
const url = server.url;
|
|
10204
|
+
let answer = probed.get(url);
|
|
10205
|
+
if (answer === void 0) {
|
|
10206
|
+
answer = context.probe === void 0 || probed.size >= MAX_PROBES ? Promise.resolve("unknown") : context.probe(url).catch(() => "unknown");
|
|
10207
|
+
probed.set(url, answer);
|
|
10208
|
+
}
|
|
10209
|
+
classified.push({ ...base, kind: await answer });
|
|
9656
10210
|
}
|
|
9657
|
-
|
|
9658
|
-
|
|
10211
|
+
return classified;
|
|
10212
|
+
}
|
|
10213
|
+
async function inspectServers(input) {
|
|
10214
|
+
if (input.tool === "codex") {
|
|
10215
|
+
const read2 = await readCodexServers(input);
|
|
10216
|
+
return read2.state === "read" ? {
|
|
10217
|
+
state: "read",
|
|
10218
|
+
servers: await classifyServers(
|
|
10219
|
+
read2.servers.map((server) => ({
|
|
10220
|
+
...server,
|
|
10221
|
+
scope: null
|
|
10222
|
+
})),
|
|
10223
|
+
input
|
|
10224
|
+
)
|
|
10225
|
+
} : { state: read2.state };
|
|
9659
10226
|
}
|
|
9660
|
-
|
|
10227
|
+
const read = await readClaudeServers(input);
|
|
10228
|
+
return read.state === "read" ? { state: "read", servers: await classifyServers(read.servers, input) } : { state: read.state };
|
|
10229
|
+
}
|
|
10230
|
+
function names(servers) {
|
|
10231
|
+
return [...new Set(servers.map((server) => server.name))];
|
|
10232
|
+
}
|
|
10233
|
+
function projectServersOf(servers) {
|
|
10234
|
+
return servers.filter(
|
|
10235
|
+
(server) => server.effective && server.kind === "this-project"
|
|
10236
|
+
);
|
|
10237
|
+
}
|
|
10238
|
+
function orgWideServersOf(servers) {
|
|
10239
|
+
return servers.filter(
|
|
10240
|
+
(server) => server.effective && server.kind === "org-wide"
|
|
10241
|
+
);
|
|
10242
|
+
}
|
|
10243
|
+
function registrationOf(servers, name) {
|
|
10244
|
+
const here = names(projectServersOf(servers));
|
|
10245
|
+
const sameName2 = servers.filter((server) => server.name === name);
|
|
10246
|
+
const state = here.length === 0 ? sameName2.length > 0 ? "elsewhere" : "missing" : !here.includes(name) && here.every((n) => n === LEGACY_SERVER_NAME) ? "legacy" : "registered";
|
|
10247
|
+
return {
|
|
10248
|
+
state,
|
|
10249
|
+
projectServers: here.map(shownName),
|
|
10250
|
+
orgWide: names(orgWideServersOf(servers)).map(shownName),
|
|
10251
|
+
foreignSameName: sameName2.some(
|
|
10252
|
+
(server) => server.effective && (server.kind === "foreign" || server.kind === "command" || server.kind === "unknown")
|
|
10253
|
+
)
|
|
10254
|
+
};
|
|
9661
10255
|
}
|
|
9662
10256
|
|
|
9663
10257
|
// src/lib/instructions/hook.ts
|
|
9664
10258
|
import { createHash as createHash4 } from "crypto";
|
|
9665
10259
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
9666
10260
|
import { tmpdir } from "os";
|
|
9667
|
-
import
|
|
10261
|
+
import path13 from "path";
|
|
9668
10262
|
|
|
9669
10263
|
// src/lib/instructions/identifiers.ts
|
|
9670
10264
|
var IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
@@ -9673,11 +10267,11 @@ function isIdentifier(value) {
|
|
|
9673
10267
|
}
|
|
9674
10268
|
|
|
9675
10269
|
// src/lib/instructions/hook.ts
|
|
9676
|
-
var CLAUDE_SETTINGS_RELATIVE_PATH =
|
|
10270
|
+
var CLAUDE_SETTINGS_RELATIVE_PATH = path13.join(
|
|
9677
10271
|
".claude",
|
|
9678
10272
|
"settings.local.json"
|
|
9679
10273
|
);
|
|
9680
|
-
var CODEX_HOOKS_RELATIVE_PATH =
|
|
10274
|
+
var CODEX_HOOKS_RELATIVE_PATH = path13.join(".codex", "hooks.json");
|
|
9681
10275
|
var CLAUDE_SETTINGS_POSIX_PATH = ".claude/settings.local.json";
|
|
9682
10276
|
var CODEX_HOOKS_POSIX_PATH = ".codex/hooks.json";
|
|
9683
10277
|
var HOOK_TIMEOUT_SECONDS = 15;
|
|
@@ -9694,10 +10288,10 @@ function subcommandsForEvent(event) {
|
|
|
9694
10288
|
return event === "Stop" ? STOP_SUBCOMMANDS : SESSION_START_SUBCOMMANDS;
|
|
9695
10289
|
}
|
|
9696
10290
|
async function withHookConfigLock(root, posixPath, work) {
|
|
9697
|
-
const directory =
|
|
10291
|
+
const directory = path13.join(tmpdir(), "fabric-instructions-hook-locks");
|
|
9698
10292
|
await mkdir3(directory, { recursive: true });
|
|
9699
|
-
const identity = createHash4("sha256").update(
|
|
9700
|
-
const lockPath2 =
|
|
10293
|
+
const identity = createHash4("sha256").update(path13.resolve(root)).update("\0").update(posixPath).digest("hex");
|
|
10294
|
+
const lockPath2 = path13.join(directory, `${identity}.lock`);
|
|
9701
10295
|
try {
|
|
9702
10296
|
return await withExclusiveLock(work, {
|
|
9703
10297
|
lockPath: lockPath2,
|
|
@@ -9752,7 +10346,7 @@ async function assertKeyStaysOutside(destination, configPath) {
|
|
|
9752
10346
|
}
|
|
9753
10347
|
}
|
|
9754
10348
|
async function readHookConfig(root, hookTarget) {
|
|
9755
|
-
const settingsPath =
|
|
10349
|
+
const settingsPath = path13.join(root, hookTarget.relativePath);
|
|
9756
10350
|
let existingRaw;
|
|
9757
10351
|
try {
|
|
9758
10352
|
const existing = await readFileSafely(root, hookTarget.posixPath, {
|
|
@@ -9831,7 +10425,7 @@ function pruneMatching(groups, projectId, subcommands) {
|
|
|
9831
10425
|
async function mergeCommandHook(input) {
|
|
9832
10426
|
const hookTarget = hookTargetFor(input.tool ?? "claude-code");
|
|
9833
10427
|
return withHookConfigLock(input.root, hookTarget.posixPath, async () => {
|
|
9834
|
-
const settingsPath =
|
|
10428
|
+
const settingsPath = path13.join(input.root, hookTarget.relativePath);
|
|
9835
10429
|
const { settings, existingRaw } = await readHookConfig(
|
|
9836
10430
|
input.root,
|
|
9837
10431
|
hookTarget
|
|
@@ -9878,7 +10472,7 @@ async function mergeSessionStartHook(input) {
|
|
|
9878
10472
|
async function removeCommandHook(input) {
|
|
9879
10473
|
const hookTarget = hookTargetFor(input.tool ?? "claude-code");
|
|
9880
10474
|
return withHookConfigLock(input.root, hookTarget.posixPath, async () => {
|
|
9881
|
-
const settingsPath =
|
|
10475
|
+
const settingsPath = path13.join(input.root, hookTarget.relativePath);
|
|
9882
10476
|
const { settings, existingRaw } = await readHookConfig(
|
|
9883
10477
|
input.root,
|
|
9884
10478
|
hookTarget
|
|
@@ -10072,9 +10666,9 @@ function isRecord2(value) {
|
|
|
10072
10666
|
}
|
|
10073
10667
|
|
|
10074
10668
|
// src/lib/instructions/agent-mcp.ts
|
|
10075
|
-
var CLAUDE_SERVER_NAME = "fabric";
|
|
10076
10669
|
var WRITE_TIMEOUT_MS = 2e4;
|
|
10077
10670
|
var LOGIN_TIMEOUT_MS2 = 5 * 6e4;
|
|
10671
|
+
var STATUS_TIMEOUT_MS = 5e3;
|
|
10078
10672
|
var TOOL_LABEL2 = {
|
|
10079
10673
|
"claude-code": "Claude Code",
|
|
10080
10674
|
codex: "Codex"
|
|
@@ -10083,11 +10677,23 @@ var TOOL_COMMAND = {
|
|
|
10083
10677
|
"claude-code": "claude",
|
|
10084
10678
|
codex: "codex"
|
|
10085
10679
|
};
|
|
10680
|
+
function isRegistered(result) {
|
|
10681
|
+
return result.outcome.kind === "registered" || result.outcome.kind === "replaced" || result.outcome.kind === "already";
|
|
10682
|
+
}
|
|
10086
10683
|
function mcpRegistrationComplete(results) {
|
|
10087
|
-
return results.every(
|
|
10088
|
-
|
|
10684
|
+
return results.every(isRegistered);
|
|
10685
|
+
}
|
|
10686
|
+
function mcpSetupFailed(results) {
|
|
10687
|
+
return results.some(
|
|
10688
|
+
(result) => !isRegistered(result) && result.outcome.kind !== "manual"
|
|
10089
10689
|
);
|
|
10090
10690
|
}
|
|
10691
|
+
function mcpManualSteps(results) {
|
|
10692
|
+
return results.filter((result) => result.outcome.kind === "manual").map((result) => ({
|
|
10693
|
+
tool: result.tool,
|
|
10694
|
+
registerLine: result.registerLine
|
|
10695
|
+
}));
|
|
10696
|
+
}
|
|
10091
10697
|
function mcpAuthenticationPending(results) {
|
|
10092
10698
|
return results.some(
|
|
10093
10699
|
(result) => result.login === "printed" || result.login === "not-finished"
|
|
@@ -10098,12 +10704,8 @@ function codexServerName(projectId) {
|
|
|
10098
10704
|
const suffix = projectId.toLowerCase().replace(/[^a-z0-9]+/g, "").slice(-SERVER_ID_SUFFIX_LENGTH);
|
|
10099
10705
|
return `fabric-${suffix === "" ? projectId : suffix}`;
|
|
10100
10706
|
}
|
|
10101
|
-
function serverNameFor(
|
|
10102
|
-
return
|
|
10103
|
-
}
|
|
10104
|
-
var PLAIN_NAME = /^[A-Za-z0-9_-]{1,64}$/;
|
|
10105
|
-
function shownName(name) {
|
|
10106
|
-
return PLAIN_NAME.test(name) ? name : "(a name with unusual characters)";
|
|
10707
|
+
function serverNameFor(projectId) {
|
|
10708
|
+
return codexServerName(projectId);
|
|
10107
10709
|
}
|
|
10108
10710
|
function registerArguments(tool, name, url) {
|
|
10109
10711
|
return tool === "codex" ? ["mcp", "add", name, "--url", url] : ["mcp", "add", "--scope", "local", "--transport", "http", name, url];
|
|
@@ -10129,19 +10731,26 @@ async function agentRegistrationFacts(input) {
|
|
|
10129
10731
|
if (hooks.state !== "ok" || hooks.commands.length === 0) {
|
|
10130
10732
|
continue;
|
|
10131
10733
|
}
|
|
10132
|
-
const name = serverNameFor(
|
|
10734
|
+
const name = serverNameFor(input.projectId);
|
|
10735
|
+
const inspected = await inspectServers({
|
|
10736
|
+
tool,
|
|
10737
|
+
origin: input.origin,
|
|
10738
|
+
projectId: input.projectId,
|
|
10739
|
+
probe: input.probe,
|
|
10740
|
+
cwd: input.cwd,
|
|
10741
|
+
home: input.home,
|
|
10742
|
+
env: input.env,
|
|
10743
|
+
platform: input.platform
|
|
10744
|
+
});
|
|
10133
10745
|
facts.push({
|
|
10134
10746
|
tool,
|
|
10135
10747
|
name,
|
|
10136
|
-
state
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
10140
|
-
|
|
10141
|
-
|
|
10142
|
-
env: input.env,
|
|
10143
|
-
platform: input.platform
|
|
10144
|
-
}),
|
|
10748
|
+
...inspected.state === "read" ? registrationOf(inspected.servers, name) : {
|
|
10749
|
+
state: inspected.state === "unlocated" ? "missing" : "unreadable",
|
|
10750
|
+
projectServers: [],
|
|
10751
|
+
orgWide: [],
|
|
10752
|
+
foreignSameName: false
|
|
10753
|
+
},
|
|
10145
10754
|
registerLine: lineOf(
|
|
10146
10755
|
TOOL_COMMAND[tool],
|
|
10147
10756
|
registerArguments(tool, name, url)
|
|
@@ -10153,36 +10762,65 @@ async function agentRegistrationFacts(input) {
|
|
|
10153
10762
|
function succeeded(run3) {
|
|
10154
10763
|
return run3.kind === "exited" && run3.code === 0;
|
|
10155
10764
|
}
|
|
10156
|
-
function
|
|
10157
|
-
|
|
10765
|
+
function sameServerUrl(left, right) {
|
|
10766
|
+
if (left === null) {
|
|
10767
|
+
return false;
|
|
10768
|
+
}
|
|
10769
|
+
const normal = normalizeServerUrl(left);
|
|
10770
|
+
return normal === null ? left === right : normal === normalizeServerUrl(right);
|
|
10771
|
+
}
|
|
10772
|
+
function surroundingsOf(servers) {
|
|
10773
|
+
const orgWide = orgWideServersOf(servers)[0];
|
|
10774
|
+
const unverified = servers.filter((server) => server.effective && server.kind === "unknown").map((server) => ({
|
|
10775
|
+
name: shownName(server.name),
|
|
10776
|
+
points: server.points
|
|
10777
|
+
}));
|
|
10778
|
+
return {
|
|
10779
|
+
...orgWide === void 0 ? {} : { orgWide: shownName(orgWide.name) },
|
|
10780
|
+
unverified
|
|
10781
|
+
};
|
|
10782
|
+
}
|
|
10783
|
+
function foundUnder(here, name) {
|
|
10784
|
+
return (here.find((server) => server.name === name) ?? here.find((server) => server.name === LEGACY_SERVER_NAME) ?? here[0])?.name;
|
|
10158
10785
|
}
|
|
10159
10786
|
async function registerWithClaude(run3, context) {
|
|
10160
|
-
const name =
|
|
10161
|
-
const
|
|
10162
|
-
|
|
10787
|
+
const name = serverNameFor(context.projectId);
|
|
10788
|
+
const inspected = await inspectServers({
|
|
10789
|
+
tool: "claude-code",
|
|
10790
|
+
origin: context.origin,
|
|
10791
|
+
projectId: context.projectId,
|
|
10792
|
+
probe: context.probe,
|
|
10163
10793
|
cwd: context.cwd,
|
|
10164
10794
|
home: context.home,
|
|
10165
10795
|
env: context.env,
|
|
10166
10796
|
platform: context.platform
|
|
10167
10797
|
});
|
|
10168
|
-
if (
|
|
10798
|
+
if (inspected.state !== "read") {
|
|
10169
10799
|
return { outcome: { kind: "failed" }, name };
|
|
10170
10800
|
}
|
|
10171
|
-
const
|
|
10172
|
-
|
|
10173
|
-
|
|
10801
|
+
const servers = inspected.servers;
|
|
10802
|
+
const around = surroundingsOf(servers);
|
|
10803
|
+
const alreadyAs = foundUnder(projectServersOf(servers), name);
|
|
10804
|
+
if (alreadyAs !== void 0) {
|
|
10805
|
+
return { outcome: { kind: "already" }, name: alreadyAs, ...around };
|
|
10174
10806
|
}
|
|
10175
|
-
|
|
10176
|
-
|
|
10807
|
+
const local = servers.find(
|
|
10808
|
+
(server) => server.scope === "local" && server.name === name
|
|
10809
|
+
);
|
|
10810
|
+
if (local !== void 0 && local.kind !== "other-project") {
|
|
10811
|
+
return {
|
|
10812
|
+
outcome: { kind: "left", scope: "local", points: local.points },
|
|
10813
|
+
name,
|
|
10814
|
+
...around
|
|
10815
|
+
};
|
|
10177
10816
|
}
|
|
10178
|
-
const
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
}
|
|
10817
|
+
const stale = servers.filter(
|
|
10818
|
+
(server) => server.scope === "local" && server.kind === "other-project" && (server.name === name || server.name === LEGACY_SERVER_NAME)
|
|
10819
|
+
).map((server) => server.name);
|
|
10820
|
+
for (const staleName of stale) {
|
|
10183
10821
|
const removed = await run3(
|
|
10184
10822
|
"claude",
|
|
10185
|
-
["mcp", "remove",
|
|
10823
|
+
["mcp", "remove", staleName, "--scope", "local"],
|
|
10186
10824
|
{ cwd: context.cwd, timeoutMs: WRITE_TIMEOUT_MS }
|
|
10187
10825
|
);
|
|
10188
10826
|
if (removed.kind === "missing") {
|
|
@@ -10200,33 +10838,45 @@ async function registerWithClaude(run3, context) {
|
|
|
10200
10838
|
if (added.kind === "missing") {
|
|
10201
10839
|
return { outcome: { kind: "skipped" }, name };
|
|
10202
10840
|
}
|
|
10203
|
-
return {
|
|
10204
|
-
outcome:
|
|
10205
|
-
name
|
|
10206
|
-
|
|
10841
|
+
return succeeded(added) ? {
|
|
10842
|
+
outcome: { kind: stale.length > 0 ? "replaced" : "registered" },
|
|
10843
|
+
name,
|
|
10844
|
+
...around,
|
|
10845
|
+
shadowed: servers.flatMap(
|
|
10846
|
+
(server) => server.name === name && (server.scope === "user" || server.scope === "project") ? [{ scope: server.scope, points: server.points }] : []
|
|
10847
|
+
)
|
|
10848
|
+
} : { outcome: { kind: "failed" }, name };
|
|
10207
10849
|
}
|
|
10208
10850
|
async function registerWithCodex(run3, context, interactive) {
|
|
10209
|
-
const
|
|
10851
|
+
const inspected = await inspectServers({
|
|
10852
|
+
tool: "codex",
|
|
10853
|
+
origin: context.origin,
|
|
10854
|
+
projectId: context.projectId,
|
|
10855
|
+
probe: context.probe,
|
|
10856
|
+
cwd: context.cwd,
|
|
10210
10857
|
home: context.home,
|
|
10211
|
-
env: context.env
|
|
10858
|
+
env: context.env,
|
|
10859
|
+
platform: context.platform
|
|
10212
10860
|
});
|
|
10213
|
-
if (
|
|
10861
|
+
if (inspected.state !== "read") {
|
|
10214
10862
|
return { outcome: { kind: "failed" }, name: context.name };
|
|
10215
10863
|
}
|
|
10216
|
-
const servers =
|
|
10217
|
-
const
|
|
10218
|
-
|
|
10219
|
-
|
|
10864
|
+
const servers = inspected.servers;
|
|
10865
|
+
const around = surroundingsOf(servers);
|
|
10866
|
+
const alreadyAs = foundUnder(projectServersOf(servers), context.name);
|
|
10867
|
+
if (alreadyAs !== void 0) {
|
|
10868
|
+
return { outcome: { kind: "already" }, name: alreadyAs, ...around };
|
|
10220
10869
|
}
|
|
10221
10870
|
const sameName2 = servers.find((server) => server.name === context.name);
|
|
10222
|
-
if (sameName2 !== void 0 &&
|
|
10871
|
+
if (sameName2 !== void 0 && sameName2.kind !== "other-project") {
|
|
10223
10872
|
return {
|
|
10224
|
-
outcome: { kind: "left",
|
|
10225
|
-
name: context.name
|
|
10873
|
+
outcome: { kind: "left", scope: null, points: sameName2.points },
|
|
10874
|
+
name: context.name,
|
|
10875
|
+
...around
|
|
10226
10876
|
};
|
|
10227
10877
|
}
|
|
10228
10878
|
if (!interactive) {
|
|
10229
|
-
return { outcome: { kind: "manual" }, name: context.name };
|
|
10879
|
+
return { outcome: { kind: "manual" }, name: context.name, ...around };
|
|
10230
10880
|
}
|
|
10231
10881
|
const written = sameName2 === void 0 ? "registered" : "replaced";
|
|
10232
10882
|
const added = await run3(
|
|
@@ -10241,7 +10891,8 @@ async function registerWithCodex(run3, context, interactive) {
|
|
|
10241
10891
|
return {
|
|
10242
10892
|
outcome: { kind: written },
|
|
10243
10893
|
name: context.name,
|
|
10244
|
-
login: "completed"
|
|
10894
|
+
login: "completed",
|
|
10895
|
+
...around
|
|
10245
10896
|
};
|
|
10246
10897
|
}
|
|
10247
10898
|
const after = await readCodexServers({
|
|
@@ -10249,27 +10900,30 @@ async function registerWithCodex(run3, context, interactive) {
|
|
|
10249
10900
|
env: context.env
|
|
10250
10901
|
});
|
|
10251
10902
|
const registered = after.state === "read" && after.servers.some(
|
|
10252
|
-
(server) => server.name === context.name && server.url
|
|
10903
|
+
(server) => server.name === context.name && sameServerUrl(server.url, context.url)
|
|
10253
10904
|
);
|
|
10254
10905
|
return registered ? {
|
|
10255
10906
|
outcome: { kind: written },
|
|
10256
10907
|
name: context.name,
|
|
10257
|
-
login: "not-finished"
|
|
10908
|
+
login: "not-finished",
|
|
10909
|
+
...around
|
|
10258
10910
|
} : { outcome: { kind: "failed" }, name: context.name };
|
|
10259
10911
|
}
|
|
10260
10912
|
async function registerAgentMcp(input) {
|
|
10261
10913
|
const url = projectResource(input.origin, "mcp", input.projectId);
|
|
10262
10914
|
const where = {
|
|
10915
|
+
projectId: input.projectId,
|
|
10263
10916
|
cwd: input.cwd,
|
|
10264
10917
|
origin: input.origin,
|
|
10265
10918
|
url,
|
|
10266
10919
|
home: input.home,
|
|
10267
10920
|
env: input.env,
|
|
10268
|
-
platform: input.platform
|
|
10921
|
+
platform: input.platform,
|
|
10922
|
+
probe: input.probe
|
|
10269
10923
|
};
|
|
10270
10924
|
const results = [];
|
|
10271
10925
|
for (const tool of input.tools) {
|
|
10272
|
-
const intendedName = serverNameFor(
|
|
10926
|
+
const intendedName = serverNameFor(input.projectId);
|
|
10273
10927
|
const command = TOOL_COMMAND[tool];
|
|
10274
10928
|
const registration = tool === "codex" ? await registerWithCodex(
|
|
10275
10929
|
input.run,
|
|
@@ -10283,6 +10937,11 @@ async function registerAgentMcp(input) {
|
|
|
10283
10937
|
expectedName: intendedName,
|
|
10284
10938
|
url,
|
|
10285
10939
|
outcome: registration.outcome,
|
|
10940
|
+
shadowed: registration.shadowed ?? [],
|
|
10941
|
+
orgWide: registration.orgWide ?? null,
|
|
10942
|
+
unverified: registration.unverified ?? [],
|
|
10943
|
+
status: "unchecked",
|
|
10944
|
+
host: describeTarget(url),
|
|
10286
10945
|
login: registration.login ?? (registration.outcome.kind === "already" && loginLine !== null ? "hint" : "unneeded"),
|
|
10287
10946
|
registerLine: lineOf(
|
|
10288
10947
|
command,
|
|
@@ -10291,6 +10950,25 @@ async function registerAgentMcp(input) {
|
|
|
10291
10950
|
loginLine
|
|
10292
10951
|
});
|
|
10293
10952
|
}
|
|
10953
|
+
for (const result of results) {
|
|
10954
|
+
if (!isRegistered(result)) {
|
|
10955
|
+
continue;
|
|
10956
|
+
}
|
|
10957
|
+
if (result.tool === "codex") {
|
|
10958
|
+
result.status = "unavailable";
|
|
10959
|
+
} else if (input.checkSignIn !== false) {
|
|
10960
|
+
result.status = await claudeStatus(
|
|
10961
|
+
input.run,
|
|
10962
|
+
result.name,
|
|
10963
|
+
input.cwd
|
|
10964
|
+
);
|
|
10965
|
+
}
|
|
10966
|
+
if (result.status === "connected") {
|
|
10967
|
+
result.login = "signed-in";
|
|
10968
|
+
} else if (result.status === "needs-sign-in" && result.outcome.kind === "already" && result.loginLine !== null) {
|
|
10969
|
+
result.login = "printed";
|
|
10970
|
+
}
|
|
10971
|
+
}
|
|
10294
10972
|
for (const result of results) {
|
|
10295
10973
|
if (result.login !== "unneeded" || result.outcome.kind !== "registered" && result.outcome.kind !== "replaced") {
|
|
10296
10974
|
continue;
|
|
@@ -10312,6 +10990,24 @@ async function registerAgentMcp(input) {
|
|
|
10312
10990
|
}
|
|
10313
10991
|
return results;
|
|
10314
10992
|
}
|
|
10993
|
+
var STATUS_LINE = /^\s*Status:\s*(?:[^\sA-Za-z0-9]+\s+)?(.+?)\s*$/m;
|
|
10994
|
+
async function claudeStatus(run3, name, cwd) {
|
|
10995
|
+
const answer = await run3("claude", ["mcp", "get", name], {
|
|
10996
|
+
cwd,
|
|
10997
|
+
timeoutMs: STATUS_TIMEOUT_MS
|
|
10998
|
+
});
|
|
10999
|
+
if (answer.kind !== "exited" || answer.code !== 0) {
|
|
11000
|
+
return "unavailable";
|
|
11001
|
+
}
|
|
11002
|
+
const status = STATUS_LINE.exec(answer.stdout)?.[1]?.toLowerCase() ?? "";
|
|
11003
|
+
if (status === "connected") {
|
|
11004
|
+
return "connected";
|
|
11005
|
+
}
|
|
11006
|
+
if (status === "needs authentication") {
|
|
11007
|
+
return "needs-sign-in";
|
|
11008
|
+
}
|
|
11009
|
+
return /^(?:failed|disconnected|not connected|error)\b/.test(status) ? "unreachable" : "unavailable";
|
|
11010
|
+
}
|
|
10315
11011
|
function agentMcpLines(results) {
|
|
10316
11012
|
const lines2 = [];
|
|
10317
11013
|
for (const result of results) {
|
|
@@ -10335,7 +11031,7 @@ function agentMcpLines(results) {
|
|
|
10335
11031
|
break;
|
|
10336
11032
|
case "left":
|
|
10337
11033
|
lines2.push(
|
|
10338
|
-
|
|
11034
|
+
`${label} already has a server named "${result.name}" ${outcome.scope === "local" ? "in this checkout's local settings" : "in its settings"} that points at ${outcome.points}, not at this project's gateway, so it was left alone. Remove it, then ${byHand(result.registerLine)}`
|
|
10339
11035
|
);
|
|
10340
11036
|
break;
|
|
10341
11037
|
case "skipped":
|
|
@@ -10358,6 +11054,21 @@ function agentMcpLines(results) {
|
|
|
10358
11054
|
return unreachable;
|
|
10359
11055
|
}
|
|
10360
11056
|
}
|
|
11057
|
+
if (result.orgWide !== null && isRegistered(result)) {
|
|
11058
|
+
lines2.push(
|
|
11059
|
+
`Your organization-wide Fabric server "${result.orgWide}" stays as it is; coding instructions use the project server "${result.name}".`
|
|
11060
|
+
);
|
|
11061
|
+
}
|
|
11062
|
+
for (const server of result.unverified) {
|
|
11063
|
+
lines2.push(
|
|
11064
|
+
`${label} has a server "${server.name}" at ${server.points} that could not be told apart from Fabric's, so it was left as it is.`
|
|
11065
|
+
);
|
|
11066
|
+
}
|
|
11067
|
+
for (const server of result.shadowed) {
|
|
11068
|
+
lines2.push(
|
|
11069
|
+
`${label} also has a "${result.name}" server in your ${server.scope} settings (${server.points}); in this checkout the project's gateway takes precedence.`
|
|
11070
|
+
);
|
|
11071
|
+
}
|
|
10361
11072
|
if (result.login === "printed") {
|
|
10362
11073
|
lines2.push(
|
|
10363
11074
|
result.loginLine === null ? `To finish, sign ${label} in to the server with its own mcp login command.` : `To finish, sign ${label} in to it: ${result.loginLine}`
|
|
@@ -10376,14 +11087,42 @@ function agentMcpLines(results) {
|
|
|
10376
11087
|
}
|
|
10377
11088
|
return lines2;
|
|
10378
11089
|
}
|
|
11090
|
+
function agentMcpSummaryLines(results) {
|
|
11091
|
+
return results.map((result) => {
|
|
11092
|
+
const label = TOOL_LABEL2[result.tool];
|
|
11093
|
+
const outcome = result.outcome;
|
|
11094
|
+
if (!isRegistered(result)) {
|
|
11095
|
+
return outcome.kind === "manual" ? `${label}: not registered yet, see above.` : `${label}: nothing usable for this project, see above.`;
|
|
11096
|
+
}
|
|
11097
|
+
const server = `"${result.name}"`;
|
|
11098
|
+
if (result.login === "completed" || result.login === "signed-in") {
|
|
11099
|
+
return `${label}: ${server} connected.`;
|
|
11100
|
+
}
|
|
11101
|
+
switch (result.status) {
|
|
11102
|
+
case "connected":
|
|
11103
|
+
return `${label}: ${server} connected.`;
|
|
11104
|
+
case "needs-sign-in":
|
|
11105
|
+
return `${label}: ${server} registered, needs sign-in.`;
|
|
11106
|
+
case "unreachable":
|
|
11107
|
+
return `${label}: ${server} registered, unreachable (${result.host}).`;
|
|
11108
|
+
case "unavailable":
|
|
11109
|
+
return result.tool === "codex" ? `${label}: ${server} registered; sign-in status not available for Codex.` : `${label}: ${server} registered; its status could not be read.`;
|
|
11110
|
+
case "unchecked":
|
|
11111
|
+
return `${label}: ${server} registered.`;
|
|
11112
|
+
default: {
|
|
11113
|
+
const unreachable = result.status;
|
|
11114
|
+
return unreachable;
|
|
11115
|
+
}
|
|
11116
|
+
}
|
|
11117
|
+
});
|
|
11118
|
+
}
|
|
10379
11119
|
|
|
10380
11120
|
// src/lib/instructions/agent-run.ts
|
|
10381
11121
|
import { spawn as spawn3 } from "child_process";
|
|
10382
|
-
import path13 from "path";
|
|
10383
11122
|
|
|
10384
11123
|
// src/lib/instructions/path-lookup.ts
|
|
10385
11124
|
import { stat as stat3 } from "fs/promises";
|
|
10386
|
-
import
|
|
11125
|
+
import path14 from "path";
|
|
10387
11126
|
var MAX_PATH_ENTRIES = 256;
|
|
10388
11127
|
var MAX_PATHEXT_ENTRIES = 32;
|
|
10389
11128
|
var DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD";
|
|
@@ -10397,7 +11136,7 @@ function readVariable(lookup, name) {
|
|
|
10397
11136
|
return key === void 0 ? void 0 : lookup.env[key];
|
|
10398
11137
|
}
|
|
10399
11138
|
function pathApi(platform) {
|
|
10400
|
-
return platform === "win32" ?
|
|
11139
|
+
return platform === "win32" ? path14.win32 : path14.posix;
|
|
10401
11140
|
}
|
|
10402
11141
|
function searchDirectories(lookup) {
|
|
10403
11142
|
const api = pathApi(lookup.platform);
|
|
@@ -10489,10 +11228,6 @@ function launch(file, args, platform, env2) {
|
|
|
10489
11228
|
}
|
|
10490
11229
|
return { file, args: [...args], verbatim: false };
|
|
10491
11230
|
}
|
|
10492
|
-
function taskkillPath(env2 = process.env) {
|
|
10493
|
-
const root = env2.SystemRoot ?? env2.SYSTEMROOT ?? env2.windir ?? "C:\\Windows";
|
|
10494
|
-
return path13.win32.join(root, "System32", "taskkill.exe");
|
|
10495
|
-
}
|
|
10496
11231
|
function stop(child, signal) {
|
|
10497
11232
|
if (process.platform === "win32" && child.pid !== void 0) {
|
|
10498
11233
|
try {
|
|
@@ -10597,8 +11332,8 @@ import { createHash as createHash5 } from "crypto";
|
|
|
10597
11332
|
import { chmod } from "fs/promises";
|
|
10598
11333
|
|
|
10599
11334
|
// src/lib/instructions/lock.ts
|
|
10600
|
-
import { readFile as
|
|
10601
|
-
import
|
|
11335
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
11336
|
+
import path15 from "path";
|
|
10602
11337
|
var LOCK_DIRECTORY = ".fabric";
|
|
10603
11338
|
var LOCK_FILENAME = "instructions.lock";
|
|
10604
11339
|
var LOCK_VERSION = 3;
|
|
@@ -10606,13 +11341,13 @@ var READABLE_LOCK_VERSIONS = [1, 2, LOCK_VERSION];
|
|
|
10606
11341
|
var LOCK_RELATIVE_PATH = `${LOCK_DIRECTORY}/${LOCK_FILENAME}`;
|
|
10607
11342
|
var SHA256_HEX2 = /^[0-9a-f]{64}$/;
|
|
10608
11343
|
function lockPath(destination) {
|
|
10609
|
-
return
|
|
11344
|
+
return path15.join(destination, LOCK_DIRECTORY, LOCK_FILENAME);
|
|
10610
11345
|
}
|
|
10611
11346
|
async function readLock(destination) {
|
|
10612
11347
|
const file = lockPath(destination);
|
|
10613
11348
|
let raw;
|
|
10614
11349
|
try {
|
|
10615
|
-
raw = await
|
|
11350
|
+
raw = await readFile5(file, "utf8");
|
|
10616
11351
|
} catch (error) {
|
|
10617
11352
|
if (error.code === "ENOENT") {
|
|
10618
11353
|
return null;
|
|
@@ -11214,11 +11949,11 @@ function extractBundle(archive, wanted) {
|
|
|
11214
11949
|
}
|
|
11215
11950
|
|
|
11216
11951
|
// src/lib/instructions/clone-target.ts
|
|
11217
|
-
import { lstat as
|
|
11218
|
-
import
|
|
11952
|
+
import { lstat as lstat6 } from "fs/promises";
|
|
11953
|
+
import path16 from "path";
|
|
11219
11954
|
async function resolveCloneFolder(base, folder) {
|
|
11220
|
-
const target =
|
|
11221
|
-
const stats = await
|
|
11955
|
+
const target = path16.resolve(base, folder);
|
|
11956
|
+
const stats = await lstat6(target).catch((error) => {
|
|
11222
11957
|
if (error.code === "ENOENT") {
|
|
11223
11958
|
return null;
|
|
11224
11959
|
}
|
|
@@ -11236,7 +11971,7 @@ async function resolveCloneFolder(base, folder) {
|
|
|
11236
11971
|
// src/lib/instructions/doctor.ts
|
|
11237
11972
|
import { createHash as createHash9 } from "crypto";
|
|
11238
11973
|
import { stat as stat4 } from "fs/promises";
|
|
11239
|
-
import
|
|
11974
|
+
import path18 from "path";
|
|
11240
11975
|
|
|
11241
11976
|
// src/lib/instructions/file-downloads.ts
|
|
11242
11977
|
import { createHash as createHash6 } from "crypto";
|
|
@@ -11583,11 +12318,11 @@ function isTooLarge(error) {
|
|
|
11583
12318
|
import { createHash as createHash8 } from "crypto";
|
|
11584
12319
|
import { mkdtemp, realpath as realpath3, rm } from "fs/promises";
|
|
11585
12320
|
import { tmpdir as tmpdir2 } from "os";
|
|
11586
|
-
import
|
|
12321
|
+
import path17 from "path";
|
|
11587
12322
|
var IN_MEMORY_SNAPSHOT_BYTES = 50 * 1024 * 1024;
|
|
11588
12323
|
async function stageFilesByUrl(options) {
|
|
11589
12324
|
const root = await realpath3(
|
|
11590
|
-
await mkdtemp(
|
|
12325
|
+
await mkdtemp(path17.join(tmpdir2(), "fabric-instructions-"))
|
|
11591
12326
|
);
|
|
11592
12327
|
const files = /* @__PURE__ */ new Map();
|
|
11593
12328
|
const dispose = () => rm(root, { recursive: true, force: true });
|
|
@@ -11633,11 +12368,12 @@ var MAX_LOCK_BYTES = 16 * 1024 * 1024;
|
|
|
11633
12368
|
var MAX_DOWNLOAD_BYTES = 52428800 + 1048576 + 5e3 * (512 + 2 * 1024);
|
|
11634
12369
|
var PROBE_TIMEOUT_MS = 5e3;
|
|
11635
12370
|
var PROBE_BUDGET_MS = 15e3;
|
|
11636
|
-
var
|
|
12371
|
+
var MAX_PROBES2 = 20;
|
|
11637
12372
|
var PROBE_CONCURRENCY = 4;
|
|
11638
12373
|
var CLI_INSTALL_COMMAND = "npm install -g @fabricorg/cli";
|
|
11639
12374
|
var GIT_BUDGET_MS = 1e4;
|
|
11640
12375
|
var LOCK_NOT_USED = "not used in a checkout of the repository";
|
|
12376
|
+
var LOCK_NOT_USED_DIRECT = "not used: this project reads its repository directly, so Fabric keeps no copy here";
|
|
11641
12377
|
var LOCK_NOT_HERE = "not used here: this folder is not a checkout of the project's repository";
|
|
11642
12378
|
var SKIP_AFTER_AUTH = "not evaluated: the API key check failed";
|
|
11643
12379
|
var SKIP_AFTER_ACCESS = "not evaluated: the project could not be read";
|
|
@@ -11755,9 +12491,9 @@ function buildFabricCommand(args, platform = process.platform) {
|
|
|
11755
12491
|
function sha256Hex(bytes) {
|
|
11756
12492
|
return createHash9("sha256").update(bytes).digest("hex");
|
|
11757
12493
|
}
|
|
11758
|
-
function listNames(
|
|
11759
|
-
const shown2 =
|
|
11760
|
-
const rest =
|
|
12494
|
+
function listNames(names2) {
|
|
12495
|
+
const shown2 = names2.slice(0, MAX_NAMES_IN_FIX);
|
|
12496
|
+
const rest = names2.length - shown2.length;
|
|
11761
12497
|
return rest > 0 ? `${shown2.join(", ")} and ${rest} more` : shown2.join(", ");
|
|
11762
12498
|
}
|
|
11763
12499
|
function capItems(items, status) {
|
|
@@ -11963,13 +12699,14 @@ async function resolveCheckout(input, access) {
|
|
|
11963
12699
|
}
|
|
11964
12700
|
const { repository, snapshot } = access.published;
|
|
11965
12701
|
try {
|
|
11966
|
-
|
|
12702
|
+
const report = input.inspectCheckout ? await input.inspectCheckout({ repository, snapshot }) : await inspectCheckout({
|
|
11967
12703
|
destination: input.root,
|
|
11968
12704
|
repository,
|
|
11969
12705
|
snapshot,
|
|
11970
12706
|
deadline: Date.now() + GIT_BUDGET_MS,
|
|
11971
12707
|
remote: input.remote
|
|
11972
12708
|
});
|
|
12709
|
+
return await withDirectContains(report, access.published);
|
|
11973
12710
|
} catch {
|
|
11974
12711
|
return reportFor(repository, {
|
|
11975
12712
|
class: "unknown",
|
|
@@ -11977,6 +12714,20 @@ async function resolveCheckout(input, access) {
|
|
|
11977
12714
|
});
|
|
11978
12715
|
}
|
|
11979
12716
|
}
|
|
12717
|
+
async function withDirectContains(report, published) {
|
|
12718
|
+
const direct = published.direct;
|
|
12719
|
+
const head = report.state?.head;
|
|
12720
|
+
if (report.classification.class !== "matching" || report.contains !== void 0 || direct?.availability !== "READY" || head === null || head === void 0) {
|
|
12721
|
+
return report;
|
|
12722
|
+
}
|
|
12723
|
+
const answer = await isAncestor(
|
|
12724
|
+
report.classification.toplevel,
|
|
12725
|
+
direct.currentCommitSha,
|
|
12726
|
+
head,
|
|
12727
|
+
Date.now() + GIT_BUDGET_MS
|
|
12728
|
+
);
|
|
12729
|
+
return { ...report, contains: answer.kind === "ok" ? answer.value : null };
|
|
12730
|
+
}
|
|
11980
12731
|
async function checkAuth(input, client) {
|
|
11981
12732
|
const id = "auth";
|
|
11982
12733
|
const login = loginCommand2(input);
|
|
@@ -12197,6 +12948,28 @@ function checkPublished(access) {
|
|
|
12197
12948
|
}
|
|
12198
12949
|
const answer = access.published;
|
|
12199
12950
|
const repository = answer.sourceOfTruth === "REPOSITORY";
|
|
12951
|
+
const direct = answer.direct;
|
|
12952
|
+
if (repository && answer.repository && direct?.availability === "READY") {
|
|
12953
|
+
const where = sanitizeDisplayText(
|
|
12954
|
+
`${answer.repository.host}/${answer.repository.path}`,
|
|
12955
|
+
200
|
|
12956
|
+
);
|
|
12957
|
+
const commit = sanitizeDisplayText(direct.currentCommitSha, 40);
|
|
12958
|
+
return [
|
|
12959
|
+
makeCheck(
|
|
12960
|
+
id,
|
|
12961
|
+
"server",
|
|
12962
|
+
"pass",
|
|
12963
|
+
`read directly from ${where} at ${commit.slice(0, 12)}\u2026 on ${sanitizeDisplayText(answer.repository.ref, 200)}; Fabric copies nothing`,
|
|
12964
|
+
{ repository: answer.repository }
|
|
12965
|
+
),
|
|
12966
|
+
{
|
|
12967
|
+
kind: "direct",
|
|
12968
|
+
commitSha: direct.currentCommitSha,
|
|
12969
|
+
ref: answer.repository.ref
|
|
12970
|
+
}
|
|
12971
|
+
];
|
|
12972
|
+
}
|
|
12200
12973
|
const repositoryConfig = answer.repository ?? null;
|
|
12201
12974
|
if (answer.published !== true) {
|
|
12202
12975
|
return [
|
|
@@ -12317,7 +13090,12 @@ function checkCheckout(access, snapshot, checkout) {
|
|
|
12317
13090
|
if (snapshot.kind === "unpublished") {
|
|
12318
13091
|
return makeCheck(id, "machine", "skip", NOTHING_PUBLISHED);
|
|
12319
13092
|
}
|
|
12320
|
-
const source = snapshot.
|
|
13093
|
+
const source = snapshot.kind === "direct" ? {
|
|
13094
|
+
kind: "REPOSITORY",
|
|
13095
|
+
ref: snapshot.ref,
|
|
13096
|
+
commitSha: snapshot.commitSha,
|
|
13097
|
+
current: true
|
|
13098
|
+
} : snapshot.snapshot.source;
|
|
12321
13099
|
const repository = access.kind === "ok" ? access.published.repository ?? null : null;
|
|
12322
13100
|
if (source === void 0 || source.kind !== "REPOSITORY" || repository === null) {
|
|
12323
13101
|
return makeCheck(id, "machine", "skip", PUBLISHED_NOT_SYNCED);
|
|
@@ -12368,6 +13146,12 @@ async function checkLock(input, commands, snapshot, checkout) {
|
|
|
12368
13146
|
notEvaluated
|
|
12369
13147
|
];
|
|
12370
13148
|
}
|
|
13149
|
+
if (snapshot.kind === "direct") {
|
|
13150
|
+
return [
|
|
13151
|
+
makeCheck(id, "machine", "skip", LOCK_NOT_USED_DIRECT),
|
|
13152
|
+
notEvaluated
|
|
13153
|
+
];
|
|
13154
|
+
}
|
|
12371
13155
|
const read = await readLockBounded(input.root);
|
|
12372
13156
|
if (read.kind === "unreadable") {
|
|
12373
13157
|
return [
|
|
@@ -12516,6 +13300,9 @@ async function checkDrift(input, commands, snapshot, lockState, checkout) {
|
|
|
12516
13300
|
if (snapshot.kind === "unpublished") {
|
|
12517
13301
|
return makeCheck(id, "machine", "skip", NOTHING_PUBLISHED);
|
|
12518
13302
|
}
|
|
13303
|
+
if (snapshot.kind === "direct") {
|
|
13304
|
+
return makeCheck(id, "machine", "skip", LOCK_NOT_USED_DIRECT);
|
|
13305
|
+
}
|
|
12519
13306
|
if (checkout !== null && checkoutClass(checkout) !== "upload") {
|
|
12520
13307
|
return checkoutDrift(snapshot.snapshot, checkout);
|
|
12521
13308
|
}
|
|
@@ -13223,41 +14010,86 @@ var AGENT_TITLE = {
|
|
|
13223
14010
|
codex: "Codex"
|
|
13224
14011
|
};
|
|
13225
14012
|
function agentRegistrationItems(input) {
|
|
13226
|
-
return (input.agentMcp ?? []).
|
|
13227
|
-
|
|
13228
|
-
|
|
13229
|
-
|
|
13230
|
-
|
|
13231
|
-
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
|
|
13237
|
-
|
|
13238
|
-
|
|
13239
|
-
|
|
13240
|
-
|
|
13241
|
-
|
|
13242
|
-
|
|
13243
|
-
|
|
13244
|
-
|
|
13245
|
-
|
|
13246
|
-
|
|
13247
|
-
|
|
13248
|
-
|
|
13249
|
-
|
|
13250
|
-
|
|
13251
|
-
|
|
13252
|
-
|
|
13253
|
-
|
|
13254
|
-
|
|
13255
|
-
|
|
13256
|
-
}
|
|
14013
|
+
return (input.agentMcp ?? []).flatMap((fact) => [
|
|
14014
|
+
registrationItem(fact),
|
|
14015
|
+
...registrationNotes(fact)
|
|
14016
|
+
]);
|
|
14017
|
+
}
|
|
14018
|
+
function registrationNotes(fact) {
|
|
14019
|
+
const title = AGENT_TITLE[fact.tool];
|
|
14020
|
+
const notes = [];
|
|
14021
|
+
if (fact.orgWide.length > 0) {
|
|
14022
|
+
notes.push({
|
|
14023
|
+
name: `Organization-wide Fabric server in ${title}`,
|
|
14024
|
+
status: "skip",
|
|
14025
|
+
detail: `present as ${listNames(fact.orgWide.map((name) => `"${name}"`))}; left as it is, coding instructions use the project server`
|
|
14026
|
+
});
|
|
14027
|
+
}
|
|
14028
|
+
if (fact.foreignSameName && (fact.state === "registered" || fact.state === "legacy")) {
|
|
14029
|
+
notes.push({
|
|
14030
|
+
name: `Server named "${fact.name}" in ${title}`,
|
|
14031
|
+
status: "skip",
|
|
14032
|
+
detail: "not Fabric's; left as it is"
|
|
14033
|
+
});
|
|
14034
|
+
}
|
|
14035
|
+
return notes;
|
|
14036
|
+
}
|
|
14037
|
+
function registrationItem(fact) {
|
|
14038
|
+
const name = `Fabric server in ${AGENT_TITLE[fact.tool]}`;
|
|
14039
|
+
if (fact.projectServers.length > 1) {
|
|
14040
|
+
return {
|
|
14041
|
+
name,
|
|
14042
|
+
status: "warn",
|
|
14043
|
+
detail: `this project's gateway is registered under ${fact.projectServers.length} names (${fact.projectServers.map((server) => `"${server}"`).join(", ")}); one is enough`
|
|
14044
|
+
};
|
|
14045
|
+
}
|
|
14046
|
+
switch (fact.state) {
|
|
14047
|
+
case "registered":
|
|
14048
|
+
return {
|
|
14049
|
+
name,
|
|
14050
|
+
status: "pass",
|
|
14051
|
+
detail: "registered at this project's gateway"
|
|
14052
|
+
};
|
|
14053
|
+
case "legacy":
|
|
14054
|
+
return {
|
|
14055
|
+
name,
|
|
14056
|
+
status: "warn",
|
|
14057
|
+
detail: `registered at this project's gateway under the older name "fabric", which still works; init now registers "${fact.name}"`
|
|
14058
|
+
};
|
|
14059
|
+
case "elsewhere":
|
|
14060
|
+
return {
|
|
14061
|
+
name,
|
|
14062
|
+
status: "warn",
|
|
14063
|
+
detail: `"${fact.name}" is registered, but not at this project's gateway`
|
|
14064
|
+
};
|
|
14065
|
+
case "missing":
|
|
14066
|
+
return {
|
|
14067
|
+
name,
|
|
14068
|
+
status: "skip",
|
|
14069
|
+
detail: "not registered; init registers it, unless it was run with --no-mcp or, for Codex, with no terminal to sign in at"
|
|
14070
|
+
};
|
|
14071
|
+
case "unreadable":
|
|
14072
|
+
return {
|
|
14073
|
+
name,
|
|
14074
|
+
status: "skip",
|
|
14075
|
+
detail: "its configuration could not be read"
|
|
14076
|
+
};
|
|
14077
|
+
default: {
|
|
14078
|
+
const unreachable = fact.state;
|
|
14079
|
+
return unreachable;
|
|
13257
14080
|
}
|
|
13258
|
-
}
|
|
14081
|
+
}
|
|
13259
14082
|
}
|
|
13260
14083
|
function agentRegistrationFix(input) {
|
|
14084
|
+
const legacy = (input.agentMcp ?? []).find(
|
|
14085
|
+
(fact) => fact.state === "legacy" || fact.projectServers.length > 1 && fact.projectServers.includes(LEGACY_SERVER_NAME)
|
|
14086
|
+
);
|
|
14087
|
+
if (legacy !== void 0) {
|
|
14088
|
+
return fixOf(
|
|
14089
|
+
legacy.state === "legacy" ? `optional: remove the older "fabric" server from ${AGENT_TITLE[legacy.tool]} and run init, which registers "${legacy.name}" in its place, or leave it as it is` : `optional: remove the older "fabric" server from ${AGENT_TITLE[legacy.tool]}, which "${legacy.name}" already replaces, or leave it as it is`,
|
|
14090
|
+
`${legacy.tool === "codex" ? "codex" : "claude"} mcp remove fabric${legacy.tool === "codex" ? "" : " --scope local"}`
|
|
14091
|
+
);
|
|
14092
|
+
}
|
|
13261
14093
|
const first = (input.agentMcp ?? []).find(
|
|
13262
14094
|
(fact) => fact.state === "elsewhere"
|
|
13263
14095
|
);
|
|
@@ -13419,13 +14251,13 @@ async function commandResolves(input, lookup, command) {
|
|
|
13419
14251
|
if (TOOL_NAME.test(command)) {
|
|
13420
14252
|
return await isOnPath(command, lookup) ? "found" : "missing";
|
|
13421
14253
|
}
|
|
13422
|
-
const api = input.platform === "win32" ?
|
|
14254
|
+
const api = input.platform === "win32" ? path18.win32 : path18.posix;
|
|
13423
14255
|
if (api.isAbsolute(command)) {
|
|
13424
14256
|
return await isExecutableFile(command, input.platform) ? "found" : "missing";
|
|
13425
14257
|
}
|
|
13426
14258
|
if (command.includes("/") || command.includes("\\")) {
|
|
13427
14259
|
return await isExecutableFile(
|
|
13428
|
-
|
|
14260
|
+
path18.resolve(input.root, command),
|
|
13429
14261
|
input.platform
|
|
13430
14262
|
) ? "found" : "missing";
|
|
13431
14263
|
}
|
|
@@ -13439,7 +14271,7 @@ async function runProbes(probes, items, fetchImpl) {
|
|
|
13439
14271
|
while (next < probes.length) {
|
|
13440
14272
|
const probe = probes[next++];
|
|
13441
14273
|
const remaining = PROBE_BUDGET_MS - (Date.now() - started);
|
|
13442
|
-
if (sent >=
|
|
14274
|
+
if (sent >= MAX_PROBES2 || remaining <= 0) {
|
|
13443
14275
|
continue;
|
|
13444
14276
|
}
|
|
13445
14277
|
sent++;
|
|
@@ -13602,7 +14434,7 @@ function formatDoctorText(report, destination) {
|
|
|
13602
14434
|
// src/lib/instructions/drop-lock.ts
|
|
13603
14435
|
import { createHash as createHash10 } from "crypto";
|
|
13604
14436
|
import { rmdir } from "fs/promises";
|
|
13605
|
-
import
|
|
14437
|
+
import path19 from "path";
|
|
13606
14438
|
var LOCK_RELATIVE = `${LOCK_DIRECTORY}/${LOCK_FILENAME}`;
|
|
13607
14439
|
var MAX_LOCK_BYTES2 = 16 * 1024 * 1024;
|
|
13608
14440
|
async function dropOwnLock(root, projectId) {
|
|
@@ -13622,7 +14454,7 @@ async function dropOwnLock(root, projectId) {
|
|
|
13622
14454
|
if (outcome !== "deleted") {
|
|
13623
14455
|
return false;
|
|
13624
14456
|
}
|
|
13625
|
-
await rmdir(
|
|
14457
|
+
await rmdir(path19.join(root, LOCK_DIRECTORY)).catch(() => void 0);
|
|
13626
14458
|
return true;
|
|
13627
14459
|
} catch {
|
|
13628
14460
|
return false;
|
|
@@ -13715,7 +14547,7 @@ function replacePath(text3, target, replacement) {
|
|
|
13715
14547
|
|
|
13716
14548
|
// src/lib/instructions/fast-forward-run.ts
|
|
13717
14549
|
import { mkdir as mkdir6, realpath as realpath4 } from "fs/promises";
|
|
13718
|
-
import
|
|
14550
|
+
import path22 from "path";
|
|
13719
14551
|
|
|
13720
14552
|
// src/lib/instructions/fast-forward.ts
|
|
13721
14553
|
function fastForwardEligibility(facts) {
|
|
@@ -13780,7 +14612,7 @@ function fabricCopyLag(input) {
|
|
|
13780
14612
|
return "pending";
|
|
13781
14613
|
}
|
|
13782
14614
|
function isBehindReason(reason) {
|
|
13783
|
-
return reason === "
|
|
14615
|
+
return reason === "wrong-branch" || reason === "detached" || reason === "operation";
|
|
13784
14616
|
}
|
|
13785
14617
|
function skipped(text3) {
|
|
13786
14618
|
return `${HOOK_PREFIX} sync skipped: ${text3}`;
|
|
@@ -13855,7 +14687,7 @@ function fastForwardLines(outcome, context) {
|
|
|
13855
14687
|
login: context.login,
|
|
13856
14688
|
commands: context.commands
|
|
13857
14689
|
});
|
|
13858
|
-
if (outcome.reason === "auth") {
|
|
14690
|
+
if (outcome.reason === "auth" || outcome.reason === "old-git") {
|
|
13859
14691
|
stdout.push(text3);
|
|
13860
14692
|
} else {
|
|
13861
14693
|
stderr.push(skipped(text3));
|
|
@@ -13865,13 +14697,12 @@ function fastForwardLines(outcome, context) {
|
|
|
13865
14697
|
}
|
|
13866
14698
|
case "merge-failed": {
|
|
13867
14699
|
const text3 = outcomeLine("ff-merge-failed", {
|
|
13868
|
-
|
|
13869
|
-
files: outcome.files,
|
|
14700
|
+
failure: outcome,
|
|
13870
14701
|
ref: context.ref,
|
|
13871
14702
|
remote: context.remote,
|
|
13872
14703
|
commands: context.commands
|
|
13873
14704
|
});
|
|
13874
|
-
if (outcome.reason
|
|
14705
|
+
if (isUserActionableMergeFailure(outcome.reason)) {
|
|
13875
14706
|
stdout.push(text3);
|
|
13876
14707
|
otherLine();
|
|
13877
14708
|
} else {
|
|
@@ -13905,10 +14736,10 @@ function fastForwardLines(outcome, context) {
|
|
|
13905
14736
|
}
|
|
13906
14737
|
|
|
13907
14738
|
// src/lib/instructions/fast-forward-memory.ts
|
|
13908
|
-
import { mkdir as mkdir4, readFile as
|
|
13909
|
-
import
|
|
14739
|
+
import { mkdir as mkdir4, readFile as readFile6, rename as rename2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
14740
|
+
import path20 from "path";
|
|
13910
14741
|
function noticeFile(commonDir2) {
|
|
13911
|
-
return
|
|
14742
|
+
return path20.join(commonDir2, "fabric", "ff-notice.json");
|
|
13912
14743
|
}
|
|
13913
14744
|
function isRecord4(value) {
|
|
13914
14745
|
if (typeof value !== "object" || value === null) {
|
|
@@ -13919,7 +14750,7 @@ function isRecord4(value) {
|
|
|
13919
14750
|
}
|
|
13920
14751
|
async function isNewNotice(file, key) {
|
|
13921
14752
|
try {
|
|
13922
|
-
const parsed = JSON.parse(await
|
|
14753
|
+
const parsed = JSON.parse(await readFile6(file, "utf8"));
|
|
13923
14754
|
return !(isRecord4(parsed) && parsed.projectId === key.projectId && parsed.publishedVersion === key.publishedVersion && parsed.directCommitSha === key.directCommitSha && parsed.reason === key.reason);
|
|
13924
14755
|
} catch {
|
|
13925
14756
|
return true;
|
|
@@ -13927,7 +14758,7 @@ async function isNewNotice(file, key) {
|
|
|
13927
14758
|
}
|
|
13928
14759
|
async function rememberNotice(file, key) {
|
|
13929
14760
|
try {
|
|
13930
|
-
await mkdir4(
|
|
14761
|
+
await mkdir4(path20.dirname(file), { recursive: true });
|
|
13931
14762
|
const temporary = `${file}.${process.pid}.tmp`;
|
|
13932
14763
|
const record = { v: 1, ...key };
|
|
13933
14764
|
await writeFile2(temporary, `${JSON.stringify(record)}
|
|
@@ -13944,22 +14775,27 @@ async function forgetNotice(file) {
|
|
|
13944
14775
|
|
|
13945
14776
|
// src/lib/instructions/hook-timing.ts
|
|
13946
14777
|
var hookTiming = {
|
|
13947
|
-
deadlineMs:
|
|
14778
|
+
deadlineMs: 9500,
|
|
13948
14779
|
gitMarginMs: 750,
|
|
13949
14780
|
selfUpdateBudgetMs: 4e3,
|
|
13950
|
-
selfUpdateMarginMs: 1e3
|
|
14781
|
+
selfUpdateMarginMs: 1e3,
|
|
14782
|
+
processStartedAt: void 0,
|
|
14783
|
+
exitWhenDone: false
|
|
13951
14784
|
};
|
|
14785
|
+
function sinceProcessStart(now = Date.now()) {
|
|
14786
|
+
return hookTiming.processStartedAt === void 0 ? 0 : Math.max(0, now - hookTiming.processStartedAt);
|
|
14787
|
+
}
|
|
13952
14788
|
|
|
13953
14789
|
// src/lib/instructions/hook-trace.ts
|
|
13954
|
-
import { mkdir as mkdir5, readFile as
|
|
13955
|
-
import
|
|
14790
|
+
import { mkdir as mkdir5, readFile as readFile7, rename as rename3, writeFile as writeFile3 } from "fs/promises";
|
|
14791
|
+
import path21 from "path";
|
|
13956
14792
|
var TRACE_LIMIT = 50;
|
|
13957
14793
|
function traceFile(configDir) {
|
|
13958
|
-
return
|
|
14794
|
+
return path21.join(configDir, "traces", "instructions-hook.jsonl");
|
|
13959
14795
|
}
|
|
13960
14796
|
async function appendTrace(file, entry) {
|
|
13961
14797
|
try {
|
|
13962
|
-
const previous = await
|
|
14798
|
+
const previous = await readFile7(file, "utf8").catch(() => "");
|
|
13963
14799
|
const kept = previous.split("\n").filter((line2) => {
|
|
13964
14800
|
try {
|
|
13965
14801
|
return typeof JSON.parse(line2)?.outcome === "string";
|
|
@@ -13968,7 +14804,7 @@ async function appendTrace(file, entry) {
|
|
|
13968
14804
|
}
|
|
13969
14805
|
}).slice(-(TRACE_LIMIT - 1));
|
|
13970
14806
|
kept.push(JSON.stringify(entry));
|
|
13971
|
-
await mkdir5(
|
|
14807
|
+
await mkdir5(path21.dirname(file), { recursive: true, mode: 448 });
|
|
13972
14808
|
const temporary = `${file}.${process.pid}.tmp`;
|
|
13973
14809
|
await writeFile3(temporary, `${kept.join("\n")}
|
|
13974
14810
|
`, { mode: 384 });
|
|
@@ -13981,54 +14817,57 @@ async function appendTrace(file, entry) {
|
|
|
13981
14817
|
var MERGE_RESERVE_MS = 1500;
|
|
13982
14818
|
var MERGE_FLOOR_MS = 500;
|
|
13983
14819
|
var FF_LOCK_STALE_MS = 3e4;
|
|
14820
|
+
var Phases = class {
|
|
14821
|
+
constructor() {
|
|
14822
|
+
this.totals = {};
|
|
14823
|
+
}
|
|
14824
|
+
async time(name, work) {
|
|
14825
|
+
const started = Date.now();
|
|
14826
|
+
try {
|
|
14827
|
+
return await work();
|
|
14828
|
+
} finally {
|
|
14829
|
+
this.totals[name] = (this.totals[name] ?? 0) + (Date.now() - started);
|
|
14830
|
+
}
|
|
14831
|
+
}
|
|
14832
|
+
};
|
|
13984
14833
|
var show2 = (value, max = 200) => sanitizeDisplayText(value, max);
|
|
13985
14834
|
async function samePath(a, b) {
|
|
13986
|
-
const resolve = async (value) => realpath4(value).catch(() =>
|
|
14835
|
+
const resolve = async (value) => realpath4(value).catch(() => path22.resolve(value));
|
|
13987
14836
|
const [left, right] = await Promise.all([resolve(a), resolve(b)]);
|
|
13988
14837
|
return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
13989
14838
|
}
|
|
13990
|
-
async function readState(root, traits, deadline) {
|
|
13991
|
-
const branch = await currentBranch(root, deadline);
|
|
13992
|
-
const head = await headSha(root, deadline);
|
|
13993
|
-
const clean = await isClean(root, deadline);
|
|
13994
|
-
const operation = await operationInProgress(root, deadline);
|
|
13995
|
-
if (branch.kind !== "ok" || head.kind !== "ok" || clean.kind !== "ok" || operation.kind !== "ok") {
|
|
13996
|
-
return null;
|
|
13997
|
-
}
|
|
13998
|
-
return {
|
|
13999
|
-
branch: branch.value,
|
|
14000
|
-
head: head.value,
|
|
14001
|
-
clean: clean.value,
|
|
14002
|
-
operation: operation.value,
|
|
14003
|
-
traits
|
|
14004
|
-
};
|
|
14005
|
-
}
|
|
14006
14839
|
async function readFacts(run3, deadline) {
|
|
14007
14840
|
const { classification, repository } = run3;
|
|
14008
14841
|
const root = classification.toplevel;
|
|
14009
|
-
const
|
|
14010
|
-
|
|
14842
|
+
const [branch, head, operation, locks, holders] = await Promise.all([
|
|
14843
|
+
currentBranch(root, deadline),
|
|
14844
|
+
headSha(root, deadline),
|
|
14845
|
+
operationInProgress(root, deadline),
|
|
14846
|
+
lockFilesPresent(root, deadline),
|
|
14847
|
+
worktreesOnBranch(root, repository.ref, deadline)
|
|
14848
|
+
]);
|
|
14849
|
+
if (branch.kind !== "ok" || head.kind !== "ok" || operation.kind !== "ok" || locks.kind !== "ok" || holders.kind !== "ok") {
|
|
14011
14850
|
return null;
|
|
14012
14851
|
}
|
|
14013
|
-
const upstream =
|
|
14014
|
-
|
|
14015
|
-
const holders = await worktreesOnBranch(root, repository.ref, deadline);
|
|
14016
|
-
if (upstream.kind !== "ok" || locks.kind !== "ok" || holders.kind !== "ok") {
|
|
14852
|
+
const upstream = branch.value === null ? { kind: "ok", value: null } : await upstreamOf(root, branch.value, deadline);
|
|
14853
|
+
if (upstream.kind !== "ok") {
|
|
14017
14854
|
return null;
|
|
14018
14855
|
}
|
|
14019
|
-
|
|
14020
|
-
|
|
14021
|
-
|
|
14022
|
-
heldElsewhere = true;
|
|
14023
|
-
}
|
|
14024
|
-
}
|
|
14856
|
+
const elsewhere = await Promise.all(
|
|
14857
|
+
holders.value.map(async (holder) => !await samePath(holder, root))
|
|
14858
|
+
);
|
|
14025
14859
|
return {
|
|
14026
14860
|
ref: repository.ref,
|
|
14027
14861
|
remote: classification.remote,
|
|
14028
|
-
state
|
|
14862
|
+
state: {
|
|
14863
|
+
branch: branch.value,
|
|
14864
|
+
head: head.value,
|
|
14865
|
+
operation: operation.value,
|
|
14866
|
+
traits: classification.traits
|
|
14867
|
+
},
|
|
14029
14868
|
upstream: upstream.value,
|
|
14030
14869
|
lockFiles: locks.value,
|
|
14031
|
-
heldElsewhere
|
|
14870
|
+
heldElsewhere: elsewhere.some(Boolean)
|
|
14032
14871
|
};
|
|
14033
14872
|
}
|
|
14034
14873
|
function unanswered(deadline, head, stage) {
|
|
@@ -14050,7 +14889,10 @@ async function underLock(run3, first) {
|
|
|
14050
14889
|
const root = classification.toplevel;
|
|
14051
14890
|
const remote = classification.remote;
|
|
14052
14891
|
const ref = repository.ref;
|
|
14053
|
-
const facts = await
|
|
14892
|
+
const facts = await run3.phases.time(
|
|
14893
|
+
"facts",
|
|
14894
|
+
() => readFacts(run3, deadline)
|
|
14895
|
+
);
|
|
14054
14896
|
if (facts === null) {
|
|
14055
14897
|
return unanswered(deadline, first.state.head, "read");
|
|
14056
14898
|
}
|
|
@@ -14072,14 +14914,25 @@ async function underLock(run3, first) {
|
|
|
14072
14914
|
if (Date.now() >= fetchDeadline) {
|
|
14073
14915
|
return { outcome: { kind: "deadline", stage: "fetch" }, head };
|
|
14074
14916
|
}
|
|
14075
|
-
|
|
14076
|
-
|
|
14077
|
-
|
|
14078
|
-
|
|
14079
|
-
|
|
14080
|
-
|
|
14081
|
-
|
|
14082
|
-
|
|
14917
|
+
let transport;
|
|
14918
|
+
if (run3.gitTransport) {
|
|
14919
|
+
try {
|
|
14920
|
+
transport = await run3.gitTransport();
|
|
14921
|
+
} catch {
|
|
14922
|
+
return { outcome: { kind: "fetch-failed", reason: "auth" }, head };
|
|
14923
|
+
}
|
|
14924
|
+
}
|
|
14925
|
+
const fetched = await run3.phases.time(
|
|
14926
|
+
"fetch",
|
|
14927
|
+
() => transport ? fetchRefFromUrl(
|
|
14928
|
+
root,
|
|
14929
|
+
transport.url,
|
|
14930
|
+
remote,
|
|
14931
|
+
ref,
|
|
14932
|
+
fetchDeadline,
|
|
14933
|
+
transport
|
|
14934
|
+
) : fetchRef(root, remote, ref, fetchDeadline)
|
|
14935
|
+
);
|
|
14083
14936
|
switch (fetched.kind) {
|
|
14084
14937
|
case "timed-out":
|
|
14085
14938
|
return { outcome: { kind: "deadline", stage: "fetch" }, head };
|
|
@@ -14105,11 +14958,10 @@ async function underLock(run3, first) {
|
|
|
14105
14958
|
return fetched;
|
|
14106
14959
|
}
|
|
14107
14960
|
const tip = fetched.tip;
|
|
14108
|
-
const relation = await
|
|
14109
|
-
|
|
14110
|
-
|
|
14111
|
-
|
|
14112
|
-
}
|
|
14961
|
+
const relation = await run3.phases.time(
|
|
14962
|
+
"relate",
|
|
14963
|
+
() => tipRelation(root, head, tip, deadline)
|
|
14964
|
+
);
|
|
14113
14965
|
switch (relation) {
|
|
14114
14966
|
case "current":
|
|
14115
14967
|
return { outcome: { kind: "already-current" }, head };
|
|
@@ -14123,10 +14975,20 @@ async function underLock(run3, first) {
|
|
|
14123
14975
|
default:
|
|
14124
14976
|
return relation;
|
|
14125
14977
|
}
|
|
14978
|
+
const changed = await run3.phases.time(
|
|
14979
|
+
"facts",
|
|
14980
|
+
() => factsStillMatch(run3, facts, deadline)
|
|
14981
|
+
);
|
|
14982
|
+
if (changed !== null) {
|
|
14983
|
+
return changed;
|
|
14984
|
+
}
|
|
14126
14985
|
if (deadline - Date.now() < MERGE_FLOOR_MS) {
|
|
14127
14986
|
return { outcome: { kind: "deadline", stage: "merge" }, head };
|
|
14128
14987
|
}
|
|
14129
|
-
const merged = await
|
|
14988
|
+
const merged = await run3.phases.time(
|
|
14989
|
+
"merge",
|
|
14990
|
+
() => fastForwardTo(root, tip, deadline)
|
|
14991
|
+
);
|
|
14130
14992
|
switch (merged.kind) {
|
|
14131
14993
|
case "merged":
|
|
14132
14994
|
return {
|
|
@@ -14152,11 +15014,11 @@ async function underLock(run3, first) {
|
|
|
14152
15014
|
};
|
|
14153
15015
|
}
|
|
14154
15016
|
return {
|
|
14155
|
-
outcome: {
|
|
15017
|
+
outcome: merged.reason === "local-changes" ? {
|
|
14156
15018
|
kind: "merge-failed",
|
|
14157
15019
|
reason: merged.reason,
|
|
14158
|
-
|
|
14159
|
-
},
|
|
15020
|
+
files: merged.files
|
|
15021
|
+
} : { kind: "merge-failed", reason: merged.reason },
|
|
14160
15022
|
head
|
|
14161
15023
|
};
|
|
14162
15024
|
default:
|
|
@@ -14184,13 +15046,13 @@ async function factsStillMatch(run3, expected, deadline) {
|
|
|
14184
15046
|
return null;
|
|
14185
15047
|
}
|
|
14186
15048
|
async function decide(run3) {
|
|
14187
|
-
const { classification, deadline, report } = run3;
|
|
15049
|
+
const { classification, deadline, report, phases } = run3;
|
|
14188
15050
|
const root = classification.toplevel;
|
|
14189
15051
|
const head = report.state?.head ?? null;
|
|
14190
15052
|
if (run3.optedOut) {
|
|
14191
15053
|
return { outcome: { kind: "opted-out" }, head };
|
|
14192
15054
|
}
|
|
14193
|
-
const facts = await readFacts(run3, deadline);
|
|
15055
|
+
const facts = await phases.time("facts", () => readFacts(run3, deadline));
|
|
14194
15056
|
if (facts === null) {
|
|
14195
15057
|
return unanswered(deadline, head, "read");
|
|
14196
15058
|
}
|
|
@@ -14201,15 +15063,32 @@ async function decide(run3) {
|
|
|
14201
15063
|
head: facts.state.head
|
|
14202
15064
|
};
|
|
14203
15065
|
}
|
|
14204
|
-
|
|
15066
|
+
if (run3.directCommitSha !== void 0 && facts.state.head !== null) {
|
|
15067
|
+
const holds = await phases.time(
|
|
15068
|
+
"contains",
|
|
15069
|
+
() => isAncestor(
|
|
15070
|
+
root,
|
|
15071
|
+
run3.directCommitSha,
|
|
15072
|
+
facts.state.head,
|
|
15073
|
+
deadline
|
|
15074
|
+
)
|
|
15075
|
+
);
|
|
15076
|
+
if (holds.kind === "ok" && holds.value) {
|
|
15077
|
+
return {
|
|
15078
|
+
outcome: { kind: "already-current" },
|
|
15079
|
+
head: facts.state.head
|
|
15080
|
+
};
|
|
15081
|
+
}
|
|
15082
|
+
}
|
|
15083
|
+
const common = await run3.commonDir;
|
|
14205
15084
|
if (common.kind !== "ok") {
|
|
14206
15085
|
return unanswered(deadline, head, "read");
|
|
14207
15086
|
}
|
|
14208
|
-
const directory =
|
|
15087
|
+
const directory = path22.join(common.value, "fabric");
|
|
14209
15088
|
try {
|
|
14210
15089
|
await mkdir6(directory, { recursive: true });
|
|
14211
15090
|
return await withExclusiveLock(() => underLock(run3, facts), {
|
|
14212
|
-
lockPath:
|
|
15091
|
+
lockPath: path22.join(directory, "ff.lock"),
|
|
14213
15092
|
staleMs: FF_LOCK_STALE_MS,
|
|
14214
15093
|
waitMs: 0
|
|
14215
15094
|
});
|
|
@@ -14219,7 +15098,7 @@ async function decide(run3) {
|
|
|
14219
15098
|
return {
|
|
14220
15099
|
outcome: error.abandoned ? {
|
|
14221
15100
|
kind: "abandoned-lock",
|
|
14222
|
-
lockPath:
|
|
15101
|
+
lockPath: path22.join(directory, "ff.lock")
|
|
14223
15102
|
} : { kind: "locked" },
|
|
14224
15103
|
head: now.kind === "ok" ? now.value : head
|
|
14225
15104
|
};
|
|
@@ -14243,9 +15122,12 @@ function noticeReason(outcome, lag) {
|
|
|
14243
15122
|
if (outcome.kind === "not-safe") {
|
|
14244
15123
|
return `not-safe:${outcome.reason}`;
|
|
14245
15124
|
}
|
|
14246
|
-
if (outcome.kind === "merge-failed" && (outcome.reason
|
|
15125
|
+
if (outcome.kind === "merge-failed" && isUserActionableMergeFailure(outcome.reason)) {
|
|
14247
15126
|
return `merge-failed:${outcome.reason}`;
|
|
14248
15127
|
}
|
|
15128
|
+
if (outcome.kind === "fetch-failed" && outcome.reason === "old-git") {
|
|
15129
|
+
return "fetch-failed:old-git";
|
|
15130
|
+
}
|
|
14249
15131
|
if ((outcome.kind === "fast-forwarded" || outcome.kind === "already-current") && lag !== null) {
|
|
14250
15132
|
return `fabric-lags:${lag}`;
|
|
14251
15133
|
}
|
|
@@ -14266,7 +15148,9 @@ async function runFastForward(run3) {
|
|
|
14266
15148
|
const started = Date.now();
|
|
14267
15149
|
const { repository, snapshot, classification } = run3;
|
|
14268
15150
|
const root = classification.toplevel;
|
|
14269
|
-
const
|
|
15151
|
+
const phases = new Phases();
|
|
15152
|
+
const commonDir2 = commonDir(root, run3.deadline);
|
|
15153
|
+
const decision = await decide({ ...run3, phases, commonDir: commonDir2 });
|
|
14270
15154
|
const { outcome } = decision;
|
|
14271
15155
|
const source = snapshot?.source;
|
|
14272
15156
|
const snapshotSha = source?.kind === "REPOSITORY" && source.current && isCommitSha(source.commitSha) ? source.commitSha : null;
|
|
@@ -14274,7 +15158,7 @@ async function runFastForward(run3) {
|
|
|
14274
15158
|
const moved = outcome.kind === "fast-forwarded" || outcome.kind === "already-current";
|
|
14275
15159
|
const head = outcome.kind === "fast-forwarded" ? outcome.to : decision.head;
|
|
14276
15160
|
let publishedInHistory = null;
|
|
14277
|
-
if (moved && publishedSha !== null && head !== null && publishedSha !== head) {
|
|
15161
|
+
if (moved && snapshot !== void 0 && publishedSha !== null && head !== null && publishedSha !== head) {
|
|
14278
15162
|
const answer = await isAncestor(
|
|
14279
15163
|
root,
|
|
14280
15164
|
publishedSha,
|
|
@@ -14303,10 +15187,13 @@ async function runFastForward(run3) {
|
|
|
14303
15187
|
version: outcome.kind === "fast-forwarded" && publishedSha === outcome.to && snapshot ? Number(snapshot.version) : null,
|
|
14304
15188
|
lag,
|
|
14305
15189
|
head,
|
|
14306
|
-
gaveUpAfter: describeDuration(
|
|
15190
|
+
gaveUpAfter: describeDuration(
|
|
15191
|
+
hookTiming.processStartedAt === void 0 ? hookTiming.deadlineMs : Math.round(sinceProcessStart() / 100) * 100
|
|
15192
|
+
)
|
|
14307
15193
|
};
|
|
14308
15194
|
let { stdout, stderr } = fastForwardLines(outcome, context);
|
|
14309
|
-
const
|
|
15195
|
+
const early = await commonDir2;
|
|
15196
|
+
const common = early.kind === "ok" ? early : await commonDir(
|
|
14310
15197
|
root,
|
|
14311
15198
|
Math.max(run3.deadline, Date.now() + 500)
|
|
14312
15199
|
);
|
|
@@ -14336,17 +15223,90 @@ async function runFastForward(run3) {
|
|
|
14336
15223
|
projectId: run3.projectId,
|
|
14337
15224
|
outcome: outcome.kind,
|
|
14338
15225
|
reason: reasonOf(outcome),
|
|
14339
|
-
ms: Date.now() - started
|
|
15226
|
+
ms: Date.now() - started,
|
|
15227
|
+
totalMs: sinceProcessStart(),
|
|
15228
|
+
phases: { ...run3.timings, ...phases.totals }
|
|
14340
15229
|
});
|
|
14341
15230
|
}
|
|
14342
15231
|
return { outcome, stdout, stderr };
|
|
14343
15232
|
}
|
|
14344
15233
|
|
|
15234
|
+
// src/lib/instructions/gateway-probe.ts
|
|
15235
|
+
var PROBE_TIMEOUT_MS2 = 3e3;
|
|
15236
|
+
var MAX_METADATA_CHARS = 64 * 1024;
|
|
15237
|
+
var CHALLENGE_METADATA = /resource_metadata="([^"]+)"/;
|
|
15238
|
+
function sameOrigin(left, right) {
|
|
15239
|
+
const a = normalizeServerUrl(left);
|
|
15240
|
+
const b = normalizeServerUrl(right);
|
|
15241
|
+
return a !== null && b !== null && new URL(a).origin === new URL(b).origin;
|
|
15242
|
+
}
|
|
15243
|
+
async function discard(response) {
|
|
15244
|
+
try {
|
|
15245
|
+
await response.body?.cancel();
|
|
15246
|
+
} catch {
|
|
15247
|
+
}
|
|
15248
|
+
}
|
|
15249
|
+
function request(fetcher, url) {
|
|
15250
|
+
return fetcher(url, {
|
|
15251
|
+
method: "GET",
|
|
15252
|
+
redirect: "manual",
|
|
15253
|
+
credentials: "omit",
|
|
15254
|
+
headers: { accept: "application/json" },
|
|
15255
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS2)
|
|
15256
|
+
});
|
|
15257
|
+
}
|
|
15258
|
+
function gatewayProbeFor(input) {
|
|
15259
|
+
const fetcher = input.fetch ?? ((url, init) => fetch(url, init));
|
|
15260
|
+
return async (url) => {
|
|
15261
|
+
if (!sameOrigin(url, input.origin)) {
|
|
15262
|
+
return "unknown";
|
|
15263
|
+
}
|
|
15264
|
+
try {
|
|
15265
|
+
const challenged = await request(fetcher, url);
|
|
15266
|
+
await discard(challenged);
|
|
15267
|
+
if (challenged.status >= 500) {
|
|
15268
|
+
return "unknown";
|
|
15269
|
+
}
|
|
15270
|
+
const metadataUrl = challenged.status === 401 ? CHALLENGE_METADATA.exec(
|
|
15271
|
+
challenged.headers.get("www-authenticate") ?? ""
|
|
15272
|
+
)?.[1] : void 0;
|
|
15273
|
+
if (metadataUrl === void 0) {
|
|
15274
|
+
return "foreign";
|
|
15275
|
+
}
|
|
15276
|
+
if (!sameOrigin(metadataUrl, input.origin)) {
|
|
15277
|
+
return "unknown";
|
|
15278
|
+
}
|
|
15279
|
+
const metadata = await request(fetcher, metadataUrl);
|
|
15280
|
+
if (metadata.status !== 200) {
|
|
15281
|
+
await discard(metadata);
|
|
15282
|
+
return "unknown";
|
|
15283
|
+
}
|
|
15284
|
+
const text3 = await metadata.text();
|
|
15285
|
+
if (text3.length > MAX_METADATA_CHARS) {
|
|
15286
|
+
return "unknown";
|
|
15287
|
+
}
|
|
15288
|
+
const parsed = JSON.parse(text3);
|
|
15289
|
+
const resource = typeof parsed === "object" && parsed !== null ? Reflect.get(parsed, "resource") : void 0;
|
|
15290
|
+
if (typeof resource !== "string") {
|
|
15291
|
+
return "unknown";
|
|
15292
|
+
}
|
|
15293
|
+
const relation = classifyGatewayResource(
|
|
15294
|
+
input.origin,
|
|
15295
|
+
input.projectId,
|
|
15296
|
+
resource
|
|
15297
|
+
);
|
|
15298
|
+
return relation === "unfamiliar" ? "unknown" : relation;
|
|
15299
|
+
} catch {
|
|
15300
|
+
return "unknown";
|
|
15301
|
+
}
|
|
15302
|
+
};
|
|
15303
|
+
}
|
|
15304
|
+
|
|
14345
15305
|
// src/lib/instructions/git-exclude.ts
|
|
14346
|
-
import { appendFile, lstat as
|
|
14347
|
-
import
|
|
15306
|
+
import { appendFile, lstat as lstat7, mkdir as mkdir7, readFile as readFile8 } from "fs/promises";
|
|
15307
|
+
import path23 from "path";
|
|
14348
15308
|
function relativeToTopLevel(toplevel, directory, file) {
|
|
14349
|
-
return
|
|
15309
|
+
return path23.relative(toplevel, path23.join(directory, file)).split(path23.sep).join("/");
|
|
14350
15310
|
}
|
|
14351
15311
|
function isLiteralEntry(entry) {
|
|
14352
15312
|
return !/[\n\r*?[\]\\!]/.test(entry) && !entry.startsWith("#");
|
|
@@ -14380,8 +15340,8 @@ async function excludeLocalFiles(input) {
|
|
|
14380
15340
|
if (file.kind !== "ok") {
|
|
14381
15341
|
return { kind: "failed", entries: missing };
|
|
14382
15342
|
}
|
|
14383
|
-
await mkdir7(
|
|
14384
|
-
const existing = await
|
|
15343
|
+
await mkdir7(path23.dirname(file.value), { recursive: true });
|
|
15344
|
+
const existing = await lstat7(file.value).catch((error) => {
|
|
14385
15345
|
if (error.code === "ENOENT") {
|
|
14386
15346
|
return null;
|
|
14387
15347
|
}
|
|
@@ -14390,7 +15350,7 @@ async function excludeLocalFiles(input) {
|
|
|
14390
15350
|
if (existing !== null && !existing.isFile()) {
|
|
14391
15351
|
return { kind: "failed", entries: missing };
|
|
14392
15352
|
}
|
|
14393
|
-
const text3 = existing === null ? "" : await
|
|
15353
|
+
const text3 = existing === null ? "" : await readFile8(file.value, "utf8");
|
|
14394
15354
|
const present = new Set(text3.split(/\r?\n/));
|
|
14395
15355
|
const toAdd = missing.filter((entry) => !present.has(entry));
|
|
14396
15356
|
if (toAdd.length === 0) {
|
|
@@ -14406,8 +15366,8 @@ async function excludeLocalFiles(input) {
|
|
|
14406
15366
|
}
|
|
14407
15367
|
|
|
14408
15368
|
// src/lib/instructions/hook-launcher.ts
|
|
14409
|
-
import { mkdir as mkdir8, readFile as
|
|
14410
|
-
import
|
|
15369
|
+
import { mkdir as mkdir8, readFile as readFile9, rename as rename4, rm as rm3, writeFile as writeFile4 } from "fs/promises";
|
|
15370
|
+
import path24 from "path";
|
|
14411
15371
|
var NOT_ON_PATH_WARNING = "`fabric` is not on this machine's PATH, so this session hook will not run. Run the setup line from the project's Connect dialog instead: it keeps a copy of this deployment's CLI where the hook finds it.";
|
|
14412
15372
|
var COPY_IN_USE_WARNING = "The CLI copy the session hook runs is in use and could not be replaced, so the earlier copy was kept. Run this again when no coding session is starting.";
|
|
14413
15373
|
var UNQUOTABLE_PATH = "The CLI's config folder has characters in its path that cannot be written safely into a hook command. Set XDG_CONFIG_HOME (APPDATA on Windows) to a plain path and run this again.";
|
|
@@ -14415,7 +15375,7 @@ function deploymentFolder(origin) {
|
|
|
14415
15375
|
return origin.replace(/^(https?):\/\//, "$1-").replace(/[^A-Za-z0-9.-]+/g, "-");
|
|
14416
15376
|
}
|
|
14417
15377
|
function bundleCopyPath(configDirectory, origin) {
|
|
14418
|
-
return
|
|
15378
|
+
return path24.join(
|
|
14419
15379
|
configDirectory,
|
|
14420
15380
|
"cli",
|
|
14421
15381
|
deploymentFolder(origin),
|
|
@@ -14423,11 +15383,11 @@ function bundleCopyPath(configDirectory, origin) {
|
|
|
14423
15383
|
);
|
|
14424
15384
|
}
|
|
14425
15385
|
function samePath2(a, b) {
|
|
14426
|
-
const [left, right] = [
|
|
15386
|
+
const [left, right] = [path24.resolve(a), path24.resolve(b)];
|
|
14427
15387
|
return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
14428
15388
|
}
|
|
14429
15389
|
async function writeCopy(bytes, destination) {
|
|
14430
|
-
await mkdir8(
|
|
15390
|
+
await mkdir8(path24.dirname(destination), { recursive: true });
|
|
14431
15391
|
const temporary = `${destination}.${process.pid}.tmp`;
|
|
14432
15392
|
await writeFile4(temporary, bytes, { mode: 420 });
|
|
14433
15393
|
try {
|
|
@@ -14435,7 +15395,7 @@ async function writeCopy(bytes, destination) {
|
|
|
14435
15395
|
return true;
|
|
14436
15396
|
} catch (error) {
|
|
14437
15397
|
await rm3(temporary, { force: true });
|
|
14438
|
-
const existing = await
|
|
15398
|
+
const existing = await readFile9(destination).catch(() => null);
|
|
14439
15399
|
if (existing === null) {
|
|
14440
15400
|
throw error;
|
|
14441
15401
|
}
|
|
@@ -14458,7 +15418,7 @@ async function resolveHookLauncher(input) {
|
|
|
14458
15418
|
return { prefix: `node ${word}`, warning: null };
|
|
14459
15419
|
}
|
|
14460
15420
|
const current = await writeCopy(
|
|
14461
|
-
await
|
|
15421
|
+
await readFile9(input.bundle.scriptPath),
|
|
14462
15422
|
destination
|
|
14463
15423
|
);
|
|
14464
15424
|
return {
|
|
@@ -14512,7 +15472,7 @@ async function installHooks(input) {
|
|
|
14512
15472
|
import { createHash as createHash11 } from "crypto";
|
|
14513
15473
|
import { createReadStream } from "fs";
|
|
14514
15474
|
import { mkdir as mkdir9, readdir as readdir2, stat as stat5, unlink as unlink2, writeFile as writeFile5 } from "fs/promises";
|
|
14515
|
-
import
|
|
15475
|
+
import path25 from "path";
|
|
14516
15476
|
import { createInterface } from "readline";
|
|
14517
15477
|
function parseStopHookInput(text3) {
|
|
14518
15478
|
let parsed;
|
|
@@ -14610,7 +15570,7 @@ async function pruneOldMarkers(markerDir, now) {
|
|
|
14610
15570
|
const cutoff = now.getTime() - MARKER_MAX_AGE_MS;
|
|
14611
15571
|
await Promise.all(
|
|
14612
15572
|
entries.map(async (name) => {
|
|
14613
|
-
const entryPath =
|
|
15573
|
+
const entryPath = path25.join(markerDir, name);
|
|
14614
15574
|
try {
|
|
14615
15575
|
const stats = await stat5(entryPath);
|
|
14616
15576
|
if (stats.mtimeMs < cutoff) {
|
|
@@ -14670,7 +15630,7 @@ async function runLessonPromptInner(input) {
|
|
|
14670
15630
|
debugLog(`marker prune failed: ${describeError(error)}`);
|
|
14671
15631
|
});
|
|
14672
15632
|
const key = markerKey(parsedInput.sessionId, projectId);
|
|
14673
|
-
const markerPath =
|
|
15633
|
+
const markerPath = path25.join(input.markerDir, key);
|
|
14674
15634
|
if (await markerExists(markerPath)) {
|
|
14675
15635
|
return { output: null, why: "already-asked" };
|
|
14676
15636
|
}
|
|
@@ -14767,16 +15727,16 @@ function assertLockMatchesManifest(lock, manifest) {
|
|
|
14767
15727
|
const published = new Map(
|
|
14768
15728
|
manifest.map((entry) => [entry.path, entry.sha256])
|
|
14769
15729
|
);
|
|
14770
|
-
for (const [
|
|
14771
|
-
const expected = published.get(
|
|
15730
|
+
for (const [path30, entry] of Object.entries(lock.files)) {
|
|
15731
|
+
const expected = published.get(path30);
|
|
14772
15732
|
if (expected === void 0) {
|
|
14773
15733
|
refuseStaleLedger(
|
|
14774
|
-
`it lists ${
|
|
15734
|
+
`it lists ${path30}, which version ${lock.snapshotVersion} does not contain`
|
|
14775
15735
|
);
|
|
14776
15736
|
}
|
|
14777
15737
|
if (expected !== entry.sha256) {
|
|
14778
15738
|
refuseStaleLedger(
|
|
14779
|
-
`its record of ${
|
|
15739
|
+
`its record of ${path30} is not the published file`
|
|
14780
15740
|
);
|
|
14781
15741
|
}
|
|
14782
15742
|
}
|
|
@@ -15201,14 +16161,14 @@ function splitMessage(message) {
|
|
|
15201
16161
|
}
|
|
15202
16162
|
|
|
15203
16163
|
// src/lib/instructions/pull-request-verdict.ts
|
|
15204
|
-
function pushPullRequest(pullRequest,
|
|
16164
|
+
function pushPullRequest(pullRequest, timedOut2) {
|
|
15205
16165
|
const branch = pullRequest.branch ?? null;
|
|
15206
16166
|
return {
|
|
15207
16167
|
operationId: pullRequest.operationId,
|
|
15208
16168
|
state: pullRequest.state,
|
|
15209
16169
|
url: branch?.pullRequest?.url ?? pullRequest.url,
|
|
15210
16170
|
failure: pullRequest.failure,
|
|
15211
|
-
timedOut,
|
|
16171
|
+
timedOut: timedOut2,
|
|
15212
16172
|
branch,
|
|
15213
16173
|
append: pullRequest.append ?? null
|
|
15214
16174
|
};
|
|
@@ -15591,7 +16551,7 @@ async function confirm(question, streams = { input: process.stdin, output: proce
|
|
|
15591
16551
|
}
|
|
15592
16552
|
|
|
15593
16553
|
// src/lib/instructions/resolve-project.ts
|
|
15594
|
-
import
|
|
16554
|
+
import path26 from "path";
|
|
15595
16555
|
var MAX_CANDIDATES = 8;
|
|
15596
16556
|
var MAX_CANDIDATE_CHARS = 512;
|
|
15597
16557
|
var MAX_LABEL_CHARS = 80;
|
|
@@ -15621,15 +16581,15 @@ async function resolveProjectFromCheckout(input) {
|
|
|
15621
16581
|
if (remotes2.value.length === 0) {
|
|
15622
16582
|
throw outcomeFailure("no-remote", { verb: input.verb });
|
|
15623
16583
|
}
|
|
15624
|
-
const
|
|
15625
|
-
if (
|
|
16584
|
+
const names2 = input.remote === void 0 ? remotes2.value : remotes2.value.filter((name) => name === input.remote);
|
|
16585
|
+
if (names2.length === 0) {
|
|
15626
16586
|
throw outcomeFailure("remote-missing", {
|
|
15627
16587
|
remote: sanitizeDisplayText(input.remote ?? "", 100)
|
|
15628
16588
|
});
|
|
15629
16589
|
}
|
|
15630
16590
|
const candidates = [];
|
|
15631
16591
|
let identity = null;
|
|
15632
|
-
for (const name of
|
|
16592
|
+
for (const name of names2) {
|
|
15633
16593
|
const url = await effectiveFetchUrl(toplevel, name, deadline);
|
|
15634
16594
|
if (url.kind !== "ok") {
|
|
15635
16595
|
throw unreadable(url);
|
|
@@ -15660,7 +16620,7 @@ async function resolveProjectFromCheckout(input) {
|
|
|
15660
16620
|
for (const match of matches2) {
|
|
15661
16621
|
if (await sameDirectory(
|
|
15662
16622
|
destination,
|
|
15663
|
-
|
|
16623
|
+
path26.join(toplevel, match.rootPath)
|
|
15664
16624
|
)) {
|
|
15665
16625
|
here.push(match);
|
|
15666
16626
|
}
|
|
@@ -15683,9 +16643,17 @@ async function resolveProjectFromCheckout(input) {
|
|
|
15683
16643
|
}
|
|
15684
16644
|
|
|
15685
16645
|
// src/lib/instructions/self-update.ts
|
|
16646
|
+
import { spawn as spawn4 } from "child_process";
|
|
15686
16647
|
import { createHash as createHash14 } from "crypto";
|
|
15687
|
-
import {
|
|
15688
|
-
|
|
16648
|
+
import {
|
|
16649
|
+
mkdir as mkdir10,
|
|
16650
|
+
readFile as readFile10,
|
|
16651
|
+
realpath as realpath5,
|
|
16652
|
+
rm as rm4,
|
|
16653
|
+
stat as stat6,
|
|
16654
|
+
writeFile as writeFile6
|
|
16655
|
+
} from "fs/promises";
|
|
16656
|
+
import path27 from "path";
|
|
15689
16657
|
|
|
15690
16658
|
// src/lib/instructions/npm-tarball.ts
|
|
15691
16659
|
import { gunzipSync } from "zlib";
|
|
@@ -15803,7 +16771,7 @@ function transportIsSafe(origin) {
|
|
|
15803
16771
|
const url = new URL(origin);
|
|
15804
16772
|
return url.protocol === "https:" || url.protocol === "http:" && (LOOPBACK_HOST.test(url.hostname) || url.hostname.endsWith(".localhost"));
|
|
15805
16773
|
}
|
|
15806
|
-
function reasonToStayQuiet(input
|
|
16774
|
+
function reasonToStayQuiet(input) {
|
|
15807
16775
|
if (isFlagSet(input.env[SELF_UPDATE_OPT_OUT])) {
|
|
15808
16776
|
return "opted-out";
|
|
15809
16777
|
}
|
|
@@ -15813,22 +16781,22 @@ function reasonToStayQuiet(input, now) {
|
|
|
15813
16781
|
if (!transportIsSafe(input.origin)) {
|
|
15814
16782
|
return "insecure-origin";
|
|
15815
16783
|
}
|
|
15816
|
-
if (input.deadlineAt - now < input.timing.budgetMs + input.timing.marginMs) {
|
|
15817
|
-
return "no-time";
|
|
15818
|
-
}
|
|
15819
16784
|
return null;
|
|
15820
16785
|
}
|
|
16786
|
+
function outOfTime(input, now) {
|
|
16787
|
+
return input.deadlineAt - now < input.timing.budgetMs + input.timing.marginMs;
|
|
16788
|
+
}
|
|
15821
16789
|
async function isKeptCopy(script, copy) {
|
|
15822
16790
|
const actual = await realpath5(copy).catch(() => null);
|
|
15823
16791
|
if (actual === null) {
|
|
15824
16792
|
return false;
|
|
15825
16793
|
}
|
|
15826
|
-
const running = await realpath5(script).catch(() =>
|
|
16794
|
+
const running = await realpath5(script).catch(() => path27.resolve(script));
|
|
15827
16795
|
return samePath2(running, actual);
|
|
15828
16796
|
}
|
|
15829
|
-
async function
|
|
16797
|
+
async function readState(file) {
|
|
15830
16798
|
try {
|
|
15831
|
-
const parsed = JSON.parse(await
|
|
16799
|
+
const parsed = JSON.parse(await readFile10(file, "utf8"));
|
|
15832
16800
|
return typeof parsed.checkedAt === "number" && Number.isFinite(parsed.checkedAt) && typeof parsed.tarball === "string" ? { checkedAt: parsed.checkedAt, tarball: parsed.tarball } : null;
|
|
15833
16801
|
} catch {
|
|
15834
16802
|
return null;
|
|
@@ -15836,7 +16804,7 @@ async function readState2(file) {
|
|
|
15836
16804
|
}
|
|
15837
16805
|
async function writeState(file, state) {
|
|
15838
16806
|
try {
|
|
15839
|
-
await mkdir10(
|
|
16807
|
+
await mkdir10(path27.dirname(file), { recursive: true });
|
|
15840
16808
|
await writeFile6(file, `${JSON.stringify(state)}
|
|
15841
16809
|
`);
|
|
15842
16810
|
return true;
|
|
@@ -15844,6 +16812,30 @@ async function writeState(file, state) {
|
|
|
15844
16812
|
return false;
|
|
15845
16813
|
}
|
|
15846
16814
|
}
|
|
16815
|
+
async function claimSlot(file, now, timing) {
|
|
16816
|
+
try {
|
|
16817
|
+
await mkdir10(path27.dirname(file), { recursive: true });
|
|
16818
|
+
for (let attempt2 = 0; attempt2 < 2; attempt2 += 1) {
|
|
16819
|
+
try {
|
|
16820
|
+
await writeFile6(file, `${now}
|
|
16821
|
+
`, { flag: "wx" });
|
|
16822
|
+
return true;
|
|
16823
|
+
} catch (error) {
|
|
16824
|
+
if (error.code !== "EEXIST") {
|
|
16825
|
+
return false;
|
|
16826
|
+
}
|
|
16827
|
+
const held = await stat6(file).catch(() => null);
|
|
16828
|
+
if (held !== null && now - held.mtimeMs < timing.budgetMs + timing.marginMs) {
|
|
16829
|
+
return false;
|
|
16830
|
+
}
|
|
16831
|
+
await rm4(file, { force: true });
|
|
16832
|
+
}
|
|
16833
|
+
}
|
|
16834
|
+
} catch {
|
|
16835
|
+
return false;
|
|
16836
|
+
}
|
|
16837
|
+
return false;
|
|
16838
|
+
}
|
|
15847
16839
|
function askedRecently(state, now) {
|
|
15848
16840
|
const elapsed = now - state.checkedAt;
|
|
15849
16841
|
return elapsed >= 0 && elapsed < SELF_UPDATE_INTERVAL_MS;
|
|
@@ -15974,7 +16966,7 @@ async function attempt({
|
|
|
15974
16966
|
}
|
|
15975
16967
|
async function refreshKeptCopy(input, deps = {}) {
|
|
15976
16968
|
const now = deps.now ?? Date.now;
|
|
15977
|
-
const quiet = reasonToStayQuiet(input
|
|
16969
|
+
const quiet = reasonToStayQuiet(input);
|
|
15978
16970
|
if (quiet !== null) {
|
|
15979
16971
|
return skipped2(quiet);
|
|
15980
16972
|
}
|
|
@@ -15986,45 +16978,91 @@ async function refreshKeptCopy(input, deps = {}) {
|
|
|
15986
16978
|
if (!await isKeptCopy(input.script, copy)) {
|
|
15987
16979
|
return skipped2("not-a-kept-copy");
|
|
15988
16980
|
}
|
|
15989
|
-
const stateFile =
|
|
15990
|
-
const previous = await
|
|
16981
|
+
const stateFile = path27.join(path27.dirname(copy), STATE_FILE);
|
|
16982
|
+
const previous = await readState(stateFile);
|
|
15991
16983
|
if (previous !== null && askedRecently(previous, now())) {
|
|
15992
16984
|
return skipped2("checked-recently");
|
|
15993
16985
|
}
|
|
15994
|
-
if (
|
|
15995
|
-
return skipped2("
|
|
16986
|
+
if (outOfTime(input, now())) {
|
|
16987
|
+
return skipped2("no-time");
|
|
16988
|
+
}
|
|
16989
|
+
const claimFile = `${stateFile}.claim`;
|
|
16990
|
+
if (!await claimSlot(claimFile, now(), input.timing)) {
|
|
16991
|
+
return skipped2("checked-recently");
|
|
15996
16992
|
}
|
|
15997
|
-
let signal;
|
|
15998
|
-
let outcome = { kind: "current" };
|
|
15999
16993
|
try {
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
16005
|
-
|
|
16006
|
-
|
|
16007
|
-
|
|
16008
|
-
|
|
16009
|
-
|
|
16010
|
-
|
|
16994
|
+
const latest = await readState(stateFile);
|
|
16995
|
+
if (latest !== null && askedRecently(latest, now())) {
|
|
16996
|
+
return skipped2("checked-recently");
|
|
16997
|
+
}
|
|
16998
|
+
if (!await writeState(stateFile, {
|
|
16999
|
+
checkedAt: now(),
|
|
17000
|
+
tarball: running
|
|
17001
|
+
})) {
|
|
17002
|
+
return skipped2("state-unwritable");
|
|
17003
|
+
}
|
|
17004
|
+
return await runAttempt(running);
|
|
17005
|
+
} finally {
|
|
17006
|
+
await rm4(claimFile, { force: true }).catch(() => void 0);
|
|
17007
|
+
}
|
|
17008
|
+
async function runAttempt(running2) {
|
|
17009
|
+
let signal;
|
|
17010
|
+
let outcome = { kind: "current" };
|
|
17011
|
+
try {
|
|
17012
|
+
await withDeadline(input.timing.budgetMs, async (deadline) => {
|
|
17013
|
+
signal = deadline;
|
|
17014
|
+
outcome = await attempt({
|
|
17015
|
+
input,
|
|
17016
|
+
copy,
|
|
17017
|
+
stateFile,
|
|
17018
|
+
now,
|
|
17019
|
+
fetchImpl: deps.fetchImpl ?? fetch,
|
|
17020
|
+
replace: deps.replace ?? writeCopy,
|
|
17021
|
+
signal: deadline,
|
|
17022
|
+
running: running2
|
|
17023
|
+
});
|
|
16011
17024
|
});
|
|
16012
|
-
})
|
|
16013
|
-
|
|
16014
|
-
|
|
16015
|
-
|
|
17025
|
+
} catch (error) {
|
|
17026
|
+
if (signal?.aborted) {
|
|
17027
|
+
return {
|
|
17028
|
+
kind: "failed",
|
|
17029
|
+
reason: "it ran out of time",
|
|
17030
|
+
cause: error
|
|
17031
|
+
};
|
|
17032
|
+
}
|
|
17033
|
+
return error instanceof SelfUpdateFailure ? { kind: "failed", reason: error.message, cause: error.cause } : {
|
|
16016
17034
|
kind: "failed",
|
|
16017
|
-
reason: "it
|
|
17035
|
+
reason: "it failed unexpectedly",
|
|
16018
17036
|
cause: error
|
|
16019
17037
|
};
|
|
16020
17038
|
}
|
|
16021
|
-
return
|
|
16022
|
-
|
|
16023
|
-
|
|
16024
|
-
|
|
16025
|
-
|
|
17039
|
+
return outcome;
|
|
17040
|
+
}
|
|
17041
|
+
}
|
|
17042
|
+
function startBackgroundSelfUpdate(input, spawnImpl = spawn4) {
|
|
17043
|
+
try {
|
|
17044
|
+
const child = spawnImpl(
|
|
17045
|
+
process.execPath,
|
|
17046
|
+
[
|
|
17047
|
+
input.script,
|
|
17048
|
+
"instructions",
|
|
17049
|
+
"self-update",
|
|
17050
|
+
"--base-url",
|
|
17051
|
+
input.origin
|
|
17052
|
+
],
|
|
17053
|
+
{
|
|
17054
|
+
detached: true,
|
|
17055
|
+
stdio: "ignore",
|
|
17056
|
+
windowsHide: true,
|
|
17057
|
+
env: input.env
|
|
17058
|
+
}
|
|
17059
|
+
);
|
|
17060
|
+
child.on("error", () => void 0);
|
|
17061
|
+
child.unref();
|
|
17062
|
+
return true;
|
|
17063
|
+
} catch {
|
|
17064
|
+
return false;
|
|
16026
17065
|
}
|
|
16027
|
-
return outcome;
|
|
16028
17066
|
}
|
|
16029
17067
|
function selfUpdateLine(outcome) {
|
|
16030
17068
|
switch (outcome.kind) {
|
|
@@ -16043,19 +17081,19 @@ function selfUpdateLine(outcome) {
|
|
|
16043
17081
|
}
|
|
16044
17082
|
|
|
16045
17083
|
// src/lib/instructions/tools.ts
|
|
16046
|
-
import { stat as
|
|
16047
|
-
import
|
|
17084
|
+
import { stat as stat7 } from "fs/promises";
|
|
17085
|
+
import path28 from "path";
|
|
16048
17086
|
var TOOLS = [
|
|
16049
17087
|
{ tool: "claude-code", directory: ".claude", command: "claude" },
|
|
16050
17088
|
{ tool: "codex", directory: ".codex", command: "codex" }
|
|
16051
17089
|
];
|
|
16052
17090
|
async function isDirectory(target) {
|
|
16053
|
-
return (await
|
|
17091
|
+
return (await stat7(target).catch(() => null))?.isDirectory() ?? false;
|
|
16054
17092
|
}
|
|
16055
17093
|
async function detectTools(input) {
|
|
16056
17094
|
const present = [];
|
|
16057
17095
|
for (const facts of TOOLS) {
|
|
16058
|
-
const found = await isDirectory(
|
|
17096
|
+
const found = await isDirectory(path28.join(input.root, facts.directory)) || input.home !== null && await isDirectory(path28.join(input.home, facts.directory)) || await isOnPath(facts.command, input.lookup);
|
|
16059
17097
|
if (found) {
|
|
16060
17098
|
present.push(facts.tool);
|
|
16061
17099
|
}
|
|
@@ -16082,6 +17120,13 @@ function buildInstructionsCommand() {
|
|
|
16082
17120
|
const instructions = new Command13("instructions").description(
|
|
16083
17121
|
"Keep a checkout current with a project's published coding instructions"
|
|
16084
17122
|
);
|
|
17123
|
+
instructions.command("self-update", { hidden: true }).description("Refresh the kept copy of the served CLI, out of band").option("--base-url <origin>", BASE_URL_HELP).action(async (opts) => {
|
|
17124
|
+
await refreshKeptCopyAfterHook(
|
|
17125
|
+
opts,
|
|
17126
|
+
Date.now() + hookTiming.selfUpdateBudgetMs + hookTiming.selfUpdateMarginMs + BACKGROUND_UPDATE_SLACK_MS,
|
|
17127
|
+
{ mayDefer: false }
|
|
17128
|
+
);
|
|
17129
|
+
});
|
|
16085
17130
|
instructions.command("check").description(
|
|
16086
17131
|
"Report whether the local copy is behind the published one"
|
|
16087
17132
|
).option("--project <id>", PROJECT_HELP).option("--base-url <origin>", BASE_URL_HELP).option("--remote <name>", REMOTE_HELP).option("--dest <dir>", "Destination directory (default: cwd)").option("--org <slug>", "Organization context").option(
|
|
@@ -16212,15 +17257,20 @@ async function run2(opts, verb, body) {
|
|
|
16212
17257
|
takeAuthFailure();
|
|
16213
17258
|
takeUpgradeNotice();
|
|
16214
17259
|
let hookDeadlineAt;
|
|
17260
|
+
hookPhases = {};
|
|
17261
|
+
hookTraced = false;
|
|
16215
17262
|
try {
|
|
16216
17263
|
if (opts.hook) {
|
|
16217
|
-
|
|
16218
|
-
activeDeadlineAt = Date.now() +
|
|
17264
|
+
hookPhases.node = sinceProcessStart();
|
|
17265
|
+
activeDeadlineAt = (hookTiming.processStartedAt ?? Date.now()) + hookDeadlineMs();
|
|
16219
17266
|
hookDeadlineAt = activeDeadlineAt;
|
|
16220
|
-
await withDeadline(
|
|
16221
|
-
|
|
16222
|
-
|
|
16223
|
-
|
|
17267
|
+
await withDeadline(
|
|
17268
|
+
Math.max(0, activeDeadlineAt - Date.now()),
|
|
17269
|
+
(signal) => {
|
|
17270
|
+
activeDeadline = signal;
|
|
17271
|
+
return body();
|
|
17272
|
+
}
|
|
17273
|
+
).finally(() => {
|
|
16224
17274
|
activeDeadline = void 0;
|
|
16225
17275
|
activeDeadlineAt = void 0;
|
|
16226
17276
|
});
|
|
@@ -16239,7 +17289,7 @@ async function run2(opts, verb, body) {
|
|
|
16239
17289
|
const signedOutOf = error instanceof NotSignedInError ? error.origin : authFailure?.origin ?? (failure.exitCode === 3 ? describedOrigin(opts) : null);
|
|
16240
17290
|
if (signedOutOf !== null) {
|
|
16241
17291
|
line(
|
|
16242
|
-
`${HOOK_PREFIX}: ${outcomeLine("hook-signed-out", { origin: signedOutOf || describedOrigin(opts), project: opts.project })}`
|
|
17292
|
+
`${HOOK_PREFIX}: ${outcomeLine("hook-signed-out", { origin: signedOutOf || describedOrigin(opts), project: opts.project, otherProjects: signedInForOtherProjects(signedOutOf || describedOrigin(opts), opts.project) })}`
|
|
16243
17293
|
);
|
|
16244
17294
|
} else if (error instanceof UpgradeRequiredFailure) {
|
|
16245
17295
|
line(error.line);
|
|
@@ -16249,10 +17299,17 @@ async function run2(opts, verb, body) {
|
|
|
16249
17299
|
`
|
|
16250
17300
|
);
|
|
16251
17301
|
}
|
|
17302
|
+
await traceHookFailure(opts, {
|
|
17303
|
+
signedOut: signedOutOf !== null,
|
|
17304
|
+
upgrade: error instanceof UpgradeRequiredFailure,
|
|
17305
|
+
timedOut: /gave up after/.test(failure.message),
|
|
17306
|
+
exitCode: failure.exitCode
|
|
17307
|
+
});
|
|
16252
17308
|
showUpgradeNotice(true);
|
|
16253
17309
|
if (error instanceof UpgradeRequiredFailure) {
|
|
16254
17310
|
await refreshKeptCopyAfterHook(opts, hookDeadlineAt);
|
|
16255
17311
|
}
|
|
17312
|
+
await endHookProcess();
|
|
16256
17313
|
process.exit(0);
|
|
16257
17314
|
}
|
|
16258
17315
|
process.stderr.write(`\u2717 ${failure.message}
|
|
@@ -16263,17 +17320,58 @@ async function run2(opts, verb, body) {
|
|
|
16263
17320
|
showUpgradeNotice(Boolean(opts.hook));
|
|
16264
17321
|
if (opts.hook) {
|
|
16265
17322
|
await refreshKeptCopyAfterHook(opts, hookDeadlineAt);
|
|
17323
|
+
await endHookProcess();
|
|
17324
|
+
}
|
|
17325
|
+
}
|
|
17326
|
+
var hookPhases = {};
|
|
17327
|
+
var hookTraced = false;
|
|
17328
|
+
async function timedPhase(name, work) {
|
|
17329
|
+
const started = Date.now();
|
|
17330
|
+
try {
|
|
17331
|
+
return await work();
|
|
17332
|
+
} finally {
|
|
17333
|
+
hookPhases[name] = (hookPhases[name] ?? 0) + (Date.now() - started);
|
|
17334
|
+
}
|
|
17335
|
+
}
|
|
17336
|
+
async function traceHookFailure(opts, why) {
|
|
17337
|
+
if (hookTraced) {
|
|
17338
|
+
return;
|
|
17339
|
+
}
|
|
17340
|
+
const reason = why.signedOut ? "not-signed-in" : why.upgrade ? "upgrade-required" : why.timedOut ? "deadline" : `failed-${why.exitCode}`;
|
|
17341
|
+
await appendTrace(traceFile(path29.dirname(getConfigPath())), {
|
|
17342
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17343
|
+
projectId: opts.project ?? "",
|
|
17344
|
+
outcome: "skipped",
|
|
17345
|
+
reason,
|
|
17346
|
+
ms: sinceProcessStart(),
|
|
17347
|
+
totalMs: sinceProcessStart(),
|
|
17348
|
+
phases: { ...hookPhases }
|
|
17349
|
+
});
|
|
17350
|
+
}
|
|
17351
|
+
async function endHookProcess() {
|
|
17352
|
+
if (!hookTiming.exitWhenDone) {
|
|
17353
|
+
return;
|
|
16266
17354
|
}
|
|
17355
|
+
await Promise.all(
|
|
17356
|
+
[process.stdout, process.stderr].map(
|
|
17357
|
+
(stream) => new Promise((resolve) => {
|
|
17358
|
+
stream.write("", () => resolve());
|
|
17359
|
+
})
|
|
17360
|
+
)
|
|
17361
|
+
);
|
|
17362
|
+
process.exit(0);
|
|
16267
17363
|
}
|
|
16268
|
-
|
|
17364
|
+
var BACKGROUND_UPDATE_SLACK_MS = 5e3;
|
|
17365
|
+
async function refreshKeptCopyAfterHook(opts, deadlineAt, { mayDefer = true } = {}) {
|
|
16269
17366
|
const origin = opts.baseUrl === void 0 ? null : normalizeOrigin(opts.baseUrl);
|
|
16270
17367
|
if (origin === null || deadlineAt === void 0) {
|
|
16271
17368
|
return;
|
|
16272
17369
|
}
|
|
16273
|
-
const
|
|
17370
|
+
const started = Date.now();
|
|
17371
|
+
const input = {
|
|
16274
17372
|
origin,
|
|
16275
17373
|
script: bundleScriptPath(),
|
|
16276
|
-
configDirectory:
|
|
17374
|
+
configDirectory: path29.dirname(getConfigPath()),
|
|
16277
17375
|
runningTarball: bundleTarballPath(),
|
|
16278
17376
|
env: process.env,
|
|
16279
17377
|
deadlineAt,
|
|
@@ -16281,7 +17379,25 @@ async function refreshKeptCopyAfterHook(opts, deadlineAt) {
|
|
|
16281
17379
|
budgetMs: hookTiming.selfUpdateBudgetMs,
|
|
16282
17380
|
marginMs: hookTiming.selfUpdateMarginMs
|
|
16283
17381
|
}
|
|
16284
|
-
}
|
|
17382
|
+
};
|
|
17383
|
+
const outcome = await refreshKeptCopy(input);
|
|
17384
|
+
if (mayDefer && outcome.kind === "skipped" && outcome.reason === "no-time") {
|
|
17385
|
+
startBackgroundSelfUpdate({
|
|
17386
|
+
script: input.script,
|
|
17387
|
+
origin,
|
|
17388
|
+
env: process.env
|
|
17389
|
+
});
|
|
17390
|
+
}
|
|
17391
|
+
if (outcome.kind !== "skipped") {
|
|
17392
|
+
await appendTrace(traceFile(path29.dirname(getConfigPath())), {
|
|
17393
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17394
|
+
projectId: "",
|
|
17395
|
+
outcome: "self-update",
|
|
17396
|
+
reason: outcome.kind,
|
|
17397
|
+
ms: Date.now() - started,
|
|
17398
|
+
totalMs: sinceProcessStart()
|
|
17399
|
+
});
|
|
17400
|
+
}
|
|
16285
17401
|
if (outcome.kind === "failed" && process.env.FABRIC_DEBUG) {
|
|
16286
17402
|
process.stderr.write(
|
|
16287
17403
|
`debug: ${describeError(outcome.cause ?? outcome.reason)}
|
|
@@ -16333,7 +17449,7 @@ function gitDeadline() {
|
|
|
16333
17449
|
return activeDeadlineAt === void 0 ? Date.now() + GIT_BUDGET_MS2 : activeDeadlineAt - hookTiming.gitMarginMs;
|
|
16334
17450
|
}
|
|
16335
17451
|
function destinationOf(opts) {
|
|
16336
|
-
return
|
|
17452
|
+
return path29.resolve(opts.dest ?? process.cwd());
|
|
16337
17453
|
}
|
|
16338
17454
|
function instructionsClient(opts, timeoutMs, { neverRetry = false } = {}) {
|
|
16339
17455
|
if (!getApiKey(deploymentOrigin(opts), opts.project)) {
|
|
@@ -16364,9 +17480,19 @@ function deploymentOrigin(opts) {
|
|
|
16364
17480
|
function describedOrigin(opts) {
|
|
16365
17481
|
return normalizeOrigin(opts.baseUrl ?? getBaseUrl() ?? DEFAULT_ORIGIN) ?? DEFAULT_ORIGIN;
|
|
16366
17482
|
}
|
|
17483
|
+
function signedInForOtherProjects(origin, project) {
|
|
17484
|
+
return project !== void 0 && listProjectSignIns(origin).some((entry) => entry.projectId !== project);
|
|
17485
|
+
}
|
|
16367
17486
|
var NotSignedInError = class extends CliFailure {
|
|
16368
17487
|
constructor(origin, project) {
|
|
16369
|
-
super(
|
|
17488
|
+
super(
|
|
17489
|
+
outcomeLine("not-signed-in", {
|
|
17490
|
+
origin,
|
|
17491
|
+
project,
|
|
17492
|
+
otherProjects: signedInForOtherProjects(origin, project)
|
|
17493
|
+
}),
|
|
17494
|
+
3
|
|
17495
|
+
);
|
|
16370
17496
|
this.origin = origin;
|
|
16371
17497
|
this.name = "NotSignedInError";
|
|
16372
17498
|
}
|
|
@@ -16383,7 +17509,7 @@ async function ensureSignedIn(opts) {
|
|
|
16383
17509
|
if (opts.hook || runningInCi()) {
|
|
16384
17510
|
throw new NotSignedInError(origin, opts.project);
|
|
16385
17511
|
}
|
|
16386
|
-
const
|
|
17512
|
+
const forProject2 = opts.project === void 0 ? "" : ` for project ${opts.project}`;
|
|
16387
17513
|
try {
|
|
16388
17514
|
const who2 = await signInWithBrowser({
|
|
16389
17515
|
baseUrl: opts.baseUrl ?? getBaseUrl() ?? origin,
|
|
@@ -16393,7 +17519,7 @@ async function ensureSignedIn(opts) {
|
|
|
16393
17519
|
...opts.project === void 0 ? {} : { project: opts.project },
|
|
16394
17520
|
announce: (url) => {
|
|
16395
17521
|
process.stderr.write(
|
|
16396
|
-
`Opening your browser to sign in to ${origin}${
|
|
17522
|
+
`Opening your browser to sign in to ${origin}${forProject2}.
|
|
16397
17523
|
If it does not open, visit:
|
|
16398
17524
|
|
|
16399
17525
|
${url}
|
|
@@ -16405,7 +17531,7 @@ Waiting up to 5 minutes for the sign-in to finish.
|
|
|
16405
17531
|
}
|
|
16406
17532
|
});
|
|
16407
17533
|
process.stderr.write(
|
|
16408
|
-
`Signed in to ${origin}${
|
|
17534
|
+
`Signed in to ${origin}${forProject2} as ${sanitizeDisplayText(who2.name, 80)}.
|
|
16409
17535
|
`
|
|
16410
17536
|
);
|
|
16411
17537
|
} catch {
|
|
@@ -16479,6 +17605,30 @@ async function directGitTransport(opts, direct) {
|
|
|
16479
17605
|
}
|
|
16480
17606
|
return { url: base.toString(), authorization: `Bearer ${token}` };
|
|
16481
17607
|
}
|
|
17608
|
+
function lazyGitTransport(opts, direct) {
|
|
17609
|
+
if (!opts.hook || direct.gitGateway?.version !== "v1") {
|
|
17610
|
+
return void 0;
|
|
17611
|
+
}
|
|
17612
|
+
let failure;
|
|
17613
|
+
let pending;
|
|
17614
|
+
return {
|
|
17615
|
+
resolve: () => {
|
|
17616
|
+
pending ??= timedPhase(
|
|
17617
|
+
"token",
|
|
17618
|
+
() => directGitTransport(opts, direct)
|
|
17619
|
+
).catch((error) => {
|
|
17620
|
+
failure = error;
|
|
17621
|
+
throw error;
|
|
17622
|
+
});
|
|
17623
|
+
return pending;
|
|
17624
|
+
},
|
|
17625
|
+
rethrow: () => {
|
|
17626
|
+
if (failure !== void 0) {
|
|
17627
|
+
throw failure;
|
|
17628
|
+
}
|
|
17629
|
+
}
|
|
17630
|
+
};
|
|
17631
|
+
}
|
|
16482
17632
|
function hasSignInFor(origin, project) {
|
|
16483
17633
|
if (project === void 0) {
|
|
16484
17634
|
return Boolean(getApiKey(origin));
|
|
@@ -16548,14 +17698,17 @@ async function fetchPublished(client, opts, sinceDigest, guard) {
|
|
|
16548
17698
|
const org = orgSlugFor(opts);
|
|
16549
17699
|
let published;
|
|
16550
17700
|
try {
|
|
16551
|
-
published = await
|
|
16552
|
-
|
|
16553
|
-
|
|
16554
|
-
|
|
17701
|
+
published = await timedPhase(
|
|
17702
|
+
"api",
|
|
17703
|
+
() => client.instructions.getPublished(opts.project, {
|
|
17704
|
+
org,
|
|
17705
|
+
sinceDigest
|
|
17706
|
+
})
|
|
17707
|
+
);
|
|
16555
17708
|
} catch (error) {
|
|
16556
17709
|
throw fixedFailure(error);
|
|
16557
17710
|
}
|
|
16558
|
-
await guard?.admit(published);
|
|
17711
|
+
await timedPhase("classify", async () => guard?.admit(published));
|
|
16559
17712
|
return published;
|
|
16560
17713
|
}
|
|
16561
17714
|
var SourceGuard = class {
|
|
@@ -16608,19 +17761,23 @@ function directRepositoryRead(published) {
|
|
|
16608
17761
|
const direct = published.direct;
|
|
16609
17762
|
return published.sourceOfTruth === "REPOSITORY" && published.repository !== void 0 && published.repository !== null && direct?.availability === "READY" ? direct : null;
|
|
16610
17763
|
}
|
|
17764
|
+
var NO_VERIFY_FOR_DIRECT = "--verify has nothing to check: this project reads its repository directly, so Fabric keeps no copy here. Git is the record of what this checkout holds.";
|
|
16611
17765
|
async function directRepositoryCheckoutStatus(input) {
|
|
16612
17766
|
const commit = input.direct.currentCommitSha.slice(0, 7);
|
|
16613
17767
|
const repository = repositoryName(input.repository);
|
|
16614
17768
|
if (input.checkout.class !== "matching") {
|
|
16615
17769
|
return {
|
|
16616
17770
|
current: false,
|
|
17771
|
+
verdict: "no-checkout",
|
|
16617
17772
|
line: `Coding instructions are read directly from ${repository} at ${commit}. Open a matching native checkout before reading them locally.`
|
|
16618
17773
|
};
|
|
16619
17774
|
}
|
|
16620
|
-
const
|
|
16621
|
-
|
|
17775
|
+
const state = input.report.state;
|
|
17776
|
+
const head = state?.head;
|
|
17777
|
+
if (state === null || head === null || head === void 0) {
|
|
16622
17778
|
return {
|
|
16623
17779
|
current: false,
|
|
17780
|
+
verdict: "unknown",
|
|
16624
17781
|
line: `Coding instructions are read directly from ${repository} at ${commit}; this checkout could not be compared to that commit.`
|
|
16625
17782
|
};
|
|
16626
17783
|
}
|
|
@@ -16633,6 +17790,7 @@ async function directRepositoryCheckoutStatus(input) {
|
|
|
16633
17790
|
if (contains.kind === "ok" && contains.value) {
|
|
16634
17791
|
return {
|
|
16635
17792
|
current: true,
|
|
17793
|
+
verdict: "current",
|
|
16636
17794
|
line: `Coding instructions are read directly from ${repository} at ${commit}; this checkout already contains that commit.`
|
|
16637
17795
|
};
|
|
16638
17796
|
}
|
|
@@ -16646,12 +17804,20 @@ async function directRepositoryCheckoutStatus(input) {
|
|
|
16646
17804
|
if (behind.kind === "ok") {
|
|
16647
17805
|
return {
|
|
16648
17806
|
current: false,
|
|
16649
|
-
|
|
17807
|
+
verdict: behind.value ? "behind" : "diverged",
|
|
17808
|
+
line: behind.value ? directBehindLine({
|
|
17809
|
+
repository: input.repository,
|
|
17810
|
+
commitSha: input.direct.currentCommitSha,
|
|
17811
|
+
ref: input.direct.ref,
|
|
17812
|
+
remote: input.checkout.remote,
|
|
17813
|
+
state
|
|
17814
|
+
}) : `Coding instructions are read directly from ${repository} at ${commit}; this checkout has diverged. Inspect its Git history before choosing how to update it.`
|
|
16650
17815
|
};
|
|
16651
17816
|
}
|
|
16652
17817
|
}
|
|
16653
17818
|
return {
|
|
16654
17819
|
current: false,
|
|
17820
|
+
verdict: "not-fetched",
|
|
16655
17821
|
line: `Coding instructions are read directly from ${repository} at ${commit}; fetch that commit with your normal Git remote before continuing.`
|
|
16656
17822
|
};
|
|
16657
17823
|
}
|
|
@@ -16743,13 +17909,26 @@ async function runCheck(opts, format) {
|
|
|
16743
17909
|
projectId: opts.project,
|
|
16744
17910
|
destination,
|
|
16745
17911
|
direct,
|
|
16746
|
-
|
|
17912
|
+
// The same words and verdict as the text mode: the checkout
|
|
17913
|
+
// block's own line knows nothing of a direct read.
|
|
17914
|
+
checkout: checkout === null ? null : {
|
|
17915
|
+
...checkout.json,
|
|
17916
|
+
line: status.current ? null : status.line,
|
|
17917
|
+
verdict: status.verdict
|
|
17918
|
+
},
|
|
17919
|
+
verified: false
|
|
16747
17920
|
},
|
|
16748
17921
|
{ format: "json" }
|
|
16749
17922
|
);
|
|
16750
|
-
} else
|
|
16751
|
-
|
|
17923
|
+
} else {
|
|
17924
|
+
if (!opts.hook || !status.current) {
|
|
17925
|
+
process.stdout.write(`${status.line}
|
|
17926
|
+
`);
|
|
17927
|
+
}
|
|
17928
|
+
if (opts.verify) {
|
|
17929
|
+
process.stdout.write(`${NO_VERIFY_FOR_DIRECT}
|
|
16752
17930
|
`);
|
|
17931
|
+
}
|
|
16753
17932
|
}
|
|
16754
17933
|
return;
|
|
16755
17934
|
}
|
|
@@ -16907,7 +18086,8 @@ async function runDoctorCommand(opts, format) {
|
|
|
16907
18086
|
origin,
|
|
16908
18087
|
home: machine.home(),
|
|
16909
18088
|
env: machine.env(),
|
|
16910
|
-
platform: machine.platform()
|
|
18089
|
+
platform: machine.platform(),
|
|
18090
|
+
probe: opts.probeNetwork ? gatewayProbeFor({ origin, projectId: opts.project }) : void 0
|
|
16911
18091
|
}),
|
|
16912
18092
|
client: () => instructionsClient({ ...opts, hook: false }, CHECK_TIMEOUT_MS),
|
|
16913
18093
|
createDownloadUrl: (projectId, options) => downloadUrlClient({
|
|
@@ -17019,25 +18199,34 @@ async function syncOnce(opts) {
|
|
|
17019
18199
|
7
|
|
17020
18200
|
);
|
|
17021
18201
|
}
|
|
17022
|
-
const
|
|
17023
|
-
|
|
17024
|
-
|
|
17025
|
-
|
|
17026
|
-
|
|
17027
|
-
|
|
17028
|
-
|
|
17029
|
-
|
|
17030
|
-
|
|
17031
|
-
|
|
17032
|
-
|
|
17033
|
-
|
|
17034
|
-
|
|
18202
|
+
const checkout = guard.checkout;
|
|
18203
|
+
const report = await timedPhase(
|
|
18204
|
+
"report",
|
|
18205
|
+
() => reportForClassification({
|
|
18206
|
+
classification: checkout,
|
|
18207
|
+
repository: directRepository,
|
|
18208
|
+
snapshot: void 0,
|
|
18209
|
+
deadline: gitDeadline(),
|
|
18210
|
+
contentChanges: opts.hook ? "when-behind" : "always"
|
|
18211
|
+
})
|
|
18212
|
+
);
|
|
18213
|
+
const status = await timedPhase(
|
|
18214
|
+
"status",
|
|
18215
|
+
() => directRepositoryCheckoutStatus({
|
|
18216
|
+
repository: directRepository,
|
|
18217
|
+
direct,
|
|
18218
|
+
checkout,
|
|
18219
|
+
report
|
|
18220
|
+
})
|
|
18221
|
+
);
|
|
18222
|
+
const lazyTransport = lazyGitTransport(opts, direct);
|
|
17035
18223
|
const fastForward = opts.hook && guard.checkout.class === "matching" ? await runFastForward({
|
|
17036
18224
|
classification: guard.checkout,
|
|
17037
18225
|
repository: directRepository,
|
|
17038
18226
|
snapshot: void 0,
|
|
17039
18227
|
directCommitSha: direct.currentCommitSha,
|
|
17040
|
-
...
|
|
18228
|
+
...lazyTransport === void 0 ? {} : { gitTransport: lazyTransport.resolve },
|
|
18229
|
+
timings: hookPhases,
|
|
17041
18230
|
report: {
|
|
17042
18231
|
...report,
|
|
17043
18232
|
reportKind: status.current ? "current" : "behind",
|
|
@@ -17046,8 +18235,10 @@ async function syncOnce(opts) {
|
|
|
17046
18235
|
projectId: opts.project,
|
|
17047
18236
|
deadline: gitDeadline(),
|
|
17048
18237
|
optedOut: opts.fastForward === false,
|
|
17049
|
-
traceFile: traceFile(
|
|
18238
|
+
traceFile: traceFile(path29.dirname(getConfigPath()))
|
|
17050
18239
|
}) : null;
|
|
18240
|
+
hookTraced = fastForward !== null;
|
|
18241
|
+
lazyTransport?.rethrow();
|
|
17051
18242
|
return {
|
|
17052
18243
|
kind: "reported",
|
|
17053
18244
|
destination: existing,
|
|
@@ -17066,12 +18257,17 @@ async function syncOnce(opts) {
|
|
|
17066
18257
|
if (refused !== null) {
|
|
17067
18258
|
throw refused;
|
|
17068
18259
|
}
|
|
17069
|
-
const
|
|
17070
|
-
|
|
17071
|
-
|
|
17072
|
-
|
|
17073
|
-
|
|
17074
|
-
|
|
18260
|
+
const checkout = guard.checkout;
|
|
18261
|
+
const report = await timedPhase(
|
|
18262
|
+
"report",
|
|
18263
|
+
() => reportForClassification({
|
|
18264
|
+
classification: checkout,
|
|
18265
|
+
repository: published.repository,
|
|
18266
|
+
snapshot: published.snapshot,
|
|
18267
|
+
deadline: gitDeadline(),
|
|
18268
|
+
contentChanges: opts.hook ? "when-behind" : "always"
|
|
18269
|
+
})
|
|
18270
|
+
);
|
|
17075
18271
|
const fastForward = opts.hook && guard.checkout.class === "matching" && published.repository ? await runFastForward({
|
|
17076
18272
|
classification: guard.checkout,
|
|
17077
18273
|
repository: published.repository,
|
|
@@ -17080,7 +18276,8 @@ async function syncOnce(opts) {
|
|
|
17080
18276
|
projectId: opts.project,
|
|
17081
18277
|
deadline: gitDeadline(),
|
|
17082
18278
|
optedOut: opts.fastForward === false,
|
|
17083
|
-
traceFile: traceFile(
|
|
18279
|
+
traceFile: traceFile(path29.dirname(getConfigPath())),
|
|
18280
|
+
timings: hookPhases
|
|
17084
18281
|
}) : null;
|
|
17085
18282
|
return {
|
|
17086
18283
|
kind: "reported",
|
|
@@ -17800,7 +18997,7 @@ async function runInit(opts, format) {
|
|
|
17800
18997
|
);
|
|
17801
18998
|
cloned = true;
|
|
17802
18999
|
if (cloneFolder !== void 0 && checkout.class === "unmapped") {
|
|
17803
|
-
destination =
|
|
19000
|
+
destination = path29.join(checkout.toplevel, checkout.rootPath);
|
|
17804
19001
|
existing = await resolveExistingRoot(destination);
|
|
17805
19002
|
checkout = await classifyCheckout({
|
|
17806
19003
|
destination: existing,
|
|
@@ -17868,12 +19065,12 @@ async function runInit(opts, format) {
|
|
|
17868
19065
|
throw outcomeFailure("lessons-need-claude", {});
|
|
17869
19066
|
}
|
|
17870
19067
|
if (matching !== null) {
|
|
17871
|
-
const prefix =
|
|
19068
|
+
const prefix = path29.relative(matching.toplevel, root);
|
|
17872
19069
|
const relativeSetupPaths = [
|
|
17873
19070
|
...tools.tools.map(
|
|
17874
|
-
(tool) =>
|
|
19071
|
+
(tool) => path29.join(prefix, hookPathFor(tool)).replaceAll(path29.sep, "/")
|
|
17875
19072
|
),
|
|
17876
|
-
|
|
19073
|
+
path29.join(prefix, LOCK_DIRECTORY, LOCK_FILENAME).replaceAll(path29.sep, "/")
|
|
17877
19074
|
];
|
|
17878
19075
|
const tracked = await trackedPaths(
|
|
17879
19076
|
matching.toplevel,
|
|
@@ -17912,7 +19109,7 @@ async function runInit(opts, format) {
|
|
|
17912
19109
|
const applies = opts.reportOnly ? false : matching !== null ? true : Boolean(opts.apply);
|
|
17913
19110
|
const launcher = await resolveHookLauncher({
|
|
17914
19111
|
origin: deploymentOrigin(opts),
|
|
17915
|
-
configDirectory:
|
|
19112
|
+
configDirectory: path29.dirname(getConfigPath()),
|
|
17916
19113
|
lookup: { env: machine.env(), platform: machine.platform() },
|
|
17917
19114
|
bundle: isServedBundle() ? { scriptPath: bundleScriptPath() } : null
|
|
17918
19115
|
});
|
|
@@ -17955,6 +19152,10 @@ async function runInit(opts, format) {
|
|
|
17955
19152
|
tools: tools.tools,
|
|
17956
19153
|
projectId: opts.project,
|
|
17957
19154
|
origin: deploymentOrigin(opts),
|
|
19155
|
+
probe: gatewayProbeFor({
|
|
19156
|
+
origin: deploymentOrigin(opts),
|
|
19157
|
+
projectId: opts.project
|
|
19158
|
+
}),
|
|
17958
19159
|
cwd: toplevel ?? root,
|
|
17959
19160
|
home: machine.home(),
|
|
17960
19161
|
env: machine.env(),
|
|
@@ -17965,9 +19166,12 @@ async function runInit(opts, format) {
|
|
|
17965
19166
|
platform: machine.platform()
|
|
17966
19167
|
}
|
|
17967
19168
|
}),
|
|
17968
|
-
interactive: format !== "json" && canPrompt() && process.stdout.isTTY === true && !runningInCi()
|
|
19169
|
+
interactive: format !== "json" && canPrompt() && process.stdout.isTTY === true && !runningInCi(),
|
|
19170
|
+
checkSignIn: format !== "json"
|
|
17969
19171
|
});
|
|
17970
19172
|
const mcpComplete = !mcpRequested || mcpRegistrationComplete(mcp);
|
|
19173
|
+
const mcpFailed = mcpRequested && mcpSetupFailed(mcp);
|
|
19174
|
+
const mcpManual = mcpManualSteps(mcp);
|
|
17971
19175
|
const mcpAuthPending = mcpAuthenticationPending(mcp);
|
|
17972
19176
|
const first = hooks[0];
|
|
17973
19177
|
if (format === "json") {
|
|
@@ -17989,12 +19193,14 @@ async function runInit(opts, format) {
|
|
|
17989
19193
|
lessonsHook: Boolean(opts.lessons),
|
|
17990
19194
|
mcpRequested,
|
|
17991
19195
|
mcpComplete,
|
|
19196
|
+
manualSteps: mcpManual,
|
|
17992
19197
|
mcpAuthenticationPending: mcpAuthPending,
|
|
17993
19198
|
mcp: mcp.map((result) => ({
|
|
17994
19199
|
tool: result.tool,
|
|
17995
19200
|
name: result.name,
|
|
17996
19201
|
url: result.url,
|
|
17997
19202
|
outcome: result.outcome.kind,
|
|
19203
|
+
status: result.status,
|
|
17998
19204
|
login: result.login,
|
|
17999
19205
|
registerLine: result.registerLine,
|
|
18000
19206
|
loginLine: result.loginLine
|
|
@@ -18006,7 +19212,7 @@ async function runInit(opts, format) {
|
|
|
18006
19212
|
},
|
|
18007
19213
|
{ format: "json" }
|
|
18008
19214
|
);
|
|
18009
|
-
if (
|
|
19215
|
+
if (mcpFailed) {
|
|
18010
19216
|
throw new CliFailure(
|
|
18011
19217
|
"Coding tool setup is incomplete: the Fabric MCP server was not registered for every selected tool. Complete the action shown above, then run init again.",
|
|
18012
19218
|
7
|
|
@@ -18059,7 +19265,10 @@ async function runInit(opts, format) {
|
|
|
18059
19265
|
if (launcher.warning !== null) {
|
|
18060
19266
|
line(launcher.warning);
|
|
18061
19267
|
}
|
|
18062
|
-
for (const mcpLine of
|
|
19268
|
+
for (const mcpLine of [
|
|
19269
|
+
...agentMcpLines(mcp),
|
|
19270
|
+
...agentMcpSummaryLines(mcp)
|
|
19271
|
+
]) {
|
|
18063
19272
|
line(mcpLine);
|
|
18064
19273
|
}
|
|
18065
19274
|
if (mcpAuthPending) {
|
|
@@ -18072,7 +19281,7 @@ async function runInit(opts, format) {
|
|
|
18072
19281
|
outcomeLine("cloned-into", {
|
|
18073
19282
|
where: folderForCommand(
|
|
18074
19283
|
opts.dest,
|
|
18075
|
-
|
|
19284
|
+
path29.relative(
|
|
18076
19285
|
await resolveExistingRoot(destinationOf(opts)),
|
|
18077
19286
|
root
|
|
18078
19287
|
)
|
|
@@ -18080,7 +19289,7 @@ async function runInit(opts, format) {
|
|
|
18080
19289
|
})
|
|
18081
19290
|
);
|
|
18082
19291
|
}
|
|
18083
|
-
if (
|
|
19292
|
+
if (mcpFailed) {
|
|
18084
19293
|
line(
|
|
18085
19294
|
"The session hook was set up, but the Fabric MCP server was not registered for every selected tool."
|
|
18086
19295
|
);
|
|
@@ -18118,8 +19327,8 @@ async function runLessonPromptCommand(opts) {
|
|
|
18118
19327
|
stdinText = await readAllStdin(process.stdin);
|
|
18119
19328
|
} catch {
|
|
18120
19329
|
}
|
|
18121
|
-
const markerDir =
|
|
18122
|
-
|
|
19330
|
+
const markerDir = path29.join(
|
|
19331
|
+
path29.dirname(getConfigPath()),
|
|
18123
19332
|
"lesson-prompts"
|
|
18124
19333
|
);
|
|
18125
19334
|
const result = await runLessonPrompt({
|
|
@@ -19021,6 +20230,8 @@ function buildWorkspacesCommand() {
|
|
|
19021
20230
|
}
|
|
19022
20231
|
|
|
19023
20232
|
// src/bin/fabric.ts
|
|
20233
|
+
hookTiming.processStartedAt = Math.round(Date.now() - process.uptime() * 1e3);
|
|
20234
|
+
hookTiming.exitWhenDone = true;
|
|
19024
20235
|
var program = new Command22();
|
|
19025
20236
|
program.name("fabric").description("Fabric AI platform CLI").version(package_default.version).option(
|
|
19026
20237
|
"--format <format>",
|