@hasna/skills 0.1.63 → 0.1.66
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 +61 -22
- package/bin/index.js +2120 -670
- package/bin/mcp.js +555 -249
- package/bin/migrate.js +229 -33
- package/bin/server.js +1542 -327
- package/bin/worker.js +716 -207
- package/dist/cli/commands/registry-reconcile.d.ts +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +682 -276
- package/dist/lib/agent-sync.d.ts +26 -6
- package/dist/lib/auth-store.d.ts +37 -0
- package/dist/lib/config.d.ts +51 -0
- package/dist/lib/home-census.d.ts +4 -0
- package/dist/lib/home-migration.d.ts +8 -9
- package/dist/lib/native-storage.d.ts +29 -1
- package/dist/lib/portable-skills.d.ts +49 -6
- package/dist/lib/pull.d.ts +31 -0
- package/dist/lib/registry-reconcile.d.ts +114 -0
- package/dist/lib/registry-types.d.ts +9 -0
- package/dist/lib/registry.d.ts +7 -4
- package/dist/lib/remote-client.d.ts +118 -1
- package/dist/lib/remote-registry.d.ts +26 -0
- package/dist/lib/revision.d.ts +29 -0
- package/dist/lib/run-routing.d.ts +60 -0
- package/dist/sdk/index.js +14412 -13338
- package/dist/server/app.d.ts +4 -1
- package/dist/server/config.d.ts +12 -2
- package/dist/server/rows.d.ts +2 -1
- package/dist/server/skills-api.d.ts +108 -6
- package/dist/server/sqlite-store.d.ts +20 -3
- package/dist/server/store-fixtures.d.ts +6 -0
- package/dist/server/store.d.ts +31 -5
- package/dist/server/types.d.ts +98 -3
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +57 -2
- package/migrations/postgres/0004_hosted_pins.sql +28 -0
- package/migrations/postgres/0005_revision_tombstone_registry.sql +37 -0
- package/migrations/postgres/0005_tag_projection.sql +36 -0
- package/migrations/sqlite/0004_hosted_pins.sql +21 -0
- package/migrations/sqlite/0005_revision_tombstone_registry.sql +18 -0
- package/migrations/sqlite/0005_tag_projection.sql +25 -0
- package/package.json +3 -2
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";
|
|
@@ -154,6 +154,11 @@ function normalizeConfigValue(key, value) {
|
|
|
154
154
|
}
|
|
155
155
|
var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
|
|
156
156
|
var INSTALLED_SKILLS_DIRNAME = "installed";
|
|
157
|
+
var SKILLS_CACHE_DIRNAME = "skills";
|
|
158
|
+
var LAYOUT_MIGRATION_RECORD = ".layout-migration.json";
|
|
159
|
+
function isOwnerLayoutMigrated(appDir) {
|
|
160
|
+
return existsSync(join(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD));
|
|
161
|
+
}
|
|
157
162
|
function getDataDir() {
|
|
158
163
|
const override = process.env[DATA_DIR_ENV];
|
|
159
164
|
if (override) {
|
|
@@ -267,15 +272,16 @@ function unsetConfig(key, scope = "project") {
|
|
|
267
272
|
// src/lib/portable-skills.ts
|
|
268
273
|
import {
|
|
269
274
|
cpSync as cpSync2,
|
|
270
|
-
existsSync as
|
|
275
|
+
existsSync as existsSync6,
|
|
271
276
|
mkdirSync as mkdirSync3,
|
|
272
|
-
|
|
277
|
+
mkdtempSync,
|
|
278
|
+
readdirSync as readdirSync5,
|
|
273
279
|
renameSync,
|
|
274
280
|
rmSync,
|
|
275
|
-
statSync as
|
|
281
|
+
statSync as statSync6,
|
|
276
282
|
writeFileSync as writeFileSync3
|
|
277
283
|
} from "fs";
|
|
278
|
-
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";
|
|
279
285
|
|
|
280
286
|
// src/lib/registry-data/development-tools.ts
|
|
281
287
|
var DEVELOPMENT_TOOLS_SKILLS = [
|
|
@@ -435,7 +441,7 @@ var DEVELOPMENT_TOOLS_SKILLS = [
|
|
|
435
441
|
{
|
|
436
442
|
name: "monitor",
|
|
437
443
|
displayName: "Monitor",
|
|
438
|
-
description: "Operate the
|
|
444
|
+
description: "Operate the monitor MCP for machine health, processes, cron jobs, and cleanup workflows",
|
|
439
445
|
category: "Development Tools",
|
|
440
446
|
tags: ["monitoring", "mcp", "processes", "operations"]
|
|
441
447
|
},
|
|
@@ -481,6 +487,14 @@ var DEVELOPMENT_TOOLS_SKILLS = [
|
|
|
481
487
|
description: "Validate configuration files for syntax and schema compliance",
|
|
482
488
|
category: "Development Tools",
|
|
483
489
|
tags: ["config", "validation", "schema", "linting"]
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
name: "session-inject-monitor",
|
|
493
|
+
displayName: "Session Inject Monitor",
|
|
494
|
+
description: "Set up a declarative monitor that injects a prompt into a live coding-agent session when a watched source (conversations, email, todos, knowledge, command output) has new content",
|
|
495
|
+
category: "Development Tools",
|
|
496
|
+
tags: ["monitor", "session", "injection", "automation", "wake"],
|
|
497
|
+
kind: "instruction"
|
|
484
498
|
}
|
|
485
499
|
];
|
|
486
500
|
|
|
@@ -868,7 +882,7 @@ var DESIGN_BRANDING_SKILLS = [
|
|
|
868
882
|
displayName: "Site Analyze",
|
|
869
883
|
description: "Analyze any website's design system \u2014 detects shadcn/ui, Tailwind, extracts colors, typography, and components via Playwright + Claude Vision.",
|
|
870
884
|
category: "Design & Branding",
|
|
871
|
-
tags: ["design", "shadcn", "tailwind", "colors", "typography", "playwright", "analysis", "
|
|
885
|
+
tags: ["design", "shadcn", "tailwind", "colors", "typography", "playwright", "analysis", "styles"]
|
|
872
886
|
}
|
|
873
887
|
];
|
|
874
888
|
|
|
@@ -978,11 +992,9 @@ var SKILLS = [
|
|
|
978
992
|
...EVENT_MANAGEMENT_SKILLS
|
|
979
993
|
];
|
|
980
994
|
|
|
981
|
-
// src/lib/skill-validation.ts
|
|
982
|
-
import { existsSync as existsSync2, lstatSync, readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
983
|
-
import { isAbsolute, join as join2, normalize } from "path";
|
|
984
|
-
|
|
985
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";
|
|
986
998
|
var HOSTED_RUNTIMES = new Set(["hosted"]);
|
|
987
999
|
var HOSTED_SOURCES = new Set(["remote", "private-hosted"]);
|
|
988
1000
|
function normalizeMarker(value) {
|
|
@@ -994,6 +1006,16 @@ function isHostedMetadataPackage(pkg) {
|
|
|
994
1006
|
return false;
|
|
995
1007
|
return HOSTED_RUNTIMES.has(normalizeMarker(skills.runtime)) || HOSTED_SOURCES.has(normalizeMarker(skills.source));
|
|
996
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
|
+
}
|
|
997
1019
|
var HOSTED_METADATA_SET_EMPTY_ERROR = [
|
|
998
1020
|
"The hosted metadata skill set is empty, but the packaging guards that depend on it",
|
|
999
1021
|
"only mean anything while it is non-empty: an empty set makes every one of them pass",
|
|
@@ -1010,6 +1032,8 @@ var BRACE_SOURCE_EXCLUSION = new RegExp(`^!skills/\\{(${SLUG}(?:,${SLUG})+)\\}/s
|
|
|
1010
1032
|
var SINGLE_SOURCE_EXCLUSION = new RegExp(`^!skills/(${SLUG})/src$`);
|
|
1011
1033
|
|
|
1012
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";
|
|
1013
1037
|
var VALID_SKILL_KINDS = ["executable", "instruction"];
|
|
1014
1038
|
var DOC_FILES = ["SKILL.md", "README.md", "CLAUDE.md"];
|
|
1015
1039
|
var RESERVED_SKILL_ENTRIES = new Set([
|
|
@@ -1063,7 +1087,7 @@ function add(target, code, message) {
|
|
|
1063
1087
|
target.push({ code, message });
|
|
1064
1088
|
}
|
|
1065
1089
|
function readJsonFile(path) {
|
|
1066
|
-
return JSON.parse(
|
|
1090
|
+
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
1067
1091
|
}
|
|
1068
1092
|
function asRecord(value) {
|
|
1069
1093
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
@@ -1147,7 +1171,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
1147
1171
|
binCommands: [],
|
|
1148
1172
|
docFiles: []
|
|
1149
1173
|
};
|
|
1150
|
-
if (!
|
|
1174
|
+
if (!existsSync3(skillPath)) {
|
|
1151
1175
|
add(issues, "skill.dir_missing", `Skill directory not found: ${skillPath}`);
|
|
1152
1176
|
return {
|
|
1153
1177
|
name: bareName,
|
|
@@ -1161,8 +1185,8 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
1161
1185
|
if (!VALID_BIN_COMMAND.test(bareName)) {
|
|
1162
1186
|
add(issues, "skill.name_invalid", `Skill name '${bareName}' must use lowercase letters, numbers, dots, underscores, or hyphens`);
|
|
1163
1187
|
}
|
|
1164
|
-
for (const entry of
|
|
1165
|
-
const entryPath =
|
|
1188
|
+
for (const entry of readdirSync3(skillPath).sort()) {
|
|
1189
|
+
const entryPath = join3(skillPath, entry);
|
|
1166
1190
|
if (RESERVED_SKILL_ENTRIES.has(entry)) {
|
|
1167
1191
|
add(issues, "skill.reserved_file", `Reserved file '${entry}' is not allowed in skill packages`);
|
|
1168
1192
|
}
|
|
@@ -1174,15 +1198,15 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
1174
1198
|
}
|
|
1175
1199
|
}
|
|
1176
1200
|
for (const docFile of DOC_FILES) {
|
|
1177
|
-
if (
|
|
1201
|
+
if (existsSync3(join3(skillPath, docFile)))
|
|
1178
1202
|
metadata.docFiles.push(docFile);
|
|
1179
1203
|
}
|
|
1180
1204
|
if (metadata.docFiles.length === 0) {
|
|
1181
1205
|
add(issues, "skill.docs_missing", "Missing documentation file: expected SKILL.md, README.md, or CLAUDE.md");
|
|
1182
1206
|
}
|
|
1183
|
-
const skillMdPath =
|
|
1184
|
-
if (
|
|
1185
|
-
const frontmatter = parseSkillFrontmatter(
|
|
1207
|
+
const skillMdPath = join3(skillPath, "SKILL.md");
|
|
1208
|
+
if (existsSync3(skillMdPath)) {
|
|
1209
|
+
const frontmatter = parseSkillFrontmatter(readFileSync3(skillMdPath, "utf-8"));
|
|
1186
1210
|
if (!frontmatter) {
|
|
1187
1211
|
add(warnings, "skill.frontmatter_missing", "SKILL.md has no YAML frontmatter");
|
|
1188
1212
|
} else {
|
|
@@ -1224,8 +1248,8 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
1224
1248
|
}
|
|
1225
1249
|
metadata.kind = resolvedKind;
|
|
1226
1250
|
const isInstruction = resolvedKind === "instruction";
|
|
1227
|
-
const pkgPath =
|
|
1228
|
-
if (!
|
|
1251
|
+
const pkgPath = join3(skillPath, "package.json");
|
|
1252
|
+
if (!existsSync3(pkgPath)) {
|
|
1229
1253
|
if (!isInstruction)
|
|
1230
1254
|
add(issues, "package.missing", "Missing package.json");
|
|
1231
1255
|
} else {
|
|
@@ -1283,10 +1307,10 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
1283
1307
|
add(issues, "package.bin_target_unsafe", `package.json bin '${command}' target '${target}' must stay inside the skill directory`);
|
|
1284
1308
|
continue;
|
|
1285
1309
|
}
|
|
1286
|
-
const targetPath =
|
|
1287
|
-
if (!
|
|
1310
|
+
const targetPath = join3(skillPath, target);
|
|
1311
|
+
if (!existsSync3(targetPath)) {
|
|
1288
1312
|
add(warnings, "package.bin_target_missing", `package.json bin '${command}' target '${target}' is not present before build`);
|
|
1289
|
-
} else if (
|
|
1313
|
+
} else if (statSync3(targetPath).isDirectory()) {
|
|
1290
1314
|
add(issues, "package.bin_target_directory", `package.json bin '${command}' target '${target}' must point to a file, not a directory`);
|
|
1291
1315
|
}
|
|
1292
1316
|
}
|
|
@@ -1301,18 +1325,18 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
1301
1325
|
metadata.runtime = "none";
|
|
1302
1326
|
} else {
|
|
1303
1327
|
metadata.runtime = hostedMetadata ? "hosted" : "local";
|
|
1304
|
-
const srcDir =
|
|
1328
|
+
const srcDir = join3(skillPath, "src");
|
|
1305
1329
|
if (hostedMetadata) {
|
|
1306
|
-
if (
|
|
1330
|
+
if (existsSync3(srcDir)) {
|
|
1307
1331
|
add(issues, "skill.hosted_source_forbidden", "Hosted metadata skills must not include local implementation source");
|
|
1308
1332
|
}
|
|
1309
|
-
} else if (!
|
|
1333
|
+
} else if (!existsSync3(srcDir)) {
|
|
1310
1334
|
add(issues, "skill.src_missing", "Missing src/ directory");
|
|
1311
|
-
} else if (!
|
|
1335
|
+
} else if (!existsSync3(join3(srcDir, "index.ts")) && !existsSync3(join3(srcDir, "index.js"))) {
|
|
1312
1336
|
add(issues, "skill.src_index_missing", "Missing src/index.ts or src/index.js");
|
|
1313
1337
|
} else {
|
|
1314
|
-
const indexPath =
|
|
1315
|
-
const size =
|
|
1338
|
+
const indexPath = existsSync3(join3(srcDir, "index.ts")) ? join3(srcDir, "index.ts") : join3(srcDir, "index.js");
|
|
1339
|
+
const size = statSync3(indexPath).size;
|
|
1316
1340
|
if (size < 50)
|
|
1317
1341
|
add(warnings, "skill.src_index_minimal", `Source entry point is very small (${size}B)`);
|
|
1318
1342
|
}
|
|
@@ -1335,9 +1359,9 @@ function validateRegistryConsistency(registry, skillsDir) {
|
|
|
1335
1359
|
seen.add(name);
|
|
1336
1360
|
return false;
|
|
1337
1361
|
})));
|
|
1338
|
-
const skillDirs =
|
|
1339
|
-
const fullPath =
|
|
1340
|
-
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();
|
|
1341
1365
|
}) : [];
|
|
1342
1366
|
const directoryNames = new Set(skillDirs);
|
|
1343
1367
|
const registryNameSet = new Set(registryNames);
|
|
@@ -1353,8 +1377,8 @@ function validateRegistryConsistency(registry, skillsDir) {
|
|
|
1353
1377
|
|
|
1354
1378
|
// src/lib/skill-hash.ts
|
|
1355
1379
|
import { createHash } from "crypto";
|
|
1356
|
-
import { existsSync as
|
|
1357
|
-
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";
|
|
1358
1382
|
var CONTENT_HASH_ALGORITHM = "sha256";
|
|
1359
1383
|
var HASH_EXCLUDE_DIRS = new Set([".git", "node_modules", "dist", "build", ".turbo"]);
|
|
1360
1384
|
var HASH_COVERAGE = [
|
|
@@ -1413,10 +1437,10 @@ function collectBundleFiles(skillPath) {
|
|
|
1413
1437
|
if (seen.has(entry))
|
|
1414
1438
|
continue;
|
|
1415
1439
|
seen.add(entry);
|
|
1416
|
-
const absolute =
|
|
1417
|
-
if (!
|
|
1440
|
+
const absolute = join4(skillPath, entry);
|
|
1441
|
+
if (!existsSync4(absolute))
|
|
1418
1442
|
continue;
|
|
1419
|
-
if (
|
|
1443
|
+
if (statSync4(absolute).isDirectory())
|
|
1420
1444
|
collectDirectory(files, absolute, entry);
|
|
1421
1445
|
else
|
|
1422
1446
|
collectFile(files, absolute, entry);
|
|
@@ -1424,14 +1448,14 @@ function collectBundleFiles(skillPath) {
|
|
|
1424
1448
|
return files.sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
|
|
1425
1449
|
}
|
|
1426
1450
|
function collectDirectory(files, dir, rel) {
|
|
1427
|
-
for (const entry of
|
|
1451
|
+
for (const entry of readdirSync4(dir).sort()) {
|
|
1428
1452
|
if (entry.startsWith("."))
|
|
1429
1453
|
continue;
|
|
1430
|
-
const absolute =
|
|
1454
|
+
const absolute = join4(dir, entry);
|
|
1431
1455
|
const childRel = `${rel}/${entry}`;
|
|
1432
1456
|
let stats;
|
|
1433
1457
|
try {
|
|
1434
|
-
stats =
|
|
1458
|
+
stats = statSync4(absolute);
|
|
1435
1459
|
} catch {
|
|
1436
1460
|
continue;
|
|
1437
1461
|
}
|
|
@@ -1447,7 +1471,7 @@ function collectDirectory(files, dir, rel) {
|
|
|
1447
1471
|
}
|
|
1448
1472
|
}
|
|
1449
1473
|
function collectFile(files, absolute, rel) {
|
|
1450
|
-
const buffer =
|
|
1474
|
+
const buffer = readFileSync4(absolute);
|
|
1451
1475
|
if (rel === "skill.json") {
|
|
1452
1476
|
files.push({ rel: rel.split(sep).join("/"), content: new TextEncoder().encode(canonicalizeManifest(new TextDecoder().decode(buffer))) });
|
|
1453
1477
|
return;
|
|
@@ -1647,14 +1671,14 @@ function validateRuntimeContract(manifest, issues, strict) {
|
|
|
1647
1671
|
// src/lib/portable-skills-files.ts
|
|
1648
1672
|
import {
|
|
1649
1673
|
cpSync,
|
|
1650
|
-
existsSync as
|
|
1674
|
+
existsSync as existsSync5,
|
|
1651
1675
|
lstatSync as lstatSync2,
|
|
1652
1676
|
mkdirSync as mkdirSync2,
|
|
1653
|
-
readFileSync as
|
|
1677
|
+
readFileSync as readFileSync5,
|
|
1654
1678
|
realpathSync,
|
|
1655
1679
|
writeFileSync as writeFileSync2
|
|
1656
1680
|
} from "fs";
|
|
1657
|
-
import { basename, dirname as dirname2, join as
|
|
1681
|
+
import { basename, dirname as dirname2, join as join5, relative } from "path";
|
|
1658
1682
|
var ANY_SEGMENT_COPY_EXCLUDES = new Set([
|
|
1659
1683
|
".git",
|
|
1660
1684
|
".DS_Store",
|
|
@@ -1697,12 +1721,12 @@ function normalizePortableSkillName(name) {
|
|
|
1697
1721
|
return normalized;
|
|
1698
1722
|
}
|
|
1699
1723
|
function readPortableSkillManifest(skillPath, fallbackName = basename(skillPath)) {
|
|
1700
|
-
const skillJsonPath =
|
|
1701
|
-
const skillMdPath =
|
|
1702
|
-
const pkgPath =
|
|
1703
|
-
const jsonManifest =
|
|
1704
|
-
const frontmatter =
|
|
1705
|
-
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;
|
|
1706
1730
|
const name = normalizePortableSkillName(stringField(jsonManifest, "name") ?? frontmatter?.name ?? stringValue(pkg?.name) ?? fallbackName);
|
|
1707
1731
|
const description = stringField(jsonManifest, "description") ?? frontmatter?.description ?? stringValue(pkg?.description) ?? `${name} skill`;
|
|
1708
1732
|
const version = stringField(jsonManifest, "version") ?? frontmatter?.version ?? stringValue(pkg?.version) ?? PORTABLE_SKILL_DEFAULT_VERSION;
|
|
@@ -1748,7 +1772,7 @@ function createInstructionManifest(name, options) {
|
|
|
1748
1772
|
}
|
|
1749
1773
|
function writeInstructionSkillTemplate(skillPath, manifest) {
|
|
1750
1774
|
mkdirSync2(skillPath, { recursive: true });
|
|
1751
|
-
writeFileSync2(
|
|
1775
|
+
writeFileSync2(join5(skillPath, "SKILL.md"), renderInstructionSkillMd(manifest));
|
|
1752
1776
|
writeSkillJsonWithHash(skillPath, manifest);
|
|
1753
1777
|
}
|
|
1754
1778
|
function renderInstructionSkillMd(manifest) {
|
|
@@ -1797,12 +1821,12 @@ function createPortableManifest(name, options) {
|
|
|
1797
1821
|
};
|
|
1798
1822
|
}
|
|
1799
1823
|
function writePortableSkillTemplate(skillPath, manifest) {
|
|
1800
|
-
mkdirSync2(
|
|
1801
|
-
writeFileSync2(
|
|
1802
|
-
writeFileSync2(
|
|
1803
|
-
writeFileSync2(
|
|
1804
|
-
writeFileSync2(
|
|
1805
|
-
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));
|
|
1806
1830
|
writeSkillJsonWithHash(skillPath, manifest);
|
|
1807
1831
|
}
|
|
1808
1832
|
function fillContractDefaults(manifest, entrypoint) {
|
|
@@ -1827,7 +1851,7 @@ function writeSkillJsonWithHash(skillPath, manifest) {
|
|
|
1827
1851
|
content_hash: undefined
|
|
1828
1852
|
}
|
|
1829
1853
|
};
|
|
1830
|
-
writeFileSync2(
|
|
1854
|
+
writeFileSync2(join5(skillPath, "skill.json"), `${JSON.stringify({ ...existing, ...renderSkillJsonObject(withoutHash) }, null, 2)}
|
|
1831
1855
|
`);
|
|
1832
1856
|
const hash = computeContentHash(skillPath);
|
|
1833
1857
|
const withHash = {
|
|
@@ -1837,16 +1861,16 @@ function writeSkillJsonWithHash(skillPath, manifest) {
|
|
|
1837
1861
|
content_hash: hash
|
|
1838
1862
|
}
|
|
1839
1863
|
};
|
|
1840
|
-
writeFileSync2(
|
|
1864
|
+
writeFileSync2(join5(skillPath, "skill.json"), `${JSON.stringify({ ...existing, ...renderSkillJsonObject(withHash) }, null, 2)}
|
|
1841
1865
|
`);
|
|
1842
1866
|
return withHash;
|
|
1843
1867
|
}
|
|
1844
1868
|
function readExistingSkillJson(skillPath) {
|
|
1845
|
-
const path =
|
|
1846
|
-
if (!
|
|
1869
|
+
const path = join5(skillPath, "skill.json");
|
|
1870
|
+
if (!existsSync5(path))
|
|
1847
1871
|
return {};
|
|
1848
1872
|
try {
|
|
1849
|
-
const parsed = JSON.parse(
|
|
1873
|
+
const parsed = JSON.parse(readFileSync5(path, "utf-8"));
|
|
1850
1874
|
return isRecord2(parsed) ? parsed : {};
|
|
1851
1875
|
} catch {
|
|
1852
1876
|
return {};
|
|
@@ -1876,28 +1900,28 @@ function ensurePortableSkillFiles(skillPath, manifest) {
|
|
|
1876
1900
|
tags: next.tags?.length ? next.tags : ["custom", next.name]
|
|
1877
1901
|
};
|
|
1878
1902
|
const entry = next.commands[0]?.entry ?? "src/index.ts";
|
|
1879
|
-
if (entry && !
|
|
1880
|
-
mkdirSync2(dirname2(
|
|
1881
|
-
writeFileSync2(
|
|
1903
|
+
if (entry && !existsSync5(join5(skillPath, entry))) {
|
|
1904
|
+
mkdirSync2(dirname2(join5(skillPath, entry)), { recursive: true });
|
|
1905
|
+
writeFileSync2(join5(skillPath, entry), renderEntrypoint(next));
|
|
1882
1906
|
}
|
|
1883
|
-
if (!
|
|
1884
|
-
writeFileSync2(
|
|
1907
|
+
if (!existsSync5(join5(skillPath, "SKILL.md")))
|
|
1908
|
+
writeFileSync2(join5(skillPath, "SKILL.md"), renderSkillMd(next));
|
|
1885
1909
|
else
|
|
1886
|
-
writeFileSync2(
|
|
1887
|
-
if (!
|
|
1888
|
-
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));
|
|
1889
1913
|
ensurePackageJson(skillPath, next);
|
|
1890
|
-
if (!
|
|
1891
|
-
writeFileSync2(
|
|
1914
|
+
if (!existsSync5(join5(skillPath, "tsconfig.json")))
|
|
1915
|
+
writeFileSync2(join5(skillPath, "tsconfig.json"), renderTsconfig());
|
|
1892
1916
|
writeSkillJsonWithHash(skillPath, next);
|
|
1893
1917
|
return readPortableSkillManifest(skillPath, next.name);
|
|
1894
1918
|
}
|
|
1895
1919
|
function ensurePackageJson(skillPath, manifest) {
|
|
1896
|
-
const pkgPath =
|
|
1920
|
+
const pkgPath = join5(skillPath, "package.json");
|
|
1897
1921
|
const first = manifest.commands[0] ?? { name: manifest.name, entry: "src/index.ts" };
|
|
1898
1922
|
const commandName = normalizePortableSkillName(first.name || manifest.name);
|
|
1899
1923
|
const entry = (first.entry ?? "src/index.ts").replace(/^\.\//, "");
|
|
1900
|
-
if (!
|
|
1924
|
+
if (!existsSync5(pkgPath)) {
|
|
1901
1925
|
writeFileSync2(pkgPath, renderPackageJson(manifest));
|
|
1902
1926
|
return;
|
|
1903
1927
|
}
|
|
@@ -1940,8 +1964,8 @@ function ensureInstructionSkillFiles(skillPath, manifest) {
|
|
|
1940
1964
|
inputs: [],
|
|
1941
1965
|
commands: []
|
|
1942
1966
|
};
|
|
1943
|
-
if (!
|
|
1944
|
-
writeFileSync2(
|
|
1967
|
+
if (!existsSync5(join5(skillPath, "SKILL.md"))) {
|
|
1968
|
+
writeFileSync2(join5(skillPath, "SKILL.md"), renderSkillMd(next));
|
|
1945
1969
|
}
|
|
1946
1970
|
writeSkillJsonWithHash(skillPath, next);
|
|
1947
1971
|
return readPortableSkillManifest(skillPath, next.name);
|
|
@@ -2192,7 +2216,7 @@ function inferPackageCommands(pkg, fallbackName) {
|
|
|
2192
2216
|
return;
|
|
2193
2217
|
}
|
|
2194
2218
|
function readJsonObject(path) {
|
|
2195
|
-
const parsed = JSON.parse(
|
|
2219
|
+
const parsed = JSON.parse(readFileSync5(path, "utf-8"));
|
|
2196
2220
|
if (!isRecord2(parsed))
|
|
2197
2221
|
throw new Error(`${basename(path)} must contain a JSON object`);
|
|
2198
2222
|
return parsed;
|
|
@@ -2233,33 +2257,36 @@ var LEGACY_CUSTOM_DIRNAME = "custom";
|
|
|
2233
2257
|
function getPortableSkillsRoot(options = {}) {
|
|
2234
2258
|
if (options.rootDir)
|
|
2235
2259
|
return options.rootDir;
|
|
2236
|
-
const appDir = options.homeDir ?
|
|
2237
|
-
const
|
|
2260
|
+
const appDir = options.homeDir ? join6(options.homeDir, ".hasna", "skills") : getDataDir();
|
|
2261
|
+
const cache = join6(appDir, SKILLS_CACHE_DIRNAME);
|
|
2262
|
+
if (isOwnerLayoutMigrated(appDir) && safeIsDirectory(cache))
|
|
2263
|
+
return cache;
|
|
2264
|
+
const installed = join6(appDir, INSTALLED_SKILLS_DIRNAME);
|
|
2238
2265
|
migrateLegacySkillLayout(appDir, installed);
|
|
2239
2266
|
return installed;
|
|
2240
2267
|
}
|
|
2241
2268
|
function looksLikeSkillDirectory(path) {
|
|
2242
2269
|
if (!safeIsDirectory(path))
|
|
2243
2270
|
return false;
|
|
2244
|
-
return
|
|
2271
|
+
return existsSync6(join6(path, "SKILL.md")) || existsSync6(join6(path, "skill.json")) || existsSync6(join6(path, "package.json"));
|
|
2245
2272
|
}
|
|
2246
2273
|
function migrateLegacySkillLayout(appDir, installed) {
|
|
2247
2274
|
if (!safeIsDirectory(appDir))
|
|
2248
2275
|
return;
|
|
2249
2276
|
const candidates = [];
|
|
2250
2277
|
try {
|
|
2251
|
-
for (const entry of
|
|
2278
|
+
for (const entry of readdirSync5(appDir)) {
|
|
2252
2279
|
if (entry.startsWith(".") || entry === INSTALLED_SKILLS_DIRNAME)
|
|
2253
2280
|
continue;
|
|
2254
|
-
const path =
|
|
2281
|
+
const path = join6(appDir, entry);
|
|
2255
2282
|
if (entry === LEGACY_CUSTOM_DIRNAME) {
|
|
2256
2283
|
if (!safeIsDirectory(path))
|
|
2257
2284
|
continue;
|
|
2258
2285
|
try {
|
|
2259
|
-
for (const nested of
|
|
2286
|
+
for (const nested of readdirSync5(path)) {
|
|
2260
2287
|
if (nested.startsWith("."))
|
|
2261
2288
|
continue;
|
|
2262
|
-
const nestedPath =
|
|
2289
|
+
const nestedPath = join6(path, nested);
|
|
2263
2290
|
if (looksLikeSkillDirectory(nestedPath))
|
|
2264
2291
|
candidates.push({ from: nestedPath, name: nested });
|
|
2265
2292
|
}
|
|
@@ -2273,10 +2300,10 @@ function migrateLegacySkillLayout(appDir, installed) {
|
|
|
2273
2300
|
return;
|
|
2274
2301
|
}
|
|
2275
2302
|
for (const { from, name } of candidates) {
|
|
2276
|
-
const target =
|
|
2277
|
-
if (
|
|
2303
|
+
const target = join6(installed, name);
|
|
2304
|
+
if (existsSync6(target))
|
|
2278
2305
|
continue;
|
|
2279
|
-
const staging =
|
|
2306
|
+
const staging = join6(installed, `.migrating-${name}-${process.pid}`);
|
|
2280
2307
|
try {
|
|
2281
2308
|
rmSync(staging, { recursive: true, force: true });
|
|
2282
2309
|
cpSync2(from, staging, { recursive: true, errorOnExist: false });
|
|
@@ -2289,7 +2316,7 @@ function migrateLegacySkillLayout(appDir, installed) {
|
|
|
2289
2316
|
}
|
|
2290
2317
|
}
|
|
2291
2318
|
function getPortableSkillPath(name, options = {}) {
|
|
2292
|
-
return
|
|
2319
|
+
return join6(getPortableSkillsRoot(options), normalizePortableSkillName(name));
|
|
2293
2320
|
}
|
|
2294
2321
|
function findPortableSkill(name, options = {}) {
|
|
2295
2322
|
let normalized;
|
|
@@ -2299,7 +2326,7 @@ function findPortableSkill(name, options = {}) {
|
|
|
2299
2326
|
return null;
|
|
2300
2327
|
}
|
|
2301
2328
|
const path = getPortableSkillPath(normalized, options);
|
|
2302
|
-
if (!
|
|
2329
|
+
if (!existsSync6(path) || !statSync6(path).isDirectory())
|
|
2303
2330
|
return null;
|
|
2304
2331
|
try {
|
|
2305
2332
|
return summarizePortableSkill(path, normalized);
|
|
@@ -2312,10 +2339,10 @@ function listPortableSkills(options = {}) {
|
|
|
2312
2339
|
if (!safeIsDirectory(root))
|
|
2313
2340
|
return [];
|
|
2314
2341
|
const skills = [];
|
|
2315
|
-
for (const entry of
|
|
2342
|
+
for (const entry of readdirSync5(root).sort()) {
|
|
2316
2343
|
if (entry.startsWith("."))
|
|
2317
2344
|
continue;
|
|
2318
|
-
const path =
|
|
2345
|
+
const path = join6(root, entry);
|
|
2319
2346
|
if (!safeIsDirectory(path))
|
|
2320
2347
|
continue;
|
|
2321
2348
|
try {
|
|
@@ -2337,7 +2364,8 @@ function listPortableSkillMetas(options = {}) {
|
|
|
2337
2364
|
tags: manifest.tags || ["custom"],
|
|
2338
2365
|
version: skill.version,
|
|
2339
2366
|
...manifest.kind ? { kind: manifest.kind } : {},
|
|
2340
|
-
source: "custom"
|
|
2367
|
+
source: "custom",
|
|
2368
|
+
...isHostedMetadataSkillDir(skill.path) ? { serverOwned: true } : {}
|
|
2341
2369
|
};
|
|
2342
2370
|
});
|
|
2343
2371
|
}
|
|
@@ -2348,8 +2376,8 @@ function isOfficialSkillName(name) {
|
|
|
2348
2376
|
function scaffoldPortableSkill(name, options = {}) {
|
|
2349
2377
|
const skillName = normalizePortableSkillName(name);
|
|
2350
2378
|
const root = getPortableSkillsRoot(options);
|
|
2351
|
-
const skillPath =
|
|
2352
|
-
if (
|
|
2379
|
+
const skillPath = join6(root, skillName);
|
|
2380
|
+
if (existsSync6(skillPath)) {
|
|
2353
2381
|
if (!options.overwrite)
|
|
2354
2382
|
throw new Error(`Skill '${skillName}' already exists at ${skillPath}`);
|
|
2355
2383
|
rmSync(skillPath, { recursive: true, force: true });
|
|
@@ -2367,7 +2395,7 @@ function scaffoldPortableSkill(name, options = {}) {
|
|
|
2367
2395
|
}
|
|
2368
2396
|
function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
2369
2397
|
const absoluteSource = normalize2(sourceDir);
|
|
2370
|
-
if (!
|
|
2398
|
+
if (!existsSync6(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
|
|
2371
2399
|
throw new Error(`Import directory not found: ${sourceDir}`);
|
|
2372
2400
|
}
|
|
2373
2401
|
const continueOnError = options.continueOnError ?? true;
|
|
@@ -2378,9 +2406,9 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
|
2378
2406
|
};
|
|
2379
2407
|
const imported = [];
|
|
2380
2408
|
const skipped = [];
|
|
2381
|
-
const entries =
|
|
2409
|
+
const entries = readdirSync5(absoluteSource, { withFileTypes: true }).map((entry) => entry.name).filter((entryName) => !entryName.startsWith(".")).sort();
|
|
2382
2410
|
for (const entryName of entries) {
|
|
2383
|
-
const childPath =
|
|
2411
|
+
const childPath = join6(absoluteSource, entryName);
|
|
2384
2412
|
if (!safeIsDirectory(childPath))
|
|
2385
2413
|
continue;
|
|
2386
2414
|
if (!isSkillCandidate(childPath)) {
|
|
@@ -2409,11 +2437,11 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
|
2409
2437
|
};
|
|
2410
2438
|
}
|
|
2411
2439
|
function isSkillCandidate(dir) {
|
|
2412
|
-
return
|
|
2440
|
+
return existsSync6(join6(dir, "SKILL.md")) || existsSync6(join6(dir, "skill.json")) || existsSync6(join6(dir, "package.json"));
|
|
2413
2441
|
}
|
|
2414
2442
|
function portPortableSkill(sourcePath, options = {}) {
|
|
2415
2443
|
const absoluteSource = normalize2(sourcePath);
|
|
2416
|
-
if (!
|
|
2444
|
+
if (!existsSync6(absoluteSource) || !statSync6(absoluteSource).isDirectory()) {
|
|
2417
2445
|
throw new Error(`Skill source directory not found: ${sourcePath}`);
|
|
2418
2446
|
}
|
|
2419
2447
|
const inferred = readPortableSkillManifest(absoluteSource, basename2(absoluteSource));
|
|
@@ -2425,8 +2453,8 @@ function portPortableSkill(sourcePath, options = {}) {
|
|
|
2425
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.`);
|
|
2426
2454
|
}
|
|
2427
2455
|
const root = getPortableSkillsRoot(options);
|
|
2428
|
-
const destination =
|
|
2429
|
-
if (
|
|
2456
|
+
const destination = join6(root, skillName);
|
|
2457
|
+
if (existsSync6(destination)) {
|
|
2430
2458
|
if (!options.overwrite)
|
|
2431
2459
|
throw new Error(`Skill '${skillName}' already exists at ${destination}`);
|
|
2432
2460
|
rmSync(destination, { recursive: true, force: true });
|
|
@@ -2448,19 +2476,10 @@ function safeNormalizeName(name) {
|
|
|
2448
2476
|
return;
|
|
2449
2477
|
}
|
|
2450
2478
|
}
|
|
2451
|
-
function
|
|
2452
|
-
const name = normalizePortableSkillName(input.name);
|
|
2453
|
-
const root = getPortableSkillsRoot(options);
|
|
2454
|
-
const skillPath = join5(root, name);
|
|
2455
|
-
const created = !existsSync5(skillPath);
|
|
2456
|
-
mkdirSync3(skillPath, { recursive: true });
|
|
2457
|
-
const skillMd = input.skillMd.endsWith(`
|
|
2458
|
-
`) ? input.skillMd : `${input.skillMd}
|
|
2459
|
-
`;
|
|
2460
|
-
writeFileSync3(join5(skillPath, "SKILL.md"), skillMd);
|
|
2479
|
+
function buildCorpusManifest(input, name) {
|
|
2461
2480
|
const frontmatter = parseSkillFrontmatter(input.skillMd) ?? undefined;
|
|
2462
2481
|
const kind = input.meta?.kind ?? parseSkillKind(frontmatter?.kind) ?? "instruction";
|
|
2463
|
-
|
|
2482
|
+
return {
|
|
2464
2483
|
$schema: PORTABLE_SKILL_SCHEMA,
|
|
2465
2484
|
standard: PORTABLE_SKILL_STANDARD,
|
|
2466
2485
|
name,
|
|
@@ -2473,19 +2492,60 @@ function writeCorpusSkill(input, options = {}) {
|
|
|
2473
2492
|
inputs: [],
|
|
2474
2493
|
commands: []
|
|
2475
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);
|
|
2476
2504
|
writeSkillJsonWithHash(skillPath, manifest);
|
|
2477
2505
|
return { name, path: skillPath, manifest, created };
|
|
2478
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
|
+
}
|
|
2479
2539
|
function validatePortableSkillDirectory(name, skillPath) {
|
|
2480
2540
|
const normalizedName = normalizePortableSkillName(name);
|
|
2481
2541
|
const base = validateSkillDirectory(normalizedName, skillPath);
|
|
2482
2542
|
const issues = [...base.issues];
|
|
2483
2543
|
const warnings = [...base.warnings];
|
|
2484
2544
|
let manifest;
|
|
2485
|
-
if (
|
|
2486
|
-
const skillJsonPath =
|
|
2487
|
-
const skillMdPath =
|
|
2488
|
-
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)) {
|
|
2489
2549
|
add3(issues, "portable.manifest_missing", "Missing portable manifest: expected SKILL.md frontmatter and/or skill.json");
|
|
2490
2550
|
}
|
|
2491
2551
|
try {
|
|
@@ -2504,7 +2564,7 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
2504
2564
|
add3(issues, "portable.version_missing", "Portable manifest missing version");
|
|
2505
2565
|
}
|
|
2506
2566
|
const contractIssues = validatePortableManifestContract(manifest, {
|
|
2507
|
-
strict:
|
|
2567
|
+
strict: existsSync6(join6(skillPath, "skill.json")),
|
|
2508
2568
|
skillPath
|
|
2509
2569
|
});
|
|
2510
2570
|
for (const issue of contractIssues)
|
|
@@ -2540,10 +2600,10 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
2540
2600
|
add3(issues, "portable.command_entry_unsafe", `Command '${command.name}' entry '${command.entry}' must stay inside the skill directory`);
|
|
2541
2601
|
continue;
|
|
2542
2602
|
}
|
|
2543
|
-
const entryPath =
|
|
2544
|
-
if (!
|
|
2603
|
+
const entryPath = join6(skillPath, command.entry);
|
|
2604
|
+
if (!existsSync6(entryPath))
|
|
2545
2605
|
add3(issues, "portable.command_entry_missing", `Command '${command.name}' entry '${command.entry}' is missing`);
|
|
2546
|
-
else if (
|
|
2606
|
+
else if (statSync6(entryPath).isDirectory())
|
|
2547
2607
|
add3(issues, "portable.command_entry_directory", `Command '${command.name}' entry '${command.entry}' must be a file`);
|
|
2548
2608
|
}
|
|
2549
2609
|
}
|
|
@@ -2551,7 +2611,7 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
2551
2611
|
} catch (error) {
|
|
2552
2612
|
add3(issues, "portable.manifest_invalid", error.message);
|
|
2553
2613
|
}
|
|
2554
|
-
if (manifest?.kind !== "instruction" && !
|
|
2614
|
+
if (manifest?.kind !== "instruction" && !existsSync6(join6(skillPath, "AGENTS.md"))) {
|
|
2555
2615
|
add3(issues, "portable.agents_missing", "Missing AGENTS.md with build-out instructions for coding agents");
|
|
2556
2616
|
}
|
|
2557
2617
|
}
|
|
@@ -2587,13 +2647,13 @@ async function runPortableSkill(name, args, options = {}) {
|
|
|
2587
2647
|
if (!isSafeRelativePath2(command.entry)) {
|
|
2588
2648
|
return { exitCode: 1, error: `Portable skill '${name}' command entry is unsafe` };
|
|
2589
2649
|
}
|
|
2590
|
-
const entryPath =
|
|
2591
|
-
if (!
|
|
2650
|
+
const entryPath = join6(skill.path, command.entry);
|
|
2651
|
+
if (!existsSync6(entryPath)) {
|
|
2592
2652
|
return { exitCode: 1, error: `Entry point '${command.entry}' not found in portable skill '${name}'` };
|
|
2593
2653
|
}
|
|
2594
|
-
const pkgPath =
|
|
2595
|
-
const nodeModules =
|
|
2596
|
-
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)) {
|
|
2597
2657
|
const install = Bun.spawn(["bun", "install", "--no-save"], {
|
|
2598
2658
|
cwd: skill.path,
|
|
2599
2659
|
stdout: "pipe",
|
|
@@ -2633,7 +2693,7 @@ function summarizePortableSkill(skillPath, fallbackName) {
|
|
|
2633
2693
|
}
|
|
2634
2694
|
function safeIsDirectory(path) {
|
|
2635
2695
|
try {
|
|
2636
|
-
return
|
|
2696
|
+
return statSync6(path).isDirectory();
|
|
2637
2697
|
} catch {
|
|
2638
2698
|
return false;
|
|
2639
2699
|
}
|
|
@@ -2862,20 +2922,20 @@ function parseSkillMdFrontmatter(content) {
|
|
|
2862
2922
|
return Object.keys(result).length > 0 ? result : null;
|
|
2863
2923
|
}
|
|
2864
2924
|
function discoverSkillsInDir(dir, source = "custom") {
|
|
2865
|
-
if (!
|
|
2925
|
+
if (!existsSync7(dir))
|
|
2866
2926
|
return [];
|
|
2867
2927
|
const result = [];
|
|
2868
2928
|
try {
|
|
2869
|
-
const entries =
|
|
2929
|
+
const entries = readdirSync6(dir, { withFileTypes: true });
|
|
2870
2930
|
for (const entry of entries) {
|
|
2871
2931
|
if (!entry.isDirectory())
|
|
2872
2932
|
continue;
|
|
2873
|
-
const skillMdPath =
|
|
2874
|
-
if (!
|
|
2933
|
+
const skillMdPath = join7(dir, entry.name, "SKILL.md");
|
|
2934
|
+
if (!existsSync7(skillMdPath))
|
|
2875
2935
|
continue;
|
|
2876
2936
|
let content;
|
|
2877
2937
|
try {
|
|
2878
|
-
content =
|
|
2938
|
+
content = readFileSync6(skillMdPath, "utf-8");
|
|
2879
2939
|
} catch {
|
|
2880
2940
|
continue;
|
|
2881
2941
|
}
|
|
@@ -2890,6 +2950,7 @@ function discoverSkillsInDir(dir, source = "custom") {
|
|
|
2890
2950
|
category: fm.category || "Development Tools",
|
|
2891
2951
|
tags: fm.tags || [],
|
|
2892
2952
|
...fm.kind ? { kind: fm.kind } : {},
|
|
2953
|
+
...isHostedMetadataSkillDir(join7(dir, entry.name)) ? { serverOwned: true } : {},
|
|
2893
2954
|
source
|
|
2894
2955
|
});
|
|
2895
2956
|
}
|
|
@@ -2898,20 +2959,20 @@ function discoverSkillsInDir(dir, source = "custom") {
|
|
|
2898
2959
|
}
|
|
2899
2960
|
function findExtensionSkillPath(name) {
|
|
2900
2961
|
const config = loadConfig();
|
|
2901
|
-
if (!config.extensionsDir || !
|
|
2962
|
+
if (!config.extensionsDir || !existsSync7(config.extensionsDir))
|
|
2902
2963
|
return null;
|
|
2903
2964
|
try {
|
|
2904
|
-
const entries =
|
|
2965
|
+
const entries = readdirSync6(config.extensionsDir, { withFileTypes: true });
|
|
2905
2966
|
for (const entry of entries) {
|
|
2906
2967
|
if (!entry.isDirectory())
|
|
2907
2968
|
continue;
|
|
2908
|
-
const skillDir =
|
|
2909
|
-
const skillMdPath =
|
|
2910
|
-
if (!
|
|
2969
|
+
const skillDir = join7(config.extensionsDir, entry.name);
|
|
2970
|
+
const skillMdPath = join7(skillDir, "SKILL.md");
|
|
2971
|
+
if (!existsSync7(skillMdPath))
|
|
2911
2972
|
continue;
|
|
2912
2973
|
let content;
|
|
2913
2974
|
try {
|
|
2914
|
-
content =
|
|
2975
|
+
content = readFileSync6(skillMdPath, "utf-8");
|
|
2915
2976
|
} catch {
|
|
2916
2977
|
continue;
|
|
2917
2978
|
}
|
|
@@ -2944,7 +3005,7 @@ function loadRegistry(cwd) {
|
|
|
2944
3005
|
const official = SKILLS.map((s) => ({ ...s, source: "official" }));
|
|
2945
3006
|
const extensions = config.extensionsDir ? discoverSkillsInDir(config.extensionsDir, "extension") : [];
|
|
2946
3007
|
const portableCustom = listPortableSkillMetas();
|
|
2947
|
-
const legacyCustom = discoverSkillsInDir(
|
|
3008
|
+
const legacyCustom = discoverSkillsInDir(join7(dataDir, "custom"));
|
|
2948
3009
|
const globalCustom = mergeCustomSkills([...legacyCustom, ...portableCustom]);
|
|
2949
3010
|
registryCache = mergeSkillRegistryLists(official, extensions, globalCustom);
|
|
2950
3011
|
registryCacheTime = now;
|
|
@@ -2991,7 +3052,7 @@ function getAllTags() {
|
|
|
2991
3052
|
return Array.from(tagSet).sort();
|
|
2992
3053
|
}
|
|
2993
3054
|
// src/lib/installer.ts
|
|
2994
|
-
import { existsSync as existsSync10, readFileSync as
|
|
3055
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9, rmSync as rmSync3 } from "fs";
|
|
2995
3056
|
import { dirname as dirname5, join as join10 } from "path";
|
|
2996
3057
|
import { homedir as homedir3 } from "os";
|
|
2997
3058
|
import { fileURLToPath } from "url";
|
|
@@ -3000,36 +3061,19 @@ import { fileURLToPath } from "url";
|
|
|
3000
3061
|
import {
|
|
3001
3062
|
cpSync as cpSync3,
|
|
3002
3063
|
existsSync as existsSync8,
|
|
3003
|
-
mkdirSync as
|
|
3004
|
-
mkdtempSync,
|
|
3005
|
-
readFileSync as
|
|
3064
|
+
mkdirSync as mkdirSync4,
|
|
3065
|
+
mkdtempSync as mkdtempSync2,
|
|
3066
|
+
readFileSync as readFileSync7,
|
|
3006
3067
|
readdirSync as readdirSync7,
|
|
3007
|
-
renameSync as
|
|
3008
|
-
rmSync as
|
|
3068
|
+
renameSync as renameSync2,
|
|
3069
|
+
rmSync as rmSync2,
|
|
3009
3070
|
statSync as statSync7,
|
|
3010
|
-
writeFileSync as
|
|
3071
|
+
writeFileSync as writeFileSync4
|
|
3011
3072
|
} from "fs";
|
|
3012
3073
|
import { homedir as homedir2 } from "os";
|
|
3013
3074
|
import { basename as basename3, dirname as dirname4, join as join8 } from "path";
|
|
3014
|
-
|
|
3015
3075
|
// src/lib/home-migration.ts
|
|
3016
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync6, renameSync as renameSync2, rmSync as rmSync2, statSync as statSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
3017
|
-
import { join as join7 } from "path";
|
|
3018
|
-
var SKILLS_CACHE_DIRNAME = "skills";
|
|
3019
|
-
var LAYOUT_MIGRATION_RECORD = ".layout-migration.json";
|
|
3020
|
-
function layoutMigrationRecordPath(appDir) {
|
|
3021
|
-
return join7(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD);
|
|
3022
|
-
}
|
|
3023
|
-
function isOwnerLayoutMigrated(appDir) {
|
|
3024
|
-
return existsSync7(layoutMigrationRecordPath(appDir));
|
|
3025
|
-
}
|
|
3026
3076
|
function resolveCorpusRoot(options = {}) {
|
|
3027
|
-
if (options.rootDir)
|
|
3028
|
-
return options.rootDir;
|
|
3029
|
-
const appDir = options.homeDir ? join7(options.homeDir, ".hasna", "skills") : getDataDir();
|
|
3030
|
-
const cache = join7(appDir, SKILLS_CACHE_DIRNAME);
|
|
3031
|
-
if (isOwnerLayoutMigrated(appDir) && existsSync7(cache))
|
|
3032
|
-
return cache;
|
|
3033
3077
|
return getPortableSkillsRoot(options);
|
|
3034
3078
|
}
|
|
3035
3079
|
|
|
@@ -3075,6 +3119,10 @@ ${lines.join(`
|
|
|
3075
3119
|
---
|
|
3076
3120
|
${body}`;
|
|
3077
3121
|
}
|
|
3122
|
+
var POINTER_MARKER_PHRASE = "This is an executable skill from the @hasna/skills catalog";
|
|
3123
|
+
function isPointerSkillMd(markdown) {
|
|
3124
|
+
return markdown.includes(POINTER_MARKER_PHRASE) && /^kind:\s*executable\b/m.test(markdown);
|
|
3125
|
+
}
|
|
3078
3126
|
function pointerSkillMd(name, description) {
|
|
3079
3127
|
const display = name.replace(/[-_]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
3080
3128
|
return [
|
|
@@ -3100,7 +3148,7 @@ function resolveSyncCorpus(options = {}) {
|
|
|
3100
3148
|
if (explicit) {
|
|
3101
3149
|
const roots = packageSourceRoots(explicit);
|
|
3102
3150
|
if (roots.length === 0) {
|
|
3103
|
-
throw new Error(`SKILLS_SOURCE '${explicit}' contains no skills: expected a corpus directory or a package root with skills
|
|
3151
|
+
throw new Error(`SKILLS_SOURCE '${explicit}' contains no skills: expected a corpus directory or a package root with skills/`);
|
|
3104
3152
|
}
|
|
3105
3153
|
return { roots, source: "source" };
|
|
3106
3154
|
}
|
|
@@ -3108,7 +3156,7 @@ function resolveSyncCorpus(options = {}) {
|
|
|
3108
3156
|
}
|
|
3109
3157
|
function packageSourceRoots(source) {
|
|
3110
3158
|
const roots = [];
|
|
3111
|
-
for (const sub of ["skills"
|
|
3159
|
+
for (const sub of ["skills"]) {
|
|
3112
3160
|
const candidate = join8(source, sub);
|
|
3113
3161
|
if (existsSync8(candidate) && isDirectory(candidate))
|
|
3114
3162
|
roots.push(candidate);
|
|
@@ -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);
|
|
@@ -3233,12 +3281,27 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
3233
3281
|
reason: "an unmanaged SKILL.md already exists here (hand-authored); pass --force to overwrite"
|
|
3234
3282
|
};
|
|
3235
3283
|
}
|
|
3284
|
+
if (dirExists && managed && hasSkillMd && !options.force && isPointerSkillMd(skillMd)) {
|
|
3285
|
+
let existingIsStub = false;
|
|
3286
|
+
try {
|
|
3287
|
+
existingIsStub = isPointerSkillMd(readFileSync7(skillMdPath, "utf-8"));
|
|
3288
|
+
} catch {
|
|
3289
|
+
existingIsStub = false;
|
|
3290
|
+
}
|
|
3291
|
+
if (!existingIsStub) {
|
|
3292
|
+
return {
|
|
3293
|
+
action: "skip",
|
|
3294
|
+
path: skillMdPath,
|
|
3295
|
+
reason: "refusing to replace a content-bearing managed home with an executable pointer stub (the corpus entry lacks kind: instruction); pass --force to overwrite"
|
|
3296
|
+
};
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3236
3299
|
const action = dirExists ? "update" : "create";
|
|
3237
3300
|
if (options.dryRun)
|
|
3238
3301
|
return { action, path: skillMdPath };
|
|
3239
3302
|
const parentDir = dirname4(dir);
|
|
3240
|
-
|
|
3241
|
-
const transactionDir =
|
|
3303
|
+
mkdirSync4(parentDir, { recursive: true });
|
|
3304
|
+
const transactionDir = mkdtempSync2(join8(parentDir, `.hasna-skills-write-${basename3(dir)}-`));
|
|
3242
3305
|
const candidateDir = join8(transactionDir, "candidate");
|
|
3243
3306
|
const backupDir = join8(transactionDir, "backup");
|
|
3244
3307
|
const candidateSkillMdPath = join8(candidateDir, "SKILL.md");
|
|
@@ -3249,19 +3312,19 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
3249
3312
|
source: options.source ?? "corpus",
|
|
3250
3313
|
syncedAt: new Date().toISOString()
|
|
3251
3314
|
};
|
|
3252
|
-
const renameDirectory = options.renameDirectory ??
|
|
3315
|
+
const renameDirectory = options.renameDirectory ?? renameSync2;
|
|
3253
3316
|
let originalMoved = false;
|
|
3254
3317
|
let preserveTransaction = false;
|
|
3255
3318
|
try {
|
|
3256
3319
|
if (options.resourceDir) {
|
|
3257
3320
|
cpSync3(options.resourceDir, candidateDir, { recursive: true, force: true });
|
|
3258
3321
|
} else {
|
|
3259
|
-
|
|
3322
|
+
mkdirSync4(candidateDir, { recursive: true });
|
|
3260
3323
|
}
|
|
3261
|
-
|
|
3324
|
+
writeFileSync4(candidateSkillMdPath, skillMd.endsWith(`
|
|
3262
3325
|
`) ? skillMd : `${skillMd}
|
|
3263
3326
|
`);
|
|
3264
|
-
|
|
3327
|
+
writeFileSync4(candidateMarkerPath, `${JSON.stringify(marker, null, 2)}
|
|
3265
3328
|
`);
|
|
3266
3329
|
if (dirExists) {
|
|
3267
3330
|
originalMoved = true;
|
|
@@ -3272,7 +3335,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
3272
3335
|
if (originalMoved && existsSync8(backupDir)) {
|
|
3273
3336
|
try {
|
|
3274
3337
|
if (existsSync8(dir))
|
|
3275
|
-
|
|
3338
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
3276
3339
|
renameDirectory(backupDir, dir);
|
|
3277
3340
|
originalMoved = false;
|
|
3278
3341
|
} catch (rollbackError) {
|
|
@@ -3284,7 +3347,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
3284
3347
|
} finally {
|
|
3285
3348
|
if (!preserveTransaction) {
|
|
3286
3349
|
try {
|
|
3287
|
-
|
|
3350
|
+
rmSync2(transactionDir, { recursive: true, force: true });
|
|
3288
3351
|
} catch {}
|
|
3289
3352
|
}
|
|
3290
3353
|
}
|
|
@@ -3294,14 +3357,14 @@ function removeManagedAgentSkill(skill, agent, homeDir = homedir2()) {
|
|
|
3294
3357
|
const dir = join8(agentGlobalSkillsDir(agent, homeDir), skill);
|
|
3295
3358
|
if (!existsSync8(join8(dir, SYNC_MARKER_FILE)))
|
|
3296
3359
|
return false;
|
|
3297
|
-
|
|
3360
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
3298
3361
|
return true;
|
|
3299
3362
|
}
|
|
3300
3363
|
function sourceSkillMd(skillPath, name, description, kind, preferBundledDocs = false) {
|
|
3301
|
-
if (kind === "instruction" || preferBundledDocs) {
|
|
3364
|
+
if (kind === undefined || kind === "instruction" || preferBundledDocs) {
|
|
3302
3365
|
const skillMdPath = join8(skillPath, "SKILL.md");
|
|
3303
3366
|
if (existsSync8(skillMdPath))
|
|
3304
|
-
return
|
|
3367
|
+
return readFileSync7(skillMdPath, "utf-8");
|
|
3305
3368
|
}
|
|
3306
3369
|
return pointerSkillMd(name, description);
|
|
3307
3370
|
}
|
|
@@ -3331,7 +3394,7 @@ function normalizeSkillName(name) {
|
|
|
3331
3394
|
}
|
|
3332
3395
|
|
|
3333
3396
|
// src/lib/project-state.ts
|
|
3334
|
-
import { existsSync as existsSync9, mkdirSync as
|
|
3397
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
3335
3398
|
import { join as join9 } from "path";
|
|
3336
3399
|
var VALID_PIN_SOURCES = [
|
|
3337
3400
|
"official",
|
|
@@ -3357,7 +3420,7 @@ function loadProjectConfig(targetDir = process.cwd()) {
|
|
|
3357
3420
|
if (!existsSync9(path))
|
|
3358
3421
|
return null;
|
|
3359
3422
|
try {
|
|
3360
|
-
return normalizeProjectConfig(JSON.parse(
|
|
3423
|
+
return normalizeProjectConfig(JSON.parse(readFileSync8(path, "utf-8")));
|
|
3361
3424
|
} catch {
|
|
3362
3425
|
return null;
|
|
3363
3426
|
}
|
|
@@ -3378,9 +3441,9 @@ function ensureProjectConfig(targetDir = process.cwd()) {
|
|
|
3378
3441
|
}
|
|
3379
3442
|
function saveProjectConfig(config, targetDir = process.cwd()) {
|
|
3380
3443
|
const dir = getProjectStateDir(targetDir);
|
|
3381
|
-
|
|
3444
|
+
mkdirSync5(dir, { recursive: true });
|
|
3382
3445
|
const normalized = normalizeProjectConfig({ ...config, updatedAt: new Date().toISOString() });
|
|
3383
|
-
|
|
3446
|
+
writeFileSync5(getProjectConfigPath(targetDir), JSON.stringify(normalized, null, 2) + `
|
|
3384
3447
|
`);
|
|
3385
3448
|
}
|
|
3386
3449
|
function pinProjectSkill(name, details = {}, targetDir = process.cwd()) {
|
|
@@ -3559,7 +3622,7 @@ function createLocalSkillManifest(name, generateSkillMd) {
|
|
|
3559
3622
|
let skillMd = "";
|
|
3560
3623
|
const skillMdPath = join10(sourcePath, "SKILL.md");
|
|
3561
3624
|
if (existsSync10(skillMdPath)) {
|
|
3562
|
-
skillMd =
|
|
3625
|
+
skillMd = readFileSync9(skillMdPath, "utf-8");
|
|
3563
3626
|
} else if (generateSkillMd) {
|
|
3564
3627
|
skillMd = generateSkillMd(name) ?? "";
|
|
3565
3628
|
} else {
|
|
@@ -3670,14 +3733,14 @@ function removeSkillForAgent(name, options) {
|
|
|
3670
3733
|
const dir = getAgentSkillPath(canonicalName, options.agent, scope, options.projectDir);
|
|
3671
3734
|
if (!existsSync10(join10(dir, SYNC_MARKER_FILE)))
|
|
3672
3735
|
return false;
|
|
3673
|
-
|
|
3736
|
+
rmSync3(dir, { recursive: true, force: true });
|
|
3674
3737
|
return true;
|
|
3675
3738
|
}
|
|
3676
3739
|
function resolveAgentSkillMd(name, generateSkillMd) {
|
|
3677
3740
|
const sourcePath = getSkillPath(name);
|
|
3678
3741
|
const skillMdPath = join10(sourcePath, "SKILL.md");
|
|
3679
3742
|
if (existsSync10(skillMdPath))
|
|
3680
|
-
return
|
|
3743
|
+
return readFileSync9(skillMdPath, "utf-8");
|
|
3681
3744
|
if (generateSkillMd)
|
|
3682
3745
|
return generateSkillMd(name);
|
|
3683
3746
|
return generateMinimalSkillMd(name);
|
|
@@ -3733,21 +3796,21 @@ function readBundledSkillVersion(name) {
|
|
|
3733
3796
|
if (!existsSync10(pkgPath))
|
|
3734
3797
|
return "unknown";
|
|
3735
3798
|
try {
|
|
3736
|
-
const pkg = JSON.parse(
|
|
3799
|
+
const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
|
|
3737
3800
|
return pkg.version || "unknown";
|
|
3738
3801
|
} catch {
|
|
3739
3802
|
return "unknown";
|
|
3740
3803
|
}
|
|
3741
3804
|
}
|
|
3742
3805
|
function readFileIfExists(path) {
|
|
3743
|
-
return existsSync10(path) ?
|
|
3806
|
+
return existsSync10(path) ? readFileSync9(path, "utf-8") : null;
|
|
3744
3807
|
}
|
|
3745
3808
|
function loadProjectConfigCompat(targetDir) {
|
|
3746
3809
|
return loadProjectConfig(targetDir);
|
|
3747
3810
|
}
|
|
3748
3811
|
// src/lib/run-state.ts
|
|
3749
3812
|
import { createHash as createHash2, randomBytes } from "crypto";
|
|
3750
|
-
import { existsSync as existsSync11, mkdirSync as
|
|
3813
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync10, readdirSync as readdirSync8, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
3751
3814
|
import { extname, join as join11, relative as relative2 } from "path";
|
|
3752
3815
|
function createSkillRun(params, targetDir = process.cwd()) {
|
|
3753
3816
|
const now = new Date;
|
|
@@ -3758,9 +3821,9 @@ function createSkillRun(params, targetDir = process.cwd()) {
|
|
|
3758
3821
|
const runDir = join11(root, "runs", day, id);
|
|
3759
3822
|
const logsDir = join11(runDir, "logs");
|
|
3760
3823
|
const exportDir = join11(root, "exports", skillName, id);
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3824
|
+
mkdirSync6(logsDir, { recursive: true });
|
|
3825
|
+
mkdirSync6(exportDir, { recursive: true });
|
|
3826
|
+
mkdirSync6(join11(root, "tmp"), { recursive: true });
|
|
3764
3827
|
const record = {
|
|
3765
3828
|
id,
|
|
3766
3829
|
skill: skillName,
|
|
@@ -3811,15 +3874,15 @@ function updateSkillRun(context, patch) {
|
|
|
3811
3874
|
return context.record;
|
|
3812
3875
|
}
|
|
3813
3876
|
function writeRunLogs(context, stdout = "", stderr = "") {
|
|
3814
|
-
|
|
3815
|
-
|
|
3877
|
+
writeFileSync6(join11(context.logsDir, "stdout.log"), stdout);
|
|
3878
|
+
writeFileSync6(join11(context.logsDir, "stderr.log"), stderr);
|
|
3816
3879
|
}
|
|
3817
3880
|
function appendRunEvent(context, event, data = {}) {
|
|
3818
3881
|
const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
|
|
3819
3882
|
`;
|
|
3820
3883
|
const path = join11(context.runDir, "events.ndjson");
|
|
3821
|
-
const previous = existsSync11(path) ?
|
|
3822
|
-
|
|
3884
|
+
const previous = existsSync11(path) ? readFileSync10(path, "utf-8") : "";
|
|
3885
|
+
writeFileSync6(path, previous + line);
|
|
3823
3886
|
}
|
|
3824
3887
|
function listSkillRuns(targetDir = process.cwd(), limit = 50) {
|
|
3825
3888
|
const runsRoot = join11(getProjectStateDir(targetDir), "runs");
|
|
@@ -3855,11 +3918,11 @@ function getRunExportDir(runId, skill, targetDir = process.cwd()) {
|
|
|
3855
3918
|
return join11(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
|
|
3856
3919
|
}
|
|
3857
3920
|
function writeRunRecord(context) {
|
|
3858
|
-
|
|
3921
|
+
writeFileSync6(join11(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
|
|
3859
3922
|
`);
|
|
3860
3923
|
}
|
|
3861
3924
|
function writeArtifactsManifest(context, artifacts) {
|
|
3862
|
-
|
|
3925
|
+
writeFileSync6(join11(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
|
|
3863
3926
|
`);
|
|
3864
3927
|
}
|
|
3865
3928
|
function collectRunArtifacts(context) {
|
|
@@ -3868,7 +3931,7 @@ function collectRunArtifacts(context) {
|
|
|
3868
3931
|
const artifacts = [];
|
|
3869
3932
|
for (const path of walkFiles(context.exportDir)) {
|
|
3870
3933
|
const stat = statSync8(path);
|
|
3871
|
-
const bytes =
|
|
3934
|
+
const bytes = readFileSync10(path);
|
|
3872
3935
|
artifacts.push({
|
|
3873
3936
|
path: toProjectRelative(context.targetDir, path),
|
|
3874
3937
|
mime: mimeForPath(path),
|
|
@@ -3883,7 +3946,7 @@ function readRunRecord(runDir) {
|
|
|
3883
3946
|
if (!existsSync11(path))
|
|
3884
3947
|
return null;
|
|
3885
3948
|
try {
|
|
3886
|
-
return JSON.parse(
|
|
3949
|
+
return JSON.parse(readFileSync10(path, "utf-8"));
|
|
3887
3950
|
} catch {
|
|
3888
3951
|
return null;
|
|
3889
3952
|
}
|
|
@@ -3937,7 +4000,7 @@ function mimeForPath(path) {
|
|
|
3937
4000
|
}
|
|
3938
4001
|
}
|
|
3939
4002
|
// src/lib/skillinfo.ts
|
|
3940
|
-
import { existsSync as existsSync12, readFileSync as
|
|
4003
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
3941
4004
|
import { join as join12 } from "path";
|
|
3942
4005
|
function isInstructionSkillDir(skillPath, meta) {
|
|
3943
4006
|
if (meta?.kind === "instruction")
|
|
@@ -3946,7 +4009,7 @@ function isInstructionSkillDir(skillPath, meta) {
|
|
|
3946
4009
|
if (!existsSync12(skillMdPath))
|
|
3947
4010
|
return false;
|
|
3948
4011
|
try {
|
|
3949
|
-
return parseSkillFrontmatter(
|
|
4012
|
+
return parseSkillFrontmatter(readFileSync11(skillMdPath, "utf-8"))?.kind === "instruction";
|
|
3950
4013
|
} catch {
|
|
3951
4014
|
return false;
|
|
3952
4015
|
}
|
|
@@ -4031,7 +4094,7 @@ function getSkillRequirements(name) {
|
|
|
4031
4094
|
const pkgPath = join12(skillPath, "package.json");
|
|
4032
4095
|
if (existsSync12(pkgPath)) {
|
|
4033
4096
|
try {
|
|
4034
|
-
const pkg = JSON.parse(
|
|
4097
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
4035
4098
|
dependencies = pkg.dependencies || {};
|
|
4036
4099
|
} catch {}
|
|
4037
4100
|
}
|
|
@@ -4064,7 +4127,7 @@ async function runSkill(name, args, options = {}) {
|
|
|
4064
4127
|
}
|
|
4065
4128
|
let entryPoint;
|
|
4066
4129
|
try {
|
|
4067
|
-
const pkg = JSON.parse(
|
|
4130
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
4068
4131
|
if (pkg.bin) {
|
|
4069
4132
|
const binValues = Object.values(pkg.bin);
|
|
4070
4133
|
entryPoint = binValues[0];
|
|
@@ -4170,7 +4233,7 @@ function generateSkillMd(name) {
|
|
|
4170
4233
|
const pkgPath = join12(skillPath, "package.json");
|
|
4171
4234
|
if (existsSync12(pkgPath)) {
|
|
4172
4235
|
try {
|
|
4173
|
-
const pkg = JSON.parse(
|
|
4236
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
4174
4237
|
if (pkg.bin) {
|
|
4175
4238
|
const binKeys = Object.keys(pkg.bin);
|
|
4176
4239
|
if (binKeys.length > 0)
|
|
@@ -4246,7 +4309,7 @@ function extractEnvVars(text) {
|
|
|
4246
4309
|
function readIfExists(path) {
|
|
4247
4310
|
try {
|
|
4248
4311
|
if (existsSync12(path)) {
|
|
4249
|
-
return
|
|
4312
|
+
return readFileSync11(path, "utf-8");
|
|
4250
4313
|
}
|
|
4251
4314
|
} catch {}
|
|
4252
4315
|
return null;
|
|
@@ -8249,18 +8312,22 @@ function requireApiUrl(action = "This command", config, env) {
|
|
|
8249
8312
|
}
|
|
8250
8313
|
|
|
8251
8314
|
// src/lib/auth-store.ts
|
|
8252
|
-
import { existsSync as existsSync13, mkdirSync as
|
|
8253
|
-
import { join as join13 } from "path";
|
|
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";
|
|
8254
8317
|
import { homedir as homedir4 } from "os";
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8318
|
+
function getAuthFilePath() {
|
|
8319
|
+
return join13(getDataDir(), "auth.json");
|
|
8320
|
+
}
|
|
8321
|
+
function legacyAuthFilePath() {
|
|
8322
|
+
return join13(process.env["HOME"] || process.env["USERPROFILE"] || homedir4(), ".skills", "auth.json");
|
|
8323
|
+
}
|
|
8258
8324
|
var cachedConfig;
|
|
8259
8325
|
function getAuthConfig() {
|
|
8260
8326
|
if (cachedConfig !== undefined)
|
|
8261
8327
|
return cachedConfig;
|
|
8262
8328
|
try {
|
|
8263
|
-
const
|
|
8329
|
+
const file = existsSync13(getAuthFilePath()) ? getAuthFilePath() : legacyAuthFilePath();
|
|
8330
|
+
const raw = readFileSync12(file, "utf-8");
|
|
8264
8331
|
const config = JSON.parse(raw);
|
|
8265
8332
|
if (!config.apiKey) {
|
|
8266
8333
|
cachedConfig = null;
|
|
@@ -9084,6 +9151,30 @@ function pickNumber(record, key) {
|
|
|
9084
9151
|
}
|
|
9085
9152
|
|
|
9086
9153
|
// src/lib/remote-client.ts
|
|
9154
|
+
class RemoteRouteUnsupportedError extends Error {
|
|
9155
|
+
path;
|
|
9156
|
+
status;
|
|
9157
|
+
instance;
|
|
9158
|
+
constructor(path, status, instance) {
|
|
9159
|
+
super(`The configured Skills instance does not support ${path} (HTTP ${status}). ` + `The instance at ${instance} predates this client feature \u2014 upgrade the server, or ` + `use a client version that matches it.`);
|
|
9160
|
+
this.path = path;
|
|
9161
|
+
this.status = status;
|
|
9162
|
+
this.instance = instance;
|
|
9163
|
+
this.name = "RemoteRouteUnsupportedError";
|
|
9164
|
+
}
|
|
9165
|
+
}
|
|
9166
|
+
|
|
9167
|
+
class RemoteRequestError extends Error {
|
|
9168
|
+
path;
|
|
9169
|
+
status;
|
|
9170
|
+
constructor(path, status, statusText) {
|
|
9171
|
+
super(`Remote request to ${path} failed: HTTP ${status}${statusText ? ` ${statusText}` : ""}`);
|
|
9172
|
+
this.path = path;
|
|
9173
|
+
this.status = status;
|
|
9174
|
+
this.name = "RemoteRequestError";
|
|
9175
|
+
}
|
|
9176
|
+
}
|
|
9177
|
+
|
|
9087
9178
|
class RemoteSkillsClient {
|
|
9088
9179
|
apiUrl;
|
|
9089
9180
|
apiKey;
|
|
@@ -9101,6 +9192,20 @@ class RemoteSkillsClient {
|
|
|
9101
9192
|
}
|
|
9102
9193
|
});
|
|
9103
9194
|
}
|
|
9195
|
+
async requestNewRoute(path, options, opts = {}) {
|
|
9196
|
+
const response = await this.request(path, options);
|
|
9197
|
+
const routePath = path.split("?")[0];
|
|
9198
|
+
if (response.status === 404 || response.status === 405) {
|
|
9199
|
+
if (response.status === 404 && opts.domainNotFoundCodes?.length && await responseBodyCarriesCode(response, opts.domainNotFoundCodes)) {
|
|
9200
|
+
return response;
|
|
9201
|
+
}
|
|
9202
|
+
throw new RemoteRouteUnsupportedError(routePath, response.status, this.apiUrl);
|
|
9203
|
+
}
|
|
9204
|
+
if (!response.ok) {
|
|
9205
|
+
throw new RemoteRequestError(routePath, response.status, response.statusText);
|
|
9206
|
+
}
|
|
9207
|
+
return response;
|
|
9208
|
+
}
|
|
9104
9209
|
async listSkills() {
|
|
9105
9210
|
const res = await this.request("/api/v1/skills");
|
|
9106
9211
|
return res.json();
|
|
@@ -9117,6 +9222,14 @@ class RemoteSkillsClient {
|
|
|
9117
9222
|
return null;
|
|
9118
9223
|
return res.json();
|
|
9119
9224
|
}
|
|
9225
|
+
async getSkillStatus(slug) {
|
|
9226
|
+
const res = await this.request(`/api/v1/skills/${encodeURIComponent(slug)}`, { method: "GET" });
|
|
9227
|
+
let body = null;
|
|
9228
|
+
try {
|
|
9229
|
+
body = await res.json();
|
|
9230
|
+
} catch {}
|
|
9231
|
+
return { status: res.status, body };
|
|
9232
|
+
}
|
|
9120
9233
|
async submitRun(slug, input, args) {
|
|
9121
9234
|
const res = await this.request(`/api/v1/runs/${slug}`, {
|
|
9122
9235
|
method: "POST",
|
|
@@ -9150,15 +9263,18 @@ class RemoteSkillsClient {
|
|
|
9150
9263
|
method: "GET"
|
|
9151
9264
|
});
|
|
9152
9265
|
}
|
|
9153
|
-
async publishSkill(manifest, bundle) {
|
|
9266
|
+
async publishSkill(manifest, bundle, ifMatch) {
|
|
9154
9267
|
const form = new FormData;
|
|
9155
9268
|
form.set("manifest", JSON.stringify(manifest));
|
|
9156
9269
|
if (bundle) {
|
|
9157
9270
|
form.set("bundle", new Blob([bundle], { type: "application/gzip" }), `${String(manifest.slug ?? "skill")}.tar.gz`);
|
|
9158
9271
|
}
|
|
9272
|
+
const headers = { Authorization: `Bearer ${this.apiKey}` };
|
|
9273
|
+
if (ifMatch)
|
|
9274
|
+
headers["If-Match"] = ifMatch;
|
|
9159
9275
|
return fetch(`${this.apiUrl}/api/v1/skills`, {
|
|
9160
9276
|
method: "POST",
|
|
9161
|
-
headers
|
|
9277
|
+
headers,
|
|
9162
9278
|
body: form
|
|
9163
9279
|
});
|
|
9164
9280
|
}
|
|
@@ -9174,6 +9290,135 @@ class RemoteSkillsClient {
|
|
|
9174
9290
|
return null;
|
|
9175
9291
|
return response;
|
|
9176
9292
|
}
|
|
9293
|
+
async listPins() {
|
|
9294
|
+
const response = await this.requestNewRoute("/api/v1/pins");
|
|
9295
|
+
return normalizePinList(await response.json());
|
|
9296
|
+
}
|
|
9297
|
+
async pin(slug, metadata) {
|
|
9298
|
+
const path = `/api/v1/pins/${encodeURIComponent(slug)}`;
|
|
9299
|
+
const response = await this.requestNewRoute(path, {
|
|
9300
|
+
method: "PUT",
|
|
9301
|
+
body: JSON.stringify({ ...metadata ? { metadata } : {} })
|
|
9302
|
+
});
|
|
9303
|
+
return normalizePin(await response.json());
|
|
9304
|
+
}
|
|
9305
|
+
async unpin(slug) {
|
|
9306
|
+
const path = `/api/v1/pins/${encodeURIComponent(slug)}`;
|
|
9307
|
+
const response = await this.requestNewRoute(path, { method: "DELETE" }, { domainNotFoundCodes: ["PIN_NOT_FOUND"] });
|
|
9308
|
+
return response.status !== 404;
|
|
9309
|
+
}
|
|
9310
|
+
async listTags() {
|
|
9311
|
+
const response = await this.requestNewRoute("/api/v1/tags");
|
|
9312
|
+
const payload = await response.json();
|
|
9313
|
+
if (!Array.isArray(payload)) {
|
|
9314
|
+
throw new Error("Remote tags payload did not match the expected contract (expected an array of tag names)");
|
|
9315
|
+
}
|
|
9316
|
+
for (const tag of payload) {
|
|
9317
|
+
if (typeof tag !== "string" || tag.trim().length === 0) {
|
|
9318
|
+
throw new Error("Remote tags payload did not match the expected contract (every element must be a non-empty tag name)");
|
|
9319
|
+
}
|
|
9320
|
+
}
|
|
9321
|
+
return payload;
|
|
9322
|
+
}
|
|
9323
|
+
async skillsByTag(tag) {
|
|
9324
|
+
const path = `/api/v1/tags/${encodeURIComponent(tag)}/skills`;
|
|
9325
|
+
const response = await this.requestNewRoute(path);
|
|
9326
|
+
return normalizeSkillSummaryList(await response.json());
|
|
9327
|
+
}
|
|
9328
|
+
async listUpdatedSince(since, options = {}) {
|
|
9329
|
+
const params = new URLSearchParams({ since });
|
|
9330
|
+
if (options.cursor)
|
|
9331
|
+
params.set("cursor", options.cursor);
|
|
9332
|
+
if (options.limit !== undefined)
|
|
9333
|
+
params.set("limit", String(options.limit));
|
|
9334
|
+
const response = await this.requestNewRoute(`/api/v1/skills/updated?${params.toString()}`);
|
|
9335
|
+
return normalizeUpdatedSincePage(await response.json());
|
|
9336
|
+
}
|
|
9337
|
+
}
|
|
9338
|
+
function requireOptionalString(record, field) {
|
|
9339
|
+
if (record[field] === undefined)
|
|
9340
|
+
return;
|
|
9341
|
+
if (typeof record[field] !== "string") {
|
|
9342
|
+
throw new Error(`Remote payload did not match the expected contract (${field} must be a string when present)`);
|
|
9343
|
+
}
|
|
9344
|
+
return record[field];
|
|
9345
|
+
}
|
|
9346
|
+
function normalizePin(entry) {
|
|
9347
|
+
if (!entry || typeof entry !== "object") {
|
|
9348
|
+
throw new Error("Remote pin payload did not match the expected contract (expected an object)");
|
|
9349
|
+
}
|
|
9350
|
+
const record = entry;
|
|
9351
|
+
const slug = typeof record.slug === "string" && record.slug.trim() ? record.slug.trim() : undefined;
|
|
9352
|
+
if (!slug) {
|
|
9353
|
+
throw new Error("Remote pin payload did not match the expected contract (missing slug)");
|
|
9354
|
+
}
|
|
9355
|
+
let metadata;
|
|
9356
|
+
if (record.metadata !== undefined) {
|
|
9357
|
+
if (!record.metadata || typeof record.metadata !== "object" || Array.isArray(record.metadata)) {
|
|
9358
|
+
throw new Error("Remote pin payload did not match the expected contract (metadata must be a JSON object when present)");
|
|
9359
|
+
}
|
|
9360
|
+
metadata = record.metadata;
|
|
9361
|
+
}
|
|
9362
|
+
const pinnedAt = requireOptionalString(record, "pinnedAt");
|
|
9363
|
+
return {
|
|
9364
|
+
slug,
|
|
9365
|
+
...pinnedAt !== undefined ? { pinnedAt } : {},
|
|
9366
|
+
...metadata ? { metadata } : {}
|
|
9367
|
+
};
|
|
9368
|
+
}
|
|
9369
|
+
function normalizePinList(payload) {
|
|
9370
|
+
if (!Array.isArray(payload)) {
|
|
9371
|
+
throw new Error("Remote pins payload did not match the expected contract (expected an array of pins)");
|
|
9372
|
+
}
|
|
9373
|
+
return payload.map(normalizePin);
|
|
9374
|
+
}
|
|
9375
|
+
function normalizeSkillSummary(entry) {
|
|
9376
|
+
if (!entry || typeof entry !== "object") {
|
|
9377
|
+
throw new Error("Remote skill payload did not match the expected contract (expected an object)");
|
|
9378
|
+
}
|
|
9379
|
+
const record = entry;
|
|
9380
|
+
const slug = typeof record.slug === "string" && record.slug.trim() ? record.slug.trim() : undefined;
|
|
9381
|
+
if (!slug) {
|
|
9382
|
+
throw new Error("Remote skill payload did not match the expected contract (missing slug)");
|
|
9383
|
+
}
|
|
9384
|
+
return {
|
|
9385
|
+
slug,
|
|
9386
|
+
...requireOptionalString(record, "name") !== undefined ? { name: requireOptionalString(record, "name") } : {},
|
|
9387
|
+
...requireOptionalString(record, "version") !== undefined ? { version: requireOptionalString(record, "version") } : {},
|
|
9388
|
+
...requireOptionalString(record, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record, "updatedAt") } : {}
|
|
9389
|
+
};
|
|
9390
|
+
}
|
|
9391
|
+
function normalizeSkillSummaryList(payload) {
|
|
9392
|
+
if (!Array.isArray(payload)) {
|
|
9393
|
+
throw new Error("Remote skills payload did not match the expected contract (expected an array of skills)");
|
|
9394
|
+
}
|
|
9395
|
+
return payload.map(normalizeSkillSummary);
|
|
9396
|
+
}
|
|
9397
|
+
async function responseBodyCarriesCode(response, codes) {
|
|
9398
|
+
try {
|
|
9399
|
+
const payload = await response.clone().json();
|
|
9400
|
+
if (!payload || typeof payload !== "object")
|
|
9401
|
+
return false;
|
|
9402
|
+
const code = payload.code;
|
|
9403
|
+
return typeof code === "string" && codes.includes(code);
|
|
9404
|
+
} catch {
|
|
9405
|
+
return false;
|
|
9406
|
+
}
|
|
9407
|
+
}
|
|
9408
|
+
function normalizeUpdatedSincePage(payload) {
|
|
9409
|
+
if (!payload || typeof payload !== "object") {
|
|
9410
|
+
throw new Error("Updated-since payload did not match the expected contract (expected an object)");
|
|
9411
|
+
}
|
|
9412
|
+
const record = payload;
|
|
9413
|
+
if (!Array.isArray(record.skills)) {
|
|
9414
|
+
throw new Error("Updated-since payload did not match the expected contract (missing skills array)");
|
|
9415
|
+
}
|
|
9416
|
+
const skills = record.skills.map(normalizeSkillSummary);
|
|
9417
|
+
const nextCursor = record.nextCursor === undefined || record.nextCursor === null ? null : record.nextCursor;
|
|
9418
|
+
if (nextCursor !== null && typeof nextCursor !== "string") {
|
|
9419
|
+
throw new Error("Updated-since payload did not match the expected contract (nextCursor must be a string or absent)");
|
|
9420
|
+
}
|
|
9421
|
+
return { skills, nextCursor };
|
|
9177
9422
|
}
|
|
9178
9423
|
function createRemoteSkillsClient() {
|
|
9179
9424
|
const apiKey = getApiKey();
|
|
@@ -9182,7 +9427,7 @@ function createRemoteSkillsClient() {
|
|
|
9182
9427
|
return new RemoteSkillsClient(apiKey);
|
|
9183
9428
|
}
|
|
9184
9429
|
// src/lib/scheduler.ts
|
|
9185
|
-
import { existsSync as existsSync14, readFileSync as
|
|
9430
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8 } from "fs";
|
|
9186
9431
|
import { join as join14 } from "path";
|
|
9187
9432
|
function getSchedulesPath(targetDir = process.cwd()) {
|
|
9188
9433
|
return join14(targetDir, ".skills", "schedules.json");
|
|
@@ -9191,7 +9436,7 @@ function loadSchedules(targetDir = process.cwd()) {
|
|
|
9191
9436
|
const path = getSchedulesPath(targetDir);
|
|
9192
9437
|
if (existsSync14(path)) {
|
|
9193
9438
|
try {
|
|
9194
|
-
return JSON.parse(
|
|
9439
|
+
return JSON.parse(readFileSync13(path, "utf-8"));
|
|
9195
9440
|
} catch {}
|
|
9196
9441
|
}
|
|
9197
9442
|
return { version: 1, schedules: [] };
|
|
@@ -9200,8 +9445,8 @@ function saveSchedules(data, targetDir = process.cwd()) {
|
|
|
9200
9445
|
const path = getSchedulesPath(targetDir);
|
|
9201
9446
|
const dir = join14(targetDir, ".skills");
|
|
9202
9447
|
if (!existsSync14(dir))
|
|
9203
|
-
|
|
9204
|
-
|
|
9448
|
+
mkdirSync8(dir, { recursive: true });
|
|
9449
|
+
writeFileSync8(path, JSON.stringify(data, null, 2));
|
|
9205
9450
|
}
|
|
9206
9451
|
function validateCronField(expr, min, max, label) {
|
|
9207
9452
|
for (const part of expr.split(",")) {
|
|
@@ -9386,11 +9631,34 @@ function recordScheduleRun(id, status, targetDir) {
|
|
|
9386
9631
|
saveSchedules(data, targetDir);
|
|
9387
9632
|
}
|
|
9388
9633
|
// src/lib/pull.ts
|
|
9389
|
-
import { existsSync as existsSync15, mkdirSync as
|
|
9390
|
-
import { dirname 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";
|
|
9391
9636
|
|
|
9392
|
-
// src/lib/
|
|
9637
|
+
// src/lib/revision.ts
|
|
9393
9638
|
import { createHash as createHash3 } from "crypto";
|
|
9639
|
+
var REVISION_ID_PATTERN = /^[0-9a-f]{64}$/;
|
|
9640
|
+
function revisionIdOf(content) {
|
|
9641
|
+
const canonical = JSON.stringify({
|
|
9642
|
+
slug: content.slug,
|
|
9643
|
+
displayName: content.displayName,
|
|
9644
|
+
description: content.description,
|
|
9645
|
+
category: content.category,
|
|
9646
|
+
tags: content.tags,
|
|
9647
|
+
source: content.source,
|
|
9648
|
+
kind: content.kind,
|
|
9649
|
+
version: content.version ?? null,
|
|
9650
|
+
skillMd: content.skillMd ?? null,
|
|
9651
|
+
bundleSha256: content.bundleSha256 ?? null,
|
|
9652
|
+
bundleByteSize: content.bundleByteSize ?? null
|
|
9653
|
+
});
|
|
9654
|
+
return createHash3("sha256").update(canonical).digest("hex");
|
|
9655
|
+
}
|
|
9656
|
+
function revisionIdOfRecord(record) {
|
|
9657
|
+
return revisionIdOf(record);
|
|
9658
|
+
}
|
|
9659
|
+
|
|
9660
|
+
// src/lib/skill-bundle.ts
|
|
9661
|
+
import { createHash as createHash4 } from "crypto";
|
|
9394
9662
|
var BLOCK = 512;
|
|
9395
9663
|
var ANY_SEGMENT_EXCLUDES = new Set([
|
|
9396
9664
|
".git",
|
|
@@ -9403,6 +9671,7 @@ var ANY_SEGMENT_EXCLUDES = new Set([
|
|
|
9403
9671
|
".docker"
|
|
9404
9672
|
]);
|
|
9405
9673
|
var ROOT_EXCLUDES = new Set(["dist", "build", ".turbo"]);
|
|
9674
|
+
var TOOL_SIDECAR_FILENAMES = new Set([".hasna-skills.json"]);
|
|
9406
9675
|
var CREDENTIAL_FILENAMES = new Set([
|
|
9407
9676
|
".npmrc",
|
|
9408
9677
|
".pypirc",
|
|
@@ -9519,7 +9788,7 @@ function ownBytes(view) {
|
|
|
9519
9788
|
return out;
|
|
9520
9789
|
}
|
|
9521
9790
|
function sha256Hex(bytes) {
|
|
9522
|
-
return
|
|
9791
|
+
return createHash4("sha256").update(bytes).digest("hex");
|
|
9523
9792
|
}
|
|
9524
9793
|
function unpackSkillBundle(bundle) {
|
|
9525
9794
|
return readTar(ownBytes(Bun.gunzipSync(ownBytes(bundle))));
|
|
@@ -9598,9 +9867,9 @@ function verifyBundleSignature(bytes, signature, key) {
|
|
|
9598
9867
|
// src/lib/pull.ts
|
|
9599
9868
|
var BUNDLE_DIGEST_HEADER = "X-Skill-Bundle-Sha256";
|
|
9600
9869
|
var BUNDLE_SIGNATURE_HEADER = "X-Skill-Bundle-Signature";
|
|
9870
|
+
var BUNDLE_REVISION_ID_HEADER = "X-Skill-Revision-Id";
|
|
9871
|
+
var BUNDLE_REVISION_NUMBER_HEADER = "X-Skill-Revision-Number";
|
|
9601
9872
|
var PULL_MARKER_FILE = ".hasna-skills.json";
|
|
9602
|
-
var SKILLS_CACHE_DIRNAME2 = "skills";
|
|
9603
|
-
var LAYOUT_MIGRATION_RECORD2 = ".layout-migration.json";
|
|
9604
9873
|
|
|
9605
9874
|
class PullSkillError extends Error {
|
|
9606
9875
|
detail;
|
|
@@ -9650,6 +9919,15 @@ async function pullOne(client, rawName, corpusOptions, verify) {
|
|
|
9650
9919
|
return { name: slug, success: false, error: `Failed to fetch '${slug}': ${error.message}` };
|
|
9651
9920
|
}
|
|
9652
9921
|
if (bundleResponse && !bundleResponse.ok) {
|
|
9922
|
+
if (bundleResponse.status === 410) {
|
|
9923
|
+
return reconcileTombstone(slug, corpusOptions);
|
|
9924
|
+
}
|
|
9925
|
+
if (bundleResponse.status === 404) {
|
|
9926
|
+
const marker = readPullMarker(join15(getPortableSkillsRoot(corpusOptions), slug));
|
|
9927
|
+
if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
|
|
9928
|
+
return { name: slug, success: true, purged: true, removed: false };
|
|
9929
|
+
}
|
|
9930
|
+
}
|
|
9653
9931
|
return {
|
|
9654
9932
|
name: slug,
|
|
9655
9933
|
success: false,
|
|
@@ -9675,17 +9953,78 @@ async function pullOne(client, rawName, corpusOptions, verify) {
|
|
|
9675
9953
|
if (skillMd === null) {
|
|
9676
9954
|
return { name: slug, success: false, error: `Skill '${slug}' was not found on the configured Skills instance.` };
|
|
9677
9955
|
}
|
|
9678
|
-
|
|
9679
|
-
|
|
9956
|
+
if (!meta?.revisionId) {
|
|
9957
|
+
const marker = readPullMarker(join15(getPortableSkillsRoot(corpusOptions), slug));
|
|
9958
|
+
if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
|
|
9959
|
+
return { name: slug, success: true, purged: true, removed: false };
|
|
9960
|
+
}
|
|
9961
|
+
}
|
|
9962
|
+
let provenRevisionId;
|
|
9963
|
+
try {
|
|
9964
|
+
provenRevisionId = meta?.revisionId ? provenRevision({ ...meta, skillMd }, slug, {}) : undefined;
|
|
9965
|
+
} catch (error) {
|
|
9966
|
+
if (error instanceof PullSkillError) {
|
|
9967
|
+
return { name: slug, success: false, error: error.message };
|
|
9968
|
+
}
|
|
9969
|
+
throw error;
|
|
9970
|
+
}
|
|
9971
|
+
const written = installCorpusSkillAtomically({ name: slug, skillMd, meta }, corpusOptions);
|
|
9972
|
+
writePullMarker(written.path, {
|
|
9973
|
+
skill: slug,
|
|
9974
|
+
version: written.manifest.version,
|
|
9975
|
+
...provenRevisionId ? { revisionId: provenRevisionId } : {}
|
|
9976
|
+
});
|
|
9680
9977
|
return {
|
|
9681
9978
|
name: slug,
|
|
9682
9979
|
success: true,
|
|
9683
9980
|
path: written.path,
|
|
9684
9981
|
kind: written.manifest.kind,
|
|
9685
9982
|
version: written.manifest.version,
|
|
9686
|
-
created: written.created
|
|
9983
|
+
created: written.created,
|
|
9984
|
+
...provenRevisionId ? { revisionId: provenRevisionId } : {}
|
|
9687
9985
|
};
|
|
9688
9986
|
}
|
|
9987
|
+
function provenRevision(meta, slug, bundle) {
|
|
9988
|
+
const declared = meta.revisionId;
|
|
9989
|
+
if (!declared)
|
|
9990
|
+
return "";
|
|
9991
|
+
const source = meta.publishedSource;
|
|
9992
|
+
if (typeof source !== "string" || source.length === 0) {
|
|
9993
|
+
throw new PullSkillError(`Revision proof failed for '${slug}': the instance declared revision '${declared.slice(0, 12)}\u2026' but did not serve the content fields needed to recompute it (publishedSource). Nothing was installed.`);
|
|
9994
|
+
}
|
|
9995
|
+
const recomputed = revisionIdOf({
|
|
9996
|
+
slug,
|
|
9997
|
+
displayName: meta.displayName ?? "",
|
|
9998
|
+
description: meta.description ?? "",
|
|
9999
|
+
category: meta.category ?? "",
|
|
10000
|
+
tags: meta.tags ?? [],
|
|
10001
|
+
source,
|
|
10002
|
+
kind: meta.kind ?? "instruction",
|
|
10003
|
+
...meta.version ? { version: meta.version } : {},
|
|
10004
|
+
...typeof meta.skillMd === "string" && meta.skillMd.length > 0 ? { skillMd: meta.skillMd } : {},
|
|
10005
|
+
...bundle.sha256 ? { bundleSha256: bundle.sha256 } : {},
|
|
10006
|
+
...bundle.byteSize !== undefined && bundle.byteSize !== null ? { bundleByteSize: bundle.byteSize } : {}
|
|
10007
|
+
});
|
|
10008
|
+
if (recomputed !== declared) {
|
|
10009
|
+
throw new PullSkillError(`Revision proof failed for '${slug}': the instance declared revision '${declared.slice(0, 12)}\u2026' but the served content recomputes to '${recomputed.slice(0, 12)}\u2026'. The declared revision does not identify the content that was received. Nothing was installed.`);
|
|
10010
|
+
}
|
|
10011
|
+
return declared;
|
|
10012
|
+
}
|
|
10013
|
+
function reconcileTombstone(slug, corpusOptions) {
|
|
10014
|
+
const target = join15(getPortableSkillsRoot(corpusOptions), slug);
|
|
10015
|
+
if (!existsSync15(join15(target, PULL_MARKER_FILE))) {
|
|
10016
|
+
return { name: slug, success: true, tombstoned: true, removed: false, leftInPlace: true };
|
|
10017
|
+
}
|
|
10018
|
+
rmSync4(target, { recursive: true, force: true });
|
|
10019
|
+
return { name: slug, success: true, tombstoned: true, removed: true };
|
|
10020
|
+
}
|
|
10021
|
+
function readPullMarker(dir) {
|
|
10022
|
+
try {
|
|
10023
|
+
return JSON.parse(readFileSync14(join15(dir, PULL_MARKER_FILE), "utf-8"));
|
|
10024
|
+
} catch {
|
|
10025
|
+
return null;
|
|
10026
|
+
}
|
|
10027
|
+
}
|
|
9689
10028
|
function installVerifiedBundle(slug, response, meta, corpusOptions, verify) {
|
|
9690
10029
|
return response.arrayBuffer().then((buffer) => {
|
|
9691
10030
|
const verified = verifyBundleResponseBytes(buffer, response, verify);
|
|
@@ -9697,11 +10036,17 @@ function installVerifiedBundle(slug, response, meta, corpusOptions, verify) {
|
|
|
9697
10036
|
}
|
|
9698
10037
|
const version = str(meta?.version) ?? versionFromEntries(entries) ?? "unknown";
|
|
9699
10038
|
const sourceCommit = sourceCommitFromEntries(entries);
|
|
10039
|
+
const declaredRevision = verified.revisionId ?? meta?.revisionId;
|
|
10040
|
+
if (declaredRevision && meta?.revisionId && verified.revisionId && declaredRevision !== meta.revisionId) {
|
|
10041
|
+
throw new PullSkillError(`Revision proof failed for '${slug}': the bundle header declares '${verified.revisionId.slice(0, 12)}\u2026' but the metadata declares '${meta.revisionId.slice(0, 12)}\u2026'. The instance is inconsistent. Nothing was installed.`);
|
|
10042
|
+
}
|
|
10043
|
+
const provenRevisionId = declaredRevision ? provenRevision(meta ?? {}, slug, { sha256: verified.contentHash, byteSize: verified.bytes.byteLength }) : undefined;
|
|
9700
10044
|
const installed = installBundleAtomically(slug, entries, corpusOptions, {
|
|
9701
10045
|
version,
|
|
9702
10046
|
contentHash: verified.contentHash,
|
|
9703
10047
|
...sourceCommit ? { sourceCommit } : {},
|
|
9704
|
-
...verified.signature ? { signature: verified.signature } : {}
|
|
10048
|
+
...verified.signature ? { signature: verified.signature } : {},
|
|
10049
|
+
...provenRevisionId ? { revisionId: provenRevisionId } : {}
|
|
9705
10050
|
});
|
|
9706
10051
|
return {
|
|
9707
10052
|
name: slug,
|
|
@@ -9711,6 +10056,7 @@ function installVerifiedBundle(slug, response, meta, corpusOptions, verify) {
|
|
|
9711
10056
|
version,
|
|
9712
10057
|
contentHash: verified.contentHash,
|
|
9713
10058
|
...sourceCommit ? { sourceCommit } : {},
|
|
10059
|
+
...provenRevisionId ? { revisionId: provenRevisionId } : {},
|
|
9714
10060
|
created: installed.created
|
|
9715
10061
|
};
|
|
9716
10062
|
});
|
|
@@ -9761,11 +10107,20 @@ function sourceCommitFromEntries(entries) {
|
|
|
9761
10107
|
function verifyBundleResponseBytes(buffer, response, verify = {}) {
|
|
9762
10108
|
const serverHash = response.headers.get(BUNDLE_DIGEST_HEADER);
|
|
9763
10109
|
const signature = response.headers.get(BUNDLE_SIGNATURE_HEADER);
|
|
10110
|
+
const revisionId = response.headers.get(BUNDLE_REVISION_ID_HEADER);
|
|
10111
|
+
const revisionNumberRaw = response.headers.get(BUNDLE_REVISION_NUMBER_HEADER);
|
|
9764
10112
|
const bytes = new Uint8Array(buffer.slice(0));
|
|
9765
10113
|
const contentHash = sha256Hex(bytes);
|
|
9766
10114
|
if (serverHash && serverHash.toLowerCase() !== contentHash) {
|
|
9767
10115
|
throw new PullSkillError(`Bundle digest mismatch: the instance declared ${serverHash} but the received bundle hashes to ${contentHash}.`, ["The bundle was tampered with or truncated in transit. Nothing was installed."]);
|
|
9768
10116
|
}
|
|
10117
|
+
if (revisionId && !REVISION_ID_PATTERN.test(revisionId)) {
|
|
10118
|
+
throw new PullSkillError(`Malformed revision id: the instance declared '${revisionId}', which is not a 64-character lowercase hex sha-256.`, ["The revision headers were tampered with or the instance is broken. Nothing was installed."]);
|
|
10119
|
+
}
|
|
10120
|
+
const revisionNumber = revisionNumberRaw === null ? undefined : Number(revisionNumberRaw);
|
|
10121
|
+
if (revisionNumberRaw !== null && (!Number.isInteger(revisionNumber) || revisionNumber < 0)) {
|
|
10122
|
+
throw new PullSkillError(`Malformed revision number: the instance declared '${revisionNumberRaw}', which is not a non-negative integer.`, ["The revision headers were tampered with or the instance is broken. Nothing was installed."]);
|
|
10123
|
+
}
|
|
9769
10124
|
if (signature) {
|
|
9770
10125
|
const key = verify.signingKey;
|
|
9771
10126
|
if (!key) {
|
|
@@ -9778,43 +10133,46 @@ function verifyBundleResponseBytes(buffer, response, verify = {}) {
|
|
|
9778
10133
|
bytes,
|
|
9779
10134
|
contentHash,
|
|
9780
10135
|
...serverHash ? { serverHash } : {},
|
|
9781
|
-
...signature ? { signature } : {}
|
|
10136
|
+
...signature ? { signature } : {},
|
|
10137
|
+
...revisionId ? { revisionId } : {},
|
|
10138
|
+
...revisionNumberRaw === null || revisionNumber === undefined ? {} : { revisionNumber }
|
|
9782
10139
|
};
|
|
9783
10140
|
}
|
|
9784
10141
|
function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
9785
10142
|
const root = getPortableSkillsRoot(options);
|
|
9786
|
-
|
|
10143
|
+
mkdirSync9(root, { recursive: true });
|
|
9787
10144
|
const target = join15(root, name);
|
|
9788
10145
|
const created = !existsSync15(target);
|
|
9789
|
-
const staging =
|
|
10146
|
+
const staging = mkdtempSync3(join15(root, `.pull-${name}-`));
|
|
9790
10147
|
let moved = false;
|
|
9791
10148
|
let backup = null;
|
|
9792
10149
|
try {
|
|
9793
10150
|
for (const entry of entries) {
|
|
9794
10151
|
const destination = join15(staging, entry.path);
|
|
9795
|
-
|
|
9796
|
-
|
|
10152
|
+
mkdirSync9(dirname7(destination), { recursive: true });
|
|
10153
|
+
writeFileSync9(destination, entry.bytes, { mode: entry.mode });
|
|
9797
10154
|
}
|
|
9798
10155
|
writePullMarker(staging, {
|
|
9799
10156
|
skill: name,
|
|
9800
10157
|
...marker.version ? { version: marker.version } : {},
|
|
9801
10158
|
...marker.contentHash ? { contentHash: marker.contentHash } : {},
|
|
9802
10159
|
...marker.sourceCommit ? { sourceCommit: marker.sourceCommit } : {},
|
|
9803
|
-
...marker.signature ? { signature: marker.signature } : {}
|
|
10160
|
+
...marker.signature ? { signature: marker.signature } : {},
|
|
10161
|
+
...marker.revisionId ? { revisionId: marker.revisionId } : {}
|
|
9804
10162
|
});
|
|
9805
10163
|
if (existsSync15(target)) {
|
|
9806
|
-
backup =
|
|
9807
|
-
|
|
10164
|
+
backup = mkdtempSync3(join15(root, `.pull-backup-${name}-`));
|
|
10165
|
+
renameSync3(target, join15(backup, name));
|
|
9808
10166
|
moved = true;
|
|
9809
10167
|
}
|
|
9810
|
-
|
|
10168
|
+
renameSync3(staging, target);
|
|
9811
10169
|
if (moved && backup)
|
|
9812
|
-
|
|
10170
|
+
rmSync4(backup, { recursive: true, force: true });
|
|
9813
10171
|
} catch (error) {
|
|
9814
|
-
|
|
10172
|
+
rmSync4(staging, { recursive: true, force: true });
|
|
9815
10173
|
if (moved && backup && existsSync15(join15(backup, name))) {
|
|
9816
10174
|
try {
|
|
9817
|
-
|
|
10175
|
+
renameSync3(join15(backup, name), target);
|
|
9818
10176
|
} catch {}
|
|
9819
10177
|
}
|
|
9820
10178
|
throw error;
|
|
@@ -9825,14 +10183,15 @@ function writePullMarker(dir, record) {
|
|
|
9825
10183
|
const marker = {
|
|
9826
10184
|
managedBy: "@hasna/skills",
|
|
9827
10185
|
skill: record.skill,
|
|
9828
|
-
source: "pull",
|
|
10186
|
+
source: record.source ?? "pull",
|
|
9829
10187
|
...record.version ? { version: record.version } : {},
|
|
9830
10188
|
...record.contentHash ? { contentHash: record.contentHash } : {},
|
|
9831
10189
|
...record.sourceCommit ? { sourceCommit: record.sourceCommit } : {},
|
|
9832
10190
|
...record.signature ? { signature: record.signature } : {},
|
|
10191
|
+
...record.revisionId ? { revisionId: record.revisionId } : {},
|
|
9833
10192
|
syncedAt: new Date().toISOString()
|
|
9834
10193
|
};
|
|
9835
|
-
|
|
10194
|
+
writeFileSync9(join15(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
|
|
9836
10195
|
`);
|
|
9837
10196
|
}
|
|
9838
10197
|
async function safeMeta(client, slug) {
|
|
@@ -9853,20 +10212,14 @@ async function safeMeta(client, slug) {
|
|
|
9853
10212
|
...str(record.category) ? { category: str(record.category) } : {},
|
|
9854
10213
|
...tags && tags.length ? { tags } : {},
|
|
9855
10214
|
...str(record.version) ? { version: str(record.version) } : {},
|
|
9856
|
-
...kind ? { kind } : {}
|
|
10215
|
+
...kind ? { kind } : {},
|
|
10216
|
+
...REVISION_ID_PATTERN.test(str(record.revisionId) ?? "") ? { revisionId: str(record.revisionId) } : {},
|
|
10217
|
+
...typeof record.skillMd === "string" && record.skillMd.length > 0 ? { skillMd: record.skillMd } : {},
|
|
10218
|
+
...str(record.publishedSource) ? { publishedSource: str(record.publishedSource) } : {}
|
|
9857
10219
|
};
|
|
9858
10220
|
}
|
|
9859
10221
|
function pickCorpusOptions(options) {
|
|
9860
|
-
return { rootDir:
|
|
9861
|
-
}
|
|
9862
|
-
function resolvePullCorpusRoot(options) {
|
|
9863
|
-
if (options.rootDir)
|
|
9864
|
-
return options.rootDir;
|
|
9865
|
-
const appDir = options.homeDir ? join15(options.homeDir, ".hasna", "skills") : getDataDir();
|
|
9866
|
-
const cache = join15(appDir, SKILLS_CACHE_DIRNAME2);
|
|
9867
|
-
if (existsSync15(join15(cache, LAYOUT_MIGRATION_RECORD2)) && existsSync15(cache))
|
|
9868
|
-
return cache;
|
|
9869
|
-
return getPortableSkillsRoot(options);
|
|
10222
|
+
return { rootDir: resolveCorpusRoot(options) };
|
|
9870
10223
|
}
|
|
9871
10224
|
function extractSlug(entry) {
|
|
9872
10225
|
if (!entry || typeof entry !== "object")
|
|
@@ -9984,12 +10337,12 @@ function findSkillsParityForMcpTool(tool) {
|
|
|
9984
10337
|
return SKILLS_CLI_MCP_PARITY.find((entry) => entry.mcpTools.includes(tool));
|
|
9985
10338
|
}
|
|
9986
10339
|
// src/lib/registry-sync.ts
|
|
9987
|
-
import { mkdirSync as
|
|
9988
|
-
import { dirname as
|
|
10340
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
10341
|
+
import { dirname as dirname8, relative as relative3 } from "path";
|
|
9989
10342
|
// package.json
|
|
9990
10343
|
var package_default = {
|
|
9991
10344
|
name: "@hasna/skills",
|
|
9992
|
-
version: "0.1.
|
|
10345
|
+
version: "0.1.66",
|
|
9993
10346
|
description: "Skills library for AI coding agents",
|
|
9994
10347
|
type: "module",
|
|
9995
10348
|
bin: {
|
|
@@ -10063,6 +10416,7 @@ var package_default = {
|
|
|
10063
10416
|
license: "Apache-2.0",
|
|
10064
10417
|
devDependencies: {
|
|
10065
10418
|
"@types/bun": "latest",
|
|
10419
|
+
"@types/node": "25.2.3",
|
|
10066
10420
|
"@types/react": "^18.2.0",
|
|
10067
10421
|
"bun-types": "1.3.14",
|
|
10068
10422
|
"react-devtools-core": "^7.0.1",
|
|
@@ -10071,7 +10425,7 @@ var package_default = {
|
|
|
10071
10425
|
dependencies: {
|
|
10072
10426
|
"@aws-sdk/client-ecs": "^3.1079.0",
|
|
10073
10427
|
"@aws-sdk/client-s3": "^3.1079.0",
|
|
10074
|
-
"@hasna/events": "0.1.
|
|
10428
|
+
"@hasna/events": "0.1.16",
|
|
10075
10429
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
10076
10430
|
chalk: "^5.3.0",
|
|
10077
10431
|
commander: "^12.1.0",
|
|
@@ -10156,8 +10510,8 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
10156
10510
|
};
|
|
10157
10511
|
}
|
|
10158
10512
|
function writeRegistrySyncArtifact(path, artifact) {
|
|
10159
|
-
|
|
10160
|
-
|
|
10513
|
+
mkdirSync10(dirname8(path), { recursive: true });
|
|
10514
|
+
writeFileSync10(path, `${JSON.stringify(artifact, null, 2)}
|
|
10161
10515
|
`);
|
|
10162
10516
|
}
|
|
10163
10517
|
function buildDocs(name) {
|
|
@@ -10996,17 +11350,17 @@ function clone2(value) {
|
|
|
10996
11350
|
return JSON.parse(JSON.stringify(value));
|
|
10997
11351
|
}
|
|
10998
11352
|
// src/lib/feedback.ts
|
|
10999
|
-
import { existsSync as existsSync16, mkdirSync as
|
|
11000
|
-
import { dirname as
|
|
11353
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync11 } from "fs";
|
|
11354
|
+
import { dirname as dirname9, join as join16 } from "path";
|
|
11001
11355
|
import { Database } from "bun:sqlite";
|
|
11002
11356
|
function getFeedbackDbPath() {
|
|
11003
11357
|
return join16(getDataDir(), "skills.db");
|
|
11004
11358
|
}
|
|
11005
11359
|
function getFeedbackDb() {
|
|
11006
11360
|
const dbPath = getFeedbackDbPath();
|
|
11007
|
-
const dir =
|
|
11361
|
+
const dir = dirname9(dbPath);
|
|
11008
11362
|
if (!existsSync16(dir))
|
|
11009
|
-
|
|
11363
|
+
mkdirSync11(dir, { recursive: true });
|
|
11010
11364
|
const db = new Database(dbPath);
|
|
11011
11365
|
db.exec("PRAGMA journal_mode = WAL");
|
|
11012
11366
|
db.exec([
|
|
@@ -11040,16 +11394,16 @@ function saveFeedback(input) {
|
|
|
11040
11394
|
return { saved: true, category, path: getFeedbackDbPath() };
|
|
11041
11395
|
}
|
|
11042
11396
|
// src/lib/native-storage.ts
|
|
11043
|
-
import { createHash as
|
|
11397
|
+
import { createHash as createHash5, createHmac as createHmac2 } from "crypto";
|
|
11044
11398
|
import {
|
|
11045
11399
|
existsSync as existsSync17,
|
|
11046
|
-
mkdirSync as
|
|
11047
|
-
readFileSync as
|
|
11400
|
+
mkdirSync as mkdirSync12,
|
|
11401
|
+
readFileSync as readFileSync15,
|
|
11048
11402
|
readdirSync as readdirSync9,
|
|
11049
11403
|
statSync as statSync9,
|
|
11050
|
-
writeFileSync as
|
|
11404
|
+
writeFileSync as writeFileSync11
|
|
11051
11405
|
} from "fs";
|
|
11052
|
-
import { dirname as
|
|
11406
|
+
import { dirname as dirname10, join as join17, normalize as normalize3, relative as relative4, sep as sep2 } from "path";
|
|
11053
11407
|
var SKILLS_STORAGE_TABLES = [
|
|
11054
11408
|
"skills_sync_records",
|
|
11055
11409
|
"skills_sync_cursors"
|
|
@@ -11087,6 +11441,55 @@ var SKILLS_NATIVE_STORAGE_FALLBACK_ENV = {
|
|
|
11087
11441
|
};
|
|
11088
11442
|
var SKILLS_STORAGE_ENV = SKILLS_NATIVE_STORAGE_ENV;
|
|
11089
11443
|
var SKILLS_STORAGE_FALLBACK_ENV = SKILLS_NATIVE_STORAGE_FALLBACK_ENV;
|
|
11444
|
+
var storageCapabilities = {
|
|
11445
|
+
version: 1,
|
|
11446
|
+
values: [
|
|
11447
|
+
"SKILLS_NATIVE_STORAGE_ENV",
|
|
11448
|
+
"SKILLS_NATIVE_STORAGE_FALLBACK_ENV",
|
|
11449
|
+
"SKILLS_STORAGE_ENV",
|
|
11450
|
+
"SKILLS_STORAGE_FALLBACK_ENV",
|
|
11451
|
+
"SKILLS_STORAGE_TABLES",
|
|
11452
|
+
"STORAGE_TABLES",
|
|
11453
|
+
"SkillsPostgresSyncStore",
|
|
11454
|
+
"SkillsS3ObjectStore",
|
|
11455
|
+
"buildSkillsS3ObjectUrl",
|
|
11456
|
+
"createSkillsPostgresSyncStore",
|
|
11457
|
+
"createSkillsS3ObjectStore",
|
|
11458
|
+
"createSkillsSnapshotSyncRecord",
|
|
11459
|
+
"exportSkillsLocalSnapshot",
|
|
11460
|
+
"getSkillsNativeStorageStatus",
|
|
11461
|
+
"getSkillsStorageDatabaseEnv",
|
|
11462
|
+
"getSkillsStorageDatabaseUrl",
|
|
11463
|
+
"getSkillsStorageStatus",
|
|
11464
|
+
"getStorageDatabaseEnv",
|
|
11465
|
+
"getStorageDatabaseUrl",
|
|
11466
|
+
"getStorageStatus",
|
|
11467
|
+
"importSkillsLocalSnapshot",
|
|
11468
|
+
"planSkillsS3SnapshotUpload",
|
|
11469
|
+
"resolveSkillsNativeStorageConfig",
|
|
11470
|
+
"resolveStorageConfig",
|
|
11471
|
+
"signSkillsAwsV4Request",
|
|
11472
|
+
"skillsPostgresSyncSchemaSql",
|
|
11473
|
+
"storageCapabilities",
|
|
11474
|
+
"uploadSkillsSnapshotFilesToS3"
|
|
11475
|
+
],
|
|
11476
|
+
types: [
|
|
11477
|
+
"AwsCredentials",
|
|
11478
|
+
"SignSkillsAwsV4RequestOptions",
|
|
11479
|
+
"SkillsFetch",
|
|
11480
|
+
"SkillsLocalSnapshot",
|
|
11481
|
+
"SkillsNativeStorageConfig",
|
|
11482
|
+
"SkillsNativeStorageStatus",
|
|
11483
|
+
"SkillsPostgresQueryClient",
|
|
11484
|
+
"SkillsS3ObjectStoreOptions",
|
|
11485
|
+
"SkillsS3PutObjectOptions",
|
|
11486
|
+
"SkillsS3SnapshotPlanEntry",
|
|
11487
|
+
"SkillsS3StoredObject",
|
|
11488
|
+
"SkillsSnapshotFile",
|
|
11489
|
+
"SkillsStorageTable",
|
|
11490
|
+
"SkillsSyncRecord"
|
|
11491
|
+
]
|
|
11492
|
+
};
|
|
11090
11493
|
var SKILLS_ENV_NAMESPACE = "SKILLS";
|
|
11091
11494
|
function resolveSkillsNativeStorageConfig(env = process.env) {
|
|
11092
11495
|
assertNoRetiredModeEnvVars(env, {
|
|
@@ -11128,7 +11531,7 @@ function getSkillsNativeStorageStatus(options = {}) {
|
|
|
11128
11531
|
const s3BucketEnv = readStorageEnv(env, "s3Bucket");
|
|
11129
11532
|
const targetDir = options.targetDir ?? process.cwd();
|
|
11130
11533
|
return {
|
|
11131
|
-
package: "
|
|
11534
|
+
package: "skills",
|
|
11132
11535
|
tables: [...SKILLS_STORAGE_TABLES],
|
|
11133
11536
|
env: {
|
|
11134
11537
|
databaseUrl: SKILLS_NATIVE_STORAGE_ENV.databaseUrl,
|
|
@@ -11162,12 +11565,12 @@ function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
|
|
|
11162
11565
|
const files = [];
|
|
11163
11566
|
if (existsSync17(projectStateDir)) {
|
|
11164
11567
|
for (const filePath of walkFiles2(projectStateDir)) {
|
|
11165
|
-
const bytes =
|
|
11568
|
+
const bytes = readFileSync15(filePath);
|
|
11166
11569
|
const relativePath = toPosix(relative4(targetDir, filePath));
|
|
11167
11570
|
files.push({
|
|
11168
11571
|
path: relativePath,
|
|
11169
11572
|
sizeBytes: bytes.byteLength,
|
|
11170
|
-
sha256:
|
|
11573
|
+
sha256: createHash5("sha256").update(bytes).digest("hex"),
|
|
11171
11574
|
...options.includeFileContents ? { contentBase64: Buffer.from(bytes).toString("base64") } : {}
|
|
11172
11575
|
});
|
|
11173
11576
|
}
|
|
@@ -11192,12 +11595,12 @@ function importSkillsLocalSnapshot(snapshot, targetDir = process.cwd(), options
|
|
|
11192
11595
|
continue;
|
|
11193
11596
|
}
|
|
11194
11597
|
const bytes = Buffer.from(file.contentBase64, "base64");
|
|
11195
|
-
const hash =
|
|
11598
|
+
const hash = createHash5("sha256").update(bytes).digest("hex");
|
|
11196
11599
|
if (hash !== file.sha256) {
|
|
11197
11600
|
throw new Error(`Snapshot file checksum mismatch: ${file.path}`);
|
|
11198
11601
|
}
|
|
11199
|
-
|
|
11200
|
-
|
|
11602
|
+
mkdirSync12(dirname10(absolutePath), { recursive: true });
|
|
11603
|
+
writeFileSync11(absolutePath, bytes);
|
|
11201
11604
|
written += 1;
|
|
11202
11605
|
}
|
|
11203
11606
|
return { written, skipped };
|
|
@@ -11300,7 +11703,7 @@ function createSkillsSnapshotSyncRecord(snapshot, options = {}) {
|
|
|
11300
11703
|
kind: "local-snapshot",
|
|
11301
11704
|
id: options.id ?? "project-state",
|
|
11302
11705
|
updatedAt: snapshot.exportedAt,
|
|
11303
|
-
source: options.source ?? "
|
|
11706
|
+
source: options.source ?? "skills",
|
|
11304
11707
|
payload: snapshot
|
|
11305
11708
|
};
|
|
11306
11709
|
}
|
|
@@ -11524,7 +11927,7 @@ function toIsoString(value) {
|
|
|
11524
11927
|
return Number.isNaN(date.getTime()) ? value : date.toISOString();
|
|
11525
11928
|
}
|
|
11526
11929
|
function sha256Hex2(value) {
|
|
11527
|
-
return
|
|
11930
|
+
return createHash5("sha256").update(value).digest("hex");
|
|
11528
11931
|
}
|
|
11529
11932
|
function normalizeHeaders(headers) {
|
|
11530
11933
|
const result = {};
|
|
@@ -11593,6 +11996,7 @@ export {
|
|
|
11593
11996
|
unpinProjectSkill,
|
|
11594
11997
|
syncSkillsToAgents,
|
|
11595
11998
|
summarizeMcpToolContract,
|
|
11999
|
+
storageCapabilities,
|
|
11596
12000
|
skillsPostgresSyncSchemaSql,
|
|
11597
12001
|
skillExists,
|
|
11598
12002
|
signSkillsAwsV4Request,
|
|
@@ -11747,6 +12151,8 @@ export {
|
|
|
11747
12151
|
SKILLS_CLI_MCP_PARITY,
|
|
11748
12152
|
SKILLS,
|
|
11749
12153
|
RemoteSkillsClient,
|
|
12154
|
+
RemoteRouteUnsupportedError,
|
|
12155
|
+
RemoteRequestError,
|
|
11750
12156
|
REMOTE_SKILL_RUN_CONTRACT_VERSION,
|
|
11751
12157
|
PullSkillError,
|
|
11752
12158
|
PROJECT_CONFIG_FILE,
|