@hasna/skills 0.10.4 → 0.10.5
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 +13 -0
- package/bin/index.js +148 -33
- 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/registry.d.ts +2 -0
- package/dist/index.js +4 -1
- package/dist/lib/review-export.d.ts +16 -0
- package/dist/sdk/index.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -561,6 +561,18 @@ skills pull --all --selection-profile default --json
|
|
|
561
561
|
skills pull pdf-generate@0.5.2 --selection-profile default --json
|
|
562
562
|
```
|
|
563
563
|
|
|
564
|
+
To inspect a published version that is not selected, export its immutable bundle
|
|
565
|
+
to an existing owner-only directory outside Git and agent-native discovery:
|
|
566
|
+
|
|
567
|
+
```bash
|
|
568
|
+
skills review-export example-skill@1.0.0 --output /absolute/private/review/example-skill-1.0.0.tar.gz --json
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
The command verifies the version metadata, response headers, byte count, and
|
|
572
|
+
SHA-256 before creating a mode-0600 file. It prints a metadata-only receipt and
|
|
573
|
+
does not install or select the skill. Review and scan the private archive before
|
|
574
|
+
reading its content.
|
|
575
|
+
|
|
564
576
|
Use `sync --selection-profile default --station station-example` when you also
|
|
565
577
|
need a station receipt. Native-folder migration options belong to the older
|
|
566
578
|
sync mode and cannot be mixed with profile sync.
|
|
@@ -945,6 +957,7 @@ of app folders, and `XDG_CONFIG_HOME` is not consulted at all.
|
|
|
945
957
|
| `skills hook agents --json` | | Report maintained adapters and explicit coverage limits |
|
|
946
958
|
| `skills migrate native` | | Inventory native copies; `--apply` archives managed copies, with explicit `--include-unmanaged`, `--include-vendor`, or reviewed `--target-manifest` selection |
|
|
947
959
|
| `skills pull --all --selection-profile <id>` | | With CLI loading active, refresh the selected profile into the verified cache |
|
|
960
|
+
| `skills review-export <slug@version> --output <path>` | | Export one immutable hosted bundle to a private archive for review without selecting it |
|
|
948
961
|
| `skills sync --selection-profile <id> [--check] [--station <id>]` | | Sync or check the selected profile/cache; optionally record station state |
|
|
949
962
|
| `skills station-state <id>` | | Read a station's sync receipt in the authenticated workspace |
|
|
950
963
|
| `skills run <name> [args]` | | Execute a skill directly |
|
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.5",
|
|
36872
36872
|
description: "Skills library for AI coding agents",
|
|
36873
36873
|
type: "module",
|
|
36874
36874
|
bin: {
|
|
@@ -51954,6 +51954,9 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
51954
51954
|
return false;
|
|
51955
51955
|
if (entry.path.startsWith(canonicalAgentPath(join20(home, ".codex", "plugins", "cache"), aliases) + sep4))
|
|
51956
51956
|
return true;
|
|
51957
|
+
const runtimeCache = canonicalAgentPath(join20(home, ".cache", "codex-runtimes"), aliases);
|
|
51958
|
+
if (entry.path.startsWith(runtimeCache + sep4) && discovery?.method === "automatic" && discovery.roots.some((root) => entry.path.startsWith(root + sep4)))
|
|
51959
|
+
return true;
|
|
51957
51960
|
if (!entry.path.startsWith(canonicalAgentPath(join20(home, ".codex", "skills", ".system"), aliases) + sep4))
|
|
51958
51961
|
return false;
|
|
51959
51962
|
return Array.isArray(binding.disabledBuiltins) && binding.disabledBuiltins.some((item) => item.path === entry.path && item.hash === entry.hash);
|
|
@@ -111809,10 +111812,100 @@ var init_registry_sync = __esm(() => {
|
|
|
111809
111812
|
init_skill_validation();
|
|
111810
111813
|
});
|
|
111811
111814
|
|
|
111815
|
+
// src/lib/review-export.ts
|
|
111816
|
+
import { constants as constants22, closeSync as closeSync25, existsSync as existsSync35, fstatSync as fstatSync25, fsyncSync as fsyncSync7, lstatSync as lstatSync31, openSync as openSync25, realpathSync as realpathSync15, statSync as statSync23, writeFileSync as writeFileSync30 } from "fs";
|
|
111817
|
+
import { basename as basename14, dirname as dirname36, isAbsolute as isAbsolute29, join as join61, parse as parse12, resolve as resolve26, sep as sep13 } from "path";
|
|
111818
|
+
function exactVersion(spec) {
|
|
111819
|
+
const at2 = spec.indexOf("@");
|
|
111820
|
+
const slug2 = spec.slice(0, at2);
|
|
111821
|
+
const version2 = spec.slice(at2 + 1);
|
|
111822
|
+
if (at2 < 1 || slug2.length > 128 || !SLUG2.test(slug2) || !isValidSkillVersion(version2)) {
|
|
111823
|
+
throw new Error("Review export requires an exact valid slug@version.");
|
|
111824
|
+
}
|
|
111825
|
+
return { slug: slug2, version: version2 };
|
|
111826
|
+
}
|
|
111827
|
+
function assertPrivateDestination(output2) {
|
|
111828
|
+
if (!isAbsolute29(output2) || resolve26(output2) !== output2 || !basename14(output2).endsWith(".tar.gz")) {
|
|
111829
|
+
throw new Error("Review output must be an absolute, normalized .tar.gz path.");
|
|
111830
|
+
}
|
|
111831
|
+
const parent = dirname36(output2);
|
|
111832
|
+
if (realpathSync15(parent) !== parent || lstatSync31(parent).isSymbolicLink()) {
|
|
111833
|
+
throw new Error("Review output parent must be a real directory, not a symlink.");
|
|
111834
|
+
}
|
|
111835
|
+
const parentStat = statSync23(parent);
|
|
111836
|
+
if (!parentStat.isDirectory() || (parentStat.mode & 63) !== 0 || process.getuid && parentStat.uid !== process.getuid()) {
|
|
111837
|
+
throw new Error("Review output parent must be an owner-only directory (mode 0700).");
|
|
111838
|
+
}
|
|
111839
|
+
const segments = parent.toLowerCase().split(sep13);
|
|
111840
|
+
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")) {
|
|
111841
|
+
throw new Error("Review output cannot be placed in agent-native discovery or Skills installation paths.");
|
|
111842
|
+
}
|
|
111843
|
+
for (let current = parent;; current = dirname36(current)) {
|
|
111844
|
+
if (existsSync35(join61(current, ".git"))) {
|
|
111845
|
+
throw new Error("Review output cannot be placed inside a Git worktree.");
|
|
111846
|
+
}
|
|
111847
|
+
if (current === parse12(current).root)
|
|
111848
|
+
break;
|
|
111849
|
+
}
|
|
111850
|
+
return output2;
|
|
111851
|
+
}
|
|
111852
|
+
async function exportSkillVersionForReview(spec, output2, client2) {
|
|
111853
|
+
const { slug: slug2, version: version2 } = exactVersion(spec);
|
|
111854
|
+
const target = assertPrivateDestination(output2);
|
|
111855
|
+
if (existsSync35(target))
|
|
111856
|
+
throw new Error("Review output already exists; choose a new filename.");
|
|
111857
|
+
const metadata = await client2.getSkillVersion(slug2, version2);
|
|
111858
|
+
if (!metadata)
|
|
111859
|
+
throw new Error(`Published skill version ${slug2}@${version2} was not found.`);
|
|
111860
|
+
if (!/^[a-f0-9]{64}$/i.test(metadata.bundleSha256) || !Number.isSafeInteger(metadata.bundleByteSize) || metadata.bundleByteSize < 1 || metadata.bundleByteSize > MAX_REVIEW_BUNDLE_BYTES) {
|
|
111861
|
+
throw new Error("Published bundle metadata is invalid or exceeds the review size limit.");
|
|
111862
|
+
}
|
|
111863
|
+
const response = await client2.getBundle(slug2, version2);
|
|
111864
|
+
if (!response)
|
|
111865
|
+
throw new Error(`Published bundle for ${slug2}@${version2} is unavailable.`);
|
|
111866
|
+
if (!response.ok) {
|
|
111867
|
+
response.body?.cancel().catch(() => {});
|
|
111868
|
+
throw new Error(`Published bundle request failed: HTTP ${response.status}.`);
|
|
111869
|
+
}
|
|
111870
|
+
const headerSha = response.headers.get("X-Skill-Bundle-Sha256");
|
|
111871
|
+
const headerVersion = response.headers.get("X-Skill-Version");
|
|
111872
|
+
if (!headerSha || headerSha.toLowerCase() !== metadata.bundleSha256.toLowerCase() || headerVersion !== version2) {
|
|
111873
|
+
response.body?.cancel().catch(() => {});
|
|
111874
|
+
throw new Error("Published bundle headers do not match the exact version metadata.");
|
|
111875
|
+
}
|
|
111876
|
+
const bytes = await readBoundedResponse(response, Math.min(metadata.bundleByteSize, MAX_REVIEW_BUNDLE_BYTES));
|
|
111877
|
+
if (bytes.byteLength !== metadata.bundleByteSize || sha256Hex(bytes) !== metadata.bundleSha256.toLowerCase()) {
|
|
111878
|
+
throw new Error("Published bundle bytes do not match the exact version metadata.");
|
|
111879
|
+
}
|
|
111880
|
+
const fd = openSync25(target, constants22.O_WRONLY | constants22.O_CREAT | constants22.O_EXCL | constants22.O_NOFOLLOW, 384);
|
|
111881
|
+
try {
|
|
111882
|
+
writeFileSync30(fd, bytes);
|
|
111883
|
+
fsyncSync7(fd);
|
|
111884
|
+
if (fstatSync25(fd).size !== bytes.byteLength)
|
|
111885
|
+
throw new Error("Incomplete review output write.");
|
|
111886
|
+
} catch (error61) {
|
|
111887
|
+
throw new Error(`Review output write failed. A private partial file may remain at ${target}; use Trash to remove it before reusing this path.`, { cause: error61 });
|
|
111888
|
+
} finally {
|
|
111889
|
+
closeSync25(fd);
|
|
111890
|
+
}
|
|
111891
|
+
return { slug: slug2, version: version2, bundleSha256: metadata.bundleSha256.toLowerCase(), bundleByteSize: bytes.byteLength, output: target };
|
|
111892
|
+
}
|
|
111893
|
+
var MAX_REVIEW_BUNDLE_BYTES, SLUG2, NATIVE_CONTAINERS;
|
|
111894
|
+
var init_review_export = __esm(() => {
|
|
111895
|
+
init_remote_files();
|
|
111896
|
+
init_skill_bundle();
|
|
111897
|
+
init_skill_version();
|
|
111898
|
+
init_native_discovery_roots();
|
|
111899
|
+
MAX_REVIEW_BUNDLE_BYTES = 32 * 1024 * 1024;
|
|
111900
|
+
SLUG2 = /^[a-z0-9][a-z0-9._-]*$/;
|
|
111901
|
+
NATIVE_CONTAINERS = new Set(NATIVE_SKILL_ROOTS.map(([, root2]) => root2.split("/")[0]).filter((root2) => root2 !== ".config"));
|
|
111902
|
+
});
|
|
111903
|
+
|
|
111812
111904
|
// src/cli/commands/registry.ts
|
|
111813
111905
|
var exports_registry2 = {};
|
|
111814
111906
|
__export(exports_registry2, {
|
|
111815
111907
|
registerVersions: () => registerVersions,
|
|
111908
|
+
registerReviewExport: () => registerReviewExport,
|
|
111816
111909
|
registerRegistry: () => registerRegistry,
|
|
111817
111910
|
registerPull: () => registerPull,
|
|
111818
111911
|
registerLifecycle: () => registerLifecycle
|
|
@@ -111824,12 +111917,12 @@ function registerRegistry(parent) {
|
|
|
111824
111917
|
async function writeJson2(value, space) {
|
|
111825
111918
|
const text4 = `${JSON.stringify(value, null, space)}
|
|
111826
111919
|
`;
|
|
111827
|
-
await new Promise((
|
|
111920
|
+
await new Promise((resolve27, reject2) => {
|
|
111828
111921
|
process.stdout.write(text4, (error61) => {
|
|
111829
111922
|
if (error61)
|
|
111830
111923
|
reject2(error61);
|
|
111831
111924
|
else
|
|
111832
|
-
|
|
111925
|
+
resolve27();
|
|
111833
111926
|
});
|
|
111834
111927
|
});
|
|
111835
111928
|
}
|
|
@@ -111971,6 +112064,26 @@ Versions of ${name}
|
|
|
111971
112064
|
}
|
|
111972
112065
|
});
|
|
111973
112066
|
}
|
|
112067
|
+
function registerReviewExport(parent) {
|
|
112068
|
+
parent.command("review-export").argument("<slug@version>", "Exact published skill version to review").requiredOption("--output <path>", "New .tar.gz path inside an existing owner-only directory outside Git and agent discovery").option("--json", "Print a metadata-only receipt", false).description("Export an exact hosted bundle for private review without changing skill selection").action(async (spec, options) => {
|
|
112069
|
+
try {
|
|
112070
|
+
const client2 = await createRemoteSkillsClient();
|
|
112071
|
+
if (!client2)
|
|
112072
|
+
throw new Error("No Skills instance credential is configured. Run skills login.");
|
|
112073
|
+
const receipt = await exportSkillVersionForReview(spec, options.output, client2);
|
|
112074
|
+
if (options.json)
|
|
112075
|
+
console.log(JSON.stringify(receipt));
|
|
112076
|
+
else
|
|
112077
|
+
console.log(`Exported ${receipt.slug}@${receipt.version} to ${receipt.output} (${receipt.bundleSha256}).`);
|
|
112078
|
+
} catch (error61) {
|
|
112079
|
+
if (options.json)
|
|
112080
|
+
console.log(JSON.stringify({ error: error61.message }));
|
|
112081
|
+
else
|
|
112082
|
+
console.error(source_default.red(error61.message));
|
|
112083
|
+
process.exitCode = 1;
|
|
112084
|
+
}
|
|
112085
|
+
});
|
|
112086
|
+
}
|
|
111974
112087
|
function registerLifecycle(parent) {
|
|
111975
112088
|
parent.command("lifecycle").argument("<name>", "Skill name on the configured instance").requiredOption("--state <state>", "Lifecycle state: active or archived").requiredOption("--revision <revision>", "Exact revision id from skills info/get").option("--reason <reason>", "Bounded archive reason").option("--replacement <slug>", "Optional successor slug").option("--json", "Output as JSON", false).description("Change a hosted skill's active/archive lifecycle with an exact revision guard").action(async (name, options) => {
|
|
111976
112089
|
if (options.state !== "active" && options.state !== "archived")
|
|
@@ -111999,6 +112112,7 @@ var init_registry2 = __esm(() => {
|
|
|
111999
112112
|
init_selection_resolver();
|
|
112000
112113
|
init_context();
|
|
112001
112114
|
init_selection_cache();
|
|
112115
|
+
init_review_export();
|
|
112002
112116
|
});
|
|
112003
112117
|
|
|
112004
112118
|
// src/cli/commands/publish.ts
|
|
@@ -112012,9 +112126,9 @@ __export(exports_publish, {
|
|
|
112012
112126
|
PushSkillError: () => PushSkillError
|
|
112013
112127
|
});
|
|
112014
112128
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
112015
|
-
import { existsSync as
|
|
112129
|
+
import { existsSync as existsSync36, readFileSync as readFileSync43 } from "fs";
|
|
112016
112130
|
import { hostname as hostname5 } from "os";
|
|
112017
|
-
import { join as
|
|
112131
|
+
import { join as join62 } from "path";
|
|
112018
112132
|
function registerPublish(parent) {
|
|
112019
112133
|
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) => {
|
|
112020
112134
|
try {
|
|
@@ -112066,8 +112180,8 @@ async function pushSkill(name, options = {}) {
|
|
|
112066
112180
|
const packed = packSkillBundle(skill.path, { maxUnpackedBytes: MAX_UNPACKED_BYTES });
|
|
112067
112181
|
await verifyPackedCanonicalHash(skill.name, packed);
|
|
112068
112182
|
const versionManifest = buildVersionManifest(skill.path, packed);
|
|
112069
|
-
const skillMdPath =
|
|
112070
|
-
const skillMd =
|
|
112183
|
+
const skillMdPath = join62(skill.path, "SKILL.md");
|
|
112184
|
+
const skillMd = existsSync36(skillMdPath) ? readFileSync43(skillMdPath, "utf-8") : undefined;
|
|
112071
112185
|
const base3 = {
|
|
112072
112186
|
slug: skill.name,
|
|
112073
112187
|
path: skill.path,
|
|
@@ -112305,7 +112419,7 @@ var exports_private_publications = {};
|
|
|
112305
112419
|
__export(exports_private_publications, {
|
|
112306
112420
|
registerPrivatePublications: () => registerPrivatePublications
|
|
112307
112421
|
});
|
|
112308
|
-
import { resolve as
|
|
112422
|
+
import { resolve as resolve27 } from "path";
|
|
112309
112423
|
function verifyOptions(options, action) {
|
|
112310
112424
|
if (options.userId === undefined !== (options.membershipId === undefined) || options.userId !== undefined && (!publicationUuid(options.userId) || !publicationUuid(options.membershipId)))
|
|
112311
112425
|
throw new PrivatePublicationError("PUBLICATION_CONTEXT_REQUIRED", "Provide both observed --user-id and --membership-id UUIDs.");
|
|
@@ -112350,9 +112464,9 @@ function registerPrivatePublications(parent) {
|
|
|
112350
112464
|
if (code2 === null)
|
|
112351
112465
|
return;
|
|
112352
112466
|
const context2 = options.userId ? { userId: options.userId, membershipId: options.membershipId } : undefined;
|
|
112353
|
-
const client2 = await privatePublicationSession(options.email, code2, context2), directory2 =
|
|
112467
|
+
const client2 = await privatePublicationSession(options.email, code2, context2), directory2 = resolve27(options.recoveryDir);
|
|
112354
112468
|
if (action === "publish")
|
|
112355
|
-
await preparePrivatePublication(client2,
|
|
112469
|
+
await preparePrivatePublication(client2, resolve27(args2[0]), directory2, { skillId: options.skillId, expectedCurrentVersionId: options.expectedCurrentVersion ?? null, idempotencyKey: options.idempotencyKey });
|
|
112356
112470
|
const result2 = action === "publish" || action === "resume" ? await continuePrivatePublication(client2, directory2, { confirm: true, waitMs: Number(options.waitSeconds) * 1000 }) : await inspectPrivatePublication(client2, directory2, action === "cancel");
|
|
112357
112471
|
print2(result2, options.json);
|
|
112358
112472
|
} catch (error61) {
|
|
@@ -112454,7 +112568,7 @@ __export(exports_auth, {
|
|
|
112454
112568
|
import { createInterface as createInterface3 } from "readline";
|
|
112455
112569
|
function prompt(question) {
|
|
112456
112570
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
112457
|
-
return new Promise((
|
|
112571
|
+
return new Promise((resolve28) => {
|
|
112458
112572
|
let settled = false;
|
|
112459
112573
|
const finish = (answer) => {
|
|
112460
112574
|
if (settled)
|
|
@@ -112463,7 +112577,7 @@ function prompt(question) {
|
|
|
112463
112577
|
rl.close();
|
|
112464
112578
|
if (answer === null)
|
|
112465
112579
|
process.exitCode = 130;
|
|
112466
|
-
|
|
112580
|
+
resolve28(answer);
|
|
112467
112581
|
};
|
|
112468
112582
|
rl.once("SIGINT", () => finish(null));
|
|
112469
112583
|
rl.once("close", () => finish(null));
|
|
@@ -112641,7 +112755,7 @@ Skipped. Run it any time: skills setup agents`));
|
|
|
112641
112755
|
function promptHidden(question) {
|
|
112642
112756
|
const stdin = process.stdin;
|
|
112643
112757
|
process.stdout.write(question);
|
|
112644
|
-
return new Promise((
|
|
112758
|
+
return new Promise((resolve28) => {
|
|
112645
112759
|
let value = "";
|
|
112646
112760
|
const done = (answer) => {
|
|
112647
112761
|
stdin.off("data", onData);
|
|
@@ -112651,7 +112765,7 @@ function promptHidden(question) {
|
|
|
112651
112765
|
`);
|
|
112652
112766
|
if (answer === null)
|
|
112653
112767
|
process.exitCode = 130;
|
|
112654
|
-
|
|
112768
|
+
resolve28(answer);
|
|
112655
112769
|
};
|
|
112656
112770
|
const onData = (chunk2) => {
|
|
112657
112771
|
for (const character of chunk2.toString()) {
|
|
@@ -113197,7 +113311,7 @@ function promptInvitationProof(kind) {
|
|
|
113197
113311
|
const label = kind === "token" ? "invitation token" : "recovery code";
|
|
113198
113312
|
const character = kind === "token" ? /^[A-Za-z0-9_-]$/ : /^\d$/;
|
|
113199
113313
|
const { stdin, stderr: output2 } = process, wasRaw = stdin.isRaw, wasFlowing = stdin.readableFlowing;
|
|
113200
|
-
return new Promise((
|
|
113314
|
+
return new Promise((resolve28) => {
|
|
113201
113315
|
let value = "", settled = false, overflow = false;
|
|
113202
113316
|
const finish = (answer) => {
|
|
113203
113317
|
if (settled)
|
|
@@ -113214,7 +113328,7 @@ function promptInvitationProof(kind) {
|
|
|
113214
113328
|
`);
|
|
113215
113329
|
if (answer === null)
|
|
113216
113330
|
process.exitCode = 130;
|
|
113217
|
-
|
|
113331
|
+
resolve28(answer);
|
|
113218
113332
|
};
|
|
113219
113333
|
const cancel = () => finish(null);
|
|
113220
113334
|
const keypress = (text4, key) => {
|
|
@@ -113786,11 +113900,11 @@ var init_storage2 = __esm(() => {
|
|
|
113786
113900
|
});
|
|
113787
113901
|
|
|
113788
113902
|
// src/lib/registry-reconcile.ts
|
|
113789
|
-
import { existsSync as
|
|
113790
|
-
import { join as
|
|
113903
|
+
import { existsSync as existsSync37, readFileSync as readFileSync44, statSync as statSync24, writeFileSync as writeFileSync31 } from "fs";
|
|
113904
|
+
import { join as join63 } from "path";
|
|
113791
113905
|
function isDirectory2(path) {
|
|
113792
113906
|
try {
|
|
113793
|
-
return
|
|
113907
|
+
return statSync24(path).isDirectory();
|
|
113794
113908
|
} catch {
|
|
113795
113909
|
return false;
|
|
113796
113910
|
}
|
|
@@ -113798,12 +113912,12 @@ function isDirectory2(path) {
|
|
|
113798
113912
|
function migrationNeeded(options) {
|
|
113799
113913
|
if (options.rootDir)
|
|
113800
113914
|
return false;
|
|
113801
|
-
const appDir = options.homeDir ?
|
|
113802
|
-
return !(isOwnerLayoutMigrated(appDir) && isDirectory2(
|
|
113915
|
+
const appDir = options.homeDir ? join63(options.homeDir, ".hasna", "skills") : getDataDir();
|
|
113916
|
+
return !(isOwnerLayoutMigrated(appDir) && isDirectory2(join63(appDir, SKILLS_CACHE_DIRNAME)));
|
|
113803
113917
|
}
|
|
113804
113918
|
function readBaseline(skillDir) {
|
|
113805
|
-
const markerPath =
|
|
113806
|
-
if (!
|
|
113919
|
+
const markerPath = join63(skillDir, PULL_MARKER_FILE);
|
|
113920
|
+
if (!existsSync37(markerPath))
|
|
113807
113921
|
return;
|
|
113808
113922
|
try {
|
|
113809
113923
|
const marker = JSON.parse(readFileSync44(markerPath, "utf-8"));
|
|
@@ -113818,8 +113932,8 @@ function readBaseline(skillDir) {
|
|
|
113818
113932
|
}
|
|
113819
113933
|
}
|
|
113820
113934
|
function readCursor(root2) {
|
|
113821
|
-
const path =
|
|
113822
|
-
if (!
|
|
113935
|
+
const path = join63(root2, SYNC_CURSOR_FILE);
|
|
113936
|
+
if (!existsSync37(path))
|
|
113823
113937
|
return { runCount: 0 };
|
|
113824
113938
|
try {
|
|
113825
113939
|
const cursor2 = JSON.parse(readFileSync44(path, "utf-8"));
|
|
@@ -113831,12 +113945,12 @@ function readCursor(root2) {
|
|
|
113831
113945
|
function resolveCorpusRootReadOnly(options) {
|
|
113832
113946
|
if (options.rootDir)
|
|
113833
113947
|
return { root: options.rootDir, migrationPending: false };
|
|
113834
|
-
const appDir = options.homeDir ?
|
|
113835
|
-
const cache3 =
|
|
113948
|
+
const appDir = options.homeDir ? join63(options.homeDir, ".hasna", "skills") : getDataDirReadOnly();
|
|
113949
|
+
const cache3 = join63(appDir, SKILLS_CACHE_DIRNAME);
|
|
113836
113950
|
if (isOwnerLayoutMigrated(appDir) && isDirectory2(cache3)) {
|
|
113837
113951
|
return { root: cache3, migrationPending: false };
|
|
113838
113952
|
}
|
|
113839
|
-
return { root:
|
|
113953
|
+
return { root: join63(appDir, INSTALLED_SKILLS_DIRNAME), migrationPending: true };
|
|
113840
113954
|
}
|
|
113841
113955
|
function remoteRowToSkill(record11) {
|
|
113842
113956
|
const slug2 = typeof record11.slug === "string" ? record11.slug : typeof record11.name === "string" ? record11.name : undefined;
|
|
@@ -113850,7 +113964,7 @@ function remoteRowToSkill(record11) {
|
|
|
113850
113964
|
}
|
|
113851
113965
|
function recheckLocalSide(plannedLocal, localDir, ops = {
|
|
113852
113966
|
pack: (dir) => packSkillBundle(dir).sha256,
|
|
113853
|
-
exists:
|
|
113967
|
+
exists: existsSync37
|
|
113854
113968
|
}) {
|
|
113855
113969
|
let localNow;
|
|
113856
113970
|
try {
|
|
@@ -113970,7 +114084,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
113970
114084
|
for (const slug2 of allSlugs) {
|
|
113971
114085
|
const local = locals.get(slug2);
|
|
113972
114086
|
const remote = remotes.get(slug2);
|
|
113973
|
-
const baseline = local ? readBaseline(
|
|
114087
|
+
const baseline = local ? readBaseline(join63(root2, slug2)) : undefined;
|
|
113974
114088
|
const { state, reason } = classifySkill(local, remote, baseline);
|
|
113975
114089
|
let { action, reason: actionReason } = resolveAction(state, direction, conflict);
|
|
113976
114090
|
if (state === "remote-only" && isDigestless(remote)) {
|
|
@@ -114029,7 +114143,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
114029
114143
|
try {
|
|
114030
114144
|
await pushSkill(slug2, { rootDir: root2, client: client2 });
|
|
114031
114145
|
const pushed = locals.get(slug2);
|
|
114032
|
-
writePullMarker(
|
|
114146
|
+
writePullMarker(join63(root2, slug2), {
|
|
114033
114147
|
skill: slug2,
|
|
114034
114148
|
...pushed?.version ? { version: pushed.version } : {},
|
|
114035
114149
|
...pushed?.sha256 ? { contentHash: pushed.sha256 } : {},
|
|
@@ -114101,7 +114215,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
114101
114215
|
runCount: readCursor(root2).runCount + 1,
|
|
114102
114216
|
summary
|
|
114103
114217
|
};
|
|
114104
|
-
|
|
114218
|
+
writeFileSync31(join63(root2, SYNC_CURSOR_FILE), `${JSON.stringify(cursor2, null, 2)}
|
|
114105
114219
|
`);
|
|
114106
114220
|
return {
|
|
114107
114221
|
corpusRoot: root2,
|
|
@@ -122407,11 +122521,12 @@ var { registerPortableSkillCommands: registerPortableSkillCommands2 } = await Pr
|
|
|
122407
122521
|
registerPortableSkillCommands2(program2);
|
|
122408
122522
|
var { registerSchedule: registerSchedule2 } = await Promise.resolve().then(() => (init_schedule(), exports_schedule));
|
|
122409
122523
|
registerSchedule2(program2);
|
|
122410
|
-
var { registerRegistry: registerRegistry2, registerPull: registerPull2, registerVersions: registerVersions2, registerLifecycle: registerLifecycle2 } = await Promise.resolve().then(() => (init_registry2(), exports_registry2));
|
|
122524
|
+
var { registerRegistry: registerRegistry2, registerPull: registerPull2, registerVersions: registerVersions2, registerLifecycle: registerLifecycle2, registerReviewExport: registerReviewExport2 } = await Promise.resolve().then(() => (init_registry2(), exports_registry2));
|
|
122411
122525
|
registerRegistry2(program2);
|
|
122412
122526
|
registerPull2(program2);
|
|
122413
122527
|
registerVersions2(program2);
|
|
122414
122528
|
registerLifecycle2(program2);
|
|
122529
|
+
registerReviewExport2(program2);
|
|
122415
122530
|
var { registerPublish: registerPublish2 } = await Promise.resolve().then(() => (init_publish(), exports_publish));
|
|
122416
122531
|
registerPublish2(program2);
|
|
122417
122532
|
var { registerPrivatePublications: registerPrivatePublications2 } = await Promise.resolve().then(() => (init_private_publications(), exports_private_publications));
|
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.5",
|
|
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.5",
|
|
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.5",
|
|
24129
24129
|
description: "Skills library for AI coding agents",
|
|
24130
24130
|
type: "module",
|
|
24131
24131
|
bin: {
|
|
@@ -13,4 +13,6 @@ export declare function registerPull(parent: Command): void;
|
|
|
13
13
|
* instance, newest first, with the one the instance currently serves marked (hasna/apps#1630).
|
|
14
14
|
*/
|
|
15
15
|
export declare function registerVersions(parent: Command): void;
|
|
16
|
+
/** Read one immutable bundle into a private review artifact without selecting or installing it. */
|
|
17
|
+
export declare function registerReviewExport(parent: Command): void;
|
|
16
18
|
export declare function registerLifecycle(parent: Command): void;
|
package/dist/index.js
CHANGED
|
@@ -9611,7 +9611,7 @@ import { isAbsolute as isAbsolute11, join as join23, resolve as resolve11 } from
|
|
|
9611
9611
|
// package.json
|
|
9612
9612
|
var package_default = {
|
|
9613
9613
|
name: "@hasna/skills",
|
|
9614
|
-
version: "0.10.
|
|
9614
|
+
version: "0.10.5",
|
|
9615
9615
|
description: "Skills library for AI coding agents",
|
|
9616
9616
|
type: "module",
|
|
9617
9617
|
bin: {
|
|
@@ -14133,6 +14133,9 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
14133
14133
|
return false;
|
|
14134
14134
|
if (entry.path.startsWith(canonicalAgentPath(join16(home, ".codex", "plugins", "cache"), aliases) + sep4))
|
|
14135
14135
|
return true;
|
|
14136
|
+
const runtimeCache = canonicalAgentPath(join16(home, ".cache", "codex-runtimes"), aliases);
|
|
14137
|
+
if (entry.path.startsWith(runtimeCache + sep4) && discovery?.method === "automatic" && discovery.roots.some((root) => entry.path.startsWith(root + sep4)))
|
|
14138
|
+
return true;
|
|
14136
14139
|
if (!entry.path.startsWith(canonicalAgentPath(join16(home, ".codex", "skills", ".system"), aliases) + sep4))
|
|
14137
14140
|
return false;
|
|
14138
14141
|
return Array.isArray(binding.disabledBuiltins) && binding.disabledBuiltins.some((item) => item.path === entry.path && item.hash === entry.hash);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface ReviewExportClient {
|
|
2
|
+
getSkillVersion(slug: string, version: string): Promise<{
|
|
3
|
+
bundleSha256: string;
|
|
4
|
+
bundleByteSize: number;
|
|
5
|
+
} | null>;
|
|
6
|
+
getBundle(slug: string, version: string): Promise<Response | null>;
|
|
7
|
+
}
|
|
8
|
+
export interface ReviewExportReceipt {
|
|
9
|
+
slug: string;
|
|
10
|
+
version: string;
|
|
11
|
+
bundleSha256: string;
|
|
12
|
+
bundleByteSize: number;
|
|
13
|
+
output: string;
|
|
14
|
+
}
|
|
15
|
+
/** Export for human review only. This never changes corpus, cache, profiles, or native discovery. */
|
|
16
|
+
export declare function exportSkillVersionForReview(spec: string, output: string, client: ReviewExportClient): Promise<ReviewExportReceipt>;
|
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.5",
|
|
32377
32377
|
description: "Skills library for AI coding agents",
|
|
32378
32378
|
type: "module",
|
|
32379
32379
|
bin: {
|
|
@@ -42004,6 +42004,9 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
42004
42004
|
return false;
|
|
42005
42005
|
if (entry.path.startsWith(canonicalAgentPath(join21(home, ".codex", "plugins", "cache"), aliases) + sep4))
|
|
42006
42006
|
return true;
|
|
42007
|
+
const runtimeCache = canonicalAgentPath(join21(home, ".cache", "codex-runtimes"), aliases);
|
|
42008
|
+
if (entry.path.startsWith(runtimeCache + sep4) && discovery?.method === "automatic" && discovery.roots.some((root2) => entry.path.startsWith(root2 + sep4)))
|
|
42009
|
+
return true;
|
|
42007
42010
|
if (!entry.path.startsWith(canonicalAgentPath(join21(home, ".codex", "skills", ".system"), aliases) + sep4))
|
|
42008
42011
|
return false;
|
|
42009
42012
|
return Array.isArray(binding.disabledBuiltins) && binding.disabledBuiltins.some((item) => item.path === entry.path && item.hash === entry.hash);
|