@hasna/skills 0.10.8 → 0.10.10
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 +1145 -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.10",
|
|
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,864 @@ 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
|
+
const seenDirectories = new Set;
|
|
107444
|
+
for (const rawDir of pathInput.split(delimiter).filter(Boolean)) {
|
|
107445
|
+
const dir = resolve22(rawDir);
|
|
107446
|
+
if (seenDirectories.has(dir))
|
|
107447
|
+
continue;
|
|
107448
|
+
seenDirectories.add(dir);
|
|
107449
|
+
for (const [name, target] of Object.entries(current.bins)) {
|
|
107450
|
+
const path = join54(dir, name);
|
|
107451
|
+
if (!existsSync33(path))
|
|
107452
|
+
continue;
|
|
107453
|
+
assertNoSymlinkAncestors(dir);
|
|
107454
|
+
let stat3;
|
|
107455
|
+
try {
|
|
107456
|
+
stat3 = lstatSync27(path);
|
|
107457
|
+
} catch {
|
|
107458
|
+
continue;
|
|
107459
|
+
}
|
|
107460
|
+
if (!stat3.isSymbolicLink())
|
|
107461
|
+
continue;
|
|
107462
|
+
let actual;
|
|
107463
|
+
try {
|
|
107464
|
+
actual = realpathSync11(path);
|
|
107465
|
+
} catch {
|
|
107466
|
+
throw new Error("LAUNCHER_TARGET_UNREADABLE");
|
|
107467
|
+
}
|
|
107468
|
+
if (actual !== join54(currentPackageRoot, target))
|
|
107469
|
+
continue;
|
|
107470
|
+
const oldLinkTarget = readlinkSync4(path);
|
|
107471
|
+
if (resolve22(dirname29(path), oldLinkTarget) !== actual)
|
|
107472
|
+
throw new Error("LAUNCHER_SYMLINK_CHAIN_UNSUPPORTED");
|
|
107473
|
+
const newTarget = join54(runtimeRoot, "__target__", "node_modules", "@hasna", "skills", target);
|
|
107474
|
+
launchers.push({ path, oldTarget: actual, oldLinkTarget, newTarget, backupPath: `${path}.skills-prev-${randomUUID9()}` });
|
|
107475
|
+
}
|
|
107476
|
+
}
|
|
107477
|
+
if (!launchers.some((item) => item.path === launcherPath))
|
|
107478
|
+
throw new Error("ACTIVE_LAUNCHER_NOT_IN_PATH_INVENTORY");
|
|
107479
|
+
if (new Set(launchers.map((item) => item.path)).size !== launchers.length)
|
|
107480
|
+
throw new Error("LAUNCHER_PATH_DUPLICATE");
|
|
107481
|
+
return { home, runtimeRoot, currentPackageRoot, currentVersion: current.version, bin: current.bins, launchers };
|
|
107482
|
+
}
|
|
107483
|
+
function verifyIntegrity(bytes, integrity) {
|
|
107484
|
+
const match = /^sha512-([A-Za-z0-9+/]+={0,2})$/.exec(integrity);
|
|
107485
|
+
if (!match)
|
|
107486
|
+
throw new Error("REGISTRY_INTEGRITY_UNSUPPORTED");
|
|
107487
|
+
const actual = createHash26("sha512").update(bytes).digest("base64");
|
|
107488
|
+
if (actual !== match[1])
|
|
107489
|
+
throw new Error("TARBALL_INTEGRITY_MISMATCH");
|
|
107490
|
+
}
|
|
107491
|
+
function expectedTarballUrl(input2, origin, version2) {
|
|
107492
|
+
if (typeof input2 !== "string")
|
|
107493
|
+
throw new Error("REGISTRY_TARBALL_URL_INVALID");
|
|
107494
|
+
let url2;
|
|
107495
|
+
try {
|
|
107496
|
+
url2 = new URL(input2);
|
|
107497
|
+
} catch {
|
|
107498
|
+
throw new Error("REGISTRY_TARBALL_URL_INVALID");
|
|
107499
|
+
}
|
|
107500
|
+
const base3 = new URL(origin);
|
|
107501
|
+
if (url2.protocol !== "https:" && base3.protocol === "https:")
|
|
107502
|
+
throw new Error("REGISTRY_TARBALL_URL_INVALID");
|
|
107503
|
+
if (url2.origin !== base3.origin || url2.username || url2.password || url2.search || url2.hash)
|
|
107504
|
+
throw new Error("REGISTRY_TARBALL_URL_INVALID");
|
|
107505
|
+
if (!url2.pathname.endsWith(`/skills-${version2}.tgz`))
|
|
107506
|
+
throw new Error("REGISTRY_TARBALL_URL_INVALID");
|
|
107507
|
+
return url2.toString();
|
|
107508
|
+
}
|
|
107509
|
+
function tarField(block, start, length) {
|
|
107510
|
+
const field2 = block.subarray(start, start + length);
|
|
107511
|
+
const end = field2.indexOf(0);
|
|
107512
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(end < 0 ? field2 : field2.subarray(0, end));
|
|
107513
|
+
}
|
|
107514
|
+
function tarOctal2(block, start, length) {
|
|
107515
|
+
const raw = new TextDecoder().decode(block.subarray(start, start + length)).replace(/[\0 ]+$/g, "").trim();
|
|
107516
|
+
if (!raw)
|
|
107517
|
+
return 0;
|
|
107518
|
+
if (!/^[0-7]+$/.test(raw))
|
|
107519
|
+
throw new Error("TARBALL_TAR_NUMERIC_FIELD_INVALID");
|
|
107520
|
+
const value = Number.parseInt(raw, 8);
|
|
107521
|
+
if (!Number.isSafeInteger(value))
|
|
107522
|
+
throw new Error("TARBALL_TAR_NUMERIC_FIELD_INVALID");
|
|
107523
|
+
return value;
|
|
107524
|
+
}
|
|
107525
|
+
async function gunzipCapped(bytes, limit) {
|
|
107526
|
+
if (bytes[0] !== 31 || bytes[1] !== 139)
|
|
107527
|
+
throw new Error("TARBALL_GZIP_REQUIRED");
|
|
107528
|
+
const stream = Readable.from([Buffer.from(bytes)]).pipe(createGunzip2());
|
|
107529
|
+
const chunks = [];
|
|
107530
|
+
let size2 = 0;
|
|
107531
|
+
try {
|
|
107532
|
+
for await (const chunk2 of stream) {
|
|
107533
|
+
const part = Buffer.from(chunk2);
|
|
107534
|
+
size2 += part.byteLength;
|
|
107535
|
+
if (size2 > limit) {
|
|
107536
|
+
stream.destroy();
|
|
107537
|
+
throw new Error("TARBALL_EXPANDED_SIZE_INVALID");
|
|
107538
|
+
}
|
|
107539
|
+
chunks.push(part);
|
|
107540
|
+
}
|
|
107541
|
+
} finally {
|
|
107542
|
+
stream.destroy();
|
|
107543
|
+
}
|
|
107544
|
+
return Buffer.concat(chunks, size2);
|
|
107545
|
+
}
|
|
107546
|
+
async function preflightTarball(bytes, limits = {}) {
|
|
107547
|
+
const expanded = await gunzipCapped(bytes, limits.maxExpandedBytes ?? MAX_TARBALL_EXPANDED_BYTES);
|
|
107548
|
+
const entries = [];
|
|
107549
|
+
const paths = new Set;
|
|
107550
|
+
let offset = 0, ended = false, fileBytes = 0;
|
|
107551
|
+
while (offset + 512 <= expanded.byteLength) {
|
|
107552
|
+
const block = expanded.subarray(offset, offset + 512);
|
|
107553
|
+
if (block.every((byte) => byte === 0)) {
|
|
107554
|
+
ended = true;
|
|
107555
|
+
break;
|
|
107556
|
+
}
|
|
107557
|
+
if (entries.length >= (limits.maxEntries ?? MAX_TARBALL_ENTRIES))
|
|
107558
|
+
throw new Error("TARBALL_ENTRY_COUNT_INVALID");
|
|
107559
|
+
const expectedChecksum = tarOctal2(block, 148, 8);
|
|
107560
|
+
let checksum = 0;
|
|
107561
|
+
for (let i = 0;i < 512; i++)
|
|
107562
|
+
checksum += i >= 148 && i < 156 ? 32 : block[i];
|
|
107563
|
+
if (checksum !== expectedChecksum)
|
|
107564
|
+
throw new Error("TARBALL_TAR_CHECKSUM_INVALID");
|
|
107565
|
+
const name = tarField(block, 0, 100), prefix = tarField(block, 345, 155);
|
|
107566
|
+
const rawPath = `${prefix ? `${prefix}/` : ""}${name}`.replace(/\/$/, "");
|
|
107567
|
+
const pathParts2 = rawPath.split("/");
|
|
107568
|
+
if (rawPath !== "package" && !rawPath.startsWith("package/") || rawPath.startsWith("/") || rawPath.includes("\\") || pathParts2.some((part) => !part || part === "." || part === ".."))
|
|
107569
|
+
throw new Error("TARBALL_PATH_INVALID");
|
|
107570
|
+
if (paths.has(rawPath))
|
|
107571
|
+
throw new Error("TARBALL_DUPLICATE_PATH");
|
|
107572
|
+
paths.add(rawPath);
|
|
107573
|
+
const flag = block[156] === 0 ? "0" : String.fromCharCode(block[156]);
|
|
107574
|
+
const size2 = tarOctal2(block, 124, 12);
|
|
107575
|
+
const kind = flag === "0" ? "file" : flag === "5" ? "directory" : null;
|
|
107576
|
+
if (!kind || kind === "directory" && size2 !== 0 || rawPath === "package" && kind !== "directory")
|
|
107577
|
+
throw new Error("TARBALL_ENTRY_TYPE_UNSUPPORTED");
|
|
107578
|
+
fileBytes += size2;
|
|
107579
|
+
if (fileBytes > (limits.maxExpandedBytes ?? MAX_TARBALL_EXPANDED_BYTES))
|
|
107580
|
+
throw new Error("TARBALL_EXPANDED_SIZE_INVALID");
|
|
107581
|
+
entries.push({ path: rawPath, kind, size: size2 });
|
|
107582
|
+
const padded = Math.ceil(size2 / 512) * 512;
|
|
107583
|
+
offset += 512 + padded;
|
|
107584
|
+
if (offset > expanded.byteLength)
|
|
107585
|
+
throw new Error("TARBALL_TAR_TRUNCATED");
|
|
107586
|
+
}
|
|
107587
|
+
if (!ended || !expanded.subarray(offset).every((byte) => byte === 0))
|
|
107588
|
+
throw new Error("TARBALL_TAR_TERMINATOR_INVALID");
|
|
107589
|
+
return { entries, expandedBytes: expanded.byteLength };
|
|
107590
|
+
}
|
|
107591
|
+
async function downloadPackage(version2, stagingRoot, registryOrigin, fetcher) {
|
|
107592
|
+
const metadataUrl = `${registryOrigin}/@hasna%2fskills/${encodeURIComponent(version2)}`;
|
|
107593
|
+
const timeout = AbortSignal.timeout(30000);
|
|
107594
|
+
const metadataResponse = await fetcher(metadataUrl, { redirect: "error", signal: timeout });
|
|
107595
|
+
if (!metadataResponse.ok || new URL(metadataResponse.url || metadataUrl).origin !== new URL(registryOrigin).origin)
|
|
107596
|
+
throw new Error("REGISTRY_METADATA_FETCH_FAILED");
|
|
107597
|
+
const metadata = object8(JSON.parse(new TextDecoder().decode(await readBodyCapped(metadataResponse, 5 * 1024 * 1024, "REGISTRY_METADATA_SIZE_INVALID"))), "REGISTRY_METADATA_INVALID");
|
|
107598
|
+
if (metadata.name !== PACKAGE_NAME || metadata.version !== version2 || metadata._id !== `${PACKAGE_NAME}@${version2}`)
|
|
107599
|
+
throw new Error("REGISTRY_PACKAGE_IDENTITY_MISMATCH");
|
|
107600
|
+
const dist = object8(metadata.dist, "REGISTRY_DIST_INVALID");
|
|
107601
|
+
const integrity = typeof dist.integrity === "string" ? dist.integrity : "";
|
|
107602
|
+
const tarballUrl = expectedTarballUrl(dist.tarball, registryOrigin, version2);
|
|
107603
|
+
const response = await fetcher(tarballUrl, { redirect: "error", signal: AbortSignal.timeout(30000) });
|
|
107604
|
+
if (!response.ok || new URL(response.url || tarballUrl).origin !== new URL(registryOrigin).origin)
|
|
107605
|
+
throw new Error("REGISTRY_TARBALL_FETCH_FAILED");
|
|
107606
|
+
const bytes = await readBodyCapped(response, MAX_TARBALL_BYTES, "TARBALL_SIZE_INVALID");
|
|
107607
|
+
if (!bytes.byteLength)
|
|
107608
|
+
throw new Error("TARBALL_SIZE_INVALID");
|
|
107609
|
+
verifyIntegrity(bytes, integrity);
|
|
107610
|
+
const preflight = await preflightTarball(bytes);
|
|
107611
|
+
const archive = new Bun.Archive(bytes);
|
|
107612
|
+
const files = await archive.files();
|
|
107613
|
+
const regularFiles = preflight.entries.filter((entry) => entry.kind === "file");
|
|
107614
|
+
if (files.size !== regularFiles.length || regularFiles.some((entry) => files.get(entry.path)?.size !== entry.size))
|
|
107615
|
+
throw new Error("TARBALL_ARCHIVE_INTERPRETATION_MISMATCH");
|
|
107616
|
+
const packageJson = files.get("package/package.json");
|
|
107617
|
+
if (!packageJson)
|
|
107618
|
+
throw new Error("TARBALL_PACKAGE_JSON_MISSING");
|
|
107619
|
+
const unpacked = object8(JSON.parse(await packageJson.text()), "TARBALL_PACKAGE_JSON_INVALID");
|
|
107620
|
+
if (unpacked.name !== PACKAGE_NAME || unpacked.version !== version2)
|
|
107621
|
+
throw new Error("TARBALL_PACKAGE_IDENTITY_MISMATCH");
|
|
107622
|
+
const bins = packageBins(unpacked);
|
|
107623
|
+
const archiveRoot = join54(stagingRoot, "verified-archive");
|
|
107624
|
+
mkdirSync24(archiveRoot, { mode: 448 });
|
|
107625
|
+
await archive.extract(archiveRoot);
|
|
107626
|
+
const extractedPackage = join54(archiveRoot, "package");
|
|
107627
|
+
const extracted = readTree(extractedPackage, false);
|
|
107628
|
+
const tarballPath = join54(stagingRoot, "verified-package.tgz");
|
|
107629
|
+
writeFileSync23(tarballPath, bytes, { flag: "wx", mode: 384 });
|
|
107630
|
+
if (!readFileSync37(tarballPath).equals(Buffer.from(bytes)))
|
|
107631
|
+
throw new Error("TARBALL_READBACK_MISMATCH");
|
|
107632
|
+
return {
|
|
107633
|
+
tarballPath,
|
|
107634
|
+
tarballUrl,
|
|
107635
|
+
integrity,
|
|
107636
|
+
sha256: hash5(bytes),
|
|
107637
|
+
byteSize: bytes.byteLength,
|
|
107638
|
+
packageTreeSha256: extracted.digest,
|
|
107639
|
+
packageBins: bins
|
|
107640
|
+
};
|
|
107641
|
+
}
|
|
107642
|
+
async function readBodyCapped(response, limit, errorCode) {
|
|
107643
|
+
if (!response.body)
|
|
107644
|
+
throw new Error("HTTP_RESPONSE_BODY_MISSING");
|
|
107645
|
+
const reader = response.body.getReader();
|
|
107646
|
+
const chunks = [];
|
|
107647
|
+
let size2 = 0;
|
|
107648
|
+
try {
|
|
107649
|
+
for (;; ) {
|
|
107650
|
+
const { done, value } = await reader.read();
|
|
107651
|
+
if (done)
|
|
107652
|
+
break;
|
|
107653
|
+
size2 += value.byteLength;
|
|
107654
|
+
if (size2 > limit) {
|
|
107655
|
+
await reader.cancel();
|
|
107656
|
+
throw new Error(errorCode);
|
|
107657
|
+
}
|
|
107658
|
+
chunks.push(value);
|
|
107659
|
+
}
|
|
107660
|
+
} finally {
|
|
107661
|
+
reader.releaseLock();
|
|
107662
|
+
}
|
|
107663
|
+
const bytes = new Uint8Array(size2);
|
|
107664
|
+
let offset = 0;
|
|
107665
|
+
for (const chunk2 of chunks) {
|
|
107666
|
+
bytes.set(chunk2, offset);
|
|
107667
|
+
offset += chunk2.byteLength;
|
|
107668
|
+
}
|
|
107669
|
+
return bytes;
|
|
107670
|
+
}
|
|
107671
|
+
function readTree(root2, includeModes) {
|
|
107672
|
+
const base3 = realpathSync11(root2);
|
|
107673
|
+
const entries = [];
|
|
107674
|
+
const walk2 = (dir, prefix = "") => {
|
|
107675
|
+
for (const item of readdirSync20(dir).sort()) {
|
|
107676
|
+
const path = join54(dir, item);
|
|
107677
|
+
const rel = prefix ? `${prefix}/${item}` : item;
|
|
107678
|
+
const stat3 = lstatSync27(path);
|
|
107679
|
+
if (stat3.isSymbolicLink()) {
|
|
107680
|
+
const link2 = readlinkSync4(path);
|
|
107681
|
+
const resolved = resolve22(dirname29(path), link2);
|
|
107682
|
+
if (!isWithin(base3, resolved))
|
|
107683
|
+
throw new Error("TREE_SYMLINK_ESCAPES_ROOT");
|
|
107684
|
+
let physicalTarget;
|
|
107685
|
+
try {
|
|
107686
|
+
physicalTarget = realpathSync11(path);
|
|
107687
|
+
} catch {
|
|
107688
|
+
throw new Error("TREE_SYMLINK_TARGET_MISSING");
|
|
107689
|
+
}
|
|
107690
|
+
if (!isWithin(base3, physicalTarget))
|
|
107691
|
+
throw new Error("TREE_SYMLINK_ESCAPES_ROOT");
|
|
107692
|
+
entries.push({ path: rel, kind: "symlink", target: link2, ...includeModes ? { mode: stat3.mode & 511 } : {} });
|
|
107693
|
+
} else if (stat3.isDirectory()) {
|
|
107694
|
+
entries.push({ path: rel, kind: "directory", ...includeModes ? { mode: stat3.mode & 511 } : {} });
|
|
107695
|
+
walk2(path, rel);
|
|
107696
|
+
} else if (stat3.isFile()) {
|
|
107697
|
+
if (stat3.nlink !== 1)
|
|
107698
|
+
throw new Error("TREE_HARDLINK_UNSUPPORTED");
|
|
107699
|
+
const bytes = readFileSync37(path);
|
|
107700
|
+
entries.push({ path: rel, kind: "file", sha256: hash5(bytes), ...includeModes ? { mode: stat3.mode & 511 } : {} });
|
|
107701
|
+
} else
|
|
107702
|
+
throw new Error("TREE_ENTRY_TYPE_UNSUPPORTED");
|
|
107703
|
+
}
|
|
107704
|
+
};
|
|
107705
|
+
walk2(base3);
|
|
107706
|
+
return { entries, digest: hash5(new TextEncoder().encode(JSON.stringify(entries))) };
|
|
107707
|
+
}
|
|
107708
|
+
function normalizeModes(root2) {
|
|
107709
|
+
const walk2 = (dir) => {
|
|
107710
|
+
for (const item of readdirSync20(dir)) {
|
|
107711
|
+
const path = join54(dir, item);
|
|
107712
|
+
const stat3 = lstatSync27(path);
|
|
107713
|
+
if (stat3.isSymbolicLink())
|
|
107714
|
+
continue;
|
|
107715
|
+
if (stat3.isDirectory()) {
|
|
107716
|
+
chmodSync4(path, stat3.mode & 493);
|
|
107717
|
+
walk2(path);
|
|
107718
|
+
} else if (stat3.isFile()) {
|
|
107719
|
+
chmodSync4(path, stat3.mode & 493);
|
|
107720
|
+
} else
|
|
107721
|
+
throw new Error("RUNTIME_ENTRY_TYPE_UNSUPPORTED");
|
|
107722
|
+
}
|
|
107723
|
+
};
|
|
107724
|
+
walk2(root2);
|
|
107725
|
+
}
|
|
107726
|
+
function writeJsonPrivate(path, value) {
|
|
107727
|
+
const bytes = Buffer.from(`${JSON.stringify(value, null, 2)}
|
|
107728
|
+
`);
|
|
107729
|
+
writeFileSync23(path, bytes, { flag: "wx", mode: 384 });
|
|
107730
|
+
chmodSync4(path, 384);
|
|
107731
|
+
if (!readFileSync37(path).equals(bytes))
|
|
107732
|
+
throw new Error("PRIVATE_JSON_READBACK_MISMATCH");
|
|
107733
|
+
}
|
|
107734
|
+
function writePreimageManifest(stage, configs, launchers) {
|
|
107735
|
+
const path = join54(stage, "preimage", "manifest.json");
|
|
107736
|
+
const value = { schema: "skills.copyfile-preimage.v1", configs, launchers };
|
|
107737
|
+
const bytes = Buffer.from(`${JSON.stringify(value, null, 2)}
|
|
107738
|
+
`);
|
|
107739
|
+
writeFileSync23(path, bytes, { flag: "wx", mode: 256 });
|
|
107740
|
+
chmodSync4(path, 256);
|
|
107741
|
+
if (!readFileSync37(path).equals(bytes))
|
|
107742
|
+
throw new Error("PREIMAGE_MANIFEST_READBACK_MISMATCH");
|
|
107743
|
+
return hash5(bytes);
|
|
107744
|
+
}
|
|
107745
|
+
function verifyPreimageManifest(runtimePath, receipt, home) {
|
|
107746
|
+
const path = join54(runtimePath, "preimage", "manifest.json");
|
|
107747
|
+
const bytes = readFileSync37(path);
|
|
107748
|
+
if (hash5(bytes) !== receipt.preimageSha256)
|
|
107749
|
+
throw new Error("PREIMAGE_MANIFEST_DRIFT");
|
|
107750
|
+
const manifest = object8(JSON.parse(bytes.toString("utf8")), "PREIMAGE_MANIFEST_INVALID");
|
|
107751
|
+
const expected = { schema: "skills.copyfile-preimage.v1", configs: receipt.configs, launchers: receipt.launchers };
|
|
107752
|
+
if (JSON.stringify(manifest) !== JSON.stringify(expected))
|
|
107753
|
+
throw new Error("PREIMAGE_RECEIPT_MISMATCH");
|
|
107754
|
+
if (receipt.configs.length !== CONFIG_PREIMAGE_PATHS.length)
|
|
107755
|
+
throw new Error("PREIMAGE_CONFIG_SET_INVALID");
|
|
107756
|
+
for (const [index, relativePath] of CONFIG_PREIMAGE_PATHS.entries()) {
|
|
107757
|
+
const config2 = receipt.configs[index];
|
|
107758
|
+
if (config2.relativePath !== relativePath || config2.sourcePath !== join54(home, relativePath))
|
|
107759
|
+
throw new Error("PREIMAGE_CONFIG_BINDING_INVALID");
|
|
107760
|
+
if (config2.present) {
|
|
107761
|
+
const backup = join54(runtimePath, "preimage", "configs", relativePath);
|
|
107762
|
+
const stat3 = lstatSync27(backup);
|
|
107763
|
+
const saved = readFileSync37(backup);
|
|
107764
|
+
if (!stat3.isFile() || stat3.isSymbolicLink() || hash5(saved) !== config2.sha256 || saved.byteLength !== config2.byteSize)
|
|
107765
|
+
throw new Error("PREIMAGE_CONFIG_DRIFT");
|
|
107766
|
+
}
|
|
107767
|
+
}
|
|
107768
|
+
}
|
|
107769
|
+
function captureConfigPreimages(home, stage) {
|
|
107770
|
+
const root2 = join54(stage, "preimage", "configs");
|
|
107771
|
+
mkdirSync24(root2, { recursive: true, mode: 448 });
|
|
107772
|
+
return CONFIG_PREIMAGE_PATHS.map((relativePath) => {
|
|
107773
|
+
const sourcePath = join54(home, relativePath);
|
|
107774
|
+
if (!entryExists(sourcePath))
|
|
107775
|
+
return { sourcePath, relativePath, present: false };
|
|
107776
|
+
const stat3 = lstatSync27(sourcePath);
|
|
107777
|
+
if (!stat3.isFile() || stat3.isSymbolicLink())
|
|
107778
|
+
throw new Error("CONFIG_PREIMAGE_NOT_REGULAR_FILE");
|
|
107779
|
+
const bytes = readFileSync37(sourcePath);
|
|
107780
|
+
const backupPath = join54(root2, relativePath);
|
|
107781
|
+
mkdirSync24(dirname29(backupPath), { recursive: true, mode: 448 });
|
|
107782
|
+
writeFileSync23(backupPath, bytes, { flag: "wx", mode: 384 });
|
|
107783
|
+
chmodSync4(backupPath, 384);
|
|
107784
|
+
const readback = readFileSync37(backupPath);
|
|
107785
|
+
if (!readback.equals(bytes))
|
|
107786
|
+
throw new Error("CONFIG_PREIMAGE_READBACK_MISMATCH");
|
|
107787
|
+
return { sourcePath, relativePath, present: true, sha256: hash5(bytes), byteSize: bytes.byteLength };
|
|
107788
|
+
});
|
|
107789
|
+
}
|
|
107790
|
+
function assertConfigUnchanged(configs) {
|
|
107791
|
+
for (const config2 of configs) {
|
|
107792
|
+
const present = entryExists(config2.sourcePath);
|
|
107793
|
+
if (present !== config2.present)
|
|
107794
|
+
throw new Error("CONFIG_DRIFT_DURING_UPDATE");
|
|
107795
|
+
if (present) {
|
|
107796
|
+
const stat3 = lstatSync27(config2.sourcePath);
|
|
107797
|
+
if (!stat3.isFile() || stat3.isSymbolicLink() || hash5(readFileSync37(config2.sourcePath)) !== config2.sha256) {
|
|
107798
|
+
throw new Error("CONFIG_DRIFT_DURING_UPDATE");
|
|
107799
|
+
}
|
|
107800
|
+
}
|
|
107801
|
+
}
|
|
107802
|
+
}
|
|
107803
|
+
async function runNpm(args2, cwd2, home, staging) {
|
|
107804
|
+
const userNpmrc = join54(staging, "npm-user.npmrc"), globalNpmrc = join54(staging, "npm-global.npmrc");
|
|
107805
|
+
if (!existsSync33(userNpmrc))
|
|
107806
|
+
writeFileSync23(userNpmrc, `registry=${REGISTRY_ORIGIN}/
|
|
107807
|
+
ignore-scripts=true
|
|
107808
|
+
`, { mode: 384, flag: "wx" });
|
|
107809
|
+
if (!existsSync33(globalNpmrc))
|
|
107810
|
+
writeFileSync23(globalNpmrc, `registry=${REGISTRY_ORIGIN}/
|
|
107811
|
+
ignore-scripts=true
|
|
107812
|
+
`, { mode: 384, flag: "wx" });
|
|
107813
|
+
const env3 = {
|
|
107814
|
+
PATH: process.env.PATH ?? "",
|
|
107815
|
+
HOME: home,
|
|
107816
|
+
TMPDIR: staging,
|
|
107817
|
+
TMP: staging,
|
|
107818
|
+
TEMP: staging,
|
|
107819
|
+
CI: "1",
|
|
107820
|
+
NPM_CONFIG_USERCONFIG: userNpmrc,
|
|
107821
|
+
NPM_CONFIG_GLOBALCONFIG: globalNpmrc,
|
|
107822
|
+
NPM_CONFIG_CACHE: join54(staging, "npm-cache"),
|
|
107823
|
+
NPM_CONFIG_REGISTRY: `${REGISTRY_ORIGIN}/`,
|
|
107824
|
+
NPM_CONFIG_IGNORE_SCRIPTS: "true"
|
|
107825
|
+
};
|
|
107826
|
+
const proc = Bun.spawn(["npm", ...args2, "--registry", `${REGISTRY_ORIGIN}/`, "--ignore-scripts", "--no-audit", "--no-fund"], {
|
|
107827
|
+
cwd: cwd2,
|
|
107828
|
+
env: env3,
|
|
107829
|
+
stdin: "ignore",
|
|
107830
|
+
stdout: "ignore",
|
|
107831
|
+
stderr: "ignore"
|
|
107832
|
+
});
|
|
107833
|
+
const exitCode = await proc.exited;
|
|
107834
|
+
if (exitCode !== 0)
|
|
107835
|
+
throw new Error("DEPENDENCY_GRAPH_INSTALL_FAILED");
|
|
107836
|
+
}
|
|
107837
|
+
function comparePackageTrees(expectedRoot, installedRoot) {
|
|
107838
|
+
const expected = readTree(expectedRoot, false);
|
|
107839
|
+
const installed = readTree(installedRoot, false);
|
|
107840
|
+
if (JSON.stringify(expected.entries) !== JSON.stringify(installed.entries))
|
|
107841
|
+
throw new Error("INSTALLED_PACKAGE_BYTES_MISMATCH");
|
|
107842
|
+
return expected.digest;
|
|
107843
|
+
}
|
|
107844
|
+
function replaceLink(path, target, backupPath, oldLinkTarget) {
|
|
107845
|
+
const stat3 = lstatSync27(path);
|
|
107846
|
+
if (!stat3.isSymbolicLink() || entryExists(backupPath))
|
|
107847
|
+
throw new Error("LAUNCHER_PREIMAGE_DRIFT");
|
|
107848
|
+
const temp = `${path}.skills-next-${randomUUID9()}`;
|
|
107849
|
+
symlinkSync(oldLinkTarget, backupPath);
|
|
107850
|
+
if (readlinkSync4(backupPath) !== oldLinkTarget || realpathSync11(backupPath) !== resolve22(dirname29(path), oldLinkTarget))
|
|
107851
|
+
throw new Error("LAUNCHER_BACKUP_READBACK_MISMATCH");
|
|
107852
|
+
symlinkSync(target, temp);
|
|
107853
|
+
if (realpathSync11(temp) !== target)
|
|
107854
|
+
throw new Error("LAUNCHER_NEW_LINK_READBACK_MISMATCH");
|
|
107855
|
+
renameSync13(temp, path);
|
|
107856
|
+
}
|
|
107857
|
+
function atomicJson(path, value) {
|
|
107858
|
+
const temp = `${path}.next-${randomUUID9()}`;
|
|
107859
|
+
const bytes = Buffer.from(`${JSON.stringify(value, null, 2)}
|
|
107860
|
+
`);
|
|
107861
|
+
writeFileSync23(temp, bytes, { flag: "wx", mode: 384 });
|
|
107862
|
+
chmodSync4(temp, 384);
|
|
107863
|
+
renameSync13(temp, path);
|
|
107864
|
+
if (!readFileSync37(path).equals(bytes))
|
|
107865
|
+
throw new Error("RECEIPT_READBACK_MISMATCH");
|
|
107866
|
+
}
|
|
107867
|
+
function persistReceipt(path, value) {
|
|
107868
|
+
if (entryExists(path)) {
|
|
107869
|
+
const priorBytes = readFileSync37(path);
|
|
107870
|
+
const prior = object8(JSON.parse(priorBytes.toString("utf8")), "RECEIPT_INVALID");
|
|
107871
|
+
if (prior.id !== value.id || prior.schema !== "skills.copyfile-runtime-receipt.v1" || typeof prior.state !== "string" || !/^[a-z-]{1,32}$/.test(prior.state))
|
|
107872
|
+
throw new Error("RECEIPT_ID_DRIFT");
|
|
107873
|
+
const nextBytes = Buffer.from(`${JSON.stringify(value, null, 2)}
|
|
107874
|
+
`);
|
|
107875
|
+
if (priorBytes.equals(nextBytes))
|
|
107876
|
+
return;
|
|
107877
|
+
const historyRoot = join54(dirname29(path), "receipt-history");
|
|
107878
|
+
if (!entryExists(historyRoot))
|
|
107879
|
+
mkdirSync24(historyRoot, { mode: 448 });
|
|
107880
|
+
const historyStat = lstatSync27(historyRoot);
|
|
107881
|
+
if (!historyStat.isDirectory() || historyStat.isSymbolicLink() || (historyStat.mode & 63) !== 0)
|
|
107882
|
+
throw new Error("RECEIPT_HISTORY_NOT_PRIVATE");
|
|
107883
|
+
const historyPath = join54(historyRoot, `${value.id}-${String(prior.state)}-${randomUUID9()}.json`);
|
|
107884
|
+
writeFileSync23(historyPath, priorBytes, { flag: "wx", mode: 256 });
|
|
107885
|
+
chmodSync4(historyPath, 256);
|
|
107886
|
+
if (!readFileSync37(historyPath).equals(priorBytes))
|
|
107887
|
+
throw new Error("RECEIPT_HISTORY_READBACK_MISMATCH");
|
|
107888
|
+
}
|
|
107889
|
+
atomicJson(path, value);
|
|
107890
|
+
}
|
|
107891
|
+
async function updateCopyfileRuntime(version2, options = {}) {
|
|
107892
|
+
if (!new RegExp(SEMVER_PATTERN).test(version2))
|
|
107893
|
+
throw new Error("EXACT_VERSION_REQUIRED");
|
|
107894
|
+
if (!STABLE_SEMVER_PATTERN.test(version2))
|
|
107895
|
+
throw new Error("EXACT_STABLE_VERSION_REQUIRED");
|
|
107896
|
+
const home = resolve22(options.homeDir ?? process.env.HOME ?? "");
|
|
107897
|
+
if (!home || !existsSync33(home))
|
|
107898
|
+
throw new Error("HOME_NOT_FOUND");
|
|
107899
|
+
const layout = resolveRuntimeLayout(home, options.pathValue ?? process.env.PATH ?? "");
|
|
107900
|
+
if (process.platform === "win32")
|
|
107901
|
+
throw new Error("COPYFILE_RUNTIME_PLATFORM_UNSUPPORTED");
|
|
107902
|
+
const registryOrigin = options.registryOrigin ?? REGISTRY_ORIGIN;
|
|
107903
|
+
const origin = new URL(registryOrigin);
|
|
107904
|
+
if (origin.protocol !== "https:" && options.registryOrigin === undefined)
|
|
107905
|
+
throw new Error("REGISTRY_ORIGIN_INVALID");
|
|
107906
|
+
if (origin.username || origin.password || origin.search || origin.hash)
|
|
107907
|
+
throw new Error("REGISTRY_ORIGIN_INVALID");
|
|
107908
|
+
const runtimeRootStat = lstatSync27(layout.runtimeRoot);
|
|
107909
|
+
if ((runtimeRootStat.mode & 63) !== 0)
|
|
107910
|
+
throw new Error("RUNTIME_ROOT_NOT_PRIVATE");
|
|
107911
|
+
const id2 = randomUUID9();
|
|
107912
|
+
const releaseLock = acquireRuntimeLock(layout.runtimeRoot);
|
|
107913
|
+
const stagePath = join54(layout.runtimeRoot, `.stage-${version2}-${id2}`);
|
|
107914
|
+
const finalPath = join54(layout.runtimeRoot, `${version2}-copyfile`);
|
|
107915
|
+
const launchers = layout.launchers.map((item) => ({ ...item, newTarget: join54(finalPath, "node_modules", "@hasna", "skills", layout.bin[basename13(item.path)]) }));
|
|
107916
|
+
let switched = [];
|
|
107917
|
+
let moved = false;
|
|
107918
|
+
try {
|
|
107919
|
+
if (entryExists(finalPath))
|
|
107920
|
+
throw new Error("TARGET_RUNTIME_ALREADY_EXISTS");
|
|
107921
|
+
mkdirSync24(stagePath, { mode: 448 });
|
|
107922
|
+
chmodSync4(stagePath, 448);
|
|
107923
|
+
const configPreimages = captureConfigPreimages(home, stagePath);
|
|
107924
|
+
const fetcher = options.fetcher ?? fetch;
|
|
107925
|
+
const artifact = await downloadPackage(version2, stagePath, origin.origin, fetcher);
|
|
107926
|
+
if (artifact.packageBins && JSON.stringify(Object.entries(artifact.packageBins).sort()) !== JSON.stringify(Object.entries(layout.bin).sort())) {
|
|
107927
|
+
throw new Error("PACKAGE_BIN_SET_CHANGED");
|
|
107928
|
+
}
|
|
107929
|
+
const stagePackage = join54(stagePath, "install");
|
|
107930
|
+
mkdirSync24(stagePackage, { mode: 448 });
|
|
107931
|
+
copyFileSync(artifact.tarballPath, join54(stagePackage, "verified.tgz"));
|
|
107932
|
+
writeFileSync23(join54(stagePackage, "package.json"), JSON.stringify({ private: true, dependencies: { [PACKAGE_NAME]: "file:./verified.tgz" } }, null, 2), { mode: 384, flag: "wx" });
|
|
107933
|
+
await runNpm(["install", "--package-lock-only", "--prefix", stagePackage], stagePackage, home, stagePath);
|
|
107934
|
+
await runNpm(["ci", "--prefix", stagePackage], stagePackage, home, stagePath);
|
|
107935
|
+
const lockBytes = readFileSync37(join54(stagePackage, "package-lock.json"));
|
|
107936
|
+
const installedPackage = join54(stagePackage, "node_modules", "@hasna", "skills");
|
|
107937
|
+
const installed = readPackage(join54(installedPackage, "package.json"));
|
|
107938
|
+
if (installed.version !== version2 || JSON.stringify(Object.entries(installed.bins).sort()) !== JSON.stringify(Object.entries(layout.bin).sort())) {
|
|
107939
|
+
throw new Error("INSTALLED_PACKAGE_IDENTITY_MISMATCH");
|
|
107940
|
+
}
|
|
107941
|
+
const unpackedPackage = join54(stagePath, "verified-archive", "package");
|
|
107942
|
+
const packageTreeSha256 = comparePackageTrees(unpackedPackage, installedPackage);
|
|
107943
|
+
if (packageTreeSha256 !== artifact.packageTreeSha256)
|
|
107944
|
+
throw new Error("PACKAGE_TREE_HASH_MISMATCH");
|
|
107945
|
+
const nodeModules = join54(stagePackage, "node_modules");
|
|
107946
|
+
normalizeModes(nodeModules);
|
|
107947
|
+
chmodSync4(stagePath, 448);
|
|
107948
|
+
const runtimeTree = readTree(nodeModules, true);
|
|
107949
|
+
if (runtimeTree.entries.some((entry) => entry.kind === "file" && ((entry.mode ?? 0) & 18) !== 0)) {
|
|
107950
|
+
throw new Error("RUNTIME_FILE_MODE_UNSAFE");
|
|
107951
|
+
}
|
|
107952
|
+
assertConfigUnchanged(configPreimages);
|
|
107953
|
+
const targetPackageRoot = join54(finalPath, "node_modules", "@hasna", "skills");
|
|
107954
|
+
const preimageSha256 = writePreimageManifest(stagePath, configPreimages, launchers);
|
|
107955
|
+
const receipt = {
|
|
107956
|
+
schema: "skills.copyfile-runtime-receipt.v1",
|
|
107957
|
+
id: id2,
|
|
107958
|
+
state: "prepared",
|
|
107959
|
+
package: PACKAGE_NAME,
|
|
107960
|
+
currentVersion: layout.currentVersion,
|
|
107961
|
+
targetVersion: version2,
|
|
107962
|
+
registryOrigin: origin.origin,
|
|
107963
|
+
tarballUrl: artifact.tarballUrl,
|
|
107964
|
+
tarballIntegrity: artifact.integrity,
|
|
107965
|
+
tarballSha256: artifact.sha256,
|
|
107966
|
+
tarballBytes: artifact.byteSize,
|
|
107967
|
+
packageTreeSha256,
|
|
107968
|
+
installLockSha256: hash5(lockBytes),
|
|
107969
|
+
runtimeTreeSha256: runtimeTree.digest,
|
|
107970
|
+
runtimeRoot: layout.runtimeRoot,
|
|
107971
|
+
currentPackageRoot: layout.currentPackageRoot,
|
|
107972
|
+
targetPackageRoot,
|
|
107973
|
+
configs: configPreimages,
|
|
107974
|
+
preimageSha256,
|
|
107975
|
+
launchers,
|
|
107976
|
+
switchedLaunchers: [],
|
|
107977
|
+
rollbackCompletedLaunchers: []
|
|
107978
|
+
};
|
|
107979
|
+
writeJsonPrivate(join54(stagePath, "rollout-receipt.json"), receipt);
|
|
107980
|
+
const finalNodeModules = join54(stagePackage, "node_modules");
|
|
107981
|
+
const stagedNodeModules = join54(stagePath, "node_modules");
|
|
107982
|
+
renameSync13(finalNodeModules, stagedNodeModules);
|
|
107983
|
+
copyFileSync(join54(stagePackage, "package-lock.json"), join54(stagePath, "install-lock.json"));
|
|
107984
|
+
chmodSync4(join54(stagePath, "install-lock.json"), 384);
|
|
107985
|
+
const stagedTree = readTree(stagedNodeModules, true);
|
|
107986
|
+
if (stagedTree.digest !== runtimeTree.digest)
|
|
107987
|
+
throw new Error("STAGED_RUNTIME_READBACK_MISMATCH");
|
|
107988
|
+
receipt.targetPackageRoot = join54(finalPath, "node_modules", "@hasna", "skills");
|
|
107989
|
+
receipt.runtimeTreeSha256 = stagedTree.digest;
|
|
107990
|
+
persistReceipt(join54(stagePath, "rollout-receipt.json"), receipt);
|
|
107991
|
+
renameSync13(stagePath, finalPath);
|
|
107992
|
+
moved = true;
|
|
107993
|
+
const finalReceiptBytes = Buffer.from(`${JSON.stringify(receipt, null, 2)}
|
|
107994
|
+
`);
|
|
107995
|
+
if (!readFileSync37(join54(finalPath, "rollout-receipt.json")).equals(finalReceiptBytes))
|
|
107996
|
+
throw new Error("RECEIPT_MOVE_READBACK_MISMATCH");
|
|
107997
|
+
assertConfigUnchanged(configPreimages);
|
|
107998
|
+
for (const item of launchers) {
|
|
107999
|
+
if (realpathSync11(item.path) !== item.oldTarget)
|
|
108000
|
+
throw new Error("LAUNCHER_DRIFT_BEFORE_SWITCH");
|
|
108001
|
+
receipt.state = "switching";
|
|
108002
|
+
receipt.switchedLaunchers = [...receipt.switchedLaunchers, item.path];
|
|
108003
|
+
persistReceipt(join54(finalPath, "rollout-receipt.json"), receipt);
|
|
108004
|
+
replaceLink(item.path, item.newTarget, item.backupPath, item.oldLinkTarget);
|
|
108005
|
+
if (realpathSync11(item.path) !== item.newTarget || realpathSync11(item.backupPath) !== item.oldTarget) {
|
|
108006
|
+
throw new Error("LAUNCHER_SWITCH_READBACK_MISMATCH");
|
|
108007
|
+
}
|
|
108008
|
+
switched.push(item.path);
|
|
108009
|
+
options.onLauncherSwitched?.(item.path);
|
|
108010
|
+
}
|
|
108011
|
+
receipt.state = "switched";
|
|
108012
|
+
persistReceipt(join54(finalPath, "rollout-receipt.json"), receipt);
|
|
108013
|
+
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 };
|
|
108014
|
+
} catch (error61) {
|
|
108015
|
+
if (switched.length > 0 || moved) {
|
|
108016
|
+
let rollbackFailed = false;
|
|
108017
|
+
for (const item of [...launchers].reverse()) {
|
|
108018
|
+
try {
|
|
108019
|
+
const currentTarget = realpathSync11(item.path);
|
|
108020
|
+
if (currentTarget === item.oldTarget && readlinkSync4(item.path) === item.oldLinkTarget)
|
|
108021
|
+
continue;
|
|
108022
|
+
if (currentTarget !== item.newTarget)
|
|
108023
|
+
throw new Error("LAUNCHER_CHANGED_DURING_ROLLBACK");
|
|
108024
|
+
const temp = `${item.path}.skills-rollback-${id2}`;
|
|
108025
|
+
const afterPath = `${item.path}.skills-after-${id2}`;
|
|
108026
|
+
if (entryExists(afterPath))
|
|
108027
|
+
throw new Error("ROLLBACK_PRESERVATION_PATH_EXISTS");
|
|
108028
|
+
symlinkSync(item.newTarget, afterPath);
|
|
108029
|
+
symlinkSync(item.oldLinkTarget, temp);
|
|
108030
|
+
renameSync13(temp, item.path);
|
|
108031
|
+
} catch {
|
|
108032
|
+
rollbackFailed = true;
|
|
108033
|
+
}
|
|
108034
|
+
}
|
|
108035
|
+
try {
|
|
108036
|
+
const receiptPath = join54(finalPath, "rollout-receipt.json");
|
|
108037
|
+
const receipt = JSON.parse(readFileSync37(receiptPath, "utf8"));
|
|
108038
|
+
receipt.state = rollbackFailed ? "rollback-required" : "rolled-back";
|
|
108039
|
+
persistReceipt(receiptPath, receipt);
|
|
108040
|
+
} catch {
|
|
108041
|
+
rollbackFailed = true;
|
|
108042
|
+
}
|
|
108043
|
+
throw new Error(rollbackFailed ? "UPDATE_FAILED_ROLLBACK_REQUIRED" : "UPDATE_FAILED_ROLLED_BACK");
|
|
108044
|
+
}
|
|
108045
|
+
throw error61 instanceof Error ? error61 : new Error("COPYFILE_UPDATE_FAILED");
|
|
108046
|
+
} finally {
|
|
108047
|
+
releaseLock();
|
|
108048
|
+
}
|
|
108049
|
+
}
|
|
108050
|
+
function rollbackCopyfileRuntime(receiptId, options = {}) {
|
|
108051
|
+
if (!/^[0-9a-f-]{36}$/.test(receiptId))
|
|
108052
|
+
throw new Error("RECEIPT_ID_INVALID");
|
|
108053
|
+
const home = resolve22(options.homeDir ?? process.env.HOME ?? "");
|
|
108054
|
+
if (!home || !existsSync33(home))
|
|
108055
|
+
throw new Error("HOME_NOT_FOUND");
|
|
108056
|
+
const runtimeRoot = join54(realpathSync11(home), ".hasna", "skills", "runtime");
|
|
108057
|
+
assertNoSymlinkAncestors(runtimeRoot);
|
|
108058
|
+
const rootStat = lstatSync27(runtimeRoot);
|
|
108059
|
+
if (!rootStat.isDirectory() || rootStat.isSymbolicLink() || (rootStat.mode & 63) !== 0)
|
|
108060
|
+
throw new Error("RUNTIME_ROOT_NOT_PRIVATE");
|
|
108061
|
+
const releaseLock = acquireRuntimeLock(runtimeRoot);
|
|
108062
|
+
try {
|
|
108063
|
+
const matches2 = readdirSync20(runtimeRoot).filter((name) => {
|
|
108064
|
+
if (!/^\d+\.\d+\.\d+-copyfile$/.test(name))
|
|
108065
|
+
return false;
|
|
108066
|
+
const receipt2 = join54(runtimeRoot, name, "rollout-receipt.json");
|
|
108067
|
+
if (!existsSync33(receipt2))
|
|
108068
|
+
return false;
|
|
108069
|
+
try {
|
|
108070
|
+
return JSON.parse(readFileSync37(receipt2, "utf8")).id === receiptId;
|
|
108071
|
+
} catch {
|
|
108072
|
+
return false;
|
|
108073
|
+
}
|
|
108074
|
+
});
|
|
108075
|
+
if (matches2.length !== 1)
|
|
108076
|
+
throw new Error("RECEIPT_NOT_FOUND_OR_AMBIGUOUS");
|
|
108077
|
+
const receiptPath = join54(runtimeRoot, matches2[0], "rollout-receipt.json");
|
|
108078
|
+
const receipt = object8(JSON.parse(readFileSync37(receiptPath, "utf8")), "RECEIPT_INVALID");
|
|
108079
|
+
if (receipt.schema !== "skills.copyfile-runtime-receipt.v1" || !["switched", "switching", "rollback-required"].includes(receipt.state) || receipt.runtimeRoot !== runtimeRoot) {
|
|
108080
|
+
throw new Error("RECEIPT_STATE_INVALID");
|
|
108081
|
+
}
|
|
108082
|
+
if (receipt.package !== PACKAGE_NAME || matches2[0] !== `${receipt.targetVersion}-copyfile` || !new RegExp(SEMVER_PATTERN).test(receipt.currentVersion) || !new RegExp(SEMVER_PATTERN).test(receipt.targetVersion))
|
|
108083
|
+
throw new Error("RECEIPT_BINDING_INVALID");
|
|
108084
|
+
const runtimePath = join54(runtimeRoot, matches2[0]);
|
|
108085
|
+
if (receipt.targetPackageRoot !== join54(runtimePath, "node_modules", "@hasna", "skills") || receipt.currentPackageRoot !== join54(runtimeRoot, `${receipt.currentVersion}-copyfile`, "node_modules", "@hasna", "skills"))
|
|
108086
|
+
throw new Error("RECEIPT_RUNTIME_BINDING_INVALID");
|
|
108087
|
+
if (readTree(join54(runtimePath, "node_modules"), true).digest !== receipt.runtimeTreeSha256) {
|
|
108088
|
+
throw new Error("ACTIVE_RUNTIME_TREE_DRIFT");
|
|
108089
|
+
}
|
|
108090
|
+
verifyPreimageManifest(runtimePath, receipt, home);
|
|
108091
|
+
const oldBins = readPackage(join54(receipt.currentPackageRoot, "package.json")).bins;
|
|
108092
|
+
const newPackage = readPackage(join54(receipt.targetPackageRoot, "package.json"));
|
|
108093
|
+
if (newPackage.version !== receipt.targetVersion || JSON.stringify(Object.entries(oldBins).sort()) !== JSON.stringify(Object.entries(newPackage.bins).sort()))
|
|
108094
|
+
throw new Error("RECEIPT_BIN_BINDING_INVALID");
|
|
108095
|
+
const switched = [];
|
|
108096
|
+
const seen = new Set;
|
|
108097
|
+
for (const item of receipt.launchers) {
|
|
108098
|
+
const bin = oldBins[basename13(item.path)];
|
|
108099
|
+
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)
|
|
108100
|
+
throw new Error("RECEIPT_LAUNCHER_BINDING_INVALID");
|
|
108101
|
+
assertNoSymlinkAncestors(dirname29(item.path));
|
|
108102
|
+
seen.add(item.path);
|
|
108103
|
+
const stat3 = lstatSync27(item.path);
|
|
108104
|
+
if (!stat3.isSymbolicLink())
|
|
108105
|
+
throw new Error("LAUNCHER_DRIFT_ROLLBACK_REFUSED");
|
|
108106
|
+
const activeTarget = realpathSync11(item.path);
|
|
108107
|
+
if (activeTarget !== item.newTarget && (activeTarget !== item.oldTarget || readlinkSync4(item.path) !== item.oldLinkTarget))
|
|
108108
|
+
throw new Error("LAUNCHER_DRIFT_ROLLBACK_REFUSED");
|
|
108109
|
+
const hasBackup = entryExists(item.backupPath);
|
|
108110
|
+
if (activeTarget === item.newTarget && !hasBackup)
|
|
108111
|
+
throw new Error("LAUNCHER_PREIMAGE_DRIFT_ROLLBACK_REFUSED");
|
|
108112
|
+
if (hasBackup) {
|
|
108113
|
+
const backup = lstatSync27(item.backupPath);
|
|
108114
|
+
if (!backup.isSymbolicLink() || readlinkSync4(item.backupPath) !== item.oldLinkTarget || realpathSync11(item.backupPath) !== item.oldTarget)
|
|
108115
|
+
throw new Error("LAUNCHER_PREIMAGE_DRIFT_ROLLBACK_REFUSED");
|
|
108116
|
+
}
|
|
108117
|
+
const afterPath = `${item.path}.skills-after-${receiptId}`;
|
|
108118
|
+
if (entryExists(afterPath)) {
|
|
108119
|
+
const after2 = lstatSync27(afterPath);
|
|
108120
|
+
if (!after2.isSymbolicLink() || realpathSync11(afterPath) !== item.newTarget)
|
|
108121
|
+
throw new Error("ROLLBACK_PRESERVATION_DRIFT");
|
|
108122
|
+
}
|
|
108123
|
+
switched.push(item);
|
|
108124
|
+
}
|
|
108125
|
+
for (const item of switched.reverse()) {
|
|
108126
|
+
const activeTarget = realpathSync11(item.path);
|
|
108127
|
+
if (activeTarget === item.oldTarget && readlinkSync4(item.path) === item.oldLinkTarget)
|
|
108128
|
+
continue;
|
|
108129
|
+
receipt.state = "rollback-required";
|
|
108130
|
+
persistReceipt(receiptPath, receipt);
|
|
108131
|
+
const afterPath = `${item.path}.skills-after-${receiptId}`;
|
|
108132
|
+
if (!entryExists(afterPath))
|
|
108133
|
+
symlinkSync(item.newTarget, afterPath);
|
|
108134
|
+
const temp = `${item.path}.skills-rollback-${receiptId}-${randomUUID9()}`;
|
|
108135
|
+
symlinkSync(item.oldLinkTarget, temp);
|
|
108136
|
+
renameSync13(temp, item.path);
|
|
108137
|
+
receipt.rollbackCompletedLaunchers = [...new Set([...receipt.rollbackCompletedLaunchers, item.path])];
|
|
108138
|
+
persistReceipt(receiptPath, receipt);
|
|
108139
|
+
}
|
|
108140
|
+
receipt.state = "rolled-back";
|
|
108141
|
+
persistReceipt(receiptPath, receipt);
|
|
108142
|
+
return { rolledBack: true, receiptId, restoredVersion: receipt.currentVersion, launcherCount: switched.length };
|
|
108143
|
+
} finally {
|
|
108144
|
+
releaseLock();
|
|
108145
|
+
}
|
|
108146
|
+
}
|
|
108147
|
+
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;
|
|
108148
|
+
var init_runtime_copyfile = __esm(() => {
|
|
108149
|
+
init_skill_contract();
|
|
108150
|
+
STABLE_SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
108151
|
+
MAX_TARBALL_BYTES = 100 * 1024 * 1024;
|
|
108152
|
+
MAX_TARBALL_EXPANDED_BYTES = 256 * 1024 * 1024;
|
|
108153
|
+
CONFIG_PREIMAGE_PATHS = [
|
|
108154
|
+
".claude/settings.json",
|
|
108155
|
+
".codex/config.toml",
|
|
108156
|
+
".codex/hooks.json",
|
|
108157
|
+
".hasna/skills/agent-policy.json"
|
|
108158
|
+
];
|
|
108159
|
+
});
|
|
108160
|
+
|
|
107311
108161
|
// src/cli/commands/runtime.ts
|
|
107312
108162
|
var exports_runtime = {};
|
|
107313
108163
|
__export(exports_runtime, {
|
|
107314
108164
|
registerRuntime: () => registerRuntime
|
|
107315
108165
|
});
|
|
107316
|
-
import { createHash as
|
|
107317
|
-
import { lstatSync as
|
|
107318
|
-
import { basename as
|
|
108166
|
+
import { createHash as createHash27, randomUUID as randomUUID10 } from "crypto";
|
|
108167
|
+
import { lstatSync as lstatSync28, mkdirSync as mkdirSync25, readFileSync as readFileSync38, realpathSync as realpathSync12, writeFileSync as writeFileSync24 } from "fs";
|
|
108168
|
+
import { basename as basename14, isAbsolute as isAbsolute23, join as join55 } from "path";
|
|
107319
108169
|
import { createInterface } from "readline";
|
|
107320
108170
|
function registerRuntime(parent) {
|
|
107321
108171
|
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 +108204,9 @@ function registerRuntime(parent) {
|
|
|
107354
108204
|
if (!expected)
|
|
107355
108205
|
throw new Error("Cloud artifact not found");
|
|
107356
108206
|
const bytes = await client2.download(id2, name);
|
|
107357
|
-
if (bytes.length !== expected.byteSize ||
|
|
108207
|
+
if (bytes.length !== expected.byteSize || createHash27("sha256").update(bytes).digest("hex") !== expected.sha256)
|
|
107358
108208
|
throw new Error("Cloud artifact integrity verification failed");
|
|
107359
|
-
|
|
108209
|
+
writeFileSync24(options.output, bytes, { flag: "wx", mode: 384 });
|
|
107360
108210
|
console.log(JSON.stringify({ path: options.output, sha256: expected.sha256, byteSize: bytes.length }));
|
|
107361
108211
|
} catch (error61) {
|
|
107362
108212
|
console.error(JSON.stringify({ error: error61.message }));
|
|
@@ -107384,7 +108234,40 @@ function registerRuntime(parent) {
|
|
|
107384
108234
|
});
|
|
107385
108235
|
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
108236
|
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) => {
|
|
108237
|
+
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) => {
|
|
108238
|
+
if (options.version && options.rollback) {
|
|
108239
|
+
console.error(JSON.stringify({ error: "UPDATE_AND_ROLLBACK_ARE_MUTUALLY_EXCLUSIVE" }));
|
|
108240
|
+
process.exitCode = 1;
|
|
108241
|
+
return;
|
|
108242
|
+
}
|
|
108243
|
+
if (options.version) {
|
|
108244
|
+
try {
|
|
108245
|
+
const result2 = await updateCopyfileRuntime(options.version);
|
|
108246
|
+
if (options.json)
|
|
108247
|
+
console.log(JSON.stringify(result2));
|
|
108248
|
+
else
|
|
108249
|
+
console.log(source_default.green(`Updated @hasna/skills to exact version ${options.version} via copyfile runtime.`));
|
|
108250
|
+
} catch (error61) {
|
|
108251
|
+
const message = error61 instanceof Error && /^[A-Z0-9_]+$/.test(error61.message) ? error61.message : "COPYFILE_UPDATE_FAILED";
|
|
108252
|
+
console.error(JSON.stringify({ updated: false, error: message }));
|
|
108253
|
+
process.exitCode = 1;
|
|
108254
|
+
}
|
|
108255
|
+
return;
|
|
108256
|
+
}
|
|
108257
|
+
if (options.rollback) {
|
|
108258
|
+
try {
|
|
108259
|
+
const result2 = rollbackCopyfileRuntime(options.rollback);
|
|
108260
|
+
if (options.json)
|
|
108261
|
+
console.log(JSON.stringify(result2));
|
|
108262
|
+
else
|
|
108263
|
+
console.log(source_default.green(`Restored Skills runtime launchers to ${String(result2.restoredVersion)}.`));
|
|
108264
|
+
} catch (error61) {
|
|
108265
|
+
const message = error61 instanceof Error && /^[A-Z0-9_]+$/.test(error61.message) ? error61.message : "COPYFILE_ROLLBACK_FAILED";
|
|
108266
|
+
console.error(JSON.stringify({ rolledBack: false, error: message }));
|
|
108267
|
+
process.exitCode = 1;
|
|
108268
|
+
}
|
|
108269
|
+
return;
|
|
108270
|
+
}
|
|
107388
108271
|
if (process.env.SKILLS_TEST_MODE === "1") {
|
|
107389
108272
|
if (options.json)
|
|
107390
108273
|
console.log(JSON.stringify({ updated: false, error: "Self-update disabled in test mode" }));
|
|
@@ -107442,14 +108325,14 @@ Updating ${name}...
|
|
|
107442
108325
|
async function readUpdatedVersion(bunExecutable) {
|
|
107443
108326
|
const output2 = await readUpdateCommand([bunExecutable, "pm", "bin", "-g"]);
|
|
107444
108327
|
const globalBin = output2.replace(/\r?\n$/, "");
|
|
107445
|
-
if (!globalBin || !
|
|
108328
|
+
if (!globalBin || !isAbsolute23(globalBin) || /[\r\n\0]/.test(globalBin))
|
|
107446
108329
|
throw new Error("Update bin discovery failed");
|
|
107447
108330
|
const installed = Bun.which("skills", { PATH: globalBin, cwd: globalBin });
|
|
107448
108331
|
const selected = Bun.which("skills");
|
|
107449
108332
|
if (!installed || !selected)
|
|
107450
108333
|
throw new Error("Updated command is unavailable");
|
|
107451
|
-
const selectedPath =
|
|
107452
|
-
if (selectedPath !==
|
|
108334
|
+
const selectedPath = realpathSync12(selected);
|
|
108335
|
+
if (selectedPath !== realpathSync12(installed))
|
|
107453
108336
|
throw new Error("Updated command is shadowed on PATH");
|
|
107454
108337
|
const version2 = (await readUpdateCommand([selectedPath, "--version"])).trim();
|
|
107455
108338
|
if (!new RegExp(SEMVER_PATTERN).test(version2))
|
|
@@ -107580,14 +108463,14 @@ function requireHttpUrl(value) {
|
|
|
107580
108463
|
}
|
|
107581
108464
|
function promptLine(question) {
|
|
107582
108465
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
107583
|
-
return new Promise((
|
|
108466
|
+
return new Promise((resolve23) => {
|
|
107584
108467
|
let settled = false;
|
|
107585
108468
|
const finish = (answer) => {
|
|
107586
108469
|
if (settled)
|
|
107587
108470
|
return;
|
|
107588
108471
|
settled = true;
|
|
107589
108472
|
rl.close();
|
|
107590
|
-
|
|
108473
|
+
resolve23(answer);
|
|
107591
108474
|
};
|
|
107592
108475
|
rl.once("SIGINT", () => finish(null));
|
|
107593
108476
|
rl.once("close", () => finish(null));
|
|
@@ -107707,7 +108590,7 @@ async function handleRun(name, args2, options, separatorBeforeSkill) {
|
|
|
107707
108590
|
if (!Number.isFinite(timeoutMs) || timeoutMs < 1 || timeoutMs > 1200000 || !Number.isFinite(intervalMs) || intervalMs < 100 || intervalMs > 60000)
|
|
107708
108591
|
throw new Error("Invalid cloud polling bounds");
|
|
107709
108592
|
const client3 = await CloudExecutionClient.configured();
|
|
107710
|
-
const admitted = await client3.submit(slug2, version2, input2, options.idempotencyKey ??
|
|
108593
|
+
const admitted = await client3.submit(slug2, version2, input2, options.idempotencyKey ?? randomUUID10(), selected?.selection);
|
|
107711
108594
|
if (options.wait)
|
|
107712
108595
|
console.error(JSON.stringify({ executionId: admitted.id, status: admitted.status }));
|
|
107713
108596
|
const result3 = options.wait ? await client3.wait(admitted.id, { timeoutMs, intervalMs }) : admitted;
|
|
@@ -107764,10 +108647,10 @@ async function handleRun(name, args2, options, separatorBeforeSkill) {
|
|
|
107764
108647
|
try {
|
|
107765
108648
|
parsePollingOptions(options);
|
|
107766
108649
|
inputFiles = (options.file ?? []).map((path) => {
|
|
107767
|
-
const info =
|
|
108650
|
+
const info = lstatSync28(path);
|
|
107768
108651
|
if (!info.isFile() || info.size > 20 * 1024 * 1024)
|
|
107769
108652
|
throw new Error("Input must be a regular file no larger than 20 MiB");
|
|
107770
|
-
return { name:
|
|
108653
|
+
return { name: basename14(path), bytes: new Uint8Array(readFileSync38(path)) };
|
|
107771
108654
|
});
|
|
107772
108655
|
describeRemoteFiles(inputFiles);
|
|
107773
108656
|
client2 = new RemoteSkillsClient(routing.apiKey, routing.apiOrigin);
|
|
@@ -108145,7 +109028,7 @@ async function handleExportsDownload(runId2, options) {
|
|
|
108145
109028
|
const canonicalSkill = typeof remoteRun.skill === "string" ? remoteRun.skill : "remote";
|
|
108146
109029
|
const requestedSkill = typeof remoteRun.requestedSlug === "string" && remoteRun.requestedSlug.trim() ? remoteRun.requestedSlug : canonicalSkill;
|
|
108147
109030
|
const exportDir = getRunExportDir(runId2, requestedSkill);
|
|
108148
|
-
|
|
109031
|
+
mkdirSync25(exportDir, { recursive: true });
|
|
108149
109032
|
const downloaded = [];
|
|
108150
109033
|
for (const artifact of artifacts) {
|
|
108151
109034
|
const artifactId = String(artifact.id || "");
|
|
@@ -108153,9 +109036,9 @@ async function handleExportsDownload(runId2, options) {
|
|
|
108153
109036
|
continue;
|
|
108154
109037
|
const verified = await client2.getVerifiedRunArtifact(runId2, artifactId);
|
|
108155
109038
|
const relativePath = safeArtifactRelativePath(typeof artifact.relativePath === "string" ? artifact.relativePath : artifact.fileName, String(artifact.fileName || artifactId));
|
|
108156
|
-
const outputPath =
|
|
109039
|
+
const outputPath = join55(exportDir, relativePath);
|
|
108157
109040
|
ensureSafeExportParent(exportDir, relativePath);
|
|
108158
|
-
|
|
109041
|
+
writeFileSync24(outputPath, verified.bytes, { flag: "wx", mode: 384 });
|
|
108159
109042
|
downloaded.push({ id: artifactId, path: outputPath, byteSize: verified.byteSize });
|
|
108160
109043
|
}
|
|
108161
109044
|
const payload = {
|
|
@@ -108192,14 +109075,14 @@ function ensureSafeExportParent(root2, relativePath) {
|
|
|
108192
109075
|
let parent = root2;
|
|
108193
109076
|
for (const part of ["", ...parts]) {
|
|
108194
109077
|
if (part)
|
|
108195
|
-
parent =
|
|
109078
|
+
parent = join55(parent, part);
|
|
108196
109079
|
try {
|
|
108197
|
-
if (!
|
|
109080
|
+
if (!lstatSync28(parent).isDirectory() || lstatSync28(parent).isSymbolicLink())
|
|
108198
109081
|
throw new Error("Unsafe artifact directory");
|
|
108199
109082
|
} catch (error61) {
|
|
108200
109083
|
if (error61.code !== "ENOENT")
|
|
108201
109084
|
throw error61;
|
|
108202
|
-
|
|
109085
|
+
mkdirSync25(parent, { mode: 448 });
|
|
108203
109086
|
}
|
|
108204
109087
|
}
|
|
108205
109088
|
}
|
|
@@ -108232,7 +109115,7 @@ async function pollRemoteRun(client2, runId2, options) {
|
|
|
108232
109115
|
const remaining = deadline - Date.now();
|
|
108233
109116
|
if (remaining <= 0)
|
|
108234
109117
|
break;
|
|
108235
|
-
await new Promise((
|
|
109118
|
+
await new Promise((resolve23) => setTimeout(resolve23, Math.min(options.intervalMs, remaining)));
|
|
108236
109119
|
}
|
|
108237
109120
|
return {
|
|
108238
109121
|
run: current ?? { id: runId2, status: "queued" },
|
|
@@ -108403,6 +109286,7 @@ var init_runtime = __esm(() => {
|
|
|
108403
109286
|
init_remote_client();
|
|
108404
109287
|
init_remote_account2();
|
|
108405
109288
|
init_remote_files();
|
|
109289
|
+
init_runtime_copyfile();
|
|
108406
109290
|
});
|
|
108407
109291
|
|
|
108408
109292
|
// src/cli/commands/customer-verification.ts
|
|
@@ -108424,7 +109308,7 @@ async function readCode() {
|
|
|
108424
109308
|
function promptCode() {
|
|
108425
109309
|
const { stdin, stderr: output2 } = process;
|
|
108426
109310
|
const { isRaw: wasRaw, readableFlowing: wasFlowing } = stdin;
|
|
108427
|
-
return new Promise((
|
|
109311
|
+
return new Promise((resolve23) => {
|
|
108428
109312
|
let value = "", settled = false;
|
|
108429
109313
|
const finish = (answer) => {
|
|
108430
109314
|
if (settled)
|
|
@@ -108441,7 +109325,7 @@ function promptCode() {
|
|
|
108441
109325
|
`);
|
|
108442
109326
|
if (answer === null)
|
|
108443
109327
|
process.exitCode = 130;
|
|
108444
|
-
|
|
109328
|
+
resolve23(answer);
|
|
108445
109329
|
};
|
|
108446
109330
|
const cancel = () => finish(null);
|
|
108447
109331
|
const keypress = (text4, key) => {
|
|
@@ -108486,7 +109370,7 @@ var exports_recurring = {};
|
|
|
108486
109370
|
__export(exports_recurring, {
|
|
108487
109371
|
registerRecurringCommands: () => registerRecurringCommands
|
|
108488
109372
|
});
|
|
108489
|
-
import { constants as constants19, closeSync as closeSync22, fstatSync as fstatSync22, lstatSync as
|
|
109373
|
+
import { constants as constants19, closeSync as closeSync22, fstatSync as fstatSync22, lstatSync as lstatSync29, openSync as openSync22, readSync as readSync16 } from "fs";
|
|
108490
109374
|
import { createInterface as createInterface2 } from "readline";
|
|
108491
109375
|
function readRequest(path) {
|
|
108492
109376
|
const fd = openSync22(path, constants19.O_RDONLY | constants19.O_NOFOLLOW | constants19.O_NONBLOCK);
|
|
@@ -108502,7 +109386,7 @@ function readRequest(path) {
|
|
|
108502
109386
|
break;
|
|
108503
109387
|
length += n2;
|
|
108504
109388
|
}
|
|
108505
|
-
const after2 = fstatSync22(fd), current =
|
|
109389
|
+
const after2 = fstatSync22(fd), current = lstatSync29(path);
|
|
108506
109390
|
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
109391
|
throw new RecurringInputError;
|
|
108508
109392
|
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, length)));
|
|
@@ -108513,10 +109397,10 @@ function readRequest(path) {
|
|
|
108513
109397
|
}
|
|
108514
109398
|
}
|
|
108515
109399
|
async function acceptAtTerminal(draft) {
|
|
108516
|
-
await new Promise((
|
|
109400
|
+
await new Promise((resolve23, reject2) => process.stderr.write(`${jsonText(draft)}
|
|
108517
109401
|
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((
|
|
109402
|
+
`, (error61) => error61 ? reject2(error61) : resolve23()));
|
|
109403
|
+
return new Promise((resolve23) => {
|
|
108520
109404
|
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
108521
109405
|
let settled = false;
|
|
108522
109406
|
const finish = (accepted) => {
|
|
@@ -108527,7 +109411,7 @@ This grants an additional recurring budget. Each occurrence reprices within the
|
|
|
108527
109411
|
rl.close();
|
|
108528
109412
|
if (!accepted)
|
|
108529
109413
|
process.exitCode = 130;
|
|
108530
|
-
|
|
109414
|
+
resolve23(accepted);
|
|
108531
109415
|
};
|
|
108532
109416
|
const timer = setTimeout(() => finish(false), 5 * 60 * 1000);
|
|
108533
109417
|
rl.once("SIGINT", () => finish(false));
|
|
@@ -108786,9 +109670,9 @@ var init_completion = __esm(() => {
|
|
|
108786
109670
|
});
|
|
108787
109671
|
|
|
108788
109672
|
// src/lib/portable-snapshot-filter.ts
|
|
108789
|
-
import { readdirSync as
|
|
109673
|
+
import { readdirSync as readdirSync21, statSync as statSync18 } from "fs";
|
|
108790
109674
|
import { homedir as homedir11 } from "os";
|
|
108791
|
-
import { join as
|
|
109675
|
+
import { join as join56, sep as sep10 } from "path";
|
|
108792
109676
|
function isExcludedSkillFileName(fileName) {
|
|
108793
109677
|
if (EXCLUDE_FILE_NAMES.has(fileName)) {
|
|
108794
109678
|
return true;
|
|
@@ -108811,27 +109695,27 @@ function isPortableWithinSkill(relativeParts) {
|
|
|
108811
109695
|
function homePathFor(definition, homesRoot) {
|
|
108812
109696
|
const home = homesRoot ?? homedir11();
|
|
108813
109697
|
if (definition.subClass === "skills" || definition.subClass === "custom") {
|
|
108814
|
-
return
|
|
109698
|
+
return join56(skillsDataRootForHome(home), definition.name);
|
|
108815
109699
|
}
|
|
108816
109700
|
if (definition.agent === "opencode") {
|
|
108817
|
-
return
|
|
109701
|
+
return join56(home, ".config", "opencode", "skills");
|
|
108818
109702
|
}
|
|
108819
|
-
return
|
|
109703
|
+
return join56(home, `.${definition.agent}`, "skills");
|
|
108820
109704
|
}
|
|
108821
109705
|
function destinationFor(definition, stationId, relativePath) {
|
|
108822
|
-
const category = definition.subClass === "agent-homes" ?
|
|
108823
|
-
return
|
|
109706
|
+
const category = definition.subClass === "agent-homes" ? join56("agent-homes", definition.agent ?? "") : definition.name;
|
|
109707
|
+
return join56("resources", stationId, "skills", category, ...relativePath.split(sep10));
|
|
108824
109708
|
}
|
|
108825
109709
|
function walkEntries(absoluteRoot) {
|
|
108826
109710
|
let entries;
|
|
108827
109711
|
try {
|
|
108828
|
-
entries =
|
|
109712
|
+
entries = readdirSync21(absoluteRoot, { withFileTypes: true });
|
|
108829
109713
|
} catch {
|
|
108830
109714
|
return [];
|
|
108831
109715
|
}
|
|
108832
109716
|
const output2 = [];
|
|
108833
109717
|
for (const entry of entries) {
|
|
108834
|
-
const childFull =
|
|
109718
|
+
const childFull = join56(absoluteRoot, entry.name);
|
|
108835
109719
|
if (entry.isSymbolicLink()) {
|
|
108836
109720
|
output2.push({ kind: "symlink", relativePath: entry.name, fullPath: childFull });
|
|
108837
109721
|
continue;
|
|
@@ -108842,7 +109726,7 @@ function walkEntries(absoluteRoot) {
|
|
|
108842
109726
|
}
|
|
108843
109727
|
const nested = walkEntries(childFull);
|
|
108844
109728
|
for (const item of nested) {
|
|
108845
|
-
output2.push({ ...item, relativePath:
|
|
109729
|
+
output2.push({ ...item, relativePath: join56(entry.name, item.relativePath) });
|
|
108846
109730
|
}
|
|
108847
109731
|
continue;
|
|
108848
109732
|
}
|
|
@@ -108935,22 +109819,22 @@ var init_portable_snapshot_filter = __esm(() => {
|
|
|
108935
109819
|
});
|
|
108936
109820
|
|
|
108937
109821
|
// src/lib/station-snapshot.ts
|
|
108938
|
-
import { createHash as
|
|
109822
|
+
import { createHash as createHash28 } from "crypto";
|
|
108939
109823
|
import {
|
|
108940
|
-
copyFileSync,
|
|
108941
|
-
mkdirSync as
|
|
108942
|
-
readFileSync as
|
|
109824
|
+
copyFileSync as copyFileSync2,
|
|
109825
|
+
mkdirSync as mkdirSync26,
|
|
109826
|
+
readFileSync as readFileSync39,
|
|
108943
109827
|
statSync as statSync19,
|
|
108944
|
-
writeFileSync as
|
|
109828
|
+
writeFileSync as writeFileSync25
|
|
108945
109829
|
} from "fs";
|
|
108946
|
-
import { dirname as
|
|
109830
|
+
import { dirname as dirname31, isAbsolute as isAbsolute24, relative as relative9, resolve as resolve23, sep as sep11 } from "path";
|
|
108947
109831
|
function validateStationId(stationId) {
|
|
108948
109832
|
if (!/^[a-z0-9-]+$/.test(stationId)) {
|
|
108949
109833
|
throw new StationSnapshotError("INVALID_STATION", `station id must be a slug, got: ${stationId}`);
|
|
108950
109834
|
}
|
|
108951
109835
|
}
|
|
108952
109836
|
function sha256File(filePath) {
|
|
108953
|
-
return
|
|
109837
|
+
return createHash28("sha256").update(readFileSync39(filePath)).digest("hex");
|
|
108954
109838
|
}
|
|
108955
109839
|
function scanHome(definition, homesRoot) {
|
|
108956
109840
|
const homePath = homePathFor(definition, homesRoot);
|
|
@@ -108958,7 +109842,7 @@ function scanHome(definition, homesRoot) {
|
|
|
108958
109842
|
const portable = [];
|
|
108959
109843
|
const skipped = [];
|
|
108960
109844
|
for (const entry of entries) {
|
|
108961
|
-
const relativeParts = entry.relativePath.split(
|
|
109845
|
+
const relativeParts = entry.relativePath.split(sep11);
|
|
108962
109846
|
const fileName = relativeParts[relativeParts.length - 1];
|
|
108963
109847
|
if (entry.kind === "symlink") {
|
|
108964
109848
|
skipped.push({ relativePath: entry.relativePath, reason: "symlink" });
|
|
@@ -109021,7 +109905,7 @@ function humanHomes(scanned) {
|
|
|
109021
109905
|
}));
|
|
109022
109906
|
}
|
|
109023
109907
|
function writeStationSnapshot(options) {
|
|
109024
|
-
const repoRoot =
|
|
109908
|
+
const repoRoot = resolve23(options.repoRoot ?? process.cwd());
|
|
109025
109909
|
const { scanned, plans, totalBytes } = planStationSnapshot(options);
|
|
109026
109910
|
const manifestFiles = plans.map((plan) => ({
|
|
109027
109911
|
relativePath: plan.source.relativePath,
|
|
@@ -109047,9 +109931,9 @@ function writeStationSnapshot(options) {
|
|
|
109047
109931
|
const conflicts = [];
|
|
109048
109932
|
const untouched = [];
|
|
109049
109933
|
for (const plan of plans) {
|
|
109050
|
-
const destination =
|
|
109051
|
-
const destinationRelative =
|
|
109052
|
-
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(
|
|
109934
|
+
const destination = resolve23(repoRoot, plan.destination);
|
|
109935
|
+
const destinationRelative = relative9(repoRoot, destination);
|
|
109936
|
+
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep11) || isAbsolute24(destinationRelative)) {
|
|
109053
109937
|
throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
|
|
109054
109938
|
}
|
|
109055
109939
|
let existingDigest = null;
|
|
@@ -109070,9 +109954,9 @@ function writeStationSnapshot(options) {
|
|
|
109070
109954
|
}
|
|
109071
109955
|
let written = 0;
|
|
109072
109956
|
for (const plan of untouched) {
|
|
109073
|
-
const destination =
|
|
109074
|
-
|
|
109075
|
-
|
|
109957
|
+
const destination = resolve23(repoRoot, plan.destination);
|
|
109958
|
+
mkdirSync26(dirname31(destination), { recursive: true });
|
|
109959
|
+
copyFileSync2(plan.source.fullPath, destination);
|
|
109076
109960
|
written += 1;
|
|
109077
109961
|
}
|
|
109078
109962
|
const unchanged3 = plans.length - untouched.length;
|
|
@@ -109089,9 +109973,9 @@ function writeStationSnapshot(options) {
|
|
|
109089
109973
|
},
|
|
109090
109974
|
files: manifestFiles
|
|
109091
109975
|
};
|
|
109092
|
-
const manifestPath =
|
|
109093
|
-
|
|
109094
|
-
|
|
109976
|
+
const manifestPath = resolve23(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
|
|
109977
|
+
mkdirSync26(dirname31(manifestPath), { recursive: true });
|
|
109978
|
+
writeFileSync25(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
109095
109979
|
`);
|
|
109096
109980
|
return {
|
|
109097
109981
|
...base3,
|
|
@@ -109122,8 +110006,8 @@ var exports_create_sync_config = {};
|
|
|
109122
110006
|
__export(exports_create_sync_config, {
|
|
109123
110007
|
registerCreateSync: () => registerCreateSync
|
|
109124
110008
|
});
|
|
109125
|
-
import { existsSync as
|
|
109126
|
-
import { join as
|
|
110009
|
+
import { existsSync as existsSync34 } from "fs";
|
|
110010
|
+
import { join as join57 } from "path";
|
|
109127
110011
|
function registerCreateSync(parent) {
|
|
109128
110012
|
const configCmd = parent.command("config").description("Manage skills configuration");
|
|
109129
110013
|
configCmd.command("show", { isDefault: true }).option("--json", "Output as JSON", false).description("Show current merged configuration").action((options) => {
|
|
@@ -109203,13 +110087,13 @@ function registerCreateSync(parent) {
|
|
|
109203
110087
|
const pp = getConfigPath("project");
|
|
109204
110088
|
if (options.json) {
|
|
109205
110089
|
console.log(JSON.stringify({
|
|
109206
|
-
global: { path: gp, exists:
|
|
109207
|
-
project: { path: pp, exists:
|
|
110090
|
+
global: { path: gp, exists: existsSync34(gp) },
|
|
110091
|
+
project: { path: pp, exists: existsSync34(pp) }
|
|
109208
110092
|
}, null, 2));
|
|
109209
110093
|
return;
|
|
109210
110094
|
}
|
|
109211
|
-
console.log(`${source_default.cyan("global")}: ${gp}${
|
|
109212
|
-
console.log(`${source_default.cyan("project")}: ${pp}${
|
|
110095
|
+
console.log(`${source_default.cyan("global")}: ${gp}${existsSync34(gp) ? source_default.green(" (exists)") : source_default.dim(" (not found)")}`);
|
|
110096
|
+
console.log(`${source_default.cyan("project")}: ${pp}${existsSync34(pp) ? source_default.green(" (exists)") : source_default.dim(" (not found)")}`);
|
|
109213
110097
|
});
|
|
109214
110098
|
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
110099
|
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 +110117,7 @@ function handleCreate(name, options) {
|
|
|
109233
110117
|
console.log(source_default.green(`\u2713 Created custom skill '${result2.name}' at ${result2.path}`));
|
|
109234
110118
|
console.log(source_default.dim(` Category: ${result2.manifest.category}`));
|
|
109235
110119
|
console.log(source_default.dim(` Tags: ${result2.manifest.tags?.join(", ")}`));
|
|
109236
|
-
console.log(` ${source_default.cyan("Edit:")} ${
|
|
110120
|
+
console.log(` ${source_default.cyan("Edit:")} ${join57(result2.path, options.kind === "instruction" ? "SKILL.md" : "src/index.ts")}`);
|
|
109237
110121
|
console.log(` ${source_default.cyan("Prepare:")} skills prepare ${result2.name} --version <new-semver>`);
|
|
109238
110122
|
}
|
|
109239
110123
|
} catch (error61) {
|
|
@@ -109569,14 +110453,14 @@ var init_codex_native_config = () => {};
|
|
|
109569
110453
|
|
|
109570
110454
|
// src/lib/codex-hook-rpc.ts
|
|
109571
110455
|
import { execFile, spawn as spawn3 } from "child_process";
|
|
109572
|
-
import { realpathSync as
|
|
109573
|
-
import { isAbsolute as
|
|
110456
|
+
import { realpathSync as realpathSync13, statSync as statSync20 } from "fs";
|
|
110457
|
+
import { isAbsolute as isAbsolute25 } from "path";
|
|
109574
110458
|
import { promisify } from "util";
|
|
109575
110459
|
async function connectCodexHookRpc(options) {
|
|
109576
|
-
const found =
|
|
110460
|
+
const found = isAbsolute25(options.command) ? options.command : Bun.which(options.command, { PATH: process.env.PATH });
|
|
109577
110461
|
if (!found)
|
|
109578
110462
|
throw new Error("CODEX_HOOK_TRUST_NATIVE_UNAVAILABLE: install Codex or pass --codex-command");
|
|
109579
|
-
const binary =
|
|
110463
|
+
const binary = realpathSync13(found), stat3 = statSync20(binary);
|
|
109580
110464
|
if (!stat3.isFile() || (stat3.mode & 18) !== 0)
|
|
109581
110465
|
throw new Error("CODEX_HOOK_TRUST_UNSAFE_EXECUTABLE");
|
|
109582
110466
|
let version2;
|
|
@@ -109589,7 +110473,7 @@ async function connectCodexHookRpc(options) {
|
|
|
109589
110473
|
if (!SUPPORTED_CODEX_HOOK_VERSIONS.includes(version2))
|
|
109590
110474
|
throw new Error("CODEX_HOOK_TRUST_NATIVE_UNSUPPORTED_VERSION");
|
|
109591
110475
|
const current = statSync20(binary);
|
|
109592
|
-
if (
|
|
110476
|
+
if (realpathSync13(found) !== binary || ["dev", "ino", "size", "mtimeMs", "ctimeMs"].some((k) => stat3[k] !== current[k]))
|
|
109593
110477
|
throw new Error("CODEX_HOOK_TRUST_NATIVE_EXECUTABLE_CHANGED");
|
|
109594
110478
|
const env3 = { ...process.env, HOME: options.home, NO_COLOR: "1" };
|
|
109595
110479
|
if (options.codexHome === undefined)
|
|
@@ -109664,13 +110548,13 @@ async function connectCodexHookRpc(options) {
|
|
|
109664
110548
|
request(method2, params) {
|
|
109665
110549
|
if (stopped)
|
|
109666
110550
|
return Promise.reject(new Error("CODEX_HOOK_TRUST_NATIVE_RPC_CLOSED"));
|
|
109667
|
-
return new Promise((
|
|
110551
|
+
return new Promise((resolve24, reject2) => {
|
|
109668
110552
|
const id2 = nextId++;
|
|
109669
110553
|
const timer = setTimeout(() => {
|
|
109670
110554
|
fail5();
|
|
109671
110555
|
child.kill();
|
|
109672
110556
|
}, 20000);
|
|
109673
|
-
pending.set(id2, { resolve:
|
|
110557
|
+
pending.set(id2, { resolve: resolve24, reject: reject2, timer });
|
|
109674
110558
|
child.stdin.write(JSON.stringify({ id: id2, method: method2, params }) + `
|
|
109675
110559
|
`);
|
|
109676
110560
|
});
|
|
@@ -109680,14 +110564,14 @@ async function connectCodexHookRpc(options) {
|
|
|
109680
110564
|
child.stdin.end();
|
|
109681
110565
|
if (child.exitCode !== null || child.signalCode !== null)
|
|
109682
110566
|
return;
|
|
109683
|
-
await new Promise((
|
|
110567
|
+
await new Promise((resolve24) => {
|
|
109684
110568
|
const timer = setTimeout(() => {
|
|
109685
110569
|
child.kill();
|
|
109686
|
-
|
|
110570
|
+
resolve24();
|
|
109687
110571
|
}, 1000);
|
|
109688
110572
|
child.once("exit", () => {
|
|
109689
110573
|
clearTimeout(timer);
|
|
109690
|
-
|
|
110574
|
+
resolve24();
|
|
109691
110575
|
});
|
|
109692
110576
|
});
|
|
109693
110577
|
}
|
|
@@ -109708,21 +110592,21 @@ var init_codex_hook_rpc = __esm(() => {
|
|
|
109708
110592
|
});
|
|
109709
110593
|
|
|
109710
110594
|
// src/lib/codex-hook-trust-identity.ts
|
|
109711
|
-
import { realpathSync as
|
|
109712
|
-
import { dirname as
|
|
110595
|
+
import { realpathSync as realpathSync14 } from "fs";
|
|
110596
|
+
import { dirname as dirname32, isAbsolute as isAbsolute26, join as join58 } from "path";
|
|
109713
110597
|
import { isDeepStrictEqual as isDeepStrictEqual11 } from "util";
|
|
109714
110598
|
function inspectRecordedSkillsCli(command, expected) {
|
|
109715
|
-
need4(
|
|
109716
|
-
const resolved =
|
|
110599
|
+
need4(isAbsolute26(command) && !command.includes("\x00"), "RECONCILE_RECORDED_COMMAND_REQUIRED");
|
|
110600
|
+
const resolved = realpathSync14(command);
|
|
109717
110601
|
const cli = snapshot(resolved, false, { readOnlyPackage: true });
|
|
109718
|
-
const manifest = snapshot(
|
|
110602
|
+
const manifest = snapshot(join58(dirname32(dirname32(resolved)), "package.json"), false, { readOnlyPackage: true });
|
|
109719
110603
|
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 ===
|
|
110604
|
+
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
110605
|
need4((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
|
|
109722
110606
|
const receipt = { path: resolved, version: pkg.version, sha256: cli.sha256, manifestSha256: manifest.sha256 };
|
|
109723
110607
|
need4(isDeepStrictEqual11(receipt, expected), "RECONCILE_SKILLS_BINDING_CHANGED");
|
|
109724
110608
|
return { receipt, recheck() {
|
|
109725
|
-
need4(
|
|
110609
|
+
need4(realpathSync14(command) === resolved, "SKILLS_COMMAND_CHANGED");
|
|
109726
110610
|
unchanged2(cli);
|
|
109727
110611
|
unchanged2(manifest);
|
|
109728
110612
|
} };
|
|
@@ -109730,22 +110614,22 @@ function inspectRecordedSkillsCli(command, expected) {
|
|
|
109730
110614
|
function bindSkillsCli(command, reviewed) {
|
|
109731
110615
|
const normal = Bun.which("skills", { PATH: process.env.PATH });
|
|
109732
110616
|
need4(normal, "SKILLS_COMMAND_UNAVAILABLE");
|
|
109733
|
-
const resolved =
|
|
109734
|
-
const bound =
|
|
109735
|
-
need4(bound &&
|
|
110617
|
+
const resolved = realpathSync14(normal);
|
|
110618
|
+
const bound = isAbsolute26(command) ? command : command === "skills" ? normal : undefined;
|
|
110619
|
+
need4(bound && realpathSync14(bound) === resolved, "SKILLS_COMMAND_MISMATCH");
|
|
109736
110620
|
const entrypoint = reviewed?.path ?? process.argv[1];
|
|
109737
|
-
need4(entrypoint &&
|
|
110621
|
+
need4(entrypoint && realpathSync14(entrypoint) === resolved, "SKILLS_ENTRYPOINT_MISMATCH");
|
|
109738
110622
|
const cli = snapshot(resolved, false, { readOnlyPackage: true });
|
|
109739
|
-
const manifest = snapshot(
|
|
110623
|
+
const manifest = snapshot(join58(dirname32(dirname32(resolved)), "package.json"), false, { readOnlyPackage: true });
|
|
109740
110624
|
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 ===
|
|
110625
|
+
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
110626
|
if (reviewed)
|
|
109743
110627
|
need4(/^[a-f0-9]{64}$/.test(reviewed.sha256) && reviewed.sha256 === cli.sha256 && reviewed.version === pkg.version, "SKILLS_RELEASE_MISMATCH");
|
|
109744
110628
|
need4((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
|
|
109745
110629
|
return {
|
|
109746
110630
|
receipt: { path: resolved, version: pkg.version, sha256: cli.sha256, manifestSha256: manifest.sha256 },
|
|
109747
110631
|
recheck() {
|
|
109748
|
-
need4(Bun.which("skills", { PATH: process.env.PATH }) === normal &&
|
|
110632
|
+
need4(Bun.which("skills", { PATH: process.env.PATH }) === normal && realpathSync14(normal) === resolved && realpathSync14(bound) === resolved && realpathSync14(entrypoint) === resolved, "SKILLS_COMMAND_CHANGED");
|
|
109749
110633
|
unchanged2(cli);
|
|
109750
110634
|
unchanged2(manifest);
|
|
109751
110635
|
}
|
|
@@ -109779,23 +110663,23 @@ var init_codex_hook_policy_recovery = __esm(() => {
|
|
|
109779
110663
|
});
|
|
109780
110664
|
|
|
109781
110665
|
// src/lib/agent-codex-trust.ts
|
|
109782
|
-
import { lstatSync as
|
|
109783
|
-
import { createHash as
|
|
110666
|
+
import { lstatSync as lstatSync30, mkdirSync as mkdirSync27, readdirSync as readdirSync22, readFileSync as readFileSync40, realpathSync as realpathSync15, statSync as statSync21 } from "fs";
|
|
110667
|
+
import { createHash as createHash29, randomUUID as randomUUID11 } from "crypto";
|
|
109784
110668
|
import { execFileSync } from "child_process";
|
|
109785
|
-
import { dirname as
|
|
110669
|
+
import { dirname as dirname33, isAbsolute as isAbsolute27, join as join59, resolve as resolve24 } from "path";
|
|
109786
110670
|
import { homedir as homedir12 } from "os";
|
|
109787
110671
|
import { isDeepStrictEqual as isDeepStrictEqual13 } from "util";
|
|
109788
110672
|
function verifyReleaseArtifact(proof, skillsCli) {
|
|
109789
|
-
need4(
|
|
109790
|
-
const tarPath =
|
|
110673
|
+
need4(isAbsolute27(proof.packageTarPath) && !proof.packageTarPath.includes("\x00"), "RECONCILE_RELEASE_PROVENANCE_PATH");
|
|
110674
|
+
const tarPath = realpathSync15(proof.packageTarPath);
|
|
109791
110675
|
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",
|
|
110676
|
+
const tarBytes = readFileSync40(tarPath);
|
|
110677
|
+
need4(tarBytes.byteLength === proof.packageTarBytes && createHash29("sha256").update(tarBytes).digest("hex") === proof.packageTarSha256, "RECONCILE_RELEASE_ARTIFACT_CHANGED");
|
|
110678
|
+
const packageRoot = dirname33(dirname33(realpathSync15(skillsCli.receipt.path)));
|
|
110679
|
+
const manifest = readFileSync40(join59(packageRoot, "package.json"));
|
|
110680
|
+
need4(createHash29("sha256").update(manifest).digest("hex") === skillsCli.receipt.manifestSha256 && skillsCli.receipt.manifestSha256 === proof.manifestSha256, "RECONCILE_RELEASE_MANIFEST_CHANGED");
|
|
110681
|
+
need4(createHash29("sha256").update(readFileSync40(skillsCli.receipt.path)).digest("hex") === proof.executableSha256 && skillsCli.receipt.sha256 === proof.executableSha256, "RECONCILE_RELEASE_EXECUTABLE_CHANGED");
|
|
110682
|
+
const members2 = [["package/package.json", manifest], ["package/bin/index.js", readFileSync40(skillsCli.receipt.path)]];
|
|
109799
110683
|
for (const [member, expected] of members2) {
|
|
109800
110684
|
let actual;
|
|
109801
110685
|
try {
|
|
@@ -109809,26 +110693,26 @@ function verifyReleaseArtifact(proof, skillsCli) {
|
|
|
109809
110693
|
async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc) {
|
|
109810
110694
|
let rpc;
|
|
109811
110695
|
try {
|
|
109812
|
-
const home =
|
|
109813
|
-
const parent =
|
|
110696
|
+
const home = resolve24(options.home ?? homedir12()), dataDir3 = resolve24(options.dataDir ?? getDataDirReadOnly());
|
|
110697
|
+
const parent = resolve24(join59(dataDir3, "native-hook-trust")), journal = resolve24(options.journal);
|
|
109814
110698
|
need4(journal.startsWith(parent + "/") && /^[a-f0-9-]{36}$/.test(journal.slice(parent.length + 1)), "RECONCILE_JOURNAL_PATH");
|
|
109815
|
-
const dir =
|
|
110699
|
+
const dir = lstatSync30(journal, { throwIfNoEntry: false });
|
|
109816
110700
|
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(
|
|
110701
|
+
need4(!lstatSync30(join59(journal, "receipt.json"), { throwIfNoEntry: false }), "RECONCILE_ALREADY_COMPLETE");
|
|
110702
|
+
need4(lstatSync30(join59(journal, "stopped.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_JOURNAL_INCOMPLETE");
|
|
110703
|
+
const stoppedFile = snapshot(join59(journal, "stopped.json"), true);
|
|
110704
|
+
const intentFile = snapshot(join59(journal, "intent.json"), true), intent = JSON.parse(intentFile.text);
|
|
110705
|
+
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
110706
|
const legacyIntent = intent.home === undefined && intent.configSha256 === undefined && intent.configVersion === undefined && intent.declarations === undefined && intent.admitted === undefined && intent.nativeHooks === undefined;
|
|
109823
110707
|
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: `${
|
|
110708
|
+
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
110709
|
const admitted = intent.admitted ?? intent.hooks;
|
|
109826
110710
|
const configSha256 = intent.configSha256 ?? configBefore.sha256, configVersion = intent.configVersion ?? intent.expectedVersion;
|
|
109827
110711
|
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
110712
|
need4(makePlanDigest(digestInput) === intent.planDigest, "RECONCILE_PLAN_CHANGED");
|
|
109829
110713
|
need4(isDeepStrictEqual13(intent.hooks, admitted.filter((hook) => !hook.enabled || hook.trustStatus !== "trusted")), "RECONCILE_INTENT_HOOKS_CHANGED");
|
|
109830
110714
|
need4(configBefore.sha256 === (intent.beforeSha256 ?? intent.configSha256) && hooksBefore.sha256 === intent.hooksSha256 && policyBefore.sha256 === intent.policySha256, "RECONCILE_JOURNAL_CHANGED");
|
|
109831
|
-
const policyCurrent = snapshot(
|
|
110715
|
+
const policyCurrent = snapshot(join59(dataDir3, "agent-policy.json"), true);
|
|
109832
110716
|
const { claudeDiscoveryRecovery: claudeRecovery, nativeHookAdditionsRecovery: additionsRecovery } = options;
|
|
109833
110717
|
const recovery = claudeRecovery ?? additionsRecovery;
|
|
109834
110718
|
need4(!recovery || !options.supersedeBinding, "RECONCILE_RECOVERY_MODE_CONFLICT");
|
|
@@ -109845,17 +110729,17 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
109845
110729
|
if (supersededBinding || recovery) {
|
|
109846
110730
|
need4(Array.isArray(intent.nativeHooks) && intent.nativeHooks.length > 0, "RECONCILE_NATIVE_INVENTORY_REQUIRED");
|
|
109847
110731
|
const proof = options.releaseProof;
|
|
109848
|
-
need4(!!proof &&
|
|
110732
|
+
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
110733
|
}
|
|
109850
110734
|
const currentConfig = snapshot(intent.configPath, true);
|
|
109851
110735
|
const policy = parseManagedSkillPolicy(policyCurrent.text), binding = policy.bridge;
|
|
109852
|
-
const alias = binding.rootAliases?.find((item) => item.agent === "codex"), physicalRoot = alias?.target ??
|
|
109853
|
-
need4(
|
|
110736
|
+
const alias = binding.rootAliases?.find((item) => item.agent === "codex"), physicalRoot = alias?.target ?? join59(home, ".codex"), hooksPath = join59(physicalRoot, "hooks.json");
|
|
110737
|
+
need4(resolve24(intent.configPath) === resolve24(join59(physicalRoot, "config.toml")), "RECONCILE_CONFIG_PATH_CHANGED");
|
|
109854
110738
|
const nativeConfigPath = intent.nativeConfigPath ?? intent.configPath;
|
|
109855
|
-
need4([
|
|
109856
|
-
const codexHome = nativeConfigPath !== intent.configPath ? undefined :
|
|
110739
|
+
need4([join59(home, ".codex/config.toml"), join59(physicalRoot, "config.toml")].includes(nativeConfigPath), "RECONCILE_NATIVE_CONFIG_PATH_CHANGED");
|
|
110740
|
+
const codexHome = nativeConfigPath !== intent.configPath ? undefined : dirname33(nativeConfigPath);
|
|
109857
110741
|
const hooksCurrent = snapshot(hooksPath);
|
|
109858
|
-
const additions = additionsRecovery ? reviewNativeHookAdditions({ path: additionsRecovery.path, intent: intentFile, policy: policyCurrent, configBefore, configCurrent: currentConfig, hooksBefore, hooksCurrent, nativeHooksPath:
|
|
110742
|
+
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
110743
|
const managedHooks = [...intent.hooks, ...additions?.hooks ?? []];
|
|
109860
110744
|
const managedKeys = managedHooks.map((h) => h.key);
|
|
109861
110745
|
const witnessSame = codexTrustReconcileWitness(configBefore.text, managedKeys) === codexTrustReconcileWitness(currentConfig.text, managedKeys);
|
|
@@ -109919,7 +110803,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
109919
110803
|
need4(isDeepStrictEqual13(actual, expected), "RECONCILE_UNRELATED_HOOK_CHANGED");
|
|
109920
110804
|
}
|
|
109921
110805
|
for (const wanted of intent.hooks) {
|
|
109922
|
-
const matches2 = nativeHooks.filter((h2) => h2.key === wanted.key || h2.key === wanted.key.replace(
|
|
110806
|
+
const matches2 = nativeHooks.filter((h2) => h2.key === wanted.key || h2.key === wanted.key.replace(join59(home, ".codex/hooks.json"), hooksPath));
|
|
109923
110807
|
need4(matches2.length === 1, "RECONCILE_NATIVE_IDENTITY_CHANGED");
|
|
109924
110808
|
const h = matches2[0];
|
|
109925
110809
|
const trusted = h.trustStatus === "trusted";
|
|
@@ -109968,7 +110852,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
109968
110852
|
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
110853
|
if (preview2)
|
|
109970
110854
|
return receipt;
|
|
109971
|
-
save(
|
|
110855
|
+
save(join59(journal, "receipt.json"), json2(receipt));
|
|
109972
110856
|
return receipt;
|
|
109973
110857
|
} catch (error61) {
|
|
109974
110858
|
const message = error61 instanceof Error && error61.message.startsWith("CODEX_HOOK_TRUST_") ? error61.message : "CODEX_HOOK_TRUST_RECONCILE_REFUSED";
|
|
@@ -109980,15 +110864,15 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
109980
110864
|
async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRpc) {
|
|
109981
110865
|
let rpc, journal;
|
|
109982
110866
|
try {
|
|
109983
|
-
const home =
|
|
110867
|
+
const home = resolve24(options.home ?? homedir12()), dataDir3 = resolve24(options.dataDir ?? getDataDirReadOnly());
|
|
109984
110868
|
assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home });
|
|
109985
|
-
const policyFile = snapshot(
|
|
110869
|
+
const policyFile = snapshot(join59(dataDir3, "agent-policy.json"), true), policy = parseManagedSkillPolicy(policyFile.text);
|
|
109986
110870
|
const binding = policy.bridge, alias = binding.rootAliases?.find((item) => item.agent === "codex");
|
|
109987
110871
|
const skillsCli = bindSkillsCli(binding.commands.codex, options.reviewedSkillsCli);
|
|
109988
|
-
const physicalRoot = alias?.target ??
|
|
109989
|
-
need4(options.codexHome === undefined ||
|
|
110872
|
+
const physicalRoot = alias?.target ?? join59(home, ".codex"), hooksPath = join59(physicalRoot, "hooks.json"), configPath = join59(physicalRoot, "config.toml");
|
|
110873
|
+
need4(options.codexHome === undefined || isAbsolute27(options.codexHome) && [join59(home, ".codex"), physicalRoot].includes(options.codexHome), "NATIVE_HOME_NOT_ADMITTED");
|
|
109990
110874
|
const codexHome = options.codexHome === undefined ? undefined : physicalRoot;
|
|
109991
|
-
const nativeConfigPath =
|
|
110875
|
+
const nativeConfigPath = join59(codexHome ?? join59(home, ".codex"), "config.toml");
|
|
109992
110876
|
const hooksFile = snapshot(hooksPath), configFile = snapshot(configPath, true), original = structure3(configFile.text);
|
|
109993
110877
|
const declarations = JSON.parse(hooksFile.text).hooks, expected = [];
|
|
109994
110878
|
for (const [event, [nativeEvent, label]] of Object.entries(eventNames)) {
|
|
@@ -109997,25 +110881,25 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
109997
110881
|
need4(Array.isArray(groups), "DECLARATION_CHANGED");
|
|
109998
110882
|
const matches2 = groups.flatMap((group, gi) => (group.hooks ?? []).flatMap((entry, hi) => entry.command === command ? [{ group, entry, gi, hi }] : []));
|
|
109999
110883
|
need4(matches2.length === 1 && isDeepStrictEqual13(matches2[0].group, { hooks: [{ type: "command", command, timeout: 15 }] }), "DECLARATION_CHANGED");
|
|
110000
|
-
expected.push({ event: nativeEvent, key: `${
|
|
110884
|
+
expected.push({ event: nativeEvent, key: `${join59(home, ".codex/hooks.json")}:${label}:${matches2[0].gi}:${matches2[0].hi}`, command });
|
|
110001
110885
|
}
|
|
110002
110886
|
codexTrustTextWitness(configFile.text, expected.map((h) => h.key));
|
|
110003
110887
|
rpc = await connect({ command: options.codexCommand ?? "codex", home, codexHome });
|
|
110004
110888
|
need4(SUPPORTED_CODEX_HOOK_VERSIONS.includes(rpc.version), "NATIVE_UNSUPPORTED_VERSION");
|
|
110005
110889
|
const list2 = async () => {
|
|
110006
110890
|
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 ${
|
|
110891
|
+
const nativeRoots = [...new Set([physicalRoot, join59(home, ".codex")])];
|
|
110892
|
+
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
110893
|
const entry = response?.data?.[0];
|
|
110010
110894
|
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
110895
|
return response.data[0].hooks;
|
|
110012
110896
|
};
|
|
110013
110897
|
const admit = (hooks) => expected.map((wanted) => {
|
|
110014
|
-
const canonicalKey = wanted.key.replace(
|
|
110898
|
+
const canonicalKey = wanted.key.replace(join59(home, ".codex/hooks.json"), hooksPath);
|
|
110015
110899
|
const matches2 = hooks.filter((h2) => h2.key === wanted.key || h2.key === canonicalKey);
|
|
110016
110900
|
need4(matches2.length === 1, "AMBIGUOUS_IDENTITY");
|
|
110017
110901
|
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,
|
|
110902
|
+
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
110903
|
need4(hooks.filter((other) => other.command === wanted.command).length === 1, "AMBIGUOUS_IDENTITY");
|
|
110020
110904
|
need4(["trusted", "untrusted", "modified"].includes(h.trustStatus), "UNKNOWN_TRUST_STATUS");
|
|
110021
110905
|
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 +110925,27 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
110041
110925
|
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
110926
|
if (options.apply)
|
|
110043
110927
|
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 =
|
|
110928
|
+
const parent = join59(dataDir3, "native-hook-trust");
|
|
110929
|
+
const st = lstatSync30(parent, { throwIfNoEntry: false });
|
|
110046
110930
|
if (st) {
|
|
110047
110931
|
need4(st.isDirectory() && !st.isSymbolicLink() && st.uid === process.getuid() && (st.mode & 511) === 448, "UNSAFE_JOURNAL");
|
|
110048
|
-
const priorJournals =
|
|
110932
|
+
const priorJournals = readdirSync22(parent);
|
|
110049
110933
|
need4(priorJournals.length < 1000, "JOURNAL_BOUND");
|
|
110050
110934
|
for (const name of priorJournals) {
|
|
110051
110935
|
need4(/^[a-f0-9-]{36}$/.test(name), "UNSAFE_JOURNAL");
|
|
110052
|
-
need4(
|
|
110936
|
+
need4(lstatSync30(join59(parent, name, "receipt.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_REQUIRED: inspect the incomplete private journal before applying again");
|
|
110053
110937
|
}
|
|
110054
110938
|
}
|
|
110055
110939
|
if (!options.apply || !planned.length)
|
|
110056
110940
|
return baseReceipt;
|
|
110057
110941
|
if (!st)
|
|
110058
|
-
|
|
110059
|
-
journal =
|
|
110060
|
-
|
|
110061
|
-
save(
|
|
110062
|
-
save(
|
|
110063
|
-
save(
|
|
110064
|
-
save(
|
|
110942
|
+
mkdirSync27(parent, { mode: 448 });
|
|
110943
|
+
journal = join59(parent, randomUUID11());
|
|
110944
|
+
mkdirSync27(journal, { mode: 448 });
|
|
110945
|
+
save(join59(journal, "config.before.toml"), configFile.bytes);
|
|
110946
|
+
save(join59(journal, "hooks.before.json"), hooksFile.bytes);
|
|
110947
|
+
save(join59(journal, "policy.before.json"), policyFile.bytes);
|
|
110948
|
+
save(join59(journal, "intent.json"), json2({ ...digestInput, planDigest, hooks: planned, nativeHooks: discovered.map(safeNativeHook) }));
|
|
110065
110949
|
need4(isDeepStrictEqual13(admit(await list2()), admitted), "NATIVE_DISCOVERY_CHANGED");
|
|
110066
110950
|
checkInputs();
|
|
110067
110951
|
let result2;
|
|
@@ -110077,7 +110961,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
110077
110961
|
for (const h of planned)
|
|
110078
110962
|
expectedConfig.hooks.state[h.key] = { ...expectedConfig.hooks.state[h.key], enabled: true, trusted_hash: h.currentHash };
|
|
110079
110963
|
need4(isDeepStrictEqual13(structure3(after2.text), expectedConfig) && codexTrustTextWitness(after2.text, keys3) === preservationText, "PRESERVATION_FAILED");
|
|
110080
|
-
const savedConfig = snapshot(
|
|
110964
|
+
const savedConfig = snapshot(join59(journal, "config.before.toml"), true);
|
|
110081
110965
|
need4(savedConfig.sha256 === configFile.sha256, "JOURNAL_CHANGED");
|
|
110082
110966
|
const discoveryTransition = binding.discovery?.codex ? createCodexHookDiscoveryRecovery(binding.discovery.codex, savedConfig, after2, planned) : undefined;
|
|
110083
110967
|
unchanged2(policyFile);
|
|
@@ -110093,13 +110977,13 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
110093
110977
|
skillsCli.recheck();
|
|
110094
110978
|
assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home, codexDiscoveryRecovery: discoveryTransition });
|
|
110095
110979
|
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(
|
|
110980
|
+
save(join59(journal, "receipt.json"), json2(receipt));
|
|
110097
110981
|
return receipt;
|
|
110098
110982
|
} catch (error61) {
|
|
110099
110983
|
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
110984
|
if (journal) {
|
|
110101
110985
|
try {
|
|
110102
|
-
save(
|
|
110986
|
+
save(join59(journal, "stopped.json"), json2({ error: message.split(":")[0], automaticRollback: false, reconcileBeforeRetry: true }));
|
|
110103
110987
|
} catch {}
|
|
110104
110988
|
}
|
|
110105
110989
|
throw new Error(message);
|
|
@@ -110108,7 +110992,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
110108
110992
|
}
|
|
110109
110993
|
}
|
|
110110
110994
|
var json2 = (value) => JSON.stringify(value, null, 2) + `
|
|
110111
|
-
`, structure3 = (text4) => JSON.parse(JSON.stringify(Bun.TOML.parse(text4))), eventNames, makePlanDigest = (value) =>
|
|
110995
|
+
`, 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
110996
|
const copy = structuredClone(value);
|
|
110113
110997
|
if (copy?.bridge?.commands && Object.hasOwn(copy.bridge.commands, "codex"))
|
|
110114
110998
|
copy.bridge.commands.codex = "<reviewed-codex-binding>";
|
|
@@ -110219,8 +111103,8 @@ var exports_agent_integration = {};
|
|
|
110219
111103
|
__export(exports_agent_integration, {
|
|
110220
111104
|
registerAgentIntegration: () => registerAgentIntegration
|
|
110221
111105
|
});
|
|
110222
|
-
import { readFileSync as
|
|
110223
|
-
import { isAbsolute as
|
|
111106
|
+
import { readFileSync as readFileSync41 } from "fs";
|
|
111107
|
+
import { isAbsolute as isAbsolute28 } from "path";
|
|
110224
111108
|
function pinnedHookProfile(input3, configuredProfile) {
|
|
110225
111109
|
try {
|
|
110226
111110
|
const parsed = parseSkillContextInput(JSON.stringify(input3));
|
|
@@ -110297,7 +111181,7 @@ function registerAgentIntegration(parent) {
|
|
|
110297
111181
|
});
|
|
110298
111182
|
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
111183
|
try {
|
|
110300
|
-
const discoveryInputs = options.discoveryInputs ? JSON.parse(
|
|
111184
|
+
const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync41(options.discoveryInputs, "utf8")) : undefined;
|
|
110301
111185
|
const plan = planAgentIntegration({ agents: agents(options.agent), command: options.command, profileId: options.selectionProfile, includeVendor: options.includeVendor, discoveryInputs, allowRootAliases: options.allowRootAliases });
|
|
110302
111186
|
const result2 = options.apply ? applyAgentIntegration(plan) : { changed: [], backups: [] };
|
|
110303
111187
|
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 +111239,7 @@ function registerAgentIntegration(parent) {
|
|
|
110355
111239
|
try {
|
|
110356
111240
|
if (agents(options.agent).length !== 1)
|
|
110357
111241
|
throw new Error("A hook invocation requires one agent");
|
|
110358
|
-
const inputText =
|
|
111242
|
+
const inputText = readFileSync41(0, "utf8");
|
|
110359
111243
|
if (inputText.length > 1024 * 1024)
|
|
110360
111244
|
throw new Error("Hook input is too large");
|
|
110361
111245
|
let input3 = JSON.parse(inputText);
|
|
@@ -110368,12 +111252,12 @@ function registerAgentIntegration(parent) {
|
|
|
110368
111252
|
input3.hook_event_name = event;
|
|
110369
111253
|
const projects = [process.cwd()];
|
|
110370
111254
|
if (input3.cwd !== undefined) {
|
|
110371
|
-
if (typeof input3.cwd !== "string" || !
|
|
111255
|
+
if (typeof input3.cwd !== "string" || !isAbsolute28(input3.cwd) || input3.cwd.includes("\x00"))
|
|
110372
111256
|
throw new Error("Invalid native hook working directory");
|
|
110373
111257
|
projects.push(input3.cwd);
|
|
110374
111258
|
}
|
|
110375
111259
|
if (options.agent === "cursor") {
|
|
110376
|
-
if (!Array.isArray(input3.workspace_roots) || input3.workspace_roots.length > 32 || input3.workspace_roots.some((path) => typeof path !== "string" || !
|
|
111260
|
+
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
111261
|
throw new Error("Invalid Cursor workspace roots");
|
|
110378
111262
|
projects.push(...input3.workspace_roots);
|
|
110379
111263
|
input3.cwd ??= input3.workspace_roots[0] ?? process.cwd();
|
|
@@ -110453,7 +111337,7 @@ function registerAgentIntegration(parent) {
|
|
|
110453
111337
|
try {
|
|
110454
111338
|
if (options.agent && !INTEGRATION_AGENTS.includes(options.agent))
|
|
110455
111339
|
throw new Error(`Unsupported migration agent: ${options.agent}`);
|
|
110456
|
-
const discoveryInputs = options.discoveryInputs ? JSON.parse(
|
|
111340
|
+
const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync41(options.discoveryInputs, "utf8")) : undefined;
|
|
110457
111341
|
if (options.targetManifest && (options.includeUnmanaged || options.includeVendor))
|
|
110458
111342
|
throw new Error("--target-manifest cannot be combined with broad native migration selectors");
|
|
110459
111343
|
const targetManifest = options.targetManifest ? readNativeMigrationTargetManifest(options.targetManifest) : undefined;
|
|
@@ -110623,7 +111507,7 @@ var exports_profiles = {};
|
|
|
110623
111507
|
__export(exports_profiles, {
|
|
110624
111508
|
registerProfiles: () => registerProfiles
|
|
110625
111509
|
});
|
|
110626
|
-
import { closeSync as closeSync23, constants as constants20, fstatSync as fstatSync23, openSync as openSync23, readSync as readSync17, writeFileSync as
|
|
111510
|
+
import { closeSync as closeSync23, constants as constants20, fstatSync as fstatSync23, openSync as openSync23, readSync as readSync17, writeFileSync as writeFileSync26 } from "fs";
|
|
110627
111511
|
function readProfileInput(path) {
|
|
110628
111512
|
const fd = openSync23(path, constants20.O_RDONLY | constants20.O_NONBLOCK);
|
|
110629
111513
|
try {
|
|
@@ -110651,7 +111535,7 @@ function registerProfiles(parent) {
|
|
|
110651
111535
|
try {
|
|
110652
111536
|
const profile = await readSkillProfile(id3), text4 = JSON.stringify(profile, null, 2);
|
|
110653
111537
|
if (options.save)
|
|
110654
|
-
|
|
111538
|
+
writeFileSync26(options.save, JSON.stringify(profile), { mode: 384, flag: "wx" });
|
|
110655
111539
|
await writeCliOutput(options.json ? text4 : `${profile.id} at ${profile.revision}: ${profile.selections.map((skill) => `${skill.slug}@${skill.version}`).join(", ")}`);
|
|
110656
111540
|
} catch (error61) {
|
|
110657
111541
|
console.error(error61.message);
|
|
@@ -110698,7 +111582,7 @@ import {
|
|
|
110698
111582
|
fstatSync as fstatSync24,
|
|
110699
111583
|
openSync as openSync24,
|
|
110700
111584
|
readSync as readSync18,
|
|
110701
|
-
writeFileSync as
|
|
111585
|
+
writeFileSync as writeFileSync27
|
|
110702
111586
|
} from "fs";
|
|
110703
111587
|
function readInput(path) {
|
|
110704
111588
|
const fd = openSync24(path, constants21.O_RDONLY | constants21.O_NONBLOCK);
|
|
@@ -110731,7 +111615,7 @@ function registerGrants(parent) {
|
|
|
110731
111615
|
try {
|
|
110732
111616
|
const policy = await readExecutionGrantPolicy(profile, options.revision);
|
|
110733
111617
|
if (options.save)
|
|
110734
|
-
|
|
111618
|
+
writeFileSync27(options.save, JSON.stringify(policy, null, 2), {
|
|
110735
111619
|
mode: 384,
|
|
110736
111620
|
flag: "wx"
|
|
110737
111621
|
});
|
|
@@ -110758,28 +111642,28 @@ var init_grants = __esm(() => {
|
|
|
110758
111642
|
});
|
|
110759
111643
|
|
|
110760
111644
|
// src/lib/station-hydrate.ts
|
|
110761
|
-
import { createHash as
|
|
111645
|
+
import { createHash as createHash30 } from "crypto";
|
|
110762
111646
|
import {
|
|
110763
|
-
copyFileSync as
|
|
110764
|
-
mkdirSync as
|
|
110765
|
-
readdirSync as
|
|
110766
|
-
readFileSync as
|
|
111647
|
+
copyFileSync as copyFileSync3,
|
|
111648
|
+
mkdirSync as mkdirSync28,
|
|
111649
|
+
readdirSync as readdirSync23,
|
|
111650
|
+
readFileSync as readFileSync42,
|
|
110767
111651
|
statSync as statSync22,
|
|
110768
|
-
writeFileSync as
|
|
111652
|
+
writeFileSync as writeFileSync28
|
|
110769
111653
|
} from "fs";
|
|
110770
|
-
import { dirname as
|
|
111654
|
+
import { dirname as dirname34, join as join60, resolve as resolve25, sep as sep12 } from "path";
|
|
110771
111655
|
function fail5(code2, message, detail = []) {
|
|
110772
111656
|
throw new StationSnapshotError(code2, message, detail);
|
|
110773
111657
|
}
|
|
110774
111658
|
function snapshotRootFor(repoRoot, stationId) {
|
|
110775
|
-
return
|
|
111659
|
+
return join60(repoRoot, "resources", stationId, "skills");
|
|
110776
111660
|
}
|
|
110777
111661
|
function readSnapshotManifest(repoRoot, stationId) {
|
|
110778
111662
|
const snapshotRoot = snapshotRootFor(repoRoot, stationId);
|
|
110779
|
-
const manifestPath =
|
|
111663
|
+
const manifestPath = join60(snapshotRoot, "sync-manifest.json");
|
|
110780
111664
|
let manifest;
|
|
110781
111665
|
try {
|
|
110782
|
-
manifest = JSON.parse(
|
|
111666
|
+
manifest = JSON.parse(readFileSync42(manifestPath, "utf8"));
|
|
110783
111667
|
} catch (error61) {
|
|
110784
111668
|
fail5("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error61.message}`);
|
|
110785
111669
|
}
|
|
@@ -110803,10 +111687,10 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
110803
111687
|
const hashMismatches = [];
|
|
110804
111688
|
const skippedByRule = [];
|
|
110805
111689
|
for (const agent of SYNC_AGENTS) {
|
|
110806
|
-
const agentRoot =
|
|
111690
|
+
const agentRoot = join60(snapshotRoot, "agent-homes", agent);
|
|
110807
111691
|
let identEntries;
|
|
110808
111692
|
try {
|
|
110809
|
-
identEntries =
|
|
111693
|
+
identEntries = readdirSync23(agentRoot, { withFileTypes: true });
|
|
110810
111694
|
} catch {
|
|
110811
111695
|
continue;
|
|
110812
111696
|
}
|
|
@@ -110814,10 +111698,10 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
110814
111698
|
if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
|
|
110815
111699
|
continue;
|
|
110816
111700
|
}
|
|
110817
|
-
const identRoot =
|
|
111701
|
+
const identRoot = join60(agentRoot, identEntry.name);
|
|
110818
111702
|
const entries = walkEntries(identRoot);
|
|
110819
111703
|
for (const entry of entries) {
|
|
110820
|
-
const relativeParts = [identEntry.name, ...entry.relativePath.split(
|
|
111704
|
+
const relativeParts = [identEntry.name, ...entry.relativePath.split(sep12)];
|
|
110821
111705
|
if (entry.kind === "symlink") {
|
|
110822
111706
|
symlinks.push({ ident: identEntry.name, agent, relativePath: entry.relativePath });
|
|
110823
111707
|
continue;
|
|
@@ -110841,8 +111725,8 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
110841
111725
|
});
|
|
110842
111726
|
continue;
|
|
110843
111727
|
}
|
|
110844
|
-
const withinIdent = relativeParts.slice(1).join(
|
|
110845
|
-
const homeRelative = relativeParts.join(
|
|
111728
|
+
const withinIdent = relativeParts.slice(1).join(sep12);
|
|
111729
|
+
const homeRelative = relativeParts.join(sep12);
|
|
110846
111730
|
if (REFUSED_SCANNER_FLAGGED.has(homeRelative)) {
|
|
110847
111731
|
skippedByRule.push({
|
|
110848
111732
|
ident: identEntry.name,
|
|
@@ -110915,7 +111799,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
110915
111799
|
for (const copy of copies) {
|
|
110916
111800
|
let isStub = false;
|
|
110917
111801
|
try {
|
|
110918
|
-
isStub = isPointerSkillMd(
|
|
111802
|
+
isStub = isPointerSkillMd(readFileSync42(copy.fullPath, "utf8"));
|
|
110919
111803
|
} catch {
|
|
110920
111804
|
isStub = false;
|
|
110921
111805
|
}
|
|
@@ -110961,12 +111845,12 @@ function skillSha256(skill) {
|
|
|
110961
111845
|
return sha256File(skill.files[0].winner.fullPath);
|
|
110962
111846
|
}
|
|
110963
111847
|
const joined = skill.files.map((file2) => sha256File(file2.winner.fullPath));
|
|
110964
|
-
return
|
|
111848
|
+
return createHash30("sha256").update(joined.sort().join(`
|
|
110965
111849
|
`)).digest("hex");
|
|
110966
111850
|
}
|
|
110967
111851
|
function writeStationHydration(options) {
|
|
110968
|
-
const repoRoot =
|
|
110969
|
-
const cacheRoot =
|
|
111852
|
+
const repoRoot = resolve25(options.repoRoot ?? process.cwd());
|
|
111853
|
+
const cacheRoot = resolve25(options.cacheRoot ?? resolveCorpusRoot());
|
|
110970
111854
|
const plan = planStationHydration(options.stationId, repoRoot);
|
|
110971
111855
|
const resultSkills = plan.winners.map((skill) => ({
|
|
110972
111856
|
ident: skill.ident,
|
|
@@ -110999,7 +111883,7 @@ function writeStationHydration(options) {
|
|
|
110999
111883
|
const toWrite = [];
|
|
111000
111884
|
for (const skill of plan.winners) {
|
|
111001
111885
|
for (const file2 of skill.files) {
|
|
111002
|
-
const destination =
|
|
111886
|
+
const destination = join60(cacheRoot, skill.ident, file2.withinIdent);
|
|
111003
111887
|
const digest4 = sha256File(file2.winner.fullPath);
|
|
111004
111888
|
let existingDigest = null;
|
|
111005
111889
|
try {
|
|
@@ -111020,8 +111904,8 @@ function writeStationHydration(options) {
|
|
|
111020
111904
|
}
|
|
111021
111905
|
let written = 0;
|
|
111022
111906
|
for (const entry of toWrite) {
|
|
111023
|
-
|
|
111024
|
-
|
|
111907
|
+
mkdirSync28(dirname34(entry.destination), { recursive: true });
|
|
111908
|
+
copyFileSync3(entry.fullPath, entry.destination);
|
|
111025
111909
|
written += 1;
|
|
111026
111910
|
}
|
|
111027
111911
|
const unchanged3 = plan.totalFiles - written;
|
|
@@ -111041,9 +111925,9 @@ function writeStationHydration(options) {
|
|
|
111041
111925
|
},
|
|
111042
111926
|
skills: resultSkills
|
|
111043
111927
|
};
|
|
111044
|
-
const hydrationManifestPath =
|
|
111045
|
-
|
|
111046
|
-
|
|
111928
|
+
const hydrationManifestPath = join60(dirname34(cacheRoot), `hydration-${options.stationId}.json`);
|
|
111929
|
+
mkdirSync28(dirname34(hydrationManifestPath), { recursive: true });
|
|
111930
|
+
writeFileSync28(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
|
|
111047
111931
|
`);
|
|
111048
111932
|
return {
|
|
111049
111933
|
...base3,
|
|
@@ -111118,15 +112002,15 @@ var init_hydrate = __esm(() => {
|
|
|
111118
112002
|
});
|
|
111119
112003
|
|
|
111120
112004
|
// src/lib/prepare-skill.ts
|
|
111121
|
-
import { createHash as
|
|
111122
|
-
import { chmodSync as
|
|
112005
|
+
import { createHash as createHash31, randomUUID as randomUUID12 } from "crypto";
|
|
112006
|
+
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
112007
|
import { tmpdir as tmpdir2 } from "os";
|
|
111124
|
-
import { dirname as
|
|
112008
|
+
import { dirname as dirname35, isAbsolute as isAbsolute29, join as join61, relative as relative10, resolve as resolve26, sep as sep13 } from "path";
|
|
111125
112009
|
function prepareSkill(name, options) {
|
|
111126
112010
|
const normalized = normalizePortableSkillName(name);
|
|
111127
112011
|
if (!new RegExp(SEMVER_PATTERN).test(options.version))
|
|
111128
112012
|
throw new Error("--version must be a semantic version, for example 0.2.0.");
|
|
111129
|
-
const path =
|
|
112013
|
+
const path = join61(getPortableSkillsRootReadOnly(options), normalized);
|
|
111130
112014
|
assertPortableAuthoringPath(path, options);
|
|
111131
112015
|
const before2 = snapshot2(path);
|
|
111132
112016
|
const manifestFile = before2.files.find((file2) => file2.path === "skill.json");
|
|
@@ -111161,17 +112045,17 @@ function prepareSkill(name, options) {
|
|
|
111161
112045
|
throw new Error("skill.json provenance must be an object.");
|
|
111162
112046
|
const provenance = manifest.provenance;
|
|
111163
112047
|
const candidate2 = { ...manifest, version: options.version, kind, provenance: { ...provenance } };
|
|
111164
|
-
const staging = mkdtempSync6(
|
|
112048
|
+
const staging = mkdtempSync6(join61(tmpdir2(), "skills-prepare-"));
|
|
111165
112049
|
try {
|
|
111166
112050
|
for (const directory2 of before2.directories)
|
|
111167
|
-
|
|
112051
|
+
mkdirSync29(join61(staging, directory2), { recursive: true });
|
|
111168
112052
|
for (const file2 of before2.files) {
|
|
111169
|
-
const destination =
|
|
111170
|
-
|
|
111171
|
-
|
|
112053
|
+
const destination = join61(staging, file2.path);
|
|
112054
|
+
mkdirSync29(dirname35(destination), { recursive: true });
|
|
112055
|
+
writeFileSync29(destination, file2.bytes, { mode: file2.mode });
|
|
111172
112056
|
}
|
|
111173
|
-
const stagedManifest =
|
|
111174
|
-
|
|
112057
|
+
const stagedManifest = join61(staging, "skill.json");
|
|
112058
|
+
writeFileSync29(stagedManifest, `${JSON.stringify(candidate2, null, 2)}
|
|
111175
112059
|
`);
|
|
111176
112060
|
const contentHash = computeContentHash(staging);
|
|
111177
112061
|
const changed3 = options.version !== previousVersion || kind !== manifest.kind || contentHash !== provenance?.content_hash;
|
|
@@ -111181,7 +112065,7 @@ function prepareSkill(name, options) {
|
|
|
111181
112065
|
candidate2.provenance.content_hash = contentHash;
|
|
111182
112066
|
const candidateBytes = `${JSON.stringify(candidate2, null, 2)}
|
|
111183
112067
|
`;
|
|
111184
|
-
|
|
112068
|
+
writeFileSync29(stagedManifest, candidateBytes);
|
|
111185
112069
|
validateRawCandidate(candidate2);
|
|
111186
112070
|
validateRuntimeEntrypoint(candidate2, staging);
|
|
111187
112071
|
const validation = validatePortableSkillDirectory(normalized, staging);
|
|
@@ -111193,11 +112077,11 @@ ${validation.issues.map((issue2) => `${issue2.code}: ${issue2.message}`).join(`
|
|
|
111193
112077
|
if (snapshot2(path).identity !== before2.identity)
|
|
111194
112078
|
throw new Error("Skill changed during preparation; review the draft and run prepare again.");
|
|
111195
112079
|
if (changed3 && !options.dryRun) {
|
|
111196
|
-
const temporary =
|
|
112080
|
+
const temporary = join61(path, `.skill-prepare-${randomUUID12()}.tmp`);
|
|
111197
112081
|
try {
|
|
111198
|
-
|
|
111199
|
-
|
|
111200
|
-
|
|
112082
|
+
writeFileSync29(temporary, candidateBytes, { flag: "wx", mode: manifestFile.mode });
|
|
112083
|
+
chmodSync5(temporary, manifestFile.mode);
|
|
112084
|
+
renameSync14(temporary, join61(path, "skill.json"));
|
|
111201
112085
|
} finally {
|
|
111202
112086
|
rmSync9(temporary, { force: true });
|
|
111203
112087
|
}
|
|
@@ -111227,8 +112111,8 @@ function validateRuntimeEntrypoint(candidate2, root2) {
|
|
|
111227
112111
|
const entry = candidate2.runtime.entrypoint;
|
|
111228
112112
|
if (typeof entry !== "string")
|
|
111229
112113
|
return;
|
|
111230
|
-
const target =
|
|
111231
|
-
if (
|
|
112114
|
+
const target = resolve26(root2, entry), rel = relative10(root2, target);
|
|
112115
|
+
if (isAbsolute29(entry) || /^[a-zA-Z]:/.test(entry) || entry.includes("\\") || !rel || rel === ".." || rel.startsWith(`..${sep13}`) || isAbsolute29(rel) || !existsSync35(target) || !lstatSync31(target).isFile()) {
|
|
111232
112116
|
throw new Error("Executable runtime.entrypoint must name an existing regular file inside the skill directory.");
|
|
111233
112117
|
}
|
|
111234
112118
|
}
|
|
@@ -111313,25 +112197,25 @@ function compareVersions(left, right) {
|
|
|
111313
112197
|
return 0;
|
|
111314
112198
|
}
|
|
111315
112199
|
function snapshot2(root2) {
|
|
111316
|
-
const rootStat =
|
|
112200
|
+
const rootStat = lstatSync31(root2);
|
|
111317
112201
|
if (rootStat.isSymbolicLink() || !rootStat.isDirectory())
|
|
111318
112202
|
throw new Error("Skill root must be a real directory, not a symlink.");
|
|
111319
112203
|
const files = [], directories = [];
|
|
111320
|
-
const
|
|
112204
|
+
const hash6 = createHash31("sha256").update(`${rootStat.dev}:${rootStat.ino}\x00`);
|
|
111321
112205
|
let count = 0, bytes = 0;
|
|
111322
|
-
const walk2 = (
|
|
111323
|
-
for (const name of
|
|
111324
|
-
if (IGNORED.has(name.toLowerCase()) || !
|
|
112206
|
+
const walk2 = (relative11) => {
|
|
112207
|
+
for (const name of readdirSync24(join61(root2, relative11)).sort()) {
|
|
112208
|
+
if (IGNORED.has(name.toLowerCase()) || !relative11 && ROOT_IGNORED.has(name.toLowerCase()))
|
|
111325
112209
|
continue;
|
|
111326
|
-
const path =
|
|
112210
|
+
const path = relative11 ? `${relative11}/${name}` : name;
|
|
111327
112211
|
if (++count > 20000)
|
|
111328
112212
|
throw new Error("Skill exceeds the preparation entry limit (20000).");
|
|
111329
|
-
const absolute =
|
|
112213
|
+
const absolute = join61(root2, path), stat3 = lstatSync31(absolute);
|
|
111330
112214
|
if (stat3.isSymbolicLink())
|
|
111331
112215
|
throw new Error(`Skill contains a symlink: ${path}. No files were changed.`);
|
|
111332
112216
|
if (stat3.isDirectory() && name === "node_modules")
|
|
111333
112217
|
continue;
|
|
111334
|
-
|
|
112218
|
+
hash6.update(JSON.stringify([path, stat3.dev, stat3.ino, stat3.mode]));
|
|
111335
112219
|
if (stat3.isDirectory()) {
|
|
111336
112220
|
directories.push(path);
|
|
111337
112221
|
walk2(path);
|
|
@@ -111339,18 +112223,18 @@ function snapshot2(root2) {
|
|
|
111339
112223
|
bytes += stat3.size;
|
|
111340
112224
|
if (bytes > 50000000)
|
|
111341
112225
|
throw new Error("Skill exceeds the preparation size limit (50000000 bytes).");
|
|
111342
|
-
const content =
|
|
112226
|
+
const content = readFileSync43(absolute);
|
|
111343
112227
|
if (content.length !== stat3.size)
|
|
111344
112228
|
throw new Error("Skill changed during preparation.");
|
|
111345
112229
|
files.push({ path, bytes: content, mode: stat3.mode & 511 });
|
|
111346
|
-
|
|
112230
|
+
hash6.update(`\x00${content.length}\x00`).update(content);
|
|
111347
112231
|
} else
|
|
111348
112232
|
throw new Error(`Unsupported skill entry: ${path}.`);
|
|
111349
|
-
|
|
112233
|
+
hash6.update("\x00");
|
|
111350
112234
|
}
|
|
111351
112235
|
};
|
|
111352
112236
|
walk2("");
|
|
111353
|
-
return { files, directories, identity:
|
|
112237
|
+
return { files, directories, identity: hash6.digest("hex") };
|
|
111354
112238
|
}
|
|
111355
112239
|
var MAX_PACK_PARITY_PATHS = 8, MAX_PACK_PARITY_PATH_CHARS = 120, IGNORED, ROOT_IGNORED;
|
|
111356
112240
|
var init_prepare_skill = __esm(() => {
|
|
@@ -111741,8 +112625,8 @@ var init_schedule = __esm(() => {
|
|
|
111741
112625
|
});
|
|
111742
112626
|
|
|
111743
112627
|
// src/lib/registry-sync.ts
|
|
111744
|
-
import { mkdirSync as
|
|
111745
|
-
import { dirname as
|
|
112628
|
+
import { mkdirSync as mkdirSync30, writeFileSync as writeFileSync30 } from "fs";
|
|
112629
|
+
import { dirname as dirname36, relative as relative11 } from "path";
|
|
111746
112630
|
function createRegistrySyncArtifact(options = {}) {
|
|
111747
112631
|
const profile = options.profile ?? "all";
|
|
111748
112632
|
const includeDocs = options.includeDocs ?? true;
|
|
@@ -111754,7 +112638,7 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
111754
112638
|
const registry2 = [...loadRegistryProfile(profile)].sort((a, b) => a.name.localeCompare(b.name));
|
|
111755
112639
|
const skills = registry2.map((skill) => {
|
|
111756
112640
|
const skillPath = getSkillPath(skill.name);
|
|
111757
|
-
const directory2 =
|
|
112641
|
+
const directory2 = relative11(process.cwd(), skillPath) || skillPath;
|
|
111758
112642
|
const validation = includeValidation ? validateSkillDirectory(skill.name, skillPath, skill) : undefined;
|
|
111759
112643
|
const docs = includeDocs ? buildDocs(skill.name) : undefined;
|
|
111760
112644
|
return {
|
|
@@ -111799,8 +112683,8 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
111799
112683
|
};
|
|
111800
112684
|
}
|
|
111801
112685
|
function writeRegistrySyncArtifact(path, artifact) {
|
|
111802
|
-
|
|
111803
|
-
|
|
112686
|
+
mkdirSync30(dirname36(path), { recursive: true });
|
|
112687
|
+
writeFileSync30(path, `${JSON.stringify(artifact, null, 2)}
|
|
111804
112688
|
`);
|
|
111805
112689
|
}
|
|
111806
112690
|
function buildDocs(name) {
|
|
@@ -111819,8 +112703,8 @@ var init_registry_sync = __esm(() => {
|
|
|
111819
112703
|
});
|
|
111820
112704
|
|
|
111821
112705
|
// src/lib/review-export.ts
|
|
111822
|
-
import { constants as constants22, closeSync as closeSync25, existsSync as
|
|
111823
|
-
import { basename as
|
|
112706
|
+
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";
|
|
112707
|
+
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
112708
|
function exactVersion(spec) {
|
|
111825
112709
|
const at2 = spec.indexOf("@");
|
|
111826
112710
|
const slug2 = spec.slice(0, at2);
|
|
@@ -111831,23 +112715,23 @@ function exactVersion(spec) {
|
|
|
111831
112715
|
return { slug: slug2, version: version2 };
|
|
111832
112716
|
}
|
|
111833
112717
|
function assertPrivateDestination(output2) {
|
|
111834
|
-
if (!
|
|
112718
|
+
if (!isAbsolute30(output2) || resolve27(output2) !== output2 || !basename15(output2).endsWith(".tar.gz")) {
|
|
111835
112719
|
throw new Error("Review output must be an absolute, normalized .tar.gz path.");
|
|
111836
112720
|
}
|
|
111837
|
-
const parent =
|
|
111838
|
-
if (
|
|
112721
|
+
const parent = dirname37(output2);
|
|
112722
|
+
if (realpathSync16(parent) !== parent || lstatSync32(parent).isSymbolicLink()) {
|
|
111839
112723
|
throw new Error("Review output parent must be a real directory, not a symlink.");
|
|
111840
112724
|
}
|
|
111841
112725
|
const parentStat = statSync23(parent);
|
|
111842
112726
|
if (!parentStat.isDirectory() || (parentStat.mode & 63) !== 0 || process.getuid && parentStat.uid !== process.getuid()) {
|
|
111843
112727
|
throw new Error("Review output parent must be an owner-only directory (mode 0700).");
|
|
111844
112728
|
}
|
|
111845
|
-
const segments = parent.toLowerCase().split(
|
|
112729
|
+
const segments = parent.toLowerCase().split(sep14);
|
|
111846
112730
|
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
112731
|
throw new Error("Review output cannot be placed in agent-native discovery or Skills installation paths.");
|
|
111848
112732
|
}
|
|
111849
|
-
for (let current = parent;; current =
|
|
111850
|
-
if (
|
|
112733
|
+
for (let current = parent;; current = dirname37(current)) {
|
|
112734
|
+
if (existsSync36(join62(current, ".git"))) {
|
|
111851
112735
|
throw new Error("Review output cannot be placed inside a Git worktree.");
|
|
111852
112736
|
}
|
|
111853
112737
|
if (current === parse12(current).root)
|
|
@@ -111858,7 +112742,7 @@ function assertPrivateDestination(output2) {
|
|
|
111858
112742
|
async function exportSkillVersionForReview(spec, output2, client2) {
|
|
111859
112743
|
const { slug: slug2, version: version2 } = exactVersion(spec);
|
|
111860
112744
|
const target = assertPrivateDestination(output2);
|
|
111861
|
-
if (
|
|
112745
|
+
if (existsSync36(target))
|
|
111862
112746
|
throw new Error("Review output already exists; choose a new filename.");
|
|
111863
112747
|
const metadata = await client2.getSkillVersion(slug2, version2);
|
|
111864
112748
|
if (!metadata)
|
|
@@ -111885,7 +112769,7 @@ async function exportSkillVersionForReview(spec, output2, client2) {
|
|
|
111885
112769
|
}
|
|
111886
112770
|
const fd = openSync25(target, constants22.O_WRONLY | constants22.O_CREAT | constants22.O_EXCL | constants22.O_NOFOLLOW, 384);
|
|
111887
112771
|
try {
|
|
111888
|
-
|
|
112772
|
+
writeFileSync31(fd, bytes);
|
|
111889
112773
|
fsyncSync7(fd);
|
|
111890
112774
|
if (fstatSync25(fd).size !== bytes.byteLength)
|
|
111891
112775
|
throw new Error("Incomplete review output write.");
|
|
@@ -111923,12 +112807,12 @@ function registerRegistry(parent) {
|
|
|
111923
112807
|
async function writeJson2(value, space) {
|
|
111924
112808
|
const text4 = `${JSON.stringify(value, null, space)}
|
|
111925
112809
|
`;
|
|
111926
|
-
await new Promise((
|
|
112810
|
+
await new Promise((resolve28, reject2) => {
|
|
111927
112811
|
process.stdout.write(text4, (error61) => {
|
|
111928
112812
|
if (error61)
|
|
111929
112813
|
reject2(error61);
|
|
111930
112814
|
else
|
|
111931
|
-
|
|
112815
|
+
resolve28();
|
|
111932
112816
|
});
|
|
111933
112817
|
});
|
|
111934
112818
|
}
|
|
@@ -112132,9 +113016,9 @@ __export(exports_publish, {
|
|
|
112132
113016
|
PushSkillError: () => PushSkillError
|
|
112133
113017
|
});
|
|
112134
113018
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
112135
|
-
import { existsSync as
|
|
113019
|
+
import { existsSync as existsSync37, readFileSync as readFileSync44 } from "fs";
|
|
112136
113020
|
import { hostname as hostname5 } from "os";
|
|
112137
|
-
import { join as
|
|
113021
|
+
import { join as join63 } from "path";
|
|
112138
113022
|
function registerPublish(parent) {
|
|
112139
113023
|
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
113024
|
try {
|
|
@@ -112186,8 +113070,8 @@ async function pushSkill(name, options = {}) {
|
|
|
112186
113070
|
const packed = packSkillBundle(skill.path, { maxUnpackedBytes: MAX_UNPACKED_BYTES });
|
|
112187
113071
|
await verifyPackedCanonicalHash(skill.name, packed);
|
|
112188
113072
|
const versionManifest = buildVersionManifest(skill.path, packed);
|
|
112189
|
-
const skillMdPath =
|
|
112190
|
-
const skillMd =
|
|
113073
|
+
const skillMdPath = join63(skill.path, "SKILL.md");
|
|
113074
|
+
const skillMd = existsSync37(skillMdPath) ? readFileSync44(skillMdPath, "utf-8") : undefined;
|
|
112191
113075
|
const base3 = {
|
|
112192
113076
|
slug: skill.name,
|
|
112193
113077
|
path: skill.path,
|
|
@@ -112425,7 +113309,7 @@ var exports_private_publications = {};
|
|
|
112425
113309
|
__export(exports_private_publications, {
|
|
112426
113310
|
registerPrivatePublications: () => registerPrivatePublications
|
|
112427
113311
|
});
|
|
112428
|
-
import { resolve as
|
|
113312
|
+
import { resolve as resolve28 } from "path";
|
|
112429
113313
|
function verifyOptions(options, action) {
|
|
112430
113314
|
if (options.userId === undefined !== (options.membershipId === undefined) || options.userId !== undefined && (!publicationUuid(options.userId) || !publicationUuid(options.membershipId)))
|
|
112431
113315
|
throw new PrivatePublicationError("PUBLICATION_CONTEXT_REQUIRED", "Provide both observed --user-id and --membership-id UUIDs.");
|
|
@@ -112470,9 +113354,9 @@ function registerPrivatePublications(parent) {
|
|
|
112470
113354
|
if (code2 === null)
|
|
112471
113355
|
return;
|
|
112472
113356
|
const context2 = options.userId ? { userId: options.userId, membershipId: options.membershipId } : undefined;
|
|
112473
|
-
const client2 = await privatePublicationSession(options.email, code2, context2), directory2 =
|
|
113357
|
+
const client2 = await privatePublicationSession(options.email, code2, context2), directory2 = resolve28(options.recoveryDir);
|
|
112474
113358
|
if (action === "publish")
|
|
112475
|
-
await preparePrivatePublication(client2,
|
|
113359
|
+
await preparePrivatePublication(client2, resolve28(args2[0]), directory2, { skillId: options.skillId, expectedCurrentVersionId: options.expectedCurrentVersion ?? null, idempotencyKey: options.idempotencyKey });
|
|
112476
113360
|
const result2 = action === "publish" || action === "resume" ? await continuePrivatePublication(client2, directory2, { confirm: true, waitMs: Number(options.waitSeconds) * 1000 }) : await inspectPrivatePublication(client2, directory2, action === "cancel");
|
|
112477
113361
|
print2(result2, options.json);
|
|
112478
113362
|
} catch (error61) {
|
|
@@ -112574,7 +113458,7 @@ __export(exports_auth, {
|
|
|
112574
113458
|
import { createInterface as createInterface3 } from "readline";
|
|
112575
113459
|
function prompt(question) {
|
|
112576
113460
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
112577
|
-
return new Promise((
|
|
113461
|
+
return new Promise((resolve29) => {
|
|
112578
113462
|
let settled = false;
|
|
112579
113463
|
const finish = (answer) => {
|
|
112580
113464
|
if (settled)
|
|
@@ -112583,7 +113467,7 @@ function prompt(question) {
|
|
|
112583
113467
|
rl.close();
|
|
112584
113468
|
if (answer === null)
|
|
112585
113469
|
process.exitCode = 130;
|
|
112586
|
-
|
|
113470
|
+
resolve29(answer);
|
|
112587
113471
|
};
|
|
112588
113472
|
rl.once("SIGINT", () => finish(null));
|
|
112589
113473
|
rl.once("close", () => finish(null));
|
|
@@ -112761,7 +113645,7 @@ Skipped. Run it any time: skills setup agents`));
|
|
|
112761
113645
|
function promptHidden(question) {
|
|
112762
113646
|
const stdin = process.stdin;
|
|
112763
113647
|
process.stdout.write(question);
|
|
112764
|
-
return new Promise((
|
|
113648
|
+
return new Promise((resolve29) => {
|
|
112765
113649
|
let value = "";
|
|
112766
113650
|
const done = (answer) => {
|
|
112767
113651
|
stdin.off("data", onData);
|
|
@@ -112771,7 +113655,7 @@ function promptHidden(question) {
|
|
|
112771
113655
|
`);
|
|
112772
113656
|
if (answer === null)
|
|
112773
113657
|
process.exitCode = 130;
|
|
112774
|
-
|
|
113658
|
+
resolve29(answer);
|
|
112775
113659
|
};
|
|
112776
113660
|
const onData = (chunk2) => {
|
|
112777
113661
|
for (const character of chunk2.toString()) {
|
|
@@ -113317,7 +114201,7 @@ function promptInvitationProof(kind) {
|
|
|
113317
114201
|
const label = kind === "token" ? "invitation token" : "recovery code";
|
|
113318
114202
|
const character = kind === "token" ? /^[A-Za-z0-9_-]$/ : /^\d$/;
|
|
113319
114203
|
const { stdin, stderr: output2 } = process, wasRaw = stdin.isRaw, wasFlowing = stdin.readableFlowing;
|
|
113320
|
-
return new Promise((
|
|
114204
|
+
return new Promise((resolve29) => {
|
|
113321
114205
|
let value = "", settled = false, overflow = false;
|
|
113322
114206
|
const finish = (answer) => {
|
|
113323
114207
|
if (settled)
|
|
@@ -113334,7 +114218,7 @@ function promptInvitationProof(kind) {
|
|
|
113334
114218
|
`);
|
|
113335
114219
|
if (answer === null)
|
|
113336
114220
|
process.exitCode = 130;
|
|
113337
|
-
|
|
114221
|
+
resolve29(answer);
|
|
113338
114222
|
};
|
|
113339
114223
|
const cancel = () => finish(null);
|
|
113340
114224
|
const keypress = (text4, key) => {
|
|
@@ -113906,8 +114790,8 @@ var init_storage2 = __esm(() => {
|
|
|
113906
114790
|
});
|
|
113907
114791
|
|
|
113908
114792
|
// src/lib/registry-reconcile.ts
|
|
113909
|
-
import { existsSync as
|
|
113910
|
-
import { join as
|
|
114793
|
+
import { existsSync as existsSync38, readFileSync as readFileSync45, statSync as statSync24, writeFileSync as writeFileSync32 } from "fs";
|
|
114794
|
+
import { join as join64 } from "path";
|
|
113911
114795
|
function isDirectory2(path) {
|
|
113912
114796
|
try {
|
|
113913
114797
|
return statSync24(path).isDirectory();
|
|
@@ -113918,15 +114802,15 @@ function isDirectory2(path) {
|
|
|
113918
114802
|
function migrationNeeded(options) {
|
|
113919
114803
|
if (options.rootDir)
|
|
113920
114804
|
return false;
|
|
113921
|
-
const appDir = options.homeDir ?
|
|
113922
|
-
return !(isOwnerLayoutMigrated(appDir) && isDirectory2(
|
|
114805
|
+
const appDir = options.homeDir ? join64(options.homeDir, ".hasna", "skills") : getDataDir();
|
|
114806
|
+
return !(isOwnerLayoutMigrated(appDir) && isDirectory2(join64(appDir, SKILLS_CACHE_DIRNAME)));
|
|
113923
114807
|
}
|
|
113924
114808
|
function readBaseline(skillDir) {
|
|
113925
|
-
const markerPath =
|
|
113926
|
-
if (!
|
|
114809
|
+
const markerPath = join64(skillDir, PULL_MARKER_FILE);
|
|
114810
|
+
if (!existsSync38(markerPath))
|
|
113927
114811
|
return;
|
|
113928
114812
|
try {
|
|
113929
|
-
const marker = JSON.parse(
|
|
114813
|
+
const marker = JSON.parse(readFileSync45(markerPath, "utf-8"));
|
|
113930
114814
|
if (!isSkillsOwnershipMarker(marker))
|
|
113931
114815
|
return;
|
|
113932
114816
|
return {
|
|
@@ -113938,11 +114822,11 @@ function readBaseline(skillDir) {
|
|
|
113938
114822
|
}
|
|
113939
114823
|
}
|
|
113940
114824
|
function readCursor(root2) {
|
|
113941
|
-
const path =
|
|
113942
|
-
if (!
|
|
114825
|
+
const path = join64(root2, SYNC_CURSOR_FILE);
|
|
114826
|
+
if (!existsSync38(path))
|
|
113943
114827
|
return { runCount: 0 };
|
|
113944
114828
|
try {
|
|
113945
|
-
const cursor2 = JSON.parse(
|
|
114829
|
+
const cursor2 = JSON.parse(readFileSync45(path, "utf-8"));
|
|
113946
114830
|
return { runCount: typeof cursor2.runCount === "number" ? cursor2.runCount : 0 };
|
|
113947
114831
|
} catch {
|
|
113948
114832
|
return { runCount: 0 };
|
|
@@ -113951,12 +114835,12 @@ function readCursor(root2) {
|
|
|
113951
114835
|
function resolveCorpusRootReadOnly(options) {
|
|
113952
114836
|
if (options.rootDir)
|
|
113953
114837
|
return { root: options.rootDir, migrationPending: false };
|
|
113954
|
-
const appDir = options.homeDir ?
|
|
113955
|
-
const cache3 =
|
|
114838
|
+
const appDir = options.homeDir ? join64(options.homeDir, ".hasna", "skills") : getDataDirReadOnly();
|
|
114839
|
+
const cache3 = join64(appDir, SKILLS_CACHE_DIRNAME);
|
|
113956
114840
|
if (isOwnerLayoutMigrated(appDir) && isDirectory2(cache3)) {
|
|
113957
114841
|
return { root: cache3, migrationPending: false };
|
|
113958
114842
|
}
|
|
113959
|
-
return { root:
|
|
114843
|
+
return { root: join64(appDir, INSTALLED_SKILLS_DIRNAME), migrationPending: true };
|
|
113960
114844
|
}
|
|
113961
114845
|
function remoteRowToSkill(record11) {
|
|
113962
114846
|
const slug2 = typeof record11.slug === "string" ? record11.slug : typeof record11.name === "string" ? record11.name : undefined;
|
|
@@ -113970,7 +114854,7 @@ function remoteRowToSkill(record11) {
|
|
|
113970
114854
|
}
|
|
113971
114855
|
function recheckLocalSide(plannedLocal, localDir, ops = {
|
|
113972
114856
|
pack: (dir) => packSkillBundle(dir).sha256,
|
|
113973
|
-
exists:
|
|
114857
|
+
exists: existsSync38
|
|
113974
114858
|
}) {
|
|
113975
114859
|
let localNow;
|
|
113976
114860
|
try {
|
|
@@ -114090,7 +114974,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
114090
114974
|
for (const slug2 of allSlugs) {
|
|
114091
114975
|
const local = locals.get(slug2);
|
|
114092
114976
|
const remote = remotes.get(slug2);
|
|
114093
|
-
const baseline = local ? readBaseline(
|
|
114977
|
+
const baseline = local ? readBaseline(join64(root2, slug2)) : undefined;
|
|
114094
114978
|
const { state, reason } = classifySkill(local, remote, baseline);
|
|
114095
114979
|
let { action, reason: actionReason } = resolveAction(state, direction, conflict);
|
|
114096
114980
|
if (state === "remote-only" && isDigestless(remote)) {
|
|
@@ -114149,7 +115033,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
114149
115033
|
try {
|
|
114150
115034
|
await pushSkill(slug2, { rootDir: root2, client: client2 });
|
|
114151
115035
|
const pushed = locals.get(slug2);
|
|
114152
|
-
writePullMarker(
|
|
115036
|
+
writePullMarker(join64(root2, slug2), {
|
|
114153
115037
|
skill: slug2,
|
|
114154
115038
|
...pushed?.version ? { version: pushed.version } : {},
|
|
114155
115039
|
...pushed?.sha256 ? { contentHash: pushed.sha256 } : {},
|
|
@@ -114221,7 +115105,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
114221
115105
|
runCount: readCursor(root2).runCount + 1,
|
|
114222
115106
|
summary
|
|
114223
115107
|
};
|
|
114224
|
-
|
|
115108
|
+
writeFileSync32(join64(root2, SYNC_CURSOR_FILE), `${JSON.stringify(cursor2, null, 2)}
|
|
114225
115109
|
`);
|
|
114226
115110
|
return {
|
|
114227
115111
|
corpusRoot: root2,
|