skilld 0.15.0 → 0.15.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/README.md CHANGED
@@ -59,33 +59,21 @@ If you need to re-configure skilld, just run `npx -y skilld config` to update yo
59
59
  - **LLM is optional** - Skills work without any LLM, but enhancing with one makes them significantly better.
60
60
  - **Multi-agent** - Run `skilld install --agent gemini-cli` to sync skills to another agent. The doc cache is shared.
61
61
 
62
- ## FAQ
62
+ ## Installation
63
63
 
64
- ### Why don't the skills run?
64
+ ### Global
65
65
 
66
- Try this in your project/user prompt:
66
+ Install globally to use `skilld` across all projects without `npx`:
67
67
 
68
- ```md
69
- Before modifying code, evaluate each installed skill against the current task.
70
- For each skill, determine YES/NO relevance and invoke all YES skills before proceeding.
68
+ ```bash
69
+ npm install -g skilld
70
+ # or
71
+ pnpm add -g skilld
71
72
  ```
72
73
 
73
- ### How is this different from Context7?
74
-
75
- Context7 is an MCP that fetches raw doc chunks at query time. You get different results each prompt, no curation, and it requires their server. Skilld is local-first: it generates a SKILL.md that lives in your project, tied to your actual package versions. No MCP dependency, no per-prompt latency, and it goes further with LLM-enhanced sections, prompt injection sanitization, and semantic search.
74
+ Then run `skilld` in any project directory.
76
75
 
77
- ### Will I be prompt injected?
78
-
79
- Skilld pulls issues from GitHub which could be abused for potential prompt injection.
80
-
81
- Skilld treats all data as untrusted, running in permissioned environments and using best practices to avoid injections.
82
- However, always be cautious when using skills from untrusted sources.
83
-
84
- ### Do skills update when my deps update?
85
-
86
- Yes. Run `skilld update` to regenerate outdated skills, or add `skilld update -b` to your prepare script and they regenerate in the background whenever you install packages.
87
-
88
- ## Installation
76
+ ### Per-Project
89
77
 
90
78
  If you'd like to install skilld and track the lock file references, add it as a dev dependency:
91
79
 
@@ -109,6 +97,32 @@ Add to `package.json` to keep skills fresh on install:
109
97
  }
110
98
  ```
111
99
 
100
+ ## FAQ
101
+
102
+ ### Why don't the skills run?
103
+
104
+ Try this in your project/user prompt:
105
+
106
+ ```md
107
+ Before modifying code, evaluate each installed skill against the current task.
108
+ For each skill, determine YES/NO relevance and invoke all YES skills before proceeding.
109
+ ```
110
+
111
+ ### How is this different from Context7?
112
+
113
+ Context7 is an MCP that fetches raw doc chunks at query time. You get different results each prompt, no curation, and it requires their server. Skilld is local-first: it generates a SKILL.md that lives in your project, tied to your actual package versions. No MCP dependency, no per-prompt latency, and it goes further with LLM-enhanced sections, prompt injection sanitization, and semantic search.
114
+
115
+ ### Will I be prompt injected?
116
+
117
+ Skilld pulls issues from GitHub which could be abused for potential prompt injection.
118
+
119
+ Skilld treats all data as untrusted, running in permissioned environments and using best practices to avoid injections.
120
+ However, always be cautious when using skills from untrusted sources.
121
+
122
+ ### Do skills update when my deps update?
123
+
124
+ Yes. Run `skilld update` to regenerate outdated skills, or add `skilld update -b` to your prepare script and they regenerate in the background whenever you install packages.
125
+
112
126
  ## CLI Usage
113
127
 
114
128
  ```bash
package/dist/cli.mjs CHANGED
@@ -1304,7 +1304,8 @@ async function fetchAndCacheResources(opts) {
1304
1304
  hasDiscussions: features.discussions && existsSync(discussionsDir),
1305
1305
  hasReleases: features.releases && existsSync(releasesPath),
1306
1306
  warnings,
1307
- repoInfo
1307
+ repoInfo,
1308
+ usedCache: useCache
1308
1309
  };
1309
1310
  }
1310
1311
  async function indexResources(opts) {
@@ -1527,7 +1528,7 @@ async function selectSkillSections(message = "Generate SKILL.md with LLM") {
1527
1528
  cancelled: false
1528
1529
  };
1529
1530
  }
1530
- async function selectLlmConfig(presetModel, message) {
1531
+ async function selectLlmConfig(presetModel, message, usedCache) {
1531
1532
  if (presetModel) return {
1532
1533
  model: presetModel,
1533
1534
  sections: DEFAULT_SECTIONS
@@ -1535,6 +1536,10 @@ async function selectLlmConfig(presetModel, message) {
1535
1536
  if (!isInteractive()) return null;
1536
1537
  const defaultModel = await selectModel(true);
1537
1538
  if (!defaultModel) return null;
1539
+ if (usedCache) return {
1540
+ model: defaultModel,
1541
+ sections: DEFAULT_SECTIONS
1542
+ };
1538
1543
  const defaultModelName = getModelName(defaultModel);
1539
1544
  const choice = await p.select({
1540
1545
  message: "Generate enhanced SKILL.md?",
@@ -1943,8 +1948,9 @@ async function syncPackagesParallel(config) {
1943
1948
  for (const [, data] of skillData) for (const w of data.warnings) p.log.warn(`\x1B[33m${w}\x1B[0m`);
1944
1949
  if (errors.length > 0) for (const { pkg, reason } of errors) p.log.error(` ${pkg}: ${reason}`);
1945
1950
  const globalConfig = readConfig();
1951
+ const allCached = successfulPkgs.every((pkg) => skillData.get(pkg)?.usedCache);
1946
1952
  if (successfulPkgs.length > 0 && !globalConfig.skipLlm && !(config.yes && !config.model)) {
1947
- const llmConfig = await selectLlmConfig(config.model);
1953
+ const llmConfig = await selectLlmConfig(config.model, void 0, allCached);
1948
1954
  if (llmConfig) {
1949
1955
  p.log.step(getModelLabel(llmConfig.model));
1950
1956
  for (const pkg of successfulPkgs) states.set(pkg, {
@@ -2089,7 +2095,8 @@ async function syncBaseSkill(packageSpec, config, cwd, update) {
2089
2095
  relatedSkills,
2090
2096
  packages: allPackages.length > 1 ? allPackages : void 0,
2091
2097
  warnings: resources.warnings,
2092
- features
2098
+ features,
2099
+ usedCache: resources.usedCache
2093
2100
  };
2094
2101
  }
2095
2102
  async function enhanceWithLLM(packageName, data, config, cwd, update, sections, customPrompt) {
@@ -2470,7 +2477,7 @@ async function syncSinglePackage(packageSpec, config) {
2470
2477
  if (resources.hasIssues) resParts.push("issues");
2471
2478
  if (resources.hasDiscussions) resParts.push("discussions");
2472
2479
  if (resources.hasReleases) resParts.push("releases");
2473
- resSpin.stop(`Fetched ${resParts.length > 0 ? resParts.join(", ") : "resources"}`);
2480
+ resSpin.stop(resources.usedCache ? `Loaded ${resParts.length > 0 ? resParts.join(", ") : "resources"} (cached)` : `Fetched ${resParts.length > 0 ? resParts.join(", ") : "resources"}`);
2474
2481
  for (const w of resources.warnings) p.log.warn(`\x1B[33m${w}\x1B[0m`);
2475
2482
  linkAllReferences(skillDir, packageName, cwd, version, resources.docsType, void 0, features, resources.repoInfo);
2476
2483
  if (features.search) {
@@ -2526,7 +2533,7 @@ async function syncSinglePackage(packageSpec, config) {
2526
2533
  writeFileSync(join(skillDir, "SKILL.md"), baseSkillMd);
2527
2534
  p.log.success(config.mode === "update" ? `Updated skill: ${relative(cwd, skillDir)}` : `Created base skill: ${relative(cwd, skillDir)}`);
2528
2535
  if (!readConfig().skipLlm && (!config.yes || config.model)) {
2529
- const llmConfig = await selectLlmConfig(config.model);
2536
+ const llmConfig = await selectLlmConfig(config.model, void 0, resources.usedCache);
2530
2537
  if (llmConfig) {
2531
2538
  p.log.step(getModelLabel(llmConfig.model));
2532
2539
  await enhanceSkillWithLLM({
@@ -3421,35 +3428,46 @@ const removeCommandDef = defineCommand({
3421
3428
  });
3422
3429
  var search_exports = /* @__PURE__ */ __exportAll({
3423
3430
  findPackageDbs: () => findPackageDbs,
3431
+ listLockPackages: () => listLockPackages,
3424
3432
  parseFilterPrefix: () => parseFilterPrefix,
3425
3433
  searchCommand: () => searchCommand,
3426
3434
  searchCommandDef: () => searchCommandDef
3427
3435
  });
3428
3436
  function findPackageDbs(packageFilter) {
3429
- const cwd = process.cwd();
3437
+ const lock = readProjectLock(process.cwd());
3438
+ if (!lock) return [];
3439
+ return filterLockDbs(lock, packageFilter);
3440
+ }
3441
+ function readProjectLock(cwd) {
3430
3442
  const shared = getSharedSkillsDir(cwd);
3431
3443
  if (shared) {
3432
3444
  const lock = readLock(shared);
3433
- if (lock) return filterLockDbs(lock, packageFilter);
3445
+ if (lock) return lock;
3434
3446
  }
3435
3447
  const agent = detectTargetAgent();
3436
- if (!agent) return [];
3437
- const lock = readLock(`${cwd}/${targets[agent].skillsDir}`);
3448
+ if (!agent) return null;
3449
+ return readLock(`${cwd}/${targets[agent].skillsDir}`);
3450
+ }
3451
+ function listLockPackages(cwd = process.cwd()) {
3452
+ const lock = readProjectLock(cwd);
3438
3453
  if (!lock) return [];
3439
- return filterLockDbs(lock, packageFilter);
3454
+ return [...new Set(Object.values(lock.skills).map((s) => s.packageName).filter(Boolean))];
3440
3455
  }
3441
3456
  function filterLockDbs(lock, packageFilter) {
3442
3457
  if (!lock) return [];
3443
- const normalize = (s) => s.toLowerCase().replace(/[-_@/]/g, "");
3458
+ const tokenize = (s) => s.toLowerCase().replace(/@/g, "").split(/[-_/]+/).filter(Boolean);
3444
3459
  return Object.values(lock.skills).filter((info) => {
3445
3460
  if (!info.packageName || !info.version) return false;
3446
3461
  if (!packageFilter) return true;
3447
- const f = normalize(packageFilter);
3448
- return normalize(info.packageName).includes(f) || normalize(info.packageName) === f;
3462
+ const filterTokens = tokenize(packageFilter);
3463
+ const nameTokens = tokenize(info.packageName);
3464
+ return filterTokens.every((ft) => nameTokens.some((nt) => nt.includes(ft) || ft.includes(nt)));
3449
3465
  }).map((info) => {
3450
3466
  const exact = getPackageDbPath(info.packageName, info.version);
3451
3467
  if (existsSync(exact)) return exact;
3452
- return findAnyPackageDb(info.packageName);
3468
+ const fallback = findAnyPackageDb(info.packageName);
3469
+ if (fallback) p.log.warn(`Using cached search index for ${info.packageName} (v${info.version} not indexed). Run \`skilld update ${info.packageName}\` to re-index.`);
3470
+ return fallback;
3453
3471
  }).filter((db) => !!db);
3454
3472
  }
3455
3473
  function findAnyPackageDb(name) {
@@ -3493,8 +3511,11 @@ function parseFilterPrefix(rawQuery) {
3493
3511
  async function searchCommand(rawQuery, packageFilter) {
3494
3512
  const dbs = findPackageDbs(packageFilter);
3495
3513
  if (dbs.length === 0) {
3496
- if (packageFilter) p.log.warn(`No docs indexed for "${packageFilter}". Run \`skilld add ${packageFilter}\` first.`);
3497
- else p.log.warn("No docs indexed yet. Run `skilld add <package>` first.");
3514
+ if (packageFilter) {
3515
+ const available = listLockPackages();
3516
+ if (available.length > 0) p.log.warn(`No docs indexed for "${packageFilter}". Available: ${available.join(", ")}`);
3517
+ else p.log.warn(`No docs indexed for "${packageFilter}". Run \`skilld add ${packageFilter}\` first.`);
3518
+ } else p.log.warn("No docs indexed yet. Run `skilld add <package>` first.");
3498
3519
  return;
3499
3520
  }
3500
3521
  const { query, filter } = parseFilterPrefix(rawQuery);
@@ -3570,7 +3591,11 @@ const SPINNER_FRAMES = [
3570
3591
  async function interactiveSearch(packageFilter) {
3571
3592
  const dbs = findPackageDbs(packageFilter);
3572
3593
  if (dbs.length === 0) {
3573
- const msg = packageFilter ? `No docs indexed for "${packageFilter}". Run \`skilld add ${packageFilter}\` first.` : "No docs indexed yet. Run `skilld add <package>` first.";
3594
+ let msg;
3595
+ if (packageFilter) {
3596
+ const available = listLockPackages();
3597
+ msg = available.length > 0 ? `No docs indexed for "${packageFilter}". Available: ${available.join(", ")}` : `No docs indexed for "${packageFilter}". Run \`skilld add ${packageFilter}\` first.`;
3598
+ } else msg = "No docs indexed yet. Run `skilld add <package>` first.";
3574
3599
  process.stderr.write(`\x1B[33m${msg}\x1B[0m\n`);
3575
3600
  return;
3576
3601
  }