skilld 1.7.3 → 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 (161) 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 +737 -619
  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 +11 -8
  10. package/dist/_chunks/assemble.mjs.map +1 -1
  11. package/dist/_chunks/author.mjs +77 -131
  12. package/dist/_chunks/author.mjs.map +1 -1
  13. package/dist/_chunks/cache.mjs +320 -54
  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 +7 -4
  20. package/dist/_chunks/detect.mjs +54 -44
  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 +2 -2
  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 +86 -371
  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 +31 -7
  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 +73 -0
  50. package/dist/_chunks/monorepo.mjs.map +1 -0
  51. package/dist/_chunks/package-json.mjs.map +1 -1
  52. package/dist/_chunks/paths.mjs +47 -0
  53. package/dist/_chunks/paths.mjs.map +1 -0
  54. package/dist/_chunks/pipeline.mjs +985 -0
  55. package/dist/_chunks/pipeline.mjs.map +1 -0
  56. package/dist/_chunks/pool2.mjs +2 -2
  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 +9 -8
  65. package/dist/_chunks/prepare2.mjs.map +1 -1
  66. package/dist/_chunks/prompts.mjs +784 -26
  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 +9 -8
  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 -4
  82. package/dist/_chunks/search.mjs.map +1 -1
  83. package/dist/_chunks/{sources.mjs → semver.mjs} +1128 -838
  84. package/dist/_chunks/semver.mjs.map +1 -0
  85. package/dist/_chunks/skill-installer.mjs +2 -0
  86. package/dist/_chunks/skill-installer2.mjs +154 -0
  87. package/dist/_chunks/skill-installer2.mjs.map +1 -0
  88. package/dist/_chunks/skills.mjs +12 -12
  89. package/dist/_chunks/skills.mjs.map +1 -1
  90. package/dist/_chunks/store.mjs +107 -0
  91. package/dist/_chunks/store.mjs.map +1 -0
  92. package/dist/_chunks/sync.mjs +761 -1349
  93. package/dist/_chunks/sync.mjs.map +1 -1
  94. package/dist/_chunks/sync2.mjs +2 -3
  95. package/dist/_chunks/telemetry.mjs +26 -0
  96. package/dist/_chunks/telemetry.mjs.map +1 -0
  97. package/dist/_chunks/uninstall.mjs +15 -13
  98. package/dist/_chunks/uninstall.mjs.map +1 -1
  99. package/dist/_chunks/update.mjs +171 -0
  100. package/dist/_chunks/update.mjs.map +1 -0
  101. package/dist/_chunks/upload.mjs +4 -4
  102. package/dist/_chunks/validate.mjs +1 -1
  103. package/dist/_chunks/version.mjs +16 -27
  104. package/dist/_chunks/version.mjs.map +1 -1
  105. package/dist/_chunks/whoami.mjs +21 -0
  106. package/dist/_chunks/whoami.mjs.map +1 -0
  107. package/dist/_chunks/wizard.mjs +2 -190
  108. package/dist/_chunks/wizard2.mjs +200 -0
  109. package/dist/_chunks/wizard2.mjs.map +1 -0
  110. package/dist/cli.mjs +77 -59
  111. package/dist/cli.mjs.map +1 -1
  112. package/dist/prepare.mjs +5 -4
  113. package/dist/prepare.mjs.map +1 -1
  114. package/dist/retriv/worker.d.mts +5 -1
  115. package/dist/retriv/worker.d.mts.map +1 -1
  116. package/dist/retriv/worker.mjs +1 -1
  117. package/package.json +20 -29
  118. package/dist/_chunks/author-group.mjs +0 -17
  119. package/dist/_chunks/author-group.mjs.map +0 -1
  120. package/dist/_chunks/cli-helpers.mjs +0 -335
  121. package/dist/_chunks/cli-helpers.mjs.map +0 -1
  122. package/dist/_chunks/cli-helpers2.mjs +0 -2
  123. package/dist/_chunks/config.mjs +0 -122
  124. package/dist/_chunks/config.mjs.map +0 -1
  125. package/dist/_chunks/index.d.mts +0 -151
  126. package/dist/_chunks/index.d.mts.map +0 -1
  127. package/dist/_chunks/index2.d.mts +0 -44
  128. package/dist/_chunks/index2.d.mts.map +0 -1
  129. package/dist/_chunks/index3.d.mts +0 -589
  130. package/dist/_chunks/index3.d.mts.map +0 -1
  131. package/dist/_chunks/prefix.mjs +0 -108
  132. package/dist/_chunks/prefix.mjs.map +0 -1
  133. package/dist/_chunks/retriv.mjs.map +0 -1
  134. package/dist/_chunks/setup.mjs +0 -17
  135. package/dist/_chunks/setup.mjs.map +0 -1
  136. package/dist/_chunks/shared.mjs +0 -503
  137. package/dist/_chunks/shared.mjs.map +0 -1
  138. package/dist/_chunks/skill.mjs +0 -329
  139. package/dist/_chunks/skill.mjs.map +0 -1
  140. package/dist/_chunks/sources.mjs.map +0 -1
  141. package/dist/_chunks/sync-registry.mjs +0 -59
  142. package/dist/_chunks/sync-registry.mjs.map +0 -1
  143. package/dist/_chunks/sync-shared.mjs +0 -2
  144. package/dist/_chunks/sync-shared2.mjs +0 -1020
  145. package/dist/_chunks/sync-shared2.mjs.map +0 -1
  146. package/dist/_chunks/types.d.mts +0 -88
  147. package/dist/_chunks/types.d.mts.map +0 -1
  148. package/dist/_chunks/wizard.mjs.map +0 -1
  149. package/dist/agent/index.d.mts +0 -346
  150. package/dist/agent/index.d.mts.map +0 -1
  151. package/dist/agent/index.mjs +0 -5
  152. package/dist/cache/index.d.mts +0 -2
  153. package/dist/cache/index.mjs +0 -4
  154. package/dist/index.d.mts +0 -5
  155. package/dist/index.mjs +0 -5
  156. package/dist/retriv/index.d.mts +0 -3
  157. package/dist/retriv/index.mjs +0 -2
  158. package/dist/sources/index.d.mts +0 -2
  159. package/dist/sources/index.mjs +0 -3
  160. package/dist/types.d.mts +0 -4
  161. package/dist/types.mjs +0 -1
@@ -1,266 +1,519 @@
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";
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";
1
+ import { i as getModelLabel, n as detectImportedPackages } from "./agent.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";
6
8
  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";
9
- 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";
13
- 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";
15
- import { t as getProjectState } from "./skills.mjs";
16
- import { n as resolveSkillName, r as toStoragePackageName, t as parseSkillInput } from "./prefix.mjs";
17
- 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";
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";
19
12
  import { n as shutdownWorker } from "./pool2.mjs";
20
- import { dirname, join, relative, resolve } from "pathe";
21
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
22
- import pLimit from "p-limit";
23
- import { isCI } from "std-env";
13
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
14
+ import { styleText } from "node:util";
24
15
  import * as p from "@clack/prompts";
25
- import { defineCommand } from "citty";
16
+ import pLimit from "p-limit";
17
+ import { join, relative, resolve } from "pathe";
26
18
  import logUpdate from "log-update";
27
- const TELEMETRY_URL = "https://add-skill.vercel.sh/t";
28
- const SKILLS_VERSION = "1.3.9";
29
- function isEnabled() {
30
- return !process.env.DISABLE_TELEMETRY && !process.env.DO_NOT_TRACK;
31
- }
32
- function track(data) {
33
- if (!isEnabled()) return;
34
- try {
35
- const params = new URLSearchParams();
36
- params.set("v", SKILLS_VERSION);
37
- if (isCI) params.set("ci", "1");
38
- for (const [key, value] of Object.entries(data)) if (value !== void 0 && value !== null) params.set(key, String(value));
39
- fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {});
40
- } catch {}
41
- }
42
- async function syncGitSkills(opts) {
43
- const { source, agent, global: isGlobal, yes } = opts;
44
- const cwd = process.cwd();
45
- const agentConfig = targets[agent];
46
- const baseDir = isGlobal ? join(CACHE_DIR, "skills") : join(cwd, agentConfig.skillsDir);
47
- const label = source.type === "local" ? source.localPath : `${source.owner}/${source.repo}`;
48
- const spin = timedSpinner();
49
- spin.start(`Fetching skills from ${label}`);
50
- const { skills } = await fetchGitSkills(source, (msg) => spin.message(msg));
51
- if (skills.length === 0) {
52
- if (source.type === "github" && source.owner && source.repo) {
53
- spin.stop(`No pre-authored skills in ${label}, generating from repo docs...`);
54
- return syncGitHubRepo(opts);
19
+ import { createHooks } from "hookable";
20
+ const npmResolver = async (spec, opts) => {
21
+ const resolution = await resolvePackageOrCrate(spec, {
22
+ cwd: opts.cwd,
23
+ onProgress: (msg) => opts.onProgress(`${spec}: ${msg}`)
24
+ });
25
+ const { isCrate, packageName, identityPackageName, storagePackageName, requestedTag, localVersion, attempts, registryVersion } = resolution;
26
+ if (!resolution.resolved) {
27
+ const result = {
28
+ identityName: identityPackageName,
29
+ attempts,
30
+ registryVersion
31
+ };
32
+ if (!isCrate) {
33
+ const shipped = handleShippedSkills(packageName, localVersion || registryVersion || "latest", opts.cwd, opts.agent, opts.global);
34
+ if (shipped) result.shipped = shipped.shipped;
55
35
  }
56
- spin.stop(`No skills found in ${label}`);
57
- return;
36
+ return result;
58
37
  }
59
- spin.stop(`Found ${skills.length} skill(s) in ${label}`);
60
- let selected = skills;
61
- if (opts.skillFilter?.length) {
62
- const filterSet = new Set(opts.skillFilter.map((s) => s.toLowerCase().replace(/-skilld$/, "")));
63
- selected = skills.filter((s) => filterSet.has(s.name.toLowerCase().replace(/-skilld$/, "")));
64
- if (selected.length === 0) {
65
- p.log.warn(`No skills matched: ${opts.skillFilter.join(", ")}`);
66
- p.log.message(`Available: ${skills.map((s) => s.name).join(", ")}`);
67
- return;
38
+ const resolved = resolution.resolved;
39
+ return {
40
+ identityName: identityPackageName,
41
+ storageName: storagePackageName,
42
+ version: isCrate ? resolved.version || requestedTag || "latest" : localVersion || resolved.version || "latest",
43
+ resolved,
44
+ kind: isCrate ? "crate" : "npm",
45
+ requestedTag,
46
+ localVersion
47
+ };
48
+ };
49
+ function createGithubResolver(owner, repo) {
50
+ return async (_spec, opts) => {
51
+ const resolved = await resolveGitHubRepo(owner, repo, (msg) => opts.onProgress(msg));
52
+ if (!resolved) return {
53
+ identityName: `${owner}-${repo}`,
54
+ attempts: [{
55
+ source: "github-meta",
56
+ status: "not-found",
57
+ message: `Could not find docs for ${owner}/${repo}`
58
+ }]
59
+ };
60
+ const repoUrl = `https://github.com/${owner}/${repo}`;
61
+ const name = `${owner}-${repo}`;
62
+ return {
63
+ identityName: name,
64
+ storageName: name,
65
+ version: resolved.version || "main",
66
+ resolved: {
67
+ ...resolved,
68
+ repoUrl
69
+ },
70
+ kind: "github"
71
+ };
72
+ };
73
+ }
74
+ const RATE_LIMIT_RE = /\b429\b|rate.?limit|exhausted.*capacity|quota.*reset/i;
75
+ async function runBaseSync(spec, config, hooks, resolver, cwd, defaultSections) {
76
+ await hooks.callHook("resolve:start", { spec });
77
+ const resolverResult = await resolver(spec, {
78
+ cwd,
79
+ agent: config.agent,
80
+ global: config.global,
81
+ onProgress: (msg) => hooks.callHook("resolve:progress", {
82
+ spec,
83
+ message: msg
84
+ })
85
+ });
86
+ if (!("resolved" in resolverResult)) {
87
+ if (resolverResult.shipped && resolverResult.shipped.length > 0) {
88
+ for (const s of resolverResult.shipped) await hooks.callHook("shipped:installed", {
89
+ spec,
90
+ skillName: s.skillName,
91
+ skillDir: s.skillDir
92
+ });
93
+ return { kind: "shipped" };
68
94
  }
69
- } else if (source.skillPath) selected = skills;
70
- else if (skills.length > 1 && !yes) {
71
- const choices = await p.autocompleteMultiselect({
72
- message: `Select skills to install from ${label}`,
73
- options: skills.map((s) => ({
74
- label: s.name.replace(/-skilld$/, ""),
75
- value: s.name,
76
- hint: s.description || s.path
77
- })),
78
- initialValues: []
95
+ await hooks.callHook("resolve:failed", {
96
+ spec,
97
+ identityName: resolverResult.identityName,
98
+ attempts: resolverResult.attempts
79
99
  });
80
- if (p.isCancel(choices)) return;
81
- const selectedNames = new Set(choices);
82
- selected = skills.filter((s) => selectedNames.has(s.name));
83
- if (selected.length === 0) return;
100
+ return {
101
+ kind: "unresolved",
102
+ unresolved: resolverResult
103
+ };
84
104
  }
85
- mkdirSync(baseDir, { recursive: true });
86
- for (const skill of selected) {
87
- const skillDir = join(baseDir, skill.name);
88
- mkdirSync(skillDir, { recursive: true });
89
- writeSkillMd(skillDir, sanitizeMarkdown(skill.content));
90
- if (skill.files.length > 0) for (const f of skill.files) {
91
- const filePath = join(skillDir, f.path);
92
- mkdirSync(dirname(filePath), { recursive: true });
93
- writeFileSync(filePath, f.content);
105
+ const { identityName, storageName, version, resolved, kind, requestedTag, localVersion } = resolverResult;
106
+ const cache = createReferenceCache(storageName, version);
107
+ if (config.force) cache.clearForce();
108
+ const useCache = cache.has();
109
+ if (kind !== "crate" && !existsSync(join(cwd, "node_modules", identityName))) {
110
+ await hooks.callHook("dist:downloading", { spec });
111
+ await fetchPkgDist(identityName, version);
112
+ }
113
+ if (kind !== "crate") {
114
+ const shipped = handleShippedSkills(identityName, version, cwd, config.agent, config.global);
115
+ if (shipped) {
116
+ linkShippedToAgents(shipped.shipped, cwd, config.agent, config.global);
117
+ for (const s of shipped.shipped) await hooks.callHook("shipped:installed", {
118
+ spec,
119
+ skillName: s.skillName,
120
+ skillDir: s.skillDir
121
+ });
122
+ return { kind: "shipped" };
94
123
  }
95
- 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"
103
- });
104
124
  }
105
- if (!isGlobal) registerProject(cwd);
106
- if (source.type !== "local" && source.owner && source.repo) track({
107
- event: "install",
108
- source: `${source.owner}/${source.repo}`,
109
- skills: selected.map((s) => s.name).join(","),
110
- agents: agent,
111
- ...isGlobal && { global: "1" },
112
- sourceType: source.type
125
+ await hooks.callHook("resolve:done", {
126
+ spec,
127
+ version,
128
+ cached: useCache,
129
+ force: config.force
113
130
  });
114
- const names = selected.map((s) => `\x1B[36m${s.name}\x1B[0m`).join(", ");
115
- p.log.success(`Installed ${names}`);
116
- }
117
- async function syncGitHubRepo(opts) {
118
- const { source, agent, global: isGlobal, yes } = opts;
119
- const owner = source.owner;
120
- const repo = source.repo;
121
- 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;
131
+ if (kind === "npm" && !localVersion && !requestedTag && !isPrerelease(version)) {
132
+ const nextTag = resolved.distTags?.next ?? resolved.distTags?.beta ?? resolved.distTags?.alpha;
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
+ });
128
137
  }
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);
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);
139
148
  mkdirSync(skillDir, { recursive: true });
140
- const features = readConfig().features ?? defaultFeatures;
141
- const resSpin = timedSpinner();
142
- resSpin.start("Finding resources");
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
+ await hooks.callHook("fetch:start", { spec });
143
175
  const resources = await fetchAndCacheResources({
144
- packageName,
176
+ packageName: storageName,
145
177
  resolved,
146
178
  version,
147
179
  useCache,
148
180
  features,
149
- from: opts.from,
150
- onProgress: (msg) => resSpin.message(msg)
181
+ from: config.from,
182
+ onProgress: (msg) => hooks.callHook("fetch:progress", {
183
+ spec,
184
+ message: msg
185
+ })
151
186
  });
152
- const resParts = [];
187
+ const parts = [];
153
188
  if (resources.docsToIndex.length > 0) {
154
189
  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,
190
+ if (docCount > 0) parts.push(`${docCount} docs`);
191
+ }
192
+ if (resources.hasIssues) parts.push("issues");
193
+ if (resources.hasDiscussions) parts.push("discussions");
194
+ if (resources.hasReleases) parts.push("releases");
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 });
205
+ const prepared = await prepareSkillReferences({
206
+ packageName: storageName,
207
+ version,
208
+ cwd,
209
+ skillDir,
210
+ resources,
211
+ features,
212
+ baseDir,
213
+ onIndexProgress: (msg) => hooks.callHook("index:progress", {
214
+ spec,
215
+ message: msg
216
+ })
217
+ });
218
+ if (features.search) await hooks.callHook("index:done", { spec });
219
+ if (!isEject) {
220
+ const repoSlug = parseGitHubRepoSlug(resolved.repoUrl);
221
+ cache.linkPkgNamed(skillDir, cwd);
222
+ const lock = {
223
+ packageName: identityName,
168
224
  version,
225
+ repo: repoSlug,
226
+ source: resources.docSource,
227
+ syncedAt: todayIsoDate(),
228
+ generator: "skilld"
229
+ };
230
+ installSkill({
169
231
  cwd,
170
- docsToIndex: resources.docsToIndex,
171
- features,
172
- onProgress: (msg) => idxSpin.message(msg)
232
+ agent: config.agent,
233
+ global: config.global,
234
+ baseDir,
235
+ skillDirName,
236
+ lock,
237
+ dedupePackageName: identityName,
238
+ skipLinkAgents: true
173
239
  });
174
- idxSpin.stop("Search index ready");
175
240
  }
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,
241
+ const allPackages = parsePackageNames((isEject ? void 0 : readLock(baseDir)?.skills[skillDirName])?.packages);
242
+ const ctx = buildSkillContext({
243
+ packageName: identityName,
244
+ cachePackageName: storageName,
181
245
  version,
182
- repo: `${owner}/${repo}`,
183
- source: resources.docSource,
184
- syncedAt: todayIsoDate(),
185
- generator: "skilld"
186
- });
187
- 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,
198
- hasShippedDocs: shippedDocs,
199
- pkgFiles,
200
- dirName: skillDirName,
201
- repoUrl,
246
+ skillDir,
247
+ skillDirName,
248
+ resources,
249
+ prepared,
250
+ resolved,
251
+ packages: allPackages,
202
252
  features
203
253
  });
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,
254
+ ctx.overheadLines = writeBaseSkill(ctx, { eject: isEject }).split("\n").length;
255
+ await hooks.callHook("base:done", {
256
+ spec,
257
+ skillDir: relative(cwd, skillDir),
258
+ mode: config.mode === "update" ? "update" : "add"
259
+ });
260
+ const allSectionsCached = !config.force && applyCachedSections(ctx, defaultSections, { eject: isEject });
261
+ if (allSectionsCached) await hooks.callHook("sections:cached", { spec });
262
+ return {
263
+ kind: "ready",
264
+ state: {
265
+ ctx,
209
266
  skillDir,
267
+ skillDirName,
268
+ baseDir,
269
+ updateCtx,
270
+ allSectionsCached,
271
+ identityName,
272
+ storageName,
210
273
  version,
211
- hasIssues: resources.hasIssues,
212
- hasDiscussions: resources.hasDiscussions,
213
- hasReleases: resources.hasReleases,
214
- hasChangelog,
215
274
  docsType: resources.docsType,
216
- hasShippedDocs: shippedDocs,
217
- pkgFiles,
218
- sections: llmConfig.sections,
219
- customPrompt: llmConfig.customPrompt,
220
- features
275
+ repoInfo: resources.repoInfo
276
+ }
277
+ };
278
+ }
279
+ async function runEnhancePhase(state, llmConfig, config, hooks, cwd) {
280
+ const isEject = !!config.eject;
281
+ const spec = state.identityName;
282
+ if (llmConfig?.promptOnly) writePromptFiles({
283
+ ...state.ctx,
284
+ packageName: state.ctx.cachePackageName ?? state.ctx.packageName,
285
+ cachePackageName: void 0
286
+ }, {
287
+ sections: llmConfig.sections,
288
+ customPrompt: llmConfig.customPrompt
289
+ });
290
+ else if (llmConfig) await enhanceWithHooks(state.ctx, llmConfig, {
291
+ ...config,
292
+ eject: isEject
293
+ }, hooks, spec);
294
+ if (isEject) {
295
+ const cache = createReferenceCache(state.storageName, state.version);
296
+ if (!config.debug) cache.clearSkillInternal(state.skillDir);
297
+ cache.eject(state.skillDir, cwd, state.docsType, {
298
+ features: state.ctx.features ?? getActiveFeatures(),
299
+ repoInfo: state.repoInfo
221
300
  });
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
- });
301
+ return;
302
+ }
303
+ const shared = config.global ? false : getSharedSkillsDir(cwd) ?? false;
304
+ if (shared) linkSkillToAgents(state.skillDirName, shared, cwd, config.agent);
305
+ await ensureProjectFiles({
306
+ cwd,
307
+ agent: config.agent,
308
+ global: config.global,
309
+ shared
310
+ });
311
+ }
312
+ async function enhanceWithHooks(ctx, llmConfig, config, hooks, spec) {
313
+ await hooks.callHook("enhance:start", {
314
+ spec,
315
+ modelLabel: getModelLabel(llmConfig.model)
316
+ });
317
+ const result = await runSkillEnhancement(ctx, {
318
+ model: llmConfig.model,
319
+ force: config.force,
320
+ debug: config.debug,
321
+ sections: llmConfig.sections,
322
+ customPrompt: llmConfig.customPrompt,
323
+ eject: !!config.eject
324
+ }, (progress) => hooks.callHook("enhance:progress", {
325
+ spec,
326
+ progress
327
+ }));
328
+ if (result.wasOptimized) await hooks.callHook("enhance:done", {
329
+ spec,
330
+ usage: result.usage ? { totalTokens: result.usage.totalTokens } : void 0,
331
+ cost: result.cost,
332
+ debugLogsDir: result.debugLogsDir,
333
+ error: result.error,
334
+ warnings: result.warnings
335
+ });
336
+ else await hooks.callHook("enhance:failed", {
337
+ spec,
338
+ error: result.error ?? "",
339
+ rateLimited: !!result.error && RATE_LIMIT_RE.test(result.error)
340
+ });
341
+ }
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
+ };
245
378
  }
379
+ return {
380
+ kind: "ready",
381
+ spec,
382
+ state: result.state
383
+ };
246
384
  }
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);
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
+ };
253
408
  }
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"
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 }) {
426
+ let spinner = null;
427
+ let resourceSpinner = null;
428
+ let indexSpinner = null;
429
+ let llmLog = null;
430
+ let currentSpec = "";
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
489
+ });
490
+ });
491
+ hooks.hook("enhance:progress", ({ progress }) => {
492
+ if (!llmLog) return;
493
+ const line = `${progress.section ? `[${progress.section}] ` : ""}${progress.chunk}`;
494
+ llmLog.message(line);
495
+ });
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));
507
+ });
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)}`);
262
516
  });
263
- p.outro(`Synced ${owner}/${repo} to ${relative(cwd, skillDir)}`);
264
517
  }
265
518
  const STATUS_ICONS = {
266
519
  pending: "○",
@@ -274,419 +527,290 @@ const STATUS_ICONS = {
274
527
  error: "✗"
275
528
  };
276
529
  const STATUS_COLORS = {
277
- pending: "\x1B[90m",
278
- resolving: "\x1B[36m",
279
- downloading: "\x1B[36m",
280
- embedding: "\x1B[36m",
281
- exploring: "\x1B[34m",
282
- thinking: "\x1B[35m",
283
- generating: "\x1B[33m",
284
- done: "\x1B[32m",
285
- 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"
286
539
  };
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..."
540
+ function renderParallel(r) {
541
+ const maxNameLen = Math.max(...[...r.states.keys()].map((n) => n.length), 20);
542
+ const lines = [...r.states.values()].map((s) => {
543
+ const icon = styleText(STATUS_COLORS[s.status], STATUS_ICONS[s.status]);
544
+ const name = s.name.padEnd(maxNameLen);
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}`;
296
549
  });
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);
550
+ const doneCount = [...r.states.values()].filter((s) => s.status === "done").length;
551
+ const errorCount = [...r.states.values()].filter((s) => s.status === "error").length;
552
+ logUpdate(`${styleText("bold", `${r.verb} ${r.total} packages`)} (${doneCount} done${errorCount > 0 ? `, ${errorCount} failed` : ""})\n` + lines.join("\n"));
553
+ }
554
+ function bindParallelUi(hooks, render) {
555
+ function update(spec, status, message, ver) {
556
+ const state = render.states.get(spec);
557
+ if (!state) return;
316
558
  if (!state.startedAt && status !== "pending") state.startedAt = performance.now();
317
559
  if ((status === "done" || status === "error") && !state.completedAt) state.completedAt = performance.now();
318
560
  state.status = status;
319
561
  state.message = message;
320
562
  state.streamPreview = void 0;
321
- if (version) state.version = version;
322
- render();
563
+ if (ver) state.version = ver;
564
+ renderParallel(render);
323
565
  }
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"));
591
+ }
592
+ const DIFF_RANK = {
593
+ major: 5,
594
+ premajor: 4,
595
+ minor: 3,
596
+ preminor: 2,
597
+ patch: 1,
598
+ prepatch: 1,
599
+ prerelease: 0
600
+ };
601
+ async function syncPackagesParallel(config) {
602
+ const { packages, concurrency = 5 } = config;
603
+ const cwd = process.cwd();
604
+ const states = /* @__PURE__ */ new Map();
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
+ }
615
+ const render = {
616
+ states,
617
+ verb: config.mode === "update" ? "Updating" : "Syncing",
618
+ total: packages.length
619
+ };
324
620
  ensureCacheDir();
325
- render();
621
+ renderParallel(render);
622
+ const run = createSyncRun({
623
+ cwd,
624
+ resolver: npmResolver,
625
+ agent: config.agent,
626
+ global: config.global,
627
+ mode: config.mode,
628
+ force: config.force,
629
+ debug: config.debug,
630
+ defaultSections: DEFAULT_SECTIONS
631
+ });
632
+ bindParallelUi(run.hooks, render);
326
633
  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))));
634
+ const baseResults = await Promise.allSettled(packages.map((spec) => limit(() => run.runBase(spec))));
329
635
  logUpdate.done();
330
- const successfulPkgs = [];
331
- const shippedPkgs = [];
636
+ const ready = [];
637
+ const shippedCount = [];
332
638
  const errors = [];
639
+ const aggregatedWarnings = [];
333
640
  for (let i = 0; i < baseResults.length; i++) {
641
+ const spec = packages[i];
334
642
  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") {
643
+ if (r.status === "rejected") {
340
644
  const err = r.reason;
341
- const reason = err instanceof Error ? `${err.message}\n${err.stack}` : String(err);
645
+ const reason = err instanceof Error ? err.message : String(err);
646
+ const slot = states.get(spec);
647
+ if (slot) slot.status = "error";
342
648
  errors.push({
343
- pkg: packages[i],
649
+ spec,
344
650
  reason
345
651
  });
346
- }
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));
652
+ continue;
653
+ }
654
+ const result = r.value;
655
+ if (result.kind === "shipped") {
656
+ shippedCount.push(spec);
657
+ continue;
658
+ }
659
+ if (result.kind === "unresolved") {
660
+ const npmAttempt = result.attempts.find((a) => a.source === "npm");
661
+ let reason;
662
+ if (npmAttempt?.status === "not-found") {
663
+ const suggestions = await searchNpmPackages(result.identityName, 3);
664
+ const hint = suggestions.length > 0 ? ` (try: ${suggestions.map((s) => s.name).join(", ")})` : "";
665
+ reason = (npmAttempt.message || "Not on npm") + hint;
666
+ } else reason = result.attempts.filter((a) => a.status !== "success").map((a) => a.message || a.source).join("; ") || "No docs found";
667
+ const slot = states.get(spec);
668
+ if (slot) {
669
+ slot.status = "error";
670
+ slot.message = reason;
368
671
  }
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
672
+ errors.push({
673
+ spec,
674
+ reason
675
+ });
676
+ continue;
677
+ }
678
+ if (result.kind === "error") {
679
+ errors.push({
680
+ spec,
681
+ reason: result.reason
389
682
  });
390
- cachedPkgs.push(pkg);
683
+ continue;
391
684
  }
685
+ if (result.kind === "ready") ready.push({
686
+ spec,
687
+ state: result.state
688
+ });
392
689
  }
393
- const uncachedPkgs = successfulPkgs.filter((pkg) => !cachedPkgs.includes(pkg));
690
+ renderParallel(render);
691
+ logUpdate.done();
692
+ const pastVerb = config.mode === "update" ? "Updated" : "Created";
693
+ p.log.success(`${pastVerb} ${ready.length} base skills${shippedCount.length > 0 ? ` (${shippedCount.length} shipped)` : ""}`);
694
+ for (const w of aggregatedWarnings) p.log.warn(styleText("yellow", w));
695
+ for (const { spec, reason } of errors) p.log.error(` ${spec}: ${reason}`);
696
+ const cachedPkgs = [];
697
+ const uncached = [];
698
+ for (const r of ready) if (r.state.allSectionsCached) cachedPkgs.push(r.spec);
699
+ else uncached.push(r);
394
700
  if (cachedPkgs.length > 0) p.log.success(`Applied cached SKILL.md sections for ${cachedPkgs.join(", ")}`);
395
701
  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;
702
+ const resolvedModel = await resolveAutoModel(config.model, config.yes);
703
+ if (uncached.length > 0 && !globalConfig.skipLlm && !(config.yes && !resolvedModel)) {
704
+ const llmConfig = await selectLlmConfig(resolvedModel, void 0, config.mode === "update" ? aggregateUpdateCtx(uncached) : void 0);
705
+ if (llmConfig?.promptOnly) {
706
+ for (const r of uncached) {
707
+ const slot = states.get(r.spec);
708
+ if (slot) {
709
+ slot.status = "done";
710
+ slot.message = "Prompts written";
425
711
  }
426
712
  }
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) {
713
+ renderParallel(render);
714
+ for (const r of uncached) await run.runEnhance(r.state, llmConfig);
715
+ } else if (llmConfig) {
457
716
  p.log.step(getModelLabel(llmConfig.model));
458
- for (const pkg of uncachedPkgs) states.set(pkg, {
459
- name: pkg,
460
- status: "pending",
461
- message: "Waiting..."
462
- });
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))));
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
+ }
726
+ renderParallel(render);
727
+ const llmResults = await Promise.allSettled(uncached.map((r) => limit(() => run.runEnhance(r.state, llmConfig))));
468
728
  logUpdate.done();
469
- const llmSucceeded = llmResults.filter((r) => r.status === "fulfilled").length;
470
- p.log.success(`Enhanced ${llmSucceeded}/${uncachedPkgs.length} skills with LLM`);
729
+ const llmSucceeded = llmResults.filter((x) => x.status === "fulfilled").length;
730
+ p.log.success(`Enhanced ${llmSucceeded}/${uncached.length} skills with LLM`);
471
731
  }
472
- }
473
- await ensureGitignore(getSharedSkillsDir(cwd) ? SHARED_SKILLS_DIR : agent.skillsDir, cwd, config.global);
474
- await ensureAgentInstructions(config.agent, cwd, config.global);
732
+ } else for (const r of ready) await run.runEnhance(r.state, null);
733
+ await ensureProjectFiles({
734
+ cwd,
735
+ agent: config.agent,
736
+ global: config.global
737
+ });
475
738
  await shutdownWorker();
476
- p.outro(`${pastVerb} ${successfulPkgs.length}/${packages.length} packages`);
477
- const { suggestPrepareHook } = await import("./cli-helpers2.mjs");
739
+ p.outro(`${pastVerb} ${ready.length}/${packages.length} packages`);
740
+ const { suggestPrepareHook } = await import("./prepare-hook.mjs");
478
741
  try {
479
742
  await suggestPrepareHook(cwd);
480
743
  } catch (err) {
481
744
  p.log.warn(`Failed to suggest prepare hook: ${err instanceof Error ? err.message : String(err)}`);
482
745
  }
483
746
  }
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,
747
+ function aggregateUpdateCtx(ready) {
748
+ let maxDiff = "";
749
+ let allEnhanced = true;
750
+ let anySyncedAt;
751
+ for (const r of ready) {
752
+ const u = r.state.updateCtx;
753
+ if (!u?.wasEnhanced) allEnhanced = false;
754
+ if (u?.syncedAt && (!anySyncedAt || u.syncedAt < anySyncedAt)) anySyncedAt = u.syncedAt;
755
+ if (u?.oldVersion && u.newVersion) {
756
+ const diff = semverDiff(u.oldVersion, u.newVersion);
757
+ if (diff && (DIFF_RANK[diff] ?? 0) > (DIFF_RANK[maxDiff] ?? -1)) maxDiff = diff;
758
+ }
759
+ }
760
+ const first = ready[0]?.state.updateCtx;
761
+ return {
762
+ oldVersion: ready.length === 1 ? first?.oldVersion : void 0,
763
+ newVersion: ready.length === 1 ? first?.newVersion : void 0,
764
+ syncedAt: anySyncedAt,
765
+ wasEnhanced: allEnhanced,
766
+ bumpType: maxDiff || void 0
767
+ };
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({
489
775
  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";
505
- }
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,
776
+ agent: config.agent,
777
+ global: config.global,
778
+ baseDir,
779
+ skillDirName,
780
+ lock: {
781
+ packageName: identityName,
568
782
  version,
569
- cwd,
570
- docsToIndex: resources.docsToIndex,
571
- features,
572
- onProgress: (msg) => update(packageName, "embedding", msg, versionKey)
573
- });
574
- }
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"
783
+ repo: repoSlug,
784
+ source: existingLock.source,
785
+ syncedAt: todayIsoDate(),
786
+ generator: "skilld"
787
+ },
788
+ skipLinkAgents: true
588
789
  });
589
790
  const updatedLock = readLock(baseDir)?.skills[skillDirName];
590
791
  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,
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,
597
800
  relatedSkills,
598
- hasIssues: resources.hasIssues,
599
- hasDiscussions: resources.hasDiscussions,
600
- hasReleases: resources.hasReleases,
601
- hasChangelog,
602
- docsType: resources.docsType,
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",
603
805
  hasShippedDocs: shippedDocs,
604
806
  pkgFiles,
605
807
  dirName: skillDirName,
606
- packages: allPackages.length > 1 ? allPackages : void 0,
607
- repoUrl: resolved.repoUrl,
808
+ packages: allPackages,
608
809
  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);
614
- 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
634
- };
635
- }
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
810
  });
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);
811
+ const sharedDir = !config.global && getSharedSkillsDir(cwd);
812
+ if (sharedDir) linkSkillToAgents(skillDirName, sharedDir, cwd, config.agent);
813
+ p.outro(`Merged ${identityName} into ${skillDirName}`);
690
814
  }
691
815
  const RESOLVE_SOURCE_LABELS = {
692
816
  "npm": "npm registry",
@@ -700,20 +824,14 @@ const RESOLVE_SOURCE_LABELS = {
700
824
  };
701
825
  function showResolveAttempts(attempts) {
702
826
  if (attempts.length === 0) return;
703
- p.log.message("\x1B[90mDoc resolution:\x1B[0m");
827
+ p.log.message(styleText("gray", "Doc resolution:"));
704
828
  for (const attempt of attempts) {
705
- const icon = attempt.status === "success" ? "\x1B[32m✓\x1B[0m" : "\x1B[90m✗\x1B[0m";
706
- const source = `\x1B[90m${RESOLVE_SOURCE_LABELS[attempt.source] ?? attempt.source}\x1B[0m`;
707
- 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}`)}` : "";
708
832
  p.log.message(` ${icon} ${source}${msg}`);
709
833
  }
710
834
  }
711
- function isCrateSpec(spec) {
712
- return spec.startsWith("crate:");
713
- }
714
- function toCrateIdentity(crateName) {
715
- return `crate:${crateName}`;
716
- }
717
835
  async function syncCommand(state, opts) {
718
836
  if (opts.packages && opts.packages.length > 0) {
719
837
  const crateSpecs = opts.packages.filter(isCrateSpec);
@@ -805,52 +923,38 @@ async function pickFromList(packages, state) {
805
923
  }
806
924
  return selected;
807
925
  }
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}`);
926
+ async function runSimpleSync(packageSpec, config) {
821
927
  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,
928
+ const isEject = !!config.eject;
929
+ const run = createSyncRun({
829
930
  cwd,
830
- onProgress: (step) => spin.message(`${packageName}: ${RESOLVE_STEP_LABELS[step]}`)
931
+ resolver: npmResolver,
932
+ agent: config.agent,
933
+ global: config.global,
934
+ mode: config.mode,
935
+ force: config.force,
936
+ noSearch: config.noSearch,
937
+ name: config.name,
938
+ from: config.from,
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)
831
946
  });
832
- let resolved = resolveResult.package;
833
- if (!resolved && !isCrate) {
834
- spin.message(`Resolving local package: ${packageName}`);
835
- resolved = await resolveLocalDep(packageName, cwd);
947
+ bindClackUi(run.hooks, { cwd });
948
+ const base = await run.runBase(packageSpec);
949
+ if (base.kind === "shipped") {
950
+ p.outro(`Synced ${packageSpec}`);
951
+ return;
836
952
  }
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);
953
+ if (base.kind === "unresolved") {
954
+ if (!isCrateSpec(packageSpec)) {
955
+ const suggestions = await searchNpmPackages(base.identityName);
851
956
  if (suggestions.length > 0) {
852
- spin.stop(`Package "${packageName}" not found on npm`);
853
- showResolveAttempts(resolveResult.attempts);
957
+ showResolveAttempts(base.attempts);
854
958
  const selected = await p.select({
855
959
  message: "Did you mean one of these?",
856
960
  options: [...suggestions.map((s) => ({
@@ -866,725 +970,33 @@ async function syncSinglePackage(packageSpec, config) {
866
970
  return;
867
971
  }
868
972
  }
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}`);
889
- return;
890
- }
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}`);
973
+ showResolveAttempts(base.attempts);
959
974
  return;
960
975
  }
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
- }
976
+ if (base.kind === "merged" || base.kind === "error") return;
977
+ const { state } = base;
1083
978
  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
- }
979
+ const resolvedModel = await resolveAutoModel(config.model, config.yes);
980
+ let llmConfig = null;
981
+ if (!state.allSectionsCached && !globalConfig.skipLlm && !(config.yes && !resolvedModel)) llmConfig = await selectLlmConfig(resolvedModel, void 0, state.updateCtx);
982
+ await run.runEnhance(state, llmConfig);
1152
983
  await shutdownWorker();
1153
984
  const ejectMsg = isEject ? " (ejected)" : "";
1154
- const relDir = relative(cwd, skillDir);
1155
- p.outro(config.mode === "update" ? `Updated ${identityPackageName}${ejectMsg}` : `Synced ${identityPackageName} → ${relDir}${ejectMsg}`);
985
+ const relDir = relative(cwd, state.skillDir);
986
+ p.outro(config.mode === "update" ? `Updated ${state.identityName}${ejectMsg}` : `Synced ${state.identityName} → ${relDir}${ejectMsg}`);
1156
987
  try {
1157
988
  await suggestPrepareHook(cwd);
1158
989
  } catch (err) {
1159
990
  p.log.warn(`Failed to suggest prepare hook: ${err instanceof Error ? err.message : String(err)}`);
1160
991
  }
1161
992
  }
1162
- const addCommandDef = defineCommand({
1163
- meta: {
1164
- name: "add",
1165
- description: "Install skills (npm:<pkg>, crate:<name>, gh:<owner/repo>, @<curator>)"
1166
- },
1167
- args: {
1168
- package: {
1169
- type: "positional",
1170
- description: "Package(s) to sync (space/comma-separated; npm:<pkg>, crate:<name>, or owner/repo)",
1171
- required: true
1172
- },
1173
- skill: {
1174
- type: "string",
1175
- alias: "s",
1176
- description: "Select specific skills from a git repo (comma-separated)",
1177
- valueHint: "name"
1178
- },
1179
- ...sharedArgs
1180
- },
1181
- async run({ args }) {
1182
- const cwd = process.cwd();
1183
- let agent = resolveAgent(args.agent);
1184
- if (!agent) {
1185
- agent = await promptForAgent();
1186
- if (!agent) return;
1187
- }
1188
- const rawInputs = [...new Set([args.package, ...args._ || []].map((s) => s.trim()).filter(Boolean))];
1189
- if (agent === "none") {
1190
- const packages = [...new Set(rawInputs.flatMap((s) => s.split(/[,\s]+/)).map((s) => s.trim()).filter(Boolean))];
1191
- for (const pkg of packages) await exportPortablePrompts(pkg, {
1192
- force: args.force,
1193
- agent: "none"
1194
- });
1195
- return;
1196
- }
1197
- if (!hasCompletedWizard()) await runWizard({ agent });
1198
- const parsedSources = rawInputs.map(parseSkillInput);
1199
- const gitSources = [];
1200
- const npmEntries = [];
1201
- const crateSpecs = [];
1202
- const unsupported = [];
1203
- for (const source of parsedSources) switch (source.type) {
1204
- case "git":
1205
- gitSources.push(source.source);
1206
- break;
1207
- case "npm":
1208
- npmEntries.push({
1209
- name: source.package,
1210
- spec: source.tag ? `${source.package}@${source.tag}` : source.package
1211
- });
1212
- break;
1213
- case "crate":
1214
- crateSpecs.push(source.version ? `crate:${source.package}@${source.version}` : `crate:${source.package}`);
1215
- break;
1216
- case "bare":
1217
- p.log.warn(`Bare names are deprecated. Use \x1B[36mnpm:${source.package}\x1B[0m instead.`);
1218
- npmEntries.push({
1219
- name: source.package,
1220
- spec: source.tag ? `${source.package}@${source.tag}` : source.package
1221
- });
1222
- break;
1223
- case "curator":
1224
- unsupported.push(`@${source.handle} (curator)`);
1225
- break;
1226
- case "collection":
1227
- unsupported.push(`@${source.handle}/${source.name} (collection)`);
1228
- break;
1229
- default: throw new Error(`Unhandled SkillSource type: ${JSON.stringify(source)}`);
1230
- }
1231
- if (unsupported.length > 0) {
1232
- p.log.error(`Curator and collection installs are not yet available:\n ${unsupported.join("\n ")}\n\nFollow https://skilld.dev for launch updates.`);
1233
- process.exitCode = 1;
1234
- if (gitSources.length === 0 && npmEntries.length === 0 && crateSpecs.length === 0) return;
1235
- }
1236
- if (gitSources.length > 0) for (const source of gitSources) {
1237
- const skillFilter = args.skill ? args.skill.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean) : void 0;
1238
- await syncGitSkills({
1239
- source,
1240
- global: args.global,
1241
- agent,
1242
- yes: args.yes,
1243
- model: args.model,
1244
- force: args.force,
1245
- debug: args.debug,
1246
- skillFilter
1247
- });
1248
- }
1249
- if (npmEntries.length > 0) {
1250
- const { syncRegistrySkill } = await import("./sync-registry.mjs");
1251
- const seen = /* @__PURE__ */ new Set();
1252
- const dedupedEntries = npmEntries.filter((e) => {
1253
- if (seen.has(e.name)) return false;
1254
- seen.add(e.name);
1255
- return true;
1256
- });
1257
- const fallbackPackages = [];
1258
- for (const entry of dedupedEntries) {
1259
- const result = await syncRegistrySkill({
1260
- packageName: entry.name,
1261
- agent,
1262
- cwd
1263
- });
1264
- if (result) p.log.success(`Installed \x1B[36m${result.name}\x1B[0m from registry`);
1265
- else fallbackPackages.push(entry.spec);
1266
- }
1267
- if (fallbackPackages.length > 0) {
1268
- const state = await getProjectState(cwd);
1269
- p.intro(introLine({
1270
- state,
1271
- agentId: agent || void 0
1272
- }));
1273
- await syncCommand(state, {
1274
- packages: [...fallbackPackages, ...crateSpecs],
1275
- global: args.global,
1276
- agent,
1277
- model: args.model,
1278
- yes: args.yes,
1279
- force: args.force,
1280
- debug: args.debug
1281
- });
1282
- return;
1283
- }
1284
- }
1285
- if (crateSpecs.length > 0) {
1286
- const state = await getProjectState(cwd);
1287
- p.intro(introLine({
1288
- state,
1289
- agentId: agent || void 0
1290
- }));
1291
- await syncCommand(state, {
1292
- packages: crateSpecs,
1293
- global: args.global,
1294
- agent,
1295
- model: args.model,
1296
- yes: args.yes,
1297
- force: args.force,
1298
- debug: args.debug
1299
- });
1300
- }
1301
- }
1302
- });
1303
- const ejectCommandDef = defineCommand({
1304
- meta: {
1305
- name: "eject",
1306
- description: "Eject skill with references as real files (portable, no symlinks)"
1307
- },
1308
- args: {
1309
- package: {
1310
- type: "positional",
1311
- description: "Package to eject",
1312
- required: true
1313
- },
1314
- name: {
1315
- type: "string",
1316
- alias: "n",
1317
- description: "Custom skill directory name (default: derived from package)"
1318
- },
1319
- out: {
1320
- type: "string",
1321
- alias: "o",
1322
- description: "Output directory path override"
1323
- },
1324
- from: {
1325
- type: "string",
1326
- description: "Collect releases/issues/discussions from this date onward (YYYY-MM-DD)"
1327
- },
1328
- search: {
1329
- type: "boolean",
1330
- description: "Build search index / embeddings (use --no-search to skip)",
1331
- default: true
1332
- },
1333
- ...sharedArgs
1334
- },
1335
- async run({ args }) {
1336
- const cwd = process.cwd();
1337
- const resolved = resolveAgent(args.agent);
1338
- const agent = resolved && resolved !== "none" ? resolved : "claude-code";
1339
- if (!hasCompletedWizard()) await runWizard({ agent });
1340
- const state = await getProjectState(cwd);
1341
- p.intro(introLine({
1342
- state,
1343
- agentId: agent || void 0
1344
- }));
1345
- return syncCommand(state, {
1346
- packages: [args.package],
1347
- global: args.global,
1348
- agent,
1349
- model: args.model,
1350
- yes: args.yes,
1351
- force: args.force,
1352
- debug: args.debug,
1353
- eject: args.out || true,
1354
- name: args.name,
1355
- from: args.from,
1356
- noSearch: !args.search
1357
- });
1358
- }
1359
- });
1360
- const updateCommandDef = defineCommand({
1361
- meta: {
1362
- name: "update",
1363
- description: "Update outdated skills"
1364
- },
1365
- args: {
1366
- package: {
1367
- type: "positional",
1368
- description: "Package(s) to update (space or comma-separated). Without args, syncs all outdated.",
1369
- required: false
1370
- },
1371
- background: {
1372
- type: "boolean",
1373
- alias: "b",
1374
- description: "Run in background (detached process, non-interactive)",
1375
- default: false
1376
- },
1377
- ...sharedArgs
1378
- },
1379
- async run({ args }) {
1380
- const cwd = process.cwd();
1381
- if (args.background) {
1382
- const { spawn } = await import("node:child_process");
1383
- const updateArgs = [
1384
- "update",
1385
- ...args.package ? [args.package] : [],
1386
- ...args.agent ? ["--agent", args.agent] : [],
1387
- ...args.model ? ["--model", args.model] : []
1388
- ];
1389
- spawn(process.execPath, [process.argv[1], ...updateArgs], {
1390
- cwd,
1391
- detached: true,
1392
- stdio: "ignore"
1393
- }).unref();
1394
- return;
1395
- }
1396
- const silent = !isInteractive();
1397
- let agent = resolveAgent(args.agent);
1398
- if (!agent) {
1399
- agent = await promptForAgent();
1400
- if (!agent) return;
1401
- }
1402
- if (agent === "none") {
1403
- const state = await getProjectState(cwd);
1404
- 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);
1405
- if (packages.length === 0) {
1406
- if (!silent) p.log.success("All skills up to date");
1407
- return;
1408
- }
1409
- for (const pkg of packages) await exportPortablePrompts(pkg, {
1410
- force: args.force,
1411
- agent: "none"
1412
- });
1413
- return;
1414
- }
1415
- const config = readConfig();
1416
- const state = await getProjectState(cwd);
1417
- if (!silent) {
1418
- const generators = getInstalledGenerators();
1419
- p.intro(introLine({
1420
- state,
1421
- generators,
1422
- modelId: config.model,
1423
- agentId: config.agent || agent || void 0
1424
- }));
1425
- }
1426
- if (args.package) {
1427
- const raw = [...new Set([args.package, ...args._ || []].flatMap((s) => s.split(/[,\s]+/)).map((s) => s.trim()).filter(Boolean))];
1428
- const packages = [];
1429
- for (const r of raw) {
1430
- const name = resolveSkillName(r);
1431
- if (!name) {
1432
- p.log.warn(`Cannot update \x1B[36m${r}\x1B[0m: curator/collection inputs are not addressable here.`);
1433
- continue;
1434
- }
1435
- packages.push(name);
1436
- }
1437
- if (packages.length === 0) return;
1438
- return syncCommand(state, {
1439
- packages,
1440
- global: args.global,
1441
- agent,
1442
- model: args.model || (silent ? config.model : void 0),
1443
- yes: args.yes || silent,
1444
- force: args.force,
1445
- debug: args.debug,
1446
- mode: "update"
1447
- });
1448
- }
1449
- const crateSpecs = state.skills.map((s) => s.info?.packageName).filter((name) => !!name && name.startsWith("crate:"));
1450
- if (state.outdated.length === 0 && crateSpecs.length === 0) {
1451
- p.log.success("All skills up to date");
1452
- return;
1453
- }
1454
- return syncCommand(state, {
1455
- packages: [...state.outdated.map((s) => s.packageName || s.name), ...crateSpecs],
1456
- global: args.global,
1457
- agent,
1458
- model: args.model || (silent ? config.model : void 0),
1459
- yes: args.yes || silent,
1460
- force: args.force,
1461
- debug: args.debug,
1462
- mode: "update"
1463
- });
1464
- }
1465
- });
1466
- async function exportPortablePrompts(packageSpec, opts) {
1467
- const { name: packageName } = parsePackageSpec(packageSpec);
1468
- const sections = opts.sections ?? DEFAULT_SECTIONS;
1469
- const spin = timedSpinner();
1470
- spin.start(`Resolving ${packageSpec}`);
1471
- 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,
1475
- 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
- }
1482
- if (!resolved) {
1483
- spin.stop(`Could not find docs for: ${packageName}`);
993
+ async function syncSinglePackage(packageSpec, config) {
994
+ if (isCrateSpec(packageSpec) && !packageSpec.slice(6).trim()) {
995
+ p.log.error("Invalid crate spec. Use format: crate:<name>");
1484
996
  return;
1485
997
  }
1486
- const version = localVersion || resolved.version || "latest";
1487
- const versionKey = getVersionKey(version);
1488
- const useCache = !opts.force && isCached(packageName, version);
1489
- if (!existsSync(join(cwd, "node_modules", packageName))) {
1490
- spin.message(`Downloading ${packageName}@${version} dist`);
1491
- await fetchPkgDist(packageName, version);
1492
- }
1493
- spin.stop(`Resolved ${packageName}@${useCache ? versionKey : version}`);
1494
- ensureCacheDir();
1495
- const skillDirName = computeSkillDirName(packageName);
1496
- const features = readConfig().features ?? defaultFeatures;
1497
- const agent = opts.agent === "none" ? null : opts.agent ?? await import("./detect2.mjs").then((m) => m.detectTargetAgent());
1498
- const baseDir = agent ? resolveBaseDir(cwd, agent, false) : join(cwd, ".claude", "skills");
1499
- const skillDir = opts.out ? resolve(cwd, opts.out) : join(baseDir, skillDirName);
1500
- if (existsSync(skillDir) && !opts.force) {
1501
- const existing = Object.values(SECTION_OUTPUT_FILES).filter((f) => existsSync(join(skillDir, f)));
1502
- if (existing.length > 0) p.log.warn(`Overwriting existing output files in ${relative(cwd, skillDir)}: ${existing.join(", ")}`);
1503
- }
1504
- mkdirSync(skillDir, { recursive: true });
1505
- const resSpin = timedSpinner();
1506
- resSpin.start("Fetching resources");
1507
- const resources = await fetchAndCacheResources({
1508
- packageName,
1509
- resolved,
1510
- version,
1511
- useCache,
1512
- features,
1513
- onProgress: (msg) => resSpin.message(msg)
1514
- });
1515
- resSpin.stop("Resources ready");
1516
- 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);
1521
- const docFiles = listReferenceFiles(skillDir);
1522
- const prompts = buildAllSectionPrompts({
1523
- packageName,
1524
- skillDir,
1525
- version,
1526
- hasIssues: resources.hasIssues,
1527
- hasDiscussions: resources.hasDiscussions,
1528
- hasReleases: resources.hasReleases,
1529
- hasChangelog,
1530
- docFiles,
1531
- docsType: resources.docsType,
1532
- hasShippedDocs: shippedDocs,
1533
- pkgFiles,
1534
- features,
1535
- sections
1536
- });
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
1542
- });
1543
- for (const [section, prompt] of prompts) {
1544
- const portable = portabilizePrompt(prompt, section);
1545
- writeFileSync(join(skillDir, `PROMPT_${section}.md`), portable);
1546
- }
1547
- const relatedSkills = await findRelatedSkills(packageName, join(skillDir, ".."));
1548
- writeGeneratedSkillMd(skillDir, {
1549
- name: packageName,
1550
- version,
1551
- releasedAt: resolved.releasedAt,
1552
- description: resolved.description,
1553
- distTags: resolved.distTags,
1554
- relatedSkills,
1555
- hasIssues: resources.hasIssues,
1556
- hasDiscussions: resources.hasDiscussions,
1557
- hasReleases: resources.hasReleases,
1558
- hasChangelog,
1559
- docsType: resources.docsType,
1560
- hasShippedDocs: shippedDocs,
1561
- pkgFiles,
1562
- repoUrl: resolved.repoUrl,
1563
- features,
1564
- eject: true
1565
- });
1566
- writeLock(baseDir, skillDirName, {
1567
- packageName,
1568
- version,
1569
- repo: parseGitHubRepoSlug(resolved.repoUrl),
1570
- source: resources.docSource,
1571
- syncedAt: todayIsoDate(),
1572
- generator: "skilld"
1573
- });
1574
- 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);
1581
- const relDir = relative(cwd, skillDir);
1582
- const sectionList = [...prompts.keys()];
1583
- p.log.success(`Skill installed to ${relDir}`);
1584
- const promptFiles = sectionList.map((s) => `PROMPT_${s}.md`).join(", ");
1585
- const outputFileList = sectionList.map((s) => SECTION_OUTPUT_FILES[s]).join(", ");
1586
- 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`);
998
+ return runSimpleSync(packageSpec, config);
1587
999
  }
1588
- export { syncCommand as a, isCrateSpec as i, ejectCommandDef as n, updateCommandDef as o, exportPortablePrompts as r, addCommandDef as t };
1000
+ export { createGithubResolver as i, bindClackUi as n, createSyncRun as r, syncCommand as t };
1589
1001
 
1590
1002
  //# sourceMappingURL=sync.mjs.map