skilld 0.7.0 → 0.8.2

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,21 +5,22 @@ 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 { A as resolveEntryFiles, D as fetchGitSkills, I as fetchReleaseNotes, J as generateIssueIndex, K as fetchGitHubIssues, L as generateReleaseIndex, M as formatDiscussionAsMarkdown, N as generateDiscussionIndex, O as parseGitSkillInput, P as fetchBlogReleases, T as isShallowGitDocs, W as parseGitHubUrl, Y as isGhAvailable, 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 formatIssueAsMarkdown, r as fetchNpmRegistryMeta, s as readLocalDependencies, t as fetchLatestVersion, u as resolveLocalPackageDocs, v as normalizeLlmsLinks, w as fetchReadmeContent, x as fetchGitDocs, z as $fetch } from "./_chunks/npm.mjs";
8
+ import { a as getSharedSkillsDir, i as SHARED_SKILLS_DIR, n as yamlParseKV, o as mapInsert, r as yamlUnescape, s as getBlogPreset, 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";
10
10
  import "./sources/index.mjs";
11
- import { _ as detectTargetAgent, a as optimizeDocs, d as unlinkSkillFromAgents, g as detectInstalledAgents, i as getModelName, l as linkSkillToAgents, n as getAvailableModels, o as generateSkillMd, r as getModelLabel, s as computeSkillDirName, t as detectImportedPackages, v as getAgentVersion, y as targets } from "./_chunks/detect-imports.mjs";
11
+ import { S as targets, _ as maxItems, a as getModelName, b as detectTargetAgent, c as computeSkillDirName, 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";
13
13
  import { n as shutdownWorker } from "./_chunks/pool2.mjs";
14
14
  import { createRequire } from "node:module";
15
15
  import { homedir } from "node:os";
16
16
  import { dirname, join, relative, resolve } from "pathe";
17
17
  import { appendFileSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
18
- import { execSync, spawn } from "node:child_process";
18
+ import { execSync } from "node:child_process";
19
19
  import pLimit from "p-limit";
20
20
  import * as p from "@clack/prompts";
21
21
  import { defineCommand, runMain } from "citty";
22
22
  import { detectCurrentAgent } from "unagent/env";
23
+ import { isCI } from "std-env";
23
24
  import logUpdate, { createLogUpdate } from "log-update";
24
25
  const defaultFeatures = {
25
26
  search: true,
@@ -450,11 +451,6 @@ function removeLockEntry(skillsDir, skillName) {
450
451
  }
451
452
  writeFileSync(lockPath, serializeLock(lock));
452
453
  }
453
- const SHARED_SKILLS_DIR = ".skills";
454
- function getSharedSkillsDir(cwd = process.cwd()) {
455
- const dir = join(cwd, SHARED_SKILLS_DIR);
456
- return existsSync(dir) ? dir : null;
457
- }
458
454
  function* iterateSkills(opts = {}) {
459
455
  const { scope = "all", cwd = process.cwd() } = opts;
460
456
  const agentTypes = opts.agents ?? Object.keys(targets);
@@ -546,7 +542,8 @@ function* iterateSkills(opts = {}) {
546
542
  }
547
543
  function isOutdated(skill, depVersion) {
548
544
  if (!skill.info?.version) return true;
549
- return skill.info.version.split(".").slice(0, 2).join(".") !== depVersion.replace(/^[\^~]/, "").split(".").slice(0, 2).join(".");
545
+ const depClean = depVersion.replace(/^[\^~]/, "");
546
+ return skill.info.version !== depClean;
550
547
  }
551
548
  async function getProjectState(cwd = process.cwd()) {
552
549
  const skills = [...iterateSkills({
@@ -804,61 +801,6 @@ function formatCompactSnippet(r, cols) {
804
801
  preview: firstLine.length > maxPreview ? `${firstLine.slice(0, maxPreview - 1)}…` : firstLine
805
802
  };
806
803
  }
807
- async function syncGitSkills(opts) {
808
- const { source, agent, global: isGlobal, yes } = opts;
809
- const cwd = process.cwd();
810
- const agentConfig = targets[agent];
811
- const baseDir = isGlobal ? join(CACHE_DIR, "skills") : join(cwd, agentConfig.skillsDir);
812
- const label = source.type === "local" ? source.localPath : `${source.owner}/${source.repo}`;
813
- const spin = timedSpinner();
814
- spin.start(`Fetching skills from ${label}`);
815
- const { skills, commitSha } = await fetchGitSkills(source, (msg) => spin.message(msg));
816
- if (skills.length === 0) {
817
- spin.stop(`No skills found in ${label}`);
818
- return;
819
- }
820
- spin.stop(`Found ${skills.length} skill(s) in ${label}`);
821
- let selected = skills;
822
- if (source.skillPath) selected = skills;
823
- else if (skills.length > 1 && !yes) {
824
- const choices = await p.multiselect({
825
- message: `Select skills to install from ${label}`,
826
- options: skills.map((s) => ({
827
- label: s.name,
828
- value: s.name,
829
- hint: s.description || s.path
830
- })),
831
- initialValues: skills.map((s) => s.name)
832
- });
833
- if (p.isCancel(choices)) return;
834
- const selectedNames = new Set(choices);
835
- selected = skills.filter((s) => selectedNames.has(s.name));
836
- }
837
- mkdirSync(baseDir, { recursive: true });
838
- for (const skill of selected) {
839
- const skillDir = join(baseDir, skill.name);
840
- mkdirSync(skillDir, { recursive: true });
841
- writeFileSync(join(skillDir, "SKILL.md"), sanitizeMarkdown(skill.content));
842
- if (skill.files.length > 0) for (const f of skill.files) {
843
- const filePath = join(skillDir, ".skilld", f.path);
844
- mkdirSync(dirname(filePath), { recursive: true });
845
- writeFileSync(filePath, f.content);
846
- }
847
- const sourceType = source.type === "local" ? "local" : source.type;
848
- writeLock(baseDir, skill.name, {
849
- source: sourceType,
850
- repo: source.type === "local" ? source.localPath : `${source.owner}/${source.repo}`,
851
- path: skill.path || void 0,
852
- ref: source.ref || "main",
853
- commit: commitSha,
854
- syncedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
855
- generator: "external"
856
- });
857
- }
858
- if (!isGlobal) registerProject(cwd);
859
- const names = selected.map((s) => `\x1B[36m${s.name}\x1B[0m`).join(", ");
860
- p.log.success(`Installed ${names}`);
861
- }
862
804
  const RESOLVE_STEP_LABELS = {
863
805
  "npm": "npm registry",
864
806
  "github-docs": "GitHub docs",
@@ -1308,6 +1250,84 @@ async function indexResources(opts) {
1308
1250
  }
1309
1251
  });
1310
1252
  }
1253
+ const TELEMETRY_URL = "https://add-skill.vercel.sh/t";
1254
+ const SKILLS_VERSION = "1.3.9";
1255
+ function isEnabled() {
1256
+ return !process.env.DISABLE_TELEMETRY && !process.env.DO_NOT_TRACK;
1257
+ }
1258
+ function track(data) {
1259
+ if (!isEnabled()) return;
1260
+ try {
1261
+ const params = new URLSearchParams();
1262
+ params.set("v", SKILLS_VERSION);
1263
+ if (isCI) params.set("ci", "1");
1264
+ for (const [key, value] of Object.entries(data)) if (value !== void 0 && value !== null) params.set(key, String(value));
1265
+ fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {});
1266
+ } catch {}
1267
+ }
1268
+ async function syncGitSkills(opts) {
1269
+ const { source, agent, global: isGlobal, yes } = opts;
1270
+ const cwd = process.cwd();
1271
+ const agentConfig = targets[agent];
1272
+ const baseDir = isGlobal ? join(CACHE_DIR, "skills") : join(cwd, agentConfig.skillsDir);
1273
+ const label = source.type === "local" ? source.localPath : `${source.owner}/${source.repo}`;
1274
+ const spin = timedSpinner();
1275
+ spin.start(`Fetching skills from ${label}`);
1276
+ const { skills, commitSha } = await fetchGitSkills(source, (msg) => spin.message(msg));
1277
+ if (skills.length === 0) {
1278
+ spin.stop(`No skills found in ${label}`);
1279
+ return;
1280
+ }
1281
+ spin.stop(`Found ${skills.length} skill(s) in ${label}`);
1282
+ let selected = skills;
1283
+ if (source.skillPath) selected = skills;
1284
+ else if (skills.length > 1 && !yes) {
1285
+ const choices = await p.multiselect({
1286
+ message: `Select skills to install from ${label}`,
1287
+ options: skills.map((s) => ({
1288
+ label: s.name,
1289
+ value: s.name,
1290
+ hint: s.description || s.path
1291
+ })),
1292
+ initialValues: skills.map((s) => s.name)
1293
+ });
1294
+ if (p.isCancel(choices)) return;
1295
+ const selectedNames = new Set(choices);
1296
+ selected = skills.filter((s) => selectedNames.has(s.name));
1297
+ }
1298
+ mkdirSync(baseDir, { recursive: true });
1299
+ for (const skill of selected) {
1300
+ const skillDir = join(baseDir, skill.name);
1301
+ mkdirSync(skillDir, { recursive: true });
1302
+ writeFileSync(join(skillDir, "SKILL.md"), sanitizeMarkdown(skill.content));
1303
+ if (skill.files.length > 0) for (const f of skill.files) {
1304
+ const filePath = join(skillDir, ".skilld", f.path);
1305
+ mkdirSync(dirname(filePath), { recursive: true });
1306
+ writeFileSync(filePath, f.content);
1307
+ }
1308
+ const sourceType = source.type === "local" ? "local" : source.type;
1309
+ writeLock(baseDir, skill.name, {
1310
+ source: sourceType,
1311
+ repo: source.type === "local" ? source.localPath : `${source.owner}/${source.repo}`,
1312
+ path: skill.path || void 0,
1313
+ ref: source.ref || "main",
1314
+ commit: commitSha,
1315
+ syncedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
1316
+ generator: "external"
1317
+ });
1318
+ }
1319
+ if (!isGlobal) registerProject(cwd);
1320
+ if (source.type !== "local" && source.owner && source.repo) track({
1321
+ event: "install",
1322
+ source: `${source.owner}/${source.repo}`,
1323
+ skills: selected.map((s) => s.name).join(","),
1324
+ agents: agent,
1325
+ ...isGlobal && { global: "1" },
1326
+ sourceType: source.type
1327
+ });
1328
+ const names = selected.map((s) => `\x1B[36m${s.name}\x1B[0m`).join(", ");
1329
+ p.log.success(`Installed ${names}`);
1330
+ }
1311
1331
  const STATUS_ICONS = {
1312
1332
  pending: "○",
1313
1333
  resolving: "◐",
@@ -1355,7 +1375,7 @@ async function syncPackagesParallel(config) {
1355
1375
  });
1356
1376
  const doneCount = [...states.values()].filter((s) => s.status === "done").length;
1357
1377
  const errorCount = [...states.values()].filter((s) => s.status === "error").length;
1358
- logUpdate(`\x1B[1mSyncing ${packages.length} packages\x1B[0m (${doneCount} done${errorCount > 0 ? `, ${errorCount} failed` : ""})\n` + lines.join("\n"));
1378
+ logUpdate(`\x1B[1m${config.mode === "update" ? "Updating" : "Syncing"} ${packages.length} packages\x1B[0m (${doneCount} done${errorCount > 0 ? `, ${errorCount} failed` : ""})\n` + lines.join("\n"));
1359
1379
  }
1360
1380
  function update(pkg, status, message, version) {
1361
1381
  const state = states.get(pkg);
@@ -1391,7 +1411,8 @@ async function syncPackagesParallel(config) {
1391
1411
  });
1392
1412
  }
1393
1413
  }
1394
- const skillMsg = `Created ${successfulPkgs.length} base skills${shippedPkgs.length > 1 ? ` (Skipping ${shippedPkgs.length})` : ""}`;
1414
+ const pastVerb = config.mode === "update" ? "Updated" : "Created";
1415
+ const skillMsg = `${pastVerb} ${successfulPkgs.length} base skills${shippedPkgs.length > 1 ? ` (Skipping ${shippedPkgs.length})` : ""}`;
1395
1416
  p.log.success(skillMsg);
1396
1417
  for (const [, data] of skillData) for (const w of data.warnings) p.log.warn(`\x1B[33m${w}\x1B[0m`);
1397
1418
  if (errors.length > 0) for (const { pkg, reason } of errors) p.log.error(` ${pkg}: ${reason}`);
@@ -1418,7 +1439,7 @@ async function syncPackagesParallel(config) {
1418
1439
  await ensureGitignore(getSharedSkillsDir(cwd) ? SHARED_SKILLS_DIR : agent.skillsDir, cwd, config.global);
1419
1440
  await ensureAgentInstructions(config.agent, cwd, config.global);
1420
1441
  await shutdownWorker();
1421
- p.outro(`Synced ${successfulPkgs.length}/${packages.length} packages`);
1442
+ p.outro(`${pastVerb} ${successfulPkgs.length}/${packages.length} packages`);
1422
1443
  }
1423
1444
  async function syncBaseSkill(packageName, config, cwd, update) {
1424
1445
  const localVersion = (await readLocalDependencies(cwd).catch(() => [])).find((d) => d.name === packageName)?.version;
@@ -1526,7 +1547,7 @@ async function syncBaseSkill(packageName, config, cwd, update) {
1526
1547
  const shared = !config.global && getSharedSkillsDir(cwd);
1527
1548
  if (shared) linkSkillToAgents(skillDirName, shared, cwd);
1528
1549
  if (!config.global) registerProject(cwd);
1529
- update(packageName, "done", "Base skill created", versionKey);
1550
+ update(packageName, "done", config.mode === "update" ? "Skill updated" : "Base skill created", versionKey);
1530
1551
  return {
1531
1552
  resolved,
1532
1553
  version,
@@ -1601,6 +1622,98 @@ async function enhanceWithLLM(packageName, data, config, cwd, update, sections,
1601
1622
  }
1602
1623
  update(packageName, "done", "Skill optimized", versionKey);
1603
1624
  }
1625
+ function hasGhCli() {
1626
+ if (process.env.SKILLD_NO_GH) return false;
1627
+ try {
1628
+ execSync("gh --version", { stdio: "ignore" });
1629
+ return true;
1630
+ } catch {
1631
+ return false;
1632
+ }
1633
+ }
1634
+ async function runWizard() {
1635
+ if (!isInteractive()) return;
1636
+ p.note("Skilld gives your AI agent skill knowledge on your NPM\ndependencies gathered from versioned docs, source code\nand GitHub issues.", "Welcome to skilld");
1637
+ const ghInstalled = hasGhCli();
1638
+ if (ghInstalled) p.log.success("GitHub CLI detected — will use it to pull issues and discussions.");
1639
+ else p.log.warn("GitHub CLI not found. Install it to enable issues/discussions:\n \x1B[36mhttps://cli.github.com\x1B[0m");
1640
+ const selected = await p.multiselect({
1641
+ message: "Which features would you like to enable?",
1642
+ options: [
1643
+ {
1644
+ label: "Semantic + token search",
1645
+ value: "search",
1646
+ hint: "local query engine to cut token costs and speed up grep"
1647
+ },
1648
+ {
1649
+ label: "Release notes",
1650
+ value: "releases",
1651
+ hint: "track changelogs for installed packages"
1652
+ },
1653
+ {
1654
+ label: "GitHub issues",
1655
+ value: "issues",
1656
+ hint: "surface common problems and solutions",
1657
+ disabled: !ghInstalled
1658
+ },
1659
+ {
1660
+ label: "GitHub discussions",
1661
+ value: "discussions",
1662
+ hint: "include Q&A and community knowledge",
1663
+ disabled: !ghInstalled
1664
+ }
1665
+ ],
1666
+ initialValues: [...Object.entries(defaultFeatures).filter(([, v]) => v).map(([k]) => k), ...ghInstalled ? ["issues", "discussions"] : []],
1667
+ required: false
1668
+ });
1669
+ if (p.isCancel(selected)) {
1670
+ p.cancel("Setup cancelled");
1671
+ process.exit(0);
1672
+ }
1673
+ const features = {
1674
+ search: selected.includes("search"),
1675
+ issues: selected.includes("issues"),
1676
+ discussions: selected.includes("discussions"),
1677
+ releases: selected.includes("releases")
1678
+ };
1679
+ const allModels = process.env.SKILLD_NO_AGENTS ? [] : await getAvailableModels();
1680
+ let modelId;
1681
+ if (allModels.length > 0) {
1682
+ p.note("Skills work without an LLM, but one can rewrite your\nSKILL.md files with best practices and better structure.\n\x1B[90mThis is separate from the agent where skills are installed —\nthe target agent is auto-detected from your project files.\x1B[0m", "Optional: LLM optimization");
1683
+ const modelChoice = await p.select({
1684
+ message: "Model for generating SKILL.md",
1685
+ options: [{
1686
+ label: "Skip",
1687
+ value: "",
1688
+ hint: "use raw docs, no LLM needed"
1689
+ }, ...allModels.map((m) => ({
1690
+ label: m.recommended ? `${m.name} (Recommended)` : m.name,
1691
+ value: m.id,
1692
+ hint: `${m.agentName} · ${m.hint}`
1693
+ }))]
1694
+ });
1695
+ if (p.isCancel(modelChoice)) {
1696
+ p.cancel("Setup cancelled");
1697
+ process.exit(0);
1698
+ }
1699
+ modelId = modelChoice || void 0;
1700
+ } else {
1701
+ p.log.warn("No supported LLM CLIs detected (claude, gemini, codex).\n Skills will still work, but won't be LLM-optimized.");
1702
+ const proceed = await p.confirm({
1703
+ message: "Continue without LLM optimization?",
1704
+ initialValue: true
1705
+ });
1706
+ if (p.isCancel(proceed) || !proceed) {
1707
+ p.cancel("Setup cancelled");
1708
+ process.exit(0);
1709
+ }
1710
+ }
1711
+ updateConfig({
1712
+ features,
1713
+ ...modelId ? { model: modelId } : { skipLlm: true }
1714
+ });
1715
+ p.outro("Thanks, you're all set! Change config anytime with `skilld config`.");
1716
+ }
1604
1717
  var sync_exports = /* @__PURE__ */ __exportAll({
1605
1718
  DEFAULT_SECTIONS: () => DEFAULT_SECTIONS,
1606
1719
  SKILLD_MARKER_END: () => SKILLD_MARKER_END,
@@ -1686,7 +1799,8 @@ async function syncCommand(state, opts) {
1686
1799
  model: opts.model,
1687
1800
  yes: opts.yes,
1688
1801
  force: opts.force,
1689
- debug: opts.debug
1802
+ debug: opts.debug,
1803
+ mode: opts.mode
1690
1804
  });
1691
1805
  await syncSinglePackage(opts.packages[0], opts);
1692
1806
  return;
@@ -1703,7 +1817,8 @@ async function syncCommand(state, opts) {
1703
1817
  model: opts.model,
1704
1818
  yes: opts.yes,
1705
1819
  force: opts.force,
1706
- debug: opts.debug
1820
+ debug: opts.debug,
1821
+ mode: opts.mode
1707
1822
  });
1708
1823
  await syncSinglePackage(packages[0], opts);
1709
1824
  }
@@ -1790,6 +1905,7 @@ async function selectModel(skipPrompt) {
1790
1905
  }
1791
1906
  const DEFAULT_SECTIONS = ["best-practices", "api-changes"];
1792
1907
  async function selectSkillSections(message = "Generate SKILL.md with LLM") {
1908
+ p.log.info("More sections = less budget each. Fewer sections = deeper coverage.");
1793
1909
  const selected = await p.multiselect({
1794
1910
  message,
1795
1911
  options: [
@@ -1826,6 +1942,25 @@ async function selectSkillSections(message = "Generate SKILL.md with LLM") {
1826
1942
  sections: [],
1827
1943
  cancelled: false
1828
1944
  };
1945
+ if (sections.length > 1) {
1946
+ const n = sections.length;
1947
+ const budgetLines = [];
1948
+ for (const s of sections) switch (s) {
1949
+ case "api-changes":
1950
+ budgetLines.push(` API changes ≤${maxLines(50, 80, n)} lines, ${maxItems(6, 12, n)} items`);
1951
+ break;
1952
+ case "best-practices":
1953
+ budgetLines.push(` Best practices ≤${maxLines(80, 150, n)} lines, ${maxItems(4, 10, n)} items`);
1954
+ break;
1955
+ case "api":
1956
+ budgetLines.push(` Doc map ≤${maxLines(15, 25, n)} lines`);
1957
+ break;
1958
+ case "custom":
1959
+ budgetLines.push(` Custom ≤${maxLines(50, 80, n)} lines`);
1960
+ break;
1961
+ }
1962
+ p.log.info(`Budget (${n} sections):\n${budgetLines.join("\n")}`);
1963
+ }
1829
1964
  let customPrompt;
1830
1965
  if (sections.includes("custom")) {
1831
1966
  const heading = await p.text({
@@ -2054,7 +2189,7 @@ async function syncSinglePackage(packageName, config) {
2054
2189
  features
2055
2190
  });
2056
2191
  writeFileSync(join(skillDir, "SKILL.md"), baseSkillMd);
2057
- p.log.success(`Created base skill: ${relative(cwd, skillDir)}`);
2192
+ p.log.success(config.mode === "update" ? `Updated skill: ${relative(cwd, skillDir)}` : `Created base skill: ${relative(cwd, skillDir)}`);
2058
2193
  if (!readConfig().skipLlm && (!config.yes || config.model)) {
2059
2194
  const llmConfig = await selectLlmConfig(config.model);
2060
2195
  if (llmConfig) {
@@ -2089,7 +2224,7 @@ async function syncSinglePackage(packageName, config) {
2089
2224
  await ensureGitignore(shared ? SHARED_SKILLS_DIR : targets[config.agent].skillsDir, cwd, config.global);
2090
2225
  await ensureAgentInstructions(config.agent, cwd, config.global);
2091
2226
  await shutdownWorker();
2092
- p.outro(`Synced ${packageName} to ${relative(cwd, skillDir)}`);
2227
+ p.outro(config.mode === "update" ? `Updated ${packageName}` : `Synced ${packageName} to ${relative(cwd, skillDir)}`);
2093
2228
  }
2094
2229
  async function enhanceSkillWithLLM(opts) {
2095
2230
  const { packageName, version, skillDir, dirName, model, resolved, relatedSkills, hasIssues, hasDiscussions, hasReleases, hasChangelog, docsType, hasShippedDocs: shippedDocs, pkgFiles, force, debug, sections, customPrompt, packages, features } = opts;
@@ -2111,11 +2246,7 @@ async function enhanceSkillWithLLM(opts) {
2111
2246
  sections,
2112
2247
  customPrompt,
2113
2248
  features,
2114
- onProgress: ({ type, chunk, section }) => {
2115
- const prefix = section ? `\x1B[90m[${section}]\x1B[0m ` : "";
2116
- if (type === "reasoning" && chunk.startsWith("[")) llmLog.message(`${prefix}${chunk}`);
2117
- else if (type === "text") llmLog.message(`${prefix}Writing...`);
2118
- }
2249
+ onProgress: createToolProgress(llmLog)
2119
2250
  });
2120
2251
  if (wasOptimized) {
2121
2252
  const costParts = [];
@@ -2172,6 +2303,7 @@ const addCommandDef = defineCommand({
2172
2303
  agent = await promptForAgent();
2173
2304
  if (!agent) return;
2174
2305
  }
2306
+ if (!hasCompletedWizard()) await runWizard();
2175
2307
  const rawInputs = [...new Set([args.package, ...args._ || []].map((s) => s.trim()).filter(Boolean))];
2176
2308
  const gitSources = [];
2177
2309
  const npmTokens = [];
@@ -2213,31 +2345,57 @@ const updateCommandDef = defineCommand({
2213
2345
  description: "Package(s) to update (space or comma-separated). Without args, syncs all outdated.",
2214
2346
  required: false
2215
2347
  },
2348
+ background: {
2349
+ type: "boolean",
2350
+ alias: "b",
2351
+ description: "Run in background (detached process, non-interactive)",
2352
+ default: false
2353
+ },
2216
2354
  ...sharedArgs
2217
2355
  },
2218
2356
  async run({ args }) {
2219
2357
  const cwd = process.cwd();
2358
+ if (args.background) {
2359
+ const { spawn } = await import("node:child_process");
2360
+ const updateArgs = [
2361
+ "update",
2362
+ ...args.package ? [args.package] : [],
2363
+ ...args.agent ? ["--agent", args.agent] : [],
2364
+ ...args.model ? ["--model", args.model] : []
2365
+ ];
2366
+ spawn(process.execPath, [process.argv[1], ...updateArgs], {
2367
+ cwd,
2368
+ detached: true,
2369
+ stdio: "ignore"
2370
+ }).unref();
2371
+ return;
2372
+ }
2373
+ const silent = !isInteractive();
2220
2374
  let agent = resolveAgent(args.agent);
2221
2375
  if (!agent) {
2376
+ if (silent) return;
2222
2377
  agent = await promptForAgent();
2223
2378
  if (!agent) return;
2224
2379
  }
2225
- const state = await getProjectState(cwd);
2226
- const generators = getInstalledGenerators();
2227
2380
  const config = readConfig();
2228
- p.intro(introLine({
2229
- state,
2230
- generators,
2231
- modelId: config.model
2232
- }));
2381
+ const state = await getProjectState(cwd);
2382
+ if (!silent) {
2383
+ const generators = getInstalledGenerators();
2384
+ p.intro(introLine({
2385
+ state,
2386
+ generators,
2387
+ modelId: config.model
2388
+ }));
2389
+ }
2233
2390
  if (args.package) return syncCommand(state, {
2234
2391
  packages: [...new Set([args.package, ...args._ || []].flatMap((s) => s.split(/[,\s]+/)).map((s) => s.trim()).filter(Boolean))],
2235
2392
  global: args.global,
2236
2393
  agent,
2237
- model: args.model,
2238
- yes: args.yes,
2394
+ model: args.model || (silent ? config.model : void 0),
2395
+ yes: args.yes || silent,
2239
2396
  force: args.force,
2240
- debug: args.debug
2397
+ debug: args.debug,
2398
+ mode: "update"
2241
2399
  });
2242
2400
  if (state.outdated.length === 0) {
2243
2401
  p.log.success("All skills up to date");
@@ -2247,10 +2405,11 @@ const updateCommandDef = defineCommand({
2247
2405
  packages: state.outdated.map((s) => s.packageName || s.name),
2248
2406
  global: args.global,
2249
2407
  agent,
2250
- model: args.model,
2251
- yes: args.yes,
2408
+ model: args.model || (silent ? config.model : void 0),
2409
+ yes: args.yes || silent,
2252
2410
  force: args.force,
2253
- debug: args.debug
2411
+ debug: args.debug,
2412
+ mode: "update"
2254
2413
  });
2255
2414
  }
2256
2415
  });
@@ -2634,11 +2793,7 @@ async function enhanceRegenerated(pkgName, version, skillDir, model, sections, c
2634
2793
  sections,
2635
2794
  customPrompt,
2636
2795
  features,
2637
- onProgress: ({ type, chunk, section }) => {
2638
- const prefix = section ? `\x1B[90m[${section}]\x1B[0m ` : "";
2639
- if (type === "reasoning" && chunk.startsWith("[")) llmLog.message(`${prefix}${chunk}`);
2640
- else if (type === "text") llmLog.message(`${prefix}Writing...`);
2641
- }
2796
+ onProgress: createToolProgress(llmLog)
2642
2797
  });
2643
2798
  if (wasOptimized) {
2644
2799
  llmLog.success("Generated best practices");
@@ -3282,13 +3437,11 @@ async function countEmbeddings(packageName, version) {
3282
3437
  if (!existsSync(dbPath)) return null;
3283
3438
  try {
3284
3439
  const { DatabaseSync } = await import("node:sqlite");
3285
- const db = new DatabaseSync(dbPath, {
3440
+ using db = new DatabaseSync(dbPath, {
3286
3441
  open: true,
3287
3442
  readOnly: true
3288
3443
  });
3289
- const row = db.prepare("SELECT count(*) as cnt FROM vector_metadata").get();
3290
- db.close();
3291
- return row?.cnt ?? null;
3444
+ return db.prepare("SELECT count(*) as cnt FROM vector_metadata").get()?.cnt ?? null;
3292
3445
  } catch {
3293
3446
  return null;
3294
3447
  }
@@ -3363,14 +3516,12 @@ async function statusCommand(opts = {}) {
3363
3516
  const globalPkgs = /* @__PURE__ */ new Map();
3364
3517
  for (const skill of allSkills) {
3365
3518
  const key = skill.info?.packageName || skill.name;
3366
- const map = skill.scope === "local" ? localPkgs : globalPkgs;
3367
- if (!map.has(key)) map.set(key, {
3519
+ mapInsert(skill.scope === "local" ? localPkgs : globalPkgs, key, () => ({
3368
3520
  name: skill.name,
3369
3521
  info: skill.info || {},
3370
- agents: new Set([skill.agent]),
3522
+ agents: /* @__PURE__ */ new Set(),
3371
3523
  scope: skill.scope
3372
- });
3373
- else map.get(key).agents.add(skill.agent);
3524
+ })).agents.add(skill.agent);
3374
3525
  }
3375
3526
  const buildPackageLines = async (pkgs) => {
3376
3527
  const lines = [];
@@ -3551,8 +3702,8 @@ async function uninstallCommand(opts) {
3551
3702
  if (untrackedByDir.size > 0) {
3552
3703
  const groupedUntracked = /* @__PURE__ */ new Map();
3553
3704
  for (const [_dir, { label, skills }] of untrackedByDir) {
3554
- if (!groupedUntracked.has(label)) groupedUntracked.set(label, /* @__PURE__ */ new Set());
3555
- for (const s of skills) groupedUntracked.get(label).add(s);
3705
+ const set = mapInsert(groupedUntracked, label, () => /* @__PURE__ */ new Set());
3706
+ for (const s of skills) set.add(s);
3556
3707
  }
3557
3708
  const totalUntracked = [...groupedUntracked.values()].reduce((sum, s) => sum + s.size, 0);
3558
3709
  p.log.warn(`${totalUntracked} untracked skill(s) will remain (not managed by skilld):`);
@@ -3565,8 +3716,7 @@ async function uninstallCommand(opts) {
3565
3716
  const groups = /* @__PURE__ */ new Map();
3566
3717
  for (const item of toRemove) {
3567
3718
  const [prefix, name] = item.label.includes(": ") ? item.label.split(": ", 2) : ["other", item.label];
3568
- if (!groups.has(prefix)) groups.set(prefix, []);
3569
- groups.get(prefix).push({
3719
+ mapInsert(groups, prefix, () => []).push({
3570
3720
  name,
3571
3721
  version: item.version
3572
3722
  });
@@ -3609,98 +3759,6 @@ const uninstallCommandDef = defineCommand({
3609
3759
  });
3610
3760
  }
3611
3761
  });
3612
- function hasGhCli() {
3613
- if (process.env.SKILLD_NO_GH) return false;
3614
- try {
3615
- execSync("gh --version", { stdio: "ignore" });
3616
- return true;
3617
- } catch {
3618
- return false;
3619
- }
3620
- }
3621
- async function runWizard() {
3622
- if (!isInteractive()) return;
3623
- p.note("Skilld gives your AI agent skill knowledge on your NPM\ndependencies gathered from versioned docs, source code\nand GitHub issues.", "Welcome to skilld");
3624
- const ghInstalled = hasGhCli();
3625
- if (ghInstalled) p.log.success("GitHub CLI detected — will use it to pull issues and discussions.");
3626
- else p.log.warn("GitHub CLI not found. Install it to enable issues/discussions:\n \x1B[36mhttps://cli.github.com\x1B[0m");
3627
- const selected = await p.multiselect({
3628
- message: "Which features would you like to enable?",
3629
- options: [
3630
- {
3631
- label: "Semantic + token search",
3632
- value: "search",
3633
- hint: "local query engine to cut token costs and speed up grep"
3634
- },
3635
- {
3636
- label: "Release notes",
3637
- value: "releases",
3638
- hint: "track changelogs for installed packages"
3639
- },
3640
- {
3641
- label: "GitHub issues",
3642
- value: "issues",
3643
- hint: "surface common problems and solutions",
3644
- disabled: !ghInstalled
3645
- },
3646
- {
3647
- label: "GitHub discussions",
3648
- value: "discussions",
3649
- hint: "include Q&A and community knowledge",
3650
- disabled: !ghInstalled
3651
- }
3652
- ],
3653
- initialValues: [...Object.entries(defaultFeatures).filter(([, v]) => v).map(([k]) => k), ...ghInstalled ? ["issues", "discussions"] : []],
3654
- required: false
3655
- });
3656
- if (p.isCancel(selected)) {
3657
- p.cancel("Setup cancelled");
3658
- process.exit(0);
3659
- }
3660
- const features = {
3661
- search: selected.includes("search"),
3662
- issues: selected.includes("issues"),
3663
- discussions: selected.includes("discussions"),
3664
- releases: selected.includes("releases")
3665
- };
3666
- const allModels = process.env.SKILLD_NO_AGENTS ? [] : await getAvailableModels();
3667
- let modelId;
3668
- if (allModels.length > 0) {
3669
- p.note("Skills work without an LLM, but one can rewrite your\nSKILL.md files with best practices and better structure.\n\x1B[90mThis is separate from the agent where skills are installed —\nthe target agent is auto-detected from your project files.\x1B[0m", "Optional: LLM optimization");
3670
- const modelChoice = await p.select({
3671
- message: "Model for generating SKILL.md",
3672
- options: [{
3673
- label: "Skip",
3674
- value: "",
3675
- hint: "use raw docs, no LLM needed"
3676
- }, ...allModels.map((m) => ({
3677
- label: m.recommended ? `${m.name} (Recommended)` : m.name,
3678
- value: m.id,
3679
- hint: `${m.agentName} · ${m.hint}`
3680
- }))]
3681
- });
3682
- if (p.isCancel(modelChoice)) {
3683
- p.cancel("Setup cancelled");
3684
- process.exit(0);
3685
- }
3686
- modelId = modelChoice || void 0;
3687
- } else {
3688
- p.log.warn("No supported LLM CLIs detected (claude, gemini, codex).\n Skills will still work, but won't be LLM-optimized.");
3689
- const proceed = await p.confirm({
3690
- message: "Continue without LLM optimization?",
3691
- initialValue: true
3692
- });
3693
- if (p.isCancel(proceed) || !proceed) {
3694
- p.cancel("Setup cancelled");
3695
- process.exit(0);
3696
- }
3697
- }
3698
- updateConfig({
3699
- features,
3700
- ...modelId ? { model: modelId } : { skipLlm: true }
3701
- });
3702
- p.outro("Thanks, you're all set! Change config anytime with `skilld config`.");
3703
- }
3704
3762
  const _emit = process.emit;
3705
3763
  process.emit = (event, ...args) => event === "warning" && args[0]?.name === "ExperimentalWarning" && args[0]?.message?.includes("SQLite") ? false : _emit.apply(process, [event, ...args]);
3706
3764
  const NOISE_CHARS = "⣿⡿⣷⣾⣽⣻⢿⡷⣯⣟⡾⣵⣳⢾⡽⣞⡷⣝⢯";
@@ -3790,21 +3848,6 @@ async function brandLoader(work, minMs = 1500) {
3790
3848
  logUpdate.done();
3791
3849
  return result;
3792
3850
  }
3793
- async function prepareSync(cwd, agentFlag) {
3794
- const agent = resolveAgent(agentFlag);
3795
- if (!agent) return;
3796
- const state = await getProjectState(cwd);
3797
- if (state.outdated.length === 0) {
3798
- p.log.success("Skills up to date");
3799
- return;
3800
- }
3801
- await syncCommand(state, {
3802
- packages: state.outdated.map((s) => s.packageName || s.name),
3803
- global: false,
3804
- agent,
3805
- yes: true
3806
- });
3807
- }
3808
3851
  const SUBCOMMAND_NAMES = [
3809
3852
  "add",
3810
3853
  "update",
@@ -3823,20 +3866,7 @@ runMain(defineCommand({
3823
3866
  version,
3824
3867
  description: "Sync package documentation for agentic use"
3825
3868
  },
3826
- args: {
3827
- prepare: {
3828
- type: "boolean",
3829
- description: "Non-interactive sync for pnpm prepare hook (outdated only, no LLM, always exits 0)",
3830
- default: false
3831
- },
3832
- background: {
3833
- type: "boolean",
3834
- alias: "b",
3835
- description: "Run --prepare in background (detached process)",
3836
- default: false
3837
- },
3838
- agent: sharedArgs.agent
3839
- },
3869
+ args: { agent: sharedArgs.agent },
3840
3870
  subCommands: {
3841
3871
  add: () => Promise.resolve().then(() => sync_exports).then((m) => m.addCommandDef),
3842
3872
  update: () => Promise.resolve().then(() => sync_exports).then((m) => m.updateCommandDef),
@@ -3853,22 +3883,6 @@ runMain(defineCommand({
3853
3883
  const firstArg = process.argv[2];
3854
3884
  if (firstArg && !firstArg.startsWith("-") && SUBCOMMAND_NAMES.includes(firstArg)) return;
3855
3885
  const cwd = process.cwd();
3856
- if (args.prepare) {
3857
- if (args.background) {
3858
- spawn(process.execPath, [
3859
- process.argv[1],
3860
- "--prepare",
3861
- ...args.agent ? ["--agent", args.agent] : []
3862
- ], {
3863
- cwd,
3864
- detached: true,
3865
- stdio: "ignore"
3866
- }).unref();
3867
- return;
3868
- }
3869
- await prepareSync(cwd, args.agent).catch(() => {});
3870
- return;
3871
- }
3872
3886
  if (!isInteractive()) {
3873
3887
  const state = await getProjectState(cwd);
3874
3888
  const status = formatStatus(state.synced.length, state.outdated.length);