@hasna/skills 0.10.8 → 0.10.9
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/bin/index.js +1141 -261
- package/bin/mcp.js +4 -4
- package/bin/migrate.js +4 -4
- package/bin/server.js +4 -4
- package/bin/worker.js +4 -4
- package/dist/cli/commands/runtime-copyfile.d.ts +25 -0
- package/dist/index.js +4 -4
- package/dist/lib/infra-identifiers.d.ts +1 -2
- package/dist/lib/release-review-linkage.d.ts +2 -2
- package/dist/lib/selection-cache.d.ts +1 -1
- package/dist/sdk/index.js +4 -4
- package/package.json +4 -4
package/bin/index.js
CHANGED
|
@@ -36868,7 +36868,7 @@ var package_default;
|
|
|
36868
36868
|
var init_package = __esm(() => {
|
|
36869
36869
|
package_default = {
|
|
36870
36870
|
name: "@hasna/skills",
|
|
36871
|
-
version: "0.10.
|
|
36871
|
+
version: "0.10.9",
|
|
36872
36872
|
description: "Skills library for AI coding agents",
|
|
36873
36873
|
type: "module",
|
|
36874
36874
|
bin: {
|
|
@@ -36990,12 +36990,12 @@ var init_package = __esm(() => {
|
|
|
36990
36990
|
},
|
|
36991
36991
|
repository: {
|
|
36992
36992
|
type: "git",
|
|
36993
|
-
url: "https://github.com/hasna/
|
|
36993
|
+
url: "https://github.com/hasna/skills.git",
|
|
36994
36994
|
directory: "apps/skills"
|
|
36995
36995
|
},
|
|
36996
|
-
homepage: "https://github.com/hasna/
|
|
36996
|
+
homepage: "https://github.com/hasna/skills/tree/main/apps/skills",
|
|
36997
36997
|
bugs: {
|
|
36998
|
-
url: "https://github.com/hasna/
|
|
36998
|
+
url: "https://github.com/hasna/skills/issues"
|
|
36999
36999
|
}
|
|
37000
37000
|
};
|
|
37001
37001
|
});
|
|
@@ -107308,14 +107308,860 @@ var init_remote_account2 = __esm(() => {
|
|
|
107308
107308
|
init_remote_client();
|
|
107309
107309
|
});
|
|
107310
107310
|
|
|
107311
|
+
// src/cli/commands/runtime-copyfile.ts
|
|
107312
|
+
import { createHash as createHash26, randomUUID as randomUUID9 } from "crypto";
|
|
107313
|
+
import { Readable } from "stream";
|
|
107314
|
+
import { createGunzip as createGunzip2 } from "zlib";
|
|
107315
|
+
import {
|
|
107316
|
+
chmodSync as chmodSync4,
|
|
107317
|
+
copyFileSync,
|
|
107318
|
+
existsSync as existsSync33,
|
|
107319
|
+
lstatSync as lstatSync27,
|
|
107320
|
+
mkdirSync as mkdirSync24,
|
|
107321
|
+
readFileSync as readFileSync37,
|
|
107322
|
+
readdirSync as readdirSync20,
|
|
107323
|
+
readlinkSync as readlinkSync4,
|
|
107324
|
+
realpathSync as realpathSync11,
|
|
107325
|
+
renameSync as renameSync13,
|
|
107326
|
+
symlinkSync,
|
|
107327
|
+
writeFileSync as writeFileSync23
|
|
107328
|
+
} from "fs";
|
|
107329
|
+
import { basename as basename13, delimiter, dirname as dirname29, isAbsolute as isAbsolute22, join as join54, relative as relative8, resolve as resolve22, sep as sep9 } from "path";
|
|
107330
|
+
function object8(value, code2) {
|
|
107331
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
107332
|
+
throw new Error(code2);
|
|
107333
|
+
return value;
|
|
107334
|
+
}
|
|
107335
|
+
function hash5(bytes) {
|
|
107336
|
+
return createHash26("sha256").update(bytes).digest("hex");
|
|
107337
|
+
}
|
|
107338
|
+
function isWithin(parent, child) {
|
|
107339
|
+
const rel = relative8(parent, child);
|
|
107340
|
+
return rel === "" || !rel.startsWith(`..${sep9}`) && rel !== ".." && !isAbsolute22(rel);
|
|
107341
|
+
}
|
|
107342
|
+
function entryExists(path) {
|
|
107343
|
+
try {
|
|
107344
|
+
lstatSync27(path);
|
|
107345
|
+
return true;
|
|
107346
|
+
} catch {
|
|
107347
|
+
return false;
|
|
107348
|
+
}
|
|
107349
|
+
}
|
|
107350
|
+
function assertNoSymlinkAncestors(path) {
|
|
107351
|
+
const absolute = resolve22(path);
|
|
107352
|
+
const parts = absolute.split(sep9).filter(Boolean);
|
|
107353
|
+
let current = sep9;
|
|
107354
|
+
for (const part of parts) {
|
|
107355
|
+
current = join54(current, part);
|
|
107356
|
+
const stat3 = lstatSync27(current);
|
|
107357
|
+
if (stat3.isSymbolicLink())
|
|
107358
|
+
throw new Error("PATH_ANCESTOR_SYMLINK_UNSUPPORTED");
|
|
107359
|
+
if (!stat3.isDirectory())
|
|
107360
|
+
throw new Error("PATH_ANCESTOR_NOT_DIRECTORY");
|
|
107361
|
+
}
|
|
107362
|
+
}
|
|
107363
|
+
function acquireRuntimeLock(runtimeRoot) {
|
|
107364
|
+
const lockPath = join54(runtimeRoot, ".copyfile-update-lock");
|
|
107365
|
+
try {
|
|
107366
|
+
mkdirSync24(lockPath, { mode: 448 });
|
|
107367
|
+
} catch {
|
|
107368
|
+
throw new Error("RUNTIME_UPDATE_LOCKED");
|
|
107369
|
+
}
|
|
107370
|
+
try {
|
|
107371
|
+
writeJsonPrivate(join54(lockPath, "lock.json"), { operationId: randomUUID9(), pid: process.pid, startedAt: new Date().toISOString() });
|
|
107372
|
+
} catch {
|
|
107373
|
+
try {
|
|
107374
|
+
renameSync13(lockPath, join54(runtimeRoot, `.copyfile-update-lock-incomplete-${randomUUID9()}`));
|
|
107375
|
+
} catch {}
|
|
107376
|
+
throw new Error("RUNTIME_LOCK_INITIALIZATION_FAILED");
|
|
107377
|
+
}
|
|
107378
|
+
return () => {
|
|
107379
|
+
const released = join54(runtimeRoot, `.copyfile-update-lock-released-${randomUUID9()}`);
|
|
107380
|
+
writeJsonPrivate(join54(lockPath, "released.json"), { completedAt: new Date().toISOString() });
|
|
107381
|
+
renameSync13(lockPath, released);
|
|
107382
|
+
};
|
|
107383
|
+
}
|
|
107384
|
+
function packageBins(pkg) {
|
|
107385
|
+
const raw = pkg.bin;
|
|
107386
|
+
const entries = typeof raw === "string" ? [[PACKAGE_NAME.slice(PACKAGE_NAME.lastIndexOf("/") + 1), raw]] : raw && typeof raw === "object" && !Array.isArray(raw) ? Object.entries(raw) : [];
|
|
107387
|
+
const output2 = {};
|
|
107388
|
+
for (const [name, value] of entries) {
|
|
107389
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(name) || typeof value !== "string" || !value || isAbsolute22(value)) {
|
|
107390
|
+
throw new Error("PACKAGE_BIN_INVALID");
|
|
107391
|
+
}
|
|
107392
|
+
const normalized = value.replaceAll("\\", "/");
|
|
107393
|
+
if (normalized.split("/").some((part) => part === ".." || part === "."))
|
|
107394
|
+
throw new Error("PACKAGE_BIN_INVALID");
|
|
107395
|
+
output2[name] = normalized;
|
|
107396
|
+
}
|
|
107397
|
+
if (!output2.skills)
|
|
107398
|
+
throw new Error("PACKAGE_BIN_INVALID");
|
|
107399
|
+
return output2;
|
|
107400
|
+
}
|
|
107401
|
+
function readPackage(path) {
|
|
107402
|
+
let data;
|
|
107403
|
+
try {
|
|
107404
|
+
data = object8(JSON.parse(readFileSync37(path, "utf8")), "PACKAGE_JSON_INVALID");
|
|
107405
|
+
} catch {
|
|
107406
|
+
throw new Error("PACKAGE_JSON_INVALID");
|
|
107407
|
+
}
|
|
107408
|
+
if (data.name !== PACKAGE_NAME || typeof data.version !== "string" || !new RegExp(SEMVER_PATTERN).test(data.version)) {
|
|
107409
|
+
throw new Error("PACKAGE_IDENTITY_INVALID");
|
|
107410
|
+
}
|
|
107411
|
+
return { data, version: data.version, bins: packageBins(data) };
|
|
107412
|
+
}
|
|
107413
|
+
function resolveRuntimeLayout(homeInput, pathInput) {
|
|
107414
|
+
const home = realpathSync11(homeInput);
|
|
107415
|
+
const runtimeRoot = join54(home, ".hasna", "skills", "runtime");
|
|
107416
|
+
assertNoSymlinkAncestors(runtimeRoot);
|
|
107417
|
+
const runtimeRootStat = lstatSync27(runtimeRoot);
|
|
107418
|
+
if (!runtimeRootStat.isDirectory() || runtimeRootStat.isSymbolicLink() || (runtimeRootStat.mode & 63) !== 0) {
|
|
107419
|
+
throw new Error("RUNTIME_ROOT_NOT_PRIVATE");
|
|
107420
|
+
}
|
|
107421
|
+
const selectedLauncher = Bun.which("skills", { PATH: pathInput });
|
|
107422
|
+
if (!selectedLauncher)
|
|
107423
|
+
throw new Error("ACTIVE_SKILLS_LAUNCHER_NOT_FOUND");
|
|
107424
|
+
const launcherPath = resolve22(selectedLauncher);
|
|
107425
|
+
assertNoSymlinkAncestors(dirname29(launcherPath));
|
|
107426
|
+
const launcherStat = lstatSync27(launcherPath);
|
|
107427
|
+
if (!launcherStat.isSymbolicLink())
|
|
107428
|
+
throw new Error("ACTIVE_SKILLS_LAUNCHER_NOT_MANAGED_LINK");
|
|
107429
|
+
const activeBinary = realpathSync11(launcherPath);
|
|
107430
|
+
const currentPackageRoot = dirname29(dirname29(activeBinary));
|
|
107431
|
+
assertNoSymlinkAncestors(currentPackageRoot);
|
|
107432
|
+
if (!isWithin(runtimeRoot, currentPackageRoot))
|
|
107433
|
+
throw new Error("ACTIVE_RUNTIME_OUTSIDE_COPYFILE_ROOT");
|
|
107434
|
+
const relativePackage = relative8(runtimeRoot, currentPackageRoot).split(sep9);
|
|
107435
|
+
if (relativePackage.length !== 4 || relativePackage[1] !== "node_modules" || relativePackage[2] !== "@hasna" || relativePackage[3] !== "skills") {
|
|
107436
|
+
throw new Error("ACTIVE_RUNTIME_LAYOUT_UNSUPPORTED");
|
|
107437
|
+
}
|
|
107438
|
+
const current = readPackage(join54(currentPackageRoot, "package.json"));
|
|
107439
|
+
if (!activeBinary.startsWith(`${currentPackageRoot}${sep9}`) || activeBinary !== join54(currentPackageRoot, current.bins.skills)) {
|
|
107440
|
+
throw new Error("ACTIVE_RUNTIME_ENTRYPOINT_MISMATCH");
|
|
107441
|
+
}
|
|
107442
|
+
const launchers = [];
|
|
107443
|
+
for (const rawDir of pathInput.split(delimiter).filter(Boolean)) {
|
|
107444
|
+
const dir = resolve22(rawDir);
|
|
107445
|
+
for (const [name, target] of Object.entries(current.bins)) {
|
|
107446
|
+
const path = join54(dir, name);
|
|
107447
|
+
if (!existsSync33(path))
|
|
107448
|
+
continue;
|
|
107449
|
+
assertNoSymlinkAncestors(dir);
|
|
107450
|
+
let stat3;
|
|
107451
|
+
try {
|
|
107452
|
+
stat3 = lstatSync27(path);
|
|
107453
|
+
} catch {
|
|
107454
|
+
continue;
|
|
107455
|
+
}
|
|
107456
|
+
if (!stat3.isSymbolicLink())
|
|
107457
|
+
continue;
|
|
107458
|
+
let actual;
|
|
107459
|
+
try {
|
|
107460
|
+
actual = realpathSync11(path);
|
|
107461
|
+
} catch {
|
|
107462
|
+
throw new Error("LAUNCHER_TARGET_UNREADABLE");
|
|
107463
|
+
}
|
|
107464
|
+
if (actual !== join54(currentPackageRoot, target))
|
|
107465
|
+
continue;
|
|
107466
|
+
const oldLinkTarget = readlinkSync4(path);
|
|
107467
|
+
if (resolve22(dirname29(path), oldLinkTarget) !== actual)
|
|
107468
|
+
throw new Error("LAUNCHER_SYMLINK_CHAIN_UNSUPPORTED");
|
|
107469
|
+
const newTarget = join54(runtimeRoot, "__target__", "node_modules", "@hasna", "skills", target);
|
|
107470
|
+
launchers.push({ path, oldTarget: actual, oldLinkTarget, newTarget, backupPath: `${path}.skills-prev-${randomUUID9()}` });
|
|
107471
|
+
}
|
|
107472
|
+
}
|
|
107473
|
+
if (!launchers.some((item) => item.path === launcherPath))
|
|
107474
|
+
throw new Error("ACTIVE_LAUNCHER_NOT_IN_PATH_INVENTORY");
|
|
107475
|
+
if (new Set(launchers.map((item) => item.path)).size !== launchers.length)
|
|
107476
|
+
throw new Error("LAUNCHER_PATH_DUPLICATE");
|
|
107477
|
+
return { home, runtimeRoot, currentPackageRoot, currentVersion: current.version, bin: current.bins, launchers };
|
|
107478
|
+
}
|
|
107479
|
+
function verifyIntegrity(bytes, integrity) {
|
|
107480
|
+
const match = /^sha512-([A-Za-z0-9+/]+={0,2})$/.exec(integrity);
|
|
107481
|
+
if (!match)
|
|
107482
|
+
throw new Error("REGISTRY_INTEGRITY_UNSUPPORTED");
|
|
107483
|
+
const actual = createHash26("sha512").update(bytes).digest("base64");
|
|
107484
|
+
if (actual !== match[1])
|
|
107485
|
+
throw new Error("TARBALL_INTEGRITY_MISMATCH");
|
|
107486
|
+
}
|
|
107487
|
+
function expectedTarballUrl(input2, origin, version2) {
|
|
107488
|
+
if (typeof input2 !== "string")
|
|
107489
|
+
throw new Error("REGISTRY_TARBALL_URL_INVALID");
|
|
107490
|
+
let url2;
|
|
107491
|
+
try {
|
|
107492
|
+
url2 = new URL(input2);
|
|
107493
|
+
} catch {
|
|
107494
|
+
throw new Error("REGISTRY_TARBALL_URL_INVALID");
|
|
107495
|
+
}
|
|
107496
|
+
const base3 = new URL(origin);
|
|
107497
|
+
if (url2.protocol !== "https:" && base3.protocol === "https:")
|
|
107498
|
+
throw new Error("REGISTRY_TARBALL_URL_INVALID");
|
|
107499
|
+
if (url2.origin !== base3.origin || url2.username || url2.password || url2.search || url2.hash)
|
|
107500
|
+
throw new Error("REGISTRY_TARBALL_URL_INVALID");
|
|
107501
|
+
if (!url2.pathname.endsWith(`/skills-${version2}.tgz`))
|
|
107502
|
+
throw new Error("REGISTRY_TARBALL_URL_INVALID");
|
|
107503
|
+
return url2.toString();
|
|
107504
|
+
}
|
|
107505
|
+
function tarField(block, start, length) {
|
|
107506
|
+
const field2 = block.subarray(start, start + length);
|
|
107507
|
+
const end = field2.indexOf(0);
|
|
107508
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(end < 0 ? field2 : field2.subarray(0, end));
|
|
107509
|
+
}
|
|
107510
|
+
function tarOctal2(block, start, length) {
|
|
107511
|
+
const raw = new TextDecoder().decode(block.subarray(start, start + length)).replace(/[\0 ]+$/g, "").trim();
|
|
107512
|
+
if (!raw)
|
|
107513
|
+
return 0;
|
|
107514
|
+
if (!/^[0-7]+$/.test(raw))
|
|
107515
|
+
throw new Error("TARBALL_TAR_NUMERIC_FIELD_INVALID");
|
|
107516
|
+
const value = Number.parseInt(raw, 8);
|
|
107517
|
+
if (!Number.isSafeInteger(value))
|
|
107518
|
+
throw new Error("TARBALL_TAR_NUMERIC_FIELD_INVALID");
|
|
107519
|
+
return value;
|
|
107520
|
+
}
|
|
107521
|
+
async function gunzipCapped(bytes, limit) {
|
|
107522
|
+
if (bytes[0] !== 31 || bytes[1] !== 139)
|
|
107523
|
+
throw new Error("TARBALL_GZIP_REQUIRED");
|
|
107524
|
+
const stream = Readable.from([Buffer.from(bytes)]).pipe(createGunzip2());
|
|
107525
|
+
const chunks = [];
|
|
107526
|
+
let size2 = 0;
|
|
107527
|
+
try {
|
|
107528
|
+
for await (const chunk2 of stream) {
|
|
107529
|
+
const part = Buffer.from(chunk2);
|
|
107530
|
+
size2 += part.byteLength;
|
|
107531
|
+
if (size2 > limit) {
|
|
107532
|
+
stream.destroy();
|
|
107533
|
+
throw new Error("TARBALL_EXPANDED_SIZE_INVALID");
|
|
107534
|
+
}
|
|
107535
|
+
chunks.push(part);
|
|
107536
|
+
}
|
|
107537
|
+
} finally {
|
|
107538
|
+
stream.destroy();
|
|
107539
|
+
}
|
|
107540
|
+
return Buffer.concat(chunks, size2);
|
|
107541
|
+
}
|
|
107542
|
+
async function preflightTarball(bytes, limits = {}) {
|
|
107543
|
+
const expanded = await gunzipCapped(bytes, limits.maxExpandedBytes ?? MAX_TARBALL_EXPANDED_BYTES);
|
|
107544
|
+
const entries = [];
|
|
107545
|
+
const paths = new Set;
|
|
107546
|
+
let offset = 0, ended = false, fileBytes = 0;
|
|
107547
|
+
while (offset + 512 <= expanded.byteLength) {
|
|
107548
|
+
const block = expanded.subarray(offset, offset + 512);
|
|
107549
|
+
if (block.every((byte) => byte === 0)) {
|
|
107550
|
+
ended = true;
|
|
107551
|
+
break;
|
|
107552
|
+
}
|
|
107553
|
+
if (entries.length >= (limits.maxEntries ?? MAX_TARBALL_ENTRIES))
|
|
107554
|
+
throw new Error("TARBALL_ENTRY_COUNT_INVALID");
|
|
107555
|
+
const expectedChecksum = tarOctal2(block, 148, 8);
|
|
107556
|
+
let checksum = 0;
|
|
107557
|
+
for (let i = 0;i < 512; i++)
|
|
107558
|
+
checksum += i >= 148 && i < 156 ? 32 : block[i];
|
|
107559
|
+
if (checksum !== expectedChecksum)
|
|
107560
|
+
throw new Error("TARBALL_TAR_CHECKSUM_INVALID");
|
|
107561
|
+
const name = tarField(block, 0, 100), prefix = tarField(block, 345, 155);
|
|
107562
|
+
const rawPath = `${prefix ? `${prefix}/` : ""}${name}`.replace(/\/$/, "");
|
|
107563
|
+
const pathParts2 = rawPath.split("/");
|
|
107564
|
+
if (rawPath !== "package" && !rawPath.startsWith("package/") || rawPath.startsWith("/") || rawPath.includes("\\") || pathParts2.some((part) => !part || part === "." || part === ".."))
|
|
107565
|
+
throw new Error("TARBALL_PATH_INVALID");
|
|
107566
|
+
if (paths.has(rawPath))
|
|
107567
|
+
throw new Error("TARBALL_DUPLICATE_PATH");
|
|
107568
|
+
paths.add(rawPath);
|
|
107569
|
+
const flag = block[156] === 0 ? "0" : String.fromCharCode(block[156]);
|
|
107570
|
+
const size2 = tarOctal2(block, 124, 12);
|
|
107571
|
+
const kind = flag === "0" ? "file" : flag === "5" ? "directory" : null;
|
|
107572
|
+
if (!kind || kind === "directory" && size2 !== 0 || rawPath === "package" && kind !== "directory")
|
|
107573
|
+
throw new Error("TARBALL_ENTRY_TYPE_UNSUPPORTED");
|
|
107574
|
+
fileBytes += size2;
|
|
107575
|
+
if (fileBytes > (limits.maxExpandedBytes ?? MAX_TARBALL_EXPANDED_BYTES))
|
|
107576
|
+
throw new Error("TARBALL_EXPANDED_SIZE_INVALID");
|
|
107577
|
+
entries.push({ path: rawPath, kind, size: size2 });
|
|
107578
|
+
const padded = Math.ceil(size2 / 512) * 512;
|
|
107579
|
+
offset += 512 + padded;
|
|
107580
|
+
if (offset > expanded.byteLength)
|
|
107581
|
+
throw new Error("TARBALL_TAR_TRUNCATED");
|
|
107582
|
+
}
|
|
107583
|
+
if (!ended || !expanded.subarray(offset).every((byte) => byte === 0))
|
|
107584
|
+
throw new Error("TARBALL_TAR_TERMINATOR_INVALID");
|
|
107585
|
+
return { entries, expandedBytes: expanded.byteLength };
|
|
107586
|
+
}
|
|
107587
|
+
async function downloadPackage(version2, stagingRoot, registryOrigin, fetcher) {
|
|
107588
|
+
const metadataUrl = `${registryOrigin}/@hasna%2fskills/${encodeURIComponent(version2)}`;
|
|
107589
|
+
const timeout = AbortSignal.timeout(30000);
|
|
107590
|
+
const metadataResponse = await fetcher(metadataUrl, { redirect: "error", signal: timeout });
|
|
107591
|
+
if (!metadataResponse.ok || new URL(metadataResponse.url || metadataUrl).origin !== new URL(registryOrigin).origin)
|
|
107592
|
+
throw new Error("REGISTRY_METADATA_FETCH_FAILED");
|
|
107593
|
+
const metadata = object8(JSON.parse(new TextDecoder().decode(await readBodyCapped(metadataResponse, 5 * 1024 * 1024, "REGISTRY_METADATA_SIZE_INVALID"))), "REGISTRY_METADATA_INVALID");
|
|
107594
|
+
if (metadata.name !== PACKAGE_NAME || metadata.version !== version2 || metadata._id !== `${PACKAGE_NAME}@${version2}`)
|
|
107595
|
+
throw new Error("REGISTRY_PACKAGE_IDENTITY_MISMATCH");
|
|
107596
|
+
const dist = object8(metadata.dist, "REGISTRY_DIST_INVALID");
|
|
107597
|
+
const integrity = typeof dist.integrity === "string" ? dist.integrity : "";
|
|
107598
|
+
const tarballUrl = expectedTarballUrl(dist.tarball, registryOrigin, version2);
|
|
107599
|
+
const response = await fetcher(tarballUrl, { redirect: "error", signal: AbortSignal.timeout(30000) });
|
|
107600
|
+
if (!response.ok || new URL(response.url || tarballUrl).origin !== new URL(registryOrigin).origin)
|
|
107601
|
+
throw new Error("REGISTRY_TARBALL_FETCH_FAILED");
|
|
107602
|
+
const bytes = await readBodyCapped(response, MAX_TARBALL_BYTES, "TARBALL_SIZE_INVALID");
|
|
107603
|
+
if (!bytes.byteLength)
|
|
107604
|
+
throw new Error("TARBALL_SIZE_INVALID");
|
|
107605
|
+
verifyIntegrity(bytes, integrity);
|
|
107606
|
+
const preflight = await preflightTarball(bytes);
|
|
107607
|
+
const archive = new Bun.Archive(bytes);
|
|
107608
|
+
const files = await archive.files();
|
|
107609
|
+
const regularFiles = preflight.entries.filter((entry) => entry.kind === "file");
|
|
107610
|
+
if (files.size !== regularFiles.length || regularFiles.some((entry) => files.get(entry.path)?.size !== entry.size))
|
|
107611
|
+
throw new Error("TARBALL_ARCHIVE_INTERPRETATION_MISMATCH");
|
|
107612
|
+
const packageJson = files.get("package/package.json");
|
|
107613
|
+
if (!packageJson)
|
|
107614
|
+
throw new Error("TARBALL_PACKAGE_JSON_MISSING");
|
|
107615
|
+
const unpacked = object8(JSON.parse(await packageJson.text()), "TARBALL_PACKAGE_JSON_INVALID");
|
|
107616
|
+
if (unpacked.name !== PACKAGE_NAME || unpacked.version !== version2)
|
|
107617
|
+
throw new Error("TARBALL_PACKAGE_IDENTITY_MISMATCH");
|
|
107618
|
+
const bins = packageBins(unpacked);
|
|
107619
|
+
const archiveRoot = join54(stagingRoot, "verified-archive");
|
|
107620
|
+
mkdirSync24(archiveRoot, { mode: 448 });
|
|
107621
|
+
await archive.extract(archiveRoot);
|
|
107622
|
+
const extractedPackage = join54(archiveRoot, "package");
|
|
107623
|
+
const extracted = readTree(extractedPackage, false);
|
|
107624
|
+
const tarballPath = join54(stagingRoot, "verified-package.tgz");
|
|
107625
|
+
writeFileSync23(tarballPath, bytes, { flag: "wx", mode: 384 });
|
|
107626
|
+
if (!readFileSync37(tarballPath).equals(Buffer.from(bytes)))
|
|
107627
|
+
throw new Error("TARBALL_READBACK_MISMATCH");
|
|
107628
|
+
return {
|
|
107629
|
+
tarballPath,
|
|
107630
|
+
tarballUrl,
|
|
107631
|
+
integrity,
|
|
107632
|
+
sha256: hash5(bytes),
|
|
107633
|
+
byteSize: bytes.byteLength,
|
|
107634
|
+
packageTreeSha256: extracted.digest,
|
|
107635
|
+
packageBins: bins
|
|
107636
|
+
};
|
|
107637
|
+
}
|
|
107638
|
+
async function readBodyCapped(response, limit, errorCode) {
|
|
107639
|
+
if (!response.body)
|
|
107640
|
+
throw new Error("HTTP_RESPONSE_BODY_MISSING");
|
|
107641
|
+
const reader = response.body.getReader();
|
|
107642
|
+
const chunks = [];
|
|
107643
|
+
let size2 = 0;
|
|
107644
|
+
try {
|
|
107645
|
+
for (;; ) {
|
|
107646
|
+
const { done, value } = await reader.read();
|
|
107647
|
+
if (done)
|
|
107648
|
+
break;
|
|
107649
|
+
size2 += value.byteLength;
|
|
107650
|
+
if (size2 > limit) {
|
|
107651
|
+
await reader.cancel();
|
|
107652
|
+
throw new Error(errorCode);
|
|
107653
|
+
}
|
|
107654
|
+
chunks.push(value);
|
|
107655
|
+
}
|
|
107656
|
+
} finally {
|
|
107657
|
+
reader.releaseLock();
|
|
107658
|
+
}
|
|
107659
|
+
const bytes = new Uint8Array(size2);
|
|
107660
|
+
let offset = 0;
|
|
107661
|
+
for (const chunk2 of chunks) {
|
|
107662
|
+
bytes.set(chunk2, offset);
|
|
107663
|
+
offset += chunk2.byteLength;
|
|
107664
|
+
}
|
|
107665
|
+
return bytes;
|
|
107666
|
+
}
|
|
107667
|
+
function readTree(root2, includeModes) {
|
|
107668
|
+
const base3 = realpathSync11(root2);
|
|
107669
|
+
const entries = [];
|
|
107670
|
+
const walk2 = (dir, prefix = "") => {
|
|
107671
|
+
for (const item of readdirSync20(dir).sort()) {
|
|
107672
|
+
const path = join54(dir, item);
|
|
107673
|
+
const rel = prefix ? `${prefix}/${item}` : item;
|
|
107674
|
+
const stat3 = lstatSync27(path);
|
|
107675
|
+
if (stat3.isSymbolicLink()) {
|
|
107676
|
+
const link2 = readlinkSync4(path);
|
|
107677
|
+
const resolved = resolve22(dirname29(path), link2);
|
|
107678
|
+
if (!isWithin(base3, resolved))
|
|
107679
|
+
throw new Error("TREE_SYMLINK_ESCAPES_ROOT");
|
|
107680
|
+
let physicalTarget;
|
|
107681
|
+
try {
|
|
107682
|
+
physicalTarget = realpathSync11(path);
|
|
107683
|
+
} catch {
|
|
107684
|
+
throw new Error("TREE_SYMLINK_TARGET_MISSING");
|
|
107685
|
+
}
|
|
107686
|
+
if (!isWithin(base3, physicalTarget))
|
|
107687
|
+
throw new Error("TREE_SYMLINK_ESCAPES_ROOT");
|
|
107688
|
+
entries.push({ path: rel, kind: "symlink", target: link2, ...includeModes ? { mode: stat3.mode & 511 } : {} });
|
|
107689
|
+
} else if (stat3.isDirectory()) {
|
|
107690
|
+
entries.push({ path: rel, kind: "directory", ...includeModes ? { mode: stat3.mode & 511 } : {} });
|
|
107691
|
+
walk2(path, rel);
|
|
107692
|
+
} else if (stat3.isFile()) {
|
|
107693
|
+
if (stat3.nlink !== 1)
|
|
107694
|
+
throw new Error("TREE_HARDLINK_UNSUPPORTED");
|
|
107695
|
+
const bytes = readFileSync37(path);
|
|
107696
|
+
entries.push({ path: rel, kind: "file", sha256: hash5(bytes), ...includeModes ? { mode: stat3.mode & 511 } : {} });
|
|
107697
|
+
} else
|
|
107698
|
+
throw new Error("TREE_ENTRY_TYPE_UNSUPPORTED");
|
|
107699
|
+
}
|
|
107700
|
+
};
|
|
107701
|
+
walk2(base3);
|
|
107702
|
+
return { entries, digest: hash5(new TextEncoder().encode(JSON.stringify(entries))) };
|
|
107703
|
+
}
|
|
107704
|
+
function normalizeModes(root2) {
|
|
107705
|
+
const walk2 = (dir) => {
|
|
107706
|
+
for (const item of readdirSync20(dir)) {
|
|
107707
|
+
const path = join54(dir, item);
|
|
107708
|
+
const stat3 = lstatSync27(path);
|
|
107709
|
+
if (stat3.isSymbolicLink())
|
|
107710
|
+
continue;
|
|
107711
|
+
if (stat3.isDirectory()) {
|
|
107712
|
+
chmodSync4(path, stat3.mode & 493);
|
|
107713
|
+
walk2(path);
|
|
107714
|
+
} else if (stat3.isFile()) {
|
|
107715
|
+
chmodSync4(path, stat3.mode & 493);
|
|
107716
|
+
} else
|
|
107717
|
+
throw new Error("RUNTIME_ENTRY_TYPE_UNSUPPORTED");
|
|
107718
|
+
}
|
|
107719
|
+
};
|
|
107720
|
+
walk2(root2);
|
|
107721
|
+
}
|
|
107722
|
+
function writeJsonPrivate(path, value) {
|
|
107723
|
+
const bytes = Buffer.from(`${JSON.stringify(value, null, 2)}
|
|
107724
|
+
`);
|
|
107725
|
+
writeFileSync23(path, bytes, { flag: "wx", mode: 384 });
|
|
107726
|
+
chmodSync4(path, 384);
|
|
107727
|
+
if (!readFileSync37(path).equals(bytes))
|
|
107728
|
+
throw new Error("PRIVATE_JSON_READBACK_MISMATCH");
|
|
107729
|
+
}
|
|
107730
|
+
function writePreimageManifest(stage, configs, launchers) {
|
|
107731
|
+
const path = join54(stage, "preimage", "manifest.json");
|
|
107732
|
+
const value = { schema: "skills.copyfile-preimage.v1", configs, launchers };
|
|
107733
|
+
const bytes = Buffer.from(`${JSON.stringify(value, null, 2)}
|
|
107734
|
+
`);
|
|
107735
|
+
writeFileSync23(path, bytes, { flag: "wx", mode: 256 });
|
|
107736
|
+
chmodSync4(path, 256);
|
|
107737
|
+
if (!readFileSync37(path).equals(bytes))
|
|
107738
|
+
throw new Error("PREIMAGE_MANIFEST_READBACK_MISMATCH");
|
|
107739
|
+
return hash5(bytes);
|
|
107740
|
+
}
|
|
107741
|
+
function verifyPreimageManifest(runtimePath, receipt, home) {
|
|
107742
|
+
const path = join54(runtimePath, "preimage", "manifest.json");
|
|
107743
|
+
const bytes = readFileSync37(path);
|
|
107744
|
+
if (hash5(bytes) !== receipt.preimageSha256)
|
|
107745
|
+
throw new Error("PREIMAGE_MANIFEST_DRIFT");
|
|
107746
|
+
const manifest = object8(JSON.parse(bytes.toString("utf8")), "PREIMAGE_MANIFEST_INVALID");
|
|
107747
|
+
const expected = { schema: "skills.copyfile-preimage.v1", configs: receipt.configs, launchers: receipt.launchers };
|
|
107748
|
+
if (JSON.stringify(manifest) !== JSON.stringify(expected))
|
|
107749
|
+
throw new Error("PREIMAGE_RECEIPT_MISMATCH");
|
|
107750
|
+
if (receipt.configs.length !== CONFIG_PREIMAGE_PATHS.length)
|
|
107751
|
+
throw new Error("PREIMAGE_CONFIG_SET_INVALID");
|
|
107752
|
+
for (const [index, relativePath] of CONFIG_PREIMAGE_PATHS.entries()) {
|
|
107753
|
+
const config2 = receipt.configs[index];
|
|
107754
|
+
if (config2.relativePath !== relativePath || config2.sourcePath !== join54(home, relativePath))
|
|
107755
|
+
throw new Error("PREIMAGE_CONFIG_BINDING_INVALID");
|
|
107756
|
+
if (config2.present) {
|
|
107757
|
+
const backup = join54(runtimePath, "preimage", "configs", relativePath);
|
|
107758
|
+
const stat3 = lstatSync27(backup);
|
|
107759
|
+
const saved = readFileSync37(backup);
|
|
107760
|
+
if (!stat3.isFile() || stat3.isSymbolicLink() || hash5(saved) !== config2.sha256 || saved.byteLength !== config2.byteSize)
|
|
107761
|
+
throw new Error("PREIMAGE_CONFIG_DRIFT");
|
|
107762
|
+
}
|
|
107763
|
+
}
|
|
107764
|
+
}
|
|
107765
|
+
function captureConfigPreimages(home, stage) {
|
|
107766
|
+
const root2 = join54(stage, "preimage", "configs");
|
|
107767
|
+
mkdirSync24(root2, { recursive: true, mode: 448 });
|
|
107768
|
+
return CONFIG_PREIMAGE_PATHS.map((relativePath) => {
|
|
107769
|
+
const sourcePath = join54(home, relativePath);
|
|
107770
|
+
if (!entryExists(sourcePath))
|
|
107771
|
+
return { sourcePath, relativePath, present: false };
|
|
107772
|
+
const stat3 = lstatSync27(sourcePath);
|
|
107773
|
+
if (!stat3.isFile() || stat3.isSymbolicLink())
|
|
107774
|
+
throw new Error("CONFIG_PREIMAGE_NOT_REGULAR_FILE");
|
|
107775
|
+
const bytes = readFileSync37(sourcePath);
|
|
107776
|
+
const backupPath = join54(root2, relativePath);
|
|
107777
|
+
mkdirSync24(dirname29(backupPath), { recursive: true, mode: 448 });
|
|
107778
|
+
writeFileSync23(backupPath, bytes, { flag: "wx", mode: 384 });
|
|
107779
|
+
chmodSync4(backupPath, 384);
|
|
107780
|
+
const readback = readFileSync37(backupPath);
|
|
107781
|
+
if (!readback.equals(bytes))
|
|
107782
|
+
throw new Error("CONFIG_PREIMAGE_READBACK_MISMATCH");
|
|
107783
|
+
return { sourcePath, relativePath, present: true, sha256: hash5(bytes), byteSize: bytes.byteLength };
|
|
107784
|
+
});
|
|
107785
|
+
}
|
|
107786
|
+
function assertConfigUnchanged(configs) {
|
|
107787
|
+
for (const config2 of configs) {
|
|
107788
|
+
const present = entryExists(config2.sourcePath);
|
|
107789
|
+
if (present !== config2.present)
|
|
107790
|
+
throw new Error("CONFIG_DRIFT_DURING_UPDATE");
|
|
107791
|
+
if (present) {
|
|
107792
|
+
const stat3 = lstatSync27(config2.sourcePath);
|
|
107793
|
+
if (!stat3.isFile() || stat3.isSymbolicLink() || hash5(readFileSync37(config2.sourcePath)) !== config2.sha256) {
|
|
107794
|
+
throw new Error("CONFIG_DRIFT_DURING_UPDATE");
|
|
107795
|
+
}
|
|
107796
|
+
}
|
|
107797
|
+
}
|
|
107798
|
+
}
|
|
107799
|
+
async function runNpm(args2, cwd2, home, staging) {
|
|
107800
|
+
const userNpmrc = join54(staging, "npm-user.npmrc"), globalNpmrc = join54(staging, "npm-global.npmrc");
|
|
107801
|
+
if (!existsSync33(userNpmrc))
|
|
107802
|
+
writeFileSync23(userNpmrc, `registry=${REGISTRY_ORIGIN}/
|
|
107803
|
+
ignore-scripts=true
|
|
107804
|
+
`, { mode: 384, flag: "wx" });
|
|
107805
|
+
if (!existsSync33(globalNpmrc))
|
|
107806
|
+
writeFileSync23(globalNpmrc, `registry=${REGISTRY_ORIGIN}/
|
|
107807
|
+
ignore-scripts=true
|
|
107808
|
+
`, { mode: 384, flag: "wx" });
|
|
107809
|
+
const env3 = {
|
|
107810
|
+
PATH: process.env.PATH ?? "",
|
|
107811
|
+
HOME: home,
|
|
107812
|
+
TMPDIR: staging,
|
|
107813
|
+
TMP: staging,
|
|
107814
|
+
TEMP: staging,
|
|
107815
|
+
CI: "1",
|
|
107816
|
+
NPM_CONFIG_USERCONFIG: userNpmrc,
|
|
107817
|
+
NPM_CONFIG_GLOBALCONFIG: globalNpmrc,
|
|
107818
|
+
NPM_CONFIG_CACHE: join54(staging, "npm-cache"),
|
|
107819
|
+
NPM_CONFIG_REGISTRY: `${REGISTRY_ORIGIN}/`,
|
|
107820
|
+
NPM_CONFIG_IGNORE_SCRIPTS: "true"
|
|
107821
|
+
};
|
|
107822
|
+
const proc = Bun.spawn(["npm", ...args2, "--registry", `${REGISTRY_ORIGIN}/`, "--ignore-scripts", "--no-audit", "--no-fund"], {
|
|
107823
|
+
cwd: cwd2,
|
|
107824
|
+
env: env3,
|
|
107825
|
+
stdin: "ignore",
|
|
107826
|
+
stdout: "ignore",
|
|
107827
|
+
stderr: "ignore"
|
|
107828
|
+
});
|
|
107829
|
+
const exitCode = await proc.exited;
|
|
107830
|
+
if (exitCode !== 0)
|
|
107831
|
+
throw new Error("DEPENDENCY_GRAPH_INSTALL_FAILED");
|
|
107832
|
+
}
|
|
107833
|
+
function comparePackageTrees(expectedRoot, installedRoot) {
|
|
107834
|
+
const expected = readTree(expectedRoot, false);
|
|
107835
|
+
const installed = readTree(installedRoot, false);
|
|
107836
|
+
if (JSON.stringify(expected.entries) !== JSON.stringify(installed.entries))
|
|
107837
|
+
throw new Error("INSTALLED_PACKAGE_BYTES_MISMATCH");
|
|
107838
|
+
return expected.digest;
|
|
107839
|
+
}
|
|
107840
|
+
function replaceLink(path, target, backupPath, oldLinkTarget) {
|
|
107841
|
+
const stat3 = lstatSync27(path);
|
|
107842
|
+
if (!stat3.isSymbolicLink() || entryExists(backupPath))
|
|
107843
|
+
throw new Error("LAUNCHER_PREIMAGE_DRIFT");
|
|
107844
|
+
const temp = `${path}.skills-next-${randomUUID9()}`;
|
|
107845
|
+
symlinkSync(oldLinkTarget, backupPath);
|
|
107846
|
+
if (readlinkSync4(backupPath) !== oldLinkTarget || realpathSync11(backupPath) !== resolve22(dirname29(path), oldLinkTarget))
|
|
107847
|
+
throw new Error("LAUNCHER_BACKUP_READBACK_MISMATCH");
|
|
107848
|
+
symlinkSync(target, temp);
|
|
107849
|
+
if (realpathSync11(temp) !== target)
|
|
107850
|
+
throw new Error("LAUNCHER_NEW_LINK_READBACK_MISMATCH");
|
|
107851
|
+
renameSync13(temp, path);
|
|
107852
|
+
}
|
|
107853
|
+
function atomicJson(path, value) {
|
|
107854
|
+
const temp = `${path}.next-${randomUUID9()}`;
|
|
107855
|
+
const bytes = Buffer.from(`${JSON.stringify(value, null, 2)}
|
|
107856
|
+
`);
|
|
107857
|
+
writeFileSync23(temp, bytes, { flag: "wx", mode: 384 });
|
|
107858
|
+
chmodSync4(temp, 384);
|
|
107859
|
+
renameSync13(temp, path);
|
|
107860
|
+
if (!readFileSync37(path).equals(bytes))
|
|
107861
|
+
throw new Error("RECEIPT_READBACK_MISMATCH");
|
|
107862
|
+
}
|
|
107863
|
+
function persistReceipt(path, value) {
|
|
107864
|
+
if (entryExists(path)) {
|
|
107865
|
+
const priorBytes = readFileSync37(path);
|
|
107866
|
+
const prior = object8(JSON.parse(priorBytes.toString("utf8")), "RECEIPT_INVALID");
|
|
107867
|
+
if (prior.id !== value.id || prior.schema !== "skills.copyfile-runtime-receipt.v1" || typeof prior.state !== "string" || !/^[a-z-]{1,32}$/.test(prior.state))
|
|
107868
|
+
throw new Error("RECEIPT_ID_DRIFT");
|
|
107869
|
+
const nextBytes = Buffer.from(`${JSON.stringify(value, null, 2)}
|
|
107870
|
+
`);
|
|
107871
|
+
if (priorBytes.equals(nextBytes))
|
|
107872
|
+
return;
|
|
107873
|
+
const historyRoot = join54(dirname29(path), "receipt-history");
|
|
107874
|
+
if (!entryExists(historyRoot))
|
|
107875
|
+
mkdirSync24(historyRoot, { mode: 448 });
|
|
107876
|
+
const historyStat = lstatSync27(historyRoot);
|
|
107877
|
+
if (!historyStat.isDirectory() || historyStat.isSymbolicLink() || (historyStat.mode & 63) !== 0)
|
|
107878
|
+
throw new Error("RECEIPT_HISTORY_NOT_PRIVATE");
|
|
107879
|
+
const historyPath = join54(historyRoot, `${value.id}-${String(prior.state)}-${randomUUID9()}.json`);
|
|
107880
|
+
writeFileSync23(historyPath, priorBytes, { flag: "wx", mode: 256 });
|
|
107881
|
+
chmodSync4(historyPath, 256);
|
|
107882
|
+
if (!readFileSync37(historyPath).equals(priorBytes))
|
|
107883
|
+
throw new Error("RECEIPT_HISTORY_READBACK_MISMATCH");
|
|
107884
|
+
}
|
|
107885
|
+
atomicJson(path, value);
|
|
107886
|
+
}
|
|
107887
|
+
async function updateCopyfileRuntime(version2, options = {}) {
|
|
107888
|
+
if (!new RegExp(SEMVER_PATTERN).test(version2))
|
|
107889
|
+
throw new Error("EXACT_VERSION_REQUIRED");
|
|
107890
|
+
if (!STABLE_SEMVER_PATTERN.test(version2))
|
|
107891
|
+
throw new Error("EXACT_STABLE_VERSION_REQUIRED");
|
|
107892
|
+
const home = resolve22(options.homeDir ?? process.env.HOME ?? "");
|
|
107893
|
+
if (!home || !existsSync33(home))
|
|
107894
|
+
throw new Error("HOME_NOT_FOUND");
|
|
107895
|
+
const layout = resolveRuntimeLayout(home, options.pathValue ?? process.env.PATH ?? "");
|
|
107896
|
+
if (process.platform === "win32")
|
|
107897
|
+
throw new Error("COPYFILE_RUNTIME_PLATFORM_UNSUPPORTED");
|
|
107898
|
+
const registryOrigin = options.registryOrigin ?? REGISTRY_ORIGIN;
|
|
107899
|
+
const origin = new URL(registryOrigin);
|
|
107900
|
+
if (origin.protocol !== "https:" && options.registryOrigin === undefined)
|
|
107901
|
+
throw new Error("REGISTRY_ORIGIN_INVALID");
|
|
107902
|
+
if (origin.username || origin.password || origin.search || origin.hash)
|
|
107903
|
+
throw new Error("REGISTRY_ORIGIN_INVALID");
|
|
107904
|
+
const runtimeRootStat = lstatSync27(layout.runtimeRoot);
|
|
107905
|
+
if ((runtimeRootStat.mode & 63) !== 0)
|
|
107906
|
+
throw new Error("RUNTIME_ROOT_NOT_PRIVATE");
|
|
107907
|
+
const id2 = randomUUID9();
|
|
107908
|
+
const releaseLock = acquireRuntimeLock(layout.runtimeRoot);
|
|
107909
|
+
const stagePath = join54(layout.runtimeRoot, `.stage-${version2}-${id2}`);
|
|
107910
|
+
const finalPath = join54(layout.runtimeRoot, `${version2}-copyfile`);
|
|
107911
|
+
const launchers = layout.launchers.map((item) => ({ ...item, newTarget: join54(finalPath, "node_modules", "@hasna", "skills", layout.bin[basename13(item.path)]) }));
|
|
107912
|
+
let switched = [];
|
|
107913
|
+
let moved = false;
|
|
107914
|
+
try {
|
|
107915
|
+
if (entryExists(finalPath))
|
|
107916
|
+
throw new Error("TARGET_RUNTIME_ALREADY_EXISTS");
|
|
107917
|
+
mkdirSync24(stagePath, { mode: 448 });
|
|
107918
|
+
chmodSync4(stagePath, 448);
|
|
107919
|
+
const configPreimages = captureConfigPreimages(home, stagePath);
|
|
107920
|
+
const fetcher = options.fetcher ?? fetch;
|
|
107921
|
+
const artifact = await downloadPackage(version2, stagePath, origin.origin, fetcher);
|
|
107922
|
+
if (artifact.packageBins && JSON.stringify(Object.entries(artifact.packageBins).sort()) !== JSON.stringify(Object.entries(layout.bin).sort())) {
|
|
107923
|
+
throw new Error("PACKAGE_BIN_SET_CHANGED");
|
|
107924
|
+
}
|
|
107925
|
+
const stagePackage = join54(stagePath, "install");
|
|
107926
|
+
mkdirSync24(stagePackage, { mode: 448 });
|
|
107927
|
+
copyFileSync(artifact.tarballPath, join54(stagePackage, "verified.tgz"));
|
|
107928
|
+
writeFileSync23(join54(stagePackage, "package.json"), JSON.stringify({ private: true, dependencies: { [PACKAGE_NAME]: "file:./verified.tgz" } }, null, 2), { mode: 384, flag: "wx" });
|
|
107929
|
+
await runNpm(["install", "--package-lock-only", "--prefix", stagePackage], stagePackage, home, stagePath);
|
|
107930
|
+
await runNpm(["ci", "--prefix", stagePackage], stagePackage, home, stagePath);
|
|
107931
|
+
const lockBytes = readFileSync37(join54(stagePackage, "package-lock.json"));
|
|
107932
|
+
const installedPackage = join54(stagePackage, "node_modules", "@hasna", "skills");
|
|
107933
|
+
const installed = readPackage(join54(installedPackage, "package.json"));
|
|
107934
|
+
if (installed.version !== version2 || JSON.stringify(Object.entries(installed.bins).sort()) !== JSON.stringify(Object.entries(layout.bin).sort())) {
|
|
107935
|
+
throw new Error("INSTALLED_PACKAGE_IDENTITY_MISMATCH");
|
|
107936
|
+
}
|
|
107937
|
+
const unpackedPackage = join54(stagePath, "verified-archive", "package");
|
|
107938
|
+
const packageTreeSha256 = comparePackageTrees(unpackedPackage, installedPackage);
|
|
107939
|
+
if (packageTreeSha256 !== artifact.packageTreeSha256)
|
|
107940
|
+
throw new Error("PACKAGE_TREE_HASH_MISMATCH");
|
|
107941
|
+
const nodeModules = join54(stagePackage, "node_modules");
|
|
107942
|
+
normalizeModes(nodeModules);
|
|
107943
|
+
chmodSync4(stagePath, 448);
|
|
107944
|
+
const runtimeTree = readTree(nodeModules, true);
|
|
107945
|
+
if (runtimeTree.entries.some((entry) => entry.kind === "file" && ((entry.mode ?? 0) & 18) !== 0)) {
|
|
107946
|
+
throw new Error("RUNTIME_FILE_MODE_UNSAFE");
|
|
107947
|
+
}
|
|
107948
|
+
assertConfigUnchanged(configPreimages);
|
|
107949
|
+
const targetPackageRoot = join54(finalPath, "node_modules", "@hasna", "skills");
|
|
107950
|
+
const preimageSha256 = writePreimageManifest(stagePath, configPreimages, launchers);
|
|
107951
|
+
const receipt = {
|
|
107952
|
+
schema: "skills.copyfile-runtime-receipt.v1",
|
|
107953
|
+
id: id2,
|
|
107954
|
+
state: "prepared",
|
|
107955
|
+
package: PACKAGE_NAME,
|
|
107956
|
+
currentVersion: layout.currentVersion,
|
|
107957
|
+
targetVersion: version2,
|
|
107958
|
+
registryOrigin: origin.origin,
|
|
107959
|
+
tarballUrl: artifact.tarballUrl,
|
|
107960
|
+
tarballIntegrity: artifact.integrity,
|
|
107961
|
+
tarballSha256: artifact.sha256,
|
|
107962
|
+
tarballBytes: artifact.byteSize,
|
|
107963
|
+
packageTreeSha256,
|
|
107964
|
+
installLockSha256: hash5(lockBytes),
|
|
107965
|
+
runtimeTreeSha256: runtimeTree.digest,
|
|
107966
|
+
runtimeRoot: layout.runtimeRoot,
|
|
107967
|
+
currentPackageRoot: layout.currentPackageRoot,
|
|
107968
|
+
targetPackageRoot,
|
|
107969
|
+
configs: configPreimages,
|
|
107970
|
+
preimageSha256,
|
|
107971
|
+
launchers,
|
|
107972
|
+
switchedLaunchers: [],
|
|
107973
|
+
rollbackCompletedLaunchers: []
|
|
107974
|
+
};
|
|
107975
|
+
writeJsonPrivate(join54(stagePath, "rollout-receipt.json"), receipt);
|
|
107976
|
+
const finalNodeModules = join54(stagePackage, "node_modules");
|
|
107977
|
+
const stagedNodeModules = join54(stagePath, "node_modules");
|
|
107978
|
+
renameSync13(finalNodeModules, stagedNodeModules);
|
|
107979
|
+
copyFileSync(join54(stagePackage, "package-lock.json"), join54(stagePath, "install-lock.json"));
|
|
107980
|
+
chmodSync4(join54(stagePath, "install-lock.json"), 384);
|
|
107981
|
+
const stagedTree = readTree(stagedNodeModules, true);
|
|
107982
|
+
if (stagedTree.digest !== runtimeTree.digest)
|
|
107983
|
+
throw new Error("STAGED_RUNTIME_READBACK_MISMATCH");
|
|
107984
|
+
receipt.targetPackageRoot = join54(finalPath, "node_modules", "@hasna", "skills");
|
|
107985
|
+
receipt.runtimeTreeSha256 = stagedTree.digest;
|
|
107986
|
+
persistReceipt(join54(stagePath, "rollout-receipt.json"), receipt);
|
|
107987
|
+
renameSync13(stagePath, finalPath);
|
|
107988
|
+
moved = true;
|
|
107989
|
+
const finalReceiptBytes = Buffer.from(`${JSON.stringify(receipt, null, 2)}
|
|
107990
|
+
`);
|
|
107991
|
+
if (!readFileSync37(join54(finalPath, "rollout-receipt.json")).equals(finalReceiptBytes))
|
|
107992
|
+
throw new Error("RECEIPT_MOVE_READBACK_MISMATCH");
|
|
107993
|
+
assertConfigUnchanged(configPreimages);
|
|
107994
|
+
for (const item of launchers) {
|
|
107995
|
+
if (realpathSync11(item.path) !== item.oldTarget)
|
|
107996
|
+
throw new Error("LAUNCHER_DRIFT_BEFORE_SWITCH");
|
|
107997
|
+
receipt.state = "switching";
|
|
107998
|
+
receipt.switchedLaunchers = [...receipt.switchedLaunchers, item.path];
|
|
107999
|
+
persistReceipt(join54(finalPath, "rollout-receipt.json"), receipt);
|
|
108000
|
+
replaceLink(item.path, item.newTarget, item.backupPath, item.oldLinkTarget);
|
|
108001
|
+
if (realpathSync11(item.path) !== item.newTarget || realpathSync11(item.backupPath) !== item.oldTarget) {
|
|
108002
|
+
throw new Error("LAUNCHER_SWITCH_READBACK_MISMATCH");
|
|
108003
|
+
}
|
|
108004
|
+
switched.push(item.path);
|
|
108005
|
+
options.onLauncherSwitched?.(item.path);
|
|
108006
|
+
}
|
|
108007
|
+
receipt.state = "switched";
|
|
108008
|
+
persistReceipt(join54(finalPath, "rollout-receipt.json"), receipt);
|
|
108009
|
+
return { updated: true, version: version2, currentVersion: layout.currentVersion, receiptId: id2, runtimeRoot: finalPath, tarballSha256: artifact.sha256, tarballIntegrity: artifact.integrity, runtimeTreeSha256: stagedTree.digest, launcherCount: launchers.length, configCount: configPreimages.length };
|
|
108010
|
+
} catch (error61) {
|
|
108011
|
+
if (switched.length > 0 || moved) {
|
|
108012
|
+
let rollbackFailed = false;
|
|
108013
|
+
for (const item of [...launchers].reverse()) {
|
|
108014
|
+
try {
|
|
108015
|
+
const currentTarget = realpathSync11(item.path);
|
|
108016
|
+
if (currentTarget === item.oldTarget && readlinkSync4(item.path) === item.oldLinkTarget)
|
|
108017
|
+
continue;
|
|
108018
|
+
if (currentTarget !== item.newTarget)
|
|
108019
|
+
throw new Error("LAUNCHER_CHANGED_DURING_ROLLBACK");
|
|
108020
|
+
const temp = `${item.path}.skills-rollback-${id2}`;
|
|
108021
|
+
const afterPath = `${item.path}.skills-after-${id2}`;
|
|
108022
|
+
if (entryExists(afterPath))
|
|
108023
|
+
throw new Error("ROLLBACK_PRESERVATION_PATH_EXISTS");
|
|
108024
|
+
symlinkSync(item.newTarget, afterPath);
|
|
108025
|
+
symlinkSync(item.oldLinkTarget, temp);
|
|
108026
|
+
renameSync13(temp, item.path);
|
|
108027
|
+
} catch {
|
|
108028
|
+
rollbackFailed = true;
|
|
108029
|
+
}
|
|
108030
|
+
}
|
|
108031
|
+
try {
|
|
108032
|
+
const receiptPath = join54(finalPath, "rollout-receipt.json");
|
|
108033
|
+
const receipt = JSON.parse(readFileSync37(receiptPath, "utf8"));
|
|
108034
|
+
receipt.state = rollbackFailed ? "rollback-required" : "rolled-back";
|
|
108035
|
+
persistReceipt(receiptPath, receipt);
|
|
108036
|
+
} catch {
|
|
108037
|
+
rollbackFailed = true;
|
|
108038
|
+
}
|
|
108039
|
+
throw new Error(rollbackFailed ? "UPDATE_FAILED_ROLLBACK_REQUIRED" : "UPDATE_FAILED_ROLLED_BACK");
|
|
108040
|
+
}
|
|
108041
|
+
throw error61 instanceof Error ? error61 : new Error("COPYFILE_UPDATE_FAILED");
|
|
108042
|
+
} finally {
|
|
108043
|
+
releaseLock();
|
|
108044
|
+
}
|
|
108045
|
+
}
|
|
108046
|
+
function rollbackCopyfileRuntime(receiptId, options = {}) {
|
|
108047
|
+
if (!/^[0-9a-f-]{36}$/.test(receiptId))
|
|
108048
|
+
throw new Error("RECEIPT_ID_INVALID");
|
|
108049
|
+
const home = resolve22(options.homeDir ?? process.env.HOME ?? "");
|
|
108050
|
+
if (!home || !existsSync33(home))
|
|
108051
|
+
throw new Error("HOME_NOT_FOUND");
|
|
108052
|
+
const runtimeRoot = join54(realpathSync11(home), ".hasna", "skills", "runtime");
|
|
108053
|
+
assertNoSymlinkAncestors(runtimeRoot);
|
|
108054
|
+
const rootStat = lstatSync27(runtimeRoot);
|
|
108055
|
+
if (!rootStat.isDirectory() || rootStat.isSymbolicLink() || (rootStat.mode & 63) !== 0)
|
|
108056
|
+
throw new Error("RUNTIME_ROOT_NOT_PRIVATE");
|
|
108057
|
+
const releaseLock = acquireRuntimeLock(runtimeRoot);
|
|
108058
|
+
try {
|
|
108059
|
+
const matches2 = readdirSync20(runtimeRoot).filter((name) => {
|
|
108060
|
+
if (!/^\d+\.\d+\.\d+-copyfile$/.test(name))
|
|
108061
|
+
return false;
|
|
108062
|
+
const receipt2 = join54(runtimeRoot, name, "rollout-receipt.json");
|
|
108063
|
+
if (!existsSync33(receipt2))
|
|
108064
|
+
return false;
|
|
108065
|
+
try {
|
|
108066
|
+
return JSON.parse(readFileSync37(receipt2, "utf8")).id === receiptId;
|
|
108067
|
+
} catch {
|
|
108068
|
+
return false;
|
|
108069
|
+
}
|
|
108070
|
+
});
|
|
108071
|
+
if (matches2.length !== 1)
|
|
108072
|
+
throw new Error("RECEIPT_NOT_FOUND_OR_AMBIGUOUS");
|
|
108073
|
+
const receiptPath = join54(runtimeRoot, matches2[0], "rollout-receipt.json");
|
|
108074
|
+
const receipt = object8(JSON.parse(readFileSync37(receiptPath, "utf8")), "RECEIPT_INVALID");
|
|
108075
|
+
if (receipt.schema !== "skills.copyfile-runtime-receipt.v1" || !["switched", "switching", "rollback-required"].includes(receipt.state) || receipt.runtimeRoot !== runtimeRoot) {
|
|
108076
|
+
throw new Error("RECEIPT_STATE_INVALID");
|
|
108077
|
+
}
|
|
108078
|
+
if (receipt.package !== PACKAGE_NAME || matches2[0] !== `${receipt.targetVersion}-copyfile` || !new RegExp(SEMVER_PATTERN).test(receipt.currentVersion) || !new RegExp(SEMVER_PATTERN).test(receipt.targetVersion))
|
|
108079
|
+
throw new Error("RECEIPT_BINDING_INVALID");
|
|
108080
|
+
const runtimePath = join54(runtimeRoot, matches2[0]);
|
|
108081
|
+
if (receipt.targetPackageRoot !== join54(runtimePath, "node_modules", "@hasna", "skills") || receipt.currentPackageRoot !== join54(runtimeRoot, `${receipt.currentVersion}-copyfile`, "node_modules", "@hasna", "skills"))
|
|
108082
|
+
throw new Error("RECEIPT_RUNTIME_BINDING_INVALID");
|
|
108083
|
+
if (readTree(join54(runtimePath, "node_modules"), true).digest !== receipt.runtimeTreeSha256) {
|
|
108084
|
+
throw new Error("ACTIVE_RUNTIME_TREE_DRIFT");
|
|
108085
|
+
}
|
|
108086
|
+
verifyPreimageManifest(runtimePath, receipt, home);
|
|
108087
|
+
const oldBins = readPackage(join54(receipt.currentPackageRoot, "package.json")).bins;
|
|
108088
|
+
const newPackage = readPackage(join54(receipt.targetPackageRoot, "package.json"));
|
|
108089
|
+
if (newPackage.version !== receipt.targetVersion || JSON.stringify(Object.entries(oldBins).sort()) !== JSON.stringify(Object.entries(newPackage.bins).sort()))
|
|
108090
|
+
throw new Error("RECEIPT_BIN_BINDING_INVALID");
|
|
108091
|
+
const switched = [];
|
|
108092
|
+
const seen = new Set;
|
|
108093
|
+
for (const item of receipt.launchers) {
|
|
108094
|
+
const bin = oldBins[basename13(item.path)];
|
|
108095
|
+
if (!bin || !isAbsolute22(item.path) || seen.has(item.path) || item.oldTarget !== join54(receipt.currentPackageRoot, bin) || item.newTarget !== join54(receipt.targetPackageRoot, bin) || !/^.+\.skills-prev-[0-9a-f-]{36}$/.test(item.backupPath) || !item.backupPath.startsWith(`${item.path}.skills-prev-`) || typeof item.oldLinkTarget !== "string" || resolve22(dirname29(item.path), item.oldLinkTarget) !== item.oldTarget)
|
|
108096
|
+
throw new Error("RECEIPT_LAUNCHER_BINDING_INVALID");
|
|
108097
|
+
assertNoSymlinkAncestors(dirname29(item.path));
|
|
108098
|
+
seen.add(item.path);
|
|
108099
|
+
const stat3 = lstatSync27(item.path);
|
|
108100
|
+
if (!stat3.isSymbolicLink())
|
|
108101
|
+
throw new Error("LAUNCHER_DRIFT_ROLLBACK_REFUSED");
|
|
108102
|
+
const activeTarget = realpathSync11(item.path);
|
|
108103
|
+
if (activeTarget !== item.newTarget && (activeTarget !== item.oldTarget || readlinkSync4(item.path) !== item.oldLinkTarget))
|
|
108104
|
+
throw new Error("LAUNCHER_DRIFT_ROLLBACK_REFUSED");
|
|
108105
|
+
const hasBackup = entryExists(item.backupPath);
|
|
108106
|
+
if (activeTarget === item.newTarget && !hasBackup)
|
|
108107
|
+
throw new Error("LAUNCHER_PREIMAGE_DRIFT_ROLLBACK_REFUSED");
|
|
108108
|
+
if (hasBackup) {
|
|
108109
|
+
const backup = lstatSync27(item.backupPath);
|
|
108110
|
+
if (!backup.isSymbolicLink() || readlinkSync4(item.backupPath) !== item.oldLinkTarget || realpathSync11(item.backupPath) !== item.oldTarget)
|
|
108111
|
+
throw new Error("LAUNCHER_PREIMAGE_DRIFT_ROLLBACK_REFUSED");
|
|
108112
|
+
}
|
|
108113
|
+
const afterPath = `${item.path}.skills-after-${receiptId}`;
|
|
108114
|
+
if (entryExists(afterPath)) {
|
|
108115
|
+
const after2 = lstatSync27(afterPath);
|
|
108116
|
+
if (!after2.isSymbolicLink() || realpathSync11(afterPath) !== item.newTarget)
|
|
108117
|
+
throw new Error("ROLLBACK_PRESERVATION_DRIFT");
|
|
108118
|
+
}
|
|
108119
|
+
switched.push(item);
|
|
108120
|
+
}
|
|
108121
|
+
for (const item of switched.reverse()) {
|
|
108122
|
+
const activeTarget = realpathSync11(item.path);
|
|
108123
|
+
if (activeTarget === item.oldTarget && readlinkSync4(item.path) === item.oldLinkTarget)
|
|
108124
|
+
continue;
|
|
108125
|
+
receipt.state = "rollback-required";
|
|
108126
|
+
persistReceipt(receiptPath, receipt);
|
|
108127
|
+
const afterPath = `${item.path}.skills-after-${receiptId}`;
|
|
108128
|
+
if (!entryExists(afterPath))
|
|
108129
|
+
symlinkSync(item.newTarget, afterPath);
|
|
108130
|
+
const temp = `${item.path}.skills-rollback-${receiptId}-${randomUUID9()}`;
|
|
108131
|
+
symlinkSync(item.oldLinkTarget, temp);
|
|
108132
|
+
renameSync13(temp, item.path);
|
|
108133
|
+
receipt.rollbackCompletedLaunchers = [...new Set([...receipt.rollbackCompletedLaunchers, item.path])];
|
|
108134
|
+
persistReceipt(receiptPath, receipt);
|
|
108135
|
+
}
|
|
108136
|
+
receipt.state = "rolled-back";
|
|
108137
|
+
persistReceipt(receiptPath, receipt);
|
|
108138
|
+
return { rolledBack: true, receiptId, restoredVersion: receipt.currentVersion, launcherCount: switched.length };
|
|
108139
|
+
} finally {
|
|
108140
|
+
releaseLock();
|
|
108141
|
+
}
|
|
108142
|
+
}
|
|
108143
|
+
var PACKAGE_NAME = "@hasna/skills", REGISTRY_ORIGIN = "https://registry.npmjs.org", STABLE_SEMVER_PATTERN, MAX_TARBALL_BYTES, MAX_TARBALL_EXPANDED_BYTES, MAX_TARBALL_ENTRIES = 20000, CONFIG_PREIMAGE_PATHS;
|
|
108144
|
+
var init_runtime_copyfile = __esm(() => {
|
|
108145
|
+
init_skill_contract();
|
|
108146
|
+
STABLE_SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
108147
|
+
MAX_TARBALL_BYTES = 100 * 1024 * 1024;
|
|
108148
|
+
MAX_TARBALL_EXPANDED_BYTES = 256 * 1024 * 1024;
|
|
108149
|
+
CONFIG_PREIMAGE_PATHS = [
|
|
108150
|
+
".claude/settings.json",
|
|
108151
|
+
".codex/config.toml",
|
|
108152
|
+
".codex/hooks.json",
|
|
108153
|
+
".hasna/skills/agent-policy.json"
|
|
108154
|
+
];
|
|
108155
|
+
});
|
|
108156
|
+
|
|
107311
108157
|
// src/cli/commands/runtime.ts
|
|
107312
108158
|
var exports_runtime = {};
|
|
107313
108159
|
__export(exports_runtime, {
|
|
107314
108160
|
registerRuntime: () => registerRuntime
|
|
107315
108161
|
});
|
|
107316
|
-
import { createHash as
|
|
107317
|
-
import { lstatSync as
|
|
107318
|
-
import { basename as
|
|
108162
|
+
import { createHash as createHash27, randomUUID as randomUUID10 } from "crypto";
|
|
108163
|
+
import { lstatSync as lstatSync28, mkdirSync as mkdirSync25, readFileSync as readFileSync38, realpathSync as realpathSync12, writeFileSync as writeFileSync24 } from "fs";
|
|
108164
|
+
import { basename as basename14, isAbsolute as isAbsolute23, join as join55 } from "path";
|
|
107319
108165
|
import { createInterface } from "readline";
|
|
107320
108166
|
function registerRuntime(parent) {
|
|
107321
108167
|
parent.command("run").argument("<skill>", "Skill name").argument("[args...]", "Arguments to pass to the skill; use -- to end Skills options").allowUnknownOption(true).passThroughOptions(true).option("--json", "Output result as JSON", false).option("--remote", "Run on the configured server, using its catalog and quote", false).option("--target <target>", "Execution target: local or cloud").option("--secret-bindings <path>", "Reviewed, exact-selection local execution vault bindings (JSON file)").option("--secret-bindings-template", "Print a scoped bindings template without resolving secrets or running the skill", false).option("--input <json>", "Structured cloud execution input").option("--skill-version <version>", "Exact selected skill version").option("--selection-profile <id>", "Selected Skills profile").option("--session <id>", "Pinned Skills session").option("--cached", "Use the explicit verified selected cache", false).option("--yes", "Approve the server's quoted credit cost for this run", false).option("--idempotency-key <key>", "Reuse this key when retrying the same remote submission").option("--file <path>", "Attach a local input file to a remote run (repeatable)", (value, prior) => [...prior, value], []).option("--wait", "Poll remote runs until a terminal status", false).option("--poll-interval-ms <ms>", "Remote polling interval in milliseconds", "1000").option("--poll-timeout-ms <ms>", "Maximum time to wait for a remote run", "300000").description("Run a skill directly").action(async (name, args2, options, command) => {
|
|
@@ -107354,9 +108200,9 @@ function registerRuntime(parent) {
|
|
|
107354
108200
|
if (!expected)
|
|
107355
108201
|
throw new Error("Cloud artifact not found");
|
|
107356
108202
|
const bytes = await client2.download(id2, name);
|
|
107357
|
-
if (bytes.length !== expected.byteSize ||
|
|
108203
|
+
if (bytes.length !== expected.byteSize || createHash27("sha256").update(bytes).digest("hex") !== expected.sha256)
|
|
107358
108204
|
throw new Error("Cloud artifact integrity verification failed");
|
|
107359
|
-
|
|
108205
|
+
writeFileSync24(options.output, bytes, { flag: "wx", mode: 384 });
|
|
107360
108206
|
console.log(JSON.stringify({ path: options.output, sha256: expected.sha256, byteSize: bytes.length }));
|
|
107361
108207
|
} catch (error61) {
|
|
107362
108208
|
console.error(JSON.stringify({ error: error61.message }));
|
|
@@ -107384,7 +108230,40 @@ function registerRuntime(parent) {
|
|
|
107384
108230
|
});
|
|
107385
108231
|
const setup = parent.command("setup").description("Point this CLI at a Skills API server, or register agent integrations").option("--api-url <url>", "Skills API origin to send remote work to").option("--global", "Accepted for compatibility; the API origin is always per-user", false).option("--json", "Output setup result as JSON", false).action(async (options) => handleSetup(options));
|
|
107386
108232
|
setup.command("agents").option("--json", "Output registration result as JSON", false).description("Register the Skills MCP server with all supported agents").action(async (options) => handleMcp({ register: "all", json: options.json }));
|
|
107387
|
-
parent.command("self-update").description("Update @hasna/skills to the latest version").option("--json", "Output result as JSON", false).action(async (options) => {
|
|
108233
|
+
parent.command("self-update").description("Update @hasna/skills to the latest version").option("--json", "Output result as JSON", false).option("--version <version>", "Install this exact @hasna/skills version as a staged copyfile runtime").option("--rollback <receipt-id>", "Restore launcher links from an exact copyfile rollout receipt").action(async (options) => {
|
|
108234
|
+
if (options.version && options.rollback) {
|
|
108235
|
+
console.error(JSON.stringify({ error: "UPDATE_AND_ROLLBACK_ARE_MUTUALLY_EXCLUSIVE" }));
|
|
108236
|
+
process.exitCode = 1;
|
|
108237
|
+
return;
|
|
108238
|
+
}
|
|
108239
|
+
if (options.version) {
|
|
108240
|
+
try {
|
|
108241
|
+
const result2 = await updateCopyfileRuntime(options.version);
|
|
108242
|
+
if (options.json)
|
|
108243
|
+
console.log(JSON.stringify(result2));
|
|
108244
|
+
else
|
|
108245
|
+
console.log(source_default.green(`Updated @hasna/skills to exact version ${options.version} via copyfile runtime.`));
|
|
108246
|
+
} catch (error61) {
|
|
108247
|
+
const message = error61 instanceof Error && /^[A-Z0-9_]+$/.test(error61.message) ? error61.message : "COPYFILE_UPDATE_FAILED";
|
|
108248
|
+
console.error(JSON.stringify({ updated: false, error: message }));
|
|
108249
|
+
process.exitCode = 1;
|
|
108250
|
+
}
|
|
108251
|
+
return;
|
|
108252
|
+
}
|
|
108253
|
+
if (options.rollback) {
|
|
108254
|
+
try {
|
|
108255
|
+
const result2 = rollbackCopyfileRuntime(options.rollback);
|
|
108256
|
+
if (options.json)
|
|
108257
|
+
console.log(JSON.stringify(result2));
|
|
108258
|
+
else
|
|
108259
|
+
console.log(source_default.green(`Restored Skills runtime launchers to ${String(result2.restoredVersion)}.`));
|
|
108260
|
+
} catch (error61) {
|
|
108261
|
+
const message = error61 instanceof Error && /^[A-Z0-9_]+$/.test(error61.message) ? error61.message : "COPYFILE_ROLLBACK_FAILED";
|
|
108262
|
+
console.error(JSON.stringify({ rolledBack: false, error: message }));
|
|
108263
|
+
process.exitCode = 1;
|
|
108264
|
+
}
|
|
108265
|
+
return;
|
|
108266
|
+
}
|
|
107388
108267
|
if (process.env.SKILLS_TEST_MODE === "1") {
|
|
107389
108268
|
if (options.json)
|
|
107390
108269
|
console.log(JSON.stringify({ updated: false, error: "Self-update disabled in test mode" }));
|
|
@@ -107442,14 +108321,14 @@ Updating ${name}...
|
|
|
107442
108321
|
async function readUpdatedVersion(bunExecutable) {
|
|
107443
108322
|
const output2 = await readUpdateCommand([bunExecutable, "pm", "bin", "-g"]);
|
|
107444
108323
|
const globalBin = output2.replace(/\r?\n$/, "");
|
|
107445
|
-
if (!globalBin || !
|
|
108324
|
+
if (!globalBin || !isAbsolute23(globalBin) || /[\r\n\0]/.test(globalBin))
|
|
107446
108325
|
throw new Error("Update bin discovery failed");
|
|
107447
108326
|
const installed = Bun.which("skills", { PATH: globalBin, cwd: globalBin });
|
|
107448
108327
|
const selected = Bun.which("skills");
|
|
107449
108328
|
if (!installed || !selected)
|
|
107450
108329
|
throw new Error("Updated command is unavailable");
|
|
107451
|
-
const selectedPath =
|
|
107452
|
-
if (selectedPath !==
|
|
108330
|
+
const selectedPath = realpathSync12(selected);
|
|
108331
|
+
if (selectedPath !== realpathSync12(installed))
|
|
107453
108332
|
throw new Error("Updated command is shadowed on PATH");
|
|
107454
108333
|
const version2 = (await readUpdateCommand([selectedPath, "--version"])).trim();
|
|
107455
108334
|
if (!new RegExp(SEMVER_PATTERN).test(version2))
|
|
@@ -107580,14 +108459,14 @@ function requireHttpUrl(value) {
|
|
|
107580
108459
|
}
|
|
107581
108460
|
function promptLine(question) {
|
|
107582
108461
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
107583
|
-
return new Promise((
|
|
108462
|
+
return new Promise((resolve23) => {
|
|
107584
108463
|
let settled = false;
|
|
107585
108464
|
const finish = (answer) => {
|
|
107586
108465
|
if (settled)
|
|
107587
108466
|
return;
|
|
107588
108467
|
settled = true;
|
|
107589
108468
|
rl.close();
|
|
107590
|
-
|
|
108469
|
+
resolve23(answer);
|
|
107591
108470
|
};
|
|
107592
108471
|
rl.once("SIGINT", () => finish(null));
|
|
107593
108472
|
rl.once("close", () => finish(null));
|
|
@@ -107707,7 +108586,7 @@ async function handleRun(name, args2, options, separatorBeforeSkill) {
|
|
|
107707
108586
|
if (!Number.isFinite(timeoutMs) || timeoutMs < 1 || timeoutMs > 1200000 || !Number.isFinite(intervalMs) || intervalMs < 100 || intervalMs > 60000)
|
|
107708
108587
|
throw new Error("Invalid cloud polling bounds");
|
|
107709
108588
|
const client3 = await CloudExecutionClient.configured();
|
|
107710
|
-
const admitted = await client3.submit(slug2, version2, input2, options.idempotencyKey ??
|
|
108589
|
+
const admitted = await client3.submit(slug2, version2, input2, options.idempotencyKey ?? randomUUID10(), selected?.selection);
|
|
107711
108590
|
if (options.wait)
|
|
107712
108591
|
console.error(JSON.stringify({ executionId: admitted.id, status: admitted.status }));
|
|
107713
108592
|
const result3 = options.wait ? await client3.wait(admitted.id, { timeoutMs, intervalMs }) : admitted;
|
|
@@ -107764,10 +108643,10 @@ async function handleRun(name, args2, options, separatorBeforeSkill) {
|
|
|
107764
108643
|
try {
|
|
107765
108644
|
parsePollingOptions(options);
|
|
107766
108645
|
inputFiles = (options.file ?? []).map((path) => {
|
|
107767
|
-
const info =
|
|
108646
|
+
const info = lstatSync28(path);
|
|
107768
108647
|
if (!info.isFile() || info.size > 20 * 1024 * 1024)
|
|
107769
108648
|
throw new Error("Input must be a regular file no larger than 20 MiB");
|
|
107770
|
-
return { name:
|
|
108649
|
+
return { name: basename14(path), bytes: new Uint8Array(readFileSync38(path)) };
|
|
107771
108650
|
});
|
|
107772
108651
|
describeRemoteFiles(inputFiles);
|
|
107773
108652
|
client2 = new RemoteSkillsClient(routing.apiKey, routing.apiOrigin);
|
|
@@ -108145,7 +109024,7 @@ async function handleExportsDownload(runId2, options) {
|
|
|
108145
109024
|
const canonicalSkill = typeof remoteRun.skill === "string" ? remoteRun.skill : "remote";
|
|
108146
109025
|
const requestedSkill = typeof remoteRun.requestedSlug === "string" && remoteRun.requestedSlug.trim() ? remoteRun.requestedSlug : canonicalSkill;
|
|
108147
109026
|
const exportDir = getRunExportDir(runId2, requestedSkill);
|
|
108148
|
-
|
|
109027
|
+
mkdirSync25(exportDir, { recursive: true });
|
|
108149
109028
|
const downloaded = [];
|
|
108150
109029
|
for (const artifact of artifacts) {
|
|
108151
109030
|
const artifactId = String(artifact.id || "");
|
|
@@ -108153,9 +109032,9 @@ async function handleExportsDownload(runId2, options) {
|
|
|
108153
109032
|
continue;
|
|
108154
109033
|
const verified = await client2.getVerifiedRunArtifact(runId2, artifactId);
|
|
108155
109034
|
const relativePath = safeArtifactRelativePath(typeof artifact.relativePath === "string" ? artifact.relativePath : artifact.fileName, String(artifact.fileName || artifactId));
|
|
108156
|
-
const outputPath =
|
|
109035
|
+
const outputPath = join55(exportDir, relativePath);
|
|
108157
109036
|
ensureSafeExportParent(exportDir, relativePath);
|
|
108158
|
-
|
|
109037
|
+
writeFileSync24(outputPath, verified.bytes, { flag: "wx", mode: 384 });
|
|
108159
109038
|
downloaded.push({ id: artifactId, path: outputPath, byteSize: verified.byteSize });
|
|
108160
109039
|
}
|
|
108161
109040
|
const payload = {
|
|
@@ -108192,14 +109071,14 @@ function ensureSafeExportParent(root2, relativePath) {
|
|
|
108192
109071
|
let parent = root2;
|
|
108193
109072
|
for (const part of ["", ...parts]) {
|
|
108194
109073
|
if (part)
|
|
108195
|
-
parent =
|
|
109074
|
+
parent = join55(parent, part);
|
|
108196
109075
|
try {
|
|
108197
|
-
if (!
|
|
109076
|
+
if (!lstatSync28(parent).isDirectory() || lstatSync28(parent).isSymbolicLink())
|
|
108198
109077
|
throw new Error("Unsafe artifact directory");
|
|
108199
109078
|
} catch (error61) {
|
|
108200
109079
|
if (error61.code !== "ENOENT")
|
|
108201
109080
|
throw error61;
|
|
108202
|
-
|
|
109081
|
+
mkdirSync25(parent, { mode: 448 });
|
|
108203
109082
|
}
|
|
108204
109083
|
}
|
|
108205
109084
|
}
|
|
@@ -108232,7 +109111,7 @@ async function pollRemoteRun(client2, runId2, options) {
|
|
|
108232
109111
|
const remaining = deadline - Date.now();
|
|
108233
109112
|
if (remaining <= 0)
|
|
108234
109113
|
break;
|
|
108235
|
-
await new Promise((
|
|
109114
|
+
await new Promise((resolve23) => setTimeout(resolve23, Math.min(options.intervalMs, remaining)));
|
|
108236
109115
|
}
|
|
108237
109116
|
return {
|
|
108238
109117
|
run: current ?? { id: runId2, status: "queued" },
|
|
@@ -108403,6 +109282,7 @@ var init_runtime = __esm(() => {
|
|
|
108403
109282
|
init_remote_client();
|
|
108404
109283
|
init_remote_account2();
|
|
108405
109284
|
init_remote_files();
|
|
109285
|
+
init_runtime_copyfile();
|
|
108406
109286
|
});
|
|
108407
109287
|
|
|
108408
109288
|
// src/cli/commands/customer-verification.ts
|
|
@@ -108424,7 +109304,7 @@ async function readCode() {
|
|
|
108424
109304
|
function promptCode() {
|
|
108425
109305
|
const { stdin, stderr: output2 } = process;
|
|
108426
109306
|
const { isRaw: wasRaw, readableFlowing: wasFlowing } = stdin;
|
|
108427
|
-
return new Promise((
|
|
109307
|
+
return new Promise((resolve23) => {
|
|
108428
109308
|
let value = "", settled = false;
|
|
108429
109309
|
const finish = (answer) => {
|
|
108430
109310
|
if (settled)
|
|
@@ -108441,7 +109321,7 @@ function promptCode() {
|
|
|
108441
109321
|
`);
|
|
108442
109322
|
if (answer === null)
|
|
108443
109323
|
process.exitCode = 130;
|
|
108444
|
-
|
|
109324
|
+
resolve23(answer);
|
|
108445
109325
|
};
|
|
108446
109326
|
const cancel = () => finish(null);
|
|
108447
109327
|
const keypress = (text4, key) => {
|
|
@@ -108486,7 +109366,7 @@ var exports_recurring = {};
|
|
|
108486
109366
|
__export(exports_recurring, {
|
|
108487
109367
|
registerRecurringCommands: () => registerRecurringCommands
|
|
108488
109368
|
});
|
|
108489
|
-
import { constants as constants19, closeSync as closeSync22, fstatSync as fstatSync22, lstatSync as
|
|
109369
|
+
import { constants as constants19, closeSync as closeSync22, fstatSync as fstatSync22, lstatSync as lstatSync29, openSync as openSync22, readSync as readSync16 } from "fs";
|
|
108490
109370
|
import { createInterface as createInterface2 } from "readline";
|
|
108491
109371
|
function readRequest(path) {
|
|
108492
109372
|
const fd = openSync22(path, constants19.O_RDONLY | constants19.O_NOFOLLOW | constants19.O_NONBLOCK);
|
|
@@ -108502,7 +109382,7 @@ function readRequest(path) {
|
|
|
108502
109382
|
break;
|
|
108503
109383
|
length += n2;
|
|
108504
109384
|
}
|
|
108505
|
-
const after2 = fstatSync22(fd), current =
|
|
109385
|
+
const after2 = fstatSync22(fd), current = lstatSync29(path);
|
|
108506
109386
|
if (length !== before2.size || after2.size !== before2.size || before2.mtimeMs !== after2.mtimeMs || before2.ctimeMs !== after2.ctimeMs || current.dev !== before2.dev || current.ino !== before2.ino || !current.isFile() || current.isSymbolicLink())
|
|
108507
109387
|
throw new RecurringInputError;
|
|
108508
109388
|
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, length)));
|
|
@@ -108513,10 +109393,10 @@ function readRequest(path) {
|
|
|
108513
109393
|
}
|
|
108514
109394
|
}
|
|
108515
109395
|
async function acceptAtTerminal(draft) {
|
|
108516
|
-
await new Promise((
|
|
109396
|
+
await new Promise((resolve23, reject2) => process.stderr.write(`${jsonText(draft)}
|
|
108517
109397
|
This grants an additional recurring budget. Each occurrence reprices within the approved ceilings. Revocation prevents new authority; an already-authorized attempt may finish, and cancellation is separate.
|
|
108518
|
-
`, (error61) => error61 ? reject2(error61) :
|
|
108519
|
-
return new Promise((
|
|
109398
|
+
`, (error61) => error61 ? reject2(error61) : resolve23()));
|
|
109399
|
+
return new Promise((resolve23) => {
|
|
108520
109400
|
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
108521
109401
|
let settled = false;
|
|
108522
109402
|
const finish = (accepted) => {
|
|
@@ -108527,7 +109407,7 @@ This grants an additional recurring budget. Each occurrence reprices within the
|
|
|
108527
109407
|
rl.close();
|
|
108528
109408
|
if (!accepted)
|
|
108529
109409
|
process.exitCode = 130;
|
|
108530
|
-
|
|
109410
|
+
resolve23(accepted);
|
|
108531
109411
|
};
|
|
108532
109412
|
const timer = setTimeout(() => finish(false), 5 * 60 * 1000);
|
|
108533
109413
|
rl.once("SIGINT", () => finish(false));
|
|
@@ -108786,9 +109666,9 @@ var init_completion = __esm(() => {
|
|
|
108786
109666
|
});
|
|
108787
109667
|
|
|
108788
109668
|
// src/lib/portable-snapshot-filter.ts
|
|
108789
|
-
import { readdirSync as
|
|
109669
|
+
import { readdirSync as readdirSync21, statSync as statSync18 } from "fs";
|
|
108790
109670
|
import { homedir as homedir11 } from "os";
|
|
108791
|
-
import { join as
|
|
109671
|
+
import { join as join56, sep as sep10 } from "path";
|
|
108792
109672
|
function isExcludedSkillFileName(fileName) {
|
|
108793
109673
|
if (EXCLUDE_FILE_NAMES.has(fileName)) {
|
|
108794
109674
|
return true;
|
|
@@ -108811,27 +109691,27 @@ function isPortableWithinSkill(relativeParts) {
|
|
|
108811
109691
|
function homePathFor(definition, homesRoot) {
|
|
108812
109692
|
const home = homesRoot ?? homedir11();
|
|
108813
109693
|
if (definition.subClass === "skills" || definition.subClass === "custom") {
|
|
108814
|
-
return
|
|
109694
|
+
return join56(skillsDataRootForHome(home), definition.name);
|
|
108815
109695
|
}
|
|
108816
109696
|
if (definition.agent === "opencode") {
|
|
108817
|
-
return
|
|
109697
|
+
return join56(home, ".config", "opencode", "skills");
|
|
108818
109698
|
}
|
|
108819
|
-
return
|
|
109699
|
+
return join56(home, `.${definition.agent}`, "skills");
|
|
108820
109700
|
}
|
|
108821
109701
|
function destinationFor(definition, stationId, relativePath) {
|
|
108822
|
-
const category = definition.subClass === "agent-homes" ?
|
|
108823
|
-
return
|
|
109702
|
+
const category = definition.subClass === "agent-homes" ? join56("agent-homes", definition.agent ?? "") : definition.name;
|
|
109703
|
+
return join56("resources", stationId, "skills", category, ...relativePath.split(sep10));
|
|
108824
109704
|
}
|
|
108825
109705
|
function walkEntries(absoluteRoot) {
|
|
108826
109706
|
let entries;
|
|
108827
109707
|
try {
|
|
108828
|
-
entries =
|
|
109708
|
+
entries = readdirSync21(absoluteRoot, { withFileTypes: true });
|
|
108829
109709
|
} catch {
|
|
108830
109710
|
return [];
|
|
108831
109711
|
}
|
|
108832
109712
|
const output2 = [];
|
|
108833
109713
|
for (const entry of entries) {
|
|
108834
|
-
const childFull =
|
|
109714
|
+
const childFull = join56(absoluteRoot, entry.name);
|
|
108835
109715
|
if (entry.isSymbolicLink()) {
|
|
108836
109716
|
output2.push({ kind: "symlink", relativePath: entry.name, fullPath: childFull });
|
|
108837
109717
|
continue;
|
|
@@ -108842,7 +109722,7 @@ function walkEntries(absoluteRoot) {
|
|
|
108842
109722
|
}
|
|
108843
109723
|
const nested = walkEntries(childFull);
|
|
108844
109724
|
for (const item of nested) {
|
|
108845
|
-
output2.push({ ...item, relativePath:
|
|
109725
|
+
output2.push({ ...item, relativePath: join56(entry.name, item.relativePath) });
|
|
108846
109726
|
}
|
|
108847
109727
|
continue;
|
|
108848
109728
|
}
|
|
@@ -108935,22 +109815,22 @@ var init_portable_snapshot_filter = __esm(() => {
|
|
|
108935
109815
|
});
|
|
108936
109816
|
|
|
108937
109817
|
// src/lib/station-snapshot.ts
|
|
108938
|
-
import { createHash as
|
|
109818
|
+
import { createHash as createHash28 } from "crypto";
|
|
108939
109819
|
import {
|
|
108940
|
-
copyFileSync,
|
|
108941
|
-
mkdirSync as
|
|
108942
|
-
readFileSync as
|
|
109820
|
+
copyFileSync as copyFileSync2,
|
|
109821
|
+
mkdirSync as mkdirSync26,
|
|
109822
|
+
readFileSync as readFileSync39,
|
|
108943
109823
|
statSync as statSync19,
|
|
108944
|
-
writeFileSync as
|
|
109824
|
+
writeFileSync as writeFileSync25
|
|
108945
109825
|
} from "fs";
|
|
108946
|
-
import { dirname as
|
|
109826
|
+
import { dirname as dirname31, isAbsolute as isAbsolute24, relative as relative9, resolve as resolve23, sep as sep11 } from "path";
|
|
108947
109827
|
function validateStationId(stationId) {
|
|
108948
109828
|
if (!/^[a-z0-9-]+$/.test(stationId)) {
|
|
108949
109829
|
throw new StationSnapshotError("INVALID_STATION", `station id must be a slug, got: ${stationId}`);
|
|
108950
109830
|
}
|
|
108951
109831
|
}
|
|
108952
109832
|
function sha256File(filePath) {
|
|
108953
|
-
return
|
|
109833
|
+
return createHash28("sha256").update(readFileSync39(filePath)).digest("hex");
|
|
108954
109834
|
}
|
|
108955
109835
|
function scanHome(definition, homesRoot) {
|
|
108956
109836
|
const homePath = homePathFor(definition, homesRoot);
|
|
@@ -108958,7 +109838,7 @@ function scanHome(definition, homesRoot) {
|
|
|
108958
109838
|
const portable = [];
|
|
108959
109839
|
const skipped = [];
|
|
108960
109840
|
for (const entry of entries) {
|
|
108961
|
-
const relativeParts = entry.relativePath.split(
|
|
109841
|
+
const relativeParts = entry.relativePath.split(sep11);
|
|
108962
109842
|
const fileName = relativeParts[relativeParts.length - 1];
|
|
108963
109843
|
if (entry.kind === "symlink") {
|
|
108964
109844
|
skipped.push({ relativePath: entry.relativePath, reason: "symlink" });
|
|
@@ -109021,7 +109901,7 @@ function humanHomes(scanned) {
|
|
|
109021
109901
|
}));
|
|
109022
109902
|
}
|
|
109023
109903
|
function writeStationSnapshot(options) {
|
|
109024
|
-
const repoRoot =
|
|
109904
|
+
const repoRoot = resolve23(options.repoRoot ?? process.cwd());
|
|
109025
109905
|
const { scanned, plans, totalBytes } = planStationSnapshot(options);
|
|
109026
109906
|
const manifestFiles = plans.map((plan) => ({
|
|
109027
109907
|
relativePath: plan.source.relativePath,
|
|
@@ -109047,9 +109927,9 @@ function writeStationSnapshot(options) {
|
|
|
109047
109927
|
const conflicts = [];
|
|
109048
109928
|
const untouched = [];
|
|
109049
109929
|
for (const plan of plans) {
|
|
109050
|
-
const destination =
|
|
109051
|
-
const destinationRelative =
|
|
109052
|
-
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(
|
|
109930
|
+
const destination = resolve23(repoRoot, plan.destination);
|
|
109931
|
+
const destinationRelative = relative9(repoRoot, destination);
|
|
109932
|
+
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep11) || isAbsolute24(destinationRelative)) {
|
|
109053
109933
|
throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
|
|
109054
109934
|
}
|
|
109055
109935
|
let existingDigest = null;
|
|
@@ -109070,9 +109950,9 @@ function writeStationSnapshot(options) {
|
|
|
109070
109950
|
}
|
|
109071
109951
|
let written = 0;
|
|
109072
109952
|
for (const plan of untouched) {
|
|
109073
|
-
const destination =
|
|
109074
|
-
|
|
109075
|
-
|
|
109953
|
+
const destination = resolve23(repoRoot, plan.destination);
|
|
109954
|
+
mkdirSync26(dirname31(destination), { recursive: true });
|
|
109955
|
+
copyFileSync2(plan.source.fullPath, destination);
|
|
109076
109956
|
written += 1;
|
|
109077
109957
|
}
|
|
109078
109958
|
const unchanged3 = plans.length - untouched.length;
|
|
@@ -109089,9 +109969,9 @@ function writeStationSnapshot(options) {
|
|
|
109089
109969
|
},
|
|
109090
109970
|
files: manifestFiles
|
|
109091
109971
|
};
|
|
109092
|
-
const manifestPath =
|
|
109093
|
-
|
|
109094
|
-
|
|
109972
|
+
const manifestPath = resolve23(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
|
|
109973
|
+
mkdirSync26(dirname31(manifestPath), { recursive: true });
|
|
109974
|
+
writeFileSync25(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
109095
109975
|
`);
|
|
109096
109976
|
return {
|
|
109097
109977
|
...base3,
|
|
@@ -109122,8 +110002,8 @@ var exports_create_sync_config = {};
|
|
|
109122
110002
|
__export(exports_create_sync_config, {
|
|
109123
110003
|
registerCreateSync: () => registerCreateSync
|
|
109124
110004
|
});
|
|
109125
|
-
import { existsSync as
|
|
109126
|
-
import { join as
|
|
110005
|
+
import { existsSync as existsSync34 } from "fs";
|
|
110006
|
+
import { join as join57 } from "path";
|
|
109127
110007
|
function registerCreateSync(parent) {
|
|
109128
110008
|
const configCmd = parent.command("config").description("Manage skills configuration");
|
|
109129
110009
|
configCmd.command("show", { isDefault: true }).option("--json", "Output as JSON", false).description("Show current merged configuration").action((options) => {
|
|
@@ -109203,13 +110083,13 @@ function registerCreateSync(parent) {
|
|
|
109203
110083
|
const pp = getConfigPath("project");
|
|
109204
110084
|
if (options.json) {
|
|
109205
110085
|
console.log(JSON.stringify({
|
|
109206
|
-
global: { path: gp, exists:
|
|
109207
|
-
project: { path: pp, exists:
|
|
110086
|
+
global: { path: gp, exists: existsSync34(gp) },
|
|
110087
|
+
project: { path: pp, exists: existsSync34(pp) }
|
|
109208
110088
|
}, null, 2));
|
|
109209
110089
|
return;
|
|
109210
110090
|
}
|
|
109211
|
-
console.log(`${source_default.cyan("global")}: ${gp}${
|
|
109212
|
-
console.log(`${source_default.cyan("project")}: ${pp}${
|
|
110091
|
+
console.log(`${source_default.cyan("global")}: ${gp}${existsSync34(gp) ? source_default.green(" (exists)") : source_default.dim(" (not found)")}`);
|
|
110092
|
+
console.log(`${source_default.cyan("project")}: ${pp}${existsSync34(pp) ? source_default.green(" (exists)") : source_default.dim(" (not found)")}`);
|
|
109213
110093
|
});
|
|
109214
110094
|
parent.command("create").argument("<name>", "Skill name (e.g. my-tool)").option("--category <category>", "Skill category", "Development Tools").option("--description <description>", "Short description of what the skill does").option("--kind <kind>", "Skill class: executable or instruction", "executable").option("--tags <tags>", "Comma-separated tags (e.g. api,testing,automation)").option("--global", "Deprecated; custom skills are always global", false).option("--json", "Output result as JSON", false).description("Scaffold a new custom skill directory").action((name, options) => handleCreate(name, options));
|
|
109215
110095
|
parent.command("sync").alias("render").option("--selection-profile <id>", "Consume the exact versions selected by an API profile").option("--profile <id>", "Selection profile (alias for --selection-profile; use before sync for credential profile)").option("--project", "Record exact selected versions in this project's lockfile", false).option("--no-station-report", "Verify and activate the selected profile without reporting station state").argument("[names...]", "Skills to sync (default: every skill in this machine's corpus)").option("--for <agent>", `Target one agent (${SYNC_AGENTS.join(", ")}, or all)`, "all").option("--all", "Sync every corpus skill (the default)", false).option("--source <path>", "Canonical corpus source: a directory of skill folders, or a package root with skills/ (overrides $SKILLS_SOURCE)").option("--dry-run", "Show what would be written without touching any agent folder", false).option("--force", "Adopt an unmanaged skill that already has SKILL.md; other unmarked directories are never overwritten", false).option("--check", "Home drift census (missing-from-home / stray-in-home / diverged); exits non-zero on drift, writes nothing", false).option("--adopt", "Unmarked-home adoption mode: hash unmarked home skills against the corpus; exact matches are marked (dry-run by default)", false).option("--prune", "Prune mode: list (or with --apply, remove) marked home skill dirs that have no canonical corpus entry", false).option("--apply", "Write adoption markers / conflicts ledger, or perform prune removals", false).option("--json", "Output as JSON", false).option("--station <id>", "Per-station snapshot mode: snapshot the installed skill homes into resources/<station>/skills with a v3 sync-manifest (dry-run by default; --populate writes)").option("--populate", "Write the per-station snapshot (station mode; the default is dry-run)", false).option("--repo-root <path>", "Station snapshot destination repo root (default: cwd)").option("--homes-root <dir>", "Build the station snapshot from a staged mirror of the skill homes instead of this machine's $HOME").description("Sync an API selection profile into the Skills cache; native migration flags remain available for older installations").action((names, options) => handleSync(names, options));
|
|
@@ -109233,7 +110113,7 @@ function handleCreate(name, options) {
|
|
|
109233
110113
|
console.log(source_default.green(`\u2713 Created custom skill '${result2.name}' at ${result2.path}`));
|
|
109234
110114
|
console.log(source_default.dim(` Category: ${result2.manifest.category}`));
|
|
109235
110115
|
console.log(source_default.dim(` Tags: ${result2.manifest.tags?.join(", ")}`));
|
|
109236
|
-
console.log(` ${source_default.cyan("Edit:")} ${
|
|
110116
|
+
console.log(` ${source_default.cyan("Edit:")} ${join57(result2.path, options.kind === "instruction" ? "SKILL.md" : "src/index.ts")}`);
|
|
109237
110117
|
console.log(` ${source_default.cyan("Prepare:")} skills prepare ${result2.name} --version <new-semver>`);
|
|
109238
110118
|
}
|
|
109239
110119
|
} catch (error61) {
|
|
@@ -109569,14 +110449,14 @@ var init_codex_native_config = () => {};
|
|
|
109569
110449
|
|
|
109570
110450
|
// src/lib/codex-hook-rpc.ts
|
|
109571
110451
|
import { execFile, spawn as spawn3 } from "child_process";
|
|
109572
|
-
import { realpathSync as
|
|
109573
|
-
import { isAbsolute as
|
|
110452
|
+
import { realpathSync as realpathSync13, statSync as statSync20 } from "fs";
|
|
110453
|
+
import { isAbsolute as isAbsolute25 } from "path";
|
|
109574
110454
|
import { promisify } from "util";
|
|
109575
110455
|
async function connectCodexHookRpc(options) {
|
|
109576
|
-
const found =
|
|
110456
|
+
const found = isAbsolute25(options.command) ? options.command : Bun.which(options.command, { PATH: process.env.PATH });
|
|
109577
110457
|
if (!found)
|
|
109578
110458
|
throw new Error("CODEX_HOOK_TRUST_NATIVE_UNAVAILABLE: install Codex or pass --codex-command");
|
|
109579
|
-
const binary =
|
|
110459
|
+
const binary = realpathSync13(found), stat3 = statSync20(binary);
|
|
109580
110460
|
if (!stat3.isFile() || (stat3.mode & 18) !== 0)
|
|
109581
110461
|
throw new Error("CODEX_HOOK_TRUST_UNSAFE_EXECUTABLE");
|
|
109582
110462
|
let version2;
|
|
@@ -109589,7 +110469,7 @@ async function connectCodexHookRpc(options) {
|
|
|
109589
110469
|
if (!SUPPORTED_CODEX_HOOK_VERSIONS.includes(version2))
|
|
109590
110470
|
throw new Error("CODEX_HOOK_TRUST_NATIVE_UNSUPPORTED_VERSION");
|
|
109591
110471
|
const current = statSync20(binary);
|
|
109592
|
-
if (
|
|
110472
|
+
if (realpathSync13(found) !== binary || ["dev", "ino", "size", "mtimeMs", "ctimeMs"].some((k) => stat3[k] !== current[k]))
|
|
109593
110473
|
throw new Error("CODEX_HOOK_TRUST_NATIVE_EXECUTABLE_CHANGED");
|
|
109594
110474
|
const env3 = { ...process.env, HOME: options.home, NO_COLOR: "1" };
|
|
109595
110475
|
if (options.codexHome === undefined)
|
|
@@ -109664,13 +110544,13 @@ async function connectCodexHookRpc(options) {
|
|
|
109664
110544
|
request(method2, params) {
|
|
109665
110545
|
if (stopped)
|
|
109666
110546
|
return Promise.reject(new Error("CODEX_HOOK_TRUST_NATIVE_RPC_CLOSED"));
|
|
109667
|
-
return new Promise((
|
|
110547
|
+
return new Promise((resolve24, reject2) => {
|
|
109668
110548
|
const id2 = nextId++;
|
|
109669
110549
|
const timer = setTimeout(() => {
|
|
109670
110550
|
fail5();
|
|
109671
110551
|
child.kill();
|
|
109672
110552
|
}, 20000);
|
|
109673
|
-
pending.set(id2, { resolve:
|
|
110553
|
+
pending.set(id2, { resolve: resolve24, reject: reject2, timer });
|
|
109674
110554
|
child.stdin.write(JSON.stringify({ id: id2, method: method2, params }) + `
|
|
109675
110555
|
`);
|
|
109676
110556
|
});
|
|
@@ -109680,14 +110560,14 @@ async function connectCodexHookRpc(options) {
|
|
|
109680
110560
|
child.stdin.end();
|
|
109681
110561
|
if (child.exitCode !== null || child.signalCode !== null)
|
|
109682
110562
|
return;
|
|
109683
|
-
await new Promise((
|
|
110563
|
+
await new Promise((resolve24) => {
|
|
109684
110564
|
const timer = setTimeout(() => {
|
|
109685
110565
|
child.kill();
|
|
109686
|
-
|
|
110566
|
+
resolve24();
|
|
109687
110567
|
}, 1000);
|
|
109688
110568
|
child.once("exit", () => {
|
|
109689
110569
|
clearTimeout(timer);
|
|
109690
|
-
|
|
110570
|
+
resolve24();
|
|
109691
110571
|
});
|
|
109692
110572
|
});
|
|
109693
110573
|
}
|
|
@@ -109708,21 +110588,21 @@ var init_codex_hook_rpc = __esm(() => {
|
|
|
109708
110588
|
});
|
|
109709
110589
|
|
|
109710
110590
|
// src/lib/codex-hook-trust-identity.ts
|
|
109711
|
-
import { realpathSync as
|
|
109712
|
-
import { dirname as
|
|
110591
|
+
import { realpathSync as realpathSync14 } from "fs";
|
|
110592
|
+
import { dirname as dirname32, isAbsolute as isAbsolute26, join as join58 } from "path";
|
|
109713
110593
|
import { isDeepStrictEqual as isDeepStrictEqual11 } from "util";
|
|
109714
110594
|
function inspectRecordedSkillsCli(command, expected) {
|
|
109715
|
-
need4(
|
|
109716
|
-
const resolved =
|
|
110595
|
+
need4(isAbsolute26(command) && !command.includes("\x00"), "RECONCILE_RECORDED_COMMAND_REQUIRED");
|
|
110596
|
+
const resolved = realpathSync14(command);
|
|
109717
110597
|
const cli = snapshot(resolved, false, { readOnlyPackage: true });
|
|
109718
|
-
const manifest = snapshot(
|
|
110598
|
+
const manifest = snapshot(join58(dirname32(dirname32(resolved)), "package.json"), false, { readOnlyPackage: true });
|
|
109719
110599
|
const pkg = JSON.parse(manifest.text);
|
|
109720
|
-
need4(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved ===
|
|
110600
|
+
need4(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved === join58(dirname32(manifest.file), "bin/index.js"), "SKILLS_PACKAGE_MISMATCH");
|
|
109721
110601
|
need4((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
|
|
109722
110602
|
const receipt = { path: resolved, version: pkg.version, sha256: cli.sha256, manifestSha256: manifest.sha256 };
|
|
109723
110603
|
need4(isDeepStrictEqual11(receipt, expected), "RECONCILE_SKILLS_BINDING_CHANGED");
|
|
109724
110604
|
return { receipt, recheck() {
|
|
109725
|
-
need4(
|
|
110605
|
+
need4(realpathSync14(command) === resolved, "SKILLS_COMMAND_CHANGED");
|
|
109726
110606
|
unchanged2(cli);
|
|
109727
110607
|
unchanged2(manifest);
|
|
109728
110608
|
} };
|
|
@@ -109730,22 +110610,22 @@ function inspectRecordedSkillsCli(command, expected) {
|
|
|
109730
110610
|
function bindSkillsCli(command, reviewed) {
|
|
109731
110611
|
const normal = Bun.which("skills", { PATH: process.env.PATH });
|
|
109732
110612
|
need4(normal, "SKILLS_COMMAND_UNAVAILABLE");
|
|
109733
|
-
const resolved =
|
|
109734
|
-
const bound =
|
|
109735
|
-
need4(bound &&
|
|
110613
|
+
const resolved = realpathSync14(normal);
|
|
110614
|
+
const bound = isAbsolute26(command) ? command : command === "skills" ? normal : undefined;
|
|
110615
|
+
need4(bound && realpathSync14(bound) === resolved, "SKILLS_COMMAND_MISMATCH");
|
|
109736
110616
|
const entrypoint = reviewed?.path ?? process.argv[1];
|
|
109737
|
-
need4(entrypoint &&
|
|
110617
|
+
need4(entrypoint && realpathSync14(entrypoint) === resolved, "SKILLS_ENTRYPOINT_MISMATCH");
|
|
109738
110618
|
const cli = snapshot(resolved, false, { readOnlyPackage: true });
|
|
109739
|
-
const manifest = snapshot(
|
|
110619
|
+
const manifest = snapshot(join58(dirname32(dirname32(resolved)), "package.json"), false, { readOnlyPackage: true });
|
|
109740
110620
|
const pkg = JSON.parse(manifest.text);
|
|
109741
|
-
need4(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved ===
|
|
110621
|
+
need4(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved === join58(dirname32(manifest.file), "bin/index.js"), "SKILLS_PACKAGE_MISMATCH");
|
|
109742
110622
|
if (reviewed)
|
|
109743
110623
|
need4(/^[a-f0-9]{64}$/.test(reviewed.sha256) && reviewed.sha256 === cli.sha256 && reviewed.version === pkg.version, "SKILLS_RELEASE_MISMATCH");
|
|
109744
110624
|
need4((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
|
|
109745
110625
|
return {
|
|
109746
110626
|
receipt: { path: resolved, version: pkg.version, sha256: cli.sha256, manifestSha256: manifest.sha256 },
|
|
109747
110627
|
recheck() {
|
|
109748
|
-
need4(Bun.which("skills", { PATH: process.env.PATH }) === normal &&
|
|
110628
|
+
need4(Bun.which("skills", { PATH: process.env.PATH }) === normal && realpathSync14(normal) === resolved && realpathSync14(bound) === resolved && realpathSync14(entrypoint) === resolved, "SKILLS_COMMAND_CHANGED");
|
|
109749
110629
|
unchanged2(cli);
|
|
109750
110630
|
unchanged2(manifest);
|
|
109751
110631
|
}
|
|
@@ -109779,23 +110659,23 @@ var init_codex_hook_policy_recovery = __esm(() => {
|
|
|
109779
110659
|
});
|
|
109780
110660
|
|
|
109781
110661
|
// src/lib/agent-codex-trust.ts
|
|
109782
|
-
import { lstatSync as
|
|
109783
|
-
import { createHash as
|
|
110662
|
+
import { lstatSync as lstatSync30, mkdirSync as mkdirSync27, readdirSync as readdirSync22, readFileSync as readFileSync40, realpathSync as realpathSync15, statSync as statSync21 } from "fs";
|
|
110663
|
+
import { createHash as createHash29, randomUUID as randomUUID11 } from "crypto";
|
|
109784
110664
|
import { execFileSync } from "child_process";
|
|
109785
|
-
import { dirname as
|
|
110665
|
+
import { dirname as dirname33, isAbsolute as isAbsolute27, join as join59, resolve as resolve24 } from "path";
|
|
109786
110666
|
import { homedir as homedir12 } from "os";
|
|
109787
110667
|
import { isDeepStrictEqual as isDeepStrictEqual13 } from "util";
|
|
109788
110668
|
function verifyReleaseArtifact(proof, skillsCli) {
|
|
109789
|
-
need4(
|
|
109790
|
-
const tarPath =
|
|
110669
|
+
need4(isAbsolute27(proof.packageTarPath) && !proof.packageTarPath.includes("\x00"), "RECONCILE_RELEASE_PROVENANCE_PATH");
|
|
110670
|
+
const tarPath = realpathSync15(proof.packageTarPath);
|
|
109791
110671
|
need4(statSync21(tarPath).isFile(), "RECONCILE_RELEASE_PROVENANCE_PATH");
|
|
109792
|
-
const tarBytes =
|
|
109793
|
-
need4(tarBytes.byteLength === proof.packageTarBytes &&
|
|
109794
|
-
const packageRoot =
|
|
109795
|
-
const manifest =
|
|
109796
|
-
need4(
|
|
109797
|
-
need4(
|
|
109798
|
-
const members2 = [["package/package.json", manifest], ["package/bin/index.js",
|
|
110672
|
+
const tarBytes = readFileSync40(tarPath);
|
|
110673
|
+
need4(tarBytes.byteLength === proof.packageTarBytes && createHash29("sha256").update(tarBytes).digest("hex") === proof.packageTarSha256, "RECONCILE_RELEASE_ARTIFACT_CHANGED");
|
|
110674
|
+
const packageRoot = dirname33(dirname33(realpathSync15(skillsCli.receipt.path)));
|
|
110675
|
+
const manifest = readFileSync40(join59(packageRoot, "package.json"));
|
|
110676
|
+
need4(createHash29("sha256").update(manifest).digest("hex") === skillsCli.receipt.manifestSha256 && skillsCli.receipt.manifestSha256 === proof.manifestSha256, "RECONCILE_RELEASE_MANIFEST_CHANGED");
|
|
110677
|
+
need4(createHash29("sha256").update(readFileSync40(skillsCli.receipt.path)).digest("hex") === proof.executableSha256 && skillsCli.receipt.sha256 === proof.executableSha256, "RECONCILE_RELEASE_EXECUTABLE_CHANGED");
|
|
110678
|
+
const members2 = [["package/package.json", manifest], ["package/bin/index.js", readFileSync40(skillsCli.receipt.path)]];
|
|
109799
110679
|
for (const [member, expected] of members2) {
|
|
109800
110680
|
let actual;
|
|
109801
110681
|
try {
|
|
@@ -109809,26 +110689,26 @@ function verifyReleaseArtifact(proof, skillsCli) {
|
|
|
109809
110689
|
async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc) {
|
|
109810
110690
|
let rpc;
|
|
109811
110691
|
try {
|
|
109812
|
-
const home =
|
|
109813
|
-
const parent =
|
|
110692
|
+
const home = resolve24(options.home ?? homedir12()), dataDir3 = resolve24(options.dataDir ?? getDataDirReadOnly());
|
|
110693
|
+
const parent = resolve24(join59(dataDir3, "native-hook-trust")), journal = resolve24(options.journal);
|
|
109814
110694
|
need4(journal.startsWith(parent + "/") && /^[a-f0-9-]{36}$/.test(journal.slice(parent.length + 1)), "RECONCILE_JOURNAL_PATH");
|
|
109815
|
-
const dir =
|
|
110695
|
+
const dir = lstatSync30(journal, { throwIfNoEntry: false });
|
|
109816
110696
|
need4(!!dir && dir.isDirectory() && !dir.isSymbolicLink() && dir.uid === process.getuid() && (dir.mode & 511) === 448, "UNSAFE_JOURNAL");
|
|
109817
|
-
need4(!
|
|
109818
|
-
need4(
|
|
109819
|
-
const stoppedFile = snapshot(
|
|
109820
|
-
const intentFile = snapshot(
|
|
109821
|
-
const configBefore = snapshot(
|
|
110697
|
+
need4(!lstatSync30(join59(journal, "receipt.json"), { throwIfNoEntry: false }), "RECONCILE_ALREADY_COMPLETE");
|
|
110698
|
+
need4(lstatSync30(join59(journal, "stopped.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_JOURNAL_INCOMPLETE");
|
|
110699
|
+
const stoppedFile = snapshot(join59(journal, "stopped.json"), true);
|
|
110700
|
+
const intentFile = snapshot(join59(journal, "intent.json"), true), intent = JSON.parse(intentFile.text);
|
|
110701
|
+
const configBefore = snapshot(join59(journal, "config.before.toml"), true), hooksBefore = snapshot(join59(journal, "hooks.before.json"), true), policyBefore = snapshot(join59(journal, "policy.before.json"), true);
|
|
109822
110702
|
const legacyIntent = intent.home === undefined && intent.configSha256 === undefined && intent.configVersion === undefined && intent.declarations === undefined && intent.admitted === undefined && intent.nativeHooks === undefined;
|
|
109823
110703
|
need4(intent.version === 1 && /^[a-f0-9]{64}$/.test(intent.planDigest) && typeof intent.configPath === "string" && /^[a-f0-9]{64}$/.test(intent.policySha256) && /^[a-f0-9]{64}$/.test(intent.hooksSha256) && Array.isArray(intent.hooks) && intent.hooks.length > 0 && intent.hooks.length <= 100 && (legacyIntent || typeof intent.home === "string" && intent.home === home && /^[a-f0-9]{64}$/.test(intent.configSha256) && typeof intent.configVersion === "string" && Array.isArray(intent.declarations) && Array.isArray(intent.admitted) && Array.isArray(intent.nativeHooks)), "RECONCILE_INTENT_INVALID");
|
|
109824
|
-
const declarations = intent.declarations ?? intent.hooks.map((hook) => ({ event: hook.event, key: `${
|
|
110704
|
+
const declarations = intent.declarations ?? intent.hooks.map((hook) => ({ event: hook.event, key: `${join59(home, ".codex/hooks.json")}${hook.key.slice(hook.key.indexOf(".codex/hooks.json") + ".codex/hooks.json".length)}`, command: hook.command }));
|
|
109825
110705
|
const admitted = intent.admitted ?? intent.hooks;
|
|
109826
110706
|
const configSha256 = intent.configSha256 ?? configBefore.sha256, configVersion = intent.configVersion ?? intent.expectedVersion;
|
|
109827
110707
|
const digestInput = { version: 1, home: intent.home ?? home, configPath: intent.configPath, ...intent.nativeConfigPath !== undefined ? { nativeConfigPath: intent.nativeConfigPath } : {}, configSha256, configVersion, policySha256: intent.policySha256, hooksSha256: intent.hooksSha256, skillsCli: intent.skillsCli, nativeVersion: intent.nativeVersion, declarations, admitted };
|
|
109828
110708
|
need4(makePlanDigest(digestInput) === intent.planDigest, "RECONCILE_PLAN_CHANGED");
|
|
109829
110709
|
need4(isDeepStrictEqual13(intent.hooks, admitted.filter((hook) => !hook.enabled || hook.trustStatus !== "trusted")), "RECONCILE_INTENT_HOOKS_CHANGED");
|
|
109830
110710
|
need4(configBefore.sha256 === (intent.beforeSha256 ?? intent.configSha256) && hooksBefore.sha256 === intent.hooksSha256 && policyBefore.sha256 === intent.policySha256, "RECONCILE_JOURNAL_CHANGED");
|
|
109831
|
-
const policyCurrent = snapshot(
|
|
110711
|
+
const policyCurrent = snapshot(join59(dataDir3, "agent-policy.json"), true);
|
|
109832
110712
|
const { claudeDiscoveryRecovery: claudeRecovery, nativeHookAdditionsRecovery: additionsRecovery } = options;
|
|
109833
110713
|
const recovery = claudeRecovery ?? additionsRecovery;
|
|
109834
110714
|
need4(!recovery || !options.supersedeBinding, "RECONCILE_RECOVERY_MODE_CONFLICT");
|
|
@@ -109845,17 +110725,17 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
109845
110725
|
if (supersededBinding || recovery) {
|
|
109846
110726
|
need4(Array.isArray(intent.nativeHooks) && intent.nativeHooks.length > 0, "RECONCILE_NATIVE_INVENTORY_REQUIRED");
|
|
109847
110727
|
const proof = options.releaseProof;
|
|
109848
|
-
need4(!!proof &&
|
|
110728
|
+
need4(!!proof && isAbsolute27(proof.packageTarPath) && /^[a-f0-9]{64}$/.test(proof.packageTarSha256) && Number.isInteger(proof.packageTarBytes) && proof.packageTarBytes > 0 && /^[a-f0-9]{64}$/.test(proof.manifestSha256) && /^[a-f0-9]{64}$/.test(proof.executableSha256), "RECONCILE_RELEASE_PROVENANCE_REQUIRED");
|
|
109849
110729
|
}
|
|
109850
110730
|
const currentConfig = snapshot(intent.configPath, true);
|
|
109851
110731
|
const policy = parseManagedSkillPolicy(policyCurrent.text), binding = policy.bridge;
|
|
109852
|
-
const alias = binding.rootAliases?.find((item) => item.agent === "codex"), physicalRoot = alias?.target ??
|
|
109853
|
-
need4(
|
|
110732
|
+
const alias = binding.rootAliases?.find((item) => item.agent === "codex"), physicalRoot = alias?.target ?? join59(home, ".codex"), hooksPath = join59(physicalRoot, "hooks.json");
|
|
110733
|
+
need4(resolve24(intent.configPath) === resolve24(join59(physicalRoot, "config.toml")), "RECONCILE_CONFIG_PATH_CHANGED");
|
|
109854
110734
|
const nativeConfigPath = intent.nativeConfigPath ?? intent.configPath;
|
|
109855
|
-
need4([
|
|
109856
|
-
const codexHome = nativeConfigPath !== intent.configPath ? undefined :
|
|
110735
|
+
need4([join59(home, ".codex/config.toml"), join59(physicalRoot, "config.toml")].includes(nativeConfigPath), "RECONCILE_NATIVE_CONFIG_PATH_CHANGED");
|
|
110736
|
+
const codexHome = nativeConfigPath !== intent.configPath ? undefined : dirname33(nativeConfigPath);
|
|
109857
110737
|
const hooksCurrent = snapshot(hooksPath);
|
|
109858
|
-
const additions = additionsRecovery ? reviewNativeHookAdditions({ path: additionsRecovery.path, intent: intentFile, policy: policyCurrent, configBefore, configCurrent: currentConfig, hooksBefore, hooksCurrent, nativeHooksPath:
|
|
110738
|
+
const additions = additionsRecovery ? reviewNativeHookAdditions({ path: additionsRecovery.path, intent: intentFile, policy: policyCurrent, configBefore, configCurrent: currentConfig, hooksBefore, hooksCurrent, nativeHooksPath: join59(dirname33(nativeConfigPath), "hooks.json"), journalHooks: intent.hooks, recordedNativeHooks: intent.nativeHooks }) : undefined;
|
|
109859
110739
|
const managedHooks = [...intent.hooks, ...additions?.hooks ?? []];
|
|
109860
110740
|
const managedKeys = managedHooks.map((h) => h.key);
|
|
109861
110741
|
const witnessSame = codexTrustReconcileWitness(configBefore.text, managedKeys) === codexTrustReconcileWitness(currentConfig.text, managedKeys);
|
|
@@ -109919,7 +110799,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
109919
110799
|
need4(isDeepStrictEqual13(actual, expected), "RECONCILE_UNRELATED_HOOK_CHANGED");
|
|
109920
110800
|
}
|
|
109921
110801
|
for (const wanted of intent.hooks) {
|
|
109922
|
-
const matches2 = nativeHooks.filter((h2) => h2.key === wanted.key || h2.key === wanted.key.replace(
|
|
110802
|
+
const matches2 = nativeHooks.filter((h2) => h2.key === wanted.key || h2.key === wanted.key.replace(join59(home, ".codex/hooks.json"), hooksPath));
|
|
109923
110803
|
need4(matches2.length === 1, "RECONCILE_NATIVE_IDENTITY_CHANGED");
|
|
109924
110804
|
const h = matches2[0];
|
|
109925
110805
|
const trusted = h.trustStatus === "trusted";
|
|
@@ -109968,7 +110848,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
109968
110848
|
const receipt = { version: 1, status: preview2 ? "recovery-review-required" : supersededBinding ? "superseded" : "reconciled", reconciled: !preview2, supersededBinding, automaticRollback: false, journal, planDigest: intent.planDigest, nativeVersion: rpc.version, nativeStateVerified: !nativeTrustPending, nativeTrustPending, bindingRefreshRequired: !!configDiscoveryRecovery, nativeExecutionVerified: false, configSha256: currentConfig.sha256, hooksSha256: hooksCurrent.sha256, policySha256: policyBefore.sha256, policyBeforeSha256: policyBefore.sha256, policyCurrentSha256: policyCurrent.sha256, previousSkillsCli: intent.skillsCli, skillsCli: skillsCli.receipt, releaseProof: options.releaseProof, nativeInventoryScope: recordedNativeHooks ? "complete" : "declared-managed-hooks-only", unrelatedNativeHooksPreserved: !!recordedNativeHooks, unrelatedSettingsAndCommentsPreserved: true, ...recoveryPlan ? { recoveryPlan, recoveryPlanDigest } : {} };
|
|
109969
110849
|
if (preview2)
|
|
109970
110850
|
return receipt;
|
|
109971
|
-
save(
|
|
110851
|
+
save(join59(journal, "receipt.json"), json2(receipt));
|
|
109972
110852
|
return receipt;
|
|
109973
110853
|
} catch (error61) {
|
|
109974
110854
|
const message = error61 instanceof Error && error61.message.startsWith("CODEX_HOOK_TRUST_") ? error61.message : "CODEX_HOOK_TRUST_RECONCILE_REFUSED";
|
|
@@ -109980,15 +110860,15 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
109980
110860
|
async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRpc) {
|
|
109981
110861
|
let rpc, journal;
|
|
109982
110862
|
try {
|
|
109983
|
-
const home =
|
|
110863
|
+
const home = resolve24(options.home ?? homedir12()), dataDir3 = resolve24(options.dataDir ?? getDataDirReadOnly());
|
|
109984
110864
|
assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home });
|
|
109985
|
-
const policyFile = snapshot(
|
|
110865
|
+
const policyFile = snapshot(join59(dataDir3, "agent-policy.json"), true), policy = parseManagedSkillPolicy(policyFile.text);
|
|
109986
110866
|
const binding = policy.bridge, alias = binding.rootAliases?.find((item) => item.agent === "codex");
|
|
109987
110867
|
const skillsCli = bindSkillsCli(binding.commands.codex, options.reviewedSkillsCli);
|
|
109988
|
-
const physicalRoot = alias?.target ??
|
|
109989
|
-
need4(options.codexHome === undefined ||
|
|
110868
|
+
const physicalRoot = alias?.target ?? join59(home, ".codex"), hooksPath = join59(physicalRoot, "hooks.json"), configPath = join59(physicalRoot, "config.toml");
|
|
110869
|
+
need4(options.codexHome === undefined || isAbsolute27(options.codexHome) && [join59(home, ".codex"), physicalRoot].includes(options.codexHome), "NATIVE_HOME_NOT_ADMITTED");
|
|
109990
110870
|
const codexHome = options.codexHome === undefined ? undefined : physicalRoot;
|
|
109991
|
-
const nativeConfigPath =
|
|
110871
|
+
const nativeConfigPath = join59(codexHome ?? join59(home, ".codex"), "config.toml");
|
|
109992
110872
|
const hooksFile = snapshot(hooksPath), configFile = snapshot(configPath, true), original = structure3(configFile.text);
|
|
109993
110873
|
const declarations = JSON.parse(hooksFile.text).hooks, expected = [];
|
|
109994
110874
|
for (const [event, [nativeEvent, label]] of Object.entries(eventNames)) {
|
|
@@ -109997,25 +110877,25 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
109997
110877
|
need4(Array.isArray(groups), "DECLARATION_CHANGED");
|
|
109998
110878
|
const matches2 = groups.flatMap((group, gi) => (group.hooks ?? []).flatMap((entry, hi) => entry.command === command ? [{ group, entry, gi, hi }] : []));
|
|
109999
110879
|
need4(matches2.length === 1 && isDeepStrictEqual13(matches2[0].group, { hooks: [{ type: "command", command, timeout: 15 }] }), "DECLARATION_CHANGED");
|
|
110000
|
-
expected.push({ event: nativeEvent, key: `${
|
|
110880
|
+
expected.push({ event: nativeEvent, key: `${join59(home, ".codex/hooks.json")}:${label}:${matches2[0].gi}:${matches2[0].hi}`, command });
|
|
110001
110881
|
}
|
|
110002
110882
|
codexTrustTextWitness(configFile.text, expected.map((h) => h.key));
|
|
110003
110883
|
rpc = await connect({ command: options.codexCommand ?? "codex", home, codexHome });
|
|
110004
110884
|
need4(SUPPORTED_CODEX_HOOK_VERSIONS.includes(rpc.version), "NATIVE_UNSUPPORTED_VERSION");
|
|
110005
110885
|
const list2 = async () => {
|
|
110006
110886
|
const response = await rpc.request("hooks/list", { cwds: [home] });
|
|
110007
|
-
const nativeRoots = [...new Set([physicalRoot,
|
|
110008
|
-
const coexistenceWarnings = nativeRoots.flatMap((hooksRoot) => nativeRoots.map((configRoot) => `loading hooks from both ${
|
|
110887
|
+
const nativeRoots = [...new Set([physicalRoot, join59(home, ".codex")])];
|
|
110888
|
+
const coexistenceWarnings = nativeRoots.flatMap((hooksRoot) => nativeRoots.map((configRoot) => `loading hooks from both ${join59(hooksRoot, "hooks.json")} and ${join59(configRoot, "config.toml")}; prefer a single representation for this layer`));
|
|
110009
110889
|
const entry = response?.data?.[0];
|
|
110010
110890
|
need4(Array.isArray(response?.data) && response.data.length === 1 && entry.cwd === home && Array.isArray(entry.hooks) && entry.hooks.length <= 1e4 && Array.isArray(entry.errors) && entry.errors.length === 0 && Array.isArray(entry.warnings) && entry.warnings.length <= 1 && entry.warnings.every((warning) => typeof warning === "string" && coexistenceWarnings.includes(warning)), "NATIVE_DISCOVERY_REFUSED");
|
|
110011
110891
|
return response.data[0].hooks;
|
|
110012
110892
|
};
|
|
110013
110893
|
const admit = (hooks) => expected.map((wanted) => {
|
|
110014
|
-
const canonicalKey = wanted.key.replace(
|
|
110894
|
+
const canonicalKey = wanted.key.replace(join59(home, ".codex/hooks.json"), hooksPath);
|
|
110015
110895
|
const matches2 = hooks.filter((h2) => h2.key === wanted.key || h2.key === canonicalKey);
|
|
110016
110896
|
need4(matches2.length === 1, "AMBIGUOUS_IDENTITY");
|
|
110017
110897
|
const h = matches2[0];
|
|
110018
|
-
need4(h.eventName === wanted.event && h.handlerType === "command" && h.command === wanted.command && h.matcher === null && h.timeoutSec === 15 && h.async === false && h.statusMessage === null && h.additionalContextLimit === null && [hooksPath,
|
|
110898
|
+
need4(h.eventName === wanted.event && h.handlerType === "command" && h.command === wanted.command && h.matcher === null && h.timeoutSec === 15 && h.async === false && h.statusMessage === null && h.additionalContextLimit === null && [hooksPath, join59(home, ".codex/hooks.json")].includes(h.sourcePath) && h.source === "user" && h.pluginId === null && h.isManaged === false && /^sha256:[a-f0-9]{64}$/.test(h.currentHash) && typeof h.enabled === "boolean", "NATIVE_IDENTITY_CHANGED");
|
|
110019
110899
|
need4(hooks.filter((other) => other.command === wanted.command).length === 1, "AMBIGUOUS_IDENTITY");
|
|
110020
110900
|
need4(["trusted", "untrusted", "modified"].includes(h.trustStatus), "UNKNOWN_TRUST_STATUS");
|
|
110021
110901
|
return { key: h.key, event: wanted.event, command: wanted.command, handlerType: h.handlerType, timeoutSec: h.timeoutSec, sourcePath: h.sourcePath, currentHash: h.currentHash, enabled: h.enabled, trustStatus: h.trustStatus };
|
|
@@ -110041,27 +110921,27 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
110041
110921
|
const baseReceipt = { planDigest, skillsCli: skillsCli.receipt, nativeVersion: rpc.version, nativeConfigPath, ownedNativeProcessId: rpc.processId, agent: "codex", applied: false, planned, nativeEligible: planned.length === 0, bindingRefreshRequired: false, transport: "owned-stdio-process", existingSessionsReloaded: false, nativeExecutionVerified: false };
|
|
110042
110922
|
if (options.apply)
|
|
110043
110923
|
need4(options.reviewedPlanDigest === planDigest, "PLAN_CHANGED: run the dry-run again and pass its reviewed --plan-digest with --apply");
|
|
110044
|
-
const parent =
|
|
110045
|
-
const st =
|
|
110924
|
+
const parent = join59(dataDir3, "native-hook-trust");
|
|
110925
|
+
const st = lstatSync30(parent, { throwIfNoEntry: false });
|
|
110046
110926
|
if (st) {
|
|
110047
110927
|
need4(st.isDirectory() && !st.isSymbolicLink() && st.uid === process.getuid() && (st.mode & 511) === 448, "UNSAFE_JOURNAL");
|
|
110048
|
-
const priorJournals =
|
|
110928
|
+
const priorJournals = readdirSync22(parent);
|
|
110049
110929
|
need4(priorJournals.length < 1000, "JOURNAL_BOUND");
|
|
110050
110930
|
for (const name of priorJournals) {
|
|
110051
110931
|
need4(/^[a-f0-9-]{36}$/.test(name), "UNSAFE_JOURNAL");
|
|
110052
|
-
need4(
|
|
110932
|
+
need4(lstatSync30(join59(parent, name, "receipt.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_REQUIRED: inspect the incomplete private journal before applying again");
|
|
110053
110933
|
}
|
|
110054
110934
|
}
|
|
110055
110935
|
if (!options.apply || !planned.length)
|
|
110056
110936
|
return baseReceipt;
|
|
110057
110937
|
if (!st)
|
|
110058
|
-
|
|
110059
|
-
journal =
|
|
110060
|
-
|
|
110061
|
-
save(
|
|
110062
|
-
save(
|
|
110063
|
-
save(
|
|
110064
|
-
save(
|
|
110938
|
+
mkdirSync27(parent, { mode: 448 });
|
|
110939
|
+
journal = join59(parent, randomUUID11());
|
|
110940
|
+
mkdirSync27(journal, { mode: 448 });
|
|
110941
|
+
save(join59(journal, "config.before.toml"), configFile.bytes);
|
|
110942
|
+
save(join59(journal, "hooks.before.json"), hooksFile.bytes);
|
|
110943
|
+
save(join59(journal, "policy.before.json"), policyFile.bytes);
|
|
110944
|
+
save(join59(journal, "intent.json"), json2({ ...digestInput, planDigest, hooks: planned, nativeHooks: discovered.map(safeNativeHook) }));
|
|
110065
110945
|
need4(isDeepStrictEqual13(admit(await list2()), admitted), "NATIVE_DISCOVERY_CHANGED");
|
|
110066
110946
|
checkInputs();
|
|
110067
110947
|
let result2;
|
|
@@ -110077,7 +110957,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
110077
110957
|
for (const h of planned)
|
|
110078
110958
|
expectedConfig.hooks.state[h.key] = { ...expectedConfig.hooks.state[h.key], enabled: true, trusted_hash: h.currentHash };
|
|
110079
110959
|
need4(isDeepStrictEqual13(structure3(after2.text), expectedConfig) && codexTrustTextWitness(after2.text, keys3) === preservationText, "PRESERVATION_FAILED");
|
|
110080
|
-
const savedConfig = snapshot(
|
|
110960
|
+
const savedConfig = snapshot(join59(journal, "config.before.toml"), true);
|
|
110081
110961
|
need4(savedConfig.sha256 === configFile.sha256, "JOURNAL_CHANGED");
|
|
110082
110962
|
const discoveryTransition = binding.discovery?.codex ? createCodexHookDiscoveryRecovery(binding.discovery.codex, savedConfig, after2, planned) : undefined;
|
|
110083
110963
|
unchanged2(policyFile);
|
|
@@ -110093,13 +110973,13 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
110093
110973
|
skillsCli.recheck();
|
|
110094
110974
|
assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home, codexDiscoveryRecovery: discoveryTransition });
|
|
110095
110975
|
const receipt = { ...baseReceipt, applied: true, nativeEligible: true, bindingRefreshRequired: !!discoveryTransition, ...discoveryTransition ? { discoveryTransition } : {}, nativeConfigVersion: result2.version, journal, beforeSha256: configFile.sha256, afterSha256: after2.sha256, unrelatedSettingsAndCommentsPreserved: true };
|
|
110096
|
-
save(
|
|
110976
|
+
save(join59(journal, "receipt.json"), json2(receipt));
|
|
110097
110977
|
return receipt;
|
|
110098
110978
|
} catch (error61) {
|
|
110099
110979
|
const message = error61 instanceof Error && error61.message.startsWith("CODEX_HOOK_TRUST_") ? error61.message : "CODEX_HOOK_TRUST_REFUSED: verify managed bridge installation and native Codex support";
|
|
110100
110980
|
if (journal) {
|
|
110101
110981
|
try {
|
|
110102
|
-
save(
|
|
110982
|
+
save(join59(journal, "stopped.json"), json2({ error: message.split(":")[0], automaticRollback: false, reconcileBeforeRetry: true }));
|
|
110103
110983
|
} catch {}
|
|
110104
110984
|
}
|
|
110105
110985
|
throw new Error(message);
|
|
@@ -110108,7 +110988,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
110108
110988
|
}
|
|
110109
110989
|
}
|
|
110110
110990
|
var json2 = (value) => JSON.stringify(value, null, 2) + `
|
|
110111
|
-
`, structure3 = (text4) => JSON.parse(JSON.stringify(Bun.TOML.parse(text4))), eventNames, makePlanDigest = (value) =>
|
|
110991
|
+
`, structure3 = (text4) => JSON.parse(JSON.stringify(Bun.TOML.parse(text4))), eventNames, makePlanDigest = (value) => createHash29("sha256").update(json2(value)).digest("hex"), safeNativeHook = (hook) => ({ key: hook.key, eventName: hook.eventName, handlerType: hook.handlerType, command: hook.command, matcher: hook.matcher, timeoutSec: hook.timeoutSec, async: hook.async, statusMessage: hook.statusMessage, additionalContextLimit: hook.additionalContextLimit, sourcePath: hook.sourcePath, source: hook.source, pluginId: hook.pluginId, isManaged: hook.isManaged, currentHash: hook.currentHash, enabled: hook.enabled, trustStatus: hook.trustStatus }), withoutCodexCommand = (value) => {
|
|
110112
110992
|
const copy = structuredClone(value);
|
|
110113
110993
|
if (copy?.bridge?.commands && Object.hasOwn(copy.bridge.commands, "codex"))
|
|
110114
110994
|
copy.bridge.commands.codex = "<reviewed-codex-binding>";
|
|
@@ -110219,8 +111099,8 @@ var exports_agent_integration = {};
|
|
|
110219
111099
|
__export(exports_agent_integration, {
|
|
110220
111100
|
registerAgentIntegration: () => registerAgentIntegration
|
|
110221
111101
|
});
|
|
110222
|
-
import { readFileSync as
|
|
110223
|
-
import { isAbsolute as
|
|
111102
|
+
import { readFileSync as readFileSync41 } from "fs";
|
|
111103
|
+
import { isAbsolute as isAbsolute28 } from "path";
|
|
110224
111104
|
function pinnedHookProfile(input3, configuredProfile) {
|
|
110225
111105
|
try {
|
|
110226
111106
|
const parsed = parseSkillContextInput(JSON.stringify(input3));
|
|
@@ -110297,7 +111177,7 @@ function registerAgentIntegration(parent) {
|
|
|
110297
111177
|
});
|
|
110298
111178
|
hook.command("install").option("--agent <agent>", `Agent to configure: ${INTEGRATION_AGENTS.join(", ")}, all`, "all").option("--command <path>", "Skills executable used by the hook (preserves existing binding; new agents use skills)").option("--selection-profile <id>", "Selection profile (preserves existing binding; new agents use default)").option("--include-vendor", "Retained for compatibility; vendor system skills are always inventoried and disabled", false).option("--discovery-inputs <file>", "Advanced reviewed active plugin roots and source hashes for unsupported registrations").option("--allow-root-aliases", "Allow home .claude/.codex aliases to existing directories within this home", false).option("--apply", "Apply the plan, preserving prior configuration in private backups", false).option("--json", "Output a receipt as JSON", false).description("Plan or install one Skills CLI bridge plus native prompt hooks").action(async (options) => {
|
|
110299
111179
|
try {
|
|
110300
|
-
const discoveryInputs = options.discoveryInputs ? JSON.parse(
|
|
111180
|
+
const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync41(options.discoveryInputs, "utf8")) : undefined;
|
|
110301
111181
|
const plan = planAgentIntegration({ agents: agents(options.agent), command: options.command, profileId: options.selectionProfile, includeVendor: options.includeVendor, discoveryInputs, allowRootAliases: options.allowRootAliases });
|
|
110302
111182
|
const result2 = options.apply ? applyAgentIntegration(plan) : { changed: [], backups: [] };
|
|
110303
111183
|
const receipt = { applied: options.apply, planned: plan.changes.map((change) => change.path), ...result2, rootAliases: plan.rootAliases ?? [], discovery: plan.discoveryAfter, nativeSkills: plan.nativeSkills.map((entry) => ({ agent: entry.agent, path: entry.path, managed: entry.managed, vendor: entry.vendor, system: entry.system === true, bridge: entry.bridge === true })), requiresNativeRetirement: plan.nativeSkills.some((entry) => !entry.bridge && !entry.system) };
|
|
@@ -110355,7 +111235,7 @@ function registerAgentIntegration(parent) {
|
|
|
110355
111235
|
try {
|
|
110356
111236
|
if (agents(options.agent).length !== 1)
|
|
110357
111237
|
throw new Error("A hook invocation requires one agent");
|
|
110358
|
-
const inputText =
|
|
111238
|
+
const inputText = readFileSync41(0, "utf8");
|
|
110359
111239
|
if (inputText.length > 1024 * 1024)
|
|
110360
111240
|
throw new Error("Hook input is too large");
|
|
110361
111241
|
let input3 = JSON.parse(inputText);
|
|
@@ -110368,12 +111248,12 @@ function registerAgentIntegration(parent) {
|
|
|
110368
111248
|
input3.hook_event_name = event;
|
|
110369
111249
|
const projects = [process.cwd()];
|
|
110370
111250
|
if (input3.cwd !== undefined) {
|
|
110371
|
-
if (typeof input3.cwd !== "string" || !
|
|
111251
|
+
if (typeof input3.cwd !== "string" || !isAbsolute28(input3.cwd) || input3.cwd.includes("\x00"))
|
|
110372
111252
|
throw new Error("Invalid native hook working directory");
|
|
110373
111253
|
projects.push(input3.cwd);
|
|
110374
111254
|
}
|
|
110375
111255
|
if (options.agent === "cursor") {
|
|
110376
|
-
if (!Array.isArray(input3.workspace_roots) || input3.workspace_roots.length > 32 || input3.workspace_roots.some((path) => typeof path !== "string" || !
|
|
111256
|
+
if (!Array.isArray(input3.workspace_roots) || input3.workspace_roots.length > 32 || input3.workspace_roots.some((path) => typeof path !== "string" || !isAbsolute28(path) || path.includes("\x00")))
|
|
110377
111257
|
throw new Error("Invalid Cursor workspace roots");
|
|
110378
111258
|
projects.push(...input3.workspace_roots);
|
|
110379
111259
|
input3.cwd ??= input3.workspace_roots[0] ?? process.cwd();
|
|
@@ -110453,7 +111333,7 @@ function registerAgentIntegration(parent) {
|
|
|
110453
111333
|
try {
|
|
110454
111334
|
if (options.agent && !INTEGRATION_AGENTS.includes(options.agent))
|
|
110455
111335
|
throw new Error(`Unsupported migration agent: ${options.agent}`);
|
|
110456
|
-
const discoveryInputs = options.discoveryInputs ? JSON.parse(
|
|
111336
|
+
const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync41(options.discoveryInputs, "utf8")) : undefined;
|
|
110457
111337
|
if (options.targetManifest && (options.includeUnmanaged || options.includeVendor))
|
|
110458
111338
|
throw new Error("--target-manifest cannot be combined with broad native migration selectors");
|
|
110459
111339
|
const targetManifest = options.targetManifest ? readNativeMigrationTargetManifest(options.targetManifest) : undefined;
|
|
@@ -110623,7 +111503,7 @@ var exports_profiles = {};
|
|
|
110623
111503
|
__export(exports_profiles, {
|
|
110624
111504
|
registerProfiles: () => registerProfiles
|
|
110625
111505
|
});
|
|
110626
|
-
import { closeSync as closeSync23, constants as constants20, fstatSync as fstatSync23, openSync as openSync23, readSync as readSync17, writeFileSync as
|
|
111506
|
+
import { closeSync as closeSync23, constants as constants20, fstatSync as fstatSync23, openSync as openSync23, readSync as readSync17, writeFileSync as writeFileSync26 } from "fs";
|
|
110627
111507
|
function readProfileInput(path) {
|
|
110628
111508
|
const fd = openSync23(path, constants20.O_RDONLY | constants20.O_NONBLOCK);
|
|
110629
111509
|
try {
|
|
@@ -110651,7 +111531,7 @@ function registerProfiles(parent) {
|
|
|
110651
111531
|
try {
|
|
110652
111532
|
const profile = await readSkillProfile(id3), text4 = JSON.stringify(profile, null, 2);
|
|
110653
111533
|
if (options.save)
|
|
110654
|
-
|
|
111534
|
+
writeFileSync26(options.save, JSON.stringify(profile), { mode: 384, flag: "wx" });
|
|
110655
111535
|
await writeCliOutput(options.json ? text4 : `${profile.id} at ${profile.revision}: ${profile.selections.map((skill) => `${skill.slug}@${skill.version}`).join(", ")}`);
|
|
110656
111536
|
} catch (error61) {
|
|
110657
111537
|
console.error(error61.message);
|
|
@@ -110698,7 +111578,7 @@ import {
|
|
|
110698
111578
|
fstatSync as fstatSync24,
|
|
110699
111579
|
openSync as openSync24,
|
|
110700
111580
|
readSync as readSync18,
|
|
110701
|
-
writeFileSync as
|
|
111581
|
+
writeFileSync as writeFileSync27
|
|
110702
111582
|
} from "fs";
|
|
110703
111583
|
function readInput(path) {
|
|
110704
111584
|
const fd = openSync24(path, constants21.O_RDONLY | constants21.O_NONBLOCK);
|
|
@@ -110731,7 +111611,7 @@ function registerGrants(parent) {
|
|
|
110731
111611
|
try {
|
|
110732
111612
|
const policy = await readExecutionGrantPolicy(profile, options.revision);
|
|
110733
111613
|
if (options.save)
|
|
110734
|
-
|
|
111614
|
+
writeFileSync27(options.save, JSON.stringify(policy, null, 2), {
|
|
110735
111615
|
mode: 384,
|
|
110736
111616
|
flag: "wx"
|
|
110737
111617
|
});
|
|
@@ -110758,28 +111638,28 @@ var init_grants = __esm(() => {
|
|
|
110758
111638
|
});
|
|
110759
111639
|
|
|
110760
111640
|
// src/lib/station-hydrate.ts
|
|
110761
|
-
import { createHash as
|
|
111641
|
+
import { createHash as createHash30 } from "crypto";
|
|
110762
111642
|
import {
|
|
110763
|
-
copyFileSync as
|
|
110764
|
-
mkdirSync as
|
|
110765
|
-
readdirSync as
|
|
110766
|
-
readFileSync as
|
|
111643
|
+
copyFileSync as copyFileSync3,
|
|
111644
|
+
mkdirSync as mkdirSync28,
|
|
111645
|
+
readdirSync as readdirSync23,
|
|
111646
|
+
readFileSync as readFileSync42,
|
|
110767
111647
|
statSync as statSync22,
|
|
110768
|
-
writeFileSync as
|
|
111648
|
+
writeFileSync as writeFileSync28
|
|
110769
111649
|
} from "fs";
|
|
110770
|
-
import { dirname as
|
|
111650
|
+
import { dirname as dirname34, join as join60, resolve as resolve25, sep as sep12 } from "path";
|
|
110771
111651
|
function fail5(code2, message, detail = []) {
|
|
110772
111652
|
throw new StationSnapshotError(code2, message, detail);
|
|
110773
111653
|
}
|
|
110774
111654
|
function snapshotRootFor(repoRoot, stationId) {
|
|
110775
|
-
return
|
|
111655
|
+
return join60(repoRoot, "resources", stationId, "skills");
|
|
110776
111656
|
}
|
|
110777
111657
|
function readSnapshotManifest(repoRoot, stationId) {
|
|
110778
111658
|
const snapshotRoot = snapshotRootFor(repoRoot, stationId);
|
|
110779
|
-
const manifestPath =
|
|
111659
|
+
const manifestPath = join60(snapshotRoot, "sync-manifest.json");
|
|
110780
111660
|
let manifest;
|
|
110781
111661
|
try {
|
|
110782
|
-
manifest = JSON.parse(
|
|
111662
|
+
manifest = JSON.parse(readFileSync42(manifestPath, "utf8"));
|
|
110783
111663
|
} catch (error61) {
|
|
110784
111664
|
fail5("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error61.message}`);
|
|
110785
111665
|
}
|
|
@@ -110803,10 +111683,10 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
110803
111683
|
const hashMismatches = [];
|
|
110804
111684
|
const skippedByRule = [];
|
|
110805
111685
|
for (const agent of SYNC_AGENTS) {
|
|
110806
|
-
const agentRoot =
|
|
111686
|
+
const agentRoot = join60(snapshotRoot, "agent-homes", agent);
|
|
110807
111687
|
let identEntries;
|
|
110808
111688
|
try {
|
|
110809
|
-
identEntries =
|
|
111689
|
+
identEntries = readdirSync23(agentRoot, { withFileTypes: true });
|
|
110810
111690
|
} catch {
|
|
110811
111691
|
continue;
|
|
110812
111692
|
}
|
|
@@ -110814,10 +111694,10 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
110814
111694
|
if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
|
|
110815
111695
|
continue;
|
|
110816
111696
|
}
|
|
110817
|
-
const identRoot =
|
|
111697
|
+
const identRoot = join60(agentRoot, identEntry.name);
|
|
110818
111698
|
const entries = walkEntries(identRoot);
|
|
110819
111699
|
for (const entry of entries) {
|
|
110820
|
-
const relativeParts = [identEntry.name, ...entry.relativePath.split(
|
|
111700
|
+
const relativeParts = [identEntry.name, ...entry.relativePath.split(sep12)];
|
|
110821
111701
|
if (entry.kind === "symlink") {
|
|
110822
111702
|
symlinks.push({ ident: identEntry.name, agent, relativePath: entry.relativePath });
|
|
110823
111703
|
continue;
|
|
@@ -110841,8 +111721,8 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
110841
111721
|
});
|
|
110842
111722
|
continue;
|
|
110843
111723
|
}
|
|
110844
|
-
const withinIdent = relativeParts.slice(1).join(
|
|
110845
|
-
const homeRelative = relativeParts.join(
|
|
111724
|
+
const withinIdent = relativeParts.slice(1).join(sep12);
|
|
111725
|
+
const homeRelative = relativeParts.join(sep12);
|
|
110846
111726
|
if (REFUSED_SCANNER_FLAGGED.has(homeRelative)) {
|
|
110847
111727
|
skippedByRule.push({
|
|
110848
111728
|
ident: identEntry.name,
|
|
@@ -110915,7 +111795,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
110915
111795
|
for (const copy of copies) {
|
|
110916
111796
|
let isStub = false;
|
|
110917
111797
|
try {
|
|
110918
|
-
isStub = isPointerSkillMd(
|
|
111798
|
+
isStub = isPointerSkillMd(readFileSync42(copy.fullPath, "utf8"));
|
|
110919
111799
|
} catch {
|
|
110920
111800
|
isStub = false;
|
|
110921
111801
|
}
|
|
@@ -110961,12 +111841,12 @@ function skillSha256(skill) {
|
|
|
110961
111841
|
return sha256File(skill.files[0].winner.fullPath);
|
|
110962
111842
|
}
|
|
110963
111843
|
const joined = skill.files.map((file2) => sha256File(file2.winner.fullPath));
|
|
110964
|
-
return
|
|
111844
|
+
return createHash30("sha256").update(joined.sort().join(`
|
|
110965
111845
|
`)).digest("hex");
|
|
110966
111846
|
}
|
|
110967
111847
|
function writeStationHydration(options) {
|
|
110968
|
-
const repoRoot =
|
|
110969
|
-
const cacheRoot =
|
|
111848
|
+
const repoRoot = resolve25(options.repoRoot ?? process.cwd());
|
|
111849
|
+
const cacheRoot = resolve25(options.cacheRoot ?? resolveCorpusRoot());
|
|
110970
111850
|
const plan = planStationHydration(options.stationId, repoRoot);
|
|
110971
111851
|
const resultSkills = plan.winners.map((skill) => ({
|
|
110972
111852
|
ident: skill.ident,
|
|
@@ -110999,7 +111879,7 @@ function writeStationHydration(options) {
|
|
|
110999
111879
|
const toWrite = [];
|
|
111000
111880
|
for (const skill of plan.winners) {
|
|
111001
111881
|
for (const file2 of skill.files) {
|
|
111002
|
-
const destination =
|
|
111882
|
+
const destination = join60(cacheRoot, skill.ident, file2.withinIdent);
|
|
111003
111883
|
const digest4 = sha256File(file2.winner.fullPath);
|
|
111004
111884
|
let existingDigest = null;
|
|
111005
111885
|
try {
|
|
@@ -111020,8 +111900,8 @@ function writeStationHydration(options) {
|
|
|
111020
111900
|
}
|
|
111021
111901
|
let written = 0;
|
|
111022
111902
|
for (const entry of toWrite) {
|
|
111023
|
-
|
|
111024
|
-
|
|
111903
|
+
mkdirSync28(dirname34(entry.destination), { recursive: true });
|
|
111904
|
+
copyFileSync3(entry.fullPath, entry.destination);
|
|
111025
111905
|
written += 1;
|
|
111026
111906
|
}
|
|
111027
111907
|
const unchanged3 = plan.totalFiles - written;
|
|
@@ -111041,9 +111921,9 @@ function writeStationHydration(options) {
|
|
|
111041
111921
|
},
|
|
111042
111922
|
skills: resultSkills
|
|
111043
111923
|
};
|
|
111044
|
-
const hydrationManifestPath =
|
|
111045
|
-
|
|
111046
|
-
|
|
111924
|
+
const hydrationManifestPath = join60(dirname34(cacheRoot), `hydration-${options.stationId}.json`);
|
|
111925
|
+
mkdirSync28(dirname34(hydrationManifestPath), { recursive: true });
|
|
111926
|
+
writeFileSync28(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
|
|
111047
111927
|
`);
|
|
111048
111928
|
return {
|
|
111049
111929
|
...base3,
|
|
@@ -111118,15 +111998,15 @@ var init_hydrate = __esm(() => {
|
|
|
111118
111998
|
});
|
|
111119
111999
|
|
|
111120
112000
|
// src/lib/prepare-skill.ts
|
|
111121
|
-
import { createHash as
|
|
111122
|
-
import { chmodSync as
|
|
112001
|
+
import { createHash as createHash31, randomUUID as randomUUID12 } from "crypto";
|
|
112002
|
+
import { chmodSync as chmodSync5, existsSync as existsSync35, lstatSync as lstatSync31, mkdirSync as mkdirSync29, mkdtempSync as mkdtempSync6, readFileSync as readFileSync43, readdirSync as readdirSync24, renameSync as renameSync14, rmSync as rmSync9, writeFileSync as writeFileSync29 } from "fs";
|
|
111123
112003
|
import { tmpdir as tmpdir2 } from "os";
|
|
111124
|
-
import { dirname as
|
|
112004
|
+
import { dirname as dirname35, isAbsolute as isAbsolute29, join as join61, relative as relative10, resolve as resolve26, sep as sep13 } from "path";
|
|
111125
112005
|
function prepareSkill(name, options) {
|
|
111126
112006
|
const normalized = normalizePortableSkillName(name);
|
|
111127
112007
|
if (!new RegExp(SEMVER_PATTERN).test(options.version))
|
|
111128
112008
|
throw new Error("--version must be a semantic version, for example 0.2.0.");
|
|
111129
|
-
const path =
|
|
112009
|
+
const path = join61(getPortableSkillsRootReadOnly(options), normalized);
|
|
111130
112010
|
assertPortableAuthoringPath(path, options);
|
|
111131
112011
|
const before2 = snapshot2(path);
|
|
111132
112012
|
const manifestFile = before2.files.find((file2) => file2.path === "skill.json");
|
|
@@ -111161,17 +112041,17 @@ function prepareSkill(name, options) {
|
|
|
111161
112041
|
throw new Error("skill.json provenance must be an object.");
|
|
111162
112042
|
const provenance = manifest.provenance;
|
|
111163
112043
|
const candidate2 = { ...manifest, version: options.version, kind, provenance: { ...provenance } };
|
|
111164
|
-
const staging = mkdtempSync6(
|
|
112044
|
+
const staging = mkdtempSync6(join61(tmpdir2(), "skills-prepare-"));
|
|
111165
112045
|
try {
|
|
111166
112046
|
for (const directory2 of before2.directories)
|
|
111167
|
-
|
|
112047
|
+
mkdirSync29(join61(staging, directory2), { recursive: true });
|
|
111168
112048
|
for (const file2 of before2.files) {
|
|
111169
|
-
const destination =
|
|
111170
|
-
|
|
111171
|
-
|
|
112049
|
+
const destination = join61(staging, file2.path);
|
|
112050
|
+
mkdirSync29(dirname35(destination), { recursive: true });
|
|
112051
|
+
writeFileSync29(destination, file2.bytes, { mode: file2.mode });
|
|
111172
112052
|
}
|
|
111173
|
-
const stagedManifest =
|
|
111174
|
-
|
|
112053
|
+
const stagedManifest = join61(staging, "skill.json");
|
|
112054
|
+
writeFileSync29(stagedManifest, `${JSON.stringify(candidate2, null, 2)}
|
|
111175
112055
|
`);
|
|
111176
112056
|
const contentHash = computeContentHash(staging);
|
|
111177
112057
|
const changed3 = options.version !== previousVersion || kind !== manifest.kind || contentHash !== provenance?.content_hash;
|
|
@@ -111181,7 +112061,7 @@ function prepareSkill(name, options) {
|
|
|
111181
112061
|
candidate2.provenance.content_hash = contentHash;
|
|
111182
112062
|
const candidateBytes = `${JSON.stringify(candidate2, null, 2)}
|
|
111183
112063
|
`;
|
|
111184
|
-
|
|
112064
|
+
writeFileSync29(stagedManifest, candidateBytes);
|
|
111185
112065
|
validateRawCandidate(candidate2);
|
|
111186
112066
|
validateRuntimeEntrypoint(candidate2, staging);
|
|
111187
112067
|
const validation = validatePortableSkillDirectory(normalized, staging);
|
|
@@ -111193,11 +112073,11 @@ ${validation.issues.map((issue2) => `${issue2.code}: ${issue2.message}`).join(`
|
|
|
111193
112073
|
if (snapshot2(path).identity !== before2.identity)
|
|
111194
112074
|
throw new Error("Skill changed during preparation; review the draft and run prepare again.");
|
|
111195
112075
|
if (changed3 && !options.dryRun) {
|
|
111196
|
-
const temporary =
|
|
112076
|
+
const temporary = join61(path, `.skill-prepare-${randomUUID12()}.tmp`);
|
|
111197
112077
|
try {
|
|
111198
|
-
|
|
111199
|
-
|
|
111200
|
-
|
|
112078
|
+
writeFileSync29(temporary, candidateBytes, { flag: "wx", mode: manifestFile.mode });
|
|
112079
|
+
chmodSync5(temporary, manifestFile.mode);
|
|
112080
|
+
renameSync14(temporary, join61(path, "skill.json"));
|
|
111201
112081
|
} finally {
|
|
111202
112082
|
rmSync9(temporary, { force: true });
|
|
111203
112083
|
}
|
|
@@ -111227,8 +112107,8 @@ function validateRuntimeEntrypoint(candidate2, root2) {
|
|
|
111227
112107
|
const entry = candidate2.runtime.entrypoint;
|
|
111228
112108
|
if (typeof entry !== "string")
|
|
111229
112109
|
return;
|
|
111230
|
-
const target =
|
|
111231
|
-
if (
|
|
112110
|
+
const target = resolve26(root2, entry), rel = relative10(root2, target);
|
|
112111
|
+
if (isAbsolute29(entry) || /^[a-zA-Z]:/.test(entry) || entry.includes("\\") || !rel || rel === ".." || rel.startsWith(`..${sep13}`) || isAbsolute29(rel) || !existsSync35(target) || !lstatSync31(target).isFile()) {
|
|
111232
112112
|
throw new Error("Executable runtime.entrypoint must name an existing regular file inside the skill directory.");
|
|
111233
112113
|
}
|
|
111234
112114
|
}
|
|
@@ -111313,25 +112193,25 @@ function compareVersions(left, right) {
|
|
|
111313
112193
|
return 0;
|
|
111314
112194
|
}
|
|
111315
112195
|
function snapshot2(root2) {
|
|
111316
|
-
const rootStat =
|
|
112196
|
+
const rootStat = lstatSync31(root2);
|
|
111317
112197
|
if (rootStat.isSymbolicLink() || !rootStat.isDirectory())
|
|
111318
112198
|
throw new Error("Skill root must be a real directory, not a symlink.");
|
|
111319
112199
|
const files = [], directories = [];
|
|
111320
|
-
const
|
|
112200
|
+
const hash6 = createHash31("sha256").update(`${rootStat.dev}:${rootStat.ino}\x00`);
|
|
111321
112201
|
let count = 0, bytes = 0;
|
|
111322
|
-
const walk2 = (
|
|
111323
|
-
for (const name of
|
|
111324
|
-
if (IGNORED.has(name.toLowerCase()) || !
|
|
112202
|
+
const walk2 = (relative11) => {
|
|
112203
|
+
for (const name of readdirSync24(join61(root2, relative11)).sort()) {
|
|
112204
|
+
if (IGNORED.has(name.toLowerCase()) || !relative11 && ROOT_IGNORED.has(name.toLowerCase()))
|
|
111325
112205
|
continue;
|
|
111326
|
-
const path =
|
|
112206
|
+
const path = relative11 ? `${relative11}/${name}` : name;
|
|
111327
112207
|
if (++count > 20000)
|
|
111328
112208
|
throw new Error("Skill exceeds the preparation entry limit (20000).");
|
|
111329
|
-
const absolute =
|
|
112209
|
+
const absolute = join61(root2, path), stat3 = lstatSync31(absolute);
|
|
111330
112210
|
if (stat3.isSymbolicLink())
|
|
111331
112211
|
throw new Error(`Skill contains a symlink: ${path}. No files were changed.`);
|
|
111332
112212
|
if (stat3.isDirectory() && name === "node_modules")
|
|
111333
112213
|
continue;
|
|
111334
|
-
|
|
112214
|
+
hash6.update(JSON.stringify([path, stat3.dev, stat3.ino, stat3.mode]));
|
|
111335
112215
|
if (stat3.isDirectory()) {
|
|
111336
112216
|
directories.push(path);
|
|
111337
112217
|
walk2(path);
|
|
@@ -111339,18 +112219,18 @@ function snapshot2(root2) {
|
|
|
111339
112219
|
bytes += stat3.size;
|
|
111340
112220
|
if (bytes > 50000000)
|
|
111341
112221
|
throw new Error("Skill exceeds the preparation size limit (50000000 bytes).");
|
|
111342
|
-
const content =
|
|
112222
|
+
const content = readFileSync43(absolute);
|
|
111343
112223
|
if (content.length !== stat3.size)
|
|
111344
112224
|
throw new Error("Skill changed during preparation.");
|
|
111345
112225
|
files.push({ path, bytes: content, mode: stat3.mode & 511 });
|
|
111346
|
-
|
|
112226
|
+
hash6.update(`\x00${content.length}\x00`).update(content);
|
|
111347
112227
|
} else
|
|
111348
112228
|
throw new Error(`Unsupported skill entry: ${path}.`);
|
|
111349
|
-
|
|
112229
|
+
hash6.update("\x00");
|
|
111350
112230
|
}
|
|
111351
112231
|
};
|
|
111352
112232
|
walk2("");
|
|
111353
|
-
return { files, directories, identity:
|
|
112233
|
+
return { files, directories, identity: hash6.digest("hex") };
|
|
111354
112234
|
}
|
|
111355
112235
|
var MAX_PACK_PARITY_PATHS = 8, MAX_PACK_PARITY_PATH_CHARS = 120, IGNORED, ROOT_IGNORED;
|
|
111356
112236
|
var init_prepare_skill = __esm(() => {
|
|
@@ -111741,8 +112621,8 @@ var init_schedule = __esm(() => {
|
|
|
111741
112621
|
});
|
|
111742
112622
|
|
|
111743
112623
|
// src/lib/registry-sync.ts
|
|
111744
|
-
import { mkdirSync as
|
|
111745
|
-
import { dirname as
|
|
112624
|
+
import { mkdirSync as mkdirSync30, writeFileSync as writeFileSync30 } from "fs";
|
|
112625
|
+
import { dirname as dirname36, relative as relative11 } from "path";
|
|
111746
112626
|
function createRegistrySyncArtifact(options = {}) {
|
|
111747
112627
|
const profile = options.profile ?? "all";
|
|
111748
112628
|
const includeDocs = options.includeDocs ?? true;
|
|
@@ -111754,7 +112634,7 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
111754
112634
|
const registry2 = [...loadRegistryProfile(profile)].sort((a, b) => a.name.localeCompare(b.name));
|
|
111755
112635
|
const skills = registry2.map((skill) => {
|
|
111756
112636
|
const skillPath = getSkillPath(skill.name);
|
|
111757
|
-
const directory2 =
|
|
112637
|
+
const directory2 = relative11(process.cwd(), skillPath) || skillPath;
|
|
111758
112638
|
const validation = includeValidation ? validateSkillDirectory(skill.name, skillPath, skill) : undefined;
|
|
111759
112639
|
const docs = includeDocs ? buildDocs(skill.name) : undefined;
|
|
111760
112640
|
return {
|
|
@@ -111799,8 +112679,8 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
111799
112679
|
};
|
|
111800
112680
|
}
|
|
111801
112681
|
function writeRegistrySyncArtifact(path, artifact) {
|
|
111802
|
-
|
|
111803
|
-
|
|
112682
|
+
mkdirSync30(dirname36(path), { recursive: true });
|
|
112683
|
+
writeFileSync30(path, `${JSON.stringify(artifact, null, 2)}
|
|
111804
112684
|
`);
|
|
111805
112685
|
}
|
|
111806
112686
|
function buildDocs(name) {
|
|
@@ -111819,8 +112699,8 @@ var init_registry_sync = __esm(() => {
|
|
|
111819
112699
|
});
|
|
111820
112700
|
|
|
111821
112701
|
// src/lib/review-export.ts
|
|
111822
|
-
import { constants as constants22, closeSync as closeSync25, existsSync as
|
|
111823
|
-
import { basename as
|
|
112702
|
+
import { constants as constants22, closeSync as closeSync25, existsSync as existsSync36, fstatSync as fstatSync25, fsyncSync as fsyncSync7, lstatSync as lstatSync32, openSync as openSync25, realpathSync as realpathSync16, statSync as statSync23, writeFileSync as writeFileSync31 } from "fs";
|
|
112703
|
+
import { basename as basename15, dirname as dirname37, isAbsolute as isAbsolute30, join as join62, parse as parse12, resolve as resolve27, sep as sep14 } from "path";
|
|
111824
112704
|
function exactVersion(spec) {
|
|
111825
112705
|
const at2 = spec.indexOf("@");
|
|
111826
112706
|
const slug2 = spec.slice(0, at2);
|
|
@@ -111831,23 +112711,23 @@ function exactVersion(spec) {
|
|
|
111831
112711
|
return { slug: slug2, version: version2 };
|
|
111832
112712
|
}
|
|
111833
112713
|
function assertPrivateDestination(output2) {
|
|
111834
|
-
if (!
|
|
112714
|
+
if (!isAbsolute30(output2) || resolve27(output2) !== output2 || !basename15(output2).endsWith(".tar.gz")) {
|
|
111835
112715
|
throw new Error("Review output must be an absolute, normalized .tar.gz path.");
|
|
111836
112716
|
}
|
|
111837
|
-
const parent =
|
|
111838
|
-
if (
|
|
112717
|
+
const parent = dirname37(output2);
|
|
112718
|
+
if (realpathSync16(parent) !== parent || lstatSync32(parent).isSymbolicLink()) {
|
|
111839
112719
|
throw new Error("Review output parent must be a real directory, not a symlink.");
|
|
111840
112720
|
}
|
|
111841
112721
|
const parentStat = statSync23(parent);
|
|
111842
112722
|
if (!parentStat.isDirectory() || (parentStat.mode & 63) !== 0 || process.getuid && parentStat.uid !== process.getuid()) {
|
|
111843
112723
|
throw new Error("Review output parent must be an owner-only directory (mode 0700).");
|
|
111844
112724
|
}
|
|
111845
|
-
const segments = parent.toLowerCase().split(
|
|
112725
|
+
const segments = parent.toLowerCase().split(sep14);
|
|
111846
112726
|
if (segments.some((segment) => NATIVE_CONTAINERS.has(segment)) || segments.some((segment, index) => segment === ".config" && segments[index + 1] === "opencode") || segments.some((segment, index) => segment === ".hasna" && segments[index + 1] === "skills")) {
|
|
111847
112727
|
throw new Error("Review output cannot be placed in agent-native discovery or Skills installation paths.");
|
|
111848
112728
|
}
|
|
111849
|
-
for (let current = parent;; current =
|
|
111850
|
-
if (
|
|
112729
|
+
for (let current = parent;; current = dirname37(current)) {
|
|
112730
|
+
if (existsSync36(join62(current, ".git"))) {
|
|
111851
112731
|
throw new Error("Review output cannot be placed inside a Git worktree.");
|
|
111852
112732
|
}
|
|
111853
112733
|
if (current === parse12(current).root)
|
|
@@ -111858,7 +112738,7 @@ function assertPrivateDestination(output2) {
|
|
|
111858
112738
|
async function exportSkillVersionForReview(spec, output2, client2) {
|
|
111859
112739
|
const { slug: slug2, version: version2 } = exactVersion(spec);
|
|
111860
112740
|
const target = assertPrivateDestination(output2);
|
|
111861
|
-
if (
|
|
112741
|
+
if (existsSync36(target))
|
|
111862
112742
|
throw new Error("Review output already exists; choose a new filename.");
|
|
111863
112743
|
const metadata = await client2.getSkillVersion(slug2, version2);
|
|
111864
112744
|
if (!metadata)
|
|
@@ -111885,7 +112765,7 @@ async function exportSkillVersionForReview(spec, output2, client2) {
|
|
|
111885
112765
|
}
|
|
111886
112766
|
const fd = openSync25(target, constants22.O_WRONLY | constants22.O_CREAT | constants22.O_EXCL | constants22.O_NOFOLLOW, 384);
|
|
111887
112767
|
try {
|
|
111888
|
-
|
|
112768
|
+
writeFileSync31(fd, bytes);
|
|
111889
112769
|
fsyncSync7(fd);
|
|
111890
112770
|
if (fstatSync25(fd).size !== bytes.byteLength)
|
|
111891
112771
|
throw new Error("Incomplete review output write.");
|
|
@@ -111923,12 +112803,12 @@ function registerRegistry(parent) {
|
|
|
111923
112803
|
async function writeJson2(value, space) {
|
|
111924
112804
|
const text4 = `${JSON.stringify(value, null, space)}
|
|
111925
112805
|
`;
|
|
111926
|
-
await new Promise((
|
|
112806
|
+
await new Promise((resolve28, reject2) => {
|
|
111927
112807
|
process.stdout.write(text4, (error61) => {
|
|
111928
112808
|
if (error61)
|
|
111929
112809
|
reject2(error61);
|
|
111930
112810
|
else
|
|
111931
|
-
|
|
112811
|
+
resolve28();
|
|
111932
112812
|
});
|
|
111933
112813
|
});
|
|
111934
112814
|
}
|
|
@@ -112132,9 +113012,9 @@ __export(exports_publish, {
|
|
|
112132
113012
|
PushSkillError: () => PushSkillError
|
|
112133
113013
|
});
|
|
112134
113014
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
112135
|
-
import { existsSync as
|
|
113015
|
+
import { existsSync as existsSync37, readFileSync as readFileSync44 } from "fs";
|
|
112136
113016
|
import { hostname as hostname5 } from "os";
|
|
112137
|
-
import { join as
|
|
113017
|
+
import { join as join63 } from "path";
|
|
112138
113018
|
function registerPublish(parent) {
|
|
112139
113019
|
parent.command("push").argument("<name>", "Name of a skill in the local corpus (~/.hasna/skills/installed or the migrated ~/.hasna/skills/skills)").option("--version <version>", "Override the version recorded on the instance").option("--force-new-version", "If name@version already exists with different content, publish as the next patch version", false).option("--dry-run", "Pack and validate without uploading", false).option("--json", "Output result as JSON", false).description("Publish a local skill to the configured skills instance").action(async (name, options) => {
|
|
112140
113020
|
try {
|
|
@@ -112186,8 +113066,8 @@ async function pushSkill(name, options = {}) {
|
|
|
112186
113066
|
const packed = packSkillBundle(skill.path, { maxUnpackedBytes: MAX_UNPACKED_BYTES });
|
|
112187
113067
|
await verifyPackedCanonicalHash(skill.name, packed);
|
|
112188
113068
|
const versionManifest = buildVersionManifest(skill.path, packed);
|
|
112189
|
-
const skillMdPath =
|
|
112190
|
-
const skillMd =
|
|
113069
|
+
const skillMdPath = join63(skill.path, "SKILL.md");
|
|
113070
|
+
const skillMd = existsSync37(skillMdPath) ? readFileSync44(skillMdPath, "utf-8") : undefined;
|
|
112191
113071
|
const base3 = {
|
|
112192
113072
|
slug: skill.name,
|
|
112193
113073
|
path: skill.path,
|
|
@@ -112425,7 +113305,7 @@ var exports_private_publications = {};
|
|
|
112425
113305
|
__export(exports_private_publications, {
|
|
112426
113306
|
registerPrivatePublications: () => registerPrivatePublications
|
|
112427
113307
|
});
|
|
112428
|
-
import { resolve as
|
|
113308
|
+
import { resolve as resolve28 } from "path";
|
|
112429
113309
|
function verifyOptions(options, action) {
|
|
112430
113310
|
if (options.userId === undefined !== (options.membershipId === undefined) || options.userId !== undefined && (!publicationUuid(options.userId) || !publicationUuid(options.membershipId)))
|
|
112431
113311
|
throw new PrivatePublicationError("PUBLICATION_CONTEXT_REQUIRED", "Provide both observed --user-id and --membership-id UUIDs.");
|
|
@@ -112470,9 +113350,9 @@ function registerPrivatePublications(parent) {
|
|
|
112470
113350
|
if (code2 === null)
|
|
112471
113351
|
return;
|
|
112472
113352
|
const context2 = options.userId ? { userId: options.userId, membershipId: options.membershipId } : undefined;
|
|
112473
|
-
const client2 = await privatePublicationSession(options.email, code2, context2), directory2 =
|
|
113353
|
+
const client2 = await privatePublicationSession(options.email, code2, context2), directory2 = resolve28(options.recoveryDir);
|
|
112474
113354
|
if (action === "publish")
|
|
112475
|
-
await preparePrivatePublication(client2,
|
|
113355
|
+
await preparePrivatePublication(client2, resolve28(args2[0]), directory2, { skillId: options.skillId, expectedCurrentVersionId: options.expectedCurrentVersion ?? null, idempotencyKey: options.idempotencyKey });
|
|
112476
113356
|
const result2 = action === "publish" || action === "resume" ? await continuePrivatePublication(client2, directory2, { confirm: true, waitMs: Number(options.waitSeconds) * 1000 }) : await inspectPrivatePublication(client2, directory2, action === "cancel");
|
|
112477
113357
|
print2(result2, options.json);
|
|
112478
113358
|
} catch (error61) {
|
|
@@ -112574,7 +113454,7 @@ __export(exports_auth, {
|
|
|
112574
113454
|
import { createInterface as createInterface3 } from "readline";
|
|
112575
113455
|
function prompt(question) {
|
|
112576
113456
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
112577
|
-
return new Promise((
|
|
113457
|
+
return new Promise((resolve29) => {
|
|
112578
113458
|
let settled = false;
|
|
112579
113459
|
const finish = (answer) => {
|
|
112580
113460
|
if (settled)
|
|
@@ -112583,7 +113463,7 @@ function prompt(question) {
|
|
|
112583
113463
|
rl.close();
|
|
112584
113464
|
if (answer === null)
|
|
112585
113465
|
process.exitCode = 130;
|
|
112586
|
-
|
|
113466
|
+
resolve29(answer);
|
|
112587
113467
|
};
|
|
112588
113468
|
rl.once("SIGINT", () => finish(null));
|
|
112589
113469
|
rl.once("close", () => finish(null));
|
|
@@ -112761,7 +113641,7 @@ Skipped. Run it any time: skills setup agents`));
|
|
|
112761
113641
|
function promptHidden(question) {
|
|
112762
113642
|
const stdin = process.stdin;
|
|
112763
113643
|
process.stdout.write(question);
|
|
112764
|
-
return new Promise((
|
|
113644
|
+
return new Promise((resolve29) => {
|
|
112765
113645
|
let value = "";
|
|
112766
113646
|
const done = (answer) => {
|
|
112767
113647
|
stdin.off("data", onData);
|
|
@@ -112771,7 +113651,7 @@ function promptHidden(question) {
|
|
|
112771
113651
|
`);
|
|
112772
113652
|
if (answer === null)
|
|
112773
113653
|
process.exitCode = 130;
|
|
112774
|
-
|
|
113654
|
+
resolve29(answer);
|
|
112775
113655
|
};
|
|
112776
113656
|
const onData = (chunk2) => {
|
|
112777
113657
|
for (const character of chunk2.toString()) {
|
|
@@ -113317,7 +114197,7 @@ function promptInvitationProof(kind) {
|
|
|
113317
114197
|
const label = kind === "token" ? "invitation token" : "recovery code";
|
|
113318
114198
|
const character = kind === "token" ? /^[A-Za-z0-9_-]$/ : /^\d$/;
|
|
113319
114199
|
const { stdin, stderr: output2 } = process, wasRaw = stdin.isRaw, wasFlowing = stdin.readableFlowing;
|
|
113320
|
-
return new Promise((
|
|
114200
|
+
return new Promise((resolve29) => {
|
|
113321
114201
|
let value = "", settled = false, overflow = false;
|
|
113322
114202
|
const finish = (answer) => {
|
|
113323
114203
|
if (settled)
|
|
@@ -113334,7 +114214,7 @@ function promptInvitationProof(kind) {
|
|
|
113334
114214
|
`);
|
|
113335
114215
|
if (answer === null)
|
|
113336
114216
|
process.exitCode = 130;
|
|
113337
|
-
|
|
114217
|
+
resolve29(answer);
|
|
113338
114218
|
};
|
|
113339
114219
|
const cancel = () => finish(null);
|
|
113340
114220
|
const keypress = (text4, key) => {
|
|
@@ -113906,8 +114786,8 @@ var init_storage2 = __esm(() => {
|
|
|
113906
114786
|
});
|
|
113907
114787
|
|
|
113908
114788
|
// src/lib/registry-reconcile.ts
|
|
113909
|
-
import { existsSync as
|
|
113910
|
-
import { join as
|
|
114789
|
+
import { existsSync as existsSync38, readFileSync as readFileSync45, statSync as statSync24, writeFileSync as writeFileSync32 } from "fs";
|
|
114790
|
+
import { join as join64 } from "path";
|
|
113911
114791
|
function isDirectory2(path) {
|
|
113912
114792
|
try {
|
|
113913
114793
|
return statSync24(path).isDirectory();
|
|
@@ -113918,15 +114798,15 @@ function isDirectory2(path) {
|
|
|
113918
114798
|
function migrationNeeded(options) {
|
|
113919
114799
|
if (options.rootDir)
|
|
113920
114800
|
return false;
|
|
113921
|
-
const appDir = options.homeDir ?
|
|
113922
|
-
return !(isOwnerLayoutMigrated(appDir) && isDirectory2(
|
|
114801
|
+
const appDir = options.homeDir ? join64(options.homeDir, ".hasna", "skills") : getDataDir();
|
|
114802
|
+
return !(isOwnerLayoutMigrated(appDir) && isDirectory2(join64(appDir, SKILLS_CACHE_DIRNAME)));
|
|
113923
114803
|
}
|
|
113924
114804
|
function readBaseline(skillDir) {
|
|
113925
|
-
const markerPath =
|
|
113926
|
-
if (!
|
|
114805
|
+
const markerPath = join64(skillDir, PULL_MARKER_FILE);
|
|
114806
|
+
if (!existsSync38(markerPath))
|
|
113927
114807
|
return;
|
|
113928
114808
|
try {
|
|
113929
|
-
const marker = JSON.parse(
|
|
114809
|
+
const marker = JSON.parse(readFileSync45(markerPath, "utf-8"));
|
|
113930
114810
|
if (!isSkillsOwnershipMarker(marker))
|
|
113931
114811
|
return;
|
|
113932
114812
|
return {
|
|
@@ -113938,11 +114818,11 @@ function readBaseline(skillDir) {
|
|
|
113938
114818
|
}
|
|
113939
114819
|
}
|
|
113940
114820
|
function readCursor(root2) {
|
|
113941
|
-
const path =
|
|
113942
|
-
if (!
|
|
114821
|
+
const path = join64(root2, SYNC_CURSOR_FILE);
|
|
114822
|
+
if (!existsSync38(path))
|
|
113943
114823
|
return { runCount: 0 };
|
|
113944
114824
|
try {
|
|
113945
|
-
const cursor2 = JSON.parse(
|
|
114825
|
+
const cursor2 = JSON.parse(readFileSync45(path, "utf-8"));
|
|
113946
114826
|
return { runCount: typeof cursor2.runCount === "number" ? cursor2.runCount : 0 };
|
|
113947
114827
|
} catch {
|
|
113948
114828
|
return { runCount: 0 };
|
|
@@ -113951,12 +114831,12 @@ function readCursor(root2) {
|
|
|
113951
114831
|
function resolveCorpusRootReadOnly(options) {
|
|
113952
114832
|
if (options.rootDir)
|
|
113953
114833
|
return { root: options.rootDir, migrationPending: false };
|
|
113954
|
-
const appDir = options.homeDir ?
|
|
113955
|
-
const cache3 =
|
|
114834
|
+
const appDir = options.homeDir ? join64(options.homeDir, ".hasna", "skills") : getDataDirReadOnly();
|
|
114835
|
+
const cache3 = join64(appDir, SKILLS_CACHE_DIRNAME);
|
|
113956
114836
|
if (isOwnerLayoutMigrated(appDir) && isDirectory2(cache3)) {
|
|
113957
114837
|
return { root: cache3, migrationPending: false };
|
|
113958
114838
|
}
|
|
113959
|
-
return { root:
|
|
114839
|
+
return { root: join64(appDir, INSTALLED_SKILLS_DIRNAME), migrationPending: true };
|
|
113960
114840
|
}
|
|
113961
114841
|
function remoteRowToSkill(record11) {
|
|
113962
114842
|
const slug2 = typeof record11.slug === "string" ? record11.slug : typeof record11.name === "string" ? record11.name : undefined;
|
|
@@ -113970,7 +114850,7 @@ function remoteRowToSkill(record11) {
|
|
|
113970
114850
|
}
|
|
113971
114851
|
function recheckLocalSide(plannedLocal, localDir, ops = {
|
|
113972
114852
|
pack: (dir) => packSkillBundle(dir).sha256,
|
|
113973
|
-
exists:
|
|
114853
|
+
exists: existsSync38
|
|
113974
114854
|
}) {
|
|
113975
114855
|
let localNow;
|
|
113976
114856
|
try {
|
|
@@ -114090,7 +114970,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
114090
114970
|
for (const slug2 of allSlugs) {
|
|
114091
114971
|
const local = locals.get(slug2);
|
|
114092
114972
|
const remote = remotes.get(slug2);
|
|
114093
|
-
const baseline = local ? readBaseline(
|
|
114973
|
+
const baseline = local ? readBaseline(join64(root2, slug2)) : undefined;
|
|
114094
114974
|
const { state, reason } = classifySkill(local, remote, baseline);
|
|
114095
114975
|
let { action, reason: actionReason } = resolveAction(state, direction, conflict);
|
|
114096
114976
|
if (state === "remote-only" && isDigestless(remote)) {
|
|
@@ -114149,7 +115029,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
114149
115029
|
try {
|
|
114150
115030
|
await pushSkill(slug2, { rootDir: root2, client: client2 });
|
|
114151
115031
|
const pushed = locals.get(slug2);
|
|
114152
|
-
writePullMarker(
|
|
115032
|
+
writePullMarker(join64(root2, slug2), {
|
|
114153
115033
|
skill: slug2,
|
|
114154
115034
|
...pushed?.version ? { version: pushed.version } : {},
|
|
114155
115035
|
...pushed?.sha256 ? { contentHash: pushed.sha256 } : {},
|
|
@@ -114221,7 +115101,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
114221
115101
|
runCount: readCursor(root2).runCount + 1,
|
|
114222
115102
|
summary
|
|
114223
115103
|
};
|
|
114224
|
-
|
|
115104
|
+
writeFileSync32(join64(root2, SYNC_CURSOR_FILE), `${JSON.stringify(cursor2, null, 2)}
|
|
114225
115105
|
`);
|
|
114226
115106
|
return {
|
|
114227
115107
|
corpusRoot: root2,
|