skilld 0.15.2 → 0.15.4
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 +1 -0
- package/dist/_chunks/{detect-imports.mjs → agent.mjs} +42 -6
- package/dist/_chunks/agent.mjs.map +1 -0
- package/dist/_chunks/{storage.mjs → cache.mjs} +81 -1
- package/dist/_chunks/cache.mjs.map +1 -0
- package/dist/_chunks/cache2.mjs +6 -0
- package/dist/_chunks/config.mjs +23 -0
- package/dist/_chunks/config.mjs.map +1 -1
- package/dist/_chunks/config2.mjs +12 -0
- package/dist/_chunks/{embedding-cache2.mjs → embedding-cache.mjs} +1 -1
- package/dist/_chunks/embedding-cache.mjs.map +1 -0
- package/dist/_chunks/formatting.mjs +86 -0
- package/dist/_chunks/formatting.mjs.map +1 -0
- package/dist/_chunks/{version.d.mts → index.d.mts} +1 -1
- package/dist/_chunks/index.d.mts.map +1 -0
- package/dist/_chunks/{utils.d.mts → index2.d.mts} +1 -1
- package/dist/_chunks/index2.d.mts.map +1 -0
- package/dist/_chunks/install.mjs +15 -0
- package/dist/_chunks/list.mjs +13 -0
- package/dist/_chunks/markdown.mjs +7 -0
- package/dist/_chunks/markdown.mjs.map +1 -1
- package/dist/_chunks/{pool2.mjs → pool.mjs} +1 -1
- package/dist/_chunks/pool.mjs.map +1 -0
- package/dist/_chunks/prompts.mjs +232 -0
- package/dist/_chunks/prompts.mjs.map +1 -1
- package/dist/_chunks/remove.mjs +12 -0
- package/dist/_chunks/sanitize.mjs +71 -0
- package/dist/_chunks/sanitize.mjs.map +1 -1
- package/dist/_chunks/search-interactive.mjs +14 -0
- package/dist/_chunks/search-interactive2.mjs +236 -0
- package/dist/_chunks/search-interactive2.mjs.map +1 -0
- package/dist/_chunks/search.mjs +171 -0
- package/dist/_chunks/search.mjs.map +1 -0
- package/dist/_chunks/search2.mjs +13 -0
- package/dist/_chunks/shared.mjs +4 -0
- package/dist/_chunks/shared.mjs.map +1 -1
- package/dist/_chunks/skills.mjs +552 -0
- package/dist/_chunks/skills.mjs.map +1 -0
- package/dist/_chunks/{npm.mjs → sources.mjs} +401 -4
- package/dist/_chunks/sources.mjs.map +1 -0
- package/dist/_chunks/status.mjs +13 -0
- package/dist/_chunks/sync.mjs +2026 -0
- package/dist/_chunks/sync.mjs.map +1 -0
- package/dist/_chunks/sync2.mjs +14 -0
- package/dist/_chunks/uninstall.mjs +15 -0
- package/dist/_chunks/validate.mjs +3 -0
- package/dist/_chunks/validate.mjs.map +1 -1
- package/dist/_chunks/yaml.mjs +19 -0
- package/dist/_chunks/yaml.mjs.map +1 -1
- package/dist/agent/index.d.mts +1 -1
- package/dist/agent/index.mjs +4 -3
- package/dist/cache/index.d.mts +2 -2
- package/dist/cache/index.mjs +2 -1
- package/dist/cli.mjs +173 -3082
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +2 -3
- package/dist/index.mjs +4 -4
- package/dist/retriv/index.d.mts.map +1 -1
- package/dist/retriv/index.mjs +26 -5
- package/dist/retriv/index.mjs.map +1 -1
- package/dist/retriv/worker.mjs +3 -3
- package/dist/sources/index.d.mts +2 -2
- package/dist/sources/index.mjs +2 -1
- package/dist/types.d.mts +2 -3
- package/package.json +10 -10
- package/dist/_chunks/detect-imports.mjs.map +0 -1
- package/dist/_chunks/embedding-cache2.mjs.map +0 -1
- package/dist/_chunks/npm.mjs.map +0 -1
- package/dist/_chunks/pool2.mjs.map +0 -1
- package/dist/_chunks/storage.mjs.map +0 -1
- package/dist/_chunks/utils.d.mts.map +0 -1
- package/dist/_chunks/version.d.mts.map +0 -1
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
import { n as yamlParseKV, r as yamlUnescape, t as yamlEscape } from "./yaml.mjs";
|
|
2
|
+
import { i as parseFrontmatter } from "./markdown.mjs";
|
|
3
|
+
import { a as semverGt, n as getSharedSkillsDir } from "./shared.mjs";
|
|
4
|
+
import { s as readLocalDependencies } from "./sources.mjs";
|
|
5
|
+
import { _ as targets, g as getAgentVersion, h as detectTargetAgent, m as detectInstalledAgents } from "./prompts.mjs";
|
|
6
|
+
import { a as getModelName } from "./agent.mjs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join } from "pathe";
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import * as p from "@clack/prompts";
|
|
12
|
+
import { detectCurrentAgent } from "unagent/env";
|
|
13
|
+
import { dirname as dirname$1, resolve as resolve$1 } from "node:path";
|
|
14
|
+
const defaultFeatures = {
|
|
15
|
+
search: true,
|
|
16
|
+
issues: true,
|
|
17
|
+
discussions: true,
|
|
18
|
+
releases: true
|
|
19
|
+
};
|
|
20
|
+
const CONFIG_DIR = join(homedir(), ".skilld");
|
|
21
|
+
const CONFIG_PATH = join(CONFIG_DIR, "config.yaml");
|
|
22
|
+
function hasConfig() {
|
|
23
|
+
return existsSync(CONFIG_PATH);
|
|
24
|
+
}
|
|
25
|
+
/** Whether the first-run wizard has been completed (not just agent selection) */
|
|
26
|
+
function hasCompletedWizard() {
|
|
27
|
+
if (!existsSync(CONFIG_PATH)) return false;
|
|
28
|
+
const config = readConfig();
|
|
29
|
+
return config.features !== void 0 || config.model !== void 0 || config.skipLlm !== void 0;
|
|
30
|
+
}
|
|
31
|
+
function readConfig() {
|
|
32
|
+
if (!existsSync(CONFIG_PATH)) return {};
|
|
33
|
+
const content = readFileSync(CONFIG_PATH, "utf-8");
|
|
34
|
+
const config = {};
|
|
35
|
+
let inBlock = null;
|
|
36
|
+
const projects = [];
|
|
37
|
+
const features = {};
|
|
38
|
+
for (const line of content.split("\n")) {
|
|
39
|
+
if (line.startsWith("projects:")) {
|
|
40
|
+
inBlock = "projects";
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (line.startsWith("features:")) {
|
|
44
|
+
inBlock = "features";
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (inBlock === "projects") {
|
|
48
|
+
if (line.startsWith(" - ")) {
|
|
49
|
+
projects.push(yamlUnescape(line.slice(4)));
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
inBlock = null;
|
|
53
|
+
}
|
|
54
|
+
if (inBlock === "features") {
|
|
55
|
+
const m = line.match(/^ {2}(\w+):\s*(.+)/);
|
|
56
|
+
if (m) {
|
|
57
|
+
const key = m[1];
|
|
58
|
+
if (key in defaultFeatures) features[key] = m[2] === "true";
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
inBlock = null;
|
|
62
|
+
}
|
|
63
|
+
const kv = yamlParseKV(line);
|
|
64
|
+
if (!kv) continue;
|
|
65
|
+
const [key, value] = kv;
|
|
66
|
+
if (key === "model" && value) config.model = value;
|
|
67
|
+
if (key === "agent" && value) config.agent = value;
|
|
68
|
+
if (key === "skipLlm") config.skipLlm = value === "true";
|
|
69
|
+
}
|
|
70
|
+
if (projects.length > 0) config.projects = projects;
|
|
71
|
+
if (Object.keys(features).length > 0) config.features = {
|
|
72
|
+
...defaultFeatures,
|
|
73
|
+
...features
|
|
74
|
+
};
|
|
75
|
+
return config;
|
|
76
|
+
}
|
|
77
|
+
function writeConfig(config) {
|
|
78
|
+
mkdirSync(CONFIG_DIR, {
|
|
79
|
+
recursive: true,
|
|
80
|
+
mode: 448
|
|
81
|
+
});
|
|
82
|
+
let yaml = "";
|
|
83
|
+
if (config.model) yaml += `model: ${config.model}\n`;
|
|
84
|
+
if (config.agent) yaml += `agent: ${config.agent}\n`;
|
|
85
|
+
if (config.skipLlm) yaml += `skipLlm: true\n`;
|
|
86
|
+
if (config.features) {
|
|
87
|
+
yaml += "features:\n";
|
|
88
|
+
for (const [k, v] of Object.entries(config.features)) yaml += ` ${k}: ${v}\n`;
|
|
89
|
+
}
|
|
90
|
+
if (config.projects?.length) {
|
|
91
|
+
yaml += "projects:\n";
|
|
92
|
+
for (const p of config.projects) yaml += ` - ${yamlEscape(p)}\n`;
|
|
93
|
+
}
|
|
94
|
+
writeFileSync(CONFIG_PATH, yaml, { mode: 384 });
|
|
95
|
+
}
|
|
96
|
+
function updateConfig(updates) {
|
|
97
|
+
writeConfig({
|
|
98
|
+
...readConfig(),
|
|
99
|
+
...updates
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function registerProject(projectPath) {
|
|
103
|
+
const config = readConfig();
|
|
104
|
+
const projects = new Set(config.projects || []);
|
|
105
|
+
projects.add(projectPath);
|
|
106
|
+
writeConfig({
|
|
107
|
+
...config,
|
|
108
|
+
projects: [...projects]
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function unregisterProject(projectPath) {
|
|
112
|
+
const config = readConfig();
|
|
113
|
+
const projects = (config.projects || []).filter((p) => p !== projectPath);
|
|
114
|
+
writeConfig({
|
|
115
|
+
...config,
|
|
116
|
+
projects
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function getRegisteredProjects() {
|
|
120
|
+
return readConfig().projects || [];
|
|
121
|
+
}
|
|
122
|
+
function findPackageJson() {
|
|
123
|
+
let dir = dirname$1(fileURLToPath(import.meta.url));
|
|
124
|
+
for (let i = 0; i < 5; i++) {
|
|
125
|
+
const candidate = resolve$1(dir, "package.json");
|
|
126
|
+
try {
|
|
127
|
+
const content = readFileSync(candidate, "utf8");
|
|
128
|
+
if (content) return content;
|
|
129
|
+
} catch {}
|
|
130
|
+
dir = resolve$1(dir, "..");
|
|
131
|
+
}
|
|
132
|
+
return "{\"version\":\"0.0.0\"}";
|
|
133
|
+
}
|
|
134
|
+
const version = JSON.parse(findPackageJson()).version;
|
|
135
|
+
const sharedArgs = {
|
|
136
|
+
global: {
|
|
137
|
+
type: "boolean",
|
|
138
|
+
alias: "g",
|
|
139
|
+
description: "Install globally to ~/.skilld/skills",
|
|
140
|
+
default: false
|
|
141
|
+
},
|
|
142
|
+
agent: {
|
|
143
|
+
type: "enum",
|
|
144
|
+
options: Object.keys(targets),
|
|
145
|
+
alias: "a",
|
|
146
|
+
description: "Agent where skills are installed"
|
|
147
|
+
},
|
|
148
|
+
model: {
|
|
149
|
+
type: "string",
|
|
150
|
+
alias: "m",
|
|
151
|
+
description: "LLM model for skill generation",
|
|
152
|
+
valueHint: "id"
|
|
153
|
+
},
|
|
154
|
+
yes: {
|
|
155
|
+
type: "boolean",
|
|
156
|
+
alias: "y",
|
|
157
|
+
description: "Skip prompts, use defaults",
|
|
158
|
+
default: false
|
|
159
|
+
},
|
|
160
|
+
force: {
|
|
161
|
+
type: "boolean",
|
|
162
|
+
alias: "f",
|
|
163
|
+
description: "Ignore all caches, re-fetch docs and regenerate",
|
|
164
|
+
default: false
|
|
165
|
+
},
|
|
166
|
+
debug: {
|
|
167
|
+
type: "boolean",
|
|
168
|
+
description: "Save raw LLM output to logs/ for each section",
|
|
169
|
+
default: false
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
/** Check if the current environment supports interactive prompts */
|
|
173
|
+
function isInteractive() {
|
|
174
|
+
if (detectCurrentAgent()) return false;
|
|
175
|
+
if (process.env.CI) return false;
|
|
176
|
+
if (!process.stdout.isTTY) return false;
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
/** Exit with error if interactive terminal is required but unavailable */
|
|
180
|
+
function requireInteractive(command) {
|
|
181
|
+
if (!isInteractive()) {
|
|
182
|
+
console.error(`Error: \`skilld ${command}\` requires an interactive terminal`);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** Resolve agent from flags/cwd/config. cwd is source of truth over config. */
|
|
187
|
+
function resolveAgent(agentFlag) {
|
|
188
|
+
return agentFlag ?? detectTargetAgent() ?? readConfig().agent ?? null;
|
|
189
|
+
}
|
|
190
|
+
/** Prompt user to pick an agent when auto-detection fails */
|
|
191
|
+
async function promptForAgent() {
|
|
192
|
+
const installed = detectInstalledAgents();
|
|
193
|
+
if (!isInteractive()) {
|
|
194
|
+
if (installed.length === 1) {
|
|
195
|
+
updateConfig({ agent: installed[0] });
|
|
196
|
+
return installed[0];
|
|
197
|
+
}
|
|
198
|
+
console.error("Error: could not auto-detect agent. Pass --agent <name> to specify.");
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
const options = (installed.length ? installed : Object.keys(targets)).map((id) => ({
|
|
202
|
+
label: targets[id].displayName,
|
|
203
|
+
value: id,
|
|
204
|
+
hint: targets[id].skillsDir
|
|
205
|
+
}));
|
|
206
|
+
const hint = installed.length ? `Detected ${installed.map((t) => targets[t].displayName).join(", ")} but couldn't determine which to use` : "No agents auto-detected";
|
|
207
|
+
p.log.warn(`Could not detect which coding agent to install skills for.\n ${hint}`);
|
|
208
|
+
const choice = await p.select({
|
|
209
|
+
message: "Which coding agent should skills be installed for?",
|
|
210
|
+
options
|
|
211
|
+
});
|
|
212
|
+
if (p.isCancel(choice)) return null;
|
|
213
|
+
updateConfig({ agent: choice });
|
|
214
|
+
p.log.success(`Default agent set to ${targets[choice].displayName}`);
|
|
215
|
+
return choice;
|
|
216
|
+
}
|
|
217
|
+
/** Get installed LLM generators with working CLIs (verified via --version) */
|
|
218
|
+
function getInstalledGenerators() {
|
|
219
|
+
return detectInstalledAgents().filter((id) => targets[id].cli).map((id) => {
|
|
220
|
+
const ver = getAgentVersion(id);
|
|
221
|
+
return ver ? {
|
|
222
|
+
name: targets[id].displayName,
|
|
223
|
+
version: ver
|
|
224
|
+
} : null;
|
|
225
|
+
}).filter((a) => a !== null);
|
|
226
|
+
}
|
|
227
|
+
function relativeTime(date) {
|
|
228
|
+
const diff = Date.now() - date.getTime();
|
|
229
|
+
const mins = Math.floor(diff / 6e4);
|
|
230
|
+
const hours = Math.floor(diff / 36e5);
|
|
231
|
+
const days = Math.floor(diff / 864e5);
|
|
232
|
+
if (mins < 1) return "just now";
|
|
233
|
+
if (mins < 60) return `${mins}m ago`;
|
|
234
|
+
if (hours < 24) return `${hours}h ago`;
|
|
235
|
+
return `${days}d ago`;
|
|
236
|
+
}
|
|
237
|
+
function getLastSynced(state) {
|
|
238
|
+
let latest = null;
|
|
239
|
+
for (const skill of state.skills) if (skill.info?.syncedAt) {
|
|
240
|
+
const d = new Date(skill.info.syncedAt);
|
|
241
|
+
if (!latest || d > latest) latest = d;
|
|
242
|
+
}
|
|
243
|
+
return latest ? relativeTime(latest) : null;
|
|
244
|
+
}
|
|
245
|
+
function introLine({ state, generators, modelId }) {
|
|
246
|
+
const name = "\x1B[1m\x1B[35mskilld\x1B[0m";
|
|
247
|
+
const ver = `\x1B[90mv${version}\x1B[0m`;
|
|
248
|
+
const lastSynced = getLastSynced(state);
|
|
249
|
+
const synced = lastSynced ? ` · \x1B[90msynced ${lastSynced}\x1B[0m` : "";
|
|
250
|
+
const modelStr = modelId ? ` · ${getModelName(modelId)}` : "";
|
|
251
|
+
const genStr = generators?.length ? generators.map((g) => `${g.name} v${g.version}`).join(", ") : "";
|
|
252
|
+
return `${name} ${ver}${synced}${genStr ? `\n\x1B[90m↳ ${genStr}${modelStr}\x1B[0m` : ""}`;
|
|
253
|
+
}
|
|
254
|
+
function formatStatus(synced, outdated) {
|
|
255
|
+
const parts = [];
|
|
256
|
+
if (synced > 0) parts.push(`\x1B[32m${synced} synced\x1B[0m`);
|
|
257
|
+
if (outdated > 0) parts.push(`\x1B[33m${outdated} outdated\x1B[0m`);
|
|
258
|
+
return `Skills: ${parts.join(" · ")}`;
|
|
259
|
+
}
|
|
260
|
+
function getRepoHint(name, cwd) {
|
|
261
|
+
const pkgJsonPath = join(cwd, "node_modules", name, "package.json");
|
|
262
|
+
if (!existsSync(pkgJsonPath)) return void 0;
|
|
263
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
264
|
+
const url = typeof pkg.repository === "string" ? pkg.repository : pkg.repository?.url;
|
|
265
|
+
if (!url) return void 0;
|
|
266
|
+
return url.replace(/^git\+/, "").replace(/\.git$/, "").replace(/^git:\/\//, "https://").replace(/^ssh:\/\/git@github\.com/, "https://github.com").replace(/^https?:\/\/(www\.)?github\.com\//, "");
|
|
267
|
+
}
|
|
268
|
+
function parsePackages(packages) {
|
|
269
|
+
if (!packages) return [];
|
|
270
|
+
return packages.split(",").map((s) => {
|
|
271
|
+
const trimmed = s.trim();
|
|
272
|
+
const atIdx = trimmed.lastIndexOf("@");
|
|
273
|
+
if (atIdx <= 0) return {
|
|
274
|
+
name: trimmed,
|
|
275
|
+
version: ""
|
|
276
|
+
};
|
|
277
|
+
return {
|
|
278
|
+
name: trimmed.slice(0, atIdx),
|
|
279
|
+
version: trimmed.slice(atIdx + 1)
|
|
280
|
+
};
|
|
281
|
+
}).filter((p) => p.name);
|
|
282
|
+
}
|
|
283
|
+
function serializePackages(pkgs) {
|
|
284
|
+
return pkgs.map((p) => `${p.name}@${p.version}`).join(", ");
|
|
285
|
+
}
|
|
286
|
+
const SKILL_FM_KEYS = [
|
|
287
|
+
"packageName",
|
|
288
|
+
"version",
|
|
289
|
+
"packages",
|
|
290
|
+
"repo",
|
|
291
|
+
"source",
|
|
292
|
+
"syncedAt",
|
|
293
|
+
"generator",
|
|
294
|
+
"path",
|
|
295
|
+
"ref",
|
|
296
|
+
"commit"
|
|
297
|
+
];
|
|
298
|
+
function parseSkillFrontmatter(skillPath) {
|
|
299
|
+
if (!existsSync(skillPath)) return null;
|
|
300
|
+
const fm = parseFrontmatter(readFileSync(skillPath, "utf-8"));
|
|
301
|
+
if (Object.keys(fm).length === 0) return null;
|
|
302
|
+
const info = {};
|
|
303
|
+
for (const key of SKILL_FM_KEYS) if (fm[key]) info[key] = fm[key];
|
|
304
|
+
return info;
|
|
305
|
+
}
|
|
306
|
+
function readLock(skillsDir) {
|
|
307
|
+
const lockPath = join(skillsDir, "skilld-lock.yaml");
|
|
308
|
+
if (!existsSync(lockPath)) return null;
|
|
309
|
+
const content = readFileSync(lockPath, "utf-8");
|
|
310
|
+
const skills = {};
|
|
311
|
+
let currentSkill = null;
|
|
312
|
+
for (const line of content.split("\n")) {
|
|
313
|
+
const skillMatch = line.match(/^ {2}(\S+):$/);
|
|
314
|
+
if (skillMatch) {
|
|
315
|
+
currentSkill = skillMatch[1];
|
|
316
|
+
skills[currentSkill] = {};
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (currentSkill && line.startsWith(" ")) {
|
|
320
|
+
const kv = yamlParseKV(line);
|
|
321
|
+
if (kv) skills[currentSkill][kv[0]] = kv[1];
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return { skills };
|
|
325
|
+
}
|
|
326
|
+
function serializeLock(lock) {
|
|
327
|
+
let yaml = "skills:\n";
|
|
328
|
+
for (const [name, skill] of Object.entries(lock.skills)) {
|
|
329
|
+
yaml += ` ${name}:\n`;
|
|
330
|
+
if (skill.packageName) yaml += ` packageName: ${yamlEscape(skill.packageName)}\n`;
|
|
331
|
+
if (skill.version) yaml += ` version: ${yamlEscape(skill.version)}\n`;
|
|
332
|
+
if (skill.packages) yaml += ` packages: ${yamlEscape(skill.packages)}\n`;
|
|
333
|
+
if (skill.repo) yaml += ` repo: ${yamlEscape(skill.repo)}\n`;
|
|
334
|
+
if (skill.source) yaml += ` source: ${yamlEscape(skill.source)}\n`;
|
|
335
|
+
if (skill.syncedAt) yaml += ` syncedAt: ${yamlEscape(skill.syncedAt)}\n`;
|
|
336
|
+
if (skill.generator) yaml += ` generator: ${yamlEscape(skill.generator)}\n`;
|
|
337
|
+
if (skill.path) yaml += ` path: ${yamlEscape(skill.path)}\n`;
|
|
338
|
+
if (skill.ref) yaml += ` ref: ${yamlEscape(skill.ref)}\n`;
|
|
339
|
+
if (skill.commit) yaml += ` commit: ${yamlEscape(skill.commit)}\n`;
|
|
340
|
+
}
|
|
341
|
+
return yaml;
|
|
342
|
+
}
|
|
343
|
+
function writeLock(skillsDir, skillName, info) {
|
|
344
|
+
const lockPath = join(skillsDir, "skilld-lock.yaml");
|
|
345
|
+
let lock = { skills: {} };
|
|
346
|
+
if (existsSync(lockPath)) lock = readLock(skillsDir) || { skills: {} };
|
|
347
|
+
const existing = lock.skills[skillName];
|
|
348
|
+
if (existing && info.packageName) {
|
|
349
|
+
const existingPkgs = parsePackages(existing.packages);
|
|
350
|
+
if (existing.packageName && !existingPkgs.some((p) => p.name === existing.packageName)) existingPkgs.unshift({
|
|
351
|
+
name: existing.packageName,
|
|
352
|
+
version: existing.version || ""
|
|
353
|
+
});
|
|
354
|
+
const idx = existingPkgs.findIndex((p) => p.name === info.packageName);
|
|
355
|
+
if (idx >= 0) existingPkgs[idx].version = info.version || "";
|
|
356
|
+
else existingPkgs.push({
|
|
357
|
+
name: info.packageName,
|
|
358
|
+
version: info.version || ""
|
|
359
|
+
});
|
|
360
|
+
info.packages = serializePackages(existingPkgs);
|
|
361
|
+
info.packageName = existingPkgs[0].name;
|
|
362
|
+
info.version = existingPkgs[0].version;
|
|
363
|
+
if (!info.repo && existing.repo) info.repo = existing.repo;
|
|
364
|
+
if (!info.source && existing.source) info.source = existing.source;
|
|
365
|
+
if (!info.generator && existing.generator) info.generator = existing.generator;
|
|
366
|
+
}
|
|
367
|
+
lock.skills[skillName] = info;
|
|
368
|
+
writeFileSync(lockPath, serializeLock(lock));
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Merge multiple lockfiles, preferring the most recently synced entry per skill.
|
|
372
|
+
*/
|
|
373
|
+
function mergeLocks(locks) {
|
|
374
|
+
const merged = {};
|
|
375
|
+
for (const lock of locks) for (const [name, info] of Object.entries(lock.skills)) {
|
|
376
|
+
const existing = merged[name];
|
|
377
|
+
if (!existing || info.syncedAt && (!existing.syncedAt || info.syncedAt > existing.syncedAt)) merged[name] = info;
|
|
378
|
+
}
|
|
379
|
+
return { skills: merged };
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Sync a lockfile to all other dirs that already have a skilld-lock.yaml.
|
|
383
|
+
* Only updates existing lockfiles — does not create new ones.
|
|
384
|
+
*/
|
|
385
|
+
function syncLockfilesToDirs(sourceLock, dirs) {
|
|
386
|
+
for (const dir of dirs) {
|
|
387
|
+
const lockPath = join(dir, "skilld-lock.yaml");
|
|
388
|
+
if (!existsSync(lockPath)) continue;
|
|
389
|
+
const existing = readLock(dir);
|
|
390
|
+
if (!existing) continue;
|
|
391
|
+
writeFileSync(lockPath, serializeLock(mergeLocks([existing, sourceLock])));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function removeLockEntry(skillsDir, skillName) {
|
|
395
|
+
const lockPath = join(skillsDir, "skilld-lock.yaml");
|
|
396
|
+
const lock = readLock(skillsDir);
|
|
397
|
+
if (!lock) return;
|
|
398
|
+
delete lock.skills[skillName];
|
|
399
|
+
if (Object.keys(lock.skills).length === 0) {
|
|
400
|
+
unlinkSync(lockPath);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
writeFileSync(lockPath, serializeLock(lock));
|
|
404
|
+
}
|
|
405
|
+
function* iterateSkills(opts = {}) {
|
|
406
|
+
const { scope = "all", cwd = process.cwd() } = opts;
|
|
407
|
+
const agentTypes = opts.agents ?? Object.keys(targets);
|
|
408
|
+
const sharedDir = getSharedSkillsDir(cwd);
|
|
409
|
+
let yieldedLocal = false;
|
|
410
|
+
if (sharedDir && (scope === "local" || scope === "all")) {
|
|
411
|
+
yieldedLocal = true;
|
|
412
|
+
const lock = readLock(sharedDir);
|
|
413
|
+
const entries = readdirSync(sharedDir).filter((f) => !f.startsWith(".") && f !== "skilld-lock.yaml");
|
|
414
|
+
const firstAgent = agentTypes[0] ?? Object.keys(targets)[0];
|
|
415
|
+
for (const name of entries) {
|
|
416
|
+
const dir = join(sharedDir, name);
|
|
417
|
+
if (lock?.skills[name]) yield {
|
|
418
|
+
name,
|
|
419
|
+
dir,
|
|
420
|
+
agent: firstAgent,
|
|
421
|
+
info: lock.skills[name],
|
|
422
|
+
scope: "local"
|
|
423
|
+
};
|
|
424
|
+
else {
|
|
425
|
+
const info = parseSkillFrontmatter(join(dir, ".skilld", "_SKILL.md"));
|
|
426
|
+
if (info?.generator === "skilld") yield {
|
|
427
|
+
name,
|
|
428
|
+
dir,
|
|
429
|
+
agent: firstAgent,
|
|
430
|
+
info,
|
|
431
|
+
scope: "local"
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
for (const agentType of agentTypes) {
|
|
437
|
+
const agent = targets[agentType];
|
|
438
|
+
if (!yieldedLocal && (scope === "local" || scope === "all")) {
|
|
439
|
+
const localDir = join(cwd, agent.skillsDir);
|
|
440
|
+
if (existsSync(localDir)) {
|
|
441
|
+
const lock = readLock(localDir);
|
|
442
|
+
const entries = readdirSync(localDir).filter((f) => !f.startsWith(".") && f !== "skilld-lock.yaml");
|
|
443
|
+
for (const name of entries) {
|
|
444
|
+
const dir = join(localDir, name);
|
|
445
|
+
if (lock?.skills[name]) yield {
|
|
446
|
+
name,
|
|
447
|
+
dir,
|
|
448
|
+
agent: agentType,
|
|
449
|
+
info: lock.skills[name],
|
|
450
|
+
scope: "local"
|
|
451
|
+
};
|
|
452
|
+
else {
|
|
453
|
+
const info = parseSkillFrontmatter(join(dir, ".skilld", "_SKILL.md"));
|
|
454
|
+
if (info?.generator === "skilld") yield {
|
|
455
|
+
name,
|
|
456
|
+
dir,
|
|
457
|
+
agent: agentType,
|
|
458
|
+
info,
|
|
459
|
+
scope: "local"
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if ((scope === "global" || scope === "all") && agent.globalSkillsDir) {
|
|
466
|
+
const globalDir = agent.globalSkillsDir;
|
|
467
|
+
if (existsSync(globalDir)) {
|
|
468
|
+
const lock = readLock(globalDir);
|
|
469
|
+
const entries = readdirSync(globalDir).filter((f) => !f.startsWith(".") && f !== "skilld-lock.yaml");
|
|
470
|
+
for (const name of entries) {
|
|
471
|
+
const dir = join(globalDir, name);
|
|
472
|
+
if (lock?.skills[name]) yield {
|
|
473
|
+
name,
|
|
474
|
+
dir,
|
|
475
|
+
agent: agentType,
|
|
476
|
+
info: lock.skills[name],
|
|
477
|
+
scope: "global"
|
|
478
|
+
};
|
|
479
|
+
else {
|
|
480
|
+
const info = parseSkillFrontmatter(join(dir, ".skilld", "_SKILL.md"));
|
|
481
|
+
if (info?.generator === "skilld") yield {
|
|
482
|
+
name,
|
|
483
|
+
dir,
|
|
484
|
+
agent: agentType,
|
|
485
|
+
info,
|
|
486
|
+
scope: "global"
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function isOutdated(skill, depVersion) {
|
|
495
|
+
if (!skill.info?.version) return true;
|
|
496
|
+
return semverGt(depVersion.replace(/^[\^~]/, ""), skill.info.version);
|
|
497
|
+
}
|
|
498
|
+
async function getProjectState(cwd = process.cwd()) {
|
|
499
|
+
const skills = [...iterateSkills({
|
|
500
|
+
scope: "local",
|
|
501
|
+
cwd
|
|
502
|
+
})];
|
|
503
|
+
const localDeps = await readLocalDependencies(cwd).catch(() => []);
|
|
504
|
+
const deps = new Map(localDeps.map((d) => [d.name, d.version]));
|
|
505
|
+
const skillByName = new Map(skills.map((s) => [s.name, s]));
|
|
506
|
+
const skillByPkgName = /* @__PURE__ */ new Map();
|
|
507
|
+
for (const s of skills) {
|
|
508
|
+
if (s.info?.packageName) skillByPkgName.set(s.info.packageName, s);
|
|
509
|
+
for (const pkg of parsePackages(s.info?.packages)) skillByPkgName.set(pkg.name, s);
|
|
510
|
+
}
|
|
511
|
+
const missing = [];
|
|
512
|
+
const outdated = [];
|
|
513
|
+
const synced = [];
|
|
514
|
+
const matchedSkillNames = /* @__PURE__ */ new Set();
|
|
515
|
+
for (const [pkgName, version] of deps) {
|
|
516
|
+
const normalizedName = pkgName.replace(/^@/, "").replace(/\//g, "-");
|
|
517
|
+
const skill = skillByName.get(`${normalizedName}-skilld`) || skillByName.get(normalizedName) || skillByName.get(pkgName) || skillByPkgName.get(pkgName);
|
|
518
|
+
if (!skill) missing.push(pkgName);
|
|
519
|
+
else {
|
|
520
|
+
matchedSkillNames.add(skill.name);
|
|
521
|
+
if (isOutdated(skill, version)) outdated.push({
|
|
522
|
+
...skill,
|
|
523
|
+
packageName: pkgName,
|
|
524
|
+
latestVersion: version
|
|
525
|
+
});
|
|
526
|
+
else synced.push({
|
|
527
|
+
...skill,
|
|
528
|
+
packageName: pkgName,
|
|
529
|
+
latestVersion: version
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return {
|
|
534
|
+
skills,
|
|
535
|
+
deps,
|
|
536
|
+
missing,
|
|
537
|
+
outdated,
|
|
538
|
+
synced,
|
|
539
|
+
unmatched: skills.filter((s) => !matchedSkillNames.has(s.name))
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function getSkillsDir(agent, scope, cwd = process.cwd()) {
|
|
543
|
+
const agentConfig = targets[agent];
|
|
544
|
+
if (scope === "global") {
|
|
545
|
+
if (!agentConfig.globalSkillsDir) throw new Error(`Agent ${agent} does not support global skills`);
|
|
546
|
+
return agentConfig.globalSkillsDir;
|
|
547
|
+
}
|
|
548
|
+
return getSharedSkillsDir(cwd) || join(cwd, agentConfig.skillsDir);
|
|
549
|
+
}
|
|
550
|
+
export { getRegisteredProjects as C, registerProject as D, readConfig as E, unregisterProject as O, defaultFeatures as S, hasConfig as T, relativeTime as _, mergeLocks as a, sharedArgs as b, removeLockEntry as c, formatStatus as d, getInstalledGenerators as f, promptForAgent as g, isInteractive as h, iterateSkills as i, updateConfig as k, syncLockfilesToDirs as l, introLine as m, getSkillsDir as n, parsePackages as o, getRepoHint as p, isOutdated as r, readLock as s, getProjectState as t, writeLock as u, requireInteractive as v, hasCompletedWizard as w, version as x, resolveAgent as y };
|
|
551
|
+
|
|
552
|
+
//# sourceMappingURL=skills.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.mjs","names":["dirname","resolve","agents","agents"],"sources":["../../src/core/config.ts","../../src/version.ts","../../src/cli-helpers.ts","../../src/core/lockfile.ts","../../src/core/skills.ts"],"sourcesContent":["import type { OptimizeModel } from '../agent/index.ts'\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'pathe'\nimport { yamlEscape, yamlParseKV, yamlUnescape } from './yaml.ts'\n\nexport interface FeaturesConfig {\n search: boolean\n issues: boolean\n discussions: boolean\n releases: boolean\n}\n\nexport const defaultFeatures: FeaturesConfig = {\n search: true,\n issues: true,\n discussions: true,\n releases: true,\n}\n\nexport interface SkilldConfig {\n model?: OptimizeModel\n agent?: string\n features?: FeaturesConfig\n projects?: string[]\n skipLlm?: boolean\n}\n\nconst CONFIG_DIR = join(homedir(), '.skilld')\nconst CONFIG_PATH = join(CONFIG_DIR, 'config.yaml')\n\nexport function hasConfig(): boolean {\n return existsSync(CONFIG_PATH)\n}\n\n/** Whether the first-run wizard has been completed (not just agent selection) */\nexport function hasCompletedWizard(): boolean {\n if (!existsSync(CONFIG_PATH))\n return false\n const config = readConfig()\n return config.features !== undefined || config.model !== undefined || config.skipLlm !== undefined\n}\n\nexport function readConfig(): SkilldConfig {\n if (!existsSync(CONFIG_PATH))\n return {}\n\n const content = readFileSync(CONFIG_PATH, 'utf-8')\n const config: SkilldConfig = {}\n let inBlock: 'projects' | 'features' | null = null\n const projects: string[] = []\n const features: Partial<FeaturesConfig> = {}\n\n for (const line of content.split('\\n')) {\n if (line.startsWith('projects:')) {\n inBlock = 'projects'\n continue\n }\n if (line.startsWith('features:')) {\n inBlock = 'features'\n continue\n }\n if (inBlock === 'projects') {\n if (line.startsWith(' - ')) {\n projects.push(yamlUnescape(line.slice(4)))\n continue\n }\n inBlock = null\n }\n if (inBlock === 'features') {\n const m = line.match(/^ {2}(\\w+):\\s*(.+)/)\n if (m) {\n const key = m[1] as keyof FeaturesConfig\n if (key in defaultFeatures)\n features[key] = m[2] === 'true'\n continue\n }\n inBlock = null\n }\n const kv = yamlParseKV(line)\n if (!kv)\n continue\n const [key, value] = kv\n if (key === 'model' && value)\n config.model = value as OptimizeModel\n if (key === 'agent' && value)\n config.agent = value\n if (key === 'skipLlm')\n config.skipLlm = value === 'true'\n }\n\n if (projects.length > 0)\n config.projects = projects\n if (Object.keys(features).length > 0)\n config.features = { ...defaultFeatures, ...features }\n return config\n}\n\nexport function writeConfig(config: SkilldConfig): void {\n mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 })\n\n let yaml = ''\n if (config.model)\n yaml += `model: ${config.model}\\n`\n if (config.agent)\n yaml += `agent: ${config.agent}\\n`\n if (config.skipLlm)\n yaml += `skipLlm: true\\n`\n if (config.features) {\n yaml += 'features:\\n'\n for (const [k, v] of Object.entries(config.features)) {\n yaml += ` ${k}: ${v}\\n`\n }\n }\n if (config.projects?.length) {\n yaml += 'projects:\\n'\n for (const p of config.projects) {\n yaml += ` - ${yamlEscape(p)}\\n`\n }\n }\n\n writeFileSync(CONFIG_PATH, yaml, { mode: 0o600 })\n}\n\nexport function updateConfig(updates: Partial<SkilldConfig>): void {\n const config = readConfig()\n writeConfig({ ...config, ...updates })\n}\n\nexport function registerProject(projectPath: string): void {\n const config = readConfig()\n const projects = new Set(config.projects || [])\n projects.add(projectPath)\n writeConfig({ ...config, projects: [...projects] })\n}\n\nexport function unregisterProject(projectPath: string): void {\n const config = readConfig()\n const projects = (config.projects || []).filter(p => p !== projectPath)\n writeConfig({ ...config, projects })\n}\n\nexport function getRegisteredProjects(): string[] {\n return readConfig().projects || []\n}\n","import { readFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n// Walk up from current file to find package.json (works in both src/ and dist/_chunks/)\nfunction findPackageJson(): string {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (let i = 0; i < 5; i++) {\n const candidate = resolve(dir, 'package.json')\n try {\n const content = readFileSync(candidate, 'utf8')\n if (content)\n return content\n }\n catch {}\n dir = resolve(dir, '..')\n }\n return '{\"version\":\"0.0.0\"}'\n}\n\nexport const version: string = JSON.parse(findPackageJson()).version\n","/**\n * Shared CLI helpers used by subcommand definitions and the main CLI entry.\n * Extracted to avoid circular deps between cli.ts and commands/*.ts.\n */\n\nimport type { AgentType, OptimizeModel } from './agent/index.ts'\nimport type { ProjectState } from './core/skills.ts'\nimport { existsSync, readFileSync } from 'node:fs'\nimport * as p from '@clack/prompts'\nimport { join } from 'pathe'\nimport { detectCurrentAgent } from 'unagent/env'\nimport { agents, detectInstalledAgents, detectTargetAgent, getAgentVersion, getModelName } from './agent/index.ts'\nimport { readConfig, updateConfig } from './core/config.ts'\nimport { version } from './version.ts'\n\nexport type { AgentType, OptimizeModel }\n\nexport interface IntroOptions {\n state: ProjectState\n generators?: Array<{ name: string, version: string }>\n modelId?: string\n}\n\nexport const sharedArgs = {\n global: {\n type: 'boolean' as const,\n alias: 'g',\n description: 'Install globally to ~/.skilld/skills',\n default: false,\n },\n agent: {\n type: 'enum' as const,\n options: Object.keys(agents),\n alias: 'a',\n description: 'Agent where skills are installed',\n },\n model: {\n type: 'string' as const,\n alias: 'm',\n description: 'LLM model for skill generation',\n valueHint: 'id',\n },\n yes: {\n type: 'boolean' as const,\n alias: 'y',\n description: 'Skip prompts, use defaults',\n default: false,\n },\n force: {\n type: 'boolean' as const,\n alias: 'f',\n description: 'Ignore all caches, re-fetch docs and regenerate',\n default: false,\n },\n debug: {\n type: 'boolean' as const,\n description: 'Save raw LLM output to logs/ for each section',\n default: false,\n },\n}\n\n/** Check if the current environment supports interactive prompts */\nexport function isInteractive(): boolean {\n if (detectCurrentAgent())\n return false\n if (process.env.CI)\n return false\n if (!process.stdout.isTTY)\n return false\n return true\n}\n\n/** Exit with error if interactive terminal is required but unavailable */\nexport function requireInteractive(command: string): void {\n if (!isInteractive()) {\n console.error(`Error: \\`skilld ${command}\\` requires an interactive terminal`)\n process.exit(1)\n }\n}\n\n/** Resolve agent from flags/cwd/config. cwd is source of truth over config. */\nexport function resolveAgent(agentFlag?: string): AgentType | null {\n return (agentFlag as AgentType | undefined)\n ?? detectTargetAgent()\n ?? (readConfig().agent as AgentType | undefined)\n ?? null\n}\n\n/** Prompt user to pick an agent when auto-detection fails */\nexport async function promptForAgent(): Promise<AgentType | null> {\n const installed = detectInstalledAgents()\n\n // Non-interactive: auto-select sole installed agent or error\n if (!isInteractive()) {\n if (installed.length === 1) {\n updateConfig({ agent: installed[0] })\n return installed[0]!\n }\n console.error('Error: could not auto-detect agent. Pass --agent <name> to specify.')\n process.exit(1)\n }\n\n const options = (installed.length ? installed : Object.keys(agents) as AgentType[])\n .map(id => ({ label: agents[id].displayName, value: id, hint: agents[id].skillsDir }))\n\n const hint = installed.length\n ? `Detected ${installed.map(t => agents[t].displayName).join(', ')} but couldn't determine which to use`\n : 'No agents auto-detected'\n\n p.log.warn(`Could not detect which coding agent to install skills for.\\n ${hint}`)\n\n const choice = await p.select({\n message: 'Which coding agent should skills be installed for?',\n options,\n })\n\n if (p.isCancel(choice))\n return null\n\n // Save as default so they don't get asked again\n updateConfig({ agent: choice })\n p.log.success(`Default agent set to ${agents[choice].displayName}`)\n return choice\n}\n\n/** Get installed LLM generators with working CLIs (verified via --version) */\nexport function getInstalledGenerators(): Array<{ name: string, version: string }> {\n const installed = detectInstalledAgents()\n return installed\n .filter(id => agents[id].cli)\n .map((id) => {\n const ver = getAgentVersion(id)\n return ver ? { name: agents[id].displayName, version: ver } : null\n })\n .filter((a): a is { name: string, version: string } => a !== null)\n}\n\nexport function relativeTime(date: Date): string {\n const now = Date.now()\n const diff = now - date.getTime()\n const mins = Math.floor(diff / 60000)\n const hours = Math.floor(diff / 3600000)\n const days = Math.floor(diff / 86400000)\n if (mins < 1)\n return 'just now'\n if (mins < 60)\n return `${mins}m ago`\n if (hours < 24)\n return `${hours}h ago`\n return `${days}d ago`\n}\n\nexport function getLastSynced(state: ProjectState): string | null {\n let latest: Date | null = null\n for (const skill of state.skills) {\n if (skill.info?.syncedAt) {\n const d = new Date(skill.info.syncedAt)\n if (!latest || d > latest)\n latest = d\n }\n }\n return latest ? relativeTime(latest) : null\n}\n\nexport function introLine({ state, generators, modelId }: IntroOptions): string {\n const name = '\\x1B[1m\\x1B[35mskilld\\x1B[0m'\n const ver = `\\x1B[90mv${version}\\x1B[0m`\n const lastSynced = getLastSynced(state)\n const synced = lastSynced ? ` · \\x1B[90msynced ${lastSynced}\\x1B[0m` : ''\n const modelStr = modelId ? ` · ${getModelName(modelId as any)}` : ''\n const genStr = generators?.length\n ? generators.map(g => `${g.name} v${g.version}`).join(', ')\n : ''\n const genLine = genStr ? `\\n\\x1B[90m↳ ${genStr}${modelStr}\\x1B[0m` : ''\n return `${name} ${ver}${synced}${genLine}`\n}\n\nexport function formatStatus(synced: number, outdated: number): string {\n const parts: string[] = []\n if (synced > 0)\n parts.push(`\\x1B[32m${synced} synced\\x1B[0m`)\n if (outdated > 0)\n parts.push(`\\x1B[33m${outdated} outdated\\x1B[0m`)\n return `Skills: ${parts.join(' · ')}`\n}\n\nexport function getRepoHint(name: string, cwd: string): string | undefined {\n const pkgJsonPath = join(cwd, 'node_modules', name, 'package.json')\n if (!existsSync(pkgJsonPath))\n return undefined\n const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'))\n const url = typeof pkg.repository === 'string'\n ? pkg.repository\n : pkg.repository?.url\n if (!url)\n return undefined\n return url\n .replace(/^git\\+/, '')\n .replace(/\\.git$/, '')\n .replace(/^git:\\/\\//, 'https://')\n .replace(/^ssh:\\/\\/git@github\\.com/, 'https://github.com')\n .replace(/^https?:\\/\\/(www\\.)?github\\.com\\//, '')\n}\n","import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { join } from 'pathe'\nimport { parseFrontmatter } from './markdown.ts'\nimport { yamlEscape, yamlParseKV } from './yaml.ts'\n\nexport interface SkillInfo {\n packageName?: string\n version?: string\n /** All tracked packages as comma-separated \"name@version\" pairs (multi-package skills) */\n packages?: string\n repo?: string\n source?: string\n syncedAt?: string\n generator?: string\n /** Skill path within repo (git-sourced skills) */\n path?: string\n /** Git ref tracked for updates */\n ref?: string\n /** Git commit SHA at install time */\n commit?: string\n}\n\nexport function parsePackages(packages?: string): Array<{ name: string, version: string }> {\n if (!packages)\n return []\n return packages.split(',').map((s) => {\n const trimmed = s.trim()\n const atIdx = trimmed.lastIndexOf('@')\n if (atIdx <= 0)\n return { name: trimmed, version: '' }\n return { name: trimmed.slice(0, atIdx), version: trimmed.slice(atIdx + 1) }\n }).filter(p => p.name)\n}\n\nexport function serializePackages(pkgs: Array<{ name: string, version: string }>): string {\n return pkgs.map(p => `${p.name}@${p.version}`).join(', ')\n}\n\nexport interface SkilldLock {\n skills: Record<string, SkillInfo>\n}\n\nconst SKILL_FM_KEYS: (keyof SkillInfo)[] = ['packageName', 'version', 'packages', 'repo', 'source', 'syncedAt', 'generator', 'path', 'ref', 'commit']\n\nexport function parseSkillFrontmatter(skillPath: string): SkillInfo | null {\n if (!existsSync(skillPath))\n return null\n const content = readFileSync(skillPath, 'utf-8')\n const fm = parseFrontmatter(content)\n if (Object.keys(fm).length === 0)\n return null\n\n const info: SkillInfo = {}\n for (const key of SKILL_FM_KEYS) {\n if (fm[key])\n info[key] = fm[key]\n }\n return info\n}\n\nexport function readLock(skillsDir: string): SkilldLock | null {\n const lockPath = join(skillsDir, 'skilld-lock.yaml')\n if (!existsSync(lockPath))\n return null\n const content = readFileSync(lockPath, 'utf-8')\n\n const skills: Record<string, SkillInfo> = {}\n let currentSkill: string | null = null\n\n for (const line of content.split('\\n')) {\n const skillMatch = line.match(/^ {2}(\\S+):$/)\n if (skillMatch) {\n currentSkill = skillMatch[1]\n skills[currentSkill] = {}\n continue\n }\n if (currentSkill && line.startsWith(' ')) {\n const kv = yamlParseKV(line)\n if (kv)\n (skills[currentSkill] as any)[kv[0]] = kv[1]\n }\n }\n return { skills }\n}\n\nfunction serializeLock(lock: SkilldLock): string {\n let yaml = 'skills:\\n'\n for (const [name, skill] of Object.entries(lock.skills)) {\n yaml += ` ${name}:\\n`\n if (skill.packageName)\n yaml += ` packageName: ${yamlEscape(skill.packageName)}\\n`\n if (skill.version)\n yaml += ` version: ${yamlEscape(skill.version)}\\n`\n if (skill.packages)\n yaml += ` packages: ${yamlEscape(skill.packages)}\\n`\n if (skill.repo)\n yaml += ` repo: ${yamlEscape(skill.repo)}\\n`\n if (skill.source)\n yaml += ` source: ${yamlEscape(skill.source)}\\n`\n if (skill.syncedAt)\n yaml += ` syncedAt: ${yamlEscape(skill.syncedAt)}\\n`\n if (skill.generator)\n yaml += ` generator: ${yamlEscape(skill.generator)}\\n`\n if (skill.path)\n yaml += ` path: ${yamlEscape(skill.path)}\\n`\n if (skill.ref)\n yaml += ` ref: ${yamlEscape(skill.ref)}\\n`\n if (skill.commit)\n yaml += ` commit: ${yamlEscape(skill.commit)}\\n`\n }\n return yaml\n}\n\nexport function writeLock(skillsDir: string, skillName: string, info: SkillInfo): void {\n const lockPath = join(skillsDir, 'skilld-lock.yaml')\n let lock: SkilldLock = { skills: {} }\n if (existsSync(lockPath)) {\n lock = readLock(skillsDir) || { skills: {} }\n }\n\n const existing = lock.skills[skillName]\n if (existing && info.packageName) {\n // Merge packages list\n const existingPkgs = parsePackages(existing.packages)\n // Also include existing primary if not yet in packages list\n if (existing.packageName && !existingPkgs.some(p => p.name === existing.packageName)) {\n existingPkgs.unshift({ name: existing.packageName, version: existing.version || '' })\n }\n // Add/update new package\n const idx = existingPkgs.findIndex(p => p.name === info.packageName)\n if (idx >= 0) {\n existingPkgs[idx]!.version = info.version || ''\n }\n else {\n existingPkgs.push({ name: info.packageName, version: info.version || '' })\n }\n info.packages = serializePackages(existingPkgs)\n // Keep primary as first package\n info.packageName = existingPkgs[0]!.name\n info.version = existingPkgs[0]!.version\n // Preserve fields from existing entry that aren't in new info\n if (!info.repo && existing.repo)\n info.repo = existing.repo\n if (!info.source && existing.source)\n info.source = existing.source\n if (!info.generator && existing.generator)\n info.generator = existing.generator\n }\n\n lock.skills[skillName] = info\n writeFileSync(lockPath, serializeLock(lock))\n}\n\n/**\n * Merge multiple lockfiles, preferring the most recently synced entry per skill.\n */\nexport function mergeLocks(locks: SkilldLock[]): SkilldLock {\n const merged: Record<string, SkillInfo> = {}\n for (const lock of locks) {\n for (const [name, info] of Object.entries(lock.skills)) {\n const existing = merged[name]\n if (!existing || (info.syncedAt && (!existing.syncedAt || info.syncedAt > existing.syncedAt)))\n merged[name] = info\n }\n }\n return { skills: merged }\n}\n\n/**\n * Sync a lockfile to all other dirs that already have a skilld-lock.yaml.\n * Only updates existing lockfiles — does not create new ones.\n */\nexport function syncLockfilesToDirs(sourceLock: SkilldLock, dirs: string[]): void {\n for (const dir of dirs) {\n const lockPath = join(dir, 'skilld-lock.yaml')\n if (!existsSync(lockPath))\n continue\n const existing = readLock(dir)\n if (!existing)\n continue\n // Merge source into existing\n const merged = mergeLocks([existing, sourceLock])\n writeFileSync(lockPath, serializeLock(merged))\n }\n}\n\nexport function removeLockEntry(skillsDir: string, skillName: string): void {\n const lockPath = join(skillsDir, 'skilld-lock.yaml')\n const lock = readLock(skillsDir)\n if (!lock)\n return\n\n delete lock.skills[skillName]\n\n if (Object.keys(lock.skills).length === 0) {\n unlinkSync(lockPath)\n return\n }\n\n writeFileSync(lockPath, serializeLock(lock))\n}\n","import type { AgentType } from '../agent/index.ts'\nimport type { SkillInfo } from './lockfile.ts'\nimport { existsSync, readdirSync } from 'node:fs'\nimport { join } from 'pathe'\nimport { agents } from '../agent/index.ts'\nimport { readLocalDependencies } from '../sources/index.ts'\nimport { parsePackages, parseSkillFrontmatter, readLock } from './lockfile.ts'\nimport { getSharedSkillsDir, semverGt } from './shared.ts'\n\nexport interface SkillEntry {\n name: string\n dir: string\n agent: AgentType\n info: SkillInfo | null\n scope: 'local' | 'global'\n /** Original package name from package.json (e.g., @scope/pkg) */\n packageName?: string\n /** Latest version from package.json deps */\n latestVersion?: string\n}\n\nexport interface ProjectState {\n skills: SkillEntry[]\n deps: Map<string, string>\n missing: string[]\n outdated: SkillEntry[]\n synced: SkillEntry[]\n /** Skills in lockfile but not matched to any local dep */\n unmatched: SkillEntry[]\n}\n\nexport interface IterateSkillsOptions {\n scope?: 'local' | 'global' | 'all'\n agents?: AgentType[]\n cwd?: string\n}\n\nexport function* iterateSkills(opts: IterateSkillsOptions = {}): Generator<SkillEntry> {\n const { scope = 'all', cwd = process.cwd() } = opts\n const agentTypes = opts.agents ?? (Object.keys(agents) as AgentType[])\n\n // When shared dir exists, read local skills from there (avoid duplicates from agent symlinks)\n const sharedDir = getSharedSkillsDir(cwd)\n let yieldedLocal = false\n\n if (sharedDir && (scope === 'local' || scope === 'all')) {\n yieldedLocal = true\n const lock = readLock(sharedDir)\n const entries = readdirSync(sharedDir).filter(f => !f.startsWith('.') && f !== 'skilld-lock.yaml')\n // Use first detected agent as the representative\n const firstAgent = agentTypes[0] ?? (Object.keys(agents) as AgentType[])[0]!\n for (const name of entries) {\n const dir = join(sharedDir, name)\n if (lock?.skills[name]) {\n yield { name, dir, agent: firstAgent, info: lock.skills[name], scope: 'local' }\n }\n else {\n const info = parseSkillFrontmatter(join(dir, '.skilld', '_SKILL.md'))\n if (info?.generator === 'skilld') {\n yield { name, dir, agent: firstAgent, info, scope: 'local' }\n }\n }\n }\n }\n\n for (const agentType of agentTypes) {\n const agent = agents[agentType]\n\n // Local skills (skip if already yielded from shared dir)\n if (!yieldedLocal && (scope === 'local' || scope === 'all')) {\n const localDir = join(cwd, agent.skillsDir)\n if (existsSync(localDir)) {\n const lock = readLock(localDir)\n const entries = readdirSync(localDir).filter(f => !f.startsWith('.') && f !== 'skilld-lock.yaml')\n for (const name of entries) {\n const dir = join(localDir, name)\n // Only track skills in lockfile OR with generator: \"skilld\"\n if (lock?.skills[name]) {\n yield { name, dir, agent: agentType, info: lock.skills[name], scope: 'local' }\n }\n else {\n const info = parseSkillFrontmatter(join(dir, '.skilld', '_SKILL.md'))\n if (info?.generator === 'skilld') {\n yield { name, dir, agent: agentType, info, scope: 'local' }\n }\n }\n }\n }\n }\n\n // Global skills\n if ((scope === 'global' || scope === 'all') && agent.globalSkillsDir) {\n const globalDir = agent.globalSkillsDir\n if (existsSync(globalDir)) {\n const lock = readLock(globalDir)\n const entries = readdirSync(globalDir).filter(f => !f.startsWith('.') && f !== 'skilld-lock.yaml')\n for (const name of entries) {\n const dir = join(globalDir, name)\n // Only track skills in lockfile OR with generator: \"skilld\"\n if (lock?.skills[name]) {\n yield { name, dir, agent: agentType, info: lock.skills[name], scope: 'global' }\n }\n else {\n const info = parseSkillFrontmatter(join(dir, '.skilld', '_SKILL.md'))\n if (info?.generator === 'skilld') {\n yield { name, dir, agent: agentType, info, scope: 'global' }\n }\n }\n }\n }\n }\n }\n}\n\nexport function isOutdated(skill: SkillEntry, depVersion: string): boolean {\n if (!skill.info?.version)\n return true\n\n const depClean = depVersion.replace(/^[\\^~]/, '')\n\n return semverGt(depClean, skill.info.version)\n}\n\nexport async function getProjectState(cwd: string = process.cwd()): Promise<ProjectState> {\n const skills = [...iterateSkills({ scope: 'local', cwd })]\n\n // Get package.json deps\n const localDeps = await readLocalDependencies(cwd).catch(() => [])\n const deps = new Map(localDeps.map(d => [d.name, d.version]))\n\n // Build skill name -> entry map (for lookup by package name)\n const skillByName = new Map(skills.map(s => [s.name, s]))\n\n // Secondary lookup: packageName from lockfile (shipped skills have different names)\n // Also includes all packages from multi-package skills\n const skillByPkgName = new Map<string, SkillEntry>()\n for (const s of skills) {\n if (s.info?.packageName)\n skillByPkgName.set(s.info.packageName, s)\n for (const pkg of parsePackages(s.info?.packages))\n skillByPkgName.set(pkg.name, s)\n }\n\n const missing: string[] = []\n const outdated: SkillEntry[] = []\n const synced: SkillEntry[] = []\n const matchedSkillNames = new Set<string>()\n\n for (const [pkgName, version] of deps) {\n // Normalize package name (e.g., @scope/pkg -> scope-pkg)\n const normalizedName = pkgName.replace(/^@/, '').replace(/\\//g, '-')\n const skill = skillByName.get(`${normalizedName}-skilld`) || skillByName.get(normalizedName) || skillByName.get(pkgName) || skillByPkgName.get(pkgName)\n\n if (!skill) {\n missing.push(pkgName)\n }\n else {\n matchedSkillNames.add(skill.name)\n if (isOutdated(skill, version)) {\n outdated.push({ ...skill, packageName: pkgName, latestVersion: version })\n }\n else {\n synced.push({ ...skill, packageName: pkgName, latestVersion: version })\n }\n }\n }\n\n // Skills in lockfile but not matched to any local dep\n const unmatched = skills.filter(s => !matchedSkillNames.has(s.name))\n\n return { skills, deps, missing, outdated, synced, unmatched }\n}\n\nexport function getSkillsDir(agent: AgentType, scope: 'local' | 'global', cwd: string = process.cwd()): string {\n const agentConfig = agents[agent]\n if (scope === 'global') {\n if (!agentConfig.globalSkillsDir) {\n throw new Error(`Agent ${agent} does not support global skills`)\n }\n return agentConfig.globalSkillsDir\n }\n return getSharedSkillsDir(cwd) || join(cwd, agentConfig.skillsDir)\n}\n"],"mappings":"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"}
|