@hasna/skills 0.1.72 → 0.2.0
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 +4 -2
- package/bin/index.js +24514 -24319
- package/bin/mcp.js +217 -198
- package/bin/migrate.js +114 -62
- package/bin/server.js +3384 -2892
- package/bin/worker.js +2095 -1820
- package/dist/cli/commands/install.d.ts +16 -0
- package/dist/cli/commands/publish.d.ts +35 -0
- package/dist/cli/commands/registry.d.ts +5 -0
- package/dist/index.js +475 -263
- package/dist/lib/app-home.d.ts +27 -1
- package/dist/lib/feedback.d.ts +6 -0
- package/dist/lib/installer.d.ts +2 -0
- package/dist/lib/pull.d.ts +18 -1
- package/dist/lib/remote-client.d.ts +15 -1
- package/dist/lib/skill-version.d.ts +11 -0
- package/dist/sdk/index.js +5470 -4975
- package/dist/server/app.d.ts +3 -0
- package/dist/server/artifact-storage.d.ts +27 -0
- package/dist/server/config.d.ts +2 -0
- package/dist/server/rows.d.ts +2 -1
- package/dist/server/seed-bundled.d.ts +20 -0
- package/dist/server/skills-api.d.ts +18 -1
- package/dist/server/sqlite-store.d.ts +3 -1
- package/dist/server/store.d.ts +6 -1
- package/dist/server/types.d.ts +43 -0
- package/dist/storage.js +62 -61
- package/migrations/postgres/0006_skill_versions.sql +30 -0
- package/migrations/sqlite/0006_skill_versions.sql +17 -0
- package/package.json +2 -3
package/dist/index.js
CHANGED
|
@@ -48,11 +48,11 @@ var __require = import.meta.require;
|
|
|
48
48
|
|
|
49
49
|
// src/lib/registry.ts
|
|
50
50
|
import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync as readdirSync6 } from "fs";
|
|
51
|
-
import { join as
|
|
51
|
+
import { join as join8 } from "path";
|
|
52
52
|
|
|
53
53
|
// src/lib/config.ts
|
|
54
54
|
import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
|
|
55
|
-
import { join as
|
|
55
|
+
import { join as join2, dirname } from "path";
|
|
56
56
|
|
|
57
57
|
// src/lib/retired-settings.ts
|
|
58
58
|
var RETIRED_ENV_SUFFIXES = ["_STORAGE_MODE", "_DEPLOYMENT_MODE", "_CLOUD_MODE"];
|
|
@@ -102,94 +102,87 @@ function assertNoRetiredConfigKeys(config, source) {
|
|
|
102
102
|
|
|
103
103
|
// src/lib/app-home.ts
|
|
104
104
|
import { existsSync } from "fs";
|
|
105
|
-
import { homedir as homedir2 } from "os";
|
|
106
|
-
import { join as join2, resolve } from "path";
|
|
107
|
-
|
|
108
|
-
// ../../node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
|
|
109
105
|
import { homedir } from "os";
|
|
110
|
-
import { join } from "path";
|
|
111
|
-
|
|
106
|
+
import { join, resolve } from "path";
|
|
107
|
+
import { homedir as pathsResolverHomedir } from "os";
|
|
108
|
+
import { join as pathsResolverJoin } from "path";
|
|
109
|
+
var PATHS_RESOLVER_KIND_ENV = {
|
|
112
110
|
config: "HASNA_CONFIG_HOME",
|
|
113
111
|
data: "HASNA_DATA_HOME",
|
|
114
112
|
state: "HASNA_STATE_HOME",
|
|
115
113
|
cache: "HASNA_CACHE_HOME"
|
|
116
114
|
};
|
|
117
|
-
var
|
|
118
|
-
function
|
|
115
|
+
var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
116
|
+
function pathsResolverAssertApp(app) {
|
|
119
117
|
if (typeof app !== "string" || app.length === 0) {
|
|
120
118
|
throw new TypeError("paths: app must be a non-empty string");
|
|
121
119
|
}
|
|
122
|
-
if (!
|
|
120
|
+
if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
|
|
123
121
|
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
124
122
|
}
|
|
125
123
|
}
|
|
126
|
-
function
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const value = envOf(options)[KIND_ENV[kind]];
|
|
131
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
132
|
-
}
|
|
133
|
-
function isMacOS(platform) {
|
|
134
|
-
return platform === "darwin";
|
|
124
|
+
function pathsResolverAssertKind(kind) {
|
|
125
|
+
if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
|
|
126
|
+
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
|
|
127
|
+
}
|
|
135
128
|
}
|
|
136
|
-
function
|
|
137
|
-
|
|
138
|
-
|
|
129
|
+
function pathsResolverBaseDir(kind, options) {
|
|
130
|
+
pathsResolverAssertKind(kind);
|
|
131
|
+
const env = options.env ?? process.env;
|
|
132
|
+
const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
|
|
133
|
+
if (typeof override === "string" && override.length > 0)
|
|
139
134
|
return override;
|
|
140
|
-
const home = options.home ??
|
|
135
|
+
const home = options.home ?? pathsResolverHomedir();
|
|
141
136
|
const platform = options.platform ?? process.platform;
|
|
142
|
-
if (
|
|
137
|
+
if (platform === "darwin") {
|
|
143
138
|
switch (kind) {
|
|
144
139
|
case "config":
|
|
145
140
|
case "data":
|
|
146
|
-
return
|
|
141
|
+
return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
|
|
147
142
|
case "cache":
|
|
148
|
-
return
|
|
143
|
+
return pathsResolverJoin(home, "Library", "Caches", "Hasna");
|
|
149
144
|
case "state":
|
|
150
|
-
return
|
|
145
|
+
return pathsResolverJoin(home, "Library", "Logs", "Hasna");
|
|
151
146
|
}
|
|
152
147
|
}
|
|
153
148
|
switch (kind) {
|
|
154
149
|
case "config":
|
|
155
|
-
return
|
|
150
|
+
return pathsResolverJoin(home, ".config", "hasna");
|
|
156
151
|
case "data":
|
|
157
|
-
return
|
|
152
|
+
return pathsResolverJoin(home, ".local", "share", "hasna");
|
|
158
153
|
case "state":
|
|
159
|
-
return
|
|
154
|
+
return pathsResolverJoin(home, ".local", "state", "hasna");
|
|
160
155
|
case "cache":
|
|
161
|
-
return
|
|
156
|
+
return pathsResolverJoin(home, ".cache", "hasna");
|
|
162
157
|
}
|
|
163
158
|
}
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
const appSegment = options.internal === true ?
|
|
167
|
-
return
|
|
159
|
+
function pathsResolverResolve(kind, options) {
|
|
160
|
+
pathsResolverAssertApp(options.app);
|
|
161
|
+
const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
|
|
162
|
+
return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
|
|
168
163
|
}
|
|
169
164
|
function dataDir(options) {
|
|
170
|
-
return
|
|
165
|
+
return pathsResolverResolve("data", options);
|
|
171
166
|
}
|
|
172
|
-
|
|
173
|
-
// src/lib/app-home.ts
|
|
174
167
|
var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
|
|
175
168
|
var HASNA_SKILLS_HOME_ENV = "HASNA_SKILLS_HOME";
|
|
176
169
|
var SKILLS_HOME_ENV = "SKILLS_HOME";
|
|
177
170
|
var DEFAULT_SQLITE_FILENAME = "server.db";
|
|
178
171
|
var GLOBAL_CONFIG_FILENAME = "config.json";
|
|
179
172
|
function effectiveHome() {
|
|
180
|
-
return process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
173
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir() || "/tmp";
|
|
181
174
|
}
|
|
182
175
|
function legacyDataRoot() {
|
|
183
|
-
return
|
|
176
|
+
return join(effectiveHome(), ".hasna", "skills");
|
|
184
177
|
}
|
|
185
|
-
function resolverDataRoot(home = effectiveHome()) {
|
|
186
|
-
return dataDir({ app: "skills", home });
|
|
178
|
+
function resolverDataRoot(home = effectiveHome(), env) {
|
|
179
|
+
return dataDir({ app: "skills", home, env });
|
|
187
180
|
}
|
|
188
181
|
function adoptResolverDataRoot(resolved, env = process.env) {
|
|
189
182
|
const dataOverride = env.HASNA_DATA_HOME;
|
|
190
183
|
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
191
184
|
return true;
|
|
192
|
-
return existsSync(
|
|
185
|
+
return existsSync(join(resolved, DEFAULT_SQLITE_FILENAME)) || existsSync(join(resolved, GLOBAL_CONFIG_FILENAME));
|
|
193
186
|
}
|
|
194
187
|
function exactDataRoot() {
|
|
195
188
|
for (const key of [DATA_DIR_ENV, HASNA_SKILLS_HOME_ENV, SKILLS_HOME_ENV]) {
|
|
@@ -213,8 +206,16 @@ function getDataRoot() {
|
|
|
213
206
|
return adoptResolverDataRoot(resolved) ? resolve(resolved) : resolve(legacyDataRoot());
|
|
214
207
|
}
|
|
215
208
|
function skillsDataRootForHome(home) {
|
|
216
|
-
const
|
|
217
|
-
|
|
209
|
+
const isOwnHome = resolve(home) === resolve(effectiveHome()) || resolve(home) === resolve(homedir());
|
|
210
|
+
if (isOwnHome) {
|
|
211
|
+
const exact = exactDataRoot();
|
|
212
|
+
if (exact)
|
|
213
|
+
return exact;
|
|
214
|
+
const resolved2 = resolverDataRoot(home);
|
|
215
|
+
return adoptResolverDataRoot(resolved2) ? resolve(resolved2) : resolve(join(home, ".hasna", "skills"));
|
|
216
|
+
}
|
|
217
|
+
const resolved = resolverDataRoot(home, {});
|
|
218
|
+
return adoptResolverDataRoot(resolved, {}) ? resolve(resolved) : resolve(join(home, ".hasna", "skills"));
|
|
218
219
|
}
|
|
219
220
|
|
|
220
221
|
// src/lib/config.ts
|
|
@@ -235,8 +236,8 @@ function mergeDirectoryContents(sourceDir, targetDir) {
|
|
|
235
236
|
return;
|
|
236
237
|
mkdirSync(targetDir, { recursive: true });
|
|
237
238
|
for (const entry of readdirSync(sourceDir)) {
|
|
238
|
-
const sourcePath =
|
|
239
|
-
const targetPath =
|
|
239
|
+
const sourcePath = join2(sourceDir, entry);
|
|
240
|
+
const targetPath = join2(targetDir, entry);
|
|
240
241
|
try {
|
|
241
242
|
const sourceStat = statSync(sourcePath);
|
|
242
243
|
if (sourceStat.isDirectory()) {
|
|
@@ -272,7 +273,7 @@ var INSTALLED_SKILLS_DIRNAME = "installed";
|
|
|
272
273
|
var SKILLS_CACHE_DIRNAME = "skills";
|
|
273
274
|
var LAYOUT_MIGRATION_RECORD = ".layout-migration.json";
|
|
274
275
|
function isOwnerLayoutMigrated(appDir) {
|
|
275
|
-
return existsSync2(
|
|
276
|
+
return existsSync2(join2(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD));
|
|
276
277
|
}
|
|
277
278
|
function getDataDir() {
|
|
278
279
|
const root = getDataRoot();
|
|
@@ -282,23 +283,23 @@ function getDataDir() {
|
|
|
282
283
|
if (hasOperatorOverride())
|
|
283
284
|
return root;
|
|
284
285
|
const home = effectiveHome();
|
|
285
|
-
const oldDir =
|
|
286
|
-
const oldConfigFile =
|
|
286
|
+
const oldDir = join2(home, ".skills");
|
|
287
|
+
const oldConfigFile = join2(home, ".skillsrc");
|
|
287
288
|
try {
|
|
288
289
|
mergeDirectoryContents(oldDir, root);
|
|
289
290
|
} catch {}
|
|
290
|
-
if (existsSync2(oldConfigFile) && !existsSync2(
|
|
291
|
+
if (existsSync2(oldConfigFile) && !existsSync2(join2(root, "config.json"))) {
|
|
291
292
|
try {
|
|
292
|
-
copyFileSync(oldConfigFile,
|
|
293
|
+
copyFileSync(oldConfigFile, join2(root, "config.json"));
|
|
293
294
|
} catch {}
|
|
294
295
|
}
|
|
295
296
|
return root;
|
|
296
297
|
}
|
|
297
298
|
function getConfigPath(scope) {
|
|
298
299
|
if (scope === "global") {
|
|
299
|
-
return
|
|
300
|
+
return join2(getDataDir(), "config.json");
|
|
300
301
|
}
|
|
301
|
-
return
|
|
302
|
+
return join2(process.cwd(), "skills.config.json");
|
|
302
303
|
}
|
|
303
304
|
function readConfigFile(path) {
|
|
304
305
|
if (!existsSync2(path))
|
|
@@ -393,7 +394,7 @@ import {
|
|
|
393
394
|
statSync as statSync6,
|
|
394
395
|
writeFileSync as writeFileSync3
|
|
395
396
|
} from "fs";
|
|
396
|
-
import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute2, join as
|
|
397
|
+
import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute2, join as join7, normalize as normalize2 } from "path";
|
|
397
398
|
|
|
398
399
|
// src/lib/registry-data/development-tools.ts
|
|
399
400
|
var DEVELOPMENT_TOOLS_SKILLS = [
|
|
@@ -607,14 +608,6 @@ var DEVELOPMENT_TOOLS_SKILLS = [
|
|
|
607
608
|
category: "Development Tools",
|
|
608
609
|
tags: ["storage", "backend", "postgresql", "sqlite", "two-backend", "oss-app"],
|
|
609
610
|
kind: "instruction"
|
|
610
|
-
},
|
|
611
|
-
{
|
|
612
|
-
name: "session-inject-monitor",
|
|
613
|
-
displayName: "Session Inject Monitor",
|
|
614
|
-
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",
|
|
615
|
-
category: "Development Tools",
|
|
616
|
-
tags: ["monitor", "session", "injection", "automation", "wake"],
|
|
617
|
-
kind: "instruction"
|
|
618
611
|
}
|
|
619
612
|
];
|
|
620
613
|
|
|
@@ -1114,7 +1107,7 @@ var SKILLS = [
|
|
|
1114
1107
|
|
|
1115
1108
|
// src/lib/hosted-skill-set.ts
|
|
1116
1109
|
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
1117
|
-
import { join as
|
|
1110
|
+
import { join as join3 } from "path";
|
|
1118
1111
|
var HOSTED_RUNTIMES = new Set(["hosted"]);
|
|
1119
1112
|
var HOSTED_SOURCES = new Set(["remote", "private-hosted"]);
|
|
1120
1113
|
function normalizeMarker(value) {
|
|
@@ -1127,7 +1120,7 @@ function isHostedMetadataPackage(pkg) {
|
|
|
1127
1120
|
return HOSTED_RUNTIMES.has(normalizeMarker(skills.runtime)) || HOSTED_SOURCES.has(normalizeMarker(skills.source));
|
|
1128
1121
|
}
|
|
1129
1122
|
function isHostedMetadataSkillDir(skillDir) {
|
|
1130
|
-
const pkgPath =
|
|
1123
|
+
const pkgPath = join3(skillDir, "package.json");
|
|
1131
1124
|
if (!existsSync3(pkgPath))
|
|
1132
1125
|
return false;
|
|
1133
1126
|
try {
|
|
@@ -1153,7 +1146,7 @@ var SINGLE_SOURCE_EXCLUSION = new RegExp(`^!skills/(${SLUG})/src$`);
|
|
|
1153
1146
|
|
|
1154
1147
|
// src/lib/skill-validation.ts
|
|
1155
1148
|
import { existsSync as existsSync4, lstatSync, readFileSync as readFileSync3, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
1156
|
-
import { isAbsolute, join as
|
|
1149
|
+
import { isAbsolute, join as join4, normalize } from "path";
|
|
1157
1150
|
var VALID_SKILL_KINDS = ["executable", "instruction"];
|
|
1158
1151
|
var DOC_FILES = ["SKILL.md", "README.md", "CLAUDE.md"];
|
|
1159
1152
|
var RESERVED_SKILL_ENTRIES = new Set([
|
|
@@ -1306,7 +1299,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
1306
1299
|
add(issues, "skill.name_invalid", `Skill name '${bareName}' must use lowercase letters, numbers, dots, underscores, or hyphens`);
|
|
1307
1300
|
}
|
|
1308
1301
|
for (const entry of readdirSync3(skillPath).sort()) {
|
|
1309
|
-
const entryPath =
|
|
1302
|
+
const entryPath = join4(skillPath, entry);
|
|
1310
1303
|
if (RESERVED_SKILL_ENTRIES.has(entry)) {
|
|
1311
1304
|
add(issues, "skill.reserved_file", `Reserved file '${entry}' is not allowed in skill packages`);
|
|
1312
1305
|
}
|
|
@@ -1318,13 +1311,13 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
1318
1311
|
}
|
|
1319
1312
|
}
|
|
1320
1313
|
for (const docFile of DOC_FILES) {
|
|
1321
|
-
if (existsSync4(
|
|
1314
|
+
if (existsSync4(join4(skillPath, docFile)))
|
|
1322
1315
|
metadata.docFiles.push(docFile);
|
|
1323
1316
|
}
|
|
1324
1317
|
if (metadata.docFiles.length === 0) {
|
|
1325
1318
|
add(issues, "skill.docs_missing", "Missing documentation file: expected SKILL.md, README.md, or CLAUDE.md");
|
|
1326
1319
|
}
|
|
1327
|
-
const skillMdPath =
|
|
1320
|
+
const skillMdPath = join4(skillPath, "SKILL.md");
|
|
1328
1321
|
if (existsSync4(skillMdPath)) {
|
|
1329
1322
|
const frontmatter = parseSkillFrontmatter(readFileSync3(skillMdPath, "utf-8"));
|
|
1330
1323
|
if (!frontmatter) {
|
|
@@ -1368,7 +1361,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
1368
1361
|
}
|
|
1369
1362
|
metadata.kind = resolvedKind;
|
|
1370
1363
|
const isInstruction = resolvedKind === "instruction";
|
|
1371
|
-
const pkgPath =
|
|
1364
|
+
const pkgPath = join4(skillPath, "package.json");
|
|
1372
1365
|
if (!existsSync4(pkgPath)) {
|
|
1373
1366
|
if (!isInstruction)
|
|
1374
1367
|
add(issues, "package.missing", "Missing package.json");
|
|
@@ -1427,7 +1420,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
1427
1420
|
add(issues, "package.bin_target_unsafe", `package.json bin '${command}' target '${target}' must stay inside the skill directory`);
|
|
1428
1421
|
continue;
|
|
1429
1422
|
}
|
|
1430
|
-
const targetPath =
|
|
1423
|
+
const targetPath = join4(skillPath, target);
|
|
1431
1424
|
if (!existsSync4(targetPath)) {
|
|
1432
1425
|
add(warnings, "package.bin_target_missing", `package.json bin '${command}' target '${target}' is not present before build`);
|
|
1433
1426
|
} else if (statSync3(targetPath).isDirectory()) {
|
|
@@ -1445,17 +1438,17 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
1445
1438
|
metadata.runtime = "none";
|
|
1446
1439
|
} else {
|
|
1447
1440
|
metadata.runtime = hostedMetadata ? "hosted" : "local";
|
|
1448
|
-
const srcDir =
|
|
1441
|
+
const srcDir = join4(skillPath, "src");
|
|
1449
1442
|
if (hostedMetadata) {
|
|
1450
1443
|
if (existsSync4(srcDir)) {
|
|
1451
1444
|
add(issues, "skill.hosted_source_forbidden", "Hosted metadata skills must not include local implementation source");
|
|
1452
1445
|
}
|
|
1453
1446
|
} else if (!existsSync4(srcDir)) {
|
|
1454
1447
|
add(issues, "skill.src_missing", "Missing src/ directory");
|
|
1455
|
-
} else if (!existsSync4(
|
|
1448
|
+
} else if (!existsSync4(join4(srcDir, "index.ts")) && !existsSync4(join4(srcDir, "index.js"))) {
|
|
1456
1449
|
add(issues, "skill.src_index_missing", "Missing src/index.ts or src/index.js");
|
|
1457
1450
|
} else {
|
|
1458
|
-
const indexPath = existsSync4(
|
|
1451
|
+
const indexPath = existsSync4(join4(srcDir, "index.ts")) ? join4(srcDir, "index.ts") : join4(srcDir, "index.js");
|
|
1459
1452
|
const size = statSync3(indexPath).size;
|
|
1460
1453
|
if (size < 50)
|
|
1461
1454
|
add(warnings, "skill.src_index_minimal", `Source entry point is very small (${size}B)`);
|
|
@@ -1480,7 +1473,7 @@ function validateRegistryConsistency(registry, skillsDir) {
|
|
|
1480
1473
|
return false;
|
|
1481
1474
|
})));
|
|
1482
1475
|
const skillDirs = existsSync4(skillsDir) ? readdirSync3(skillsDir).filter((entry) => {
|
|
1483
|
-
const fullPath =
|
|
1476
|
+
const fullPath = join4(skillsDir, entry);
|
|
1484
1477
|
return !entry.startsWith(".") && entry !== "_common" && statSync3(fullPath).isDirectory();
|
|
1485
1478
|
}) : [];
|
|
1486
1479
|
const directoryNames = new Set(skillDirs);
|
|
@@ -1498,7 +1491,7 @@ function validateRegistryConsistency(registry, skillsDir) {
|
|
|
1498
1491
|
// src/lib/skill-hash.ts
|
|
1499
1492
|
import { createHash } from "crypto";
|
|
1500
1493
|
import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
|
|
1501
|
-
import { join as
|
|
1494
|
+
import { join as join5, sep } from "path";
|
|
1502
1495
|
var CONTENT_HASH_ALGORITHM = "sha256";
|
|
1503
1496
|
var HASH_EXCLUDE_DIRS = new Set([".git", "node_modules", "dist", "build", ".turbo"]);
|
|
1504
1497
|
var HASH_COVERAGE = [
|
|
@@ -1557,7 +1550,7 @@ function collectBundleFiles(skillPath) {
|
|
|
1557
1550
|
if (seen.has(entry))
|
|
1558
1551
|
continue;
|
|
1559
1552
|
seen.add(entry);
|
|
1560
|
-
const absolute =
|
|
1553
|
+
const absolute = join5(skillPath, entry);
|
|
1561
1554
|
if (!existsSync5(absolute))
|
|
1562
1555
|
continue;
|
|
1563
1556
|
if (statSync4(absolute).isDirectory())
|
|
@@ -1571,7 +1564,7 @@ function collectDirectory(files, dir, rel) {
|
|
|
1571
1564
|
for (const entry of readdirSync4(dir).sort()) {
|
|
1572
1565
|
if (entry.startsWith("."))
|
|
1573
1566
|
continue;
|
|
1574
|
-
const absolute =
|
|
1567
|
+
const absolute = join5(dir, entry);
|
|
1575
1568
|
const childRel = `${rel}/${entry}`;
|
|
1576
1569
|
let stats;
|
|
1577
1570
|
try {
|
|
@@ -1798,7 +1791,7 @@ import {
|
|
|
1798
1791
|
realpathSync,
|
|
1799
1792
|
writeFileSync as writeFileSync2
|
|
1800
1793
|
} from "fs";
|
|
1801
|
-
import { basename, dirname as dirname2, join as
|
|
1794
|
+
import { basename, dirname as dirname2, join as join6, relative } from "path";
|
|
1802
1795
|
var ANY_SEGMENT_COPY_EXCLUDES = new Set([
|
|
1803
1796
|
".git",
|
|
1804
1797
|
".DS_Store",
|
|
@@ -1841,9 +1834,9 @@ function normalizePortableSkillName(name) {
|
|
|
1841
1834
|
return normalized;
|
|
1842
1835
|
}
|
|
1843
1836
|
function readPortableSkillManifest(skillPath, fallbackName = basename(skillPath)) {
|
|
1844
|
-
const skillJsonPath =
|
|
1845
|
-
const skillMdPath =
|
|
1846
|
-
const pkgPath =
|
|
1837
|
+
const skillJsonPath = join6(skillPath, "skill.json");
|
|
1838
|
+
const skillMdPath = join6(skillPath, "SKILL.md");
|
|
1839
|
+
const pkgPath = join6(skillPath, "package.json");
|
|
1847
1840
|
const jsonManifest = existsSync6(skillJsonPath) ? readJsonObject(skillJsonPath) : undefined;
|
|
1848
1841
|
const frontmatter = existsSync6(skillMdPath) ? parseSkillFrontmatter(readFileSync5(skillMdPath, "utf-8")) ?? undefined : undefined;
|
|
1849
1842
|
const pkg = existsSync6(pkgPath) ? readJsonObject(pkgPath) : undefined;
|
|
@@ -1892,7 +1885,7 @@ function createInstructionManifest(name, options) {
|
|
|
1892
1885
|
}
|
|
1893
1886
|
function writeInstructionSkillTemplate(skillPath, manifest) {
|
|
1894
1887
|
mkdirSync2(skillPath, { recursive: true });
|
|
1895
|
-
writeFileSync2(
|
|
1888
|
+
writeFileSync2(join6(skillPath, "SKILL.md"), renderInstructionSkillMd(manifest));
|
|
1896
1889
|
writeSkillJsonWithHash(skillPath, manifest);
|
|
1897
1890
|
}
|
|
1898
1891
|
function renderInstructionSkillMd(manifest) {
|
|
@@ -1941,12 +1934,12 @@ function createPortableManifest(name, options) {
|
|
|
1941
1934
|
};
|
|
1942
1935
|
}
|
|
1943
1936
|
function writePortableSkillTemplate(skillPath, manifest) {
|
|
1944
|
-
mkdirSync2(
|
|
1945
|
-
writeFileSync2(
|
|
1946
|
-
writeFileSync2(
|
|
1947
|
-
writeFileSync2(
|
|
1948
|
-
writeFileSync2(
|
|
1949
|
-
writeFileSync2(
|
|
1937
|
+
mkdirSync2(join6(skillPath, "src"), { recursive: true });
|
|
1938
|
+
writeFileSync2(join6(skillPath, "SKILL.md"), renderSkillMd(manifest));
|
|
1939
|
+
writeFileSync2(join6(skillPath, "AGENTS.md"), renderAgentsMd(manifest));
|
|
1940
|
+
writeFileSync2(join6(skillPath, "package.json"), renderPackageJson(manifest));
|
|
1941
|
+
writeFileSync2(join6(skillPath, "tsconfig.json"), renderTsconfig());
|
|
1942
|
+
writeFileSync2(join6(skillPath, "src", "index.ts"), renderEntrypoint(manifest));
|
|
1950
1943
|
writeSkillJsonWithHash(skillPath, manifest);
|
|
1951
1944
|
}
|
|
1952
1945
|
function fillContractDefaults(manifest, entrypoint) {
|
|
@@ -1971,7 +1964,7 @@ function writeSkillJsonWithHash(skillPath, manifest) {
|
|
|
1971
1964
|
content_hash: undefined
|
|
1972
1965
|
}
|
|
1973
1966
|
};
|
|
1974
|
-
writeFileSync2(
|
|
1967
|
+
writeFileSync2(join6(skillPath, "skill.json"), `${JSON.stringify({ ...existing, ...renderSkillJsonObject(withoutHash) }, null, 2)}
|
|
1975
1968
|
`);
|
|
1976
1969
|
const hash = computeContentHash(skillPath);
|
|
1977
1970
|
const withHash = {
|
|
@@ -1981,12 +1974,12 @@ function writeSkillJsonWithHash(skillPath, manifest) {
|
|
|
1981
1974
|
content_hash: hash
|
|
1982
1975
|
}
|
|
1983
1976
|
};
|
|
1984
|
-
writeFileSync2(
|
|
1977
|
+
writeFileSync2(join6(skillPath, "skill.json"), `${JSON.stringify({ ...existing, ...renderSkillJsonObject(withHash) }, null, 2)}
|
|
1985
1978
|
`);
|
|
1986
1979
|
return withHash;
|
|
1987
1980
|
}
|
|
1988
1981
|
function readExistingSkillJson(skillPath) {
|
|
1989
|
-
const path =
|
|
1982
|
+
const path = join6(skillPath, "skill.json");
|
|
1990
1983
|
if (!existsSync6(path))
|
|
1991
1984
|
return {};
|
|
1992
1985
|
try {
|
|
@@ -2020,24 +2013,24 @@ function ensurePortableSkillFiles(skillPath, manifest) {
|
|
|
2020
2013
|
tags: next.tags?.length ? next.tags : ["custom", next.name]
|
|
2021
2014
|
};
|
|
2022
2015
|
const entry = next.commands[0]?.entry ?? "src/index.ts";
|
|
2023
|
-
if (entry && !existsSync6(
|
|
2024
|
-
mkdirSync2(dirname2(
|
|
2025
|
-
writeFileSync2(
|
|
2016
|
+
if (entry && !existsSync6(join6(skillPath, entry))) {
|
|
2017
|
+
mkdirSync2(dirname2(join6(skillPath, entry)), { recursive: true });
|
|
2018
|
+
writeFileSync2(join6(skillPath, entry), renderEntrypoint(next));
|
|
2026
2019
|
}
|
|
2027
|
-
if (!existsSync6(
|
|
2028
|
-
writeFileSync2(
|
|
2020
|
+
if (!existsSync6(join6(skillPath, "SKILL.md")))
|
|
2021
|
+
writeFileSync2(join6(skillPath, "SKILL.md"), renderSkillMd(next));
|
|
2029
2022
|
else
|
|
2030
|
-
writeFileSync2(
|
|
2031
|
-
if (!existsSync6(
|
|
2032
|
-
writeFileSync2(
|
|
2023
|
+
writeFileSync2(join6(skillPath, "SKILL.md"), ensureSkillMdFrontmatter(readFileSync5(join6(skillPath, "SKILL.md"), "utf-8"), next));
|
|
2024
|
+
if (!existsSync6(join6(skillPath, "AGENTS.md")))
|
|
2025
|
+
writeFileSync2(join6(skillPath, "AGENTS.md"), renderAgentsMd(next));
|
|
2033
2026
|
ensurePackageJson(skillPath, next);
|
|
2034
|
-
if (!existsSync6(
|
|
2035
|
-
writeFileSync2(
|
|
2027
|
+
if (!existsSync6(join6(skillPath, "tsconfig.json")))
|
|
2028
|
+
writeFileSync2(join6(skillPath, "tsconfig.json"), renderTsconfig());
|
|
2036
2029
|
writeSkillJsonWithHash(skillPath, next);
|
|
2037
2030
|
return readPortableSkillManifest(skillPath, next.name);
|
|
2038
2031
|
}
|
|
2039
2032
|
function ensurePackageJson(skillPath, manifest) {
|
|
2040
|
-
const pkgPath =
|
|
2033
|
+
const pkgPath = join6(skillPath, "package.json");
|
|
2041
2034
|
const first = manifest.commands[0] ?? { name: manifest.name, entry: "src/index.ts" };
|
|
2042
2035
|
const commandName = normalizePortableSkillName(first.name || manifest.name);
|
|
2043
2036
|
const entry = (first.entry ?? "src/index.ts").replace(/^\.\//, "");
|
|
@@ -2084,8 +2077,8 @@ function ensureInstructionSkillFiles(skillPath, manifest) {
|
|
|
2084
2077
|
inputs: [],
|
|
2085
2078
|
commands: []
|
|
2086
2079
|
};
|
|
2087
|
-
if (!existsSync6(
|
|
2088
|
-
writeFileSync2(
|
|
2080
|
+
if (!existsSync6(join6(skillPath, "SKILL.md"))) {
|
|
2081
|
+
writeFileSync2(join6(skillPath, "SKILL.md"), renderSkillMd(next));
|
|
2089
2082
|
}
|
|
2090
2083
|
writeSkillJsonWithHash(skillPath, next);
|
|
2091
2084
|
return readPortableSkillManifest(skillPath, next.name);
|
|
@@ -2377,18 +2370,18 @@ var LEGACY_CUSTOM_DIRNAME = "custom";
|
|
|
2377
2370
|
function getPortableSkillsRoot(options = {}) {
|
|
2378
2371
|
if (options.rootDir)
|
|
2379
2372
|
return options.rootDir;
|
|
2380
|
-
const appDir = options.homeDir ?
|
|
2381
|
-
const cache =
|
|
2373
|
+
const appDir = options.homeDir ? join7(options.homeDir, ".hasna", "skills") : getDataDir();
|
|
2374
|
+
const cache = join7(appDir, SKILLS_CACHE_DIRNAME);
|
|
2382
2375
|
if (isOwnerLayoutMigrated(appDir) && safeIsDirectory(cache))
|
|
2383
2376
|
return cache;
|
|
2384
|
-
const installed =
|
|
2377
|
+
const installed = join7(appDir, INSTALLED_SKILLS_DIRNAME);
|
|
2385
2378
|
migrateLegacySkillLayout(appDir, installed);
|
|
2386
2379
|
return installed;
|
|
2387
2380
|
}
|
|
2388
2381
|
function looksLikeSkillDirectory(path) {
|
|
2389
2382
|
if (!safeIsDirectory(path))
|
|
2390
2383
|
return false;
|
|
2391
|
-
return existsSync7(
|
|
2384
|
+
return existsSync7(join7(path, "SKILL.md")) || existsSync7(join7(path, "skill.json")) || existsSync7(join7(path, "package.json"));
|
|
2392
2385
|
}
|
|
2393
2386
|
function migrateLegacySkillLayout(appDir, installed) {
|
|
2394
2387
|
if (!safeIsDirectory(appDir))
|
|
@@ -2398,7 +2391,7 @@ function migrateLegacySkillLayout(appDir, installed) {
|
|
|
2398
2391
|
for (const entry of readdirSync5(appDir)) {
|
|
2399
2392
|
if (entry.startsWith(".") || entry === INSTALLED_SKILLS_DIRNAME)
|
|
2400
2393
|
continue;
|
|
2401
|
-
const path =
|
|
2394
|
+
const path = join7(appDir, entry);
|
|
2402
2395
|
if (entry === LEGACY_CUSTOM_DIRNAME) {
|
|
2403
2396
|
if (!safeIsDirectory(path))
|
|
2404
2397
|
continue;
|
|
@@ -2406,7 +2399,7 @@ function migrateLegacySkillLayout(appDir, installed) {
|
|
|
2406
2399
|
for (const nested of readdirSync5(path)) {
|
|
2407
2400
|
if (nested.startsWith("."))
|
|
2408
2401
|
continue;
|
|
2409
|
-
const nestedPath =
|
|
2402
|
+
const nestedPath = join7(path, nested);
|
|
2410
2403
|
if (looksLikeSkillDirectory(nestedPath))
|
|
2411
2404
|
candidates.push({ from: nestedPath, name: nested });
|
|
2412
2405
|
}
|
|
@@ -2420,10 +2413,10 @@ function migrateLegacySkillLayout(appDir, installed) {
|
|
|
2420
2413
|
return;
|
|
2421
2414
|
}
|
|
2422
2415
|
for (const { from, name } of candidates) {
|
|
2423
|
-
const target =
|
|
2416
|
+
const target = join7(installed, name);
|
|
2424
2417
|
if (existsSync7(target))
|
|
2425
2418
|
continue;
|
|
2426
|
-
const staging =
|
|
2419
|
+
const staging = join7(installed, `.migrating-${name}-${process.pid}`);
|
|
2427
2420
|
try {
|
|
2428
2421
|
rmSync(staging, { recursive: true, force: true });
|
|
2429
2422
|
cpSync2(from, staging, { recursive: true, errorOnExist: false });
|
|
@@ -2436,7 +2429,7 @@ function migrateLegacySkillLayout(appDir, installed) {
|
|
|
2436
2429
|
}
|
|
2437
2430
|
}
|
|
2438
2431
|
function getPortableSkillPath(name, options = {}) {
|
|
2439
|
-
return
|
|
2432
|
+
return join7(getPortableSkillsRoot(options), normalizePortableSkillName(name));
|
|
2440
2433
|
}
|
|
2441
2434
|
function findPortableSkill(name, options = {}) {
|
|
2442
2435
|
let normalized;
|
|
@@ -2462,7 +2455,7 @@ function listPortableSkills(options = {}) {
|
|
|
2462
2455
|
for (const entry of readdirSync5(root).sort()) {
|
|
2463
2456
|
if (entry.startsWith("."))
|
|
2464
2457
|
continue;
|
|
2465
|
-
const path =
|
|
2458
|
+
const path = join7(root, entry);
|
|
2466
2459
|
if (!safeIsDirectory(path))
|
|
2467
2460
|
continue;
|
|
2468
2461
|
try {
|
|
@@ -2496,7 +2489,7 @@ function isOfficialSkillName(name) {
|
|
|
2496
2489
|
function scaffoldPortableSkill(name, options = {}) {
|
|
2497
2490
|
const skillName = normalizePortableSkillName(name);
|
|
2498
2491
|
const root = getPortableSkillsRoot(options);
|
|
2499
|
-
const skillPath =
|
|
2492
|
+
const skillPath = join7(root, skillName);
|
|
2500
2493
|
if (existsSync7(skillPath)) {
|
|
2501
2494
|
if (!options.overwrite)
|
|
2502
2495
|
throw new Error(`Skill '${skillName}' already exists at ${skillPath}`);
|
|
@@ -2528,7 +2521,7 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
|
2528
2521
|
const skipped = [];
|
|
2529
2522
|
const entries = readdirSync5(absoluteSource, { withFileTypes: true }).map((entry) => entry.name).filter((entryName) => !entryName.startsWith(".")).sort();
|
|
2530
2523
|
for (const entryName of entries) {
|
|
2531
|
-
const childPath =
|
|
2524
|
+
const childPath = join7(absoluteSource, entryName);
|
|
2532
2525
|
if (!safeIsDirectory(childPath))
|
|
2533
2526
|
continue;
|
|
2534
2527
|
if (!isSkillCandidate(childPath)) {
|
|
@@ -2557,7 +2550,7 @@ function portPortableSkillDirectory(sourceDir, options = {}) {
|
|
|
2557
2550
|
};
|
|
2558
2551
|
}
|
|
2559
2552
|
function isSkillCandidate(dir) {
|
|
2560
|
-
return existsSync7(
|
|
2553
|
+
return existsSync7(join7(dir, "SKILL.md")) || existsSync7(join7(dir, "skill.json")) || existsSync7(join7(dir, "package.json"));
|
|
2561
2554
|
}
|
|
2562
2555
|
function portPortableSkill(sourcePath, options = {}) {
|
|
2563
2556
|
const absoluteSource = normalize2(sourcePath);
|
|
@@ -2573,7 +2566,7 @@ function portPortableSkill(sourcePath, options = {}) {
|
|
|
2573
2566
|
throw new Error(`${via} Importing it would shadow the official '${skillName}'. ` + `Pass --name to choose a different name, or --allow-shadow to override deliberately.`);
|
|
2574
2567
|
}
|
|
2575
2568
|
const root = getPortableSkillsRoot(options);
|
|
2576
|
-
const destination =
|
|
2569
|
+
const destination = join7(root, skillName);
|
|
2577
2570
|
if (existsSync7(destination)) {
|
|
2578
2571
|
if (!options.overwrite)
|
|
2579
2572
|
throw new Error(`Skill '${skillName}' already exists at ${destination}`);
|
|
@@ -2616,10 +2609,10 @@ function buildCorpusManifest(input, name) {
|
|
|
2616
2609
|
function writeCorpusSkill(input, options = {}) {
|
|
2617
2610
|
const name = normalizePortableSkillName(input.name);
|
|
2618
2611
|
const root = getPortableSkillsRoot(options);
|
|
2619
|
-
const skillPath =
|
|
2612
|
+
const skillPath = join7(root, name);
|
|
2620
2613
|
const created = !existsSync7(skillPath);
|
|
2621
2614
|
mkdirSync3(skillPath, { recursive: true });
|
|
2622
|
-
writeFileSync3(
|
|
2615
|
+
writeFileSync3(join7(skillPath, "SKILL.md"), input.skillMd);
|
|
2623
2616
|
const manifest = buildCorpusManifest(input, name);
|
|
2624
2617
|
writeSkillJsonWithHash(skillPath, manifest);
|
|
2625
2618
|
return { name, path: skillPath, manifest, created };
|
|
@@ -2627,19 +2620,19 @@ function writeCorpusSkill(input, options = {}) {
|
|
|
2627
2620
|
function installCorpusSkillAtomically(input, options = {}) {
|
|
2628
2621
|
const name = normalizePortableSkillName(input.name);
|
|
2629
2622
|
const root = getPortableSkillsRoot(options);
|
|
2630
|
-
const target =
|
|
2623
|
+
const target = join7(root, name);
|
|
2631
2624
|
const created = !existsSync7(target);
|
|
2632
2625
|
mkdirSync3(root, { recursive: true });
|
|
2633
|
-
const staging = mkdtempSync(
|
|
2626
|
+
const staging = mkdtempSync(join7(root, `.pull-${name}-`));
|
|
2634
2627
|
let moved = false;
|
|
2635
2628
|
let backup = null;
|
|
2636
2629
|
try {
|
|
2637
|
-
writeFileSync3(
|
|
2630
|
+
writeFileSync3(join7(staging, "SKILL.md"), input.skillMd);
|
|
2638
2631
|
const manifest = buildCorpusManifest(input, name);
|
|
2639
2632
|
writeSkillJsonWithHash(staging, manifest);
|
|
2640
2633
|
if (existsSync7(target)) {
|
|
2641
|
-
backup = mkdtempSync(
|
|
2642
|
-
renameSync(target,
|
|
2634
|
+
backup = mkdtempSync(join7(root, `.pull-backup-${name}-`));
|
|
2635
|
+
renameSync(target, join7(backup, name));
|
|
2643
2636
|
moved = true;
|
|
2644
2637
|
}
|
|
2645
2638
|
renameSync(staging, target);
|
|
@@ -2648,9 +2641,9 @@ function installCorpusSkillAtomically(input, options = {}) {
|
|
|
2648
2641
|
return { name, path: target, manifest, created };
|
|
2649
2642
|
} catch (error) {
|
|
2650
2643
|
rmSync(staging, { recursive: true, force: true });
|
|
2651
|
-
if (moved && backup && existsSync7(
|
|
2644
|
+
if (moved && backup && existsSync7(join7(backup, name))) {
|
|
2652
2645
|
try {
|
|
2653
|
-
renameSync(
|
|
2646
|
+
renameSync(join7(backup, name), target);
|
|
2654
2647
|
} catch {}
|
|
2655
2648
|
}
|
|
2656
2649
|
throw error;
|
|
@@ -2663,8 +2656,8 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
2663
2656
|
const warnings = [...base.warnings];
|
|
2664
2657
|
let manifest;
|
|
2665
2658
|
if (existsSync7(skillPath)) {
|
|
2666
|
-
const skillJsonPath =
|
|
2667
|
-
const skillMdPath =
|
|
2659
|
+
const skillJsonPath = join7(skillPath, "skill.json");
|
|
2660
|
+
const skillMdPath = join7(skillPath, "SKILL.md");
|
|
2668
2661
|
if (!existsSync7(skillJsonPath) && !existsSync7(skillMdPath)) {
|
|
2669
2662
|
add3(issues, "portable.manifest_missing", "Missing portable manifest: expected SKILL.md frontmatter and/or skill.json");
|
|
2670
2663
|
}
|
|
@@ -2684,7 +2677,7 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
2684
2677
|
add3(issues, "portable.version_missing", "Portable manifest missing version");
|
|
2685
2678
|
}
|
|
2686
2679
|
const contractIssues = validatePortableManifestContract(manifest, {
|
|
2687
|
-
strict: existsSync7(
|
|
2680
|
+
strict: existsSync7(join7(skillPath, "skill.json")),
|
|
2688
2681
|
skillPath
|
|
2689
2682
|
});
|
|
2690
2683
|
for (const issue of contractIssues)
|
|
@@ -2720,7 +2713,7 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
2720
2713
|
add3(issues, "portable.command_entry_unsafe", `Command '${command.name}' entry '${command.entry}' must stay inside the skill directory`);
|
|
2721
2714
|
continue;
|
|
2722
2715
|
}
|
|
2723
|
-
const entryPath =
|
|
2716
|
+
const entryPath = join7(skillPath, command.entry);
|
|
2724
2717
|
if (!existsSync7(entryPath))
|
|
2725
2718
|
add3(issues, "portable.command_entry_missing", `Command '${command.name}' entry '${command.entry}' is missing`);
|
|
2726
2719
|
else if (statSync6(entryPath).isDirectory())
|
|
@@ -2731,7 +2724,7 @@ function validatePortableSkillDirectory(name, skillPath) {
|
|
|
2731
2724
|
} catch (error) {
|
|
2732
2725
|
add3(issues, "portable.manifest_invalid", error.message);
|
|
2733
2726
|
}
|
|
2734
|
-
if (manifest?.kind !== "instruction" && !existsSync7(
|
|
2727
|
+
if (manifest?.kind !== "instruction" && !existsSync7(join7(skillPath, "AGENTS.md"))) {
|
|
2735
2728
|
add3(issues, "portable.agents_missing", "Missing AGENTS.md with build-out instructions for coding agents");
|
|
2736
2729
|
}
|
|
2737
2730
|
}
|
|
@@ -2767,12 +2760,12 @@ async function runPortableSkill(name, args, options = {}) {
|
|
|
2767
2760
|
if (!isSafeRelativePath2(command.entry)) {
|
|
2768
2761
|
return { exitCode: 1, error: `Portable skill '${name}' command entry is unsafe` };
|
|
2769
2762
|
}
|
|
2770
|
-
const entryPath =
|
|
2763
|
+
const entryPath = join7(skill.path, command.entry);
|
|
2771
2764
|
if (!existsSync7(entryPath)) {
|
|
2772
2765
|
return { exitCode: 1, error: `Entry point '${command.entry}' not found in portable skill '${name}'` };
|
|
2773
2766
|
}
|
|
2774
|
-
const pkgPath =
|
|
2775
|
-
const nodeModules =
|
|
2767
|
+
const pkgPath = join7(skill.path, "package.json");
|
|
2768
|
+
const nodeModules = join7(skill.path, "node_modules");
|
|
2776
2769
|
if (existsSync7(pkgPath) && !existsSync7(nodeModules) && hasPackageDependencies(pkgPath)) {
|
|
2777
2770
|
const install = Bun.spawn(["bun", "install", "--no-save"], {
|
|
2778
2771
|
cwd: skill.path,
|
|
@@ -3050,7 +3043,7 @@ function discoverSkillsInDir(dir, source = "custom") {
|
|
|
3050
3043
|
for (const entry of entries) {
|
|
3051
3044
|
if (!entry.isDirectory())
|
|
3052
3045
|
continue;
|
|
3053
|
-
const skillMdPath =
|
|
3046
|
+
const skillMdPath = join8(dir, entry.name, "SKILL.md");
|
|
3054
3047
|
if (!existsSync8(skillMdPath))
|
|
3055
3048
|
continue;
|
|
3056
3049
|
let content;
|
|
@@ -3070,7 +3063,7 @@ function discoverSkillsInDir(dir, source = "custom") {
|
|
|
3070
3063
|
category: fm.category || "Development Tools",
|
|
3071
3064
|
tags: fm.tags || [],
|
|
3072
3065
|
...fm.kind ? { kind: fm.kind } : {},
|
|
3073
|
-
...isHostedMetadataSkillDir(
|
|
3066
|
+
...isHostedMetadataSkillDir(join8(dir, entry.name)) ? { serverOwned: true } : {},
|
|
3074
3067
|
source
|
|
3075
3068
|
});
|
|
3076
3069
|
}
|
|
@@ -3086,8 +3079,8 @@ function findExtensionSkillPath(name) {
|
|
|
3086
3079
|
for (const entry of entries) {
|
|
3087
3080
|
if (!entry.isDirectory())
|
|
3088
3081
|
continue;
|
|
3089
|
-
const skillDir =
|
|
3090
|
-
const skillMdPath =
|
|
3082
|
+
const skillDir = join8(config.extensionsDir, entry.name);
|
|
3083
|
+
const skillMdPath = join8(skillDir, "SKILL.md");
|
|
3091
3084
|
if (!existsSync8(skillMdPath))
|
|
3092
3085
|
continue;
|
|
3093
3086
|
let content;
|
|
@@ -3125,7 +3118,7 @@ function loadRegistry(cwd) {
|
|
|
3125
3118
|
const official = SKILLS.map((s) => ({ ...s, source: "official" }));
|
|
3126
3119
|
const extensions = config.extensionsDir ? discoverSkillsInDir(config.extensionsDir, "extension") : [];
|
|
3127
3120
|
const portableCustom = listPortableSkillMetas();
|
|
3128
|
-
const legacyCustom = discoverSkillsInDir(
|
|
3121
|
+
const legacyCustom = discoverSkillsInDir(join8(dataDir2, "custom"));
|
|
3129
3122
|
const globalCustom = mergeCustomSkills([...legacyCustom, ...portableCustom]);
|
|
3130
3123
|
registryCache = mergeSkillRegistryLists(official, extensions, globalCustom);
|
|
3131
3124
|
registryCacheTime = now;
|
|
@@ -3173,8 +3166,8 @@ function getAllTags() {
|
|
|
3173
3166
|
}
|
|
3174
3167
|
// src/lib/installer.ts
|
|
3175
3168
|
import { existsSync as existsSync11, readFileSync as readFileSync9, rmSync as rmSync3 } from "fs";
|
|
3176
|
-
import { dirname as dirname5, join as
|
|
3177
|
-
import { homedir as
|
|
3169
|
+
import { dirname as dirname5, join as join11 } from "path";
|
|
3170
|
+
import { homedir as homedir3 } from "os";
|
|
3178
3171
|
import { fileURLToPath } from "url";
|
|
3179
3172
|
|
|
3180
3173
|
// src/lib/agent-sync.ts
|
|
@@ -3190,8 +3183,8 @@ import {
|
|
|
3190
3183
|
statSync as statSync7,
|
|
3191
3184
|
writeFileSync as writeFileSync4
|
|
3192
3185
|
} from "fs";
|
|
3193
|
-
import { homedir as
|
|
3194
|
-
import { basename as basename3, dirname as dirname4, join as
|
|
3186
|
+
import { homedir as homedir2 } from "os";
|
|
3187
|
+
import { basename as basename3, dirname as dirname4, join as join9 } from "path";
|
|
3195
3188
|
// src/lib/home-migration.ts
|
|
3196
3189
|
function resolveCorpusRoot(options = {}) {
|
|
3197
3190
|
return getPortableSkillsRoot(options);
|
|
@@ -3213,12 +3206,12 @@ function resolveSyncAgents(arg) {
|
|
|
3213
3206
|
}
|
|
3214
3207
|
return [arg];
|
|
3215
3208
|
}
|
|
3216
|
-
function agentGlobalSkillsDir(agent, homeDir =
|
|
3209
|
+
function agentGlobalSkillsDir(agent, homeDir = homedir2()) {
|
|
3217
3210
|
switch (agent) {
|
|
3218
3211
|
case "opencode":
|
|
3219
|
-
return
|
|
3212
|
+
return join9(homeDir, ".config", "opencode", "skills");
|
|
3220
3213
|
default:
|
|
3221
|
-
return
|
|
3214
|
+
return join9(homeDir, `.${agent}`, "skills");
|
|
3222
3215
|
}
|
|
3223
3216
|
}
|
|
3224
3217
|
function adaptSkillMdForAgent(skillMd, agent) {
|
|
@@ -3277,7 +3270,7 @@ function resolveSyncCorpus(options = {}) {
|
|
|
3277
3270
|
function packageSourceRoots(source) {
|
|
3278
3271
|
const roots = [];
|
|
3279
3272
|
for (const sub of ["skills"]) {
|
|
3280
|
-
const candidate =
|
|
3273
|
+
const candidate = join9(source, sub);
|
|
3281
3274
|
if (existsSync9(candidate) && isDirectory(candidate))
|
|
3282
3275
|
roots.push(candidate);
|
|
3283
3276
|
}
|
|
@@ -3293,10 +3286,10 @@ function containsSkillDirectories(path) {
|
|
|
3293
3286
|
return false;
|
|
3294
3287
|
}
|
|
3295
3288
|
return entries.some((entry) => {
|
|
3296
|
-
const candidate =
|
|
3289
|
+
const candidate = join9(path, entry);
|
|
3297
3290
|
if (!isDirectory(candidate))
|
|
3298
3291
|
return false;
|
|
3299
|
-
return existsSync9(
|
|
3292
|
+
return existsSync9(join9(candidate, "SKILL.md")) || existsSync9(join9(candidate, "skill.json")) || existsSync9(join9(candidate, "package.json"));
|
|
3300
3293
|
});
|
|
3301
3294
|
}
|
|
3302
3295
|
function isDirectory(path) {
|
|
@@ -3309,7 +3302,7 @@ function isDirectory(path) {
|
|
|
3309
3302
|
function syncSkillsToAgents(options = {}) {
|
|
3310
3303
|
const requested = normalizeRequested(options.names);
|
|
3311
3304
|
const agents = options.agents?.length ? options.agents : [...SYNC_AGENTS];
|
|
3312
|
-
const homeDir = options.homeDir ??
|
|
3305
|
+
const homeDir = options.homeDir ?? homedir2();
|
|
3313
3306
|
const { roots, source } = resolveSyncCorpus(options);
|
|
3314
3307
|
const corpus = listPortableSkillsAcrossRoots(roots);
|
|
3315
3308
|
const byName = new Map(corpus.map((skill) => [skill.name, skill]));
|
|
@@ -3335,7 +3328,7 @@ function syncSkillsToAgents(options = {}) {
|
|
|
3335
3328
|
actions.push({
|
|
3336
3329
|
skill: name,
|
|
3337
3330
|
agent,
|
|
3338
|
-
path:
|
|
3331
|
+
path: join9(agentGlobalSkillsDir(agent, homeDir), name, "SKILL.md"),
|
|
3339
3332
|
action: "skip",
|
|
3340
3333
|
reason: "not found in this machine's corpus"
|
|
3341
3334
|
});
|
|
@@ -3364,8 +3357,8 @@ function syncSkillsToAgents(options = {}) {
|
|
|
3364
3357
|
return { actions };
|
|
3365
3358
|
}
|
|
3366
3359
|
function writeManagedAgentSkill(params) {
|
|
3367
|
-
const homeDir = params.homeDir ??
|
|
3368
|
-
const dir =
|
|
3360
|
+
const homeDir = params.homeDir ?? homedir2();
|
|
3361
|
+
const dir = join9(agentGlobalSkillsDir(params.agent, homeDir), params.skill);
|
|
3369
3362
|
const result = writeManagedSkillDir(dir, params.skillMd, {
|
|
3370
3363
|
skill: params.skill,
|
|
3371
3364
|
source: params.source,
|
|
@@ -3382,8 +3375,8 @@ function writeManagedAgentSkill(params) {
|
|
|
3382
3375
|
};
|
|
3383
3376
|
}
|
|
3384
3377
|
function writeManagedSkillDir(dir, skillMd, options) {
|
|
3385
|
-
const skillMdPath =
|
|
3386
|
-
const markerPath =
|
|
3378
|
+
const skillMdPath = join9(dir, "SKILL.md");
|
|
3379
|
+
const markerPath = join9(dir, SYNC_MARKER_FILE);
|
|
3387
3380
|
const dirExists = existsSync9(dir);
|
|
3388
3381
|
const managed = existsSync9(markerPath);
|
|
3389
3382
|
const hasSkillMd = existsSync9(skillMdPath);
|
|
@@ -3421,11 +3414,11 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
3421
3414
|
return { action, path: skillMdPath };
|
|
3422
3415
|
const parentDir = dirname4(dir);
|
|
3423
3416
|
mkdirSync4(parentDir, { recursive: true });
|
|
3424
|
-
const transactionDir = mkdtempSync2(
|
|
3425
|
-
const candidateDir =
|
|
3426
|
-
const backupDir =
|
|
3427
|
-
const candidateSkillMdPath =
|
|
3428
|
-
const candidateMarkerPath =
|
|
3417
|
+
const transactionDir = mkdtempSync2(join9(parentDir, `.hasna-skills-write-${basename3(dir)}-`));
|
|
3418
|
+
const candidateDir = join9(transactionDir, "candidate");
|
|
3419
|
+
const backupDir = join9(transactionDir, "backup");
|
|
3420
|
+
const candidateSkillMdPath = join9(candidateDir, "SKILL.md");
|
|
3421
|
+
const candidateMarkerPath = join9(candidateDir, SYNC_MARKER_FILE);
|
|
3429
3422
|
const marker = {
|
|
3430
3423
|
managedBy: SYNC_MARKER_MANAGED_BY,
|
|
3431
3424
|
skill: options.skill,
|
|
@@ -3473,16 +3466,16 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
3473
3466
|
}
|
|
3474
3467
|
return { action, path: skillMdPath };
|
|
3475
3468
|
}
|
|
3476
|
-
function removeManagedAgentSkill(skill, agent, homeDir =
|
|
3477
|
-
const dir =
|
|
3478
|
-
if (!existsSync9(
|
|
3469
|
+
function removeManagedAgentSkill(skill, agent, homeDir = homedir2()) {
|
|
3470
|
+
const dir = join9(agentGlobalSkillsDir(agent, homeDir), skill);
|
|
3471
|
+
if (!existsSync9(join9(dir, SYNC_MARKER_FILE)))
|
|
3479
3472
|
return false;
|
|
3480
3473
|
rmSync2(dir, { recursive: true, force: true });
|
|
3481
3474
|
return true;
|
|
3482
3475
|
}
|
|
3483
3476
|
function sourceSkillMd(skillPath, name, description, kind, preferBundledDocs = false) {
|
|
3484
3477
|
if (kind === undefined || kind === "instruction" || preferBundledDocs) {
|
|
3485
|
-
const skillMdPath =
|
|
3478
|
+
const skillMdPath = join9(skillPath, "SKILL.md");
|
|
3486
3479
|
if (existsSync9(skillMdPath))
|
|
3487
3480
|
return readFileSync7(skillMdPath, "utf-8");
|
|
3488
3481
|
}
|
|
@@ -3515,7 +3508,7 @@ function normalizeSkillName(name) {
|
|
|
3515
3508
|
|
|
3516
3509
|
// src/lib/project-state.ts
|
|
3517
3510
|
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
3518
|
-
import { join as
|
|
3511
|
+
import { join as join10 } from "path";
|
|
3519
3512
|
var VALID_PIN_SOURCES = [
|
|
3520
3513
|
"official",
|
|
3521
3514
|
"custom",
|
|
@@ -3530,10 +3523,10 @@ var SKILLS_PROJECT_DIR = ".skills";
|
|
|
3530
3523
|
var PROJECT_CONFIG_FILE = "project.json";
|
|
3531
3524
|
var DEFAULT_EXPORT_DIR = ".skills/exports";
|
|
3532
3525
|
function getProjectStateDir(targetDir = process.cwd()) {
|
|
3533
|
-
return
|
|
3526
|
+
return join10(targetDir, SKILLS_PROJECT_DIR);
|
|
3534
3527
|
}
|
|
3535
3528
|
function getProjectConfigPath(targetDir = process.cwd()) {
|
|
3536
|
-
return
|
|
3529
|
+
return join10(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
|
|
3537
3530
|
}
|
|
3538
3531
|
function loadProjectConfig(targetDir = process.cwd()) {
|
|
3539
3532
|
const path = getProjectConfigPath(targetDir);
|
|
@@ -3658,12 +3651,12 @@ var __dirname2 = dirname5(fileURLToPath(import.meta.url));
|
|
|
3658
3651
|
function findSkillsDir() {
|
|
3659
3652
|
let dir = __dirname2;
|
|
3660
3653
|
for (let i = 0;i < 5; i++) {
|
|
3661
|
-
const candidate =
|
|
3654
|
+
const candidate = join11(dir, "skills");
|
|
3662
3655
|
if (existsSync11(candidate) && !dir.includes(".skills"))
|
|
3663
3656
|
return candidate;
|
|
3664
3657
|
dir = dirname5(dir);
|
|
3665
3658
|
}
|
|
3666
|
-
return
|
|
3659
|
+
return join11(__dirname2, "..", "skills");
|
|
3667
3660
|
}
|
|
3668
3661
|
var SKILLS_DIR = findSkillsDir();
|
|
3669
3662
|
function getSkillPath(name) {
|
|
@@ -3671,13 +3664,13 @@ function getSkillPath(name) {
|
|
|
3671
3664
|
const portable = findPortableSkill(skillName);
|
|
3672
3665
|
if (portable)
|
|
3673
3666
|
return portable.path;
|
|
3674
|
-
const legacyCustomPath =
|
|
3667
|
+
const legacyCustomPath = join11(getDataDir(), "custom", skillName);
|
|
3675
3668
|
if (existsSync11(legacyCustomPath))
|
|
3676
3669
|
return legacyCustomPath;
|
|
3677
3670
|
const extensionPath = findExtensionSkillPath(skillName);
|
|
3678
3671
|
if (extensionPath)
|
|
3679
3672
|
return extensionPath;
|
|
3680
|
-
return
|
|
3673
|
+
return join11(SKILLS_DIR, skillName);
|
|
3681
3674
|
}
|
|
3682
3675
|
function getCanonicalSkillName(name) {
|
|
3683
3676
|
return getSkill(name)?.name ?? resolveSkillAlias(normalizeSkillSlug(name));
|
|
@@ -3740,7 +3733,7 @@ function createLocalSkillManifest(name, generateSkillMd) {
|
|
|
3740
3733
|
if (!existsSync11(sourcePath))
|
|
3741
3734
|
return null;
|
|
3742
3735
|
let skillMd = "";
|
|
3743
|
-
const skillMdPath =
|
|
3736
|
+
const skillMdPath = join11(sourcePath, "SKILL.md");
|
|
3744
3737
|
if (existsSync11(skillMdPath)) {
|
|
3745
3738
|
skillMd = readFileSync9(skillMdPath, "utf-8");
|
|
3746
3739
|
} else if (generateSkillMd) {
|
|
@@ -3814,16 +3807,16 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
|
|
|
3814
3807
|
const base = projectDir || process.cwd();
|
|
3815
3808
|
switch (agent) {
|
|
3816
3809
|
case "pi":
|
|
3817
|
-
return scope === "project" ?
|
|
3810
|
+
return scope === "project" ? join11(base, ".pi", "skills") : join11(homedir3(), ".pi", "agent", "skills");
|
|
3818
3811
|
case "opencode":
|
|
3819
|
-
return scope === "project" ?
|
|
3812
|
+
return scope === "project" ? join11(base, ".opencode", "skills") : join11(homedir3(), ".config", "opencode", "skills");
|
|
3820
3813
|
default:
|
|
3821
|
-
return scope === "project" ?
|
|
3814
|
+
return scope === "project" ? join11(base, `.${agent}`, "skills") : join11(homedir3(), `.${agent}`, "skills");
|
|
3822
3815
|
}
|
|
3823
3816
|
}
|
|
3824
3817
|
function getAgentSkillPath(name, agent, scope = "global", projectDir) {
|
|
3825
3818
|
const skillName = normalizeSkillName(getCanonicalSkillName(name));
|
|
3826
|
-
return
|
|
3819
|
+
return join11(getAgentSkillsDir(agent, scope, projectDir), skillName);
|
|
3827
3820
|
}
|
|
3828
3821
|
function installSkillForAgent(name, options, generateSkillMd) {
|
|
3829
3822
|
const canonicalName = getCanonicalSkillName(name);
|
|
@@ -3851,14 +3844,14 @@ function removeSkillForAgent(name, options) {
|
|
|
3851
3844
|
const canonicalName = getCanonicalSkillName(name);
|
|
3852
3845
|
const scope = options.scope ?? "global";
|
|
3853
3846
|
const dir = getAgentSkillPath(canonicalName, options.agent, scope, options.projectDir);
|
|
3854
|
-
if (!existsSync11(
|
|
3847
|
+
if (!existsSync11(join11(dir, SYNC_MARKER_FILE)))
|
|
3855
3848
|
return false;
|
|
3856
3849
|
rmSync3(dir, { recursive: true, force: true });
|
|
3857
3850
|
return true;
|
|
3858
3851
|
}
|
|
3859
3852
|
function resolveAgentSkillMd(name, generateSkillMd) {
|
|
3860
3853
|
const sourcePath = getSkillPath(name);
|
|
3861
|
-
const skillMdPath =
|
|
3854
|
+
const skillMdPath = join11(sourcePath, "SKILL.md");
|
|
3862
3855
|
if (existsSync11(skillMdPath))
|
|
3863
3856
|
return readFileSync9(skillMdPath, "utf-8");
|
|
3864
3857
|
if (generateSkillMd)
|
|
@@ -3893,7 +3886,7 @@ function generateMinimalSkillMd(name) {
|
|
|
3893
3886
|
"---",
|
|
3894
3887
|
""
|
|
3895
3888
|
].filter(Boolean);
|
|
3896
|
-
const fallbackDoc = readFileIfExists(
|
|
3889
|
+
const fallbackDoc = readFileIfExists(join11(sourcePath, "README.md")) || readFileIfExists(join11(sourcePath, "CLAUDE.md"));
|
|
3897
3890
|
if (fallbackDoc)
|
|
3898
3891
|
return `${frontmatter.join(`
|
|
3899
3892
|
`)}${fallbackDoc.trim()}
|
|
@@ -3912,7 +3905,7 @@ skills run ${canonicalName}
|
|
|
3912
3905
|
`;
|
|
3913
3906
|
}
|
|
3914
3907
|
function readBundledSkillVersion(name) {
|
|
3915
|
-
const pkgPath =
|
|
3908
|
+
const pkgPath = join11(getSkillPath(name), "package.json");
|
|
3916
3909
|
if (!existsSync11(pkgPath))
|
|
3917
3910
|
return "unknown";
|
|
3918
3911
|
try {
|
|
@@ -3931,19 +3924,19 @@ function loadProjectConfigCompat(targetDir) {
|
|
|
3931
3924
|
// src/lib/run-state.ts
|
|
3932
3925
|
import { createHash as createHash2, randomBytes } from "crypto";
|
|
3933
3926
|
import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync10, readdirSync as readdirSync8, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
3934
|
-
import { extname, join as
|
|
3927
|
+
import { extname, join as join12, relative as relative2 } from "path";
|
|
3935
3928
|
function createSkillRun(params, targetDir = process.cwd()) {
|
|
3936
3929
|
const now = new Date;
|
|
3937
3930
|
const id = createRunId(now);
|
|
3938
3931
|
const day = now.toISOString().slice(0, 10);
|
|
3939
3932
|
const skillName = normalizeSkillName(params.skill);
|
|
3940
3933
|
const root = getProjectStateDir(targetDir);
|
|
3941
|
-
const runDir =
|
|
3942
|
-
const logsDir =
|
|
3943
|
-
const exportDir =
|
|
3934
|
+
const runDir = join12(root, "runs", day, id);
|
|
3935
|
+
const logsDir = join12(runDir, "logs");
|
|
3936
|
+
const exportDir = join12(root, "exports", skillName, id);
|
|
3944
3937
|
mkdirSync6(logsDir, { recursive: true });
|
|
3945
3938
|
mkdirSync6(exportDir, { recursive: true });
|
|
3946
|
-
mkdirSync6(
|
|
3939
|
+
mkdirSync6(join12(root, "tmp"), { recursive: true });
|
|
3947
3940
|
const record = {
|
|
3948
3941
|
id,
|
|
3949
3942
|
skill: skillName,
|
|
@@ -3994,27 +3987,27 @@ function updateSkillRun(context, patch) {
|
|
|
3994
3987
|
return context.record;
|
|
3995
3988
|
}
|
|
3996
3989
|
function writeRunLogs(context, stdout = "", stderr = "") {
|
|
3997
|
-
writeFileSync6(
|
|
3998
|
-
writeFileSync6(
|
|
3990
|
+
writeFileSync6(join12(context.logsDir, "stdout.log"), stdout);
|
|
3991
|
+
writeFileSync6(join12(context.logsDir, "stderr.log"), stderr);
|
|
3999
3992
|
}
|
|
4000
3993
|
function appendRunEvent(context, event, data = {}) {
|
|
4001
3994
|
const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
|
|
4002
3995
|
`;
|
|
4003
|
-
const path =
|
|
3996
|
+
const path = join12(context.runDir, "events.ndjson");
|
|
4004
3997
|
const previous = existsSync12(path) ? readFileSync10(path, "utf-8") : "";
|
|
4005
3998
|
writeFileSync6(path, previous + line);
|
|
4006
3999
|
}
|
|
4007
4000
|
function listSkillRuns(targetDir = process.cwd(), limit = 50) {
|
|
4008
|
-
const runsRoot =
|
|
4001
|
+
const runsRoot = join12(getProjectStateDir(targetDir), "runs");
|
|
4009
4002
|
if (!existsSync12(runsRoot))
|
|
4010
4003
|
return [];
|
|
4011
4004
|
const records = [];
|
|
4012
4005
|
for (const day of readdirSync8(runsRoot).sort().reverse()) {
|
|
4013
|
-
const dayDir =
|
|
4006
|
+
const dayDir = join12(runsRoot, day);
|
|
4014
4007
|
if (!statSync8(dayDir).isDirectory())
|
|
4015
4008
|
continue;
|
|
4016
4009
|
for (const runId of readdirSync8(dayDir).sort().reverse()) {
|
|
4017
|
-
const record = readRunRecord(
|
|
4010
|
+
const record = readRunRecord(join12(dayDir, runId));
|
|
4018
4011
|
if (record)
|
|
4019
4012
|
records.push(record);
|
|
4020
4013
|
if (records.length >= limit)
|
|
@@ -4024,25 +4017,25 @@ function listSkillRuns(targetDir = process.cwd(), limit = 50) {
|
|
|
4024
4017
|
return records;
|
|
4025
4018
|
}
|
|
4026
4019
|
function findSkillRun(runId, targetDir = process.cwd()) {
|
|
4027
|
-
const runsRoot =
|
|
4020
|
+
const runsRoot = join12(getProjectStateDir(targetDir), "runs");
|
|
4028
4021
|
if (!existsSync12(runsRoot))
|
|
4029
4022
|
return null;
|
|
4030
4023
|
for (const day of readdirSync8(runsRoot)) {
|
|
4031
|
-
const record = readRunRecord(
|
|
4024
|
+
const record = readRunRecord(join12(runsRoot, day, runId));
|
|
4032
4025
|
if (record)
|
|
4033
4026
|
return record;
|
|
4034
4027
|
}
|
|
4035
4028
|
return null;
|
|
4036
4029
|
}
|
|
4037
4030
|
function getRunExportDir(runId, skill, targetDir = process.cwd()) {
|
|
4038
|
-
return
|
|
4031
|
+
return join12(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
|
|
4039
4032
|
}
|
|
4040
4033
|
function writeRunRecord(context) {
|
|
4041
|
-
writeFileSync6(
|
|
4034
|
+
writeFileSync6(join12(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
|
|
4042
4035
|
`);
|
|
4043
4036
|
}
|
|
4044
4037
|
function writeArtifactsManifest(context, artifacts) {
|
|
4045
|
-
writeFileSync6(
|
|
4038
|
+
writeFileSync6(join12(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
|
|
4046
4039
|
`);
|
|
4047
4040
|
}
|
|
4048
4041
|
function collectRunArtifacts(context) {
|
|
@@ -4062,7 +4055,7 @@ function collectRunArtifacts(context) {
|
|
|
4062
4055
|
return artifacts.sort((a, b) => a.path.localeCompare(b.path));
|
|
4063
4056
|
}
|
|
4064
4057
|
function readRunRecord(runDir) {
|
|
4065
|
-
const path =
|
|
4058
|
+
const path = join12(runDir, "run.json");
|
|
4066
4059
|
if (!existsSync12(path))
|
|
4067
4060
|
return null;
|
|
4068
4061
|
try {
|
|
@@ -4074,7 +4067,7 @@ function readRunRecord(runDir) {
|
|
|
4074
4067
|
function walkFiles(dir) {
|
|
4075
4068
|
const files = [];
|
|
4076
4069
|
for (const entry of readdirSync8(dir)) {
|
|
4077
|
-
const full =
|
|
4070
|
+
const full = join12(dir, entry);
|
|
4078
4071
|
if (statSync8(full).isDirectory())
|
|
4079
4072
|
files.push(...walkFiles(full));
|
|
4080
4073
|
else
|
|
@@ -4121,11 +4114,11 @@ function mimeForPath(path) {
|
|
|
4121
4114
|
}
|
|
4122
4115
|
// src/lib/skillinfo.ts
|
|
4123
4116
|
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
|
|
4124
|
-
import { join as
|
|
4117
|
+
import { join as join13 } from "path";
|
|
4125
4118
|
function isInstructionSkillDir(skillPath, meta) {
|
|
4126
4119
|
if (meta?.kind === "instruction")
|
|
4127
4120
|
return true;
|
|
4128
|
-
const skillMdPath =
|
|
4121
|
+
const skillMdPath = join13(skillPath, "SKILL.md");
|
|
4129
4122
|
if (!existsSync13(skillMdPath))
|
|
4130
4123
|
return false;
|
|
4131
4124
|
try {
|
|
@@ -4155,9 +4148,9 @@ function getSkillDocs(name) {
|
|
|
4155
4148
|
if (!existsSync13(skillPath))
|
|
4156
4149
|
return null;
|
|
4157
4150
|
return {
|
|
4158
|
-
skillMd: readIfExists(
|
|
4159
|
-
readme: readIfExists(
|
|
4160
|
-
claudeMd: readIfExists(
|
|
4151
|
+
skillMd: readIfExists(join13(skillPath, "SKILL.md")),
|
|
4152
|
+
readme: readIfExists(join13(skillPath, "README.md")),
|
|
4153
|
+
claudeMd: readIfExists(join13(skillPath, "CLAUDE.md"))
|
|
4161
4154
|
};
|
|
4162
4155
|
}
|
|
4163
4156
|
function getSkillBestDoc(name) {
|
|
@@ -4172,7 +4165,7 @@ function getSkillRequirements(name) {
|
|
|
4172
4165
|
return null;
|
|
4173
4166
|
const texts = [];
|
|
4174
4167
|
for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
|
|
4175
|
-
const content = readIfExists(
|
|
4168
|
+
const content = readIfExists(join13(skillPath, file));
|
|
4176
4169
|
if (content)
|
|
4177
4170
|
texts.push(content);
|
|
4178
4171
|
}
|
|
@@ -4211,7 +4204,7 @@ function getSkillRequirements(name) {
|
|
|
4211
4204
|
const skillName = normalizeSkillName(name);
|
|
4212
4205
|
let cliCommand = `skills run ${skillName}`;
|
|
4213
4206
|
let dependencies = {};
|
|
4214
|
-
const pkgPath =
|
|
4207
|
+
const pkgPath = join13(skillPath, "package.json");
|
|
4215
4208
|
if (existsSync13(pkgPath)) {
|
|
4216
4209
|
try {
|
|
4217
4210
|
const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
@@ -4241,7 +4234,7 @@ async function runSkill(name, args, options = {}) {
|
|
|
4241
4234
|
error: `Skill '${name}' is an instruction skill (kind: instruction) and is not runnable. Instruction skills are consumed by coding agents via SKILL.md, not executed with 'skills run'.`
|
|
4242
4235
|
};
|
|
4243
4236
|
}
|
|
4244
|
-
const pkgPath =
|
|
4237
|
+
const pkgPath = join13(skillPath, "package.json");
|
|
4245
4238
|
if (!existsSync13(pkgPath)) {
|
|
4246
4239
|
return { exitCode: 1, error: `No package.json in skill '${name}'` };
|
|
4247
4240
|
}
|
|
@@ -4261,11 +4254,11 @@ async function runSkill(name, args, options = {}) {
|
|
|
4261
4254
|
} catch {
|
|
4262
4255
|
return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
|
|
4263
4256
|
}
|
|
4264
|
-
const entryPath =
|
|
4257
|
+
const entryPath = join13(skillPath, entryPoint);
|
|
4265
4258
|
if (!existsSync13(entryPath)) {
|
|
4266
4259
|
return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
|
|
4267
4260
|
}
|
|
4268
|
-
const nodeModules =
|
|
4261
|
+
const nodeModules = join13(skillPath, "node_modules");
|
|
4269
4262
|
if (!existsSync13(nodeModules)) {
|
|
4270
4263
|
const install = Bun.spawn(["bun", "install", "--no-save"], {
|
|
4271
4264
|
cwd: skillPath,
|
|
@@ -4347,10 +4340,10 @@ function generateSkillMd(name) {
|
|
|
4347
4340
|
"---"
|
|
4348
4341
|
].join(`
|
|
4349
4342
|
`);
|
|
4350
|
-
const readme = readIfExists(
|
|
4351
|
-
const claudeMd = readIfExists(
|
|
4343
|
+
const readme = readIfExists(join13(skillPath, "README.md"));
|
|
4344
|
+
const claudeMd = readIfExists(join13(skillPath, "CLAUDE.md"));
|
|
4352
4345
|
let cliCommand = null;
|
|
4353
|
-
const pkgPath =
|
|
4346
|
+
const pkgPath = join13(skillPath, "package.json");
|
|
4354
4347
|
if (existsSync13(pkgPath)) {
|
|
4355
4348
|
try {
|
|
4356
4349
|
const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
@@ -8433,13 +8426,13 @@ function requireApiUrl(action = "This command", config, env) {
|
|
|
8433
8426
|
|
|
8434
8427
|
// src/lib/auth-store.ts
|
|
8435
8428
|
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync7, unlinkSync } from "fs";
|
|
8436
|
-
import { dirname as dirname6, join as
|
|
8437
|
-
import { homedir as
|
|
8429
|
+
import { dirname as dirname6, join as join14 } from "path";
|
|
8430
|
+
import { homedir as homedir4 } from "os";
|
|
8438
8431
|
function getAuthFilePath() {
|
|
8439
|
-
return
|
|
8432
|
+
return join14(getDataDir(), "auth.json");
|
|
8440
8433
|
}
|
|
8441
8434
|
function legacyAuthFilePath() {
|
|
8442
|
-
return
|
|
8435
|
+
return join14(process.env["HOME"] || process.env["USERPROFILE"] || homedir4(), ".skills", "auth.json");
|
|
8443
8436
|
}
|
|
8444
8437
|
var cachedConfig;
|
|
8445
8438
|
function getAuthConfig() {
|
|
@@ -9404,12 +9397,30 @@ class RemoteSkillsClient {
|
|
|
9404
9397
|
async downloadSkillBundle(slug) {
|
|
9405
9398
|
return this.request(`/api/v1/skills/${encodeURIComponent(slug)}/bundle`, { method: "GET" });
|
|
9406
9399
|
}
|
|
9407
|
-
async getBundle(slug) {
|
|
9408
|
-
const
|
|
9400
|
+
async getBundle(slug, version) {
|
|
9401
|
+
const path = version ? `/api/v1/skills/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}/bundle` : `/api/v1/skills/${encodeURIComponent(slug)}/bundle`;
|
|
9402
|
+
const response = await this.request(path, { method: "GET" });
|
|
9409
9403
|
if (response.status === 404)
|
|
9410
9404
|
return null;
|
|
9411
9405
|
return response;
|
|
9412
9406
|
}
|
|
9407
|
+
async listSkillVersions(slug) {
|
|
9408
|
+
const response = await this.requestNewRoute(`/api/v1/skills/${encodeURIComponent(slug)}/versions`, undefined, { domainNotFoundCodes: ["SKILL_NOT_FOUND"] });
|
|
9409
|
+
if (response.status === 404)
|
|
9410
|
+
return [];
|
|
9411
|
+
if (!response.ok)
|
|
9412
|
+
throw new Error(`versions request failed: ${response.status}`);
|
|
9413
|
+
const body = await response.json();
|
|
9414
|
+
return Array.isArray(body.versions) ? body.versions : [];
|
|
9415
|
+
}
|
|
9416
|
+
async getSkillVersion(slug, version) {
|
|
9417
|
+
const response = await this.requestNewRoute(`/api/v1/skills/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}`, undefined, { domainNotFoundCodes: ["SKILL_NOT_FOUND", "SKILL_VERSION_NOT_FOUND"] });
|
|
9418
|
+
if (response.status === 404)
|
|
9419
|
+
return null;
|
|
9420
|
+
if (!response.ok)
|
|
9421
|
+
throw new Error(`version request failed: ${response.status}`);
|
|
9422
|
+
return await response.json();
|
|
9423
|
+
}
|
|
9413
9424
|
async listPins() {
|
|
9414
9425
|
const response = await this.requestNewRoute("/api/v1/pins");
|
|
9415
9426
|
return normalizePinList(await response.json());
|
|
@@ -9548,9 +9559,9 @@ function createRemoteSkillsClient() {
|
|
|
9548
9559
|
}
|
|
9549
9560
|
// src/lib/scheduler.ts
|
|
9550
9561
|
import { existsSync as existsSync15, readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8 } from "fs";
|
|
9551
|
-
import { join as
|
|
9562
|
+
import { join as join15 } from "path";
|
|
9552
9563
|
function getSchedulesPath(targetDir = process.cwd()) {
|
|
9553
|
-
return
|
|
9564
|
+
return join15(targetDir, ".skills", "schedules.json");
|
|
9554
9565
|
}
|
|
9555
9566
|
function loadSchedules(targetDir = process.cwd()) {
|
|
9556
9567
|
const path = getSchedulesPath(targetDir);
|
|
@@ -9563,7 +9574,7 @@ function loadSchedules(targetDir = process.cwd()) {
|
|
|
9563
9574
|
}
|
|
9564
9575
|
function saveSchedules(data, targetDir = process.cwd()) {
|
|
9565
9576
|
const path = getSchedulesPath(targetDir);
|
|
9566
|
-
const dir =
|
|
9577
|
+
const dir = join15(targetDir, ".skills");
|
|
9567
9578
|
if (!existsSync15(dir))
|
|
9568
9579
|
mkdirSync8(dir, { recursive: true });
|
|
9569
9580
|
writeFileSync8(path, JSON.stringify(data, null, 2));
|
|
@@ -9751,7 +9762,7 @@ function recordScheduleRun(id, status, targetDir) {
|
|
|
9751
9762
|
saveSchedules(data, targetDir);
|
|
9752
9763
|
}
|
|
9753
9764
|
// src/lib/pull.ts
|
|
9754
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync3, readFileSync as
|
|
9765
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync3, readFileSync as readFileSync15, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
|
|
9755
9766
|
import { dirname as dirname7, join as join17 } from "path";
|
|
9756
9767
|
|
|
9757
9768
|
// src/lib/revision.ts
|
|
@@ -9779,6 +9790,8 @@ function revisionIdOfRecord(record) {
|
|
|
9779
9790
|
|
|
9780
9791
|
// src/lib/skill-bundle.ts
|
|
9781
9792
|
import { createHash as createHash4 } from "crypto";
|
|
9793
|
+
import { readFileSync as readFileSync14, readdirSync as readdirSync9, statSync as statSync9 } from "fs";
|
|
9794
|
+
import { join as join16, relative as relative3 } from "path";
|
|
9782
9795
|
var BLOCK = 512;
|
|
9783
9796
|
var ANY_SEGMENT_EXCLUDES = new Set([
|
|
9784
9797
|
".git",
|
|
@@ -9805,6 +9818,7 @@ var CREDENTIAL_FILENAMES = new Set([
|
|
|
9805
9818
|
"id_ecdsa",
|
|
9806
9819
|
"id_ed25519"
|
|
9807
9820
|
]);
|
|
9821
|
+
var CREDENTIAL_EXTENSIONS = [".pem", ".key", ".p12", ".pfx", ".keystore", ".jks"];
|
|
9808
9822
|
var ENV_TEMPLATE_NAMES = new Set([".env.example", ".env.sample", ".env.template", ".env.dist"]);
|
|
9809
9823
|
var NON_DOTENV_EXTENSIONS = new Set([
|
|
9810
9824
|
"ts",
|
|
@@ -9901,6 +9915,27 @@ var NON_DOTENV_EXTENSIONS = new Set([
|
|
|
9901
9915
|
"tar",
|
|
9902
9916
|
"wasm"
|
|
9903
9917
|
]);
|
|
9918
|
+
function isDotenvFile(lower) {
|
|
9919
|
+
if (lower === ".env" || lower.startsWith(".env."))
|
|
9920
|
+
return true;
|
|
9921
|
+
if (lower === "env")
|
|
9922
|
+
return true;
|
|
9923
|
+
if (lower.startsWith("env.")) {
|
|
9924
|
+
const extension = lower.slice(lower.lastIndexOf(".") + 1);
|
|
9925
|
+
return !NON_DOTENV_EXTENSIONS.has(extension);
|
|
9926
|
+
}
|
|
9927
|
+
return false;
|
|
9928
|
+
}
|
|
9929
|
+
function isCredentialFile(name) {
|
|
9930
|
+
const lower = name.toLowerCase();
|
|
9931
|
+
if (ENV_TEMPLATE_NAMES.has(lower))
|
|
9932
|
+
return false;
|
|
9933
|
+
if (isDotenvFile(lower))
|
|
9934
|
+
return true;
|
|
9935
|
+
if (CREDENTIAL_FILENAMES.has(lower))
|
|
9936
|
+
return true;
|
|
9937
|
+
return CREDENTIAL_EXTENSIONS.some((extension) => lower.endsWith(extension));
|
|
9938
|
+
}
|
|
9904
9939
|
function ownBytes(view) {
|
|
9905
9940
|
const source = view instanceof ArrayBuffer ? new Uint8Array(view) : view;
|
|
9906
9941
|
const out = new Uint8Array(new ArrayBuffer(source.byteLength));
|
|
@@ -9910,9 +9945,117 @@ function ownBytes(view) {
|
|
|
9910
9945
|
function sha256Hex(bytes) {
|
|
9911
9946
|
return createHash4("sha256").update(bytes).digest("hex");
|
|
9912
9947
|
}
|
|
9948
|
+
function collectSkillBundleEntries(dir) {
|
|
9949
|
+
const entries = [];
|
|
9950
|
+
walk(dir, dir, entries);
|
|
9951
|
+
return entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
9952
|
+
}
|
|
9953
|
+
function walk(root, current, out) {
|
|
9954
|
+
for (const entry of readdirSync9(current, { withFileTypes: true })) {
|
|
9955
|
+
const absolute = join16(current, entry.name);
|
|
9956
|
+
const rel = relative3(root, absolute).split("\\").join("/");
|
|
9957
|
+
const isRootLevel = !rel.includes("/");
|
|
9958
|
+
if (ANY_SEGMENT_EXCLUDES.has(entry.name.toLowerCase()))
|
|
9959
|
+
continue;
|
|
9960
|
+
if (isRootLevel && ROOT_EXCLUDES.has(entry.name.toLowerCase()))
|
|
9961
|
+
continue;
|
|
9962
|
+
if (isRootLevel && TOOL_SIDECAR_FILENAMES.has(entry.name.toLowerCase()))
|
|
9963
|
+
continue;
|
|
9964
|
+
if (entry.name.startsWith("._"))
|
|
9965
|
+
continue;
|
|
9966
|
+
if (entry.isSymbolicLink())
|
|
9967
|
+
continue;
|
|
9968
|
+
if (entry.isDirectory()) {
|
|
9969
|
+
walk(root, absolute, out);
|
|
9970
|
+
continue;
|
|
9971
|
+
}
|
|
9972
|
+
if (!entry.isFile())
|
|
9973
|
+
continue;
|
|
9974
|
+
if (isCredentialFile(entry.name))
|
|
9975
|
+
continue;
|
|
9976
|
+
const stats = statSync9(absolute);
|
|
9977
|
+
out.push({
|
|
9978
|
+
path: rel,
|
|
9979
|
+
bytes: ownBytes(readFileSync14(absolute)),
|
|
9980
|
+
mode: stats.mode & 64 ? 493 : 420
|
|
9981
|
+
});
|
|
9982
|
+
}
|
|
9983
|
+
}
|
|
9984
|
+
function packSkillBundle(dir, options = {}) {
|
|
9985
|
+
const entries = collectSkillBundleEntries(dir);
|
|
9986
|
+
if (entries.length === 0) {
|
|
9987
|
+
throw new Error(`Nothing to pack: ${dir} contains no files after exclusions (.git, node_modules, dist, .env)`);
|
|
9988
|
+
}
|
|
9989
|
+
const unpackedByteSize = entries.reduce((sum, entry) => sum + entry.bytes.byteLength, 0);
|
|
9990
|
+
const max = options.maxUnpackedBytes ?? 0;
|
|
9991
|
+
if (max > 0 && unpackedByteSize > max) {
|
|
9992
|
+
throw new Error(`Skill sources are ${unpackedByteSize} bytes, over the ${max} byte limit. Remove build output or large fixtures.`);
|
|
9993
|
+
}
|
|
9994
|
+
const tar = writeTar(entries);
|
|
9995
|
+
const bytes = canonicalGzip(tar);
|
|
9996
|
+
return {
|
|
9997
|
+
bytes,
|
|
9998
|
+
sha256: sha256Hex(bytes),
|
|
9999
|
+
fileCount: entries.length,
|
|
10000
|
+
unpackedByteSize,
|
|
10001
|
+
paths: entries.map((entry) => entry.path)
|
|
10002
|
+
};
|
|
10003
|
+
}
|
|
10004
|
+
function canonicalGzip(tar) {
|
|
10005
|
+
const bytes = ownBytes(Bun.gzipSync(tar, { level: 6 }));
|
|
10006
|
+
if (bytes.byteLength >= 10) {
|
|
10007
|
+
bytes[4] = 0;
|
|
10008
|
+
bytes[5] = 0;
|
|
10009
|
+
bytes[6] = 0;
|
|
10010
|
+
bytes[7] = 0;
|
|
10011
|
+
bytes[9] = 255;
|
|
10012
|
+
}
|
|
10013
|
+
return bytes;
|
|
10014
|
+
}
|
|
9913
10015
|
function unpackSkillBundle(bundle) {
|
|
9914
10016
|
return readTar(ownBytes(Bun.gunzipSync(ownBytes(bundle))));
|
|
9915
10017
|
}
|
|
10018
|
+
function writeTar(entries) {
|
|
10019
|
+
const blocks = [];
|
|
10020
|
+
for (const entry of entries) {
|
|
10021
|
+
blocks.push(ustarHeader(entry));
|
|
10022
|
+
blocks.push(entry.bytes);
|
|
10023
|
+
const remainder = entry.bytes.byteLength % BLOCK;
|
|
10024
|
+
if (remainder !== 0)
|
|
10025
|
+
blocks.push(new Uint8Array(new ArrayBuffer(BLOCK - remainder)));
|
|
10026
|
+
}
|
|
10027
|
+
blocks.push(new Uint8Array(new ArrayBuffer(BLOCK * 2)));
|
|
10028
|
+
return concat(blocks);
|
|
10029
|
+
}
|
|
10030
|
+
function ustarHeader(entry) {
|
|
10031
|
+
const header = new Uint8Array(new ArrayBuffer(BLOCK));
|
|
10032
|
+
const encoder = new TextEncoder;
|
|
10033
|
+
const put = (offset, length, value) => {
|
|
10034
|
+
const encoded = encoder.encode(value);
|
|
10035
|
+
if (encoded.byteLength > length) {
|
|
10036
|
+
throw new Error(`Cannot pack '${entry.path}': field does not fit in a ustar header (${encoded.byteLength} > ${length})`);
|
|
10037
|
+
}
|
|
10038
|
+
header.set(encoded, offset);
|
|
10039
|
+
};
|
|
10040
|
+
if (encoder.encode(entry.path).byteLength > 100) {
|
|
10041
|
+
throw new Error(`Cannot pack '${entry.path}': path is longer than the 100 bytes a ustar header holds`);
|
|
10042
|
+
}
|
|
10043
|
+
put(0, 100, entry.path);
|
|
10044
|
+
put(100, 8, `${entry.mode.toString(8).padStart(7, "0")}\x00`);
|
|
10045
|
+
put(108, 8, "0000000\x00");
|
|
10046
|
+
put(116, 8, "0000000\x00");
|
|
10047
|
+
put(124, 12, `${entry.bytes.byteLength.toString(8).padStart(11, "0")}\x00`);
|
|
10048
|
+
put(136, 12, `${0 .toString(8).padStart(11, "0")}\x00`);
|
|
10049
|
+
put(148, 8, " ");
|
|
10050
|
+
put(156, 1, "0");
|
|
10051
|
+
put(257, 6, "ustar\x00");
|
|
10052
|
+
put(263, 2, "00");
|
|
10053
|
+
let checksum = 0;
|
|
10054
|
+
for (const byte of header)
|
|
10055
|
+
checksum += byte;
|
|
10056
|
+
put(148, 8, `${checksum.toString(8).padStart(6, "0")}\x00 `);
|
|
10057
|
+
return header;
|
|
10058
|
+
}
|
|
9916
10059
|
function readTar(tar) {
|
|
9917
10060
|
const decoder = new TextDecoder;
|
|
9918
10061
|
const entries = [];
|
|
@@ -9961,6 +10104,23 @@ function trimNul(value) {
|
|
|
9961
10104
|
const end = value.indexOf("\x00");
|
|
9962
10105
|
return end === -1 ? value : value.slice(0, end);
|
|
9963
10106
|
}
|
|
10107
|
+
function concat(chunks) {
|
|
10108
|
+
const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
|
|
10109
|
+
const merged = new Uint8Array(new ArrayBuffer(total));
|
|
10110
|
+
let offset = 0;
|
|
10111
|
+
for (const chunk of chunks) {
|
|
10112
|
+
merged.set(chunk, offset);
|
|
10113
|
+
offset += chunk.byteLength;
|
|
10114
|
+
}
|
|
10115
|
+
return merged;
|
|
10116
|
+
}
|
|
10117
|
+
|
|
10118
|
+
// src/lib/skill-version.ts
|
|
10119
|
+
var SKILL_VERSION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/;
|
|
10120
|
+
function isValidSkillVersion(value) {
|
|
10121
|
+
return typeof value === "string" && SKILL_VERSION_PATTERN.test(value) && !/^\.+$/.test(value) && !value.includes("..");
|
|
10122
|
+
}
|
|
10123
|
+
var SKILL_VERSION_RULE = "1-128 characters: letters, digits, '.', '_', '+', '-'; must start with a letter or digit; no '..'";
|
|
9964
10124
|
|
|
9965
10125
|
// src/lib/skill-bundles.ts
|
|
9966
10126
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
@@ -10025,19 +10185,30 @@ async function resolveTargetSlugs(client, options) {
|
|
|
10025
10185
|
return explicit;
|
|
10026
10186
|
}
|
|
10027
10187
|
async function pullOne(client, rawName, corpusOptions, verify) {
|
|
10188
|
+
const { name: bareName, version: requestedVersion } = splitNameVersion(rawName);
|
|
10028
10189
|
let slug;
|
|
10029
10190
|
try {
|
|
10030
|
-
slug = normalizePortableSkillName(
|
|
10191
|
+
slug = normalizePortableSkillName(bareName);
|
|
10031
10192
|
} catch (error) {
|
|
10032
10193
|
return { name: rawName, success: false, error: error.message };
|
|
10033
10194
|
}
|
|
10195
|
+
if (requestedVersion !== undefined && !isValidSkillVersion(requestedVersion)) {
|
|
10196
|
+
return { name: rawName, success: false, error: `'${requestedVersion}' is not a valid skill version (${SKILL_VERSION_RULE}).` };
|
|
10197
|
+
}
|
|
10034
10198
|
const meta = await safeMeta(client, slug);
|
|
10035
10199
|
let bundleResponse;
|
|
10036
10200
|
try {
|
|
10037
|
-
bundleResponse = await client.getBundle(slug);
|
|
10201
|
+
bundleResponse = await client.getBundle(slug, requestedVersion);
|
|
10038
10202
|
} catch (error) {
|
|
10039
10203
|
return { name: slug, success: false, error: `Failed to fetch '${slug}': ${error.message}` };
|
|
10040
10204
|
}
|
|
10205
|
+
if (requestedVersion && (!bundleResponse || bundleResponse.status === 404)) {
|
|
10206
|
+
return {
|
|
10207
|
+
name: rawName,
|
|
10208
|
+
success: false,
|
|
10209
|
+
error: `Version '${requestedVersion}' of '${slug}' is not published on the configured instance (run 'skills versions ${slug}' to list what exists).`
|
|
10210
|
+
};
|
|
10211
|
+
}
|
|
10041
10212
|
if (bundleResponse && !bundleResponse.ok) {
|
|
10042
10213
|
if (bundleResponse.status === 410) {
|
|
10043
10214
|
return reconcileTombstone(slug, corpusOptions);
|
|
@@ -10056,7 +10227,15 @@ async function pullOne(client, rawName, corpusOptions, verify) {
|
|
|
10056
10227
|
}
|
|
10057
10228
|
if (bundleResponse) {
|
|
10058
10229
|
try {
|
|
10059
|
-
|
|
10230
|
+
let expectedSha256;
|
|
10231
|
+
if (requestedVersion && client.getSkillVersion) {
|
|
10232
|
+
const recorded = await client.getSkillVersion(slug, requestedVersion);
|
|
10233
|
+
if (!recorded) {
|
|
10234
|
+
return { name: rawName, success: false, error: `Version '${requestedVersion}' of '${slug}' is not published on the configured instance (run 'skills versions ${slug}' to list what exists).` };
|
|
10235
|
+
}
|
|
10236
|
+
expectedSha256 = typeof recorded.bundleSha256 === "string" ? recorded.bundleSha256 : undefined;
|
|
10237
|
+
}
|
|
10238
|
+
return await installVerifiedBundle(slug, bundleResponse, meta, corpusOptions, verify, requestedVersion ? { version: requestedVersion, expectedSha256 } : undefined);
|
|
10060
10239
|
} catch (error) {
|
|
10061
10240
|
if (error instanceof PullSkillError) {
|
|
10062
10241
|
return { name: slug, success: false, error: error.message };
|
|
@@ -10140,23 +10319,30 @@ function reconcileTombstone(slug, corpusOptions) {
|
|
|
10140
10319
|
}
|
|
10141
10320
|
function readPullMarker(dir) {
|
|
10142
10321
|
try {
|
|
10143
|
-
return JSON.parse(
|
|
10322
|
+
return JSON.parse(readFileSync15(join17(dir, PULL_MARKER_FILE), "utf-8"));
|
|
10144
10323
|
} catch {
|
|
10145
10324
|
return null;
|
|
10146
10325
|
}
|
|
10147
10326
|
}
|
|
10148
|
-
function installVerifiedBundle(slug, response, meta, corpusOptions, verify) {
|
|
10327
|
+
function installVerifiedBundle(slug, response, meta, corpusOptions, verify, exact) {
|
|
10149
10328
|
return response.arrayBuffer().then((buffer) => {
|
|
10150
10329
|
const verified = verifyBundleResponseBytes(buffer, response, verify);
|
|
10330
|
+
if (exact?.expectedSha256 && exact.expectedSha256 !== verified.contentHash) {
|
|
10331
|
+
throw new PullSkillError(`Digest proof failed for '${slug}@${exact.version}': the registry records bundle ${exact.expectedSha256.slice(0, 12)}\u2026 but the received bytes hash to ${verified.contentHash.slice(0, 12)}\u2026. Nothing was installed.`);
|
|
10332
|
+
}
|
|
10333
|
+
const served = str(response.headers.get("X-Skill-Version"));
|
|
10334
|
+
if (exact && served && served !== exact.version) {
|
|
10335
|
+
throw new PullSkillError(`Version proof failed for '${slug}': asked for '${exact.version}', the instance served '${served}'. Nothing was installed.`);
|
|
10336
|
+
}
|
|
10151
10337
|
let entries;
|
|
10152
10338
|
try {
|
|
10153
10339
|
entries = unpackSkillBundle(verified.bytes);
|
|
10154
10340
|
} catch (error) {
|
|
10155
10341
|
throw new PullSkillError(`Bundle for '${slug}' could not be unpacked: ${error.message}`);
|
|
10156
10342
|
}
|
|
10157
|
-
const version = str(meta?.version) ?? versionFromEntries(entries) ?? "unknown";
|
|
10343
|
+
const version = exact?.version ?? served ?? str(meta?.version) ?? versionFromEntries(entries) ?? "unknown";
|
|
10158
10344
|
const sourceCommit = sourceCommitFromEntries(entries);
|
|
10159
|
-
const declaredRevision = verified.revisionId ?? meta?.revisionId;
|
|
10345
|
+
const declaredRevision = exact ? undefined : verified.revisionId ?? meta?.revisionId;
|
|
10160
10346
|
if (declaredRevision && meta?.revisionId && verified.revisionId && declaredRevision !== meta.revisionId) {
|
|
10161
10347
|
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.`);
|
|
10162
10348
|
}
|
|
@@ -10353,6 +10539,15 @@ function dedupe(values) {
|
|
|
10353
10539
|
function str(value) {
|
|
10354
10540
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
10355
10541
|
}
|
|
10542
|
+
function splitNameVersion(raw) {
|
|
10543
|
+
const trimmed = raw.trim();
|
|
10544
|
+
const at = trimmed.indexOf("@");
|
|
10545
|
+
if (at <= 0)
|
|
10546
|
+
return { name: trimmed };
|
|
10547
|
+
const name = trimmed.slice(0, at);
|
|
10548
|
+
const version = trimmed.slice(at + 1);
|
|
10549
|
+
return version ? { name, version } : { name };
|
|
10550
|
+
}
|
|
10356
10551
|
// src/lib/cli-mcp-parity.ts
|
|
10357
10552
|
var SKILLS_CLI_MCP_PARITY = [
|
|
10358
10553
|
{
|
|
@@ -10458,11 +10653,11 @@ function findSkillsParityForMcpTool(tool) {
|
|
|
10458
10653
|
}
|
|
10459
10654
|
// src/lib/registry-sync.ts
|
|
10460
10655
|
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
10461
|
-
import { dirname as dirname8, relative as
|
|
10656
|
+
import { dirname as dirname8, relative as relative4 } from "path";
|
|
10462
10657
|
// package.json
|
|
10463
10658
|
var package_default = {
|
|
10464
10659
|
name: "@hasna/skills",
|
|
10465
|
-
version: "0.
|
|
10660
|
+
version: "0.2.0",
|
|
10466
10661
|
description: "Skills library for AI coding agents",
|
|
10467
10662
|
type: "module",
|
|
10468
10663
|
bin: {
|
|
@@ -10542,7 +10737,7 @@ var package_default = {
|
|
|
10542
10737
|
author: "Hasna",
|
|
10543
10738
|
license: "Apache-2.0",
|
|
10544
10739
|
devDependencies: {
|
|
10545
|
-
"@types/bun": "
|
|
10740
|
+
"@types/bun": "1.3.14",
|
|
10546
10741
|
"@types/node": "25.2.3",
|
|
10547
10742
|
"@types/react": "^18.2.0",
|
|
10548
10743
|
"bun-types": "1.3.14",
|
|
@@ -10554,7 +10749,6 @@ var package_default = {
|
|
|
10554
10749
|
"@aws-sdk/client-ecs": "^3.1079.0",
|
|
10555
10750
|
"@aws-sdk/client-s3": "^3.1079.0",
|
|
10556
10751
|
"@hasna/events": "0.1.16",
|
|
10557
|
-
"@hasna/paths": "0.1.0",
|
|
10558
10752
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
10559
10753
|
chalk: "^5.3.0",
|
|
10560
10754
|
commander: "^12.1.0",
|
|
@@ -10594,7 +10788,7 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
10594
10788
|
const registry = [...loadRegistryProfile(profile)].sort((a, b) => a.name.localeCompare(b.name));
|
|
10595
10789
|
const skills = registry.map((skill) => {
|
|
10596
10790
|
const skillPath = getSkillPath(skill.name);
|
|
10597
|
-
const directory =
|
|
10791
|
+
const directory = relative4(process.cwd(), skillPath) || skillPath;
|
|
10598
10792
|
const validation = includeValidation ? validateSkillDirectory(skill.name, skillPath, skill) : undefined;
|
|
10599
10793
|
const docs = includeDocs ? buildDocs(skill.name) : undefined;
|
|
10600
10794
|
return {
|
|
@@ -11479,7 +11673,7 @@ function clone2(value) {
|
|
|
11479
11673
|
return JSON.parse(JSON.stringify(value));
|
|
11480
11674
|
}
|
|
11481
11675
|
// src/lib/feedback.ts
|
|
11482
|
-
import { existsSync as existsSync17, mkdirSync as mkdirSync11 } from "fs";
|
|
11676
|
+
import { appendFileSync, existsSync as existsSync17, mkdirSync as mkdirSync11 } from "fs";
|
|
11483
11677
|
import { dirname as dirname9, join as join18 } from "path";
|
|
11484
11678
|
import { Database } from "bun:sqlite";
|
|
11485
11679
|
function getFeedbackDbPath() {
|
|
@@ -11514,6 +11708,15 @@ function saveFeedback(input) {
|
|
|
11514
11708
|
if (!message)
|
|
11515
11709
|
throw new Error("Feedback message is required");
|
|
11516
11710
|
const category = input.category ?? "general";
|
|
11711
|
+
if (isApiMode()) {
|
|
11712
|
+
const path = join18(getDataDir(), "feedback.jsonl");
|
|
11713
|
+
const dir = dirname9(path);
|
|
11714
|
+
if (!existsSync17(dir))
|
|
11715
|
+
mkdirSync11(dir, { recursive: true });
|
|
11716
|
+
appendFileSync(path, JSON.stringify({ message, category, email: input.email ?? null, agent: input.agent ?? null, version: input.version ?? null, createdAt: new Date().toISOString() }) + `
|
|
11717
|
+
`);
|
|
11718
|
+
return { saved: true, category, path };
|
|
11719
|
+
}
|
|
11517
11720
|
const db = getFeedbackDb();
|
|
11518
11721
|
try {
|
|
11519
11722
|
db.run("INSERT INTO feedback (message, email, category, agent, version) VALUES (?, ?, ?, ?, ?)", [message, input.email || null, category, input.agent || null, input.version || null]);
|
|
@@ -11522,17 +11725,26 @@ function saveFeedback(input) {
|
|
|
11522
11725
|
}
|
|
11523
11726
|
return { saved: true, category, path: getFeedbackDbPath() };
|
|
11524
11727
|
}
|
|
11728
|
+
function isApiMode(env = process.env) {
|
|
11729
|
+
if (env.HASNA_SKILLS_API_URL?.trim())
|
|
11730
|
+
return true;
|
|
11731
|
+
try {
|
|
11732
|
+
return Boolean(resolveApiUrl(undefined, env));
|
|
11733
|
+
} catch {
|
|
11734
|
+
return Boolean(env.SKILLS_API_URL?.trim());
|
|
11735
|
+
}
|
|
11736
|
+
}
|
|
11525
11737
|
// src/lib/native-storage.ts
|
|
11526
11738
|
import { createHash as createHash5, createHmac as createHmac2 } from "crypto";
|
|
11527
11739
|
import {
|
|
11528
11740
|
existsSync as existsSync18,
|
|
11529
11741
|
mkdirSync as mkdirSync12,
|
|
11530
|
-
readFileSync as
|
|
11531
|
-
readdirSync as
|
|
11532
|
-
statSync as
|
|
11742
|
+
readFileSync as readFileSync16,
|
|
11743
|
+
readdirSync as readdirSync10,
|
|
11744
|
+
statSync as statSync10,
|
|
11533
11745
|
writeFileSync as writeFileSync11
|
|
11534
11746
|
} from "fs";
|
|
11535
|
-
import { dirname as dirname10, join as join19, normalize as normalize3, relative as
|
|
11747
|
+
import { dirname as dirname10, join as join19, normalize as normalize3, relative as relative5, sep as sep2 } from "path";
|
|
11536
11748
|
var SKILLS_STORAGE_TABLES = [
|
|
11537
11749
|
"skills_sync_records",
|
|
11538
11750
|
"skills_sync_cursors"
|
|
@@ -11694,8 +11906,8 @@ function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
|
|
|
11694
11906
|
const files = [];
|
|
11695
11907
|
if (existsSync18(projectStateDir)) {
|
|
11696
11908
|
for (const filePath of walkFiles2(projectStateDir)) {
|
|
11697
|
-
const bytes =
|
|
11698
|
-
const relativePath = toPosix(
|
|
11909
|
+
const bytes = readFileSync16(filePath);
|
|
11910
|
+
const relativePath = toPosix(relative5(targetDir, filePath));
|
|
11699
11911
|
files.push({
|
|
11700
11912
|
path: relativePath,
|
|
11701
11913
|
sizeBytes: bytes.byteLength,
|
|
@@ -12025,9 +12237,9 @@ function parsePositiveInteger(value) {
|
|
|
12025
12237
|
}
|
|
12026
12238
|
function walkFiles2(dir) {
|
|
12027
12239
|
const files = [];
|
|
12028
|
-
for (const entry of
|
|
12240
|
+
for (const entry of readdirSync10(dir)) {
|
|
12029
12241
|
const full = join19(dir, entry);
|
|
12030
|
-
const stats =
|
|
12242
|
+
const stats = statSync10(full);
|
|
12031
12243
|
if (stats.isDirectory())
|
|
12032
12244
|
files.push(...walkFiles2(full));
|
|
12033
12245
|
else
|
|
@@ -12108,15 +12320,15 @@ import { createHash as createHash6 } from "crypto";
|
|
|
12108
12320
|
import {
|
|
12109
12321
|
copyFileSync as copyFileSync2,
|
|
12110
12322
|
mkdirSync as mkdirSync13,
|
|
12111
|
-
readFileSync as
|
|
12112
|
-
statSync as
|
|
12323
|
+
readFileSync as readFileSync17,
|
|
12324
|
+
statSync as statSync12,
|
|
12113
12325
|
writeFileSync as writeFileSync12
|
|
12114
12326
|
} from "fs";
|
|
12115
|
-
import { dirname as dirname11, isAbsolute as isAbsolute3, relative as
|
|
12327
|
+
import { dirname as dirname11, isAbsolute as isAbsolute3, relative as relative6, resolve as resolve2, sep as sep4 } from "path";
|
|
12116
12328
|
|
|
12117
12329
|
// src/lib/portable-snapshot-filter.ts
|
|
12118
|
-
import { readdirSync as
|
|
12119
|
-
import { homedir as
|
|
12330
|
+
import { readdirSync as readdirSync11, statSync as statSync11 } from "fs";
|
|
12331
|
+
import { homedir as homedir5 } from "os";
|
|
12120
12332
|
import { join as join20, sep as sep3 } from "path";
|
|
12121
12333
|
var SYNC_HOMES = [
|
|
12122
12334
|
{ name: "skills", subClass: "skills", agent: null },
|
|
@@ -12208,7 +12420,7 @@ function isPortableWithinSkill(relativeParts) {
|
|
|
12208
12420
|
return PORTABLE_SUBDIRS.has(second);
|
|
12209
12421
|
}
|
|
12210
12422
|
function homePathFor(definition, homesRoot) {
|
|
12211
|
-
const home = homesRoot ??
|
|
12423
|
+
const home = homesRoot ?? homedir5();
|
|
12212
12424
|
if (definition.subClass === "skills" || definition.subClass === "custom") {
|
|
12213
12425
|
return join20(skillsDataRootForHome(home), definition.name);
|
|
12214
12426
|
}
|
|
@@ -12224,7 +12436,7 @@ function destinationFor(definition, stationId, relativePath) {
|
|
|
12224
12436
|
function walkEntries(absoluteRoot) {
|
|
12225
12437
|
let entries;
|
|
12226
12438
|
try {
|
|
12227
|
-
entries =
|
|
12439
|
+
entries = readdirSync11(absoluteRoot, { withFileTypes: true });
|
|
12228
12440
|
} catch {
|
|
12229
12441
|
return [];
|
|
12230
12442
|
}
|
|
@@ -12253,7 +12465,7 @@ function walkEntries(absoluteRoot) {
|
|
|
12253
12465
|
}
|
|
12254
12466
|
function isRegularFile(filePath) {
|
|
12255
12467
|
try {
|
|
12256
|
-
return
|
|
12468
|
+
return statSync11(filePath).isFile();
|
|
12257
12469
|
} catch {
|
|
12258
12470
|
return false;
|
|
12259
12471
|
}
|
|
@@ -12279,7 +12491,7 @@ function validateStationId(stationId) {
|
|
|
12279
12491
|
}
|
|
12280
12492
|
}
|
|
12281
12493
|
function sha256File(filePath) {
|
|
12282
|
-
return createHash6("sha256").update(
|
|
12494
|
+
return createHash6("sha256").update(readFileSync17(filePath)).digest("hex");
|
|
12283
12495
|
}
|
|
12284
12496
|
function scanHome(definition, homesRoot) {
|
|
12285
12497
|
const homePath = homePathFor(definition, homesRoot);
|
|
@@ -12309,7 +12521,7 @@ function scanHome(definition, homesRoot) {
|
|
|
12309
12521
|
skipped.push({ relativePath: entry.relativePath, reason: "not-regular-file" });
|
|
12310
12522
|
continue;
|
|
12311
12523
|
}
|
|
12312
|
-
const info =
|
|
12524
|
+
const info = statSync12(entry.fullPath);
|
|
12313
12525
|
portable.push({
|
|
12314
12526
|
relativePath: entry.relativePath,
|
|
12315
12527
|
fullPath: entry.fullPath,
|
|
@@ -12377,7 +12589,7 @@ function writeStationSnapshot(options) {
|
|
|
12377
12589
|
const untouched = [];
|
|
12378
12590
|
for (const plan of plans) {
|
|
12379
12591
|
const destination = resolve2(repoRoot, plan.destination);
|
|
12380
|
-
const destinationRelative =
|
|
12592
|
+
const destinationRelative = relative6(repoRoot, destination);
|
|
12381
12593
|
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep4) || isAbsolute3(destinationRelative)) {
|
|
12382
12594
|
throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
|
|
12383
12595
|
}
|
|
@@ -12434,9 +12646,9 @@ import { createHash as createHash7 } from "crypto";
|
|
|
12434
12646
|
import {
|
|
12435
12647
|
copyFileSync as copyFileSync3,
|
|
12436
12648
|
mkdirSync as mkdirSync14,
|
|
12437
|
-
readdirSync as
|
|
12438
|
-
readFileSync as
|
|
12439
|
-
statSync as
|
|
12649
|
+
readdirSync as readdirSync12,
|
|
12650
|
+
readFileSync as readFileSync18,
|
|
12651
|
+
statSync as statSync13,
|
|
12440
12652
|
writeFileSync as writeFileSync13
|
|
12441
12653
|
} from "fs";
|
|
12442
12654
|
import { dirname as dirname12, join as join21, resolve as resolve3, sep as sep5 } from "path";
|
|
@@ -12454,7 +12666,7 @@ function readSnapshotManifest(repoRoot, stationId) {
|
|
|
12454
12666
|
const manifestPath = join21(snapshotRoot, "sync-manifest.json");
|
|
12455
12667
|
let manifest;
|
|
12456
12668
|
try {
|
|
12457
|
-
manifest = JSON.parse(
|
|
12669
|
+
manifest = JSON.parse(readFileSync18(manifestPath, "utf8"));
|
|
12458
12670
|
} catch (error) {
|
|
12459
12671
|
fail("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error.message}`);
|
|
12460
12672
|
}
|
|
@@ -12481,7 +12693,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
12481
12693
|
const agentRoot = join21(snapshotRoot, "agent-homes", agent);
|
|
12482
12694
|
let identEntries;
|
|
12483
12695
|
try {
|
|
12484
|
-
identEntries =
|
|
12696
|
+
identEntries = readdirSync12(agentRoot, { withFileTypes: true });
|
|
12485
12697
|
} catch {
|
|
12486
12698
|
continue;
|
|
12487
12699
|
}
|
|
@@ -12536,7 +12748,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
12536
12748
|
});
|
|
12537
12749
|
continue;
|
|
12538
12750
|
}
|
|
12539
|
-
const info =
|
|
12751
|
+
const info = statSync13(entry.fullPath);
|
|
12540
12752
|
const manifestHash = manifestHashes.get(`${agent}${MANIFEST_HASH_KEY_SEP}${homeRelative}`) ?? null;
|
|
12541
12753
|
let verified = false;
|
|
12542
12754
|
if (manifestHash !== null) {
|
|
@@ -12590,7 +12802,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
12590
12802
|
for (const copy of copies) {
|
|
12591
12803
|
let isStub = false;
|
|
12592
12804
|
try {
|
|
12593
|
-
isStub = isPointerSkillMd(
|
|
12805
|
+
isStub = isPointerSkillMd(readFileSync18(copy.fullPath, "utf8"));
|
|
12594
12806
|
} catch {
|
|
12595
12807
|
isStub = false;
|
|
12596
12808
|
}
|