skilld 0.9.5 → 0.10.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.
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 { a as getSharedSkillsDir, c as getBlogPreset, i as SHARED_SKILLS_DIR, n as yamlParseKV, o as mapInsert, r as yamlUnescape, s as semverGt, t as yamlEscape } from "./_chunks/yaml.mjs";
9
- import { 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, T as isShallowGitDocs, X as isGhAvailable, Y as generateIssueIndex, d as resolvePackageDocs, 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 { 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 } 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
@@ -1275,6 +1276,258 @@ function copyCachedSubdir(cacheDir, refsDir, subdir) {
1275
1276
  }
1276
1277
  walk(srcDir, "");
1277
1278
  }
1279
+ async function ensureGitignore(skillsDir, cwd, isGlobal) {
1280
+ if (isGlobal) return;
1281
+ const gitignorePath = join(cwd, ".gitignore");
1282
+ const pattern = ".skilld";
1283
+ if (existsSync(gitignorePath)) {
1284
+ if (readFileSync(gitignorePath, "utf-8").split("\n").some((line) => line.trim() === pattern)) return;
1285
+ }
1286
+ if (!isInteractive()) {
1287
+ const entry = `\n# Skilld references (recreated by \`skilld install\`)\n${pattern}\n`;
1288
+ if (existsSync(gitignorePath)) appendFileSync(gitignorePath, `${readFileSync(gitignorePath, "utf-8").endsWith("\n") ? "" : "\n"}${entry}`);
1289
+ else writeFileSync(gitignorePath, entry);
1290
+ return;
1291
+ }
1292
+ 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`);
1293
+ const add = await p.confirm({
1294
+ message: `Add \`${pattern}\` to .gitignore?`,
1295
+ initialValue: true
1296
+ });
1297
+ if (p.isCancel(add) || !add) return;
1298
+ const entry = `\n# Skilld references (recreated by \`skilld install\`)\n${pattern}\n`;
1299
+ if (existsSync(gitignorePath)) appendFileSync(gitignorePath, `${readFileSync(gitignorePath, "utf-8").endsWith("\n") ? "" : "\n"}${entry}`);
1300
+ else writeFileSync(gitignorePath, entry);
1301
+ p.log.success("Updated .gitignore");
1302
+ }
1303
+ const SKILLD_MARKER_START = "<!-- skilld -->";
1304
+ const SKILLD_MARKER_END = "<!-- /skilld -->";
1305
+ const SKILLD_INSTRUCTIONS = `${SKILLD_MARKER_START}
1306
+ Before modifying code, evaluate each installed skill against the current task.
1307
+ For each skill, determine YES/NO relevance and invoke all YES skills before proceeding.
1308
+ ${SKILLD_MARKER_END}`;
1309
+ async function ensureAgentInstructions(agent, cwd, isGlobal) {
1310
+ if (isGlobal) return;
1311
+ const agentConfig = targets[agent];
1312
+ if (!agentConfig.instructionFile) return;
1313
+ const filePath = join(cwd, agentConfig.instructionFile);
1314
+ if (existsSync(filePath)) {
1315
+ if (readFileSync(filePath, "utf-8").includes(SKILLD_MARKER_START)) return;
1316
+ }
1317
+ if (!isInteractive()) {
1318
+ if (existsSync(filePath)) appendFileSync(filePath, `${readFileSync(filePath, "utf-8").endsWith("\n") ? "" : "\n"}\n${SKILLD_INSTRUCTIONS}\n`);
1319
+ else writeFileSync(filePath, `${SKILLD_INSTRUCTIONS}\n`);
1320
+ return;
1321
+ }
1322
+ p.note(SKILLD_INSTRUCTIONS, `Will be added to ${agentConfig.instructionFile}`);
1323
+ const add = await p.confirm({
1324
+ message: `Add skill activation instructions to ${agentConfig.instructionFile}?`,
1325
+ initialValue: true
1326
+ });
1327
+ if (p.isCancel(add) || !add) return;
1328
+ if (existsSync(filePath)) appendFileSync(filePath, `${readFileSync(filePath, "utf-8").endsWith("\n") ? "" : "\n"}\n${SKILLD_INSTRUCTIONS}\n`);
1329
+ else writeFileSync(filePath, `${SKILLD_INSTRUCTIONS}\n`);
1330
+ p.log.success(`Updated ${agentConfig.instructionFile}`);
1331
+ }
1332
+ async function selectModel(skipPrompt) {
1333
+ const config = readConfig();
1334
+ const available = await getAvailableModels();
1335
+ if (available.length === 0) {
1336
+ p.log.warn("No LLM CLIs found (claude, gemini, codex)");
1337
+ return null;
1338
+ }
1339
+ if (config.model && available.some((m) => m.id === config.model)) return config.model;
1340
+ if (skipPrompt) return available.find((m) => m.recommended)?.id ?? available[0].id;
1341
+ const modelChoice = await p.select({
1342
+ message: "Model for SKILL.md generation",
1343
+ options: available.map((m) => ({
1344
+ label: m.recommended ? `${m.name} (Recommended)` : m.name,
1345
+ value: m.id,
1346
+ hint: `${m.agentName} · ${m.hint}`
1347
+ })),
1348
+ initialValue: available.find((m) => m.recommended)?.id ?? available[0].id
1349
+ });
1350
+ if (p.isCancel(modelChoice)) {
1351
+ p.cancel("Cancelled");
1352
+ return null;
1353
+ }
1354
+ updateConfig({ model: modelChoice });
1355
+ return modelChoice;
1356
+ }
1357
+ const DEFAULT_SECTIONS = ["best-practices", "api-changes"];
1358
+ async function selectSkillSections(message = "Generate SKILL.md with LLM") {
1359
+ p.log.info("More sections = less budget each. Fewer sections = deeper coverage.");
1360
+ const selected = await p.multiselect({
1361
+ message,
1362
+ options: [
1363
+ {
1364
+ label: "API changes",
1365
+ value: "api-changes",
1366
+ hint: "new/deprecated APIs from version history"
1367
+ },
1368
+ {
1369
+ label: "Best practices",
1370
+ value: "best-practices",
1371
+ hint: "gotchas, pitfalls, patterns"
1372
+ },
1373
+ {
1374
+ label: "Doc map",
1375
+ value: "api",
1376
+ hint: "compact index of exports linked to source files"
1377
+ },
1378
+ {
1379
+ label: "Custom section",
1380
+ value: "custom",
1381
+ hint: "add your own section"
1382
+ }
1383
+ ],
1384
+ initialValues: DEFAULT_SECTIONS,
1385
+ required: false
1386
+ });
1387
+ if (p.isCancel(selected)) return {
1388
+ sections: [],
1389
+ cancelled: true
1390
+ };
1391
+ const sections = selected;
1392
+ if (sections.length === 0) return {
1393
+ sections: [],
1394
+ cancelled: false
1395
+ };
1396
+ if (sections.length > 1) {
1397
+ const n = sections.length;
1398
+ const budgetLines = [];
1399
+ for (const s of sections) switch (s) {
1400
+ case "api-changes":
1401
+ budgetLines.push(` API changes ≤${maxLines(50, 80, n)} lines, ${maxItems(6, 12, n)} items`);
1402
+ break;
1403
+ case "best-practices":
1404
+ budgetLines.push(` Best practices ≤${maxLines(80, 150, n)} lines, ${maxItems(4, 10, n)} items`);
1405
+ break;
1406
+ case "api":
1407
+ budgetLines.push(` Doc map ≤${maxLines(15, 25, n)} lines`);
1408
+ break;
1409
+ case "custom":
1410
+ budgetLines.push(` Custom ≤${maxLines(50, 80, n)} lines`);
1411
+ break;
1412
+ }
1413
+ p.log.info(`Budget (${n} sections):\n${budgetLines.join("\n")}`);
1414
+ }
1415
+ let customPrompt;
1416
+ if (sections.includes("custom")) {
1417
+ const heading = await p.text({
1418
+ message: "Section heading",
1419
+ placeholder: "e.g. \"Migration from v2\" or \"SSR Patterns\""
1420
+ });
1421
+ if (p.isCancel(heading)) return {
1422
+ sections: [],
1423
+ cancelled: true
1424
+ };
1425
+ const body = await p.text({
1426
+ message: "Instructions for this section",
1427
+ placeholder: "e.g. \"Document breaking changes and migration steps from v2 to v3\""
1428
+ });
1429
+ if (p.isCancel(body)) return {
1430
+ sections: [],
1431
+ cancelled: true
1432
+ };
1433
+ customPrompt = {
1434
+ heading,
1435
+ body
1436
+ };
1437
+ }
1438
+ return {
1439
+ sections,
1440
+ customPrompt,
1441
+ cancelled: false
1442
+ };
1443
+ }
1444
+ async function selectLlmConfig(presetModel, message) {
1445
+ if (presetModel) return {
1446
+ model: presetModel,
1447
+ sections: DEFAULT_SECTIONS
1448
+ };
1449
+ if (!isInteractive()) {
1450
+ const model = await selectModel(true);
1451
+ if (!model) return null;
1452
+ return {
1453
+ model,
1454
+ sections: DEFAULT_SECTIONS
1455
+ };
1456
+ }
1457
+ const model = await selectModel(false);
1458
+ if (!model) return null;
1459
+ const modelName = getModelName(model);
1460
+ const { sections, customPrompt, cancelled } = await selectSkillSections(message ? `${message} (${modelName})` : `Generate SKILL.md with ${modelName}`);
1461
+ if (cancelled || sections.length === 0) return null;
1462
+ return {
1463
+ model,
1464
+ sections,
1465
+ customPrompt
1466
+ };
1467
+ }
1468
+ async function enhanceSkillWithLLM(opts) {
1469
+ const { packageName, version, skillDir, dirName, model, resolved, relatedSkills, hasIssues, hasDiscussions, hasReleases, hasChangelog, docsType, hasShippedDocs: shippedDocs, pkgFiles, force, debug, sections, customPrompt, packages, features, eject } = opts;
1470
+ const effectiveFeatures = eject && features ? {
1471
+ ...features,
1472
+ search: false
1473
+ } : features;
1474
+ const llmLog = p.taskLog({ title: `Agent exploring ${packageName}` });
1475
+ const docFiles = listReferenceFiles(skillDir);
1476
+ const { optimized, wasOptimized, usage, cost, warnings, debugLogsDir } = await optimizeDocs({
1477
+ packageName,
1478
+ skillDir,
1479
+ model,
1480
+ version,
1481
+ hasGithub: hasIssues || hasDiscussions,
1482
+ hasReleases,
1483
+ hasChangelog,
1484
+ docFiles,
1485
+ docsType,
1486
+ hasShippedDocs: shippedDocs,
1487
+ noCache: force,
1488
+ debug,
1489
+ sections,
1490
+ customPrompt,
1491
+ features: effectiveFeatures,
1492
+ pkgFiles,
1493
+ onProgress: createToolProgress(llmLog)
1494
+ });
1495
+ if (wasOptimized) {
1496
+ const costParts = [];
1497
+ if (usage) {
1498
+ const totalK = Math.round(usage.totalTokens / 1e3);
1499
+ costParts.push(`${totalK}k tokens`);
1500
+ }
1501
+ if (cost) costParts.push(`$${cost.toFixed(2)}`);
1502
+ const costSuffix = costParts.length > 0 ? ` (${costParts.join(", ")})` : "";
1503
+ llmLog.success(`Generated best practices${costSuffix}`);
1504
+ if (debugLogsDir) p.log.info(`Debug logs: ${debugLogsDir}`);
1505
+ if (warnings?.length) for (const w of warnings) p.log.warn(`\x1B[33m${w}\x1B[0m`);
1506
+ const skillMd = generateSkillMd({
1507
+ name: packageName,
1508
+ version,
1509
+ releasedAt: resolved.releasedAt,
1510
+ dependencies: resolved.dependencies,
1511
+ distTags: resolved.distTags,
1512
+ body: optimized,
1513
+ relatedSkills,
1514
+ hasIssues,
1515
+ hasDiscussions,
1516
+ hasReleases,
1517
+ hasChangelog,
1518
+ docsType,
1519
+ hasShippedDocs: shippedDocs,
1520
+ pkgFiles,
1521
+ generatedBy: getModelLabel(model),
1522
+ dirName,
1523
+ packages,
1524
+ repoUrl: resolved.repoUrl,
1525
+ features,
1526
+ eject
1527
+ });
1528
+ writeFileSync(join(skillDir, "SKILL.md"), skillMd);
1529
+ } else llmLog.error("LLM optimization failed");
1530
+ }
1278
1531
  const TELEMETRY_URL = "https://add-skill.vercel.sh/t";
1279
1532
  const SKILLS_VERSION = "1.3.9";
1280
1533
  function isEnabled() {
@@ -1300,6 +1553,10 @@ async function syncGitSkills(opts) {
1300
1553
  spin.start(`Fetching skills from ${label}`);
1301
1554
  const { skills, commitSha } = await fetchGitSkills(source, (msg) => spin.message(msg));
1302
1555
  if (skills.length === 0) {
1556
+ if (source.type === "github" && source.owner && source.repo) {
1557
+ spin.stop(`No pre-authored skills in ${label}, generating from repo docs...`);
1558
+ return syncGitHubRepo(opts);
1559
+ }
1303
1560
  spin.stop(`No skills found in ${label}`);
1304
1561
  return;
1305
1562
  }
@@ -1353,6 +1610,140 @@ async function syncGitSkills(opts) {
1353
1610
  const names = selected.map((s) => `\x1B[36m${s.name}\x1B[0m`).join(", ");
1354
1611
  p.log.success(`Installed ${names}`);
1355
1612
  }
1613
+ async function syncGitHubRepo(opts) {
1614
+ const { source, agent, global: isGlobal, yes } = opts;
1615
+ const owner = source.owner;
1616
+ const repo = source.repo;
1617
+ const cwd = process.cwd();
1618
+ const spin = timedSpinner();
1619
+ spin.start(`Resolving ${owner}/${repo}`);
1620
+ const resolved = await resolveGitHubRepo(owner, repo, (msg) => spin.message(msg));
1621
+ if (!resolved) {
1622
+ spin.stop(`Could not find docs for ${owner}/${repo}`);
1623
+ return;
1624
+ }
1625
+ const repoUrl = `https://github.com/${owner}/${repo}`;
1626
+ const packageName = `${owner}-${repo}`;
1627
+ const version = resolved.version || "main";
1628
+ const versionKey = getVersionKey(version);
1629
+ const useCache = isCached(packageName, version);
1630
+ spin.stop(`Resolved ${owner}/${repo}@${useCache ? versionKey : version}${useCache ? " (cached)" : ""}`);
1631
+ ensureCacheDir();
1632
+ const baseDir = resolveBaseDir(cwd, agent, isGlobal);
1633
+ const skillDirName = sanitizeName(`${owner}-${repo}`);
1634
+ const skillDir = join(baseDir, skillDirName);
1635
+ mkdirSync(skillDir, { recursive: true });
1636
+ const features = readConfig().features ?? defaultFeatures;
1637
+ const resSpin = timedSpinner();
1638
+ resSpin.start("Finding resources");
1639
+ const resources = await fetchAndCacheResources({
1640
+ packageName,
1641
+ resolved,
1642
+ version,
1643
+ useCache,
1644
+ features,
1645
+ from: opts.from,
1646
+ onProgress: (msg) => resSpin.message(msg)
1647
+ });
1648
+ const resParts = [];
1649
+ if (resources.docsToIndex.length > 0) {
1650
+ const docCount = resources.docsToIndex.filter((d) => d.metadata?.type === "doc").length;
1651
+ if (docCount > 0) resParts.push(`${docCount} docs`);
1652
+ }
1653
+ if (resources.hasIssues) resParts.push("issues");
1654
+ if (resources.hasDiscussions) resParts.push("discussions");
1655
+ if (resources.hasReleases) resParts.push("releases");
1656
+ resSpin.stop(`Fetched ${resParts.length > 0 ? resParts.join(", ") : "resources"}`);
1657
+ for (const w of resources.warnings) p.log.warn(`\x1B[33m${w}\x1B[0m`);
1658
+ linkAllReferences(skillDir, packageName, cwd, version, resources.docsType, void 0, features);
1659
+ if (features.search) {
1660
+ const idxSpin = timedSpinner();
1661
+ idxSpin.start("Creating search index");
1662
+ await indexResources({
1663
+ packageName,
1664
+ version,
1665
+ cwd,
1666
+ docsToIndex: resources.docsToIndex,
1667
+ features,
1668
+ onProgress: (msg) => idxSpin.message(msg)
1669
+ });
1670
+ idxSpin.stop("Search index ready");
1671
+ }
1672
+ const hasChangelog = detectChangelog(resolvePkgDir(packageName, cwd, version), getCacheDir(packageName, version));
1673
+ const shippedDocs = hasShippedDocs(packageName, cwd, version);
1674
+ const pkgFiles = getPkgKeyFiles(packageName, cwd, version);
1675
+ writeLock(baseDir, skillDirName, {
1676
+ packageName,
1677
+ version,
1678
+ repo: `${owner}/${repo}`,
1679
+ source: resources.docSource,
1680
+ syncedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
1681
+ generator: "skilld"
1682
+ });
1683
+ const baseSkillMd = generateSkillMd({
1684
+ name: packageName,
1685
+ version,
1686
+ releasedAt: resolved.releasedAt,
1687
+ description: resolved.description,
1688
+ relatedSkills: [],
1689
+ hasIssues: resources.hasIssues,
1690
+ hasDiscussions: resources.hasDiscussions,
1691
+ hasReleases: resources.hasReleases,
1692
+ hasChangelog,
1693
+ docsType: resources.docsType,
1694
+ hasShippedDocs: shippedDocs,
1695
+ pkgFiles,
1696
+ dirName: skillDirName,
1697
+ repoUrl,
1698
+ features
1699
+ });
1700
+ writeFileSync(join(skillDir, "SKILL.md"), baseSkillMd);
1701
+ p.log.success(`Created base skill: ${relative(cwd, skillDir)}`);
1702
+ if (!readConfig().skipLlm && (!yes || opts.model)) {
1703
+ const llmConfig = await selectLlmConfig(opts.model);
1704
+ if (llmConfig) {
1705
+ p.log.step(getModelLabel(llmConfig.model));
1706
+ await enhanceSkillWithLLM({
1707
+ packageName,
1708
+ version,
1709
+ skillDir,
1710
+ dirName: skillDirName,
1711
+ model: llmConfig.model,
1712
+ resolved,
1713
+ relatedSkills: [],
1714
+ hasIssues: resources.hasIssues,
1715
+ hasDiscussions: resources.hasDiscussions,
1716
+ hasReleases: resources.hasReleases,
1717
+ hasChangelog,
1718
+ docsType: resources.docsType,
1719
+ hasShippedDocs: shippedDocs,
1720
+ pkgFiles,
1721
+ force: opts.force,
1722
+ debug: opts.debug,
1723
+ sections: llmConfig.sections,
1724
+ customPrompt: llmConfig.customPrompt,
1725
+ features
1726
+ });
1727
+ }
1728
+ }
1729
+ const shared = !isGlobal && getSharedSkillsDir(cwd);
1730
+ if (shared) linkSkillToAgents(skillDirName, shared, cwd);
1731
+ if (!isGlobal) {
1732
+ registerProject(cwd);
1733
+ await ensureGitignore(shared || targets[agent].skillsDir, cwd, isGlobal);
1734
+ await ensureAgentInstructions(agent, cwd, isGlobal);
1735
+ }
1736
+ await shutdownWorker();
1737
+ track({
1738
+ event: "install",
1739
+ source: `${owner}/${repo}`,
1740
+ skills: skillDirName,
1741
+ agents: agent,
1742
+ ...isGlobal && { global: "1" },
1743
+ sourceType: "github-generated"
1744
+ });
1745
+ p.outro(`Synced ${owner}/${repo} to ${relative(cwd, skillDir)}`);
1746
+ }
1356
1747
  const STATUS_ICONS = {
1357
1748
  pending: "○",
1358
1749
  resolving: "◐",
@@ -1466,9 +1857,10 @@ async function syncPackagesParallel(config) {
1466
1857
  await shutdownWorker();
1467
1858
  p.outro(`${pastVerb} ${successfulPkgs.length}/${packages.length} packages`);
1468
1859
  }
1469
- async function syncBaseSkill(packageName, config, cwd, update) {
1860
+ async function syncBaseSkill(packageSpec, config, cwd, update) {
1861
+ const { name: packageName, tag: requestedTag } = parsePackageSpec(packageSpec);
1470
1862
  const localVersion = (await readLocalDependencies(cwd).catch(() => [])).find((d) => d.name === packageName)?.version;
1471
- const { package: resolvedPkg, attempts } = await resolvePackageDocsWithAttempts(packageName, {
1863
+ const { package: resolvedPkg, attempts } = await resolvePackageDocsWithAttempts(requestedTag ? packageSpec : packageName, {
1472
1864
  version: localVersion,
1473
1865
  cwd,
1474
1866
  onProgress: (step) => update(packageName, "resolving", RESOLVE_STEP_LABELS[step])
@@ -1612,6 +2004,7 @@ async function enhanceWithLLM(packageName, data, config, cwd, update, sections,
1612
2004
  sections,
1613
2005
  customPrompt,
1614
2006
  features: data.features,
2007
+ pkgFiles: data.pkgFiles,
1615
2008
  onProgress: (progress) => {
1616
2009
  const status = progress.type === "reasoning" ? "exploring" : "generating";
1617
2010
  const sectionPrefix = progress.section ? `[${progress.section}] ` : "";
@@ -1740,17 +2133,8 @@ async function runWizard() {
1740
2133
  p.outro("Thanks, you're all set! Change config anytime with `skilld config`.");
1741
2134
  }
1742
2135
  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
2136
  addCommandDef: () => addCommandDef,
1747
2137
  ejectCommandDef: () => ejectCommandDef,
1748
- enhanceSkillWithLLM: () => enhanceSkillWithLLM,
1749
- ensureAgentInstructions: () => ensureAgentInstructions,
1750
- ensureGitignore: () => ensureGitignore,
1751
- selectLlmConfig: () => selectLlmConfig,
1752
- selectModel: () => selectModel,
1753
- selectSkillSections: () => selectSkillSections,
1754
2138
  syncCommand: () => syncCommand,
1755
2139
  updateCommandDef: () => updateCommandDef
1756
2140
  });
@@ -1764,59 +2148,6 @@ function showResolveAttempts(attempts) {
1764
2148
  p.log.message(` ${icon} ${source}${msg}`);
1765
2149
  }
1766
2150
  }
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
2151
  async function syncCommand(state, opts) {
1821
2152
  if (opts.packages && opts.packages.length > 0) {
1822
2153
  if (opts.packages.length > 1) return syncPackagesParallel({
@@ -1905,148 +2236,13 @@ async function pickFromList(packages, state) {
1905
2236
  }
1906
2237
  return selected;
1907
2238
  }
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) {
2239
+ async function syncSinglePackage(packageSpec, config) {
2240
+ const { name: packageName, tag: requestedTag } = parsePackageSpec(packageSpec);
2045
2241
  const spin = timedSpinner();
2046
- spin.start(`Resolving ${packageName}`);
2242
+ spin.start(`Resolving ${packageSpec}`);
2047
2243
  const cwd = process.cwd();
2048
2244
  const localVersion = (await readLocalDependencies(cwd).catch(() => [])).find((d) => d.name === packageName)?.version;
2049
- const resolveResult = await resolvePackageDocsWithAttempts(packageName, {
2245
+ const resolveResult = await resolvePackageDocsWithAttempts(requestedTag ? packageSpec : packageName, {
2050
2246
  version: localVersion,
2051
2247
  cwd,
2052
2248
  onProgress: (step) => spin.message(`${packageName}: ${RESOLVE_STEP_LABELS[step]}`)
@@ -2102,7 +2298,7 @@ async function syncSinglePackage(packageName, config) {
2102
2298
  ensureCacheDir();
2103
2299
  const baseDir = resolveBaseDir(cwd, config.agent, config.global);
2104
2300
  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);
2301
+ const skillDir = config.eject ? typeof config.eject === "string" ? join(resolve(cwd, config.eject), skillDirName) : join(cwd, "skills", skillDirName) : join(baseDir, skillDirName);
2106
2302
  mkdirSync(skillDir, { recursive: true });
2107
2303
  const existingLock = config.eject ? void 0 : readLock(baseDir)?.skills[skillDirName];
2108
2304
  if (existingLock && existingLock.packageName !== packageName) {
@@ -2267,68 +2463,6 @@ async function syncSinglePackage(packageName, config) {
2267
2463
  const ejectMsg = isEject ? " (ejected)" : "";
2268
2464
  p.outro(config.mode === "update" ? `Updated ${packageName}${ejectMsg}` : `Synced ${packageName} to ${relative(cwd, skillDir)}${ejectMsg}`);
2269
2465
  }
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
2466
  const addCommandDef = defineCommand({
2333
2467
  meta: {
2334
2468
  name: "add",
@@ -2362,7 +2496,10 @@ const addCommandDef = defineCommand({
2362
2496
  source,
2363
2497
  global: args.global,
2364
2498
  agent,
2365
- yes: args.yes
2499
+ yes: args.yes,
2500
+ model: args.model,
2501
+ force: args.force,
2502
+ debug: args.debug
2366
2503
  });
2367
2504
  if (npmTokens.length > 0) {
2368
2505
  const packages = [...new Set(npmTokens.flatMap((s) => s.split(/[,\s]+/)).map((s) => s.trim()).filter(Boolean))];
@@ -2886,6 +3023,7 @@ async function enhanceRegenerated(pkgName, version, skillDir, model, sections, c
2886
3023
  sections,
2887
3024
  customPrompt,
2888
3025
  features,
3026
+ pkgFiles: getPkgKeyFiles(pkgName, process.cwd(), version),
2889
3027
  onProgress: createToolProgress(llmLog)
2890
3028
  });
2891
3029
  if (wasOptimized) {