@hasna/skills 0.8.6 → 0.8.8
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 +16 -0
- package/bin/index.js +79 -19
- package/bin/mcp.js +47 -2
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/index.js +65 -3
- package/dist/sdk/index.js +65 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,6 +70,22 @@ skills hook install --agent all --selection-profile default --json
|
|
|
70
70
|
skills hook install --agent all --selection-profile default --apply --json
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
+
Native migration includes the current directory, every ancestor, and the global
|
|
74
|
+
agent directories. Use `--project /path/to/project` to include another project
|
|
75
|
+
and its ancestors. This matches the prompt hook's project discovery, including
|
|
76
|
+
skills inherited from a parent workspace. Preview the JSON inventory before
|
|
77
|
+
applying it; archived copies retain recovery receipts. A drift refusal reports
|
|
78
|
+
the affected paths without printing their skill documents.
|
|
79
|
+
|
|
80
|
+
Claude hook installation sets `syncClaudeAiSkills: false` in user settings to
|
|
81
|
+
stop [account skill synchronization](https://code.claude.com/docs/en/skills#skills-synced-from-claudeai)
|
|
82
|
+
from recreating native copies. The prompt hook checks that setting. Vendor
|
|
83
|
+
migration also inventories `~/.claude/plugins/synced/` and archives its skill
|
|
84
|
+
documents while preserving other plugin files. Whole-plugin synchronization
|
|
85
|
+
remains unchanged because synced plugins can also provide hooks, MCP servers,
|
|
86
|
+
and language servers. A later plugin download that restores a skill document
|
|
87
|
+
will trigger another drift refusal and require review.
|
|
88
|
+
|
|
73
89
|
Native hook invocations must use their installed adapter's selection profile.
|
|
74
90
|
An old client command or environment override naming another profile refuses
|
|
75
91
|
before synchronization or context loading. Review the hook installation and
|
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.8.
|
|
36871
|
+
version: "0.8.8",
|
|
36872
36872
|
description: "Skills library for AI coding agents",
|
|
36873
36873
|
type: "module",
|
|
36874
36874
|
bin: {
|
|
@@ -38212,6 +38212,35 @@ function isSafeRelativePath(value) {
|
|
|
38212
38212
|
const normalized = normalize(value).replace(/\\/g, "/");
|
|
38213
38213
|
return normalized !== ".." && !normalized.startsWith("../") && !normalized.includes("/../");
|
|
38214
38214
|
}
|
|
38215
|
+
function validateRuntimeEntry(skillPath, entrypoint, issues, warnings) {
|
|
38216
|
+
if (typeof entrypoint !== "string" || !isSafeRelativePath(entrypoint) || /^[a-zA-Z]:/.test(entrypoint) || entrypoint.includes("\\") || entrypoint.split("/").some((part) => !part || part === "." || part === "..")) {
|
|
38217
|
+
add2(issues, "skill.runtime_entrypoint_unsafe", "runtime.entrypoint must be a canonical relative file path inside the skill directory");
|
|
38218
|
+
return;
|
|
38219
|
+
}
|
|
38220
|
+
const parts = entrypoint.split("/");
|
|
38221
|
+
let current = skillPath;
|
|
38222
|
+
for (let index = 0;index < parts.length; index++) {
|
|
38223
|
+
current = join8(current, parts[index]);
|
|
38224
|
+
let info;
|
|
38225
|
+
try {
|
|
38226
|
+
info = lstatSync(current);
|
|
38227
|
+
} catch {
|
|
38228
|
+
add2(issues, "skill.runtime_entrypoint_missing", "The declared runtime.entrypoint is missing or inaccessible");
|
|
38229
|
+
return;
|
|
38230
|
+
}
|
|
38231
|
+
if (info.isSymbolicLink()) {
|
|
38232
|
+
add2(issues, "skill.runtime_entrypoint_symlink", "runtime.entrypoint must not traverse symbolic links");
|
|
38233
|
+
return;
|
|
38234
|
+
}
|
|
38235
|
+
if (index < parts.length - 1 ? !info.isDirectory() : !info.isFile()) {
|
|
38236
|
+
add2(issues, "skill.runtime_entrypoint_not_file", "runtime.entrypoint must resolve to a regular file");
|
|
38237
|
+
return;
|
|
38238
|
+
}
|
|
38239
|
+
if (index === parts.length - 1 && info.size < 50) {
|
|
38240
|
+
add2(warnings, "skill.runtime_entrypoint_minimal", `Source entry point is very small (${info.size}B)`);
|
|
38241
|
+
}
|
|
38242
|
+
}
|
|
38243
|
+
}
|
|
38215
38244
|
function isHostedPackageMetadata(pkg) {
|
|
38216
38245
|
return isHostedMetadataPackage(pkg);
|
|
38217
38246
|
}
|
|
@@ -38447,10 +38476,22 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
38447
38476
|
} else {
|
|
38448
38477
|
metadata.runtime = hostedMetadata ? "hosted" : "local";
|
|
38449
38478
|
const srcDir = join8(skillPath, "src");
|
|
38479
|
+
let declaredEntrypoint;
|
|
38480
|
+
const manifestPath = join8(skillPath, "skill.json");
|
|
38481
|
+
if (!hostedMetadata && existsSync8(manifestPath)) {
|
|
38482
|
+
try {
|
|
38483
|
+
const manifest = asRecord(readJsonFile(manifestPath));
|
|
38484
|
+
declaredEntrypoint = asRecord(manifest?.runtime)?.entrypoint;
|
|
38485
|
+
} catch {
|
|
38486
|
+
add2(issues, "skill.manifest_invalid", "skill.json must contain valid JSON");
|
|
38487
|
+
}
|
|
38488
|
+
}
|
|
38450
38489
|
if (hostedMetadata) {
|
|
38451
38490
|
if (existsSync8(srcDir)) {
|
|
38452
38491
|
add2(issues, "skill.hosted_source_forbidden", "Hosted metadata skills must not include local implementation source");
|
|
38453
38492
|
}
|
|
38493
|
+
} else if (declaredEntrypoint !== undefined && declaredEntrypoint !== null) {
|
|
38494
|
+
validateRuntimeEntry(skillPath, declaredEntrypoint, issues, warnings);
|
|
38454
38495
|
} else if (!existsSync8(srcDir)) {
|
|
38455
38496
|
add2(issues, "skill.src_missing", "Missing src/ directory");
|
|
38456
38497
|
} else if (!existsSync8(join8(srcDir, "index.ts")) && !existsSync8(join8(srcDir, "index.js"))) {
|
|
@@ -40028,7 +40069,11 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
40028
40069
|
"package.missing",
|
|
40029
40070
|
"package.bin_missing",
|
|
40030
40071
|
"skill.src_missing",
|
|
40031
|
-
"skill.src_index_missing"
|
|
40072
|
+
"skill.src_index_missing",
|
|
40073
|
+
"skill.runtime_entrypoint_unsafe",
|
|
40074
|
+
"skill.runtime_entrypoint_missing",
|
|
40075
|
+
"skill.runtime_entrypoint_symlink",
|
|
40076
|
+
"skill.runtime_entrypoint_not_file"
|
|
40032
40077
|
]);
|
|
40033
40078
|
for (let i = issues.length - 1;i >= 0; i--) {
|
|
40034
40079
|
if (executableOnlyCodes.has(issues[i].code))
|
|
@@ -51183,11 +51228,26 @@ function treeHash(root) {
|
|
|
51183
51228
|
visit2(root);
|
|
51184
51229
|
return hash2.digest("hex");
|
|
51185
51230
|
}
|
|
51231
|
+
function projectAncestorDirectories(projects) {
|
|
51232
|
+
const directories = new Set;
|
|
51233
|
+
for (const project of projects) {
|
|
51234
|
+
for (let path = resolve10(project), depth = 0;; depth++) {
|
|
51235
|
+
if (depth >= 100)
|
|
51236
|
+
throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
|
|
51237
|
+
directories.add(path);
|
|
51238
|
+
const parent = dirname10(path);
|
|
51239
|
+
if (parent === path)
|
|
51240
|
+
break;
|
|
51241
|
+
path = parent;
|
|
51242
|
+
}
|
|
51243
|
+
}
|
|
51244
|
+
return [...directories];
|
|
51245
|
+
}
|
|
51186
51246
|
function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
51187
51247
|
const aliases = rootAliases(home, options.allowRootAliases);
|
|
51188
51248
|
const roots = ROOTS.map(([agent, path]) => [agent, canonicalAgentPath(join21(home, path), aliases)]);
|
|
51189
51249
|
const bridgePaths = Object.values(AGENT_ADAPTERS).map((adapter) => canonicalAgentPath(join21(home, adapter.root, CLI_BRIDGE_NAME), aliases));
|
|
51190
|
-
for (const project of
|
|
51250
|
+
for (const project of projectAncestorDirectories([...options.projectDirs ?? [], ...options.projectDir ? [options.projectDir] : []])) {
|
|
51191
51251
|
for (const [agent, path] of ROOTS)
|
|
51192
51252
|
roots.push([agent, canonicalAgentPath(join21(project, path), aliases)]);
|
|
51193
51253
|
}
|
|
@@ -51290,6 +51350,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
51290
51350
|
if (options.includeVendor) {
|
|
51291
51351
|
for (const agent of ["codex", "claude"])
|
|
51292
51352
|
scanRoot(agent, canonicalAgentPath(join21(home, `.${agent}`, "plugins", "cache"), aliases), true, true);
|
|
51353
|
+
scanRoot("claude", canonicalAgentPath(join21(home, ".claude", "plugins", "synced"), aliases), true);
|
|
51293
51354
|
scanRoot("gemini", join21(home, ".gemini", "extensions"), true);
|
|
51294
51355
|
}
|
|
51295
51356
|
const configured = options.configured ? INTEGRATION_AGENTS.flatMap((agent) => resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, canonical: (path) => canonicalAgentPath(path, aliases) }).roots.map((path) => ({ agent, path }))) : [];
|
|
@@ -51354,6 +51415,7 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
|
|
|
51354
51415
|
config.permissions.deny = nativeReady ? denied.filter((rule) => rule !== "Skill") : [...new Set([...denied, "Skill"])];
|
|
51355
51416
|
config.permissions.allow = nativeReady ? [...new Set([...allowed, `Skill(${CLI_BRIDGE_NAME})`])] : allowed;
|
|
51356
51417
|
config.disableBundledSkills = true;
|
|
51418
|
+
config.syncClaudeAiSkills = false;
|
|
51357
51419
|
}
|
|
51358
51420
|
if (agent === "gemini") {
|
|
51359
51421
|
config.skills ??= {};
|
|
@@ -51766,18 +51828,7 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
51766
51828
|
const expected = canonicalAgentPath(join21(home, AGENT_ADAPTERS[agent].root, CLI_BRIDGE_NAME), aliases);
|
|
51767
51829
|
if (!isOwnedCliBridge(expected, [expected]))
|
|
51768
51830
|
throw new Error("NATIVE_SKILL_DRIFT: the native Skills bridge is missing or modified; repair it before continuing");
|
|
51769
|
-
const roots =
|
|
51770
|
-
for (const project of [options.projectDir ?? process.cwd(), ...options.projectDirs ?? [], ...agent === "hermes" && process.env.TERMINAL_CWD ? [process.cwd()] : []]) {
|
|
51771
|
-
for (let path = resolve10(project), depth = 0;; depth++) {
|
|
51772
|
-
if (depth >= 100)
|
|
51773
|
-
throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
|
|
51774
|
-
roots.add(path);
|
|
51775
|
-
const parent = dirname10(path);
|
|
51776
|
-
if (parent === path)
|
|
51777
|
-
break;
|
|
51778
|
-
path = parent;
|
|
51779
|
-
}
|
|
51780
|
-
}
|
|
51831
|
+
const roots = projectAncestorDirectories([options.projectDir ?? process.cwd(), ...options.projectDirs ?? [], ...agent === "hermes" && process.env.TERMINAL_CWD ? [process.cwd()] : []]);
|
|
51781
51832
|
const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes"].includes(agent) && entry.path.includes(`${sep5}.agents${sep5}skills${sep5}`) || agent === "opencode" && entry.agent === "claude";
|
|
51782
51833
|
assertProjectDiscovery(agent, [...roots], home, (path) => canonicalAgentPath(path, aliases));
|
|
51783
51834
|
const configPath = canonicalAgentPath(join21(home, AGENT_ADAPTERS[agent].config), aliases), config = agent === "hermes" ? parseHermesConfig(readOptional(configPath)) : jsonObject(readOptional(configPath), configPath);
|
|
@@ -51804,6 +51855,8 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
51804
51855
|
}
|
|
51805
51856
|
if (agent === "claude" && (config.disableBundledSkills !== true || config.disableAllHooks === true || !config.permissions?.allow?.includes(`Skill(${CLI_BRIDGE_NAME})`) || config.permissions?.deny?.includes("Skill")))
|
|
51806
51857
|
throw new Error("NATIVE_SKILL_DRIFT: Claude bridge or bundled-skill protection changed; retire copies and run skills hook install");
|
|
51858
|
+
if (agent === "claude" && config.syncClaudeAiSkills !== false)
|
|
51859
|
+
throw new Error("NATIVE_SKILL_DRIFT: Claude account skill synchronization is not disabled (syncClaudeAiSkills); run skills hook install to prevent native copies from returning");
|
|
51807
51860
|
if (agent === "gemini" && (config.hooksConfig?.enabled === false || config.skills?.enabled !== true || !["antigravity-support", "skill-creator"].every((name) => config.skills?.disabled?.includes(name)) || config.skills?.disabled?.includes(CLI_BRIDGE_NAME)))
|
|
51808
51861
|
throw new Error("NATIVE_SKILL_DRIFT: Gemini bridge or bundled-skill protection changed; run skills hook install");
|
|
51809
51862
|
}
|
|
@@ -51835,8 +51888,15 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
51835
51888
|
throw new Error(`NATIVE_SKILL_DRIFT: ${error.message}`);
|
|
51836
51889
|
}
|
|
51837
51890
|
const inventory = inventoryNativeSkills(home, { includeVendor: true, guardHermes: agent === "hermes", projectDirs: [...roots], agentRoots: discovery.roots.map((path) => ({ agent, path })), allowRootAliases: aliases.length > 0 });
|
|
51838
|
-
|
|
51839
|
-
|
|
51891
|
+
const unexpected = inventory.filter((entry) => visible(entry) && !entry.bridge && !disabledBuiltin(entry));
|
|
51892
|
+
if (unexpected.length) {
|
|
51893
|
+
const paths = unexpected.slice(0, 3).map((entry) => {
|
|
51894
|
+
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")}`);
|
|
51895
|
+
return escaped.length > 768 ? `${escaped.slice(0, 384)}...${escaped.slice(-381)}` : escaped;
|
|
51896
|
+
});
|
|
51897
|
+
const remaining = unexpected.length - paths.length;
|
|
51898
|
+
throw new Error(`NATIVE_SKILL_DRIFT: ${unexpected.length} unexpected native skill copies were found: ${paths.join(", ")}${remaining ? `; ${remaining} more` : ""}. Review skills migrate native --project <working-directory> --include-unmanaged --include-vendor --json before continuing; it inventories that directory and its ancestors. Use --apply after reviewing the archive plan.`);
|
|
51899
|
+
}
|
|
51840
51900
|
recheckRootAliases(aliases);
|
|
51841
51901
|
}
|
|
51842
51902
|
function normalizeAgentHookPrompt(agent, event, prompt) {
|
|
@@ -105564,10 +105624,10 @@ function registerAgentIntegration(parent) {
|
|
|
105564
105624
|
}
|
|
105565
105625
|
});
|
|
105566
105626
|
const migrate = parent.command("migrate").description("Preserve and retire native agent skill copies");
|
|
105567
|
-
migrate.command("native").option("--project <directory>", "Also inventory
|
|
105627
|
+
migrate.command("native").option("--project <directory>", "Also inventory a project and its ancestors (the current directory and its ancestors are always included)").option("--include-unmanaged", "Archive user-authored skills as well as Skills-managed copies", false).option("--include-vendor", "Retire vendor SKILL.md discovery files while preserving plugin scripts and assets", false).option("--discovery-inputs <file>", "Advanced reviewed active plugin roots and source hashes").option("--allow-root-aliases", "Allow home .claude/.codex aliases to existing directories within this home", false).option("--apply", "Move selected skills to private archives outside agent discovery roots", false).option("--json", "Output inventory and archive receipt as JSON", false).description("Inventory native skill copies; preserve complete directories before retiring them").action(async (options) => {
|
|
105568
105628
|
try {
|
|
105569
105629
|
const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync37(options.discoveryInputs, "utf8")) : undefined;
|
|
105570
|
-
const inventory = inventoryNativeSkills(undefined, {
|
|
105630
|
+
const inventory = inventoryNativeSkills(undefined, { projectDirs: [process.cwd(), ...options.project ? [options.project] : []], includeVendor: options.includeVendor, configured: options.includeVendor, discoveryInputs, allowRootAliases: options.allowRootAliases });
|
|
105571
105631
|
const result2 = options.apply ? archiveNativeSkills(inventory, { includeUnmanaged: options.includeUnmanaged, includeVendor: options.includeVendor, allowRootAliases: options.allowRootAliases }) : { entries: [] };
|
|
105572
105632
|
if (options.json)
|
|
105573
105633
|
await writeCliOutput(JSON.stringify({ applied: options.apply, inventory, ...result2 }));
|
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.8.
|
|
20022
|
+
version: "0.8.8",
|
|
20023
20023
|
description: "Skills library for AI coding agents",
|
|
20024
20024
|
type: "module",
|
|
20025
20025
|
bin: {
|
|
@@ -36478,6 +36478,35 @@ function isSafeRelativePath(value) {
|
|
|
36478
36478
|
const normalized = normalize(value).replace(/\\/g, "/");
|
|
36479
36479
|
return normalized !== ".." && !normalized.startsWith("../") && !normalized.includes("/../");
|
|
36480
36480
|
}
|
|
36481
|
+
function validateRuntimeEntry(skillPath, entrypoint, issues, warnings) {
|
|
36482
|
+
if (typeof entrypoint !== "string" || !isSafeRelativePath(entrypoint) || /^[a-zA-Z]:/.test(entrypoint) || entrypoint.includes("\\") || entrypoint.split("/").some((part) => !part || part === "." || part === "..")) {
|
|
36483
|
+
add(issues, "skill.runtime_entrypoint_unsafe", "runtime.entrypoint must be a canonical relative file path inside the skill directory");
|
|
36484
|
+
return;
|
|
36485
|
+
}
|
|
36486
|
+
const parts = entrypoint.split("/");
|
|
36487
|
+
let current = skillPath;
|
|
36488
|
+
for (let index = 0;index < parts.length; index++) {
|
|
36489
|
+
current = join7(current, parts[index]);
|
|
36490
|
+
let info;
|
|
36491
|
+
try {
|
|
36492
|
+
info = lstatSync3(current);
|
|
36493
|
+
} catch {
|
|
36494
|
+
add(issues, "skill.runtime_entrypoint_missing", "The declared runtime.entrypoint is missing or inaccessible");
|
|
36495
|
+
return;
|
|
36496
|
+
}
|
|
36497
|
+
if (info.isSymbolicLink()) {
|
|
36498
|
+
add(issues, "skill.runtime_entrypoint_symlink", "runtime.entrypoint must not traverse symbolic links");
|
|
36499
|
+
return;
|
|
36500
|
+
}
|
|
36501
|
+
if (index < parts.length - 1 ? !info.isDirectory() : !info.isFile()) {
|
|
36502
|
+
add(issues, "skill.runtime_entrypoint_not_file", "runtime.entrypoint must resolve to a regular file");
|
|
36503
|
+
return;
|
|
36504
|
+
}
|
|
36505
|
+
if (index === parts.length - 1 && info.size < 50) {
|
|
36506
|
+
add(warnings, "skill.runtime_entrypoint_minimal", `Source entry point is very small (${info.size}B)`);
|
|
36507
|
+
}
|
|
36508
|
+
}
|
|
36509
|
+
}
|
|
36481
36510
|
function isHostedPackageMetadata(pkg) {
|
|
36482
36511
|
return isHostedMetadataPackage(pkg);
|
|
36483
36512
|
}
|
|
@@ -36713,10 +36742,22 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
36713
36742
|
} else {
|
|
36714
36743
|
metadata.runtime = hostedMetadata ? "hosted" : "local";
|
|
36715
36744
|
const srcDir = join7(skillPath, "src");
|
|
36745
|
+
let declaredEntrypoint;
|
|
36746
|
+
const manifestPath = join7(skillPath, "skill.json");
|
|
36747
|
+
if (!hostedMetadata && existsSync4(manifestPath)) {
|
|
36748
|
+
try {
|
|
36749
|
+
const manifest = asRecord(readJsonFile(manifestPath));
|
|
36750
|
+
declaredEntrypoint = asRecord(manifest?.runtime)?.entrypoint;
|
|
36751
|
+
} catch {
|
|
36752
|
+
add(issues, "skill.manifest_invalid", "skill.json must contain valid JSON");
|
|
36753
|
+
}
|
|
36754
|
+
}
|
|
36716
36755
|
if (hostedMetadata) {
|
|
36717
36756
|
if (existsSync4(srcDir)) {
|
|
36718
36757
|
add(issues, "skill.hosted_source_forbidden", "Hosted metadata skills must not include local implementation source");
|
|
36719
36758
|
}
|
|
36759
|
+
} else if (declaredEntrypoint !== undefined && declaredEntrypoint !== null) {
|
|
36760
|
+
validateRuntimeEntry(skillPath, declaredEntrypoint, issues, warnings);
|
|
36720
36761
|
} else if (!existsSync4(srcDir)) {
|
|
36721
36762
|
add(issues, "skill.src_missing", "Missing src/ directory");
|
|
36722
36763
|
} else if (!existsSync4(join7(srcDir, "index.ts")) && !existsSync4(join7(srcDir, "index.js"))) {
|
|
@@ -38176,7 +38217,11 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
38176
38217
|
"package.missing",
|
|
38177
38218
|
"package.bin_missing",
|
|
38178
38219
|
"skill.src_missing",
|
|
38179
|
-
"skill.src_index_missing"
|
|
38220
|
+
"skill.src_index_missing",
|
|
38221
|
+
"skill.runtime_entrypoint_unsafe",
|
|
38222
|
+
"skill.runtime_entrypoint_missing",
|
|
38223
|
+
"skill.runtime_entrypoint_symlink",
|
|
38224
|
+
"skill.runtime_entrypoint_not_file"
|
|
38180
38225
|
]);
|
|
38181
38226
|
for (let i = issues.length - 1;i >= 0; i--) {
|
|
38182
38227
|
if (executableOnlyCodes.has(issues[i].code))
|
package/bin/migrate.js
CHANGED
package/bin/server.js
CHANGED
|
@@ -83749,7 +83749,7 @@ var require_protocols9 = __commonJS((exports) => {
|
|
|
83749
83749
|
// package.json
|
|
83750
83750
|
var package_default = {
|
|
83751
83751
|
name: "@hasna/skills",
|
|
83752
|
-
version: "0.8.
|
|
83752
|
+
version: "0.8.8",
|
|
83753
83753
|
description: "Skills library for AI coding agents",
|
|
83754
83754
|
type: "module",
|
|
83755
83755
|
bin: {
|
package/bin/worker.js
CHANGED
|
@@ -76168,7 +76168,7 @@ import { randomUUID as randomUUID6 } from "crypto";
|
|
|
76168
76168
|
// package.json
|
|
76169
76169
|
var package_default = {
|
|
76170
76170
|
name: "@hasna/skills",
|
|
76171
|
-
version: "0.8.
|
|
76171
|
+
version: "0.8.8",
|
|
76172
76172
|
description: "Skills library for AI coding agents",
|
|
76173
76173
|
type: "module",
|
|
76174
76174
|
bin: {
|
package/dist/index.js
CHANGED
|
@@ -7376,6 +7376,35 @@ function isSafeRelativePath(value) {
|
|
|
7376
7376
|
const normalized = normalize(value).replace(/\\/g, "/");
|
|
7377
7377
|
return normalized !== ".." && !normalized.startsWith("../") && !normalized.includes("/../");
|
|
7378
7378
|
}
|
|
7379
|
+
function validateRuntimeEntry(skillPath, entrypoint, issues, warnings) {
|
|
7380
|
+
if (typeof entrypoint !== "string" || !isSafeRelativePath(entrypoint) || /^[a-zA-Z]:/.test(entrypoint) || entrypoint.includes("\\") || entrypoint.split("/").some((part) => !part || part === "." || part === "..")) {
|
|
7381
|
+
add(issues, "skill.runtime_entrypoint_unsafe", "runtime.entrypoint must be a canonical relative file path inside the skill directory");
|
|
7382
|
+
return;
|
|
7383
|
+
}
|
|
7384
|
+
const parts = entrypoint.split("/");
|
|
7385
|
+
let current = skillPath;
|
|
7386
|
+
for (let index = 0;index < parts.length; index++) {
|
|
7387
|
+
current = join4(current, parts[index]);
|
|
7388
|
+
let info;
|
|
7389
|
+
try {
|
|
7390
|
+
info = lstatSync(current);
|
|
7391
|
+
} catch {
|
|
7392
|
+
add(issues, "skill.runtime_entrypoint_missing", "The declared runtime.entrypoint is missing or inaccessible");
|
|
7393
|
+
return;
|
|
7394
|
+
}
|
|
7395
|
+
if (info.isSymbolicLink()) {
|
|
7396
|
+
add(issues, "skill.runtime_entrypoint_symlink", "runtime.entrypoint must not traverse symbolic links");
|
|
7397
|
+
return;
|
|
7398
|
+
}
|
|
7399
|
+
if (index < parts.length - 1 ? !info.isDirectory() : !info.isFile()) {
|
|
7400
|
+
add(issues, "skill.runtime_entrypoint_not_file", "runtime.entrypoint must resolve to a regular file");
|
|
7401
|
+
return;
|
|
7402
|
+
}
|
|
7403
|
+
if (index === parts.length - 1 && info.size < 50) {
|
|
7404
|
+
add(warnings, "skill.runtime_entrypoint_minimal", `Source entry point is very small (${info.size}B)`);
|
|
7405
|
+
}
|
|
7406
|
+
}
|
|
7407
|
+
}
|
|
7379
7408
|
function isHostedPackageMetadata(pkg) {
|
|
7380
7409
|
return isHostedMetadataPackage(pkg);
|
|
7381
7410
|
}
|
|
@@ -7611,10 +7640,22 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
7611
7640
|
} else {
|
|
7612
7641
|
metadata.runtime = hostedMetadata ? "hosted" : "local";
|
|
7613
7642
|
const srcDir = join4(skillPath, "src");
|
|
7643
|
+
let declaredEntrypoint;
|
|
7644
|
+
const manifestPath = join4(skillPath, "skill.json");
|
|
7645
|
+
if (!hostedMetadata && existsSync4(manifestPath)) {
|
|
7646
|
+
try {
|
|
7647
|
+
const manifest = asRecord(readJsonFile(manifestPath));
|
|
7648
|
+
declaredEntrypoint = asRecord(manifest?.runtime)?.entrypoint;
|
|
7649
|
+
} catch {
|
|
7650
|
+
add(issues, "skill.manifest_invalid", "skill.json must contain valid JSON");
|
|
7651
|
+
}
|
|
7652
|
+
}
|
|
7614
7653
|
if (hostedMetadata) {
|
|
7615
7654
|
if (existsSync4(srcDir)) {
|
|
7616
7655
|
add(issues, "skill.hosted_source_forbidden", "Hosted metadata skills must not include local implementation source");
|
|
7617
7656
|
}
|
|
7657
|
+
} else if (declaredEntrypoint !== undefined && declaredEntrypoint !== null) {
|
|
7658
|
+
validateRuntimeEntry(skillPath, declaredEntrypoint, issues, warnings);
|
|
7618
7659
|
} else if (!existsSync4(srcDir)) {
|
|
7619
7660
|
add(issues, "skill.src_missing", "Missing src/ directory");
|
|
7620
7661
|
} else if (!existsSync4(join4(srcDir, "index.ts")) && !existsSync4(join4(srcDir, "index.js"))) {
|
|
@@ -9146,7 +9187,11 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
9146
9187
|
"package.missing",
|
|
9147
9188
|
"package.bin_missing",
|
|
9148
9189
|
"skill.src_missing",
|
|
9149
|
-
"skill.src_index_missing"
|
|
9190
|
+
"skill.src_index_missing",
|
|
9191
|
+
"skill.runtime_entrypoint_unsafe",
|
|
9192
|
+
"skill.runtime_entrypoint_missing",
|
|
9193
|
+
"skill.runtime_entrypoint_symlink",
|
|
9194
|
+
"skill.runtime_entrypoint_not_file"
|
|
9150
9195
|
]);
|
|
9151
9196
|
for (let i = issues.length - 1;i >= 0; i--) {
|
|
9152
9197
|
if (executableOnlyCodes.has(issues[i].code))
|
|
@@ -11648,11 +11693,26 @@ function treeHash(root) {
|
|
|
11648
11693
|
visit2(root);
|
|
11649
11694
|
return hash2.digest("hex");
|
|
11650
11695
|
}
|
|
11696
|
+
function projectAncestorDirectories(projects) {
|
|
11697
|
+
const directories = new Set;
|
|
11698
|
+
for (const project of projects) {
|
|
11699
|
+
for (let path = resolve9(project), depth = 0;; depth++) {
|
|
11700
|
+
if (depth >= 100)
|
|
11701
|
+
throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
|
|
11702
|
+
directories.add(path);
|
|
11703
|
+
const parent = dirname10(path);
|
|
11704
|
+
if (parent === path)
|
|
11705
|
+
break;
|
|
11706
|
+
path = parent;
|
|
11707
|
+
}
|
|
11708
|
+
}
|
|
11709
|
+
return [...directories];
|
|
11710
|
+
}
|
|
11651
11711
|
function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
11652
11712
|
const aliases = rootAliases(home, options.allowRootAliases);
|
|
11653
11713
|
const roots = ROOTS.map(([agent, path]) => [agent, canonicalAgentPath(join17(home, path), aliases)]);
|
|
11654
11714
|
const bridgePaths = Object.values(AGENT_ADAPTERS).map((adapter) => canonicalAgentPath(join17(home, adapter.root, CLI_BRIDGE_NAME), aliases));
|
|
11655
|
-
for (const project of
|
|
11715
|
+
for (const project of projectAncestorDirectories([...options.projectDirs ?? [], ...options.projectDir ? [options.projectDir] : []])) {
|
|
11656
11716
|
for (const [agent, path] of ROOTS)
|
|
11657
11717
|
roots.push([agent, canonicalAgentPath(join17(project, path), aliases)]);
|
|
11658
11718
|
}
|
|
@@ -11755,6 +11815,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
11755
11815
|
if (options.includeVendor) {
|
|
11756
11816
|
for (const agent of ["codex", "claude"])
|
|
11757
11817
|
scanRoot(agent, canonicalAgentPath(join17(home, `.${agent}`, "plugins", "cache"), aliases), true, true);
|
|
11818
|
+
scanRoot("claude", canonicalAgentPath(join17(home, ".claude", "plugins", "synced"), aliases), true);
|
|
11758
11819
|
scanRoot("gemini", join17(home, ".gemini", "extensions"), true);
|
|
11759
11820
|
}
|
|
11760
11821
|
const configured = options.configured ? INTEGRATION_AGENTS.flatMap((agent) => resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, canonical: (path) => canonicalAgentPath(path, aliases) }).roots.map((path) => ({ agent, path }))) : [];
|
|
@@ -11819,6 +11880,7 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
|
|
|
11819
11880
|
config.permissions.deny = nativeReady ? denied.filter((rule) => rule !== "Skill") : [...new Set([...denied, "Skill"])];
|
|
11820
11881
|
config.permissions.allow = nativeReady ? [...new Set([...allowed, `Skill(${CLI_BRIDGE_NAME})`])] : allowed;
|
|
11821
11882
|
config.disableBundledSkills = true;
|
|
11883
|
+
config.syncClaudeAiSkills = false;
|
|
11822
11884
|
}
|
|
11823
11885
|
if (agent === "gemini") {
|
|
11824
11886
|
config.skills ??= {};
|
|
@@ -37660,7 +37722,7 @@ import { dirname as dirname14, relative as relative7 } from "path";
|
|
|
37660
37722
|
// package.json
|
|
37661
37723
|
var package_default = {
|
|
37662
37724
|
name: "@hasna/skills",
|
|
37663
|
-
version: "0.8.
|
|
37725
|
+
version: "0.8.8",
|
|
37664
37726
|
description: "Skills library for AI coding agents",
|
|
37665
37727
|
type: "module",
|
|
37666
37728
|
bin: {
|
package/dist/sdk/index.js
CHANGED
|
@@ -96141,7 +96141,7 @@ async function resolveExecutionGrant(input) {
|
|
|
96141
96141
|
// package.json
|
|
96142
96142
|
var package_default = {
|
|
96143
96143
|
name: "@hasna/skills",
|
|
96144
|
-
version: "0.8.
|
|
96144
|
+
version: "0.8.8",
|
|
96145
96145
|
description: "Skills library for AI coding agents",
|
|
96146
96146
|
type: "module",
|
|
96147
96147
|
bin: {
|
|
@@ -97782,11 +97782,26 @@ function treeHash(root) {
|
|
|
97782
97782
|
visit2(root);
|
|
97783
97783
|
return hash2.digest("hex");
|
|
97784
97784
|
}
|
|
97785
|
+
function projectAncestorDirectories(projects) {
|
|
97786
|
+
const directories = new Set;
|
|
97787
|
+
for (const project of projects) {
|
|
97788
|
+
for (let path = resolve8(project), depth = 0;; depth++) {
|
|
97789
|
+
if (depth >= 100)
|
|
97790
|
+
throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
|
|
97791
|
+
directories.add(path);
|
|
97792
|
+
const parent = dirname9(path);
|
|
97793
|
+
if (parent === path)
|
|
97794
|
+
break;
|
|
97795
|
+
path = parent;
|
|
97796
|
+
}
|
|
97797
|
+
}
|
|
97798
|
+
return [...directories];
|
|
97799
|
+
}
|
|
97785
97800
|
function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
97786
97801
|
const aliases = rootAliases(home, options.allowRootAliases);
|
|
97787
97802
|
const roots = ROOTS.map(([agent, path]) => [agent, canonicalAgentPath(join16(home, path), aliases)]);
|
|
97788
97803
|
const bridgePaths = Object.values(AGENT_ADAPTERS).map((adapter) => canonicalAgentPath(join16(home, adapter.root, CLI_BRIDGE_NAME), aliases));
|
|
97789
|
-
for (const project of
|
|
97804
|
+
for (const project of projectAncestorDirectories([...options.projectDirs ?? [], ...options.projectDir ? [options.projectDir] : []])) {
|
|
97790
97805
|
for (const [agent, path] of ROOTS)
|
|
97791
97806
|
roots.push([agent, canonicalAgentPath(join16(project, path), aliases)]);
|
|
97792
97807
|
}
|
|
@@ -97889,6 +97904,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
97889
97904
|
if (options.includeVendor) {
|
|
97890
97905
|
for (const agent of ["codex", "claude"])
|
|
97891
97906
|
scanRoot(agent, canonicalAgentPath(join16(home, `.${agent}`, "plugins", "cache"), aliases), true, true);
|
|
97907
|
+
scanRoot("claude", canonicalAgentPath(join16(home, ".claude", "plugins", "synced"), aliases), true);
|
|
97892
97908
|
scanRoot("gemini", join16(home, ".gemini", "extensions"), true);
|
|
97893
97909
|
}
|
|
97894
97910
|
const configured = options.configured ? INTEGRATION_AGENTS.flatMap((agent) => resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, canonical: (path) => canonicalAgentPath(path, aliases) }).roots.map((path) => ({ agent, path }))) : [];
|
|
@@ -97953,6 +97969,7 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
|
|
|
97953
97969
|
config.permissions.deny = nativeReady ? denied.filter((rule) => rule !== "Skill") : [...new Set([...denied, "Skill"])];
|
|
97954
97970
|
config.permissions.allow = nativeReady ? [...new Set([...allowed, `Skill(${CLI_BRIDGE_NAME})`])] : allowed;
|
|
97955
97971
|
config.disableBundledSkills = true;
|
|
97972
|
+
config.syncClaudeAiSkills = false;
|
|
97956
97973
|
}
|
|
97957
97974
|
if (agent === "gemini") {
|
|
97958
97975
|
config.skills ??= {};
|
|
@@ -98480,6 +98497,35 @@ function isSafeRelativePath(value) {
|
|
|
98480
98497
|
const normalized = normalize(value).replace(/\\/g, "/");
|
|
98481
98498
|
return normalized !== ".." && !normalized.startsWith("../") && !normalized.includes("/../");
|
|
98482
98499
|
}
|
|
98500
|
+
function validateRuntimeEntry(skillPath, entrypoint, issues, warnings) {
|
|
98501
|
+
if (typeof entrypoint !== "string" || !isSafeRelativePath(entrypoint) || /^[a-zA-Z]:/.test(entrypoint) || entrypoint.includes("\\") || entrypoint.split("/").some((part) => !part || part === "." || part === "..")) {
|
|
98502
|
+
add(issues, "skill.runtime_entrypoint_unsafe", "runtime.entrypoint must be a canonical relative file path inside the skill directory");
|
|
98503
|
+
return;
|
|
98504
|
+
}
|
|
98505
|
+
const parts = entrypoint.split("/");
|
|
98506
|
+
let current = skillPath;
|
|
98507
|
+
for (let index = 0;index < parts.length; index++) {
|
|
98508
|
+
current = join18(current, parts[index]);
|
|
98509
|
+
let info;
|
|
98510
|
+
try {
|
|
98511
|
+
info = lstatSync13(current);
|
|
98512
|
+
} catch {
|
|
98513
|
+
add(issues, "skill.runtime_entrypoint_missing", "The declared runtime.entrypoint is missing or inaccessible");
|
|
98514
|
+
return;
|
|
98515
|
+
}
|
|
98516
|
+
if (info.isSymbolicLink()) {
|
|
98517
|
+
add(issues, "skill.runtime_entrypoint_symlink", "runtime.entrypoint must not traverse symbolic links");
|
|
98518
|
+
return;
|
|
98519
|
+
}
|
|
98520
|
+
if (index < parts.length - 1 ? !info.isDirectory() : !info.isFile()) {
|
|
98521
|
+
add(issues, "skill.runtime_entrypoint_not_file", "runtime.entrypoint must resolve to a regular file");
|
|
98522
|
+
return;
|
|
98523
|
+
}
|
|
98524
|
+
if (index === parts.length - 1 && info.size < 50) {
|
|
98525
|
+
add(warnings, "skill.runtime_entrypoint_minimal", `Source entry point is very small (${info.size}B)`);
|
|
98526
|
+
}
|
|
98527
|
+
}
|
|
98528
|
+
}
|
|
98483
98529
|
function isHostedPackageMetadata(pkg) {
|
|
98484
98530
|
return isHostedMetadataPackage(pkg);
|
|
98485
98531
|
}
|
|
@@ -98715,10 +98761,22 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
98715
98761
|
} else {
|
|
98716
98762
|
metadata.runtime = hostedMetadata ? "hosted" : "local";
|
|
98717
98763
|
const srcDir = join18(skillPath, "src");
|
|
98764
|
+
let declaredEntrypoint;
|
|
98765
|
+
const manifestPath = join18(skillPath, "skill.json");
|
|
98766
|
+
if (!hostedMetadata && existsSync6(manifestPath)) {
|
|
98767
|
+
try {
|
|
98768
|
+
const manifest = asRecord(readJsonFile(manifestPath));
|
|
98769
|
+
declaredEntrypoint = asRecord(manifest?.runtime)?.entrypoint;
|
|
98770
|
+
} catch {
|
|
98771
|
+
add(issues, "skill.manifest_invalid", "skill.json must contain valid JSON");
|
|
98772
|
+
}
|
|
98773
|
+
}
|
|
98718
98774
|
if (hostedMetadata) {
|
|
98719
98775
|
if (existsSync6(srcDir)) {
|
|
98720
98776
|
add(issues, "skill.hosted_source_forbidden", "Hosted metadata skills must not include local implementation source");
|
|
98721
98777
|
}
|
|
98778
|
+
} else if (declaredEntrypoint !== undefined && declaredEntrypoint !== null) {
|
|
98779
|
+
validateRuntimeEntry(skillPath, declaredEntrypoint, issues, warnings);
|
|
98722
98780
|
} else if (!existsSync6(srcDir)) {
|
|
98723
98781
|
add(issues, "skill.src_missing", "Missing src/ directory");
|
|
98724
98782
|
} else if (!existsSync6(join18(srcDir, "index.ts")) && !existsSync6(join18(srcDir, "index.js"))) {
|
|
@@ -100216,7 +100274,11 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
100216
100274
|
"package.missing",
|
|
100217
100275
|
"package.bin_missing",
|
|
100218
100276
|
"skill.src_missing",
|
|
100219
|
-
"skill.src_index_missing"
|
|
100277
|
+
"skill.src_index_missing",
|
|
100278
|
+
"skill.runtime_entrypoint_unsafe",
|
|
100279
|
+
"skill.runtime_entrypoint_missing",
|
|
100280
|
+
"skill.runtime_entrypoint_symlink",
|
|
100281
|
+
"skill.runtime_entrypoint_not_file"
|
|
100220
100282
|
]);
|
|
100221
100283
|
for (let i = issues.length - 1;i >= 0; i--) {
|
|
100222
100284
|
if (executableOnlyCodes.has(issues[i].code))
|