@hasna/skills 0.1.63 → 0.1.64
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 +28 -6
- package/bin/index.js +1593 -228
- package/bin/mcp.js +290 -24
- package/bin/migrate.js +229 -33
- package/bin/server.js +774 -116
- package/bin/worker.js +558 -79
- package/dist/cli/commands/registry-reconcile.d.ts +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +559 -251
- package/dist/lib/agent-sync.d.ts +26 -6
- package/dist/lib/auth-store.d.ts +37 -0
- package/dist/lib/config.d.ts +51 -0
- package/dist/lib/home-census.d.ts +4 -0
- package/dist/lib/home-migration.d.ts +8 -9
- package/dist/lib/native-storage.d.ts +1 -1
- package/dist/lib/portable-skills.d.ts +35 -6
- package/dist/lib/pull.d.ts +31 -0
- package/dist/lib/registry-reconcile.d.ts +114 -0
- package/dist/lib/registry.d.ts +7 -4
- package/dist/lib/remote-client.d.ts +118 -1
- package/dist/lib/remote-registry.d.ts +26 -0
- package/dist/lib/revision.d.ts +29 -0
- package/dist/sdk/index.js +1351 -353
- package/dist/server/app.d.ts +1 -1
- package/dist/server/config.d.ts +12 -2
- package/dist/server/rows.d.ts +2 -1
- package/dist/server/skills-api.d.ts +108 -6
- package/dist/server/sqlite-store.d.ts +20 -3
- package/dist/server/store.d.ts +31 -5
- package/dist/server/types.d.ts +98 -3
- package/dist/storage.js +7 -2
- package/migrations/postgres/0004_hosted_pins.sql +28 -0
- package/migrations/postgres/0005_revision_tombstone_registry.sql +37 -0
- package/migrations/postgres/0005_tag_projection.sql +36 -0
- package/migrations/sqlite/0004_hosted_pins.sql +21 -0
- package/migrations/sqlite/0005_revision_tombstone_registry.sql +18 -0
- package/migrations/sqlite/0005_tag_projection.sql +25 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -154,6 +154,11 @@ function normalizeConfigValue(key, value) {
|
|
|
154
154
|
}
|
|
155
155
|
var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
|
|
156
156
|
var INSTALLED_SKILLS_DIRNAME = "installed";
|
|
157
|
+
var SKILLS_CACHE_DIRNAME = "skills";
|
|
158
|
+
var LAYOUT_MIGRATION_RECORD = ".layout-migration.json";
|
|
159
|
+
function isOwnerLayoutMigrated(appDir) {
|
|
160
|
+
return existsSync(join(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD));
|
|
161
|
+
}
|
|
157
162
|
function getDataDir() {
|
|
158
163
|
const override = process.env[DATA_DIR_ENV];
|
|
159
164
|
if (override) {
|
|
@@ -435,7 +440,7 @@ var DEVELOPMENT_TOOLS_SKILLS = [
|
|
|
435
440
|
{
|
|
436
441
|
name: "monitor",
|
|
437
442
|
displayName: "Monitor",
|
|
438
|
-
description: "Operate the
|
|
443
|
+
description: "Operate the monitor MCP for machine health, processes, cron jobs, and cleanup workflows",
|
|
439
444
|
category: "Development Tools",
|
|
440
445
|
tags: ["monitoring", "mcp", "processes", "operations"]
|
|
441
446
|
},
|
|
@@ -481,6 +486,14 @@ var DEVELOPMENT_TOOLS_SKILLS = [
|
|
|
481
486
|
description: "Validate configuration files for syntax and schema compliance",
|
|
482
487
|
category: "Development Tools",
|
|
483
488
|
tags: ["config", "validation", "schema", "linting"]
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
name: "session-inject-monitor",
|
|
492
|
+
displayName: "Session Inject Monitor",
|
|
493
|
+
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",
|
|
494
|
+
category: "Development Tools",
|
|
495
|
+
tags: ["monitor", "session", "injection", "automation", "wake"],
|
|
496
|
+
kind: "instruction"
|
|
484
497
|
}
|
|
485
498
|
];
|
|
486
499
|
|
|
@@ -868,7 +881,7 @@ var DESIGN_BRANDING_SKILLS = [
|
|
|
868
881
|
displayName: "Site Analyze",
|
|
869
882
|
description: "Analyze any website's design system \u2014 detects shadcn/ui, Tailwind, extracts colors, typography, and components via Playwright + Claude Vision.",
|
|
870
883
|
category: "Design & Branding",
|
|
871
|
-
tags: ["design", "shadcn", "tailwind", "colors", "typography", "playwright", "analysis", "
|
|
884
|
+
tags: ["design", "shadcn", "tailwind", "colors", "typography", "playwright", "analysis", "styles"]
|
|
872
885
|
}
|
|
873
886
|
];
|
|
874
887
|
|
|
@@ -2234,6 +2247,9 @@ function getPortableSkillsRoot(options = {}) {
|
|
|
2234
2247
|
if (options.rootDir)
|
|
2235
2248
|
return options.rootDir;
|
|
2236
2249
|
const appDir = options.homeDir ? join5(options.homeDir, ".hasna", "skills") : getDataDir();
|
|
2250
|
+
const cache = join5(appDir, SKILLS_CACHE_DIRNAME);
|
|
2251
|
+
if (isOwnerLayoutMigrated(appDir) && safeIsDirectory(cache))
|
|
2252
|
+
return cache;
|
|
2237
2253
|
const installed = join5(appDir, INSTALLED_SKILLS_DIRNAME);
|
|
2238
2254
|
migrateLegacySkillLayout(appDir, installed);
|
|
2239
2255
|
return installed;
|
|
@@ -2454,10 +2470,7 @@ function writeCorpusSkill(input, options = {}) {
|
|
|
2454
2470
|
const skillPath = join5(root, name);
|
|
2455
2471
|
const created = !existsSync5(skillPath);
|
|
2456
2472
|
mkdirSync3(skillPath, { recursive: true });
|
|
2457
|
-
|
|
2458
|
-
`) ? input.skillMd : `${input.skillMd}
|
|
2459
|
-
`;
|
|
2460
|
-
writeFileSync3(join5(skillPath, "SKILL.md"), skillMd);
|
|
2473
|
+
writeFileSync3(join5(skillPath, "SKILL.md"), input.skillMd);
|
|
2461
2474
|
const frontmatter = parseSkillFrontmatter(input.skillMd) ?? undefined;
|
|
2462
2475
|
const kind = input.meta?.kind ?? parseSkillKind(frontmatter?.kind) ?? "instruction";
|
|
2463
2476
|
const manifest = {
|
|
@@ -2991,45 +3004,28 @@ function getAllTags() {
|
|
|
2991
3004
|
return Array.from(tagSet).sort();
|
|
2992
3005
|
}
|
|
2993
3006
|
// src/lib/installer.ts
|
|
2994
|
-
import { existsSync as
|
|
2995
|
-
import { dirname as dirname5, join as
|
|
3007
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8, rmSync as rmSync3 } from "fs";
|
|
3008
|
+
import { dirname as dirname5, join as join9 } from "path";
|
|
2996
3009
|
import { homedir as homedir3 } from "os";
|
|
2997
3010
|
import { fileURLToPath } from "url";
|
|
2998
3011
|
|
|
2999
3012
|
// src/lib/agent-sync.ts
|
|
3000
3013
|
import {
|
|
3001
3014
|
cpSync as cpSync3,
|
|
3002
|
-
existsSync as
|
|
3003
|
-
mkdirSync as
|
|
3015
|
+
existsSync as existsSync7,
|
|
3016
|
+
mkdirSync as mkdirSync4,
|
|
3004
3017
|
mkdtempSync,
|
|
3005
3018
|
readFileSync as readFileSync6,
|
|
3006
|
-
readdirSync as
|
|
3007
|
-
renameSync as
|
|
3008
|
-
rmSync as
|
|
3009
|
-
statSync as
|
|
3010
|
-
writeFileSync as
|
|
3019
|
+
readdirSync as readdirSync6,
|
|
3020
|
+
renameSync as renameSync2,
|
|
3021
|
+
rmSync as rmSync2,
|
|
3022
|
+
statSync as statSync6,
|
|
3023
|
+
writeFileSync as writeFileSync4
|
|
3011
3024
|
} from "fs";
|
|
3012
3025
|
import { homedir as homedir2 } from "os";
|
|
3013
|
-
import { basename as basename3, dirname as dirname4, join as
|
|
3014
|
-
|
|
3026
|
+
import { basename as basename3, dirname as dirname4, join as join7 } from "path";
|
|
3015
3027
|
// src/lib/home-migration.ts
|
|
3016
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync6, renameSync as renameSync2, rmSync as rmSync2, statSync as statSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
3017
|
-
import { join as join7 } from "path";
|
|
3018
|
-
var SKILLS_CACHE_DIRNAME = "skills";
|
|
3019
|
-
var LAYOUT_MIGRATION_RECORD = ".layout-migration.json";
|
|
3020
|
-
function layoutMigrationRecordPath(appDir) {
|
|
3021
|
-
return join7(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD);
|
|
3022
|
-
}
|
|
3023
|
-
function isOwnerLayoutMigrated(appDir) {
|
|
3024
|
-
return existsSync7(layoutMigrationRecordPath(appDir));
|
|
3025
|
-
}
|
|
3026
3028
|
function resolveCorpusRoot(options = {}) {
|
|
3027
|
-
if (options.rootDir)
|
|
3028
|
-
return options.rootDir;
|
|
3029
|
-
const appDir = options.homeDir ? join7(options.homeDir, ".hasna", "skills") : getDataDir();
|
|
3030
|
-
const cache = join7(appDir, SKILLS_CACHE_DIRNAME);
|
|
3031
|
-
if (isOwnerLayoutMigrated(appDir) && existsSync7(cache))
|
|
3032
|
-
return cache;
|
|
3033
3029
|
return getPortableSkillsRoot(options);
|
|
3034
3030
|
}
|
|
3035
3031
|
|
|
@@ -3052,9 +3048,9 @@ function resolveSyncAgents(arg) {
|
|
|
3052
3048
|
function agentGlobalSkillsDir(agent, homeDir = homedir2()) {
|
|
3053
3049
|
switch (agent) {
|
|
3054
3050
|
case "opencode":
|
|
3055
|
-
return
|
|
3051
|
+
return join7(homeDir, ".config", "opencode", "skills");
|
|
3056
3052
|
default:
|
|
3057
|
-
return
|
|
3053
|
+
return join7(homeDir, `.${agent}`, "skills");
|
|
3058
3054
|
}
|
|
3059
3055
|
}
|
|
3060
3056
|
function adaptSkillMdForAgent(skillMd, agent) {
|
|
@@ -3075,6 +3071,10 @@ ${lines.join(`
|
|
|
3075
3071
|
---
|
|
3076
3072
|
${body}`;
|
|
3077
3073
|
}
|
|
3074
|
+
var POINTER_MARKER_PHRASE = "This is an executable skill from the @hasna/skills catalog";
|
|
3075
|
+
function isPointerSkillMd(markdown) {
|
|
3076
|
+
return markdown.includes(POINTER_MARKER_PHRASE) && /^kind:\s*executable\b/m.test(markdown);
|
|
3077
|
+
}
|
|
3078
3078
|
function pointerSkillMd(name, description) {
|
|
3079
3079
|
const display = name.replace(/[-_]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
3080
3080
|
return [
|
|
@@ -3100,7 +3100,7 @@ function resolveSyncCorpus(options = {}) {
|
|
|
3100
3100
|
if (explicit) {
|
|
3101
3101
|
const roots = packageSourceRoots(explicit);
|
|
3102
3102
|
if (roots.length === 0) {
|
|
3103
|
-
throw new Error(`SKILLS_SOURCE '${explicit}' contains no skills: expected a corpus directory or a package root with skills
|
|
3103
|
+
throw new Error(`SKILLS_SOURCE '${explicit}' contains no skills: expected a corpus directory or a package root with skills/`);
|
|
3104
3104
|
}
|
|
3105
3105
|
return { roots, source: "source" };
|
|
3106
3106
|
}
|
|
@@ -3108,9 +3108,9 @@ function resolveSyncCorpus(options = {}) {
|
|
|
3108
3108
|
}
|
|
3109
3109
|
function packageSourceRoots(source) {
|
|
3110
3110
|
const roots = [];
|
|
3111
|
-
for (const sub of ["skills"
|
|
3112
|
-
const candidate =
|
|
3113
|
-
if (
|
|
3111
|
+
for (const sub of ["skills"]) {
|
|
3112
|
+
const candidate = join7(source, sub);
|
|
3113
|
+
if (existsSync7(candidate) && isDirectory(candidate))
|
|
3114
3114
|
roots.push(candidate);
|
|
3115
3115
|
}
|
|
3116
3116
|
if (roots.length > 0)
|
|
@@ -3120,20 +3120,20 @@ function packageSourceRoots(source) {
|
|
|
3120
3120
|
function containsSkillDirectories(path) {
|
|
3121
3121
|
let entries;
|
|
3122
3122
|
try {
|
|
3123
|
-
entries =
|
|
3123
|
+
entries = readdirSync6(path);
|
|
3124
3124
|
} catch {
|
|
3125
3125
|
return false;
|
|
3126
3126
|
}
|
|
3127
3127
|
return entries.some((entry) => {
|
|
3128
|
-
const candidate =
|
|
3128
|
+
const candidate = join7(path, entry);
|
|
3129
3129
|
if (!isDirectory(candidate))
|
|
3130
3130
|
return false;
|
|
3131
|
-
return
|
|
3131
|
+
return existsSync7(join7(candidate, "SKILL.md")) || existsSync7(join7(candidate, "skill.json")) || existsSync7(join7(candidate, "package.json"));
|
|
3132
3132
|
});
|
|
3133
3133
|
}
|
|
3134
3134
|
function isDirectory(path) {
|
|
3135
3135
|
try {
|
|
3136
|
-
return
|
|
3136
|
+
return statSync6(path).isDirectory();
|
|
3137
3137
|
} catch {
|
|
3138
3138
|
return false;
|
|
3139
3139
|
}
|
|
@@ -3167,7 +3167,7 @@ function syncSkillsToAgents(options = {}) {
|
|
|
3167
3167
|
actions.push({
|
|
3168
3168
|
skill: name,
|
|
3169
3169
|
agent,
|
|
3170
|
-
path:
|
|
3170
|
+
path: join7(agentGlobalSkillsDir(agent, homeDir), name, "SKILL.md"),
|
|
3171
3171
|
action: "skip",
|
|
3172
3172
|
reason: "not found in this machine's corpus"
|
|
3173
3173
|
});
|
|
@@ -3197,7 +3197,7 @@ function syncSkillsToAgents(options = {}) {
|
|
|
3197
3197
|
}
|
|
3198
3198
|
function writeManagedAgentSkill(params) {
|
|
3199
3199
|
const homeDir = params.homeDir ?? homedir2();
|
|
3200
|
-
const dir =
|
|
3200
|
+
const dir = join7(agentGlobalSkillsDir(params.agent, homeDir), params.skill);
|
|
3201
3201
|
const result = writeManagedSkillDir(dir, params.skillMd, {
|
|
3202
3202
|
skill: params.skill,
|
|
3203
3203
|
source: params.source,
|
|
@@ -3214,11 +3214,11 @@ function writeManagedAgentSkill(params) {
|
|
|
3214
3214
|
};
|
|
3215
3215
|
}
|
|
3216
3216
|
function writeManagedSkillDir(dir, skillMd, options) {
|
|
3217
|
-
const skillMdPath =
|
|
3218
|
-
const markerPath =
|
|
3219
|
-
const dirExists =
|
|
3220
|
-
const managed =
|
|
3221
|
-
const hasSkillMd =
|
|
3217
|
+
const skillMdPath = join7(dir, "SKILL.md");
|
|
3218
|
+
const markerPath = join7(dir, SYNC_MARKER_FILE);
|
|
3219
|
+
const dirExists = existsSync7(dir);
|
|
3220
|
+
const managed = existsSync7(markerPath);
|
|
3221
|
+
const hasSkillMd = existsSync7(skillMdPath);
|
|
3222
3222
|
if (dirExists && !managed && !hasSkillMd) {
|
|
3223
3223
|
return {
|
|
3224
3224
|
action: "skip",
|
|
@@ -3233,35 +3233,50 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
3233
3233
|
reason: "an unmanaged SKILL.md already exists here (hand-authored); pass --force to overwrite"
|
|
3234
3234
|
};
|
|
3235
3235
|
}
|
|
3236
|
+
if (dirExists && managed && hasSkillMd && !options.force && isPointerSkillMd(skillMd)) {
|
|
3237
|
+
let existingIsStub = false;
|
|
3238
|
+
try {
|
|
3239
|
+
existingIsStub = isPointerSkillMd(readFileSync6(skillMdPath, "utf-8"));
|
|
3240
|
+
} catch {
|
|
3241
|
+
existingIsStub = false;
|
|
3242
|
+
}
|
|
3243
|
+
if (!existingIsStub) {
|
|
3244
|
+
return {
|
|
3245
|
+
action: "skip",
|
|
3246
|
+
path: skillMdPath,
|
|
3247
|
+
reason: "refusing to replace a content-bearing managed home with an executable pointer stub (the corpus entry lacks kind: instruction); pass --force to overwrite"
|
|
3248
|
+
};
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3236
3251
|
const action = dirExists ? "update" : "create";
|
|
3237
3252
|
if (options.dryRun)
|
|
3238
3253
|
return { action, path: skillMdPath };
|
|
3239
3254
|
const parentDir = dirname4(dir);
|
|
3240
|
-
|
|
3241
|
-
const transactionDir = mkdtempSync(
|
|
3242
|
-
const candidateDir =
|
|
3243
|
-
const backupDir =
|
|
3244
|
-
const candidateSkillMdPath =
|
|
3245
|
-
const candidateMarkerPath =
|
|
3255
|
+
mkdirSync4(parentDir, { recursive: true });
|
|
3256
|
+
const transactionDir = mkdtempSync(join7(parentDir, `.hasna-skills-write-${basename3(dir)}-`));
|
|
3257
|
+
const candidateDir = join7(transactionDir, "candidate");
|
|
3258
|
+
const backupDir = join7(transactionDir, "backup");
|
|
3259
|
+
const candidateSkillMdPath = join7(candidateDir, "SKILL.md");
|
|
3260
|
+
const candidateMarkerPath = join7(candidateDir, SYNC_MARKER_FILE);
|
|
3246
3261
|
const marker = {
|
|
3247
3262
|
managedBy: SYNC_MARKER_MANAGED_BY,
|
|
3248
3263
|
skill: options.skill,
|
|
3249
3264
|
source: options.source ?? "corpus",
|
|
3250
3265
|
syncedAt: new Date().toISOString()
|
|
3251
3266
|
};
|
|
3252
|
-
const renameDirectory = options.renameDirectory ??
|
|
3267
|
+
const renameDirectory = options.renameDirectory ?? renameSync2;
|
|
3253
3268
|
let originalMoved = false;
|
|
3254
3269
|
let preserveTransaction = false;
|
|
3255
3270
|
try {
|
|
3256
3271
|
if (options.resourceDir) {
|
|
3257
3272
|
cpSync3(options.resourceDir, candidateDir, { recursive: true, force: true });
|
|
3258
3273
|
} else {
|
|
3259
|
-
|
|
3274
|
+
mkdirSync4(candidateDir, { recursive: true });
|
|
3260
3275
|
}
|
|
3261
|
-
|
|
3276
|
+
writeFileSync4(candidateSkillMdPath, skillMd.endsWith(`
|
|
3262
3277
|
`) ? skillMd : `${skillMd}
|
|
3263
3278
|
`);
|
|
3264
|
-
|
|
3279
|
+
writeFileSync4(candidateMarkerPath, `${JSON.stringify(marker, null, 2)}
|
|
3265
3280
|
`);
|
|
3266
3281
|
if (dirExists) {
|
|
3267
3282
|
originalMoved = true;
|
|
@@ -3269,10 +3284,10 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
3269
3284
|
}
|
|
3270
3285
|
renameDirectory(candidateDir, dir);
|
|
3271
3286
|
} catch (error) {
|
|
3272
|
-
if (originalMoved &&
|
|
3287
|
+
if (originalMoved && existsSync7(backupDir)) {
|
|
3273
3288
|
try {
|
|
3274
|
-
if (
|
|
3275
|
-
|
|
3289
|
+
if (existsSync7(dir))
|
|
3290
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
3276
3291
|
renameDirectory(backupDir, dir);
|
|
3277
3292
|
originalMoved = false;
|
|
3278
3293
|
} catch (rollbackError) {
|
|
@@ -3284,23 +3299,23 @@ function writeManagedSkillDir(dir, skillMd, options) {
|
|
|
3284
3299
|
} finally {
|
|
3285
3300
|
if (!preserveTransaction) {
|
|
3286
3301
|
try {
|
|
3287
|
-
|
|
3302
|
+
rmSync2(transactionDir, { recursive: true, force: true });
|
|
3288
3303
|
} catch {}
|
|
3289
3304
|
}
|
|
3290
3305
|
}
|
|
3291
3306
|
return { action, path: skillMdPath };
|
|
3292
3307
|
}
|
|
3293
3308
|
function removeManagedAgentSkill(skill, agent, homeDir = homedir2()) {
|
|
3294
|
-
const dir =
|
|
3295
|
-
if (!
|
|
3309
|
+
const dir = join7(agentGlobalSkillsDir(agent, homeDir), skill);
|
|
3310
|
+
if (!existsSync7(join7(dir, SYNC_MARKER_FILE)))
|
|
3296
3311
|
return false;
|
|
3297
|
-
|
|
3312
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
3298
3313
|
return true;
|
|
3299
3314
|
}
|
|
3300
3315
|
function sourceSkillMd(skillPath, name, description, kind, preferBundledDocs = false) {
|
|
3301
3316
|
if (kind === "instruction" || preferBundledDocs) {
|
|
3302
|
-
const skillMdPath =
|
|
3303
|
-
if (
|
|
3317
|
+
const skillMdPath = join7(skillPath, "SKILL.md");
|
|
3318
|
+
if (existsSync7(skillMdPath))
|
|
3304
3319
|
return readFileSync6(skillMdPath, "utf-8");
|
|
3305
3320
|
}
|
|
3306
3321
|
return pointerSkillMd(name, description);
|
|
@@ -3331,8 +3346,8 @@ function normalizeSkillName(name) {
|
|
|
3331
3346
|
}
|
|
3332
3347
|
|
|
3333
3348
|
// src/lib/project-state.ts
|
|
3334
|
-
import { existsSync as
|
|
3335
|
-
import { join as
|
|
3349
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
3350
|
+
import { join as join8 } from "path";
|
|
3336
3351
|
var VALID_PIN_SOURCES = [
|
|
3337
3352
|
"official",
|
|
3338
3353
|
"custom",
|
|
@@ -3347,14 +3362,14 @@ var SKILLS_PROJECT_DIR = ".skills";
|
|
|
3347
3362
|
var PROJECT_CONFIG_FILE = "project.json";
|
|
3348
3363
|
var DEFAULT_EXPORT_DIR = ".skills/exports";
|
|
3349
3364
|
function getProjectStateDir(targetDir = process.cwd()) {
|
|
3350
|
-
return
|
|
3365
|
+
return join8(targetDir, SKILLS_PROJECT_DIR);
|
|
3351
3366
|
}
|
|
3352
3367
|
function getProjectConfigPath(targetDir = process.cwd()) {
|
|
3353
|
-
return
|
|
3368
|
+
return join8(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
|
|
3354
3369
|
}
|
|
3355
3370
|
function loadProjectConfig(targetDir = process.cwd()) {
|
|
3356
3371
|
const path = getProjectConfigPath(targetDir);
|
|
3357
|
-
if (!
|
|
3372
|
+
if (!existsSync8(path))
|
|
3358
3373
|
return null;
|
|
3359
3374
|
try {
|
|
3360
3375
|
return normalizeProjectConfig(JSON.parse(readFileSync7(path, "utf-8")));
|
|
@@ -3378,9 +3393,9 @@ function ensureProjectConfig(targetDir = process.cwd()) {
|
|
|
3378
3393
|
}
|
|
3379
3394
|
function saveProjectConfig(config, targetDir = process.cwd()) {
|
|
3380
3395
|
const dir = getProjectStateDir(targetDir);
|
|
3381
|
-
|
|
3396
|
+
mkdirSync5(dir, { recursive: true });
|
|
3382
3397
|
const normalized = normalizeProjectConfig({ ...config, updatedAt: new Date().toISOString() });
|
|
3383
|
-
|
|
3398
|
+
writeFileSync5(getProjectConfigPath(targetDir), JSON.stringify(normalized, null, 2) + `
|
|
3384
3399
|
`);
|
|
3385
3400
|
}
|
|
3386
3401
|
function pinProjectSkill(name, details = {}, targetDir = process.cwd()) {
|
|
@@ -3475,12 +3490,12 @@ var __dirname2 = dirname5(fileURLToPath(import.meta.url));
|
|
|
3475
3490
|
function findSkillsDir() {
|
|
3476
3491
|
let dir = __dirname2;
|
|
3477
3492
|
for (let i = 0;i < 5; i++) {
|
|
3478
|
-
const candidate =
|
|
3479
|
-
if (
|
|
3493
|
+
const candidate = join9(dir, "skills");
|
|
3494
|
+
if (existsSync9(candidate) && !dir.includes(".skills"))
|
|
3480
3495
|
return candidate;
|
|
3481
3496
|
dir = dirname5(dir);
|
|
3482
3497
|
}
|
|
3483
|
-
return
|
|
3498
|
+
return join9(__dirname2, "..", "skills");
|
|
3484
3499
|
}
|
|
3485
3500
|
var SKILLS_DIR = findSkillsDir();
|
|
3486
3501
|
function getSkillPath(name) {
|
|
@@ -3488,25 +3503,25 @@ function getSkillPath(name) {
|
|
|
3488
3503
|
const portable = findPortableSkill(skillName);
|
|
3489
3504
|
if (portable)
|
|
3490
3505
|
return portable.path;
|
|
3491
|
-
const legacyCustomPath =
|
|
3492
|
-
if (
|
|
3506
|
+
const legacyCustomPath = join9(getDataDir(), "custom", skillName);
|
|
3507
|
+
if (existsSync9(legacyCustomPath))
|
|
3493
3508
|
return legacyCustomPath;
|
|
3494
3509
|
const extensionPath = findExtensionSkillPath(skillName);
|
|
3495
3510
|
if (extensionPath)
|
|
3496
3511
|
return extensionPath;
|
|
3497
|
-
return
|
|
3512
|
+
return join9(SKILLS_DIR, skillName);
|
|
3498
3513
|
}
|
|
3499
3514
|
function getCanonicalSkillName(name) {
|
|
3500
3515
|
return getSkill(name)?.name ?? resolveSkillAlias(normalizeSkillSlug(name));
|
|
3501
3516
|
}
|
|
3502
3517
|
function skillExists(name) {
|
|
3503
|
-
return
|
|
3518
|
+
return existsSync9(getSkillPath(name));
|
|
3504
3519
|
}
|
|
3505
3520
|
function installSkill(name, options = {}) {
|
|
3506
3521
|
const { targetDir = process.cwd(), overwrite = false } = options;
|
|
3507
3522
|
const canonicalName = getCanonicalSkillName(name);
|
|
3508
3523
|
const skillName = normalizeSkillName(canonicalName);
|
|
3509
|
-
if (!
|
|
3524
|
+
if (!existsSync9(getSkillPath(name))) {
|
|
3510
3525
|
const knownOfficial = Boolean(getSkill(name));
|
|
3511
3526
|
return {
|
|
3512
3527
|
skill: canonicalName,
|
|
@@ -3533,7 +3548,7 @@ function installSkill(name, options = {}) {
|
|
|
3533
3548
|
}
|
|
3534
3549
|
function installSkillSource(name, _options = {}) {
|
|
3535
3550
|
const canonicalName = getCanonicalSkillName(name);
|
|
3536
|
-
if (!
|
|
3551
|
+
if (!existsSync9(getSkillPath(name))) {
|
|
3537
3552
|
return { skill: canonicalName, success: false, error: `Skill '${name}' not found`, mode: "source" };
|
|
3538
3553
|
}
|
|
3539
3554
|
return {
|
|
@@ -3554,11 +3569,11 @@ function installSkillManifest(manifest, _options = {}) {
|
|
|
3554
3569
|
}
|
|
3555
3570
|
function createLocalSkillManifest(name, generateSkillMd) {
|
|
3556
3571
|
const sourcePath = getSkillPath(name);
|
|
3557
|
-
if (!
|
|
3572
|
+
if (!existsSync9(sourcePath))
|
|
3558
3573
|
return null;
|
|
3559
3574
|
let skillMd = "";
|
|
3560
|
-
const skillMdPath =
|
|
3561
|
-
if (
|
|
3575
|
+
const skillMdPath = join9(sourcePath, "SKILL.md");
|
|
3576
|
+
if (existsSync9(skillMdPath)) {
|
|
3562
3577
|
skillMd = readFileSync8(skillMdPath, "utf-8");
|
|
3563
3578
|
} else if (generateSkillMd) {
|
|
3564
3579
|
skillMd = generateSkillMd(name) ?? "";
|
|
@@ -3631,20 +3646,20 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
|
|
|
3631
3646
|
const base = projectDir || process.cwd();
|
|
3632
3647
|
switch (agent) {
|
|
3633
3648
|
case "pi":
|
|
3634
|
-
return scope === "project" ?
|
|
3649
|
+
return scope === "project" ? join9(base, ".pi", "skills") : join9(homedir3(), ".pi", "agent", "skills");
|
|
3635
3650
|
case "opencode":
|
|
3636
|
-
return scope === "project" ?
|
|
3651
|
+
return scope === "project" ? join9(base, ".opencode", "skills") : join9(homedir3(), ".config", "opencode", "skills");
|
|
3637
3652
|
default:
|
|
3638
|
-
return scope === "project" ?
|
|
3653
|
+
return scope === "project" ? join9(base, `.${agent}`, "skills") : join9(homedir3(), `.${agent}`, "skills");
|
|
3639
3654
|
}
|
|
3640
3655
|
}
|
|
3641
3656
|
function getAgentSkillPath(name, agent, scope = "global", projectDir) {
|
|
3642
3657
|
const skillName = normalizeSkillName(getCanonicalSkillName(name));
|
|
3643
|
-
return
|
|
3658
|
+
return join9(getAgentSkillsDir(agent, scope, projectDir), skillName);
|
|
3644
3659
|
}
|
|
3645
3660
|
function installSkillForAgent(name, options, generateSkillMd) {
|
|
3646
3661
|
const canonicalName = getCanonicalSkillName(name);
|
|
3647
|
-
if (!
|
|
3662
|
+
if (!existsSync9(getSkillPath(name))) {
|
|
3648
3663
|
return { skill: canonicalName, success: false, error: `Skill '${name}' not found` };
|
|
3649
3664
|
}
|
|
3650
3665
|
const scope = options.scope ?? "global";
|
|
@@ -3668,15 +3683,15 @@ function removeSkillForAgent(name, options) {
|
|
|
3668
3683
|
const canonicalName = getCanonicalSkillName(name);
|
|
3669
3684
|
const scope = options.scope ?? "global";
|
|
3670
3685
|
const dir = getAgentSkillPath(canonicalName, options.agent, scope, options.projectDir);
|
|
3671
|
-
if (!
|
|
3686
|
+
if (!existsSync9(join9(dir, SYNC_MARKER_FILE)))
|
|
3672
3687
|
return false;
|
|
3673
|
-
|
|
3688
|
+
rmSync3(dir, { recursive: true, force: true });
|
|
3674
3689
|
return true;
|
|
3675
3690
|
}
|
|
3676
3691
|
function resolveAgentSkillMd(name, generateSkillMd) {
|
|
3677
3692
|
const sourcePath = getSkillPath(name);
|
|
3678
|
-
const skillMdPath =
|
|
3679
|
-
if (
|
|
3693
|
+
const skillMdPath = join9(sourcePath, "SKILL.md");
|
|
3694
|
+
if (existsSync9(skillMdPath))
|
|
3680
3695
|
return readFileSync8(skillMdPath, "utf-8");
|
|
3681
3696
|
if (generateSkillMd)
|
|
3682
3697
|
return generateSkillMd(name);
|
|
@@ -3695,7 +3710,7 @@ function warnMissingDependencies(name, targetDir) {
|
|
|
3695
3710
|
}
|
|
3696
3711
|
function generateMinimalSkillMd(name) {
|
|
3697
3712
|
const sourcePath = getSkillPath(name);
|
|
3698
|
-
if (!
|
|
3713
|
+
if (!existsSync9(sourcePath))
|
|
3699
3714
|
return null;
|
|
3700
3715
|
const canonicalName = getCanonicalSkillName(name);
|
|
3701
3716
|
const meta = getSkill(canonicalName);
|
|
@@ -3710,7 +3725,7 @@ function generateMinimalSkillMd(name) {
|
|
|
3710
3725
|
"---",
|
|
3711
3726
|
""
|
|
3712
3727
|
].filter(Boolean);
|
|
3713
|
-
const fallbackDoc = readFileIfExists(
|
|
3728
|
+
const fallbackDoc = readFileIfExists(join9(sourcePath, "README.md")) || readFileIfExists(join9(sourcePath, "CLAUDE.md"));
|
|
3714
3729
|
if (fallbackDoc)
|
|
3715
3730
|
return `${frontmatter.join(`
|
|
3716
3731
|
`)}${fallbackDoc.trim()}
|
|
@@ -3729,8 +3744,8 @@ skills run ${canonicalName}
|
|
|
3729
3744
|
`;
|
|
3730
3745
|
}
|
|
3731
3746
|
function readBundledSkillVersion(name) {
|
|
3732
|
-
const pkgPath =
|
|
3733
|
-
if (!
|
|
3747
|
+
const pkgPath = join9(getSkillPath(name), "package.json");
|
|
3748
|
+
if (!existsSync9(pkgPath))
|
|
3734
3749
|
return "unknown";
|
|
3735
3750
|
try {
|
|
3736
3751
|
const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
@@ -3740,27 +3755,27 @@ function readBundledSkillVersion(name) {
|
|
|
3740
3755
|
}
|
|
3741
3756
|
}
|
|
3742
3757
|
function readFileIfExists(path) {
|
|
3743
|
-
return
|
|
3758
|
+
return existsSync9(path) ? readFileSync8(path, "utf-8") : null;
|
|
3744
3759
|
}
|
|
3745
3760
|
function loadProjectConfigCompat(targetDir) {
|
|
3746
3761
|
return loadProjectConfig(targetDir);
|
|
3747
3762
|
}
|
|
3748
3763
|
// src/lib/run-state.ts
|
|
3749
3764
|
import { createHash as createHash2, randomBytes } from "crypto";
|
|
3750
|
-
import { existsSync as
|
|
3751
|
-
import { extname, join as
|
|
3765
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync9, readdirSync as readdirSync7, statSync as statSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
3766
|
+
import { extname, join as join10, relative as relative2 } from "path";
|
|
3752
3767
|
function createSkillRun(params, targetDir = process.cwd()) {
|
|
3753
3768
|
const now = new Date;
|
|
3754
3769
|
const id = createRunId(now);
|
|
3755
3770
|
const day = now.toISOString().slice(0, 10);
|
|
3756
3771
|
const skillName = normalizeSkillName(params.skill);
|
|
3757
3772
|
const root = getProjectStateDir(targetDir);
|
|
3758
|
-
const runDir =
|
|
3759
|
-
const logsDir =
|
|
3760
|
-
const exportDir =
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3773
|
+
const runDir = join10(root, "runs", day, id);
|
|
3774
|
+
const logsDir = join10(runDir, "logs");
|
|
3775
|
+
const exportDir = join10(root, "exports", skillName, id);
|
|
3776
|
+
mkdirSync6(logsDir, { recursive: true });
|
|
3777
|
+
mkdirSync6(exportDir, { recursive: true });
|
|
3778
|
+
mkdirSync6(join10(root, "tmp"), { recursive: true });
|
|
3764
3779
|
const record = {
|
|
3765
3780
|
id,
|
|
3766
3781
|
skill: skillName,
|
|
@@ -3811,27 +3826,27 @@ function updateSkillRun(context, patch) {
|
|
|
3811
3826
|
return context.record;
|
|
3812
3827
|
}
|
|
3813
3828
|
function writeRunLogs(context, stdout = "", stderr = "") {
|
|
3814
|
-
|
|
3815
|
-
|
|
3829
|
+
writeFileSync6(join10(context.logsDir, "stdout.log"), stdout);
|
|
3830
|
+
writeFileSync6(join10(context.logsDir, "stderr.log"), stderr);
|
|
3816
3831
|
}
|
|
3817
3832
|
function appendRunEvent(context, event, data = {}) {
|
|
3818
3833
|
const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
|
|
3819
3834
|
`;
|
|
3820
|
-
const path =
|
|
3821
|
-
const previous =
|
|
3822
|
-
|
|
3835
|
+
const path = join10(context.runDir, "events.ndjson");
|
|
3836
|
+
const previous = existsSync10(path) ? readFileSync9(path, "utf-8") : "";
|
|
3837
|
+
writeFileSync6(path, previous + line);
|
|
3823
3838
|
}
|
|
3824
3839
|
function listSkillRuns(targetDir = process.cwd(), limit = 50) {
|
|
3825
|
-
const runsRoot =
|
|
3826
|
-
if (!
|
|
3840
|
+
const runsRoot = join10(getProjectStateDir(targetDir), "runs");
|
|
3841
|
+
if (!existsSync10(runsRoot))
|
|
3827
3842
|
return [];
|
|
3828
3843
|
const records = [];
|
|
3829
|
-
for (const day of
|
|
3830
|
-
const dayDir =
|
|
3831
|
-
if (!
|
|
3844
|
+
for (const day of readdirSync7(runsRoot).sort().reverse()) {
|
|
3845
|
+
const dayDir = join10(runsRoot, day);
|
|
3846
|
+
if (!statSync7(dayDir).isDirectory())
|
|
3832
3847
|
continue;
|
|
3833
|
-
for (const runId of
|
|
3834
|
-
const record = readRunRecord(
|
|
3848
|
+
for (const runId of readdirSync7(dayDir).sort().reverse()) {
|
|
3849
|
+
const record = readRunRecord(join10(dayDir, runId));
|
|
3835
3850
|
if (record)
|
|
3836
3851
|
records.push(record);
|
|
3837
3852
|
if (records.length >= limit)
|
|
@@ -3841,33 +3856,33 @@ function listSkillRuns(targetDir = process.cwd(), limit = 50) {
|
|
|
3841
3856
|
return records;
|
|
3842
3857
|
}
|
|
3843
3858
|
function findSkillRun(runId, targetDir = process.cwd()) {
|
|
3844
|
-
const runsRoot =
|
|
3845
|
-
if (!
|
|
3859
|
+
const runsRoot = join10(getProjectStateDir(targetDir), "runs");
|
|
3860
|
+
if (!existsSync10(runsRoot))
|
|
3846
3861
|
return null;
|
|
3847
|
-
for (const day of
|
|
3848
|
-
const record = readRunRecord(
|
|
3862
|
+
for (const day of readdirSync7(runsRoot)) {
|
|
3863
|
+
const record = readRunRecord(join10(runsRoot, day, runId));
|
|
3849
3864
|
if (record)
|
|
3850
3865
|
return record;
|
|
3851
3866
|
}
|
|
3852
3867
|
return null;
|
|
3853
3868
|
}
|
|
3854
3869
|
function getRunExportDir(runId, skill, targetDir = process.cwd()) {
|
|
3855
|
-
return
|
|
3870
|
+
return join10(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
|
|
3856
3871
|
}
|
|
3857
3872
|
function writeRunRecord(context) {
|
|
3858
|
-
|
|
3873
|
+
writeFileSync6(join10(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
|
|
3859
3874
|
`);
|
|
3860
3875
|
}
|
|
3861
3876
|
function writeArtifactsManifest(context, artifacts) {
|
|
3862
|
-
|
|
3877
|
+
writeFileSync6(join10(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
|
|
3863
3878
|
`);
|
|
3864
3879
|
}
|
|
3865
3880
|
function collectRunArtifacts(context) {
|
|
3866
|
-
if (!
|
|
3881
|
+
if (!existsSync10(context.exportDir))
|
|
3867
3882
|
return [];
|
|
3868
3883
|
const artifacts = [];
|
|
3869
3884
|
for (const path of walkFiles(context.exportDir)) {
|
|
3870
|
-
const stat =
|
|
3885
|
+
const stat = statSync7(path);
|
|
3871
3886
|
const bytes = readFileSync9(path);
|
|
3872
3887
|
artifacts.push({
|
|
3873
3888
|
path: toProjectRelative(context.targetDir, path),
|
|
@@ -3879,8 +3894,8 @@ function collectRunArtifacts(context) {
|
|
|
3879
3894
|
return artifacts.sort((a, b) => a.path.localeCompare(b.path));
|
|
3880
3895
|
}
|
|
3881
3896
|
function readRunRecord(runDir) {
|
|
3882
|
-
const path =
|
|
3883
|
-
if (!
|
|
3897
|
+
const path = join10(runDir, "run.json");
|
|
3898
|
+
if (!existsSync10(path))
|
|
3884
3899
|
return null;
|
|
3885
3900
|
try {
|
|
3886
3901
|
return JSON.parse(readFileSync9(path, "utf-8"));
|
|
@@ -3890,9 +3905,9 @@ function readRunRecord(runDir) {
|
|
|
3890
3905
|
}
|
|
3891
3906
|
function walkFiles(dir) {
|
|
3892
3907
|
const files = [];
|
|
3893
|
-
for (const entry of
|
|
3894
|
-
const full =
|
|
3895
|
-
if (
|
|
3908
|
+
for (const entry of readdirSync7(dir)) {
|
|
3909
|
+
const full = join10(dir, entry);
|
|
3910
|
+
if (statSync7(full).isDirectory())
|
|
3896
3911
|
files.push(...walkFiles(full));
|
|
3897
3912
|
else
|
|
3898
3913
|
files.push(full);
|
|
@@ -3937,13 +3952,13 @@ function mimeForPath(path) {
|
|
|
3937
3952
|
}
|
|
3938
3953
|
}
|
|
3939
3954
|
// src/lib/skillinfo.ts
|
|
3940
|
-
import { existsSync as
|
|
3941
|
-
import { join as
|
|
3955
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
3956
|
+
import { join as join11 } from "path";
|
|
3942
3957
|
function isInstructionSkillDir(skillPath, meta) {
|
|
3943
3958
|
if (meta?.kind === "instruction")
|
|
3944
3959
|
return true;
|
|
3945
|
-
const skillMdPath =
|
|
3946
|
-
if (!
|
|
3960
|
+
const skillMdPath = join11(skillPath, "SKILL.md");
|
|
3961
|
+
if (!existsSync11(skillMdPath))
|
|
3947
3962
|
return false;
|
|
3948
3963
|
try {
|
|
3949
3964
|
return parseSkillFrontmatter(readFileSync10(skillMdPath, "utf-8"))?.kind === "instruction";
|
|
@@ -3969,12 +3984,12 @@ var HOSTED_PROVIDER_ENV_PREFIXES = [
|
|
|
3969
3984
|
];
|
|
3970
3985
|
function getSkillDocs(name) {
|
|
3971
3986
|
const skillPath = getSkillPath(name);
|
|
3972
|
-
if (!
|
|
3987
|
+
if (!existsSync11(skillPath))
|
|
3973
3988
|
return null;
|
|
3974
3989
|
return {
|
|
3975
|
-
skillMd: readIfExists(
|
|
3976
|
-
readme: readIfExists(
|
|
3977
|
-
claudeMd: readIfExists(
|
|
3990
|
+
skillMd: readIfExists(join11(skillPath, "SKILL.md")),
|
|
3991
|
+
readme: readIfExists(join11(skillPath, "README.md")),
|
|
3992
|
+
claudeMd: readIfExists(join11(skillPath, "CLAUDE.md"))
|
|
3978
3993
|
};
|
|
3979
3994
|
}
|
|
3980
3995
|
function getSkillBestDoc(name) {
|
|
@@ -3985,11 +4000,11 @@ function getSkillBestDoc(name) {
|
|
|
3985
4000
|
}
|
|
3986
4001
|
function getSkillRequirements(name) {
|
|
3987
4002
|
const skillPath = getSkillPath(name);
|
|
3988
|
-
if (!
|
|
4003
|
+
if (!existsSync11(skillPath))
|
|
3989
4004
|
return null;
|
|
3990
4005
|
const texts = [];
|
|
3991
4006
|
for (const file of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
|
|
3992
|
-
const content = readIfExists(
|
|
4007
|
+
const content = readIfExists(join11(skillPath, file));
|
|
3993
4008
|
if (content)
|
|
3994
4009
|
texts.push(content);
|
|
3995
4010
|
}
|
|
@@ -4028,8 +4043,8 @@ function getSkillRequirements(name) {
|
|
|
4028
4043
|
const skillName = normalizeSkillName(name);
|
|
4029
4044
|
let cliCommand = `skills run ${skillName}`;
|
|
4030
4045
|
let dependencies = {};
|
|
4031
|
-
const pkgPath =
|
|
4032
|
-
if (
|
|
4046
|
+
const pkgPath = join11(skillPath, "package.json");
|
|
4047
|
+
if (existsSync11(pkgPath)) {
|
|
4033
4048
|
try {
|
|
4034
4049
|
const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
|
|
4035
4050
|
dependencies = pkg.dependencies || {};
|
|
@@ -4049,7 +4064,7 @@ async function runSkill(name, args, options = {}) {
|
|
|
4049
4064
|
const meta = getSkill(name);
|
|
4050
4065
|
const canonicalName = meta?.name ?? name;
|
|
4051
4066
|
const skillPath = getSkillPath(canonicalName);
|
|
4052
|
-
if (!
|
|
4067
|
+
if (!existsSync11(skillPath)) {
|
|
4053
4068
|
return { exitCode: 1, error: `Skill '${name}' not found` };
|
|
4054
4069
|
}
|
|
4055
4070
|
if (isInstructionSkillDir(skillPath, meta)) {
|
|
@@ -4058,8 +4073,8 @@ async function runSkill(name, args, options = {}) {
|
|
|
4058
4073
|
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'.`
|
|
4059
4074
|
};
|
|
4060
4075
|
}
|
|
4061
|
-
const pkgPath =
|
|
4062
|
-
if (!
|
|
4076
|
+
const pkgPath = join11(skillPath, "package.json");
|
|
4077
|
+
if (!existsSync11(pkgPath)) {
|
|
4063
4078
|
return { exitCode: 1, error: `No package.json in skill '${name}'` };
|
|
4064
4079
|
}
|
|
4065
4080
|
let entryPoint;
|
|
@@ -4078,12 +4093,12 @@ async function runSkill(name, args, options = {}) {
|
|
|
4078
4093
|
} catch {
|
|
4079
4094
|
return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
|
|
4080
4095
|
}
|
|
4081
|
-
const entryPath =
|
|
4082
|
-
if (!
|
|
4096
|
+
const entryPath = join11(skillPath, entryPoint);
|
|
4097
|
+
if (!existsSync11(entryPath)) {
|
|
4083
4098
|
return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
|
|
4084
4099
|
}
|
|
4085
|
-
const nodeModules =
|
|
4086
|
-
if (!
|
|
4100
|
+
const nodeModules = join11(skillPath, "node_modules");
|
|
4101
|
+
if (!existsSync11(nodeModules)) {
|
|
4087
4102
|
const install = Bun.spawn(["bun", "install", "--no-save"], {
|
|
4088
4103
|
cwd: skillPath,
|
|
4089
4104
|
stdout: "pipe",
|
|
@@ -4155,7 +4170,7 @@ function generateSkillMd(name) {
|
|
|
4155
4170
|
if (!meta)
|
|
4156
4171
|
return null;
|
|
4157
4172
|
const skillPath = getSkillPath(name);
|
|
4158
|
-
if (!
|
|
4173
|
+
if (!existsSync11(skillPath))
|
|
4159
4174
|
return null;
|
|
4160
4175
|
const frontmatter = [
|
|
4161
4176
|
"---",
|
|
@@ -4164,11 +4179,11 @@ function generateSkillMd(name) {
|
|
|
4164
4179
|
"---"
|
|
4165
4180
|
].join(`
|
|
4166
4181
|
`);
|
|
4167
|
-
const readme = readIfExists(
|
|
4168
|
-
const claudeMd = readIfExists(
|
|
4182
|
+
const readme = readIfExists(join11(skillPath, "README.md"));
|
|
4183
|
+
const claudeMd = readIfExists(join11(skillPath, "CLAUDE.md"));
|
|
4169
4184
|
let cliCommand = null;
|
|
4170
|
-
const pkgPath =
|
|
4171
|
-
if (
|
|
4185
|
+
const pkgPath = join11(skillPath, "package.json");
|
|
4186
|
+
if (existsSync11(pkgPath)) {
|
|
4172
4187
|
try {
|
|
4173
4188
|
const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
|
|
4174
4189
|
if (pkg.bin) {
|
|
@@ -4245,7 +4260,7 @@ function extractEnvVars(text) {
|
|
|
4245
4260
|
}
|
|
4246
4261
|
function readIfExists(path) {
|
|
4247
4262
|
try {
|
|
4248
|
-
if (
|
|
4263
|
+
if (existsSync11(path)) {
|
|
4249
4264
|
return readFileSync10(path, "utf-8");
|
|
4250
4265
|
}
|
|
4251
4266
|
} catch {}
|
|
@@ -8249,18 +8264,22 @@ function requireApiUrl(action = "This command", config, env) {
|
|
|
8249
8264
|
}
|
|
8250
8265
|
|
|
8251
8266
|
// src/lib/auth-store.ts
|
|
8252
|
-
import { existsSync as
|
|
8253
|
-
import { join as
|
|
8267
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync7, unlinkSync } from "fs";
|
|
8268
|
+
import { dirname as dirname6, join as join12 } from "path";
|
|
8254
8269
|
import { homedir as homedir4 } from "os";
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8270
|
+
function getAuthFilePath() {
|
|
8271
|
+
return join12(getDataDir(), "auth.json");
|
|
8272
|
+
}
|
|
8273
|
+
function legacyAuthFilePath() {
|
|
8274
|
+
return join12(process.env["HOME"] || process.env["USERPROFILE"] || homedir4(), ".skills", "auth.json");
|
|
8275
|
+
}
|
|
8258
8276
|
var cachedConfig;
|
|
8259
8277
|
function getAuthConfig() {
|
|
8260
8278
|
if (cachedConfig !== undefined)
|
|
8261
8279
|
return cachedConfig;
|
|
8262
8280
|
try {
|
|
8263
|
-
const
|
|
8281
|
+
const file = existsSync12(getAuthFilePath()) ? getAuthFilePath() : legacyAuthFilePath();
|
|
8282
|
+
const raw = readFileSync11(file, "utf-8");
|
|
8264
8283
|
const config = JSON.parse(raw);
|
|
8265
8284
|
if (!config.apiKey) {
|
|
8266
8285
|
cachedConfig = null;
|
|
@@ -9084,6 +9103,30 @@ function pickNumber(record, key) {
|
|
|
9084
9103
|
}
|
|
9085
9104
|
|
|
9086
9105
|
// src/lib/remote-client.ts
|
|
9106
|
+
class RemoteRouteUnsupportedError extends Error {
|
|
9107
|
+
path;
|
|
9108
|
+
status;
|
|
9109
|
+
instance;
|
|
9110
|
+
constructor(path, status, instance) {
|
|
9111
|
+
super(`The configured Skills instance does not support ${path} (HTTP ${status}). ` + `The instance at ${instance} predates this client feature \u2014 upgrade the server, or ` + `use a client version that matches it.`);
|
|
9112
|
+
this.path = path;
|
|
9113
|
+
this.status = status;
|
|
9114
|
+
this.instance = instance;
|
|
9115
|
+
this.name = "RemoteRouteUnsupportedError";
|
|
9116
|
+
}
|
|
9117
|
+
}
|
|
9118
|
+
|
|
9119
|
+
class RemoteRequestError extends Error {
|
|
9120
|
+
path;
|
|
9121
|
+
status;
|
|
9122
|
+
constructor(path, status, statusText) {
|
|
9123
|
+
super(`Remote request to ${path} failed: HTTP ${status}${statusText ? ` ${statusText}` : ""}`);
|
|
9124
|
+
this.path = path;
|
|
9125
|
+
this.status = status;
|
|
9126
|
+
this.name = "RemoteRequestError";
|
|
9127
|
+
}
|
|
9128
|
+
}
|
|
9129
|
+
|
|
9087
9130
|
class RemoteSkillsClient {
|
|
9088
9131
|
apiUrl;
|
|
9089
9132
|
apiKey;
|
|
@@ -9101,6 +9144,20 @@ class RemoteSkillsClient {
|
|
|
9101
9144
|
}
|
|
9102
9145
|
});
|
|
9103
9146
|
}
|
|
9147
|
+
async requestNewRoute(path, options, opts = {}) {
|
|
9148
|
+
const response = await this.request(path, options);
|
|
9149
|
+
const routePath = path.split("?")[0];
|
|
9150
|
+
if (response.status === 404 || response.status === 405) {
|
|
9151
|
+
if (response.status === 404 && opts.domainNotFoundCodes?.length && await responseBodyCarriesCode(response, opts.domainNotFoundCodes)) {
|
|
9152
|
+
return response;
|
|
9153
|
+
}
|
|
9154
|
+
throw new RemoteRouteUnsupportedError(routePath, response.status, this.apiUrl);
|
|
9155
|
+
}
|
|
9156
|
+
if (!response.ok) {
|
|
9157
|
+
throw new RemoteRequestError(routePath, response.status, response.statusText);
|
|
9158
|
+
}
|
|
9159
|
+
return response;
|
|
9160
|
+
}
|
|
9104
9161
|
async listSkills() {
|
|
9105
9162
|
const res = await this.request("/api/v1/skills");
|
|
9106
9163
|
return res.json();
|
|
@@ -9117,6 +9174,14 @@ class RemoteSkillsClient {
|
|
|
9117
9174
|
return null;
|
|
9118
9175
|
return res.json();
|
|
9119
9176
|
}
|
|
9177
|
+
async getSkillStatus(slug) {
|
|
9178
|
+
const res = await this.request(`/api/v1/skills/${encodeURIComponent(slug)}`, { method: "GET" });
|
|
9179
|
+
let body = null;
|
|
9180
|
+
try {
|
|
9181
|
+
body = await res.json();
|
|
9182
|
+
} catch {}
|
|
9183
|
+
return { status: res.status, body };
|
|
9184
|
+
}
|
|
9120
9185
|
async submitRun(slug, input, args) {
|
|
9121
9186
|
const res = await this.request(`/api/v1/runs/${slug}`, {
|
|
9122
9187
|
method: "POST",
|
|
@@ -9150,15 +9215,18 @@ class RemoteSkillsClient {
|
|
|
9150
9215
|
method: "GET"
|
|
9151
9216
|
});
|
|
9152
9217
|
}
|
|
9153
|
-
async publishSkill(manifest, bundle) {
|
|
9218
|
+
async publishSkill(manifest, bundle, ifMatch) {
|
|
9154
9219
|
const form = new FormData;
|
|
9155
9220
|
form.set("manifest", JSON.stringify(manifest));
|
|
9156
9221
|
if (bundle) {
|
|
9157
9222
|
form.set("bundle", new Blob([bundle], { type: "application/gzip" }), `${String(manifest.slug ?? "skill")}.tar.gz`);
|
|
9158
9223
|
}
|
|
9224
|
+
const headers = { Authorization: `Bearer ${this.apiKey}` };
|
|
9225
|
+
if (ifMatch)
|
|
9226
|
+
headers["If-Match"] = ifMatch;
|
|
9159
9227
|
return fetch(`${this.apiUrl}/api/v1/skills`, {
|
|
9160
9228
|
method: "POST",
|
|
9161
|
-
headers
|
|
9229
|
+
headers,
|
|
9162
9230
|
body: form
|
|
9163
9231
|
});
|
|
9164
9232
|
}
|
|
@@ -9174,6 +9242,135 @@ class RemoteSkillsClient {
|
|
|
9174
9242
|
return null;
|
|
9175
9243
|
return response;
|
|
9176
9244
|
}
|
|
9245
|
+
async listPins() {
|
|
9246
|
+
const response = await this.requestNewRoute("/api/v1/pins");
|
|
9247
|
+
return normalizePinList(await response.json());
|
|
9248
|
+
}
|
|
9249
|
+
async pin(slug, metadata) {
|
|
9250
|
+
const path = `/api/v1/pins/${encodeURIComponent(slug)}`;
|
|
9251
|
+
const response = await this.requestNewRoute(path, {
|
|
9252
|
+
method: "PUT",
|
|
9253
|
+
body: JSON.stringify({ ...metadata ? { metadata } : {} })
|
|
9254
|
+
});
|
|
9255
|
+
return normalizePin(await response.json());
|
|
9256
|
+
}
|
|
9257
|
+
async unpin(slug) {
|
|
9258
|
+
const path = `/api/v1/pins/${encodeURIComponent(slug)}`;
|
|
9259
|
+
const response = await this.requestNewRoute(path, { method: "DELETE" }, { domainNotFoundCodes: ["PIN_NOT_FOUND"] });
|
|
9260
|
+
return response.status !== 404;
|
|
9261
|
+
}
|
|
9262
|
+
async listTags() {
|
|
9263
|
+
const response = await this.requestNewRoute("/api/v1/tags");
|
|
9264
|
+
const payload = await response.json();
|
|
9265
|
+
if (!Array.isArray(payload)) {
|
|
9266
|
+
throw new Error("Remote tags payload did not match the expected contract (expected an array of tag names)");
|
|
9267
|
+
}
|
|
9268
|
+
for (const tag of payload) {
|
|
9269
|
+
if (typeof tag !== "string" || tag.trim().length === 0) {
|
|
9270
|
+
throw new Error("Remote tags payload did not match the expected contract (every element must be a non-empty tag name)");
|
|
9271
|
+
}
|
|
9272
|
+
}
|
|
9273
|
+
return payload;
|
|
9274
|
+
}
|
|
9275
|
+
async skillsByTag(tag) {
|
|
9276
|
+
const path = `/api/v1/tags/${encodeURIComponent(tag)}/skills`;
|
|
9277
|
+
const response = await this.requestNewRoute(path);
|
|
9278
|
+
return normalizeSkillSummaryList(await response.json());
|
|
9279
|
+
}
|
|
9280
|
+
async listUpdatedSince(since, options = {}) {
|
|
9281
|
+
const params = new URLSearchParams({ since });
|
|
9282
|
+
if (options.cursor)
|
|
9283
|
+
params.set("cursor", options.cursor);
|
|
9284
|
+
if (options.limit !== undefined)
|
|
9285
|
+
params.set("limit", String(options.limit));
|
|
9286
|
+
const response = await this.requestNewRoute(`/api/v1/skills/updated?${params.toString()}`);
|
|
9287
|
+
return normalizeUpdatedSincePage(await response.json());
|
|
9288
|
+
}
|
|
9289
|
+
}
|
|
9290
|
+
function requireOptionalString(record, field) {
|
|
9291
|
+
if (record[field] === undefined)
|
|
9292
|
+
return;
|
|
9293
|
+
if (typeof record[field] !== "string") {
|
|
9294
|
+
throw new Error(`Remote payload did not match the expected contract (${field} must be a string when present)`);
|
|
9295
|
+
}
|
|
9296
|
+
return record[field];
|
|
9297
|
+
}
|
|
9298
|
+
function normalizePin(entry) {
|
|
9299
|
+
if (!entry || typeof entry !== "object") {
|
|
9300
|
+
throw new Error("Remote pin payload did not match the expected contract (expected an object)");
|
|
9301
|
+
}
|
|
9302
|
+
const record = entry;
|
|
9303
|
+
const slug = typeof record.slug === "string" && record.slug.trim() ? record.slug.trim() : undefined;
|
|
9304
|
+
if (!slug) {
|
|
9305
|
+
throw new Error("Remote pin payload did not match the expected contract (missing slug)");
|
|
9306
|
+
}
|
|
9307
|
+
let metadata;
|
|
9308
|
+
if (record.metadata !== undefined) {
|
|
9309
|
+
if (!record.metadata || typeof record.metadata !== "object" || Array.isArray(record.metadata)) {
|
|
9310
|
+
throw new Error("Remote pin payload did not match the expected contract (metadata must be a JSON object when present)");
|
|
9311
|
+
}
|
|
9312
|
+
metadata = record.metadata;
|
|
9313
|
+
}
|
|
9314
|
+
const pinnedAt = requireOptionalString(record, "pinnedAt");
|
|
9315
|
+
return {
|
|
9316
|
+
slug,
|
|
9317
|
+
...pinnedAt !== undefined ? { pinnedAt } : {},
|
|
9318
|
+
...metadata ? { metadata } : {}
|
|
9319
|
+
};
|
|
9320
|
+
}
|
|
9321
|
+
function normalizePinList(payload) {
|
|
9322
|
+
if (!Array.isArray(payload)) {
|
|
9323
|
+
throw new Error("Remote pins payload did not match the expected contract (expected an array of pins)");
|
|
9324
|
+
}
|
|
9325
|
+
return payload.map(normalizePin);
|
|
9326
|
+
}
|
|
9327
|
+
function normalizeSkillSummary(entry) {
|
|
9328
|
+
if (!entry || typeof entry !== "object") {
|
|
9329
|
+
throw new Error("Remote skill payload did not match the expected contract (expected an object)");
|
|
9330
|
+
}
|
|
9331
|
+
const record = entry;
|
|
9332
|
+
const slug = typeof record.slug === "string" && record.slug.trim() ? record.slug.trim() : undefined;
|
|
9333
|
+
if (!slug) {
|
|
9334
|
+
throw new Error("Remote skill payload did not match the expected contract (missing slug)");
|
|
9335
|
+
}
|
|
9336
|
+
return {
|
|
9337
|
+
slug,
|
|
9338
|
+
...requireOptionalString(record, "name") !== undefined ? { name: requireOptionalString(record, "name") } : {},
|
|
9339
|
+
...requireOptionalString(record, "version") !== undefined ? { version: requireOptionalString(record, "version") } : {},
|
|
9340
|
+
...requireOptionalString(record, "updatedAt") !== undefined ? { updatedAt: requireOptionalString(record, "updatedAt") } : {}
|
|
9341
|
+
};
|
|
9342
|
+
}
|
|
9343
|
+
function normalizeSkillSummaryList(payload) {
|
|
9344
|
+
if (!Array.isArray(payload)) {
|
|
9345
|
+
throw new Error("Remote skills payload did not match the expected contract (expected an array of skills)");
|
|
9346
|
+
}
|
|
9347
|
+
return payload.map(normalizeSkillSummary);
|
|
9348
|
+
}
|
|
9349
|
+
async function responseBodyCarriesCode(response, codes) {
|
|
9350
|
+
try {
|
|
9351
|
+
const payload = await response.clone().json();
|
|
9352
|
+
if (!payload || typeof payload !== "object")
|
|
9353
|
+
return false;
|
|
9354
|
+
const code = payload.code;
|
|
9355
|
+
return typeof code === "string" && codes.includes(code);
|
|
9356
|
+
} catch {
|
|
9357
|
+
return false;
|
|
9358
|
+
}
|
|
9359
|
+
}
|
|
9360
|
+
function normalizeUpdatedSincePage(payload) {
|
|
9361
|
+
if (!payload || typeof payload !== "object") {
|
|
9362
|
+
throw new Error("Updated-since payload did not match the expected contract (expected an object)");
|
|
9363
|
+
}
|
|
9364
|
+
const record = payload;
|
|
9365
|
+
if (!Array.isArray(record.skills)) {
|
|
9366
|
+
throw new Error("Updated-since payload did not match the expected contract (missing skills array)");
|
|
9367
|
+
}
|
|
9368
|
+
const skills = record.skills.map(normalizeSkillSummary);
|
|
9369
|
+
const nextCursor = record.nextCursor === undefined || record.nextCursor === null ? null : record.nextCursor;
|
|
9370
|
+
if (nextCursor !== null && typeof nextCursor !== "string") {
|
|
9371
|
+
throw new Error("Updated-since payload did not match the expected contract (nextCursor must be a string or absent)");
|
|
9372
|
+
}
|
|
9373
|
+
return { skills, nextCursor };
|
|
9177
9374
|
}
|
|
9178
9375
|
function createRemoteSkillsClient() {
|
|
9179
9376
|
const apiKey = getApiKey();
|
|
@@ -9182,14 +9379,14 @@ function createRemoteSkillsClient() {
|
|
|
9182
9379
|
return new RemoteSkillsClient(apiKey);
|
|
9183
9380
|
}
|
|
9184
9381
|
// src/lib/scheduler.ts
|
|
9185
|
-
import { existsSync as
|
|
9186
|
-
import { join as
|
|
9382
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8 } from "fs";
|
|
9383
|
+
import { join as join13 } from "path";
|
|
9187
9384
|
function getSchedulesPath(targetDir = process.cwd()) {
|
|
9188
|
-
return
|
|
9385
|
+
return join13(targetDir, ".skills", "schedules.json");
|
|
9189
9386
|
}
|
|
9190
9387
|
function loadSchedules(targetDir = process.cwd()) {
|
|
9191
9388
|
const path = getSchedulesPath(targetDir);
|
|
9192
|
-
if (
|
|
9389
|
+
if (existsSync13(path)) {
|
|
9193
9390
|
try {
|
|
9194
9391
|
return JSON.parse(readFileSync12(path, "utf-8"));
|
|
9195
9392
|
} catch {}
|
|
@@ -9198,10 +9395,10 @@ function loadSchedules(targetDir = process.cwd()) {
|
|
|
9198
9395
|
}
|
|
9199
9396
|
function saveSchedules(data, targetDir = process.cwd()) {
|
|
9200
9397
|
const path = getSchedulesPath(targetDir);
|
|
9201
|
-
const dir =
|
|
9202
|
-
if (!
|
|
9203
|
-
|
|
9204
|
-
|
|
9398
|
+
const dir = join13(targetDir, ".skills");
|
|
9399
|
+
if (!existsSync13(dir))
|
|
9400
|
+
mkdirSync8(dir, { recursive: true });
|
|
9401
|
+
writeFileSync8(path, JSON.stringify(data, null, 2));
|
|
9205
9402
|
}
|
|
9206
9403
|
function validateCronField(expr, min, max, label) {
|
|
9207
9404
|
for (const part of expr.split(",")) {
|
|
@@ -9386,11 +9583,34 @@ function recordScheduleRun(id, status, targetDir) {
|
|
|
9386
9583
|
saveSchedules(data, targetDir);
|
|
9387
9584
|
}
|
|
9388
9585
|
// src/lib/pull.ts
|
|
9389
|
-
import { existsSync as
|
|
9390
|
-
import { dirname as
|
|
9586
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync2, readFileSync as readFileSync13, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
|
|
9587
|
+
import { dirname as dirname7, join as join14 } from "path";
|
|
9391
9588
|
|
|
9392
|
-
// src/lib/
|
|
9589
|
+
// src/lib/revision.ts
|
|
9393
9590
|
import { createHash as createHash3 } from "crypto";
|
|
9591
|
+
var REVISION_ID_PATTERN = /^[0-9a-f]{64}$/;
|
|
9592
|
+
function revisionIdOf(content) {
|
|
9593
|
+
const canonical = JSON.stringify({
|
|
9594
|
+
slug: content.slug,
|
|
9595
|
+
displayName: content.displayName,
|
|
9596
|
+
description: content.description,
|
|
9597
|
+
category: content.category,
|
|
9598
|
+
tags: content.tags,
|
|
9599
|
+
source: content.source,
|
|
9600
|
+
kind: content.kind,
|
|
9601
|
+
version: content.version ?? null,
|
|
9602
|
+
skillMd: content.skillMd ?? null,
|
|
9603
|
+
bundleSha256: content.bundleSha256 ?? null,
|
|
9604
|
+
bundleByteSize: content.bundleByteSize ?? null
|
|
9605
|
+
});
|
|
9606
|
+
return createHash3("sha256").update(canonical).digest("hex");
|
|
9607
|
+
}
|
|
9608
|
+
function revisionIdOfRecord(record) {
|
|
9609
|
+
return revisionIdOf(record);
|
|
9610
|
+
}
|
|
9611
|
+
|
|
9612
|
+
// src/lib/skill-bundle.ts
|
|
9613
|
+
import { createHash as createHash4 } from "crypto";
|
|
9394
9614
|
var BLOCK = 512;
|
|
9395
9615
|
var ANY_SEGMENT_EXCLUDES = new Set([
|
|
9396
9616
|
".git",
|
|
@@ -9403,6 +9623,7 @@ var ANY_SEGMENT_EXCLUDES = new Set([
|
|
|
9403
9623
|
".docker"
|
|
9404
9624
|
]);
|
|
9405
9625
|
var ROOT_EXCLUDES = new Set(["dist", "build", ".turbo"]);
|
|
9626
|
+
var TOOL_SIDECAR_FILENAMES = new Set([".hasna-skills.json"]);
|
|
9406
9627
|
var CREDENTIAL_FILENAMES = new Set([
|
|
9407
9628
|
".npmrc",
|
|
9408
9629
|
".pypirc",
|
|
@@ -9519,7 +9740,7 @@ function ownBytes(view) {
|
|
|
9519
9740
|
return out;
|
|
9520
9741
|
}
|
|
9521
9742
|
function sha256Hex(bytes) {
|
|
9522
|
-
return
|
|
9743
|
+
return createHash4("sha256").update(bytes).digest("hex");
|
|
9523
9744
|
}
|
|
9524
9745
|
function unpackSkillBundle(bundle) {
|
|
9525
9746
|
return readTar(ownBytes(Bun.gunzipSync(ownBytes(bundle))));
|
|
@@ -9598,9 +9819,9 @@ function verifyBundleSignature(bytes, signature, key) {
|
|
|
9598
9819
|
// src/lib/pull.ts
|
|
9599
9820
|
var BUNDLE_DIGEST_HEADER = "X-Skill-Bundle-Sha256";
|
|
9600
9821
|
var BUNDLE_SIGNATURE_HEADER = "X-Skill-Bundle-Signature";
|
|
9822
|
+
var BUNDLE_REVISION_ID_HEADER = "X-Skill-Revision-Id";
|
|
9823
|
+
var BUNDLE_REVISION_NUMBER_HEADER = "X-Skill-Revision-Number";
|
|
9601
9824
|
var PULL_MARKER_FILE = ".hasna-skills.json";
|
|
9602
|
-
var SKILLS_CACHE_DIRNAME2 = "skills";
|
|
9603
|
-
var LAYOUT_MIGRATION_RECORD2 = ".layout-migration.json";
|
|
9604
9825
|
|
|
9605
9826
|
class PullSkillError extends Error {
|
|
9606
9827
|
detail;
|
|
@@ -9650,6 +9871,15 @@ async function pullOne(client, rawName, corpusOptions, verify) {
|
|
|
9650
9871
|
return { name: slug, success: false, error: `Failed to fetch '${slug}': ${error.message}` };
|
|
9651
9872
|
}
|
|
9652
9873
|
if (bundleResponse && !bundleResponse.ok) {
|
|
9874
|
+
if (bundleResponse.status === 410) {
|
|
9875
|
+
return reconcileTombstone(slug, corpusOptions);
|
|
9876
|
+
}
|
|
9877
|
+
if (bundleResponse.status === 404) {
|
|
9878
|
+
const marker = readPullMarker(join14(getPortableSkillsRoot(corpusOptions), slug));
|
|
9879
|
+
if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
|
|
9880
|
+
return { name: slug, success: true, purged: true, removed: false };
|
|
9881
|
+
}
|
|
9882
|
+
}
|
|
9653
9883
|
return {
|
|
9654
9884
|
name: slug,
|
|
9655
9885
|
success: false,
|
|
@@ -9675,17 +9905,78 @@ async function pullOne(client, rawName, corpusOptions, verify) {
|
|
|
9675
9905
|
if (skillMd === null) {
|
|
9676
9906
|
return { name: slug, success: false, error: `Skill '${slug}' was not found on the configured Skills instance.` };
|
|
9677
9907
|
}
|
|
9908
|
+
if (!meta?.revisionId) {
|
|
9909
|
+
const marker = readPullMarker(join14(getPortableSkillsRoot(corpusOptions), slug));
|
|
9910
|
+
if (marker && typeof marker.revisionId === "string" && marker.revisionId) {
|
|
9911
|
+
return { name: slug, success: true, purged: true, removed: false };
|
|
9912
|
+
}
|
|
9913
|
+
}
|
|
9914
|
+
let provenRevisionId;
|
|
9915
|
+
try {
|
|
9916
|
+
provenRevisionId = meta?.revisionId ? provenRevision({ ...meta, skillMd }, slug, {}) : undefined;
|
|
9917
|
+
} catch (error) {
|
|
9918
|
+
if (error instanceof PullSkillError) {
|
|
9919
|
+
return { name: slug, success: false, error: error.message };
|
|
9920
|
+
}
|
|
9921
|
+
throw error;
|
|
9922
|
+
}
|
|
9678
9923
|
const written = writeCorpusSkill({ name: slug, skillMd, meta }, corpusOptions);
|
|
9679
|
-
writePullMarker(written.path, {
|
|
9924
|
+
writePullMarker(written.path, {
|
|
9925
|
+
skill: slug,
|
|
9926
|
+
version: written.manifest.version,
|
|
9927
|
+
...provenRevisionId ? { revisionId: provenRevisionId } : {}
|
|
9928
|
+
});
|
|
9680
9929
|
return {
|
|
9681
9930
|
name: slug,
|
|
9682
9931
|
success: true,
|
|
9683
9932
|
path: written.path,
|
|
9684
9933
|
kind: written.manifest.kind,
|
|
9685
9934
|
version: written.manifest.version,
|
|
9686
|
-
created: written.created
|
|
9935
|
+
created: written.created,
|
|
9936
|
+
...provenRevisionId ? { revisionId: provenRevisionId } : {}
|
|
9687
9937
|
};
|
|
9688
9938
|
}
|
|
9939
|
+
function provenRevision(meta, slug, bundle) {
|
|
9940
|
+
const declared = meta.revisionId;
|
|
9941
|
+
if (!declared)
|
|
9942
|
+
return "";
|
|
9943
|
+
const source = meta.publishedSource;
|
|
9944
|
+
if (typeof source !== "string" || source.length === 0) {
|
|
9945
|
+
throw new PullSkillError(`Revision proof failed for '${slug}': the instance declared revision '${declared.slice(0, 12)}\u2026' but did not serve the content fields needed to recompute it (publishedSource). Nothing was installed.`);
|
|
9946
|
+
}
|
|
9947
|
+
const recomputed = revisionIdOf({
|
|
9948
|
+
slug,
|
|
9949
|
+
displayName: meta.displayName ?? "",
|
|
9950
|
+
description: meta.description ?? "",
|
|
9951
|
+
category: meta.category ?? "",
|
|
9952
|
+
tags: meta.tags ?? [],
|
|
9953
|
+
source,
|
|
9954
|
+
kind: meta.kind ?? "instruction",
|
|
9955
|
+
...meta.version ? { version: meta.version } : {},
|
|
9956
|
+
...typeof meta.skillMd === "string" && meta.skillMd.length > 0 ? { skillMd: meta.skillMd } : {},
|
|
9957
|
+
...bundle.sha256 ? { bundleSha256: bundle.sha256 } : {},
|
|
9958
|
+
...bundle.byteSize !== undefined && bundle.byteSize !== null ? { bundleByteSize: bundle.byteSize } : {}
|
|
9959
|
+
});
|
|
9960
|
+
if (recomputed !== declared) {
|
|
9961
|
+
throw new PullSkillError(`Revision proof failed for '${slug}': the instance declared revision '${declared.slice(0, 12)}\u2026' but the served content recomputes to '${recomputed.slice(0, 12)}\u2026'. The declared revision does not identify the content that was received. Nothing was installed.`);
|
|
9962
|
+
}
|
|
9963
|
+
return declared;
|
|
9964
|
+
}
|
|
9965
|
+
function reconcileTombstone(slug, corpusOptions) {
|
|
9966
|
+
const target = join14(getPortableSkillsRoot(corpusOptions), slug);
|
|
9967
|
+
if (!existsSync14(join14(target, PULL_MARKER_FILE))) {
|
|
9968
|
+
return { name: slug, success: true, tombstoned: true, removed: false, leftInPlace: true };
|
|
9969
|
+
}
|
|
9970
|
+
rmSync4(target, { recursive: true, force: true });
|
|
9971
|
+
return { name: slug, success: true, tombstoned: true, removed: true };
|
|
9972
|
+
}
|
|
9973
|
+
function readPullMarker(dir) {
|
|
9974
|
+
try {
|
|
9975
|
+
return JSON.parse(readFileSync13(join14(dir, PULL_MARKER_FILE), "utf-8"));
|
|
9976
|
+
} catch {
|
|
9977
|
+
return null;
|
|
9978
|
+
}
|
|
9979
|
+
}
|
|
9689
9980
|
function installVerifiedBundle(slug, response, meta, corpusOptions, verify) {
|
|
9690
9981
|
return response.arrayBuffer().then((buffer) => {
|
|
9691
9982
|
const verified = verifyBundleResponseBytes(buffer, response, verify);
|
|
@@ -9697,11 +9988,17 @@ function installVerifiedBundle(slug, response, meta, corpusOptions, verify) {
|
|
|
9697
9988
|
}
|
|
9698
9989
|
const version = str(meta?.version) ?? versionFromEntries(entries) ?? "unknown";
|
|
9699
9990
|
const sourceCommit = sourceCommitFromEntries(entries);
|
|
9991
|
+
const declaredRevision = verified.revisionId ?? meta?.revisionId;
|
|
9992
|
+
if (declaredRevision && meta?.revisionId && verified.revisionId && declaredRevision !== meta.revisionId) {
|
|
9993
|
+
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.`);
|
|
9994
|
+
}
|
|
9995
|
+
const provenRevisionId = declaredRevision ? provenRevision(meta ?? {}, slug, { sha256: verified.contentHash, byteSize: verified.bytes.byteLength }) : undefined;
|
|
9700
9996
|
const installed = installBundleAtomically(slug, entries, corpusOptions, {
|
|
9701
9997
|
version,
|
|
9702
9998
|
contentHash: verified.contentHash,
|
|
9703
9999
|
...sourceCommit ? { sourceCommit } : {},
|
|
9704
|
-
...verified.signature ? { signature: verified.signature } : {}
|
|
10000
|
+
...verified.signature ? { signature: verified.signature } : {},
|
|
10001
|
+
...provenRevisionId ? { revisionId: provenRevisionId } : {}
|
|
9705
10002
|
});
|
|
9706
10003
|
return {
|
|
9707
10004
|
name: slug,
|
|
@@ -9711,6 +10008,7 @@ function installVerifiedBundle(slug, response, meta, corpusOptions, verify) {
|
|
|
9711
10008
|
version,
|
|
9712
10009
|
contentHash: verified.contentHash,
|
|
9713
10010
|
...sourceCommit ? { sourceCommit } : {},
|
|
10011
|
+
...provenRevisionId ? { revisionId: provenRevisionId } : {},
|
|
9714
10012
|
created: installed.created
|
|
9715
10013
|
};
|
|
9716
10014
|
});
|
|
@@ -9761,11 +10059,20 @@ function sourceCommitFromEntries(entries) {
|
|
|
9761
10059
|
function verifyBundleResponseBytes(buffer, response, verify = {}) {
|
|
9762
10060
|
const serverHash = response.headers.get(BUNDLE_DIGEST_HEADER);
|
|
9763
10061
|
const signature = response.headers.get(BUNDLE_SIGNATURE_HEADER);
|
|
10062
|
+
const revisionId = response.headers.get(BUNDLE_REVISION_ID_HEADER);
|
|
10063
|
+
const revisionNumberRaw = response.headers.get(BUNDLE_REVISION_NUMBER_HEADER);
|
|
9764
10064
|
const bytes = new Uint8Array(buffer.slice(0));
|
|
9765
10065
|
const contentHash = sha256Hex(bytes);
|
|
9766
10066
|
if (serverHash && serverHash.toLowerCase() !== contentHash) {
|
|
9767
10067
|
throw new PullSkillError(`Bundle digest mismatch: the instance declared ${serverHash} but the received bundle hashes to ${contentHash}.`, ["The bundle was tampered with or truncated in transit. Nothing was installed."]);
|
|
9768
10068
|
}
|
|
10069
|
+
if (revisionId && !REVISION_ID_PATTERN.test(revisionId)) {
|
|
10070
|
+
throw new PullSkillError(`Malformed revision id: the instance declared '${revisionId}', which is not a 64-character lowercase hex sha-256.`, ["The revision headers were tampered with or the instance is broken. Nothing was installed."]);
|
|
10071
|
+
}
|
|
10072
|
+
const revisionNumber = revisionNumberRaw === null ? undefined : Number(revisionNumberRaw);
|
|
10073
|
+
if (revisionNumberRaw !== null && (!Number.isInteger(revisionNumber) || revisionNumber < 0)) {
|
|
10074
|
+
throw new PullSkillError(`Malformed revision number: the instance declared '${revisionNumberRaw}', which is not a non-negative integer.`, ["The revision headers were tampered with or the instance is broken. Nothing was installed."]);
|
|
10075
|
+
}
|
|
9769
10076
|
if (signature) {
|
|
9770
10077
|
const key = verify.signingKey;
|
|
9771
10078
|
if (!key) {
|
|
@@ -9778,43 +10085,46 @@ function verifyBundleResponseBytes(buffer, response, verify = {}) {
|
|
|
9778
10085
|
bytes,
|
|
9779
10086
|
contentHash,
|
|
9780
10087
|
...serverHash ? { serverHash } : {},
|
|
9781
|
-
...signature ? { signature } : {}
|
|
10088
|
+
...signature ? { signature } : {},
|
|
10089
|
+
...revisionId ? { revisionId } : {},
|
|
10090
|
+
...revisionNumberRaw === null || revisionNumber === undefined ? {} : { revisionNumber }
|
|
9782
10091
|
};
|
|
9783
10092
|
}
|
|
9784
10093
|
function installBundleAtomically(name, entries, options = {}, marker = {}) {
|
|
9785
10094
|
const root = getPortableSkillsRoot(options);
|
|
9786
|
-
|
|
9787
|
-
const target =
|
|
9788
|
-
const created = !
|
|
9789
|
-
const staging = mkdtempSync2(
|
|
10095
|
+
mkdirSync9(root, { recursive: true });
|
|
10096
|
+
const target = join14(root, name);
|
|
10097
|
+
const created = !existsSync14(target);
|
|
10098
|
+
const staging = mkdtempSync2(join14(root, `.pull-${name}-`));
|
|
9790
10099
|
let moved = false;
|
|
9791
10100
|
let backup = null;
|
|
9792
10101
|
try {
|
|
9793
10102
|
for (const entry of entries) {
|
|
9794
|
-
const destination =
|
|
9795
|
-
|
|
9796
|
-
|
|
10103
|
+
const destination = join14(staging, entry.path);
|
|
10104
|
+
mkdirSync9(dirname7(destination), { recursive: true });
|
|
10105
|
+
writeFileSync9(destination, entry.bytes, { mode: entry.mode });
|
|
9797
10106
|
}
|
|
9798
10107
|
writePullMarker(staging, {
|
|
9799
10108
|
skill: name,
|
|
9800
10109
|
...marker.version ? { version: marker.version } : {},
|
|
9801
10110
|
...marker.contentHash ? { contentHash: marker.contentHash } : {},
|
|
9802
10111
|
...marker.sourceCommit ? { sourceCommit: marker.sourceCommit } : {},
|
|
9803
|
-
...marker.signature ? { signature: marker.signature } : {}
|
|
10112
|
+
...marker.signature ? { signature: marker.signature } : {},
|
|
10113
|
+
...marker.revisionId ? { revisionId: marker.revisionId } : {}
|
|
9804
10114
|
});
|
|
9805
|
-
if (
|
|
9806
|
-
backup = mkdtempSync2(
|
|
9807
|
-
|
|
10115
|
+
if (existsSync14(target)) {
|
|
10116
|
+
backup = mkdtempSync2(join14(root, `.pull-backup-${name}-`));
|
|
10117
|
+
renameSync3(target, join14(backup, name));
|
|
9808
10118
|
moved = true;
|
|
9809
10119
|
}
|
|
9810
|
-
|
|
10120
|
+
renameSync3(staging, target);
|
|
9811
10121
|
if (moved && backup)
|
|
9812
|
-
|
|
10122
|
+
rmSync4(backup, { recursive: true, force: true });
|
|
9813
10123
|
} catch (error) {
|
|
9814
|
-
|
|
9815
|
-
if (moved && backup &&
|
|
10124
|
+
rmSync4(staging, { recursive: true, force: true });
|
|
10125
|
+
if (moved && backup && existsSync14(join14(backup, name))) {
|
|
9816
10126
|
try {
|
|
9817
|
-
|
|
10127
|
+
renameSync3(join14(backup, name), target);
|
|
9818
10128
|
} catch {}
|
|
9819
10129
|
}
|
|
9820
10130
|
throw error;
|
|
@@ -9825,14 +10135,15 @@ function writePullMarker(dir, record) {
|
|
|
9825
10135
|
const marker = {
|
|
9826
10136
|
managedBy: "@hasna/skills",
|
|
9827
10137
|
skill: record.skill,
|
|
9828
|
-
source: "pull",
|
|
10138
|
+
source: record.source ?? "pull",
|
|
9829
10139
|
...record.version ? { version: record.version } : {},
|
|
9830
10140
|
...record.contentHash ? { contentHash: record.contentHash } : {},
|
|
9831
10141
|
...record.sourceCommit ? { sourceCommit: record.sourceCommit } : {},
|
|
9832
10142
|
...record.signature ? { signature: record.signature } : {},
|
|
10143
|
+
...record.revisionId ? { revisionId: record.revisionId } : {},
|
|
9833
10144
|
syncedAt: new Date().toISOString()
|
|
9834
10145
|
};
|
|
9835
|
-
|
|
10146
|
+
writeFileSync9(join14(dir, PULL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
|
|
9836
10147
|
`);
|
|
9837
10148
|
}
|
|
9838
10149
|
async function safeMeta(client, slug) {
|
|
@@ -9853,20 +10164,14 @@ async function safeMeta(client, slug) {
|
|
|
9853
10164
|
...str(record.category) ? { category: str(record.category) } : {},
|
|
9854
10165
|
...tags && tags.length ? { tags } : {},
|
|
9855
10166
|
...str(record.version) ? { version: str(record.version) } : {},
|
|
9856
|
-
...kind ? { kind } : {}
|
|
10167
|
+
...kind ? { kind } : {},
|
|
10168
|
+
...REVISION_ID_PATTERN.test(str(record.revisionId) ?? "") ? { revisionId: str(record.revisionId) } : {},
|
|
10169
|
+
...typeof record.skillMd === "string" && record.skillMd.length > 0 ? { skillMd: record.skillMd } : {},
|
|
10170
|
+
...str(record.publishedSource) ? { publishedSource: str(record.publishedSource) } : {}
|
|
9857
10171
|
};
|
|
9858
10172
|
}
|
|
9859
10173
|
function pickCorpusOptions(options) {
|
|
9860
|
-
return { rootDir:
|
|
9861
|
-
}
|
|
9862
|
-
function resolvePullCorpusRoot(options) {
|
|
9863
|
-
if (options.rootDir)
|
|
9864
|
-
return options.rootDir;
|
|
9865
|
-
const appDir = options.homeDir ? join15(options.homeDir, ".hasna", "skills") : getDataDir();
|
|
9866
|
-
const cache = join15(appDir, SKILLS_CACHE_DIRNAME2);
|
|
9867
|
-
if (existsSync15(join15(cache, LAYOUT_MIGRATION_RECORD2)) && existsSync15(cache))
|
|
9868
|
-
return cache;
|
|
9869
|
-
return getPortableSkillsRoot(options);
|
|
10174
|
+
return { rootDir: resolveCorpusRoot(options) };
|
|
9870
10175
|
}
|
|
9871
10176
|
function extractSlug(entry) {
|
|
9872
10177
|
if (!entry || typeof entry !== "object")
|
|
@@ -9984,12 +10289,12 @@ function findSkillsParityForMcpTool(tool) {
|
|
|
9984
10289
|
return SKILLS_CLI_MCP_PARITY.find((entry) => entry.mcpTools.includes(tool));
|
|
9985
10290
|
}
|
|
9986
10291
|
// src/lib/registry-sync.ts
|
|
9987
|
-
import { mkdirSync as
|
|
9988
|
-
import { dirname as
|
|
10292
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
10293
|
+
import { dirname as dirname8, relative as relative3 } from "path";
|
|
9989
10294
|
// package.json
|
|
9990
10295
|
var package_default = {
|
|
9991
10296
|
name: "@hasna/skills",
|
|
9992
|
-
version: "0.1.
|
|
10297
|
+
version: "0.1.64",
|
|
9993
10298
|
description: "Skills library for AI coding agents",
|
|
9994
10299
|
type: "module",
|
|
9995
10300
|
bin: {
|
|
@@ -10063,6 +10368,7 @@ var package_default = {
|
|
|
10063
10368
|
license: "Apache-2.0",
|
|
10064
10369
|
devDependencies: {
|
|
10065
10370
|
"@types/bun": "latest",
|
|
10371
|
+
"@types/node": "25.2.3",
|
|
10066
10372
|
"@types/react": "^18.2.0",
|
|
10067
10373
|
"bun-types": "1.3.14",
|
|
10068
10374
|
"react-devtools-core": "^7.0.1",
|
|
@@ -10071,7 +10377,7 @@ var package_default = {
|
|
|
10071
10377
|
dependencies: {
|
|
10072
10378
|
"@aws-sdk/client-ecs": "^3.1079.0",
|
|
10073
10379
|
"@aws-sdk/client-s3": "^3.1079.0",
|
|
10074
|
-
"@hasna/events": "0.1.
|
|
10380
|
+
"@hasna/events": "0.1.16",
|
|
10075
10381
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
10076
10382
|
chalk: "^5.3.0",
|
|
10077
10383
|
commander: "^12.1.0",
|
|
@@ -10156,8 +10462,8 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
10156
10462
|
};
|
|
10157
10463
|
}
|
|
10158
10464
|
function writeRegistrySyncArtifact(path, artifact) {
|
|
10159
|
-
|
|
10160
|
-
|
|
10465
|
+
mkdirSync10(dirname8(path), { recursive: true });
|
|
10466
|
+
writeFileSync10(path, `${JSON.stringify(artifact, null, 2)}
|
|
10161
10467
|
`);
|
|
10162
10468
|
}
|
|
10163
10469
|
function buildDocs(name) {
|
|
@@ -10996,17 +11302,17 @@ function clone2(value) {
|
|
|
10996
11302
|
return JSON.parse(JSON.stringify(value));
|
|
10997
11303
|
}
|
|
10998
11304
|
// src/lib/feedback.ts
|
|
10999
|
-
import { existsSync as
|
|
11000
|
-
import { dirname as
|
|
11305
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync11 } from "fs";
|
|
11306
|
+
import { dirname as dirname9, join as join15 } from "path";
|
|
11001
11307
|
import { Database } from "bun:sqlite";
|
|
11002
11308
|
function getFeedbackDbPath() {
|
|
11003
|
-
return
|
|
11309
|
+
return join15(getDataDir(), "skills.db");
|
|
11004
11310
|
}
|
|
11005
11311
|
function getFeedbackDb() {
|
|
11006
11312
|
const dbPath = getFeedbackDbPath();
|
|
11007
|
-
const dir =
|
|
11008
|
-
if (!
|
|
11009
|
-
|
|
11313
|
+
const dir = dirname9(dbPath);
|
|
11314
|
+
if (!existsSync15(dir))
|
|
11315
|
+
mkdirSync11(dir, { recursive: true });
|
|
11010
11316
|
const db = new Database(dbPath);
|
|
11011
11317
|
db.exec("PRAGMA journal_mode = WAL");
|
|
11012
11318
|
db.exec([
|
|
@@ -11040,16 +11346,16 @@ function saveFeedback(input) {
|
|
|
11040
11346
|
return { saved: true, category, path: getFeedbackDbPath() };
|
|
11041
11347
|
}
|
|
11042
11348
|
// src/lib/native-storage.ts
|
|
11043
|
-
import { createHash as
|
|
11349
|
+
import { createHash as createHash5, createHmac as createHmac2 } from "crypto";
|
|
11044
11350
|
import {
|
|
11045
|
-
existsSync as
|
|
11046
|
-
mkdirSync as
|
|
11047
|
-
readFileSync as
|
|
11048
|
-
readdirSync as
|
|
11049
|
-
statSync as
|
|
11050
|
-
writeFileSync as
|
|
11351
|
+
existsSync as existsSync16,
|
|
11352
|
+
mkdirSync as mkdirSync12,
|
|
11353
|
+
readFileSync as readFileSync14,
|
|
11354
|
+
readdirSync as readdirSync8,
|
|
11355
|
+
statSync as statSync8,
|
|
11356
|
+
writeFileSync as writeFileSync11
|
|
11051
11357
|
} from "fs";
|
|
11052
|
-
import { dirname as
|
|
11358
|
+
import { dirname as dirname10, join as join16, normalize as normalize3, relative as relative4, sep as sep2 } from "path";
|
|
11053
11359
|
var SKILLS_STORAGE_TABLES = [
|
|
11054
11360
|
"skills_sync_records",
|
|
11055
11361
|
"skills_sync_cursors"
|
|
@@ -11128,7 +11434,7 @@ function getSkillsNativeStorageStatus(options = {}) {
|
|
|
11128
11434
|
const s3BucketEnv = readStorageEnv(env, "s3Bucket");
|
|
11129
11435
|
const targetDir = options.targetDir ?? process.cwd();
|
|
11130
11436
|
return {
|
|
11131
|
-
package: "
|
|
11437
|
+
package: "skills",
|
|
11132
11438
|
tables: [...SKILLS_STORAGE_TABLES],
|
|
11133
11439
|
env: {
|
|
11134
11440
|
databaseUrl: SKILLS_NATIVE_STORAGE_ENV.databaseUrl,
|
|
@@ -11137,7 +11443,7 @@ function getSkillsNativeStorageStatus(options = {}) {
|
|
|
11137
11443
|
local: {
|
|
11138
11444
|
dataDir: getDataDir(),
|
|
11139
11445
|
projectStateDir: getProjectStateDir(targetDir),
|
|
11140
|
-
feedbackDbPath:
|
|
11446
|
+
feedbackDbPath: join16(getDataDir(), "skills.db")
|
|
11141
11447
|
},
|
|
11142
11448
|
remote: {
|
|
11143
11449
|
databaseConfigured: Boolean(config.databaseUrl),
|
|
@@ -11160,14 +11466,14 @@ function getStorageStatus(options = {}) {
|
|
|
11160
11466
|
function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
|
|
11161
11467
|
const projectStateDir = getProjectStateDir(targetDir);
|
|
11162
11468
|
const files = [];
|
|
11163
|
-
if (
|
|
11469
|
+
if (existsSync16(projectStateDir)) {
|
|
11164
11470
|
for (const filePath of walkFiles2(projectStateDir)) {
|
|
11165
|
-
const bytes =
|
|
11471
|
+
const bytes = readFileSync14(filePath);
|
|
11166
11472
|
const relativePath = toPosix(relative4(targetDir, filePath));
|
|
11167
11473
|
files.push({
|
|
11168
11474
|
path: relativePath,
|
|
11169
11475
|
sizeBytes: bytes.byteLength,
|
|
11170
|
-
sha256:
|
|
11476
|
+
sha256: createHash5("sha256").update(bytes).digest("hex"),
|
|
11171
11477
|
...options.includeFileContents ? { contentBase64: Buffer.from(bytes).toString("base64") } : {}
|
|
11172
11478
|
});
|
|
11173
11479
|
}
|
|
@@ -11187,17 +11493,17 @@ function importSkillsLocalSnapshot(snapshot, targetDir = process.cwd(), options
|
|
|
11187
11493
|
continue;
|
|
11188
11494
|
}
|
|
11189
11495
|
const absolutePath = resolveSnapshotPath(targetDir, file.path);
|
|
11190
|
-
if (
|
|
11496
|
+
if (existsSync16(absolutePath) && !options.overwrite) {
|
|
11191
11497
|
skipped += 1;
|
|
11192
11498
|
continue;
|
|
11193
11499
|
}
|
|
11194
11500
|
const bytes = Buffer.from(file.contentBase64, "base64");
|
|
11195
|
-
const hash =
|
|
11501
|
+
const hash = createHash5("sha256").update(bytes).digest("hex");
|
|
11196
11502
|
if (hash !== file.sha256) {
|
|
11197
11503
|
throw new Error(`Snapshot file checksum mismatch: ${file.path}`);
|
|
11198
11504
|
}
|
|
11199
|
-
|
|
11200
|
-
|
|
11505
|
+
mkdirSync12(dirname10(absolutePath), { recursive: true });
|
|
11506
|
+
writeFileSync11(absolutePath, bytes);
|
|
11201
11507
|
written += 1;
|
|
11202
11508
|
}
|
|
11203
11509
|
return { written, skipped };
|
|
@@ -11300,7 +11606,7 @@ function createSkillsSnapshotSyncRecord(snapshot, options = {}) {
|
|
|
11300
11606
|
kind: "local-snapshot",
|
|
11301
11607
|
id: options.id ?? "project-state",
|
|
11302
11608
|
updatedAt: snapshot.exportedAt,
|
|
11303
|
-
source: options.source ?? "
|
|
11609
|
+
source: options.source ?? "skills",
|
|
11304
11610
|
payload: snapshot
|
|
11305
11611
|
};
|
|
11306
11612
|
}
|
|
@@ -11493,9 +11799,9 @@ function parsePositiveInteger(value) {
|
|
|
11493
11799
|
}
|
|
11494
11800
|
function walkFiles2(dir) {
|
|
11495
11801
|
const files = [];
|
|
11496
|
-
for (const entry of
|
|
11497
|
-
const full =
|
|
11498
|
-
const stats =
|
|
11802
|
+
for (const entry of readdirSync8(dir)) {
|
|
11803
|
+
const full = join16(dir, entry);
|
|
11804
|
+
const stats = statSync8(full);
|
|
11499
11805
|
if (stats.isDirectory())
|
|
11500
11806
|
files.push(...walkFiles2(full));
|
|
11501
11807
|
else
|
|
@@ -11511,7 +11817,7 @@ function resolveSnapshotPath(targetDir, snapshotPath) {
|
|
|
11511
11817
|
if (!toPosix(normalizedPath).startsWith(".skills/")) {
|
|
11512
11818
|
throw new Error(`Snapshot path must stay inside .skills: ${snapshotPath}`);
|
|
11513
11819
|
}
|
|
11514
|
-
return
|
|
11820
|
+
return join16(targetDir, normalizedPath);
|
|
11515
11821
|
}
|
|
11516
11822
|
function normalizeS3Prefix(prefix) {
|
|
11517
11823
|
return (prefix ?? "").trim().replace(/^\/+|\/+$/g, "");
|
|
@@ -11524,7 +11830,7 @@ function toIsoString(value) {
|
|
|
11524
11830
|
return Number.isNaN(date.getTime()) ? value : date.toISOString();
|
|
11525
11831
|
}
|
|
11526
11832
|
function sha256Hex2(value) {
|
|
11527
|
-
return
|
|
11833
|
+
return createHash5("sha256").update(value).digest("hex");
|
|
11528
11834
|
}
|
|
11529
11835
|
function normalizeHeaders(headers) {
|
|
11530
11836
|
const result = {};
|
|
@@ -11747,6 +12053,8 @@ export {
|
|
|
11747
12053
|
SKILLS_CLI_MCP_PARITY,
|
|
11748
12054
|
SKILLS,
|
|
11749
12055
|
RemoteSkillsClient,
|
|
12056
|
+
RemoteRouteUnsupportedError,
|
|
12057
|
+
RemoteRequestError,
|
|
11750
12058
|
REMOTE_SKILL_RUN_CONTRACT_VERSION,
|
|
11751
12059
|
PullSkillError,
|
|
11752
12060
|
PROJECT_CONFIG_FILE,
|