@hasna/hooks 0.12.3 → 0.12.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -2
- package/bin/hooks-mcp.js +2013 -847
- package/bin/index.js +2489 -1255
- package/bin/native-safety-entry.js +15 -15
- package/bin/native-safety-worker.cjs +20 -0
- package/dist/index.js +2267 -1046
- package/dist/lib/claude-skills-coordination.d.ts +25 -3
- package/dist/lib/codex-safety-trust.d.ts +9 -2
- package/dist/lib/codex-settings.d.ts +6 -3
- package/dist/lib/installer.d.ts +9 -1
- package/dist/lib/native-safety-registration.d.ts +8 -2
- package/dist/native-safety.d.ts +4 -1
- package/dist/native-safety.js +2 -0
- package/hooks/codewith-native-common.ts +3 -3
- package/hooks/hook-signed-link-guard/src/hook.ts +5 -2
- package/hooks/hook-trash-guard/src/hook.ts +7 -4
- package/hooks/hook-workspace-repos-guard/src/hook.ts +8 -5
- package/hooks/native-safety-composite.ts +75 -0
- package/hooks/native-safety-entry.ts +7 -53
- package/hooks/native-safety-worker.ts +19 -0
- package/package.json +5 -4
- package/scripts/build-native-worker.ts +152 -0
package/bin/hooks-mcp.js
CHANGED
|
@@ -15,7 +15,6 @@ var __export = (target, all) => {
|
|
|
15
15
|
});
|
|
16
16
|
};
|
|
17
17
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
18
|
-
var __require = import.meta.require;
|
|
19
18
|
|
|
20
19
|
// src/lib/app-home.ts
|
|
21
20
|
import { existsSync } from "fs";
|
|
@@ -1642,14 +1641,14 @@ function assertUsableCredential(appName, source, value) {
|
|
|
1642
1641
|
return;
|
|
1643
1642
|
throw new CredentialResolutionError(appName, `The credential from ${source} contains characters that cannot be sent in an HTTP header ` + `(a control character or non-ASCII byte). A file written with CR-only line endings is the usual ` + `cause. Rewrite that credential file with one LF-terminated KEY=value line. ` + `The value is not shown here, and is deliberately never logged.`, [source]);
|
|
1644
1643
|
}
|
|
1645
|
-
function sealCredential(
|
|
1646
|
-
const { apiKey } =
|
|
1644
|
+
function sealCredential(fields2) {
|
|
1645
|
+
const { apiKey } = fields2;
|
|
1647
1646
|
const visible = {
|
|
1648
|
-
tier:
|
|
1649
|
-
source:
|
|
1650
|
-
deliberate:
|
|
1651
|
-
diskCandidates: Object.freeze([...
|
|
1652
|
-
warning:
|
|
1647
|
+
tier: fields2.tier,
|
|
1648
|
+
source: fields2.source,
|
|
1649
|
+
deliberate: fields2.deliberate,
|
|
1650
|
+
diskCandidates: Object.freeze([...fields2.diskCandidates]),
|
|
1651
|
+
warning: fields2.warning
|
|
1653
1652
|
};
|
|
1654
1653
|
const sealed = { ...visible };
|
|
1655
1654
|
Object.defineProperty(sealed, "apiKey", {
|
|
@@ -1658,9 +1657,9 @@ function sealCredential(fields) {
|
|
|
1658
1657
|
writable: false,
|
|
1659
1658
|
configurable: false
|
|
1660
1659
|
});
|
|
1661
|
-
if (
|
|
1660
|
+
if (fields2.pointerVaultKey !== undefined) {
|
|
1662
1661
|
Object.defineProperty(sealed, "pointerVaultKey", {
|
|
1663
|
-
value:
|
|
1662
|
+
value: fields2.pointerVaultKey,
|
|
1664
1663
|
enumerable: false,
|
|
1665
1664
|
writable: false,
|
|
1666
1665
|
configurable: false
|
|
@@ -2258,8 +2257,8 @@ var init_local_opt_in = __esm(() => {
|
|
|
2258
2257
|
});
|
|
2259
2258
|
|
|
2260
2259
|
// src/lib/hook-env.ts
|
|
2261
|
-
import { dirname as
|
|
2262
|
-
import { existsSync as
|
|
2260
|
+
import { dirname as dirname12, isAbsolute as isAbsolute9 } from "path";
|
|
2261
|
+
import { existsSync as existsSync14, realpathSync as realpathSync10, statSync } from "fs";
|
|
2263
2262
|
import { tmpdir } from "os";
|
|
2264
2263
|
function isDeniedEnvName(name) {
|
|
2265
2264
|
const upper = name.toUpperCase();
|
|
@@ -2278,7 +2277,7 @@ function isBashFunctionEnvName(name) {
|
|
|
2278
2277
|
return name.startsWith("BASH_FUNC_");
|
|
2279
2278
|
}
|
|
2280
2279
|
function isUnsafePathEntry(entry, home) {
|
|
2281
|
-
if (entry === "" || !
|
|
2280
|
+
if (entry === "" || !isAbsolute9(entry))
|
|
2282
2281
|
return true;
|
|
2283
2282
|
if (home && (entry === home || entry.startsWith(home + "/")))
|
|
2284
2283
|
return true;
|
|
@@ -2286,16 +2285,16 @@ function isUnsafePathEntry(entry, home) {
|
|
|
2286
2285
|
return true;
|
|
2287
2286
|
if (entry === "/var/tmp" || entry.startsWith("/var/tmp/"))
|
|
2288
2287
|
return true;
|
|
2289
|
-
const
|
|
2288
|
+
const canonical6 = (path) => {
|
|
2290
2289
|
try {
|
|
2291
|
-
return
|
|
2290
|
+
return realpathSync10(path);
|
|
2292
2291
|
} catch {
|
|
2293
2292
|
return path;
|
|
2294
2293
|
}
|
|
2295
2294
|
};
|
|
2296
|
-
const resolved =
|
|
2295
|
+
const resolved = canonical6(entry);
|
|
2297
2296
|
for (const root of [home, tmpdir(), "/tmp", "/var/tmp"].filter((value) => Boolean(value))) {
|
|
2298
|
-
const blocked =
|
|
2297
|
+
const blocked = canonical6(root);
|
|
2299
2298
|
if (resolved === blocked || resolved.startsWith(blocked + "/"))
|
|
2300
2299
|
return true;
|
|
2301
2300
|
}
|
|
@@ -2307,9 +2306,9 @@ function isUnsafePathEntry(entry, home) {
|
|
|
2307
2306
|
}
|
|
2308
2307
|
function sanitizeHookPath(pathValue, home) {
|
|
2309
2308
|
const baseline = PATH_BASELINE.slice();
|
|
2310
|
-
if (process.platform === "darwin" &&
|
|
2309
|
+
if (process.platform === "darwin" && existsSync14("/opt/homebrew/bin"))
|
|
2311
2310
|
baseline.push("/opt/homebrew/bin");
|
|
2312
|
-
const bunDir =
|
|
2311
|
+
const bunDir = dirname12(process.execPath);
|
|
2313
2312
|
const seen = new Set;
|
|
2314
2313
|
const out = [];
|
|
2315
2314
|
for (const dir of baseline) {
|
|
@@ -2434,9 +2433,9 @@ __export(exports_run, {
|
|
|
2434
2433
|
});
|
|
2435
2434
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
2436
2435
|
import { rmSync as rmSync2, writeFileSync as writeFileSync10 } from "fs";
|
|
2437
|
-
import { basename as
|
|
2436
|
+
import { basename as basename6, dirname as dirname18, isAbsolute as isAbsolute17, join as join13 } from "path";
|
|
2438
2437
|
function extensionOf(scriptPath) {
|
|
2439
|
-
const base =
|
|
2438
|
+
const base = basename6(scriptPath);
|
|
2440
2439
|
const dot = base.lastIndexOf(".");
|
|
2441
2440
|
return dot > 0 ? base.slice(dot + 1) : "ts";
|
|
2442
2441
|
}
|
|
@@ -2448,9 +2447,9 @@ function shebangInterpreter(firstLine) {
|
|
|
2448
2447
|
return null;
|
|
2449
2448
|
const parts = rest.split(/\s+/);
|
|
2450
2449
|
if (parts[0] === "env" || parts[0] === "/usr/bin/env") {
|
|
2451
|
-
return parts[1] && parts[1] !== "-S" ?
|
|
2450
|
+
return parts[1] && parts[1] !== "-S" ? basename6(parts[1]) : parts[2] ? basename6(parts[2]) : null;
|
|
2452
2451
|
}
|
|
2453
|
-
return
|
|
2452
|
+
return basename6(parts[0]);
|
|
2454
2453
|
}
|
|
2455
2454
|
function interpreterFor(name, scriptPath, content) {
|
|
2456
2455
|
const ext = extensionOf(scriptPath);
|
|
@@ -2503,9 +2502,9 @@ async function readPipeWithDeadline(stream, deadlineMs) {
|
|
|
2503
2502
|
return chunks.join("");
|
|
2504
2503
|
}
|
|
2505
2504
|
async function executeVerifiedScript(options) {
|
|
2506
|
-
const scriptDir =
|
|
2505
|
+
const scriptDir = dirname18(options.scriptPath);
|
|
2507
2506
|
const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
|
|
2508
|
-
const tempPath =
|
|
2507
|
+
const tempPath = join13(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
|
|
2509
2508
|
let proc = null;
|
|
2510
2509
|
let timedOut = false;
|
|
2511
2510
|
const timeoutTimer = options.timeout ? setTimeout(() => {
|
|
@@ -2664,29 +2663,29 @@ function resolveEventType(inputEvent, fallbackEvent) {
|
|
|
2664
2663
|
return fromInput;
|
|
2665
2664
|
return normalizeEventType(fallbackEvent ?? null);
|
|
2666
2665
|
}
|
|
2667
|
-
function recordHookRun(
|
|
2668
|
-
const eventType =
|
|
2666
|
+
function recordHookRun(record4) {
|
|
2667
|
+
const eventType = record4.eventType ? normalizeEventType(record4.eventType) : null;
|
|
2669
2668
|
if (!eventType) {
|
|
2670
|
-
process.stderr.write(`[hooks db-writer] skipped event for '${
|
|
2669
|
+
process.stderr.write(`[hooks db-writer] skipped event for '${record4.hookName}': unsupported event type '${String(record4.eventType)}'
|
|
2671
2670
|
`);
|
|
2672
2671
|
return;
|
|
2673
2672
|
}
|
|
2674
2673
|
const metadata = {
|
|
2675
|
-
...
|
|
2676
|
-
version:
|
|
2677
|
-
sha256:
|
|
2678
|
-
exit_code:
|
|
2674
|
+
...record4.metadata ?? {},
|
|
2675
|
+
version: record4.version ?? null,
|
|
2676
|
+
sha256: record4.sha256 ?? null,
|
|
2677
|
+
exit_code: record4.exitCode
|
|
2679
2678
|
};
|
|
2680
2679
|
writeHookEvent({
|
|
2681
|
-
session_id:
|
|
2682
|
-
hook_name:
|
|
2680
|
+
session_id: record4.sessionId ?? "cli",
|
|
2681
|
+
hook_name: record4.hookName,
|
|
2683
2682
|
event_type: eventType,
|
|
2684
|
-
tool_name:
|
|
2685
|
-
tool_input:
|
|
2686
|
-
result:
|
|
2687
|
-
error:
|
|
2688
|
-
duration_ms:
|
|
2689
|
-
project_dir:
|
|
2683
|
+
tool_name: record4.toolName ?? null,
|
|
2684
|
+
tool_input: record4.toolInput !== undefined ? JSON.stringify(record4.toolInput) : null,
|
|
2685
|
+
result: record4.result ?? null,
|
|
2686
|
+
error: record4.error ?? null,
|
|
2687
|
+
duration_ms: record4.durationMs,
|
|
2688
|
+
project_dir: record4.projectDir ?? null,
|
|
2690
2689
|
metadata: JSON.stringify(metadata)
|
|
2691
2690
|
});
|
|
2692
2691
|
}
|
|
@@ -2713,15 +2712,15 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
|
2713
2712
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2714
2713
|
import { z as z3 } from "zod";
|
|
2715
2714
|
init_registry();
|
|
2716
|
-
import { existsSync as
|
|
2717
|
-
import { join as
|
|
2715
|
+
import { existsSync as existsSync15, readFileSync as readFileSync9 } from "fs";
|
|
2716
|
+
import { join as join18, dirname as dirname20 } from "path";
|
|
2718
2717
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2719
2718
|
|
|
2720
2719
|
// src/lib/installer.ts
|
|
2721
2720
|
init_registry();
|
|
2722
|
-
import { existsSync as
|
|
2723
|
-
import { join as
|
|
2724
|
-
import { homedir as
|
|
2721
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync7, readdirSync as readdirSync4, realpathSync as realpathSync8, rmdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
2722
|
+
import { join as join11, dirname as dirname7, basename as basename3, resolve as resolve16 } from "path";
|
|
2723
|
+
import { homedir as homedir7 } from "os";
|
|
2725
2724
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2726
2725
|
|
|
2727
2726
|
// src/lib/readiness-process.ts
|
|
@@ -3332,16 +3331,16 @@ function assertSettingsPreimage(raw, expected) {
|
|
|
3332
3331
|
throw new Error("Hook settings changed since planning; no settings were written.");
|
|
3333
3332
|
}
|
|
3334
3333
|
}
|
|
3335
|
-
function writeCodexSettings(path, settings, expectedSha256) {
|
|
3334
|
+
function writeCodexSettings(path, settings, expectedSha256, beforeWrite) {
|
|
3336
3335
|
const snapshot = snapshots.get(settings);
|
|
3337
3336
|
if (!snapshot || snapshot.path !== path)
|
|
3338
3337
|
throw new Error("Codex settings require a complete current read before writing.");
|
|
3339
3338
|
assertSettingsPreimage(snapshot.raw, expectedSha256);
|
|
3340
|
-
writeHookSettingsPreimage(path, settings, snapshot.raw);
|
|
3339
|
+
writeHookSettingsPreimage(path, settings, snapshot.raw, beforeWrite);
|
|
3341
3340
|
snapshots.set(settings, { path, raw: JSON.stringify(settings, null, 2) + `
|
|
3342
3341
|
` });
|
|
3343
3342
|
}
|
|
3344
|
-
function writeHookSettingsPreimage(path, settings, before) {
|
|
3343
|
+
function writeHookSettingsPreimage(path, settings, before, beforeWrite) {
|
|
3345
3344
|
mkdirSync4(dirname5(path), { recursive: true, mode: 448 });
|
|
3346
3345
|
const lock = `${path}.hooks-write.lock`;
|
|
3347
3346
|
let fd;
|
|
@@ -3359,6 +3358,7 @@ function writeHookSettingsPreimage(path, settings, before) {
|
|
|
3359
3358
|
writeFileSync3(temp, next, { flag: "wx", mode: 384 });
|
|
3360
3359
|
if (current(path) !== before)
|
|
3361
3360
|
throw new Error("Hook settings changed since reading; re-read before retrying.");
|
|
3361
|
+
beforeWrite?.();
|
|
3362
3362
|
renameSync2(temp, path);
|
|
3363
3363
|
} finally {
|
|
3364
3364
|
if (existsSync9(temp))
|
|
@@ -3370,8 +3370,11 @@ function writeHookSettingsPreimage(path, settings, before) {
|
|
|
3370
3370
|
|
|
3371
3371
|
// src/lib/claude-skills-coordination.ts
|
|
3372
3372
|
import { createHash as createHash6 } from "crypto";
|
|
3373
|
+
import { existsSync as existsSync11, lstatSync as lstatSync7 } from "fs";
|
|
3374
|
+
import { homedir as homedir6 } from "os";
|
|
3375
|
+
import { join as join10, resolve as resolve7 } from "path";
|
|
3373
3376
|
|
|
3374
|
-
// ../../node_modules/.bun/@hasna+skills@0.
|
|
3377
|
+
// ../../node_modules/.bun/@hasna+skills@0.10.28+a00d3064559565bb/node_modules/@hasna/skills/dist/index.js
|
|
3375
3378
|
import { existsSync as existsSync10 } from "fs";
|
|
3376
3379
|
import { homedir as homedir5 } from "os";
|
|
3377
3380
|
import { join as join9, resolve as resolve3 } from "path";
|
|
@@ -3381,10 +3384,10 @@ import { createHash as createHash5 } from "crypto";
|
|
|
3381
3384
|
import { existsSync as existsSync52, readdirSync as readdirSync3, readFileSync as readFileSync42, statSync as statSync3 } from "fs";
|
|
3382
3385
|
import { join as join52, sep as sep2 } from "path";
|
|
3383
3386
|
import { basename as basename2, isAbsolute as isAbsolute22, resolve as resolve22 } from "path";
|
|
3384
|
-
import { createHash as
|
|
3385
|
-
import { lstatSync as
|
|
3386
|
-
import { basename as
|
|
3387
|
-
import { isDeepStrictEqual as
|
|
3387
|
+
import { createHash as createHash18 } from "crypto";
|
|
3388
|
+
import { lstatSync as lstatSync21, readFileSync as readFileSync17, readdirSync as readdirSync10, realpathSync as realpathSync7 } from "fs";
|
|
3389
|
+
import { basename as basename9, dirname as dirname17, isAbsolute as isAbsolute16, join as join27, resolve as resolve15, sep as sep6 } from "path";
|
|
3390
|
+
import { isDeepStrictEqual as isDeepStrictEqual5 } from "util";
|
|
3388
3391
|
import { dirname as dirname32, isAbsolute as isAbsolute32, join as join72, resolve as resolve32 } from "path";
|
|
3389
3392
|
import { lstatSync as lstatSync32, readFileSync as readFileSync62, opendirSync } from "fs";
|
|
3390
3393
|
import { createHash as createHash22 } from "crypto";
|
|
@@ -3396,34 +3399,42 @@ import { dirname as dirname52, isAbsolute as isAbsolute5, resolve as resolve5 }
|
|
|
3396
3399
|
import { createHash as createHash42 } from "crypto";
|
|
3397
3400
|
import { lstatSync as lstatSync6, readlinkSync } from "fs";
|
|
3398
3401
|
import { dirname as dirname6, isAbsolute as isAbsolute6, join as join92, parse, resolve as resolve6, sep as sep22 } from "path";
|
|
3399
|
-
import { closeSync as
|
|
3400
|
-
import { basename as
|
|
3401
|
-
import { isAbsolute as
|
|
3402
|
+
import { closeSync as closeSync10, constants as constants9, fstatSync as fstatSync10, lstatSync as lstatSync18, openSync as openSync10, opendirSync as opendirSync5, readSync as readSync8 } from "fs";
|
|
3403
|
+
import { basename as basename7, dirname as dirname14, isAbsolute as isAbsolute14, join as join26, resolve as resolve13 } from "path";
|
|
3404
|
+
import { isAbsolute as isAbsolute13, join as join25, resolve as resolve12 } from "path";
|
|
3402
3405
|
import { createRequire } from "module";
|
|
3403
|
-
import { createHash as createHash10, randomUUID as randomUUID2 } from "crypto";
|
|
3404
|
-
import { existsSync as existsSync82, lstatSync as lstatSync14, statSync as statSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync11, readdirSync as readdirSync7, opendirSync as opendirSync3, readlinkSync as readlinkSync2, realpathSync as realpathSync32, renameSync as renameSync22, rmdirSync as rmdirSync2, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, chmodSync, openSync as openSync10, closeSync as closeSync10, fsyncSync as fsyncSync2, fstatSync as fstatSync10, readSync as readSync6, constants as constants7, linkSync } from "fs";
|
|
3405
|
-
import { dirname as dirname10, join as join16, resolve as resolve9, relative as relative4, isAbsolute as isAbsolute9, sep as sep4 } from "path";
|
|
3406
|
-
import { homedir as homedir22 } from "os";
|
|
3407
|
-
import { closeSync as closeSync8, constants as constants5, fstatSync as fstatSync8, lstatSync as lstatSync11, openSync as openSync8, readSync as readSync4 } from "fs";
|
|
3408
|
-
import { join as join14 } from "path";
|
|
3409
3406
|
import { createHash as createHash8 } from "crypto";
|
|
3410
|
-
import { lstatSync as
|
|
3411
|
-
import { join as join15 } from "path";
|
|
3412
|
-
import { createHash as createHash9 } from "crypto";
|
|
3413
|
-
import { closeSync as closeSync9, constants as constants6, fstatSync as fstatSync9, lstatSync as lstatSync13, openSync as openSync9, readSync as readSync5 } from "fs";
|
|
3407
|
+
import { closeSync as closeSync6, constants as constants5, fstatSync as fstatSync6, lstatSync as lstatSync11, openSync as openSync6, readSync as readSync4 } from "fs";
|
|
3414
3408
|
import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute8, resolve as resolve8 } from "path";
|
|
3409
|
+
import { basename as basename5, dirname as dirname10, join as join14, relative as relative4, resolve as resolve9, sep as sep4 } from "path";
|
|
3410
|
+
import { lstatSync as lstatSync12, readdirSync as readdirSync6, readlinkSync as readlinkSync3, realpathSync as realpathSync5 } from "fs";
|
|
3411
|
+
import { realpathSync as realpathSync4 } from "fs";
|
|
3412
|
+
import { isAbsolute as isAbsolute10, normalize as normalize2 } from "path";
|
|
3413
|
+
import { createHash as createHash9 } from "crypto";
|
|
3414
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
3415
|
+
import { createHash as createHash10 } from "crypto";
|
|
3416
|
+
import { createHash as createHash12, randomUUID as randomUUID2 } from "crypto";
|
|
3417
|
+
import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
|
|
3418
|
+
import { existsSync as existsSync82, lstatSync as lstatSync15, statSync as statSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync12, readdirSync as readdirSync8, opendirSync as opendirSync3, readlinkSync as readlinkSync4, realpathSync as realpathSync6, renameSync as renameSync22, rmdirSync as rmdirSync2, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, chmodSync, openSync as openSync8, closeSync as closeSync8, fsyncSync as fsyncSync2, fstatSync as fstatSync8, readSync as readSync6, constants as constants7, linkSync } from "fs";
|
|
3419
|
+
import { dirname as dirname11, join as join17, resolve as resolve10, relative as relative5, isAbsolute as isAbsolute11, sep as sep5 } from "path";
|
|
3420
|
+
import { homedir as homedir22 } from "os";
|
|
3421
|
+
import { closeSync as closeSync7, constants as constants6, fstatSync as fstatSync7, lstatSync as lstatSync13, openSync as openSync7, readSync as readSync5 } from "fs";
|
|
3422
|
+
import { join as join15 } from "path";
|
|
3415
3423
|
import { createHash as createHash11 } from "crypto";
|
|
3416
|
-
import {
|
|
3417
|
-
import {
|
|
3424
|
+
import { lstatSync as lstatSync14, readFileSync as readFileSync11, readdirSync as readdirSync7 } from "fs";
|
|
3425
|
+
import { join as join16 } from "path";
|
|
3418
3426
|
import { createHash as createHash13 } from "crypto";
|
|
3419
|
-
import {
|
|
3420
|
-
import {
|
|
3427
|
+
import { constants as constants8, closeSync as closeSync9, fchmodSync, fstatSync as fstatSync9, fsyncSync as fsyncSync3, linkSync as linkSync2, lstatSync as lstatSync17, mkdirSync as mkdirSync8, openSync as openSync9, opendirSync as opendirSync4, readSync as readSync7, renameSync as renameSync4, rmdirSync as rmdirSync3, rmSync as rmSync3, unlinkSync as unlinkSync4, writeSync as writeSync2 } from "fs";
|
|
3428
|
+
import { dirname as dirname13, isAbsolute as isAbsolute12, join as join23, parse as parse5, resolve as resolve11 } from "path";
|
|
3421
3429
|
import { createHash as createHash15 } from "crypto";
|
|
3422
|
-
import { closeSync as
|
|
3423
|
-
import {
|
|
3424
|
-
import {
|
|
3425
|
-
import {
|
|
3426
|
-
import { createHash as
|
|
3430
|
+
import { closeSync as closeSync11, constants as constants10, fstatSync as fstatSync11, lstatSync as lstatSync19, openSync as openSync11, readSync as readSync9 } from "fs";
|
|
3431
|
+
import { basename as basename8, dirname as dirname15, isAbsolute as isAbsolute15, resolve as resolve14 } from "path";
|
|
3432
|
+
import { createHash as createHash17 } from "crypto";
|
|
3433
|
+
import { closeSync as closeSync12, constants as constants11, fstatSync as fstatSync12, fsyncSync as fsyncSync4, lstatSync as lstatSync20, openSync as openSync12, readSync as readSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
3434
|
+
import { createHash as createHash16 } from "crypto";
|
|
3435
|
+
import { dirname as dirname16 } from "path";
|
|
3436
|
+
import { isAbsolute as isAbsolute18 } from "path";
|
|
3437
|
+
import { createHash as createHash222 } from "crypto";
|
|
3427
3438
|
var __defProp2 = Object.defineProperty;
|
|
3428
3439
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
3429
3440
|
var __returnValue2 = (v) => v;
|
|
@@ -3439,7 +3450,7 @@ var __export2 = (target, all) => {
|
|
|
3439
3450
|
set: __exportSetter2.bind(all, name)
|
|
3440
3451
|
});
|
|
3441
3452
|
};
|
|
3442
|
-
var
|
|
3453
|
+
var __require = import.meta.require;
|
|
3443
3454
|
var require_identity = __commonJS((exports) => {
|
|
3444
3455
|
var ALIAS = Symbol.for("yaml.alias");
|
|
3445
3456
|
var DOC = Symbol.for("yaml.document");
|
|
@@ -4977,7 +4988,7 @@ ${ctx.indent}`;
|
|
|
4977
4988
|
exports.stringifyPair = stringifyPair;
|
|
4978
4989
|
});
|
|
4979
4990
|
var require_log = __commonJS((exports) => {
|
|
4980
|
-
var node_process =
|
|
4991
|
+
var node_process = __require("process");
|
|
4981
4992
|
function debug(logLevel, ...messages) {
|
|
4982
4993
|
if (logLevel === "debug")
|
|
4983
4994
|
console.log(...messages);
|
|
@@ -5778,7 +5789,7 @@ var require_schema2 = __commonJS((exports) => {
|
|
|
5778
5789
|
exports.schema = schema;
|
|
5779
5790
|
});
|
|
5780
5791
|
var require_binary = __commonJS((exports) => {
|
|
5781
|
-
var node_buffer =
|
|
5792
|
+
var node_buffer = __require("buffer");
|
|
5782
5793
|
var Scalar = require_Scalar();
|
|
5783
5794
|
var stringifyString = require_stringifyString();
|
|
5784
5795
|
var binary = {
|
|
@@ -8085,7 +8096,7 @@ var require_compose_doc = __commonJS((exports) => {
|
|
|
8085
8096
|
exports.composeDoc = composeDoc;
|
|
8086
8097
|
});
|
|
8087
8098
|
var require_composer = __commonJS((exports) => {
|
|
8088
|
-
var node_process =
|
|
8099
|
+
var node_process = __require("process");
|
|
8089
8100
|
var directives = require_directives();
|
|
8090
8101
|
var Document = require_Document();
|
|
8091
8102
|
var errors = require_errors();
|
|
@@ -9296,7 +9307,7 @@ var require_line_counter = __commonJS((exports) => {
|
|
|
9296
9307
|
exports.LineCounter = LineCounter;
|
|
9297
9308
|
});
|
|
9298
9309
|
var require_parser = __commonJS((exports) => {
|
|
9299
|
-
var node_process =
|
|
9310
|
+
var node_process = __require("process");
|
|
9300
9311
|
var cst = require_cst();
|
|
9301
9312
|
var lexer = require_lexer();
|
|
9302
9313
|
function includesToken(list, type) {
|
|
@@ -10846,6 +10857,33 @@ function assertAgentPolicyCollections(policy) {
|
|
|
10846
10857
|
for (const key of ["agent", "home", "alias", "target", "link", "aliasIdentity", "targetIdentity"])
|
|
10847
10858
|
text(alias[key]);
|
|
10848
10859
|
}
|
|
10860
|
+
if (bridge.codexPluginSkills !== undefined)
|
|
10861
|
+
for (const control of array(bridge.codexPluginSkills, 4096)) {
|
|
10862
|
+
requireBound(object(control) && Object.keys(control).every((key) => ["name", "pluginId", "namespace", "pluginParent", "manifestSha256", "appSha256", "mcpSha256", "remotePluginId"].includes(key)));
|
|
10863
|
+
text(control.name, 129);
|
|
10864
|
+
text(control.pluginId, 1024);
|
|
10865
|
+
text(control.namespace, 64);
|
|
10866
|
+
text(control.pluginParent);
|
|
10867
|
+
requireBound(isAbsolute22(control.pluginParent) && resolve22(control.pluginParent) === control.pluginParent && /^[a-f0-9]{64}$/.test(control.manifestSha256));
|
|
10868
|
+
if (control.remotePluginId !== undefined)
|
|
10869
|
+
requireBound(typeof control.remotePluginId === "string" && /^[A-Za-z0-9_~-]{1,1024}$/.test(control.remotePluginId));
|
|
10870
|
+
if (control.mcpSha256 !== undefined)
|
|
10871
|
+
requireBound(typeof control.mcpSha256 === "string" && /^[a-f0-9]{64}$/.test(control.mcpSha256));
|
|
10872
|
+
if (control.appSha256 !== undefined)
|
|
10873
|
+
requireBound(typeof control.appSha256 === "string" && /^[a-f0-9]{64}$/.test(control.appSha256));
|
|
10874
|
+
}
|
|
10875
|
+
if (bridge.codexInactivePlugins !== undefined) {
|
|
10876
|
+
const controls = array(bridge.codexInactivePlugins, 4096), ids = new Set;
|
|
10877
|
+
requireBound(controls.length === 0 || object(bridge.codexPluginSkillReview) && bridge.codexPluginSkillReview.version === "codex-cli 0.160.0" && typeof bridge.codexPluginSkillReview.catalogSha256 === "string" && /^[a-f0-9]{64}$/.test(bridge.codexPluginSkillReview.catalogSha256));
|
|
10878
|
+
for (const control of controls) {
|
|
10879
|
+
requireBound(object(control) && Object.keys(control).every((key) => ["pluginId", "namespace", "pluginParent", "sourceType", "sourceSha256"].includes(key)));
|
|
10880
|
+
text(control.pluginId, 1024);
|
|
10881
|
+
text(control.namespace, 64);
|
|
10882
|
+
text(control.pluginParent);
|
|
10883
|
+
requireBound(!ids.has(control.pluginId) && isAbsolute22(control.pluginParent) && resolve22(control.pluginParent) === control.pluginParent && ["local", "git", "npm"].includes(control.sourceType) && typeof control.sourceSha256 === "string" && /^[a-f0-9]{64}$/.test(control.sourceSha256));
|
|
10884
|
+
ids.add(control.pluginId);
|
|
10885
|
+
}
|
|
10886
|
+
}
|
|
10849
10887
|
if (bridge.disabledBuiltins !== undefined)
|
|
10850
10888
|
for (const builtin of array(bridge.disabledBuiltins, AGENT_POLICY_LIMITS.builtinNames)) {
|
|
10851
10889
|
requireBound(object(builtin));
|
|
@@ -10858,6 +10896,25 @@ function assertAgentPolicyCollections(policy) {
|
|
|
10858
10896
|
text(agent, 128);
|
|
10859
10897
|
requireBound(object(value));
|
|
10860
10898
|
text(value.agent, 128);
|
|
10899
|
+
if (value.codexInstallationInputs !== undefined) {
|
|
10900
|
+
const proof = record(value.codexInstallationInputs, 4), inputs = array(proof.plugins, 4096), ids = new Set;
|
|
10901
|
+
requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && proof.version === "codex-cli 0.160.0" && typeof proof.catalogSha256 === "string" && /^[a-f0-9]{64}$/.test(proof.catalogSha256) && inputs.length > 0 && object(bridge.codexPluginSkillReview) && bridge.codexPluginSkillReview.version === proof.version && bridge.codexPluginSkillReview.catalogSha256 === proof.catalogSha256);
|
|
10902
|
+
for (const input of inputs) {
|
|
10903
|
+
requireBound(object(input) && Object.keys(input).every((key) => ["pluginId", "namespace", "pluginParent", "sourceRoot", "sourceSha256"].includes(key)));
|
|
10904
|
+
text(input.pluginId, 1024);
|
|
10905
|
+
text(input.namespace, 64);
|
|
10906
|
+
text(input.pluginParent);
|
|
10907
|
+
text(input.sourceRoot);
|
|
10908
|
+
requireBound(!ids.has(input.pluginId) && [input.pluginParent, input.sourceRoot].every((path) => isAbsolute22(path) && resolve22(path) === path) && typeof input.sourceSha256 === "string" && /^[a-f0-9]{64}$/.test(input.sourceSha256));
|
|
10909
|
+
ids.add(input.pluginId);
|
|
10910
|
+
}
|
|
10911
|
+
if (proof.directories !== undefined)
|
|
10912
|
+
for (const directory of array(proof.directories, AGENT_POLICY_LIMITS.discoveryDirectories)) {
|
|
10913
|
+
requireBound(object(directory));
|
|
10914
|
+
text(directory.path);
|
|
10915
|
+
requireBound(directory.sha256 === null || typeof directory.sha256 === "string" && /^[a-f0-9]{64}$/.test(directory.sha256));
|
|
10916
|
+
}
|
|
10917
|
+
}
|
|
10861
10918
|
for (const root of array(value.roots, AGENT_POLICY_LIMITS.discoveryRoots))
|
|
10862
10919
|
text(root);
|
|
10863
10920
|
for (const source of array(value.sources, AGENT_POLICY_LIMITS.discoverySources)) {
|
|
@@ -10865,7 +10922,9 @@ function assertAgentPolicyCollections(policy) {
|
|
|
10865
10922
|
text(source.path);
|
|
10866
10923
|
requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
|
|
10867
10924
|
if (source.hashMode !== undefined)
|
|
10868
|
-
requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-marketplace-registry-v2"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
|
|
10925
|
+
requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-marketplace-registry-v2", "codex-settings-v1", "codex-settings-v2", "codex-settings-v3"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
|
|
10926
|
+
if (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3")
|
|
10927
|
+
requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute22(source.path) && resolve22(source.path) === source.path && basename2(source.path) === "config.toml");
|
|
10869
10928
|
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3")
|
|
10870
10929
|
requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute22(source.path) && resolve22(source.path) === source.path && basename2(source.path) === "settings.json");
|
|
10871
10930
|
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
|
|
@@ -11136,7 +11195,7 @@ function safe(path) {
|
|
|
11136
11195
|
function same(before, after) {
|
|
11137
11196
|
return Boolean(after && after.isDirectory() && before.dev === after.dev && before.ino === after.ino && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs);
|
|
11138
11197
|
}
|
|
11139
|
-
function capture(path, budget) {
|
|
11198
|
+
function capture(path, budget, excludedRoots) {
|
|
11140
11199
|
safe(path);
|
|
11141
11200
|
const root = lstatSync4(path, { throwIfNoEntry: false });
|
|
11142
11201
|
if (!root)
|
|
@@ -11165,7 +11224,7 @@ function capture(path, budget) {
|
|
|
11165
11224
|
budget.bytes += Buffer.byteLength(row);
|
|
11166
11225
|
need(budget.bytes <= AGENT_POLICY_LIMITS.discoveryDirectoryBytes, "Native discovery directory metadata limit exceeded");
|
|
11167
11226
|
hash.update(row);
|
|
11168
|
-
if (stat.isDirectory())
|
|
11227
|
+
if (stat.isDirectory() && !excludedRoots.includes(child))
|
|
11169
11228
|
walk(child, childRelative, stat, depth + 1);
|
|
11170
11229
|
}
|
|
11171
11230
|
need(same(before, lstatSync4(current2, { throwIfNoEntry: false })), "Native discovery directory changed while reading");
|
|
@@ -11174,16 +11233,18 @@ function capture(path, budget) {
|
|
|
11174
11233
|
safe(path);
|
|
11175
11234
|
return { path, sha256: hash.digest("hex") };
|
|
11176
11235
|
}
|
|
11177
|
-
function captureDiscoveryDirectories(paths) {
|
|
11236
|
+
function captureDiscoveryDirectories(paths, excludedRoots = []) {
|
|
11178
11237
|
need(Array.isArray(paths) && paths.length <= AGENT_POLICY_LIMITS.discoveryDirectories && new Set(paths).size === paths.length, "Invalid native discovery directory collection");
|
|
11179
11238
|
const budget = { entries: 0, bytes: 0 };
|
|
11180
|
-
|
|
11239
|
+
for (const root of excludedRoots)
|
|
11240
|
+
safe(root);
|
|
11241
|
+
return paths.map((path) => capture(path, budget, excludedRoots));
|
|
11181
11242
|
}
|
|
11182
|
-
function verifyDiscoveryDirectories(directories) {
|
|
11243
|
+
function verifyDiscoveryDirectories(directories, excludedRoots = []) {
|
|
11183
11244
|
need(Array.isArray(directories) && directories.length <= AGENT_POLICY_LIMITS.discoveryDirectories, "Invalid native discovery directory collection");
|
|
11184
11245
|
for (const directory of directories)
|
|
11185
11246
|
need(directory && typeof directory === "object" && (directory.sha256 === null || typeof directory.sha256 === "string" && /^[a-f0-9]{64}$/.test(directory.sha256)), "Invalid native discovery directory digest");
|
|
11186
|
-
const current2 = captureDiscoveryDirectories(directories.map((directory) => directory.path));
|
|
11247
|
+
const current2 = captureDiscoveryDirectories(directories.map((directory) => directory.path), excludedRoots);
|
|
11187
11248
|
for (let i = 0;i < directories.length; i++)
|
|
11188
11249
|
need(current2[i].sha256 === directories[i].sha256, `Native discovery directory membership changed; run skills hook install with a fresh discovery review: ${directories[i].path}`);
|
|
11189
11250
|
}
|
|
@@ -11359,236 +11420,6 @@ function hashDiscoveryPathFile(path, budget, changes) {
|
|
|
11359
11420
|
throw new Error("Discovery path input changed during read");
|
|
11360
11421
|
return createHash42("sha256").update(JSON.stringify({ version: 1, path, rows: before.rows, content })).digest("hex");
|
|
11361
11422
|
}
|
|
11362
|
-
var package_default = {
|
|
11363
|
-
name: "@hasna/skills",
|
|
11364
|
-
version: "0.9.17",
|
|
11365
|
-
description: "Skills library for AI coding agents",
|
|
11366
|
-
type: "module",
|
|
11367
|
-
bin: {
|
|
11368
|
-
skills: "bin/index.js",
|
|
11369
|
-
"skills-mcp": "bin/mcp.js",
|
|
11370
|
-
"skills-serve": "bin/server.js",
|
|
11371
|
-
"skills-server": "bin/server.js",
|
|
11372
|
-
"skills-worker": "bin/worker.js",
|
|
11373
|
-
"skills-maintenance": "bin/maintenance.js",
|
|
11374
|
-
"skills-migrate": "bin/migrate.js"
|
|
11375
|
-
},
|
|
11376
|
-
exports: {
|
|
11377
|
-
".": {
|
|
11378
|
-
import: "./dist/index.js",
|
|
11379
|
-
types: "./dist/index.d.ts"
|
|
11380
|
-
},
|
|
11381
|
-
"./storage": {
|
|
11382
|
-
import: "./dist/storage.js",
|
|
11383
|
-
types: "./dist/storage.d.ts"
|
|
11384
|
-
},
|
|
11385
|
-
"./sdk": {
|
|
11386
|
-
import: "./dist/sdk/index.js",
|
|
11387
|
-
types: "./dist/sdk/index.d.ts"
|
|
11388
|
-
},
|
|
11389
|
-
"./admin-contract": {
|
|
11390
|
-
import: "./dist/admin-contract.js",
|
|
11391
|
-
types: "./dist/admin-contract.d.ts"
|
|
11392
|
-
}
|
|
11393
|
-
},
|
|
11394
|
-
files: [
|
|
11395
|
-
"dist/",
|
|
11396
|
-
"!dist/**/*.test.d.ts",
|
|
11397
|
-
"!dist/**/*.fixture.d.ts",
|
|
11398
|
-
"!dist/test-preload.d.ts",
|
|
11399
|
-
"!dist/platform",
|
|
11400
|
-
"bin/",
|
|
11401
|
-
"migrations/",
|
|
11402
|
-
"docs/skill-standard.md",
|
|
11403
|
-
"docs/plugin-admission.md",
|
|
11404
|
-
"schemas/",
|
|
11405
|
-
"LICENSE",
|
|
11406
|
-
"README.md",
|
|
11407
|
-
"!dist/**/*test-utils.d.ts",
|
|
11408
|
-
"!dist/**/*fixtures.d.ts"
|
|
11409
|
-
],
|
|
11410
|
-
main: "./dist/index.js",
|
|
11411
|
-
types: "./dist/index.d.ts",
|
|
11412
|
-
scripts: {
|
|
11413
|
-
clean: "rm -rf bin/ dist/",
|
|
11414
|
-
build: "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build ./src/server/maintenance.ts --outfile ./bin/maintenance.js --target bun && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
11415
|
-
"build:js": "rm -rf dist && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
11416
|
-
test: "bun test --timeout 30000",
|
|
11417
|
-
dev: "bun run ./src/cli/index.tsx",
|
|
11418
|
-
"dev:watch": "bun --watch run ./src/cli/index.tsx",
|
|
11419
|
-
"dev:mcp": "bun --watch run ./src/mcp/index.ts",
|
|
11420
|
-
"dev:server": "bun --watch run ./src/server/index.ts",
|
|
11421
|
-
"dev:worker": "bun --watch run ./src/server/worker.ts",
|
|
11422
|
-
"docker:check": "bash scripts/docker-build-check.sh",
|
|
11423
|
-
migrate: "bun run ./src/server/migrate.ts",
|
|
11424
|
-
typecheck: "tsc --noEmit",
|
|
11425
|
-
"verify:release": "bun run scripts/release-guard.ts",
|
|
11426
|
-
"verify:consumer-types": "bun run scripts/consumer-types.ts",
|
|
11427
|
-
prepack: "bun run verify:generated && bun run verify:release && bun run verify:consumer-types",
|
|
11428
|
-
prepublishOnly: "bun run verify:producer && bun run typecheck && bun run test",
|
|
11429
|
-
"verify:producer": "bun run scripts/release-dependencies.ts",
|
|
11430
|
-
"verify:generated": "HASNA_SKILLS_RELEASE_ARTIFACT_TEST=1 bun test src/lib/release-artifacts.test.ts",
|
|
11431
|
-
"verify:consumer-credentials": "bun run scripts/consumer-credentials.ts"
|
|
11432
|
-
},
|
|
11433
|
-
keywords: [
|
|
11434
|
-
"skills",
|
|
11435
|
-
"ai",
|
|
11436
|
-
"agent",
|
|
11437
|
-
"cli",
|
|
11438
|
-
"typescript",
|
|
11439
|
-
"bun",
|
|
11440
|
-
"claude",
|
|
11441
|
-
"codex",
|
|
11442
|
-
"gemini",
|
|
11443
|
-
"mcp",
|
|
11444
|
-
"model-context-protocol",
|
|
11445
|
-
"open-source",
|
|
11446
|
-
"skills-library",
|
|
11447
|
-
"automation"
|
|
11448
|
-
],
|
|
11449
|
-
author: "Hasna",
|
|
11450
|
-
license: "Apache-2.0",
|
|
11451
|
-
devDependencies: {
|
|
11452
|
-
"@hasna/contracts": "1.1.0",
|
|
11453
|
-
"@types/bun": "1.3.14",
|
|
11454
|
-
"@types/node": "25.2.3",
|
|
11455
|
-
"@types/react": "^18.2.0",
|
|
11456
|
-
"bun-types": "1.3.14",
|
|
11457
|
-
"react-devtools-core": "^7.0.1",
|
|
11458
|
-
typescript: "^5",
|
|
11459
|
-
yaml: "^2.9.0"
|
|
11460
|
-
},
|
|
11461
|
-
dependencies: {
|
|
11462
|
-
"@aws-sdk/client-ecs": "^3.1079.0",
|
|
11463
|
-
"@aws-sdk/client-s3": "^3.1079.0",
|
|
11464
|
-
"@hasna/events": "0.1.18",
|
|
11465
|
-
"@hasna/secrets": "0.4.2",
|
|
11466
|
-
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
11467
|
-
chalk: "^5.3.0",
|
|
11468
|
-
commander: "^12.1.0",
|
|
11469
|
-
ink: "^5.0.1",
|
|
11470
|
-
"ink-select-input": "^6.0.0",
|
|
11471
|
-
"ink-spinner": "^5.0.0",
|
|
11472
|
-
"ink-text-input": "^6.0.0",
|
|
11473
|
-
react: "^18.2.0",
|
|
11474
|
-
zod: "^4.3.6"
|
|
11475
|
-
},
|
|
11476
|
-
engines: {
|
|
11477
|
-
bun: ">=1.0.0"
|
|
11478
|
-
},
|
|
11479
|
-
publishConfig: {
|
|
11480
|
-
registry: "https://registry.npmjs.org",
|
|
11481
|
-
access: "public"
|
|
11482
|
-
},
|
|
11483
|
-
repository: {
|
|
11484
|
-
type: "git",
|
|
11485
|
-
url: "https://github.com/hasna/apps.git",
|
|
11486
|
-
directory: "apps/skills"
|
|
11487
|
-
},
|
|
11488
|
-
homepage: "https://github.com/hasna/apps/tree/main/apps/skills",
|
|
11489
|
-
bugs: {
|
|
11490
|
-
url: "https://github.com/hasna/apps/issues"
|
|
11491
|
-
}
|
|
11492
|
-
};
|
|
11493
|
-
function envToken(name) {
|
|
11494
|
-
return name.toUpperCase().replace(/-/g, "_");
|
|
11495
|
-
}
|
|
11496
|
-
function clientTransportEnvKeys(name) {
|
|
11497
|
-
const envSegment = envToken(name);
|
|
11498
|
-
return {
|
|
11499
|
-
apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
|
|
11500
|
-
apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
|
|
11501
|
-
};
|
|
11502
|
-
}
|
|
11503
|
-
function credentialPointerEnvKey(name) {
|
|
11504
|
-
return `HASNA_${envToken(name)}_API_KEY_REF`;
|
|
11505
|
-
}
|
|
11506
|
-
var MAX_PACKAGE_BYTES = 1024 * 1024;
|
|
11507
|
-
var MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
|
|
11508
|
-
var INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
|
|
11509
|
-
var CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
|
|
11510
|
-
var AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
11511
|
-
var requireSecretsSdk = createRequire(import.meta.url);
|
|
11512
|
-
var IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
11513
|
-
var AUTHORITY_OVERRIDE_HEADERS = new Set([
|
|
11514
|
-
"host",
|
|
11515
|
-
":authority",
|
|
11516
|
-
"forwarded",
|
|
11517
|
-
"x-forwarded-host",
|
|
11518
|
-
"x-original-host"
|
|
11519
|
-
]);
|
|
11520
|
-
var SKILLS_APP = "skills";
|
|
11521
|
-
var ENV_KEYS = clientTransportEnvKeys(SKILLS_APP);
|
|
11522
|
-
var SKILLS_API_URL_ENV_KEYS = ENV_KEYS.apiUrlKeys;
|
|
11523
|
-
var SKILLS_API_KEY_ENV_KEYS = ENV_KEYS.apiKeyKeys;
|
|
11524
|
-
var SKILLS_API_URL_ENV = SKILLS_API_URL_ENV_KEYS[0];
|
|
11525
|
-
var SKILLS_API_KEY_ENV = SKILLS_API_KEY_ENV_KEYS[0];
|
|
11526
|
-
|
|
11527
|
-
class SkillsFleetCredentialError extends Error {
|
|
11528
|
-
code;
|
|
11529
|
-
constructor(message, code = "MISSING_API_CREDENTIAL") {
|
|
11530
|
-
super(message);
|
|
11531
|
-
this.name = "SkillsFleetCredentialError";
|
|
11532
|
-
this.code = code;
|
|
11533
|
-
}
|
|
11534
|
-
}
|
|
11535
|
-
function normalizeSkillsApiOrigin(apiUrl) {
|
|
11536
|
-
const url = new URL(apiUrl);
|
|
11537
|
-
if (url.username || url.password || url.search || url.hash || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) {
|
|
11538
|
-
throw new SkillsFleetCredentialError("A Skills API URL must use HTTPS (or loopback HTTP), without credentials, query or fragment", "INVALID_API_URL");
|
|
11539
|
-
}
|
|
11540
|
-
const pathname = url.pathname.replace(/\/+$/, "");
|
|
11541
|
-
if (url.origin === "https://api.hasna.com" && pathname === "/skills/v1") {
|
|
11542
|
-
url.pathname = "/skills";
|
|
11543
|
-
} else if (pathname === "/api" || pathname === "/api/v1") {
|
|
11544
|
-
url.pathname = "/";
|
|
11545
|
-
} else if (pathname.endsWith("/api/v1")) {
|
|
11546
|
-
url.pathname = pathname.slice(0, -"/api/v1".length) || "/";
|
|
11547
|
-
} else if (pathname.endsWith("/api")) {
|
|
11548
|
-
url.pathname = pathname.slice(0, -"/api".length) || "/";
|
|
11549
|
-
}
|
|
11550
|
-
return url.toString().replace(/\/+$/, "");
|
|
11551
|
-
}
|
|
11552
|
-
function skillsApiRequestUrl(apiUrl, route) {
|
|
11553
|
-
const origin = normalizeSkillsApiOrigin(apiUrl);
|
|
11554
|
-
if (!route.startsWith("/api/") || route.includes("#") || route.includes("\\")) {
|
|
11555
|
-
throw new SkillsFleetCredentialError("Invalid Skills API route", "INVALID_API_URL");
|
|
11556
|
-
}
|
|
11557
|
-
if (origin === "https://api.hasna.com/skills") {
|
|
11558
|
-
if (route === "/api/auth/whoami")
|
|
11559
|
-
return `${origin}/v1/auth/whoami`;
|
|
11560
|
-
if (!route.startsWith("/api/v1/")) {
|
|
11561
|
-
throw new SkillsFleetCredentialError("The internal Skills gateway has no established login contract yet. Select an explicitly configured instance with supported authentication.", "GATEWAY_AUTH_UNAVAILABLE");
|
|
11562
|
-
}
|
|
11563
|
-
return `${origin}${route.slice("/api".length)}`;
|
|
11564
|
-
}
|
|
11565
|
-
return `${origin}${route}`;
|
|
11566
|
-
}
|
|
11567
|
-
var MAX_REMOTE_FILE_BYTES = 64 * 1024 * 1024;
|
|
11568
|
-
function selectionAliasError(selections) {
|
|
11569
|
-
const names = new Set(selections.map((selection) => selection.slug));
|
|
11570
|
-
for (const selection of selections) {
|
|
11571
|
-
if (selection.aliases === undefined)
|
|
11572
|
-
continue;
|
|
11573
|
-
if (!Array.isArray(selection.aliases) || selection.aliases.length > 32)
|
|
11574
|
-
return "Selection aliases must be an array of at most 32 names.";
|
|
11575
|
-
for (const alias of selection.aliases) {
|
|
11576
|
-
if (typeof alias !== "string" || alias.length > 128 || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(alias))
|
|
11577
|
-
return "Selection aliases must be bounded kebab-case names.";
|
|
11578
|
-
if (names.has(alias))
|
|
11579
|
-
return "Selection aliases must be unique and cannot shadow a selected canonical name.";
|
|
11580
|
-
names.add(alias);
|
|
11581
|
-
}
|
|
11582
|
-
}
|
|
11583
|
-
}
|
|
11584
|
-
var MAX_PROFILE_SELECTIONS = 4096;
|
|
11585
|
-
var MAX_PROFILE_DOCUMENT_BYTES = 8 * 1024 * 1024;
|
|
11586
|
-
var MAX_SKILL_SESSION_ID_CHARS = 256;
|
|
11587
|
-
var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 12 + 512;
|
|
11588
|
-
var MAX_RESOLVED_PROFILE_BYTES = MAX_PROFILE_DOCUMENT_BYTES - PROFILE_SESSION_ENVELOPE_BYTES;
|
|
11589
|
-
function profileDocumentBytes(value) {
|
|
11590
|
-
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
11591
|
-
}
|
|
11592
11423
|
var BLOCK = 512;
|
|
11593
11424
|
var ANY_SEGMENT_EXCLUDES = new Set([
|
|
11594
11425
|
".git",
|
|
@@ -11853,6 +11684,30 @@ var SKILL_VERSION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/;
|
|
|
11853
11684
|
function isValidSkillVersion(value) {
|
|
11854
11685
|
return typeof value === "string" && SKILL_VERSION_PATTERN.test(value) && !/^\.+$/.test(value) && !value.includes("..");
|
|
11855
11686
|
}
|
|
11687
|
+
function selectionAliasError(selections) {
|
|
11688
|
+
const names = new Set(selections.map((selection) => selection.slug));
|
|
11689
|
+
for (const selection of selections) {
|
|
11690
|
+
if (selection.aliases === undefined)
|
|
11691
|
+
continue;
|
|
11692
|
+
if (!Array.isArray(selection.aliases) || selection.aliases.length > 32)
|
|
11693
|
+
return "Selection aliases must be an array of at most 32 names.";
|
|
11694
|
+
for (const alias of selection.aliases) {
|
|
11695
|
+
if (typeof alias !== "string" || alias.length > 128 || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(alias))
|
|
11696
|
+
return "Selection aliases must be bounded kebab-case names.";
|
|
11697
|
+
if (names.has(alias))
|
|
11698
|
+
return "Selection aliases must be unique and cannot shadow a selected canonical name.";
|
|
11699
|
+
names.add(alias);
|
|
11700
|
+
}
|
|
11701
|
+
}
|
|
11702
|
+
}
|
|
11703
|
+
var MAX_PROFILE_SELECTIONS = 4096;
|
|
11704
|
+
var MAX_PROFILE_DOCUMENT_BYTES = 8 * 1024 * 1024;
|
|
11705
|
+
var MAX_SKILL_SESSION_ID_CHARS = 256;
|
|
11706
|
+
var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 12 + 512;
|
|
11707
|
+
var MAX_RESOLVED_PROFILE_BYTES = MAX_PROFILE_DOCUMENT_BYTES - PROFILE_SESSION_ENVELOPE_BYTES;
|
|
11708
|
+
function profileDocumentBytes(value) {
|
|
11709
|
+
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
11710
|
+
}
|
|
11856
11711
|
class SkillSelectionError extends Error {
|
|
11857
11712
|
code;
|
|
11858
11713
|
constructor(code, message) {
|
|
@@ -11862,7 +11717,7 @@ class SkillSelectionError extends Error {
|
|
|
11862
11717
|
}
|
|
11863
11718
|
}
|
|
11864
11719
|
var MAX_CACHED_PROFILE_AGE_MS = 24 * 60 * 60 * 1000;
|
|
11865
|
-
function
|
|
11720
|
+
function validateSelection(selection) {
|
|
11866
11721
|
if (!selection || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(selection.slug) || !isValidSkillVersion(selection.version) || !/^sha256:[a-f0-9]{64}$/.test(selection.bundleDigest) || typeof selection.workspaceId !== "string" || !selection.workspaceId.trim() || typeof selection.profileRevision !== "string" || !selection.profileRevision.trim()) {
|
|
11867
11722
|
throw new SkillSelectionError("INVALID_SELECTION", "A skill selection must carry an exact version, digest, workspace and profile revision.");
|
|
11868
11723
|
}
|
|
@@ -11885,7 +11740,7 @@ function validateResolvedProfile(profile, authority) {
|
|
|
11885
11740
|
throw new SkillSelectionError("RECEIPT_TOO_LARGE", "The resolved Skills profile exceeds its size limit.");
|
|
11886
11741
|
const slugs = new Set;
|
|
11887
11742
|
for (const selection of profile.selections) {
|
|
11888
|
-
|
|
11743
|
+
validateSelection(selection);
|
|
11889
11744
|
if (selection.authority !== profile.authority || selection.workspaceId !== profile.workspaceId || selection.profileRevision !== profile.profileRevision || slugs.has(selection.slug)) {
|
|
11890
11745
|
throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "The resolved profile contains conflicting selection identities.");
|
|
11891
11746
|
}
|
|
@@ -11895,110 +11750,276 @@ function validateResolvedProfile(profile, authority) {
|
|
|
11895
11750
|
if (aliasError)
|
|
11896
11751
|
throw new SkillSelectionError("INVALID_SELECTION_ALIASES", aliasError);
|
|
11897
11752
|
}
|
|
11898
|
-
|
|
11899
|
-
|
|
11900
|
-
|
|
11901
|
-
|
|
11902
|
-
|
|
11903
|
-
|
|
11904
|
-
|
|
11905
|
-
|
|
11906
|
-
|
|
11907
|
-
|
|
11908
|
-
|
|
11753
|
+
var package_default = {
|
|
11754
|
+
name: "@hasna/skills",
|
|
11755
|
+
version: "0.10.28",
|
|
11756
|
+
description: "Skills library for AI coding agents",
|
|
11757
|
+
type: "module",
|
|
11758
|
+
bin: {
|
|
11759
|
+
skills: "bin/index.js",
|
|
11760
|
+
"skills-mcp": "bin/mcp.js",
|
|
11761
|
+
"skills-serve": "bin/server.js",
|
|
11762
|
+
"skills-server": "bin/server.js",
|
|
11763
|
+
"skills-worker": "bin/worker.js",
|
|
11764
|
+
"skills-maintenance": "bin/maintenance.js",
|
|
11765
|
+
"skills-migrate": "bin/migrate.js"
|
|
11766
|
+
},
|
|
11767
|
+
exports: {
|
|
11768
|
+
".": {
|
|
11769
|
+
import: "./dist/index.js",
|
|
11770
|
+
types: "./dist/index.d.ts"
|
|
11771
|
+
},
|
|
11772
|
+
"./storage": {
|
|
11773
|
+
import: "./dist/storage.js",
|
|
11774
|
+
types: "./dist/storage.d.ts"
|
|
11775
|
+
},
|
|
11776
|
+
"./sdk": {
|
|
11777
|
+
import: "./dist/sdk/index.js",
|
|
11778
|
+
types: "./dist/sdk/index.d.ts"
|
|
11779
|
+
},
|
|
11780
|
+
"./admin-contract": {
|
|
11781
|
+
import: "./dist/admin-contract.js",
|
|
11782
|
+
types: "./dist/admin-contract.d.ts"
|
|
11783
|
+
}
|
|
11784
|
+
},
|
|
11785
|
+
files: [
|
|
11786
|
+
"dist/",
|
|
11787
|
+
"!dist/**/*.test.d.ts",
|
|
11788
|
+
"!dist/**/*.fixture.d.ts",
|
|
11789
|
+
"!dist/test-preload.d.ts",
|
|
11790
|
+
"!dist/platform",
|
|
11791
|
+
"bin/",
|
|
11792
|
+
"migrations/",
|
|
11793
|
+
"docs/skill-standard.md",
|
|
11794
|
+
"docs/plugin-admission.md",
|
|
11795
|
+
"schemas/",
|
|
11796
|
+
"LICENSE",
|
|
11797
|
+
"README.md",
|
|
11798
|
+
"!dist/**/*test-utils.d.ts",
|
|
11799
|
+
"!dist/**/*fixtures.d.ts"
|
|
11800
|
+
],
|
|
11801
|
+
main: "./dist/index.js",
|
|
11802
|
+
types: "./dist/index.d.ts",
|
|
11803
|
+
scripts: {
|
|
11804
|
+
clean: "rm -rf bin/ dist/",
|
|
11805
|
+
build: "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build ./src/server/maintenance.ts --outfile ./bin/maintenance.js --target bun && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
11806
|
+
"build:js": "rm -rf dist && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
11807
|
+
test: "bun test --timeout 30000",
|
|
11808
|
+
dev: "bun run ./src/cli/index.tsx",
|
|
11809
|
+
"dev:watch": "bun --watch run ./src/cli/index.tsx",
|
|
11810
|
+
"dev:mcp": "bun --watch run ./src/mcp/index.ts",
|
|
11811
|
+
"dev:server": "bun --watch run ./src/server/index.ts",
|
|
11812
|
+
"dev:worker": "bun --watch run ./src/server/worker.ts",
|
|
11813
|
+
"docker:check": "bash scripts/docker-build-check.sh",
|
|
11814
|
+
migrate: "bun run ./src/server/migrate.ts",
|
|
11815
|
+
typecheck: "tsc --noEmit",
|
|
11816
|
+
"verify:release": "bun run scripts/release-guard.ts",
|
|
11817
|
+
"verify:release-review": "bun run scripts/verify-release-review.ts",
|
|
11818
|
+
"verify:consumer-types": "bun run scripts/consumer-types.ts",
|
|
11819
|
+
prepack: "bun run verify:generated && bun run verify:release && bun run verify:consumer-types",
|
|
11820
|
+
prepublishOnly: "bun run verify:producer && bun run typecheck && bun run test",
|
|
11821
|
+
"verify:producer": "bun run scripts/release-dependencies.ts",
|
|
11822
|
+
"verify:generated": "HASNA_SKILLS_RELEASE_ARTIFACT_TEST=1 bun test src/lib/release-artifacts.test.ts",
|
|
11823
|
+
"verify:consumer-credentials": "bun run scripts/consumer-credentials.ts"
|
|
11824
|
+
},
|
|
11825
|
+
keywords: [
|
|
11826
|
+
"skills",
|
|
11827
|
+
"ai",
|
|
11828
|
+
"agent",
|
|
11829
|
+
"cli",
|
|
11830
|
+
"typescript",
|
|
11831
|
+
"bun",
|
|
11832
|
+
"claude",
|
|
11833
|
+
"codex",
|
|
11834
|
+
"gemini",
|
|
11835
|
+
"mcp",
|
|
11836
|
+
"model-context-protocol",
|
|
11837
|
+
"open-source",
|
|
11838
|
+
"skills-library",
|
|
11839
|
+
"automation"
|
|
11840
|
+
],
|
|
11841
|
+
author: "Hasna",
|
|
11842
|
+
license: "Apache-2.0",
|
|
11843
|
+
devDependencies: {
|
|
11844
|
+
"@hasna/contracts": "1.1.0",
|
|
11845
|
+
"@types/bun": "1.3.14",
|
|
11846
|
+
"@types/node": "25.2.3",
|
|
11847
|
+
"@types/react": "^18.2.0",
|
|
11848
|
+
"bun-types": "1.3.14",
|
|
11849
|
+
"react-devtools-core": "^7.0.1",
|
|
11850
|
+
typescript: "^5",
|
|
11851
|
+
yaml: "^2.9.0"
|
|
11852
|
+
},
|
|
11853
|
+
dependencies: {
|
|
11854
|
+
"@aws-sdk/client-ecs": "^3.1079.0",
|
|
11855
|
+
"@aws-sdk/client-s3": "^3.1079.0",
|
|
11856
|
+
"@hasna/events": "0.1.18",
|
|
11857
|
+
"@hasna/secrets": "0.4.2",
|
|
11858
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
11859
|
+
chalk: "^5.3.0",
|
|
11860
|
+
commander: "^12.1.0",
|
|
11861
|
+
ink: "^5.0.1",
|
|
11862
|
+
"ink-select-input": "^6.0.0",
|
|
11863
|
+
"ink-spinner": "^5.0.0",
|
|
11864
|
+
"ink-text-input": "^6.0.0",
|
|
11865
|
+
react: "^18.2.0",
|
|
11866
|
+
zod: "^4.3.6"
|
|
11867
|
+
},
|
|
11868
|
+
engines: {
|
|
11869
|
+
bun: ">=1.0.0"
|
|
11870
|
+
},
|
|
11871
|
+
publishConfig: {
|
|
11872
|
+
registry: "https://registry.npmjs.org",
|
|
11873
|
+
access: "public"
|
|
11874
|
+
},
|
|
11875
|
+
repository: {
|
|
11876
|
+
type: "git",
|
|
11877
|
+
url: "https://github.com/hasna/skills.git",
|
|
11878
|
+
directory: "apps/skills"
|
|
11879
|
+
},
|
|
11880
|
+
homepage: "https://github.com/hasna/skills/tree/main/apps/skills",
|
|
11881
|
+
bugs: {
|
|
11882
|
+
url: "https://github.com/hasna/skills/issues"
|
|
11909
11883
|
}
|
|
11910
|
-
|
|
11911
|
-
|
|
11912
|
-
|
|
11913
|
-
|
|
11914
|
-
|
|
11915
|
-
|
|
11916
|
-
|
|
11917
|
-
|
|
11884
|
+
};
|
|
11885
|
+
function envToken(name) {
|
|
11886
|
+
return name.toUpperCase().replace(/-/g, "_");
|
|
11887
|
+
}
|
|
11888
|
+
function clientTransportEnvKeys(name) {
|
|
11889
|
+
const envSegment = envToken(name);
|
|
11890
|
+
return {
|
|
11891
|
+
apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
|
|
11892
|
+
apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
|
|
11893
|
+
};
|
|
11894
|
+
}
|
|
11895
|
+
function credentialPointerEnvKey(name) {
|
|
11896
|
+
return `HASNA_${envToken(name)}_API_KEY_REF`;
|
|
11897
|
+
}
|
|
11898
|
+
var MAX_PACKAGE_BYTES = 1024 * 1024;
|
|
11899
|
+
var MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
|
|
11900
|
+
var INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
|
|
11901
|
+
var CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
|
|
11902
|
+
var AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
11903
|
+
var requireSecretsSdk = createRequire(import.meta.url);
|
|
11904
|
+
var IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
11905
|
+
var AUTHORITY_OVERRIDE_HEADERS = new Set([
|
|
11906
|
+
"host",
|
|
11907
|
+
":authority",
|
|
11908
|
+
"forwarded",
|
|
11909
|
+
"x-forwarded-host",
|
|
11910
|
+
"x-original-host"
|
|
11911
|
+
]);
|
|
11912
|
+
var SKILLS_APP = "skills";
|
|
11913
|
+
var ENV_KEYS = clientTransportEnvKeys(SKILLS_APP);
|
|
11914
|
+
var SKILLS_API_URL_ENV_KEYS = ENV_KEYS.apiUrlKeys;
|
|
11915
|
+
var SKILLS_API_KEY_ENV_KEYS = ENV_KEYS.apiKeyKeys;
|
|
11916
|
+
var SKILLS_API_URL_ENV = SKILLS_API_URL_ENV_KEYS[0];
|
|
11917
|
+
var SKILLS_API_KEY_ENV = SKILLS_API_KEY_ENV_KEYS[0];
|
|
11918
|
+
|
|
11919
|
+
class SkillsFleetCredentialError extends Error {
|
|
11920
|
+
code;
|
|
11921
|
+
next;
|
|
11922
|
+
constructor(message, code = "MISSING_API_CREDENTIAL", next) {
|
|
11923
|
+
super(message);
|
|
11924
|
+
this.name = "SkillsFleetCredentialError";
|
|
11925
|
+
this.code = code;
|
|
11926
|
+
if (next)
|
|
11927
|
+
this.next = next;
|
|
11918
11928
|
}
|
|
11919
|
-
return value;
|
|
11920
11929
|
}
|
|
11921
|
-
function
|
|
11922
|
-
const
|
|
11923
|
-
|
|
11924
|
-
|
|
11925
|
-
|
|
11930
|
+
function normalizeSkillsApiOrigin(apiUrl) {
|
|
11931
|
+
const url = new URL(apiUrl);
|
|
11932
|
+
if (url.username || url.password || url.search || url.hash || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) {
|
|
11933
|
+
throw new SkillsFleetCredentialError("A Skills API URL must use HTTPS (or loopback HTTP), without credentials, query or fragment", "INVALID_API_URL");
|
|
11934
|
+
}
|
|
11935
|
+
const pathname = url.pathname.replace(/\/+$/, "");
|
|
11936
|
+
if (url.origin === "https://api.hasna.com" && pathname === "/skills/v1") {
|
|
11937
|
+
url.pathname = "/skills";
|
|
11938
|
+
} else if (pathname === "/api" || pathname === "/api/v1") {
|
|
11939
|
+
url.pathname = "/";
|
|
11940
|
+
} else if (pathname.endsWith("/api/v1")) {
|
|
11941
|
+
url.pathname = pathname.slice(0, -"/api/v1".length) || "/";
|
|
11942
|
+
} else if (pathname.endsWith("/api")) {
|
|
11943
|
+
url.pathname = pathname.slice(0, -"/api".length) || "/";
|
|
11944
|
+
}
|
|
11945
|
+
return url.toString().replace(/\/+$/, "");
|
|
11926
11946
|
}
|
|
11927
|
-
function
|
|
11928
|
-
const
|
|
11929
|
-
|
|
11930
|
-
|
|
11931
|
-
|
|
11932
|
-
|
|
11933
|
-
if (
|
|
11934
|
-
|
|
11935
|
-
if (
|
|
11936
|
-
throw
|
|
11937
|
-
descriptor = openSync8(path, constants5.O_RDONLY | (constants5.O_NOFOLLOW ?? 0) | constants5.O_NONBLOCK);
|
|
11938
|
-
const opened = fstatSync8(descriptor);
|
|
11939
|
-
if (!opened.isFile())
|
|
11940
|
-
throw refusal();
|
|
11941
|
-
if (opened.size > AGENT_POLICY_LIMITS.bytes)
|
|
11942
|
-
throw refusal("exceeds its size limit");
|
|
11943
|
-
const bytes = Buffer.allocUnsafe(AGENT_POLICY_LIMITS.bytes + 1);
|
|
11944
|
-
let length = 0;
|
|
11945
|
-
while (length < bytes.length) {
|
|
11946
|
-
const count = readSync4(descriptor, bytes, length, bytes.length - length, null);
|
|
11947
|
-
if (count === 0)
|
|
11948
|
-
break;
|
|
11949
|
-
length += count;
|
|
11950
|
-
}
|
|
11951
|
-
if (length > AGENT_POLICY_LIMITS.bytes)
|
|
11952
|
-
throw refusal("exceeds its size limit");
|
|
11953
|
-
const text2 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes.subarray(0, length));
|
|
11954
|
-
return { text: text2, value: parseManagedSkillPolicy(text2) };
|
|
11955
|
-
} catch (error) {
|
|
11956
|
-
if (error.code === "ENOENT" && !observed) {
|
|
11957
|
-
if (lstatSync11(path, { throwIfNoEntry: false }) === undefined)
|
|
11958
|
-
return null;
|
|
11947
|
+
function skillsApiRequestUrl(apiUrl, route) {
|
|
11948
|
+
const origin = normalizeSkillsApiOrigin(apiUrl);
|
|
11949
|
+
if (!route.startsWith("/api/") || route.includes("#") || route.includes("\\")) {
|
|
11950
|
+
throw new SkillsFleetCredentialError("Invalid Skills API route", "INVALID_API_URL");
|
|
11951
|
+
}
|
|
11952
|
+
if (origin === "https://api.hasna.com/skills") {
|
|
11953
|
+
if (route === "/api/auth/whoami")
|
|
11954
|
+
return `${origin}/v1/auth/whoami`;
|
|
11955
|
+
if (!route.startsWith("/api/v1/")) {
|
|
11956
|
+
throw new SkillsFleetCredentialError("The internal Skills gateway has no established login contract yet. Select an explicitly configured instance with supported authentication.", "GATEWAY_AUTH_UNAVAILABLE");
|
|
11959
11957
|
}
|
|
11960
|
-
|
|
11961
|
-
throw error;
|
|
11962
|
-
throw refusal();
|
|
11963
|
-
} finally {
|
|
11964
|
-
if (descriptor !== undefined)
|
|
11965
|
-
closeSync8(descriptor);
|
|
11958
|
+
return `${origin}${route.slice("/api".length)}`;
|
|
11966
11959
|
}
|
|
11960
|
+
return `${origin}${route}`;
|
|
11967
11961
|
}
|
|
11968
|
-
var
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
11974
|
-
|
|
11975
|
-
|
|
11976
|
-
|
|
11977
|
-
|
|
11978
|
-
|
|
11979
|
-
|
|
11980
|
-
|
|
11981
|
-
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
11986
|
-
|
|
11987
|
-
|
|
11988
|
-
|
|
11989
|
-
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
|
|
11962
|
+
var MAX_REMOTE_FILE_BYTES = 64 * 1024 * 1024;
|
|
11963
|
+
function isCodexLocalStdioMcpServer(name, server) {
|
|
11964
|
+
const identifier2 = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(value);
|
|
11965
|
+
const refuse = () => {
|
|
11966
|
+
throw new Error("Unsupported Codex local stdio MCP controls");
|
|
11967
|
+
};
|
|
11968
|
+
const object4 = (v) => Boolean(v && typeof v === "object" && !Array.isArray(v) && !(v instanceof Date));
|
|
11969
|
+
const textField = (v, empty = false) => typeof v === "string" && (empty || Boolean(v.trim())) && Buffer.byteLength(v) <= 16384 && !/[\x00-\x1f\x7f]/.test(v);
|
|
11970
|
+
const envName = (v) => typeof v === "string" && /^[A-Za-z_][A-Za-z0-9_]{0,255}$/.test(v);
|
|
11971
|
+
const optional = (v, valid2) => v === undefined || v === null || valid2(v);
|
|
11972
|
+
const stringList = (v) => Array.isArray(v) && v.length <= 256 && v.every((item) => textField(item));
|
|
11973
|
+
const approval = (v) => ["auto", "prompt", "writes", "approve"].includes(v);
|
|
11974
|
+
const duration = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
11975
|
+
const positiveInteger = (v) => Number.isSafeInteger(v) && v > 0;
|
|
11976
|
+
const fields = [
|
|
11977
|
+
"command",
|
|
11978
|
+
"args",
|
|
11979
|
+
"cwd",
|
|
11980
|
+
"env",
|
|
11981
|
+
"env_vars",
|
|
11982
|
+
"enabled",
|
|
11983
|
+
"required",
|
|
11984
|
+
"environment_id",
|
|
11985
|
+
"startup_readiness",
|
|
11986
|
+
"supports_parallel_tool_calls",
|
|
11987
|
+
"tool_input_schema_max_bytes",
|
|
11988
|
+
"startup_timeout_sec",
|
|
11989
|
+
"startup_timeout_ms",
|
|
11990
|
+
"tool_timeout_sec",
|
|
11991
|
+
"default_tools_approval_mode",
|
|
11992
|
+
"enabled_tools",
|
|
11993
|
+
"disabled_tools",
|
|
11994
|
+
"omit_tools_from",
|
|
11995
|
+
"tools",
|
|
11996
|
+
"name"
|
|
11997
|
+
];
|
|
11993
11998
|
try {
|
|
11994
|
-
if (!
|
|
11995
|
-
|
|
11996
|
-
if (
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
const
|
|
12000
|
-
|
|
12001
|
-
|
|
11999
|
+
if (!identifier2(name) || !object4(server) || !Object.keys(server).every((key) => fields.includes(key)) || !textField(server.command) || !optional(server.args, (v) => Array.isArray(v) && v.length <= 256 && v.every((arg) => textField(arg, true))) || !optional(server.cwd, textField) || !optional(server.env, (v) => object4(v) && Object.keys(v).length <= 256 && Object.entries(v).every(([key, value]) => envName(key) && textField(value, true))) || !["enabled", "required", "supports_parallel_tool_calls"].every((key) => optional(server[key], (v) => typeof v === "boolean")) || !optional(server.environment_id, (v) => v === "local") || !optional(server.startup_readiness, (v) => ["connection", "catalog"].includes(v)) || !optional(server.tool_input_schema_max_bytes, positiveInteger) || !["startup_timeout_sec", "tool_timeout_sec"].every((key) => optional(server[key], duration)) || !optional(server.startup_timeout_ms, (v) => Number.isSafeInteger(v) && v >= 0) || !optional(server.default_tools_approval_mode, approval) || !["enabled_tools", "disabled_tools"].every((key) => optional(server[key], stringList)) || !optional(server.omit_tools_from, (v) => Array.isArray(v) && v.length <= 256 && v.every((item) => ["code_mode", "deferred", "direct"].includes(item))) || !optional(server.name, textField))
|
|
12000
|
+
refuse();
|
|
12001
|
+
if (server.env_vars !== undefined && server.env_vars !== null) {
|
|
12002
|
+
if (!Array.isArray(server.env_vars) || server.env_vars.length > 256)
|
|
12003
|
+
refuse();
|
|
12004
|
+
const names = server.env_vars.map((entry) => {
|
|
12005
|
+
if (envName(entry))
|
|
12006
|
+
return entry;
|
|
12007
|
+
if (!object4(entry) || !Object.keys(entry).every((key) => ["name", "source"].includes(key)) || !envName(entry.name) || !optional(entry.source, (v) => v === "local"))
|
|
12008
|
+
refuse();
|
|
12009
|
+
return entry.name;
|
|
12010
|
+
});
|
|
12011
|
+
if (new Set(names).size !== names.length)
|
|
12012
|
+
refuse();
|
|
12013
|
+
}
|
|
12014
|
+
if (server.tools !== undefined && server.tools !== null) {
|
|
12015
|
+
if (!object4(server.tools) || Object.keys(server.tools).length > 256)
|
|
12016
|
+
refuse();
|
|
12017
|
+
for (const [tool, config] of Object.entries(server.tools)) {
|
|
12018
|
+
if (!textField(tool) || !object4(config) || !Object.keys(config).every((key) => ["approval_mode", "output_token_limit"].includes(key)) || !optional(config.approval_mode, approval) || !optional(config.output_token_limit, positiveInteger))
|
|
12019
|
+
refuse();
|
|
12020
|
+
}
|
|
12021
|
+
}
|
|
12022
|
+
return true;
|
|
12002
12023
|
} catch {
|
|
12003
12024
|
return false;
|
|
12004
12025
|
}
|
|
@@ -12015,36 +12036,36 @@ var sameFile = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode
|
|
|
12015
12036
|
function parents(path) {
|
|
12016
12037
|
const result = [];
|
|
12017
12038
|
for (let at = dirname9(path);; at = dirname9(at)) {
|
|
12018
|
-
const stat =
|
|
12039
|
+
const stat = lstatSync11(at, { bigint: true });
|
|
12019
12040
|
need2(stat.isDirectory() && !stat.isSymbolicLink(), "path traverses a symlink or non-directory");
|
|
12020
12041
|
result.push([at, stat]);
|
|
12021
12042
|
if (at === dirname9(at))
|
|
12022
12043
|
return result;
|
|
12023
12044
|
}
|
|
12024
12045
|
}
|
|
12025
|
-
function
|
|
12046
|
+
function readNativeSettingsWitnessFile(path, budget, filename = "settings.json") {
|
|
12026
12047
|
absolutePath(path);
|
|
12027
|
-
need2(basename4(path) ===
|
|
12048
|
+
need2(basename4(path) === filename, `requires ${filename}`);
|
|
12028
12049
|
need2(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
|
|
12029
|
-
const ancestors = parents(path), initial =
|
|
12050
|
+
const ancestors = parents(path), initial = lstatSync11(path, { bigint: true });
|
|
12030
12051
|
need2(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_SETTINGS_WITNESS_LIMITS.bytes), "is not a bounded regular file");
|
|
12031
12052
|
need2(initial.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
|
|
12032
|
-
const fd =
|
|
12053
|
+
const fd = openSync6(path, constants5.O_RDONLY | constants5.O_NOFOLLOW | constants5.O_NONBLOCK);
|
|
12033
12054
|
try {
|
|
12034
|
-
const opened =
|
|
12055
|
+
const opened = fstatSync6(fd, { bigint: true });
|
|
12035
12056
|
need2(opened.isFile() && sameFile(initial, opened), "changed during open");
|
|
12036
12057
|
const bytes = Buffer.alloc(Number(opened.size) + 1);
|
|
12037
12058
|
let length = 0;
|
|
12038
12059
|
while (length < bytes.length) {
|
|
12039
|
-
const count =
|
|
12060
|
+
const count = readSync4(fd, bytes, length, bytes.length - length, null);
|
|
12040
12061
|
if (!count)
|
|
12041
12062
|
break;
|
|
12042
12063
|
length += count;
|
|
12043
12064
|
}
|
|
12044
|
-
const after =
|
|
12065
|
+
const after = fstatSync6(fd, { bigint: true }), current2 = lstatSync11(path, { bigint: true });
|
|
12045
12066
|
need2(current2.isFile() && sameFile(opened, after) && sameFile(after, current2) && BigInt(length) === opened.size, "changed during read");
|
|
12046
12067
|
for (const [ancestor, before] of ancestors) {
|
|
12047
|
-
const now =
|
|
12068
|
+
const now = lstatSync11(ancestor, { bigint: true });
|
|
12048
12069
|
need2(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
|
|
12049
12070
|
}
|
|
12050
12071
|
budget.remaining -= length;
|
|
@@ -12054,7 +12075,7 @@ function readSettings(path, budget) {
|
|
|
12054
12075
|
throw new Error("Claude settings witness is not strict UTF-8");
|
|
12055
12076
|
}
|
|
12056
12077
|
} finally {
|
|
12057
|
-
|
|
12078
|
+
closeSync6(fd);
|
|
12058
12079
|
}
|
|
12059
12080
|
}
|
|
12060
12081
|
function parse3(text2) {
|
|
@@ -12256,7 +12277,7 @@ function settingsDigest(text2, version = 1) {
|
|
|
12256
12277
|
}
|
|
12257
12278
|
return true;
|
|
12258
12279
|
});
|
|
12259
|
-
return
|
|
12280
|
+
return createHash8("sha256").update(`hasna.skills.claude-settings.v${version}\x00`).update(JSON.stringify(canonical2(value))).digest("hex");
|
|
12260
12281
|
}
|
|
12261
12282
|
function hashClaudeSettingsReplacement(text2, budget) {
|
|
12262
12283
|
need2(typeof text2 === "string", "requires settings text");
|
|
@@ -12266,10 +12287,10 @@ function hashClaudeSettingsReplacement(text2, budget) {
|
|
|
12266
12287
|
return settingsDigest(text2);
|
|
12267
12288
|
}
|
|
12268
12289
|
function captureClaudeSettings(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
12269
|
-
return { path, hashMode: "claude-settings-v1", sha256: settingsDigest(
|
|
12290
|
+
return { path, hashMode: "claude-settings-v1", sha256: settingsDigest(readNativeSettingsWitnessFile(path, budget)) };
|
|
12270
12291
|
}
|
|
12271
12292
|
function captureClaudeSettingsV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
12272
|
-
return { path, hashMode: "claude-settings-v2", sha256: settingsDigest(
|
|
12293
|
+
return { path, hashMode: "claude-settings-v2", sha256: settingsDigest(readNativeSettingsWitnessFile(path, budget), 2) };
|
|
12273
12294
|
}
|
|
12274
12295
|
function hashClaudeSettingsReplacementV2(text2, budget) {
|
|
12275
12296
|
need2(typeof text2 === "string", "requires settings text");
|
|
@@ -12279,7 +12300,7 @@ function hashClaudeSettingsReplacementV2(text2, budget) {
|
|
|
12279
12300
|
return settingsDigest(text2, 2);
|
|
12280
12301
|
}
|
|
12281
12302
|
function captureClaudeSettingsV3(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
12282
|
-
return { path, hashMode: "claude-settings-v3", sha256: settingsDigest(
|
|
12303
|
+
return { path, hashMode: "claude-settings-v3", sha256: settingsDigest(readNativeSettingsWitnessFile(path, budget), 3) };
|
|
12283
12304
|
}
|
|
12284
12305
|
function hashClaudeSettingsReplacementV3(text2, budget) {
|
|
12285
12306
|
need2(typeof text2 === "string", "requires settings text");
|
|
@@ -12288,14 +12309,849 @@ function hashClaudeSettingsReplacementV3(text2, budget) {
|
|
|
12288
12309
|
budget.remaining -= bytes;
|
|
12289
12310
|
return settingsDigest(text2, 3);
|
|
12290
12311
|
}
|
|
12291
|
-
|
|
12312
|
+
function upgradeClaudeSettingsWitness(previous, reviewedSettingsPath) {
|
|
12313
|
+
need2(previous && Object.keys(previous).every((key) => ["path", "hashMode", "sha256"].includes(key)) && (previous.hashMode === undefined || previous.hashMode === "bytes" || previous.hashMode === "claude-settings-v1" || previous.hashMode === "claude-settings-v2") && typeof previous.sha256 === "string" && /^[a-f0-9]{64}$/.test(previous.sha256), "requires an exact legacy settings witness");
|
|
12314
|
+
const budget = { remaining: 2 * CLAUDE_SETTINGS_WITNESS_LIMITS.bytes };
|
|
12315
|
+
const before = readNativeSettingsWitnessFile(reviewedSettingsPath, budget), current2 = readNativeSettingsWitnessFile(previous.path, budget);
|
|
12316
|
+
const beforeDigest = previous.hashMode === undefined || previous.hashMode === "bytes" ? createHash8("sha256").update(before).digest("hex") : settingsDigest(before, previous.hashMode === "claude-settings-v1" ? 1 : 2);
|
|
12317
|
+
need2(beforeDigest === previous.sha256, "reviewed preimage does not match the legacy witness");
|
|
12318
|
+
const sha2562 = settingsDigest(current2, 3);
|
|
12319
|
+
need2(settingsDigest(before, 3) === sha2562, "non-preference settings changed; explicit discovery review required");
|
|
12320
|
+
return { path: previous.path, hashMode: "claude-settings-v3", sha256: sha2562 };
|
|
12321
|
+
}
|
|
12322
|
+
function hashNativeJsonControls(text2, excludedMetadata) {
|
|
12323
|
+
need2(Buffer.byteLength(text2) <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes, "exceeds its byte limit");
|
|
12324
|
+
const value = parse3(text2);
|
|
12325
|
+
value.entries = value.entries.filter(([key, child]) => {
|
|
12326
|
+
if (key !== excludedMetadata)
|
|
12327
|
+
return true;
|
|
12328
|
+
need2(child.kind === "string" && child.value.length <= 128, "has invalid version metadata");
|
|
12329
|
+
return false;
|
|
12330
|
+
});
|
|
12331
|
+
return createHash8("sha256").update("hasna.skills.native-json-controls.v1\x00").update(JSON.stringify(canonical2(value))).digest("hex");
|
|
12332
|
+
}
|
|
12333
|
+
var MEASURED_CODEX_CAPABILITIES = {
|
|
12334
|
+
"codex-cli 0.153.0": ["hooks"],
|
|
12335
|
+
"codex-cli 0.154.0": ["hooks"],
|
|
12336
|
+
"codex-cli 0.155.0": ["hooks"],
|
|
12337
|
+
"codex-cli 0.155.1": ["hooks"],
|
|
12338
|
+
"codex-cli 0.156.1": ["hooks"],
|
|
12339
|
+
"codex-cli 0.157.0": ["hooks"],
|
|
12340
|
+
"codex-cli 0.157.1": ["hooks"],
|
|
12341
|
+
"codex-cli 0.158.0": ["hooks"],
|
|
12342
|
+
"codex-cli 0.159.0": ["hooks"],
|
|
12343
|
+
"codex-cli 0.159.2": ["hooks", "qualified-skill-catalog"],
|
|
12344
|
+
"codex-cli 0.160.0": ["hooks", "qualified-skill-catalog"]
|
|
12345
|
+
};
|
|
12346
|
+
function supportsCodexNativeCapability(version, capability) {
|
|
12347
|
+
return typeof version === "string" && Object.hasOwn(MEASURED_CODEX_CAPABILITIES, version) && MEASURED_CODEX_CAPABILITIES[version].includes(capability);
|
|
12348
|
+
}
|
|
12349
|
+
var SUPPORTED_CODEX_HOOK_VERSIONS = Object.freeze(Object.keys(MEASURED_CODEX_CAPABILITIES).filter((version) => supportsCodexNativeCapability(version, "hooks")));
|
|
12350
|
+
function codexPluginSourceIsConfigControlled(plugin) {
|
|
12351
|
+
const remoteRealms = ["openai-curated-remote", "created-by-me-remote", "workspace-directory", "workspace-shared-with-me", "workspace-shared-with-me-private", "workspace-shared-with-me-unlisted"];
|
|
12352
|
+
return Boolean(plugin.sourceType && ["local", "git", "npm"].includes(plugin.sourceType) && plugin.sourceSha256 && /^[a-f0-9]{64}$/.test(plugin.sourceSha256) && !plugin.remotePluginId && !remoteRealms.includes(plugin.id.slice(plugin.id.lastIndexOf("@") + 1)));
|
|
12353
|
+
}
|
|
12354
|
+
var MAX_SKILLS = 4096;
|
|
12355
|
+
var MAX_NAME_BYTES = 1024;
|
|
12356
|
+
var MAX_PATH_BYTES = 16384;
|
|
12357
|
+
function refuse() {
|
|
12358
|
+
throw new Error("CODEX_NATIVE_SKILL_CATALOG_INVALID");
|
|
12359
|
+
}
|
|
12360
|
+
var object4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
12361
|
+
var scalar = (value, max) => typeof value === "string" && value.length > 0 && value.trim() === value && Buffer.byteLength(value, "utf8") <= max && !/[\u0000-\u001f\u007f]/u.test(value);
|
|
12362
|
+
var remotePluginIdentifier = (value) => typeof value === "string" && /^[A-Za-z0-9_~-]{1,1024}$/.test(value);
|
|
12363
|
+
var absolutePath2 = (value) => scalar(value, MAX_PATH_BYTES) && isAbsolute10(value);
|
|
12364
|
+
function canonicalPath(path) {
|
|
12365
|
+
try {
|
|
12366
|
+
return realpathSync4(path);
|
|
12367
|
+
} catch {
|
|
12368
|
+
return normalize2(path);
|
|
12369
|
+
}
|
|
12370
|
+
}
|
|
12371
|
+
function isCodexNativeSkillDisabled(skill, rules) {
|
|
12372
|
+
if (!scalar(skill.name, MAX_NAME_BYTES) || !absolutePath2(skill.path))
|
|
12373
|
+
refuse();
|
|
12374
|
+
if (rules === undefined)
|
|
12375
|
+
return false;
|
|
12376
|
+
if (!Array.isArray(rules) || rules.length > MAX_SKILLS)
|
|
12377
|
+
refuse();
|
|
12378
|
+
let disabled = false;
|
|
12379
|
+
for (const rule of rules) {
|
|
12380
|
+
if (!object4(rule) || typeof rule.enabled !== "boolean")
|
|
12381
|
+
refuse();
|
|
12382
|
+
const hasName = rule.name !== undefined && rule.name !== null;
|
|
12383
|
+
const hasPath = rule.path !== undefined && rule.path !== null;
|
|
12384
|
+
if (hasName === hasPath)
|
|
12385
|
+
refuse();
|
|
12386
|
+
let matches;
|
|
12387
|
+
if (hasName) {
|
|
12388
|
+
if (typeof rule.name !== "string" || !scalar(rule.name.trim(), MAX_NAME_BYTES))
|
|
12389
|
+
refuse();
|
|
12390
|
+
matches = rule.name.trim() === skill.name;
|
|
12391
|
+
} else {
|
|
12392
|
+
if (!absolutePath2(rule.path))
|
|
12393
|
+
refuse();
|
|
12394
|
+
matches = canonicalPath(rule.path) === canonicalPath(skill.path);
|
|
12395
|
+
}
|
|
12396
|
+
if (matches)
|
|
12397
|
+
disabled = !rule.enabled;
|
|
12398
|
+
}
|
|
12399
|
+
return disabled;
|
|
12400
|
+
}
|
|
12401
|
+
var identifier2 = (v) => typeof v === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(v);
|
|
12402
|
+
var CODEX_DEFAULT_AGENT_PLUGIN_VERSION = "1.0.0";
|
|
12403
|
+
function refuse2() {
|
|
12404
|
+
throw new Error("CODEX_NATIVE_SKILL_IDENTITY_UNSUPPORTED: review native plugin names and hook controls");
|
|
12405
|
+
}
|
|
12406
|
+
function remoteInstallationMatches(parent, remotePluginId, read2) {
|
|
12407
|
+
try {
|
|
12408
|
+
if (!remotePluginIdentifier(remotePluginId) || !lstatSync12(parent).isDirectory() || realpathSync5(parent) !== parent)
|
|
12409
|
+
return false;
|
|
12410
|
+
const path = join14(parent, ".codex-remote-plugin-install.json"), stat = lstatSync12(path, { throwIfNoEntry: false });
|
|
12411
|
+
if (!stat?.isFile() || stat.isSymbolicLink() || stat.size > 16384)
|
|
12412
|
+
return false;
|
|
12413
|
+
const text2 = read2(path);
|
|
12414
|
+
hashNativeJsonControls(text2);
|
|
12415
|
+
const value = JSON.parse(text2);
|
|
12416
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 2 && value.schema_version === 1 && value.remote_plugin_id === remotePluginId);
|
|
12417
|
+
} catch {
|
|
12418
|
+
return false;
|
|
12419
|
+
}
|
|
12420
|
+
}
|
|
12421
|
+
function appControls(path, read2) {
|
|
12422
|
+
const text2 = read2(path);
|
|
12423
|
+
let value, sha2562;
|
|
12424
|
+
try {
|
|
12425
|
+
sha2562 = hashNativeJsonControls(text2);
|
|
12426
|
+
value = JSON.parse(text2);
|
|
12427
|
+
} catch {
|
|
12428
|
+
refuse2();
|
|
12429
|
+
}
|
|
12430
|
+
const object5 = (v) => Boolean(v && typeof v === "object" && !Array.isArray(v));
|
|
12431
|
+
if (!object5(value) || Object.keys(value).length !== 1 || !object5(value.apps))
|
|
12432
|
+
refuse2();
|
|
12433
|
+
const apps = Object.entries(value.apps);
|
|
12434
|
+
if (!apps.length || apps.length > 256)
|
|
12435
|
+
refuse2();
|
|
12436
|
+
for (const [name, entry] of apps) {
|
|
12437
|
+
if (!identifier2(name) || !object5(entry) || !Object.keys(entry).every((key) => ["id", "category", "required"].includes(key)) || typeof entry.id !== "string" || !/^[A-Za-z0-9_-]{1,256}$/.test(entry.id) || entry.category !== undefined && (typeof entry.category !== "string" || !entry.category.trim() || entry.category.length > 256 || /[\x00-\x1f\x7f]/.test(entry.category)) || entry.required !== undefined && typeof entry.required !== "boolean")
|
|
12438
|
+
refuse2();
|
|
12439
|
+
}
|
|
12440
|
+
return sha2562;
|
|
12441
|
+
}
|
|
12442
|
+
function mcpControls(path, read2) {
|
|
12443
|
+
const text2 = read2(path);
|
|
12444
|
+
let value, sha2562;
|
|
12445
|
+
try {
|
|
12446
|
+
sha2562 = hashNativeJsonControls(text2);
|
|
12447
|
+
value = JSON.parse(text2);
|
|
12448
|
+
} catch {
|
|
12449
|
+
refuse2();
|
|
12450
|
+
}
|
|
12451
|
+
const object5 = (v) => Boolean(v && typeof v === "object" && !Array.isArray(v));
|
|
12452
|
+
if (!object5(value) || Object.keys(value).length !== 1 || !object5(value.mcpServers))
|
|
12453
|
+
refuse2();
|
|
12454
|
+
const servers = Object.entries(value.mcpServers);
|
|
12455
|
+
if (!servers.length || servers.length > 256)
|
|
12456
|
+
refuse2();
|
|
12457
|
+
for (const [name, server] of servers)
|
|
12458
|
+
if (!isCodexLocalStdioMcpServer(name, server))
|
|
12459
|
+
refuse2();
|
|
12460
|
+
return sha2562;
|
|
12461
|
+
}
|
|
12462
|
+
function rootControls(root, read2) {
|
|
12463
|
+
const manifestPath = join14(root, ".codex-plugin/plugin.json"), manifestFile = lstatSync12(manifestPath, { throwIfNoEntry: false });
|
|
12464
|
+
if (!lstatSync12(join14(root, ".codex-plugin"), { throwIfNoEntry: false })?.isDirectory() || !manifestFile?.isFile() || manifestFile.size > 1024 * 1024)
|
|
12465
|
+
refuse2();
|
|
12466
|
+
let manifest, manifestSha256;
|
|
12467
|
+
try {
|
|
12468
|
+
const text2 = read2(manifestPath);
|
|
12469
|
+
manifestSha256 = hashNativeJsonControls(text2, "version");
|
|
12470
|
+
manifest = JSON.parse(text2);
|
|
12471
|
+
} catch {
|
|
12472
|
+
refuse2();
|
|
12473
|
+
}
|
|
12474
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest) || !identifier2(manifest.name))
|
|
12475
|
+
refuse2();
|
|
12476
|
+
const cacheVersion = basename5(root), declaredVersion = manifest.version === undefined ? "" : typeof manifest.version === "string" ? manifest.version.trim() : null;
|
|
12477
|
+
if (declaredVersion === null || (declaredVersion ? declaredVersion !== cacheVersion : cacheVersion !== CODEX_DEFAULT_AGENT_PLUGIN_VERSION))
|
|
12478
|
+
refuse2();
|
|
12479
|
+
if (manifest.hooks !== undefined || manifest.commands !== undefined || manifest.mcpServers !== undefined && manifest.mcpServers !== "./.mcp.json" || manifest.apps !== undefined && manifest.apps !== "./.app.json" || lstatSync12(join14(root, "hooks"), { throwIfNoEntry: false }))
|
|
12480
|
+
refuse2();
|
|
12481
|
+
const mcp = join14(root, ".mcp.json"), mcpPresent = lstatSync12(mcp, { throwIfNoEntry: false });
|
|
12482
|
+
if (mcpPresent && (!mcpPresent.isFile() || mcpPresent.isSymbolicLink() || mcpPresent.size > 1024 * 1024 || realpathSync5(mcp) !== mcp))
|
|
12483
|
+
refuse2();
|
|
12484
|
+
if (Boolean(mcpPresent) !== (manifest.mcpServers !== undefined))
|
|
12485
|
+
refuse2();
|
|
12486
|
+
const mcpSha256 = mcpPresent ? mcpControls(mcp, read2) : undefined;
|
|
12487
|
+
const app = join14(root, ".app.json"), present = lstatSync12(app, { throwIfNoEntry: false });
|
|
12488
|
+
if (present && (!present.isFile() || present.isSymbolicLink() || present.size > 1024 * 1024))
|
|
12489
|
+
refuse2();
|
|
12490
|
+
if (manifest.apps !== undefined && !present)
|
|
12491
|
+
refuse2();
|
|
12492
|
+
const appSha256 = present ? appControls(app, read2) : undefined;
|
|
12493
|
+
return { namespace: manifest.name, manifestSha256, ...appSha256 ? { appSha256 } : {}, ...mcpSha256 ? { mcpSha256 } : {} };
|
|
12494
|
+
}
|
|
12495
|
+
function documentIdentity(document2, cache, read2) {
|
|
12496
|
+
if (!document2.startsWith(cache + sep4) || !document2.endsWith(sep4 + "SKILL.md"))
|
|
12497
|
+
refuse2();
|
|
12498
|
+
const text2 = read2(document2);
|
|
12499
|
+
if (Buffer.byteLength(text2) > 1024 * 1024)
|
|
12500
|
+
refuse2();
|
|
12501
|
+
const lines = text2.split(/\r?\n/);
|
|
12502
|
+
if (lines[0]?.trim() !== "---")
|
|
12503
|
+
refuse2();
|
|
12504
|
+
const end = lines.findIndex((line, i) => i > 0 && line.trim() === "---");
|
|
12505
|
+
if (end < 2)
|
|
12506
|
+
refuse2();
|
|
12507
|
+
const yaml = lines.slice(1, end).join(`
|
|
12508
|
+
`);
|
|
12509
|
+
if (/(?:^|\s)[&*!]|<<\s*:/.test(yaml) || lines.slice(1, end).filter((line) => /^name\s*:/.test(line)).length !== 1)
|
|
12510
|
+
refuse2();
|
|
12511
|
+
let front;
|
|
12512
|
+
try {
|
|
12513
|
+
front = Bun.YAML.parse(yaml);
|
|
12514
|
+
} catch {
|
|
12515
|
+
refuse2();
|
|
12516
|
+
}
|
|
12517
|
+
if (!front || typeof front !== "object" || !identifier2(front.name) || typeof front.description !== "string" || !front.description.trim())
|
|
12518
|
+
refuse2();
|
|
12519
|
+
for (let root = dirname10(document2), depth = 0;root.startsWith(cache + sep4) && depth < 12; root = dirname10(root), depth++) {
|
|
12520
|
+
const path = join14(root, ".codex-plugin", "plugin.json");
|
|
12521
|
+
if (!lstatSync12(path, { throwIfNoEntry: false }))
|
|
12522
|
+
continue;
|
|
12523
|
+
const manifestFile = lstatSync12(path);
|
|
12524
|
+
if (!lstatSync12(join14(root, ".codex-plugin")).isDirectory() || !manifestFile.isFile() || manifestFile.size > 1024 * 1024)
|
|
12525
|
+
refuse2();
|
|
12526
|
+
let manifest;
|
|
12527
|
+
try {
|
|
12528
|
+
const text3 = read2(path);
|
|
12529
|
+
hashNativeJsonControls(text3);
|
|
12530
|
+
manifest = JSON.parse(text3);
|
|
12531
|
+
} catch {
|
|
12532
|
+
refuse2();
|
|
12533
|
+
}
|
|
12534
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest) || !identifier2(manifest.name))
|
|
12535
|
+
refuse2();
|
|
12536
|
+
const declaredVersion = manifest.version === undefined ? "" : typeof manifest.version === "string" ? manifest.version.trim() : null;
|
|
12537
|
+
if (declaredVersion === null || (declaredVersion ? declaredVersion !== basename5(root) : basename5(root) !== CODEX_DEFAULT_AGENT_PLUGIN_VERSION))
|
|
12538
|
+
refuse2();
|
|
12539
|
+
return { name: `${manifest.name}:${front.name}`, namespace: manifest.name, pluginParent: dirname10(root), root };
|
|
12540
|
+
}
|
|
12541
|
+
refuse2();
|
|
12542
|
+
}
|
|
12543
|
+
function identity3(document2, cache, read2) {
|
|
12544
|
+
const parsed = documentIdentity(document2, cache, read2);
|
|
12545
|
+
const controls = rootControls(parsed.root, read2);
|
|
12546
|
+
if (controls.namespace !== parsed.namespace)
|
|
12547
|
+
refuse2();
|
|
12548
|
+
return { name: parsed.name, pluginParent: parsed.pluginParent, ...controls };
|
|
12549
|
+
}
|
|
12550
|
+
function reviewedCodexPluginCapabilitiesUnchanged(cache, controls, read2) {
|
|
12551
|
+
if (!Array.isArray(controls) || controls.length > 4096)
|
|
12552
|
+
return false;
|
|
12553
|
+
let entries = 0;
|
|
12554
|
+
try {
|
|
12555
|
+
for (const parent of new Set(controls.map((control) => control.pluginParent))) {
|
|
12556
|
+
if (!parent.startsWith(cache + sep4) || !lstatSync12(parent, { throwIfNoEntry: false })?.isDirectory())
|
|
12557
|
+
return false;
|
|
12558
|
+
for (const control of controls.filter((control2) => control2.pluginParent === parent)) {
|
|
12559
|
+
if (control.remotePluginId !== undefined && !remoteInstallationMatches(parent, control.remotePluginId, read2))
|
|
12560
|
+
return false;
|
|
12561
|
+
}
|
|
12562
|
+
const names = readdirSync6(parent);
|
|
12563
|
+
entries += names.length;
|
|
12564
|
+
if (entries > 4096)
|
|
12565
|
+
return false;
|
|
12566
|
+
for (const name of names) {
|
|
12567
|
+
let root = join14(parent, name), stat = lstatSync12(root);
|
|
12568
|
+
if (stat.isSymbolicLink()) {
|
|
12569
|
+
const target = resolve9(parent, readlinkSync3(root));
|
|
12570
|
+
if (name !== "latest" || dirname10(target) !== parent || !lstatSync12(target).isDirectory() || realpathSync5(target) !== target)
|
|
12571
|
+
return false;
|
|
12572
|
+
root = target;
|
|
12573
|
+
stat = lstatSync12(root);
|
|
12574
|
+
}
|
|
12575
|
+
if (!stat.isDirectory() || ![".codex-plugin/plugin.json", ".app.json", "hooks", ".mcp.json"].some((path) => lstatSync12(join14(root, path), { throwIfNoEntry: false })))
|
|
12576
|
+
continue;
|
|
12577
|
+
const parsed = rootControls(root, read2);
|
|
12578
|
+
if (!controls.some((control) => control.pluginParent === parent && control.namespace === parsed.namespace && control.manifestSha256 === parsed.manifestSha256 && control.appSha256 === parsed.appSha256 && control.mcpSha256 === parsed.mcpSha256))
|
|
12579
|
+
return false;
|
|
12580
|
+
}
|
|
12581
|
+
}
|
|
12582
|
+
return true;
|
|
12583
|
+
} catch {
|
|
12584
|
+
return false;
|
|
12585
|
+
}
|
|
12586
|
+
}
|
|
12587
|
+
function sourceInputUnchanged(input, cache, read2) {
|
|
12588
|
+
try {
|
|
12589
|
+
const parts = relative4(cache, input.pluginParent).split(sep4);
|
|
12590
|
+
if (parts.length !== 2 || parts.some((part) => !identifier2(part)) || parts[1] !== input.namespace || input.pluginId !== `${parts[1]}@${parts[0]}` || !codexPluginSourceIsConfigControlled({ id: input.pluginId, sourceType: "local", sourceSha256: input.sourceSha256 }) || input.sourceSha256 !== hashNativeJsonControls(JSON.stringify({ type: "local", path: input.sourceRoot })) || resolve9(input.sourceRoot) !== input.sourceRoot || !lstatSync12(input.sourceRoot).isDirectory() || realpathSync5(input.sourceRoot) !== input.sourceRoot || input.sourceRoot === cache || input.sourceRoot.startsWith(cache + sep4) || cache.startsWith(input.sourceRoot + sep4))
|
|
12591
|
+
return false;
|
|
12592
|
+
const path = join14(input.sourceRoot, ".codex-plugin/plugin.json"), stat = lstatSync12(path);
|
|
12593
|
+
if (!stat.isFile() || stat.size > 1024 * 1024 || realpathSync5(path) !== path)
|
|
12594
|
+
return false;
|
|
12595
|
+
const text2 = read2(path);
|
|
12596
|
+
hashNativeJsonControls(text2);
|
|
12597
|
+
const manifest = JSON.parse(text2);
|
|
12598
|
+
return Boolean(manifest && typeof manifest === "object" && !Array.isArray(manifest) && manifest.name === input.namespace);
|
|
12599
|
+
} catch {
|
|
12600
|
+
return false;
|
|
12601
|
+
}
|
|
12602
|
+
}
|
|
12603
|
+
function reviewedCodexPluginSourceRoots(cache, inputs, read2) {
|
|
12604
|
+
if (!Array.isArray(inputs) || inputs.length > 4096 || new Set(inputs.map((input) => input.pluginId)).size !== inputs.length || new Set(inputs.map((input) => input.sourceRoot)).size !== inputs.length || inputs.some((input) => !sourceInputUnchanged(input, cache, read2)))
|
|
12605
|
+
refuse2();
|
|
12606
|
+
const roots = inputs.map((input) => input.sourceRoot);
|
|
12607
|
+
if (roots.some((root) => roots.some((other) => root !== other && root.startsWith(other + sep4))))
|
|
12608
|
+
refuse2();
|
|
12609
|
+
return roots;
|
|
12610
|
+
}
|
|
12611
|
+
function isReviewedCodexPluginInactive(document2, cache, controls, pluginSettings, rules, read2) {
|
|
12612
|
+
if (!Array.isArray(controls) || controls.length > 4096 || !pluginSettings || typeof pluginSettings !== "object" || Array.isArray(pluginSettings) || !Array.isArray(rules))
|
|
12613
|
+
return false;
|
|
12614
|
+
try {
|
|
12615
|
+
const parsed = documentIdentity(document2, cache, read2), matches = controls.filter((control2) => control2.pluginParent === parsed.pluginParent && control2.namespace === parsed.namespace);
|
|
12616
|
+
if (matches.length !== 1 || !lstatSync12(document2).isFile() || realpathSync5(document2) !== document2 || realpathSync5(parsed.root) !== parsed.root || realpathSync5(parsed.pluginParent) !== parsed.pluginParent || realpathSync5(join14(parsed.root, ".codex-plugin/plugin.json")) !== join14(parsed.root, ".codex-plugin/plugin.json"))
|
|
12617
|
+
return false;
|
|
12618
|
+
const control = matches[0], parts = relative4(cache, parsed.pluginParent).split(sep4);
|
|
12619
|
+
if (parts.length !== 2 || parts.some((part) => !identifier2(part)) || parts[1] !== parsed.namespace || control.pluginId !== `${parts[1]}@${parts[0]}` || !codexPluginSourceIsConfigControlled({ ...control, id: control.pluginId }) || !Object.hasOwn(pluginSettings, control.pluginId) || pluginSettings[control.pluginId]?.enabled !== false)
|
|
12620
|
+
return false;
|
|
12621
|
+
return !rules.some((rule) => (rule?.path === document2 || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule?.enabled !== false);
|
|
12622
|
+
} catch {
|
|
12623
|
+
return false;
|
|
12624
|
+
}
|
|
12625
|
+
}
|
|
12626
|
+
function isReviewedCodexPluginSkillDisabled(document2, cache, controls, rules, read2) {
|
|
12627
|
+
if (!document2.startsWith(cache + sep4) || !Array.isArray(controls) || controls.length > 4096)
|
|
12628
|
+
return false;
|
|
12629
|
+
let parsed;
|
|
12630
|
+
try {
|
|
12631
|
+
parsed = identity3(document2, cache, read2);
|
|
12632
|
+
} catch {
|
|
12633
|
+
return false;
|
|
12634
|
+
}
|
|
12635
|
+
const reviewed = controls.find((item) => item.name === parsed.name && item.namespace === parsed.namespace && item.pluginParent === parsed.pluginParent && item.manifestSha256 === parsed.manifestSha256 && item.appSha256 === parsed.appSha256 && item.mcpSha256 === parsed.mcpSha256);
|
|
12636
|
+
if (!reviewed || reviewed.remotePluginId !== undefined && !remoteInstallationMatches(reviewed.pluginParent, reviewed.remotePluginId, read2))
|
|
12637
|
+
return false;
|
|
12638
|
+
return isCodexNativeSkillDisabled({ name: parsed.name, path: document2, pluginId: reviewed.pluginId, enabled: true }, rules);
|
|
12639
|
+
}
|
|
12640
|
+
var sha = (text2) => createHash9("sha256").update(text2).digest("hex");
|
|
12641
|
+
var EFFORT = new Set(["none", "minimal", "low", "medium", "high", "xhigh"]);
|
|
12642
|
+
var VERBOSITY = new Set(["low", "medium", "high"]);
|
|
12643
|
+
function need3(value) {
|
|
12644
|
+
if (!value)
|
|
12645
|
+
throw new Error("Invalid Codex settings witness");
|
|
12646
|
+
}
|
|
12647
|
+
function numericTokens(text2) {
|
|
12648
|
+
const result = [];
|
|
12649
|
+
for (let at = 0;at < text2.length; ) {
|
|
12650
|
+
const char = text2[at];
|
|
12651
|
+
if (char === "#") {
|
|
12652
|
+
while (at < text2.length && text2[at] !== `
|
|
12653
|
+
`)
|
|
12654
|
+
at++;
|
|
12655
|
+
continue;
|
|
12656
|
+
}
|
|
12657
|
+
if (char === '"' || char === "'") {
|
|
12658
|
+
const quote3 = char, triple = text2.slice(at, at + 3) === quote3.repeat(3);
|
|
12659
|
+
at += triple ? 3 : 1;
|
|
12660
|
+
while (at < text2.length) {
|
|
12661
|
+
if (quote3 === '"' && text2[at] === "\\") {
|
|
12662
|
+
at += 2;
|
|
12663
|
+
continue;
|
|
12664
|
+
}
|
|
12665
|
+
if (text2.slice(at, at + (triple ? 3 : 1)) === quote3.repeat(triple ? 3 : 1)) {
|
|
12666
|
+
at += triple ? 3 : 1;
|
|
12667
|
+
break;
|
|
12668
|
+
}
|
|
12669
|
+
at++;
|
|
12670
|
+
}
|
|
12671
|
+
continue;
|
|
12672
|
+
}
|
|
12673
|
+
if (/[0-9+-]/.test(char)) {
|
|
12674
|
+
const start = at;
|
|
12675
|
+
while (at < text2.length && !/[\s,\]{}#=]/.test(text2[at]))
|
|
12676
|
+
at++;
|
|
12677
|
+
if (at > start) {
|
|
12678
|
+
result.push(text2.slice(start, at));
|
|
12679
|
+
continue;
|
|
12680
|
+
}
|
|
12681
|
+
}
|
|
12682
|
+
at++;
|
|
12683
|
+
}
|
|
12684
|
+
return result;
|
|
12685
|
+
}
|
|
12686
|
+
function canonical3(value, depth = 0, budget = { remaining: 65536 }) {
|
|
12687
|
+
need3(depth <= 32 && --budget.remaining >= 0);
|
|
12688
|
+
if (value === null)
|
|
12689
|
+
return ["null"];
|
|
12690
|
+
if (typeof value === "string")
|
|
12691
|
+
return ["string", value];
|
|
12692
|
+
if (typeof value === "boolean")
|
|
12693
|
+
return ["boolean", value];
|
|
12694
|
+
if (typeof value === "number") {
|
|
12695
|
+
need3(Number.isFinite(value));
|
|
12696
|
+
return ["number", String(value)];
|
|
12697
|
+
}
|
|
12698
|
+
if (value instanceof Date) {
|
|
12699
|
+
need3(Number.isFinite(value.getTime()));
|
|
12700
|
+
return ["date", value.toISOString()];
|
|
12701
|
+
}
|
|
12702
|
+
if (Array.isArray(value))
|
|
12703
|
+
return ["array", value.map((child) => canonical3(child, depth + 1, budget))];
|
|
12704
|
+
need3(value && typeof value === "object");
|
|
12705
|
+
return ["object", Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, child]) => [key, canonical3(child, depth + 1, budget)])];
|
|
12706
|
+
}
|
|
12707
|
+
function hashCodexSettingsReplacement(text2, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
12708
|
+
need3(Buffer.byteLength(text2) <= 1024 * 1024 && budget.remaining >= Buffer.byteLength(text2));
|
|
12709
|
+
budget.remaining -= Buffer.byteLength(text2);
|
|
12710
|
+
let config;
|
|
12711
|
+
try {
|
|
12712
|
+
config = Bun.TOML.parse(text2);
|
|
12713
|
+
} catch {
|
|
12714
|
+
throw new Error("Invalid Codex settings witness");
|
|
12715
|
+
}
|
|
12716
|
+
need3(config && typeof config === "object" && !Array.isArray(config));
|
|
12717
|
+
if (Object.hasOwn(config, "model")) {
|
|
12718
|
+
need3(typeof config.model === "string" && /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(config.model));
|
|
12719
|
+
delete config.model;
|
|
12720
|
+
}
|
|
12721
|
+
for (const [key, values] of [["model_reasoning_effort", EFFORT], ["model_verbosity", VERBOSITY]])
|
|
12722
|
+
if (Object.hasOwn(config, key)) {
|
|
12723
|
+
need3(typeof config[key] === "string" && values.has(config[key]));
|
|
12724
|
+
delete config[key];
|
|
12725
|
+
}
|
|
12726
|
+
return sha(`hasna.skills.codex-settings.v1\x00${JSON.stringify([canonical3(config), numericTokens(text2)])}`);
|
|
12727
|
+
}
|
|
12728
|
+
function captureCodexSettings(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
12729
|
+
const text2 = readNativeSettingsWitnessFile(path, budget, "config.toml");
|
|
12730
|
+
return { path, hashMode: "codex-settings-v1", sha256: hashCodexSettingsReplacement(text2) };
|
|
12731
|
+
}
|
|
12732
|
+
function record2(value) {
|
|
12733
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
|
|
12734
|
+
}
|
|
12735
|
+
function localStdio(name, row) {
|
|
12736
|
+
return name !== "codex_apps" && name !== "_codex_apps" && isCodexLocalStdioMcpServer(name, row);
|
|
12737
|
+
}
|
|
12738
|
+
function stripEmptyParent(config) {
|
|
12739
|
+
if (record2(config.mcp_servers) && !Object.keys(config.mcp_servers).length)
|
|
12740
|
+
delete config.mcp_servers;
|
|
12741
|
+
return config;
|
|
12742
|
+
}
|
|
12743
|
+
function projectRegularCodexMcp(config, source) {
|
|
12744
|
+
const unchanged2 = { config, numericSource: source };
|
|
12745
|
+
if (!record2(config.mcp_servers))
|
|
12746
|
+
return unchanged2;
|
|
12747
|
+
const candidates = Object.entries(config.mcp_servers).filter(([name, row]) => localStdio(name, row)).map(([name]) => name);
|
|
12748
|
+
if (!candidates.length)
|
|
12749
|
+
return unchanged2;
|
|
12750
|
+
const headers = [];
|
|
12751
|
+
let attempts = 0;
|
|
12752
|
+
for (const match of source.matchAll(/^[ \t]*\[{1,2}[^\r\n]*\]{1,2}[ \t]*(?:#[^\r\n]*)?(?:\r?\n|$)/gm)) {
|
|
12753
|
+
if (++attempts > 256)
|
|
12754
|
+
return unchanged2;
|
|
12755
|
+
try {
|
|
12756
|
+
Bun.TOML.parse(source.slice(0, match.index));
|
|
12757
|
+
} catch {
|
|
12758
|
+
continue;
|
|
12759
|
+
}
|
|
12760
|
+
let header;
|
|
12761
|
+
try {
|
|
12762
|
+
header = Bun.TOML.parse(match[0]);
|
|
12763
|
+
} catch {
|
|
12764
|
+
return unchanged2;
|
|
12765
|
+
}
|
|
12766
|
+
const path = [];
|
|
12767
|
+
while (record2(header) && Object.keys(header).length === 1) {
|
|
12768
|
+
const key = Object.keys(header)[0];
|
|
12769
|
+
path.push(key);
|
|
12770
|
+
header = header[key];
|
|
12771
|
+
}
|
|
12772
|
+
headers.push({ start: match.index, path });
|
|
12773
|
+
}
|
|
12774
|
+
const names = candidates.filter((name) => headers.filter((header) => header.path.length === 2 && header.path[0] === "mcp_servers" && header.path[1] === name).length === 1);
|
|
12775
|
+
if (!names.length)
|
|
12776
|
+
return unchanged2;
|
|
12777
|
+
const selected = headers.map((header, index) => ({ ...header, end: headers[index + 1]?.start ?? source.length })).filter((header) => header.path[0] === "mcp_servers" && names.includes(header.path[1]));
|
|
12778
|
+
let numericSource = source;
|
|
12779
|
+
for (const range of selected.reverse())
|
|
12780
|
+
numericSource = numericSource.slice(0, range.start) + numericSource.slice(range.end);
|
|
12781
|
+
const expected = structuredClone(config);
|
|
12782
|
+
for (const name of names)
|
|
12783
|
+
delete expected.mcp_servers[name];
|
|
12784
|
+
stripEmptyParent(expected);
|
|
12785
|
+
try {
|
|
12786
|
+
if (!isDeepStrictEqual2(stripEmptyParent(Bun.TOML.parse(numericSource)), expected))
|
|
12787
|
+
return unchanged2;
|
|
12788
|
+
} catch {
|
|
12789
|
+
return unchanged2;
|
|
12790
|
+
}
|
|
12791
|
+
return { config: expected, numericSource };
|
|
12792
|
+
}
|
|
12793
|
+
var sha2 = (text2) => createHash10("sha256").update(text2).digest("hex");
|
|
12794
|
+
var EFFORT2 = new Set(["none", "minimal", "low", "medium", "high", "xhigh"]);
|
|
12795
|
+
var VERBOSITY2 = new Set(["low", "medium", "high"]);
|
|
12796
|
+
var MODEL = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
|
|
12797
|
+
var CODEX_DISCOVERY_PROJECTION_FIELDS = ["plugins", "marketplaces", "skills"];
|
|
12798
|
+
var INFERENCE = ["model", "model_reasoning_effort", "model_verbosity"];
|
|
12799
|
+
function need4(value) {
|
|
12800
|
+
if (!value)
|
|
12801
|
+
throw new Error("Invalid Codex settings witness");
|
|
12802
|
+
}
|
|
12803
|
+
function isRecord3(value) {
|
|
12804
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
12805
|
+
}
|
|
12806
|
+
function isDate(value) {
|
|
12807
|
+
return value instanceof Date;
|
|
12808
|
+
}
|
|
12809
|
+
function numericTokens2(text2) {
|
|
12810
|
+
const result = [];
|
|
12811
|
+
for (let at = 0;at < text2.length; ) {
|
|
12812
|
+
const char = text2[at];
|
|
12813
|
+
if (char === "#") {
|
|
12814
|
+
while (at < text2.length && text2[at] !== `
|
|
12815
|
+
`)
|
|
12816
|
+
at++;
|
|
12817
|
+
continue;
|
|
12818
|
+
}
|
|
12819
|
+
if (char === '"' || char === "'") {
|
|
12820
|
+
const quote3 = char, triple = text2.slice(at, at + 3) === quote3.repeat(3);
|
|
12821
|
+
at += triple ? 3 : 1;
|
|
12822
|
+
while (at < text2.length) {
|
|
12823
|
+
if (quote3 === '"' && text2[at] === "\\") {
|
|
12824
|
+
at += 2;
|
|
12825
|
+
continue;
|
|
12826
|
+
}
|
|
12827
|
+
if (text2.slice(at, at + (triple ? 3 : 1)) === quote3.repeat(triple ? 3 : 1)) {
|
|
12828
|
+
at += triple ? 3 : 1;
|
|
12829
|
+
break;
|
|
12830
|
+
}
|
|
12831
|
+
at++;
|
|
12832
|
+
}
|
|
12833
|
+
continue;
|
|
12834
|
+
}
|
|
12835
|
+
if (/[0-9+-]/.test(char)) {
|
|
12836
|
+
const start = at;
|
|
12837
|
+
while (at < text2.length && !/[\s,\]{}#=]/.test(text2[at]))
|
|
12838
|
+
at++;
|
|
12839
|
+
if (at > start) {
|
|
12840
|
+
result.push(text2.slice(start, at));
|
|
12841
|
+
continue;
|
|
12842
|
+
}
|
|
12843
|
+
}
|
|
12844
|
+
at++;
|
|
12845
|
+
}
|
|
12846
|
+
return result.sort();
|
|
12847
|
+
}
|
|
12848
|
+
function canonical4(value, depth = 0, budget = { remaining: 65536 }) {
|
|
12849
|
+
need4(depth <= 32 && --budget.remaining >= 0);
|
|
12850
|
+
if (value === null)
|
|
12851
|
+
return ["null"];
|
|
12852
|
+
if (typeof value === "string")
|
|
12853
|
+
return ["string", value];
|
|
12854
|
+
if (typeof value === "boolean")
|
|
12855
|
+
return ["boolean", value];
|
|
12856
|
+
if (typeof value === "number") {
|
|
12857
|
+
need4(Number.isFinite(value));
|
|
12858
|
+
return ["number", String(value)];
|
|
12859
|
+
}
|
|
12860
|
+
if (isDate(value)) {
|
|
12861
|
+
need4(Number.isFinite(value.getTime()));
|
|
12862
|
+
return ["date", value.toISOString()];
|
|
12863
|
+
}
|
|
12864
|
+
if (Array.isArray(value))
|
|
12865
|
+
return ["array", value.map((child) => canonical4(child, depth + 1, budget))];
|
|
12866
|
+
need4(isRecord3(value));
|
|
12867
|
+
return ["object", Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, child]) => [key, canonical4(child, depth + 1, budget)])];
|
|
12868
|
+
}
|
|
12869
|
+
function normalize3(config, version = 2) {
|
|
12870
|
+
if (isRecord3(config.hooks)) {
|
|
12871
|
+
delete config.hooks.state;
|
|
12872
|
+
if (!Object.keys(config.hooks).length)
|
|
12873
|
+
delete config.hooks;
|
|
12874
|
+
}
|
|
12875
|
+
if (isRecord3(config.skills) && Array.isArray(config.skills.config)) {
|
|
12876
|
+
const skills = config.skills;
|
|
12877
|
+
const retained = skills.config.filter((entry) => !disablesOneSkill(entry));
|
|
12878
|
+
if (retained.length !== skills.config.length) {
|
|
12879
|
+
if (retained.length)
|
|
12880
|
+
skills.config = retained;
|
|
12881
|
+
else
|
|
12882
|
+
delete skills.config;
|
|
12883
|
+
}
|
|
12884
|
+
if (!Object.keys(skills).length)
|
|
12885
|
+
delete config.skills;
|
|
12886
|
+
}
|
|
12887
|
+
inference(config, version);
|
|
12888
|
+
if (isRecord3(config.profiles)) {
|
|
12889
|
+
for (const profile of Object.values(config.profiles))
|
|
12890
|
+
if (isRecord3(profile))
|
|
12891
|
+
inference(profile, version);
|
|
12892
|
+
}
|
|
12893
|
+
}
|
|
12894
|
+
function disablesOneSkill(entry) {
|
|
12895
|
+
if (!isRecord3(entry) || entry.enabled !== false)
|
|
12896
|
+
return false;
|
|
12897
|
+
const keys = Object.keys(entry);
|
|
12898
|
+
if (!keys.every((key) => key === "path" || key === "name" || key === "enabled"))
|
|
12899
|
+
return false;
|
|
12900
|
+
const selector = ["path", "name"].filter((key) => Object.hasOwn(entry, key));
|
|
12901
|
+
return selector.length === 1 && typeof entry[selector[0]] === "string" && entry[selector[0]].length > 0;
|
|
12902
|
+
}
|
|
12903
|
+
function inference(target, version) {
|
|
12904
|
+
const keys = version === 3 ? [...INFERENCE, "service_tier", "plan_mode_reasoning_effort", "personality"] : INFERENCE;
|
|
12905
|
+
for (const key of keys)
|
|
12906
|
+
if (Object.hasOwn(target, key)) {
|
|
12907
|
+
const value = target[key];
|
|
12908
|
+
if (key === "model")
|
|
12909
|
+
need4(typeof value === "string" && MODEL.test(value));
|
|
12910
|
+
else if (key === "personality")
|
|
12911
|
+
need4(typeof value === "string" && ["none", "friendly", "pragmatic"].includes(value));
|
|
12912
|
+
else if (version === 3 && key !== "model_verbosity") {
|
|
12913
|
+
need4(typeof value === "string" && value.length <= 128 && !/[\x00-\x1f\x7f]/.test(value));
|
|
12914
|
+
if (key !== "service_tier")
|
|
12915
|
+
need4(value.length > 0);
|
|
12916
|
+
} else
|
|
12917
|
+
need4(typeof value === "string" && (key === "model_reasoning_effort" ? EFFORT2.has(value) : VERBOSITY2.has(value)));
|
|
12918
|
+
delete target[key];
|
|
12919
|
+
}
|
|
12920
|
+
}
|
|
12921
|
+
function settingsDigest2(text2, version = 2) {
|
|
12922
|
+
let config;
|
|
12923
|
+
try {
|
|
12924
|
+
config = Bun.TOML.parse(text2);
|
|
12925
|
+
} catch {
|
|
12926
|
+
throw new Error("Invalid Codex settings witness");
|
|
12927
|
+
}
|
|
12928
|
+
need4(isRecord3(config));
|
|
12929
|
+
const projected = version === 3 ? projectRegularCodexMcp(config, text2) : { config, numericSource: text2 };
|
|
12930
|
+
normalize3(projected.config, version);
|
|
12931
|
+
return sha2(`hasna.skills.codex-settings.v${version}\x00${JSON.stringify([canonical4(projected.config), numericTokens2(projected.numericSource)])}`);
|
|
12932
|
+
}
|
|
12933
|
+
function hashCodexSettingsReplacementV2(text2, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
12934
|
+
need4(Buffer.byteLength(text2) <= 1024 * 1024 && budget.remaining >= Buffer.byteLength(text2));
|
|
12935
|
+
budget.remaining -= Buffer.byteLength(text2);
|
|
12936
|
+
return settingsDigest2(text2);
|
|
12937
|
+
}
|
|
12938
|
+
function captureCodexSettingsV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
12939
|
+
const text2 = readNativeSettingsWitnessFile(path, budget, "config.toml");
|
|
12940
|
+
return { path, hashMode: "codex-settings-v2", sha256: settingsDigest2(text2) };
|
|
12941
|
+
}
|
|
12942
|
+
function readCodexSettingsPreimage(path, budget = { remaining: 2 * 1024 * 1024 }) {
|
|
12943
|
+
return readNativeSettingsWitnessFile(path, budget, "config.toml");
|
|
12944
|
+
}
|
|
12945
|
+
function upgradeCodexSettingsWitness(previous, reviewedSettingsPath) {
|
|
12946
|
+
need4(previous && Object.keys(previous).every((key) => ["path", "hashMode", "sha256"].includes(key)) && (previous.hashMode === undefined || previous.hashMode === "bytes" || previous.hashMode === "codex-settings-v1") && /^[a-f0-9]{64}$/.test(previous.sha256));
|
|
12947
|
+
const budget = { remaining: 2 * 1024 * 1024 };
|
|
12948
|
+
const before = readNativeSettingsWitnessFile(reviewedSettingsPath, budget, "config.toml"), current2 = readNativeSettingsWitnessFile(previous.path, budget, "config.toml");
|
|
12949
|
+
need4((previous.hashMode === "codex-settings-v1" ? hashCodexSettingsReplacement(before) : sha2(before)) === previous.sha256);
|
|
12950
|
+
const sha2562 = settingsDigest2(current2);
|
|
12951
|
+
if (settingsDigest2(before) !== sha2562)
|
|
12952
|
+
throw new Error("Codex non-preference settings changed; explicit discovery review required");
|
|
12953
|
+
return { path: previous.path, hashMode: "codex-settings-v2", sha256: sha2562 };
|
|
12954
|
+
}
|
|
12955
|
+
function hashCodexSettingsReplacementV3(text2, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
12956
|
+
need4(Buffer.byteLength(text2) <= 1024 * 1024 && budget.remaining >= Buffer.byteLength(text2));
|
|
12957
|
+
budget.remaining -= Buffer.byteLength(text2);
|
|
12958
|
+
return settingsDigest2(text2, 3);
|
|
12959
|
+
}
|
|
12960
|
+
function captureCodexSettingsV3(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
12961
|
+
return { path, hashMode: "codex-settings-v3", sha256: settingsDigest2(readNativeSettingsWitnessFile(path, budget, "config.toml"), 3) };
|
|
12962
|
+
}
|
|
12963
|
+
function upgradeCodexSettingsWitnessV3(previous, reviewedSettingsPath) {
|
|
12964
|
+
need4(previous && Object.keys(previous).every((key) => ["path", "hashMode", "sha256"].includes(key)) && (previous.hashMode === undefined || ["bytes", "codex-settings-v1", "codex-settings-v2"].includes(previous.hashMode)) && /^[a-f0-9]{64}$/.test(previous.sha256));
|
|
12965
|
+
const budget = { remaining: 2 * 1024 * 1024 };
|
|
12966
|
+
const before = readNativeSettingsWitnessFile(reviewedSettingsPath, budget, "config.toml"), current2 = readNativeSettingsWitnessFile(previous.path, budget, "config.toml");
|
|
12967
|
+
const original = previous.hashMode === "codex-settings-v2" ? settingsDigest2(before) : previous.hashMode === "codex-settings-v1" ? hashCodexSettingsReplacement(before) : sha2(before);
|
|
12968
|
+
need4(original === previous.sha256);
|
|
12969
|
+
const sha2562 = settingsDigest2(current2, 3);
|
|
12970
|
+
if (settingsDigest2(before, 3) !== sha2562)
|
|
12971
|
+
throw new Error("Codex settings outside the reviewed V3 contract changed; explicit discovery review required");
|
|
12972
|
+
return { path: previous.path, hashMode: "codex-settings-v3", sha256: sha2562 };
|
|
12973
|
+
}
|
|
12974
|
+
function refusal(reason = "is unreadable") {
|
|
12975
|
+
return new SkillSelectionError("INVALID_AGENT_POLICY", `The Skills agent loading policy ${reason}; refusing legacy fallback.`);
|
|
12976
|
+
}
|
|
12977
|
+
function parseManagedSkillPolicy(text2) {
|
|
12978
|
+
if (Buffer.byteLength(text2) > AGENT_POLICY_LIMITS.bytes)
|
|
12979
|
+
throw refusal("exceeds its size limit");
|
|
12980
|
+
let value;
|
|
12981
|
+
try {
|
|
12982
|
+
value = JSON.parse(text2);
|
|
12983
|
+
} catch {
|
|
12984
|
+
throw refusal();
|
|
12985
|
+
}
|
|
12986
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || value.loading !== "cli" || value.version !== undefined && value.version !== 1)
|
|
12987
|
+
throw refusal();
|
|
12988
|
+
if (value.profileId !== undefined && (typeof value.profileId !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value.profileId) || value.profileId.includes("..")))
|
|
12989
|
+
throw refusal();
|
|
12990
|
+
try {
|
|
12991
|
+
assertAgentPolicyCollections(value);
|
|
12992
|
+
} catch {
|
|
12993
|
+
throw refusal("has invalid collection bounds");
|
|
12994
|
+
}
|
|
12995
|
+
return value;
|
|
12996
|
+
}
|
|
12997
|
+
function serializeManagedSkillPolicy(value) {
|
|
12998
|
+
const text2 = `${JSON.stringify(value, null, 2)}
|
|
12999
|
+
`;
|
|
13000
|
+
parseManagedSkillPolicy(text2);
|
|
13001
|
+
return text2;
|
|
13002
|
+
}
|
|
13003
|
+
function readManagedSkillPolicySnapshot(dataDir22 = getDataDirReadOnly()) {
|
|
13004
|
+
const path = join15(dataDir22, "agent-policy.json");
|
|
13005
|
+
let descriptor, observed = false;
|
|
13006
|
+
try {
|
|
13007
|
+
const stat = lstatSync13(path);
|
|
13008
|
+
observed = true;
|
|
13009
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
13010
|
+
throw refusal();
|
|
13011
|
+
if (stat.size > AGENT_POLICY_LIMITS.bytes)
|
|
13012
|
+
throw refusal("exceeds its size limit");
|
|
13013
|
+
descriptor = openSync7(path, constants6.O_RDONLY | (constants6.O_NOFOLLOW ?? 0) | constants6.O_NONBLOCK);
|
|
13014
|
+
const opened = fstatSync7(descriptor);
|
|
13015
|
+
if (!opened.isFile())
|
|
13016
|
+
throw refusal();
|
|
13017
|
+
if (opened.size > AGENT_POLICY_LIMITS.bytes)
|
|
13018
|
+
throw refusal("exceeds its size limit");
|
|
13019
|
+
const bytes = Buffer.allocUnsafe(AGENT_POLICY_LIMITS.bytes + 1);
|
|
13020
|
+
let length = 0;
|
|
13021
|
+
while (length < bytes.length) {
|
|
13022
|
+
const count = readSync5(descriptor, bytes, length, bytes.length - length, null);
|
|
13023
|
+
if (count === 0)
|
|
13024
|
+
break;
|
|
13025
|
+
length += count;
|
|
13026
|
+
}
|
|
13027
|
+
if (length > AGENT_POLICY_LIMITS.bytes)
|
|
13028
|
+
throw refusal("exceeds its size limit");
|
|
13029
|
+
const text2 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes.subarray(0, length));
|
|
13030
|
+
return { text: text2, value: parseManagedSkillPolicy(text2) };
|
|
13031
|
+
} catch (error) {
|
|
13032
|
+
if (error.code === "ENOENT" && !observed) {
|
|
13033
|
+
if (lstatSync13(path, { throwIfNoEntry: false }) === undefined)
|
|
13034
|
+
return null;
|
|
13035
|
+
}
|
|
13036
|
+
if (error instanceof SkillSelectionError)
|
|
13037
|
+
throw error;
|
|
13038
|
+
throw refusal();
|
|
13039
|
+
} finally {
|
|
13040
|
+
if (descriptor !== undefined)
|
|
13041
|
+
closeSync7(descriptor);
|
|
13042
|
+
}
|
|
13043
|
+
}
|
|
13044
|
+
var CLI_BRIDGE_NAME = "skills-cli";
|
|
13045
|
+
var CLI_BRIDGE_VERSION = 1;
|
|
13046
|
+
var markdown = `---
|
|
13047
|
+
name: skills-cli
|
|
13048
|
+
description: Discover, load, author, and run versioned skills through the Skills CLI when a task needs reusable instructions or an executable skill.
|
|
13049
|
+
---
|
|
13050
|
+
|
|
13051
|
+
Use the Skills CLI as the skill authority for this station.
|
|
13052
|
+
|
|
13053
|
+
- Discover available skills with \`skills list --json\` or \`skills search <query> --json\`.
|
|
13054
|
+
- Read selected instructions with \`skills load <slug>\`. Use \`skills sync\` to refresh the station's shared selection profile.
|
|
13055
|
+
- Create local drafts with \`skills new <name> --kind instruction\` or \`--kind executable\` in the Skills-owned source directory. Validate with \`skills validate <name>\`; use \`skills push --help\` for explicit versioned publication.
|
|
13056
|
+
- Run a skill only when the user's task calls for execution. Use \`skills run --help\` and select the intended local or cloud target explicitly.
|
|
13057
|
+
|
|
13058
|
+
This bridge contains no skill payloads. Do not copy skill content into agent-native skill directories or load other native skill copies. If a Skills command refuses authority, selection, integrity, or native drift, report that refusal and use its repair guidance; do not substitute bundled or stale local content. Explicit local draft authoring remains available.
|
|
13059
|
+
`;
|
|
13060
|
+
var CLI_BRIDGE_DIGEST = createHash11("sha256").update(markdown).digest("hex");
|
|
13061
|
+
var CLI_BRIDGE_FILES = Object.freeze({
|
|
13062
|
+
"SKILL.md": markdown,
|
|
13063
|
+
".hasna-skills.json": `${JSON.stringify({ managedBy: "@hasna/skills", kind: "cli-bridge", version: CLI_BRIDGE_VERSION, protocol: "selection-v1", contentSha256: CLI_BRIDGE_DIGEST }, null, 2)}
|
|
13064
|
+
`
|
|
13065
|
+
});
|
|
13066
|
+
function isOwnedCliBridge(path, expectedPaths) {
|
|
13067
|
+
if (!expectedPaths.includes(path))
|
|
13068
|
+
return false;
|
|
13069
|
+
try {
|
|
13070
|
+
if (!lstatSync14(path).isDirectory())
|
|
13071
|
+
return false;
|
|
13072
|
+
if (JSON.stringify(readdirSync7(path).sort()) !== JSON.stringify(Object.keys(CLI_BRIDGE_FILES).sort()))
|
|
13073
|
+
return false;
|
|
13074
|
+
return Object.entries(CLI_BRIDGE_FILES).every(([name, content]) => {
|
|
13075
|
+
const file = join16(path, name), stat = lstatSync14(file);
|
|
13076
|
+
return stat.isFile() && !stat.isSymbolicLink() && stat.size === Buffer.byteLength(content) && readFileSync11(file, "utf8") === content;
|
|
13077
|
+
});
|
|
13078
|
+
} catch {
|
|
13079
|
+
return false;
|
|
13080
|
+
}
|
|
13081
|
+
}
|
|
13082
|
+
var sha3 = (data) => createHash12("sha256").update(data).digest("hex");
|
|
12292
13083
|
var ROOTS = NATIVE_SKILL_ROOTS;
|
|
13084
|
+
function hasNoDarwinAcl(fd) {
|
|
13085
|
+
try {
|
|
13086
|
+
const { dlopen, read: read2 } = __require("bun:ffi");
|
|
13087
|
+
const library = dlopen("/usr/lib/libSystem.B.dylib", {
|
|
13088
|
+
acl_get_fd: { args: ["i32"], returns: "ptr" },
|
|
13089
|
+
acl_free: { args: ["ptr"], returns: "i32" },
|
|
13090
|
+
__error: { args: [], returns: "ptr" }
|
|
13091
|
+
});
|
|
13092
|
+
try {
|
|
13093
|
+
const errno = library.symbols.__error();
|
|
13094
|
+
if (!errno)
|
|
13095
|
+
return false;
|
|
13096
|
+
const acl = library.symbols.acl_get_fd(fd);
|
|
13097
|
+
if (acl) {
|
|
13098
|
+
library.symbols.acl_free(acl);
|
|
13099
|
+
return false;
|
|
13100
|
+
}
|
|
13101
|
+
return (acl === null || acl === 0) && read2.i32(errno) === 2;
|
|
13102
|
+
} finally {
|
|
13103
|
+
library.close();
|
|
13104
|
+
}
|
|
13105
|
+
} catch {
|
|
13106
|
+
return false;
|
|
13107
|
+
}
|
|
13108
|
+
}
|
|
13109
|
+
function isSystemRootAlias(path) {
|
|
13110
|
+
if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(path))
|
|
13111
|
+
return false;
|
|
13112
|
+
const target = `/private${path}`, link = `private${path}`;
|
|
13113
|
+
const identity4 = (stat) => `${stat.dev}:${stat.ino}:${stat.ctimeNs}:${stat.uid}:${stat.mode}`;
|
|
13114
|
+
try {
|
|
13115
|
+
const before = lstatSync15(path, { bigint: true });
|
|
13116
|
+
if (!before.isSymbolicLink() || before.uid !== 0n || readlinkSync4(path) !== link)
|
|
13117
|
+
return false;
|
|
13118
|
+
const directories = ["/", "/private", target].map((directory) => {
|
|
13119
|
+
const stat = lstatSync15(directory, { bigint: true });
|
|
13120
|
+
if (!stat.isDirectory() || stat.uid !== 0n)
|
|
13121
|
+
throw new Error("Untrusted system alias directory");
|
|
13122
|
+
if (directory !== "/private/tmp") {
|
|
13123
|
+
if ((stat.mode & 0o022n) !== 0n)
|
|
13124
|
+
throw new Error("Writable system alias directory");
|
|
13125
|
+
} else if ((stat.mode & 0o1000n) === 0n)
|
|
13126
|
+
throw new Error("Unprotected system temporary directory");
|
|
13127
|
+
const fd = openSync8(directory, constants7.O_RDONLY | constants7.O_DIRECTORY | constants7.O_NOFOLLOW);
|
|
13128
|
+
try {
|
|
13129
|
+
if (identity4(fstatSync8(fd, { bigint: true })) !== identity4(stat) || !hasNoDarwinAcl(fd) || identity4(fstatSync8(fd, { bigint: true })) !== identity4(stat))
|
|
13130
|
+
throw new Error("Unverified system alias directory ACL");
|
|
13131
|
+
} finally {
|
|
13132
|
+
closeSync8(fd);
|
|
13133
|
+
}
|
|
13134
|
+
return { directory, identity: identity4(stat) };
|
|
13135
|
+
});
|
|
13136
|
+
return realpathSync6(path) === target && readlinkSync4(path) === link && identity4(lstatSync15(path, { bigint: true })) === identity4(before) && directories.every((item) => identity4(lstatSync15(item.directory, { bigint: true })) === item.identity);
|
|
13137
|
+
} catch {
|
|
13138
|
+
return false;
|
|
13139
|
+
}
|
|
13140
|
+
}
|
|
13141
|
+
function canonicalSystemPath(path) {
|
|
13142
|
+
const absolute = resolve10(path), root = `/${absolute.split(sep5)[1]}`;
|
|
13143
|
+
if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !lstatSync15(root, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
13144
|
+
return absolute;
|
|
13145
|
+
if (!isSystemRootAlias(root))
|
|
13146
|
+
throw new Error(`Refusing symlink path: ${root}`);
|
|
13147
|
+
return `/private${absolute}`;
|
|
13148
|
+
}
|
|
12293
13149
|
function assertSafePath(path) {
|
|
12294
|
-
let cursor =
|
|
13150
|
+
let cursor = resolve10(path);
|
|
12295
13151
|
while (true) {
|
|
12296
|
-
if (
|
|
13152
|
+
if (lstatSync15(cursor, { throwIfNoEntry: false })?.isSymbolicLink() && !isSystemRootAlias(cursor))
|
|
12297
13153
|
throw new Error(`Refusing symlink path: ${cursor}`);
|
|
12298
|
-
const parent =
|
|
13154
|
+
const parent = dirname11(cursor);
|
|
12299
13155
|
if (parent === cursor)
|
|
12300
13156
|
return;
|
|
12301
13157
|
cursor = parent;
|
|
@@ -12303,17 +13159,17 @@ function assertSafePath(path) {
|
|
|
12303
13159
|
}
|
|
12304
13160
|
function readRootAlias(home, agent) {
|
|
12305
13161
|
assertSafePath(home);
|
|
12306
|
-
const realHome =
|
|
12307
|
-
const stat =
|
|
13162
|
+
const realHome = realpathSync6(home), alias = join17(realHome, `.${agent}`);
|
|
13163
|
+
const stat = lstatSync15(alias, { throwIfNoEntry: false, bigint: true });
|
|
12308
13164
|
if (!stat?.isSymbolicLink())
|
|
12309
13165
|
throw new Error(`Agent root alias changed after planning: ${alias}`);
|
|
12310
|
-
const link =
|
|
13166
|
+
const link = readlinkSync4(alias), targetPath = resolve10(dirname11(alias), link);
|
|
12311
13167
|
assertSafePath(targetPath);
|
|
12312
|
-
const targetStat =
|
|
13168
|
+
const targetStat = lstatSync15(targetPath, { throwIfNoEntry: false });
|
|
12313
13169
|
if (!targetStat?.isDirectory())
|
|
12314
13170
|
throw new Error(`Agent root alias must target an existing directory: ${alias}`);
|
|
12315
|
-
const target =
|
|
12316
|
-
if (!inside || inside === ".." || inside.startsWith(`..${
|
|
13171
|
+
const target = realpathSync6(targetPath), inside = relative5(realHome, target);
|
|
13172
|
+
if (!inside || inside === ".." || inside.startsWith(`..${sep5}`) || isAbsolute11(inside))
|
|
12317
13173
|
throw new Error(`Agent root alias target must be inside the real home: ${alias}`);
|
|
12318
13174
|
return { agent, home: realHome, alias, target, link, aliasIdentity: `${stat.dev}:${stat.ino}:${stat.ctimeNs}`, targetIdentity: `${targetStat.dev}:${targetStat.ino}` };
|
|
12319
13175
|
}
|
|
@@ -12322,19 +13178,46 @@ function rootAliases(home, allowed = false) {
|
|
|
12322
13178
|
return [];
|
|
12323
13179
|
const result = [];
|
|
12324
13180
|
for (const agent of ["claude", "codex"])
|
|
12325
|
-
if (
|
|
13181
|
+
if (lstatSync15(join17(home, `.${agent}`), { throwIfNoEntry: false })?.isSymbolicLink())
|
|
12326
13182
|
result.push(readRootAlias(home, agent));
|
|
12327
|
-
if (result.some((item, index) => result.slice(index + 1).some((other) => item.target === other.target || item.target.startsWith(other.target +
|
|
13183
|
+
if (result.some((item, index) => result.slice(index + 1).some((other) => item.target === other.target || item.target.startsWith(other.target + sep5) || other.target.startsWith(item.target + sep5))))
|
|
12328
13184
|
throw new Error("Agent root alias targets must not overlap");
|
|
12329
13185
|
return result;
|
|
12330
13186
|
}
|
|
12331
13187
|
function canonicalAgentPath(path, aliases) {
|
|
12332
|
-
const absolute =
|
|
12333
|
-
return binding ?
|
|
13188
|
+
const absolute = canonicalSystemPath(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep5));
|
|
13189
|
+
return binding ? join17(binding.target, relative5(binding.alias, absolute)) : absolute;
|
|
13190
|
+
}
|
|
13191
|
+
function disabledCodexSkillPaths(config, aliases) {
|
|
13192
|
+
const skills = config.skills;
|
|
13193
|
+
if (!skills || typeof skills !== "object" || Array.isArray(skills))
|
|
13194
|
+
return [];
|
|
13195
|
+
const rows = skills.config;
|
|
13196
|
+
if (!Array.isArray(rows))
|
|
13197
|
+
return [];
|
|
13198
|
+
if (rows.some((entry) => !entry || typeof entry !== "object" || Array.isArray(entry) || typeof entry.enabled !== "boolean" || entry.path !== undefined === (entry.name !== undefined) || entry.path !== undefined && (typeof entry.path !== "string" || !isAbsolute11(entry.path)) || entry.name !== undefined && (typeof entry.name !== "string" || !entry.name.trim())))
|
|
13199
|
+
return [];
|
|
13200
|
+
if (rows.some((entry) => entry.name !== undefined && entry.enabled !== false))
|
|
13201
|
+
return [];
|
|
13202
|
+
const settings = new Map;
|
|
13203
|
+
for (const entry of rows) {
|
|
13204
|
+
if (typeof entry.path !== "string" || !isAbsolute11(entry.path))
|
|
13205
|
+
continue;
|
|
13206
|
+
let document2;
|
|
13207
|
+
try {
|
|
13208
|
+
document2 = realpathSync6(canonicalAgentPath(entry.path, aliases));
|
|
13209
|
+
} catch {
|
|
13210
|
+
continue;
|
|
13211
|
+
}
|
|
13212
|
+
if (!document2.endsWith(`${sep5}SKILL.md`))
|
|
13213
|
+
continue;
|
|
13214
|
+
settings.set(document2, [...settings.get(document2) ?? [], entry.enabled === false]);
|
|
13215
|
+
}
|
|
13216
|
+
return [...settings].filter(([, values]) => values.length === 1 && values[0] === true).map(([document2]) => dirname11(document2));
|
|
12334
13217
|
}
|
|
12335
13218
|
function recheckRootAliases(aliases) {
|
|
12336
13219
|
for (const binding of aliases) {
|
|
12337
|
-
if (!["claude", "codex"].includes(binding.agent) || binding.alias !==
|
|
13220
|
+
if (!["claude", "codex"].includes(binding.agent) || binding.alias !== join17(binding.home, `.${binding.agent}`))
|
|
12338
13221
|
throw new Error("Invalid agent root alias binding");
|
|
12339
13222
|
const current2 = readRootAlias(binding.home, binding.agent);
|
|
12340
13223
|
if (JSON.stringify(current2) !== JSON.stringify(binding))
|
|
@@ -12343,12 +13226,12 @@ function recheckRootAliases(aliases) {
|
|
|
12343
13226
|
}
|
|
12344
13227
|
function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
|
|
12345
13228
|
assertSafePath(path);
|
|
12346
|
-
const before =
|
|
13229
|
+
const before = lstatSync15(path);
|
|
12347
13230
|
if (!before.isFile() || before.size > maximum)
|
|
12348
13231
|
throw new Error("Unsupported or oversized native skill file");
|
|
12349
|
-
const descriptor =
|
|
13232
|
+
const descriptor = openSync8(path, constants7.O_RDONLY | constants7.O_NOFOLLOW | constants7.O_NONBLOCK);
|
|
12350
13233
|
try {
|
|
12351
|
-
const opened =
|
|
13234
|
+
const opened = fstatSync8(descriptor);
|
|
12352
13235
|
if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino || opened.size !== before.size)
|
|
12353
13236
|
throw new Error("Native skill file changed while reading");
|
|
12354
13237
|
const bytes = Buffer.allocUnsafe(before.size + 1);
|
|
@@ -12359,26 +13242,26 @@ function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
|
|
|
12359
13242
|
break;
|
|
12360
13243
|
length += count;
|
|
12361
13244
|
}
|
|
12362
|
-
const after =
|
|
12363
|
-
if (length !== before.size || after.size !== before.size || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs)
|
|
13245
|
+
const after = fstatSync8(descriptor), current2 = lstatSync15(path);
|
|
13246
|
+
if (current2.dev !== opened.dev || current2.ino !== opened.ino || current2.size !== after.size || current2.mtimeMs !== after.mtimeMs || current2.ctimeMs !== after.ctimeMs || length !== before.size || after.size !== before.size || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs)
|
|
12364
13247
|
throw new Error("Native skill file changed while reading");
|
|
12365
13248
|
return bytes.subarray(0, length);
|
|
12366
13249
|
} finally {
|
|
12367
|
-
|
|
13250
|
+
closeSync8(descriptor);
|
|
12368
13251
|
}
|
|
12369
13252
|
}
|
|
12370
13253
|
function treeHash(root) {
|
|
12371
|
-
const hash2 =
|
|
13254
|
+
const hash2 = createHash12("sha256");
|
|
12372
13255
|
let bytes = 0, files = 0;
|
|
12373
13256
|
function visit2(path) {
|
|
12374
|
-
const stat =
|
|
13257
|
+
const stat = lstatSync15(path);
|
|
12375
13258
|
if (stat.isSymbolicLink())
|
|
12376
13259
|
throw new Error(`Refusing symlink in skill: ${path}`);
|
|
12377
|
-
const rel =
|
|
13260
|
+
const rel = relative5(root, path);
|
|
12378
13261
|
if (stat.isDirectory()) {
|
|
12379
13262
|
hash2.update(`d\x00${rel}\x00`);
|
|
12380
|
-
for (const name of
|
|
12381
|
-
visit2(
|
|
13263
|
+
for (const name of readdirSync8(path).sort())
|
|
13264
|
+
visit2(join17(path, name));
|
|
12382
13265
|
return;
|
|
12383
13266
|
}
|
|
12384
13267
|
if (!stat.isFile())
|
|
@@ -12397,11 +13280,11 @@ function treeHash(root) {
|
|
|
12397
13280
|
function projectAncestorDirectories(projects) {
|
|
12398
13281
|
const directories = new Set;
|
|
12399
13282
|
for (const project of projects) {
|
|
12400
|
-
for (let path =
|
|
13283
|
+
for (let path = canonicalSystemPath(project), depth = 0;; depth++) {
|
|
12401
13284
|
if (depth >= 100)
|
|
12402
13285
|
throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
|
|
12403
13286
|
directories.add(path);
|
|
12404
|
-
const parent =
|
|
13287
|
+
const parent = dirname11(path);
|
|
12405
13288
|
if (parent === path)
|
|
12406
13289
|
break;
|
|
12407
13290
|
path = parent;
|
|
@@ -12410,16 +13293,26 @@ function projectAncestorDirectories(projects) {
|
|
|
12410
13293
|
return [...directories];
|
|
12411
13294
|
}
|
|
12412
13295
|
function inventoryNativeSkills(home = homedir22(), options = {}) {
|
|
13296
|
+
home = canonicalSystemPath(home);
|
|
13297
|
+
if (options.reviewedCacheAlias !== undefined && (!options.includeVendor || !isAbsolute11(options.reviewedCacheAlias) || resolve10(options.reviewedCacheAlias) !== options.reviewedCacheAlias))
|
|
13298
|
+
throw new Error("A reviewed cache alias requires vendor inventory and an exact absolute path");
|
|
13299
|
+
if (options.disabledVendorPaths?.some((path) => !isAbsolute11(path) || resolve10(path) !== path))
|
|
13300
|
+
throw new Error("Disabled vendor paths must be exact absolute paths");
|
|
13301
|
+
const reviewedCacheAlias = options.reviewedCacheAlias === undefined ? undefined : canonicalSystemPath(options.reviewedCacheAlias);
|
|
13302
|
+
const disabledVendorPaths = new Set((options.disabledVendorPaths ?? []).map(canonicalSystemPath));
|
|
12413
13303
|
const aliases = rootAliases(home, options.allowRootAliases);
|
|
12414
13304
|
const selectedAgents = options.agents ? new Set(options.agents) : undefined;
|
|
12415
13305
|
const includesAgent = (agent) => !selectedAgents || selectedAgents.has(agent);
|
|
12416
13306
|
const rootDefinitions = selectedAgents ? ROOTS.filter(([agent, path]) => selectedAgents.has(agent) || selectedAgents.has("hermes") && agent === "codex" && path === ".agents/skills") : ROOTS;
|
|
12417
|
-
const roots = rootDefinitions.map(([agent, path]) => [agent, canonicalAgentPath(
|
|
12418
|
-
const bridgePaths = Object.values(AGENT_ADAPTERS).map((adapter) => canonicalAgentPath(
|
|
13307
|
+
const roots = rootDefinitions.map(([agent, path]) => [agent, canonicalAgentPath(join17(home, path), aliases)]);
|
|
13308
|
+
const bridgePaths = Object.values(AGENT_ADAPTERS).map((adapter) => canonicalAgentPath(join17(home, adapter.root, CLI_BRIDGE_NAME), aliases));
|
|
12419
13309
|
for (const project of projectAncestorDirectories([...options.projectDirs ?? [], ...options.projectDir ? [options.projectDir] : []])) {
|
|
12420
13310
|
for (const [agent, path] of rootDefinitions)
|
|
12421
|
-
roots.push([agent, canonicalAgentPath(
|
|
13311
|
+
roots.push([agent, canonicalAgentPath(join17(project, path), aliases)]);
|
|
12422
13312
|
}
|
|
13313
|
+
const installationInputs = options.codexInstallationInputRoots ?? [];
|
|
13314
|
+
if (installationInputs.some((input) => resolve10(input) !== input || realpathSync6(input) !== input || roots.some(([agent, path]) => agent === "codex" && (path === input || path.startsWith(input + sep5) || input.startsWith(path + sep5)))))
|
|
13315
|
+
throw new Error("Native installation input overlaps an unconditional skill-loading root");
|
|
12423
13316
|
const entries = [], seen = new Set;
|
|
12424
13317
|
const cacheScans = new Map, maxAliasProofEntries = 1e4;
|
|
12425
13318
|
let discoveryEntries = 0, discoveryPathBytes = 0;
|
|
@@ -12430,42 +13323,59 @@ function inventoryNativeSkills(home = homedir22(), options = {}) {
|
|
|
12430
13323
|
if (discoveryPathBytes > 4 * 1024 * 1024)
|
|
12431
13324
|
throw new Error("Native skill discovery metadata limit exceeded");
|
|
12432
13325
|
}
|
|
12433
|
-
|
|
12434
|
-
|
|
12435
|
-
|
|
13326
|
+
let reviewedCacheAliasFound = false;
|
|
13327
|
+
function verifiedSiblingCacheAlias(agent, path, parent) {
|
|
13328
|
+
const link = readlinkSync4(path), lexicalTarget = resolve10(parent, link);
|
|
13329
|
+
if (isAbsolute11(link) ? link !== lexicalTarget : dirname11(link) !== ".")
|
|
12436
13330
|
return false;
|
|
12437
|
-
|
|
13331
|
+
const target = canonicalSystemPath(lexicalTarget);
|
|
13332
|
+
if (dirname11(target) !== parent)
|
|
13333
|
+
return false;
|
|
13334
|
+
if (!lstatSync15(target, { throwIfNoEntry: false })?.isDirectory())
|
|
12438
13335
|
return false;
|
|
12439
13336
|
const scan = cacheScans.get(target);
|
|
12440
|
-
|
|
13337
|
+
if (!scan?.complete || scan.entries > maxAliasProofEntries)
|
|
13338
|
+
return false;
|
|
13339
|
+
if (scan.hasSkills) {
|
|
13340
|
+
const targetSkills = entries.filter((entry) => entry.agent === agent && entry.vendor && (entry.path === target || entry.path.startsWith(target + sep5)));
|
|
13341
|
+
if (path === reviewedCacheAlias && targetSkills.length > 0)
|
|
13342
|
+
reviewedCacheAliasFound = true;
|
|
13343
|
+
if (agent === "codex" && targetSkills.length > 0 && targetSkills.every((entry) => disabledVendorPaths.has(entry.path) || options.disabledVendorSkill?.(entry) === true))
|
|
13344
|
+
return true;
|
|
13345
|
+
if (path !== reviewedCacheAlias)
|
|
13346
|
+
return false;
|
|
13347
|
+
}
|
|
13348
|
+
return true;
|
|
12441
13349
|
}
|
|
12442
|
-
function visit2(agent, path, vendor = false, depth = 0, pluginCache = false, admitted = false) {
|
|
13350
|
+
function visit2(agent, path, vendor = false, depth = 0, pluginCache = false, admitted = false, configuredRoot = false) {
|
|
12443
13351
|
if (!admitted)
|
|
12444
13352
|
admit(path);
|
|
12445
13353
|
const scan = { complete: true, hasSkills: false, entries: 1 };
|
|
12446
13354
|
assertSafePath(path);
|
|
13355
|
+
if (configuredRoot && agent === "codex" && installationInputs.some((root) => path === root || path.startsWith(`${root}${sep5}`)))
|
|
13356
|
+
return scan;
|
|
12447
13357
|
if (!existsSync82(path))
|
|
12448
13358
|
return scan;
|
|
12449
|
-
const stat =
|
|
13359
|
+
const stat = lstatSync15(path);
|
|
12450
13360
|
if (stat.isFile())
|
|
12451
13361
|
return scan;
|
|
12452
13362
|
if (!stat.isDirectory())
|
|
12453
13363
|
throw new Error(`Unsupported native discovery entry: ${path}`);
|
|
12454
|
-
if (existsSync82(
|
|
13364
|
+
if (existsSync82(join17(path, "SKILL.md"))) {
|
|
12455
13365
|
scan.hasSkills = true;
|
|
12456
13366
|
if (seen.has(path))
|
|
12457
13367
|
return scan;
|
|
12458
13368
|
seen.add(path);
|
|
12459
13369
|
let managed = false;
|
|
12460
|
-
const marker =
|
|
13370
|
+
const marker = join17(path, ".hasna-skills.json");
|
|
12461
13371
|
if (existsSync82(marker)) {
|
|
12462
13372
|
try {
|
|
12463
|
-
managed = JSON.parse(
|
|
13373
|
+
managed = JSON.parse(readFileSync12(marker, "utf8")).managedBy === "@hasna/skills";
|
|
12464
13374
|
} catch {}
|
|
12465
13375
|
}
|
|
12466
13376
|
const bridge = !vendor && isOwnedCliBridge(path, bridgePaths);
|
|
12467
|
-
const rootAlias = aliases.find((item) => path === item.target || path.startsWith(item.target +
|
|
12468
|
-
entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...agent === "codex" && path.startsWith(canonicalAgentPath(
|
|
13377
|
+
const rootAlias = aliases.find((item) => path === item.target || path.startsWith(item.target + sep5));
|
|
13378
|
+
entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...agent === "codex" && path.startsWith(canonicalAgentPath(join17(home, ".codex", "skills", ".system"), aliases) + sep5) ? { system: true } : {}, ...bridge ? { bridge: true, bridgeHome: resolve10(home) } : {}, ...rootAlias ? { rootAlias } : {} });
|
|
12469
13379
|
return scan;
|
|
12470
13380
|
}
|
|
12471
13381
|
if (depth > (vendor ? 32 : 3))
|
|
@@ -12474,7 +13384,7 @@ function inventoryNativeSkills(home = homedir22(), options = {}) {
|
|
|
12474
13384
|
try {
|
|
12475
13385
|
let child;
|
|
12476
13386
|
while ((child = directory.readSync()) !== null) {
|
|
12477
|
-
admit(
|
|
13387
|
+
admit(join17(path, child.name));
|
|
12478
13388
|
children.push(child);
|
|
12479
13389
|
}
|
|
12480
13390
|
} finally {
|
|
@@ -12486,20 +13396,20 @@ function inventoryNativeSkills(home = homedir22(), options = {}) {
|
|
|
12486
13396
|
for (const { name } of children) {
|
|
12487
13397
|
if (name === "node_modules")
|
|
12488
13398
|
continue;
|
|
12489
|
-
if (name.startsWith(".") && name !== ".system" && agent !== "hermes" && !(options.guardHermes && `${path}${
|
|
13399
|
+
if (name.startsWith(".") && name !== ".system" && agent !== "hermes" && !(options.guardHermes && `${path}${sep5}`.includes(`${sep5}.agents${sep5}skills${sep5}`)))
|
|
12490
13400
|
continue;
|
|
12491
13401
|
const isVendor = vendor || name === ".system";
|
|
12492
13402
|
if (isVendor && !options.includeVendor)
|
|
12493
13403
|
continue;
|
|
12494
|
-
const child =
|
|
12495
|
-
if (isVendor &&
|
|
12496
|
-
const target =
|
|
12497
|
-
if (target?.isFile() || pluginCache && target?.isDirectory() &&
|
|
13404
|
+
const child = join17(path, name);
|
|
13405
|
+
if (isVendor && lstatSync15(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
13406
|
+
const target = statSync8(child, { throwIfNoEntry: false });
|
|
13407
|
+
if (target?.isFile() || pluginCache && target?.isDirectory() && verifiedSiblingCacheAlias(agent, child, path)) {
|
|
12498
13408
|
scan.entries++;
|
|
12499
13409
|
continue;
|
|
12500
13410
|
}
|
|
12501
13411
|
}
|
|
12502
|
-
const childScan = visit2(agent, child, isVendor, depth + 1, pluginCache, true);
|
|
13412
|
+
const childScan = visit2(agent, child, isVendor, depth + 1, pluginCache, true, configuredRoot);
|
|
12503
13413
|
scan.complete &&= childScan.complete;
|
|
12504
13414
|
scan.hasSkills ||= childScan.hasSkills;
|
|
12505
13415
|
scan.entries += childScan.entries;
|
|
@@ -12508,32 +13418,34 @@ function inventoryNativeSkills(home = homedir22(), options = {}) {
|
|
|
12508
13418
|
cacheScans.set(path, scan);
|
|
12509
13419
|
return scan;
|
|
12510
13420
|
}
|
|
12511
|
-
function scanRoot(agent, path, vendor = false, pluginCache = false) {
|
|
12512
|
-
if (agent === "hermes" || options.guardHermes && `${path}${
|
|
13421
|
+
function scanRoot(agent, path, vendor = false, pluginCache = false, configuredRoot = false) {
|
|
13422
|
+
if (agent === "hermes" || options.guardHermes && `${path}${sep5}`.includes(`${sep5}.agents${sep5}skills${sep5}`))
|
|
12513
13423
|
assertNoHermesLegacyShadow(path);
|
|
12514
|
-
if (!visit2(agent, path, vendor, 0, pluginCache).complete)
|
|
13424
|
+
if (!visit2(agent, path, vendor, 0, pluginCache, false, configuredRoot).complete)
|
|
12515
13425
|
throw new Error(`Native skill discovery limit exceeded; review this root before continuing: ${path}`);
|
|
12516
13426
|
}
|
|
12517
13427
|
for (const [agent, path] of roots)
|
|
12518
13428
|
scanRoot(agent, path);
|
|
12519
13429
|
if (options.includeVendor) {
|
|
12520
13430
|
for (const agent of ["codex", "claude"].filter(includesAgent))
|
|
12521
|
-
scanRoot(agent, canonicalAgentPath(
|
|
13431
|
+
scanRoot(agent, canonicalAgentPath(join17(home, `.${agent}`, "plugins", "cache"), aliases), true, true);
|
|
12522
13432
|
if (includesAgent("claude"))
|
|
12523
|
-
scanRoot("claude", canonicalAgentPath(
|
|
13433
|
+
scanRoot("claude", canonicalAgentPath(join17(home, ".claude", "plugins", "synced"), aliases), true);
|
|
12524
13434
|
if (includesAgent("gemini"))
|
|
12525
|
-
scanRoot("gemini",
|
|
13435
|
+
scanRoot("gemini", join17(home, ".gemini", "extensions"), true);
|
|
12526
13436
|
}
|
|
12527
13437
|
const configured = options.configured ? INTEGRATION_AGENTS.filter(includesAgent).flatMap((agent) => resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, canonical: (path) => canonicalAgentPath(path, aliases) }).roots.map((path) => ({ agent, path }))) : [];
|
|
12528
13438
|
for (const root of [...options.agentRoots ?? [], ...configured])
|
|
12529
13439
|
if (includesAgent(root.agent))
|
|
12530
|
-
scanRoot(root.agent, canonicalAgentPath(root.path, aliases), true);
|
|
13440
|
+
scanRoot(root.agent, canonicalAgentPath(root.path, aliases), true, false, true);
|
|
13441
|
+
if (options.reviewedCacheAlias !== undefined && !reviewedCacheAliasFound)
|
|
13442
|
+
throw new Error("Reviewed cache alias was not an inventoried skill-containing sibling alias");
|
|
12531
13443
|
recheckRootAliases(aliases);
|
|
12532
13444
|
return entries;
|
|
12533
13445
|
}
|
|
12534
13446
|
function readOptional(path) {
|
|
12535
13447
|
assertSafePath(path);
|
|
12536
|
-
return existsSync82(path) ?
|
|
13448
|
+
return existsSync82(path) ? readFileSync12(path, "utf8") : null;
|
|
12537
13449
|
}
|
|
12538
13450
|
function jsonObject(text2, path) {
|
|
12539
13451
|
if (text2 === null)
|
|
@@ -12631,12 +13543,12 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
|
|
|
12631
13543
|
}
|
|
12632
13544
|
}
|
|
12633
13545
|
function claudeSettingsConsumers(home, path, discoveries, aliases) {
|
|
12634
|
-
const
|
|
13546
|
+
const canonical5 = (value) => canonicalAgentPath(value, aliases);
|
|
12635
13547
|
const containsSettings = (root) => {
|
|
12636
|
-
const distance =
|
|
12637
|
-
return distance === "" || distance !== ".." && !distance.startsWith(`..${
|
|
13548
|
+
const distance = relative5(canonical5(root), path);
|
|
13549
|
+
return distance === "" || distance !== ".." && !distance.startsWith(`..${sep5}`) && !isAbsolute11(distance);
|
|
12638
13550
|
};
|
|
12639
|
-
return discoveries.filter((binding) => binding.agent === "claude" || binding.agent === "opencode" ||
|
|
13551
|
+
return discoveries.filter((binding) => binding.agent === "claude" || binding.agent === "opencode" || canonical5(join17(home, AGENT_ADAPTERS[binding.agent].config)) === path || binding.sources.some((source) => canonical5(source.path) === path) || binding.roots.some(containsSettings) || binding.directories?.some((directory) => containsSettings(directory.path))).map((binding) => binding.agent);
|
|
12640
13552
|
}
|
|
12641
13553
|
function verifyCoordinatedDiscovery(binding, home, aliases) {
|
|
12642
13554
|
try {
|
|
@@ -12653,8 +13565,8 @@ function verifyCoordinatedDiscovery(binding, home, aliases) {
|
|
|
12653
13565
|
}
|
|
12654
13566
|
}
|
|
12655
13567
|
function planClaudeHookEventsUpdate(options) {
|
|
12656
|
-
const home =
|
|
12657
|
-
const policyPath =
|
|
13568
|
+
const home = resolve10(options.home ?? homedir22()), dataDir22 = options.dataDir ?? getDataDirReadOnly();
|
|
13569
|
+
const policyPath = join17(dataDir22, "agent-policy.json");
|
|
12658
13570
|
assertSafePath(policyPath);
|
|
12659
13571
|
const snapshot = readManagedSkillPolicySnapshot(dataDir22);
|
|
12660
13572
|
if (!snapshot)
|
|
@@ -12665,10 +13577,10 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
12665
13577
|
}
|
|
12666
13578
|
const aliases = bridge.rootAliases ?? [];
|
|
12667
13579
|
recheckRootAliases(aliases);
|
|
12668
|
-
const path = canonicalAgentPath(
|
|
13580
|
+
const path = canonicalAgentPath(join17(home, ".claude", "settings.json"), aliases);
|
|
12669
13581
|
assertSafePath(path);
|
|
12670
|
-
const before =
|
|
12671
|
-
if ((before === null ? "absent" :
|
|
13582
|
+
const before = lstatSync15(path, { throwIfNoEntry: false }) ? new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readNativeBytes(path, 1024 * 1024)) : null;
|
|
13583
|
+
if ((before === null ? "absent" : sha3(before)) !== options.expectedSettingsSha256)
|
|
12672
13584
|
throw new Error("Claude settings preimage changed");
|
|
12673
13585
|
assertClaudeHookEventsReplacement(before, options.replacement, options.events);
|
|
12674
13586
|
const agents = bridge.agents;
|
|
@@ -12721,7 +13633,7 @@ function planClaudeHookEventsUpdate(options) {
|
|
|
12721
13633
|
}
|
|
12722
13634
|
function atomicWrite2(path, content) {
|
|
12723
13635
|
assertSafePath(path);
|
|
12724
|
-
mkdirSync5(
|
|
13636
|
+
mkdirSync5(dirname11(path), { recursive: true, mode: 448 });
|
|
12725
13637
|
const temporary = `${path}.skills-${randomUUID2()}`;
|
|
12726
13638
|
try {
|
|
12727
13639
|
writeFileSync4(temporary, content, { mode: 384, flag: "wx" });
|
|
@@ -12731,20 +13643,88 @@ function atomicWrite2(path, content) {
|
|
|
12731
13643
|
unlinkSync3(temporary);
|
|
12732
13644
|
}
|
|
12733
13645
|
}
|
|
13646
|
+
function planAgentSettingsWitnessUpgrade(options) {
|
|
13647
|
+
if (options.targetCodexVersion !== undefined && (options.agent !== "codex" || ![2, 3].includes(options.targetCodexVersion)))
|
|
13648
|
+
throw new Error("Invalid Codex witness target version");
|
|
13649
|
+
const home = resolve10(options.home ?? homedir22()), dataDir22 = options.dataDir ?? getDataDirReadOnly();
|
|
13650
|
+
const snapshot = readManagedSkillPolicySnapshot(dataDir22);
|
|
13651
|
+
if (!snapshot || sha3(snapshot.text) !== options.expectedPolicySha256)
|
|
13652
|
+
throw new Error("Managed policy preimage changed");
|
|
13653
|
+
const policy = snapshot.value, aliases = policy.bridge?.rootAliases ?? [];
|
|
13654
|
+
recheckRootAliases(aliases);
|
|
13655
|
+
const binding = policy.bridge?.discovery?.[options.agent];
|
|
13656
|
+
if (!binding || binding.agent !== options.agent || binding.method !== "reviewed")
|
|
13657
|
+
throw new Error("Settings witness upgrade requires an existing reviewed native binding");
|
|
13658
|
+
const configPath = canonicalAgentPath(join17(home, options.agent === "claude" ? ".claude/settings.json" : ".codex/config.toml"), aliases);
|
|
13659
|
+
const settings = readOptional(configPath);
|
|
13660
|
+
if (settings === null || sha3(settings) !== options.expectedSettingsSha256)
|
|
13661
|
+
throw new Error("Native settings preimage changed");
|
|
13662
|
+
const sources = binding.sources.filter((source) => source.path === configPath && source.format === undefined && source.fields === undefined);
|
|
13663
|
+
if (sources.length !== 1)
|
|
13664
|
+
throw new Error("Settings witness upgrade requires one exact settings source");
|
|
13665
|
+
const previous = sources[0];
|
|
13666
|
+
if (typeof previous.sha256 !== "string")
|
|
13667
|
+
throw new Error("Settings witness upgrade requires a preserved settings preimage");
|
|
13668
|
+
const replaced = [previous];
|
|
13669
|
+
if (options.agent === "codex") {
|
|
13670
|
+
if (previous.hashMode !== undefined && previous.hashMode !== "bytes" && previous.hashMode !== "codex-settings-v1" && !(options.targetCodexVersion === 3 && previous.hashMode === "codex-settings-v2"))
|
|
13671
|
+
throw new Error("Codex settings witness upgrade requires an eligible preserved configuration witness");
|
|
13672
|
+
const preimage = readCodexSettingsPreimage(options.reviewedPreimage);
|
|
13673
|
+
for (const source of binding.sources) {
|
|
13674
|
+
if (source === previous || source.path !== configPath)
|
|
13675
|
+
continue;
|
|
13676
|
+
const fields = source.fields;
|
|
13677
|
+
if (source.hashMode !== undefined || source.format !== "toml" || !Array.isArray(fields) || !isDeepStrictEqual4(fields, [...CODEX_DISCOVERY_PROJECTION_FIELDS]) || typeof source.sha256 !== "string")
|
|
13678
|
+
throw new Error("Codex settings witness upgrade found an unrecognized configuration witness");
|
|
13679
|
+
if (projectNativeDiscoveryFields(preimage, "toml", fields, configPath) !== source.sha256)
|
|
13680
|
+
throw new Error("Reviewed preimage does not explain the Codex discovery projection witness");
|
|
13681
|
+
replaced.push(source);
|
|
13682
|
+
}
|
|
13683
|
+
}
|
|
13684
|
+
const next = options.agent === "claude" ? upgradeClaudeSettingsWitness(previous, options.reviewedPreimage) : options.targetCodexVersion === 3 ? upgradeCodexSettingsWitnessV3(previous, options.reviewedPreimage) : upgradeCodexSettingsWitness(previous, options.reviewedPreimage);
|
|
13685
|
+
const rebound = [];
|
|
13686
|
+
for (const source of binding.sources) {
|
|
13687
|
+
if (!replaced.includes(source)) {
|
|
13688
|
+
rebound.push(source);
|
|
13689
|
+
continue;
|
|
13690
|
+
}
|
|
13691
|
+
if (source === replaced[0])
|
|
13692
|
+
rebound.push(next);
|
|
13693
|
+
}
|
|
13694
|
+
const replacement = { ...binding, sources: rebound };
|
|
13695
|
+
verifyAgentDiscovery(replacement);
|
|
13696
|
+
const after = serializeManagedSkillPolicy({ ...policy, bridge: { ...policy.bridge, discovery: { ...policy.bridge.discovery, [options.agent]: replacement } } });
|
|
13697
|
+
return { dataDir: dataDir22, profileId: policy.profileId, changes: [{ path: join17(dataDir22, "agent-policy.json"), before: snapshot.text, after }], nativeSkills: [], observedPolicy: { path: join17(dataDir22, "agent-policy.json"), before: snapshot.text }, observedSettings: { path: configPath, before: settings }, discoveryBefore: [replacement], discoveryAfter: [replacement], rootAliases: aliases, managedAgentChecks: { home, agents: [options.agent] }, settingsWitnessUpgrade: { agent: options.agent, path: configPath, fromHashMode: previous.hashMode ?? "bytes", fromSha256: previous.sha256, toHashMode: next.hashMode, toSha256: next.sha256, reviewedPreimage: options.reviewedPreimage, currentSettingsSha256: options.expectedSettingsSha256, replacedWitnesses: replaced.map((source) => ({ hashMode: source.hashMode ?? "bytes", sha256: source.sha256 })) } };
|
|
13698
|
+
}
|
|
12734
13699
|
function applyAgentIntegration(plan) {
|
|
12735
|
-
const
|
|
13700
|
+
for (const source of plan.observedNativeSources ?? [])
|
|
13701
|
+
if (sha3(readNativeBytes(source.path, 1024 * 1024)) !== source.sha256)
|
|
13702
|
+
throw new Error("Native identity source changed after planning");
|
|
13703
|
+
if (plan.observedSettings && readOptional(plan.observedSettings.path) !== plan.observedSettings.before)
|
|
13704
|
+
throw new Error("Native settings changed after witness planning");
|
|
13705
|
+
const policyPath = join17(plan.dataDir, "agent-policy.json");
|
|
12736
13706
|
assertSafePath(policyPath);
|
|
12737
|
-
const currentText = (path) =>
|
|
13707
|
+
const currentText = (path) => resolve10(path) === resolve10(policyPath) ? readManagedSkillPolicySnapshot(plan.dataDir)?.text ?? null : readOptional(path);
|
|
12738
13708
|
readManagedSkillPolicySnapshot(plan.dataDir);
|
|
12739
13709
|
for (const change of plan.changes)
|
|
12740
|
-
if (
|
|
13710
|
+
if (resolve10(change.path) === resolve10(policyPath))
|
|
12741
13711
|
parseManagedSkillPolicy(change.after);
|
|
12742
13712
|
const aliases = plan.rootAliases ?? [];
|
|
12743
13713
|
recheckRootAliases(aliases);
|
|
12744
13714
|
for (const agent of plan.retainedReviewChecks?.agents ?? [])
|
|
12745
13715
|
assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
|
|
13716
|
+
let provenDiscovery;
|
|
13717
|
+
if (plan.settingsWitnessUpgrade) {
|
|
13718
|
+
const upgrade = plan.settingsWitnessUpgrade;
|
|
13719
|
+
if (!plan.managedAgentChecks || !plan.observedPolicy?.before)
|
|
13720
|
+
throw new Error("Invalid settings witness upgrade plan");
|
|
13721
|
+
const verified = planAgentSettingsWitnessUpgrade({ agent: upgrade.agent, ...upgrade.agent === "codex" ? { targetCodexVersion: upgrade.toHashMode === "codex-settings-v3" ? 3 : 2 } : {}, reviewedPreimage: upgrade.reviewedPreimage, home: plan.managedAgentChecks.home, dataDir: plan.dataDir, expectedPolicySha256: sha3(plan.observedPolicy.before), expectedSettingsSha256: upgrade.currentSettingsSha256 });
|
|
13722
|
+
if (JSON.stringify(verified.changes) !== JSON.stringify(plan.changes) || JSON.stringify(verified.settingsWitnessUpgrade) !== JSON.stringify(upgrade))
|
|
13723
|
+
throw new Error("Settings witness upgrade plan changed");
|
|
13724
|
+
provenDiscovery = verified.discoveryBefore[0];
|
|
13725
|
+
}
|
|
12746
13726
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
12747
|
-
|
|
13727
|
+
assertManagedAgentBridgeWithDiscovery(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home }, provenDiscovery?.agent === agent ? provenDiscovery : undefined);
|
|
12748
13728
|
for (const binding of plan.discoveryBefore ?? []) {
|
|
12749
13729
|
if (plan.managedAgentChecks)
|
|
12750
13730
|
verifyCoordinatedDiscovery(binding, plan.managedAgentChecks.home, aliases);
|
|
@@ -12756,7 +13736,7 @@ function applyAgentIntegration(plan) {
|
|
|
12756
13736
|
for (const change of plan.changes)
|
|
12757
13737
|
if (currentText(change.path) !== change.before)
|
|
12758
13738
|
throw new Error(`Configuration changed after planning: ${change.path}`);
|
|
12759
|
-
const backupRoot =
|
|
13739
|
+
const backupRoot = join17(plan.dataDir, "migration", randomUUID2()), backups = [], written = [], createdDirectories = new Set;
|
|
12760
13740
|
if (!plan.changes.length) {
|
|
12761
13741
|
recheckRootAliases(aliases);
|
|
12762
13742
|
return { changed: [], backups, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
@@ -12766,8 +13746,10 @@ function applyAgentIntegration(plan) {
|
|
|
12766
13746
|
chmodSync(backupRoot, 448);
|
|
12767
13747
|
for (const [index, change] of plan.changes.entries())
|
|
12768
13748
|
if (change.before !== null) {
|
|
12769
|
-
const backup =
|
|
13749
|
+
const backup = join17(backupRoot, `${index}-${sha3(change.path).slice(0, 12)}.backup`);
|
|
12770
13750
|
writeFileSync4(backup, change.before, { mode: 384, flag: "wx" });
|
|
13751
|
+
if (readFileSync12(backup, "utf8") !== change.before)
|
|
13752
|
+
throw new Error("Native configuration preservation readback failed");
|
|
12771
13753
|
backups.push(backup);
|
|
12772
13754
|
}
|
|
12773
13755
|
try {
|
|
@@ -12775,13 +13757,18 @@ function applyAgentIntegration(plan) {
|
|
|
12775
13757
|
recheckRootAliases(aliases);
|
|
12776
13758
|
if (currentText(change.path) !== change.before)
|
|
12777
13759
|
throw new Error(`Configuration changed after planning: ${change.path}`);
|
|
12778
|
-
for (let directory =
|
|
13760
|
+
for (let directory = dirname11(change.path);!existsSync82(directory); directory = dirname11(directory))
|
|
12779
13761
|
createdDirectories.add(directory);
|
|
12780
13762
|
const after = change.after;
|
|
12781
13763
|
atomicWrite2(change.path, after);
|
|
12782
13764
|
written.push({ path: change.path, before: change.before, after });
|
|
12783
13765
|
}
|
|
12784
13766
|
recheckRootAliases(aliases);
|
|
13767
|
+
for (const source of plan.observedNativeSources ?? [])
|
|
13768
|
+
if (sha3(readNativeBytes(source.path, 1024 * 1024)) !== source.sha256)
|
|
13769
|
+
throw new Error("Native identity source changed during application");
|
|
13770
|
+
if (plan.observedSettings && readOptional(plan.observedSettings.path) !== plan.observedSettings.before)
|
|
13771
|
+
throw new Error("Native settings changed during witness application");
|
|
12785
13772
|
for (const binding of plan.discoveryAfter ?? []) {
|
|
12786
13773
|
if (plan.managedAgentChecks)
|
|
12787
13774
|
verifyCoordinatedDiscovery(binding, plan.managedAgentChecks.home, aliases);
|
|
@@ -12790,7 +13777,7 @@ function applyAgentIntegration(plan) {
|
|
|
12790
13777
|
}
|
|
12791
13778
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
12792
13779
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
|
|
12793
|
-
atomicWrite2(
|
|
13780
|
+
atomicWrite2(join17(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha3(change.before), afterHash: sha3(change.after) })), backups, ...aliases.length ? { rootAliases: aliases } : {} }) + `
|
|
12794
13781
|
`);
|
|
12795
13782
|
} catch (error) {
|
|
12796
13783
|
for (const change of written.reverse()) {
|
|
@@ -12820,8 +13807,11 @@ function applyAgentIntegration(plan) {
|
|
|
12820
13807
|
return { changed: plan.changes.map((change) => change.path), backups, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
12821
13808
|
}
|
|
12822
13809
|
function assertManagedAgentBridge(agent, options = {}) {
|
|
12823
|
-
|
|
12824
|
-
|
|
13810
|
+
assertManagedAgentBridgeWithDiscovery(agent, options);
|
|
13811
|
+
}
|
|
13812
|
+
function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscovery) {
|
|
13813
|
+
const home = resolve10(options.home ?? homedir22()), dataDir22 = options.dataDir ?? getDataDirReadOnly();
|
|
13814
|
+
assertSafePath(join17(dataDir22, "agent-policy.json"));
|
|
12825
13815
|
const snapshot = readManagedSkillPolicySnapshot(dataDir22);
|
|
12826
13816
|
if (!snapshot)
|
|
12827
13817
|
throw new Error("NATIVE_SKILL_DRIFT: install the Skills bridge with skills hook install");
|
|
@@ -12832,14 +13822,14 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
12832
13822
|
if (!Array.isArray(aliases))
|
|
12833
13823
|
throw new Error("NATIVE_SKILL_DRIFT: invalid root alias binding");
|
|
12834
13824
|
recheckRootAliases(aliases);
|
|
12835
|
-
const expected = canonicalAgentPath(
|
|
13825
|
+
const expected = canonicalAgentPath(join17(home, AGENT_ADAPTERS[agent].root, CLI_BRIDGE_NAME), aliases);
|
|
12836
13826
|
if (!isOwnedCliBridge(expected, [expected]))
|
|
12837
13827
|
throw new Error("NATIVE_SKILL_DRIFT: the native Skills bridge is missing or modified; repair it before continuing");
|
|
12838
13828
|
const roots = projectAncestorDirectories([options.projectDir ?? process.cwd(), ...options.projectDirs ?? [], ...agent === "hermes" && process.env.TERMINAL_CWD ? [process.cwd()] : []]);
|
|
12839
|
-
const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes"].includes(agent) && entry.path.includes(`${
|
|
12840
|
-
const discovery = binding.discovery?.[agent];
|
|
13829
|
+
const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes"].includes(agent) && entry.path.includes(`${sep5}.agents${sep5}skills${sep5}`) || agent === "opencode" && entry.agent === "claude";
|
|
13830
|
+
const discovery = provenDiscovery ?? binding.discovery?.[agent];
|
|
12841
13831
|
assertProjectDiscovery(agent, [...roots], home, (path) => canonicalAgentPath(path, aliases), discovery);
|
|
12842
|
-
const configPath = canonicalAgentPath(
|
|
13832
|
+
const configPath = canonicalAgentPath(join17(home, AGENT_ADAPTERS[agent].config), aliases), config = agent === "hermes" ? parseHermesConfig(readOptional(configPath)) : jsonObject(readOptional(configPath), configPath);
|
|
12843
13833
|
const command = binding.commands?.[agent], profile = binding.profiles?.[agent];
|
|
12844
13834
|
if (typeof command !== "string" || typeof profile !== "string")
|
|
12845
13835
|
throw new Error("NATIVE_SKILL_DRIFT: the native hook command/profile binding is missing");
|
|
@@ -12847,11 +13837,11 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
12847
13837
|
throw new Error("NATIVE_SKILL_DRIFT: the hook selection profile differs from its managed binding; restart the native client after reviewing skills hook install");
|
|
12848
13838
|
if (agent === "hermes") {
|
|
12849
13839
|
const supervisor2 = binding.supervisors?.hermes;
|
|
12850
|
-
if (supervisor2?.path !==
|
|
13840
|
+
if (supervisor2?.path !== join17(dataDir22, "agent-hooks", "hermes.js") || supervisor2?.sha256 !== sha3(renderHermesSupervisor(command, profile)))
|
|
12851
13841
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
|
|
12852
13842
|
assertHermesProtection(home, config, command, profile, supervisor2);
|
|
12853
13843
|
} else if (agent === "opencode") {
|
|
12854
|
-
if (JSON.stringify(config.permission?.skill) !== JSON.stringify({ "*": "deny", [CLI_BRIDGE_NAME]: "allow" }) || readOptional(
|
|
13844
|
+
if (JSON.stringify(config.permission?.skill) !== JSON.stringify({ "*": "deny", [CLI_BRIDGE_NAME]: "allow" }) || readOptional(join17(home, ".config", "opencode", "plugins", "skills-cli.js")) !== renderOpenCodePlugin(command, profile))
|
|
12855
13845
|
throw new Error("NATIVE_SKILL_DRIFT: OpenCode bridge protection changed; run skills hook install");
|
|
12856
13846
|
} else {
|
|
12857
13847
|
const required = {};
|
|
@@ -12868,21 +13858,44 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
12868
13858
|
if (agent === "gemini" && (config.hooksConfig?.enabled === false || config.skills?.enabled !== true || !["antigravity-support", "skill-creator"].every((name) => config.skills?.disabled?.includes(name)) || config.skills?.disabled?.includes(CLI_BRIDGE_NAME)))
|
|
12869
13859
|
throw new Error("NATIVE_SKILL_DRIFT: Gemini bridge or bundled-skill protection changed; run skills hook install");
|
|
12870
13860
|
}
|
|
12871
|
-
const codexPath = canonicalAgentPath(
|
|
13861
|
+
const codexPath = canonicalAgentPath(join17(home, ".codex", "config.toml"), aliases);
|
|
12872
13862
|
if (options.codexDiscoveryRecovery && (agent !== "codex" || options.codexDiscoveryRecovery.configPath !== codexPath))
|
|
12873
13863
|
throw new Error("NATIVE_SKILL_DRIFT: Codex trust recovery names a different configuration root");
|
|
12874
13864
|
const codexConfig = agent === "codex" ? Bun.TOML.parse(readOptional(codexPath) ?? "") : {};
|
|
12875
13865
|
if (agent === "codex" && codexConfig.skills?.bundled?.enabled !== false)
|
|
12876
13866
|
throw new Error("NATIVE_SKILL_DRIFT: Codex bundled skill reseeding is not disabled (skills.bundled.enabled); run skills hook install");
|
|
12877
|
-
const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path,
|
|
13867
|
+
const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join17(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
|
|
12878
13868
|
if (agent === "codex" && [...setting(expected), ...(codexConfig.skills?.config ?? []).filter((entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME)].some((entry) => entry.enabled === false))
|
|
12879
13869
|
throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
|
|
12880
|
-
|
|
12881
|
-
|
|
13870
|
+
if (agent === "codex" && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join17(home, ".codex/plugins/cache"), aliases), binding.codexPluginSkills ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024))))
|
|
13871
|
+
throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; run skills hook install with a fresh discovery review");
|
|
13872
|
+
const disabledPlugin = (entry) => {
|
|
13873
|
+
if (agent !== "codex" || !entry.vendor)
|
|
13874
|
+
return false;
|
|
13875
|
+
const document2 = join17(entry.path, "SKILL.md"), cache = canonicalAgentPath(join17(home, ".codex/plugins/cache"), aliases), read2 = (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024));
|
|
13876
|
+
return isReviewedCodexPluginInactive(document2, cache, binding.codexInactivePlugins ?? [], codexConfig.plugins, codexConfig.skills?.config ?? [], read2) || isReviewedCodexPluginSkillDisabled(document2, cache, binding.codexPluginSkills ?? [], codexConfig.skills?.config, read2);
|
|
13877
|
+
};
|
|
13878
|
+
const disabledPaths = new Set(disabledCodexSkillPaths(codexConfig, aliases));
|
|
13879
|
+
const disabledVendorSkill = (entry) => {
|
|
13880
|
+
if (agent !== "codex" || !entry.vendor)
|
|
12882
13881
|
return false;
|
|
12883
|
-
|
|
12884
|
-
|
|
12885
|
-
|
|
13882
|
+
if ((binding.codexPluginSkills ?? []).some((control) => entry.path.startsWith(control.pluginParent + sep5)))
|
|
13883
|
+
return disabledPlugin(entry);
|
|
13884
|
+
if (disabledPlugin(entry))
|
|
13885
|
+
return true;
|
|
13886
|
+
if (!disabledPaths.has(entry.path))
|
|
13887
|
+
return false;
|
|
13888
|
+
if (entry.path.startsWith(canonicalAgentPath(join17(home, ".codex", "plugins", "cache"), aliases) + sep5))
|
|
13889
|
+
return true;
|
|
13890
|
+
const runtimeCache = canonicalAgentPath(join17(home, ".cache", "codex-runtimes"), aliases);
|
|
13891
|
+
if (entry.path.startsWith(runtimeCache + sep5) && discovery?.method === "automatic" && discovery.roots.some((root) => entry.path.startsWith(root + sep5)))
|
|
13892
|
+
return true;
|
|
13893
|
+
const bundledMarketplaces = canonicalAgentPath(join17(home, ".codex", ".tmp", "bundled-marketplaces"), aliases);
|
|
13894
|
+
if (entry.path.startsWith(bundledMarketplaces + sep5) && discovery?.method === "automatic" && discovery.roots.some((root) => entry.path.startsWith(root + sep5)))
|
|
13895
|
+
return true;
|
|
13896
|
+
if (!entry.path.startsWith(canonicalAgentPath(join17(home, ".codex", "skills", ".system"), aliases) + sep5))
|
|
13897
|
+
return false;
|
|
13898
|
+
return Array.isArray(binding.disabledBuiltins) && binding.disabledBuiltins.some((item) => item.path === entry.path && item.hash === entry.hash);
|
|
12886
13899
|
};
|
|
12887
13900
|
if (!discovery || discovery.agent !== agent)
|
|
12888
13901
|
throw new Error("NATIVE_SKILL_DRIFT: native discovery coverage is missing; run skills hook install");
|
|
@@ -12898,8 +13911,16 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
12898
13911
|
} catch (error) {
|
|
12899
13912
|
throw new Error(`NATIVE_SKILL_DRIFT: ${error.message}`);
|
|
12900
13913
|
}
|
|
12901
|
-
|
|
12902
|
-
|
|
13914
|
+
let installationInputRoots = [];
|
|
13915
|
+
if (agent === "codex") {
|
|
13916
|
+
try {
|
|
13917
|
+
installationInputRoots = reviewedCodexPluginSourceRoots(canonicalAgentPath(join17(home, ".codex/plugins/cache"), aliases), discovery.codexInstallationInputs?.plugins ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)));
|
|
13918
|
+
} catch {
|
|
13919
|
+
throw new Error("NATIVE_SKILL_DRIFT: native installation input identity changed; review discovery and catalog");
|
|
13920
|
+
}
|
|
13921
|
+
}
|
|
13922
|
+
const inventory = inventoryNativeSkills(home, { includeVendor: true, guardHermes: agent === "hermes", agents: [agent], projectDirs: [...roots], agentRoots: discovery.roots.map((path) => ({ agent, path })), allowRootAliases: aliases.length > 0, disabledVendorPaths: agent === "codex" ? disabledCodexSkillPaths(codexConfig, aliases) : [], disabledVendorSkill: disabledPlugin, codexInstallationInputRoots: installationInputRoots });
|
|
13923
|
+
const unexpected = inventory.filter((entry) => visible(entry) && !entry.bridge && !disabledVendorSkill(entry));
|
|
12903
13924
|
if (unexpected.length) {
|
|
12904
13925
|
const paths = unexpected.slice(0, 3).map((entry) => {
|
|
12905
13926
|
const escaped = JSON.stringify(entry.path).replace(/[\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
@@ -12918,7 +13939,7 @@ function pluginNeed(value, message) {
|
|
|
12918
13939
|
if (!value)
|
|
12919
13940
|
pluginRefusal(message);
|
|
12920
13941
|
}
|
|
12921
|
-
var pluginHash = (bytes) =>
|
|
13942
|
+
var pluginHash = (bytes) => createHash13("sha256").update(bytes).digest("hex");
|
|
12922
13943
|
function pluginObject(value) {
|
|
12923
13944
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
12924
13945
|
}
|
|
@@ -12974,7 +13995,7 @@ function validatePluginManifest(value) {
|
|
|
12974
13995
|
pluginNeed(item.kind === "skill" || item.kind === "command", "Unknown plugin payload kind");
|
|
12975
13996
|
pluginDigest(item.sourceDigest);
|
|
12976
13997
|
pluginKeys(item.target, ["slug", "version", "bundleDigest"]);
|
|
12977
|
-
|
|
13998
|
+
validateSelection({ ...item.target, authority: "https://example.com/skills/v1", workspaceId: "validation", profileRevision: "validation" });
|
|
12978
13999
|
}
|
|
12979
14000
|
}
|
|
12980
14001
|
function pluginFileWitnesses(entries) {
|
|
@@ -12995,15 +14016,15 @@ var uid = () => typeof process.getuid === "function" ? BigInt(process.getuid())
|
|
|
12995
14016
|
var missing = (error) => Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
12996
14017
|
function stat(path) {
|
|
12997
14018
|
try {
|
|
12998
|
-
return
|
|
14019
|
+
return lstatSync17(path, { bigint: true });
|
|
12999
14020
|
} catch (error) {
|
|
13000
14021
|
if (missing(error))
|
|
13001
14022
|
return null;
|
|
13002
14023
|
return pluginRefusal("Cannot inspect plugin filesystem entry");
|
|
13003
14024
|
}
|
|
13004
14025
|
}
|
|
13005
|
-
function
|
|
13006
|
-
pluginNeed(typeof path === "string" && path.length <= 8192 &&
|
|
14026
|
+
function canonical5(path) {
|
|
14027
|
+
pluginNeed(typeof path === "string" && path.length <= 8192 && isAbsolute12(path) && resolve11(path) === path && !/[\x00-\x1f\x7f]/.test(path), "Plugin storage paths must be canonical absolute paths");
|
|
13007
14028
|
}
|
|
13008
14029
|
function owner(stats, system = false) {
|
|
13009
14030
|
const current2 = uid();
|
|
@@ -13013,12 +14034,12 @@ function same3(left, right, identityOnly = false) {
|
|
|
13013
14034
|
pluginNeed(right && left.dev === right.dev && left.ino === right.ino && left.mode === right.mode && left.uid === right.uid && left.gid === right.gid && (identityOnly || left.size === right.size && left.nlink === right.nlink && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs), "Plugin filesystem entry changed during access");
|
|
13014
14035
|
}
|
|
13015
14036
|
function ancestors(path, create = false) {
|
|
13016
|
-
|
|
13017
|
-
const result = [], parent =
|
|
14037
|
+
canonical5(path);
|
|
14038
|
+
const result = [], parent = dirname13(path), filesystemRoot = parse5(parent).root;
|
|
13018
14039
|
let current2 = filesystemRoot;
|
|
13019
14040
|
for (const segment of ["", ...parent.slice(filesystemRoot.length).split("/").filter(Boolean)]) {
|
|
13020
14041
|
if (segment)
|
|
13021
|
-
current2 =
|
|
14042
|
+
current2 = join23(current2, segment);
|
|
13022
14043
|
let currentStat = stat(current2);
|
|
13023
14044
|
if (!currentStat && create) {
|
|
13024
14045
|
try {
|
|
@@ -13064,8 +14085,8 @@ function readBytes(path, maximum, kind) {
|
|
|
13064
14085
|
fileShape(before, maximum, kind);
|
|
13065
14086
|
let fd;
|
|
13066
14087
|
try {
|
|
13067
|
-
fd =
|
|
13068
|
-
const opened =
|
|
14088
|
+
fd = openSync9(path, READ_FLAGS);
|
|
14089
|
+
const opened = fstatSync9(fd, { bigint: true });
|
|
13069
14090
|
same3(before, opened);
|
|
13070
14091
|
fileShape(opened, maximum, kind);
|
|
13071
14092
|
const bytes = new Uint8Array(Number(opened.size));
|
|
@@ -13076,7 +14097,7 @@ function readBytes(path, maximum, kind) {
|
|
|
13076
14097
|
offset += count;
|
|
13077
14098
|
}
|
|
13078
14099
|
pluginNeed(readSync7(fd, new Uint8Array(1), 0, 1, null) === 0, "Plugin file grew while being read");
|
|
13079
|
-
same3(opened,
|
|
14100
|
+
same3(opened, fstatSync9(fd, { bigint: true }));
|
|
13080
14101
|
same3(opened, stat(path));
|
|
13081
14102
|
verifyAncestors(chain);
|
|
13082
14103
|
return bytes;
|
|
@@ -13086,7 +14107,7 @@ function readBytes(path, maximum, kind) {
|
|
|
13086
14107
|
return pluginRefusal("Cannot safely read plugin file");
|
|
13087
14108
|
} finally {
|
|
13088
14109
|
if (fd !== undefined)
|
|
13089
|
-
|
|
14110
|
+
closeSync9(fd);
|
|
13090
14111
|
}
|
|
13091
14112
|
}
|
|
13092
14113
|
function exactPaths(entries) {
|
|
@@ -13116,7 +14137,7 @@ function directoryNames(path) {
|
|
|
13116
14137
|
function snapshotPluginTree(root, options = {}) {
|
|
13117
14138
|
const chain = ancestors(root), entries = [], files = [], directories = [];
|
|
13118
14139
|
let bytes = 0, visited = 0;
|
|
13119
|
-
const visit2 = (directory,
|
|
14140
|
+
const visit2 = (directory, relative6) => {
|
|
13120
14141
|
const before = stat(directory);
|
|
13121
14142
|
pluginNeed(before && before.isDirectory() && !before.isSymbolicLink(), "Plugin tree must contain real directories");
|
|
13122
14143
|
const names = directoryNames(directory);
|
|
@@ -13124,18 +14145,18 @@ function snapshotPluginTree(root, options = {}) {
|
|
|
13124
14145
|
directories.push({ path: directory, stat: before, names });
|
|
13125
14146
|
for (const name of names) {
|
|
13126
14147
|
pluginNeed(++visited <= PLUGIN_PROJECTION_LIMITS.files * 50, "Plugin tree membership exceeds its limit");
|
|
13127
|
-
const path =
|
|
14148
|
+
const path = relative6 ? `${relative6}/${name}` : name;
|
|
13128
14149
|
pluginNeed(new TextEncoder().encode(path).byteLength <= 100 && !/[\\:\x00-\x1f\x7f]/u.test(path), "Plugin entry path exceeds its limit or is unsafe");
|
|
13129
|
-
const absolute =
|
|
14150
|
+
const absolute = join23(directory, name), entryStat = stat(absolute);
|
|
13130
14151
|
pluginNeed(entryStat, "Plugin tree member disappeared");
|
|
13131
|
-
if (
|
|
14152
|
+
if (relative6 === "" && name === ".orphaned_at" && (options.nativeRuntime === "claude-2.1.274" || options.nativeRuntime === "claude-2.1.276")) {
|
|
13132
14153
|
const bytes2 = readBytes(absolute, 32, "native-pin");
|
|
13133
14154
|
pluginNeed(/^[1-9][0-9]{12}$/.test(new TextDecoder().decode(bytes2)), "Malformed native orphan timestamp");
|
|
13134
14155
|
same3(entryStat, stat(absolute));
|
|
13135
14156
|
files.push({ path: absolute, stat: entryStat });
|
|
13136
14157
|
continue;
|
|
13137
14158
|
}
|
|
13138
|
-
if (
|
|
14159
|
+
if (relative6 === "" && name === ".in_use" && (options.nativeRuntime === "claude-2.1.274" || options.nativeRuntime === "claude-2.1.276")) {
|
|
13139
14160
|
pluginNeed(entryStat.isDirectory() && !entryStat.isSymbolicLink(), "Native plugin pins require a real directory");
|
|
13140
14161
|
owner(entryStat);
|
|
13141
14162
|
const pins = directoryNames(absolute);
|
|
@@ -13143,7 +14164,7 @@ function snapshotPluginTree(root, options = {}) {
|
|
|
13143
14164
|
directories.push({ path: absolute, stat: entryStat, names: pins });
|
|
13144
14165
|
for (const pin2 of pins) {
|
|
13145
14166
|
pluginNeed(/^[1-9][0-9]{0,9}$/.test(pin2) && Number(pin2) <= 2147483647, "Unrecognized native plugin pin name");
|
|
13146
|
-
const pinPath =
|
|
14167
|
+
const pinPath = join23(absolute, pin2), pinStat = stat(pinPath);
|
|
13147
14168
|
pluginNeed(pinStat, "Native plugin pin disappeared");
|
|
13148
14169
|
const bytes2 = readBytes(pinPath, 4096, "native-pin");
|
|
13149
14170
|
let value;
|
|
@@ -13215,7 +14236,7 @@ function validatePluginTarget(value) {
|
|
|
13215
14236
|
for (const registration of value.registrations) {
|
|
13216
14237
|
pluginKeys(registration, ["scope", "projectPath"]);
|
|
13217
14238
|
pluginNeed(registration.scope === "user" || registration.scope === "project", "Unsupported plugin registration scope");
|
|
13218
|
-
pluginNeed(registration.scope === "user" ? registration.projectPath === null : typeof registration.projectPath === "string" &&
|
|
14239
|
+
pluginNeed(registration.scope === "user" ? registration.projectPath === null : typeof registration.projectPath === "string" && isAbsolute13(registration.projectPath) && resolve12(registration.projectPath) === registration.projectPath, "Plugin scope requires its exact project path");
|
|
13219
14240
|
const key = JSON.stringify([registration.scope, registration.projectPath]);
|
|
13220
14241
|
pluginNeed(!scopes.has(key), "Duplicate plugin registration scope");
|
|
13221
14242
|
scopes.add(key);
|
|
@@ -13223,7 +14244,7 @@ function validatePluginTarget(value) {
|
|
|
13223
14244
|
for (const key of ["native", "resolver"]) {
|
|
13224
14245
|
pluginKeys(value[key], key === "native" ? ["version", "executable", "digest"] : ["executable", "digest"]);
|
|
13225
14246
|
pluginText(value[key].executable);
|
|
13226
|
-
pluginNeed(
|
|
14247
|
+
pluginNeed(isAbsolute13(value[key].executable) && resolve12(value[key].executable) === value[key].executable, "Plugin executable must have a canonical absolute path");
|
|
13227
14248
|
pluginDigest(value[key].digest);
|
|
13228
14249
|
}
|
|
13229
14250
|
pluginNeed(["2.1.274", "2.1.276"].includes(value.native.version), "Plugin admission requires a certified Claude command-source runtime (2.1.274 or 2.1.276)");
|
|
@@ -13269,11 +14290,11 @@ function validateBinding2(value) {
|
|
|
13269
14290
|
for (const key of ["authority", "workspaceId", "profileId", "bundleSlug"])
|
|
13270
14291
|
pluginText(value[key]);
|
|
13271
14292
|
pluginNeed(value.principal.accountId === value.workspaceId, "Plugin principal account differs from its workspace");
|
|
13272
|
-
|
|
14293
|
+
validateSelection({ slug: value.bundleSlug, version: "1.0.0", bundleDigest: `sha256:${"0".repeat(64)}`, authority: value.authority, workspaceId: value.workspaceId, profileRevision: "validation" });
|
|
13273
14294
|
}
|
|
13274
14295
|
function validateObservedSelection(value) {
|
|
13275
14296
|
pluginKeys(value, ["slug", "version", "bundleDigest", "aliases", "triggers", "authority", "workspaceId", "profileRevision"]);
|
|
13276
|
-
|
|
14297
|
+
validateSelection(value);
|
|
13277
14298
|
if (value.triggers !== undefined) {
|
|
13278
14299
|
pluginKeys(value.triggers, ["keywords", "paths", "always"]);
|
|
13279
14300
|
pluginNeed(value.triggers.always === undefined || typeof value.triggers.always === "boolean", "Invalid plugin selection trigger");
|
|
@@ -13368,12 +14389,12 @@ function pluginResolverCommand(binding) {
|
|
|
13368
14389
|
}
|
|
13369
14390
|
function pluginBindingPath(storeRoot, id) {
|
|
13370
14391
|
pluginNeed(/^[a-f0-9]{64}$/.test(id), "Invalid plugin binding identifier");
|
|
13371
|
-
return
|
|
14392
|
+
return join25(storeRoot, "bindings", `${id}.json`);
|
|
13372
14393
|
}
|
|
13373
14394
|
function pluginReceiptPath(storeRoot, id, digest) {
|
|
13374
14395
|
pluginNeed(/^[a-f0-9]{64}$/.test(id), "Invalid plugin binding identifier");
|
|
13375
14396
|
pluginDigest(digest);
|
|
13376
|
-
return
|
|
14397
|
+
return join25(storeRoot, "receipts", id, `${digest.slice(7)}.json`);
|
|
13377
14398
|
}
|
|
13378
14399
|
function readPluginBinding(storeRoot, id) {
|
|
13379
14400
|
const value = readPluginJson(pluginBindingPath(storeRoot, id));
|
|
@@ -13393,7 +14414,7 @@ function readPluginAdmissionReceipt(storeRoot, id, digest) {
|
|
|
13393
14414
|
pluginNeed(receipt.plan.bindingId === id && receipt.plan.planDigest === digest, "Plugin receipt identity differs from its filename");
|
|
13394
14415
|
const binding = readPluginBinding(storeRoot, id);
|
|
13395
14416
|
pluginNeed(canonicalJson(binding) === canonicalJson(receipt.plan.binding) && receipt.plan.sourceCommand === pluginResolverCommand(binding), "Plugin receipt binding changed");
|
|
13396
|
-
pluginNeed(receipt.materializedPath ===
|
|
14417
|
+
pluginNeed(receipt.materializedPath === join25(storeRoot, "objects", id, digest.slice(7)), "Plugin receipt points outside its immutable materialization");
|
|
13397
14418
|
pluginText(receipt.admittedAt, 64);
|
|
13398
14419
|
pluginNeed(Number.isFinite(Date.parse(receipt.admittedAt)), "Invalid plugin admission time");
|
|
13399
14420
|
return receipt;
|
|
@@ -13406,25 +14427,25 @@ function validateManagedPluginWitnesses(value) {
|
|
|
13406
14427
|
pluginNeed(typeof item.bindingId === "string" && /^[a-f0-9]{64}$/.test(item.bindingId) && !seen.has(item.bindingId), "Invalid or duplicate managed plugin witness");
|
|
13407
14428
|
seen.add(item.bindingId);
|
|
13408
14429
|
pluginText(item.storeRoot);
|
|
13409
|
-
pluginNeed(
|
|
14430
|
+
pluginNeed(isAbsolute14(item.storeRoot) && resolve13(item.storeRoot) === item.storeRoot, "Invalid plugin receipt store path");
|
|
13410
14431
|
}
|
|
13411
14432
|
}
|
|
13412
14433
|
function safe3(path) {
|
|
13413
14434
|
pluginText(path);
|
|
13414
|
-
pluginNeed(
|
|
13415
|
-
for (let at = path;; at =
|
|
13416
|
-
pluginNeed(!
|
|
13417
|
-
if (at ===
|
|
14435
|
+
pluginNeed(isAbsolute14(path) && resolve13(path) === path, "Invalid plugin registry path");
|
|
14436
|
+
for (let at = path;; at = dirname14(at)) {
|
|
14437
|
+
pluginNeed(!lstatSync18(at, { throwIfNoEntry: false })?.isSymbolicLink(), "Plugin registry path traverses a symlink");
|
|
14438
|
+
if (at === dirname14(at))
|
|
13418
14439
|
break;
|
|
13419
14440
|
}
|
|
13420
14441
|
}
|
|
13421
14442
|
function readRegistry(path) {
|
|
13422
14443
|
safe3(path);
|
|
13423
|
-
const initial =
|
|
14444
|
+
const initial = lstatSync18(path);
|
|
13424
14445
|
pluginNeed(initial.isFile() && initial.size <= 1024 * 1024, "Plugin registry is not a bounded regular file");
|
|
13425
|
-
const fd =
|
|
14446
|
+
const fd = openSync10(path, constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
|
|
13426
14447
|
try {
|
|
13427
|
-
const opened =
|
|
14448
|
+
const opened = fstatSync10(fd);
|
|
13428
14449
|
pluginNeed(opened.isFile() && opened.ino === initial.ino && opened.dev === initial.dev, "Plugin registry changed during open");
|
|
13429
14450
|
const bytes = Buffer.alloc(1024 * 1024 + 1);
|
|
13430
14451
|
let length = 0;
|
|
@@ -13434,7 +14455,7 @@ function readRegistry(path) {
|
|
|
13434
14455
|
break;
|
|
13435
14456
|
length += count;
|
|
13436
14457
|
}
|
|
13437
|
-
const after =
|
|
14458
|
+
const after = fstatSync10(fd), current2 = lstatSync18(path);
|
|
13438
14459
|
safe3(path);
|
|
13439
14460
|
pluginNeed(length <= 1024 * 1024 && length === opened.size && after.size === opened.size && after.mtimeMs === opened.mtimeMs && after.ctimeMs === opened.ctimeMs && current2.dev === opened.dev && current2.ino === opened.ino && current2.mtimeMs === opened.mtimeMs && current2.ctimeMs === opened.ctimeMs, "Plugin registry changed during read");
|
|
13440
14461
|
let value;
|
|
@@ -13446,7 +14467,7 @@ function readRegistry(path) {
|
|
|
13446
14467
|
pluginNeed(pluginObject(value) && value.version === 2 && pluginObject(value.plugins), "Unsupported native plugin registry schema");
|
|
13447
14468
|
return { value, rawDigest: pluginHash(bytes.subarray(0, length)) };
|
|
13448
14469
|
} finally {
|
|
13449
|
-
|
|
14470
|
+
closeSync10(fd);
|
|
13450
14471
|
}
|
|
13451
14472
|
}
|
|
13452
14473
|
function nativeVersionMatches(version, receipt) {
|
|
@@ -13464,7 +14485,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
13464
14485
|
seen.add(target.pluginId);
|
|
13465
14486
|
const rows = plugins[target.pluginId];
|
|
13466
14487
|
pluginNeed(Array.isArray(rows) && rows.length === target.registrations.length, "Managed plugin registration membership changed");
|
|
13467
|
-
const [name, market] = target.pluginId.split("@"), cacheRoot =
|
|
14488
|
+
const [name, market] = target.pluginId.split("@"), cacheRoot = join26(dirname14(path), "cache", market, name);
|
|
13468
14489
|
const scopes = new Set, approvals = new Map, currentFiles = new Map;
|
|
13469
14490
|
plugins[target.pluginId] = rows.map((row) => {
|
|
13470
14491
|
pluginNeed(pluginObject(row), "Invalid managed plugin registration");
|
|
@@ -13477,10 +14498,10 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
13477
14498
|
pluginText(row.sourceProducerPath);
|
|
13478
14499
|
pluginText(row.lastUpdated, 64);
|
|
13479
14500
|
pluginNeed(Number.isFinite(Date.parse(row.lastUpdated)), "Invalid managed plugin update timestamp");
|
|
13480
|
-
pluginNeed(/^(?:[A-Za-z0-9][A-Za-z0-9._+-]*-)?[a-f0-9]{12}$/.test(row.version) && row.installPath ===
|
|
14501
|
+
pluginNeed(/^(?:[A-Za-z0-9][A-Za-z0-9._+-]*-)?[a-f0-9]{12}$/.test(row.version) && row.installPath === join26(cacheRoot, row.version), "Managed plugin cache path or version is outside its registration");
|
|
13481
14502
|
const approval = (producer) => {
|
|
13482
14503
|
pluginText(producer);
|
|
13483
|
-
const digest = `sha256:${
|
|
14504
|
+
const digest = `sha256:${basename7(producer)}`;
|
|
13484
14505
|
const receipt2 = readPluginAdmissionReceipt(witness.storeRoot, witness.bindingId, digest);
|
|
13485
14506
|
pluginNeed(receipt2.materializedPath === producer, "Managed producer path is not an approved immutable projection");
|
|
13486
14507
|
if (!verified.has(producer)) {
|
|
@@ -13507,7 +14528,7 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
13507
14528
|
return Object.fromEntries(Object.entries(row).filter(([key]) => !["version", "installPath", "sourceProducerPath", "previousProducerPaths", "lastUpdated"].includes(key)));
|
|
13508
14529
|
});
|
|
13509
14530
|
safe3(cacheRoot);
|
|
13510
|
-
const before =
|
|
14531
|
+
const before = lstatSync18(cacheRoot);
|
|
13511
14532
|
pluginNeed(before.isDirectory(), "Invalid managed plugin cache root");
|
|
13512
14533
|
const directory = opendirSync5(cacheRoot), names = [];
|
|
13513
14534
|
try {
|
|
@@ -13520,17 +14541,17 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
13520
14541
|
directory.closeSync();
|
|
13521
14542
|
}
|
|
13522
14543
|
for (const version of names.sort()) {
|
|
13523
|
-
const files = pluginFileWitnesses(snapshotPluginTree(
|
|
14544
|
+
const files = pluginFileWitnesses(snapshotPluginTree(join26(cacheRoot, version), { nativeRuntime: `claude-${target.native.version}` }));
|
|
13524
14545
|
verifiedBytes += files.reduce((total, file) => total + file.size, 0);
|
|
13525
14546
|
pluginNeed(verifiedBytes <= 256 * 1024 * 1024, "Managed plugin verification exceeds its aggregate byte budget");
|
|
13526
14547
|
const serialized = JSON.stringify(files);
|
|
13527
|
-
const expected = currentFiles.get(
|
|
14548
|
+
const expected = currentFiles.get(join26(cacheRoot, version));
|
|
13528
14549
|
pluginNeed(expected === undefined || serialized === expected, "Current native cache differs from its exact registered producer receipt");
|
|
13529
14550
|
pluginNeed([...approvals.values()].some((receipt) => nativeVersionMatches(version, receipt) && JSON.stringify(receipt.plan.files) === serialized), "A retained native plugin cache version differs from every approved projection");
|
|
13530
14551
|
}
|
|
13531
14552
|
for (const row of rows)
|
|
13532
14553
|
pluginNeed(names.includes(row.version), "Managed native plugin installation is missing");
|
|
13533
|
-
const after =
|
|
14554
|
+
const after = lstatSync18(cacheRoot);
|
|
13534
14555
|
safe3(cacheRoot);
|
|
13535
14556
|
pluginNeed(before.dev === after.dev && before.ino === after.ino && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs, "Managed cache version membership changed during verification");
|
|
13536
14557
|
}
|
|
@@ -13538,35 +14559,35 @@ function hashManagedPluginRegistry(path, managed) {
|
|
|
13538
14559
|
return pluginHash(JSON.stringify(snapshot.value));
|
|
13539
14560
|
}
|
|
13540
14561
|
var CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
|
|
13541
|
-
function
|
|
14562
|
+
function need5(value, reason) {
|
|
13542
14563
|
if (!value)
|
|
13543
14564
|
throw new Error(`Claude marketplace registry ${reason}`);
|
|
13544
14565
|
}
|
|
13545
|
-
function
|
|
13546
|
-
|
|
14566
|
+
function absolutePath3(value) {
|
|
14567
|
+
need5(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute15(value) && resolve14(value) === value, "requires a normalized absolute path");
|
|
13547
14568
|
}
|
|
13548
14569
|
var sameFile2 = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode && a.uid === b.uid && a.gid === b.gid && a.nlink === b.nlink && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs;
|
|
13549
14570
|
function parents2(path) {
|
|
13550
14571
|
const result = [];
|
|
13551
|
-
for (let at =
|
|
13552
|
-
const stat2 =
|
|
13553
|
-
|
|
14572
|
+
for (let at = dirname15(path);; at = dirname15(at)) {
|
|
14573
|
+
const stat2 = lstatSync19(at, { bigint: true });
|
|
14574
|
+
need5(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
|
|
13554
14575
|
result.push([at, stat2]);
|
|
13555
|
-
if (at ===
|
|
14576
|
+
if (at === dirname15(at))
|
|
13556
14577
|
return result;
|
|
13557
14578
|
}
|
|
13558
14579
|
}
|
|
13559
14580
|
function readRegistry2(path, budget) {
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
const ancestors2 = parents2(path), initial =
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
const fd =
|
|
14581
|
+
absolutePath3(path);
|
|
14582
|
+
need5(basename8(path) === "known_marketplaces.json", "requires known_marketplaces.json");
|
|
14583
|
+
need5(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
|
|
14584
|
+
const ancestors2 = parents2(path), initial = lstatSync19(path, { bigint: true });
|
|
14585
|
+
need5(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
|
|
14586
|
+
need5(initial.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
|
|
14587
|
+
const fd = openSync11(path, constants10.O_RDONLY | constants10.O_NOFOLLOW | constants10.O_NONBLOCK);
|
|
13567
14588
|
try {
|
|
13568
|
-
const opened =
|
|
13569
|
-
|
|
14589
|
+
const opened = fstatSync11(fd, { bigint: true });
|
|
14590
|
+
need5(opened.isFile() && sameFile2(initial, opened), "changed during open");
|
|
13570
14591
|
const bytes = Buffer.alloc(Number(opened.size) + 1);
|
|
13571
14592
|
let length = 0;
|
|
13572
14593
|
while (length < bytes.length) {
|
|
@@ -13575,11 +14596,11 @@ function readRegistry2(path, budget) {
|
|
|
13575
14596
|
break;
|
|
13576
14597
|
length += count;
|
|
13577
14598
|
}
|
|
13578
|
-
const after =
|
|
13579
|
-
|
|
14599
|
+
const after = fstatSync11(fd, { bigint: true }), current2 = lstatSync19(path, { bigint: true });
|
|
14600
|
+
need5(current2.isFile() && sameFile2(opened, after) && sameFile2(after, current2) && BigInt(length) === opened.size, "changed during read");
|
|
13580
14601
|
for (const [ancestor, before] of ancestors2) {
|
|
13581
|
-
const now =
|
|
13582
|
-
|
|
14602
|
+
const now = lstatSync19(ancestor, { bigint: true });
|
|
14603
|
+
need5(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
|
|
13583
14604
|
}
|
|
13584
14605
|
budget.remaining -= length;
|
|
13585
14606
|
try {
|
|
@@ -13588,7 +14609,7 @@ function readRegistry2(path, budget) {
|
|
|
13588
14609
|
throw new Error("Claude marketplace registry is not strict UTF-8");
|
|
13589
14610
|
}
|
|
13590
14611
|
} finally {
|
|
13591
|
-
|
|
14612
|
+
closeSync11(fd);
|
|
13592
14613
|
}
|
|
13593
14614
|
}
|
|
13594
14615
|
function parse6(text2) {
|
|
@@ -13599,7 +14620,7 @@ function parse6(text2) {
|
|
|
13599
14620
|
at++;
|
|
13600
14621
|
};
|
|
13601
14622
|
function string() {
|
|
13602
|
-
|
|
14623
|
+
need5(text2[at] === '"', "contains invalid JSON");
|
|
13603
14624
|
const start = at++;
|
|
13604
14625
|
while (at < text2.length) {
|
|
13605
14626
|
const character = text2[at++];
|
|
@@ -13610,7 +14631,7 @@ function parse6(text2) {
|
|
|
13610
14631
|
} catch {
|
|
13611
14632
|
throw new Error("Claude marketplace registry contains invalid JSON string");
|
|
13612
14633
|
}
|
|
13613
|
-
|
|
14634
|
+
need5(value2.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.stringCharacters, "exceeds its string limit");
|
|
13614
14635
|
return value2;
|
|
13615
14636
|
}
|
|
13616
14637
|
if (character === "\\")
|
|
@@ -13619,7 +14640,7 @@ function parse6(text2) {
|
|
|
13619
14640
|
throw new Error("Claude marketplace registry contains an unterminated JSON string");
|
|
13620
14641
|
}
|
|
13621
14642
|
function value(depth) {
|
|
13622
|
-
|
|
14643
|
+
need5(depth <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.depth && ++nodes <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.nodes, "exceeds its nesting or node limit");
|
|
13623
14644
|
space();
|
|
13624
14645
|
const character = text2[at];
|
|
13625
14646
|
if (character === '"')
|
|
@@ -13627,26 +14648,26 @@ function parse6(text2) {
|
|
|
13627
14648
|
if (character === "{" || character === "[") {
|
|
13628
14649
|
at++;
|
|
13629
14650
|
space();
|
|
13630
|
-
const
|
|
14651
|
+
const object5 = character === "{", end = object5 ? "}" : "]", entries = [], items = [], keys = new Set;
|
|
13631
14652
|
if (text2[at] !== end)
|
|
13632
14653
|
while (true) {
|
|
13633
|
-
if (
|
|
14654
|
+
if (object5) {
|
|
13634
14655
|
space();
|
|
13635
14656
|
const key = string();
|
|
13636
|
-
|
|
14657
|
+
need5(!keys.has(key), "contains a duplicate JSON key");
|
|
13637
14658
|
keys.add(key);
|
|
13638
14659
|
space();
|
|
13639
|
-
|
|
14660
|
+
need5(text2[at++] === ":", "contains invalid JSON");
|
|
13640
14661
|
entries.push([key, value(depth + 1)]);
|
|
13641
14662
|
} else
|
|
13642
14663
|
items.push(value(depth + 1));
|
|
13643
14664
|
space();
|
|
13644
14665
|
if (text2[at] === end)
|
|
13645
14666
|
break;
|
|
13646
|
-
|
|
14667
|
+
need5(text2[at++] === ",", "contains invalid JSON");
|
|
13647
14668
|
}
|
|
13648
14669
|
at++;
|
|
13649
|
-
return
|
|
14670
|
+
return object5 ? { kind: "object", entries } : { kind: "array", items };
|
|
13650
14671
|
}
|
|
13651
14672
|
for (const literal of ["true", "false", "null"])
|
|
13652
14673
|
if (text2.startsWith(literal, at)) {
|
|
@@ -13655,22 +14676,22 @@ function parse6(text2) {
|
|
|
13655
14676
|
}
|
|
13656
14677
|
numberPattern.lastIndex = at;
|
|
13657
14678
|
const number = numberPattern.exec(text2);
|
|
13658
|
-
|
|
14679
|
+
need5(number, "contains invalid JSON");
|
|
13659
14680
|
at += number[0].length;
|
|
13660
14681
|
return { kind: "number", value: number[0] };
|
|
13661
14682
|
}
|
|
13662
14683
|
const root2 = value(0);
|
|
13663
14684
|
space();
|
|
13664
|
-
|
|
13665
|
-
|
|
14685
|
+
need5(at === text2.length && root2.kind === "object", "requires a JSON object without trailing content");
|
|
14686
|
+
need5(root2.entries.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.rows, "exceeds its marketplace row limit");
|
|
13666
14687
|
return root2;
|
|
13667
14688
|
}
|
|
13668
14689
|
function exactKeys(value, keys) {
|
|
13669
14690
|
return value?.kind === "object" && value.entries.length === keys.length && value.entries.every(([key]) => keys.includes(key));
|
|
13670
14691
|
}
|
|
13671
|
-
var field = (
|
|
14692
|
+
var field = (object5, key) => object5.entries.find(([name]) => name === key)?.[1];
|
|
13672
14693
|
function textField(value) {
|
|
13673
|
-
|
|
14694
|
+
need5(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
|
|
13674
14695
|
return value.value;
|
|
13675
14696
|
}
|
|
13676
14697
|
function normalizeRow(row, version = 1) {
|
|
@@ -13678,7 +14699,7 @@ function normalizeRow(row, version = 1) {
|
|
|
13678
14699
|
if (version !== 2 || !exactKeys(row, ["source", "installLocation", "lastUpdated", "autoUpdate"]))
|
|
13679
14700
|
return;
|
|
13680
14701
|
const autoUpdate = field(row, "autoUpdate");
|
|
13681
|
-
|
|
14702
|
+
need5(autoUpdate?.kind === "literal" && (autoUpdate.value === "true" || autoUpdate.value === "false"), "contains an invalid automatic-update preference");
|
|
13682
14703
|
}
|
|
13683
14704
|
const source = field(row, "source");
|
|
13684
14705
|
if (source?.kind !== "object")
|
|
@@ -13691,52 +14712,52 @@ function normalizeRow(row, version = 1) {
|
|
|
13691
14712
|
return;
|
|
13692
14713
|
const sourceLocation = textField(field(source, location));
|
|
13693
14714
|
if (type.value === "directory")
|
|
13694
|
-
|
|
13695
|
-
|
|
14715
|
+
absolutePath3(sourceLocation);
|
|
14716
|
+
absolutePath3(textField(field(row, "installLocation")));
|
|
13696
14717
|
const updated = textField(field(row, "lastUpdated"));
|
|
13697
|
-
|
|
14718
|
+
need5(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(updated), "contains an invalid recognized update timestamp");
|
|
13698
14719
|
const millis = Date.parse(updated);
|
|
13699
|
-
|
|
14720
|
+
need5(Number.isFinite(millis) && new Date(millis).toISOString() === updated, "contains an invalid recognized update timestamp");
|
|
13700
14721
|
row.entries = row.entries.map(([key, value]) => [key, key === "lastUpdated" ? { kind: "validated-update-time" } : value]);
|
|
13701
14722
|
}
|
|
13702
14723
|
function captureClaudeMarketplaceRegistry(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
13703
14724
|
const value = parse6(readRegistry2(path, budget));
|
|
13704
14725
|
for (const [, row] of value.entries)
|
|
13705
14726
|
normalizeRow(row);
|
|
13706
|
-
const sha2562 =
|
|
14727
|
+
const sha2562 = createHash15("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
|
|
13707
14728
|
return { path, hashMode: "claude-marketplace-registry", sha256: sha2562 };
|
|
13708
14729
|
}
|
|
13709
14730
|
function captureClaudeMarketplaceRegistryV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
13710
14731
|
const value = parse6(readRegistry2(path, budget));
|
|
13711
14732
|
for (const [, row] of value.entries)
|
|
13712
14733
|
normalizeRow(row, 2);
|
|
13713
|
-
const sha2562 =
|
|
14734
|
+
const sha2562 = createHash15("sha256").update("hasna.skills.claude-marketplace-registry.v2\x00").update(JSON.stringify(value)).digest("hex");
|
|
13714
14735
|
return { path, hashMode: "claude-marketplace-registry-v2", sha256: sha2562 };
|
|
13715
14736
|
}
|
|
13716
|
-
function
|
|
14737
|
+
function need6(value, code) {
|
|
13717
14738
|
if (!value)
|
|
13718
14739
|
throw new Error(`CODEX_HOOK_TRUST_${code}`);
|
|
13719
14740
|
}
|
|
13720
14741
|
var sameStat = (a, b) => ["dev", "ino", "uid", "nlink", "mode", "size", "mtimeNs", "ctimeNs"].every((key) => a[key] === b[key]);
|
|
13721
14742
|
function safeParents(file) {
|
|
13722
|
-
for (let p =
|
|
13723
|
-
const s =
|
|
13724
|
-
|
|
13725
|
-
|
|
13726
|
-
if (
|
|
14743
|
+
for (let p = dirname16(file);; p = dirname16(p)) {
|
|
14744
|
+
const s = lstatSync20(p);
|
|
14745
|
+
need6(s.isDirectory() && !s.isSymbolicLink(), "UNSAFE_PARENT");
|
|
14746
|
+
need6((s.mode & 18) === 0 || s.uid === 0 && (s.mode & 512) !== 0, "UNSAFE_PARENT");
|
|
14747
|
+
if (dirname16(p) === p)
|
|
13727
14748
|
break;
|
|
13728
14749
|
}
|
|
13729
14750
|
}
|
|
13730
14751
|
function snapshot(file, privateFile = false, options = {}) {
|
|
13731
14752
|
const readOnlyPackage = options.readOnlyPackage === true && !privateFile;
|
|
13732
14753
|
safeParents(file);
|
|
13733
|
-
const initial =
|
|
13734
|
-
|
|
13735
|
-
|
|
13736
|
-
const fd =
|
|
14754
|
+
const initial = lstatSync20(file, { bigint: true });
|
|
14755
|
+
need6(initial.isFile() && initial.uid === BigInt(process.getuid()) && (initial.nlink === 1n || readOnlyPackage && initial.nlink > 1n) && (initial.mode & 0o022n) === 0n && (!privateFile || (initial.mode & 0o777n) === 0o600n), "UNSAFE_FILE");
|
|
14756
|
+
need6(initial.size <= 8n * 1024n * 1024n, "FILE_BOUND");
|
|
14757
|
+
const fd = openSync12(file, constants11.O_RDONLY | constants11.O_NOFOLLOW | constants11.O_NONBLOCK);
|
|
13737
14758
|
try {
|
|
13738
|
-
const before =
|
|
13739
|
-
|
|
14759
|
+
const before = fstatSync12(fd, { bigint: true });
|
|
14760
|
+
need6(sameStat(initial, before), "INPUT_CHANGED");
|
|
13740
14761
|
const bytes = Buffer.alloc(Number(before.size) + 1);
|
|
13741
14762
|
let length = 0;
|
|
13742
14763
|
while (length < bytes.length) {
|
|
@@ -13745,25 +14766,26 @@ function snapshot(file, privateFile = false, options = {}) {
|
|
|
13745
14766
|
break;
|
|
13746
14767
|
length += n2;
|
|
13747
14768
|
}
|
|
13748
|
-
|
|
14769
|
+
need6(length === Number(before.size) && sameStat(before, fstatSync12(fd, { bigint: true })) && sameStat(before, lstatSync20(file, { bigint: true })), "INPUT_CHANGED");
|
|
13749
14770
|
const content = bytes.subarray(0, length), text2 = new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
13750
|
-
return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before, sha256:
|
|
14771
|
+
return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before, sha256: createHash16("sha256").update(content).digest("hex") };
|
|
13751
14772
|
} finally {
|
|
13752
|
-
|
|
14773
|
+
closeSync12(fd);
|
|
13753
14774
|
}
|
|
13754
14775
|
}
|
|
13755
14776
|
function unchanged2(before) {
|
|
13756
14777
|
const after = snapshot(before.file, before.privateFile, before.readOnlyPackage ? { readOnlyPackage: true } : {});
|
|
13757
|
-
|
|
14778
|
+
need6(sameStat(before.stat, after.stat) && after.sha256 === before.sha256, "INPUT_CHANGED");
|
|
13758
14779
|
}
|
|
13759
14780
|
var events = { UserPromptSubmit: ["userPromptSubmit", "user_prompt_submit"], SessionStart: ["sessionStart", "session_start"], SubagentStart: ["subagentStart", "subagent_start"] };
|
|
13760
14781
|
var matchedEvents = { ...events, PreToolUse: ["preToolUse", "pre_tool_use"] };
|
|
13761
14782
|
var proofs = new WeakMap;
|
|
13762
|
-
var
|
|
14783
|
+
var sha4 = (text2) => createHash17("sha256").update(text2).digest("hex");
|
|
14784
|
+
var fields = [...CODEX_DISCOVERY_PROJECTION_FIELDS];
|
|
13763
14785
|
var proofs2 = new WeakMap;
|
|
13764
14786
|
function assertCodexHookDiscoveryRecovery(binding, witness) {
|
|
13765
14787
|
const proof = proofs2.get(witness);
|
|
13766
|
-
|
|
14788
|
+
need6(!!proof && binding.agent === "codex" && witness.discoverySha256 === sha4(JSON.stringify(binding)), "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
|
|
13767
14789
|
unchanged2(proof.before);
|
|
13768
14790
|
unchanged2(proof.current);
|
|
13769
14791
|
proof.recheckAdditions();
|
|
@@ -13772,10 +14794,10 @@ function verifiesCodexHookDiscoverySource(binding, source, currentDigest, witnes
|
|
|
13772
14794
|
if (!witness || source.path !== witness.configPath || source.hashMode !== "bytes")
|
|
13773
14795
|
return false;
|
|
13774
14796
|
assertCodexHookDiscoveryRecovery(binding, witness);
|
|
13775
|
-
|
|
14797
|
+
need6(source.sha256 === witness.beforeSha256 && currentDigest === witness.currentSha256, "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
|
|
13776
14798
|
return true;
|
|
13777
14799
|
}
|
|
13778
|
-
var digest = (text2) =>
|
|
14800
|
+
var digest = (text2) => createHash18("sha256").update(text2).digest("hex");
|
|
13779
14801
|
function parseConfig(text2, path, toml = false) {
|
|
13780
14802
|
try {
|
|
13781
14803
|
const value = toml ? Bun.TOML.parse(text2) : JSON.parse(text2);
|
|
@@ -13787,12 +14809,12 @@ function parseConfig(text2, path, toml = false) {
|
|
|
13787
14809
|
}
|
|
13788
14810
|
}
|
|
13789
14811
|
function safe4(path) {
|
|
13790
|
-
if (!
|
|
14812
|
+
if (!isAbsolute16(path) || path.includes("\x00"))
|
|
13791
14813
|
throw new Error("Expected an absolute native discovery path");
|
|
13792
|
-
for (let cursor = path;; cursor =
|
|
13793
|
-
if (
|
|
14814
|
+
for (let cursor = path;; cursor = dirname17(cursor)) {
|
|
14815
|
+
if (lstatSync21(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
13794
14816
|
throw new Error(`Refusing symlink native discovery input: ${cursor}`);
|
|
13795
|
-
if (
|
|
14817
|
+
if (dirname17(cursor) === cursor)
|
|
13796
14818
|
break;
|
|
13797
14819
|
}
|
|
13798
14820
|
}
|
|
@@ -13800,14 +14822,34 @@ function read2(path, changes) {
|
|
|
13800
14822
|
safe4(path);
|
|
13801
14823
|
if (changes?.has(path))
|
|
13802
14824
|
return changes.get(path);
|
|
13803
|
-
const stat2 =
|
|
14825
|
+
const stat2 = lstatSync21(path, { throwIfNoEntry: false });
|
|
13804
14826
|
if (!stat2)
|
|
13805
14827
|
return null;
|
|
13806
14828
|
if (!stat2.isFile() || stat2.size > 16 * 1024 * 1024)
|
|
13807
14829
|
throw new Error(`Unsupported or oversized native discovery input: ${path}`);
|
|
13808
|
-
return
|
|
14830
|
+
return readFileSync17(path, "utf8");
|
|
14831
|
+
}
|
|
14832
|
+
function projectNativeDiscoveryFields(text2, format, fields2, path) {
|
|
14833
|
+
const object5 = format === "yaml" ? parseHermesConfig(text2) : parseConfig(text2, path, format === "toml");
|
|
14834
|
+
if (!object5 || typeof object5 !== "object" || Array.isArray(object5))
|
|
14835
|
+
throw new Error("Expected native discovery configuration object");
|
|
14836
|
+
return digest(JSON.stringify(Object.fromEntries(fields2.map((field2) => [field2, object5[field2] ?? null]))));
|
|
14837
|
+
}
|
|
14838
|
+
function isSupersededCodexProjection(agent, configPath, source) {
|
|
14839
|
+
return agent === "codex" && source.path === configPath && source.format === "toml" && isDeepStrictEqual5(source.fields, [...CODEX_DISCOVERY_PROJECTION_FIELDS]);
|
|
14840
|
+
}
|
|
14841
|
+
function supersedesCodexProjection(sources, configPath) {
|
|
14842
|
+
return sources.some((source) => source.path === configPath && (source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3"));
|
|
13809
14843
|
}
|
|
13810
14844
|
function projected(source, changes, budget = discoveryByteBudget()) {
|
|
14845
|
+
if (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3") {
|
|
14846
|
+
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
14847
|
+
throw new Error("Codex settings witnesses require exact typed metadata");
|
|
14848
|
+
const current2 = (source.hashMode === "codex-settings-v3" ? captureCodexSettingsV3 : source.hashMode === "codex-settings-v2" ? captureCodexSettingsV2 : captureCodexSettings)(source.path, budget).sha256;
|
|
14849
|
+
if (current2 !== source.sha256)
|
|
14850
|
+
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
14851
|
+
return changes?.has(source.path) ? (source.hashMode === "codex-settings-v3" ? hashCodexSettingsReplacementV3 : source.hashMode === "codex-settings-v2" ? hashCodexSettingsReplacementV2 : hashCodexSettingsReplacement)(changes.get(source.path), budget) : current2;
|
|
14852
|
+
}
|
|
13811
14853
|
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
|
|
13812
14854
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
13813
14855
|
throw new Error("Claude settings witnesses require exact typed metadata");
|
|
@@ -13845,10 +14887,7 @@ function projected(source, changes, budget = discoveryByteBudget()) {
|
|
|
13845
14887
|
return null;
|
|
13846
14888
|
if (!source.format)
|
|
13847
14889
|
return digest(text2);
|
|
13848
|
-
|
|
13849
|
-
if (!object4 || typeof object4 !== "object" || Array.isArray(object4))
|
|
13850
|
-
throw new Error("Expected native discovery configuration object");
|
|
13851
|
-
return digest(JSON.stringify(Object.fromEntries((source.fields ?? []).map((field2) => [field2, object4[field2] ?? null]))));
|
|
14890
|
+
return projectNativeDiscoveryFields(text2, source.format, source.fields ?? [], source.path);
|
|
13852
14891
|
}
|
|
13853
14892
|
function verifyAgentDiscovery(binding, codexRecovery) {
|
|
13854
14893
|
if (codexRecovery)
|
|
@@ -13857,8 +14896,16 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
13857
14896
|
throw new Error("Invalid native discovery binding");
|
|
13858
14897
|
if (binding.agent === "hermes" && !binding.directories?.length)
|
|
13859
14898
|
throw new Error("Hermes discovery requires directory membership coverage; run skills hook install with a fresh discovery review");
|
|
13860
|
-
|
|
13861
|
-
|
|
14899
|
+
const installationRoots = codexInstallationRoots(binding);
|
|
14900
|
+
const installationInput = (path) => installationRoots.some((root2) => path === root2 || path.startsWith(root2 + sep6));
|
|
14901
|
+
if (binding.directories !== undefined) {
|
|
14902
|
+
const projected2 = binding.codexInstallationInputs?.directories ?? [];
|
|
14903
|
+
const ancestors2 = binding.directories.filter((directory) => installationRoots.some((root2) => root2.startsWith(directory.path + sep6)));
|
|
14904
|
+
if (projected2.length !== ancestors2.length || projected2.some((directory) => !ancestors2.some((original) => original.path === directory.path)))
|
|
14905
|
+
throw new Error("Missing installation input directory projection");
|
|
14906
|
+
verifyDiscoveryDirectories(binding.directories.filter((directory) => !installationInput(directory.path) && !ancestors2.includes(directory)));
|
|
14907
|
+
verifyDiscoveryDirectories(projected2, installationRoots);
|
|
14908
|
+
}
|
|
13862
14909
|
const budget = discoveryByteBudget();
|
|
13863
14910
|
for (const source of binding.sources) {
|
|
13864
14911
|
assertMarketplaceBinding(binding, source);
|
|
@@ -13868,6 +14915,10 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
13868
14915
|
throw new Error("Invalid native discovery projection");
|
|
13869
14916
|
if (source.sha256 !== null && !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
13870
14917
|
throw new Error("Invalid native discovery digest");
|
|
14918
|
+
if (installationInput(source.path)) {
|
|
14919
|
+
safe4(source.path);
|
|
14920
|
+
continue;
|
|
14921
|
+
}
|
|
13871
14922
|
const current2 = projected(source, undefined, budget);
|
|
13872
14923
|
if (current2 !== source.sha256 && !verifiesCodexHookDiscoverySource(binding, source, current2, codexRecovery))
|
|
13873
14924
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
@@ -13875,25 +14926,52 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
13875
14926
|
for (const root2 of binding.roots)
|
|
13876
14927
|
safe4(root2);
|
|
13877
14928
|
}
|
|
14929
|
+
function codexInstallationRoots(binding) {
|
|
14930
|
+
const proof = binding.codexInstallationInputs;
|
|
14931
|
+
if (proof === undefined)
|
|
14932
|
+
return [];
|
|
14933
|
+
if (binding.agent !== "codex" || binding.method !== "reviewed" || proof.version !== "codex-cli 0.160.0" || !/^[a-f0-9]{64}$/.test(proof.catalogSha256) || !Array.isArray(proof.plugins) || !proof.plugins.length)
|
|
14934
|
+
throw new Error("Invalid native installation input proof");
|
|
14935
|
+
const cache = dirname17(dirname17(proof.plugins[0].pluginParent));
|
|
14936
|
+
const config = join27(dirname17(dirname17(cache)), "config.toml"), home = dirname17(dirname17(dirname17(cache)));
|
|
14937
|
+
const configSource = binding.sources.find((source) => source.path === config && source.sha256 !== null && (!source.format || source.format === "toml" && source.fields?.includes("plugins")));
|
|
14938
|
+
const nativeRoots = NATIVE_SKILL_ROOTS.filter(([agent]) => agent === "codex").map(([, path]) => join27(home, path));
|
|
14939
|
+
if (!configSource || proof.plugins.some((input) => nativeRoots.some((root2) => root2 === input.sourceRoot || root2.startsWith(input.sourceRoot + sep6) || input.sourceRoot.startsWith(root2 + sep6)) || binding.sources.some((source) => basename9(source.path) === "config.toml" && (source.path === input.sourceRoot || source.path.startsWith(input.sourceRoot + sep6)))))
|
|
14940
|
+
throw new Error("Native installation input overlaps configuration or loading roots");
|
|
14941
|
+
if (proof.plugins.some((input) => dirname17(dirname17(input.pluginParent)) !== cache || !binding.sources.some((source) => source.path === join27(input.sourceRoot, ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined)))
|
|
14942
|
+
throw new Error("Missing native installation input source witness");
|
|
14943
|
+
return reviewedCodexPluginSourceRoots(cache, proof.plugins, (path) => {
|
|
14944
|
+
const text2 = read2(path);
|
|
14945
|
+
if (text2 === null)
|
|
14946
|
+
throw new Error("Missing native installation input");
|
|
14947
|
+
return text2;
|
|
14948
|
+
});
|
|
14949
|
+
}
|
|
13878
14950
|
function assertMarketplaceBinding(binding, source) {
|
|
13879
|
-
if ((source.hashMode === "
|
|
14951
|
+
if ((source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3") && (binding.agent !== "codex" || binding.method !== "reviewed" || basename9(source.path) !== "config.toml"))
|
|
14952
|
+
throw new Error("Codex settings witnesses require explicit reviewed Codex configuration");
|
|
14953
|
+
if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename9(source.path) !== "settings.json"))
|
|
13880
14954
|
throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
|
|
13881
14955
|
if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
13882
14956
|
throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
|
|
13883
14957
|
}
|
|
13884
14958
|
function rebindAgentDiscovery(binding, changes) {
|
|
13885
14959
|
const budget = discoveryByteBudget();
|
|
14960
|
+
const installationRoots = codexInstallationRoots(binding);
|
|
13886
14961
|
return { ...binding, sources: binding.sources.map((source) => {
|
|
13887
14962
|
assertMarketplaceBinding(binding, source);
|
|
14963
|
+
if (!changes.has(source.path) && installationRoots.some((root2) => source.path.startsWith(root2 + sep6)))
|
|
14964
|
+
return source;
|
|
13888
14965
|
const sha2562 = projected(source, changes, budget);
|
|
13889
14966
|
if (!changes.has(source.path) && sha2562 !== source.sha256)
|
|
13890
14967
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
13891
14968
|
return { ...source, sha256: sha2562 };
|
|
13892
14969
|
}) };
|
|
13893
14970
|
}
|
|
13894
|
-
function assertProjectDiscovery(agent, directories, home,
|
|
14971
|
+
function assertProjectDiscovery(agent, directories, home, canonical6 = resolve15, reviewed) {
|
|
14972
|
+
const homeConfigPath = canonical6(agentDiscoveryConfigPath(home, agent));
|
|
13895
14973
|
for (const directory of directories) {
|
|
13896
|
-
const isHome =
|
|
14974
|
+
const isHome = resolve15(directory) === resolve15(home);
|
|
13897
14975
|
if (agent === "hermes") {
|
|
13898
14976
|
assertHermesEnvironment(home);
|
|
13899
14977
|
continue;
|
|
@@ -13902,10 +14980,11 @@ function assertProjectDiscovery(agent, directories, home, canonical4 = resolve14
|
|
|
13902
14980
|
continue;
|
|
13903
14981
|
const paths = agent === "claude" ? [...isHome ? [] : [".claude/settings.json"], ".claude/settings.local.json"] : agent === "codex" ? [".codex/config.toml"] : agent === "gemini" ? [".gemini/settings.json"] : agent === "opencode" ? ["opencode.json", "opencode.jsonc", ".opencode/opencode.json", ".opencode/opencode.jsonc"] : [".cursor/hooks.json"];
|
|
13904
14982
|
for (const suffix of paths) {
|
|
13905
|
-
const path =
|
|
14983
|
+
const path = canonical6(join27(directory, suffix)), raw = read2(path);
|
|
13906
14984
|
if (raw === null)
|
|
13907
14985
|
continue;
|
|
13908
|
-
|
|
14986
|
+
const isRootAliasedHomeConfig = agent === "claude" && suffix === ".claude/settings.json" || agent === "codex" && suffix === ".codex/config.toml";
|
|
14987
|
+
if (isRootAliasedHomeConfig && path === homeConfigPath)
|
|
13909
14988
|
continue;
|
|
13910
14989
|
if (suffix.endsWith(".jsonc"))
|
|
13911
14990
|
throw new Error(`NATIVE_SKILL_DRIFT: project JSONC discovery configuration requires review: ${path}`);
|
|
@@ -13917,28 +14996,28 @@ function assertProjectDiscovery(agent, directories, home, canonical4 = resolve14
|
|
|
13917
14996
|
const managed = reviewed.sources.flatMap((source) => source.hashMode === "claude-plugin-registry" ? source.managedPlugins ?? [] : []);
|
|
13918
14997
|
if (exact && projected(exact) === exact.sha256 && managed.length) {
|
|
13919
14998
|
const bindings = managed.map((item) => readPluginBinding(item.storeRoot, item.bindingId));
|
|
13920
|
-
admittedPlugins = Object.entries(config.enabledPlugins).every(([id, enabled]) => typeof enabled === "boolean" && bindings.some((binding) => binding.target.pluginId === id && binding.target.registrations.some((scope) => scope.scope === "project" && scope.projectPath ===
|
|
14999
|
+
admittedPlugins = Object.entries(config.enabledPlugins).every(([id, enabled]) => typeof enabled === "boolean" && bindings.some((binding) => binding.target.pluginId === id && binding.target.registrations.some((scope) => scope.scope === "project" && scope.projectPath === canonical6(directory))));
|
|
13921
15000
|
}
|
|
13922
15001
|
}
|
|
13923
15002
|
if (keys.some((key) => config[key] !== undefined && !(key === "enabledPlugins" && admittedPlugins)) || config.disableAllHooks === true || config.disableBundledSkills === false || config.hooksConfig?.enabled === false || config.permission?.skill !== undefined || config.permissions?.deny?.some((rule) => typeof rule === "string" && /^Skill(?:\(|$)/.test(rule)))
|
|
13924
15003
|
throw new Error(`NATIVE_SKILL_DRIFT: higher-precedence project skill or hook configuration requires review: ${path}`);
|
|
13925
15004
|
}
|
|
13926
15005
|
if (agent === "claude") {
|
|
13927
|
-
const commands =
|
|
15006
|
+
const commands = canonical6(join27(directory, ".claude/commands"));
|
|
13928
15007
|
safe4(commands);
|
|
13929
|
-
if (
|
|
15008
|
+
if (lstatSync21(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(commands).length)
|
|
13930
15009
|
throw new Error("NATIVE_SKILL_DRIFT: legacy project command discovery requires a dedicated format adapter");
|
|
13931
15010
|
}
|
|
13932
15011
|
}
|
|
13933
15012
|
}
|
|
13934
15013
|
function agentDiscoveryConfigPath(home, agent) {
|
|
13935
|
-
return
|
|
15014
|
+
return join27(home, agent === "hermes" ? ".hermes/config.yaml" : agent === "opencode" ? ".config/opencode/opencode.json" : `.${agent}/${agent === "codex" ? "config.toml" : agent === "cursor" ? "hooks.json" : "settings.json"}`);
|
|
13936
15015
|
}
|
|
13937
15016
|
function resolveAgentDiscovery(options) {
|
|
13938
|
-
const
|
|
15017
|
+
const canonical6 = options.canonical ?? resolve15, home = resolve15(options.home), agent = options.agent;
|
|
13939
15018
|
const sources = [], roots = new Set, builtinNames = [];
|
|
13940
|
-
const witness = (path, format,
|
|
13941
|
-
const source = { path:
|
|
15019
|
+
const witness = (path, format, fields2) => {
|
|
15020
|
+
const source = { path: canonical6(path), sha256: null, ...format ? { format, fields: fields2 } : {} };
|
|
13942
15021
|
source.sha256 = projected(source);
|
|
13943
15022
|
sources.push(source);
|
|
13944
15023
|
return read2(source.path);
|
|
@@ -13946,13 +15025,13 @@ function resolveAgentDiscovery(options) {
|
|
|
13946
15025
|
if (agent === "hermes")
|
|
13947
15026
|
assertHermesEnvironment(home);
|
|
13948
15027
|
const configPath = agentDiscoveryConfigPath(home, agent);
|
|
13949
|
-
const configText = witness(configPath, agent === "hermes" ? "yaml" : agent === "codex" ? "toml" : "json", agent === "hermes" ? ["skills", "plugins", "hooks"] : agent === "claude" ? ["enabledPlugins", "extraKnownMarketplaces"] : agent === "codex" ? [
|
|
15028
|
+
const configText = witness(configPath, agent === "hermes" ? "yaml" : agent === "codex" ? "toml" : "json", agent === "hermes" ? ["skills", "plugins", "hooks"] : agent === "claude" ? ["enabledPlugins", "extraKnownMarketplaces"] : agent === "codex" ? [...CODEX_DISCOVERY_PROJECTION_FIELDS] : agent === "gemini" ? ["skills", "extensions", "security"] : agent === "opencode" ? ["plugin", "skills"] : ["version"]);
|
|
13950
15029
|
const config = configText === null ? {} : agent === "hermes" ? parseHermesConfig(configText) : parseConfig(configText, configPath, agent === "codex");
|
|
13951
15030
|
const unresolved = (detail) => {
|
|
13952
15031
|
throw new Error(`Native discovery is unresolved (${agent}: ${detail}); provide a reviewed --discovery-inputs file`);
|
|
13953
15032
|
};
|
|
13954
15033
|
if (agent === "hermes") {
|
|
13955
|
-
const profile = witness(
|
|
15034
|
+
const profile = witness(join27(home, ".hermes/active_profile"));
|
|
13956
15035
|
if (profile?.trim() && profile.trim() !== "default")
|
|
13957
15036
|
unresolved("a non-default profile is active");
|
|
13958
15037
|
const declared = config.skills?.external_dirs ?? [], paths = typeof declared === "string" ? [declared] : declared;
|
|
@@ -13966,7 +15045,7 @@ function resolveAgentDiscovery(options) {
|
|
|
13966
15045
|
continue;
|
|
13967
15046
|
if (normalized.startsWith("~") && normalized !== "~" && !normalized.startsWith("~/"))
|
|
13968
15047
|
unresolved("user-specific tilde expansion requires a separate discovery adapter");
|
|
13969
|
-
const path = normalized === "~" ? home : normalized.startsWith("~/") ?
|
|
15048
|
+
const path = normalized === "~" ? home : normalized.startsWith("~/") ? join27(home, normalized.slice(2)) : resolve15(home, ".hermes", normalized);
|
|
13970
15049
|
safe4(path);
|
|
13971
15050
|
roots.add(path);
|
|
13972
15051
|
}
|
|
@@ -13975,24 +15054,24 @@ function resolveAgentDiscovery(options) {
|
|
|
13975
15054
|
const executable = Bun.which("gemini");
|
|
13976
15055
|
if (executable) {
|
|
13977
15056
|
let packageRoot;
|
|
13978
|
-
for (let directory =
|
|
13979
|
-
const path =
|
|
15057
|
+
for (let directory = dirname17(realpathSync7(executable)), depth = 0;depth < 8; directory = dirname17(directory), depth++) {
|
|
15058
|
+
const path = join27(directory, "package.json"), raw = read2(path);
|
|
13980
15059
|
if (raw !== null && parseConfig(raw, path).name === "@google/gemini-cli") {
|
|
13981
15060
|
packageRoot = directory;
|
|
13982
15061
|
witness(path);
|
|
13983
15062
|
break;
|
|
13984
15063
|
}
|
|
13985
|
-
if (
|
|
15064
|
+
if (dirname17(directory) === directory)
|
|
13986
15065
|
break;
|
|
13987
15066
|
}
|
|
13988
15067
|
if (!packageRoot)
|
|
13989
15068
|
unresolved("installed Gemini package layout is not recognized");
|
|
13990
|
-
const builtinRoot =
|
|
15069
|
+
const builtinRoot = join27(packageRoot, "bundle/builtin");
|
|
13991
15070
|
safe4(builtinRoot);
|
|
13992
|
-
if (!
|
|
15071
|
+
if (!lstatSync21(builtinRoot, { throwIfNoEntry: false })?.isDirectory())
|
|
13993
15072
|
unresolved("installed Gemini builtin root is missing");
|
|
13994
|
-
for (const name of
|
|
13995
|
-
const text2 = witness(
|
|
15073
|
+
for (const name of readdirSync10(builtinRoot).sort()) {
|
|
15074
|
+
const text2 = witness(join27(builtinRoot, name, "SKILL.md"));
|
|
13996
15075
|
const match = text2?.match(/^---\r?\n[\s\S]*?^name:\s*([a-z0-9][a-z0-9-]*)\s*$/m);
|
|
13997
15076
|
if (!match?.[1] || builtinNames.includes(match[1]))
|
|
13998
15077
|
unresolved("installed Gemini builtin name is missing or ambiguous");
|
|
@@ -14005,11 +15084,12 @@ function resolveAgentDiscovery(options) {
|
|
|
14005
15084
|
if (retained.agent !== agent || retained.method !== "reviewed")
|
|
14006
15085
|
unresolved("invalid retained review");
|
|
14007
15086
|
verifyAgentDiscovery(retained);
|
|
14008
|
-
if (!retained.sources.some((source) => source.path ===
|
|
15087
|
+
if (!retained.sources.some((source) => source.path === canonical6(configPath) && source.format === undefined && source.fields === undefined))
|
|
14009
15088
|
unresolved("retained review is missing the full agent configuration source");
|
|
14010
|
-
if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && source.path !==
|
|
15089
|
+
if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3"))) && source.path !== canonical6(configPath)))
|
|
14011
15090
|
unresolved("retained settings witness names another configuration source");
|
|
14012
|
-
|
|
15091
|
+
const covered = supersedesCodexProjection(retained.sources, canonical6(configPath)) ? sources.filter((source) => !isSupersededCodexProjection(agent, canonical6(configPath), source)) : sources;
|
|
15092
|
+
if (covered.some((source) => !retained.sources.some((saved) => isDeepStrictEqual5(saved, source))) || [...roots].some((root2) => !retained.roots.includes(root2)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
|
|
14013
15093
|
unresolved("retained review is missing current runtime discovery coverage");
|
|
14014
15094
|
return retained;
|
|
14015
15095
|
}
|
|
@@ -14023,48 +15103,49 @@ function resolveAgentDiscovery(options) {
|
|
|
14023
15103
|
if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
|
|
14024
15104
|
throw new Error("Explicit discovery reviews require full source-file hashes");
|
|
14025
15105
|
verifyAgentDiscovery(supplied);
|
|
14026
|
-
if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && source.path !==
|
|
15106
|
+
if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3"))) && source.path !== canonical6(configPath)))
|
|
14027
15107
|
throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
|
|
14028
|
-
if (!review.sources.some((source) => source.path ===
|
|
15108
|
+
if (!review.sources.some((source) => source.path === canonical6(configPath)))
|
|
14029
15109
|
throw new Error("Discovery review must include the agent configuration source");
|
|
14030
|
-
|
|
15110
|
+
const reviewedSources = supersedesCodexProjection(review.sources, canonical6(configPath)) ? sources.filter((source) => !isSupersededCodexProjection(agent, canonical6(configPath), source)) : sources;
|
|
15111
|
+
return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: [...reviewedSources, ...review.sources], ...agent === "gemini" ? { builtinNames } : {} };
|
|
14031
15112
|
}
|
|
14032
15113
|
function plugin(root2) {
|
|
14033
|
-
root2 =
|
|
15114
|
+
root2 = canonical6(root2);
|
|
14034
15115
|
safe4(root2);
|
|
14035
|
-
const manifestPath =
|
|
15116
|
+
const manifestPath = join27(root2, agent === "claude" ? ".claude-plugin/plugin.json" : ".codex-plugin/plugin.json");
|
|
14036
15117
|
const raw = witness(manifestPath);
|
|
14037
15118
|
if (raw === null)
|
|
14038
15119
|
unresolved("plugin manifest missing");
|
|
14039
15120
|
const manifest = parseConfig(raw, manifestPath);
|
|
14040
|
-
const hooks = witness(
|
|
15121
|
+
const hooks = witness(join27(root2, "hooks/hooks.json"));
|
|
14041
15122
|
if (manifest.hooks !== undefined || hooks !== null)
|
|
14042
15123
|
unresolved("plugin hooks require a separate no-skill-injection review");
|
|
14043
|
-
const commands =
|
|
15124
|
+
const commands = join27(root2, "commands");
|
|
14044
15125
|
safe4(commands);
|
|
14045
|
-
if (agent === "claude" && (manifest.commands !== undefined ||
|
|
15126
|
+
if (agent === "claude" && (manifest.commands !== undefined || lstatSync21(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(commands).length))
|
|
14046
15127
|
unresolved("legacy command discovery requires retirement or a dedicated format adapter");
|
|
14047
|
-
const rootSkill = witness(
|
|
15128
|
+
const rootSkill = witness(join27(root2, "SKILL.md"));
|
|
14048
15129
|
if (rootSkill !== null)
|
|
14049
15130
|
roots.add(root2);
|
|
14050
15131
|
const declared = manifest.skills === undefined ? ["./skills"] : typeof manifest.skills === "string" ? [manifest.skills] : manifest.skills;
|
|
14051
15132
|
if (!Array.isArray(declared) || declared.some((path) => typeof path !== "string"))
|
|
14052
15133
|
unresolved("unsupported plugin skill paths");
|
|
14053
15134
|
if (agent === "claude")
|
|
14054
|
-
roots.add(
|
|
15135
|
+
roots.add(join27(root2, "skills"));
|
|
14055
15136
|
for (const value of declared) {
|
|
14056
|
-
const path =
|
|
14057
|
-
if (path !== root2 && !path.startsWith(root2 +
|
|
15137
|
+
const path = resolve15(root2, value);
|
|
15138
|
+
if (path !== root2 && !path.startsWith(root2 + sep6))
|
|
14058
15139
|
unresolved("plugin skill path escapes its root");
|
|
14059
|
-
roots.add(path.endsWith(`${
|
|
15140
|
+
roots.add(path.endsWith(`${sep6}SKILL.md`) ? dirname17(path) : path);
|
|
14060
15141
|
}
|
|
14061
15142
|
}
|
|
14062
15143
|
if (agent === "claude") {
|
|
14063
|
-
const registrations = witness(
|
|
14064
|
-
const installed = registrations === null ? {} : parseConfig(registrations,
|
|
14065
|
-
const commands =
|
|
15144
|
+
const registrations = witness(join27(home, ".claude/plugins/installed_plugins.json"));
|
|
15145
|
+
const installed = registrations === null ? {} : parseConfig(registrations, join27(home, ".claude/plugins/installed_plugins.json")).plugins;
|
|
15146
|
+
const commands = canonical6(join27(home, ".claude/commands"));
|
|
14066
15147
|
safe4(commands);
|
|
14067
|
-
if (
|
|
15148
|
+
if (lstatSync21(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(commands).length)
|
|
14068
15149
|
unresolved("legacy home command discovery requires a dedicated format adapter");
|
|
14069
15150
|
for (const [id, enabled] of Object.entries(config.enabledPlugins ?? {})) {
|
|
14070
15151
|
if (enabled === false)
|
|
@@ -14084,34 +15165,34 @@ function resolveAgentDiscovery(options) {
|
|
|
14084
15165
|
unresolved("unsupported plugin enablement");
|
|
14085
15166
|
const split = id.lastIndexOf("@"), name = id.slice(0, split), marketplace = id.slice(split + 1);
|
|
14086
15167
|
const market = config.marketplaces?.[marketplace] ?? (marketplace === "personal" ? { source_type: "local", source: home } : undefined);
|
|
14087
|
-
if (split < 1 || market?.source_type !== "local" || typeof market.source !== "string" || !
|
|
15168
|
+
if (split < 1 || market?.source_type !== "local" || typeof market.source !== "string" || !isAbsolute16(market.source))
|
|
14088
15169
|
unresolved("enabled plugin marketplace is not an explicit local source");
|
|
14089
|
-
const catalogText = witness(
|
|
15170
|
+
const catalogText = witness(join27(market.source, ".agents/plugins/marketplace.json"));
|
|
14090
15171
|
if (catalogText === null)
|
|
14091
15172
|
unresolved("marketplace catalog missing");
|
|
14092
|
-
const catalog = parseConfig(catalogText,
|
|
15173
|
+
const catalog = parseConfig(catalogText, join27(market.source, ".agents/plugins/marketplace.json")), matches = Array.isArray(catalog.plugins) ? catalog.plugins.filter((item) => item.name === name) : [];
|
|
14093
15174
|
if (matches.length !== 1 || matches[0]?.source?.source !== "local" || typeof matches[0]?.source?.path !== "string")
|
|
14094
15175
|
unresolved("plugin catalog source is missing or ambiguous");
|
|
14095
|
-
plugin(
|
|
15176
|
+
plugin(resolve15(market.source, matches[0].source.path));
|
|
14096
15177
|
}
|
|
14097
15178
|
} else if (agent === "opencode") {
|
|
14098
|
-
if (witness(
|
|
15179
|
+
if (witness(join27(home, ".config/opencode/opencode.jsonc")) !== null || witness(join27(home, ".config/opencode/config.json")) !== null)
|
|
14099
15180
|
unresolved("additional config layers require review");
|
|
14100
15181
|
if ((config.plugin?.length ?? 0) > 0 || config.skills?.urls?.length)
|
|
14101
15182
|
unresolved("external plugin or remote skill source");
|
|
14102
15183
|
for (const path of config.skills?.paths ?? []) {
|
|
14103
|
-
if (typeof path !== "string" || !
|
|
15184
|
+
if (typeof path !== "string" || !isAbsolute16(path))
|
|
14104
15185
|
unresolved("relative or malformed added skill root");
|
|
14105
15186
|
roots.add(path);
|
|
14106
15187
|
}
|
|
14107
|
-
const localPlugins =
|
|
15188
|
+
const localPlugins = join27(home, ".config/opencode/plugins");
|
|
14108
15189
|
safe4(localPlugins);
|
|
14109
|
-
if (
|
|
15190
|
+
if (lstatSync21(localPlugins, { throwIfNoEntry: false }) && readdirSync10(localPlugins).some((name) => name !== "skills-cli.js"))
|
|
14110
15191
|
unresolved("additional local plugin hooks require review");
|
|
14111
15192
|
} else if (agent === "hermes") {
|
|
14112
|
-
for (const directory of [
|
|
15193
|
+
for (const directory of [join27(home, ".hermes/plugins"), join27(home, ".hermes/hermes-agent")]) {
|
|
14113
15194
|
safe4(directory);
|
|
14114
|
-
if (
|
|
15195
|
+
if (lstatSync21(directory, { throwIfNoEntry: false }))
|
|
14115
15196
|
unresolved("installed runtime/plugin discovery requires reviewed source bindings");
|
|
14116
15197
|
}
|
|
14117
15198
|
if (Bun.which("hermes") || Object.keys(config.plugins ?? {}).length)
|
|
@@ -14119,24 +15200,24 @@ function resolveAgentDiscovery(options) {
|
|
|
14119
15200
|
} else if (agent === "gemini") {
|
|
14120
15201
|
if (Object.keys(config.extensions ?? {}).length || config.skills?.paths?.length)
|
|
14121
15202
|
unresolved("custom extension or skill paths");
|
|
14122
|
-
const directory =
|
|
15203
|
+
const directory = join27(home, ".gemini/extensions");
|
|
14123
15204
|
safe4(directory);
|
|
14124
|
-
if (
|
|
14125
|
-
for (const name of
|
|
14126
|
-
const root2 =
|
|
15205
|
+
if (lstatSync21(directory, { throwIfNoEntry: false }))
|
|
15206
|
+
for (const name of readdirSync10(directory)) {
|
|
15207
|
+
const root2 = join27(directory, name);
|
|
14127
15208
|
safe4(root2);
|
|
14128
|
-
if (!
|
|
15209
|
+
if (!lstatSync21(root2)?.isDirectory())
|
|
14129
15210
|
continue;
|
|
14130
|
-
const raw = witness(
|
|
15211
|
+
const raw = witness(join27(root2, "gemini-extension.json"));
|
|
14131
15212
|
if (raw === null)
|
|
14132
15213
|
unresolved("extension registration missing");
|
|
14133
|
-
const manifest = parseConfig(raw,
|
|
14134
|
-
if (manifest.hooks !== undefined || witness(
|
|
15214
|
+
const manifest = parseConfig(raw, join27(root2, "gemini-extension.json"));
|
|
15215
|
+
if (manifest.hooks !== undefined || witness(join27(root2, "hooks/hooks.json")) !== null || witness(join27(root2, ".gemini-extension-install.json")) !== null)
|
|
14135
15216
|
unresolved("extension hooks or linked registration require review");
|
|
14136
|
-
roots.add(
|
|
15217
|
+
roots.add(join27(root2, "skills"));
|
|
14137
15218
|
}
|
|
14138
15219
|
}
|
|
14139
|
-
return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([
|
|
15220
|
+
return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([join27(home, ".hermes/plugins"), join27(home, ".hermes/hermes-agent")].map((path) => canonical6(path))) } : {} };
|
|
14140
15221
|
}
|
|
14141
15222
|
var NATIVE_DISCOVERY_ROOTS = NATIVE_SKILL_ROOTS.map(([, path]) => path.split("/"));
|
|
14142
15223
|
var OFFICIAL_SKILL_NAMES = new Set(SKILLS.map((skill) => skill.name));
|
|
@@ -14191,7 +15272,7 @@ __export2(exports_external, {
|
|
|
14191
15272
|
regexes: () => exports_regexes,
|
|
14192
15273
|
regex: () => _regex,
|
|
14193
15274
|
refine: () => refine,
|
|
14194
|
-
record: () =>
|
|
15275
|
+
record: () => record3,
|
|
14195
15276
|
readonly: () => readonly,
|
|
14196
15277
|
property: () => _property,
|
|
14197
15278
|
properties: () => _properties,
|
|
@@ -14207,7 +15288,7 @@ __export2(exports_external, {
|
|
|
14207
15288
|
overwrite: () => _overwrite,
|
|
14208
15289
|
output: () => output,
|
|
14209
15290
|
optional: () => optional,
|
|
14210
|
-
object: () =>
|
|
15291
|
+
object: () => object5,
|
|
14211
15292
|
number: () => number2,
|
|
14212
15293
|
nullish: () => nullish2,
|
|
14213
15294
|
nullable: () => nullable,
|
|
@@ -14256,7 +15337,7 @@ __export2(exports_external, {
|
|
|
14256
15337
|
input: () => input,
|
|
14257
15338
|
includes: () => _includes,
|
|
14258
15339
|
httpUrl: () => httpUrl,
|
|
14259
|
-
hostname: () =>
|
|
15340
|
+
hostname: () => hostname3,
|
|
14260
15341
|
hex: () => hex2,
|
|
14261
15342
|
hash: () => hash2,
|
|
14262
15343
|
guid: () => guid2,
|
|
@@ -14833,9 +15914,9 @@ function floatSafeRemainder(val, step) {
|
|
|
14833
15914
|
return ratio - roundedRatio;
|
|
14834
15915
|
}
|
|
14835
15916
|
var EVALUATING = /* @__PURE__ */ Symbol("evaluating");
|
|
14836
|
-
function defineLazy(
|
|
15917
|
+
function defineLazy(object5, key, getter) {
|
|
14837
15918
|
let value = undefined;
|
|
14838
|
-
Object.defineProperty(
|
|
15919
|
+
Object.defineProperty(object5, key, {
|
|
14839
15920
|
get() {
|
|
14840
15921
|
if (value === EVALUATING) {
|
|
14841
15922
|
return;
|
|
@@ -14847,7 +15928,7 @@ function defineLazy(object4, key, getter) {
|
|
|
14847
15928
|
return value;
|
|
14848
15929
|
},
|
|
14849
15930
|
set(v) {
|
|
14850
|
-
Object.defineProperty(
|
|
15931
|
+
Object.defineProperty(object5, key, {
|
|
14851
15932
|
value: v
|
|
14852
15933
|
});
|
|
14853
15934
|
},
|
|
@@ -16032,7 +17113,7 @@ __export2(exports_regexes, {
|
|
|
16032
17113
|
idnEmail: () => idnEmail,
|
|
16033
17114
|
httpProtocol: () => httpProtocol,
|
|
16034
17115
|
html5Email: () => html5Email,
|
|
16035
|
-
hostname: () =>
|
|
17116
|
+
hostname: () => hostname2,
|
|
16036
17117
|
hex: () => hex,
|
|
16037
17118
|
guid: () => guid,
|
|
16038
17119
|
extendedDuration: () => extendedDuration,
|
|
@@ -16094,7 +17175,7 @@ var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]
|
|
|
16094
17175
|
var cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;
|
|
16095
17176
|
var base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
|
|
16096
17177
|
var base64url = /^[A-Za-z0-9_-]*$/;
|
|
16097
|
-
var
|
|
17178
|
+
var hostname2 = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
|
|
16098
17179
|
var domain = /^(?=.{1,253}$)([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/;
|
|
16099
17180
|
var httpProtocol = /^https?$/;
|
|
16100
17181
|
var e164 = /^\+[1-9]\d{6,14}$/;
|
|
@@ -16924,9 +18005,9 @@ var asciiTabOrNewline = /[\t\n\r]/g;
|
|
|
16924
18005
|
function stripTabAndNewline(value) {
|
|
16925
18006
|
return value.replace(asciiTabOrNewline, "");
|
|
16926
18007
|
}
|
|
16927
|
-
function urlHostnameOk(url,
|
|
16928
|
-
|
|
16929
|
-
return
|
|
18008
|
+
function urlHostnameOk(url, hostname3) {
|
|
18009
|
+
hostname3.lastIndex = 0;
|
|
18010
|
+
return hostname3.test(url.hostname);
|
|
16930
18011
|
}
|
|
16931
18012
|
function urlProtocolOk(url, protocol) {
|
|
16932
18013
|
protocol.lastIndex = 0;
|
|
@@ -17416,15 +18497,15 @@ var $ZodDate = /* @__PURE__ */ $constructor("$ZodDate", (inst, def) => {
|
|
|
17416
18497
|
} catch (_err) {}
|
|
17417
18498
|
}
|
|
17418
18499
|
const input = payload.value;
|
|
17419
|
-
const
|
|
17420
|
-
const isValidDate =
|
|
18500
|
+
const isDate2 = input instanceof Date;
|
|
18501
|
+
const isValidDate = isDate2 && !Number.isNaN(input.getTime());
|
|
17421
18502
|
if (isValidDate)
|
|
17422
18503
|
return payload;
|
|
17423
18504
|
payload.issues.push({
|
|
17424
18505
|
expected: "date",
|
|
17425
18506
|
code: "invalid_type",
|
|
17426
18507
|
input,
|
|
17427
|
-
...
|
|
18508
|
+
...isDate2 ? { received: "Invalid Date" } : {},
|
|
17428
18509
|
inst
|
|
17429
18510
|
});
|
|
17430
18511
|
return payload;
|
|
@@ -29176,8 +30257,8 @@ function foldObjects(members2) {
|
|
|
29176
30257
|
}
|
|
29177
30258
|
const properties = {};
|
|
29178
30259
|
const required2 = new Set;
|
|
29179
|
-
for (const
|
|
29180
|
-
for (const key in
|
|
30260
|
+
for (const object5 of objects) {
|
|
30261
|
+
for (const key in object5.properties) {
|
|
29181
30262
|
if (Object.prototype.hasOwnProperty.call(properties, key))
|
|
29182
30263
|
continue;
|
|
29183
30264
|
const parts = [];
|
|
@@ -29191,18 +30272,18 @@ function foldObjects(members2) {
|
|
|
29191
30272
|
const merged = parts.length === 1 ? parts[0] : foldObjects(parts) ?? { allOf: parts };
|
|
29192
30273
|
assignProp(properties, key, merged);
|
|
29193
30274
|
}
|
|
29194
|
-
for (const key of
|
|
30275
|
+
for (const key of object5.required ?? [])
|
|
29195
30276
|
required2.add(key);
|
|
29196
30277
|
}
|
|
29197
30278
|
const folded = { type: "object", properties };
|
|
29198
30279
|
if (required2.size)
|
|
29199
30280
|
folded.required = [...required2];
|
|
29200
|
-
if (objects.every((
|
|
30281
|
+
if (objects.every((object5) => object5.additionalProperties === false)) {
|
|
29201
30282
|
folded.additionalProperties = false;
|
|
29202
30283
|
} else {
|
|
29203
30284
|
const constraints = [];
|
|
29204
|
-
for (const
|
|
29205
|
-
const constraint = undeclaredConstraint(
|
|
30285
|
+
for (const object5 of objects) {
|
|
30286
|
+
const constraint = undeclaredConstraint(object5);
|
|
29206
30287
|
if (constraint && !constraints.some((seen) => JSON.stringify(seen) === JSON.stringify(constraint)))
|
|
29207
30288
|
constraints.push(constraint);
|
|
29208
30289
|
}
|
|
@@ -29877,8 +30958,8 @@ function rewriteKeyNames(ctx) {
|
|
|
29877
30958
|
bySchema.set(entry.schema, entry);
|
|
29878
30959
|
}
|
|
29879
30960
|
const rewrites = new Map;
|
|
29880
|
-
for (const
|
|
29881
|
-
const seen = ctx.seen.get(
|
|
30961
|
+
for (const record3 of pendingRecords.get(ctx) ?? []) {
|
|
30962
|
+
const seen = ctx.seen.get(record3);
|
|
29882
30963
|
const names = (seen?.def ?? seen?.schema)?.propertyNames;
|
|
29883
30964
|
if (!names || names === true || rewrites.has(names))
|
|
29884
30965
|
continue;
|
|
@@ -30203,7 +31284,7 @@ __export2(exports_schemas2, {
|
|
|
30203
31284
|
strictObject: () => strictObject,
|
|
30204
31285
|
set: () => set,
|
|
30205
31286
|
refine: () => refine,
|
|
30206
|
-
record: () =>
|
|
31287
|
+
record: () => record3,
|
|
30207
31288
|
readonly: () => readonly,
|
|
30208
31289
|
promise: () => promise,
|
|
30209
31290
|
preprocess: () => preprocess,
|
|
@@ -30211,7 +31292,7 @@ __export2(exports_schemas2, {
|
|
|
30211
31292
|
pipe: () => pipe,
|
|
30212
31293
|
partialRecord: () => partialRecord,
|
|
30213
31294
|
optional: () => optional,
|
|
30214
|
-
object: () =>
|
|
31295
|
+
object: () => object5,
|
|
30215
31296
|
number: () => number2,
|
|
30216
31297
|
nullish: () => nullish2,
|
|
30217
31298
|
nullable: () => nullable,
|
|
@@ -30241,7 +31322,7 @@ __export2(exports_schemas2, {
|
|
|
30241
31322
|
int: () => int,
|
|
30242
31323
|
instanceof: () => _instanceof,
|
|
30243
31324
|
httpUrl: () => httpUrl,
|
|
30244
|
-
hostname: () =>
|
|
31325
|
+
hostname: () => hostname3,
|
|
30245
31326
|
hex: () => hex2,
|
|
30246
31327
|
hash: () => hash2,
|
|
30247
31328
|
guid: () => guid2,
|
|
@@ -30943,7 +32024,7 @@ var ZodCustomStringFormat = /* @__PURE__ */ $constructor("ZodCustomStringFormat"
|
|
|
30943
32024
|
function stringFormat(format, fnOrRegex, _params = {}) {
|
|
30944
32025
|
return _stringFormat(ZodCustomStringFormat, format, fnOrRegex, _params);
|
|
30945
32026
|
}
|
|
30946
|
-
function
|
|
32027
|
+
function hostname3(_params) {
|
|
30947
32028
|
return _stringFormat(ZodCustomStringFormat, "hostname", exports_regexes.hostname, _params);
|
|
30948
32029
|
}
|
|
30949
32030
|
function hex2(_params) {
|
|
@@ -31249,7 +32330,7 @@ var ZodObject = /* @__PURE__ */ $constructor("ZodObject", (inst, def) => {
|
|
|
31249
32330
|
return exports_util.required(ZodNonOptional, this, args[0]);
|
|
31250
32331
|
}
|
|
31251
32332
|
});
|
|
31252
|
-
function
|
|
32333
|
+
function object5(shape, params) {
|
|
31253
32334
|
const def = {
|
|
31254
32335
|
type: "object",
|
|
31255
32336
|
shape: shape ?? {},
|
|
@@ -31365,7 +32446,7 @@ var ZodRecord = /* @__PURE__ */ $constructor("ZodRecord", (inst, def) => {
|
|
|
31365
32446
|
inst.keyType = def.keyType;
|
|
31366
32447
|
inst.valueType = def.valueType;
|
|
31367
32448
|
});
|
|
31368
|
-
function
|
|
32449
|
+
function record3(keyType, valueType, params) {
|
|
31369
32450
|
if (!valueType || !valueType._zod) {
|
|
31370
32451
|
return new ZodRecord({
|
|
31371
32452
|
type: "record",
|
|
@@ -31831,7 +32912,7 @@ var stringbool = (...args) => _stringbool({
|
|
|
31831
32912
|
}, ...args);
|
|
31832
32913
|
function json(params) {
|
|
31833
32914
|
const jsonSchema = lazy(() => {
|
|
31834
|
-
return union([string2(params), number2(), boolean2(), _null3(), array2(jsonSchema),
|
|
32915
|
+
return union([string2(params), number2(), boolean2(), _null3(), array2(jsonSchema), record3(string2(), jsonSchema)]);
|
|
31835
32916
|
});
|
|
31836
32917
|
return jsonSchema;
|
|
31837
32918
|
}
|
|
@@ -32622,7 +33703,7 @@ function date4(params) {
|
|
|
32622
33703
|
return _coercedDate(ZodDate, params);
|
|
32623
33704
|
}
|
|
32624
33705
|
var API_URL_ENV_VAR = SKILLS_API_URL_ENV;
|
|
32625
|
-
var MISSING_API_URL_HINT = `run: skills
|
|
33706
|
+
var MISSING_API_URL_HINT = `run: skills login, or set ${API_URL_ENV_VAR}=<your Skills instance origin>, ` + `or run: skills setup --api-url <your Skills instance origin>`;
|
|
32626
33707
|
var VENDOR_TERMS = [
|
|
32627
33708
|
"Google Gemini",
|
|
32628
33709
|
"OpenAI Sora",
|
|
@@ -33018,7 +34099,7 @@ var revocation = exports_external.object({
|
|
|
33018
34099
|
});
|
|
33019
34100
|
var page = (item) => exports_external.object({ contractVersion: exports_external.literal(1), items: exports_external.array(item).max(100), nextCursor: exports_external.string().min(1).max(2048).nullable() });
|
|
33020
34101
|
var results = { preview, draft: preview.nullable(), activate: exports_external.object({ replayed: exports_external.boolean(), consent }), list: page(consent), get: consent.nullable(), occurrences: page(occurrence), revoke: revocation };
|
|
33021
|
-
var
|
|
34102
|
+
var record5 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
33022
34103
|
var uuid8 = (v) => typeof v === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v);
|
|
33023
34104
|
var text2 = (v, max = 1024) => typeof v === "string" && !!v.trim() && v.length <= max && !/[\p{Cc}\p{Cs}\u2028\u2029]/u.test(v);
|
|
33024
34105
|
var role2 = (v) => typeof v === "string" && ["owner", "admin", "member", "viewer"].includes(v);
|
|
@@ -33033,15 +34114,15 @@ function invalid3() {
|
|
|
33033
34114
|
throw new Error(invalidWorkspaceResult);
|
|
33034
34115
|
}
|
|
33035
34116
|
function organization(v) {
|
|
33036
|
-
if (!
|
|
34117
|
+
if (!record5(v) || !uuid8(v.id) || !text2(v.slug) || !text2(v.name))
|
|
33037
34118
|
return invalid3();
|
|
33038
34119
|
return { id: v.id, slug: v.slug, name: v.name };
|
|
33039
34120
|
}
|
|
33040
34121
|
function parseWorkspaceIdentity(value, expectedUserId) {
|
|
33041
|
-
if (!
|
|
34122
|
+
if (!record5(value))
|
|
33042
34123
|
return invalid3();
|
|
33043
34124
|
const user = value.user;
|
|
33044
|
-
if (!
|
|
34125
|
+
if (!record5(user) || !uuid8(user.id) || !uuid8(user.membershipId) || !text2(user.email, 320) || !(user.displayName === null || text2(user.displayName)) || !role2(user.role))
|
|
33045
34126
|
return invalid3();
|
|
33046
34127
|
if (user.id !== expectedUserId)
|
|
33047
34128
|
throw new WorkspaceIdentityMismatchError;
|
|
@@ -33053,10 +34134,10 @@ function sessionToken(value) {
|
|
|
33053
34134
|
return value;
|
|
33054
34135
|
}
|
|
33055
34136
|
function parseWorkspaceSession(value, expected) {
|
|
33056
|
-
const
|
|
33057
|
-
if (
|
|
34137
|
+
const identity4 = parseWorkspaceIdentity(value, expected.userId);
|
|
34138
|
+
if (identity4.user.membershipId !== expected.membershipId)
|
|
33058
34139
|
throw new WorkspaceIdentityMismatchError;
|
|
33059
|
-
return { token: sessionToken(value.token), ...
|
|
34140
|
+
return { token: sessionToken(value.token), ...identity4 };
|
|
33060
34141
|
}
|
|
33061
34142
|
var SKILLS_API_KEY_REF = credentialPointerEnvKey("skills");
|
|
33062
34143
|
var quoteUnavailableMessages = Object.freeze({
|
|
@@ -33111,16 +34192,16 @@ var invalid4 = () => {
|
|
|
33111
34192
|
};
|
|
33112
34193
|
var publicationUuid = (v) => typeof v === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v);
|
|
33113
34194
|
var hash3 = (v) => typeof v === "string" && /^[a-f0-9]{64}$/.test(v);
|
|
33114
|
-
var
|
|
33115
|
-
var exact = (v, keys) =>
|
|
34195
|
+
var record9 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
34196
|
+
var exact = (v, keys) => record9(v) && Object.keys(v).sort().join(",") === keys.sort().join(",");
|
|
33116
34197
|
var date5 = (v) => typeof v === "string" && /^\d{4}-\d\d-\d\dT[0-9:.]+(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/.test(v) && Number.isFinite(Date.parse(v));
|
|
33117
|
-
var publicationSha256 = (bytes) =>
|
|
34198
|
+
var publicationSha256 = (bytes) => createHash222("sha256").update(bytes).digest("hex");
|
|
33118
34199
|
function checkedPublicationDeclaration(value) {
|
|
33119
34200
|
if (!exact(value, ["idempotencyKey", "version", "expectedCurrentVersionId", "manifestText", "archiveSha256", "archiveByteSize"]) || !publicationUuid(value.idempotencyKey) || !(value.expectedCurrentVersionId === null || publicationUuid(value.expectedCurrentVersionId)) || typeof value.version !== "string" || !value.version || value.version.length > 128 || /[\p{Cc}\p{Cs}]/u.test(value.version) || typeof value.manifestText !== "string" || Buffer.byteLength(value.manifestText) > 16384 || !hash3(value.archiveSha256) || !Number.isSafeInteger(value.archiveByteSize) || Number(value.archiveByteSize) < 1 || Number(value.archiveByteSize) > PRIVATE_PUBLICATION_MAX_BYTES)
|
|
33120
34201
|
return bad();
|
|
33121
34202
|
try {
|
|
33122
34203
|
const manifest = JSON.parse(value.manifestText);
|
|
33123
|
-
if (!
|
|
34204
|
+
if (!record9(manifest) || manifest.version !== value.version || validatePortableManifestContract(manifest, { strict: true }).length || !hash3(manifest.provenance?.content_hash))
|
|
33124
34205
|
return bad();
|
|
33125
34206
|
} catch {
|
|
33126
34207
|
return bad();
|
|
@@ -33178,7 +34259,7 @@ async function boundedJson(url2, init, token, mutation, budget2 = 15000, signal)
|
|
|
33178
34259
|
return invalid4();
|
|
33179
34260
|
}
|
|
33180
34261
|
if (!response.ok) {
|
|
33181
|
-
const code =
|
|
34262
|
+
const code = record9(body) && typeof body.code === "string" && Object.hasOwn(failures2, body.code) ? body.code : null;
|
|
33182
34263
|
if (code && failures2[code][0] === response.status)
|
|
33183
34264
|
throw new PrivatePublicationError(code, failures2[code][1], mutation && code === "PUBLICATION_UNCERTAIN", response.status);
|
|
33184
34265
|
throw new PrivatePublicationError("PUBLICATION_REQUEST_FAILED", "The publication request was refused. Inspect its status before retrying.", mutation && response.status >= 500, response.status);
|
|
@@ -33233,8 +34314,8 @@ class RemotePrivatePublicationsClient {
|
|
|
33233
34314
|
if (options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 1 || options.timeoutMs > 15000))
|
|
33234
34315
|
return bad();
|
|
33235
34316
|
const response = await boundedJson(skillsApiRequestUrl(this.apiOrigin, "/api/v1/capabilities"), {}, this.#token, false, options.timeoutMs, options.signal);
|
|
33236
|
-
const p =
|
|
33237
|
-
if (!
|
|
34317
|
+
const p = record9(response) && response.privatePublishing;
|
|
34318
|
+
if (!record9(response) || response.contractVersion !== 1 || response.apiVersion !== 1 || !exact(p, ["contractVersion", "enabled", "authentication", "maxArchiveBytes", "uploadMaxTtlSeconds", "executionEnabled"]) || p.contractVersion !== 1 || typeof p.enabled !== "boolean" || p.authentication !== "interactive-session" || p.maxArchiveBytes !== PRIVATE_PUBLICATION_MAX_BYTES || p.uploadMaxTtlSeconds !== 300 || typeof p.executionEnabled !== "boolean")
|
|
33238
34319
|
throw new PrivatePublicationError("PUBLICATION_CONTRACT_UNAVAILABLE", "This server does not support the hosted private publication contract.");
|
|
33239
34320
|
return Object.freeze({ ...p });
|
|
33240
34321
|
}
|
|
@@ -33250,7 +34331,7 @@ class RemotePrivatePublicationsClient {
|
|
|
33250
34331
|
async#view(path, method, skillId, intentId, declaration, options = {}) {
|
|
33251
34332
|
const value = await boundedJson(path, { method, ...method === "GET" ? {} : { body: JSON.stringify(declaration ?? {}) } }, this.#token, method !== "GET", options.timeoutMs, options.signal);
|
|
33252
34333
|
try {
|
|
33253
|
-
if (!
|
|
34334
|
+
if (!record9(value) || !Object.keys(value).every((k) => k === "upload" || k === "changed") || value.changed !== undefined && typeof value.changed !== "boolean")
|
|
33254
34335
|
return invalid4();
|
|
33255
34336
|
return checkedPublicationView(value.upload, skillId, intentId, declaration);
|
|
33256
34337
|
} catch (error61) {
|
|
@@ -33359,12 +34440,12 @@ class RemotePrivatePublicationsClient {
|
|
|
33359
34440
|
previous = view;
|
|
33360
34441
|
if (!["queued", "verifying"].includes(view.state) || Date.now() >= until)
|
|
33361
34442
|
return view;
|
|
33362
|
-
await new Promise((
|
|
34443
|
+
await new Promise((resolve18) => {
|
|
33363
34444
|
const timer = setTimeout(done, Math.min(1000, until - Date.now()));
|
|
33364
34445
|
function done() {
|
|
33365
34446
|
clearTimeout(timer);
|
|
33366
34447
|
options.signal?.removeEventListener("abort", done);
|
|
33367
|
-
|
|
34448
|
+
resolve18();
|
|
33368
34449
|
}
|
|
33369
34450
|
options.signal?.addEventListener("abort", done, { once: true });
|
|
33370
34451
|
});
|
|
@@ -33372,7 +34453,7 @@ class RemotePrivatePublicationsClient {
|
|
|
33372
34453
|
}
|
|
33373
34454
|
}
|
|
33374
34455
|
var uuid12 = exports_external.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/);
|
|
33375
|
-
var
|
|
34456
|
+
var sha5 = exports_external.string().regex(/^[a-f0-9]{64}$/);
|
|
33376
34457
|
var common = exports_external.object({
|
|
33377
34458
|
contractVersion: exports_external.literal(1),
|
|
33378
34459
|
apiOrigin: exports_external.string().min(1).max(4096),
|
|
@@ -33385,7 +34466,7 @@ var intentSchema = exports_external.discriminatedUnion("operation", [
|
|
|
33385
34466
|
common.extend({
|
|
33386
34467
|
operation: exports_external.literal("activate"),
|
|
33387
34468
|
draftId: uuid12,
|
|
33388
|
-
termsSha256:
|
|
34469
|
+
termsSha256: sha5,
|
|
33389
34470
|
approvalDeadline: exports_external.string().datetime(),
|
|
33390
34471
|
approval: recurringActivationSchema
|
|
33391
34472
|
}).strict(),
|
|
@@ -33393,14 +34474,14 @@ var intentSchema = exports_external.discriminatedUnion("operation", [
|
|
|
33393
34474
|
]);
|
|
33394
34475
|
var stateSchema = exports_external.object({
|
|
33395
34476
|
contractVersion: exports_external.literal(1),
|
|
33396
|
-
intentSha256:
|
|
34477
|
+
intentSha256: sha5,
|
|
33397
34478
|
phase: exports_external.enum(["prepared", "pending", "observed"]),
|
|
33398
34479
|
consentId: uuid12.nullable()
|
|
33399
34480
|
}).strict();
|
|
33400
34481
|
var uuid13 = exports_external.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/);
|
|
33401
34482
|
var context = { userId: uuid13.optional(), membershipId: uuid13.optional() };
|
|
33402
34483
|
var page2 = { limit: exports_external.number().int().min(1).max(100).optional(), cursor: exports_external.string().min(1).max(2048).optional() };
|
|
33403
|
-
var directory = exports_external.string().max(4096).refine(
|
|
34484
|
+
var directory = exports_external.string().max(4096).refine(isAbsolute18, "Use an absolute recovery directory");
|
|
33404
34485
|
var email4 = exports_external.string().trim().toLowerCase().email().max(254);
|
|
33405
34486
|
var code = exports_external.string().regex(/^\d{6}$/);
|
|
33406
34487
|
var recurringSurfaceSchemas = {
|
|
@@ -34531,7 +35612,73 @@ var MANIFEST_HASH_KEY_SEP = String.fromCharCode(0);
|
|
|
34531
35612
|
var SESSION_RECONCILIATION_PLAN_TTL_MS = 5 * 60 * 1000;
|
|
34532
35613
|
|
|
34533
35614
|
// src/lib/claude-skills-coordination.ts
|
|
34534
|
-
|
|
35615
|
+
class ClaudeSettingsRefusal extends Error {
|
|
35616
|
+
code;
|
|
35617
|
+
constructor(code2, message) {
|
|
35618
|
+
super(`${code2}: ${message}`);
|
|
35619
|
+
this.code = code2;
|
|
35620
|
+
this.name = "ClaudeSettingsRefusal";
|
|
35621
|
+
}
|
|
35622
|
+
}
|
|
35623
|
+
var SKILLS_HOOK_COMMAND = /(?:^|\s)hook user-prompt --agent (?:claude|codex|gemini)(?:\s|$)/;
|
|
35624
|
+
function claudeSkillsUsage(before, home) {
|
|
35625
|
+
const evidence = [], events2 = [];
|
|
35626
|
+
const hooks = before === null ? undefined : JSON.parse(before)?.hooks;
|
|
35627
|
+
if (hooks && typeof hooks === "object" && !Array.isArray(hooks)) {
|
|
35628
|
+
for (const [event, entries] of Object.entries(hooks)) {
|
|
35629
|
+
if (!Array.isArray(entries))
|
|
35630
|
+
continue;
|
|
35631
|
+
const commands = entries.flatMap((entry) => [entry?.command, ...Array.isArray(entry?.hooks) ? entry.hooks.map((hook) => hook?.command) : []]);
|
|
35632
|
+
if (commands.some((command) => typeof command === "string" && SKILLS_HOOK_COMMAND.test(command)))
|
|
35633
|
+
events2.push(event);
|
|
35634
|
+
}
|
|
35635
|
+
}
|
|
35636
|
+
if (events2.length)
|
|
35637
|
+
evidence.push(`Skills-managed hook entries in ${events2.join(", ")}`);
|
|
35638
|
+
const bridge = join10(home, ".claude", "skills", "skills-cli", "SKILL.md");
|
|
35639
|
+
let bridgePresent;
|
|
35640
|
+
try {
|
|
35641
|
+
bridgePresent = lstatSync7(bridge, { throwIfNoEntry: false }) !== undefined;
|
|
35642
|
+
} catch (error61) {
|
|
35643
|
+
bridgePresent = !["ENOENT", "ENOTDIR"].includes(error61.code ?? "");
|
|
35644
|
+
}
|
|
35645
|
+
if (bridgePresent)
|
|
35646
|
+
evidence.push(`the Skills bridge ${bridge}`);
|
|
35647
|
+
return evidence;
|
|
35648
|
+
}
|
|
35649
|
+
function skillsPolicyLocations(dataDir3) {
|
|
35650
|
+
const state = (path) => existsSync11(path) ? "present" : "absent";
|
|
35651
|
+
if (dataDir3 !== undefined)
|
|
35652
|
+
return [`${join10(resolve7(dataDir3), "agent-policy.json")} (explicit data directory, absent)`];
|
|
35653
|
+
const env = process.env, home = env.HOME || env.USERPROFILE || homedir6() || "/tmp", searched = [];
|
|
35654
|
+
let selected = false;
|
|
35655
|
+
for (const key of ["HASNA_SKILLS_DIR", "HASNA_SKILLS_HOME", "SKILLS_HOME"]) {
|
|
35656
|
+
const value = env[key]?.trim();
|
|
35657
|
+
if (selected)
|
|
35658
|
+
searched.push(`$${key} (not consulted: an earlier override is set)`);
|
|
35659
|
+
else if (!value)
|
|
35660
|
+
searched.push(`$${key} (unset)`);
|
|
35661
|
+
else {
|
|
35662
|
+
selected = true;
|
|
35663
|
+
searched.push(`$${key} -> ${join10(resolve7(value), "agent-policy.json")} (selected, absent)`);
|
|
35664
|
+
}
|
|
35665
|
+
}
|
|
35666
|
+
const override = env.HASNA_DATA_HOME;
|
|
35667
|
+
const base = typeof override === "string" && override.length > 0 ? override : process.platform === "darwin" ? join10(home, "Library", "Application Support", "Hasna") : join10(home, ".local", "share", "hasna");
|
|
35668
|
+
const xdg = join10(base, "skills"), xdgPolicy = join10(xdg, "agent-policy.json");
|
|
35669
|
+
const adopted = typeof override === "string" && override.trim().length > 0 || existsSync11(join10(xdg, "server.db")) || existsSync11(join10(xdg, "config.json"));
|
|
35670
|
+
if (selected)
|
|
35671
|
+
searched.push(`${xdgPolicy} (not consulted, ${state(xdgPolicy)})`);
|
|
35672
|
+
else if (adopted) {
|
|
35673
|
+
selected = true;
|
|
35674
|
+
searched.push(`${xdgPolicy} (selected, absent)`);
|
|
35675
|
+
} else
|
|
35676
|
+
searched.push(`${xdgPolicy} (not selected: the directory holds no server.db or config.json and HASNA_DATA_HOME is unset; ${state(xdgPolicy)})`);
|
|
35677
|
+
const legacy = join10(home, ".hasna", "skills", "agent-policy.json");
|
|
35678
|
+
searched.push(selected ? `${legacy} (not consulted, ${state(legacy)})` : `${legacy} (selected, absent)`);
|
|
35679
|
+
return searched;
|
|
35680
|
+
}
|
|
35681
|
+
function coordinateClaudeSettings(before, replacement, options, beforeWrite) {
|
|
34535
35682
|
const prior = JSON.parse(before ?? "{}"), next = JSON.parse(replacement);
|
|
34536
35683
|
const events2 = new Set([...Object.keys(prior.hooks ?? {}), ...Object.keys(next.hooks ?? {})]);
|
|
34537
35684
|
const changed = [...events2].filter((event) => JSON.stringify(prior.hooks?.[event]) !== JSON.stringify(next.hooks?.[event]));
|
|
@@ -34542,15 +35689,20 @@ function coordinateClaudeSettings(before, replacement, options) {
|
|
|
34542
35689
|
expectedSettingsSha256: before === null ? "absent" : createHash6("sha256").update(before).digest("hex"),
|
|
34543
35690
|
replacement
|
|
34544
35691
|
});
|
|
34545
|
-
if (!plan)
|
|
35692
|
+
if (!plan) {
|
|
35693
|
+
const usage = claudeSkillsUsage(before, options.home);
|
|
35694
|
+
if (usage.length)
|
|
35695
|
+
throw new ClaudeSettingsRefusal("skills_policy_unresolved", `Skills manages ${join10(options.home, ".claude", "settings.json")} (${usage.join("; ")}), but no Skills agent policy resolved, ` + "so Hooks cannot coordinate this change and wrote nothing. Locations searched, in Skills resolution order: " + skillsPolicyLocations(options.dataDir).map((location, index) => `${index + 1}) ${location}`).join("; ") + ". Run hooks with the environment that selects the Skills data directory (for example HASNA_SKILLS_DIR), " + "or repair the integration with `skills hook install`.");
|
|
34546
35696
|
return false;
|
|
35697
|
+
}
|
|
35698
|
+
beforeWrite?.();
|
|
34547
35699
|
applyAgentIntegration(plan);
|
|
34548
35700
|
return true;
|
|
34549
35701
|
}
|
|
34550
35702
|
|
|
34551
35703
|
// src/lib/installer.ts
|
|
34552
35704
|
var __dirname2 = dirname7(fileURLToPath2(import.meta.url));
|
|
34553
|
-
var HOOKS_DIR =
|
|
35705
|
+
var HOOKS_DIR = existsSync12(join11(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join11(__dirname2, "..", "..", "hooks") : join11(__dirname2, "..", "hooks");
|
|
34554
35706
|
function normalizeHookName(name) {
|
|
34555
35707
|
return name.startsWith("hook-") ? name : `hook-${name}`;
|
|
34556
35708
|
}
|
|
@@ -34620,9 +35772,9 @@ function getTargetSettingsDir(target) {
|
|
|
34620
35772
|
}
|
|
34621
35773
|
function getGlobalSettingsPathOverride(target) {
|
|
34622
35774
|
if (target === "codex")
|
|
34623
|
-
return process.env.HASNA_HOOKS_CODEX_SETTINGS_PATH ?? (process.env.CODEX_HOME ?
|
|
35775
|
+
return process.env.HASNA_HOOKS_CODEX_SETTINGS_PATH ?? (process.env.CODEX_HOME ? resolve16(process.env.CODEX_HOME, "hooks.json") : undefined);
|
|
34624
35776
|
if (target === "claude")
|
|
34625
|
-
return process.env.HASNA_HOOKS_CLAUDE_SETTINGS_PATH ?? (process.env.CLAUDE_CONFIG_DIR ?
|
|
35777
|
+
return process.env.HASNA_HOOKS_CLAUDE_SETTINGS_PATH ?? (process.env.CLAUDE_CONFIG_DIR ? resolve16(process.env.CLAUDE_CONFIG_DIR, "settings.json") : undefined);
|
|
34626
35778
|
if (target === "gemini")
|
|
34627
35779
|
return process.env.HASNA_HOOKS_GEMINI_SETTINGS_PATH;
|
|
34628
35780
|
return;
|
|
@@ -34638,71 +35790,95 @@ function getSettingsPath(scope = "global", target = "claude", codewithConfigPath
|
|
|
34638
35790
|
return globalOverride;
|
|
34639
35791
|
const dir = getTargetSettingsDir(target);
|
|
34640
35792
|
if (target === "codex")
|
|
34641
|
-
return
|
|
35793
|
+
return join11(scope === "project" ? process.cwd() : homedir7(), dir, "hooks.json");
|
|
34642
35794
|
if (scope === "project") {
|
|
34643
|
-
return target === "codewith" ?
|
|
35795
|
+
return target === "codewith" ? join11(process.cwd(), dir, "config.toml") : join11(process.cwd(), dir, "settings.json");
|
|
34644
35796
|
}
|
|
34645
|
-
return target === "codewith" ?
|
|
35797
|
+
return target === "codewith" ? join11(homedir7(), dir, "config.toml") : join11(homedir7(), dir, "settings.json");
|
|
34646
35798
|
}
|
|
34647
35799
|
function getHookPath(name) {
|
|
34648
35800
|
const shortName = shortHookName(name);
|
|
34649
35801
|
const resolved = resolveHookDir(shortName);
|
|
34650
35802
|
if (resolved)
|
|
34651
35803
|
return resolved;
|
|
34652
|
-
return
|
|
35804
|
+
return join11(HOOKS_DIR, normalizeHookName(shortName));
|
|
34653
35805
|
}
|
|
34654
35806
|
function hookExists(name) {
|
|
34655
|
-
return
|
|
35807
|
+
return existsSync12(getHookPath(name));
|
|
34656
35808
|
}
|
|
34657
35809
|
var settingsPreimages = new WeakMap;
|
|
34658
|
-
|
|
35810
|
+
var unreadableSettings = new WeakMap;
|
|
35811
|
+
function readSettings(scope = "global", target = "claude") {
|
|
34659
35812
|
const path = getSettingsPath(scope, target);
|
|
34660
35813
|
if (target === "codex")
|
|
34661
35814
|
return readCodexSettings(path);
|
|
35815
|
+
let reason = "cannot be read";
|
|
34662
35816
|
try {
|
|
34663
|
-
if (
|
|
34664
|
-
const text3 = readFileSync7(path, "utf-8")
|
|
35817
|
+
if (existsSync12(path)) {
|
|
35818
|
+
const text3 = readFileSync7(path, "utf-8");
|
|
35819
|
+
reason = "is not valid JSON";
|
|
35820
|
+
const value = JSON.parse(text3);
|
|
35821
|
+
reason = "does not hold a JSON object";
|
|
35822
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
35823
|
+
throw new TypeError(reason);
|
|
34665
35824
|
settingsPreimages.set(value, { path, text: text3 });
|
|
34666
35825
|
return value;
|
|
34667
35826
|
}
|
|
34668
35827
|
} catch (error61) {
|
|
35828
|
+
const code2 = error61?.code;
|
|
35829
|
+
if (reason === "cannot be read" && typeof code2 === "string")
|
|
35830
|
+
reason += ` (${code2})`;
|
|
34669
35831
|
console.warn("[hooks] Cannot read hook settings; repair the selected file before installation.");
|
|
34670
35832
|
}
|
|
34671
35833
|
const empty = {};
|
|
34672
|
-
if (!
|
|
35834
|
+
if (!existsSync12(path))
|
|
34673
35835
|
settingsPreimages.set(empty, { path, text: null });
|
|
35836
|
+
else
|
|
35837
|
+
unreadableSettings.set(empty, { path, reason });
|
|
34674
35838
|
return empty;
|
|
34675
35839
|
}
|
|
35840
|
+
function unreadableSettingsRefusal(settings, path) {
|
|
35841
|
+
const captured = settingsPreimages.get(settings);
|
|
35842
|
+
if (captured?.path === path && captured.text !== null || !existsSync12(path))
|
|
35843
|
+
return;
|
|
35844
|
+
const failed = unreadableSettings.get(settings);
|
|
35845
|
+
const reason = captured?.path === path ? "was created after Hooks read the settings" : failed?.path === path ? failed.reason : "was not read as a settings object";
|
|
35846
|
+
return new ClaudeSettingsRefusal("settings_unreadable", `${path} ${reason}, so Hooks wrote nothing. Repair the file or move it aside, then retry.`);
|
|
35847
|
+
}
|
|
34676
35848
|
function settingsIdentity(path) {
|
|
34677
|
-
if (
|
|
34678
|
-
return
|
|
34679
|
-
return
|
|
35849
|
+
if (existsSync12(path))
|
|
35850
|
+
return realpathSync8(path);
|
|
35851
|
+
return existsSync12(dirname7(path)) ? join11(realpathSync8(dirname7(path)), basename3(path)) : resolve16(path);
|
|
34680
35852
|
}
|
|
34681
|
-
function writeSettings(settings, scope = "global", target = "claude", expectedSettingsSha256) {
|
|
35853
|
+
function writeSettings(settings, scope = "global", target = "claude", expectedSettingsSha256, beforeWrite) {
|
|
34682
35854
|
const path = getSettingsPath(scope, target);
|
|
34683
35855
|
if (target === "codex")
|
|
34684
|
-
return writeCodexSettings(path, settings, expectedSettingsSha256);
|
|
35856
|
+
return writeCodexSettings(path, settings, expectedSettingsSha256, beforeWrite);
|
|
35857
|
+
const refusal3 = unreadableSettingsRefusal(settings, path);
|
|
35858
|
+
if (refusal3)
|
|
35859
|
+
throw refusal3;
|
|
34685
35860
|
const captured = settingsPreimages.get(settings);
|
|
34686
35861
|
if (expectedSettingsSha256 !== undefined) {
|
|
34687
35862
|
if (!captured || captured.path !== path)
|
|
34688
35863
|
throw new Error("Hook settings require a valid captured preimage.");
|
|
34689
35864
|
assertSettingsPreimage(captured.text, expectedSettingsSha256);
|
|
34690
35865
|
}
|
|
34691
|
-
if (target === "claude" && settingsIdentity(path) === settingsIdentity(
|
|
35866
|
+
if (target === "claude" && settingsIdentity(path) === settingsIdentity(join11(homedir7(), ".claude/settings.json"))) {
|
|
34692
35867
|
const before = settingsPreimages.get(settings);
|
|
34693
35868
|
if (!before || before.path !== path)
|
|
34694
35869
|
throw new Error("Claude settings require a valid captured preimage before installation.");
|
|
34695
|
-
const current2 =
|
|
35870
|
+
const current2 = existsSync12(path) ? readFileSync7(path, "utf8") : null;
|
|
34696
35871
|
if (current2 !== before.text)
|
|
34697
35872
|
throw new Error("Claude settings changed during installation; no settings were written.");
|
|
34698
35873
|
if (coordinateClaudeSettings(before.text, JSON.stringify(settings, null, 2) + `
|
|
34699
|
-
`, { home:
|
|
35874
|
+
`, { home: homedir7() }, beforeWrite))
|
|
34700
35875
|
return;
|
|
34701
35876
|
}
|
|
34702
35877
|
if (expectedSettingsSha256 !== undefined)
|
|
34703
|
-
return writeHookSettingsPreimage(path, settings, captured.text);
|
|
35878
|
+
return writeHookSettingsPreimage(path, settings, captured.text, beforeWrite);
|
|
35879
|
+
beforeWrite?.();
|
|
34704
35880
|
const dir = dirname7(path);
|
|
34705
|
-
if (!
|
|
35881
|
+
if (!existsSync12(dir)) {
|
|
34706
35882
|
mkdirSync6(dir, { recursive: true });
|
|
34707
35883
|
}
|
|
34708
35884
|
writeFileSync5(path, JSON.stringify(settings, null, 2) + `
|
|
@@ -34801,7 +35977,7 @@ function buildCodewithTomlFragment(name, profile, mementos) {
|
|
|
34801
35977
|
function readCodewithConfig(scope, configPath) {
|
|
34802
35978
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
34803
35979
|
try {
|
|
34804
|
-
return
|
|
35980
|
+
return existsSync12(path) ? readFileSync7(path, "utf-8") : "";
|
|
34805
35981
|
} catch {
|
|
34806
35982
|
return "";
|
|
34807
35983
|
}
|
|
@@ -34809,9 +35985,9 @@ function readCodewithConfig(scope, configPath) {
|
|
|
34809
35985
|
function appendCodewithFragment(fragment, scope, configPath) {
|
|
34810
35986
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
34811
35987
|
const dir = dirname7(path);
|
|
34812
|
-
if (!
|
|
35988
|
+
if (!existsSync12(dir))
|
|
34813
35989
|
mkdirSync6(dir, { recursive: true });
|
|
34814
|
-
const existing =
|
|
35990
|
+
const existing = existsSync12(path) ? readFileSync7(path, "utf-8") : "";
|
|
34815
35991
|
const sep3 = existing.trim() ? `
|
|
34816
35992
|
|
|
34817
35993
|
` : "";
|
|
@@ -34858,7 +36034,7 @@ function detectRewriteConflict(name, scope, target, lookup = getHook) {
|
|
|
34858
36034
|
}
|
|
34859
36035
|
return;
|
|
34860
36036
|
}
|
|
34861
|
-
function installForTarget(name, scope, overwrite, target, profile, codewithMode = "fragment", codewithConfigPath, mementos, expectedSettingsSha256) {
|
|
36037
|
+
function installForTarget(name, scope, overwrite, target, profile, codewithMode = "fragment", codewithConfigPath, mementos, expectedSettingsSha256, beforeWrite) {
|
|
34862
36038
|
const shortName = shortHookName(name);
|
|
34863
36039
|
if (isNativeSafetyName(shortName) && (profile || target === "gemini" || target === "codewith" && codewithMode === "write")) {
|
|
34864
36040
|
return { hook: shortName, success: false, error: "Bundled native safety guards support Claude/Codex and managed Codewith fragments, without registry profiles.", target };
|
|
@@ -34925,7 +36101,7 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
34925
36101
|
if (rewriteConflict) {
|
|
34926
36102
|
return { hook: shortName, success: false, error: `Refused: ${rewriteConflict}`, scope, target };
|
|
34927
36103
|
}
|
|
34928
|
-
registerHook(shortName, scope, target, profile, mementos, (settings, selectedScope, selectedTarget) => writeSettings(settings, selectedScope, selectedTarget, expectedSettingsSha256));
|
|
36104
|
+
registerHook(shortName, scope, target, profile, mementos, (settings, selectedScope, selectedTarget) => writeSettings(settings, selectedScope, selectedTarget, expectedSettingsSha256, beforeWrite));
|
|
34929
36105
|
return {
|
|
34930
36106
|
hook: shortName,
|
|
34931
36107
|
success: true,
|
|
@@ -34939,12 +36115,13 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
34939
36115
|
hook: shortName,
|
|
34940
36116
|
success: false,
|
|
34941
36117
|
error: error61 instanceof Error ? error61.message : "Unknown error",
|
|
34942
|
-
target
|
|
36118
|
+
target,
|
|
36119
|
+
...error61 instanceof ClaudeSettingsRefusal ? { code: error61.code } : {}
|
|
34943
36120
|
};
|
|
34944
36121
|
}
|
|
34945
36122
|
}
|
|
34946
36123
|
function installHook(name, options = {}) {
|
|
34947
|
-
const { scope = "global", overwrite = false, target = "claude", profile, codewithMode = "fragment", codewithConfigPath, mementos, expectedSettingsSha256 } = options;
|
|
36124
|
+
const { scope = "global", overwrite = false, target = "claude", profile, codewithMode = "fragment", codewithConfigPath, mementos, expectedSettingsSha256, beforeWrite } = options;
|
|
34948
36125
|
if (expectedSettingsSha256 !== undefined && (!/^(?:absent|[a-f0-9]{64})$/.test(expectedSettingsSha256) || !isNativeSafetyName(shortHookName(name)) || target !== "codex" && target !== "claude")) {
|
|
34949
36126
|
return { hook: name, success: false, target, error: "An exact settings predecessor requires a Claude or Codex native safety guard." };
|
|
34950
36127
|
}
|
|
@@ -34988,7 +36165,7 @@ function installHook(name, options = {}) {
|
|
|
34988
36165
|
}
|
|
34989
36166
|
return { ...claudeResult, target: "all" };
|
|
34990
36167
|
}
|
|
34991
|
-
return installForTarget(name, scope, overwrite, target, profile, codewithMode, codewithConfigPath, mementos, expectedSettingsSha256);
|
|
36168
|
+
return installForTarget(name, scope, overwrite, target, profile, codewithMode, codewithConfigPath, mementos, expectedSettingsSha256, beforeWrite);
|
|
34992
36169
|
}
|
|
34993
36170
|
function registerHook(name, scope = "global", target = "claude", profile, mementos, write = writeSettings) {
|
|
34994
36171
|
const readiness = name === "trash-readiness";
|
|
@@ -35019,7 +36196,7 @@ function registerHook(name, scope = "global", target = "claude", profile, mement
|
|
|
35019
36196
|
throw new Error(`Hook '${name}' has no installable events for target '${target}'`);
|
|
35020
36197
|
}
|
|
35021
36198
|
const matcher = matcherFor(meta3, target);
|
|
35022
|
-
const settings =
|
|
36199
|
+
const settings = readSettings(scope, target);
|
|
35023
36200
|
if (!settings.hooks)
|
|
35024
36201
|
settings.hooks = {};
|
|
35025
36202
|
if (isNativeSafetyName(name) || readiness) {
|
|
@@ -35096,7 +36273,7 @@ function removeHookFromAllEvents(settings, name) {
|
|
|
35096
36273
|
}
|
|
35097
36274
|
}
|
|
35098
36275
|
function unregisterHook(name, scope = "global", target = "claude") {
|
|
35099
|
-
const settings =
|
|
36276
|
+
const settings = readSettings(scope, target);
|
|
35100
36277
|
if (!settings.hooks)
|
|
35101
36278
|
return;
|
|
35102
36279
|
removeHookFromAllEvents(settings, name);
|
|
@@ -35126,7 +36303,7 @@ function getRegisteredHooksForTarget(scope = "global", target = "claude") {
|
|
|
35126
36303
|
}
|
|
35127
36304
|
return [...new Set(registered2)];
|
|
35128
36305
|
}
|
|
35129
|
-
const settings =
|
|
36306
|
+
const settings = readSettings(scope, target);
|
|
35130
36307
|
if (!settings.hooks)
|
|
35131
36308
|
return [];
|
|
35132
36309
|
const registered = [];
|
|
@@ -35168,12 +36345,12 @@ function removeHookForTarget(name, scope, target) {
|
|
|
35168
36345
|
|
|
35169
36346
|
// src/lib/profiles.ts
|
|
35170
36347
|
init_app_home();
|
|
35171
|
-
import { existsSync as
|
|
35172
|
-
import { join as
|
|
36348
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync8, readdirSync as readdirSync9, rmSync, cpSync as cpSync2 } from "fs";
|
|
36349
|
+
import { join as join12 } from "path";
|
|
35173
36350
|
function getProfilesDir() {
|
|
35174
|
-
const newDir =
|
|
35175
|
-
const oldDir =
|
|
35176
|
-
if (!
|
|
36351
|
+
const newDir = join12(getEffectiveDataRoot(), "profiles");
|
|
36352
|
+
const oldDir = join12(getHomeDir(), ".hooks", "profiles");
|
|
36353
|
+
if (!existsSync13(newDir) && existsSync13(oldDir)) {
|
|
35177
36354
|
mkdirSync9(newDir, { recursive: true });
|
|
35178
36355
|
cpSync2(oldDir, newDir, { recursive: true });
|
|
35179
36356
|
}
|
|
@@ -35181,12 +36358,12 @@ function getProfilesDir() {
|
|
|
35181
36358
|
}
|
|
35182
36359
|
function ensureProfilesDir() {
|
|
35183
36360
|
const dir = getProfilesDir();
|
|
35184
|
-
if (!
|
|
36361
|
+
if (!existsSync13(dir)) {
|
|
35185
36362
|
mkdirSync9(dir, { recursive: true });
|
|
35186
36363
|
}
|
|
35187
36364
|
}
|
|
35188
36365
|
function profilePath(id) {
|
|
35189
|
-
return
|
|
36366
|
+
return join12(getProfilesDir(), `${id}.json`);
|
|
35190
36367
|
}
|
|
35191
36368
|
function shortUuid() {
|
|
35192
36369
|
return crypto.randomUUID().slice(0, 8);
|
|
@@ -35212,7 +36389,7 @@ function createProfile(input2) {
|
|
|
35212
36389
|
function getProfile(id) {
|
|
35213
36390
|
const path = profilePath(id);
|
|
35214
36391
|
try {
|
|
35215
|
-
if (!
|
|
36392
|
+
if (!existsSync13(path))
|
|
35216
36393
|
return null;
|
|
35217
36394
|
return JSON.parse(readFileSync8(path, "utf-8"));
|
|
35218
36395
|
} catch {
|
|
@@ -35221,14 +36398,14 @@ function getProfile(id) {
|
|
|
35221
36398
|
}
|
|
35222
36399
|
function listProfiles() {
|
|
35223
36400
|
const dir = getProfilesDir();
|
|
35224
|
-
if (!
|
|
36401
|
+
if (!existsSync13(dir))
|
|
35225
36402
|
return [];
|
|
35226
36403
|
try {
|
|
35227
|
-
const files =
|
|
36404
|
+
const files = readdirSync9(dir).filter((f) => f.endsWith(".json"));
|
|
35228
36405
|
const profiles = [];
|
|
35229
36406
|
for (const file2 of files) {
|
|
35230
36407
|
try {
|
|
35231
|
-
const content = readFileSync8(
|
|
36408
|
+
const content = readFileSync8(join12(dir, file2), "utf-8");
|
|
35232
36409
|
profiles.push(JSON.parse(content));
|
|
35233
36410
|
} catch {}
|
|
35234
36411
|
}
|
|
@@ -35763,12 +36940,12 @@ function coerceForSqlite(value) {
|
|
|
35763
36940
|
return String(value);
|
|
35764
36941
|
}
|
|
35765
36942
|
// src/mcp/server.ts
|
|
35766
|
-
var __dirname3 =
|
|
36943
|
+
var __dirname3 = dirname20(fileURLToPath3(import.meta.url));
|
|
35767
36944
|
var pkg = { name: "@hasna/hooks", version: "0.0.0" };
|
|
35768
36945
|
try {
|
|
35769
36946
|
for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
|
|
35770
|
-
const p =
|
|
35771
|
-
if (
|
|
36947
|
+
const p = join18(__dirname3, rel);
|
|
36948
|
+
if (existsSync15(p)) {
|
|
35772
36949
|
pkg = JSON.parse(readFileSync9(p, "utf-8"));
|
|
35773
36950
|
break;
|
|
35774
36951
|
}
|
|
@@ -35778,12 +36955,12 @@ async function runVerifiedHook(name, stdin, timeoutMs) {
|
|
|
35778
36955
|
const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
|
|
35779
36956
|
const resolved = resolveHook2(name);
|
|
35780
36957
|
const hookScript = resolveScriptPath2(name);
|
|
35781
|
-
if (!resolved || !hookScript || !
|
|
36958
|
+
if (!resolved || !hookScript || !existsSync15(hookScript)) {
|
|
35782
36959
|
throw new Error(`Hook '${name}' not found`);
|
|
35783
36960
|
}
|
|
35784
36961
|
const content = readFileSync9(hookScript);
|
|
35785
|
-
const
|
|
35786
|
-
const check2 = checkScriptHash(name,
|
|
36962
|
+
const sha6 = sha256Of(content);
|
|
36963
|
+
const check2 = checkScriptHash(name, sha6);
|
|
35787
36964
|
if (!check2.ok) {
|
|
35788
36965
|
throw new Error(`Hook '${name}' script changed since it was trusted (sha256 ${check2.expected} != ${check2.actual}). Run 'hooks trust ${name}' to trust the new content.`);
|
|
35789
36966
|
}
|
|
@@ -35803,7 +36980,7 @@ async function runVerifiedHook(name, stdin, timeoutMs) {
|
|
|
35803
36980
|
hookName: name,
|
|
35804
36981
|
eventType: resolveEventType2(inputJson.hook_event_name, resolved.events[0] ?? "PostToolUse"),
|
|
35805
36982
|
version: resolved.version,
|
|
35806
|
-
sha256:
|
|
36983
|
+
sha256: sha6,
|
|
35807
36984
|
sessionId: typeof inputJson.session_id === "string" ? inputJson.session_id : null,
|
|
35808
36985
|
toolName: typeof inputJson.tool_name === "string" ? inputJson.tool_name : null,
|
|
35809
36986
|
toolInput: inputJson.tool_input,
|
|
@@ -35833,7 +37010,7 @@ async function runVerifiedHook(name, stdin, timeoutMs) {
|
|
|
35833
37010
|
hookName: name,
|
|
35834
37011
|
eventType: resolveEventType2(inputJson.hook_event_name, resolved.events[0] ?? "PostToolUse"),
|
|
35835
37012
|
version: resolved.version,
|
|
35836
|
-
sha256:
|
|
37013
|
+
sha256: sha6,
|
|
35837
37014
|
sessionId: typeof inputJson.session_id === "string" ? inputJson.session_id : null,
|
|
35838
37015
|
toolName: typeof inputJson.tool_name === "string" ? inputJson.tool_name : null,
|
|
35839
37016
|
toolInput: inputJson.tool_input,
|
|
@@ -35856,6 +37033,12 @@ function formatInstallResults(results2, extra) {
|
|
|
35856
37033
|
}]
|
|
35857
37034
|
};
|
|
35858
37035
|
}
|
|
37036
|
+
function settingsChangeError(hook, scope, error61, code2) {
|
|
37037
|
+
return {
|
|
37038
|
+
content: [{ type: "text", text: JSON.stringify({ hook, scope, error: error61, ...code2 ? { code: code2 } : {} }) }],
|
|
37039
|
+
isError: true
|
|
37040
|
+
};
|
|
37041
|
+
}
|
|
35859
37042
|
function truncateText(value, max = 160) {
|
|
35860
37043
|
const text3 = String(value ?? "").replace(/\s+/g, " ").trim();
|
|
35861
37044
|
if (text3.length <= max)
|
|
@@ -35987,7 +37170,7 @@ function createHooksServer() {
|
|
|
35987
37170
|
const settingsPath = getSettingsPath(scope);
|
|
35988
37171
|
const issues = [];
|
|
35989
37172
|
const healthy = [];
|
|
35990
|
-
const settingsExist =
|
|
37173
|
+
const settingsExist = existsSync15(settingsPath);
|
|
35991
37174
|
if (!settingsExist) {
|
|
35992
37175
|
issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
|
|
35993
37176
|
}
|
|
@@ -35997,7 +37180,7 @@ function createHooksServer() {
|
|
|
35997
37180
|
const meta3 = custom2 ? resolveHookMeta(name) : getHook(name);
|
|
35998
37181
|
let hookHealthy = true;
|
|
35999
37182
|
if (custom2) {
|
|
36000
|
-
if (!
|
|
37183
|
+
if (!existsSync15(custom2.scriptPath)) {
|
|
36001
37184
|
issues.push({ hook: name, issue: `Custom hook script missing: ${custom2.scriptPath}`, severity: "error" });
|
|
36002
37185
|
hookHealthy = false;
|
|
36003
37186
|
}
|
|
@@ -36007,7 +37190,7 @@ function createHooksServer() {
|
|
|
36007
37190
|
continue;
|
|
36008
37191
|
}
|
|
36009
37192
|
const hookDir = getHookPath(name);
|
|
36010
|
-
if (!
|
|
37193
|
+
if (!existsSync15(join18(hookDir, "src", "hook.ts"))) {
|
|
36011
37194
|
issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
|
|
36012
37195
|
hookHealthy = false;
|
|
36013
37196
|
}
|
|
@@ -36051,9 +37234,9 @@ function createHooksServer() {
|
|
|
36051
37234
|
return { content: [{ type: "text", text: JSON.stringify({ error: `Hook '${name}' not found` }) }] };
|
|
36052
37235
|
}
|
|
36053
37236
|
const hookPath = getHookPath(name);
|
|
36054
|
-
const readmePath =
|
|
37237
|
+
const readmePath = join18(hookPath, "README.md");
|
|
36055
37238
|
let readme = "";
|
|
36056
|
-
if (
|
|
37239
|
+
if (existsSync15(readmePath)) {
|
|
36057
37240
|
readme = readFileSync9(readmePath, "utf-8");
|
|
36058
37241
|
}
|
|
36059
37242
|
if (verbose) {
|
|
@@ -36237,7 +37420,7 @@ function createHooksServer() {
|
|
|
36237
37420
|
const ctx = {
|
|
36238
37421
|
scope,
|
|
36239
37422
|
settings_path: settingsPath,
|
|
36240
|
-
settings_exists:
|
|
37423
|
+
settings_exists: existsSync15(settingsPath),
|
|
36241
37424
|
registered_hooks: hooks,
|
|
36242
37425
|
hook_count: hooks.length,
|
|
36243
37426
|
healthy,
|
|
@@ -36376,48 +37559,31 @@ function createHooksServer() {
|
|
|
36376
37559
|
}));
|
|
36377
37560
|
return { content: [{ type: "text", text: JSON.stringify({ results: results2, count: results2.length }) }] };
|
|
36378
37561
|
});
|
|
36379
|
-
defineTool("hooks_disable", "
|
|
37562
|
+
defineTool("hooks_disable", "Disable a registered hook: remove its Claude registration through the same coordinated settings writer as hooks_remove. The hook stays installed (trust pin, custom manifest), so hooks_enable can register it again with default options, unless it is outside the installable registry (trash-readiness, stale registrations). For trash-guard and workspace-repos-guard this removes their protection until re-enabled. Refusals are errors with a code, for example skills_policy_unresolved; nothing is written then.", {
|
|
36380
37563
|
name: z3.string().describe("Hook name to disable"),
|
|
36381
37564
|
scope: z3.enum(["global", "project"]).default("global").describe("Scope")
|
|
36382
37565
|
}, async ({ name, scope }) => {
|
|
36383
|
-
|
|
36384
|
-
let settings = {};
|
|
37566
|
+
let changed;
|
|
36385
37567
|
try {
|
|
36386
|
-
|
|
36387
|
-
|
|
36388
|
-
|
|
36389
|
-
|
|
36390
|
-
|
|
36391
|
-
const disabled = settings.hooks.__disabled ?? [];
|
|
36392
|
-
if (!disabled.includes(name))
|
|
36393
|
-
disabled.push(name);
|
|
36394
|
-
settings.hooks.__disabled = disabled;
|
|
36395
|
-
const { writeFileSync: writeFileSync6, mkdirSync: mkdirSync7 } = await import("fs");
|
|
36396
|
-
const { dirname: dirname8 } = await import("path");
|
|
36397
|
-
mkdirSync7(dirname8(settingsPath), { recursive: true });
|
|
36398
|
-
writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
36399
|
-
`);
|
|
36400
|
-
return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, scope }) }] };
|
|
37568
|
+
changed = removeHook(name, scope, "claude");
|
|
37569
|
+
} catch (error61) {
|
|
37570
|
+
return settingsChangeError(name, scope, error61 instanceof Error ? error61.message : String(error61), error61 instanceof ClaudeSettingsRefusal ? error61.code : undefined);
|
|
37571
|
+
}
|
|
37572
|
+
return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, changed, scope }) }] };
|
|
36401
37573
|
});
|
|
36402
|
-
defineTool("hooks_enable", "
|
|
37574
|
+
defineTool("hooks_enable", "Enable a hook: register it in Claude settings, as hooks_install does, unless it is already registered. Writes go through the coordinated settings writer; refusals are errors with a code, for example skills_policy_unresolved or settings_unreadable, and nothing is written then.", {
|
|
36403
37575
|
name: z3.string().describe("Hook name to enable"),
|
|
36404
|
-
scope: z3.enum(["global", "project"]).default("global").describe("Scope")
|
|
36405
|
-
|
|
36406
|
-
|
|
36407
|
-
|
|
36408
|
-
|
|
36409
|
-
|
|
36410
|
-
|
|
36411
|
-
|
|
36412
|
-
if (
|
|
36413
|
-
|
|
36414
|
-
|
|
36415
|
-
delete settings.hooks.__disabled;
|
|
36416
|
-
const { writeFileSync: writeFileSync6 } = await import("fs");
|
|
36417
|
-
writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
36418
|
-
`);
|
|
36419
|
-
}
|
|
36420
|
-
return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: false, scope }) }] };
|
|
37576
|
+
scope: z3.enum(["global", "project"]).default("global").describe("Scope"),
|
|
37577
|
+
profile: z3.string().optional().describe("Agent profile ID to scope the registration to, as in hooks_install")
|
|
37578
|
+
}, async ({ name, scope, profile }) => {
|
|
37579
|
+
const shortName = name.startsWith("hook-") ? name.slice("hook-".length) : name;
|
|
37580
|
+
if (getRegisteredHooks(scope).includes(shortName)) {
|
|
37581
|
+
return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: false, changed: false, scope }) }] };
|
|
37582
|
+
}
|
|
37583
|
+
const result = installHook(name, { scope, profile });
|
|
37584
|
+
if (!result.success)
|
|
37585
|
+
return settingsChangeError(name, scope, result.error ?? "Hook registration failed", result.code);
|
|
37586
|
+
return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: false, changed: true, scope }) }] };
|
|
36421
37587
|
});
|
|
36422
37588
|
defineTool("hooks_init", "Register a new agent profile \u2014 returns a unique agent_id for use with hook installation and execution", {
|
|
36423
37589
|
agent_type: z3.enum(["claude", "gemini", "custom"]).default("claude").describe("Type of AI agent"),
|