@hasna/skills 0.10.3 → 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 +158 -38
- 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 +14 -6
- package/dist/lib/review-export.d.ts +16 -0
- package/dist/sdk/index.js +14 -6
- 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: {
|
|
@@ -51946,12 +51946,20 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
51946
51946
|
const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join20(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
|
|
51947
51947
|
if (agent === "codex" && [...setting(expected), ...(codexConfig.skills?.config ?? []).filter((entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME)].some((entry) => entry.enabled === false))
|
|
51948
51948
|
throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
|
|
51949
|
-
const
|
|
51950
|
-
if (agent !== "codex" || !entry.vendor
|
|
51949
|
+
const disabledVendorSkill = (entry) => {
|
|
51950
|
+
if (agent !== "codex" || !entry.vendor)
|
|
51951
51951
|
return false;
|
|
51952
|
-
const matched = Array.isArray(binding.disabledBuiltins) && binding.disabledBuiltins.some((item) => item.path === entry.path && item.hash === entry.hash);
|
|
51953
51952
|
const settings = setting(entry.path);
|
|
51954
|
-
|
|
51953
|
+
if (settings.length !== 1 || settings[0]?.enabled !== false)
|
|
51954
|
+
return false;
|
|
51955
|
+
if (entry.path.startsWith(canonicalAgentPath(join20(home, ".codex", "plugins", "cache"), aliases) + sep4))
|
|
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;
|
|
51960
|
+
if (!entry.path.startsWith(canonicalAgentPath(join20(home, ".codex", "skills", ".system"), aliases) + sep4))
|
|
51961
|
+
return false;
|
|
51962
|
+
return Array.isArray(binding.disabledBuiltins) && binding.disabledBuiltins.some((item) => item.path === entry.path && item.hash === entry.hash);
|
|
51955
51963
|
};
|
|
51956
51964
|
if (!discovery || discovery.agent !== agent)
|
|
51957
51965
|
throw new Error("NATIVE_SKILL_DRIFT: native discovery coverage is missing; run skills hook install");
|
|
@@ -51968,7 +51976,7 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
51968
51976
|
throw new Error(`NATIVE_SKILL_DRIFT: ${error.message}`);
|
|
51969
51977
|
}
|
|
51970
51978
|
const inventory = inventoryNativeSkills(home, { includeVendor: true, guardHermes: agent === "hermes", agents: [agent], projectDirs: [...roots], agentRoots: discovery.roots.map((path) => ({ agent, path })), allowRootAliases: aliases.length > 0 });
|
|
51971
|
-
const unexpected = inventory.filter((entry) => visible(entry) && !entry.bridge && !
|
|
51979
|
+
const unexpected = inventory.filter((entry) => visible(entry) && !entry.bridge && !disabledVendorSkill(entry));
|
|
51972
51980
|
if (unexpected.length) {
|
|
51973
51981
|
const paths = unexpected.slice(0, 3).map((entry) => {
|
|
51974
51982
|
const escaped = JSON.stringify(entry.path).replace(/[\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
@@ -111804,10 +111812,100 @@ var init_registry_sync = __esm(() => {
|
|
|
111804
111812
|
init_skill_validation();
|
|
111805
111813
|
});
|
|
111806
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
|
+
|
|
111807
111904
|
// src/cli/commands/registry.ts
|
|
111808
111905
|
var exports_registry2 = {};
|
|
111809
111906
|
__export(exports_registry2, {
|
|
111810
111907
|
registerVersions: () => registerVersions,
|
|
111908
|
+
registerReviewExport: () => registerReviewExport,
|
|
111811
111909
|
registerRegistry: () => registerRegistry,
|
|
111812
111910
|
registerPull: () => registerPull,
|
|
111813
111911
|
registerLifecycle: () => registerLifecycle
|
|
@@ -111819,12 +111917,12 @@ function registerRegistry(parent) {
|
|
|
111819
111917
|
async function writeJson2(value, space) {
|
|
111820
111918
|
const text4 = `${JSON.stringify(value, null, space)}
|
|
111821
111919
|
`;
|
|
111822
|
-
await new Promise((
|
|
111920
|
+
await new Promise((resolve27, reject2) => {
|
|
111823
111921
|
process.stdout.write(text4, (error61) => {
|
|
111824
111922
|
if (error61)
|
|
111825
111923
|
reject2(error61);
|
|
111826
111924
|
else
|
|
111827
|
-
|
|
111925
|
+
resolve27();
|
|
111828
111926
|
});
|
|
111829
111927
|
});
|
|
111830
111928
|
}
|
|
@@ -111966,6 +112064,26 @@ Versions of ${name}
|
|
|
111966
112064
|
}
|
|
111967
112065
|
});
|
|
111968
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
|
+
}
|
|
111969
112087
|
function registerLifecycle(parent) {
|
|
111970
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) => {
|
|
111971
112089
|
if (options.state !== "active" && options.state !== "archived")
|
|
@@ -111994,6 +112112,7 @@ var init_registry2 = __esm(() => {
|
|
|
111994
112112
|
init_selection_resolver();
|
|
111995
112113
|
init_context();
|
|
111996
112114
|
init_selection_cache();
|
|
112115
|
+
init_review_export();
|
|
111997
112116
|
});
|
|
111998
112117
|
|
|
111999
112118
|
// src/cli/commands/publish.ts
|
|
@@ -112007,9 +112126,9 @@ __export(exports_publish, {
|
|
|
112007
112126
|
PushSkillError: () => PushSkillError
|
|
112008
112127
|
});
|
|
112009
112128
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
112010
|
-
import { existsSync as
|
|
112129
|
+
import { existsSync as existsSync36, readFileSync as readFileSync43 } from "fs";
|
|
112011
112130
|
import { hostname as hostname5 } from "os";
|
|
112012
|
-
import { join as
|
|
112131
|
+
import { join as join62 } from "path";
|
|
112013
112132
|
function registerPublish(parent) {
|
|
112014
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) => {
|
|
112015
112134
|
try {
|
|
@@ -112061,8 +112180,8 @@ async function pushSkill(name, options = {}) {
|
|
|
112061
112180
|
const packed = packSkillBundle(skill.path, { maxUnpackedBytes: MAX_UNPACKED_BYTES });
|
|
112062
112181
|
await verifyPackedCanonicalHash(skill.name, packed);
|
|
112063
112182
|
const versionManifest = buildVersionManifest(skill.path, packed);
|
|
112064
|
-
const skillMdPath =
|
|
112065
|
-
const skillMd =
|
|
112183
|
+
const skillMdPath = join62(skill.path, "SKILL.md");
|
|
112184
|
+
const skillMd = existsSync36(skillMdPath) ? readFileSync43(skillMdPath, "utf-8") : undefined;
|
|
112066
112185
|
const base3 = {
|
|
112067
112186
|
slug: skill.name,
|
|
112068
112187
|
path: skill.path,
|
|
@@ -112300,7 +112419,7 @@ var exports_private_publications = {};
|
|
|
112300
112419
|
__export(exports_private_publications, {
|
|
112301
112420
|
registerPrivatePublications: () => registerPrivatePublications
|
|
112302
112421
|
});
|
|
112303
|
-
import { resolve as
|
|
112422
|
+
import { resolve as resolve27 } from "path";
|
|
112304
112423
|
function verifyOptions(options, action) {
|
|
112305
112424
|
if (options.userId === undefined !== (options.membershipId === undefined) || options.userId !== undefined && (!publicationUuid(options.userId) || !publicationUuid(options.membershipId)))
|
|
112306
112425
|
throw new PrivatePublicationError("PUBLICATION_CONTEXT_REQUIRED", "Provide both observed --user-id and --membership-id UUIDs.");
|
|
@@ -112345,9 +112464,9 @@ function registerPrivatePublications(parent) {
|
|
|
112345
112464
|
if (code2 === null)
|
|
112346
112465
|
return;
|
|
112347
112466
|
const context2 = options.userId ? { userId: options.userId, membershipId: options.membershipId } : undefined;
|
|
112348
|
-
const client2 = await privatePublicationSession(options.email, code2, context2), directory2 =
|
|
112467
|
+
const client2 = await privatePublicationSession(options.email, code2, context2), directory2 = resolve27(options.recoveryDir);
|
|
112349
112468
|
if (action === "publish")
|
|
112350
|
-
await preparePrivatePublication(client2,
|
|
112469
|
+
await preparePrivatePublication(client2, resolve27(args2[0]), directory2, { skillId: options.skillId, expectedCurrentVersionId: options.expectedCurrentVersion ?? null, idempotencyKey: options.idempotencyKey });
|
|
112351
112470
|
const result2 = action === "publish" || action === "resume" ? await continuePrivatePublication(client2, directory2, { confirm: true, waitMs: Number(options.waitSeconds) * 1000 }) : await inspectPrivatePublication(client2, directory2, action === "cancel");
|
|
112352
112471
|
print2(result2, options.json);
|
|
112353
112472
|
} catch (error61) {
|
|
@@ -112449,7 +112568,7 @@ __export(exports_auth, {
|
|
|
112449
112568
|
import { createInterface as createInterface3 } from "readline";
|
|
112450
112569
|
function prompt(question) {
|
|
112451
112570
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
112452
|
-
return new Promise((
|
|
112571
|
+
return new Promise((resolve28) => {
|
|
112453
112572
|
let settled = false;
|
|
112454
112573
|
const finish = (answer) => {
|
|
112455
112574
|
if (settled)
|
|
@@ -112458,7 +112577,7 @@ function prompt(question) {
|
|
|
112458
112577
|
rl.close();
|
|
112459
112578
|
if (answer === null)
|
|
112460
112579
|
process.exitCode = 130;
|
|
112461
|
-
|
|
112580
|
+
resolve28(answer);
|
|
112462
112581
|
};
|
|
112463
112582
|
rl.once("SIGINT", () => finish(null));
|
|
112464
112583
|
rl.once("close", () => finish(null));
|
|
@@ -112636,7 +112755,7 @@ Skipped. Run it any time: skills setup agents`));
|
|
|
112636
112755
|
function promptHidden(question) {
|
|
112637
112756
|
const stdin = process.stdin;
|
|
112638
112757
|
process.stdout.write(question);
|
|
112639
|
-
return new Promise((
|
|
112758
|
+
return new Promise((resolve28) => {
|
|
112640
112759
|
let value = "";
|
|
112641
112760
|
const done = (answer) => {
|
|
112642
112761
|
stdin.off("data", onData);
|
|
@@ -112646,7 +112765,7 @@ function promptHidden(question) {
|
|
|
112646
112765
|
`);
|
|
112647
112766
|
if (answer === null)
|
|
112648
112767
|
process.exitCode = 130;
|
|
112649
|
-
|
|
112768
|
+
resolve28(answer);
|
|
112650
112769
|
};
|
|
112651
112770
|
const onData = (chunk2) => {
|
|
112652
112771
|
for (const character of chunk2.toString()) {
|
|
@@ -113192,7 +113311,7 @@ function promptInvitationProof(kind) {
|
|
|
113192
113311
|
const label = kind === "token" ? "invitation token" : "recovery code";
|
|
113193
113312
|
const character = kind === "token" ? /^[A-Za-z0-9_-]$/ : /^\d$/;
|
|
113194
113313
|
const { stdin, stderr: output2 } = process, wasRaw = stdin.isRaw, wasFlowing = stdin.readableFlowing;
|
|
113195
|
-
return new Promise((
|
|
113314
|
+
return new Promise((resolve28) => {
|
|
113196
113315
|
let value = "", settled = false, overflow = false;
|
|
113197
113316
|
const finish = (answer) => {
|
|
113198
113317
|
if (settled)
|
|
@@ -113209,7 +113328,7 @@ function promptInvitationProof(kind) {
|
|
|
113209
113328
|
`);
|
|
113210
113329
|
if (answer === null)
|
|
113211
113330
|
process.exitCode = 130;
|
|
113212
|
-
|
|
113331
|
+
resolve28(answer);
|
|
113213
113332
|
};
|
|
113214
113333
|
const cancel = () => finish(null);
|
|
113215
113334
|
const keypress = (text4, key) => {
|
|
@@ -113781,11 +113900,11 @@ var init_storage2 = __esm(() => {
|
|
|
113781
113900
|
});
|
|
113782
113901
|
|
|
113783
113902
|
// src/lib/registry-reconcile.ts
|
|
113784
|
-
import { existsSync as
|
|
113785
|
-
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";
|
|
113786
113905
|
function isDirectory2(path) {
|
|
113787
113906
|
try {
|
|
113788
|
-
return
|
|
113907
|
+
return statSync24(path).isDirectory();
|
|
113789
113908
|
} catch {
|
|
113790
113909
|
return false;
|
|
113791
113910
|
}
|
|
@@ -113793,12 +113912,12 @@ function isDirectory2(path) {
|
|
|
113793
113912
|
function migrationNeeded(options) {
|
|
113794
113913
|
if (options.rootDir)
|
|
113795
113914
|
return false;
|
|
113796
|
-
const appDir = options.homeDir ?
|
|
113797
|
-
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)));
|
|
113798
113917
|
}
|
|
113799
113918
|
function readBaseline(skillDir) {
|
|
113800
|
-
const markerPath =
|
|
113801
|
-
if (!
|
|
113919
|
+
const markerPath = join63(skillDir, PULL_MARKER_FILE);
|
|
113920
|
+
if (!existsSync37(markerPath))
|
|
113802
113921
|
return;
|
|
113803
113922
|
try {
|
|
113804
113923
|
const marker = JSON.parse(readFileSync44(markerPath, "utf-8"));
|
|
@@ -113813,8 +113932,8 @@ function readBaseline(skillDir) {
|
|
|
113813
113932
|
}
|
|
113814
113933
|
}
|
|
113815
113934
|
function readCursor(root2) {
|
|
113816
|
-
const path =
|
|
113817
|
-
if (!
|
|
113935
|
+
const path = join63(root2, SYNC_CURSOR_FILE);
|
|
113936
|
+
if (!existsSync37(path))
|
|
113818
113937
|
return { runCount: 0 };
|
|
113819
113938
|
try {
|
|
113820
113939
|
const cursor2 = JSON.parse(readFileSync44(path, "utf-8"));
|
|
@@ -113826,12 +113945,12 @@ function readCursor(root2) {
|
|
|
113826
113945
|
function resolveCorpusRootReadOnly(options) {
|
|
113827
113946
|
if (options.rootDir)
|
|
113828
113947
|
return { root: options.rootDir, migrationPending: false };
|
|
113829
|
-
const appDir = options.homeDir ?
|
|
113830
|
-
const cache3 =
|
|
113948
|
+
const appDir = options.homeDir ? join63(options.homeDir, ".hasna", "skills") : getDataDirReadOnly();
|
|
113949
|
+
const cache3 = join63(appDir, SKILLS_CACHE_DIRNAME);
|
|
113831
113950
|
if (isOwnerLayoutMigrated(appDir) && isDirectory2(cache3)) {
|
|
113832
113951
|
return { root: cache3, migrationPending: false };
|
|
113833
113952
|
}
|
|
113834
|
-
return { root:
|
|
113953
|
+
return { root: join63(appDir, INSTALLED_SKILLS_DIRNAME), migrationPending: true };
|
|
113835
113954
|
}
|
|
113836
113955
|
function remoteRowToSkill(record11) {
|
|
113837
113956
|
const slug2 = typeof record11.slug === "string" ? record11.slug : typeof record11.name === "string" ? record11.name : undefined;
|
|
@@ -113845,7 +113964,7 @@ function remoteRowToSkill(record11) {
|
|
|
113845
113964
|
}
|
|
113846
113965
|
function recheckLocalSide(plannedLocal, localDir, ops = {
|
|
113847
113966
|
pack: (dir) => packSkillBundle(dir).sha256,
|
|
113848
|
-
exists:
|
|
113967
|
+
exists: existsSync37
|
|
113849
113968
|
}) {
|
|
113850
113969
|
let localNow;
|
|
113851
113970
|
try {
|
|
@@ -113965,7 +114084,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
113965
114084
|
for (const slug2 of allSlugs) {
|
|
113966
114085
|
const local = locals.get(slug2);
|
|
113967
114086
|
const remote = remotes.get(slug2);
|
|
113968
|
-
const baseline = local ? readBaseline(
|
|
114087
|
+
const baseline = local ? readBaseline(join63(root2, slug2)) : undefined;
|
|
113969
114088
|
const { state, reason } = classifySkill(local, remote, baseline);
|
|
113970
114089
|
let { action, reason: actionReason } = resolveAction(state, direction, conflict);
|
|
113971
114090
|
if (state === "remote-only" && isDigestless(remote)) {
|
|
@@ -114024,7 +114143,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
114024
114143
|
try {
|
|
114025
114144
|
await pushSkill(slug2, { rootDir: root2, client: client2 });
|
|
114026
114145
|
const pushed = locals.get(slug2);
|
|
114027
|
-
writePullMarker(
|
|
114146
|
+
writePullMarker(join63(root2, slug2), {
|
|
114028
114147
|
skill: slug2,
|
|
114029
114148
|
...pushed?.version ? { version: pushed.version } : {},
|
|
114030
114149
|
...pushed?.sha256 ? { contentHash: pushed.sha256 } : {},
|
|
@@ -114096,7 +114215,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
114096
114215
|
runCount: readCursor(root2).runCount + 1,
|
|
114097
114216
|
summary
|
|
114098
114217
|
};
|
|
114099
|
-
|
|
114218
|
+
writeFileSync31(join63(root2, SYNC_CURSOR_FILE), `${JSON.stringify(cursor2, null, 2)}
|
|
114100
114219
|
`);
|
|
114101
114220
|
return {
|
|
114102
114221
|
corpusRoot: root2,
|
|
@@ -122402,11 +122521,12 @@ var { registerPortableSkillCommands: registerPortableSkillCommands2 } = await Pr
|
|
|
122402
122521
|
registerPortableSkillCommands2(program2);
|
|
122403
122522
|
var { registerSchedule: registerSchedule2 } = await Promise.resolve().then(() => (init_schedule(), exports_schedule));
|
|
122404
122523
|
registerSchedule2(program2);
|
|
122405
|
-
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));
|
|
122406
122525
|
registerRegistry2(program2);
|
|
122407
122526
|
registerPull2(program2);
|
|
122408
122527
|
registerVersions2(program2);
|
|
122409
122528
|
registerLifecycle2(program2);
|
|
122529
|
+
registerReviewExport2(program2);
|
|
122410
122530
|
var { registerPublish: registerPublish2 } = await Promise.resolve().then(() => (init_publish(), exports_publish));
|
|
122411
122531
|
registerPublish2(program2);
|
|
122412
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: {
|
|
@@ -14125,12 +14125,20 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
14125
14125
|
const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join16(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
|
|
14126
14126
|
if (agent === "codex" && [...setting(expected), ...(codexConfig.skills?.config ?? []).filter((entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME)].some((entry) => entry.enabled === false))
|
|
14127
14127
|
throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
|
|
14128
|
-
const
|
|
14129
|
-
if (agent !== "codex" || !entry.vendor
|
|
14128
|
+
const disabledVendorSkill = (entry) => {
|
|
14129
|
+
if (agent !== "codex" || !entry.vendor)
|
|
14130
14130
|
return false;
|
|
14131
|
-
const matched = Array.isArray(binding.disabledBuiltins) && binding.disabledBuiltins.some((item) => item.path === entry.path && item.hash === entry.hash);
|
|
14132
14131
|
const settings = setting(entry.path);
|
|
14133
|
-
|
|
14132
|
+
if (settings.length !== 1 || settings[0]?.enabled !== false)
|
|
14133
|
+
return false;
|
|
14134
|
+
if (entry.path.startsWith(canonicalAgentPath(join16(home, ".codex", "plugins", "cache"), aliases) + sep4))
|
|
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;
|
|
14139
|
+
if (!entry.path.startsWith(canonicalAgentPath(join16(home, ".codex", "skills", ".system"), aliases) + sep4))
|
|
14140
|
+
return false;
|
|
14141
|
+
return Array.isArray(binding.disabledBuiltins) && binding.disabledBuiltins.some((item) => item.path === entry.path && item.hash === entry.hash);
|
|
14134
14142
|
};
|
|
14135
14143
|
if (!discovery || discovery.agent !== agent)
|
|
14136
14144
|
throw new Error("NATIVE_SKILL_DRIFT: native discovery coverage is missing; run skills hook install");
|
|
@@ -14147,7 +14155,7 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
14147
14155
|
throw new Error(`NATIVE_SKILL_DRIFT: ${error.message}`);
|
|
14148
14156
|
}
|
|
14149
14157
|
const inventory = inventoryNativeSkills(home, { includeVendor: true, guardHermes: agent === "hermes", agents: [agent], projectDirs: [...roots], agentRoots: discovery.roots.map((path) => ({ agent, path })), allowRootAliases: aliases.length > 0 });
|
|
14150
|
-
const unexpected = inventory.filter((entry) => visible(entry) && !entry.bridge && !
|
|
14158
|
+
const unexpected = inventory.filter((entry) => visible(entry) && !entry.bridge && !disabledVendorSkill(entry));
|
|
14151
14159
|
if (unexpected.length) {
|
|
14152
14160
|
const paths = unexpected.slice(0, 3).map((entry) => {
|
|
14153
14161
|
const escaped = JSON.stringify(entry.path).replace(/[\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
@@ -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: {
|
|
@@ -41996,12 +41996,20 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
41996
41996
|
const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join21(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
|
|
41997
41997
|
if (agent === "codex" && [...setting(expected), ...(codexConfig.skills?.config ?? []).filter((entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME)].some((entry) => entry.enabled === false))
|
|
41998
41998
|
throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
|
|
41999
|
-
const
|
|
42000
|
-
if (agent !== "codex" || !entry.vendor
|
|
41999
|
+
const disabledVendorSkill = (entry) => {
|
|
42000
|
+
if (agent !== "codex" || !entry.vendor)
|
|
42001
42001
|
return false;
|
|
42002
|
-
const matched = Array.isArray(binding.disabledBuiltins) && binding.disabledBuiltins.some((item) => item.path === entry.path && item.hash === entry.hash);
|
|
42003
42002
|
const settings = setting(entry.path);
|
|
42004
|
-
|
|
42003
|
+
if (settings.length !== 1 || settings[0]?.enabled !== false)
|
|
42004
|
+
return false;
|
|
42005
|
+
if (entry.path.startsWith(canonicalAgentPath(join21(home, ".codex", "plugins", "cache"), aliases) + sep4))
|
|
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;
|
|
42010
|
+
if (!entry.path.startsWith(canonicalAgentPath(join21(home, ".codex", "skills", ".system"), aliases) + sep4))
|
|
42011
|
+
return false;
|
|
42012
|
+
return Array.isArray(binding.disabledBuiltins) && binding.disabledBuiltins.some((item) => item.path === entry.path && item.hash === entry.hash);
|
|
42005
42013
|
};
|
|
42006
42014
|
if (!discovery || discovery.agent !== agent)
|
|
42007
42015
|
throw new Error("NATIVE_SKILL_DRIFT: native discovery coverage is missing; run skills hook install");
|
|
@@ -42018,7 +42026,7 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
42018
42026
|
throw new Error(`NATIVE_SKILL_DRIFT: ${error.message}`);
|
|
42019
42027
|
}
|
|
42020
42028
|
const inventory = inventoryNativeSkills(home, { includeVendor: true, guardHermes: agent === "hermes", agents: [agent], projectDirs: [...roots], agentRoots: discovery.roots.map((path) => ({ agent, path })), allowRootAliases: aliases.length > 0 });
|
|
42021
|
-
const unexpected = inventory.filter((entry) => visible(entry) && !entry.bridge && !
|
|
42029
|
+
const unexpected = inventory.filter((entry) => visible(entry) && !entry.bridge && !disabledVendorSkill(entry));
|
|
42022
42030
|
if (unexpected.length) {
|
|
42023
42031
|
const paths = unexpected.slice(0, 3).map((entry) => {
|
|
42024
42032
|
const escaped = JSON.stringify(entry.path).replace(/[\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
|