@hasna/skills 0.10.38 → 0.10.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/bin/index.js +233 -43
- package/bin/mcp.js +1 -1
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/cli/commands/reviewed-runtime-lock.d.ts +17 -0
- package/dist/cli/commands/runtime-copyfile.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/sdk/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2418,3 +2418,29 @@ Migration compares the original witness before projecting the new contract,
|
|
|
2418
2418
|
refuses native discovery or unknown control changes, and changes policy only.
|
|
2419
2419
|
After explicit migration, ordinary supported local MCP updates require no
|
|
2420
2420
|
coordinated policy write.
|
|
2421
|
+
|
|
2422
|
+
### Reproduce a reviewed runtime dependency closure
|
|
2423
|
+
|
|
2424
|
+
For an exact-version copyfile update, supply an owner-private absolute npm
|
|
2425
|
+
lockfile path and its reviewed SHA-256 together:
|
|
2426
|
+
|
|
2427
|
+
```sh
|
|
2428
|
+
skills self-update --version <version> --reviewed-lock /absolute/package-lock.json \
|
|
2429
|
+
--reviewed-lock-sha256 <sha256> --min-release-age 7 --json
|
|
2430
|
+
```
|
|
2431
|
+
|
|
2432
|
+
Use the same lock for a separately reviewed bootstrap. The lock must describe
|
|
2433
|
+
the verified package archive as `file:./verified.tgz`, use lockfile format 3,
|
|
2434
|
+
and bind its version, integrity and dependency declarations. Registry packages
|
|
2435
|
+
must have exact versions and SHA-512 integrity at the configured npm registry.
|
|
2436
|
+
The updater verifies each frozen version against current registry metadata
|
|
2437
|
+
and the requested age policy, runs `npm ci` without re-resolving ranges, then
|
|
2438
|
+
checks lock byte continuity and the actual required dependency closure with
|
|
2439
|
+
`npm sbom --sbom-format=cyclonedx --package-lock-only=false`. This forces
|
|
2440
|
+
installed-manifest inspection; ordinary `npm ls` can trust a hidden lockfile.
|
|
2441
|
+
Existing publisher exemptions remain available through the repeatable
|
|
2442
|
+
`--min-release-age-exclude` option. The receipt records the reviewed lock hash.
|
|
2443
|
+
|
|
2444
|
+
A reviewed lock does not authorize native skill loading, profile changes,
|
|
2445
|
+
credential provisioning or session reconciliation. Without this optional input,
|
|
2446
|
+
the existing update flow continues to resolve its dependency lock normally.
|
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.41",
|
|
36872
36872
|
description: "Skills library for AI coding agents",
|
|
36873
36873
|
type: "module",
|
|
36874
36874
|
bin: {
|
|
@@ -110034,8 +110034,139 @@ var init_remote_account2 = __esm(() => {
|
|
|
110034
110034
|
init_remote_client();
|
|
110035
110035
|
});
|
|
110036
110036
|
|
|
110037
|
+
// src/cli/commands/reviewed-runtime-lock.ts
|
|
110038
|
+
import { createHash as createHash30 } from "crypto";
|
|
110039
|
+
function row(value) {
|
|
110040
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
110041
|
+
throw new Error("REVIEWED_LOCK_SHAPE_INVALID");
|
|
110042
|
+
return value;
|
|
110043
|
+
}
|
|
110044
|
+
function same4(a, b) {
|
|
110045
|
+
const entries = (v) => Object.entries(row(v ?? {})).sort(([a2], [b2]) => a2.localeCompare(b2));
|
|
110046
|
+
return JSON.stringify(entries(a)) === JSON.stringify(entries(b));
|
|
110047
|
+
}
|
|
110048
|
+
function packageName(path) {
|
|
110049
|
+
const parts = path.split("/node_modules/");
|
|
110050
|
+
if (!parts[0].startsWith("node_modules/"))
|
|
110051
|
+
throw new Error("REVIEWED_LOCK_LOCATION_INVALID");
|
|
110052
|
+
parts[0] = parts[0].slice("node_modules/".length);
|
|
110053
|
+
if (parts.some((name) => !NAME.test(name) || name.split("/").some((part) => part === "." || part === "..")))
|
|
110054
|
+
throw new Error("REVIEWED_LOCK_LOCATION_INVALID");
|
|
110055
|
+
return parts.at(-1);
|
|
110056
|
+
}
|
|
110057
|
+
function excluded(name, patterns) {
|
|
110058
|
+
return patterns.some((pattern) => new RegExp(`^${pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".")}$`).test(name));
|
|
110059
|
+
}
|
|
110060
|
+
async function metadata(name, context2) {
|
|
110061
|
+
const url2 = new URL(encodeURIComponent(name), `${context2.registryOrigin}/`);
|
|
110062
|
+
const response = await (context2.fetcher ?? fetch)(url2, { redirect: "error", signal: AbortSignal.timeout(30000) });
|
|
110063
|
+
if (!response.ok || !response.body)
|
|
110064
|
+
throw new Error("REVIEWED_LOCK_METADATA_UNAVAILABLE");
|
|
110065
|
+
const reader = response.body.getReader();
|
|
110066
|
+
const chunks = [];
|
|
110067
|
+
let size2 = 0;
|
|
110068
|
+
try {
|
|
110069
|
+
for (;; ) {
|
|
110070
|
+
const { done, value } = await reader.read();
|
|
110071
|
+
if (done)
|
|
110072
|
+
break;
|
|
110073
|
+
size2 += value.byteLength;
|
|
110074
|
+
if (size2 > MAX_METADATA_BYTES) {
|
|
110075
|
+
await reader.cancel();
|
|
110076
|
+
throw new Error("REVIEWED_LOCK_METADATA_TOO_LARGE");
|
|
110077
|
+
}
|
|
110078
|
+
chunks.push(value);
|
|
110079
|
+
}
|
|
110080
|
+
} finally {
|
|
110081
|
+
reader.releaseLock();
|
|
110082
|
+
}
|
|
110083
|
+
try {
|
|
110084
|
+
return row(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
110085
|
+
} catch {
|
|
110086
|
+
throw new Error("REVIEWED_LOCK_METADATA_INVALID");
|
|
110087
|
+
}
|
|
110088
|
+
}
|
|
110089
|
+
async function validateReviewedRuntimeLock(bytes, sha2562, context2) {
|
|
110090
|
+
if (!/^[a-f0-9]{64}$/.test(sha2562))
|
|
110091
|
+
throw new Error("REVIEWED_LOCK_SHA256_INVALID");
|
|
110092
|
+
if (bytes.byteLength > MAX_LOCK_BYTES || createHash30("sha256").update(bytes).digest("hex") !== sha2562)
|
|
110093
|
+
throw new Error("REVIEWED_LOCK_HASH_MISMATCH");
|
|
110094
|
+
if (!Number.isSafeInteger(context2.minReleaseAge) || context2.minReleaseAge < 1)
|
|
110095
|
+
throw new Error("REVIEWED_LOCK_RELEASE_AGE_REQUIRED");
|
|
110096
|
+
let lock;
|
|
110097
|
+
try {
|
|
110098
|
+
lock = row(JSON.parse(Buffer.from(bytes).toString("utf8")));
|
|
110099
|
+
} catch {
|
|
110100
|
+
throw new Error("REVIEWED_LOCK_SHAPE_INVALID");
|
|
110101
|
+
}
|
|
110102
|
+
if (lock.lockfileVersion !== 3 || lock.requires !== true)
|
|
110103
|
+
throw new Error("REVIEWED_LOCK_FORMAT_UNSUPPORTED");
|
|
110104
|
+
const packages = row(lock.packages);
|
|
110105
|
+
if (Object.keys(packages).length > 5000)
|
|
110106
|
+
throw new Error("REVIEWED_LOCK_POPULATION_TOO_LARGE");
|
|
110107
|
+
const root2 = row(packages[""]);
|
|
110108
|
+
if (!same4(root2.dependencies, { [PACKAGE2]: "file:./verified.tgz" }) || Object.keys(row(root2.devDependencies ?? {})).length || Object.keys(row(root2.optionalDependencies ?? {})).length)
|
|
110109
|
+
throw new Error("REVIEWED_LOCK_ROOT_MISMATCH");
|
|
110110
|
+
const target = row(packages[`node_modules/${PACKAGE2}`]);
|
|
110111
|
+
if (target.version !== context2.version || target.resolved !== "file:verified.tgz" || target.integrity !== context2.archiveIntegrity || target.name !== undefined && target.name !== PACKAGE2)
|
|
110112
|
+
throw new Error("REVIEWED_LOCK_ARCHIVE_MISMATCH");
|
|
110113
|
+
for (const field2 of ["dependencies", "optionalDependencies", "peerDependencies"]) {
|
|
110114
|
+
if (!same4(target[field2], context2.packageManifest[field2]))
|
|
110115
|
+
throw new Error("REVIEWED_LOCK_MANIFEST_MISMATCH");
|
|
110116
|
+
}
|
|
110117
|
+
const registry2 = new URL(context2.registryOrigin);
|
|
110118
|
+
const identities = new Map;
|
|
110119
|
+
for (const [location, value] of Object.entries(packages)) {
|
|
110120
|
+
if (!location)
|
|
110121
|
+
continue;
|
|
110122
|
+
const name = packageName(location), entry = row(value);
|
|
110123
|
+
if (entry.link || entry.dev || entry.name !== undefined && entry.name !== name)
|
|
110124
|
+
throw new Error("REVIEWED_LOCK_LOCATION_INVALID");
|
|
110125
|
+
if (location === `node_modules/${PACKAGE2}`)
|
|
110126
|
+
continue;
|
|
110127
|
+
if (typeof entry.version !== "string" || !VERSION.test(entry.version) || typeof entry.resolved !== "string" || typeof entry.integrity !== "string" || !INTEGRITY.test(entry.integrity))
|
|
110128
|
+
throw new Error("REVIEWED_LOCK_PACKAGE_INVALID");
|
|
110129
|
+
let url2;
|
|
110130
|
+
try {
|
|
110131
|
+
url2 = new URL(entry.resolved);
|
|
110132
|
+
} catch {
|
|
110133
|
+
throw new Error("REVIEWED_LOCK_REGISTRY_INVALID");
|
|
110134
|
+
}
|
|
110135
|
+
if (url2.origin !== registry2.origin || url2.username || url2.password || url2.search || url2.hash || !url2.pathname.endsWith(".tgz"))
|
|
110136
|
+
throw new Error("REVIEWED_LOCK_REGISTRY_INVALID");
|
|
110137
|
+
const entries = identities.get(name) ?? [];
|
|
110138
|
+
entries.push({ name, version: entry.version, resolved: entry.resolved, integrity: entry.integrity });
|
|
110139
|
+
identities.set(name, entries);
|
|
110140
|
+
}
|
|
110141
|
+
const names = [...identities.keys()];
|
|
110142
|
+
const now3 = context2.now ?? Date.now();
|
|
110143
|
+
for (let offset = 0;offset < names.length; offset += 6) {
|
|
110144
|
+
await Promise.all(names.slice(offset, offset + 6).map(async (name) => {
|
|
110145
|
+
const data = await metadata(name, context2), versions2 = row(data.versions), times2 = row(data.time);
|
|
110146
|
+
if (data.name !== name)
|
|
110147
|
+
throw new Error("REVIEWED_LOCK_METADATA_IDENTITY_MISMATCH");
|
|
110148
|
+
for (const entry of identities.get(name)) {
|
|
110149
|
+
const version2 = row(versions2[entry.version]), dist = row(version2.dist);
|
|
110150
|
+
if (version2.name !== name || version2.version !== entry.version || dist.integrity !== entry.integrity || dist.tarball !== entry.resolved)
|
|
110151
|
+
throw new Error("REVIEWED_LOCK_METADATA_IDENTITY_MISMATCH");
|
|
110152
|
+
const published = typeof times2[entry.version] === "string" ? Date.parse(times2[entry.version]) : NaN;
|
|
110153
|
+
if (!Number.isFinite(published) || published > now3 || !excluded(name, context2.minReleaseAgeExclude) && now3 - published < context2.minReleaseAge * 86400000)
|
|
110154
|
+
throw new Error("REVIEWED_LOCK_RELEASE_AGE_REFUSED");
|
|
110155
|
+
}
|
|
110156
|
+
}));
|
|
110157
|
+
}
|
|
110158
|
+
}
|
|
110159
|
+
var PACKAGE2 = "@hasna/skills", NAME, VERSION, INTEGRITY, MAX_LOCK_BYTES, MAX_METADATA_BYTES;
|
|
110160
|
+
var init_reviewed_runtime_lock = __esm(() => {
|
|
110161
|
+
NAME = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/;
|
|
110162
|
+
VERSION = /^\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?(?:\+[a-zA-Z0-9.-]+)?$/;
|
|
110163
|
+
INTEGRITY = /^sha512-[A-Za-z0-9+/]{86}==$/;
|
|
110164
|
+
MAX_LOCK_BYTES = 8 * 1024 * 1024;
|
|
110165
|
+
MAX_METADATA_BYTES = 32 * 1024 * 1024;
|
|
110166
|
+
});
|
|
110167
|
+
|
|
110037
110168
|
// src/cli/commands/runtime-copyfile.ts
|
|
110038
|
-
import { createHash as
|
|
110169
|
+
import { createHash as createHash31, randomUUID as randomUUID9 } from "crypto";
|
|
110039
110170
|
import { Readable } from "stream";
|
|
110040
110171
|
import { createGunzip as createGunzip2 } from "zlib";
|
|
110041
110172
|
import {
|
|
@@ -110059,7 +110190,7 @@ function object9(value, code2) {
|
|
|
110059
110190
|
return value;
|
|
110060
110191
|
}
|
|
110061
110192
|
function hash5(bytes) {
|
|
110062
|
-
return
|
|
110193
|
+
return createHash31("sha256").update(bytes).digest("hex");
|
|
110063
110194
|
}
|
|
110064
110195
|
function isWithin(parent, child) {
|
|
110065
110196
|
const rel = relative10(parent, child);
|
|
@@ -110210,7 +110341,7 @@ function verifyIntegrity(bytes, integrity) {
|
|
|
110210
110341
|
const match = /^sha512-([A-Za-z0-9+/]+={0,2})$/.exec(integrity);
|
|
110211
110342
|
if (!match)
|
|
110212
110343
|
throw new Error("REGISTRY_INTEGRITY_UNSUPPORTED");
|
|
110213
|
-
const actual =
|
|
110344
|
+
const actual = createHash31("sha512").update(bytes).digest("base64");
|
|
110214
110345
|
if (actual !== match[1])
|
|
110215
110346
|
throw new Error("TARBALL_INTEGRITY_MISMATCH");
|
|
110216
110347
|
}
|
|
@@ -110293,6 +110424,8 @@ async function preflightTarball(bytes, limits = {}) {
|
|
|
110293
110424
|
const pathParts2 = rawPath.split("/");
|
|
110294
110425
|
if (rawPath !== "package" && !rawPath.startsWith("package/") || rawPath.startsWith("/") || rawPath.includes("\\") || pathParts2.some((part) => !part || part === "." || part === ".."))
|
|
110295
110426
|
throw new Error("TARBALL_PATH_INVALID");
|
|
110427
|
+
if (pathParts2[1] === "node_modules")
|
|
110428
|
+
throw new Error("TARBALL_BUNDLED_DEPENDENCIES_UNSUPPORTED");
|
|
110296
110429
|
if (paths.has(rawPath))
|
|
110297
110430
|
throw new Error("TARBALL_DUPLICATE_PATH");
|
|
110298
110431
|
paths.add(rawPath);
|
|
@@ -110320,10 +110453,10 @@ async function downloadPackage(version2, stagingRoot, registryOrigin, fetcher) {
|
|
|
110320
110453
|
const metadataResponse = await fetcher(metadataUrl, { redirect: "error", signal: timeout });
|
|
110321
110454
|
if (!metadataResponse.ok || new URL(metadataResponse.url || metadataUrl).origin !== new URL(registryOrigin).origin)
|
|
110322
110455
|
throw new Error("REGISTRY_METADATA_FETCH_FAILED");
|
|
110323
|
-
const
|
|
110324
|
-
if (
|
|
110456
|
+
const metadata2 = object9(JSON.parse(new TextDecoder().decode(await readBodyCapped(metadataResponse, 5 * 1024 * 1024, "REGISTRY_METADATA_SIZE_INVALID"))), "REGISTRY_METADATA_INVALID");
|
|
110457
|
+
if (metadata2.name !== PACKAGE_NAME || metadata2.version !== version2 || metadata2._id !== `${PACKAGE_NAME}@${version2}`)
|
|
110325
110458
|
throw new Error("REGISTRY_PACKAGE_IDENTITY_MISMATCH");
|
|
110326
|
-
const dist = object9(
|
|
110459
|
+
const dist = object9(metadata2.dist, "REGISTRY_DIST_INVALID");
|
|
110327
110460
|
const integrity = typeof dist.integrity === "string" ? dist.integrity : "";
|
|
110328
110461
|
const tarballUrl = expectedTarballUrl(dist.tarball, registryOrigin, version2);
|
|
110329
110462
|
const response = await fetcher(tarballUrl, { redirect: "error", signal: AbortSignal.timeout(30000) });
|
|
@@ -110394,7 +110527,7 @@ async function readBodyCapped(response, limit, errorCode) {
|
|
|
110394
110527
|
}
|
|
110395
110528
|
return bytes;
|
|
110396
110529
|
}
|
|
110397
|
-
function readTree(root2, includeModes) {
|
|
110530
|
+
function readTree(root2, includeModes, excludeNpmDependencies = false) {
|
|
110398
110531
|
const base3 = realpathSync16(root2);
|
|
110399
110532
|
const entries = [];
|
|
110400
110533
|
const walk2 = (dir, prefix = "") => {
|
|
@@ -110402,6 +110535,11 @@ function readTree(root2, includeModes) {
|
|
|
110402
110535
|
const path = join58(dir, item);
|
|
110403
110536
|
const rel = prefix ? `${prefix}/${item}` : item;
|
|
110404
110537
|
const stat3 = lstatSync30(path);
|
|
110538
|
+
if (excludeNpmDependencies && !prefix && item === "node_modules") {
|
|
110539
|
+
if (!stat3.isDirectory() || stat3.isSymbolicLink())
|
|
110540
|
+
throw new Error("INSTALLED_DEPENDENCY_DIRECTORY_UNSAFE");
|
|
110541
|
+
continue;
|
|
110542
|
+
}
|
|
110405
110543
|
if (stat3.isSymbolicLink()) {
|
|
110406
110544
|
const link2 = readlinkSync5(path);
|
|
110407
110545
|
const resolved = resolve25(dirname31(path), link2);
|
|
@@ -110610,8 +110748,10 @@ ${policy.minReleaseAgeExclude.map((pattern) => `min-release-age-exclude[]=${patt
|
|
|
110610
110748
|
return npmVersion;
|
|
110611
110749
|
}
|
|
110612
110750
|
function comparePackageTrees(expectedRoot, installedRoot) {
|
|
110751
|
+
if (entryExists(join58(expectedRoot, "node_modules")))
|
|
110752
|
+
throw new Error("TARBALL_BUNDLED_DEPENDENCIES_UNSUPPORTED");
|
|
110613
110753
|
const expected = readTree(expectedRoot, false);
|
|
110614
|
-
const installed = readTree(installedRoot, false);
|
|
110754
|
+
const installed = readTree(installedRoot, false, true);
|
|
110615
110755
|
if (JSON.stringify(expected.entries) !== JSON.stringify(installed.entries))
|
|
110616
110756
|
throw new Error("INSTALLED_PACKAGE_BYTES_MISMATCH");
|
|
110617
110757
|
return expected.digest;
|
|
@@ -110987,6 +111127,24 @@ async function updateCopyfileRuntime(version2, options = {}) {
|
|
|
110987
111127
|
if (!STABLE_SEMVER_PATTERN.test(version2))
|
|
110988
111128
|
throw new Error("EXACT_STABLE_VERSION_REQUIRED");
|
|
110989
111129
|
const policy = releaseAgePolicy(options);
|
|
111130
|
+
let reviewedLockBytes;
|
|
111131
|
+
if (options.reviewedLock !== undefined || options.reviewedLockSha256 !== undefined) {
|
|
111132
|
+
if (!options.reviewedLock || !options.reviewedLockSha256)
|
|
111133
|
+
throw new Error("REVIEWED_LOCK_INPUT_PAIR_REQUIRED");
|
|
111134
|
+
if (!policy)
|
|
111135
|
+
throw new Error("REVIEWED_LOCK_RELEASE_AGE_REQUIRED");
|
|
111136
|
+
if (!isAbsolute25(options.reviewedLock))
|
|
111137
|
+
throw new Error("REVIEWED_LOCK_ABSOLUTE_PATH_REQUIRED");
|
|
111138
|
+
assertNoSymlinkAncestors(dirname31(options.reviewedLock));
|
|
111139
|
+
const stat3 = lstatSync30(options.reviewedLock);
|
|
111140
|
+
if (!stat3.isFile() || stat3.isSymbolicLink() || stat3.nlink !== 1 || stat3.uid !== process.getuid?.() || stat3.mode & 63 || stat3.size > 8 * 1024 * 1024)
|
|
111141
|
+
throw new Error("REVIEWED_LOCK_FILE_UNSAFE");
|
|
111142
|
+
reviewedLockBytes = readFileSync38(options.reviewedLock);
|
|
111143
|
+
if (!/^[a-f0-9]{64}$/.test(options.reviewedLockSha256))
|
|
111144
|
+
throw new Error("REVIEWED_LOCK_SHA256_INVALID");
|
|
111145
|
+
if (hash5(reviewedLockBytes) !== options.reviewedLockSha256)
|
|
111146
|
+
throw new Error("REVIEWED_LOCK_HASH_MISMATCH");
|
|
111147
|
+
}
|
|
110990
111148
|
const home = resolve25(options.homeDir ?? process.env.HOME ?? "");
|
|
110991
111149
|
if (!home || !existsSync34(home))
|
|
110992
111150
|
throw new Error("HOME_NOT_FOUND");
|
|
@@ -111023,12 +111181,37 @@ async function updateCopyfileRuntime(version2, options = {}) {
|
|
|
111023
111181
|
const stagePackage = join58(stagePath, "install");
|
|
111024
111182
|
mkdirSync24(stagePackage, { mode: 448 });
|
|
111025
111183
|
copyFileSync(artifact.tarballPath, join58(stagePackage, "verified.tgz"));
|
|
111026
|
-
writeFileSync23(join58(stagePackage, "package.json"), JSON.stringify({ private: true, dependencies: { [PACKAGE_NAME]: "file:./verified.tgz" } }, null, 2), { mode: 384, flag: "wx" });
|
|
111027
|
-
|
|
111184
|
+
writeFileSync23(join58(stagePackage, "package.json"), JSON.stringify({ name: "skills-runtime-install", version: "0.0.0", private: true, dependencies: { [PACKAGE_NAME]: "file:./verified.tgz" } }, null, 2), { mode: 384, flag: "wx" });
|
|
111185
|
+
let npmVersion;
|
|
111186
|
+
if (reviewedLockBytes) {
|
|
111187
|
+
await validateReviewedRuntimeLock(reviewedLockBytes, options.reviewedLockSha256, {
|
|
111188
|
+
version: version2,
|
|
111189
|
+
archiveIntegrity: artifact.integrity,
|
|
111190
|
+
packageManifest: object9(JSON.parse(readFileSync38(join58(stagePath, "verified-archive", "package", "package.json"), "utf8")), "PACKAGE_MANIFEST_INVALID"),
|
|
111191
|
+
registryOrigin: REGISTRY_ORIGIN,
|
|
111192
|
+
...policy,
|
|
111193
|
+
fetcher
|
|
111194
|
+
});
|
|
111195
|
+
writeFileSync23(join58(stagePackage, "package-lock.json"), reviewedLockBytes, { mode: 384, flag: "wx" });
|
|
111196
|
+
if (!readFileSync38(join58(stagePackage, "package-lock.json")).equals(reviewedLockBytes))
|
|
111197
|
+
throw new Error("REVIEWED_LOCK_COPY_MISMATCH");
|
|
111198
|
+
} else {
|
|
111199
|
+
npmVersion = await runNpm(["install", "--package-lock-only", "--prefix", stagePackage], stagePackage, home, stagePath, policy);
|
|
111200
|
+
}
|
|
111028
111201
|
const ciNpmVersion = await runNpm(["ci", "--prefix", stagePackage], stagePackage, home, stagePath, policy);
|
|
111029
|
-
if (npmVersion !== ciNpmVersion)
|
|
111202
|
+
if (!reviewedLockBytes && npmVersion !== ciNpmVersion)
|
|
111030
111203
|
throw new Error("NPM_VERSION_DRIFT_DURING_UPDATE");
|
|
111204
|
+
npmVersion = ciNpmVersion;
|
|
111031
111205
|
const lockBytes = readFileSync38(join58(stagePackage, "package-lock.json"));
|
|
111206
|
+
if (reviewedLockBytes) {
|
|
111207
|
+
if (!lockBytes.equals(reviewedLockBytes))
|
|
111208
|
+
throw new Error("REVIEWED_LOCK_DRIFT_DURING_INSTALL");
|
|
111209
|
+
const treeNpmVersion = await runNpm(["sbom", "--sbom-format=cyclonedx", "--package-lock-only=false", "--omit=dev", "--prefix", stagePackage], stagePackage, home, stagePath, policy);
|
|
111210
|
+
if (treeNpmVersion !== npmVersion)
|
|
111211
|
+
throw new Error("NPM_VERSION_DRIFT_DURING_UPDATE");
|
|
111212
|
+
if (!readFileSync38(join58(stagePackage, "package-lock.json")).equals(reviewedLockBytes))
|
|
111213
|
+
throw new Error("REVIEWED_LOCK_DRIFT_DURING_INSTALL");
|
|
111214
|
+
}
|
|
111032
111215
|
const installedPackage = join58(stagePackage, "node_modules", "@hasna", "skills");
|
|
111033
111216
|
const installed = readPackage(join58(installedPackage, "package.json"));
|
|
111034
111217
|
if (installed.version !== version2 || JSON.stringify(Object.entries(installed.bins).sort()) !== JSON.stringify(Object.entries(layout.bin).sort())) {
|
|
@@ -111069,6 +111252,7 @@ async function updateCopyfileRuntime(version2, options = {}) {
|
|
|
111069
111252
|
configs: configPreimages,
|
|
111070
111253
|
preimageSha256,
|
|
111071
111254
|
...policy ? { dependencyPolicy: { ...policy, npmVersion } } : {},
|
|
111255
|
+
...reviewedLockBytes ? { reviewedLockSha256: options.reviewedLockSha256 } : {},
|
|
111072
111256
|
launchers,
|
|
111073
111257
|
switchedLaunchers: [],
|
|
111074
111258
|
rollbackCompletedLaunchers: []
|
|
@@ -111107,7 +111291,7 @@ async function updateCopyfileRuntime(version2, options = {}) {
|
|
|
111107
111291
|
}
|
|
111108
111292
|
receipt.state = "switched";
|
|
111109
111293
|
persistReceipt(join58(finalPath, "rollout-receipt.json"), receipt);
|
|
111110
|
-
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, ...policy ? { dependencyPolicy: { ...policy, npmVersion } } : {} };
|
|
111294
|
+
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, ...policy ? { dependencyPolicy: { ...policy, npmVersion } } : {}, ...reviewedLockBytes ? { reviewedLockSha256: options.reviewedLockSha256 } : {} };
|
|
111111
111295
|
} catch (error61) {
|
|
111112
111296
|
if (switched.length > 0 || moved) {
|
|
111113
111297
|
let rollbackFailed = false;
|
|
@@ -111244,6 +111428,7 @@ function rollbackCopyfileRuntime(receiptId, options = {}) {
|
|
|
111244
111428
|
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;
|
|
111245
111429
|
var init_runtime_copyfile = __esm(() => {
|
|
111246
111430
|
init_skill_contract();
|
|
111431
|
+
init_reviewed_runtime_lock();
|
|
111247
111432
|
STABLE_SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
111248
111433
|
MAX_TARBALL_BYTES = 100 * 1024 * 1024;
|
|
111249
111434
|
MAX_TARBALL_EXPANDED_BYTES = 256 * 1024 * 1024;
|
|
@@ -111260,7 +111445,7 @@ var exports_runtime = {};
|
|
|
111260
111445
|
__export(exports_runtime, {
|
|
111261
111446
|
registerRuntime: () => registerRuntime
|
|
111262
111447
|
});
|
|
111263
|
-
import { createHash as
|
|
111448
|
+
import { createHash as createHash32, randomUUID as randomUUID10 } from "crypto";
|
|
111264
111449
|
import { lstatSync as lstatSync31, mkdirSync as mkdirSync25, readFileSync as readFileSync39, realpathSync as realpathSync17, writeFileSync as writeFileSync24 } from "fs";
|
|
111265
111450
|
import { basename as basename16, isAbsolute as isAbsolute26, join as join59 } from "path";
|
|
111266
111451
|
import { createInterface } from "readline";
|
|
@@ -111301,7 +111486,7 @@ function registerRuntime(parent) {
|
|
|
111301
111486
|
if (!expected)
|
|
111302
111487
|
throw new Error("Cloud artifact not found");
|
|
111303
111488
|
const bytes = await client2.download(id2, name);
|
|
111304
|
-
if (bytes.length !== expected.byteSize ||
|
|
111489
|
+
if (bytes.length !== expected.byteSize || createHash32("sha256").update(bytes).digest("hex") !== expected.sha256)
|
|
111305
111490
|
throw new Error("Cloud artifact integrity verification failed");
|
|
111306
111491
|
writeFileSync24(options.output, bytes, { flag: "wx", mode: 384 });
|
|
111307
111492
|
console.log(JSON.stringify({ path: options.output, sha256: expected.sha256, byteSize: bytes.length }));
|
|
@@ -111331,7 +111516,12 @@ function registerRuntime(parent) {
|
|
|
111331
111516
|
});
|
|
111332
111517
|
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));
|
|
111333
111518
|
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 }));
|
|
111334
|
-
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("--min-release-age <days>", "Require a positive integer dependency release age in days for an exact-version update").option("--min-release-age-exclude <pattern>", "Exclude matching package identities from the release-age policy (repeatable)", (pattern, prior) => [...prior, pattern], []).option("--rollback <receipt-id>", "Restore launcher links from an exact copyfile rollout receipt").option("--adopt-aliases", "Switch verified legacy @hasna/skills aliases to the active copyfile runtime").option("--rollback-aliases <receipt-id>", "Restore aliases from an exact adoption receipt").action(async (options) => {
|
|
111519
|
+
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("--min-release-age <days>", "Require a positive integer dependency release age in days for an exact-version update").option("--min-release-age-exclude <pattern>", "Exclude matching package identities from the release-age policy (repeatable)", (pattern, prior) => [...prior, pattern], []).option("--reviewed-lock <path>", "Install from this private absolute package-lock.json path without resolving dependency ranges").option("--reviewed-lock-sha256 <sha256>", "Exact reviewed SHA-256 of the supplied package-lock.json").option("--rollback <receipt-id>", "Restore launcher links from an exact copyfile rollout receipt").option("--adopt-aliases", "Switch verified legacy @hasna/skills aliases to the active copyfile runtime").option("--rollback-aliases <receipt-id>", "Restore aliases from an exact adoption receipt").action(async (options) => {
|
|
111520
|
+
if ((options.reviewedLock !== undefined || options.reviewedLockSha256 !== undefined) && (!options.version || options.rollback || options.adoptAliases || options.rollbackAliases)) {
|
|
111521
|
+
console.error(JSON.stringify({ error: "REVIEWED_LOCK_REQUIRES_EXACT_VERSION" }));
|
|
111522
|
+
process.exitCode = 1;
|
|
111523
|
+
return;
|
|
111524
|
+
}
|
|
111335
111525
|
if ((options.minReleaseAge !== undefined || options.minReleaseAgeExclude.length > 0) && (!options.version || options.rollback || options.adoptAliases || options.rollbackAliases)) {
|
|
111336
111526
|
console.error(JSON.stringify({ error: "RELEASE_AGE_POLICY_REQUIRES_EXACT_VERSION" }));
|
|
111337
111527
|
process.exitCode = 1;
|
|
@@ -111373,7 +111563,7 @@ function registerRuntime(parent) {
|
|
|
111373
111563
|
if (options.version) {
|
|
111374
111564
|
try {
|
|
111375
111565
|
const minReleaseAge = options.minReleaseAge === undefined ? undefined : /^\d+$/.test(options.minReleaseAge) ? Number(options.minReleaseAge) : NaN;
|
|
111376
|
-
const result2 = await updateCopyfileRuntime(options.version, { minReleaseAge, minReleaseAgeExclude: options.minReleaseAgeExclude });
|
|
111566
|
+
const result2 = await updateCopyfileRuntime(options.version, { minReleaseAge, minReleaseAgeExclude: options.minReleaseAgeExclude, reviewedLock: options.reviewedLock, reviewedLockSha256: options.reviewedLockSha256 });
|
|
111377
111567
|
if (options.json)
|
|
111378
111568
|
console.log(JSON.stringify(result2));
|
|
111379
111569
|
else
|
|
@@ -112950,7 +113140,7 @@ var init_portable_snapshot_filter = __esm(() => {
|
|
|
112950
113140
|
});
|
|
112951
113141
|
|
|
112952
113142
|
// src/lib/station-snapshot.ts
|
|
112953
|
-
import { createHash as
|
|
113143
|
+
import { createHash as createHash33 } from "crypto";
|
|
112954
113144
|
import {
|
|
112955
113145
|
copyFileSync as copyFileSync2,
|
|
112956
113146
|
mkdirSync as mkdirSync26,
|
|
@@ -112965,7 +113155,7 @@ function validateStationId(stationId) {
|
|
|
112965
113155
|
}
|
|
112966
113156
|
}
|
|
112967
113157
|
function sha256File(filePath) {
|
|
112968
|
-
return
|
|
113158
|
+
return createHash33("sha256").update(readFileSync40(filePath)).digest("hex");
|
|
112969
113159
|
}
|
|
112970
113160
|
function scanHome(definition, homesRoot) {
|
|
112971
113161
|
const homePath = homePathFor(definition, homesRoot);
|
|
@@ -113655,7 +113845,7 @@ var init_codex_hook_policy_recovery = __esm(() => {
|
|
|
113655
113845
|
|
|
113656
113846
|
// src/lib/agent-codex-trust.ts
|
|
113657
113847
|
import { lstatSync as lstatSync33, mkdirSync as mkdirSync27, readdirSync as readdirSync23, readFileSync as readFileSync41, realpathSync as realpathSync19, statSync as statSync22 } from "fs";
|
|
113658
|
-
import { createHash as
|
|
113848
|
+
import { createHash as createHash34, randomUUID as randomUUID11 } from "crypto";
|
|
113659
113849
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
113660
113850
|
import { dirname as dirname35, isAbsolute as isAbsolute29, join as join63, resolve as resolve27 } from "path";
|
|
113661
113851
|
import { homedir as homedir12 } from "os";
|
|
@@ -113665,11 +113855,11 @@ function verifyReleaseArtifact(proof, skillsCli) {
|
|
|
113665
113855
|
const tarPath = realpathSync19(proof.packageTarPath);
|
|
113666
113856
|
need5(statSync22(tarPath).isFile(), "RECONCILE_RELEASE_PROVENANCE_PATH");
|
|
113667
113857
|
const tarBytes = readFileSync41(tarPath);
|
|
113668
|
-
need5(tarBytes.byteLength === proof.packageTarBytes &&
|
|
113858
|
+
need5(tarBytes.byteLength === proof.packageTarBytes && createHash34("sha256").update(tarBytes).digest("hex") === proof.packageTarSha256, "RECONCILE_RELEASE_ARTIFACT_CHANGED");
|
|
113669
113859
|
const packageRoot = dirname35(dirname35(realpathSync19(skillsCli.receipt.path)));
|
|
113670
113860
|
const manifest = readFileSync41(join63(packageRoot, "package.json"));
|
|
113671
|
-
need5(
|
|
113672
|
-
need5(
|
|
113861
|
+
need5(createHash34("sha256").update(manifest).digest("hex") === skillsCli.receipt.manifestSha256 && skillsCli.receipt.manifestSha256 === proof.manifestSha256, "RECONCILE_RELEASE_MANIFEST_CHANGED");
|
|
113862
|
+
need5(createHash34("sha256").update(readFileSync41(skillsCli.receipt.path)).digest("hex") === proof.executableSha256 && skillsCli.receipt.sha256 === proof.executableSha256, "RECONCILE_RELEASE_EXECUTABLE_CHANGED");
|
|
113673
113863
|
const members2 = [["package/package.json", manifest], ["package/bin/index.js", readFileSync41(skillsCli.receipt.path)]];
|
|
113674
113864
|
for (const [member, expected] of members2) {
|
|
113675
113865
|
let actual;
|
|
@@ -113987,7 +114177,7 @@ async function enrollCodexNativeHooksUnlocked(options, connect) {
|
|
|
113987
114177
|
}
|
|
113988
114178
|
}
|
|
113989
114179
|
var json2 = (value) => JSON.stringify(value, null, 2) + `
|
|
113990
|
-
`, structure3 = (text4) => JSON.parse(JSON.stringify(Bun.TOML.parse(text4))), eventNames, makePlanDigest = (value) =>
|
|
114180
|
+
`, structure3 = (text4) => JSON.parse(JSON.stringify(Bun.TOML.parse(text4))), eventNames, makePlanDigest = (value) => createHash34("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) => {
|
|
113991
114181
|
const copy = structuredClone(value);
|
|
113992
114182
|
if (copy?.bridge?.commands && Object.hasOwn(copy.bridge.commands, "codex"))
|
|
113993
114183
|
copy.bridge.commands.codex = "<reviewed-codex-binding>";
|
|
@@ -114129,7 +114319,7 @@ __export(exports_agent_integration, {
|
|
|
114129
114319
|
registerAgentIntegration: () => registerAgentIntegration
|
|
114130
114320
|
});
|
|
114131
114321
|
import { existsSync as existsSync36, lstatSync as lstatSync34, readFileSync as readFileSync42, readSync as readSync17, writeFileSync as writeFileSync26 } from "fs";
|
|
114132
|
-
import { createHash as
|
|
114322
|
+
import { createHash as createHash35 } from "crypto";
|
|
114133
114323
|
import { homedir as homedir13 } from "os";
|
|
114134
114324
|
import { isAbsolute as isAbsolute30 } from "path";
|
|
114135
114325
|
function pinnedHookProfile(input3, configuredProfile) {
|
|
@@ -114254,7 +114444,7 @@ function registerAgentIntegration(parent) {
|
|
|
114254
114444
|
skillCount: catalog.skills.length,
|
|
114255
114445
|
output: options.output,
|
|
114256
114446
|
bytes: Buffer.byteLength(bytes),
|
|
114257
|
-
sha256:
|
|
114447
|
+
sha256: createHash35("sha256").update(bytes).digest("hex")
|
|
114258
114448
|
};
|
|
114259
114449
|
await writeCliOutput(options.json ? JSON.stringify(receipt) : `Captured ${receipt.skillCount} native Codex skill(s) in ${receipt.output}.`);
|
|
114260
114450
|
} catch (error61) {
|
|
@@ -114770,7 +114960,7 @@ var init_grants = __esm(() => {
|
|
|
114770
114960
|
});
|
|
114771
114961
|
|
|
114772
114962
|
// src/lib/station-hydrate.ts
|
|
114773
|
-
import { createHash as
|
|
114963
|
+
import { createHash as createHash36 } from "crypto";
|
|
114774
114964
|
import {
|
|
114775
114965
|
copyFileSync as copyFileSync3,
|
|
114776
114966
|
mkdirSync as mkdirSync28,
|
|
@@ -114973,7 +115163,7 @@ function skillSha256(skill) {
|
|
|
114973
115163
|
return sha256File(skill.files[0].winner.fullPath);
|
|
114974
115164
|
}
|
|
114975
115165
|
const joined = skill.files.map((file2) => sha256File(file2.winner.fullPath));
|
|
114976
|
-
return
|
|
115166
|
+
return createHash36("sha256").update(joined.sort().join(`
|
|
114977
115167
|
`)).digest("hex");
|
|
114978
115168
|
}
|
|
114979
115169
|
function writeStationHydration(options) {
|
|
@@ -115130,7 +115320,7 @@ var init_hydrate = __esm(() => {
|
|
|
115130
115320
|
});
|
|
115131
115321
|
|
|
115132
115322
|
// src/lib/prepare-skill.ts
|
|
115133
|
-
import { createHash as
|
|
115323
|
+
import { createHash as createHash37, randomUUID as randomUUID12 } from "crypto";
|
|
115134
115324
|
import { chmodSync as chmodSync5, existsSync as existsSync37, lstatSync as lstatSync35, mkdirSync as mkdirSync29, mkdtempSync as mkdtempSync6, readFileSync as readFileSync44, readdirSync as readdirSync25, renameSync as renameSync14, rmSync as rmSync9, writeFileSync as writeFileSync30 } from "fs";
|
|
115135
115325
|
import { tmpdir as tmpdir2 } from "os";
|
|
115136
115326
|
import { dirname as dirname37, isAbsolute as isAbsolute31, join as join65, relative as relative12, resolve as resolve29, sep as sep15 } from "path";
|
|
@@ -115329,7 +115519,7 @@ function snapshot2(root2) {
|
|
|
115329
115519
|
if (rootStat.isSymbolicLink() || !rootStat.isDirectory())
|
|
115330
115520
|
throw new Error("Skill root must be a real directory, not a symlink.");
|
|
115331
115521
|
const files = [], directories = [];
|
|
115332
|
-
const hash6 =
|
|
115522
|
+
const hash6 = createHash37("sha256").update(`${rootStat.dev}:${rootStat.ino}\x00`);
|
|
115333
115523
|
let count = 0, bytes = 0;
|
|
115334
115524
|
const walk2 = (relative13) => {
|
|
115335
115525
|
for (const name of readdirSync25(join65(root2, relative13)).sort()) {
|
|
@@ -115760,7 +115950,7 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
115760
115950
|
const includeDocs = options.includeDocs ?? true;
|
|
115761
115951
|
const includeRequirements = options.includeRequirements ?? true;
|
|
115762
115952
|
const includeValidation = options.includeValidation ?? true;
|
|
115763
|
-
const
|
|
115953
|
+
const packageName2 = options.packageName ?? "@hasna/skills";
|
|
115764
115954
|
const packageVersion = options.packageVersion ?? package_default.version;
|
|
115765
115955
|
const sourceRepository = options.sourceRepository ?? "hasna/skills";
|
|
115766
115956
|
const registry2 = [...loadRegistryProfile(profile)].sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -115778,7 +115968,7 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
115778
115968
|
tags: [...skill.tags].sort(),
|
|
115779
115969
|
dependencies: [...skill.dependencies ?? []].sort(),
|
|
115780
115970
|
source: {
|
|
115781
|
-
packageName,
|
|
115971
|
+
packageName: packageName2,
|
|
115782
115972
|
packageVersion,
|
|
115783
115973
|
repository: sourceRepository,
|
|
115784
115974
|
directory: directory2
|
|
@@ -115796,7 +115986,7 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
115796
115986
|
return {
|
|
115797
115987
|
schemaVersion: 1,
|
|
115798
115988
|
source: {
|
|
115799
|
-
packageName,
|
|
115989
|
+
packageName: packageName2,
|
|
115800
115990
|
packageVersion,
|
|
115801
115991
|
repository: sourceRepository,
|
|
115802
115992
|
profile
|
|
@@ -115872,10 +116062,10 @@ async function exportSkillVersionForReview(spec, output2, client2) {
|
|
|
115872
116062
|
const target = assertPrivateDestination(output2);
|
|
115873
116063
|
if (existsSync38(target))
|
|
115874
116064
|
throw new Error("Review output already exists; choose a new filename.");
|
|
115875
|
-
const
|
|
115876
|
-
if (!
|
|
116065
|
+
const metadata2 = await client2.getSkillVersion(slug2, version2);
|
|
116066
|
+
if (!metadata2)
|
|
115877
116067
|
throw new Error(`Published skill version ${slug2}@${version2} was not found.`);
|
|
115878
|
-
if (!/^[a-f0-9]{64}$/i.test(
|
|
116068
|
+
if (!/^[a-f0-9]{64}$/i.test(metadata2.bundleSha256) || !Number.isSafeInteger(metadata2.bundleByteSize) || metadata2.bundleByteSize < 1 || metadata2.bundleByteSize > MAX_REVIEW_BUNDLE_BYTES) {
|
|
115879
116069
|
throw new Error("Published bundle metadata is invalid or exceeds the review size limit.");
|
|
115880
116070
|
}
|
|
115881
116071
|
const response = await client2.getBundle(slug2, version2);
|
|
@@ -115887,12 +116077,12 @@ async function exportSkillVersionForReview(spec, output2, client2) {
|
|
|
115887
116077
|
}
|
|
115888
116078
|
const headerSha = response.headers.get("X-Skill-Bundle-Sha256");
|
|
115889
116079
|
const headerVersion = response.headers.get("X-Skill-Version");
|
|
115890
|
-
if (!headerSha || headerSha.toLowerCase() !==
|
|
116080
|
+
if (!headerSha || headerSha.toLowerCase() !== metadata2.bundleSha256.toLowerCase() || headerVersion !== version2) {
|
|
115891
116081
|
response.body?.cancel().catch(() => {});
|
|
115892
116082
|
throw new Error("Published bundle headers do not match the exact version metadata.");
|
|
115893
116083
|
}
|
|
115894
|
-
const bytes = await readBoundedResponse(response, Math.min(
|
|
115895
|
-
if (bytes.byteLength !==
|
|
116084
|
+
const bytes = await readBoundedResponse(response, Math.min(metadata2.bundleByteSize, MAX_REVIEW_BUNDLE_BYTES));
|
|
116085
|
+
if (bytes.byteLength !== metadata2.bundleByteSize || sha256Hex(bytes) !== metadata2.bundleSha256.toLowerCase()) {
|
|
115896
116086
|
throw new Error("Published bundle bytes do not match the exact version metadata.");
|
|
115897
116087
|
}
|
|
115898
116088
|
const fd = openSync25(target, constants23.O_WRONLY | constants23.O_CREAT | constants23.O_EXCL | constants23.O_NOFOLLOW, 384);
|
|
@@ -115906,7 +116096,7 @@ async function exportSkillVersionForReview(spec, output2, client2) {
|
|
|
115906
116096
|
} finally {
|
|
115907
116097
|
closeSync25(fd);
|
|
115908
116098
|
}
|
|
115909
|
-
return { slug: slug2, version: version2, bundleSha256:
|
|
116099
|
+
return { slug: slug2, version: version2, bundleSha256: metadata2.bundleSha256.toLowerCase(), bundleByteSize: bytes.byteLength, output: target };
|
|
115910
116100
|
}
|
|
115911
116101
|
var MAX_REVIEW_BUNDLE_BYTES, SLUG2, NATIVE_CONTAINERS;
|
|
115912
116102
|
var init_review_export = __esm(() => {
|
|
@@ -118090,10 +118280,10 @@ async function reconcileRegistry(options = {}) {
|
|
|
118090
118280
|
throw new ReconcileRegistryError(`Registry listing failed: expected an array of skills, got ${shape}.`, ["Check HASNA_SKILLS_API_URL and the resolved credential; a failed listing must not be read as an empty registry."]);
|
|
118091
118281
|
}
|
|
118092
118282
|
const remotes = new Map;
|
|
118093
|
-
for (const
|
|
118094
|
-
if (!
|
|
118283
|
+
for (const row2 of remoteRowsPayload) {
|
|
118284
|
+
if (!row2 || typeof row2 !== "object")
|
|
118095
118285
|
continue;
|
|
118096
|
-
const skill = remoteRowToSkill(
|
|
118286
|
+
const skill = remoteRowToSkill(row2);
|
|
118097
118287
|
if (skill)
|
|
118098
118288
|
remotes.set(skill.slug, skill);
|
|
118099
118289
|
}
|
package/bin/mcp.js
CHANGED
|
@@ -20019,7 +20019,7 @@ var package_default;
|
|
|
20019
20019
|
var init_package = __esm(() => {
|
|
20020
20020
|
package_default = {
|
|
20021
20021
|
name: "@hasna/skills",
|
|
20022
|
-
version: "0.10.
|
|
20022
|
+
version: "0.10.41",
|
|
20023
20023
|
description: "Skills library for AI coding agents",
|
|
20024
20024
|
type: "module",
|
|
20025
20025
|
bin: {
|
package/bin/migrate.js
CHANGED
package/bin/server.js
CHANGED
|
@@ -24122,7 +24122,7 @@ var init_dist_es9 = __esm(() => {
|
|
|
24122
24122
|
// package.json
|
|
24123
24123
|
var package_default = {
|
|
24124
24124
|
name: "@hasna/skills",
|
|
24125
|
-
version: "0.10.
|
|
24125
|
+
version: "0.10.41",
|
|
24126
24126
|
description: "Skills library for AI coding agents",
|
|
24127
24127
|
type: "module",
|
|
24128
24128
|
bin: {
|
package/bin/worker.js
CHANGED
|
@@ -24125,7 +24125,7 @@ import { randomUUID as randomUUID6 } from "crypto";
|
|
|
24125
24125
|
// package.json
|
|
24126
24126
|
var package_default = {
|
|
24127
24127
|
name: "@hasna/skills",
|
|
24128
|
-
version: "0.10.
|
|
24128
|
+
version: "0.10.41",
|
|
24129
24129
|
description: "Skills library for AI coding agents",
|
|
24130
24130
|
type: "module",
|
|
24131
24131
|
bin: {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type Row = Record<string, unknown>;
|
|
2
|
+
export interface ReviewedRuntimeLockContext {
|
|
3
|
+
version: string;
|
|
4
|
+
archiveIntegrity: string;
|
|
5
|
+
packageManifest: Row;
|
|
6
|
+
registryOrigin: string;
|
|
7
|
+
minReleaseAge: number;
|
|
8
|
+
minReleaseAgeExclude: string[];
|
|
9
|
+
fetcher?: typeof fetch;
|
|
10
|
+
now?: number;
|
|
11
|
+
}
|
|
12
|
+
/** Validate the immutable install input without resolving any version ranges.
|
|
13
|
+
* npm ci subsequently verifies archive bytes and package/root range agreement;
|
|
14
|
+
* npm sbom forceActual validates the installed required closure, including optional peers.
|
|
15
|
+
*/
|
|
16
|
+
export declare function validateReviewedRuntimeLock(bytes: Uint8Array, sha256: string, context: ReviewedRuntimeLockContext): Promise<void>;
|
|
17
|
+
export {};
|
|
@@ -28,6 +28,8 @@ export declare function updateCopyfileRuntime(version: string, options?: {
|
|
|
28
28
|
fetcher?: typeof fetch;
|
|
29
29
|
minReleaseAge?: number;
|
|
30
30
|
minReleaseAgeExclude?: string[];
|
|
31
|
+
reviewedLock?: string;
|
|
32
|
+
reviewedLockSha256?: string;
|
|
31
33
|
onLauncherSwitched?: (path: string) => void;
|
|
32
34
|
}): Promise<JsonObject>;
|
|
33
35
|
export declare function rollbackCopyfileRuntime(receiptId: string, options?: {
|
package/dist/index.js
CHANGED
|
@@ -11040,7 +11040,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
|
|
|
11040
11040
|
// package.json
|
|
11041
11041
|
var package_default = {
|
|
11042
11042
|
name: "@hasna/skills",
|
|
11043
|
-
version: "0.10.
|
|
11043
|
+
version: "0.10.41",
|
|
11044
11044
|
description: "Skills library for AI coding agents",
|
|
11045
11045
|
type: "module",
|
|
11046
11046
|
bin: {
|
package/dist/sdk/index.js
CHANGED
|
@@ -32373,7 +32373,7 @@ var init_event_sink = __esm(() => {
|
|
|
32373
32373
|
// package.json
|
|
32374
32374
|
var package_default = {
|
|
32375
32375
|
name: "@hasna/skills",
|
|
32376
|
-
version: "0.10.
|
|
32376
|
+
version: "0.10.41",
|
|
32377
32377
|
description: "Skills library for AI coding agents",
|
|
32378
32378
|
type: "module",
|
|
32379
32379
|
bin: {
|