library-skills 0.0.1 → 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Sebastián Ramírez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md CHANGED
@@ -1 +1,74 @@
1
- # Library Agent Skills
1
+ <p align="center">
2
+ <a href="https://library-skills.io"><img src="https://library-skills.io/img/logo-margin/logo-margin-vector.svg#only-light" alt="Library Skills"></a>
3
+
4
+ </p>
5
+ <p align="center">
6
+ <em>Library Skills, AI Agents using libraries, as intended, always up to date.</em>
7
+ </p>
8
+ <p align="center">
9
+ <a href="https://github.com/tiangolo/library-skills/actions?query=workflow%3ATest+event%3Apush+branch%3Amain">
10
+ <img src="https://github.com/tiangolo/library-skills/actions/workflows/test.yml/badge.svg?event=push&branch=main" alt="Test">
11
+ </a>
12
+ <a href="https://github.com/tiangolo/library-skills/actions?query=workflow%3APublish">
13
+ <img src="https://github.com/tiangolo/library-skills/actions/workflows/publish.yml/badge.svg" alt="Publish">
14
+ </a>
15
+ <a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/tiangolo/library-skills">
16
+ <img src="https://coverage-badge.samuelcolvin.workers.dev/tiangolo/library-skills.svg" alt="Coverage">
17
+ </a>
18
+ <a href="https://pypi.org/project/library-skills">
19
+ <img src="https://img.shields.io/pypi/v/library-skills?color=%2334D058&label=pypi%20package" alt="Python package version">
20
+ </a>
21
+ <a href="https://www.npmjs.com/package/library-skills">
22
+ <img src="https://img.shields.io/npm/v/library-skills?color=%2334D058&label=npm%20package" alt="npm package version">
23
+ </a>
24
+ </p>
25
+
26
+ ---
27
+
28
+ **Documentation**: [https://library-skills.io](https://library-skills.io)
29
+
30
+ **Source Code**: [https://github.com/tiangolo/library-skills](https://github.com/tiangolo/library-skills)
31
+
32
+ ---
33
+
34
+ Let your AI agents use libraries as intended, **always up to date**.
35
+
36
+ Supporting libraries (e.g. [FastAPI](https://fastapi.tiangolo.com), [Streamlit](https://streamlit.io)) include their own AI skills ([https://agentskills.io](https://agentskills.io)) embedded, updated in sync with each new version of the library.
37
+
38
+ In Python, you can install them with:
39
+
40
+ ```console
41
+ $ uvx library-skills
42
+ ```
43
+
44
+ In JavaScript/TypeScript, you can install them with:
45
+
46
+ ```console
47
+ $ npx library-skills
48
+ ```
49
+
50
+ This will scan the dependencies for the current project, find the installed libraries, and ask you which of their skills you want to install in the project.
51
+
52
+ Then it will add them to the `.agents` directory as symbolic links, so when you update the libraries, the skills are updated too.
53
+
54
+ /// tip
55
+
56
+ If you are using Claude Code, add the `--claude` CLI Option to install the skills in the `.claude/skills` directory too, as Claude Code doesn't support the standard `.agents` directory.
57
+
58
+ ///
59
+
60
+ ## Why Library Skills
61
+
62
+ <abbr title="Large Language Models">LLMs</abbr> are great at helping you code, but are trained on data that existed until a certain point in time, which in the end, is always **old data**.
63
+
64
+ Additionally, they are trained on a lot of code examples, that in many cases use **old patterns**.
65
+
66
+ When there are **new features** or changes in the libraries, agents normally don't know about them, don't know how to use them, and insist on using old, deprecated, and sometimes hallucinated patterns.
67
+
68
+ But library authors can help them, providing **official library *skills*** that are always up to date, included in each new version of the package, in sync with the version of the library installed.
69
+
70
+ And you can install and use these official **Library Skills for Agents** with one command.
71
+
72
+ ## License
73
+
74
+ This project is licensed under the terms of the [MIT license](https://github.com/tiangolo/library-skills/blob/main/LICENSE).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "library-skills",
3
- "version": "0.0.1",
4
- "description": "Library Agent Skills",
3
+ "version": "0.0.2",
4
+ "description": "Library Skills, AI Agents using libraries, as intended, always up to date.",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
@@ -19,22 +19,38 @@
19
19
  "module": "./ts/dist/index.js",
20
20
  "types": "./ts/dist/index.d.ts",
21
21
  "bin": {
22
- "library-skills": "./ts/dist/cli.js"
22
+ "library-skills": "ts/dist/cli.js"
23
23
  },
24
24
  "files": [
25
25
  "ts/dist"
26
26
  ],
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "dev": "tsup --watch",
30
+ "prepublishOnly": "npm run build",
31
+ "test:ts": "vitest run --coverage",
32
+ "typecheck": "tsc --noEmit"
33
+ },
27
34
  "author": {
28
35
  "name": "Sebastián Ramírez",
29
36
  "email": "tiangolo@gmail.com"
30
37
  },
31
38
  "license": "MIT",
32
39
  "devDependencies": {
40
+ "@types/node": "^25.6.0",
41
+ "@vitest/coverage-v8": "^4.1.5",
33
42
  "tsup": "^8.4.0",
34
- "typescript": "^5.7.0"
43
+ "typescript": "^6.0.3",
44
+ "vitest": "^4.1.5"
35
45
  },
36
- "scripts": {
37
- "build": "tsup",
38
- "dev": "tsup --watch"
46
+ "dependencies": {
47
+ "@inquirer/checkbox": "^5.1.4",
48
+ "commander": "^14.0.3",
49
+ "csv-parse": "^6.2.1",
50
+ "smol-toml": "^1.6.1"
51
+ },
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/tiangolo/library-skills.git"
39
55
  }
40
- }
56
+ }
@@ -0,0 +1,462 @@
1
+ // ts/src/scanner.ts
2
+ import {
3
+ existsSync,
4
+ lstatSync,
5
+ readFileSync,
6
+ readdirSync,
7
+ realpathSync,
8
+ statSync
9
+ } from "fs";
10
+ import {
11
+ basename,
12
+ dirname,
13
+ isAbsolute,
14
+ join,
15
+ relative,
16
+ resolve
17
+ } from "path";
18
+ import { fileURLToPath } from "url";
19
+ import { parse } from "csv-parse/sync";
20
+ var SKILL_NAME_RE = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
21
+ function scanPythonDistributions(sitePackages) {
22
+ const result = { skills: [], warnings: [] };
23
+ const seenSkillDirs = /* @__PURE__ */ new Set();
24
+ if (!isDirectory(sitePackages)) {
25
+ result.warnings.push(`Site-packages directory not found: ${sitePackages}`);
26
+ return result;
27
+ }
28
+ for (const distInfo of readdirSync(sitePackages).filter((entry) => entry.endsWith(".dist-info")).sort().map((entry) => join(sitePackages, entry))) {
29
+ if (!isDirectory(distInfo)) {
30
+ continue;
31
+ }
32
+ const dist = readDistributionInfo(distInfo);
33
+ if (dist === null) {
34
+ result.warnings.push(
35
+ `Skipping invalid distribution metadata: ${distInfo}`
36
+ );
37
+ continue;
38
+ }
39
+ const found = scanDistributionRecords({
40
+ sitePackages,
41
+ distInfo,
42
+ packageName: dist.name,
43
+ packageVersion: dist.version,
44
+ seenSkillDirs
45
+ });
46
+ result.skills.push(...found.skills);
47
+ result.warnings.push(...found.warnings);
48
+ if (found.skills.length === 0) {
49
+ const fallback = scanEditableDirectUrl({
50
+ distInfo,
51
+ packageName: dist.name,
52
+ packageVersion: dist.version,
53
+ seenSkillDirs
54
+ });
55
+ result.skills.push(...fallback.skills);
56
+ result.warnings.push(...fallback.warnings);
57
+ }
58
+ }
59
+ return result;
60
+ }
61
+ function scanNodePackages(nodeModules) {
62
+ const result = { skills: [], warnings: [] };
63
+ const seenSkillDirs = /* @__PURE__ */ new Set();
64
+ if (!isDirectory(nodeModules)) {
65
+ result.warnings.push(`node_modules directory not found: ${nodeModules}`);
66
+ return result;
67
+ }
68
+ for (const packageRoot of iterNodePackageRoots(nodeModules)) {
69
+ const packageInfo = readNodePackageInfo(packageRoot);
70
+ if (packageInfo === null) {
71
+ result.warnings.push(`Skipping invalid package metadata: ${packageRoot}`);
72
+ continue;
73
+ }
74
+ for (const skillMd of findNodeSkillMarkdownFiles(packageRoot)) {
75
+ const skillDir = realpathOrResolve(dirname(skillMd));
76
+ if (seenSkillDirs.has(skillDir)) {
77
+ continue;
78
+ }
79
+ seenSkillDirs.add(skillDir);
80
+ const [skill, warning] = loadSkill({
81
+ skillDir,
82
+ skillMd: realpathOrResolve(skillMd),
83
+ packageName: packageInfo.name,
84
+ packageVersion: packageInfo.version
85
+ });
86
+ if (skill) {
87
+ result.skills.push(skill);
88
+ } else if (warning) {
89
+ result.warnings.push(warning);
90
+ }
91
+ }
92
+ }
93
+ return result;
94
+ }
95
+ function iterNodePackageRoots(nodeModules) {
96
+ const packageRoots = [];
97
+ for (const entry of readdirSync(nodeModules).sort()) {
98
+ const fullPath = join(nodeModules, entry);
99
+ if (!isDirectory(fullPath)) {
100
+ continue;
101
+ }
102
+ if (entry.startsWith("@")) {
103
+ for (const scopedEntry of readdirSync(fullPath).sort()) {
104
+ const scopedPath = join(fullPath, scopedEntry);
105
+ if (isDirectory(scopedPath)) {
106
+ packageRoots.push(scopedPath);
107
+ }
108
+ }
109
+ } else {
110
+ packageRoots.push(fullPath);
111
+ }
112
+ }
113
+ return packageRoots;
114
+ }
115
+ function readNodePackageInfo(packageRoot) {
116
+ let data;
117
+ try {
118
+ data = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
119
+ } catch {
120
+ return null;
121
+ }
122
+ if (!isRecord(data)) {
123
+ return null;
124
+ }
125
+ const name = data["name"];
126
+ if (typeof name !== "string" || name === "") {
127
+ return null;
128
+ }
129
+ const version = data["version"];
130
+ return { name, version: typeof version === "string" ? version : "" };
131
+ }
132
+ function findNodeSkillMarkdownFiles(packageRoot) {
133
+ const skillsRoot = join(packageRoot, ".agents", "skills");
134
+ if (!isDirectory(skillsRoot)) {
135
+ return [];
136
+ }
137
+ return readdirSync(skillsRoot).sort().map((entry) => join(skillsRoot, entry, "SKILL.md")).filter((skillMd) => existsSync(skillMd));
138
+ }
139
+ function readDistributionInfo(distInfo) {
140
+ let metadataText;
141
+ try {
142
+ metadataText = readFileSync(join(distInfo, "METADATA"), "utf8");
143
+ } catch {
144
+ return null;
145
+ }
146
+ const headers = parseMetadataHeaders(metadataText);
147
+ const name = headers.get("name");
148
+ if (!name) {
149
+ return null;
150
+ }
151
+ return { name, version: headers.get("version") ?? "" };
152
+ }
153
+ function parseMetadataHeaders(text) {
154
+ const headers = /* @__PURE__ */ new Map();
155
+ let currentKey;
156
+ for (const line of text.split(/\r?\n/)) {
157
+ if (line === "") {
158
+ break;
159
+ }
160
+ if (/^\s/.test(line) && currentKey) {
161
+ headers.set(
162
+ currentKey,
163
+ `${headers.get(currentKey) ?? ""} ${line.trim()}`
164
+ );
165
+ continue;
166
+ }
167
+ const index = line.indexOf(":");
168
+ if (index === -1) {
169
+ continue;
170
+ }
171
+ currentKey = line.slice(0, index).trim().toLowerCase();
172
+ headers.set(currentKey, line.slice(index + 1).trim());
173
+ }
174
+ return headers;
175
+ }
176
+ function scanDistributionRecords({
177
+ sitePackages,
178
+ distInfo,
179
+ packageName,
180
+ packageVersion,
181
+ seenSkillDirs
182
+ }) {
183
+ const result = { skills: [], warnings: [] };
184
+ let recordText;
185
+ try {
186
+ recordText = readFileSync(join(distInfo, "RECORD"), "utf8");
187
+ } catch {
188
+ return result;
189
+ }
190
+ const rows = parse(recordText, {
191
+ relaxColumnCount: true,
192
+ skipEmptyLines: true
193
+ });
194
+ for (const row of rows) {
195
+ const installedPath = row[0];
196
+ if (!isSkillFileRecord(installedPath)) {
197
+ continue;
198
+ }
199
+ const skillMd = resolve(sitePackages, installedPath);
200
+ const skillDir = dirname(skillMd);
201
+ const resolvedSkillDir = realpathOrResolve(skillDir);
202
+ if (seenSkillDirs.has(resolvedSkillDir)) {
203
+ continue;
204
+ }
205
+ seenSkillDirs.add(resolvedSkillDir);
206
+ const [skill, warning] = loadSkill({
207
+ skillDir,
208
+ skillMd,
209
+ packageName,
210
+ packageVersion
211
+ });
212
+ if (skill) {
213
+ result.skills.push(skill);
214
+ } else if (warning) {
215
+ result.warnings.push(warning);
216
+ }
217
+ }
218
+ return result;
219
+ }
220
+ function isSkillFileRecord(installedPath) {
221
+ const parts = installedPath.split("/").filter((part) => part.length > 0);
222
+ for (const [index, part] of parts.entries()) {
223
+ if (part !== ".agents") {
224
+ continue;
225
+ }
226
+ if (parts.length > index + 3 && parts[index + 1] === "skills" && parts.at(-1) === "SKILL.md") {
227
+ return true;
228
+ }
229
+ }
230
+ return false;
231
+ }
232
+ function scanEditableDirectUrl({
233
+ distInfo,
234
+ packageName,
235
+ packageVersion,
236
+ seenSkillDirs
237
+ }) {
238
+ const result = { skills: [], warnings: [] };
239
+ const sourceRoot = readEditableSourceRoot(distInfo);
240
+ if (sourceRoot === null) {
241
+ return result;
242
+ }
243
+ for (const skillMd of findSkillMarkdownFiles(sourceRoot)) {
244
+ const resolvedSkillMd = realpathOrResolve(skillMd);
245
+ if (!isRelativeTo(resolvedSkillMd, sourceRoot)) {
246
+ continue;
247
+ }
248
+ const skillDir = dirname(resolvedSkillMd);
249
+ if (seenSkillDirs.has(skillDir)) {
250
+ continue;
251
+ }
252
+ seenSkillDirs.add(skillDir);
253
+ const [skill, warning] = loadSkill({
254
+ skillDir,
255
+ skillMd: resolvedSkillMd,
256
+ packageName,
257
+ packageVersion
258
+ });
259
+ if (skill) {
260
+ result.skills.push(skill);
261
+ } else if (warning) {
262
+ result.warnings.push(warning);
263
+ }
264
+ }
265
+ return result;
266
+ }
267
+ function readEditableSourceRoot(distInfo) {
268
+ let data;
269
+ try {
270
+ data = JSON.parse(readFileSync(join(distInfo, "direct_url.json"), "utf8"));
271
+ } catch {
272
+ return null;
273
+ }
274
+ if (!isRecord(data)) {
275
+ return null;
276
+ }
277
+ const dirInfo = data["dir_info"];
278
+ if (!isRecord(dirInfo) || dirInfo["editable"] !== true) {
279
+ return null;
280
+ }
281
+ const url = data["url"];
282
+ if (typeof url !== "string") {
283
+ return null;
284
+ }
285
+ let sourceRoot;
286
+ try {
287
+ sourceRoot = fileURLToPath(url);
288
+ } catch {
289
+ return null;
290
+ }
291
+ return isDirectory(sourceRoot) ? realpathOrResolve(sourceRoot) : null;
292
+ }
293
+ function findSkillMarkdownFiles(root) {
294
+ const found = [];
295
+ function walk(directory) {
296
+ let entries;
297
+ try {
298
+ entries = readdirSync(directory).sort();
299
+ } catch {
300
+ return;
301
+ }
302
+ for (const entry of entries) {
303
+ const fullPath = join(directory, entry);
304
+ let stat;
305
+ try {
306
+ stat = lstatSync(fullPath);
307
+ } catch {
308
+ continue;
309
+ }
310
+ if (stat.isSymbolicLink()) {
311
+ if (entry === "SKILL.md" && isSkillMarkdownPath(root, fullPath)) {
312
+ found.push(fullPath);
313
+ }
314
+ continue;
315
+ }
316
+ if (stat.isDirectory()) {
317
+ walk(fullPath);
318
+ } else if (entry === "SKILL.md" && isSkillMarkdownPath(root, fullPath)) {
319
+ found.push(fullPath);
320
+ }
321
+ }
322
+ }
323
+ walk(root);
324
+ return found;
325
+ }
326
+ function loadSkill({
327
+ skillDir,
328
+ skillMd,
329
+ packageName,
330
+ packageVersion
331
+ }) {
332
+ const [metadata, warning] = parseSkillFrontmatter(skillMd);
333
+ if (warning) {
334
+ return [null, `${skillMd}: ${warning}`];
335
+ }
336
+ const name = metadata["name"] ?? "";
337
+ const description = metadata["description"] ?? "";
338
+ const validationError = validateSkillMetadata({
339
+ name,
340
+ description,
341
+ parentDirName: basename(skillDir)
342
+ });
343
+ if (validationError) {
344
+ return [null, `${skillMd}: ${validationError}`];
345
+ }
346
+ return [
347
+ {
348
+ name,
349
+ description,
350
+ path: skillMd,
351
+ packageName,
352
+ packageVersion,
353
+ skillDir
354
+ },
355
+ null
356
+ ];
357
+ }
358
+ function parseSkillFrontmatter(skillMd) {
359
+ let text;
360
+ try {
361
+ text = readFileSync(skillMd, "utf8");
362
+ } catch (error) {
363
+ return [{}, `could not read SKILL.md (${String(error)})`];
364
+ }
365
+ if (!text.startsWith("---")) {
366
+ return [{}, "missing YAML frontmatter"];
367
+ }
368
+ const end = text.indexOf("\n---", 3);
369
+ if (end === -1) {
370
+ return [{}, "unterminated YAML frontmatter"];
371
+ }
372
+ const metadata = {};
373
+ for (const line of text.slice(3, end).split(/\r?\n/)) {
374
+ const stripped = line.trim();
375
+ if (stripped === "" || stripped.startsWith("#")) {
376
+ continue;
377
+ }
378
+ const separator = stripped.indexOf(":");
379
+ if (separator === -1) {
380
+ continue;
381
+ }
382
+ const key = stripped.slice(0, separator).trim();
383
+ if (key === "name" || key === "description") {
384
+ metadata[key] = stripped.slice(separator + 1).trim().replace(/^["']|["']$/g, "");
385
+ }
386
+ }
387
+ return [metadata, null];
388
+ }
389
+ function validateSkillMetadata({
390
+ name,
391
+ description,
392
+ parentDirName
393
+ }) {
394
+ if (!name) {
395
+ return "missing required 'name' field";
396
+ }
397
+ if (!SKILL_NAME_RE.test(name) || name.includes("--")) {
398
+ return "invalid 'name' field; use lowercase letters, numbers, and hyphens only";
399
+ }
400
+ if (name !== parentDirName) {
401
+ return `'name' field must match parent directory name (${parentDirName})`;
402
+ }
403
+ if (!description) {
404
+ return "missing required 'description' field";
405
+ }
406
+ if (description.length > 1024) {
407
+ return "'description' field must be at most 1024 characters";
408
+ }
409
+ return null;
410
+ }
411
+ function normalizePackageName(name) {
412
+ return name.replace(/[-_.]+/g, "-").toLowerCase();
413
+ }
414
+ function isDirectory(path) {
415
+ try {
416
+ return statSync(path).isDirectory();
417
+ } catch {
418
+ return false;
419
+ }
420
+ }
421
+ function isRelativeTo(path, parent) {
422
+ const childPath = resolve(path);
423
+ const parentPath = resolve(parent);
424
+ const childRelative = relative(parentPath, childPath);
425
+ return childRelative === "" || !childRelative.startsWith("..") && !isAbsolute(childRelative);
426
+ }
427
+ function isSkillMarkdownPath(root, skillMd) {
428
+ const relativePath = relative(root, skillMd);
429
+ if (relativePath === "" || relativePath.startsWith("..")) {
430
+ return false;
431
+ }
432
+ const parts = relativePath.split(/[\\/]+/);
433
+ return isSkillFileRecord(parts.join("/"));
434
+ }
435
+ function realpathOrResolve(path) {
436
+ try {
437
+ return realpathSync(path);
438
+ } catch {
439
+ return resolve(path);
440
+ }
441
+ }
442
+ function isRecord(value) {
443
+ return typeof value === "object" && value !== null && !Array.isArray(value);
444
+ }
445
+ var testing = {
446
+ findSkillMarkdownFiles,
447
+ findNodeSkillMarkdownFiles,
448
+ iterNodePackageRoots,
449
+ isSkillFileRecord,
450
+ isSkillMarkdownPath,
451
+ realpathOrResolve,
452
+ readNodePackageInfo,
453
+ readEditableSourceRoot,
454
+ scanEditableDirectUrl
455
+ };
456
+
457
+ export {
458
+ scanPythonDistributions,
459
+ scanNodePackages,
460
+ normalizePackageName,
461
+ testing
462
+ };
package/ts/dist/cli.d.ts CHANGED
@@ -1 +1,94 @@
1
1
  #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+
4
+ interface Skill {
5
+ name: string;
6
+ description: string;
7
+ path: string;
8
+ packageName: string;
9
+ skillDir: string;
10
+ packageVersion: string;
11
+ }
12
+
13
+ interface InstallTarget {
14
+ name: string;
15
+ path: string;
16
+ }
17
+
18
+ interface ProjectContext {
19
+ cwd: string;
20
+ projectRoot: string;
21
+ targetEnvironment: string | null;
22
+ sitePackagesDir: string | null;
23
+ nodeModulesDir: string | null;
24
+ }
25
+ interface InstalledStatus {
26
+ target: InstallTarget;
27
+ name: string;
28
+ type: "symlink" | "directory" | "missing";
29
+ path: string;
30
+ targetPath: string | null;
31
+ status: "up to date" | "broken" | "outdated" | "orphaned" | "name mismatch" | "hand-authored" | "new";
32
+ skill: Skill | null;
33
+ }
34
+ interface GlobalOptions {
35
+ claude?: boolean;
36
+ yes?: boolean;
37
+ check?: boolean;
38
+ all?: boolean;
39
+ skill?: string[];
40
+ }
41
+ interface ListOptions {
42
+ installed?: boolean;
43
+ json?: boolean;
44
+ claude?: boolean;
45
+ all?: boolean;
46
+ }
47
+ interface ScanOptions {
48
+ all?: boolean;
49
+ json?: boolean;
50
+ }
51
+ declare function getProjectContext(cwd?: string): ProjectContext;
52
+ declare function topLevelSkills({ context, skills, includeAll, }: {
53
+ context: ProjectContext;
54
+ skills: Skill[];
55
+ includeAll: boolean;
56
+ }): Skill[];
57
+ declare function displayPath(path: string | null | undefined, projectRoot: string): string;
58
+ declare function printTable(columns: string[], rows: Array<Record<string, string>>): void;
59
+ declare function findCollisions(skills: Skill[]): Set<string>;
60
+ declare function filterInstallableSkills({ skills, selectedNames, includeAll, }: {
61
+ skills: Skill[];
62
+ selectedNames: string[];
63
+ includeAll: boolean;
64
+ }): Skill[];
65
+ declare function installedStatuses({ targets, skills, }: {
66
+ targets: InstallTarget[];
67
+ skills: Skill[];
68
+ }): InstalledStatus[];
69
+ declare function installSelected({ skills, targets, projectRoot, copy, }: {
70
+ skills: Skill[];
71
+ targets: InstallTarget[];
72
+ projectRoot: string;
73
+ copy?: boolean;
74
+ }): number;
75
+ declare function sync(options: GlobalOptions): Promise<void>;
76
+ declare function scanCommand(options: ScanOptions): void;
77
+ declare function listCommand(options: ListOptions): void;
78
+ declare function createProgram(): Command;
79
+ declare function main(argv?: string[]): Promise<void>;
80
+ declare const testing: {
81
+ filterInstallableSkills: typeof filterInstallableSkills;
82
+ findCollisions: typeof findCollisions;
83
+ getProjectContext: typeof getProjectContext;
84
+ installSelected: typeof installSelected;
85
+ installedStatuses: typeof installedStatuses;
86
+ listCommand: typeof listCommand;
87
+ scanCommand: typeof scanCommand;
88
+ sync: typeof sync;
89
+ topLevelSkills: typeof topLevelSkills;
90
+ displayPath: typeof displayPath;
91
+ printTable: typeof printTable;
92
+ };
93
+
94
+ export { createProgram, main, testing };