skilld 0.9.6 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -5,8 +5,8 @@ import { a as getShippedSkills, b as sanitizeMarkdown, c as linkCachedDir, d as
5
5
  import "./cache/index.mjs";
6
6
  import { n as clearEmbeddingCache } from "./_chunks/embedding-cache.mjs";
7
7
  import { closePool, createIndex, openPool, searchPooled, searchSnippets } from "./retriv/index.mjs";
8
- import { i as getBlogPreset, n as yamlParseKV, r as yamlUnescape, t as yamlEscape } from "./_chunks/yaml.mjs";
9
- import { $ as mapInsert, A as resolveEntryFiles, B as $fetch, D as fetchGitSkills, G as parseGitHubUrl, I as fetchReleaseNotes, J as formatIssueAsMarkdown, L as generateReleaseIndex, M as formatDiscussionAsMarkdown, N as generateDiscussionIndex, O as parseGitSkillInput, P as fetchBlogReleases, Q as getSharedSkillsDir, T as isShallowGitDocs, X as isGhAvailable, Y as generateIssueIndex, Z as SHARED_SKILLS_DIR, d as resolvePackageDocs, et as semverGt, f as resolvePackageDocsWithAttempts, g as fetchLlmsTxt, i as fetchPkgDist, j as fetchGitHubDiscussions, m as downloadLlmsDocs, n as fetchNpmPackage, p as searchNpmPackages, q as fetchGitHubIssues, r as fetchNpmRegistryMeta, s as readLocalDependencies, t as fetchLatestVersion, u as resolveLocalPackageDocs, v as normalizeLlmsLinks, w as fetchReadmeContent, x as fetchGitDocs } from "./_chunks/npm.mjs";
8
+ import { c as getPrereleaseChangelogRef, i as getBlogPreset, n as yamlParseKV, r as yamlUnescape, t as yamlEscape } from "./_chunks/yaml.mjs";
9
+ import { $ as SHARED_SKILLS_DIR, E as normalizeLlmsLinks, F as fetchBlogReleases, K as parseGitHubUrl, L as fetchReleaseNotes, M as fetchGitHubDiscussions, N as formatDiscussionAsMarkdown, O as fetchGitSkills, P as generateDiscussionIndex, Q as isGhAvailable, R as generateReleaseIndex, S as downloadLlmsDocs, V as $fetch, X as formatIssueAsMarkdown, Y as fetchGitHubIssues, Z as generateIssueIndex, b as resolveGitHubRepo, d as resolvePackageDocs, et as getSharedSkillsDir, f as resolvePackageDocsWithAttempts, h as fetchGitDocs, i as fetchPkgDist, j as resolveEntryFiles, k as parseGitSkillInput, n as fetchNpmPackage, nt as semverGt, p as searchNpmPackages, q as parsePackageSpec, r as fetchNpmRegistryMeta, s as readLocalDependencies, t as fetchLatestVersion, tt as mapInsert, u as resolveLocalPackageDocs, v as fetchReadmeContent, w as fetchLlmsTxt, y as isShallowGitDocs, z as isPrerelease } from "./_chunks/npm.mjs";
10
10
  import "./sources/index.mjs";
11
11
  import { S as targets, _ as maxItems, a as getModelName, b as detectTargetAgent, c as computeSkillDirName, d as sanitizeName, f as unlinkSkillFromAgents, i as getModelLabel, n as createToolProgress, o as optimizeDocs, r as getAvailableModels, s as generateSkillMd, t as detectImportedPackages, u as linkSkillToAgents, v as maxLines, x as getAgentVersion, y as detectInstalledAgents } from "./_chunks/detect-imports.mjs";
12
12
  import "./agent/index.mjs";
@@ -960,7 +960,8 @@ async function fetchAndCacheResources(opts) {
960
960
  results.push(...batchResults);
961
961
  }
962
962
  for (const r of results) if (r) {
963
- const cachePath = gitDocs.docsPrefix ? r.file.replace(gitDocs.docsPrefix, "") : r.file;
963
+ const stripped = gitDocs.docsPrefix ? r.file.replace(gitDocs.docsPrefix, "") : r.file;
964
+ const cachePath = stripped.startsWith("docs/") ? stripped : `docs/${stripped}`;
964
965
  cachedDocs.push({
965
966
  path: cachePath,
966
967
  content: r.content
@@ -1150,7 +1151,8 @@ async function fetchAndCacheResources(opts) {
1150
1151
  const gh = parseGitHubUrl(resolved.repoUrl);
1151
1152
  if (gh) {
1152
1153
  onProgress("Fetching releases via GitHub API");
1153
- const releaseDocs = await fetchReleaseNotes(gh.owner, gh.repo, version, resolved.gitRef, packageName, opts.from).catch(() => []);
1154
+ const changelogRef = isPrerelease(version) ? getPrereleaseChangelogRef(packageName) : void 0;
1155
+ const releaseDocs = await fetchReleaseNotes(gh.owner, gh.repo, version, resolved.gitRef, packageName, opts.from, changelogRef).catch(() => []);
1154
1156
  let blogDocs = [];
1155
1157
  if (getBlogPreset(packageName)) {
1156
1158
  onProgress("Fetching blog release notes");
@@ -1275,6 +1277,258 @@ function copyCachedSubdir(cacheDir, refsDir, subdir) {
1275
1277
  }
1276
1278
  walk(srcDir, "");
1277
1279
  }
1280
+ async function ensureGitignore(skillsDir, cwd, isGlobal) {
1281
+ if (isGlobal) return;
1282
+ const gitignorePath = join(cwd, ".gitignore");
1283
+ const pattern = ".skilld";
1284
+ if (existsSync(gitignorePath)) {
1285
+ if (readFileSync(gitignorePath, "utf-8").split("\n").some((line) => line.trim() === pattern)) return;
1286
+ }
1287
+ if (!isInteractive()) {
1288
+ const entry = `\n# Skilld references (recreated by \`skilld install\`)\n${pattern}\n`;
1289
+ if (existsSync(gitignorePath)) appendFileSync(gitignorePath, `${readFileSync(gitignorePath, "utf-8").endsWith("\n") ? "" : "\n"}${entry}`);
1290
+ else writeFileSync(gitignorePath, entry);
1291
+ return;
1292
+ }
1293
+ p.log.info(`\x1B[1mGit guidance:\x1B[0m\n \x1B[32m✓\x1B[0m Commit: \x1B[36m${skillsDir}/*/SKILL.md\x1B[0m\n \x1B[32m✓\x1B[0m Commit: \x1B[36m${skillsDir}/skilld-lock.yaml\x1B[0m\n \x1B[31m✗\x1B[0m Ignore: \x1B[36m${pattern}\x1B[0m \x1B[90m(recreated by \`skilld install\`)\x1B[0m`);
1294
+ const add = await p.confirm({
1295
+ message: `Add \`${pattern}\` to .gitignore?`,
1296
+ initialValue: true
1297
+ });
1298
+ if (p.isCancel(add) || !add) return;
1299
+ const entry = `\n# Skilld references (recreated by \`skilld install\`)\n${pattern}\n`;
1300
+ if (existsSync(gitignorePath)) appendFileSync(gitignorePath, `${readFileSync(gitignorePath, "utf-8").endsWith("\n") ? "" : "\n"}${entry}`);
1301
+ else writeFileSync(gitignorePath, entry);
1302
+ p.log.success("Updated .gitignore");
1303
+ }
1304
+ const SKILLD_MARKER_START = "<!-- skilld -->";
1305
+ const SKILLD_MARKER_END = "<!-- /skilld -->";
1306
+ const SKILLD_INSTRUCTIONS = `${SKILLD_MARKER_START}
1307
+ Before modifying code, evaluate each installed skill against the current task.
1308
+ For each skill, determine YES/NO relevance and invoke all YES skills before proceeding.
1309
+ ${SKILLD_MARKER_END}`;
1310
+ async function ensureAgentInstructions(agent, cwd, isGlobal) {
1311
+ if (isGlobal) return;
1312
+ const agentConfig = targets[agent];
1313
+ if (!agentConfig.instructionFile) return;
1314
+ const filePath = join(cwd, agentConfig.instructionFile);
1315
+ if (existsSync(filePath)) {
1316
+ if (readFileSync(filePath, "utf-8").includes(SKILLD_MARKER_START)) return;
1317
+ }
1318
+ if (!isInteractive()) {
1319
+ if (existsSync(filePath)) appendFileSync(filePath, `${readFileSync(filePath, "utf-8").endsWith("\n") ? "" : "\n"}\n${SKILLD_INSTRUCTIONS}\n`);
1320
+ else writeFileSync(filePath, `${SKILLD_INSTRUCTIONS}\n`);
1321
+ return;
1322
+ }
1323
+ p.note(SKILLD_INSTRUCTIONS, `Will be added to ${agentConfig.instructionFile}`);
1324
+ const add = await p.confirm({
1325
+ message: `Add skill activation instructions to ${agentConfig.instructionFile}?`,
1326
+ initialValue: true
1327
+ });
1328
+ if (p.isCancel(add) || !add) return;
1329
+ if (existsSync(filePath)) appendFileSync(filePath, `${readFileSync(filePath, "utf-8").endsWith("\n") ? "" : "\n"}\n${SKILLD_INSTRUCTIONS}\n`);
1330
+ else writeFileSync(filePath, `${SKILLD_INSTRUCTIONS}\n`);
1331
+ p.log.success(`Updated ${agentConfig.instructionFile}`);
1332
+ }
1333
+ async function selectModel(skipPrompt) {
1334
+ const config = readConfig();
1335
+ const available = await getAvailableModels();
1336
+ if (available.length === 0) {
1337
+ p.log.warn("No LLM CLIs found (claude, gemini, codex)");
1338
+ return null;
1339
+ }
1340
+ if (config.model && available.some((m) => m.id === config.model)) return config.model;
1341
+ if (skipPrompt) return available.find((m) => m.recommended)?.id ?? available[0].id;
1342
+ const modelChoice = await p.select({
1343
+ message: "Model for SKILL.md generation",
1344
+ options: available.map((m) => ({
1345
+ label: m.recommended ? `${m.name} (Recommended)` : m.name,
1346
+ value: m.id,
1347
+ hint: `${m.agentName} · ${m.hint}`
1348
+ })),
1349
+ initialValue: available.find((m) => m.recommended)?.id ?? available[0].id
1350
+ });
1351
+ if (p.isCancel(modelChoice)) {
1352
+ p.cancel("Cancelled");
1353
+ return null;
1354
+ }
1355
+ updateConfig({ model: modelChoice });
1356
+ return modelChoice;
1357
+ }
1358
+ const DEFAULT_SECTIONS = ["best-practices", "api-changes"];
1359
+ async function selectSkillSections(message = "Generate SKILL.md with LLM") {
1360
+ p.log.info("More sections = less budget each. Fewer sections = deeper coverage.");
1361
+ const selected = await p.multiselect({
1362
+ message,
1363
+ options: [
1364
+ {
1365
+ label: "API changes",
1366
+ value: "api-changes",
1367
+ hint: "new/deprecated APIs from version history"
1368
+ },
1369
+ {
1370
+ label: "Best practices",
1371
+ value: "best-practices",
1372
+ hint: "gotchas, pitfalls, patterns"
1373
+ },
1374
+ {
1375
+ label: "Doc map",
1376
+ value: "api",
1377
+ hint: "compact index of exports linked to source files"
1378
+ },
1379
+ {
1380
+ label: "Custom section",
1381
+ value: "custom",
1382
+ hint: "add your own section"
1383
+ }
1384
+ ],
1385
+ initialValues: DEFAULT_SECTIONS,
1386
+ required: false
1387
+ });
1388
+ if (p.isCancel(selected)) return {
1389
+ sections: [],
1390
+ cancelled: true
1391
+ };
1392
+ const sections = selected;
1393
+ if (sections.length === 0) return {
1394
+ sections: [],
1395
+ cancelled: false
1396
+ };
1397
+ if (sections.length > 1) {
1398
+ const n = sections.length;
1399
+ const budgetLines = [];
1400
+ for (const s of sections) switch (s) {
1401
+ case "api-changes":
1402
+ budgetLines.push(` API changes ≤${maxLines(50, 80, n)} lines, ${maxItems(6, 12, n)} items`);
1403
+ break;
1404
+ case "best-practices":
1405
+ budgetLines.push(` Best practices ≤${maxLines(80, 150, n)} lines, ${maxItems(4, 10, n)} items`);
1406
+ break;
1407
+ case "api":
1408
+ budgetLines.push(` Doc map ≤${maxLines(15, 25, n)} lines`);
1409
+ break;
1410
+ case "custom":
1411
+ budgetLines.push(` Custom ≤${maxLines(50, 80, n)} lines`);
1412
+ break;
1413
+ }
1414
+ p.log.info(`Budget (${n} sections):\n${budgetLines.join("\n")}`);
1415
+ }
1416
+ let customPrompt;
1417
+ if (sections.includes("custom")) {
1418
+ const heading = await p.text({
1419
+ message: "Section heading",
1420
+ placeholder: "e.g. \"Migration from v2\" or \"SSR Patterns\""
1421
+ });
1422
+ if (p.isCancel(heading)) return {
1423
+ sections: [],
1424
+ cancelled: true
1425
+ };
1426
+ const body = await p.text({
1427
+ message: "Instructions for this section",
1428
+ placeholder: "e.g. \"Document breaking changes and migration steps from v2 to v3\""
1429
+ });
1430
+ if (p.isCancel(body)) return {
1431
+ sections: [],
1432
+ cancelled: true
1433
+ };
1434
+ customPrompt = {
1435
+ heading,
1436
+ body
1437
+ };
1438
+ }
1439
+ return {
1440
+ sections,
1441
+ customPrompt,
1442
+ cancelled: false
1443
+ };
1444
+ }
1445
+ async function selectLlmConfig(presetModel, message) {
1446
+ if (presetModel) return {
1447
+ model: presetModel,
1448
+ sections: DEFAULT_SECTIONS
1449
+ };
1450
+ if (!isInteractive()) {
1451
+ const model = await selectModel(true);
1452
+ if (!model) return null;
1453
+ return {
1454
+ model,
1455
+ sections: DEFAULT_SECTIONS
1456
+ };
1457
+ }
1458
+ const model = await selectModel(false);
1459
+ if (!model) return null;
1460
+ const modelName = getModelName(model);
1461
+ const { sections, customPrompt, cancelled } = await selectSkillSections(message ? `${message} (${modelName})` : `Generate SKILL.md with ${modelName}`);
1462
+ if (cancelled || sections.length === 0) return null;
1463
+ return {
1464
+ model,
1465
+ sections,
1466
+ customPrompt
1467
+ };
1468
+ }
1469
+ async function enhanceSkillWithLLM(opts) {
1470
+ const { packageName, version, skillDir, dirName, model, resolved, relatedSkills, hasIssues, hasDiscussions, hasReleases, hasChangelog, docsType, hasShippedDocs: shippedDocs, pkgFiles, force, debug, sections, customPrompt, packages, features, eject } = opts;
1471
+ const effectiveFeatures = eject && features ? {
1472
+ ...features,
1473
+ search: false
1474
+ } : features;
1475
+ const llmLog = p.taskLog({ title: `Agent exploring ${packageName}` });
1476
+ const docFiles = listReferenceFiles(skillDir);
1477
+ const { optimized, wasOptimized, usage, cost, warnings, debugLogsDir } = await optimizeDocs({
1478
+ packageName,
1479
+ skillDir,
1480
+ model,
1481
+ version,
1482
+ hasGithub: hasIssues || hasDiscussions,
1483
+ hasReleases,
1484
+ hasChangelog,
1485
+ docFiles,
1486
+ docsType,
1487
+ hasShippedDocs: shippedDocs,
1488
+ noCache: force,
1489
+ debug,
1490
+ sections,
1491
+ customPrompt,
1492
+ features: effectiveFeatures,
1493
+ pkgFiles,
1494
+ onProgress: createToolProgress(llmLog)
1495
+ });
1496
+ if (wasOptimized) {
1497
+ const costParts = [];
1498
+ if (usage) {
1499
+ const totalK = Math.round(usage.totalTokens / 1e3);
1500
+ costParts.push(`${totalK}k tokens`);
1501
+ }
1502
+ if (cost) costParts.push(`$${cost.toFixed(2)}`);
1503
+ const costSuffix = costParts.length > 0 ? ` (${costParts.join(", ")})` : "";
1504
+ llmLog.success(`Generated best practices${costSuffix}`);
1505
+ if (debugLogsDir) p.log.info(`Debug logs: ${debugLogsDir}`);
1506
+ if (warnings?.length) for (const w of warnings) p.log.warn(`\x1B[33m${w}\x1B[0m`);
1507
+ const skillMd = generateSkillMd({
1508
+ name: packageName,
1509
+ version,
1510
+ releasedAt: resolved.releasedAt,
1511
+ dependencies: resolved.dependencies,
1512
+ distTags: resolved.distTags,
1513
+ body: optimized,
1514
+ relatedSkills,
1515
+ hasIssues,
1516
+ hasDiscussions,
1517
+ hasReleases,
1518
+ hasChangelog,
1519
+ docsType,
1520
+ hasShippedDocs: shippedDocs,
1521
+ pkgFiles,
1522
+ generatedBy: getModelLabel(model),
1523
+ dirName,
1524
+ packages,
1525
+ repoUrl: resolved.repoUrl,
1526
+ features,
1527
+ eject
1528
+ });
1529
+ writeFileSync(join(skillDir, "SKILL.md"), skillMd);
1530
+ } else llmLog.error("LLM optimization failed");
1531
+ }
1278
1532
  const TELEMETRY_URL = "https://add-skill.vercel.sh/t";
1279
1533
  const SKILLS_VERSION = "1.3.9";
1280
1534
  function isEnabled() {
@@ -1300,6 +1554,10 @@ async function syncGitSkills(opts) {
1300
1554
  spin.start(`Fetching skills from ${label}`);
1301
1555
  const { skills, commitSha } = await fetchGitSkills(source, (msg) => spin.message(msg));
1302
1556
  if (skills.length === 0) {
1557
+ if (source.type === "github" && source.owner && source.repo) {
1558
+ spin.stop(`No pre-authored skills in ${label}, generating from repo docs...`);
1559
+ return syncGitHubRepo(opts);
1560
+ }
1303
1561
  spin.stop(`No skills found in ${label}`);
1304
1562
  return;
1305
1563
  }
@@ -1353,6 +1611,140 @@ async function syncGitSkills(opts) {
1353
1611
  const names = selected.map((s) => `\x1B[36m${s.name}\x1B[0m`).join(", ");
1354
1612
  p.log.success(`Installed ${names}`);
1355
1613
  }
1614
+ async function syncGitHubRepo(opts) {
1615
+ const { source, agent, global: isGlobal, yes } = opts;
1616
+ const owner = source.owner;
1617
+ const repo = source.repo;
1618
+ const cwd = process.cwd();
1619
+ const spin = timedSpinner();
1620
+ spin.start(`Resolving ${owner}/${repo}`);
1621
+ const resolved = await resolveGitHubRepo(owner, repo, (msg) => spin.message(msg));
1622
+ if (!resolved) {
1623
+ spin.stop(`Could not find docs for ${owner}/${repo}`);
1624
+ return;
1625
+ }
1626
+ const repoUrl = `https://github.com/${owner}/${repo}`;
1627
+ const packageName = `${owner}-${repo}`;
1628
+ const version = resolved.version || "main";
1629
+ const versionKey = getVersionKey(version);
1630
+ const useCache = isCached(packageName, version);
1631
+ spin.stop(`Resolved ${owner}/${repo}@${useCache ? versionKey : version}${useCache ? " (cached)" : ""}`);
1632
+ ensureCacheDir();
1633
+ const baseDir = resolveBaseDir(cwd, agent, isGlobal);
1634
+ const skillDirName = sanitizeName(`${owner}-${repo}`);
1635
+ const skillDir = join(baseDir, skillDirName);
1636
+ mkdirSync(skillDir, { recursive: true });
1637
+ const features = readConfig().features ?? defaultFeatures;
1638
+ const resSpin = timedSpinner();
1639
+ resSpin.start("Finding resources");
1640
+ const resources = await fetchAndCacheResources({
1641
+ packageName,
1642
+ resolved,
1643
+ version,
1644
+ useCache,
1645
+ features,
1646
+ from: opts.from,
1647
+ onProgress: (msg) => resSpin.message(msg)
1648
+ });
1649
+ const resParts = [];
1650
+ if (resources.docsToIndex.length > 0) {
1651
+ const docCount = resources.docsToIndex.filter((d) => d.metadata?.type === "doc").length;
1652
+ if (docCount > 0) resParts.push(`${docCount} docs`);
1653
+ }
1654
+ if (resources.hasIssues) resParts.push("issues");
1655
+ if (resources.hasDiscussions) resParts.push("discussions");
1656
+ if (resources.hasReleases) resParts.push("releases");
1657
+ resSpin.stop(`Fetched ${resParts.length > 0 ? resParts.join(", ") : "resources"}`);
1658
+ for (const w of resources.warnings) p.log.warn(`\x1B[33m${w}\x1B[0m`);
1659
+ linkAllReferences(skillDir, packageName, cwd, version, resources.docsType, void 0, features);
1660
+ if (features.search) {
1661
+ const idxSpin = timedSpinner();
1662
+ idxSpin.start("Creating search index");
1663
+ await indexResources({
1664
+ packageName,
1665
+ version,
1666
+ cwd,
1667
+ docsToIndex: resources.docsToIndex,
1668
+ features,
1669
+ onProgress: (msg) => idxSpin.message(msg)
1670
+ });
1671
+ idxSpin.stop("Search index ready");
1672
+ }
1673
+ const hasChangelog = detectChangelog(resolvePkgDir(packageName, cwd, version), getCacheDir(packageName, version));
1674
+ const shippedDocs = hasShippedDocs(packageName, cwd, version);
1675
+ const pkgFiles = getPkgKeyFiles(packageName, cwd, version);
1676
+ writeLock(baseDir, skillDirName, {
1677
+ packageName,
1678
+ version,
1679
+ repo: `${owner}/${repo}`,
1680
+ source: resources.docSource,
1681
+ syncedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
1682
+ generator: "skilld"
1683
+ });
1684
+ const baseSkillMd = generateSkillMd({
1685
+ name: packageName,
1686
+ version,
1687
+ releasedAt: resolved.releasedAt,
1688
+ description: resolved.description,
1689
+ relatedSkills: [],
1690
+ hasIssues: resources.hasIssues,
1691
+ hasDiscussions: resources.hasDiscussions,
1692
+ hasReleases: resources.hasReleases,
1693
+ hasChangelog,
1694
+ docsType: resources.docsType,
1695
+ hasShippedDocs: shippedDocs,
1696
+ pkgFiles,
1697
+ dirName: skillDirName,
1698
+ repoUrl,
1699
+ features
1700
+ });
1701
+ writeFileSync(join(skillDir, "SKILL.md"), baseSkillMd);
1702
+ p.log.success(`Created base skill: ${relative(cwd, skillDir)}`);
1703
+ if (!readConfig().skipLlm && (!yes || opts.model)) {
1704
+ const llmConfig = await selectLlmConfig(opts.model);
1705
+ if (llmConfig) {
1706
+ p.log.step(getModelLabel(llmConfig.model));
1707
+ await enhanceSkillWithLLM({
1708
+ packageName,
1709
+ version,
1710
+ skillDir,
1711
+ dirName: skillDirName,
1712
+ model: llmConfig.model,
1713
+ resolved,
1714
+ relatedSkills: [],
1715
+ hasIssues: resources.hasIssues,
1716
+ hasDiscussions: resources.hasDiscussions,
1717
+ hasReleases: resources.hasReleases,
1718
+ hasChangelog,
1719
+ docsType: resources.docsType,
1720
+ hasShippedDocs: shippedDocs,
1721
+ pkgFiles,
1722
+ force: opts.force,
1723
+ debug: opts.debug,
1724
+ sections: llmConfig.sections,
1725
+ customPrompt: llmConfig.customPrompt,
1726
+ features
1727
+ });
1728
+ }
1729
+ }
1730
+ const shared = !isGlobal && getSharedSkillsDir(cwd);
1731
+ if (shared) linkSkillToAgents(skillDirName, shared, cwd);
1732
+ if (!isGlobal) {
1733
+ registerProject(cwd);
1734
+ await ensureGitignore(shared || targets[agent].skillsDir, cwd, isGlobal);
1735
+ await ensureAgentInstructions(agent, cwd, isGlobal);
1736
+ }
1737
+ await shutdownWorker();
1738
+ track({
1739
+ event: "install",
1740
+ source: `${owner}/${repo}`,
1741
+ skills: skillDirName,
1742
+ agents: agent,
1743
+ ...isGlobal && { global: "1" },
1744
+ sourceType: "github-generated"
1745
+ });
1746
+ p.outro(`Synced ${owner}/${repo} to ${relative(cwd, skillDir)}`);
1747
+ }
1356
1748
  const STATUS_ICONS = {
1357
1749
  pending: "○",
1358
1750
  resolving: "◐",
@@ -1466,9 +1858,10 @@ async function syncPackagesParallel(config) {
1466
1858
  await shutdownWorker();
1467
1859
  p.outro(`${pastVerb} ${successfulPkgs.length}/${packages.length} packages`);
1468
1860
  }
1469
- async function syncBaseSkill(packageName, config, cwd, update) {
1861
+ async function syncBaseSkill(packageSpec, config, cwd, update) {
1862
+ const { name: packageName, tag: requestedTag } = parsePackageSpec(packageSpec);
1470
1863
  const localVersion = (await readLocalDependencies(cwd).catch(() => [])).find((d) => d.name === packageName)?.version;
1471
- const { package: resolvedPkg, attempts } = await resolvePackageDocsWithAttempts(packageName, {
1864
+ const { package: resolvedPkg, attempts } = await resolvePackageDocsWithAttempts(requestedTag ? packageSpec : packageName, {
1472
1865
  version: localVersion,
1473
1866
  cwd,
1474
1867
  onProgress: (step) => update(packageName, "resolving", RESOLVE_STEP_LABELS[step])
@@ -1612,6 +2005,7 @@ async function enhanceWithLLM(packageName, data, config, cwd, update, sections,
1612
2005
  sections,
1613
2006
  customPrompt,
1614
2007
  features: data.features,
2008
+ pkgFiles: data.pkgFiles,
1615
2009
  onProgress: (progress) => {
1616
2010
  const status = progress.type === "reasoning" ? "exploring" : "generating";
1617
2011
  const sectionPrefix = progress.section ? `[${progress.section}] ` : "";
@@ -1740,17 +2134,8 @@ async function runWizard() {
1740
2134
  p.outro("Thanks, you're all set! Change config anytime with `skilld config`.");
1741
2135
  }
1742
2136
  var sync_exports = /* @__PURE__ */ __exportAll({
1743
- DEFAULT_SECTIONS: () => DEFAULT_SECTIONS,
1744
- SKILLD_MARKER_END: () => SKILLD_MARKER_END,
1745
- SKILLD_MARKER_START: () => SKILLD_MARKER_START,
1746
2137
  addCommandDef: () => addCommandDef,
1747
2138
  ejectCommandDef: () => ejectCommandDef,
1748
- enhanceSkillWithLLM: () => enhanceSkillWithLLM,
1749
- ensureAgentInstructions: () => ensureAgentInstructions,
1750
- ensureGitignore: () => ensureGitignore,
1751
- selectLlmConfig: () => selectLlmConfig,
1752
- selectModel: () => selectModel,
1753
- selectSkillSections: () => selectSkillSections,
1754
2139
  syncCommand: () => syncCommand,
1755
2140
  updateCommandDef: () => updateCommandDef
1756
2141
  });
@@ -1764,59 +2149,6 @@ function showResolveAttempts(attempts) {
1764
2149
  p.log.message(` ${icon} ${source}${msg}`);
1765
2150
  }
1766
2151
  }
1767
- async function ensureGitignore(skillsDir, cwd, isGlobal) {
1768
- if (isGlobal) return;
1769
- const gitignorePath = join(cwd, ".gitignore");
1770
- const pattern = ".skilld";
1771
- if (existsSync(gitignorePath)) {
1772
- if (readFileSync(gitignorePath, "utf-8").split("\n").some((line) => line.trim() === pattern)) return;
1773
- }
1774
- if (!isInteractive()) {
1775
- const entry = `\n# Skilld references (recreated by \`skilld install\`)\n${pattern}\n`;
1776
- if (existsSync(gitignorePath)) appendFileSync(gitignorePath, `${readFileSync(gitignorePath, "utf-8").endsWith("\n") ? "" : "\n"}${entry}`);
1777
- else writeFileSync(gitignorePath, entry);
1778
- return;
1779
- }
1780
- p.log.info(`\x1B[1mGit guidance:\x1B[0m\n \x1B[32m✓\x1B[0m Commit: \x1B[36m${skillsDir}/*/SKILL.md\x1B[0m\n \x1B[32m✓\x1B[0m Commit: \x1B[36m${skillsDir}/skilld-lock.yaml\x1B[0m\n \x1B[31m✗\x1B[0m Ignore: \x1B[36m${pattern}\x1B[0m \x1B[90m(recreated by \`skilld install\`)\x1B[0m`);
1781
- const add = await p.confirm({
1782
- message: `Add \`${pattern}\` to .gitignore?`,
1783
- initialValue: true
1784
- });
1785
- if (p.isCancel(add) || !add) return;
1786
- const entry = `\n# Skilld references (recreated by \`skilld install\`)\n${pattern}\n`;
1787
- if (existsSync(gitignorePath)) appendFileSync(gitignorePath, `${readFileSync(gitignorePath, "utf-8").endsWith("\n") ? "" : "\n"}${entry}`);
1788
- else writeFileSync(gitignorePath, entry);
1789
- p.log.success("Updated .gitignore");
1790
- }
1791
- const SKILLD_MARKER_START = "<!-- skilld -->";
1792
- const SKILLD_MARKER_END = "<!-- /skilld -->";
1793
- const SKILLD_INSTRUCTIONS = `${SKILLD_MARKER_START}
1794
- Before modifying code, evaluate each installed skill against the current task.
1795
- For each skill, determine YES/NO relevance and invoke all YES skills before proceeding.
1796
- ${SKILLD_MARKER_END}`;
1797
- async function ensureAgentInstructions(agent, cwd, isGlobal) {
1798
- if (isGlobal) return;
1799
- const agentConfig = targets[agent];
1800
- if (!agentConfig.instructionFile) return;
1801
- const filePath = join(cwd, agentConfig.instructionFile);
1802
- if (existsSync(filePath)) {
1803
- if (readFileSync(filePath, "utf-8").includes(SKILLD_MARKER_START)) return;
1804
- }
1805
- if (!isInteractive()) {
1806
- if (existsSync(filePath)) appendFileSync(filePath, `${readFileSync(filePath, "utf-8").endsWith("\n") ? "" : "\n"}\n${SKILLD_INSTRUCTIONS}\n`);
1807
- else writeFileSync(filePath, `${SKILLD_INSTRUCTIONS}\n`);
1808
- return;
1809
- }
1810
- p.note(SKILLD_INSTRUCTIONS, `Will be added to ${agentConfig.instructionFile}`);
1811
- const add = await p.confirm({
1812
- message: `Add skill activation instructions to ${agentConfig.instructionFile}?`,
1813
- initialValue: true
1814
- });
1815
- if (p.isCancel(add) || !add) return;
1816
- if (existsSync(filePath)) appendFileSync(filePath, `${readFileSync(filePath, "utf-8").endsWith("\n") ? "" : "\n"}\n${SKILLD_INSTRUCTIONS}\n`);
1817
- else writeFileSync(filePath, `${SKILLD_INSTRUCTIONS}\n`);
1818
- p.log.success(`Updated ${agentConfig.instructionFile}`);
1819
- }
1820
2152
  async function syncCommand(state, opts) {
1821
2153
  if (opts.packages && opts.packages.length > 0) {
1822
2154
  if (opts.packages.length > 1) return syncPackagesParallel({
@@ -1905,148 +2237,13 @@ async function pickFromList(packages, state) {
1905
2237
  }
1906
2238
  return selected;
1907
2239
  }
1908
- async function selectModel(skipPrompt) {
1909
- const config = readConfig();
1910
- const available = await getAvailableModels();
1911
- if (available.length === 0) {
1912
- p.log.warn("No LLM CLIs found (claude, gemini, codex)");
1913
- return null;
1914
- }
1915
- if (config.model && available.some((m) => m.id === config.model)) return config.model;
1916
- if (skipPrompt) return available.find((m) => m.recommended)?.id ?? available[0].id;
1917
- const modelChoice = await p.select({
1918
- message: "Model for SKILL.md generation",
1919
- options: available.map((m) => ({
1920
- label: m.recommended ? `${m.name} (Recommended)` : m.name,
1921
- value: m.id,
1922
- hint: `${m.agentName} · ${m.hint}`
1923
- })),
1924
- initialValue: available.find((m) => m.recommended)?.id ?? available[0].id
1925
- });
1926
- if (p.isCancel(modelChoice)) {
1927
- p.cancel("Cancelled");
1928
- return null;
1929
- }
1930
- updateConfig({ model: modelChoice });
1931
- return modelChoice;
1932
- }
1933
- const DEFAULT_SECTIONS = ["best-practices", "api-changes"];
1934
- async function selectSkillSections(message = "Generate SKILL.md with LLM") {
1935
- p.log.info("More sections = less budget each. Fewer sections = deeper coverage.");
1936
- const selected = await p.multiselect({
1937
- message,
1938
- options: [
1939
- {
1940
- label: "API changes",
1941
- value: "api-changes",
1942
- hint: "new/deprecated APIs from version history"
1943
- },
1944
- {
1945
- label: "Best practices",
1946
- value: "best-practices",
1947
- hint: "gotchas, pitfalls, patterns"
1948
- },
1949
- {
1950
- label: "Doc map",
1951
- value: "api",
1952
- hint: "compact index of exports linked to source files"
1953
- },
1954
- {
1955
- label: "Custom section",
1956
- value: "custom",
1957
- hint: "add your own section"
1958
- }
1959
- ],
1960
- initialValues: DEFAULT_SECTIONS,
1961
- required: false
1962
- });
1963
- if (p.isCancel(selected)) return {
1964
- sections: [],
1965
- cancelled: true
1966
- };
1967
- const sections = selected;
1968
- if (sections.length === 0) return {
1969
- sections: [],
1970
- cancelled: false
1971
- };
1972
- if (sections.length > 1) {
1973
- const n = sections.length;
1974
- const budgetLines = [];
1975
- for (const s of sections) switch (s) {
1976
- case "api-changes":
1977
- budgetLines.push(` API changes ≤${maxLines(50, 80, n)} lines, ${maxItems(6, 12, n)} items`);
1978
- break;
1979
- case "best-practices":
1980
- budgetLines.push(` Best practices ≤${maxLines(80, 150, n)} lines, ${maxItems(4, 10, n)} items`);
1981
- break;
1982
- case "api":
1983
- budgetLines.push(` Doc map ≤${maxLines(15, 25, n)} lines`);
1984
- break;
1985
- case "custom":
1986
- budgetLines.push(` Custom ≤${maxLines(50, 80, n)} lines`);
1987
- break;
1988
- }
1989
- p.log.info(`Budget (${n} sections):\n${budgetLines.join("\n")}`);
1990
- }
1991
- let customPrompt;
1992
- if (sections.includes("custom")) {
1993
- const heading = await p.text({
1994
- message: "Section heading",
1995
- placeholder: "e.g. \"Migration from v2\" or \"SSR Patterns\""
1996
- });
1997
- if (p.isCancel(heading)) return {
1998
- sections: [],
1999
- cancelled: true
2000
- };
2001
- const body = await p.text({
2002
- message: "Instructions for this section",
2003
- placeholder: "e.g. \"Document breaking changes and migration steps from v2 to v3\""
2004
- });
2005
- if (p.isCancel(body)) return {
2006
- sections: [],
2007
- cancelled: true
2008
- };
2009
- customPrompt = {
2010
- heading,
2011
- body
2012
- };
2013
- }
2014
- return {
2015
- sections,
2016
- customPrompt,
2017
- cancelled: false
2018
- };
2019
- }
2020
- async function selectLlmConfig(presetModel, message) {
2021
- if (presetModel) return {
2022
- model: presetModel,
2023
- sections: DEFAULT_SECTIONS
2024
- };
2025
- if (!isInteractive()) {
2026
- const model = await selectModel(true);
2027
- if (!model) return null;
2028
- return {
2029
- model,
2030
- sections: DEFAULT_SECTIONS
2031
- };
2032
- }
2033
- const model = await selectModel(false);
2034
- if (!model) return null;
2035
- const modelName = getModelName(model);
2036
- const { sections, customPrompt, cancelled } = await selectSkillSections(message ? `${message} (${modelName})` : `Generate SKILL.md with ${modelName}`);
2037
- if (cancelled || sections.length === 0) return null;
2038
- return {
2039
- model,
2040
- sections,
2041
- customPrompt
2042
- };
2043
- }
2044
- async function syncSinglePackage(packageName, config) {
2240
+ async function syncSinglePackage(packageSpec, config) {
2241
+ const { name: packageName, tag: requestedTag } = parsePackageSpec(packageSpec);
2045
2242
  const spin = timedSpinner();
2046
- spin.start(`Resolving ${packageName}`);
2243
+ spin.start(`Resolving ${packageSpec}`);
2047
2244
  const cwd = process.cwd();
2048
2245
  const localVersion = (await readLocalDependencies(cwd).catch(() => [])).find((d) => d.name === packageName)?.version;
2049
- const resolveResult = await resolvePackageDocsWithAttempts(packageName, {
2246
+ const resolveResult = await resolvePackageDocsWithAttempts(requestedTag ? packageSpec : packageName, {
2050
2247
  version: localVersion,
2051
2248
  cwd,
2052
2249
  onProgress: (step) => spin.message(`${packageName}: ${RESOLVE_STEP_LABELS[step]}`)
@@ -2102,7 +2299,7 @@ async function syncSinglePackage(packageName, config) {
2102
2299
  ensureCacheDir();
2103
2300
  const baseDir = resolveBaseDir(cwd, config.agent, config.global);
2104
2301
  const skillDirName = config.name ? sanitizeName(config.name) : computeSkillDirName(packageName, resolved.repoUrl);
2105
- const skillDir = config.eject ? typeof config.eject === "string" ? resolve(cwd, config.eject) : join(cwd, "skills", skillDirName) : join(baseDir, skillDirName);
2302
+ const skillDir = config.eject ? typeof config.eject === "string" ? join(resolve(cwd, config.eject), skillDirName) : join(cwd, "skills", skillDirName) : join(baseDir, skillDirName);
2106
2303
  mkdirSync(skillDir, { recursive: true });
2107
2304
  const existingLock = config.eject ? void 0 : readLock(baseDir)?.skills[skillDirName];
2108
2305
  if (existingLock && existingLock.packageName !== packageName) {
@@ -2267,68 +2464,6 @@ async function syncSinglePackage(packageName, config) {
2267
2464
  const ejectMsg = isEject ? " (ejected)" : "";
2268
2465
  p.outro(config.mode === "update" ? `Updated ${packageName}${ejectMsg}` : `Synced ${packageName} to ${relative(cwd, skillDir)}${ejectMsg}`);
2269
2466
  }
2270
- async function enhanceSkillWithLLM(opts) {
2271
- const { packageName, version, skillDir, dirName, model, resolved, relatedSkills, hasIssues, hasDiscussions, hasReleases, hasChangelog, docsType, hasShippedDocs: shippedDocs, pkgFiles, force, debug, sections, customPrompt, packages, features, eject } = opts;
2272
- const effectiveFeatures = eject && features ? {
2273
- ...features,
2274
- search: false
2275
- } : features;
2276
- const llmLog = p.taskLog({ title: `Agent exploring ${packageName}` });
2277
- const docFiles = listReferenceFiles(skillDir);
2278
- const { optimized, wasOptimized, usage, cost, warnings, debugLogsDir } = await optimizeDocs({
2279
- packageName,
2280
- skillDir,
2281
- model,
2282
- version,
2283
- hasGithub: hasIssues || hasDiscussions,
2284
- hasReleases,
2285
- hasChangelog,
2286
- docFiles,
2287
- docsType,
2288
- hasShippedDocs: shippedDocs,
2289
- noCache: force,
2290
- debug,
2291
- sections,
2292
- customPrompt,
2293
- features: effectiveFeatures,
2294
- onProgress: createToolProgress(llmLog)
2295
- });
2296
- if (wasOptimized) {
2297
- const costParts = [];
2298
- if (usage) {
2299
- const totalK = Math.round(usage.totalTokens / 1e3);
2300
- costParts.push(`${totalK}k tokens`);
2301
- }
2302
- if (cost) costParts.push(`$${cost.toFixed(2)}`);
2303
- const costSuffix = costParts.length > 0 ? ` (${costParts.join(", ")})` : "";
2304
- llmLog.success(`Generated best practices${costSuffix}`);
2305
- if (debugLogsDir) p.log.info(`Debug logs: ${debugLogsDir}`);
2306
- if (warnings?.length) for (const w of warnings) p.log.warn(`\x1B[33m${w}\x1B[0m`);
2307
- const skillMd = generateSkillMd({
2308
- name: packageName,
2309
- version,
2310
- releasedAt: resolved.releasedAt,
2311
- dependencies: resolved.dependencies,
2312
- distTags: resolved.distTags,
2313
- body: optimized,
2314
- relatedSkills,
2315
- hasIssues,
2316
- hasDiscussions,
2317
- hasReleases,
2318
- hasChangelog,
2319
- docsType,
2320
- hasShippedDocs: shippedDocs,
2321
- pkgFiles,
2322
- generatedBy: getModelLabel(model),
2323
- dirName,
2324
- packages,
2325
- repoUrl: resolved.repoUrl,
2326
- features,
2327
- eject
2328
- });
2329
- writeFileSync(join(skillDir, "SKILL.md"), skillMd);
2330
- } else llmLog.error("LLM optimization failed");
2331
- }
2332
2467
  const addCommandDef = defineCommand({
2333
2468
  meta: {
2334
2469
  name: "add",
@@ -2362,7 +2497,10 @@ const addCommandDef = defineCommand({
2362
2497
  source,
2363
2498
  global: args.global,
2364
2499
  agent,
2365
- yes: args.yes
2500
+ yes: args.yes,
2501
+ model: args.model,
2502
+ force: args.force,
2503
+ debug: args.debug
2366
2504
  });
2367
2505
  if (npmTokens.length > 0) {
2368
2506
  const packages = [...new Set(npmTokens.flatMap((s) => s.split(/[,\s]+/)).map((s) => s.trim()).filter(Boolean))];
@@ -2886,6 +3024,7 @@ async function enhanceRegenerated(pkgName, version, skillDir, model, sections, c
2886
3024
  sections,
2887
3025
  customPrompt,
2888
3026
  features,
3027
+ pkgFiles: getPkgKeyFiles(pkgName, process.cwd(), version),
2889
3028
  onProgress: createToolProgress(llmLog)
2890
3029
  });
2891
3030
  if (wasOptimized) {