@hasna/skills 0.1.64 → 0.1.67
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 +33 -16
- package/bin/index.js +710 -511
- package/bin/mcp.js +285 -239
- package/bin/server.js +791 -232
- package/bin/worker.js +178 -148
- package/dist/index.d.ts +1 -1
- package/dist/index.js +421 -321
- package/dist/lib/agent-sync.d.ts +2 -2
- package/dist/lib/native-storage.d.ts +28 -0
- package/dist/lib/portable-skills.d.ts +14 -0
- package/dist/lib/registry-types.d.ts +9 -0
- package/dist/lib/run-routing.d.ts +60 -0
- package/dist/mcp/index.d.ts +10 -0
- package/dist/sdk/index.js +14095 -14019
- package/dist/server/app.d.ts +3 -0
- package/dist/server/store-fixtures.d.ts +6 -0
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +50 -0
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -47,8 +47,8 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
47
47
|
var __require = import.meta.require;
|
|
48
48
|
|
|
49
49
|
// src/lib/registry.ts
|
|
50
|
-
import { existsSync as
|
|
51
|
-
import { join as
|
|
50
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync6 } from "fs";
|
|
51
|
+
import { join as join7 } from "path";
|
|
52
52
|
|
|
53
53
|
// src/lib/config.ts
|
|
54
54
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
|
|
@@ -272,15 +272,16 @@ function unsetConfig(key, scope = "project") {
|
|
|
272
272
|
// src/lib/portable-skills.ts
|
|
273
273
|
import {
|
|
274
274
|
cpSync as cpSync2,
|
|
275
|
-
existsSync as
|
|
275
|
+
existsSync as existsSync6,
|
|
276
276
|
mkdirSync as mkdirSync3,
|
|
277
|
-
|
|
277
|
+
mkdtempSync,
|
|
278
|
+
readdirSync as readdirSync5,
|
|
278
279
|
renameSync,
|
|
279
280
|
rmSync,
|
|
280
|
-
statSync as
|
|
281
|
+
statSync as statSync6,
|
|
281
282
|
writeFileSync as writeFileSync3
|
|
282
283
|
} from "fs";
|
|
283
|
-
import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute2, join as
|
|
284
|
+
import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute2, join as join6, normalize as normalize2 } from "path";
|
|
284
285
|
|
|
285
286
|
// src/lib/registry-data/development-tools.ts
|
|
286
287
|
var DEVELOPMENT_TOOLS_SKILLS = [
|
|
@@ -991,11 +992,9 @@ var SKILLS = [
|
|
|
991
992
|
...EVENT_MANAGEMENT_SKILLS
|
|
992
993
|
];
|
|
993
994
|
|
|
994
|
-
// src/lib/skill-validation.ts
|
|
995
|
-
import { existsSync as existsSync2, lstatSync, readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
996
|
-
import { isAbsolute, join as join2, normalize } from "path";
|
|
997
|
-
|
|
998
995
|
// src/lib/hosted-skill-set.ts
|
|
996
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
997
|
+
import { join as join2 } from "path";
|
|
999
998
|
var HOSTED_RUNTIMES = new Set(["hosted"]);
|
|
1000
999
|
var HOSTED_SOURCES = new Set(["remote", "private-hosted"]);
|
|
1001
1000
|
function normalizeMarker(value) {
|
|
@@ -1007,6 +1006,16 @@ function isHostedMetadataPackage(pkg) {
|
|
|
1007
1006
|
return false;
|
|
1008
1007
|
return HOSTED_RUNTIMES.has(normalizeMarker(skills.runtime)) || HOSTED_SOURCES.has(normalizeMarker(skills.source));
|
|
1009
1008
|
}
|
|
1009
|
+
function isHostedMetadataSkillDir(skillDir) {
|
|
1010
|
+
const pkgPath = join2(skillDir, "package.json");
|
|
1011
|
+
if (!existsSync2(pkgPath))
|
|
1012
|
+
return false;
|
|
1013
|
+
try {
|
|
1014
|
+
return isHostedMetadataPackage(JSON.parse(readFileSync2(pkgPath, "utf8")));
|
|
1015
|
+
} catch {
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1010
1019
|
var HOSTED_METADATA_SET_EMPTY_ERROR = [
|
|
1011
1020
|
"The hosted metadata skill set is empty, but the packaging guards that depend on it",
|
|
1012
1021
|
"only mean anything while it is non-empty: an empty set makes every one of them pass",
|
|
@@ -1023,6 +1032,8 @@ var BRACE_SOURCE_EXCLUSION = new RegExp(`^!skills/\\{(${SLUG}(?:,${SLUG})+)\\}/s
|
|
|
1023
1032
|
var SINGLE_SOURCE_EXCLUSION = new RegExp(`^!skills/(${SLUG})/src$`);
|
|
1024
1033
|
|
|
1025
1034
|
// src/lib/skill-validation.ts
|
|
1035
|
+
import { existsSync as existsSync3, lstatSync, readFileSync as readFileSync3, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
1036
|
+
import { isAbsolute, join as join3, normalize } from "path";
|
|
1026
1037
|
var VALID_SKILL_KINDS = ["executable", "instruction"];
|
|
1027
1038
|
var DOC_FILES = ["SKILL.md", "README.md", "CLAUDE.md"];
|
|
1028
1039
|
var RESERVED_SKILL_ENTRIES = new Set([
|
|
@@ -1076,7 +1087,7 @@ function add(target, code, message) {
|
|
|
1076
1087
|
target.push({ code, message });
|
|
1077
1088
|
}
|
|
1078
1089
|
function readJsonFile(path) {
|
|
1079
|
-
return JSON.parse(
|
|
1090
|
+
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
1080
1091
|
}
|
|
1081
1092
|
function asRecord(value) {
|
|
1082
1093
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
@@ -1160,7 +1171,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
1160
1171
|
binCommands: [],
|
|
1161
1172
|
docFiles: []
|
|
1162
1173
|
};
|
|
1163
|
-
if (!
|
|
1174
|
+
if (!existsSync3(skillPath)) {
|
|
1164
1175
|
add(issues, "skill.dir_missing", `Skill directory not found: ${skillPath}`);
|
|
1165
1176
|
return {
|
|
1166
1177
|
name: bareName,
|
|
@@ -1174,8 +1185,8 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
1174
1185
|
if (!VALID_BIN_COMMAND.test(bareName)) {
|
|
1175
1186
|
add(issues, "skill.name_invalid", `Skill name '${bareName}' must use lowercase letters, numbers, dots, underscores, or hyphens`);
|
|
1176
1187
|
}
|
|
1177
|
-
for (const entry of
|
|
1178
|
-
const entryPath =
|
|
1188
|
+
for (const entry of readdirSync3(skillPath).sort()) {
|
|
1189
|
+
const entryPath = join3(skillPath, entry);
|
|
1179
1190
|
if (RESERVED_SKILL_ENTRIES.has(entry)) {
|
|
1180
1191
|
add(issues, "skill.reserved_file", `Reserved file '${entry}' is not allowed in skill packages`);
|
|
1181
1192
|
}
|
|
@@ -1187,15 +1198,15 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
1187
1198
|
}
|
|
1188
1199
|
}
|
|
1189
1200
|
for (const docFile of DOC_FILES) {
|
|
1190
|
-
if (
|
|
1201
|
+
if (existsSync3(join3(skillPath, docFile)))
|
|
1191
1202
|
metadata.docFiles.push(docFile);
|
|
1192
1203
|
}
|
|
1193
1204
|
if (metadata.docFiles.length === 0) {
|
|
1194
1205
|
add(issues, "skill.docs_missing", "Missing documentation file: expected SKILL.md, README.md, or CLAUDE.md");
|
|
1195
1206
|
}
|
|
1196
|
-
const skillMdPath =
|
|
1197
|
-
if (
|
|
1198
|
-
const frontmatter = parseSkillFrontmatter(
|
|
1207
|
+
const skillMdPath = join3(skillPath, "SKILL.md");
|
|
1208
|
+
if (existsSync3(skillMdPath)) {
|
|
1209
|
+
const frontmatter = parseSkillFrontmatter(readFileSync3(skillMdPath, "utf-8"));
|
|
1199
1210
|
if (!frontmatter) {
|
|
1200
1211
|
add(warnings, "skill.frontmatter_missing", "SKILL.md has no YAML frontmatter");
|
|
1201
1212
|
} else {
|
|
@@ -1237,8 +1248,8 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
1237
1248
|
}
|
|
1238
1249
|
metadata.kind = resolvedKind;
|
|
1239
1250
|
const isInstruction = resolvedKind === "instruction";
|
|
1240
|
-
const pkgPath =
|
|
1241
|
-
if (!
|
|
1251
|
+
const pkgPath = join3(skillPath, "package.json");
|
|
1252
|
+
if (!existsSync3(pkgPath)) {
|
|
1242
1253
|
if (!isInstruction)
|
|
1243
1254
|
add(issues, "package.missing", "Missing package.json");
|
|
1244
1255
|
} else {
|
|
@@ -1296,10 +1307,10 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
1296
1307
|
add(issues, "package.bin_target_unsafe", `package.json bin '${command}' target '${target}' must stay inside the skill directory`);
|
|
1297
1308
|
continue;
|
|
1298
1309
|
}
|
|
1299
|
-
const targetPath =
|
|
1300
|
-
if (!
|
|
1310
|
+
const targetPath = join3(skillPath, target);
|
|
1311
|
+
if (!existsSync3(targetPath)) {
|
|
1301
1312
|
add(warnings, "package.bin_target_missing", `package.json bin '${command}' target '${target}' is not present before build`);
|
|
1302
|
-
} else if (
|
|
1313
|
+
} else if (statSync3(targetPath).isDirectory()) {
|
|
1303
1314
|
add(issues, "package.bin_target_directory", `package.json bin '${command}' target '${target}' must point to a file, not a directory`);
|
|
1304
1315
|
}
|
|
1305
1316
|
}
|
|
@@ -1314,18 +1325,18 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
1314
1325
|
metadata.runtime = "none";
|
|
1315
1326
|
} else {
|
|
1316
1327
|
metadata.runtime = hostedMetadata ? "hosted" : "local";
|
|
1317
|
-
const srcDir =
|
|
1328
|
+
const srcDir = join3(skillPath, "src");
|
|
1318
1329
|
if (hostedMetadata) {
|
|
1319
|
-
if (
|
|
1330
|
+
if (existsSync3(srcDir)) {
|
|
1320
1331
|
add(issues, "skill.hosted_source_forbidden", "Hosted metadata skills must not include local implementation source");
|
|
1321
1332
|
}
|
|
1322
|
-
} else if (!
|
|
1333
|
+
} else if (!existsSync3(srcDir)) {
|
|
1323
1334
|
add(issues, "skill.src_missing", "Missing src/ directory");
|
|
1324
|
-
} else if (!
|
|
1335
|
+
} else if (!existsSync3(join3(srcDir, "index.ts")) && !existsSync3(join3(srcDir, "index.js"))) {
|
|
1325
1336
|
add(issues, "skill.src_index_missing", "Missing src/index.ts or src/index.js");
|
|
1326
1337
|
} else {
|
|
1327
|
-
const indexPath =
|
|
1328
|
-
const size =
|
|
1338
|
+
const indexPath = existsSync3(join3(srcDir, "index.ts")) ? join3(srcDir, "index.ts") : join3(srcDir, "index.js");
|
|
1339
|
+
const size = statSync3(indexPath).size;
|
|
1329
1340
|
if (size < 50)
|
|
1330
1341
|
add(warnings, "skill.src_index_minimal", `Source entry point is very small (${size}B)`);
|
|
1331
1342
|
}
|
|
@@ -1348,9 +1359,9 @@ function validateRegistryConsistency(registry, skillsDir) {
|
|
|
1348
1359
|
seen.add(name);
|
|
1349
1360
|
return false;
|
|
1350
1361
|
})));
|
|
1351
|
-
const skillDirs =
|
|
1352
|
-
const fullPath =
|
|
1353
|
-
return !entry.startsWith(".") && entry !== "_common" &&
|
|
1362
|
+
const skillDirs = existsSync3(skillsDir) ? readdirSync3(skillsDir).filter((entry) => {
|
|
1363
|
+
const fullPath = join3(skillsDir, entry);
|
|
1364
|
+
return !entry.startsWith(".") && entry !== "_common" && statSync3(fullPath).isDirectory();
|
|
1354
1365
|
}) : [];
|
|
1355
1366
|
const directoryNames = new Set(skillDirs);
|
|
1356
1367
|
const registryNameSet = new Set(registryNames);
|
|
@@ -1366,8 +1377,8 @@ function validateRegistryConsistency(registry, skillsDir) {
|
|
|
1366
1377
|
|
|
1367
1378
|
// src/lib/skill-hash.ts
|
|
1368
1379
|
import { createHash } from "crypto";
|
|
1369
|
-
import { existsSync as
|
|
1370
|
-
import { join as
|
|
1380
|
+
import { existsSync as existsSync4, readdirSync as readdirSync4, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
|
|
1381
|
+
import { join as join4, sep } from "path";
|
|
1371
1382
|
var CONTENT_HASH_ALGORITHM = "sha256";
|
|
1372
1383
|
var HASH_EXCLUDE_DIRS = new Set([".git", "node_modules", "dist", "build", ".turbo"]);
|
|
1373
1384
|
var HASH_COVERAGE = [
|
|
@@ -1426,10 +1437,10 @@ function collectBundleFiles(skillPath) {
|
|
|
1426
1437
|
if (seen.has(entry))
|
|
1427
1438
|
continue;
|
|
1428
1439
|
seen.add(entry);
|
|
1429
|
-
const absolute =
|
|
1430
|
-
if (!
|
|
1440
|
+
const absolute = join4(skillPath, entry);
|
|
1441
|
+
if (!existsSync4(absolute))
|
|
1431
1442
|
continue;
|
|
1432
|
-
if (
|
|
1443
|
+
if (statSync4(absolute).isDirectory())
|
|
1433
1444
|
collectDirectory(files, absolute, entry);
|
|
1434
1445
|
else
|
|
1435
1446
|
collectFile(files, absolute, entry);
|
|
@@ -1437,14 +1448,14 @@ function collectBundleFiles(skillPath) {
|
|
|
1437
1448
|
return files.sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
|
|
1438
1449
|
}
|
|
1439
1450
|
function collectDirectory(files, dir, rel) {
|
|
1440
|
-
for (const entry of
|
|
1451
|
+
for (const entry of readdirSync4(dir).sort()) {
|
|
1441
1452
|
if (entry.startsWith("."))
|
|
1442
1453
|
continue;
|
|
1443
|
-
const absolute =
|
|
1454
|
+
const absolute = join4(dir, entry);
|
|
1444
1455
|
const childRel = `${rel}/${entry}`;
|
|
1445
1456
|
let stats;
|
|
1446
1457
|
try {
|
|
1447
|
-
stats =
|
|
1458
|
+
stats = statSync4(absolute);
|
|
1448
1459
|
} catch {
|
|
1449
1460
|
continue;
|
|
1450
1461
|
}
|
|
@@ -1460,7 +1471,7 @@ function collectDirectory(files, dir, rel) {
|
|
|
1460
1471
|
}
|
|
1461
1472
|
}
|
|
1462
1473
|
function collectFile(files, absolute, rel) {
|
|
1463
|
-
const buffer =
|
|
1474
|
+
const buffer = readFileSync4(absolute);
|
|
1464
1475
|
if (rel === "skill.json") {
|
|
1465
1476
|
files.push({ rel: rel.split(sep).join("/"), content: new TextEncoder().encode(canonicalizeManifest(new TextDecoder().decode(buffer))) });
|
|
1466
1477
|
return;
|
|
@@ -1660,14 +1671,14 @@ function validateRuntimeContract(manifest, issues, strict) {
|
|
|
1660
1671
|
// src/lib/portable-skills-files.ts
|
|
1661
1672
|
import {
|
|
1662
1673
|
cpSync,
|
|
1663
|
-
existsSync as
|
|
1674
|
+
existsSync as existsSync5,
|
|
1664
1675
|
lstatSync as lstatSync2,
|
|
1665
1676
|
mkdirSync as mkdirSync2,
|
|
1666
|
-
readFileSync as
|
|
1677
|
+
readFileSync as readFileSync5,
|
|
1667
1678
|
realpathSync,
|
|
1668
1679
|
writeFileSync as writeFileSync2
|
|
1669
1680
|
} from "fs";
|
|
1670
|
-
import { basename, dirname as dirname2, join as
|
|
1681
|
+
import { basename, dirname as dirname2, join as join5, relative } from "path";
|
|
1671
1682
|
var ANY_SEGMENT_COPY_EXCLUDES = new Set([
|
|
1672
1683
|
".git",
|
|
1673
1684
|
".DS_Store",
|
|
@@ -1710,12 +1721,12 @@ function normalizePortableSkillName(name) {
|
|
|
1710
1721
|
return normalized;
|
|
1711
1722
|
}
|
|
1712
1723
|
function readPortableSkillManifest(skillPath, fallbackName = basename(skillPath)) {
|
|
1713
|
-
const skillJsonPath =
|
|
1714
|
-
const skillMdPath =
|
|
1715
|
-
const pkgPath =
|
|
1716
|
-
const jsonManifest =
|
|
1717
|
-
const frontmatter =
|
|
1718
|
-
const pkg =
|
|
1724
|
+
const skillJsonPath = join5(skillPath, "skill.json");
|
|
1725
|
+
const skillMdPath = join5(skillPath, "SKILL.md");
|
|
1726
|
+
const pkgPath = join5(skillPath, "package.json");
|
|
1727
|
+
const jsonManifest = existsSync5(skillJsonPath) ? readJsonObject(skillJsonPath) : undefined;
|
|
1728
|
+
const frontmatter = existsSync5(skillMdPath) ? parseSkillFrontmatter(readFileSync5(skillMdPath, "utf-8")) ?? undefined : undefined;
|
|
1729
|
+
const pkg = existsSync5(pkgPath) ? readJsonObject(pkgPath) : undefined;
|
|
1719
1730
|
const name = normalizePortableSkillName(stringField(jsonManifest, "name") ?? frontmatter?.name ?? stringValue(pkg?.name) ?? fallbackName);
|
|
1720
1731
|
const description = stringField(jsonManifest, "description") ?? frontmatter?.description ?? stringValue(pkg?.description) ?? `${name} skill`;
|
|
1721
1732
|
const version = stringField(jsonManifest, "version") ?? frontmatter?.version ?? stringValue(pkg?.version) ?? PORTABLE_SKILL_DEFAULT_VERSION;
|
|
@@ -1761,7 +1772,7 @@ function createInstructionManifest(name, options) {
|
|
|
1761
1772
|
}
|
|
1762
1773
|
function writeInstructionSkillTemplate(skillPath, manifest) {
|
|
1763
1774
|
mkdirSync2(skillPath, { recursive: true });
|
|
1764
|
-
writeFileSync2(
|
|
1775
|
+
writeFileSync2(join5(skillPath, "SKILL.md"), renderInstructionSkillMd(manifest));
|
|
1765
1776
|
writeSkillJsonWithHash(skillPath, manifest);
|
|
1766
1777
|
}
|
|
1767
1778
|
function renderInstructionSkillMd(manifest) {
|
|
@@ -1810,12 +1821,12 @@ function createPortableManifest(name, options) {
|
|
|
1810
1821
|
};
|
|
1811
1822
|
}
|
|
1812
1823
|
function writePortableSkillTemplate(skillPath, manifest) {
|
|
1813
|
-
mkdirSync2(
|
|
1814
|
-
writeFileSync2(
|
|
1815
|
-
writeFileSync2(
|
|
1816
|
-
writeFileSync2(
|
|
1817
|
-
writeFileSync2(
|
|
1818
|
-
writeFileSync2(
|
|
1824
|
+
mkdirSync2(join5(skillPath, "src"), { recursive: true });
|
|
1825
|
+
writeFileSync2(join5(skillPath, "SKILL.md"), renderSkillMd(manifest));
|
|
1826
|
+
writeFileSync2(join5(skillPath, "AGENTS.md"), renderAgentsMd(manifest));
|
|
1827
|
+
writeFileSync2(join5(skillPath, "package.json"), renderPackageJson(manifest));
|
|
1828
|
+
writeFileSync2(join5(skillPath, "tsconfig.json"), renderTsconfig());
|
|
1829
|
+
writeFileSync2(join5(skillPath, "src", "index.ts"), renderEntrypoint(manifest));
|
|
1819
1830
|
writeSkillJsonWithHash(skillPath, manifest);
|
|
1820
1831
|
}
|
|
1821
1832
|
function fillContractDefaults(manifest, entrypoint) {
|
|
@@ -1840,7 +1851,7 @@ function writeSkillJsonWithHash(skillPath, manifest) {
|
|
|
1840
1851
|
content_hash: undefined
|
|
1841
1852
|
}
|
|
1842
1853
|
};
|
|
1843
|
-
writeFileSync2(
|
|
1854
|
+
writeFileSync2(join5(skillPath, "skill.json"), `${JSON.stringify({ ...existing, ...renderSkillJsonObject(withoutHash) }, null, 2)}
|
|
1844
1855
|
`);
|
|
1845
1856
|
const hash = computeContentHash(skillPath);
|
|
1846
1857
|
const withHash = {
|
|
@@ -1850,16 +1861,16 @@ function writeSkillJsonWithHash(skillPath, manifest) {
|
|
|
1850
1861
|
content_hash: hash
|
|
1851
1862
|
}
|
|
1852
1863
|
};
|
|
1853
|
-
writeFileSync2(
|
|
1864
|
+
writeFileSync2(join5(skillPath, "skill.json"), `${JSON.stringify({ ...existing, ...renderSkillJsonObject(withHash) }, null, 2)}
|
|
1854
1865
|
`);
|
|
1855
1866
|
return withHash;
|
|
1856
1867
|
}
|
|
1857
1868
|
function readExistingSkillJson(skillPath) {
|
|
1858
|
-
const path =
|
|
1859
|
-
if (!
|
|
1869
|
+
const path = join5(skillPath, "skill.json");
|
|
1870
|
+
if (!existsSync5(path))
|
|
1860
1871
|
return {};
|
|
1861
1872
|
try {
|
|
1862
|
-
const parsed = JSON.parse(
|
|
1873
|
+
const parsed = JSON.parse(readFileSync5(path, "utf-8"));
|
|
1863
1874
|
return isRecord2(parsed) ? parsed : {};
|
|
1864
1875
|
} catch {
|
|
1865
1876
|
return {};
|
|
@@ -1889,28 +1900,28 @@ function ensurePortableSkillFiles(skillPath, manifest) {
|
|
|
1889
1900
|
tags: next.tags?.length ? next.tags : ["custom", next.name]
|
|
1890
1901
|
};
|
|
1891
1902
|
const entry = next.commands[0]?.entry ?? "src/index.ts";
|
|
1892
|
-
if (entry && !
|
|
1893
|
-
mkdirSync2(dirname2(
|
|
1894
|
-
writeFileSync2(
|
|
1903
|
+
if (entry && !existsSync5(join5(skillPath, entry))) {
|
|
1904
|
+
mkdirSync2(dirname2(join5(skillPath, entry)), { recursive: true });
|
|
1905
|
+
writeFileSync2(join5(skillPath, entry), renderEntrypoint(next));
|
|
1895
1906
|
}
|
|
1896
|
-
if (!
|
|
1897
|
-
writeFileSync2(
|
|
1907
|
+
if (!existsSync5(join5(skillPath, "SKILL.md")))
|
|
1908
|
+
writeFileSync2(join5(skillPath, "SKILL.md"), renderSkillMd(next));
|
|
1898
1909
|
else
|
|
1899
|
-
writeFileSync2(
|
|
1900
|
-
if (!
|
|
1901
|
-
writeFileSync2(
|
|
1910
|
+
writeFileSync2(join5(skillPath, "SKILL.md"), ensureSkillMdFrontmatter(readFileSync5(join5(skillPath, "SKILL.md"), "utf-8"), next));
|
|
1911
|
+
if (!existsSync5(join5(skillPath, "AGENTS.md")))
|
|
1912
|
+
writeFileSync2(join5(skillPath, "AGENTS.md"), renderAgentsMd(next));
|
|
1902
1913
|
ensurePackageJson(skillPath, next);
|
|
1903
|
-
if (!
|
|
1904
|
-
writeFileSync2(
|
|
1914
|
+
if (!existsSync5(join5(skillPath, "tsconfig.json")))
|
|
1915
|
+
writeFileSync2(join5(skillPath, "tsconfig.json"), renderTsconfig());
|
|
1905
1916
|
writeSkillJsonWithHash(skillPath, next);
|
|
1906
1917
|
return readPortableSkillManifest(skillPath, next.name);
|
|
1907
1918
|
}
|
|
1908
1919
|
function ensurePackageJson(skillPath, manifest) {
|
|
1909
|
-
const pkgPath =
|
|
1920
|
+
const pkgPath = join5(skillPath, "package.json");
|
|
1910
1921
|
const first = manifest.commands[0] ?? { name: manifest.name, entry: "src/index.ts" };
|
|
1911
1922
|
const commandName = normalizePortableSkillName(first.name || manifest.name);
|
|
1912
1923
|
const entry = (first.entry ?? "src/index.ts").replace(/^\.\//, "");
|
|
1913
|
-
if (!
|
|
1924
|
+
if (!existsSync5(pkgPath)) {
|
|
1914
1925
|
writeFileSync2(pkgPath, renderPackageJson(manifest));
|
|
1915
1926
|
return;
|
|
1916
1927
|
}
|
|
@@ -1953,8 +1964,8 @@ function ensureInstructionSkillFiles(skillPath, manifest) {
|
|
|
1953
1964
|
inputs: [],
|
|
1954
1965
|
commands: []
|
|
1955
1966
|
};
|
|
1956
|
-
if (!
|
|
1957
|
-
writeFileSync2(
|
|
1967
|
+
if (!existsSync5(join5(skillPath, "SKILL.md"))) {
|
|
1968
|
+
writeFileSync2(join5(skillPath, "SKILL.md"), renderSkillMd(next));
|
|
1958
1969
|
}
|
|
1959
1970
|
writeSkillJsonWithHash(skillPath, next);
|
|
1960
1971
|
return readPortableSkillManifest(skillPath, next.name);
|
|
@@ -2205,7 +2216,7 @@ function inferPackageCommands(pkg, fallbackName) {
|
|
|
2205
2216
|
return;
|
|
2206
2217
|
}
|
|
2207
2218
|
function readJsonObject(path) {
|
|
2208
|
-
const parsed = JSON.parse(
|
|
2219
|
+
const parsed = JSON.parse(readFileSync5(path, "utf-8"));
|
|
2209
2220
|
if (!isRecord2(parsed))
|
|
2210
2221
|
throw new Error(`${basename(path)} must contain a JSON object`);
|
|
2211
2222
|
return parsed;
|
|
@@ -2246,36 +2257,36 @@ var LEGACY_CUSTOM_DIRNAME = "custom";
|
|
|
2246
2257
|
function getPortableSkillsRoot(options = {}) {
|
|
2247
2258
|
if (options.rootDir)
|
|
2248
2259
|
return options.rootDir;
|
|
2249
|
-
const appDir = options.homeDir ?
|
|
2250
|
-
const cache =
|
|
2260
|
+
const appDir = options.homeDir ? join6(options.homeDir, ".hasna", "skills") : getDataDir();
|
|
2261
|
+
const cache = join6(appDir, SKILLS_CACHE_DIRNAME);
|
|
2251
2262
|
if (isOwnerLayoutMigrated(appDir) && safeIsDirectory(cache))
|
|
2252
2263
|
return cache;
|
|
2253
|
-
const installed =
|
|
2264
|
+
const installed = join6(appDir, INSTALLED_SKILLS_DIRNAME);
|
|
2254
2265
|
migrateLegacySkillLayout(appDir, installed);
|
|
2255
2266
|
return installed;
|
|
2256
2267
|
}
|
|
2257
2268
|
function looksLikeSkillDirectory(path) {
|
|
2258
2269
|
if (!safeIsDirectory(path))
|
|
2259
2270
|
return false;
|
|
2260
|
-
return
|
|
2271
|
+
return existsSync6(join6(path, "SKILL.md")) || existsSync6(join6(path, "skill.json")) || existsSync6(join6(path, "package.json"));
|
|
2261
2272
|
}
|
|
2262
2273
|
function migrateLegacySkillLayout(appDir, installed) {
|
|
2263
2274
|
if (!safeIsDirectory(appDir))
|
|
2264
2275
|
return;
|
|
2265
2276
|
const candidates = [];
|
|
2266
2277
|
try {
|
|
2267
|
-
for (const entry of
|
|
2278
|
+
for (const entry of readdirSync5(appDir)) {
|
|
2268
2279
|
if (entry.startsWith(".") || entry === INSTALLED_SKILLS_DIRNAME)
|
|
2269
2280
|
continue;
|
|
2270
|
-
const path =
|
|
2281
|
+
const path = join6(appDir, entry);
|
|
2271
2282
|
if (entry === LEGACY_CUSTOM_DIRNAME) {
|
|
2272
2283
|
if (!safeIsDirectory(path))
|
|
2273
2284
|
continue;
|
|
2274
2285
|
try {
|
|
2275
|
-
for (const nested of
|
|
2286
|
+
for (const nested of readdirSync5(path)) {
|
|
2276
2287
|
if (nested.startsWith("."))
|
|
2277
2288
|
continue;
|
|
2278
|
-
const nestedPath =
|
|
2289
|
+
const nestedPath = join6(path, nested);
|
|
2279
2290
|
if (looksLikeSkillDirectory(nestedPath))
|
|
2280
2291
|
candidates.push({ from: nestedPath, name: nested });
|
|
2281
2292
|
}
|
|
@@ -2289,10 +2300,10 @@ function migrateLegacySkillLayout(appDir, installed) {
|
|
|
2289
2300
|
return;
|
|
2290
2301
|
}
|
|
2291
2302
|
for (const { from, name } of candidates) {
|
|
2292
|
-
const target =
|
|
2293
|
-
if (
|
|
2303
|
+
const target = join6(installed, name);
|
|
2304
|
+
if (existsSync6(target))
|
|
2294
2305
|
continue;
|
|
2295
|
-
const staging =
|
|
2306
|
+
const staging = join6(installed, `.migrating-${name}-${process.pid}`);
|
|
2296
2307
|
try {
|
|
2297
2308
|
rmSync(staging, { recursive: true, force: true });
|
|
2298
2309
|
cpSync2(from, staging, { recursive: true, errorOnExist: false });
|
|
@@ -2305,7 +2316,7 @@ function migrateLegacySkillLayout(appDir, installed) {
|
|
|
2305
2316
|
}
|
|
2306
2317
|
}
|
|
2307
2318
|
function getPortableSkillPath(name, options = {}) {
|
|
2308
|
-
return
|
|
2319
|
+
return join6(getPortableSkillsRoot(options), normalizePortableSkillName(name));
|
|
2309
2320
|
}
|
|
2310
2321
|
function findPortableSkill(name, options = {}) {
|
|
2311
2322
|
let normalized;
|
|
@@ -2315,7 +2326,7 @@ function findPortableSkill(name, options = {}) {
|
|
|
2315
2326
|
return null;
|
|
2316
2327
|
}
|
|
2317
2328
|
const path = getPortableSkillPath(normalized, options);
|
|
2318
|
-
if (!
|
|
2329
|
+
if (!existsSync6(path) || !statSync6(path).isDirectory())
|
|
2319
2330
|
return null;
|
|
2320
2331
|
try {
|
|
2321
2332
|
return summarizePortableSkill(path, normalized);
|
|
@@ -2328,10 +2339,10 @@ function listPortableSkills(options = {}) {
|
|
|
2328
2339
|
if (!safeIsDirectory(root))
|
|
2329
2340
|
return [];
|
|
2330
2341
|
const skills = [];
|
|
2331
|
-
for (const entry of
|
|
2342
|
+
for (const entry of readdirSync5(root).sort()) {
|
|
2332
2343
|
if (entry.startsWith("."))
|
|
2333
2344
|
continue;
|
|
2334
|
-
const path =
|
|
2345
|
+
const path = join6(root, entry);
|
|
2335
2346
|
if (!safeIsDirectory(path))
|
|
2336
2347
|
continue;
|
|
2337
2348
|
try {
|
|
@@ -2353,7 +2364,8 @@ function listPortableSkillMetas(options = {}) {
|
|
|
2353
2364
|
tags: manifest.tags || ["custom"],
|
|
2354
2365
|
version: skill.version,
|
|
2355
2366
|
...manifest.kind ? { kind: manifest.kind } : {},
|
|
2356
|
-
source: "custom"
|
|
2367
|
+
source: "custom",
|
|
2368
|
+
...isHostedMetadataSkillDir(skill.path) ? { serverOwned: true } : {}
|
|
2357
2369
|
};
|
|
2358
2370
|
});
|
|
2359
2371
|
}
|
|
@@ -2364,8 +2376,8 @@ function isOfficialSkillName(name) {
|
|
|
2364
2376
|
function scaffoldPortableSkill(name, options = {}) {
|
|
2365
2377
|
const skillName = normalizePortableSkillName(name);
|
|
2366
2378
|
const root = getPortableSkillsRoot(options);
|
|
2367
|
-
const skillPath =
|
|
2368
|
-
if (
|
|
2379
|
+
const skillPath = join6(root, skillName);
|
|
2380
|
+
if (existsSync6(skillPath)) {
|
|
2369
2381
|
if (!options.overwrite)
|
|
2370
2382
|
throw new Error(`Skill '${skillName}' already exists at ${skillPath}`);
|
|
2371
2383
|
rmSync(skillPath, { recursive: true, force: true });
|
|
@@ -2383,7 +2395,7 @@ function scaffoldPortableSkill(name, options = {}) {
|
|
|
2383
2395
|
}
|
|
2384
2396
|
function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
2385
2397
|
const absoluteSource = normalize2(sourceDir);
|
|
2386
|
-
if (!
|
|
2398
|
+
if (!existsSync6(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
|
|
2387
2399
|
throw new Error(`Import directory not found: ${sourceDir}`);
|
|
2388
2400
|
}
|
|
2389
2401
|
const continueOnError = options.continueOnError ?? true;
|
|
@@ -2394,9 +2406,9 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
|
2394
2406
|
};
|
|
2395
2407
|
const imported = [];
|
|
2396
2408
|
const skipped = [];
|
|
2397
|
-
const entries =
|
|
2409
|
+
const entries = readdirSync5(absoluteSource, { withFileTypes: true }).map((entry) => entry.name).filter((entryName) => !entryName.startsWith(".")).sort();
|
|
2398
2410
|
for (const entryName of entries) {
|
|
2399
|
-
const childPath =
|
|
2411
|
+
const childPath = join6(absoluteSource, entryName);
|
|
2400
2412
|
if (!safeIsDirectory(childPath))
|
|
2401
2413
|
continue;
|
|
2402
2414
|
if (!isSkillCandidate(childPath)) {
|
|
@@ -2425,11 +2437,11 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
|
2425
2437
|
};
|
|
2426
2438
|
}
|
|
2427
2439
|
function isSkillCandidate(dir) {
|
|
2428
|
-
return
|
|
2440
|
+
return existsSync6(join6(dir, "SKILL.md")) || existsSync6(join6(dir, "skill.json")) || existsSync6(join6(dir, "package.json"));
|
|
2429
2441
|
}
|
|
2430
2442
|
function portPortableSkill(sourcePath, options = {}) {
|
|
2431
2443
|
const absoluteSource = normalize2(sourcePath);
|
|
2432
|
-
if (!
|
|
2444
|
+
if (!existsSync6(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
|
|
2433
2445
|
throw new Error(`Skill source directory not found: ${sourcePath}`);
|
|
2434
2446
|
}
|
|
2435
2447
|
const inferred = readPortableSkillManifest(absoluteSource, basename2(absoluteSource));
|
|
@@ -2441,8 +2453,8 @@ function portPortableSkill(sourcePath, options = {}) {
|
|
|
2441
2453
|
throw new Error(`${via} Importing it would shadow the official '${skillName}'. ` + `Pass --name to choose a different name, or --allow-shadow to override deliberately.`);
|
|
2442
2454
|
}
|
|
2443
2455
|
const root = getPortableSkillsRoot(options);
|
|
2444
|
-
const destination =
|
|
2445
|
-
if (
|
|
2456
|
+
const destination = join6(root, skillName);
|
|
2457
|
+
if (existsSync6(destination)) {
|
|
2446
2458
|
if (!options.overwrite)
|
|
2447
2459
|
throw new Error(`Skill '${skillName}' already exists at ${destination}`);
|
|
2448
2460
|
rmSync(destination, { recursive: true, force: true });
|
|
@@ -2464,16 +2476,10 @@ function safeNormalizeName(name) {
|
|
|
2464
2476
|
return;
|
|
2465
2477
|
}
|
|
2466
2478
|
}
|
|
2467
|
-
function
|
|
2468
|
-
const name = normalizePortableSkillName(input.name);
|
|
2469
|
-
const root = getPortableSkillsRoot(options);
|
|
2470
|
-
const skillPath = join5(root, name);
|
|
2471
|
-
const created = !existsSync5(skillPath);
|
|
2472
|
-
mkdirSync3(skillPath, { recursive: true });
|
|
2473
|
-
writeFileSync3(join5(skillPath, "SKILL.md"), input.skillMd);
|
|
2479
|
+
function buildCorpusManifest(input, name) {
|
|
2474
2480
|
const frontmatter = parseSkillFrontmatter(input.skillMd) ?? undefined;
|
|
2475
2481
|
const kind = input.meta?.kind ?? parseSkillKind(frontmatter?.kind) ?? "instruction";
|
|
2476
|
-
|
|
2482
|
+
return {
|
|
2477
2483
|
$schema: PORTABLE_SKILL_SCHEMA,
|
|
2478
2484
|
standard: PORTABLE_SKILL_STANDARD,
|
|
2479
2485
|
name,
|
|
@@ -2486,19 +2492,60 @@ function writeCorpusSkill(input, options = {}) {
|
|
|
2486
2492
|
inputs: [],
|
|
2487
2493
|
commands: []
|
|
2488
2494
|
};
|
|
2495
|
+
}
|
|
2496
|
+
function writeCorpusSkill(input, options = {}) {
|
|
2497
|
+
const name = normalizePortableSkillName(input.name);
|
|
2498
|
+
const root = getPortableSkillsRoot(options);
|
|
2499
|
+
const skillPath = join6(root, name);
|
|
2500
|
+
const created = !existsSync6(skillPath);
|
|
2501
|
+
mkdirSync3(skillPath, { recursive: true });
|
|
2502
|
+
writeFileSync3(join6(skillPath, "SKILL.md"), input.skillMd);
|
|
2503
|
+
const manifest = buildCorpusManifest(input, name);
|
|
2489
2504
|
writeSkillJsonWithHash(skillPath, manifest);
|
|
2490
2505
|
return { name, path: skillPath, manifest, created };
|
|
2491
2506
|
}
|
|
2507
|
+
function installCorpusSkillAtomically(input, options = {}) {
|
|
2508
|
+
const name = normalizePortableSkillName(input.name);
|
|
2509
|
+
const root = getPortableSkillsRoot(options);
|
|
2510
|
+
const target = join6(root, name);
|
|
2511
|
+
const created = !existsSync6(target);
|
|
2512
|
+
mkdirSync3(root, { recursive: true });
|
|
2513
|
+
const staging = mkdtempSync(join6(root, `.pull-${name}-`));
|
|
2514
|
+
let moved = false;
|
|
2515
|
+
let backup = null;
|
|
2516
|
+
try {
|
|
2517
|
+
writeFileSync3(join6(staging, "SKILL.md"), input.skillMd);
|
|
2518
|
+
const manifest = buildCorpusManifest(input, name);
|
|
2519
|
+
writeSkillJsonWithHash(staging, manifest);
|
|
2520
|
+
if (existsSync6(target)) {
|
|
2521
|
+
backup = mkdtempSync(join6(root, `.pull-backup-${name}-`));
|
|
2522
|
+
renameSync(target, join6(backup, name));
|
|
2523
|
+
moved = true;
|
|
2524
|
+
}
|
|
2525
|
+
renameSync(staging, target);
|
|
2526
|
+
if (moved && backup)
|
|
2527
|
+
rmSync(backup, { recursive: true, force: true });
|
|
2528
|
+
return { name, path: target, manifest, created };
|
|
2529
|
+
} catch (error) {
|
|
2530
|
+
rmSync(staging, { recursive: true, force: true });
|
|
2531
|
+
if (moved && backup && existsSync6(join6(backup, name))) {
|
|
2532
|
+
try {
|
|
2533
|
+
renameSync(join6(backup, name), target);
|
|
2534
|
+
} catch {}
|
|
2535
|
+
}
|
|
2536
|
+
throw error;
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2492
2539
|
function validatePortableSkillDirectory(name, skillPath) {
|
|
2493
2540
|
const normalizedName = normalizePortableSkillName(name);
|
|
2494
2541
|
const base = validateSkillDirectory(normalizedName, skillPath);
|
|
2495
2542
|
const issues = [...base.issues];
|
|
2496
2543
|
const warnings = [...base.warnings];
|
|
2497
2544
|
let manifest;
|
|
2498
|
-
if (
|
|
2499
|
-
const skillJsonPath =
|
|
2500
|
-
const skillMdPath =
|
|
2501
|
-
if (!
|
|
2545
|
+
if (existsSync6(skillPath)) {
|
|
2546
|
+
const skillJsonPath = join6(skillPath, "skill.json");
|
|
2547
|
+
const skillMdPath = join6(skillPath, "SKILL.md");
|
|
2548
|
+
if (!existsSync6(skillJsonPath) && !existsSync6(skillMdPath)) {
|
|
2502
2549
|
add3(issues, "portable.manifest_missing", "Missing portable manifest: expected SKILL.md frontmatter and/or skill.json");
|
|
2503
2550
|
}
|
|
2504
2551
|
try {
|
|
@@ -2517,7 +2564,7 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
2517
2564
|
add3(issues, "portable.version_missing", "Portable manifest missing version");
|
|
2518
2565
|
}
|
|
2519
2566
|
const contractIssues = validatePortableManifestContract(manifest, {
|
|
2520
|
-
strict:
|
|
2567
|
+
strict: existsSync6(join6(skillPath, "skill.json")),
|
|
2521
2568
|
skillPath
|
|
2522
2569
|
});
|
|
2523
2570
|
for (const issue of contractIssues)
|
|
@@ -2553,10 +2600,10 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
2553
2600
|
add3(issues, "portable.command_entry_unsafe", `Command '${command.name}' entry '${command.entry}' must stay inside the skill directory`);
|
|
2554
2601
|
continue;
|
|
2555
2602
|
}
|
|
2556
|
-
const entryPath =
|
|
2557
|
-
if (!
|
|
2603
|
+
const entryPath = join6(skillPath, command.entry);
|
|
2604
|
+
if (!existsSync6(entryPath))
|
|
2558
2605
|
add3(issues, "portable.command_entry_missing", `Command '${command.name}' entry '${command.entry}' is missing`);
|
|
2559
|
-
else if (
|
|
2606
|
+
else if (statSync6(entryPath).isDirectory())
|
|
2560
2607
|
add3(issues, "portable.command_entry_directory", `Command '${command.name}' entry '${command.entry}' must be a file`);
|
|
2561
2608
|
}
|
|
2562
2609
|
}
|
|
@@ -2564,7 +2611,7 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
2564
2611
|
} catch (error) {
|
|
2565
2612
|
add3(issues, "portable.manifest_invalid", error.message);
|
|
2566
2613
|
}
|
|
2567
|
-
if (manifest?.kind !== "instruction" && !
|
|
2614
|
+
if (manifest?.kind !== "instruction" && !existsSync6(join6(skillPath, "AGENTS.md"))) {
|
|
2568
2615
|
add3(issues, "portable.agents_missing", "Missing AGENTS.md with build-out instructions for coding agents");
|
|
2569
2616
|
}
|
|
2570
2617
|
}
|
|
@@ -2600,13 +2647,13 @@ async function runPortableSkill(name, args, options = {}) {
|
|
|
2600
2647
|
if (!isSafeRelativePath2(command.entry)) {
|
|
2601
2648
|
return { exitCode: 1, error: `Portable skill '${name}' command entry is unsafe` };
|
|
2602
2649
|
}
|
|
2603
|
-
const entryPath =
|
|
2604
|
-
if (!
|
|
2650
|
+
const entryPath = join6(skill.path, command.entry);
|
|
2651
|
+
if (!existsSync6(entryPath)) {
|
|
2605
2652
|
return { exitCode: 1, error: `Entry point '${command.entry}' not found in portable skill '${name}'` };
|
|
2606
2653
|
}
|
|
2607
|
-
const pkgPath =
|
|
2608
|
-
const nodeModules =
|
|
2609
|
-
if (
|
|
2654
|
+
const pkgPath = join6(skill.path, "package.json");
|
|
2655
|
+
const nodeModules = join6(skill.path, "node_modules");
|
|
2656
|
+
if (existsSync6(pkgPath) && !existsSync6(nodeModules) && hasPackageDependencies(pkgPath)) {
|
|
2610
2657
|
const install = Bun.spawn(["bun", "install", "--no-save"], {
|
|
2611
2658
|
cwd: skill.path,
|
|
2612
2659
|
stdout: "pipe",
|
|
@@ -2646,7 +2693,7 @@ function summarizePortableSkill(skillPath, fallbackName) {
|
|
|
2646
2693
|
}
|
|
2647
2694
|
function safeIsDirectory(path) {
|
|
2648
2695
|
try {
|
|
2649
|
-
return
|
|
2696
|
+
return statSync6(path).isDirectory();
|
|
2650
2697
|
} catch {
|
|
2651
2698
|
return false;
|
|
2652
2699
|
}
|
|
@@ -2875,20 +2922,20 @@ function parseSkillMdFrontmatter(content) {
|
|
|
2875
2922
|
return Object.keys(result).length > 0 ? result : null;
|
|
2876
2923
|
}
|
|
2877
2924
|
function discoverSkillsInDir(dir, source = "custom") {
|
|
2878
|
-
if (!
|
|
2925
|
+
if (!existsSync7(dir))
|
|
2879
2926
|
return [];
|
|
2880
2927
|
const result = [];
|
|
2881
2928
|
try {
|
|
2882
|
-
const entries =
|
|
2929
|
+
const entries = readdirSync6(dir, { withFileTypes: true });
|
|
2883
2930
|
for (const entry of entries) {
|
|
2884
2931
|
if (!entry.isDirectory())
|
|
2885
2932
|
continue;
|
|
2886
|
-
const skillMdPath =
|
|
2887
|
-
if (!
|
|
2933
|
+
const skillMdPath = join7(dir, entry.name, "SKILL.md");
|
|
2934
|
+
if (!existsSync7(skillMdPath))
|
|
2888
2935
|
continue;
|
|
2889
2936
|
let content;
|
|
2890
2937
|
try {
|
|
2891
|
-
content =
|
|
2938
|
+
content = readFileSync6(skillMdPath, "utf-8");
|
|
2892
2939
|
} catch {
|
|
2893
2940
|
continue;
|
|
2894
2941
|
}
|
|
@@ -2903,6 +2950,7 @@ function discoverSkillsInDir(dir, source = "custom") {
|
|
|
2903
2950
|
category: fm.category || "Development Tools",
|
|
2904
2951
|
tags: fm.tags || [],
|
|
2905
2952
|
...fm.kind ? { kind: fm.kind } : {},
|
|
2953
|
+
...isHostedMetadataSkillDir(join7(dir, entry.name)) ? { serverOwned: true } : {},
|
|
2906
2954
|
source
|
|
2907
2955
|
});
|
|
2908
2956
|
}
|
|
@@ -2911,20 +2959,20 @@ function discoverSkillsInDir(dir, source = "custom") {
|
|
|
2911
2959
|
}
|
|
2912
2960
|
function findExtensionSkillPath(name) {
|
|
2913
2961
|
const config = loadConfig();
|
|
2914
|
-
if (!config.extensionsDir || !
|
|
2962
|
+
if (!config.extensionsDir || !existsSync7(config.extensionsDir))
|
|
2915
2963
|
return null;
|
|
2916
2964
|
try {
|
|
2917
|
-
const entries =
|
|
2965
|
+
const entries = readdirSync6(config.extensionsDir, { withFileTypes: true });
|
|
2918
2966
|
for (const entry of entries) {
|
|
2919
2967
|
if (!entry.isDirectory())
|
|
2920
2968
|
continue;
|
|
2921
|
-
const skillDir =
|
|
2922
|
-
const skillMdPath =
|
|
2923
|
-
if (!
|
|
2969
|
+
const skillDir = join7(config.extensionsDir, entry.name);
|
|
2970
|
+
const skillMdPath = join7(skillDir, "SKILL.md");
|
|
2971
|
+
if (!existsSync7(skillMdPath))
|
|
2924
2972
|
continue;
|
|
2925
2973
|
let content;
|
|
2926
2974
|
try {
|
|
2927
|
-
content =
|
|
2975
|
+
content = readFileSync6(skillMdPath, "utf-8");
|
|
2928
2976
|
} catch {
|
|
2929
2977
|
continue;
|
|
2930
2978
|
}
|
|
@@ -2957,7 +3005,7 @@ function loadRegistry(cwd) {
|
|
|
2957
3005
|
const official = SKILLS.map((s) => ({ ...s, source: "official" }));
|
|
2958
3006
|
const extensions = config.extensionsDir ? discoverSkillsInDir(config.extensionsDir, "extension") : [];
|
|
2959
3007
|
const portableCustom = listPortableSkillMetas();
|
|
2960
|
-
const legacyCustom = discoverSkillsInDir(
|
|
3008
|
+
const legacyCustom = discoverSkillsInDir(join7(dataDir, "custom"));
|
|
2961
3009
|
const globalCustom = mergeCustomSkills([...legacyCustom, ...portableCustom]);
|
|
2962
3010
|
registryCache = mergeSkillRegistryLists(official, extensions, globalCustom);
|
|
2963
3011
|
registryCacheTime = now;
|
|
@@ -3004,26 +3052,26 @@ function getAllTags() {
|
|
|
3004
3052
|
return Array.from(tagSet).sort();
|
|
3005
3053
|
}
|
|
3006
3054
|
// src/lib/installer.ts
|
|
3007
|
-
import { existsSync as
|
|
3008
|
-
import { dirname as dirname5, join as
|
|
3055
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9, rmSync as rmSync3 } from "fs";
|
|
3056
|
+
import { dirname as dirname5, join as join10 } from "path";
|
|
3009
3057
|
import { homedir as homedir3 } from "os";
|
|
3010
3058
|
import { fileURLToPath } from "url";
|
|
3011
3059
|
|
|
3012
3060
|
// src/lib/agent-sync.ts
|
|
3013
3061
|
import {
|
|
3014
3062
|
cpSync as cpSync3,
|
|
3015
|
-
existsSync as
|
|
3063
|
+
existsSync as existsSync8,
|
|
3016
3064
|
mkdirSync as mkdirSync4,
|
|
3017
|
-
mkdtempSync,
|
|
3018
|
-
readFileSync as
|
|
3019
|
-
readdirSync as
|
|
3065
|
+
mkdtempSync as mkdtempSync2,
|
|
3066
|
+
readFileSync as readFileSync7,
|
|
3067
|
+
readdirSync as readdirSync7,
|
|
3020
3068
|
renameSync as renameSync2,
|
|
3021
3069
|
rmSync as rmSync2,
|
|
3022
|
-
statSync as
|
|
3070
|
+
statSync as statSync7,
|
|
3023
3071
|
writeFileSync as writeFileSync4
|
|
3024
3072
|
} from "fs";
|
|
3025
3073
|
import { homedir as homedir2 } from "os";
|
|
3026
|
-
import { basename as basename3, dirname as dirname4, join as
|
|
3074
|
+
import { basename as basename3, dirname as dirname4, join as join8 } from "path";
|
|
3027
3075
|
// src/lib/home-migration.ts
|
|
3028
3076
|
function resolveCorpusRoot(options = {}) {
|
|
3029
3077
|
return getPortableSkillsRoot(options);
|
|
@@ -3048,9 +3096,9 @@ function resolveSyncAgents(arg) {
|
|
|
3048
3096
|
function agentGlobalSkillsDir(agent, homeDir = homedir2()) {
|
|
3049
3097
|
switch (agent) {
|
|
3050
3098
|
case "opencode":
|
|
3051
|
-
return
|
|
3099
|
+
return join8(homeDir, ".config", "opencode", "skills");
|
|
3052
3100
|
default:
|
|
3053
|
-
return
|
|
3101
|
+
return join8(homeDir, `.${agent}`, "skills");
|
|
3054
3102
|
}
|
|
3055
3103
|
}
|
|
3056
3104
|
function adaptSkillMdForAgent(skillMd, agent) {
|
|
@@ -3109,8 +3157,8 @@ function resolveSyncCorpus(options = {}) {
|
|
|
3109
3157
|
function packageSourceRoots(source) {
|
|
3110
3158
|
const roots = [];
|
|
3111
3159
|
for (const sub of ["skills"]) {
|
|
3112
|
-
const candidate =
|
|
3113
|
-
if (
|
|
3160
|
+
const candidate = join8(source, sub);
|
|
3161
|
+
if (existsSync8(candidate) && isDirectory(candidate))
|
|
3114
3162
|
roots.push(candidate);
|
|
3115
3163
|
}
|
|
3116
3164
|
if (roots.length > 0)
|
|
@@ -3120,20 +3168,20 @@ function packageSourceRoots(source) {
|
|
|
3120
3168
|
function containsSkillDirectories(path) {
|
|
3121
3169
|
let entries;
|
|
3122
3170
|
try {
|
|
3123
|
-
entries =
|
|
3171
|
+
entries = readdirSync7(path);
|
|
3124
3172
|
} catch {
|
|
3125
3173
|
return false;
|
|
3126
3174
|
}
|
|
3127
3175
|
return entries.some((entry) => {
|
|
3128
|
-
const candidate =
|
|
3176
|
+
const candidate = join8(path, entry);
|
|
3129
3177
|
if (!isDirectory(candidate))
|
|
3130
3178
|
return false;
|
|
3131
|
-
return
|
|
3179
|
+
return existsSync8(join8(candidate, "SKILL.md")) || existsSync8(join8(candidate, "skill.json")) || existsSync8(join8(candidate, "package.json"));
|
|
3132
3180
|
});
|
|
3133
3181
|
}
|
|
3134
3182
|
function isDirectory(path) {
|
|
3135
3183
|
try {
|
|
3136
|
-
return
|
|
3184
|
+
return statSync7(path).isDirectory();
|
|
3137
3185
|
} catch {
|
|
3138
3186
|
return false;
|
|
3139
3187
|
}
|
|
@@ -3167,7 +3215,7 @@ function syncSkillsToAgents(options = {}) {
|
|
|
3167
3215
|
actions.push({
|
|
3168
3216
|
skill: name,
|
|
3169
3217
|
agent,
|
|
3170
|
-
path:
|
|
3218
|
+
path: join8(agentGlobalSkillsDir(agent, homeDir), name, "SKILL.md"),
|
|
3171
3219
|
action: "skip",
|
|
3172
3220
|
reason: "not found in this machine's corpus"
|
|
3173
3221
|
});
|
|
@@ -3177,7 +3225,7 @@ function syncSkillsToAgents(options = {}) {
|
|
|
3177
3225
|
}
|
|
3178
3226
|
for (const skill of targets) {
|
|
3179
3227
|
const manifest = readPortableSkillManifest(skill.path, skill.name);
|
|
3180
|
-
const kind = manifest.kind
|
|
3228
|
+
const kind = manifest.kind;
|
|
3181
3229
|
const sourceMd = sourceSkillMd(skill.path, skill.name, manifest.description, kind, source === "source");
|
|
3182
3230
|
for (const agent of agents) {
|
|
3183
3231
|
const adapted = adaptSkillMdForAgent(sourceMd, agent);
|
|
@@ -3197,7 +3245,7 @@ function syncSkillsToAgents(options = {}) {
|
|
|
3197
3245
|
}
|
|
3198
3246
|
function writeManagedAgentSkill(params) {
|
|
3199
3247
|
const homeDir = params.homeDir ?? homedir2();
|
|
3200
|
-
const dir =
|
|
3248
|
+
const dir = join8(agentGlobalSkillsDir(params.agent, homeDir), params.skill);
|
|
3201
3249
|
const result = writeManagedSkillDir(dir, params.skillMd, {
|
|
3202
3250
|
skill: params.skill,
|
|
3203
3251
|
source: params.source,
|
|
@@ -3214,11 +3262,11 @@ function writeManagedAgentSkill(params) {
|
|
|
3214
3262
|
};
|
|
3215
3263
|
}
|
|
3216
3264
|
function writeManagedSkillDir(dir, skillMd, options) {
|
|
3217
|
-
const skillMdPath =
|
|
3218
|
-
const markerPath =
|
|
3219
|
-
const dirExists =
|
|
3220
|
-
const managed =
|
|
3221
|
-
const hasSkillMd =
|
|
3265
|
+
const skillMdPath = join8(dir, "SKILL.md");
|
|
3266
|
+
const markerPath = join8(dir, SYNC_MARKER_FILE);
|
|
3267
|
+
const dirExists = existsSync8(dir);
|
|
3268
|
+
const managed = existsSync8(markerPath);
|
|
3269
|
+
const hasSkillMd = existsSync8(skillMdPath);
|
|
3222
3270
|
if (dirExists && !managed && !hasSkillMd) {
|
|
3223
3271
|
return {
|
|
3224
3272
|
action: "skip",
|
|
@@ -3236,7 +3284,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
3236
3284
|
if (dirExists && managed && hasSkillMd && !options.force && isPointerSkillMd(skillMd)) {
|
|
3237
3285
|
let existingIsStub = false;
|
|
3238
3286
|
try {
|
|
3239
|
-
existingIsStub = isPointerSkillMd(
|
|
3287
|
+
existingIsStub = isPointerSkillMd(readFileSync7(skillMdPath, "utf-8"));
|
|
3240
3288
|
} catch {
|
|
3241
3289
|
existingIsStub = false;
|
|
3242
3290
|
}
|
|
@@ -3253,11 +3301,11 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
3253
3301
|
return { action, path: skillMdPath };
|
|
3254
3302
|
const parentDir = dirname4(dir);
|
|
3255
3303
|
mkdirSync4(parentDir, { recursive: true });
|
|
3256
|
-
const transactionDir =
|
|
3257
|
-
const candidateDir =
|
|
3258
|
-
const backupDir =
|
|
3259
|
-
const candidateSkillMdPath =
|
|
3260
|
-
const candidateMarkerPath =
|
|
3304
|
+
const transactionDir = mkdtempSync2(join8(parentDir, `.hasna-skills-write-${basename3(dir)}-`));
|
|
3305
|
+
const candidateDir = join8(transactionDir, "candidate");
|
|
3306
|
+
const backupDir = join8(transactionDir, "backup");
|
|
3307
|
+
const candidateSkillMdPath = join8(candidateDir, "SKILL.md");
|
|
3308
|
+
const candidateMarkerPath = join8(candidateDir, SYNC_MARKER_FILE);
|
|
3261
3309
|
const marker = {
|
|
3262
3310
|
managedBy: SYNC_MARKER_MANAGED_BY,
|
|
3263
3311
|
skill: options.skill,
|
|
@@ -3284,9 +3332,9 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
3284
3332
|
}
|
|
3285
3333
|
renameDirectory(candidateDir, dir);
|
|
3286
3334
|
} catch (error) {
|
|
3287
|
-
if (originalMoved &&
|
|
3335
|
+
if (originalMoved && existsSync8(backupDir)) {
|
|
3288
3336
|
try {
|
|
3289
|
-
if (
|
|
3337
|
+
if (existsSync8(dir))
|
|
3290
3338
|
rmSync2(dir, { recursive: true, force: true });
|
|
3291
3339
|
renameDirectory(backupDir, dir);
|
|
3292
3340
|
originalMoved = false;
|
|
@@ -3306,17 +3354,17 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
3306
3354
|
return { action, path: skillMdPath };
|
|
3307
3355
|
}
|
|
3308
3356
|
function removeManagedAgentSkill(skill, agent, homeDir = homedir2()) {
|
|
3309
|
-
const dir =
|
|
3310
|
-
if (!
|
|
3357
|
+
const dir = join8(agentGlobalSkillsDir(agent, homeDir), skill);
|
|
3358
|
+
if (!existsSync8(join8(dir, SYNC_MARKER_FILE)))
|
|
3311
3359
|
return false;
|
|
3312
3360
|
rmSync2(dir, { recursive: true, force: true });
|
|
3313
3361
|
return true;
|
|
3314
3362
|
}
|
|
3315
3363
|
function sourceSkillMd(skillPath, name, description, kind, preferBundledDocs = false) {
|
|
3316
|
-
if (kind === "instruction" || preferBundledDocs) {
|
|
3317
|
-
const skillMdPath =
|
|
3318
|
-
if (
|
|
3319
|
-
return
|
|
3364
|
+
if (kind === undefined || kind === "instruction" || preferBundledDocs) {
|
|
3365
|
+
const skillMdPath = join8(skillPath, "SKILL.md");
|
|
3366
|
+
if (existsSync8(skillMdPath))
|
|
3367
|
+
return readFileSync7(skillMdPath, "utf-8");
|
|
3320
3368
|
}
|
|
3321
3369
|
return pointerSkillMd(name, description);
|
|
3322
3370
|
}
|
|
@@ -3346,8 +3394,8 @@ function normalizeSkillName(name) {
|
|
|
3346
3394
|
}
|
|
3347
3395
|
|
|
3348
3396
|
// src/lib/project-state.ts
|
|
3349
|
-
import { existsSync as
|
|
3350
|
-
import { join as
|
|
3397
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
3398
|
+
import { join as join9 } from "path";
|
|
3351
3399
|
var VALID_PIN_SOURCES = [
|
|
3352
3400
|
"official",
|
|
3353
3401
|
"custom",
|
|
@@ -3362,17 +3410,17 @@ var SKILLS_PROJECT_DIR = ".skills";
|
|
|
3362
3410
|
var PROJECT_CONFIG_FILE = "project.json";
|
|
3363
3411
|
var DEFAULT_EXPORT_DIR = ".skills/exports";
|
|
3364
3412
|
function getProjectStateDir(targetDir = process.cwd()) {
|
|
3365
|
-
return
|
|
3413
|
+
return join9(targetDir, SKILLS_PROJECT_DIR);
|
|
3366
3414
|
}
|
|
3367
3415
|
function getProjectConfigPath(targetDir = process.cwd()) {
|
|
3368
|
-
return
|
|
3416
|
+
return join9(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
|
|
3369
3417
|
}
|
|
3370
3418
|
function loadProjectConfig(targetDir = process.cwd()) {
|
|
3371
3419
|
const path = getProjectConfigPath(targetDir);
|
|
3372
|
-
if (!
|
|
3420
|
+
if (!existsSync9(path))
|
|
3373
3421
|
return null;
|
|
3374
3422
|
try {
|
|
3375
|
-
return normalizeProjectConfig(JSON.parse(
|
|
3423
|
+
return normalizeProjectConfig(JSON.parse(readFileSync8(path, "utf-8")));
|
|
3376
3424
|
} catch {
|
|
3377
3425
|
return null;
|
|
3378
3426
|
}
|
|
@@ -3490,12 +3538,12 @@ var __dirname2 = dirname5(fileURLToPath(import.meta.url));
|
|
|
3490
3538
|
function findSkillsDir() {
|
|
3491
3539
|
let dir = __dirname2;
|
|
3492
3540
|
for (let i = 0;i < 5; i++) {
|
|
3493
|
-
const candidate =
|
|
3494
|
-
if (
|
|
3541
|
+
const candidate = join10(dir, "skills");
|
|
3542
|
+
if (existsSync10(candidate) && !dir.includes(".skills"))
|
|
3495
3543
|
return candidate;
|
|
3496
3544
|
dir = dirname5(dir);
|
|
3497
3545
|
}
|
|
3498
|
-
return
|
|
3546
|
+
return join10(__dirname2, "..", "skills");
|
|
3499
3547
|
}
|
|
3500
3548
|
var SKILLS_DIR = findSkillsDir();
|
|
3501
3549
|
function getSkillPath(name) {
|
|
@@ -3503,25 +3551,25 @@ function getSkillPath(name) {
|
|
|
3503
3551
|
const portable = findPortableSkill(skillName);
|
|
3504
3552
|
if (portable)
|
|
3505
3553
|
return portable.path;
|
|
3506
|
-
const legacyCustomPath =
|
|
3507
|
-
if (
|
|
3554
|
+
const legacyCustomPath = join10(getDataDir(), "custom", skillName);
|
|
3555
|
+
if (existsSync10(legacyCustomPath))
|
|
3508
3556
|
return legacyCustomPath;
|
|
3509
3557
|
const extensionPath = findExtensionSkillPath(skillName);
|
|
3510
3558
|
if (extensionPath)
|
|
3511
3559
|
return extensionPath;
|
|
3512
|
-
return
|
|
3560
|
+
return join10(SKILLS_DIR, skillName);
|
|
3513
3561
|
}
|
|
3514
3562
|
function getCanonicalSkillName(name) {
|
|
3515
3563
|
return getSkill(name)?.name ?? resolveSkillAlias(normalizeSkillSlug(name));
|
|
3516
3564
|
}
|
|
3517
3565
|
function skillExists(name) {
|
|
3518
|
-
return
|
|
3566
|
+
return existsSync10(getSkillPath(name));
|
|
3519
3567
|
}
|
|
3520
3568
|
function installSkill(name, options = {}) {
|
|
3521
3569
|
const { targetDir = process.cwd(), overwrite = false } = options;
|
|
3522
3570
|
const canonicalName = getCanonicalSkillName(name);
|
|
3523
3571
|
const skillName = normalizeSkillName(canonicalName);
|
|
3524
|
-
if (!
|
|
3572
|
+
if (!existsSync10(getSkillPath(name))) {
|
|
3525
3573
|
const knownOfficial = Boolean(getSkill(name));
|
|
3526
3574
|
return {
|
|
3527
3575
|
skill: canonicalName,
|
|
@@ -3548,7 +3596,7 @@ function installSkill(name, options = {}) {
|
|
|
3548
3596
|
}
|
|
3549
3597
|
function installSkillSource(name, _options = {}) {
|
|
3550
3598
|
const canonicalName = getCanonicalSkillName(name);
|
|
3551
|
-
if (!
|
|
3599
|
+
if (!existsSync10(getSkillPath(name))) {
|
|
3552
3600
|
return { skill: canonicalName, success: false, error: `Skill '${name}' not found`, mode: "source" };
|
|
3553
3601
|
}
|
|
3554
3602
|
return {
|
|
@@ -3569,12 +3617,12 @@ function installSkillManifest(manifest, _options = {}) {
|
|
|
3569
3617
|
}
|
|
3570
3618
|
function createLocalSkillManifest(name, generateSkillMd) {
|
|
3571
3619
|
const sourcePath = getSkillPath(name);
|
|
3572
|
-
if (!
|
|
3620
|
+
if (!existsSync10(sourcePath))
|
|
3573
3621
|
return null;
|
|
3574
3622
|
let skillMd = "";
|
|
3575
|
-
const skillMdPath =
|
|
3576
|
-
if (
|
|
3577
|
-
skillMd =
|
|
3623
|
+
const skillMdPath = join10(sourcePath, "SKILL.md");
|
|
3624
|
+
if (existsSync10(skillMdPath)) {
|
|
3625
|
+
skillMd = readFileSync9(skillMdPath, "utf-8");
|
|
3578
3626
|
} else if (generateSkillMd) {
|
|
3579
3627
|
skillMd = generateSkillMd(name) ?? "";
|
|
3580
3628
|
} else {
|
|
@@ -3646,20 +3694,20 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
|
|
|
3646
3694
|
const base = projectDir || process.cwd();
|
|
3647
3695
|
switch (agent) {
|
|
3648
3696
|
case "pi":
|
|
3649
|
-
return scope === "project" ?
|
|
3697
|
+
return scope === "project" ? join10(base, ".pi", "skills") : join10(homedir3(), ".pi", "agent", "skills");
|
|
3650
3698
|
case "opencode":
|
|
3651
|
-
return scope === "project" ?
|
|
3699
|
+
return scope === "project" ? join10(base, ".opencode", "skills") : join10(homedir3(), ".config", "opencode", "skills");
|
|
3652
3700
|
default:
|
|
3653
|
-
return scope === "project" ?
|
|
3701
|
+
return scope === "project" ? join10(base, `.${agent}`, "skills") : join10(homedir3(), `.${agent}`, "skills");
|
|
3654
3702
|
}
|
|
3655
3703
|
}
|
|
3656
3704
|
function getAgentSkillPath(name, agent, scope = "global", projectDir) {
|
|
3657
3705
|
const skillName = normalizeSkillName(getCanonicalSkillName(name));
|
|
3658
|
-
return
|
|
3706
|
+
return join10(getAgentSkillsDir(agent, scope, projectDir), skillName);
|
|
3659
3707
|
}
|
|
3660
3708
|
function installSkillForAgent(name, options, generateSkillMd) {
|
|
3661
3709
|
const canonicalName = getCanonicalSkillName(name);
|
|
3662
|
-
if (!
|
|
3710
|
+
if (!existsSync10(getSkillPath(name))) {
|
|
3663
3711
|
return { skill: canonicalName, success: false, error: `Skill '${name}' not found` };
|
|
3664
3712
|
}
|
|
3665
3713
|
const scope = options.scope ?? "global";
|
|
@@ -3683,16 +3731,16 @@ function removeSkillForAgent(name, options) {
|
|
|
3683
3731
|
const canonicalName = getCanonicalSkillName(name);
|
|
3684
3732
|
const scope = options.scope ?? "global";
|
|
3685
3733
|
const dir = getAgentSkillPath(canonicalName, options.agent, scope, options.projectDir);
|
|
3686
|
-
if (!
|
|
3734
|
+
if (!existsSync10(join10(dir, SYNC_MARKER_FILE)))
|
|
3687
3735
|
return false;
|
|
3688
3736
|
rmSync3(dir, { recursive: true, force: true });
|
|
3689
3737
|
return true;
|
|
3690
3738
|
}
|
|
3691
3739
|
function resolveAgentSkillMd(name, generateSkillMd) {
|
|
3692
3740
|
const sourcePath = getSkillPath(name);
|
|
3693
|
-
const skillMdPath =
|
|
3694
|
-
if (
|
|
3695
|
-
return
|
|
3741
|
+
const skillMdPath = join10(sourcePath, "SKILL.md");
|
|
3742
|
+
if (existsSync10(skillMdPath))
|
|
3743
|
+
return readFileSync9(skillMdPath, "utf-8");
|
|
3696
3744
|
if (generateSkillMd)
|
|
3697
3745
|
return generateSkillMd(name);
|
|
3698
3746
|
return generateMinimalSkillMd(name);
|
|
@@ -3710,7 +3758,7 @@ function warnMissingDependencies(name, targetDir) {
|
|
|
3710
3758
|
}
|
|
3711
3759
|
function generateMinimalSkillMd(name) {
|
|
3712
3760
|
const sourcePath = getSkillPath(name);
|
|
3713
|
-
if (!
|
|
3761
|
+
if (!existsSync10(sourcePath))
|
|
3714
3762
|
return null;
|
|
3715
3763
|
const canonicalName = getCanonicalSkillName(name);
|
|
3716
3764
|
const meta = getSkill(canonicalName);
|
|
@@ -3725,7 +3773,7 @@ function generateMinimalSkillMd(name) {
|
|
|
3725
3773
|
"---",
|
|
3726
3774
|
""
|
|
3727
3775
|
].filter(Boolean);
|
|
3728
|
-
const fallbackDoc = readFileIfExists(
|
|
3776
|
+
const fallbackDoc = readFileIfExists(join10(sourcePath, "README.md")) || readFileIfExists(join10(sourcePath, "CLAUDE.md"));
|
|
3729
3777
|
if (fallbackDoc)
|
|
3730
3778
|
return `${frontmatter.join(`
|
|
3731
3779
|
`)}${fallbackDoc.trim()}
|
|
@@ -3744,38 +3792,38 @@ skills run ${canonicalName}
|
|
|
3744
3792
|
`;
|
|
3745
3793
|
}
|
|
3746
3794
|
function readBundledSkillVersion(name) {
|
|
3747
|
-
const pkgPath =
|
|
3748
|
-
if (!
|
|
3795
|
+
const pkgPath = join10(getSkillPath(name), "package.json");
|
|
3796
|
+
if (!existsSync10(pkgPath))
|
|
3749
3797
|
return "unknown";
|
|
3750
3798
|
try {
|
|
3751
|
-
const pkg = JSON.parse(
|
|
3799
|
+
const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
|
|
3752
3800
|
return pkg.version || "unknown";
|
|
3753
3801
|
} catch {
|
|
3754
3802
|
return "unknown";
|
|
3755
3803
|
}
|
|
3756
3804
|
}
|
|
3757
3805
|
function readFileIfExists(path) {
|
|
3758
|
-
return
|
|
3806
|
+
return existsSync10(path) ? readFileSync9(path, "utf-8") : null;
|
|
3759
3807
|
}
|
|
3760
3808
|
function loadProjectConfigCompat(targetDir) {
|
|
3761
3809
|
return loadProjectConfig(targetDir);
|
|
3762
3810
|
}
|
|
3763
3811
|
// src/lib/run-state.ts
|
|
3764
3812
|
import { createHash as createHash2, randomBytes } from "crypto";
|
|
3765
|
-
import { existsSync as
|
|
3766
|
-
import { extname, join as
|
|
3813
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync10, readdirSync as readdirSync8, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
3814
|
+
import { extname, join as join11, relative as relative2 } from "path";
|
|
3767
3815
|
function createSkillRun(params, targetDir = process.cwd()) {
|
|
3768
3816
|
const now = new Date;
|
|
3769
3817
|
const id = createRunId(now);
|
|
3770
3818
|
const day = now.toISOString().slice(0, 10);
|
|
3771
3819
|
const skillName = normalizeSkillName(params.skill);
|
|
3772
3820
|
const root = getProjectStateDir(targetDir);
|
|
3773
|
-
const runDir =
|
|
3774
|
-
const logsDir =
|
|
3775
|
-
const exportDir =
|
|
3821
|
+
const runDir = join11(root, "runs", day, id);
|
|
3822
|
+
const logsDir = join11(runDir, "logs");
|
|
3823
|
+
const exportDir = join11(root, "exports", skillName, id);
|
|
3776
3824
|
mkdirSync6(logsDir, { recursive: true });
|
|
3777
3825
|
mkdirSync6(exportDir, { recursive: true });
|
|
3778
|
-
mkdirSync6(
|
|
3826
|
+
mkdirSync6(join11(root, "tmp"), { recursive: true });
|
|
3779
3827
|
const record = {
|
|
3780
3828
|
id,
|
|
3781
3829
|
skill: skillName,
|
|
@@ -3826,27 +3874,27 @@ function updateSkillRun(context, patch) {
|
|
|
3826
3874
|
return context.record;
|
|
3827
3875
|
}
|
|
3828
3876
|
function writeRunLogs(context, stdout = "", stderr = "") {
|
|
3829
|
-
writeFileSync6(
|
|
3830
|
-
writeFileSync6(
|
|
3877
|
+
writeFileSync6(join11(context.logsDir, "stdout.log"), stdout);
|
|
3878
|
+
writeFileSync6(join11(context.logsDir, "stderr.log"), stderr);
|
|
3831
3879
|
}
|
|
3832
3880
|
function appendRunEvent(context, event, data = {}) {
|
|
3833
3881
|
const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
|
|
3834
3882
|
`;
|
|
3835
|
-
const path =
|
|
3836
|
-
const previous =
|
|
3883
|
+
const path = join11(context.runDir, "events.ndjson");
|
|
3884
|
+
const previous = existsSync11(path) ? readFileSync10(path, "utf-8") : "";
|
|
3837
3885
|
writeFileSync6(path, previous + line);
|
|
3838
3886
|
}
|
|
3839
3887
|
function listSkillRuns(targetDir = process.cwd(), limit = 50) {
|
|
3840
|
-
const runsRoot =
|
|
3841
|
-
if (!
|
|
3888
|
+
const runsRoot = join11(getProjectStateDir(targetDir), "runs");
|
|
3889
|
+
if (!existsSync11(runsRoot))
|
|
3842
3890
|
return [];
|
|
3843
3891
|
const records = [];
|
|
3844
|
-
for (const day of
|
|
3845
|
-
const dayDir =
|
|
3846
|
-
if (!
|
|
3892
|
+
for (const day of readdirSync8(runsRoot).sort().reverse()) {
|
|
3893
|
+
const dayDir = join11(runsRoot, day);
|
|
3894
|
+
if (!statSync8(dayDir).isDirectory())
|
|
3847
3895
|
continue;
|
|
3848
|
-
for (const runId of
|
|
3849
|
-
const record = readRunRecord(
|
|
3896
|
+
for (const runId of readdirSync8(dayDir).sort().reverse()) {
|
|
3897
|
+
const record = readRunRecord(join11(dayDir, runId));
|
|
3850
3898
|
if (record)
|
|
3851
3899
|
records.push(record);
|
|
3852
3900
|
if (records.length >= limit)
|
|
@@ -3856,34 +3904,34 @@ function listSkillRuns(targetDir = process.cwd(), limit = 50) {
|
|
|
3856
3904
|
return records;
|
|
3857
3905
|
}
|
|
3858
3906
|
function findSkillRun(runId, targetDir = process.cwd()) {
|
|
3859
|
-
const runsRoot =
|
|
3860
|
-
if (!
|
|
3907
|
+
const runsRoot = join11(getProjectStateDir(targetDir), "runs");
|
|
3908
|
+
if (!existsSync11(runsRoot))
|
|
3861
3909
|
return null;
|
|
3862
|
-
for (const day of
|
|
3863
|
-
const record = readRunRecord(
|
|
3910
|
+
for (const day of readdirSync8(runsRoot)) {
|
|
3911
|
+
const record = readRunRecord(join11(runsRoot, day, runId));
|
|
3864
3912
|
if (record)
|
|
3865
3913
|
return record;
|
|
3866
3914
|
}
|
|
3867
3915
|
return null;
|
|
3868
3916
|
}
|
|
3869
3917
|
function getRunExportDir(runId, skill, targetDir = process.cwd()) {
|
|
3870
|
-
return
|
|
3918
|
+
return join11(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
|
|
3871
3919
|
}
|
|
3872
3920
|
function writeRunRecord(context) {
|
|
3873
|
-
writeFileSync6(
|
|
3921
|
+
writeFileSync6(join11(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
|
|
3874
3922
|
`);
|
|
3875
3923
|
}
|
|
3876
3924
|
function writeArtifactsManifest(context, artifacts) {
|
|
3877
|
-
writeFileSync6(
|
|
3925
|
+
writeFileSync6(join11(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
|
|
3878
3926
|
`);
|
|
3879
3927
|
}
|
|
3880
3928
|
function collectRunArtifacts(context) {
|
|
3881
|
-
if (!
|
|
3929
|
+
if (!existsSync11(context.exportDir))
|
|
3882
3930
|
return [];
|
|
3883
3931
|
const artifacts = [];
|
|
3884
3932
|
for (const path of walkFiles(context.exportDir)) {
|
|
3885
|
-
const stat =
|
|
3886
|
-
const bytes =
|
|
3933
|
+
const stat = statSync8(path);
|
|
3934
|
+
const bytes = readFileSync10(path);
|
|
3887
3935
|
artifacts.push({
|
|
3888
3936
|
path: toProjectRelative(context.targetDir, path),
|
|
3889
3937
|
mime: mimeForPath(path),
|
|
@@ -3894,20 +3942,20 @@ function collectRunArtifacts(context) {
|
|
|
3894
3942
|
return artifacts.sort((a, b) => a.path.localeCompare(b.path));
|
|
3895
3943
|
}
|
|
3896
3944
|
function readRunRecord(runDir) {
|
|
3897
|
-
const path =
|
|
3898
|
-
if (!
|
|
3945
|
+
const path = join11(runDir, "run.json");
|
|
3946
|
+
if (!existsSync11(path))
|
|
3899
3947
|
return null;
|
|
3900
3948
|
try {
|
|
3901
|
-
return JSON.parse(
|
|
3949
|
+
return JSON.parse(readFileSync10(path, "utf-8"));
|
|
3902
3950
|
} catch {
|
|
3903
3951
|
return null;
|
|
3904
3952
|
}
|
|
3905
3953
|
}
|
|
3906
3954
|
function walkFiles(dir) {
|
|
3907
3955
|
const files = [];
|
|
3908
|
-
for (const entry of
|
|
3909
|
-
const full =
|
|
3910
|
-
if (
|
|
3956
|
+
for (const entry of readdirSync8(dir)) {
|
|
3957
|
+
const full = join11(dir, entry);
|
|
3958
|
+
if (statSync8(full).isDirectory())
|
|
3911
3959
|
files.push(...walkFiles(full));
|
|
3912
3960
|
else
|
|
3913
3961
|
files.push(full);
|
|
@@ -3952,16 +4000,16 @@ function mimeForPath(path) {
|
|
|
3952
4000
|
}
|
|
3953
4001
|
}
|
|
3954
4002
|
// src/lib/skillinfo.ts
|
|
3955
|
-
import { existsSync as
|
|
3956
|
-
import { join as
|
|
4003
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
4004
|
+
import { join as join12 } from "path";
|
|
3957
4005
|
function isInstructionSkillDir(skillPath, meta) {
|
|
3958
4006
|
if (meta?.kind === "instruction")
|
|
3959
4007
|
return true;
|
|
3960
|
-
const skillMdPath =
|
|
3961
|
-
if (!
|
|
4008
|
+
const skillMdPath = join12(skillPath, "SKILL.md");
|
|
4009
|
+
if (!existsSync12(skillMdPath))
|
|
3962
4010
|
return false;
|
|
3963
4011
|
try {
|
|
3964
|
-
return parseSkillFrontmatter(
|
|
4012
|
+
return parseSkillFrontmatter(readFileSync11(skillMdPath, "utf-8"))?.kind === "instruction";
|
|
3965
4013
|
} catch {
|
|
3966
4014
|
return false;
|
|
3967
4015
|
}
|
|
@@ -3984,12 +4032,12 @@ var HOSTED_PROVIDER_ENV_PREFIXES = [
|
|
|
3984
4032
|
];
|
|
3985
4033
|
function getSkillDocs(name) {
|
|
3986
4034
|
const skillPath = getSkillPath(name);
|
|
3987
|
-
if (!
|
|
4035
|
+
if (!existsSync12(skillPath))
|
|
3988
4036
|
return null;
|
|
3989
4037
|
return {
|
|
3990
|
-
skillMd: readIfExists(
|
|
3991
|
-
readme: readIfExists(
|
|
3992
|
-
claudeMd: readIfExists(
|
|
4038
|
+
skillMd: readIfExists(join12(skillPath, "SKILL.md")),
|
|
4039
|
+
readme: readIfExists(join12(skillPath, "README.md")),
|
|
4040
|
+
claudeMd: readIfExists(join12(skillPath, "CLAUDE.md"))
|
|
3993
4041
|
};
|
|
3994
4042
|
}
|
|
3995
4043
|
function getSkillBestDoc(name) {
|
|
@@ -4000,11 +4048,11 @@ function getSkillBestDoc(name) {
|
|
|
4000
4048
|
}
|
|
4001
4049
|
function getSkillRequirements(name) {
|
|
4002
4050
|
const skillPath = getSkillPath(name);
|
|
4003
|
-
if (!
|
|
4051
|
+
if (!existsSync12(skillPath))
|
|
4004
4052
|
return null;
|
|
4005
4053
|
const texts = [];
|
|
4006
4054
|
for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
|
|
4007
|
-
const content = readIfExists(
|
|
4055
|
+
const content = readIfExists(join12(skillPath, file));
|
|
4008
4056
|
if (content)
|
|
4009
4057
|
texts.push(content);
|
|
4010
4058
|
}
|
|
@@ -4043,10 +4091,10 @@ function getSkillRequirements(name) {
|
|
|
4043
4091
|
const skillName = normalizeSkillName(name);
|
|
4044
4092
|
let cliCommand = `skills run ${skillName}`;
|
|
4045
4093
|
let dependencies = {};
|
|
4046
|
-
const pkgPath =
|
|
4047
|
-
if (
|
|
4094
|
+
const pkgPath = join12(skillPath, "package.json");
|
|
4095
|
+
if (existsSync12(pkgPath)) {
|
|
4048
4096
|
try {
|
|
4049
|
-
const pkg = JSON.parse(
|
|
4097
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
4050
4098
|
dependencies = pkg.dependencies || {};
|
|
4051
4099
|
} catch {}
|
|
4052
4100
|
}
|
|
@@ -4064,7 +4112,7 @@ async function runSkill(name, args, options = {}) {
|
|
|
4064
4112
|
const meta = getSkill(name);
|
|
4065
4113
|
const canonicalName = meta?.name ?? name;
|
|
4066
4114
|
const skillPath = getSkillPath(canonicalName);
|
|
4067
|
-
if (!
|
|
4115
|
+
if (!existsSync12(skillPath)) {
|
|
4068
4116
|
return { exitCode: 1, error: `Skill '${name}' not found` };
|
|
4069
4117
|
}
|
|
4070
4118
|
if (isInstructionSkillDir(skillPath, meta)) {
|
|
@@ -4073,13 +4121,13 @@ async function runSkill(name, args, options = {}) {
|
|
|
4073
4121
|
error: `Skill '${name}' is an instruction skill (kind: instruction) and is not runnable. Instruction skills are consumed by coding agents via SKILL.md, not executed with 'skills run'.`
|
|
4074
4122
|
};
|
|
4075
4123
|
}
|
|
4076
|
-
const pkgPath =
|
|
4077
|
-
if (!
|
|
4124
|
+
const pkgPath = join12(skillPath, "package.json");
|
|
4125
|
+
if (!existsSync12(pkgPath)) {
|
|
4078
4126
|
return { exitCode: 1, error: `No package.json in skill '${name}'` };
|
|
4079
4127
|
}
|
|
4080
4128
|
let entryPoint;
|
|
4081
4129
|
try {
|
|
4082
|
-
const pkg = JSON.parse(
|
|
4130
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
4083
4131
|
if (pkg.bin) {
|
|
4084
4132
|
const binValues = Object.values(pkg.bin);
|
|
4085
4133
|
entryPoint = binValues[0];
|
|
@@ -4093,12 +4141,12 @@ async function runSkill(name, args, options = {}) {
|
|
|
4093
4141
|
} catch {
|
|
4094
4142
|
return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
|
|
4095
4143
|
}
|
|
4096
|
-
const entryPath =
|
|
4097
|
-
if (!
|
|
4144
|
+
const entryPath = join12(skillPath, entryPoint);
|
|
4145
|
+
if (!existsSync12(entryPath)) {
|
|
4098
4146
|
return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
|
|
4099
4147
|
}
|
|
4100
|
-
const nodeModules =
|
|
4101
|
-
if (!
|
|
4148
|
+
const nodeModules = join12(skillPath, "node_modules");
|
|
4149
|
+
if (!existsSync12(nodeModules)) {
|
|
4102
4150
|
const install = Bun.spawn(["bun", "install", "--no-save"], {
|
|
4103
4151
|
cwd: skillPath,
|
|
4104
4152
|
stdout: "pipe",
|
|
@@ -4170,7 +4218,7 @@ function generateSkillMd(name) {
|
|
|
4170
4218
|
if (!meta)
|
|
4171
4219
|
return null;
|
|
4172
4220
|
const skillPath = getSkillPath(name);
|
|
4173
|
-
if (!
|
|
4221
|
+
if (!existsSync12(skillPath))
|
|
4174
4222
|
return null;
|
|
4175
4223
|
const frontmatter = [
|
|
4176
4224
|
"---",
|
|
@@ -4179,13 +4227,13 @@ function generateSkillMd(name) {
|
|
|
4179
4227
|
"---"
|
|
4180
4228
|
].join(`
|
|
4181
4229
|
`);
|
|
4182
|
-
const readme = readIfExists(
|
|
4183
|
-
const claudeMd = readIfExists(
|
|
4230
|
+
const readme = readIfExists(join12(skillPath, "README.md"));
|
|
4231
|
+
const claudeMd = readIfExists(join12(skillPath, "CLAUDE.md"));
|
|
4184
4232
|
let cliCommand = null;
|
|
4185
|
-
const pkgPath =
|
|
4186
|
-
if (
|
|
4233
|
+
const pkgPath = join12(skillPath, "package.json");
|
|
4234
|
+
if (existsSync12(pkgPath)) {
|
|
4187
4235
|
try {
|
|
4188
|
-
const pkg = JSON.parse(
|
|
4236
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
4189
4237
|
if (pkg.bin) {
|
|
4190
4238
|
const binKeys = Object.keys(pkg.bin);
|
|
4191
4239
|
if (binKeys.length > 0)
|
|
@@ -4260,8 +4308,8 @@ function extractEnvVars(text) {
|
|
|
4260
4308
|
}
|
|
4261
4309
|
function readIfExists(path) {
|
|
4262
4310
|
try {
|
|
4263
|
-
if (
|
|
4264
|
-
return
|
|
4311
|
+
if (existsSync12(path)) {
|
|
4312
|
+
return readFileSync11(path, "utf-8");
|
|
4265
4313
|
}
|
|
4266
4314
|
} catch {}
|
|
4267
4315
|
return null;
|
|
@@ -8264,22 +8312,22 @@ function requireApiUrl(action = "This command", config, env) {
|
|
|
8264
8312
|
}
|
|
8265
8313
|
|
|
8266
8314
|
// src/lib/auth-store.ts
|
|
8267
|
-
import { existsSync as
|
|
8268
|
-
import { dirname as dirname6, join as
|
|
8315
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync7, unlinkSync } from "fs";
|
|
8316
|
+
import { dirname as dirname6, join as join13 } from "path";
|
|
8269
8317
|
import { homedir as homedir4 } from "os";
|
|
8270
8318
|
function getAuthFilePath() {
|
|
8271
|
-
return
|
|
8319
|
+
return join13(getDataDir(), "auth.json");
|
|
8272
8320
|
}
|
|
8273
8321
|
function legacyAuthFilePath() {
|
|
8274
|
-
return
|
|
8322
|
+
return join13(process.env["HOME"] || process.env["USERPROFILE"] || homedir4(), ".skills", "auth.json");
|
|
8275
8323
|
}
|
|
8276
8324
|
var cachedConfig;
|
|
8277
8325
|
function getAuthConfig() {
|
|
8278
8326
|
if (cachedConfig !== undefined)
|
|
8279
8327
|
return cachedConfig;
|
|
8280
8328
|
try {
|
|
8281
|
-
const file =
|
|
8282
|
-
const raw =
|
|
8329
|
+
const file = existsSync13(getAuthFilePath()) ? getAuthFilePath() : legacyAuthFilePath();
|
|
8330
|
+
const raw = readFileSync12(file, "utf-8");
|
|
8283
8331
|
const config = JSON.parse(raw);
|
|
8284
8332
|
if (!config.apiKey) {
|
|
8285
8333
|
cachedConfig = null;
|
|
@@ -9379,24 +9427,24 @@ function createRemoteSkillsClient() {
|
|
|
9379
9427
|
return new RemoteSkillsClient(apiKey);
|
|
9380
9428
|
}
|
|
9381
9429
|
// src/lib/scheduler.ts
|
|
9382
|
-
import { existsSync as
|
|
9383
|
-
import { join as
|
|
9430
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8 } from "fs";
|
|
9431
|
+
import { join as join14 } from "path";
|
|
9384
9432
|
function getSchedulesPath(targetDir = process.cwd()) {
|
|
9385
|
-
return
|
|
9433
|
+
return join14(targetDir, ".skills", "schedules.json");
|
|
9386
9434
|
}
|
|
9387
9435
|
function loadSchedules(targetDir = process.cwd()) {
|
|
9388
9436
|
const path = getSchedulesPath(targetDir);
|
|
9389
|
-
if (
|
|
9437
|
+
if (existsSync14(path)) {
|
|
9390
9438
|
try {
|
|
9391
|
-
return JSON.parse(
|
|
9439
|
+
return JSON.parse(readFileSync13(path, "utf-8"));
|
|
9392
9440
|
} catch {}
|
|
9393
9441
|
}
|
|
9394
9442
|
return { version: 1, schedules: [] };
|
|
9395
9443
|
}
|
|
9396
9444
|
function saveSchedules(data, targetDir = process.cwd()) {
|
|
9397
9445
|
const path = getSchedulesPath(targetDir);
|
|
9398
|
-
const dir =
|
|
9399
|
-
if (!
|
|
9446
|
+
const dir = join14(targetDir, ".skills");
|
|
9447
|
+
if (!existsSync14(dir))
|
|
9400
9448
|
mkdirSync8(dir, { recursive: true });
|
|
9401
9449
|
writeFileSync8(path, JSON.stringify(data, null, 2));
|
|
9402
9450
|
}
|
|
@@ -9583,8 +9631,8 @@ function recordScheduleRun(id, status, targetDir) {
|
|
|
9583
9631
|
saveSchedules(data, targetDir);
|
|
9584
9632
|
}
|
|
9585
9633
|
// src/lib/pull.ts
|
|
9586
|
-
import { existsSync as
|
|
9587
|
-
import { dirname as dirname7, join as
|
|
9634
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync3, readFileSync as readFileSync14, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
|
|
9635
|
+
import { dirname as dirname7, join as join15 } from "path";
|
|
9588
9636
|
|
|
9589
9637
|
// src/lib/revision.ts
|
|
9590
9638
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -9875,7 +9923,7 @@ async function pullOne(client, rawName, corpusOptions, verify) {
|
|
|
9875
9923
|
return reconcileTombstone(slug, corpusOptions);
|
|
9876
9924
|
}
|
|
9877
9925
|
if (bundleResponse.status === 404) {
|
|
9878
|
-
const marker = readPullMarker(
|
|
9926
|
+
const marker = readPullMarker(join15(getPortableSkillsRoot(corpusOptions), slug));
|
|
9879
9927
|
if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
|
|
9880
9928
|
return { name: slug, success: true, purged: true, removed: false };
|
|
9881
9929
|
}
|
|
@@ -9906,7 +9954,7 @@ async function pullOne(client, rawName, corpusOptions, verify) {
|
|
|
9906
9954
|
return { name: slug, success: false, error: `Skill '${slug}' was not found on the configured Skills instance.` };
|
|
9907
9955
|
}
|
|
9908
9956
|
if (!meta?.revisionId) {
|
|
9909
|
-
const marker = readPullMarker(
|
|
9957
|
+
const marker = readPullMarker(join15(getPortableSkillsRoot(corpusOptions), slug));
|
|
9910
9958
|
if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
|
|
9911
9959
|
return { name: slug, success: true, purged: true, removed: false };
|
|
9912
9960
|
}
|
|
@@ -9920,7 +9968,7 @@ async function pullOne(client, rawName, corpusOptions, verify) {
|
|
|
9920
9968
|
}
|
|
9921
9969
|
throw error;
|
|
9922
9970
|
}
|
|
9923
|
-
const written =
|
|
9971
|
+
const written = installCorpusSkillAtomically({ name: slug, skillMd, meta }, corpusOptions);
|
|
9924
9972
|
writePullMarker(written.path, {
|
|
9925
9973
|
skill: slug,
|
|
9926
9974
|
version: written.manifest.version,
|
|
@@ -9963,8 +10011,8 @@ function provenRevision(meta, slug, bundle) {
|
|
|
9963
10011
|
return declared;
|
|
9964
10012
|
}
|
|
9965
10013
|
function reconcileTombstone(slug, corpusOptions) {
|
|
9966
|
-
const target =
|
|
9967
|
-
if (!
|
|
10014
|
+
const target = join15(getPortableSkillsRoot(corpusOptions), slug);
|
|
10015
|
+
if (!existsSync15(join15(target, PULL_MARKER_FILE))) {
|
|
9968
10016
|
return { name: slug, success: true, tombstoned: true, removed: false, leftInPlace: true };
|
|
9969
10017
|
}
|
|
9970
10018
|
rmSync4(target, { recursive: true, force: true });
|
|
@@ -9972,7 +10020,7 @@ function reconcileTombstone(slug, corpusOptions) {
|
|
|
9972
10020
|
}
|
|
9973
10021
|
function readPullMarker(dir) {
|
|
9974
10022
|
try {
|
|
9975
|
-
return JSON.parse(
|
|
10023
|
+
return JSON.parse(readFileSync14(join15(dir, PULL_MARKER_FILE), "utf-8"));
|
|
9976
10024
|
} catch {
|
|
9977
10025
|
return null;
|
|
9978
10026
|
}
|
|
@@ -10093,14 +10141,14 @@ function verifyBundleResponseBytes(buffer, response, verify = {}) {
|
|
|
10093
10141
|
function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
10094
10142
|
const root = getPortableSkillsRoot(options);
|
|
10095
10143
|
mkdirSync9(root, { recursive: true });
|
|
10096
|
-
const target =
|
|
10097
|
-
const created = !
|
|
10098
|
-
const staging =
|
|
10144
|
+
const target = join15(root, name);
|
|
10145
|
+
const created = !existsSync15(target);
|
|
10146
|
+
const staging = mkdtempSync3(join15(root, `.pull-${name}-`));
|
|
10099
10147
|
let moved = false;
|
|
10100
10148
|
let backup = null;
|
|
10101
10149
|
try {
|
|
10102
10150
|
for (const entry of entries) {
|
|
10103
|
-
const destination =
|
|
10151
|
+
const destination = join15(staging, entry.path);
|
|
10104
10152
|
mkdirSync9(dirname7(destination), { recursive: true });
|
|
10105
10153
|
writeFileSync9(destination, entry.bytes, { mode: entry.mode });
|
|
10106
10154
|
}
|
|
@@ -10112,9 +10160,9 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
10112
10160
|
...marker.signature ? { signature: marker.signature } : {},
|
|
10113
10161
|
...marker.revisionId ? { revisionId: marker.revisionId } : {}
|
|
10114
10162
|
});
|
|
10115
|
-
if (
|
|
10116
|
-
backup =
|
|
10117
|
-
renameSync3(target,
|
|
10163
|
+
if (existsSync15(target)) {
|
|
10164
|
+
backup = mkdtempSync3(join15(root, `.pull-backup-${name}-`));
|
|
10165
|
+
renameSync3(target, join15(backup, name));
|
|
10118
10166
|
moved = true;
|
|
10119
10167
|
}
|
|
10120
10168
|
renameSync3(staging, target);
|
|
@@ -10122,9 +10170,9 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
|
10122
10170
|
rmSync4(backup, { recursive: true, force: true });
|
|
10123
10171
|
} catch (error) {
|
|
10124
10172
|
rmSync4(staging, { recursive: true, force: true });
|
|
10125
|
-
if (moved && backup &&
|
|
10173
|
+
if (moved && backup && existsSync15(join15(backup, name))) {
|
|
10126
10174
|
try {
|
|
10127
|
-
renameSync3(
|
|
10175
|
+
renameSync3(join15(backup, name), target);
|
|
10128
10176
|
} catch {}
|
|
10129
10177
|
}
|
|
10130
10178
|
throw error;
|
|
@@ -10143,7 +10191,7 @@ function writePullMarker(dir, record) {
|
|
|
10143
10191
|
...record.revisionId ? { revisionId: record.revisionId } : {},
|
|
10144
10192
|
syncedAt: new Date().toISOString()
|
|
10145
10193
|
};
|
|
10146
|
-
writeFileSync9(
|
|
10194
|
+
writeFileSync9(join15(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
|
|
10147
10195
|
`);
|
|
10148
10196
|
}
|
|
10149
10197
|
async function safeMeta(client, slug) {
|
|
@@ -10294,7 +10342,7 @@ import { dirname as dirname8, relative as relative3 } from "path";
|
|
|
10294
10342
|
// package.json
|
|
10295
10343
|
var package_default = {
|
|
10296
10344
|
name: "@hasna/skills",
|
|
10297
|
-
version: "0.1.
|
|
10345
|
+
version: "0.1.67",
|
|
10298
10346
|
description: "Skills library for AI coding agents",
|
|
10299
10347
|
type: "module",
|
|
10300
10348
|
bin: {
|
|
@@ -10335,6 +10383,7 @@ var package_default = {
|
|
|
10335
10383
|
scripts: {
|
|
10336
10384
|
clean: "rm -rf bin/ dist/",
|
|
10337
10385
|
build: "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
10386
|
+
"build:js": "rm -rf dist && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
10338
10387
|
test: "bun test --timeout 30000",
|
|
10339
10388
|
dev: "bun run ./src/cli/index.tsx",
|
|
10340
10389
|
"dev:watch": "bun --watch run ./src/cli/index.tsx",
|
|
@@ -10344,6 +10393,7 @@ var package_default = {
|
|
|
10344
10393
|
migrate: "bun run ./src/server/migrate.ts",
|
|
10345
10394
|
typecheck: "tsc --noEmit",
|
|
10346
10395
|
"verify:release": "bun run scripts/release-guard.ts",
|
|
10396
|
+
prepare: "bun run build:js",
|
|
10347
10397
|
prepack: "bun run build && bun run verify:release",
|
|
10348
10398
|
prepublishOnly: "bun run typecheck && bun run test",
|
|
10349
10399
|
postinstall: "mkdir -p $HOME/.hasna/skills/custom 2>/dev/null || true"
|
|
@@ -11302,16 +11352,16 @@ function clone2(value) {
|
|
|
11302
11352
|
return JSON.parse(JSON.stringify(value));
|
|
11303
11353
|
}
|
|
11304
11354
|
// src/lib/feedback.ts
|
|
11305
|
-
import { existsSync as
|
|
11306
|
-
import { dirname as dirname9, join as
|
|
11355
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync11 } from "fs";
|
|
11356
|
+
import { dirname as dirname9, join as join16 } from "path";
|
|
11307
11357
|
import { Database } from "bun:sqlite";
|
|
11308
11358
|
function getFeedbackDbPath() {
|
|
11309
|
-
return
|
|
11359
|
+
return join16(getDataDir(), "skills.db");
|
|
11310
11360
|
}
|
|
11311
11361
|
function getFeedbackDb() {
|
|
11312
11362
|
const dbPath = getFeedbackDbPath();
|
|
11313
11363
|
const dir = dirname9(dbPath);
|
|
11314
|
-
if (!
|
|
11364
|
+
if (!existsSync16(dir))
|
|
11315
11365
|
mkdirSync11(dir, { recursive: true });
|
|
11316
11366
|
const db = new Database(dbPath);
|
|
11317
11367
|
db.exec("PRAGMA journal_mode = WAL");
|
|
@@ -11348,14 +11398,14 @@ function saveFeedback(input) {
|
|
|
11348
11398
|
// src/lib/native-storage.ts
|
|
11349
11399
|
import { createHash as createHash5, createHmac as createHmac2 } from "crypto";
|
|
11350
11400
|
import {
|
|
11351
|
-
existsSync as
|
|
11401
|
+
existsSync as existsSync17,
|
|
11352
11402
|
mkdirSync as mkdirSync12,
|
|
11353
|
-
readFileSync as
|
|
11354
|
-
readdirSync as
|
|
11355
|
-
statSync as
|
|
11403
|
+
readFileSync as readFileSync15,
|
|
11404
|
+
readdirSync as readdirSync9,
|
|
11405
|
+
statSync as statSync9,
|
|
11356
11406
|
writeFileSync as writeFileSync11
|
|
11357
11407
|
} from "fs";
|
|
11358
|
-
import { dirname as dirname10, join as
|
|
11408
|
+
import { dirname as dirname10, join as join17, normalize as normalize3, relative as relative4, sep as sep2 } from "path";
|
|
11359
11409
|
var SKILLS_STORAGE_TABLES = [
|
|
11360
11410
|
"skills_sync_records",
|
|
11361
11411
|
"skills_sync_cursors"
|
|
@@ -11393,6 +11443,55 @@ var SKILLS_NATIVE_STORAGE_FALLBACK_ENV = {
|
|
|
11393
11443
|
};
|
|
11394
11444
|
var SKILLS_STORAGE_ENV = SKILLS_NATIVE_STORAGE_ENV;
|
|
11395
11445
|
var SKILLS_STORAGE_FALLBACK_ENV = SKILLS_NATIVE_STORAGE_FALLBACK_ENV;
|
|
11446
|
+
var storageCapabilities = {
|
|
11447
|
+
version: 1,
|
|
11448
|
+
values: [
|
|
11449
|
+
"SKILLS_NATIVE_STORAGE_ENV",
|
|
11450
|
+
"SKILLS_NATIVE_STORAGE_FALLBACK_ENV",
|
|
11451
|
+
"SKILLS_STORAGE_ENV",
|
|
11452
|
+
"SKILLS_STORAGE_FALLBACK_ENV",
|
|
11453
|
+
"SKILLS_STORAGE_TABLES",
|
|
11454
|
+
"STORAGE_TABLES",
|
|
11455
|
+
"SkillsPostgresSyncStore",
|
|
11456
|
+
"SkillsS3ObjectStore",
|
|
11457
|
+
"buildSkillsS3ObjectUrl",
|
|
11458
|
+
"createSkillsPostgresSyncStore",
|
|
11459
|
+
"createSkillsS3ObjectStore",
|
|
11460
|
+
"createSkillsSnapshotSyncRecord",
|
|
11461
|
+
"exportSkillsLocalSnapshot",
|
|
11462
|
+
"getSkillsNativeStorageStatus",
|
|
11463
|
+
"getSkillsStorageDatabaseEnv",
|
|
11464
|
+
"getSkillsStorageDatabaseUrl",
|
|
11465
|
+
"getSkillsStorageStatus",
|
|
11466
|
+
"getStorageDatabaseEnv",
|
|
11467
|
+
"getStorageDatabaseUrl",
|
|
11468
|
+
"getStorageStatus",
|
|
11469
|
+
"importSkillsLocalSnapshot",
|
|
11470
|
+
"planSkillsS3SnapshotUpload",
|
|
11471
|
+
"resolveSkillsNativeStorageConfig",
|
|
11472
|
+
"resolveStorageConfig",
|
|
11473
|
+
"signSkillsAwsV4Request",
|
|
11474
|
+
"skillsPostgresSyncSchemaSql",
|
|
11475
|
+
"storageCapabilities",
|
|
11476
|
+
"uploadSkillsSnapshotFilesToS3"
|
|
11477
|
+
],
|
|
11478
|
+
types: [
|
|
11479
|
+
"AwsCredentials",
|
|
11480
|
+
"SignSkillsAwsV4RequestOptions",
|
|
11481
|
+
"SkillsFetch",
|
|
11482
|
+
"SkillsLocalSnapshot",
|
|
11483
|
+
"SkillsNativeStorageConfig",
|
|
11484
|
+
"SkillsNativeStorageStatus",
|
|
11485
|
+
"SkillsPostgresQueryClient",
|
|
11486
|
+
"SkillsS3ObjectStoreOptions",
|
|
11487
|
+
"SkillsS3PutObjectOptions",
|
|
11488
|
+
"SkillsS3SnapshotPlanEntry",
|
|
11489
|
+
"SkillsS3StoredObject",
|
|
11490
|
+
"SkillsSnapshotFile",
|
|
11491
|
+
"SkillsStorageTable",
|
|
11492
|
+
"SkillsSyncRecord"
|
|
11493
|
+
]
|
|
11494
|
+
};
|
|
11396
11495
|
var SKILLS_ENV_NAMESPACE = "SKILLS";
|
|
11397
11496
|
function resolveSkillsNativeStorageConfig(env = process.env) {
|
|
11398
11497
|
assertNoRetiredModeEnvVars(env, {
|
|
@@ -11443,7 +11542,7 @@ function getSkillsNativeStorageStatus(options = {}) {
|
|
|
11443
11542
|
local: {
|
|
11444
11543
|
dataDir: getDataDir(),
|
|
11445
11544
|
projectStateDir: getProjectStateDir(targetDir),
|
|
11446
|
-
feedbackDbPath:
|
|
11545
|
+
feedbackDbPath: join17(getDataDir(), "skills.db")
|
|
11447
11546
|
},
|
|
11448
11547
|
remote: {
|
|
11449
11548
|
databaseConfigured: Boolean(config.databaseUrl),
|
|
@@ -11466,9 +11565,9 @@ function getStorageStatus(options = {}) {
|
|
|
11466
11565
|
function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
|
|
11467
11566
|
const projectStateDir = getProjectStateDir(targetDir);
|
|
11468
11567
|
const files = [];
|
|
11469
|
-
if (
|
|
11568
|
+
if (existsSync17(projectStateDir)) {
|
|
11470
11569
|
for (const filePath of walkFiles2(projectStateDir)) {
|
|
11471
|
-
const bytes =
|
|
11570
|
+
const bytes = readFileSync15(filePath);
|
|
11472
11571
|
const relativePath = toPosix(relative4(targetDir, filePath));
|
|
11473
11572
|
files.push({
|
|
11474
11573
|
path: relativePath,
|
|
@@ -11493,7 +11592,7 @@ function importSkillsLocalSnapshot(snapshot, targetDir = process.cwd(), options
|
|
|
11493
11592
|
continue;
|
|
11494
11593
|
}
|
|
11495
11594
|
const absolutePath = resolveSnapshotPath(targetDir, file.path);
|
|
11496
|
-
if (
|
|
11595
|
+
if (existsSync17(absolutePath) && !options.overwrite) {
|
|
11497
11596
|
skipped += 1;
|
|
11498
11597
|
continue;
|
|
11499
11598
|
}
|
|
@@ -11799,9 +11898,9 @@ function parsePositiveInteger(value) {
|
|
|
11799
11898
|
}
|
|
11800
11899
|
function walkFiles2(dir) {
|
|
11801
11900
|
const files = [];
|
|
11802
|
-
for (const entry of
|
|
11803
|
-
const full =
|
|
11804
|
-
const stats =
|
|
11901
|
+
for (const entry of readdirSync9(dir)) {
|
|
11902
|
+
const full = join17(dir, entry);
|
|
11903
|
+
const stats = statSync9(full);
|
|
11805
11904
|
if (stats.isDirectory())
|
|
11806
11905
|
files.push(...walkFiles2(full));
|
|
11807
11906
|
else
|
|
@@ -11817,7 +11916,7 @@ function resolveSnapshotPath(targetDir, snapshotPath) {
|
|
|
11817
11916
|
if (!toPosix(normalizedPath).startsWith(".skills/")) {
|
|
11818
11917
|
throw new Error(`Snapshot path must stay inside .skills: ${snapshotPath}`);
|
|
11819
11918
|
}
|
|
11820
|
-
return
|
|
11919
|
+
return join17(targetDir, normalizedPath);
|
|
11821
11920
|
}
|
|
11822
11921
|
function normalizeS3Prefix(prefix) {
|
|
11823
11922
|
return (prefix ?? "").trim().replace(/^\/+|\/+$/g, "");
|
|
@@ -11899,6 +11998,7 @@ export {
|
|
|
11899
11998
|
unpinProjectSkill,
|
|
11900
11999
|
syncSkillsToAgents,
|
|
11901
12000
|
summarizeMcpToolContract,
|
|
12001
|
+
storageCapabilities,
|
|
11902
12002
|
skillsPostgresSyncSchemaSql,
|
|
11903
12003
|
skillExists,
|
|
11904
12004
|
signSkillsAwsV4Request,
|