skilld 1.7.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/dist/_chunks/add.mjs +66 -0
  2. package/dist/_chunks/add.mjs.map +1 -0
  3. package/dist/_chunks/agent-prompt.mjs +88 -0
  4. package/dist/_chunks/agent-prompt.mjs.map +1 -0
  5. package/dist/_chunks/agent.mjs +81 -57
  6. package/dist/_chunks/agent.mjs.map +1 -1
  7. package/dist/_chunks/args.mjs +42 -0
  8. package/dist/_chunks/args.mjs.map +1 -0
  9. package/dist/_chunks/assemble.mjs +10 -7
  10. package/dist/_chunks/assemble.mjs.map +1 -1
  11. package/dist/_chunks/author.mjs +33 -17
  12. package/dist/_chunks/author.mjs.map +1 -1
  13. package/dist/_chunks/cache.mjs +143 -183
  14. package/dist/_chunks/cache.mjs.map +1 -1
  15. package/dist/_chunks/cache2.mjs +7 -6
  16. package/dist/_chunks/cache2.mjs.map +1 -1
  17. package/dist/_chunks/client.mjs +117 -0
  18. package/dist/_chunks/client.mjs.map +1 -0
  19. package/dist/_chunks/core.mjs +5 -5
  20. package/dist/_chunks/detect.mjs +53 -43
  21. package/dist/_chunks/detect.mjs.map +1 -1
  22. package/dist/_chunks/eject.mjs +69 -0
  23. package/dist/_chunks/eject.mjs.map +1 -0
  24. package/dist/_chunks/embedding-cache2.mjs +1 -1
  25. package/dist/_chunks/env.mjs +19 -0
  26. package/dist/_chunks/env.mjs.map +1 -0
  27. package/dist/_chunks/install-many.mjs +376 -0
  28. package/dist/_chunks/install-many.mjs.map +1 -0
  29. package/dist/_chunks/install.mjs +81 -326
  30. package/dist/_chunks/install.mjs.map +1 -1
  31. package/dist/_chunks/intro.mjs +63 -0
  32. package/dist/_chunks/intro.mjs.map +1 -0
  33. package/dist/_chunks/list.mjs +2 -2
  34. package/dist/_chunks/list.mjs.map +1 -1
  35. package/dist/_chunks/lockfile.mjs +3 -2
  36. package/dist/_chunks/lockfile.mjs.map +1 -1
  37. package/dist/_chunks/login.mjs +233 -0
  38. package/dist/_chunks/login.mjs.map +1 -0
  39. package/dist/_chunks/logout.mjs +27 -0
  40. package/dist/_chunks/logout.mjs.map +1 -0
  41. package/dist/_chunks/map.mjs +11 -0
  42. package/dist/_chunks/map.mjs.map +1 -0
  43. package/dist/_chunks/markdown.mjs +79 -54
  44. package/dist/_chunks/markdown.mjs.map +1 -1
  45. package/dist/_chunks/menu.mjs +33 -0
  46. package/dist/_chunks/menu.mjs.map +1 -0
  47. package/dist/_chunks/model-picker.mjs +61 -0
  48. package/dist/_chunks/model-picker.mjs.map +1 -0
  49. package/dist/_chunks/monorepo.mjs +4 -2
  50. package/dist/_chunks/monorepo.mjs.map +1 -1
  51. package/dist/_chunks/package-json.mjs.map +1 -1
  52. package/dist/_chunks/paths.mjs +3 -5
  53. package/dist/_chunks/paths.mjs.map +1 -1
  54. package/dist/_chunks/{sync-pipeline.mjs → pipeline.mjs} +346 -313
  55. package/dist/_chunks/pipeline.mjs.map +1 -0
  56. package/dist/_chunks/pool2.mjs +1 -1
  57. package/dist/_chunks/portable.mjs +151 -0
  58. package/dist/_chunks/portable.mjs.map +1 -0
  59. package/dist/_chunks/prepare-hook.mjs +2 -0
  60. package/dist/_chunks/prepare-hook2.mjs +61 -0
  61. package/dist/_chunks/prepare-hook2.mjs.map +1 -0
  62. package/dist/_chunks/prepare.mjs +47 -3
  63. package/dist/_chunks/prepare.mjs.map +1 -1
  64. package/dist/_chunks/prepare2.mjs +7 -6
  65. package/dist/_chunks/prepare2.mjs.map +1 -1
  66. package/dist/_chunks/prompts.mjs +484 -74
  67. package/dist/_chunks/prompts.mjs.map +1 -1
  68. package/dist/_chunks/pull.mjs +219 -0
  69. package/dist/_chunks/pull.mjs.map +1 -0
  70. package/dist/_chunks/regex.mjs +19 -0
  71. package/dist/_chunks/regex.mjs.map +1 -0
  72. package/dist/_chunks/retriv.mjs +2 -171
  73. package/dist/_chunks/retriv2.mjs +159 -0
  74. package/dist/_chunks/retriv2.mjs.map +1 -0
  75. package/dist/_chunks/sanitize.mjs +12 -9
  76. package/dist/_chunks/sanitize.mjs.map +1 -1
  77. package/dist/_chunks/search-helpers.mjs +8 -6
  78. package/dist/_chunks/search-helpers.mjs.map +1 -1
  79. package/dist/_chunks/search-interactive.mjs +23 -20
  80. package/dist/_chunks/search-interactive.mjs.map +1 -1
  81. package/dist/_chunks/search.mjs +3 -3
  82. package/dist/_chunks/search.mjs.map +1 -1
  83. package/dist/_chunks/semver.mjs +2755 -1
  84. package/dist/_chunks/semver.mjs.map +1 -1
  85. package/dist/_chunks/skill-installer2.mjs +10 -11
  86. package/dist/_chunks/skill-installer2.mjs.map +1 -1
  87. package/dist/_chunks/skills.mjs +6 -7
  88. package/dist/_chunks/skills.mjs.map +1 -1
  89. package/dist/_chunks/store.mjs +107 -0
  90. package/dist/_chunks/store.mjs.map +1 -0
  91. package/dist/_chunks/sync.mjs +411 -910
  92. package/dist/_chunks/sync.mjs.map +1 -1
  93. package/dist/_chunks/sync2.mjs +2 -5
  94. package/dist/_chunks/telemetry.mjs +26 -0
  95. package/dist/_chunks/telemetry.mjs.map +1 -0
  96. package/dist/_chunks/uninstall.mjs +12 -9
  97. package/dist/_chunks/uninstall.mjs.map +1 -1
  98. package/dist/_chunks/update.mjs +171 -0
  99. package/dist/_chunks/update.mjs.map +1 -0
  100. package/dist/_chunks/upload.mjs +3 -3
  101. package/dist/_chunks/validate.mjs +1 -1
  102. package/dist/_chunks/version.mjs +16 -17
  103. package/dist/_chunks/version.mjs.map +1 -1
  104. package/dist/_chunks/whoami.mjs +21 -0
  105. package/dist/_chunks/whoami.mjs.map +1 -0
  106. package/dist/_chunks/wizard.mjs +2 -190
  107. package/dist/_chunks/wizard2.mjs +200 -0
  108. package/dist/_chunks/wizard2.mjs.map +1 -0
  109. package/dist/cli.mjs +72 -53
  110. package/dist/cli.mjs.map +1 -1
  111. package/dist/prepare.mjs +4 -3
  112. package/dist/prepare.mjs.map +1 -1
  113. package/dist/retriv/worker.d.mts +5 -1
  114. package/dist/retriv/worker.d.mts.map +1 -1
  115. package/dist/retriv/worker.mjs +1 -1
  116. package/package.json +19 -28
  117. package/dist/_chunks/author-group.mjs +0 -17
  118. package/dist/_chunks/author-group.mjs.map +0 -1
  119. package/dist/_chunks/cli-helpers.mjs +0 -335
  120. package/dist/_chunks/cli-helpers.mjs.map +0 -1
  121. package/dist/_chunks/cli-helpers2.mjs +0 -2
  122. package/dist/_chunks/index.d.mts +0 -344
  123. package/dist/_chunks/index.d.mts.map +0 -1
  124. package/dist/_chunks/index2.d.mts +0 -279
  125. package/dist/_chunks/index2.d.mts.map +0 -1
  126. package/dist/_chunks/index3.d.mts +0 -44
  127. package/dist/_chunks/index3.d.mts.map +0 -1
  128. package/dist/_chunks/index4.d.mts +0 -553
  129. package/dist/_chunks/index4.d.mts.map +0 -1
  130. package/dist/_chunks/package-registry.mjs +0 -465
  131. package/dist/_chunks/package-registry.mjs.map +0 -1
  132. package/dist/_chunks/retriv.mjs.map +0 -1
  133. package/dist/_chunks/setup.mjs +0 -17
  134. package/dist/_chunks/setup.mjs.map +0 -1
  135. package/dist/_chunks/sources.mjs +0 -2654
  136. package/dist/_chunks/sources.mjs.map +0 -1
  137. package/dist/_chunks/sync-pipeline.mjs.map +0 -1
  138. package/dist/_chunks/sync-registry.mjs +0 -65
  139. package/dist/_chunks/sync-registry.mjs.map +0 -1
  140. package/dist/_chunks/types.d.mts +0 -76
  141. package/dist/_chunks/types.d.mts.map +0 -1
  142. package/dist/_chunks/types2.d.mts +0 -88
  143. package/dist/_chunks/types2.d.mts.map +0 -1
  144. package/dist/_chunks/wizard.mjs.map +0 -1
  145. package/dist/agent/index.d.mts +0 -2
  146. package/dist/agent/index.mjs +0 -4
  147. package/dist/cache/index.d.mts +0 -2
  148. package/dist/cache/index.mjs +0 -5
  149. package/dist/index.d.mts +0 -6
  150. package/dist/index.mjs +0 -6
  151. package/dist/retriv/index.d.mts +0 -3
  152. package/dist/retriv/index.mjs +0 -2
  153. package/dist/sources/index.d.mts +0 -3
  154. package/dist/sources/index.mjs +0 -3
  155. package/dist/types.d.mts +0 -4
  156. package/dist/types.mjs +0 -1
@@ -1,42 +1,22 @@
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
- import { n as sanitizeMarkdown } from "./sanitize.mjs";
5
- import { i as parseFrontmatter } from "./markdown.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";
7
- import { a as targets } from "./detect.mjs";
8
1
  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";
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";
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";
13
- import { t as getProjectState } from "./skills.mjs";
14
- import { t as runWizard } from "./wizard.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";
2
+ import { a as sanitizeName, c as formatDuration, g as todayIsoDate, h as timedSpinner, i as linkSkillToAgents, r as computeSkillDirName, t as writeGeneratedSkillMd } from "./prompts.mjs";
3
+ import { _ as skillRefsSection, f as getSharedSkillsDir } from "./paths.mjs";
4
+ import { c as getVersionKey } from "./prepare.mjs";
5
+ import { d as readConfig, r as ensureCacheDir, s as getActiveFeatures, t as createReferenceCache } from "./cache.mjs";
6
+ import { r as suggestPrepareHook } from "./prepare-hook2.mjs";
7
+ import { E as resolveGitHubRepo, L as parseGitHubRepoSlug, U as isPrerelease, i as resolvePackageOrCrate, l as toStoragePackageName, m as searchNpmPackages, o as isCrateSpec, p as fetchPkgDist, t as semverDiff, z as parsePackageSpec } from "./semver.mjs";
8
+ import { i as parseFrontmatter } from "./markdown.mjs";
9
+ import { a as parsePackageNames, c as readLock, n as findSkillDirByPackage } from "./lockfile.mjs";
10
+ import { a as ensureProjectFiles, c as linkShippedToAgents, l as resolveBaseDir, o as handleShippedSkills, s as installSkill } from "./skill-installer2.mjs";
11
+ import { a as prepareSkillReferences, c as selectLlmConfig, d as runSkillEnhancement, f as writeBaseSkill, i as findRelatedSkills, l as applyCachedSections, o as DEFAULT_SECTIONS, p as writePromptFiles, r as fetchAndCacheResources, s as resolveAutoModel, t as buildSkillContext } from "./pipeline.mjs";
17
12
  import { n as shutdownWorker } from "./pool2.mjs";
18
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
19
- import { dirname, join, relative, resolve } from "pathe";
20
- import pLimit from "p-limit";
13
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
14
+ import { styleText } from "node:util";
21
15
  import * as p from "@clack/prompts";
22
- import { defineCommand } from "citty";
23
- import { isCI } from "std-env";
16
+ import pLimit from "p-limit";
17
+ import { join, relative, resolve } from "pathe";
24
18
  import logUpdate from "log-update";
25
- const TELEMETRY_URL = "https://add-skill.vercel.sh/t";
26
- const SKILLS_VERSION = "1.3.9";
27
- function isEnabled() {
28
- return !process.env.DISABLE_TELEMETRY && !process.env.DO_NOT_TRACK;
29
- }
30
- function track(data) {
31
- if (!isEnabled()) return;
32
- try {
33
- const params = new URLSearchParams();
34
- params.set("v", SKILLS_VERSION);
35
- if (isCI) params.set("ci", "1");
36
- for (const [key, value] of Object.entries(data)) if (value !== void 0 && value !== null) params.set(key, String(value));
37
- fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {});
38
- } catch {}
39
- }
19
+ import { createHooks } from "hookable";
40
20
  const npmResolver = async (spec, opts) => {
41
21
  const resolution = await resolvePackageOrCrate(spec, {
42
22
  cwd: opts.cwd,
@@ -92,20 +72,31 @@ function createGithubResolver(owner, repo) {
92
72
  };
93
73
  }
94
74
  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);
75
+ async function runBaseSync(spec, config, hooks, resolver, cwd, defaultSections) {
76
+ await hooks.callHook("resolve:start", { spec });
97
77
  const resolverResult = await resolver(spec, {
98
78
  cwd,
99
79
  agent: config.agent,
100
80
  global: config.global,
101
- onProgress: (msg) => ui.resolveProgress(msg)
81
+ onProgress: (msg) => hooks.callHook("resolve:progress", {
82
+ spec,
83
+ message: msg
84
+ })
102
85
  });
103
86
  if (!("resolved" in resolverResult)) {
104
87
  if (resolverResult.shipped && resolverResult.shipped.length > 0) {
105
- for (const s of resolverResult.shipped) ui.shippedInstalled(s.skillName, relative(cwd, s.skillDir));
88
+ for (const s of resolverResult.shipped) await hooks.callHook("shipped:installed", {
89
+ spec,
90
+ skillName: s.skillName,
91
+ skillDir: s.skillDir
92
+ });
106
93
  return { kind: "shipped" };
107
94
  }
108
- ui.resolveFailed(resolverResult.identityName);
95
+ await hooks.callHook("resolve:failed", {
96
+ spec,
97
+ identityName: resolverResult.identityName,
98
+ attempts: resolverResult.attempts
99
+ });
109
100
  return {
110
101
  kind: "unresolved",
111
102
  unresolved: resolverResult
@@ -116,24 +107,33 @@ async function runBaseSync(spec, config, ui, resolver, cwd, defaultSections) {
116
107
  if (config.force) cache.clearForce();
117
108
  const useCache = cache.has();
118
109
  if (kind !== "crate" && !existsSync(join(cwd, "node_modules", identityName))) {
119
- ui.downloadingDist();
110
+ await hooks.callHook("dist:downloading", { spec });
120
111
  await fetchPkgDist(identityName, version);
121
112
  }
122
113
  if (kind !== "crate") {
123
114
  const shipped = handleShippedSkills(identityName, version, cwd, config.agent, config.global);
124
115
  if (shipped) {
125
116
  linkShippedToAgents(shipped.shipped, cwd, config.agent, config.global);
126
- for (const s of shipped.shipped) ui.shippedInstalled(s.skillName, relative(cwd, s.skillDir));
117
+ for (const s of shipped.shipped) await hooks.callHook("shipped:installed", {
118
+ spec,
119
+ skillName: s.skillName,
120
+ skillDir: s.skillDir
121
+ });
127
122
  return { kind: "shipped" };
128
123
  }
129
124
  }
130
- ui.resolveDone(version, {
125
+ await hooks.callHook("resolve:done", {
126
+ spec,
127
+ version,
131
128
  cached: useCache,
132
129
  force: config.force
133
130
  });
134
131
  if (kind === "npm" && !localVersion && !requestedTag && !isPrerelease(version)) {
135
132
  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`);
133
+ if (nextTag && (!resolved.releasedAt || !nextTag.releasedAt || nextTag.releasedAt > resolved.releasedAt)) await hooks.callHook("warn", {
134
+ spec,
135
+ message: `No local dependency found — using latest stable (${version}). Prerelease ${nextTag.version} available: skilld add ${identityName}@beta`
136
+ });
137
137
  }
138
138
  cache.ensure();
139
139
  const isEject = !!config.eject;
@@ -171,7 +171,7 @@ async function runBaseSync(spec, config, ui, resolver, cwd, defaultSections) {
171
171
  })()
172
172
  } : void 0;
173
173
  const features = getActiveFeatures(config.noSearch ? { search: false } : void 0);
174
- ui.fetchStart();
174
+ await hooks.callHook("fetch:start", { spec });
175
175
  const resources = await fetchAndCacheResources({
176
176
  packageName: storageName,
177
177
  resolved,
@@ -179,7 +179,10 @@ async function runBaseSync(spec, config, ui, resolver, cwd, defaultSections) {
179
179
  useCache,
180
180
  features,
181
181
  from: config.from,
182
- onProgress: (msg) => ui.fetchProgress(msg)
182
+ onProgress: (msg) => hooks.callHook("fetch:progress", {
183
+ spec,
184
+ message: msg
185
+ })
183
186
  });
184
187
  const parts = [];
185
188
  if (resources.docsToIndex.length > 0) {
@@ -189,9 +192,16 @@ async function runBaseSync(spec, config, ui, resolver, cwd, defaultSections) {
189
192
  if (resources.hasIssues) parts.push("issues");
190
193
  if (resources.hasDiscussions) parts.push("discussions");
191
194
  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
+ await hooks.callHook("fetch:done", {
196
+ spec,
197
+ parts,
198
+ cached: resources.usedCache
199
+ });
200
+ for (const w of resources.warnings) await hooks.callHook("warn", {
201
+ spec,
202
+ message: w
203
+ });
204
+ if (features.search) await hooks.callHook("index:start", { spec });
195
205
  const prepared = await prepareSkillReferences({
196
206
  packageName: storageName,
197
207
  version,
@@ -200,9 +210,12 @@ async function runBaseSync(spec, config, ui, resolver, cwd, defaultSections) {
200
210
  resources,
201
211
  features,
202
212
  baseDir,
203
- onIndexProgress: (msg) => ui.indexProgress(msg)
213
+ onIndexProgress: (msg) => hooks.callHook("index:progress", {
214
+ spec,
215
+ message: msg
216
+ })
204
217
  });
205
- if (features.search) ui.indexDone();
218
+ if (features.search) await hooks.callHook("index:done", { spec });
206
219
  if (!isEject) {
207
220
  const repoSlug = parseGitHubRepoSlug(resolved.repoUrl);
208
221
  cache.linkPkgNamed(skillDir, cwd);
@@ -239,9 +252,13 @@ async function runBaseSync(spec, config, ui, resolver, cwd, defaultSections) {
239
252
  features
240
253
  });
241
254
  ctx.overheadLines = writeBaseSkill(ctx, { eject: isEject }).split("\n").length;
242
- ui.baseDone(relative(cwd, skillDir), config.mode === "update" ? "update" : "add");
255
+ await hooks.callHook("base:done", {
256
+ spec,
257
+ skillDir: relative(cwd, skillDir),
258
+ mode: config.mode === "update" ? "update" : "add"
259
+ });
243
260
  const allSectionsCached = !config.force && applyCachedSections(ctx, defaultSections, { eject: isEject });
244
- if (allSectionsCached) ui.sectionsCached();
261
+ if (allSectionsCached) await hooks.callHook("sections:cached", { spec });
245
262
  return {
246
263
  kind: "ready",
247
264
  state: {
@@ -259,8 +276,9 @@ async function runBaseSync(spec, config, ui, resolver, cwd, defaultSections) {
259
276
  }
260
277
  };
261
278
  }
262
- async function runEnhancePhase(state, llmConfig, config, ui, cwd) {
279
+ async function runEnhancePhase(state, llmConfig, config, hooks, cwd) {
263
280
  const isEject = !!config.eject;
281
+ const spec = state.identityName;
264
282
  if (llmConfig?.promptOnly) writePromptFiles({
265
283
  ...state.ctx,
266
284
  packageName: state.ctx.cachePackageName ?? state.ctx.packageName,
@@ -269,10 +287,10 @@ async function runEnhancePhase(state, llmConfig, config, ui, cwd) {
269
287
  sections: llmConfig.sections,
270
288
  customPrompt: llmConfig.customPrompt
271
289
  });
272
- else if (llmConfig) await enhanceWithUi(state.ctx, llmConfig, {
290
+ else if (llmConfig) await enhanceWithHooks(state.ctx, llmConfig, {
273
291
  ...config,
274
292
  eject: isEject
275
- }, ui);
293
+ }, hooks, spec);
276
294
  if (isEject) {
277
295
  const cache = createReferenceCache(state.storageName, state.version);
278
296
  if (!config.debug) cache.clearSkillInternal(state.skillDir);
@@ -291,8 +309,11 @@ async function runEnhancePhase(state, llmConfig, config, ui, cwd) {
291
309
  shared
292
310
  });
293
311
  }
294
- async function enhanceWithUi(ctx, llmConfig, config, ui) {
295
- ui.llmStart(getModelLabel(llmConfig.model));
312
+ async function enhanceWithHooks(ctx, llmConfig, config, hooks, spec) {
313
+ await hooks.callHook("enhance:start", {
314
+ spec,
315
+ modelLabel: getModelLabel(llmConfig.model)
316
+ });
296
317
  const result = await runSkillEnhancement(ctx, {
297
318
  model: llmConfig.model,
298
319
  force: config.force,
@@ -300,276 +321,199 @@ async function enhanceWithUi(ctx, llmConfig, config, ui) {
300
321
  sections: llmConfig.sections,
301
322
  customPrompt: llmConfig.customPrompt,
302
323
  eject: !!config.eject
303
- }, (progress) => ui.llmProgress(progress));
304
- if (result.wasOptimized) ui.llmDone({
324
+ }, (progress) => hooks.callHook("enhance:progress", {
325
+ spec,
326
+ progress
327
+ }));
328
+ if (result.wasOptimized) await hooks.callHook("enhance:done", {
329
+ spec,
305
330
  usage: result.usage ? { totalTokens: result.usage.totalTokens } : void 0,
306
331
  cost: result.cost,
307
332
  debugLogsDir: result.debugLogsDir,
308
333
  error: result.error,
309
334
  warnings: result.warnings
310
335
  });
311
- else ui.llmFailed(result.error ?? "", { rateLimited: !!result.error && RATE_LIMIT_RE.test(result.error) });
336
+ else await hooks.callHook("enhance:failed", {
337
+ spec,
338
+ error: result.error ?? "",
339
+ rateLimited: !!result.error && RATE_LIMIT_RE.test(result.error)
340
+ });
312
341
  }
313
- function createClackUi({ cwd }) {
342
+ function createSyncRun(opts) {
343
+ const hooks = createHooks();
344
+ async function runBase(spec) {
345
+ const result = await runBaseSync(spec, {
346
+ agent: opts.agent,
347
+ global: opts.global,
348
+ mode: opts.mode,
349
+ force: opts.force,
350
+ noSearch: opts.noSearch,
351
+ name: opts.name,
352
+ from: opts.from,
353
+ eject: opts.eject
354
+ }, hooks, opts.resolver, opts.cwd, opts.defaultSections);
355
+ if (result.kind === "shipped") return {
356
+ kind: "shipped",
357
+ spec
358
+ };
359
+ if (result.kind === "unresolved") return {
360
+ kind: "unresolved",
361
+ spec,
362
+ identityName: result.unresolved.identityName,
363
+ attempts: result.unresolved.attempts
364
+ };
365
+ if (result.kind === "merge-needed") {
366
+ if (opts.onMergeNeeded) {
367
+ await opts.onMergeNeeded(result.state);
368
+ return {
369
+ kind: "merged",
370
+ spec
371
+ };
372
+ }
373
+ return {
374
+ kind: "error",
375
+ spec,
376
+ reason: `Skill dir already holds ${result.state.existingLock.packageName} — run sequentially to merge`
377
+ };
378
+ }
379
+ return {
380
+ kind: "ready",
381
+ spec,
382
+ state: result.state
383
+ };
384
+ }
385
+ async function runEnhance(state, llmConfig) {
386
+ await runEnhancePhase(state, llmConfig, {
387
+ agent: opts.agent,
388
+ global: opts.global,
389
+ force: opts.force,
390
+ debug: opts.debug,
391
+ eject: opts.eject
392
+ }, hooks, opts.cwd);
393
+ }
394
+ async function run(spec) {
395
+ const base = await runBase(spec);
396
+ if (base.kind !== "ready") return base;
397
+ let llmConfig = null;
398
+ if (!base.state.allSectionsCached && opts.resolveLlmConfig) llmConfig = await opts.resolveLlmConfig({ ready: [{
399
+ spec,
400
+ state: base.state
401
+ }] }) ?? null;
402
+ await runEnhance(base.state, llmConfig);
403
+ return {
404
+ kind: "enhanced",
405
+ spec,
406
+ state: base.state
407
+ };
408
+ }
409
+ async function runMany(specs, runOpts) {
410
+ const limit = pLimit(runOpts?.concurrency ?? 5);
411
+ return (await Promise.allSettled(specs.map((spec) => limit(() => runBase(spec))))).map((r, i) => r.status === "fulfilled" ? r.value : {
412
+ kind: "error",
413
+ spec: specs[i],
414
+ reason: r.reason instanceof Error ? r.reason.message : String(r.reason)
415
+ });
416
+ }
417
+ return {
418
+ hooks,
419
+ runBase,
420
+ runEnhance,
421
+ run,
422
+ runMany
423
+ };
424
+ }
425
+ function bindClackUi(hooks, { cwd }) {
314
426
  let spinner = null;
315
427
  let resourceSpinner = null;
316
428
  let indexSpinner = null;
317
429
  let llmLog = null;
318
430
  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
- }
408
- async function syncGitSkills(opts) {
409
- const { source, agent, global: isGlobal, yes } = opts;
410
- const cwd = process.cwd();
411
- const agentConfig = targets[agent];
412
- const baseDir = isGlobal ? join(CACHE_DIR, "skills") : join(cwd, agentConfig.skillsDir);
413
- const label = source.type === "local" ? source.localPath : `${source.owner}/${source.repo}`;
414
- const spin = timedSpinner();
415
- spin.start(`Fetching skills from ${label}`);
416
- const { skills } = await fetchGitSkills(source, (msg) => spin.message(msg));
417
- if (skills.length === 0) {
418
- if (source.type === "github" && source.owner && source.repo) {
419
- spin.stop(`No pre-authored skills in ${label}, generating from repo docs...`);
420
- return syncGitHubRepo(opts);
421
- }
422
- spin.stop(`No skills found in ${label}`);
423
- return;
424
- }
425
- spin.stop(`Found ${skills.length} skill(s) in ${label}`);
426
- let selected = skills;
427
- if (opts.skillFilter?.length) {
428
- const filterSet = new Set(opts.skillFilter.map((s) => s.toLowerCase().replace(/-skilld$/, "")));
429
- selected = skills.filter((s) => filterSet.has(s.name.toLowerCase().replace(/-skilld$/, "")));
430
- if (selected.length === 0) {
431
- p.log.warn(`No skills matched: ${opts.skillFilter.join(", ")}`);
432
- p.log.message(`Available: ${skills.map((s) => s.name).join(", ")}`);
433
- return;
434
- }
435
- } else if (source.skillPath) selected = skills;
436
- else if (skills.length > 1 && !yes) {
437
- const choices = await p.autocompleteMultiselect({
438
- message: `Select skills to install from ${label}`,
439
- options: skills.map((s) => ({
440
- label: s.name.replace(/-skilld$/, ""),
441
- value: s.name,
442
- hint: s.description || s.path
443
- })),
444
- initialValues: []
445
- });
446
- if (p.isCancel(choices)) return;
447
- const selectedNames = new Set(choices);
448
- selected = skills.filter((s) => selectedNames.has(s.name));
449
- if (selected.length === 0) return;
450
- }
451
- mkdirSync(baseDir, { recursive: true });
452
- for (const skill of selected) {
453
- const skillDir = join(baseDir, skill.name);
454
- mkdirSync(skillDir, { recursive: true });
455
- writeSkillMd(skillDir, sanitizeMarkdown(skill.content));
456
- if (skill.files.length > 0) for (const f of skill.files) {
457
- const filePath = join(skillDir, f.path);
458
- mkdirSync(dirname(filePath), { recursive: true });
459
- writeFileSync(filePath, f.content);
460
- }
461
- const sourceType = source.type === "local" ? "local" : source.type;
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
431
+ hooks.hook("resolve:start", ({ spec }) => {
432
+ currentSpec = spec;
433
+ spinner = timedSpinner();
434
+ spinner.start(`Resolving ${spec}`);
435
+ });
436
+ hooks.hook("resolve:progress", ({ message }) => {
437
+ spinner?.message(message);
438
+ });
439
+ hooks.hook("resolve:done", ({ version, cached, force }) => {
440
+ const suffix = force ? " (force)" : cached ? " (cached)" : "";
441
+ spinner?.stop(`Resolved ${currentSpec}@${version}${suffix}`);
442
+ spinner = null;
443
+ });
444
+ hooks.hook("resolve:failed", ({ identityName }) => {
445
+ spinner?.stop(`Could not find docs for: ${identityName}`);
446
+ spinner = null;
447
+ });
448
+ hooks.hook("dist:downloading", () => {
449
+ spinner?.message("Downloading dist");
450
+ });
451
+ hooks.hook("fetch:start", () => {
452
+ resourceSpinner = timedSpinner();
453
+ resourceSpinner.start("Finding resources");
454
+ });
455
+ hooks.hook("fetch:progress", ({ message }) => {
456
+ resourceSpinner?.message(message);
457
+ });
458
+ hooks.hook("fetch:done", ({ parts, cached }) => {
459
+ const summary = parts.length > 0 ? parts.join(", ") : "resources";
460
+ resourceSpinner?.stop(cached ? `Loaded ${summary} (cached)` : `Fetched ${summary}`);
461
+ resourceSpinner = null;
462
+ });
463
+ hooks.hook("index:start", () => {
464
+ indexSpinner = timedSpinner();
465
+ indexSpinner.start("Creating search index");
466
+ });
467
+ hooks.hook("index:progress", ({ message }) => {
468
+ indexSpinner?.message(message);
469
+ });
470
+ hooks.hook("index:done", () => {
471
+ indexSpinner?.stop("Search index ready");
472
+ indexSpinner = null;
473
+ });
474
+ hooks.hook("warn", ({ message }) => {
475
+ p.log.warn(styleText("yellow", message));
476
+ });
477
+ hooks.hook("base:done", ({ skillDir, mode }) => {
478
+ p.log.success(mode === "update" ? `Updated skill: ${skillDir}` : `Created base skill: ${skillDir}`);
479
+ });
480
+ hooks.hook("sections:cached", () => {
481
+ p.log.success("Applied cached SKILL.md sections");
482
+ });
483
+ hooks.hook("enhance:start", ({ spec, modelLabel }) => {
484
+ currentSpec = spec;
485
+ p.log.step(modelLabel);
486
+ llmLog = p.taskLog({
487
+ title: `Agent exploring ${spec}`,
488
+ limit: 3
477
489
  });
478
- }
479
- if (source.type !== "local" && source.owner && source.repo) track({
480
- event: "install",
481
- source: `${source.owner}/${source.repo}`,
482
- skills: selected.map((s) => s.name).join(","),
483
- agents: agent,
484
- ...isGlobal && { global: "1" },
485
- sourceType: source.type
486
490
  });
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
- }
492
- }
493
- async function syncGitHubRepo(opts) {
494
- const { source, agent, global: isGlobal, yes } = opts;
495
- const owner = source.owner;
496
- const repo = source.repo;
497
- const cwd = process.cwd();
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"
491
+ hooks.hook("enhance:progress", ({ progress }) => {
492
+ if (!llmLog) return;
493
+ const line = `${progress.section ? `[${progress.section}] ` : ""}${progress.chunk}`;
494
+ llmLog.message(line);
525
495
  });
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,
541
- version,
542
- repo: repoSlug,
543
- source: existingLock.source,
544
- syncedAt: todayIsoDate(),
545
- generator: "skilld"
546
- },
547
- skipLinkAgents: true
496
+ hooks.hook("enhance:done", (info) => {
497
+ if (!llmLog) return;
498
+ const parts = [];
499
+ if (info.usage) parts.push(`${Math.round(info.usage.totalTokens / 1e3)}k tokens`);
500
+ if (info.cost) parts.push(`$${info.cost.toFixed(2)}`);
501
+ const suffix = parts.length > 0 ? ` (${parts.join(", ")})` : "";
502
+ llmLog.success(`Generated best practices${suffix}`);
503
+ llmLog = null;
504
+ if (info.debugLogsDir) p.log.info(`Debug logs: ${relative(cwd, info.debugLogsDir)}`);
505
+ if (info.error) p.log.warn(styleText("yellow", `Partial failure: ${info.error}`));
506
+ if (info.warnings) for (const w of info.warnings) p.log.warn(styleText("yellow", w));
548
507
  });
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();
556
- writeGeneratedSkillMd(skillDir, {
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",
564
- hasShippedDocs: shippedDocs,
565
- pkgFiles,
566
- dirName: skillDirName,
567
- packages: allPackages,
568
- features
508
+ hooks.hook("enhance:failed", ({ error, rateLimited }) => {
509
+ if (!llmLog) return;
510
+ if (rateLimited) llmLog.error(`Rate limited by LLM provider. Try again shortly or use a different model via \`skilld config\``);
511
+ else llmLog.error(`Enhancement failed${error ? `: ${error}` : ""}`);
512
+ llmLog = null;
513
+ });
514
+ hooks.hook("shipped:installed", ({ skillName, skillDir }) => {
515
+ p.log.success(`Using published SKILL.md: ${skillName} → ${relative(cwd, skillDir)}`);
569
516
  });
570
- const sharedDir = !config.global && getSharedSkillsDir(cwd);
571
- if (sharedDir) linkSkillToAgents(skillDirName, sharedDir, cwd, config.agent);
572
- p.outro(`Merged ${identityName} into ${skillDirName}`);
573
517
  }
574
518
  const STATUS_ICONS = {
575
519
  pending: "○",
@@ -583,37 +527,34 @@ const STATUS_ICONS = {
583
527
  error: "✗"
584
528
  };
585
529
  const STATUS_COLORS = {
586
- pending: "\x1B[90m",
587
- resolving: "\x1B[36m",
588
- downloading: "\x1B[36m",
589
- embedding: "\x1B[36m",
590
- exploring: "\x1B[34m",
591
- thinking: "\x1B[35m",
592
- generating: "\x1B[33m",
593
- done: "\x1B[32m",
594
- error: "\x1B[31m"
530
+ pending: "gray",
531
+ resolving: "cyan",
532
+ downloading: "cyan",
533
+ embedding: "cyan",
534
+ exploring: "blue",
535
+ thinking: "magenta",
536
+ generating: "yellow",
537
+ done: "green",
538
+ error: "red"
595
539
  };
596
540
  function renderParallel(r) {
597
541
  const maxNameLen = Math.max(...[...r.states.keys()].map((n) => n.length), 20);
598
542
  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";
543
+ const icon = styleText(STATUS_COLORS[s.status], STATUS_ICONS[s.status]);
603
544
  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}`;
545
+ const version = s.version ? `${styleText("gray", s.version)} ` : "";
546
+ const elapsed = (s.status === "done" || s.status === "error") && s.startedAt && s.completedAt ? ` ${styleText("gray", `(${formatDuration(s.completedAt - s.startedAt)})`)}` : "";
547
+ const preview = s.streamPreview ? ` ${styleText("gray", s.streamPreview)}` : "";
548
+ return ` ${icon} ${name} ${version}${s.message}${elapsed}${preview}`;
608
549
  });
609
550
  const doneCount = [...r.states.values()].filter((s) => s.status === "done").length;
610
551
  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"));
552
+ logUpdate(`${styleText("bold", `${r.verb} ${r.total} packages`)} (${doneCount} done${errorCount > 0 ? `, ${errorCount} failed` : ""})\n` + lines.join("\n"));
612
553
  }
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) {
554
+ function bindParallelUi(hooks, render) {
555
+ function update(spec, status, message, ver) {
556
+ const state = render.states.get(spec);
557
+ if (!state) return;
617
558
  if (!state.startedAt && status !== "pending") state.startedAt = performance.now();
618
559
  if ((status === "done" || status === "error") && !state.completedAt) state.completedAt = performance.now();
619
560
  state.status = status;
@@ -622,58 +563,31 @@ function createParallelUi(name, render, version) {
622
563
  if (ver) state.version = ver;
623
564
  renderParallel(render);
624
565
  }
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
- };
566
+ hooks.hook("resolve:start", ({ spec }) => update(spec, "resolving", "Resolving..."));
567
+ hooks.hook("resolve:progress", ({ spec, message }) => update(spec, "resolving", message));
568
+ hooks.hook("resolve:done", ({ spec, version, cached, force }) => {
569
+ update(spec, "downloading", cached ? "Using cache" : force ? "Re-fetching docs..." : "Fetching docs...", version);
570
+ });
571
+ hooks.hook("resolve:failed", () => {});
572
+ hooks.hook("dist:downloading", ({ spec }) => update(spec, "downloading", "Downloading dist..."));
573
+ hooks.hook("fetch:start", () => {});
574
+ hooks.hook("fetch:progress", ({ spec, message }) => update(spec, "downloading", message));
575
+ hooks.hook("fetch:done", ({ spec }) => update(spec, "downloading", "Linking references..."));
576
+ hooks.hook("index:start", ({ spec }) => update(spec, "embedding", "Indexing docs"));
577
+ hooks.hook("index:progress", ({ spec, message }) => update(spec, "embedding", message));
578
+ hooks.hook("index:done", () => {});
579
+ hooks.hook("warn", () => {});
580
+ hooks.hook("base:done", ({ spec, mode }) => {
581
+ update(spec, "done", mode === "update" ? "Skill updated" : "Base skill created");
582
+ });
583
+ hooks.hook("sections:cached", () => {});
584
+ hooks.hook("enhance:start", ({ spec, modelLabel }) => update(spec, "generating", modelLabel));
585
+ hooks.hook("enhance:progress", ({ spec, progress }) => {
586
+ update(spec, progress.type === "reasoning" ? "exploring" : "generating", `${progress.section ? `[${progress.section}] ` : ""}${progress.chunk}`);
587
+ });
588
+ hooks.hook("enhance:done", ({ spec }) => update(spec, "done", "Skill optimized"));
589
+ hooks.hook("enhance:failed", ({ spec, error }) => update(spec, "error", error));
590
+ hooks.hook("shipped:installed", ({ spec }) => update(spec, "done", "Published SKILL.md"));
677
591
  }
678
592
  const DIFF_RANK = {
679
593
  major: 5,
@@ -688,11 +602,16 @@ async function syncPackagesParallel(config) {
688
602
  const { packages, concurrency = 5 } = config;
689
603
  const cwd = process.cwd();
690
604
  const states = /* @__PURE__ */ new Map();
691
- for (const spec of packages) states.set(spec, {
692
- name: spec,
693
- status: "pending",
694
- message: "Waiting..."
695
- });
605
+ const specToName = /* @__PURE__ */ new Map();
606
+ for (const spec of packages) {
607
+ const { name } = parsePackageSpec(spec);
608
+ specToName.set(spec, name);
609
+ states.set(spec, {
610
+ name,
611
+ status: "pending",
612
+ message: "Waiting..."
613
+ });
614
+ }
696
615
  const render = {
697
616
  states,
698
617
  verb: config.mode === "update" ? "Updating" : "Syncing",
@@ -700,17 +619,19 @@ async function syncPackagesParallel(config) {
700
619
  };
701
620
  ensureCacheDir();
702
621
  renderParallel(render);
703
- const limit = pLimit(concurrency);
704
- const baseConfig = {
622
+ const run = createSyncRun({
623
+ cwd,
624
+ resolver: npmResolver,
705
625
  agent: config.agent,
706
626
  global: config.global,
707
627
  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
- })));
628
+ force: config.force,
629
+ debug: config.debug,
630
+ defaultSections: DEFAULT_SECTIONS
631
+ });
632
+ bindParallelUi(run.hooks, render);
633
+ const limit = pLimit(concurrency);
634
+ const baseResults = await Promise.allSettled(packages.map((spec) => limit(() => run.runBase(spec))));
714
635
  logUpdate.done();
715
636
  const ready = [];
716
637
  const shippedCount = [];
@@ -736,13 +657,13 @@ async function syncPackagesParallel(config) {
736
657
  continue;
737
658
  }
738
659
  if (result.kind === "unresolved") {
739
- const npmAttempt = result.unresolved.attempts.find((a) => a.source === "npm");
660
+ const npmAttempt = result.attempts.find((a) => a.source === "npm");
740
661
  let reason;
741
662
  if (npmAttempt?.status === "not-found") {
742
- const suggestions = await searchNpmPackages(result.unresolved.identityName, 3);
663
+ const suggestions = await searchNpmPackages(result.identityName, 3);
743
664
  const hint = suggestions.length > 0 ? ` (try: ${suggestions.map((s) => s.name).join(", ")})` : "";
744
665
  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";
666
+ } else reason = result.attempts.filter((a) => a.status !== "success").map((a) => a.message || a.source).join("; ") || "No docs found";
746
667
  const slot = states.get(spec);
747
668
  if (slot) {
748
669
  slot.status = "error";
@@ -754,14 +675,14 @@ async function syncPackagesParallel(config) {
754
675
  });
755
676
  continue;
756
677
  }
757
- if (result.kind === "merge-needed") {
678
+ if (result.kind === "error") {
758
679
  errors.push({
759
680
  spec,
760
- reason: `Skill dir already holds ${result.state.existingLock.packageName} — run sequentially to merge`
681
+ reason: result.reason
761
682
  });
762
683
  continue;
763
684
  }
764
- ready.push({
685
+ if (result.kind === "ready") ready.push({
765
686
  spec,
766
687
  state: result.state
767
688
  });
@@ -770,7 +691,7 @@ async function syncPackagesParallel(config) {
770
691
  logUpdate.done();
771
692
  const pastVerb = config.mode === "update" ? "Updated" : "Created";
772
693
  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`);
694
+ for (const w of aggregatedWarnings) p.log.warn(styleText("yellow", w));
774
695
  for (const { spec, reason } of errors) p.log.error(` ${spec}: ${reason}`);
775
696
  const cachedPkgs = [];
776
697
  const uncached = [];
@@ -790,46 +711,25 @@ async function syncPackagesParallel(config) {
790
711
  }
791
712
  }
792
713
  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
- }
714
+ for (const r of uncached) await run.runEnhance(r.state, llmConfig);
802
715
  } else if (llmConfig) {
803
716
  p.log.step(getModelLabel(llmConfig.model));
804
- for (const r of uncached) states.set(r.spec, {
805
- name: r.spec,
806
- status: "pending",
807
- message: "Waiting...",
808
- version: getVersionKey(r.state.version)
809
- });
717
+ for (const r of uncached) {
718
+ const displayName = specToName.get(r.spec) ?? r.spec;
719
+ states.set(r.spec, {
720
+ name: displayName,
721
+ status: "pending",
722
+ message: "Waiting...",
723
+ version: getVersionKey(r.state.version)
724
+ });
725
+ }
810
726
  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
- })));
727
+ const llmResults = await Promise.allSettled(uncached.map((r) => limit(() => run.runEnhance(r.state, llmConfig))));
820
728
  logUpdate.done();
821
729
  const llmSucceeded = llmResults.filter((x) => x.status === "fulfilled").length;
822
730
  p.log.success(`Enhanced ${llmSucceeded}/${uncached.length} skills with LLM`);
823
731
  }
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);
832
- }
732
+ } else for (const r of ready) await run.runEnhance(r.state, null);
833
733
  await ensureProjectFiles({
834
734
  cwd,
835
735
  agent: config.agent,
@@ -837,7 +737,7 @@ async function syncPackagesParallel(config) {
837
737
  });
838
738
  await shutdownWorker();
839
739
  p.outro(`${pastVerb} ${ready.length}/${packages.length} packages`);
840
- const { suggestPrepareHook } = await import("./cli-helpers2.mjs");
740
+ const { suggestPrepareHook } = await import("./prepare-hook.mjs");
841
741
  try {
842
742
  await suggestPrepareHook(cwd);
843
743
  } catch (err) {
@@ -866,6 +766,52 @@ function aggregateUpdateCtx(ready) {
866
766
  bumpType: maxDiff || void 0
867
767
  };
868
768
  }
769
+ async function handleMerge(state, config, cwd) {
770
+ const { identityName, storageName, version, resolved, baseDir, skillDir, skillDirName, existingLock } = state;
771
+ p.log.step(`Merging ${identityName} into ${skillDirName}`);
772
+ createReferenceCache(storageName, version).linkPkgNamed(skillDir, cwd);
773
+ const repoSlug = parseGitHubRepoSlug(resolved.repoUrl);
774
+ installSkill({
775
+ cwd,
776
+ agent: config.agent,
777
+ global: config.global,
778
+ baseDir,
779
+ skillDirName,
780
+ lock: {
781
+ packageName: identityName,
782
+ version,
783
+ repo: repoSlug,
784
+ source: existingLock.source,
785
+ syncedAt: todayIsoDate(),
786
+ generator: "skilld"
787
+ },
788
+ skipLinkAgents: true
789
+ });
790
+ const updatedLock = readLock(baseDir)?.skills[skillDirName];
791
+ const allPackages = parsePackageNames(updatedLock?.packages);
792
+ const relatedSkills = await findRelatedSkills(storageName, baseDir);
793
+ const existingCache = createReferenceCache(toStoragePackageName(existingLock.packageName), existingLock.version);
794
+ const pkgFiles = existingCache.keyFiles(cwd);
795
+ const shippedDocs = existingCache.hasShipped(cwd);
796
+ const features = getActiveFeatures();
797
+ writeGeneratedSkillMd(skillDir, {
798
+ name: existingLock.packageName,
799
+ version: existingLock.version,
800
+ relatedSkills,
801
+ hasIssues: features.issues && existsSync(skillRefsSection(skillDir, "issues")),
802
+ hasDiscussions: features.discussions && existsSync(skillRefsSection(skillDir, "discussions")),
803
+ hasReleases: features.releases && existsSync(skillRefsSection(skillDir, "releases")),
804
+ docsType: existingLock.source?.includes("llms.txt") ? "llms.txt" : "docs",
805
+ hasShippedDocs: shippedDocs,
806
+ pkgFiles,
807
+ dirName: skillDirName,
808
+ packages: allPackages,
809
+ features
810
+ });
811
+ const sharedDir = !config.global && getSharedSkillsDir(cwd);
812
+ if (sharedDir) linkSkillToAgents(skillDirName, sharedDir, cwd, config.agent);
813
+ p.outro(`Merged ${identityName} into ${skillDirName}`);
814
+ }
869
815
  const RESOLVE_SOURCE_LABELS = {
870
816
  "npm": "npm registry",
871
817
  "github-docs": "GitHub versioned docs",
@@ -878,11 +824,11 @@ const RESOLVE_SOURCE_LABELS = {
878
824
  };
879
825
  function showResolveAttempts(attempts) {
880
826
  if (attempts.length === 0) return;
881
- p.log.message("\x1B[90mDoc resolution:\x1B[0m");
827
+ p.log.message(styleText("gray", "Doc resolution:"));
882
828
  for (const attempt of attempts) {
883
- const icon = attempt.status === "success" ? "\x1B[32m✓\x1B[0m" : "\x1B[90m✗\x1B[0m";
884
- const source = `\x1B[90m${RESOLVE_SOURCE_LABELS[attempt.source] ?? attempt.source}\x1B[0m`;
885
- const msg = attempt.message ? ` \x1B[90m— ${attempt.message}\x1B[0m` : "";
829
+ const icon = attempt.status === "success" ? styleText("green", "✓") : styleText("gray", "✗");
830
+ const source = styleText("gray", RESOLVE_SOURCE_LABELS[attempt.source] ?? attempt.source);
831
+ const msg = attempt.message ? ` ${styleText("gray", `— ${attempt.message}`)}` : "";
886
832
  p.log.message(` ${icon} ${source}${msg}`);
887
833
  }
888
834
  }
@@ -979,9 +925,10 @@ async function pickFromList(packages, state) {
979
925
  }
980
926
  async function runSimpleSync(packageSpec, config) {
981
927
  const cwd = process.cwd();
982
- const ui = createClackUi({ cwd });
983
928
  const isEject = !!config.eject;
984
- const result = await runBaseSync(packageSpec, {
929
+ const run = createSyncRun({
930
+ cwd,
931
+ resolver: npmResolver,
985
932
  agent: config.agent,
986
933
  global: config.global,
987
934
  mode: config.mode,
@@ -989,18 +936,25 @@ async function runSimpleSync(packageSpec, config) {
989
936
  noSearch: config.noSearch,
990
937
  name: config.name,
991
938
  from: config.from,
992
- eject: config.eject
993
- }, ui, npmResolver, cwd, DEFAULT_SECTIONS);
994
- if (result.kind === "shipped") {
939
+ debug: config.debug,
940
+ eject: config.eject,
941
+ defaultSections: DEFAULT_SECTIONS,
942
+ onMergeNeeded: (state) => handleMerge(state, {
943
+ agent: config.agent,
944
+ global: config.global
945
+ }, cwd)
946
+ });
947
+ bindClackUi(run.hooks, { cwd });
948
+ const base = await run.runBase(packageSpec);
949
+ if (base.kind === "shipped") {
995
950
  p.outro(`Synced ${packageSpec}`);
996
951
  return;
997
952
  }
998
- if (result.kind === "unresolved") {
999
- const { unresolved } = result;
953
+ if (base.kind === "unresolved") {
1000
954
  if (!isCrateSpec(packageSpec)) {
1001
- const suggestions = await searchNpmPackages(unresolved.identityName);
955
+ const suggestions = await searchNpmPackages(base.identityName);
1002
956
  if (suggestions.length > 0) {
1003
- showResolveAttempts(unresolved.attempts);
957
+ showResolveAttempts(base.attempts);
1004
958
  const selected = await p.select({
1005
959
  message: "Did you mean one of these?",
1006
960
  options: [...suggestions.map((s) => ({
@@ -1016,28 +970,16 @@ async function runSimpleSync(packageSpec, config) {
1016
970
  return;
1017
971
  }
1018
972
  }
1019
- showResolveAttempts(unresolved.attempts);
973
+ showResolveAttempts(base.attempts);
1020
974
  return;
1021
975
  }
1022
- if (result.kind === "merge-needed") {
1023
- await handleMerge(result.state, {
1024
- agent: config.agent,
1025
- global: config.global
1026
- }, cwd);
1027
- return;
1028
- }
1029
- const { state } = result;
976
+ if (base.kind === "merged" || base.kind === "error") return;
977
+ const { state } = base;
1030
978
  const globalConfig = readConfig();
1031
979
  const resolvedModel = await resolveAutoModel(config.model, config.yes);
1032
980
  let llmConfig = null;
1033
981
  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);
982
+ await run.runEnhance(state, llmConfig);
1041
983
  await shutdownWorker();
1042
984
  const ejectMsg = isEject ? " (ejected)" : "";
1043
985
  const relDir = relative(cwd, state.skillDir);
@@ -1055,447 +997,6 @@ async function syncSinglePackage(packageSpec, config) {
1055
997
  }
1056
998
  return runSimpleSync(packageSpec, config);
1057
999
  }
1058
- const addCommandDef = defineCommand({
1059
- meta: {
1060
- name: "add",
1061
- description: "Install skills (npm:<pkg>, crate:<name>, gh:<owner/repo>, @<curator>)"
1062
- },
1063
- args: {
1064
- package: {
1065
- type: "positional",
1066
- description: "Package(s) to sync (space/comma-separated; npm:<pkg>, crate:<name>, or owner/repo)",
1067
- required: true
1068
- },
1069
- skill: {
1070
- type: "string",
1071
- alias: "s",
1072
- description: "Select specific skills from a git repo (comma-separated)",
1073
- valueHint: "name"
1074
- },
1075
- ...sharedArgs
1076
- },
1077
- async run({ args }) {
1078
- const cwd = process.cwd();
1079
- let agent = resolveAgent(args.agent);
1080
- if (!agent) {
1081
- agent = await promptForAgent();
1082
- if (!agent) return;
1083
- }
1084
- const rawInputs = [...new Set([args.package, ...args._ || []].map((s) => s.trim()).filter(Boolean))];
1085
- if (agent === "none") {
1086
- const packages = [...new Set(rawInputs.flatMap((s) => s.split(/[,\s]+/)).map((s) => s.trim()).filter(Boolean))];
1087
- for (const pkg of packages) await exportPortablePrompts(pkg, {
1088
- force: args.force,
1089
- agent: "none"
1090
- });
1091
- return;
1092
- }
1093
- if (!hasCompletedWizard()) await runWizard({ agent });
1094
- const parsedSources = rawInputs.map(parseSkillInput);
1095
- const gitSources = [];
1096
- const npmEntries = [];
1097
- const crateSpecs = [];
1098
- const unsupported = [];
1099
- for (const source of parsedSources) switch (source.type) {
1100
- case "git":
1101
- gitSources.push(source.source);
1102
- break;
1103
- case "npm":
1104
- npmEntries.push({
1105
- name: source.package,
1106
- spec: source.tag ? `${source.package}@${source.tag}` : source.package
1107
- });
1108
- break;
1109
- case "crate":
1110
- crateSpecs.push(source.version ? `crate:${source.package}@${source.version}` : `crate:${source.package}`);
1111
- break;
1112
- case "bare":
1113
- p.log.warn(`Bare names are deprecated. Use \x1B[36mnpm:${source.package}\x1B[0m instead.`);
1114
- npmEntries.push({
1115
- name: source.package,
1116
- spec: source.tag ? `${source.package}@${source.tag}` : source.package
1117
- });
1118
- break;
1119
- case "curator":
1120
- unsupported.push(`@${source.handle} (curator)`);
1121
- break;
1122
- case "collection":
1123
- unsupported.push(`@${source.handle}/${source.name} (collection)`);
1124
- break;
1125
- default: throw new Error(`Unhandled SkillSource type: ${JSON.stringify(source)}`);
1126
- }
1127
- if (unsupported.length > 0) {
1128
- p.log.error(`Curator and collection installs are not yet available:\n ${unsupported.join("\n ")}\n\nFollow https://skilld.dev for launch updates.`);
1129
- process.exitCode = 1;
1130
- if (gitSources.length === 0 && npmEntries.length === 0 && crateSpecs.length === 0) return;
1131
- }
1132
- if (gitSources.length > 0) for (const source of gitSources) {
1133
- const skillFilter = args.skill ? args.skill.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean) : void 0;
1134
- await syncGitSkills({
1135
- source,
1136
- global: args.global,
1137
- agent,
1138
- yes: args.yes,
1139
- model: args.model,
1140
- force: args.force,
1141
- debug: args.debug,
1142
- skillFilter
1143
- });
1144
- }
1145
- if (npmEntries.length > 0) {
1146
- const { syncRegistrySkill } = await import("./sync-registry.mjs");
1147
- const seen = /* @__PURE__ */ new Set();
1148
- const dedupedEntries = npmEntries.filter((e) => {
1149
- if (seen.has(e.name)) return false;
1150
- seen.add(e.name);
1151
- return true;
1152
- });
1153
- const fallbackPackages = [];
1154
- for (const entry of dedupedEntries) {
1155
- const result = await syncRegistrySkill({
1156
- packageName: entry.name,
1157
- agent,
1158
- cwd
1159
- });
1160
- if (result) p.log.success(`Installed \x1B[36m${result.name}\x1B[0m from registry`);
1161
- else fallbackPackages.push(entry.spec);
1162
- }
1163
- if (fallbackPackages.length > 0) {
1164
- const state = await getProjectState(cwd);
1165
- p.intro(introLine({
1166
- state,
1167
- agentId: agent || void 0
1168
- }));
1169
- await syncCommand(state, {
1170
- packages: [...fallbackPackages, ...crateSpecs],
1171
- global: args.global,
1172
- agent,
1173
- model: args.model,
1174
- yes: args.yes,
1175
- force: args.force,
1176
- debug: args.debug
1177
- });
1178
- return;
1179
- }
1180
- }
1181
- if (crateSpecs.length > 0) {
1182
- const state = await getProjectState(cwd);
1183
- p.intro(introLine({
1184
- state,
1185
- agentId: agent || void 0
1186
- }));
1187
- await syncCommand(state, {
1188
- packages: crateSpecs,
1189
- global: args.global,
1190
- agent,
1191
- model: args.model,
1192
- yes: args.yes,
1193
- force: args.force,
1194
- debug: args.debug
1195
- });
1196
- }
1197
- }
1198
- });
1199
- const ejectCommandDef = defineCommand({
1200
- meta: {
1201
- name: "eject",
1202
- description: "Eject skill with references as real files (portable, no symlinks)"
1203
- },
1204
- args: {
1205
- package: {
1206
- type: "positional",
1207
- description: "Package to eject",
1208
- required: true
1209
- },
1210
- name: {
1211
- type: "string",
1212
- alias: "n",
1213
- description: "Custom skill directory name (default: derived from package)"
1214
- },
1215
- out: {
1216
- type: "string",
1217
- alias: "o",
1218
- description: "Output directory path override"
1219
- },
1220
- from: {
1221
- type: "string",
1222
- description: "Collect releases/issues/discussions from this date onward (YYYY-MM-DD)"
1223
- },
1224
- search: {
1225
- type: "boolean",
1226
- description: "Build search index / embeddings (use --no-search to skip)",
1227
- default: true
1228
- },
1229
- ...sharedArgs
1230
- },
1231
- async run({ args }) {
1232
- const cwd = process.cwd();
1233
- const resolved = resolveAgent(args.agent);
1234
- const agent = resolved && resolved !== "none" ? resolved : "claude-code";
1235
- if (!hasCompletedWizard()) await runWizard({ agent });
1236
- const state = await getProjectState(cwd);
1237
- p.intro(introLine({
1238
- state,
1239
- agentId: agent || void 0
1240
- }));
1241
- return syncCommand(state, {
1242
- packages: [args.package],
1243
- global: args.global,
1244
- agent,
1245
- model: args.model,
1246
- yes: args.yes,
1247
- force: args.force,
1248
- debug: args.debug,
1249
- eject: args.out || true,
1250
- name: args.name,
1251
- from: args.from,
1252
- noSearch: !args.search
1253
- });
1254
- }
1255
- });
1256
- const updateCommandDef = defineCommand({
1257
- meta: {
1258
- name: "update",
1259
- description: "Update outdated skills"
1260
- },
1261
- args: {
1262
- package: {
1263
- type: "positional",
1264
- description: "Package(s) to update (space or comma-separated). Without args, syncs all outdated.",
1265
- required: false
1266
- },
1267
- background: {
1268
- type: "boolean",
1269
- alias: "b",
1270
- description: "Run in background (detached process, non-interactive)",
1271
- default: false
1272
- },
1273
- ...sharedArgs
1274
- },
1275
- async run({ args }) {
1276
- const cwd = process.cwd();
1277
- if (args.background) {
1278
- const { spawn } = await import("node:child_process");
1279
- const updateArgs = [
1280
- "update",
1281
- ...args.package ? [args.package] : [],
1282
- ...args.agent ? ["--agent", args.agent] : [],
1283
- ...args.model ? ["--model", args.model] : []
1284
- ];
1285
- spawn(process.execPath, [process.argv[1], ...updateArgs], {
1286
- cwd,
1287
- detached: true,
1288
- stdio: "ignore"
1289
- }).unref();
1290
- return;
1291
- }
1292
- const silent = !isInteractive();
1293
- let agent = resolveAgent(args.agent);
1294
- if (!agent) {
1295
- agent = await promptForAgent();
1296
- if (!agent) return;
1297
- }
1298
- if (agent === "none") {
1299
- const state = await getProjectState(cwd);
1300
- const packages = args.package ? Array.from(new Set([args.package, ...args._ || []].flatMap((s) => s.split(/[,\s]+/)).map((s) => s.trim()).filter(Boolean)), (s) => resolveSkillName(s)).filter((s) => s !== null) : state.outdated.map((s) => s.packageName || s.name);
1301
- if (packages.length === 0) {
1302
- if (!silent) p.log.success("All skills up to date");
1303
- return;
1304
- }
1305
- for (const pkg of packages) await exportPortablePrompts(pkg, {
1306
- force: args.force,
1307
- agent: "none"
1308
- });
1309
- return;
1310
- }
1311
- const config = readConfig();
1312
- const state = await getProjectState(cwd);
1313
- if (!silent) {
1314
- const generators = getInstalledGenerators();
1315
- p.intro(introLine({
1316
- state,
1317
- generators,
1318
- modelId: config.model,
1319
- agentId: config.agent || agent || void 0
1320
- }));
1321
- }
1322
- if (args.package) {
1323
- const raw = [...new Set([args.package, ...args._ || []].flatMap((s) => s.split(/[,\s]+/)).map((s) => s.trim()).filter(Boolean))];
1324
- const packages = [];
1325
- for (const r of raw) {
1326
- const name = resolveSkillName(r);
1327
- if (!name) {
1328
- p.log.warn(`Cannot update \x1B[36m${r}\x1B[0m: curator/collection inputs are not addressable here.`);
1329
- continue;
1330
- }
1331
- packages.push(name);
1332
- }
1333
- if (packages.length === 0) return;
1334
- return syncCommand(state, {
1335
- packages,
1336
- global: args.global,
1337
- agent,
1338
- model: args.model || (silent ? config.model : void 0),
1339
- yes: args.yes || silent,
1340
- force: args.force,
1341
- debug: args.debug,
1342
- mode: "update"
1343
- });
1344
- }
1345
- const crateSpecs = state.skills.map((s) => s.info?.packageName).filter((name) => !!name && name.startsWith("crate:"));
1346
- if (state.outdated.length === 0 && crateSpecs.length === 0) {
1347
- p.log.success("All skills up to date");
1348
- return;
1349
- }
1350
- return syncCommand(state, {
1351
- packages: [...state.outdated.map((s) => s.packageName || s.name), ...crateSpecs],
1352
- global: args.global,
1353
- agent,
1354
- model: args.model || (silent ? config.model : void 0),
1355
- yes: args.yes || silent,
1356
- force: args.force,
1357
- debug: args.debug,
1358
- mode: "update"
1359
- });
1360
- }
1361
- });
1362
- async function exportPortablePrompts(packageSpec, opts) {
1363
- const sections = opts.sections ?? DEFAULT_SECTIONS;
1364
- const spin = timedSpinner();
1365
- spin.start(`Resolving ${packageSpec}`);
1366
- const cwd = process.cwd();
1367
- const { packageName, localVersion, resolved } = await resolvePackageOrCrate(packageSpec, {
1368
- cwd,
1369
- onProgress: (label) => spin.message(`${packageSpec}: ${label}`)
1370
- });
1371
- if (!resolved) {
1372
- spin.stop(`Could not find docs for: ${packageSpec}`);
1373
- return;
1374
- }
1375
- const version = localVersion || resolved.version || "latest";
1376
- const cache = createReferenceCache(packageName, version);
1377
- const useCache = !opts.force && cache.has();
1378
- if (!existsSync(join(cwd, "node_modules", packageName))) {
1379
- spin.message(`Downloading ${packageName}@${version} dist`);
1380
- await fetchPkgDist(packageName, version);
1381
- }
1382
- spin.stop(`Resolved ${packageName}@${useCache ? cache.versionKey : version}`);
1383
- cache.ensure();
1384
- const skillDirName = computeSkillDirName(packageName);
1385
- const features = getActiveFeatures();
1386
- const agent = opts.agent === "none" ? null : opts.agent ?? await import("./detect2.mjs").then((m) => m.detectTargetAgent());
1387
- const baseDir = agent ? resolveBaseDir(cwd, agent, false) : join(cwd, ".claude", "skills");
1388
- const skillDir = opts.out ? resolve(cwd, opts.out) : join(baseDir, skillDirName);
1389
- if (existsSync(skillDir) && !opts.force) {
1390
- const existing = Object.values(SECTION_OUTPUT_FILES).filter((f) => existsSync(join(skillDir, f)));
1391
- if (existing.length > 0) p.log.warn(`Overwriting existing output files in ${relative(cwd, skillDir)}: ${existing.join(", ")}`);
1392
- }
1393
- mkdirSync(skillDir, { recursive: true });
1394
- const resSpin = timedSpinner();
1395
- resSpin.start("Fetching resources");
1396
- const resources = await fetchAndCacheResources({
1397
- packageName,
1398
- resolved,
1399
- version,
1400
- useCache,
1401
- features,
1402
- onProgress: (msg) => resSpin.message(msg)
1403
- });
1404
- resSpin.stop("Resources ready");
1405
- for (const w of resources.warnings) p.log.warn(`\x1B[33m${w}\x1B[0m`);
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
- });
1415
- const docFiles = listReferenceFiles(skillDir);
1416
- const prompts = buildAllSectionPrompts({
1417
- packageName,
1418
- skillDir,
1419
- version,
1420
- hasIssues: resources.hasIssues,
1421
- hasDiscussions: resources.hasDiscussions,
1422
- hasReleases: resources.hasReleases,
1423
- hasChangelog,
1424
- docFiles,
1425
- docsType: resources.docsType,
1426
- hasShippedDocs: shippedDocs,
1427
- pkgFiles,
1428
- features,
1429
- sections
1430
- });
1431
- cache.eject(skillDir, cwd, resources.docsType, {
1432
- features,
1433
- repoInfo: resources.repoInfo
1434
- });
1435
- cache.clearSkillInternal(skillDir);
1436
- for (const [section, prompt] of prompts) {
1437
- const portable = portabilizePrompt(prompt, section);
1438
- writeFileSync(join(skillDir, `PROMPT_${section}.md`), portable);
1439
- }
1440
- writeGeneratedSkillMd(skillDir, {
1441
- name: packageName,
1442
- version,
1443
- releasedAt: resolved.releasedAt,
1444
- description: resolved.description,
1445
- distTags: resolved.distTags,
1446
- relatedSkills,
1447
- hasIssues: resources.hasIssues,
1448
- hasDiscussions: resources.hasDiscussions,
1449
- hasReleases: resources.hasReleases,
1450
- hasChangelog,
1451
- docsType: resources.docsType,
1452
- hasShippedDocs: shippedDocs,
1453
- pkgFiles,
1454
- repoUrl: resolved.repoUrl,
1455
- features,
1456
- eject: true
1457
- });
1458
- const repoSlug = parseGitHubRepoSlug(resolved.repoUrl);
1459
- if (agent) {
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
- }
1492
- const relDir = relative(cwd, skillDir);
1493
- const sectionList = [...prompts.keys()];
1494
- p.log.success(`Skill installed to ${relDir}`);
1495
- const promptFiles = sectionList.map((s) => `PROMPT_${s}.md`).join(", ");
1496
- const outputFileList = sectionList.map((s) => SECTION_OUTPUT_FILES[s]).join(", ");
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`);
1498
- }
1499
- export { updateCommandDef as a, syncCommand as i, ejectCommandDef as n, exportPortablePrompts as r, addCommandDef as t };
1000
+ export { createGithubResolver as i, bindClackUi as n, createSyncRun as r, syncCommand as t };
1500
1001
 
1501
1002
  //# sourceMappingURL=sync.mjs.map