agent-skill-manager 1.1.0 → 1.3.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.
Files changed (41) hide show
  1. package/README.md +130 -34
  2. package/dist/agent-skill-manager.js +180 -0
  3. package/dist/highlights-eq9cgrbb.scm +604 -0
  4. package/dist/highlights-ghv9g403.scm +205 -0
  5. package/dist/highlights-hk7bwhj4.scm +284 -0
  6. package/dist/highlights-r812a2qc.scm +150 -0
  7. package/dist/highlights-x6tmsnaa.scm +115 -0
  8. package/dist/injections-73j83es3.scm +27 -0
  9. package/dist/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  10. package/dist/tree-sitter-markdown-411r6y9b.wasm +0 -0
  11. package/dist/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  12. package/dist/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  13. package/dist/tree-sitter-zig-e78zbjpm.wasm +0 -0
  14. package/package.json +11 -4
  15. package/CODE_OF_CONDUCT.md +0 -59
  16. package/CONTRIBUTING.md +0 -92
  17. package/RELEASE_NOTES.md +0 -22
  18. package/SECURITY.md +0 -43
  19. package/bin/agent-skill-manager.ts +0 -12
  20. package/bun.lock +0 -204
  21. package/docs/ARCHITECTURE.md +0 -60
  22. package/docs/CHANGELOG.md +0 -46
  23. package/docs/DEPLOYMENT.md +0 -52
  24. package/docs/DEVELOPMENT.md +0 -64
  25. package/install.sh +0 -226
  26. package/src/cli.ts +0 -488
  27. package/src/config.ts +0 -109
  28. package/src/formatter.ts +0 -92
  29. package/src/index.ts +0 -324
  30. package/src/scanner.ts +0 -165
  31. package/src/uninstaller.ts +0 -226
  32. package/src/utils/colors.ts +0 -16
  33. package/src/utils/frontmatter.ts +0 -87
  34. package/src/utils/types.ts +0 -57
  35. package/src/utils/version.ts +0 -20
  36. package/src/views/config.ts +0 -147
  37. package/src/views/confirm.ts +0 -105
  38. package/src/views/dashboard.ts +0 -252
  39. package/src/views/help.ts +0 -83
  40. package/src/views/skill-detail.ts +0 -114
  41. package/src/views/skill-list.ts +0 -122
package/src/cli.ts DELETED
@@ -1,488 +0,0 @@
1
- import {
2
- loadConfig,
3
- getConfigPath,
4
- getDefaultConfig,
5
- saveConfig,
6
- } from "./config";
7
- import { scanAllSkills, searchSkills, sortSkills } from "./scanner";
8
- import {
9
- buildFullRemovalPlan,
10
- executeRemoval,
11
- getExistingTargets,
12
- } from "./uninstaller";
13
- import {
14
- formatSkillTable,
15
- formatSkillDetail,
16
- formatJSON,
17
- ansi,
18
- } from "./formatter";
19
- import { VERSION_STRING } from "./utils/version";
20
- import type { Scope, SortBy } from "./utils/types";
21
-
22
- // ─── Arg Parser ─────────────────────────────────────────────────────────────
23
-
24
- interface ParsedArgs {
25
- command: string | null;
26
- subcommand: string | null;
27
- positional: string[];
28
- flags: {
29
- help: boolean;
30
- version: boolean;
31
- json: boolean;
32
- yes: boolean;
33
- noColor: boolean;
34
- scope: Scope;
35
- sort: SortBy;
36
- };
37
- }
38
-
39
- export function parseArgs(argv: string[]): ParsedArgs {
40
- const args = argv.slice(2); // skip bun and script path
41
-
42
- const result: ParsedArgs = {
43
- command: null,
44
- subcommand: null,
45
- positional: [],
46
- flags: {
47
- help: false,
48
- version: false,
49
- json: false,
50
- yes: false,
51
- noColor: false,
52
- scope: "both",
53
- sort: "name",
54
- },
55
- };
56
-
57
- let i = 0;
58
- while (i < args.length) {
59
- const arg = args[i];
60
-
61
- // Flags
62
- if (arg === "--help" || arg === "-h") {
63
- result.flags.help = true;
64
- } else if (arg === "--version" || arg === "-v") {
65
- result.flags.version = true;
66
- } else if (arg === "--json") {
67
- result.flags.json = true;
68
- } else if (arg === "--yes" || arg === "-y") {
69
- result.flags.yes = true;
70
- } else if (arg === "--no-color") {
71
- result.flags.noColor = true;
72
- } else if (arg === "--scope" || arg === "-s") {
73
- i++;
74
- const val = args[i];
75
- if (val === "global" || val === "project" || val === "both") {
76
- result.flags.scope = val;
77
- } else {
78
- error(`Invalid scope: "${val}". Must be global, project, or both.`);
79
- process.exit(2);
80
- }
81
- } else if (arg === "--sort") {
82
- i++;
83
- const val = args[i];
84
- if (val === "name" || val === "version" || val === "location") {
85
- result.flags.sort = val;
86
- } else {
87
- error(`Invalid sort: "${val}". Must be name, version, or location.`);
88
- process.exit(2);
89
- }
90
- } else if (arg.startsWith("-")) {
91
- error(`Unknown option: ${arg}`);
92
- console.error(`Run "asm --help" for usage.`);
93
- process.exit(2);
94
- } else {
95
- // Positional: first is command, second is subcommand, rest are positional args
96
- if (!result.command) {
97
- result.command = arg;
98
- } else if (!result.subcommand) {
99
- result.subcommand = arg;
100
- } else {
101
- result.positional.push(arg);
102
- }
103
- }
104
-
105
- i++;
106
- }
107
-
108
- return result;
109
- }
110
-
111
- // ─── Output helpers ─────────────────────────────────────────────────────────
112
-
113
- function error(msg: string) {
114
- console.error(ansi.red(`Error: ${msg}`));
115
- }
116
-
117
- // ─── Help text ──────────────────────────────────────────────────────────────
118
-
119
- function printMainHelp() {
120
- console.log(`${ansi.blueBold("agent-skill-manager")} (${ansi.bold("asm")}) ${VERSION_STRING}
121
-
122
- Interactive TUI and CLI for managing installed skills for AI coding agents.
123
-
124
- ${ansi.bold("Usage:")}
125
- asm Launch interactive TUI
126
- asm <command> [options] Run a CLI command
127
-
128
- ${ansi.bold("Commands:")}
129
- list List all discovered skills
130
- search <query> Search skills by name/description/provider
131
- inspect <skill-name> Show detailed info for a skill
132
- uninstall <skill-name> Remove a skill (with confirmation)
133
- config show Print current config
134
- config path Print config file path
135
- config reset Reset config to defaults
136
- config edit Open config in $EDITOR
137
-
138
- ${ansi.bold("Global Options:")}
139
- -h, --help Show help for any command
140
- -v, --version Print version and exit
141
- --json Output as JSON (list, search, inspect)
142
- -s, --scope <scope> Filter: global, project, or both (default: both)
143
- --no-color Disable ANSI colors
144
- --sort <field> Sort by: name, version, or location (default: name)
145
- -y, --yes Skip confirmation prompts`);
146
- }
147
-
148
- function printListHelp() {
149
- console.log(`${ansi.bold("Usage:")} asm list [options]
150
-
151
- List all discovered skills.
152
-
153
- ${ansi.bold("Options:")}
154
- --sort <field> Sort by: name, version, or location (default: name)
155
- -s, --scope <s> Filter: global, project, or both (default: both)
156
- --json Output as JSON array
157
- --no-color Disable ANSI colors`);
158
- }
159
-
160
- function printSearchHelp() {
161
- console.log(`${ansi.bold("Usage:")} asm search <query> [options]
162
-
163
- Search skills by name, description, or provider.
164
-
165
- ${ansi.bold("Options:")}
166
- --sort <field> Sort by: name, version, or location (default: name)
167
- -s, --scope <s> Filter: global, project, or both (default: both)
168
- --json Output as JSON array
169
- --no-color Disable ANSI colors`);
170
- }
171
-
172
- function printInspectHelp() {
173
- console.log(`${ansi.bold("Usage:")} asm inspect <skill-name> [options]
174
-
175
- Show detailed information for a skill. The <skill-name> is the directory name.
176
-
177
- ${ansi.bold("Options:")}
178
- -s, --scope <s> Filter: global, project, or both (default: both)
179
- --json Output as JSON object
180
- --no-color Disable ANSI colors`);
181
- }
182
-
183
- function printUninstallHelp() {
184
- console.log(`${ansi.bold("Usage:")} asm uninstall <skill-name> [options]
185
-
186
- Remove a skill and its associated rule files.
187
-
188
- ${ansi.bold("Options:")}
189
- -y, --yes Skip confirmation prompt
190
- -s, --scope <s> Filter: global, project, or both (default: both)
191
- --no-color Disable ANSI colors`);
192
- }
193
-
194
- function printConfigHelp() {
195
- console.log(`${ansi.bold("Usage:")} asm config <subcommand>
196
-
197
- Manage configuration.
198
-
199
- ${ansi.bold("Subcommands:")}
200
- show Print current config as JSON
201
- path Print config file path
202
- reset Reset config to defaults (with confirmation)
203
- edit Open config in $EDITOR`);
204
- }
205
-
206
- // ─── Command Handlers ───────────────────────────────────────────────────────
207
-
208
- async function cmdList(args: ParsedArgs) {
209
- if (args.flags.help) {
210
- printListHelp();
211
- return;
212
- }
213
-
214
- const config = await loadConfig();
215
- const allSkills = await scanAllSkills(config, args.flags.scope);
216
- const sorted = sortSkills(allSkills, args.flags.sort);
217
-
218
- if (args.flags.json) {
219
- console.log(formatJSON(sorted));
220
- } else {
221
- console.log(formatSkillTable(sorted));
222
- }
223
- }
224
-
225
- async function cmdSearch(args: ParsedArgs) {
226
- if (args.flags.help) {
227
- printSearchHelp();
228
- return;
229
- }
230
-
231
- const query = args.subcommand;
232
- if (!query) {
233
- error("Missing required argument: <query>");
234
- console.error(`Run "asm search --help" for usage.`);
235
- process.exit(2);
236
- }
237
-
238
- const config = await loadConfig();
239
- const allSkills = await scanAllSkills(config, args.flags.scope);
240
- const filtered = searchSkills(allSkills, query);
241
- const sorted = sortSkills(filtered, args.flags.sort);
242
-
243
- if (args.flags.json) {
244
- console.log(formatJSON(sorted));
245
- } else {
246
- console.log(formatSkillTable(sorted));
247
- }
248
- }
249
-
250
- async function cmdInspect(args: ParsedArgs) {
251
- if (args.flags.help) {
252
- printInspectHelp();
253
- return;
254
- }
255
-
256
- const skillName = args.subcommand;
257
- if (!skillName) {
258
- error("Missing required argument: <skill-name>");
259
- console.error(`Run "asm inspect --help" for usage.`);
260
- process.exit(2);
261
- }
262
-
263
- const config = await loadConfig();
264
- const allSkills = await scanAllSkills(config, args.flags.scope);
265
- const matches = allSkills.filter((s) => s.dirName === skillName);
266
-
267
- if (matches.length === 0) {
268
- error(`Skill "${skillName}" not found.`);
269
- process.exit(1);
270
- }
271
-
272
- if (args.flags.json) {
273
- console.log(formatJSON(matches.length === 1 ? matches[0] : matches));
274
- } else {
275
- for (let i = 0; i < matches.length; i++) {
276
- if (i > 0) console.log("\n" + "─".repeat(40) + "\n");
277
- console.log(formatSkillDetail(matches[i]));
278
- }
279
- }
280
- }
281
-
282
- async function cmdUninstall(args: ParsedArgs) {
283
- if (args.flags.help) {
284
- printUninstallHelp();
285
- return;
286
- }
287
-
288
- const skillName = args.subcommand;
289
- if (!skillName) {
290
- error("Missing required argument: <skill-name>");
291
- console.error(`Run "asm uninstall --help" for usage.`);
292
- process.exit(2);
293
- }
294
-
295
- const config = await loadConfig();
296
- const allSkills = await scanAllSkills(config, args.flags.scope);
297
- const plan = buildFullRemovalPlan(skillName, allSkills, config);
298
-
299
- const existing = await getExistingTargets(plan);
300
- if (existing.length === 0) {
301
- error(`Skill "${skillName}" not found or nothing to remove.`);
302
- process.exit(1);
303
- }
304
-
305
- // Show removal plan
306
- console.error(ansi.bold("Removal plan:"));
307
- for (const target of existing) {
308
- console.error(` ${ansi.red("•")} ${target}`);
309
- }
310
-
311
- if (!args.flags.yes) {
312
- // Interactive confirmation
313
- if (!process.stdin.isTTY) {
314
- error(
315
- "Cannot prompt for confirmation in non-interactive mode. Use --yes to skip.",
316
- );
317
- process.exit(2);
318
- }
319
- process.stderr.write(`\n${ansi.bold("Proceed with removal?")} [y/N] `);
320
- const answer = await readLine();
321
- if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
322
- console.error("Aborted.");
323
- process.exit(0);
324
- }
325
- }
326
-
327
- const log = await executeRemoval(plan);
328
- for (const entry of log) {
329
- console.error(entry);
330
- }
331
- console.error(ansi.green("\nDone."));
332
- }
333
-
334
- function readLine(): Promise<string> {
335
- return new Promise((resolve) => {
336
- let data = "";
337
- process.stdin.setEncoding("utf-8");
338
- process.stdin.on("data", (chunk: string) => {
339
- data += chunk;
340
- if (data.includes("\n")) {
341
- process.stdin.removeAllListeners("data");
342
- resolve(data.trim());
343
- }
344
- });
345
- process.stdin.resume();
346
- });
347
- }
348
-
349
- async function cmdConfig(args: ParsedArgs) {
350
- if (args.flags.help) {
351
- printConfigHelp();
352
- return;
353
- }
354
-
355
- const sub = args.subcommand;
356
-
357
- if (!sub) {
358
- error("Missing subcommand. Use: show, path, reset, or edit.");
359
- console.error(`Run "asm config --help" for usage.`);
360
- process.exit(2);
361
- }
362
-
363
- switch (sub) {
364
- case "show": {
365
- const config = await loadConfig();
366
- console.log(formatJSON(config));
367
- break;
368
- }
369
- case "path": {
370
- console.log(getConfigPath());
371
- break;
372
- }
373
- case "reset": {
374
- if (!args.flags.yes) {
375
- if (!process.stdin.isTTY) {
376
- error(
377
- "Cannot prompt for confirmation in non-interactive mode. Use --yes to skip.",
378
- );
379
- process.exit(2);
380
- }
381
- process.stderr.write(
382
- `${ansi.bold("Reset config to defaults?")} [y/N] `,
383
- );
384
- const answer = await readLine();
385
- if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
386
- console.error("Aborted.");
387
- process.exit(0);
388
- }
389
- }
390
- const defaults = getDefaultConfig();
391
- await saveConfig(defaults);
392
- console.error(ansi.green("Config reset to defaults."));
393
- break;
394
- }
395
- case "edit": {
396
- const editor = process.env.VISUAL || process.env.EDITOR || "vi";
397
- const configPath = getConfigPath();
398
- // Ensure config file exists
399
- await loadConfig();
400
- const proc = Bun.spawn([editor, configPath], {
401
- stdin: "inherit",
402
- stdout: "inherit",
403
- stderr: "inherit",
404
- });
405
- await proc.exited;
406
- break;
407
- }
408
- default: {
409
- error(
410
- `Unknown config subcommand: "${sub}". Use: show, path, reset, or edit.`,
411
- );
412
- process.exit(2);
413
- }
414
- }
415
- }
416
-
417
- // ─── Main CLI dispatcher ────────────────────────────────────────────────────
418
-
419
- export async function runCLI(argv: string[]): Promise<void> {
420
- const args = parseArgs(argv);
421
-
422
- // Apply --no-color
423
- if (args.flags.noColor) {
424
- (globalThis as any).__CLI_NO_COLOR = true;
425
- }
426
-
427
- // --version at top level
428
- if (args.flags.version) {
429
- console.log(`asm ${VERSION_STRING}`);
430
- return;
431
- }
432
-
433
- // --help at top level (no command)
434
- if (!args.command && args.flags.help) {
435
- printMainHelp();
436
- return;
437
- }
438
-
439
- // No command → return null to signal TUI launch
440
- if (!args.command) {
441
- return;
442
- }
443
-
444
- switch (args.command) {
445
- case "list":
446
- await cmdList(args);
447
- break;
448
- case "search":
449
- await cmdSearch(args);
450
- break;
451
- case "inspect":
452
- await cmdInspect(args);
453
- break;
454
- case "uninstall":
455
- await cmdUninstall(args);
456
- break;
457
- case "config":
458
- await cmdConfig(args);
459
- break;
460
- default:
461
- error(`Unknown command: "${args.command}"`);
462
- console.error(`Run "asm --help" for usage.`);
463
- process.exit(2);
464
- }
465
- }
466
-
467
- // ─── Check if CLI mode should run ──────────────────────────────────────────
468
-
469
- export function isCLIMode(argv: string[]): boolean {
470
- const args = argv.slice(2);
471
- if (args.length === 0) return false;
472
-
473
- // Known commands
474
- const commands = ["list", "search", "inspect", "uninstall", "config"];
475
- const first = args[0];
476
-
477
- // If the first arg is a known command, it's CLI mode
478
- if (commands.includes(first)) return true;
479
-
480
- // --help and --version are handled in CLI mode too
481
- if (first === "--help" || first === "-h") return true;
482
- if (first === "--version" || first === "-v") return true;
483
-
484
- // Unknown flags/commands → CLI mode (will show error)
485
- if (first.startsWith("-") || first.length > 0) return true;
486
-
487
- return false;
488
- }
package/src/config.ts DELETED
@@ -1,109 +0,0 @@
1
- import { readFile, writeFile, mkdir } from "fs/promises";
2
- import { join, resolve, dirname } from "path";
3
- import { homedir } from "os";
4
- import type { AppConfig, ProviderConfig } from "./utils/types";
5
-
6
- const HOME = homedir();
7
- const CONFIG_DIR = join(HOME, ".config", "agent-skill-manager");
8
- const CONFIG_PATH = join(CONFIG_DIR, "config.json");
9
-
10
- const DEFAULT_PROVIDERS: ProviderConfig[] = [
11
- {
12
- name: "claude",
13
- label: "Claude Code",
14
- global: "~/.claude/skills",
15
- project: ".claude/skills",
16
- enabled: true,
17
- },
18
- {
19
- name: "codex",
20
- label: "Codex",
21
- global: "~/.codex/skills",
22
- project: ".codex/skills",
23
- enabled: true,
24
- },
25
- {
26
- name: "openclaw",
27
- label: "OpenClaw",
28
- global: "~/.openclaw/skills",
29
- project: ".openclaw/skills",
30
- enabled: true,
31
- },
32
- {
33
- name: "agents",
34
- label: "Agents",
35
- global: "~/.agents/skills",
36
- project: ".agents/skills",
37
- enabled: true,
38
- },
39
- ];
40
-
41
- export function getDefaultConfig(): AppConfig {
42
- return {
43
- version: 1,
44
- providers: DEFAULT_PROVIDERS.map((p) => ({ ...p })),
45
- customPaths: [],
46
- preferences: {
47
- defaultScope: "both",
48
- defaultSort: "name",
49
- },
50
- };
51
- }
52
-
53
- export function getConfigPath(): string {
54
- return CONFIG_PATH;
55
- }
56
-
57
- export function resolveProviderPath(pathTemplate: string): string {
58
- if (pathTemplate.startsWith("~/")) {
59
- return join(HOME, pathTemplate.slice(2));
60
- }
61
- if (pathTemplate.startsWith("/")) {
62
- return pathTemplate;
63
- }
64
- // Relative path — resolve from cwd (project-level)
65
- return resolve(pathTemplate);
66
- }
67
-
68
- function mergeWithDefaults(config: Partial<AppConfig>): AppConfig {
69
- const defaults = getDefaultConfig();
70
- const providers = config.providers || [];
71
-
72
- // Add any new default providers that don't exist in the saved config
73
- const existingNames = new Set(providers.map((p) => p.name));
74
- for (const defaultProvider of defaults.providers) {
75
- if (!existingNames.has(defaultProvider.name)) {
76
- providers.push({ ...defaultProvider });
77
- }
78
- }
79
-
80
- return {
81
- version: config.version ?? defaults.version,
82
- providers,
83
- customPaths: config.customPaths ?? [],
84
- preferences: {
85
- defaultScope:
86
- config.preferences?.defaultScope ?? defaults.preferences.defaultScope,
87
- defaultSort:
88
- config.preferences?.defaultSort ?? defaults.preferences.defaultSort,
89
- },
90
- };
91
- }
92
-
93
- export async function loadConfig(): Promise<AppConfig> {
94
- try {
95
- const raw = await readFile(CONFIG_PATH, "utf-8");
96
- const parsed = JSON.parse(raw);
97
- return mergeWithDefaults(parsed);
98
- } catch {
99
- // Config doesn't exist or is invalid — use defaults
100
- const config = getDefaultConfig();
101
- await saveConfig(config);
102
- return config;
103
- }
104
- }
105
-
106
- export async function saveConfig(config: AppConfig): Promise<void> {
107
- await mkdir(CONFIG_DIR, { recursive: true });
108
- await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
109
- }
package/src/formatter.ts DELETED
@@ -1,92 +0,0 @@
1
- import type { SkillInfo } from "./utils/types";
2
-
3
- // ─── Color helpers ──────────────────────────────────────────────────────────
4
-
5
- const useColor = (): boolean => {
6
- if (process.env.NO_COLOR !== undefined) return false;
7
- if ((globalThis as any).__CLI_NO_COLOR) return false;
8
- if (!process.stdout.isTTY) return false;
9
- return true;
10
- };
11
-
12
- const ansi = {
13
- bold: (s: string) => (useColor() ? `\x1b[1m${s}\x1b[0m` : s),
14
- cyan: (s: string) => (useColor() ? `\x1b[36m${s}\x1b[0m` : s),
15
- green: (s: string) => (useColor() ? `\x1b[32m${s}\x1b[0m` : s),
16
- yellow: (s: string) => (useColor() ? `\x1b[33m${s}\x1b[0m` : s),
17
- dim: (s: string) => (useColor() ? `\x1b[2m${s}\x1b[0m` : s),
18
- red: (s: string) => (useColor() ? `\x1b[31m${s}\x1b[0m` : s),
19
- blueBold: (s: string) => (useColor() ? `\x1b[1m\x1b[34m${s}\x1b[0m` : s),
20
- };
21
-
22
- export { ansi };
23
-
24
- // ─── Table formatter ────────────────────────────────────────────────────────
25
-
26
- export function formatSkillTable(skills: SkillInfo[]): string {
27
- if (skills.length === 0) {
28
- return "No skills found.";
29
- }
30
-
31
- const headers = ["Name", "Version", "Provider", "Scope", "Type", "Path"];
32
-
33
- const rows = skills.map((s) => [
34
- s.name,
35
- s.version,
36
- s.providerLabel,
37
- s.scope,
38
- s.isSymlink ? "symlink" : "directory",
39
- s.path,
40
- ]);
41
-
42
- // Calculate column widths
43
- const widths = headers.map((h, i) =>
44
- Math.max(h.length, ...rows.map((r) => r[i].length)),
45
- );
46
-
47
- const pad = (str: string, width: number) => str.padEnd(width);
48
-
49
- const headerLine = headers.map((h, i) => pad(h, widths[i])).join(" ");
50
- const separator = widths.map((w) => "─".repeat(w)).join("──");
51
- const dataLines = rows.map((row) =>
52
- row.map((cell, i) => pad(cell, widths[i])).join(" "),
53
- );
54
-
55
- return [
56
- useColor() ? ansi.bold(headerLine) : headerLine,
57
- separator,
58
- ...dataLines,
59
- ].join("\n");
60
- }
61
-
62
- // ─── Detail formatter ───────────────────────────────────────────────────────
63
-
64
- export function formatSkillDetail(skill: SkillInfo): string {
65
- const lines: string[] = [];
66
- const label = (key: string, value: string) =>
67
- `${useColor() ? ansi.bold(key + ":") : key + ":"} ${value}`;
68
-
69
- lines.push(label("Name", skill.name));
70
- lines.push(label("Version", skill.version));
71
- lines.push(label("Provider", skill.providerLabel));
72
- lines.push(label("Scope", skill.scope));
73
- lines.push(label("Location", skill.location));
74
- lines.push(label("Path", skill.path));
75
- lines.push(label("Type", skill.isSymlink ? "symlink" : "directory"));
76
- if (skill.isSymlink && skill.symlinkTarget) {
77
- lines.push(label("Symlink Target", skill.symlinkTarget));
78
- }
79
- lines.push(label("File Count", String(skill.fileCount)));
80
- if (skill.description) {
81
- lines.push("");
82
- lines.push(label("Description", skill.description));
83
- }
84
-
85
- return lines.join("\n");
86
- }
87
-
88
- // ─── JSON formatter ─────────────────────────────────────────────────────────
89
-
90
- export function formatJSON(data: unknown): string {
91
- return JSON.stringify(data, null, 2);
92
- }