skilld 1.7.3 → 1.7.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.
Files changed (96) hide show
  1. package/dist/_chunks/agent.mjs +693 -599
  2. package/dist/_chunks/agent.mjs.map +1 -1
  3. package/dist/_chunks/assemble.mjs +3 -3
  4. package/dist/_chunks/author.mjs +51 -121
  5. package/dist/_chunks/author.mjs.map +1 -1
  6. package/dist/_chunks/cache.mjs +315 -9
  7. package/dist/_chunks/cache.mjs.map +1 -1
  8. package/dist/_chunks/cache2.mjs +2 -2
  9. package/dist/_chunks/cli-helpers.mjs +3 -3
  10. package/dist/_chunks/core.mjs +7 -4
  11. package/dist/_chunks/detect.mjs +1 -1
  12. package/dist/_chunks/embedding-cache2.mjs +2 -2
  13. package/dist/_chunks/index.d.mts +305 -112
  14. package/dist/_chunks/index.d.mts.map +1 -1
  15. package/dist/_chunks/index2.d.mts +267 -32
  16. package/dist/_chunks/index2.d.mts.map +1 -1
  17. package/dist/_chunks/index3.d.mts +32 -577
  18. package/dist/_chunks/index3.d.mts.map +1 -1
  19. package/dist/_chunks/index4.d.mts +553 -0
  20. package/dist/_chunks/index4.d.mts.map +1 -0
  21. package/dist/_chunks/install.mjs +48 -88
  22. package/dist/_chunks/install.mjs.map +1 -1
  23. package/dist/_chunks/list.mjs +1 -1
  24. package/dist/_chunks/lockfile.mjs +29 -6
  25. package/dist/_chunks/lockfile.mjs.map +1 -1
  26. package/dist/_chunks/monorepo.mjs +71 -0
  27. package/dist/_chunks/monorepo.mjs.map +1 -0
  28. package/dist/_chunks/{shared.mjs → package-registry.mjs} +2 -40
  29. package/dist/_chunks/package-registry.mjs.map +1 -0
  30. package/dist/_chunks/paths.mjs +49 -0
  31. package/dist/_chunks/paths.mjs.map +1 -0
  32. package/dist/_chunks/pool2.mjs +1 -1
  33. package/dist/_chunks/prepare.mjs +1 -1
  34. package/dist/_chunks/prepare2.mjs +5 -5
  35. package/dist/_chunks/prepare2.mjs.map +1 -1
  36. package/dist/_chunks/prompts.mjs +366 -18
  37. package/dist/_chunks/prompts.mjs.map +1 -1
  38. package/dist/_chunks/search-helpers.mjs +5 -6
  39. package/dist/_chunks/search-helpers.mjs.map +1 -1
  40. package/dist/_chunks/search-interactive.mjs +1 -1
  41. package/dist/_chunks/search.mjs +1 -2
  42. package/dist/_chunks/search.mjs.map +1 -1
  43. package/dist/_chunks/semver.mjs +13 -0
  44. package/dist/_chunks/semver.mjs.map +1 -0
  45. package/dist/_chunks/skill-installer.mjs +2 -0
  46. package/dist/_chunks/skill-installer2.mjs +155 -0
  47. package/dist/_chunks/skill-installer2.mjs.map +1 -0
  48. package/dist/_chunks/skills.mjs +10 -9
  49. package/dist/_chunks/skills.mjs.map +1 -1
  50. package/dist/_chunks/sources.mjs +549 -372
  51. package/dist/_chunks/sources.mjs.map +1 -1
  52. package/dist/_chunks/sync-pipeline.mjs +952 -0
  53. package/dist/_chunks/sync-pipeline.mjs.map +1 -0
  54. package/dist/_chunks/sync-registry.mjs +19 -13
  55. package/dist/_chunks/sync-registry.mjs.map +1 -1
  56. package/dist/_chunks/sync.mjs +797 -886
  57. package/dist/_chunks/sync.mjs.map +1 -1
  58. package/dist/_chunks/sync2.mjs +4 -2
  59. package/dist/_chunks/types.d.mts +65 -77
  60. package/dist/_chunks/types.d.mts.map +1 -1
  61. package/dist/_chunks/types2.d.mts +88 -0
  62. package/dist/_chunks/types2.d.mts.map +1 -0
  63. package/dist/_chunks/uninstall.mjs +7 -8
  64. package/dist/_chunks/uninstall.mjs.map +1 -1
  65. package/dist/_chunks/upload.mjs +2 -2
  66. package/dist/_chunks/validate.mjs +1 -1
  67. package/dist/_chunks/version.mjs +3 -13
  68. package/dist/_chunks/version.mjs.map +1 -1
  69. package/dist/_chunks/wizard.mjs +2 -2
  70. package/dist/agent/index.d.mts +2 -346
  71. package/dist/agent/index.mjs +2 -3
  72. package/dist/cache/index.d.mts +2 -2
  73. package/dist/cache/index.mjs +4 -3
  74. package/dist/cli.mjs +12 -13
  75. package/dist/cli.mjs.map +1 -1
  76. package/dist/index.d.mts +5 -4
  77. package/dist/index.mjs +4 -3
  78. package/dist/prepare.mjs +2 -2
  79. package/dist/prepare.mjs.map +1 -1
  80. package/dist/retriv/index.d.mts +2 -2
  81. package/dist/retriv/worker.d.mts +1 -1
  82. package/dist/sources/index.d.mts +3 -2
  83. package/dist/sources/index.mjs +3 -3
  84. package/dist/types.d.mts +3 -3
  85. package/package.json +2 -2
  86. package/dist/_chunks/config.mjs +0 -122
  87. package/dist/_chunks/config.mjs.map +0 -1
  88. package/dist/_chunks/prefix.mjs +0 -108
  89. package/dist/_chunks/prefix.mjs.map +0 -1
  90. package/dist/_chunks/shared.mjs.map +0 -1
  91. package/dist/_chunks/skill.mjs +0 -329
  92. package/dist/_chunks/skill.mjs.map +0 -1
  93. package/dist/_chunks/sync-shared.mjs +0 -2
  94. package/dist/_chunks/sync-shared2.mjs +0 -1020
  95. package/dist/_chunks/sync-shared2.mjs.map +0 -1
  96. package/dist/agent/index.d.mts.map +0 -1
@@ -1,28 +1,26 @@
1
- import { a as getModelLabel, i as getAvailableModels, s as optimizeDocs, t as detectImportedPackages } from "./agent.mjs";
2
- import { i as CACHE_DIR, r as getVersionKey, t as getCacheDir } from "./version.mjs";
3
- import { r as resolvePkgDir } from "./prepare.mjs";
1
+ import { d as getSharedSkillsDir, g as skillRefsSection, t as CACHE_DIR } from "./paths.mjs";
2
+ import { A as getActiveFeatures, M as hasCompletedWizard, P as readConfig, h as listReferenceFiles, i as ensureCacheDir, t as createReferenceCache } from "./cache.mjs";
3
+ import { r as getVersionKey } from "./version.mjs";
4
4
  import { n as sanitizeMarkdown } from "./sanitize.mjs";
5
- import { a as hasShippedDocs, d as linkPkgNamed, g as readCachedSection, i as getPkgKeyFiles, m as listReferenceFiles, r as ensureCacheDir, s as isCached } from "./cache.mjs";
6
5
  import { i as parseFrontmatter } from "./markdown.mjs";
7
- import { a as semverDiff, n as getSharedSkillsDir, t as SHARED_SKILLS_DIR } from "./shared.mjs";
8
- import { J as isPrerelease, M as resolveGitHubRepo, f as resolvePackageDocsWithAttempts, i as fetchPkgDist, it as parsePackageSpec, m as fetchGitSkills, nt as parseGitHubRepoSlug, p as searchNpmPackages, s as readLocalDependencies, w as resolveCrateDocsWithAttempts } from "./sources.mjs";
6
+ import { P as resolveGitHubRepo, a as toStoragePackageName, b as fetchGitSkills, dt as parsePackageSpec, i as resolveSkillName, l as fetchPkgDist, lt as parseGitHubRepoSlug, n as isCrateSpec, r as parseSkillInput, t as resolvePackageOrCrate, tt as isPrerelease, v as searchNpmPackages } from "./sources.mjs";
9
7
  import { a as targets } from "./detect.mjs";
10
- import { c as wrapSection, n as SECTION_OUTPUT_FILES, r as buildAllSectionPrompts, s as portabilizePrompt, t as SECTION_MERGE_ORDER } from "./prompts.mjs";
11
- import { _ as timedSpinner, i as computeSkillDirName, n as writeGeneratedSkillMd, o as linkSkillToAgents, r as writeSkillMd, s as sanitizeName, u as formatDuration, v as todayIsoDate } from "./skill.mjs";
12
- import { a as readConfig, o as registerProject, r as hasCompletedWizard, t as defaultFeatures } from "./config.mjs";
8
+ import { i as getModelLabel, n as detectImportedPackages } from "./agent.mjs";
9
+ import { C as portabilizePrompt, E as SECTION_OUTPUT_FILES, _ as timedSpinner, i as computeSkillDirName, n as writeGeneratedSkillMd, o as linkSkillToAgents, r as writeSkillMd, s as sanitizeName, u as formatDuration, v as todayIsoDate, y as buildAllSectionPrompts } from "./prompts.mjs";
13
10
  import { S as suggestPrepareHook, _ as promptForAgent, b as resolveAgent, f as introLine, o as getInstalledGenerators, p as isInteractive, x as sharedArgs } from "./cli-helpers.mjs";
14
- import { a as readLock, c as writeLock, n as parsePackageNames, o as removeLockEntry, r as parsePackages } from "./lockfile.mjs";
11
+ import { a as parsePackageNames, c as readLock, d as writeLock, n as findSkillDirByPackage } from "./lockfile.mjs";
12
+ import { t as semverDiff } from "./semver.mjs";
15
13
  import { t as getProjectState } from "./skills.mjs";
16
- import { n as resolveSkillName, r as toStoragePackageName, t as parseSkillInput } from "./prefix.mjs";
17
14
  import { t as runWizard } from "./wizard.mjs";
18
- import { S as writePromptFiles, _ as linkAllReferences, b as selectLlmConfig, c as ejectReferences, d as ensureGitignore, f as fetchAndCacheResources, g as indexResources, h as handleShippedSkills, l as enhanceSkillWithLLM, m as forceClearCache, n as RESOLVE_STEP_LABELS, o as detectChangelog, p as findRelatedSkills, t as DEFAULT_SECTIONS, u as ensureAgentInstructions, v as resolveBaseDir, y as resolveLocalDep } from "./sync-shared2.mjs";
15
+ import { a as ensureProjectFiles, c as linkShippedToAgents, i as ensureGitignore, l as resolveBaseDir, o as handleShippedSkills, s as installSkill } from "./skill-installer2.mjs";
16
+ import { a as prepareSkillReferences, c as resolveAutoModel, d as applyCachedSections, h as writePromptFiles, i as findRelatedSkills, l as selectLlmConfig, m as writeBaseSkill, p as runSkillEnhancement, r as fetchAndCacheResources, s as DEFAULT_SECTIONS, t as buildSkillContext } from "./sync-pipeline.mjs";
19
17
  import { n as shutdownWorker } from "./pool2.mjs";
18
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
20
19
  import { dirname, join, relative, resolve } from "pathe";
21
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
22
20
  import pLimit from "p-limit";
23
- import { isCI } from "std-env";
24
21
  import * as p from "@clack/prompts";
25
22
  import { defineCommand } from "citty";
23
+ import { isCI } from "std-env";
26
24
  import logUpdate from "log-update";
27
25
  const TELEMETRY_URL = "https://add-skill.vercel.sh/t";
28
26
  const SKILLS_VERSION = "1.3.9";
@@ -39,6 +37,374 @@ function track(data) {
39
37
  fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {});
40
38
  } catch {}
41
39
  }
40
+ const npmResolver = async (spec, opts) => {
41
+ const resolution = await resolvePackageOrCrate(spec, {
42
+ cwd: opts.cwd,
43
+ onProgress: (msg) => opts.onProgress(`${spec}: ${msg}`)
44
+ });
45
+ const { isCrate, packageName, identityPackageName, storagePackageName, requestedTag, localVersion, attempts, registryVersion } = resolution;
46
+ if (!resolution.resolved) {
47
+ const result = {
48
+ identityName: identityPackageName,
49
+ attempts,
50
+ registryVersion
51
+ };
52
+ if (!isCrate) {
53
+ const shipped = handleShippedSkills(packageName, localVersion || registryVersion || "latest", opts.cwd, opts.agent, opts.global);
54
+ if (shipped) result.shipped = shipped.shipped;
55
+ }
56
+ return result;
57
+ }
58
+ const resolved = resolution.resolved;
59
+ return {
60
+ identityName: identityPackageName,
61
+ storageName: storagePackageName,
62
+ version: isCrate ? resolved.version || requestedTag || "latest" : localVersion || resolved.version || "latest",
63
+ resolved,
64
+ kind: isCrate ? "crate" : "npm",
65
+ requestedTag,
66
+ localVersion
67
+ };
68
+ };
69
+ function createGithubResolver(owner, repo) {
70
+ return async (_spec, opts) => {
71
+ const resolved = await resolveGitHubRepo(owner, repo, (msg) => opts.onProgress(msg));
72
+ if (!resolved) return {
73
+ identityName: `${owner}-${repo}`,
74
+ attempts: [{
75
+ source: "github-meta",
76
+ status: "not-found",
77
+ message: `Could not find docs for ${owner}/${repo}`
78
+ }]
79
+ };
80
+ const repoUrl = `https://github.com/${owner}/${repo}`;
81
+ const name = `${owner}-${repo}`;
82
+ return {
83
+ identityName: name,
84
+ storageName: name,
85
+ version: resolved.version || "main",
86
+ resolved: {
87
+ ...resolved,
88
+ repoUrl
89
+ },
90
+ kind: "github"
91
+ };
92
+ };
93
+ }
94
+ const RATE_LIMIT_RE = /\b429\b|rate.?limit|exhausted.*capacity|quota.*reset/i;
95
+ async function runBaseSync(spec, config, ui, resolver, cwd, defaultSections) {
96
+ ui.resolveStart(spec);
97
+ const resolverResult = await resolver(spec, {
98
+ cwd,
99
+ agent: config.agent,
100
+ global: config.global,
101
+ onProgress: (msg) => ui.resolveProgress(msg)
102
+ });
103
+ if (!("resolved" in resolverResult)) {
104
+ if (resolverResult.shipped && resolverResult.shipped.length > 0) {
105
+ for (const s of resolverResult.shipped) ui.shippedInstalled(s.skillName, relative(cwd, s.skillDir));
106
+ return { kind: "shipped" };
107
+ }
108
+ ui.resolveFailed(resolverResult.identityName);
109
+ return {
110
+ kind: "unresolved",
111
+ unresolved: resolverResult
112
+ };
113
+ }
114
+ const { identityName, storageName, version, resolved, kind, requestedTag, localVersion } = resolverResult;
115
+ const cache = createReferenceCache(storageName, version);
116
+ if (config.force) cache.clearForce();
117
+ const useCache = cache.has();
118
+ if (kind !== "crate" && !existsSync(join(cwd, "node_modules", identityName))) {
119
+ ui.downloadingDist();
120
+ await fetchPkgDist(identityName, version);
121
+ }
122
+ if (kind !== "crate") {
123
+ const shipped = handleShippedSkills(identityName, version, cwd, config.agent, config.global);
124
+ if (shipped) {
125
+ linkShippedToAgents(shipped.shipped, cwd, config.agent, config.global);
126
+ for (const s of shipped.shipped) ui.shippedInstalled(s.skillName, relative(cwd, s.skillDir));
127
+ return { kind: "shipped" };
128
+ }
129
+ }
130
+ ui.resolveDone(version, {
131
+ cached: useCache,
132
+ force: config.force
133
+ });
134
+ if (kind === "npm" && !localVersion && !requestedTag && !isPrerelease(version)) {
135
+ const nextTag = resolved.distTags?.next ?? resolved.distTags?.beta ?? resolved.distTags?.alpha;
136
+ if (nextTag && (!resolved.releasedAt || !nextTag.releasedAt || nextTag.releasedAt > resolved.releasedAt)) ui.warn(`No local dependency found — using latest stable (${version}). Prerelease ${nextTag.version} available: skilld add ${identityName}@beta`);
137
+ }
138
+ cache.ensure();
139
+ const isEject = !!config.eject;
140
+ const baseDir = resolveBaseDir(cwd, config.agent, config.global);
141
+ let skillDirName = config.name ? sanitizeName(config.name) : computeSkillDirName(storageName);
142
+ if (config.mode === "update" && !config.name && !isEject) {
143
+ const lock = readLock(baseDir);
144
+ const found = lock ? findSkillDirByPackage(lock, identityName) : null;
145
+ if (found) skillDirName = found;
146
+ }
147
+ const skillDir = isEject ? typeof config.eject === "string" ? join(resolve(cwd, config.eject), skillDirName) : join(cwd, "skills", skillDirName) : join(baseDir, skillDirName);
148
+ mkdirSync(skillDir, { recursive: true });
149
+ const existingLock = isEject ? void 0 : readLock(baseDir)?.skills[skillDirName];
150
+ if (existingLock && existingLock.packageName && existingLock.packageName !== identityName) return {
151
+ kind: "merge-needed",
152
+ state: {
153
+ identityName,
154
+ storageName,
155
+ version,
156
+ resolved,
157
+ baseDir,
158
+ skillDir,
159
+ skillDirName,
160
+ existingLock
161
+ }
162
+ };
163
+ const updateCtx = config.mode === "update" && existingLock ? {
164
+ oldVersion: existingLock.version,
165
+ newVersion: version,
166
+ syncedAt: existingLock.syncedAt,
167
+ wasEnhanced: (() => {
168
+ const skillMdPath = join(skillDir, "SKILL.md");
169
+ if (!existsSync(skillMdPath)) return false;
170
+ return !!parseFrontmatter(readFileSync(skillMdPath, "utf-8")).generated_by;
171
+ })()
172
+ } : void 0;
173
+ const features = getActiveFeatures(config.noSearch ? { search: false } : void 0);
174
+ ui.fetchStart();
175
+ const resources = await fetchAndCacheResources({
176
+ packageName: storageName,
177
+ resolved,
178
+ version,
179
+ useCache,
180
+ features,
181
+ from: config.from,
182
+ onProgress: (msg) => ui.fetchProgress(msg)
183
+ });
184
+ const parts = [];
185
+ if (resources.docsToIndex.length > 0) {
186
+ const docCount = resources.docsToIndex.filter((d) => d.metadata?.type === "doc").length;
187
+ if (docCount > 0) parts.push(`${docCount} docs`);
188
+ }
189
+ if (resources.hasIssues) parts.push("issues");
190
+ if (resources.hasDiscussions) parts.push("discussions");
191
+ if (resources.hasReleases) parts.push("releases");
192
+ ui.fetchDone(parts, resources.usedCache);
193
+ for (const w of resources.warnings) ui.warn(w);
194
+ if (features.search) ui.indexStart();
195
+ const prepared = await prepareSkillReferences({
196
+ packageName: storageName,
197
+ version,
198
+ cwd,
199
+ skillDir,
200
+ resources,
201
+ features,
202
+ baseDir,
203
+ onIndexProgress: (msg) => ui.indexProgress(msg)
204
+ });
205
+ if (features.search) ui.indexDone();
206
+ if (!isEject) {
207
+ const repoSlug = parseGitHubRepoSlug(resolved.repoUrl);
208
+ cache.linkPkgNamed(skillDir, cwd);
209
+ const lock = {
210
+ packageName: identityName,
211
+ version,
212
+ repo: repoSlug,
213
+ source: resources.docSource,
214
+ syncedAt: todayIsoDate(),
215
+ generator: "skilld"
216
+ };
217
+ installSkill({
218
+ cwd,
219
+ agent: config.agent,
220
+ global: config.global,
221
+ baseDir,
222
+ skillDirName,
223
+ lock,
224
+ dedupePackageName: identityName,
225
+ skipLinkAgents: true
226
+ });
227
+ }
228
+ const allPackages = parsePackageNames((isEject ? void 0 : readLock(baseDir)?.skills[skillDirName])?.packages);
229
+ const ctx = buildSkillContext({
230
+ packageName: identityName,
231
+ cachePackageName: storageName,
232
+ version,
233
+ skillDir,
234
+ skillDirName,
235
+ resources,
236
+ prepared,
237
+ resolved,
238
+ packages: allPackages,
239
+ features
240
+ });
241
+ ctx.overheadLines = writeBaseSkill(ctx, { eject: isEject }).split("\n").length;
242
+ ui.baseDone(relative(cwd, skillDir), config.mode === "update" ? "update" : "add");
243
+ const allSectionsCached = !config.force && applyCachedSections(ctx, defaultSections, { eject: isEject });
244
+ if (allSectionsCached) ui.sectionsCached();
245
+ return {
246
+ kind: "ready",
247
+ state: {
248
+ ctx,
249
+ skillDir,
250
+ skillDirName,
251
+ baseDir,
252
+ updateCtx,
253
+ allSectionsCached,
254
+ identityName,
255
+ storageName,
256
+ version,
257
+ docsType: resources.docsType,
258
+ repoInfo: resources.repoInfo
259
+ }
260
+ };
261
+ }
262
+ async function runEnhancePhase(state, llmConfig, config, ui, cwd) {
263
+ const isEject = !!config.eject;
264
+ if (llmConfig?.promptOnly) writePromptFiles({
265
+ ...state.ctx,
266
+ packageName: state.ctx.cachePackageName ?? state.ctx.packageName,
267
+ cachePackageName: void 0
268
+ }, {
269
+ sections: llmConfig.sections,
270
+ customPrompt: llmConfig.customPrompt
271
+ });
272
+ else if (llmConfig) await enhanceWithUi(state.ctx, llmConfig, {
273
+ ...config,
274
+ eject: isEject
275
+ }, ui);
276
+ if (isEject) {
277
+ const cache = createReferenceCache(state.storageName, state.version);
278
+ if (!config.debug) cache.clearSkillInternal(state.skillDir);
279
+ cache.eject(state.skillDir, cwd, state.docsType, {
280
+ features: state.ctx.features ?? getActiveFeatures(),
281
+ repoInfo: state.repoInfo
282
+ });
283
+ return;
284
+ }
285
+ const shared = config.global ? false : getSharedSkillsDir(cwd) ?? false;
286
+ if (shared) linkSkillToAgents(state.skillDirName, shared, cwd, config.agent);
287
+ await ensureProjectFiles({
288
+ cwd,
289
+ agent: config.agent,
290
+ global: config.global,
291
+ shared
292
+ });
293
+ }
294
+ async function enhanceWithUi(ctx, llmConfig, config, ui) {
295
+ ui.llmStart(getModelLabel(llmConfig.model));
296
+ const result = await runSkillEnhancement(ctx, {
297
+ model: llmConfig.model,
298
+ force: config.force,
299
+ debug: config.debug,
300
+ sections: llmConfig.sections,
301
+ customPrompt: llmConfig.customPrompt,
302
+ eject: !!config.eject
303
+ }, (progress) => ui.llmProgress(progress));
304
+ if (result.wasOptimized) ui.llmDone({
305
+ usage: result.usage ? { totalTokens: result.usage.totalTokens } : void 0,
306
+ cost: result.cost,
307
+ debugLogsDir: result.debugLogsDir,
308
+ error: result.error,
309
+ warnings: result.warnings
310
+ });
311
+ else ui.llmFailed(result.error ?? "", { rateLimited: !!result.error && RATE_LIMIT_RE.test(result.error) });
312
+ }
313
+ function createClackUi({ cwd }) {
314
+ let spinner = null;
315
+ let resourceSpinner = null;
316
+ let indexSpinner = null;
317
+ let llmLog = null;
318
+ let currentSpec = "";
319
+ return {
320
+ resolveStart(spec) {
321
+ currentSpec = spec;
322
+ spinner = timedSpinner();
323
+ spinner.start(`Resolving ${spec}`);
324
+ },
325
+ resolveProgress(msg) {
326
+ spinner?.message(msg);
327
+ },
328
+ resolveDone(version, opts) {
329
+ const suffix = opts.force ? " (force)" : opts.cached ? " (cached)" : "";
330
+ spinner?.stop(`Resolved ${currentSpec}@${version}${suffix}`);
331
+ spinner = null;
332
+ },
333
+ resolveFailed(identityName) {
334
+ spinner?.stop(`Could not find docs for: ${identityName}`);
335
+ spinner = null;
336
+ },
337
+ downloadingDist() {
338
+ spinner?.message("Downloading dist");
339
+ },
340
+ fetchStart() {
341
+ resourceSpinner = timedSpinner();
342
+ resourceSpinner.start("Finding resources");
343
+ },
344
+ fetchProgress(msg) {
345
+ resourceSpinner?.message(msg);
346
+ },
347
+ fetchDone(parts, cached) {
348
+ const summary = parts.length > 0 ? parts.join(", ") : "resources";
349
+ resourceSpinner?.stop(cached ? `Loaded ${summary} (cached)` : `Fetched ${summary}`);
350
+ resourceSpinner = null;
351
+ },
352
+ indexStart() {
353
+ indexSpinner = timedSpinner();
354
+ indexSpinner.start("Creating search index");
355
+ },
356
+ indexProgress(msg) {
357
+ indexSpinner?.message(msg);
358
+ },
359
+ indexDone() {
360
+ indexSpinner?.stop("Search index ready");
361
+ indexSpinner = null;
362
+ },
363
+ warn(msg) {
364
+ p.log.warn(`\x1B[33m${msg}\x1B[0m`);
365
+ },
366
+ baseDone(relPath, mode) {
367
+ p.log.success(mode === "update" ? `Updated skill: ${relPath}` : `Created base skill: ${relPath}`);
368
+ },
369
+ sectionsCached() {
370
+ p.log.success("Applied cached SKILL.md sections");
371
+ },
372
+ llmStart(modelLabel) {
373
+ p.log.step(modelLabel);
374
+ llmLog = p.taskLog({
375
+ title: `Agent exploring ${currentSpec}`,
376
+ limit: 3
377
+ });
378
+ },
379
+ llmProgress(progress) {
380
+ if (!llmLog) return;
381
+ const sectionPrefix = progress.section ? `[${progress.section}] ` : "";
382
+ const line = progress.chunk.startsWith("[") ? `${sectionPrefix}${progress.chunk}` : `${sectionPrefix}${progress.chunk}`;
383
+ llmLog.message(line);
384
+ },
385
+ llmDone(info) {
386
+ if (!llmLog) return;
387
+ const parts = [];
388
+ if (info.usage) parts.push(`${Math.round(info.usage.totalTokens / 1e3)}k tokens`);
389
+ if (info.cost) parts.push(`$${info.cost.toFixed(2)}`);
390
+ const suffix = parts.length > 0 ? ` (${parts.join(", ")})` : "";
391
+ llmLog.success(`Generated best practices${suffix}`);
392
+ llmLog = null;
393
+ if (info.debugLogsDir) p.log.info(`Debug logs: ${relative(cwd, info.debugLogsDir)}`);
394
+ if (info.error) p.log.warn(`\x1B[33mPartial failure: ${info.error}\x1B[0m`);
395
+ if (info.warnings) for (const w of info.warnings) p.log.warn(`\x1B[33m${w}\x1B[0m`);
396
+ },
397
+ llmFailed(error, opts) {
398
+ if (!llmLog) return;
399
+ if (opts.rateLimited) llmLog.error(`Rate limited by LLM provider. Try again shortly or use a different model via \`skilld config\``);
400
+ else llmLog.error(`Enhancement failed${error ? `: ${error}` : ""}`);
401
+ llmLog = null;
402
+ },
403
+ shippedInstalled(skillName, relPath) {
404
+ p.log.success(`Using published SKILL.md: ${skillName} → ${relPath}`);
405
+ }
406
+ };
407
+ }
42
408
  async function syncGitSkills(opts) {
43
409
  const { source, agent, global: isGlobal, yes } = opts;
44
410
  const cwd = process.cwd();
@@ -93,16 +459,23 @@ async function syncGitSkills(opts) {
93
459
  writeFileSync(filePath, f.content);
94
460
  }
95
461
  const sourceType = source.type === "local" ? "local" : source.type;
96
- writeLock(baseDir, skill.name, {
97
- source: sourceType,
98
- repo: source.type === "local" ? source.localPath : `${source.owner}/${source.repo}`,
99
- path: skill.path || void 0,
100
- ref: source.ref || "main",
101
- syncedAt: todayIsoDate(),
102
- generator: "external"
462
+ installSkill({
463
+ cwd,
464
+ agent,
465
+ global: isGlobal,
466
+ baseDir,
467
+ skillDirName: skill.name,
468
+ lock: {
469
+ source: sourceType,
470
+ repo: source.type === "local" ? source.localPath : `${source.owner}/${source.repo}`,
471
+ path: skill.path || void 0,
472
+ ref: source.ref || "main",
473
+ syncedAt: todayIsoDate(),
474
+ generator: "external"
475
+ },
476
+ skipLinkAgents: true
103
477
  });
104
478
  }
105
- if (!isGlobal) registerProject(cwd);
106
479
  if (source.type !== "local" && source.owner && source.repo) track({
107
480
  event: "install",
108
481
  source: `${source.owner}/${source.repo}`,
@@ -111,156 +484,92 @@ async function syncGitSkills(opts) {
111
484
  ...isGlobal && { global: "1" },
112
485
  sourceType: source.type
113
486
  });
114
- const names = selected.map((s) => `\x1B[36m${s.name}\x1B[0m`).join(", ");
115
- p.log.success(`Installed ${names}`);
487
+ for (const skill of selected) {
488
+ const skillRel = relative(cwd, join(baseDir, skill.name));
489
+ const fileLines = ["SKILL.md", ...skill.files.map((f) => f.path)].map((f) => ` \x1B[90m└\x1B[0m ${f}`).join("\n");
490
+ p.log.success(`Installed \x1B[36m${skill.name}\x1B[0m \x1B[90m→ ${skillRel}\x1B[0m\n${fileLines}`);
491
+ }
116
492
  }
117
493
  async function syncGitHubRepo(opts) {
118
494
  const { source, agent, global: isGlobal, yes } = opts;
119
495
  const owner = source.owner;
120
496
  const repo = source.repo;
121
497
  const cwd = process.cwd();
122
- const spin = timedSpinner();
123
- spin.start(`Resolving ${owner}/${repo}`);
124
- const resolved = await resolveGitHubRepo(owner, repo, (msg) => spin.message(msg));
125
- if (!resolved) {
126
- spin.stop(`Could not find docs for ${owner}/${repo}`);
127
- return;
128
- }
129
- const repoUrl = `https://github.com/${owner}/${repo}`;
130
- const packageName = `${owner}-${repo}`;
131
- const version = resolved.version || "main";
132
- const versionKey = getVersionKey(version);
133
- const useCache = isCached(packageName, version);
134
- spin.stop(`Resolved ${owner}/${repo}@${useCache ? versionKey : version}${useCache ? " (cached)" : ""}`);
135
- ensureCacheDir();
136
- const baseDir = resolveBaseDir(cwd, agent, isGlobal);
137
- const skillDirName = sanitizeName(`${owner}-${repo}`);
138
- const skillDir = join(baseDir, skillDirName);
139
- mkdirSync(skillDir, { recursive: true });
140
- const features = readConfig().features ?? defaultFeatures;
141
- const resSpin = timedSpinner();
142
- resSpin.start("Finding resources");
143
- const resources = await fetchAndCacheResources({
144
- packageName,
145
- resolved,
146
- version,
147
- useCache,
148
- features,
149
- from: opts.from,
150
- onProgress: (msg) => resSpin.message(msg)
498
+ const ui = createClackUi({ cwd });
499
+ const spec = `${owner}/${repo}`;
500
+ const result = await runBaseSync(spec, {
501
+ agent,
502
+ global: isGlobal,
503
+ force: opts.force,
504
+ from: opts.from
505
+ }, ui, createGithubResolver(owner, repo), cwd, DEFAULT_SECTIONS);
506
+ if (result.kind !== "ready") return;
507
+ const { state } = result;
508
+ const globalConfig = readConfig();
509
+ let llmConfig = null;
510
+ if (!state.allSectionsCached && !globalConfig.skipLlm && (!yes || opts.model)) llmConfig = await selectLlmConfig(opts.model);
511
+ await runEnhancePhase(state, llmConfig, {
512
+ agent,
513
+ global: isGlobal,
514
+ force: opts.force,
515
+ debug: opts.debug
516
+ }, ui, cwd);
517
+ await shutdownWorker();
518
+ track({
519
+ event: "install",
520
+ source: spec,
521
+ skills: state.skillDirName,
522
+ agents: agent,
523
+ ...isGlobal && { global: "1" },
524
+ sourceType: "github-generated"
151
525
  });
152
- const resParts = [];
153
- if (resources.docsToIndex.length > 0) {
154
- const docCount = resources.docsToIndex.filter((d) => d.metadata?.type === "doc").length;
155
- if (docCount > 0) resParts.push(`${docCount} docs`);
156
- }
157
- if (resources.hasIssues) resParts.push("issues");
158
- if (resources.hasDiscussions) resParts.push("discussions");
159
- if (resources.hasReleases) resParts.push("releases");
160
- resSpin.stop(`Fetched ${resParts.length > 0 ? resParts.join(", ") : "resources"}`);
161
- for (const w of resources.warnings) p.log.warn(`\x1B[33m${w}\x1B[0m`);
162
- linkAllReferences(skillDir, packageName, cwd, version, resources.docsType, void 0, features);
163
- if (features.search) {
164
- const idxSpin = timedSpinner();
165
- idxSpin.start("Creating search index");
166
- await indexResources({
167
- packageName,
526
+ p.outro(`Synced ${spec} to ${relative(cwd, state.skillDir)}`);
527
+ }
528
+ async function handleMerge(state, config, cwd) {
529
+ const { identityName, storageName, version, resolved, baseDir, skillDir, skillDirName, existingLock } = state;
530
+ p.log.step(`Merging ${identityName} into ${skillDirName}`);
531
+ createReferenceCache(storageName, version).linkPkgNamed(skillDir, cwd);
532
+ const repoSlug = parseGitHubRepoSlug(resolved.repoUrl);
533
+ installSkill({
534
+ cwd,
535
+ agent: config.agent,
536
+ global: config.global,
537
+ baseDir,
538
+ skillDirName,
539
+ lock: {
540
+ packageName: identityName,
168
541
  version,
169
- cwd,
170
- docsToIndex: resources.docsToIndex,
171
- features,
172
- onProgress: (msg) => idxSpin.message(msg)
173
- });
174
- idxSpin.stop("Search index ready");
175
- }
176
- const hasChangelog = detectChangelog(resolvePkgDir(packageName, cwd, version), getCacheDir(packageName, version));
177
- const shippedDocs = hasShippedDocs(packageName, cwd, version);
178
- const pkgFiles = getPkgKeyFiles(packageName, cwd, version);
179
- writeLock(baseDir, skillDirName, {
180
- packageName,
181
- version,
182
- repo: `${owner}/${repo}`,
183
- source: resources.docSource,
184
- syncedAt: todayIsoDate(),
185
- generator: "skilld"
542
+ repo: repoSlug,
543
+ source: existingLock.source,
544
+ syncedAt: todayIsoDate(),
545
+ generator: "skilld"
546
+ },
547
+ skipLinkAgents: true
186
548
  });
549
+ const updatedLock = readLock(baseDir)?.skills[skillDirName];
550
+ const allPackages = parsePackageNames(updatedLock?.packages);
551
+ const relatedSkills = await findRelatedSkills(storageName, baseDir);
552
+ const existingCache = createReferenceCache(toStoragePackageName(existingLock.packageName), existingLock.version);
553
+ const pkgFiles = existingCache.keyFiles(cwd);
554
+ const shippedDocs = existingCache.hasShipped(cwd);
555
+ const features = getActiveFeatures();
187
556
  writeGeneratedSkillMd(skillDir, {
188
- name: packageName,
189
- version,
190
- releasedAt: resolved.releasedAt,
191
- description: resolved.description,
192
- relatedSkills: [],
193
- hasIssues: resources.hasIssues,
194
- hasDiscussions: resources.hasDiscussions,
195
- hasReleases: resources.hasReleases,
196
- hasChangelog,
197
- docsType: resources.docsType,
557
+ name: existingLock.packageName,
558
+ version: existingLock.version,
559
+ relatedSkills,
560
+ hasIssues: features.issues && existsSync(skillRefsSection(skillDir, "issues")),
561
+ hasDiscussions: features.discussions && existsSync(skillRefsSection(skillDir, "discussions")),
562
+ hasReleases: features.releases && existsSync(skillRefsSection(skillDir, "releases")),
563
+ docsType: existingLock.source?.includes("llms.txt") ? "llms.txt" : "docs",
198
564
  hasShippedDocs: shippedDocs,
199
565
  pkgFiles,
200
566
  dirName: skillDirName,
201
- repoUrl,
567
+ packages: allPackages,
202
568
  features
203
569
  });
204
- p.log.success(`Created base skill: ${relative(cwd, skillDir)}`);
205
- if (!readConfig().skipLlm && (!yes || opts.model)) {
206
- const llmConfig = await selectLlmConfig(opts.model);
207
- if (llmConfig?.promptOnly) writePromptFiles({
208
- packageName,
209
- skillDir,
210
- version,
211
- hasIssues: resources.hasIssues,
212
- hasDiscussions: resources.hasDiscussions,
213
- hasReleases: resources.hasReleases,
214
- hasChangelog,
215
- docsType: resources.docsType,
216
- hasShippedDocs: shippedDocs,
217
- pkgFiles,
218
- sections: llmConfig.sections,
219
- customPrompt: llmConfig.customPrompt,
220
- features
221
- });
222
- else if (llmConfig) {
223
- p.log.step(getModelLabel(llmConfig.model));
224
- await enhanceSkillWithLLM({
225
- packageName,
226
- version,
227
- skillDir,
228
- dirName: skillDirName,
229
- model: llmConfig.model,
230
- resolved,
231
- relatedSkills: [],
232
- hasIssues: resources.hasIssues,
233
- hasDiscussions: resources.hasDiscussions,
234
- hasReleases: resources.hasReleases,
235
- hasChangelog,
236
- docsType: resources.docsType,
237
- hasShippedDocs: shippedDocs,
238
- pkgFiles,
239
- force: opts.force,
240
- debug: opts.debug,
241
- sections: llmConfig.sections,
242
- customPrompt: llmConfig.customPrompt,
243
- features
244
- });
245
- }
246
- }
247
- const shared = !isGlobal && getSharedSkillsDir(cwd);
248
- if (shared) linkSkillToAgents(skillDirName, shared, cwd, agent);
249
- if (!isGlobal) {
250
- registerProject(cwd);
251
- await ensureGitignore(shared || targets[agent].skillsDir, cwd, isGlobal);
252
- await ensureAgentInstructions(agent, cwd, isGlobal);
253
- }
254
- await shutdownWorker();
255
- track({
256
- event: "install",
257
- source: `${owner}/${repo}`,
258
- skills: skillDirName,
259
- agents: agent,
260
- ...isGlobal && { global: "1" },
261
- sourceType: "github-generated"
262
- });
263
- p.outro(`Synced ${owner}/${repo} to ${relative(cwd, skillDir)}`);
570
+ const sharedDir = !config.global && getSharedSkillsDir(cwd);
571
+ if (sharedDir) linkSkillToAgents(skillDirName, sharedDir, cwd, config.agent);
572
+ p.outro(`Merged ${identityName} into ${skillDirName}`);
264
573
  }
265
574
  const STATUS_ICONS = {
266
575
  pending: "○",
@@ -284,196 +593,250 @@ const STATUS_COLORS = {
284
593
  done: "\x1B[32m",
285
594
  error: "\x1B[31m"
286
595
  };
287
- async function syncPackagesParallel(config) {
288
- const { packages, concurrency = 5 } = config;
289
- const agent = targets[config.agent];
290
- const states = /* @__PURE__ */ new Map();
291
- const cwd = process.cwd();
292
- for (const pkg of packages) states.set(pkg, {
293
- name: pkg,
294
- status: "pending",
295
- message: "Waiting..."
596
+ function renderParallel(r) {
597
+ const maxNameLen = Math.max(...[...r.states.keys()].map((n) => n.length), 20);
598
+ const lines = [...r.states.values()].map((s) => {
599
+ const icon = STATUS_ICONS[s.status];
600
+ const color = STATUS_COLORS[s.status];
601
+ const reset = "\x1B[0m";
602
+ const dim = "\x1B[90m";
603
+ const name = s.name.padEnd(maxNameLen);
604
+ const version = s.version ? `${dim}${s.version}${reset} ` : "";
605
+ const elapsed = (s.status === "done" || s.status === "error") && s.startedAt && s.completedAt ? ` ${dim}(${formatDuration(s.completedAt - s.startedAt)})${reset}` : "";
606
+ const preview = s.streamPreview ? ` ${dim}${s.streamPreview}${reset}` : "";
607
+ return ` ${color}${icon}${reset} ${name} ${version}${s.message}${elapsed}${preview}`;
296
608
  });
297
- function render() {
298
- const maxNameLen = Math.max(...packages.map((p) => p.length), 20);
299
- const lines = Array.from(states.values(), (s) => {
300
- const icon = STATUS_ICONS[s.status];
301
- const color = STATUS_COLORS[s.status];
302
- const reset = "\x1B[0m";
303
- const dim = "\x1B[90m";
304
- const name = s.name.padEnd(maxNameLen);
305
- const version = s.version ? `${dim}${s.version}${reset} ` : "";
306
- const elapsed = (s.status === "done" || s.status === "error") && s.startedAt && s.completedAt ? ` ${dim}(${formatDuration(s.completedAt - s.startedAt)})${reset}` : "";
307
- const preview = s.streamPreview ? ` ${dim}${s.streamPreview}${reset}` : "";
308
- return ` ${color}${icon}${reset} ${name} ${version}${s.message}${elapsed}${preview}`;
309
- });
310
- const doneCount = [...states.values()].filter((s) => s.status === "done").length;
311
- const errorCount = [...states.values()].filter((s) => s.status === "error").length;
312
- logUpdate(`\x1B[1m${config.mode === "update" ? "Updating" : "Syncing"} ${packages.length} packages\x1B[0m (${doneCount} done${errorCount > 0 ? `, ${errorCount} failed` : ""})\n` + lines.join("\n"));
313
- }
314
- function update(pkg, status, message, version) {
315
- const state = states.get(pkg);
609
+ const doneCount = [...r.states.values()].filter((s) => s.status === "done").length;
610
+ const errorCount = [...r.states.values()].filter((s) => s.status === "error").length;
611
+ logUpdate(`\x1B[1m${r.verb} ${r.total} packages\x1B[0m (${doneCount} done${errorCount > 0 ? `, ${errorCount} failed` : ""})\n` + lines.join("\n"));
612
+ }
613
+ function createParallelUi(name, render, version) {
614
+ const state = render.states.get(name);
615
+ if (!state) throw new Error(`createParallelUi: no state slot for "${name}"`);
616
+ function update(status, message, ver) {
316
617
  if (!state.startedAt && status !== "pending") state.startedAt = performance.now();
317
618
  if ((status === "done" || status === "error") && !state.completedAt) state.completedAt = performance.now();
318
619
  state.status = status;
319
620
  state.message = message;
320
621
  state.streamPreview = void 0;
321
- if (version) state.version = version;
322
- render();
622
+ if (ver) state.version = ver;
623
+ renderParallel(render);
323
624
  }
625
+ if (version) state.version = version;
626
+ return {
627
+ resolveStart() {
628
+ update("resolving", "Resolving...");
629
+ },
630
+ resolveProgress(msg) {
631
+ update("resolving", msg);
632
+ },
633
+ resolveDone(ver, opts) {
634
+ update("downloading", opts.cached ? "Using cache" : opts.force ? "Re-fetching docs..." : "Fetching docs...", ver);
635
+ },
636
+ resolveFailed(_identityName) {},
637
+ downloadingDist() {
638
+ update("downloading", "Downloading dist...");
639
+ },
640
+ fetchStart() {},
641
+ fetchProgress(msg) {
642
+ update("downloading", msg);
643
+ },
644
+ fetchDone(_parts, _cached) {
645
+ update("downloading", "Linking references...");
646
+ },
647
+ indexStart() {
648
+ update("embedding", "Indexing docs");
649
+ },
650
+ indexProgress(msg) {
651
+ update("embedding", msg);
652
+ },
653
+ indexDone() {},
654
+ warn(_msg) {},
655
+ baseDone(_relPath, mode) {
656
+ update("done", mode === "update" ? "Skill updated" : "Base skill created");
657
+ },
658
+ sectionsCached() {},
659
+ llmStart(modelLabel) {
660
+ update("generating", modelLabel);
661
+ },
662
+ llmProgress(progress) {
663
+ const status = progress.type === "reasoning" ? "exploring" : "generating";
664
+ const sectionPrefix = progress.section ? `[${progress.section}] ` : "";
665
+ update(status, progress.chunk.startsWith("[") ? `${sectionPrefix}${progress.chunk}` : `${sectionPrefix}${progress.chunk}`);
666
+ },
667
+ llmDone(_info) {
668
+ update("done", "Skill optimized");
669
+ },
670
+ llmFailed(error, _opts) {
671
+ update("error", error);
672
+ },
673
+ shippedInstalled(_skillName, _relPath) {
674
+ update("done", "Published SKILL.md");
675
+ }
676
+ };
677
+ }
678
+ const DIFF_RANK = {
679
+ major: 5,
680
+ premajor: 4,
681
+ minor: 3,
682
+ preminor: 2,
683
+ patch: 1,
684
+ prepatch: 1,
685
+ prerelease: 0
686
+ };
687
+ async function syncPackagesParallel(config) {
688
+ const { packages, concurrency = 5 } = config;
689
+ const cwd = process.cwd();
690
+ const states = /* @__PURE__ */ new Map();
691
+ for (const spec of packages) states.set(spec, {
692
+ name: spec,
693
+ status: "pending",
694
+ message: "Waiting..."
695
+ });
696
+ const render = {
697
+ states,
698
+ verb: config.mode === "update" ? "Updating" : "Syncing",
699
+ total: packages.length
700
+ };
324
701
  ensureCacheDir();
325
- render();
702
+ renderParallel(render);
326
703
  const limit = pLimit(concurrency);
327
- const skillData = /* @__PURE__ */ new Map();
328
- const baseResults = await Promise.allSettled(packages.map((pkg) => limit(() => syncBaseSkill(pkg, config, cwd, update))));
704
+ const baseConfig = {
705
+ agent: config.agent,
706
+ global: config.global,
707
+ mode: config.mode,
708
+ force: config.force
709
+ };
710
+ const baseResults = await Promise.allSettled(packages.map((spec) => limit(async () => {
711
+ const { name } = parsePackageSpec(spec);
712
+ return runBaseSync(spec, baseConfig, createParallelUi(name, render), npmResolver, cwd, DEFAULT_SECTIONS);
713
+ })));
329
714
  logUpdate.done();
330
- const successfulPkgs = [];
331
- const shippedPkgs = [];
715
+ const ready = [];
716
+ const shippedCount = [];
332
717
  const errors = [];
718
+ const aggregatedWarnings = [];
333
719
  for (let i = 0; i < baseResults.length; i++) {
720
+ const spec = packages[i];
334
721
  const r = baseResults[i];
335
- if (r.status === "fulfilled" && r.value !== "shipped") {
336
- successfulPkgs.push(packages[i]);
337
- skillData.set(packages[i], r.value);
338
- } else if (r.status === "fulfilled" && r.value === "shipped") shippedPkgs.push(packages[i]);
339
- else if (r.status === "rejected") {
722
+ if (r.status === "rejected") {
340
723
  const err = r.reason;
341
- const reason = err instanceof Error ? `${err.message}\n${err.stack}` : String(err);
724
+ const reason = err instanceof Error ? err.message : String(err);
725
+ const slot = states.get(spec);
726
+ if (slot) slot.status = "error";
342
727
  errors.push({
343
- pkg: packages[i],
728
+ spec,
344
729
  reason
345
730
  });
731
+ continue;
346
732
  }
347
- }
348
- const pastVerb = config.mode === "update" ? "Updated" : "Created";
349
- const skillMsg = `${pastVerb} ${successfulPkgs.length} base skills${shippedPkgs.length > 1 ? ` (Skipping ${shippedPkgs.length})` : ""}`;
350
- p.log.success(skillMsg);
351
- for (const [, data] of skillData) for (const w of data.warnings) p.log.warn(`\x1B[33m${w}\x1B[0m`);
352
- if (errors.length > 0) for (const { pkg, reason } of errors) p.log.error(` ${pkg}: ${reason}`);
353
- const cachedPkgs = [];
354
- if (!config.force) for (const pkg of successfulPkgs) {
355
- const data = skillData.get(pkg);
356
- const resolvedName = data.resolved.name;
357
- if (DEFAULT_SECTIONS.every((s) => {
358
- const outputFile = SECTION_OUTPUT_FILES[s];
359
- return readCachedSection(resolvedName, data.version, outputFile) !== null;
360
- })) {
361
- const skillDir = join(resolveBaseDir(cwd, config.agent, config.global), data.skillDirName);
362
- const cachedParts = [];
363
- for (const s of SECTION_MERGE_ORDER) {
364
- if (!DEFAULT_SECTIONS.includes(s)) continue;
365
- const outputFile = SECTION_OUTPUT_FILES[s];
366
- const content = readCachedSection(resolvedName, data.version, outputFile);
367
- if (content) cachedParts.push(wrapSection(s, content));
733
+ const result = r.value;
734
+ if (result.kind === "shipped") {
735
+ shippedCount.push(spec);
736
+ continue;
737
+ }
738
+ if (result.kind === "unresolved") {
739
+ const npmAttempt = result.unresolved.attempts.find((a) => a.source === "npm");
740
+ let reason;
741
+ if (npmAttempt?.status === "not-found") {
742
+ const suggestions = await searchNpmPackages(result.unresolved.identityName, 3);
743
+ const hint = suggestions.length > 0 ? ` (try: ${suggestions.map((s) => s.name).join(", ")})` : "";
744
+ reason = (npmAttempt.message || "Not on npm") + hint;
745
+ } else reason = result.unresolved.attempts.filter((a) => a.status !== "success").map((a) => a.message || a.source).join("; ") || "No docs found";
746
+ const slot = states.get(spec);
747
+ if (slot) {
748
+ slot.status = "error";
749
+ slot.message = reason;
368
750
  }
369
- const cachedBody = cachedParts.join("\n\n");
370
- writeGeneratedSkillMd(skillDir, {
371
- name: resolvedName,
372
- version: data.version,
373
- releasedAt: data.resolved.releasedAt,
374
- distTags: data.resolved.distTags,
375
- body: cachedBody,
376
- relatedSkills: data.relatedSkills,
377
- hasIssues: data.hasIssues,
378
- hasDiscussions: data.hasDiscussions,
379
- hasReleases: data.hasReleases,
380
- hasChangelog: data.hasChangelog,
381
- docsType: data.docsType,
382
- hasShippedDocs: data.shippedDocs,
383
- pkgFiles: data.pkgFiles,
384
- generatedBy: "cached",
385
- dirName: data.skillDirName,
386
- packages: data.packages,
387
- repoUrl: data.resolved.repoUrl,
388
- features: data.features
751
+ errors.push({
752
+ spec,
753
+ reason
389
754
  });
390
- cachedPkgs.push(pkg);
755
+ continue;
391
756
  }
757
+ if (result.kind === "merge-needed") {
758
+ errors.push({
759
+ spec,
760
+ reason: `Skill dir already holds ${result.state.existingLock.packageName} — run sequentially to merge`
761
+ });
762
+ continue;
763
+ }
764
+ ready.push({
765
+ spec,
766
+ state: result.state
767
+ });
392
768
  }
393
- const uncachedPkgs = successfulPkgs.filter((pkg) => !cachedPkgs.includes(pkg));
769
+ renderParallel(render);
770
+ logUpdate.done();
771
+ const pastVerb = config.mode === "update" ? "Updated" : "Created";
772
+ p.log.success(`${pastVerb} ${ready.length} base skills${shippedCount.length > 0 ? ` (${shippedCount.length} shipped)` : ""}`);
773
+ for (const w of aggregatedWarnings) p.log.warn(`\x1B[33m${w}\x1B[0m`);
774
+ for (const { spec, reason } of errors) p.log.error(` ${spec}: ${reason}`);
775
+ const cachedPkgs = [];
776
+ const uncached = [];
777
+ for (const r of ready) if (r.state.allSectionsCached) cachedPkgs.push(r.spec);
778
+ else uncached.push(r);
394
779
  if (cachedPkgs.length > 0) p.log.success(`Applied cached SKILL.md sections for ${cachedPkgs.join(", ")}`);
395
780
  const globalConfig = readConfig();
396
- let resolvedModel = config.model || (config.yes && !globalConfig.skipLlm ? globalConfig.model : void 0);
397
- if (!resolvedModel && config.yes && !globalConfig.skipLlm) {
398
- const { getAvailableModels } = await import("../agent/index.mjs");
399
- const available = await getAvailableModels();
400
- const auto = available.find((m) => m.recommended)?.id ?? available[0]?.id;
401
- if (auto) resolvedModel = auto;
402
- }
403
- if (uncachedPkgs.length > 0 && !globalConfig.skipLlm && !(config.yes && !resolvedModel)) {
404
- const DIFF_RANK = {
405
- major: 5,
406
- premajor: 4,
407
- minor: 3,
408
- preminor: 2,
409
- patch: 1,
410
- prepatch: 1,
411
- prerelease: 0
412
- };
413
- let parallelUpdateCtx;
414
- if (config.mode === "update") {
415
- let maxDiff = "";
416
- let allEnhanced = true;
417
- let anySyncedAt;
418
- for (const pkg of successfulPkgs) {
419
- const data = skillData.get(pkg);
420
- if (!data.wasEnhanced) allEnhanced = false;
421
- if (data.oldSyncedAt && (!anySyncedAt || data.oldSyncedAt < anySyncedAt)) anySyncedAt = data.oldSyncedAt;
422
- if (data.oldVersion) {
423
- const diff = semverDiff(data.oldVersion, data.version);
424
- if (diff && (DIFF_RANK[diff] ?? 0) > (DIFF_RANK[maxDiff] ?? -1)) maxDiff = diff;
781
+ const resolvedModel = await resolveAutoModel(config.model, config.yes);
782
+ if (uncached.length > 0 && !globalConfig.skipLlm && !(config.yes && !resolvedModel)) {
783
+ const llmConfig = await selectLlmConfig(resolvedModel, void 0, config.mode === "update" ? aggregateUpdateCtx(uncached) : void 0);
784
+ if (llmConfig?.promptOnly) {
785
+ for (const r of uncached) {
786
+ const slot = states.get(r.spec);
787
+ if (slot) {
788
+ slot.status = "done";
789
+ slot.message = "Prompts written";
425
790
  }
426
791
  }
427
- const first = skillData.get(successfulPkgs[0]);
428
- parallelUpdateCtx = {
429
- oldVersion: successfulPkgs.length === 1 ? first.oldVersion : void 0,
430
- newVersion: successfulPkgs.length === 1 ? first.version : void 0,
431
- syncedAt: anySyncedAt,
432
- wasEnhanced: allEnhanced,
433
- bumpType: maxDiff || void 0
434
- };
435
- }
436
- const llmConfig = await selectLlmConfig(resolvedModel, void 0, parallelUpdateCtx);
437
- if (llmConfig?.promptOnly) for (const pkg of uncachedPkgs) {
438
- const data = skillData.get(pkg);
439
- writePromptFiles({
440
- packageName: pkg,
441
- skillDir: join(resolveBaseDir(cwd, config.agent, config.global), data.skillDirName),
442
- version: data.version,
443
- hasIssues: data.hasIssues,
444
- hasDiscussions: data.hasDiscussions,
445
- hasReleases: data.hasReleases,
446
- hasChangelog: data.hasChangelog,
447
- docsType: data.docsType,
448
- hasShippedDocs: data.shippedDocs,
449
- pkgFiles: data.pkgFiles,
450
- sections: llmConfig.sections,
451
- customPrompt: llmConfig.customPrompt,
452
- features: data.features,
453
- overheadLines: data.overheadLines
454
- });
455
- }
456
- else if (llmConfig) {
792
+ renderParallel(render);
793
+ for (const r of uncached) {
794
+ const ui = createParallelUi(r.spec, render, getVersionKey(r.state.version));
795
+ await runEnhancePhase(r.state, llmConfig, {
796
+ agent: config.agent,
797
+ global: config.global,
798
+ force: config.force,
799
+ debug: config.debug
800
+ }, ui, cwd);
801
+ }
802
+ } else if (llmConfig) {
457
803
  p.log.step(getModelLabel(llmConfig.model));
458
- for (const pkg of uncachedPkgs) states.set(pkg, {
459
- name: pkg,
804
+ for (const r of uncached) states.set(r.spec, {
805
+ name: r.spec,
460
806
  status: "pending",
461
- message: "Waiting..."
807
+ message: "Waiting...",
808
+ version: getVersionKey(r.state.version)
462
809
  });
463
- render();
464
- const llmResults = await Promise.allSettled(uncachedPkgs.map((pkg) => limit(() => enhanceWithLLM(pkg, skillData.get(pkg), {
465
- ...config,
466
- model: llmConfig.model
467
- }, cwd, update, llmConfig.sections, llmConfig.customPrompt))));
810
+ renderParallel(render);
811
+ const llmResults = await Promise.allSettled(uncached.map((r) => limit(async () => {
812
+ const ui = createParallelUi(r.spec, render, getVersionKey(r.state.version));
813
+ await runEnhancePhase(r.state, llmConfig, {
814
+ agent: config.agent,
815
+ global: config.global,
816
+ force: config.force,
817
+ debug: config.debug
818
+ }, ui, cwd);
819
+ })));
468
820
  logUpdate.done();
469
- const llmSucceeded = llmResults.filter((r) => r.status === "fulfilled").length;
470
- p.log.success(`Enhanced ${llmSucceeded}/${uncachedPkgs.length} skills with LLM`);
821
+ const llmSucceeded = llmResults.filter((x) => x.status === "fulfilled").length;
822
+ p.log.success(`Enhanced ${llmSucceeded}/${uncached.length} skills with LLM`);
471
823
  }
824
+ } else for (const r of ready) {
825
+ const ui = createParallelUi(r.spec, render, getVersionKey(r.state.version));
826
+ await runEnhancePhase(r.state, null, {
827
+ agent: config.agent,
828
+ global: config.global,
829
+ force: config.force,
830
+ debug: config.debug
831
+ }, ui, cwd);
472
832
  }
473
- await ensureGitignore(getSharedSkillsDir(cwd) ? SHARED_SKILLS_DIR : agent.skillsDir, cwd, config.global);
474
- await ensureAgentInstructions(config.agent, cwd, config.global);
833
+ await ensureProjectFiles({
834
+ cwd,
835
+ agent: config.agent,
836
+ global: config.global
837
+ });
475
838
  await shutdownWorker();
476
- p.outro(`${pastVerb} ${successfulPkgs.length}/${packages.length} packages`);
839
+ p.outro(`${pastVerb} ${ready.length}/${packages.length} packages`);
477
840
  const { suggestPrepareHook } = await import("./cli-helpers2.mjs");
478
841
  try {
479
842
  await suggestPrepareHook(cwd);
@@ -481,213 +844,28 @@ async function syncPackagesParallel(config) {
481
844
  p.log.warn(`Failed to suggest prepare hook: ${err instanceof Error ? err.message : String(err)}`);
482
845
  }
483
846
  }
484
- async function syncBaseSkill(packageSpec, config, cwd, update) {
485
- const { name: packageName, tag: requestedTag } = parsePackageSpec(packageSpec);
486
- const localVersion = (await readLocalDependencies(cwd).catch(() => [])).find((d) => d.name === packageName)?.version;
487
- const { package: resolvedPkg, attempts, registryVersion } = await resolvePackageDocsWithAttempts(requestedTag ? packageSpec : packageName, {
488
- version: localVersion,
489
- cwd,
490
- onProgress: (step) => update(packageName, "resolving", RESOLVE_STEP_LABELS[step])
491
- });
492
- let resolved = resolvedPkg;
493
- if (!resolved) {
494
- update(packageName, "resolving", "Local package...");
495
- resolved = await resolveLocalDep(packageName, cwd);
496
- }
497
- if (!resolved) {
498
- const shippedVersion = localVersion || registryVersion || "latest";
499
- const earlyShipped = handleShippedSkills(packageName, shippedVersion, cwd, config.agent, config.global);
500
- if (earlyShipped) {
501
- const shared = !config.global && getSharedSkillsDir(cwd);
502
- if (shared) for (const shipped of earlyShipped.shipped) linkSkillToAgents(shipped.skillName, shared, cwd, config.agent);
503
- update(packageName, "done", "Published SKILL.md", getVersionKey(shippedVersion));
504
- return "shipped";
847
+ function aggregateUpdateCtx(ready) {
848
+ let maxDiff = "";
849
+ let allEnhanced = true;
850
+ let anySyncedAt;
851
+ for (const r of ready) {
852
+ const u = r.state.updateCtx;
853
+ if (!u?.wasEnhanced) allEnhanced = false;
854
+ if (u?.syncedAt && (!anySyncedAt || u.syncedAt < anySyncedAt)) anySyncedAt = u.syncedAt;
855
+ if (u?.oldVersion && u.newVersion) {
856
+ const diff = semverDiff(u.oldVersion, u.newVersion);
857
+ if (diff && (DIFF_RANK[diff] ?? 0) > (DIFF_RANK[maxDiff] ?? -1)) maxDiff = diff;
505
858
  }
506
- const npmAttempt = attempts.find((a) => a.source === "npm");
507
- let reason;
508
- if (npmAttempt?.status === "not-found") {
509
- const suggestions = await searchNpmPackages(packageName, 3);
510
- const hint = suggestions.length > 0 ? ` (try: ${suggestions.map((s) => s.name).join(", ")})` : "";
511
- reason = (npmAttempt.message || "Not on npm") + hint;
512
- } else reason = attempts.filter((a) => a.status !== "success").map((a) => a.message || a.source).join("; ") || "No docs found";
513
- update(packageName, "error", reason);
514
- throw new Error(`Could not find docs for: ${packageName}`);
515
- }
516
- const version = localVersion || resolved.version || "latest";
517
- const versionKey = getVersionKey(version);
518
- if (!existsSync(join(cwd, "node_modules", packageName))) {
519
- update(packageName, "downloading", "Downloading dist...", versionKey);
520
- await fetchPkgDist(packageName, version);
521
- }
522
- const shippedResult = handleShippedSkills(packageName, version, cwd, config.agent, config.global);
523
- if (shippedResult) {
524
- const shared = !config.global && getSharedSkillsDir(cwd);
525
- if (shared) for (const shipped of shippedResult.shipped) linkSkillToAgents(shipped.skillName, shared, cwd, config.agent);
526
- update(packageName, "done", "Published SKILL.md", versionKey);
527
- return "shipped";
528
- }
529
- if (config.force) forceClearCache(packageName, version);
530
- const useCache = isCached(packageName, version);
531
- if (useCache) update(packageName, "downloading", "Using cache", versionKey);
532
- else update(packageName, "downloading", config.force ? "Re-fetching docs..." : "Fetching docs...", versionKey);
533
- const baseDir = resolveBaseDir(cwd, config.agent, config.global);
534
- let skillDirName = computeSkillDirName(packageName);
535
- if (config.mode === "update") {
536
- const lock = readLock(baseDir);
537
- if (lock) {
538
- for (const [name, info] of Object.entries(lock.skills)) if (info.packageName === packageName || parsePackages(info.packages).some((p) => p.name === packageName)) {
539
- skillDirName = name;
540
- break;
541
- }
542
- }
543
- }
544
- const skillDir = join(baseDir, skillDirName);
545
- mkdirSync(skillDir, { recursive: true });
546
- const preLock = config.mode === "update" ? readLock(baseDir)?.skills[skillDirName] : void 0;
547
- const preEnhanced = (() => {
548
- if (!preLock) return false;
549
- const skillMdPath = join(skillDir, "SKILL.md");
550
- if (!existsSync(skillMdPath)) return false;
551
- return !!parseFrontmatter(readFileSync(skillMdPath, "utf-8")).generated_by;
552
- })();
553
- const features = readConfig().features ?? defaultFeatures;
554
- const resources = await fetchAndCacheResources({
555
- packageName,
556
- resolved,
557
- version,
558
- useCache,
559
- features,
560
- onProgress: (msg) => update(packageName, "downloading", msg, versionKey)
561
- });
562
- update(packageName, "downloading", "Linking references...", versionKey);
563
- linkAllReferences(skillDir, packageName, cwd, version, resources.docsType, void 0, features, resources.repoInfo);
564
- if (features.search) {
565
- update(packageName, "embedding", "Indexing docs", versionKey);
566
- await indexResources({
567
- packageName,
568
- version,
569
- cwd,
570
- docsToIndex: resources.docsToIndex,
571
- features,
572
- onProgress: (msg) => update(packageName, "embedding", msg, versionKey)
573
- });
574
859
  }
575
- const hasChangelog = detectChangelog(resolvePkgDir(packageName, cwd, version), getCacheDir(packageName, version));
576
- const relatedSkills = await findRelatedSkills(packageName, baseDir);
577
- const shippedDocs = hasShippedDocs(packageName, cwd, version);
578
- const pkgFiles = getPkgKeyFiles(packageName, cwd, version);
579
- const repoSlug = parseGitHubRepoSlug(resolved.repoUrl);
580
- linkPkgNamed(skillDir, packageName, cwd, version);
581
- writeLock(baseDir, skillDirName, {
582
- packageName,
583
- version,
584
- repo: repoSlug,
585
- source: resources.docSource,
586
- syncedAt: todayIsoDate(),
587
- generator: "skilld"
588
- });
589
- const updatedLock = readLock(baseDir)?.skills[skillDirName];
590
- const allPackages = parsePackageNames(updatedLock?.packages);
591
- const overheadLines = writeGeneratedSkillMd(skillDir, {
592
- name: packageName,
593
- version,
594
- releasedAt: resolved.releasedAt,
595
- description: resolved.description,
596
- distTags: resolved.distTags,
597
- relatedSkills,
598
- hasIssues: resources.hasIssues,
599
- hasDiscussions: resources.hasDiscussions,
600
- hasReleases: resources.hasReleases,
601
- hasChangelog,
602
- docsType: resources.docsType,
603
- hasShippedDocs: shippedDocs,
604
- pkgFiles,
605
- dirName: skillDirName,
606
- packages: allPackages.length > 1 ? allPackages : void 0,
607
- repoUrl: resolved.repoUrl,
608
- features
609
- }).split("\n").length;
610
- const shared = !config.global && getSharedSkillsDir(cwd);
611
- if (shared) linkSkillToAgents(skillDirName, shared, cwd, config.agent);
612
- if (!config.global) registerProject(cwd);
613
- update(packageName, "done", config.mode === "update" ? "Skill updated" : "Base skill created", versionKey);
860
+ const first = ready[0]?.state.updateCtx;
614
861
  return {
615
- resolved,
616
- version,
617
- skillDirName,
618
- docsType: resources.docsType,
619
- hasIssues: resources.hasIssues,
620
- hasDiscussions: resources.hasDiscussions,
621
- hasReleases: resources.hasReleases,
622
- hasChangelog,
623
- shippedDocs,
624
- pkgFiles,
625
- relatedSkills,
626
- packages: allPackages.length > 1 ? allPackages : void 0,
627
- warnings: resources.warnings,
628
- features,
629
- usedCache: resources.usedCache,
630
- oldVersion: preLock?.version,
631
- oldSyncedAt: preLock?.syncedAt,
632
- wasEnhanced: preEnhanced,
633
- overheadLines
862
+ oldVersion: ready.length === 1 ? first?.oldVersion : void 0,
863
+ newVersion: ready.length === 1 ? first?.newVersion : void 0,
864
+ syncedAt: anySyncedAt,
865
+ wasEnhanced: allEnhanced,
866
+ bumpType: maxDiff || void 0
634
867
  };
635
868
  }
636
- async function enhanceWithLLM(packageName, data, config, cwd, update, sections, customPrompt) {
637
- const versionKey = getVersionKey(data.version);
638
- const skillDir = join(resolveBaseDir(cwd, config.agent, config.global), data.skillDirName);
639
- const hasGithub = data.hasIssues || data.hasDiscussions;
640
- const docFiles = listReferenceFiles(skillDir);
641
- update(packageName, "generating", config.model, versionKey);
642
- const { optimized, wasOptimized, error } = await optimizeDocs({
643
- packageName,
644
- skillDir,
645
- model: config.model,
646
- version: data.version,
647
- hasGithub,
648
- hasReleases: data.hasReleases,
649
- hasChangelog: data.hasChangelog,
650
- docFiles,
651
- docsType: data.docsType,
652
- hasShippedDocs: data.shippedDocs,
653
- noCache: config.force,
654
- debug: config.debug,
655
- sections,
656
- customPrompt,
657
- features: data.features,
658
- pkgFiles: data.pkgFiles,
659
- overheadLines: data.overheadLines,
660
- onProgress: (progress) => {
661
- const status = progress.type === "reasoning" ? "exploring" : "generating";
662
- const sectionPrefix = progress.section ? `[${progress.section}] ` : "";
663
- update(packageName, status, progress.chunk.startsWith("[") ? `${sectionPrefix}${progress.chunk}` : `${sectionPrefix}${config.model}`, versionKey);
664
- }
665
- });
666
- if (error) {
667
- update(packageName, "error", error, versionKey);
668
- throw new Error(error);
669
- }
670
- if (wasOptimized) writeGeneratedSkillMd(skillDir, {
671
- name: packageName,
672
- version: data.version,
673
- releasedAt: data.resolved.releasedAt,
674
- distTags: data.resolved.distTags,
675
- body: optimized,
676
- relatedSkills: data.relatedSkills,
677
- hasIssues: data.hasIssues,
678
- hasDiscussions: data.hasDiscussions,
679
- hasReleases: data.hasReleases,
680
- hasChangelog: data.hasChangelog,
681
- docsType: data.docsType,
682
- hasShippedDocs: data.shippedDocs,
683
- pkgFiles: data.pkgFiles,
684
- dirName: data.skillDirName,
685
- packages: data.packages,
686
- repoUrl: data.resolved.repoUrl,
687
- features: data.features
688
- });
689
- update(packageName, "done", "Skill optimized", versionKey);
690
- }
691
869
  const RESOLVE_SOURCE_LABELS = {
692
870
  "npm": "npm registry",
693
871
  "github-docs": "GitHub versioned docs",
@@ -708,12 +886,6 @@ function showResolveAttempts(attempts) {
708
886
  p.log.message(` ${icon} ${source}${msg}`);
709
887
  }
710
888
  }
711
- function isCrateSpec(spec) {
712
- return spec.startsWith("crate:");
713
- }
714
- function toCrateIdentity(crateName) {
715
- return `crate:${crateName}`;
716
- }
717
889
  async function syncCommand(state, opts) {
718
890
  if (opts.packages && opts.packages.length > 0) {
719
891
  const crateSpecs = opts.packages.filter(isCrateSpec);
@@ -805,52 +977,30 @@ async function pickFromList(packages, state) {
805
977
  }
806
978
  return selected;
807
979
  }
808
- async function syncSinglePackage(packageSpec, config) {
809
- const isCrate = isCrateSpec(packageSpec);
810
- const normalizedSpec = isCrate ? packageSpec.slice(6).trim() : packageSpec;
811
- if (isCrate && !normalizedSpec) {
812
- p.log.error("Invalid crate spec. Use format: crate:<name>");
813
- return;
814
- }
815
- const { name: parsedName, tag: requestedTag } = parsePackageSpec(normalizedSpec);
816
- const packageName = isCrate ? parsedName.toLowerCase() : parsedName;
817
- const identityPackageName = isCrate ? toCrateIdentity(packageName) : packageName;
818
- const storagePackageName = toStoragePackageName(identityPackageName);
819
- const spin = timedSpinner();
820
- spin.start(`Resolving ${packageSpec}`);
980
+ async function runSimpleSync(packageSpec, config) {
821
981
  const cwd = process.cwd();
822
- const localDeps = isCrate ? [] : await readLocalDependencies(cwd).catch(() => []);
823
- const localVersion = isCrate ? void 0 : localDeps.find((d) => d.name === packageName)?.version;
824
- const resolveResult = isCrate ? await resolveCrateDocsWithAttempts(packageName, {
825
- version: requestedTag,
826
- onProgress: (step) => spin.message(`${identityPackageName}: ${step}`)
827
- }) : await resolvePackageDocsWithAttempts(requestedTag ? normalizedSpec : packageName, {
828
- version: localVersion,
829
- cwd,
830
- onProgress: (step) => spin.message(`${packageName}: ${RESOLVE_STEP_LABELS[step]}`)
831
- });
832
- let resolved = resolveResult.package;
833
- if (!resolved && !isCrate) {
834
- spin.message(`Resolving local package: ${packageName}`);
835
- resolved = await resolveLocalDep(packageName, cwd);
982
+ const ui = createClackUi({ cwd });
983
+ const isEject = !!config.eject;
984
+ const result = await runBaseSync(packageSpec, {
985
+ agent: config.agent,
986
+ global: config.global,
987
+ mode: config.mode,
988
+ force: config.force,
989
+ noSearch: config.noSearch,
990
+ name: config.name,
991
+ from: config.from,
992
+ eject: config.eject
993
+ }, ui, npmResolver, cwd, DEFAULT_SECTIONS);
994
+ if (result.kind === "shipped") {
995
+ p.outro(`Synced ${packageSpec}`);
996
+ return;
836
997
  }
837
- if (!resolved) {
838
- if (!isCrate) {
839
- const earlyShipped = handleShippedSkills(packageName, localVersion || resolveResult.registryVersion || "latest", cwd, config.agent, config.global);
840
- if (earlyShipped) {
841
- const shared = !config.global && getSharedSkillsDir(cwd);
842
- for (const shipped of earlyShipped.shipped) {
843
- if (shared) linkSkillToAgents(shipped.skillName, shared, cwd, config.agent);
844
- p.log.success(`Using published SKILL.md: ${shipped.skillName} → ${relative(cwd, shipped.skillDir)}`);
845
- }
846
- spin.stop(`Using published SKILL.md(s) from ${packageName}`);
847
- return;
848
- }
849
- spin.message(`Searching npm for "${packageName}"...`);
850
- const suggestions = await searchNpmPackages(packageName);
998
+ if (result.kind === "unresolved") {
999
+ const { unresolved } = result;
1000
+ if (!isCrateSpec(packageSpec)) {
1001
+ const suggestions = await searchNpmPackages(unresolved.identityName);
851
1002
  if (suggestions.length > 0) {
852
- spin.stop(`Package "${packageName}" not found on npm`);
853
- showResolveAttempts(resolveResult.attempts);
1003
+ showResolveAttempts(unresolved.attempts);
854
1004
  const selected = await p.select({
855
1005
  message: "Did you mean one of these?",
856
1006
  options: [...suggestions.map((s) => ({
@@ -866,299 +1016,45 @@ async function syncSinglePackage(packageSpec, config) {
866
1016
  return;
867
1017
  }
868
1018
  }
869
- spin.stop(`Could not find docs for: ${identityPackageName}`);
870
- showResolveAttempts(resolveResult.attempts);
871
- return;
872
- }
873
- const version = isCrate ? resolved.version || requestedTag || "latest" : localVersion || resolved.version || "latest";
874
- const versionKey = getVersionKey(version);
875
- if (config.force) forceClearCache(storagePackageName, version);
876
- const useCache = isCached(storagePackageName, version);
877
- if (!isCrate && !existsSync(join(cwd, "node_modules", packageName))) {
878
- spin.message(`Downloading ${packageName}@${version} dist`);
879
- await fetchPkgDist(packageName, version);
880
- }
881
- const shippedResult = isCrate ? null : handleShippedSkills(packageName, version, cwd, config.agent, config.global);
882
- if (shippedResult) {
883
- const shared = !config.global && getSharedSkillsDir(cwd);
884
- for (const shipped of shippedResult.shipped) {
885
- if (shared) linkSkillToAgents(shipped.skillName, shared, cwd, config.agent);
886
- p.log.success(`Using published SKILL.md: ${shipped.skillName} → ${relative(cwd, shipped.skillDir)}`);
887
- }
888
- spin.stop(`Using published SKILL.md(s) from ${packageName}`);
1019
+ showResolveAttempts(unresolved.attempts);
889
1020
  return;
890
1021
  }
891
- spin.stop(`Resolved ${identityPackageName}@${useCache ? versionKey : version}${config.force ? " (force)" : useCache ? " (cached)" : ""}`);
892
- if (!isCrate && !localVersion && !requestedTag && !isPrerelease(version)) {
893
- const nextTag = resolved.distTags?.next ?? resolved.distTags?.beta ?? resolved.distTags?.alpha;
894
- if (nextTag && (!resolved.releasedAt || !nextTag.releasedAt || nextTag.releasedAt > resolved.releasedAt)) p.log.warn(`\x1B[33mNo local dependency found — using latest stable (${version}). Prerelease ${nextTag.version} available: skilld add ${packageName}@beta\x1B[0m`);
895
- }
896
- ensureCacheDir();
897
- const baseDir = resolveBaseDir(cwd, config.agent, config.global);
898
- let skillDirName = config.name ? sanitizeName(config.name) : computeSkillDirName(storagePackageName);
899
- if (config.mode === "update" && !config.name) {
900
- const lock = readLock(baseDir);
901
- if (lock) {
902
- for (const [name, info] of Object.entries(lock.skills)) if (info.packageName === identityPackageName || parsePackages(info.packages).some((p) => p.name === identityPackageName)) {
903
- skillDirName = name;
904
- break;
905
- }
906
- }
907
- }
908
- const skillDir = config.eject ? typeof config.eject === "string" ? join(resolve(cwd, config.eject), skillDirName) : join(cwd, "skills", skillDirName) : join(baseDir, skillDirName);
909
- mkdirSync(skillDir, { recursive: true });
910
- const existingLock = config.eject ? void 0 : readLock(baseDir)?.skills[skillDirName];
911
- const isMerge = existingLock && existingLock.packageName && existingLock.packageName !== identityPackageName;
912
- const updateCtx = config.mode === "update" && existingLock ? {
913
- oldVersion: existingLock.version,
914
- newVersion: version,
915
- syncedAt: existingLock.syncedAt,
916
- wasEnhanced: (() => {
917
- const skillMdPath = join(skillDir, "SKILL.md");
918
- if (!existsSync(skillMdPath)) return false;
919
- return !!parseFrontmatter(readFileSync(skillMdPath, "utf-8")).generated_by;
920
- })()
921
- } : void 0;
922
- if (isMerge) {
923
- spin.stop(`Merging ${identityPackageName} into ${skillDirName}`);
924
- linkPkgNamed(skillDir, storagePackageName, cwd, version);
925
- const repoSlug = parseGitHubRepoSlug(resolved.repoUrl);
926
- writeLock(baseDir, skillDirName, {
927
- packageName: identityPackageName,
928
- version,
929
- repo: repoSlug,
930
- source: existingLock.source,
931
- syncedAt: todayIsoDate(),
932
- generator: "skilld"
933
- });
934
- const updatedLock = readLock(baseDir)?.skills[skillDirName];
935
- const allPackages = parsePackageNames(updatedLock?.packages);
936
- const relatedSkills = await findRelatedSkills(storagePackageName, baseDir);
937
- const existingStorageName = toStoragePackageName(existingLock.packageName);
938
- const pkgFiles = getPkgKeyFiles(existingStorageName, cwd, existingLock.version);
939
- const shippedDocs = hasShippedDocs(existingStorageName, cwd, existingLock.version);
940
- const mergeFeatures = readConfig().features ?? defaultFeatures;
941
- writeGeneratedSkillMd(skillDir, {
942
- name: existingLock.packageName,
943
- version: existingLock.version,
944
- relatedSkills,
945
- hasIssues: mergeFeatures.issues && existsSync(join(skillDir, ".skilld", "issues")),
946
- hasDiscussions: mergeFeatures.discussions && existsSync(join(skillDir, ".skilld", "discussions")),
947
- hasReleases: mergeFeatures.releases && existsSync(join(skillDir, ".skilld", "releases")),
948
- docsType: existingLock.source?.includes("llms.txt") ? "llms.txt" : "docs",
949
- hasShippedDocs: shippedDocs,
950
- pkgFiles,
951
- dirName: skillDirName,
952
- packages: allPackages,
953
- features: mergeFeatures
954
- });
955
- const mergeShared = !config.global && getSharedSkillsDir(cwd);
956
- if (mergeShared) linkSkillToAgents(skillDirName, mergeShared, cwd, config.agent);
957
- if (!config.global) registerProject(cwd);
958
- p.outro(`Merged ${identityPackageName} into ${skillDirName}`);
1022
+ if (result.kind === "merge-needed") {
1023
+ await handleMerge(result.state, {
1024
+ agent: config.agent,
1025
+ global: config.global
1026
+ }, cwd);
959
1027
  return;
960
1028
  }
961
- const features = { ...readConfig().features ?? defaultFeatures };
962
- if (config.noSearch) features.search = false;
963
- const resSpin = timedSpinner();
964
- resSpin.start("Finding resources");
965
- const resources = await fetchAndCacheResources({
966
- packageName: storagePackageName,
967
- resolved,
968
- version,
969
- useCache,
970
- features,
971
- from: config.from,
972
- onProgress: (msg) => resSpin.message(msg)
973
- });
974
- const resParts = [];
975
- if (resources.docsToIndex.length > 0) {
976
- const docCount = resources.docsToIndex.filter((d) => d.metadata?.type === "doc").length;
977
- if (docCount > 0) resParts.push(`${docCount} docs`);
978
- }
979
- if (resources.hasIssues) resParts.push("issues");
980
- if (resources.hasDiscussions) resParts.push("discussions");
981
- if (resources.hasReleases) resParts.push("releases");
982
- resSpin.stop(resources.usedCache ? `Loaded ${resParts.length > 0 ? resParts.join(", ") : "resources"} (cached)` : `Fetched ${resParts.length > 0 ? resParts.join(", ") : "resources"}`);
983
- for (const w of resources.warnings) p.log.warn(`\x1B[33m${w}\x1B[0m`);
984
- linkAllReferences(skillDir, storagePackageName, cwd, version, resources.docsType, void 0, features, resources.repoInfo);
985
- if (features.search) {
986
- const idxSpin = timedSpinner();
987
- idxSpin.start("Creating search index");
988
- await indexResources({
989
- packageName: storagePackageName,
990
- version,
991
- cwd,
992
- docsToIndex: resources.docsToIndex,
993
- features,
994
- onProgress: (msg) => idxSpin.message(msg)
995
- });
996
- idxSpin.stop("Search index ready");
997
- }
998
- const hasChangelog = detectChangelog(resolvePkgDir(storagePackageName, cwd, version), getCacheDir(storagePackageName, version));
999
- const relatedSkills = await findRelatedSkills(storagePackageName, baseDir);
1000
- const shippedDocs = hasShippedDocs(storagePackageName, cwd, version);
1001
- const pkgFiles = getPkgKeyFiles(storagePackageName, cwd, version);
1002
- const repoSlug = parseGitHubRepoSlug(resolved.repoUrl);
1003
- if (!config.eject) linkPkgNamed(skillDir, storagePackageName, cwd, version);
1004
- if (!config.eject) {
1005
- writeLock(baseDir, skillDirName, {
1006
- packageName: identityPackageName,
1007
- version,
1008
- repo: repoSlug,
1009
- source: resources.docSource,
1010
- syncedAt: todayIsoDate(),
1011
- generator: "skilld"
1012
- });
1013
- const lock = readLock(baseDir);
1014
- if (lock) for (const [name, info] of Object.entries(lock.skills)) {
1015
- if (name === skillDirName) continue;
1016
- if (info.packageName === identityPackageName || parsePackages(info.packages).some((p) => p.name === identityPackageName)) {
1017
- removeLockEntry(baseDir, name);
1018
- const staleDir = join(baseDir, name);
1019
- if (existsSync(staleDir)) rmSync(staleDir, { recursive: true });
1020
- }
1021
- }
1022
- }
1023
- const allPackages = parsePackageNames((config.eject ? void 0 : readLock(baseDir)?.skills[skillDirName])?.packages);
1024
- const isEject = !!config.eject;
1025
- const overheadLines = writeGeneratedSkillMd(skillDir, {
1026
- name: identityPackageName,
1027
- version,
1028
- releasedAt: resolved.releasedAt,
1029
- description: resolved.description,
1030
- distTags: resolved.distTags,
1031
- relatedSkills,
1032
- hasIssues: resources.hasIssues,
1033
- hasDiscussions: resources.hasDiscussions,
1034
- hasReleases: resources.hasReleases,
1035
- hasChangelog,
1036
- docsType: resources.docsType,
1037
- hasShippedDocs: shippedDocs,
1038
- pkgFiles,
1039
- dirName: skillDirName,
1040
- packages: allPackages.length > 1 ? allPackages : void 0,
1041
- repoUrl: resolved.repoUrl,
1042
- features,
1043
- eject: isEject
1044
- }).split("\n").length;
1045
- p.log.success(config.mode === "update" ? `Updated skill: ${relative(cwd, skillDir)}` : `Created base skill: ${relative(cwd, skillDir)}`);
1046
- const allSectionsCached = !config.force && DEFAULT_SECTIONS.every((s) => {
1047
- const outputFile = SECTION_OUTPUT_FILES[s];
1048
- return readCachedSection(storagePackageName, version, outputFile) !== null;
1049
- });
1050
- if (allSectionsCached) {
1051
- const cachedParts = [];
1052
- for (const s of SECTION_MERGE_ORDER) {
1053
- if (!DEFAULT_SECTIONS.includes(s)) continue;
1054
- const outputFile = SECTION_OUTPUT_FILES[s];
1055
- const content = readCachedSection(storagePackageName, version, outputFile);
1056
- if (content) cachedParts.push(wrapSection(s, content));
1057
- }
1058
- const cachedBody = cachedParts.join("\n\n");
1059
- writeGeneratedSkillMd(skillDir, {
1060
- name: identityPackageName,
1061
- version,
1062
- releasedAt: resolved.releasedAt,
1063
- description: resolved.description,
1064
- distTags: resolved.distTags,
1065
- body: cachedBody,
1066
- relatedSkills,
1067
- hasIssues: resources.hasIssues,
1068
- hasDiscussions: resources.hasDiscussions,
1069
- hasReleases: resources.hasReleases,
1070
- hasChangelog,
1071
- docsType: resources.docsType,
1072
- hasShippedDocs: shippedDocs,
1073
- pkgFiles,
1074
- generatedBy: "cached",
1075
- dirName: skillDirName,
1076
- packages: allPackages.length > 1 ? allPackages : void 0,
1077
- repoUrl: resolved.repoUrl,
1078
- features,
1079
- eject: isEject
1080
- });
1081
- p.log.success("Applied cached SKILL.md sections");
1082
- }
1029
+ const { state } = result;
1083
1030
  const globalConfig = readConfig();
1084
- let resolvedModel = config.model || (config.yes && !globalConfig.skipLlm ? globalConfig.model : void 0);
1085
- if (!resolvedModel && config.yes && !globalConfig.skipLlm) {
1086
- const available = await getAvailableModels();
1087
- const auto = available.find((m) => m.recommended)?.id ?? available[0]?.id;
1088
- if (auto) resolvedModel = auto;
1089
- }
1090
- if (!allSectionsCached && !globalConfig.skipLlm && !(config.yes && !resolvedModel)) {
1091
- const llmConfig = await selectLlmConfig(resolvedModel, void 0, updateCtx);
1092
- if (llmConfig?.promptOnly) writePromptFiles({
1093
- packageName: storagePackageName,
1094
- skillDir,
1095
- version,
1096
- hasIssues: resources.hasIssues,
1097
- hasDiscussions: resources.hasDiscussions,
1098
- hasReleases: resources.hasReleases,
1099
- hasChangelog,
1100
- docsType: resources.docsType,
1101
- hasShippedDocs: shippedDocs,
1102
- pkgFiles,
1103
- sections: llmConfig.sections,
1104
- customPrompt: llmConfig.customPrompt,
1105
- features,
1106
- overheadLines
1107
- });
1108
- else if (llmConfig) {
1109
- p.log.step(getModelLabel(llmConfig.model));
1110
- await enhanceSkillWithLLM({
1111
- packageName: identityPackageName,
1112
- cachePackageName: storagePackageName,
1113
- version,
1114
- skillDir,
1115
- dirName: skillDirName,
1116
- model: llmConfig.model,
1117
- resolved,
1118
- relatedSkills,
1119
- hasIssues: resources.hasIssues,
1120
- hasDiscussions: resources.hasDiscussions,
1121
- hasReleases: resources.hasReleases,
1122
- hasChangelog,
1123
- docsType: resources.docsType,
1124
- hasShippedDocs: shippedDocs,
1125
- pkgFiles,
1126
- force: config.force,
1127
- debug: config.debug,
1128
- sections: llmConfig.sections,
1129
- customPrompt: llmConfig.customPrompt,
1130
- packages: allPackages.length > 1 ? allPackages : void 0,
1131
- features,
1132
- eject: isEject,
1133
- overheadLines
1134
- });
1135
- }
1136
- }
1137
- if (isEject) {
1138
- const skilldDir = join(skillDir, ".skilld");
1139
- if (existsSync(skilldDir) && !config.debug) rmSync(skilldDir, {
1140
- recursive: true,
1141
- force: true
1142
- });
1143
- ejectReferences(skillDir, storagePackageName, cwd, version, resources.docsType, features, resources.repoInfo);
1144
- }
1145
- if (!isEject) {
1146
- const shared = !config.global && getSharedSkillsDir(cwd);
1147
- if (shared) linkSkillToAgents(skillDirName, shared, cwd, config.agent);
1148
- if (!config.global) registerProject(cwd);
1149
- await ensureGitignore(shared ? SHARED_SKILLS_DIR : targets[config.agent].skillsDir, cwd, config.global);
1150
- await ensureAgentInstructions(config.agent, cwd, config.global);
1151
- }
1031
+ const resolvedModel = await resolveAutoModel(config.model, config.yes);
1032
+ let llmConfig = null;
1033
+ if (!state.allSectionsCached && !globalConfig.skipLlm && !(config.yes && !resolvedModel)) llmConfig = await selectLlmConfig(resolvedModel, void 0, state.updateCtx);
1034
+ await runEnhancePhase(state, llmConfig, {
1035
+ agent: config.agent,
1036
+ global: config.global,
1037
+ force: config.force,
1038
+ debug: config.debug,
1039
+ eject: config.eject
1040
+ }, ui, cwd);
1152
1041
  await shutdownWorker();
1153
1042
  const ejectMsg = isEject ? " (ejected)" : "";
1154
- const relDir = relative(cwd, skillDir);
1155
- p.outro(config.mode === "update" ? `Updated ${identityPackageName}${ejectMsg}` : `Synced ${identityPackageName} → ${relDir}${ejectMsg}`);
1043
+ const relDir = relative(cwd, state.skillDir);
1044
+ p.outro(config.mode === "update" ? `Updated ${state.identityName}${ejectMsg}` : `Synced ${state.identityName} → ${relDir}${ejectMsg}`);
1156
1045
  try {
1157
1046
  await suggestPrepareHook(cwd);
1158
1047
  } catch (err) {
1159
1048
  p.log.warn(`Failed to suggest prepare hook: ${err instanceof Error ? err.message : String(err)}`);
1160
1049
  }
1161
1050
  }
1051
+ async function syncSinglePackage(packageSpec, config) {
1052
+ if (isCrateSpec(packageSpec) && !packageSpec.slice(6).trim()) {
1053
+ p.log.error("Invalid crate spec. Use format: crate:<name>");
1054
+ return;
1055
+ }
1056
+ return runSimpleSync(packageSpec, config);
1057
+ }
1162
1058
  const addCommandDef = defineCommand({
1163
1059
  meta: {
1164
1060
  name: "add",
@@ -1464,36 +1360,29 @@ const updateCommandDef = defineCommand({
1464
1360
  }
1465
1361
  });
1466
1362
  async function exportPortablePrompts(packageSpec, opts) {
1467
- const { name: packageName } = parsePackageSpec(packageSpec);
1468
1363
  const sections = opts.sections ?? DEFAULT_SECTIONS;
1469
1364
  const spin = timedSpinner();
1470
1365
  spin.start(`Resolving ${packageSpec}`);
1471
1366
  const cwd = process.cwd();
1472
- const localVersion = (await readLocalDependencies(cwd).catch(() => [])).find((d) => d.name === packageName)?.version;
1473
- let resolved = (await resolvePackageDocsWithAttempts(packageName, {
1474
- version: localVersion,
1367
+ const { packageName, localVersion, resolved } = await resolvePackageOrCrate(packageSpec, {
1475
1368
  cwd,
1476
- onProgress: (step) => spin.message(`${packageName}: ${RESOLVE_STEP_LABELS[step]}`)
1477
- })).package;
1478
- if (!resolved) {
1479
- spin.message(`Resolving local package: ${packageName}`);
1480
- resolved = await resolveLocalDep(packageName, cwd);
1481
- }
1369
+ onProgress: (label) => spin.message(`${packageSpec}: ${label}`)
1370
+ });
1482
1371
  if (!resolved) {
1483
- spin.stop(`Could not find docs for: ${packageName}`);
1372
+ spin.stop(`Could not find docs for: ${packageSpec}`);
1484
1373
  return;
1485
1374
  }
1486
1375
  const version = localVersion || resolved.version || "latest";
1487
- const versionKey = getVersionKey(version);
1488
- const useCache = !opts.force && isCached(packageName, version);
1376
+ const cache = createReferenceCache(packageName, version);
1377
+ const useCache = !opts.force && cache.has();
1489
1378
  if (!existsSync(join(cwd, "node_modules", packageName))) {
1490
1379
  spin.message(`Downloading ${packageName}@${version} dist`);
1491
1380
  await fetchPkgDist(packageName, version);
1492
1381
  }
1493
- spin.stop(`Resolved ${packageName}@${useCache ? versionKey : version}`);
1494
- ensureCacheDir();
1382
+ spin.stop(`Resolved ${packageName}@${useCache ? cache.versionKey : version}`);
1383
+ cache.ensure();
1495
1384
  const skillDirName = computeSkillDirName(packageName);
1496
- const features = readConfig().features ?? defaultFeatures;
1385
+ const features = getActiveFeatures();
1497
1386
  const agent = opts.agent === "none" ? null : opts.agent ?? await import("./detect2.mjs").then((m) => m.detectTargetAgent());
1498
1387
  const baseDir = agent ? resolveBaseDir(cwd, agent, false) : join(cwd, ".claude", "skills");
1499
1388
  const skillDir = opts.out ? resolve(cwd, opts.out) : join(baseDir, skillDirName);
@@ -1514,10 +1403,15 @@ async function exportPortablePrompts(packageSpec, opts) {
1514
1403
  });
1515
1404
  resSpin.stop("Resources ready");
1516
1405
  for (const w of resources.warnings) p.log.warn(`\x1B[33m${w}\x1B[0m`);
1517
- linkAllReferences(skillDir, packageName, cwd, version, resources.docsType, void 0, features, resources.repoInfo);
1518
- const hasChangelog = detectChangelog(resolvePkgDir(packageName, cwd, version), getCacheDir(packageName, version));
1519
- const shippedDocs = hasShippedDocs(packageName, cwd, version);
1520
- const pkgFiles = getPkgKeyFiles(packageName, cwd, version);
1406
+ const { hasChangelog, shippedDocs, pkgFiles, relatedSkills } = await prepareSkillReferences({
1407
+ packageName,
1408
+ version,
1409
+ cwd,
1410
+ skillDir,
1411
+ resources,
1412
+ features,
1413
+ baseDir: join(skillDir, "..")
1414
+ });
1521
1415
  const docFiles = listReferenceFiles(skillDir);
1522
1416
  const prompts = buildAllSectionPrompts({
1523
1417
  packageName,
@@ -1534,17 +1428,15 @@ async function exportPortablePrompts(packageSpec, opts) {
1534
1428
  features,
1535
1429
  sections
1536
1430
  });
1537
- ejectReferences(skillDir, packageName, cwd, version, resources.docsType, features, resources.repoInfo);
1538
- const skilldDir = join(skillDir, ".skilld");
1539
- if (existsSync(skilldDir)) rmSync(skilldDir, {
1540
- recursive: true,
1541
- force: true
1431
+ cache.eject(skillDir, cwd, resources.docsType, {
1432
+ features,
1433
+ repoInfo: resources.repoInfo
1542
1434
  });
1435
+ cache.clearSkillInternal(skillDir);
1543
1436
  for (const [section, prompt] of prompts) {
1544
1437
  const portable = portabilizePrompt(prompt, section);
1545
1438
  writeFileSync(join(skillDir, `PROMPT_${section}.md`), portable);
1546
1439
  }
1547
- const relatedSkills = await findRelatedSkills(packageName, join(skillDir, ".."));
1548
1440
  writeGeneratedSkillMd(skillDir, {
1549
1441
  name: packageName,
1550
1442
  version,
@@ -1563,21 +1455,40 @@ async function exportPortablePrompts(packageSpec, opts) {
1563
1455
  features,
1564
1456
  eject: true
1565
1457
  });
1566
- writeLock(baseDir, skillDirName, {
1567
- packageName,
1568
- version,
1569
- repo: parseGitHubRepoSlug(resolved.repoUrl),
1570
- source: resources.docSource,
1571
- syncedAt: todayIsoDate(),
1572
- generator: "skilld"
1573
- });
1458
+ const repoSlug = parseGitHubRepoSlug(resolved.repoUrl);
1574
1459
  if (agent) {
1575
- const shared = getSharedSkillsDir(cwd);
1576
- if (shared) linkSkillToAgents(skillDirName, shared, cwd, agent);
1577
- await ensureGitignore(shared ? SHARED_SKILLS_DIR : targets[agent].skillsDir, cwd, false);
1578
- await ensureAgentInstructions(agent, cwd, false);
1579
- registerProject(cwd);
1580
- } else await ensureGitignore(".claude/skills", cwd, false);
1460
+ const { shared } = installSkill({
1461
+ cwd,
1462
+ agent,
1463
+ global: false,
1464
+ baseDir,
1465
+ skillDirName,
1466
+ lock: {
1467
+ packageName,
1468
+ version,
1469
+ repo: repoSlug,
1470
+ source: resources.docSource,
1471
+ syncedAt: todayIsoDate(),
1472
+ generator: "skilld"
1473
+ }
1474
+ });
1475
+ await ensureProjectFiles({
1476
+ cwd,
1477
+ agent,
1478
+ global: false,
1479
+ shared
1480
+ });
1481
+ } else {
1482
+ writeLock(baseDir, skillDirName, {
1483
+ packageName,
1484
+ version,
1485
+ repo: repoSlug,
1486
+ source: resources.docSource,
1487
+ syncedAt: todayIsoDate(),
1488
+ generator: "skilld"
1489
+ });
1490
+ await ensureGitignore(".claude/skills", cwd, false);
1491
+ }
1581
1492
  const relDir = relative(cwd, skillDir);
1582
1493
  const sectionList = [...prompts.keys()];
1583
1494
  p.log.success(`Skill installed to ${relDir}`);
@@ -1585,6 +1496,6 @@ async function exportPortablePrompts(packageSpec, opts) {
1585
1496
  const outputFileList = sectionList.map((s) => SECTION_OUTPUT_FILES[s]).join(", ");
1586
1497
  p.log.info(`Have your agent enhance the skill. Give it this prompt:\n\x1B[2m\x1B[3m Read each prompt file (${promptFiles}) in ${relDir}/, read the\n referenced files, then write your output to the matching file (${outputFileList}).\n When done, run: skilld assemble\x1B[0m`);
1587
1498
  }
1588
- export { syncCommand as a, isCrateSpec as i, ejectCommandDef as n, updateCommandDef as o, exportPortablePrompts as r, addCommandDef as t };
1499
+ export { updateCommandDef as a, syncCommand as i, ejectCommandDef as n, exportPortablePrompts as r, addCommandDef as t };
1589
1500
 
1590
1501
  //# sourceMappingURL=sync.mjs.map