autohand-cli 0.6.4 → 0.6.7

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 (76) hide show
  1. package/dist/SkillsRegistry-LXDK73BL.cjs +9 -0
  2. package/dist/SkillsRegistry-SP5MX7OA.js +9 -0
  3. package/dist/agents-FH47ZMOI.cjs +10 -0
  4. package/dist/{agents-OJWYZN6X.js → agents-NB5VQN6H.js} +2 -2
  5. package/dist/{agents-new-WQLJOXSS.js → agents-new-M325HGWT.js} +3 -2
  6. package/dist/agents-new-XLEU26YI.cjs +11 -0
  7. package/dist/{chunk-DD2YPHP5.cjs → chunk-2OBNJCG6.cjs} +15 -13
  8. package/dist/{chunk-NWXYG5PQ.js → chunk-37NUB5KX.js} +1 -1
  9. package/dist/{chunk-AL4Z4WKG.cjs → chunk-3DPDLZYY.cjs} +9 -7
  10. package/dist/chunk-3HPUOQJN.cjs +23 -0
  11. package/dist/{chunk-G7SYGATA.cjs → chunk-3ZUWWML7.cjs} +2 -2
  12. package/dist/{chunk-3Y6G5DUX.cjs → chunk-53YDUYNS.cjs} +10 -4
  13. package/dist/{chunk-PRZTK2FX.js → chunk-5WKR4HIB.js} +7 -5
  14. package/dist/{chunk-ZTA2ASFW.cjs → chunk-6ZGNSZRG.cjs} +1 -1
  15. package/dist/chunk-7BYSXAKS.js +23 -0
  16. package/dist/{chunk-6FEZ6JAQ.js → chunk-7HB7GSQF.js} +1 -1
  17. package/dist/{chunk-UBGEAEKS.js → chunk-AY2XV7TH.js} +1 -1
  18. package/dist/{chunk-7RRX7H2X.cjs → chunk-B5N5UAMO.cjs} +20 -12
  19. package/dist/{chunk-KZ2UXXLH.js → chunk-BAHUKJJR.js} +7 -5
  20. package/dist/{chunk-KT55HW6V.js → chunk-CHQMK2ZG.js} +1 -1
  21. package/dist/chunk-CVYEUA3D.cjs +528 -0
  22. package/dist/{chunk-MRQV5HMC.js → chunk-DE7YC5MB.js} +8 -6
  23. package/dist/{chunk-AD4O67ZA.cjs → chunk-EJ77L3KT.cjs} +10 -10
  24. package/dist/{chunk-737A24RB.js → chunk-FUEL6BK7.js} +9 -1
  25. package/dist/{chunk-6MCXWSR3.js → chunk-H4RPZD6H.js} +1 -1
  26. package/dist/chunk-JHGIWNHL.cjs +46 -0
  27. package/dist/{chunk-27JNK5TE.cjs → chunk-LUKMRIKJ.cjs} +40 -61
  28. package/dist/{chunk-4H3B46YX.js → chunk-MWLAHCU7.js} +9 -3
  29. package/dist/{chunk-QCMC2WOC.cjs → chunk-N6ZOJI2M.cjs} +4 -4
  30. package/dist/{chunk-2TPGTNNY.js → chunk-NGSLABLS.js} +37 -58
  31. package/dist/chunk-QHPFA6OE.js +46 -0
  32. package/dist/{chunk-M4LKQQHU.cjs → chunk-REPKBECD.cjs} +2 -2
  33. package/dist/chunk-SKU4M27Z.js +528 -0
  34. package/dist/chunk-W4XTDUGT.js +267 -0
  35. package/dist/{chunk-RDEROLKA.cjs → chunk-XAM7SFVB.cjs} +8 -6
  36. package/dist/chunk-XF4EQ3IV.cjs +267 -0
  37. package/dist/{chunk-IHJDYAYJ.cjs → chunk-XTHHDIBG.cjs} +9 -1
  38. package/dist/{chunk-5N3QP5LJ.js → chunk-YDH2BMEN.js} +19 -11
  39. package/dist/constants-ICQLSGZN.cjs +19 -0
  40. package/dist/constants-N3I2FHCM.js +19 -0
  41. package/dist/feedback-TOGESBX7.cjs +11 -0
  42. package/dist/{feedback-3THCLEBE.js → feedback-YGSYBQEW.js} +3 -2
  43. package/dist/index.cjs +1463 -1146
  44. package/dist/index.js +987 -670
  45. package/dist/login-QVBS7KBK.cjs +13 -0
  46. package/dist/login-XUVEFKCR.js +13 -0
  47. package/dist/logout-75YLPOBK.js +13 -0
  48. package/dist/logout-FYYR5KCP.cjs +13 -0
  49. package/dist/{permissions-CYW62ZK3.js → permissions-3GS4ZWVA.js} +2 -1
  50. package/dist/permissions-E3MTIE7D.cjs +10 -0
  51. package/dist/{skills-HF4SAF5O.js → skills-3M26KASS.js} +3 -1
  52. package/dist/skills-BOFY5RQN.cjs +13 -0
  53. package/dist/skills-install-WJ2LDRQG.js +680 -0
  54. package/dist/skills-install-Z3W5PQWQ.cjs +680 -0
  55. package/dist/skills-new-KIBUN63X.js +12 -0
  56. package/dist/skills-new-XDYS24XW.cjs +12 -0
  57. package/dist/{status-SLYYTKXD.js → status-6FY6RKIS.js} +1 -1
  58. package/dist/status-DJHDT6QH.cjs +9 -0
  59. package/dist/theme-2UK74UWR.cjs +13 -0
  60. package/dist/{theme-THMQ5AIN.js → theme-XNZ2X6HE.js} +3 -3
  61. package/package.json +1 -1
  62. package/dist/agents-EHLYBJLK.cjs +0 -10
  63. package/dist/agents-new-7VPASCBV.cjs +0 -10
  64. package/dist/chunk-NEAJ2UWG.js +0 -191
  65. package/dist/chunk-Z6SGIQWH.cjs +0 -191
  66. package/dist/feedback-PATTKRH5.cjs +0 -10
  67. package/dist/login-QJROML5I.js +0 -12
  68. package/dist/login-X66DSV75.cjs +0 -12
  69. package/dist/logout-3Z7R3F7J.cjs +0 -12
  70. package/dist/logout-RJ5OAXRI.js +0 -12
  71. package/dist/permissions-NOC5DMOH.cjs +0 -9
  72. package/dist/skills-U6J6DFLK.cjs +0 -11
  73. package/dist/skills-new-QDTNEG3R.js +0 -10
  74. package/dist/skills-new-UPVBHIF2.cjs +0 -10
  75. package/dist/status-GR73LEEN.cjs +0 -9
  76. package/dist/theme-YDANJLZR.cjs +0 -13
@@ -0,0 +1,528 @@
1
+ import {
2
+ validateSkillFrontmatter
3
+ } from "./chunk-QHPFA6OE.js";
4
+ import {
5
+ PROJECT_DIR_NAME
6
+ } from "./chunk-FUEL6BK7.js";
7
+
8
+ // src/skills/SkillsRegistry.ts
9
+ import fs2 from "fs-extra";
10
+ import path from "path";
11
+
12
+ // src/skills/SkillParser.ts
13
+ import fs from "fs-extra";
14
+ import YAML from "yaml";
15
+ var SkillParser = class {
16
+ /**
17
+ * Parse a SKILL.md file from disk
18
+ */
19
+ async parseFile(filePath, source) {
20
+ try {
21
+ const content = await fs.readFile(filePath, "utf-8");
22
+ return this.parseContent(content, filePath, source);
23
+ } catch (error) {
24
+ return {
25
+ success: false,
26
+ error: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`
27
+ };
28
+ }
29
+ }
30
+ /**
31
+ * Parse SKILL.md content directly from a string
32
+ */
33
+ parseContent(content, filePath, source) {
34
+ const extraction = this.extractFrontmatter(content);
35
+ if (!extraction) {
36
+ return {
37
+ success: false,
38
+ error: "No valid YAML frontmatter found. SKILL.md must start with --- delimited frontmatter."
39
+ };
40
+ }
41
+ try {
42
+ const parsed = YAML.parse(extraction.frontmatter);
43
+ const validation = validateSkillFrontmatter(parsed);
44
+ if (!validation.valid) {
45
+ return {
46
+ success: false,
47
+ error: `Invalid skill frontmatter: ${validation.errors.join("; ")}`
48
+ };
49
+ }
50
+ const skill = {
51
+ name: parsed.name,
52
+ description: parsed.description,
53
+ license: parsed.license,
54
+ compatibility: parsed.compatibility,
55
+ metadata: parsed.metadata,
56
+ "allowed-tools": parsed["allowed-tools"],
57
+ body: extraction.body,
58
+ path: filePath,
59
+ source,
60
+ isActive: false
61
+ };
62
+ return { success: true, skill };
63
+ } catch (error) {
64
+ return {
65
+ success: false,
66
+ error: `Failed to parse YAML frontmatter: ${error instanceof Error ? error.message : String(error)}`
67
+ };
68
+ }
69
+ }
70
+ /**
71
+ * Extract YAML frontmatter and body from content
72
+ * Frontmatter must be delimited by --- at start and end
73
+ */
74
+ extractFrontmatter(content) {
75
+ if (!content.startsWith("---")) {
76
+ return null;
77
+ }
78
+ const lines = content.split("\n");
79
+ let closingIndex = -1;
80
+ for (let i = 1; i < lines.length; i++) {
81
+ if (lines[i].trim() === "---") {
82
+ closingIndex = i;
83
+ break;
84
+ }
85
+ }
86
+ if (closingIndex === -1) {
87
+ return null;
88
+ }
89
+ const frontmatter = lines.slice(1, closingIndex).join("\n");
90
+ const body = lines.slice(closingIndex + 1).join("\n").trim();
91
+ return { frontmatter, body };
92
+ }
93
+ };
94
+
95
+ // src/skills/SkillsRegistry.ts
96
+ var SIMILARITY_THRESHOLD = 0.3;
97
+ var VENDOR_SOURCES = ["codex-user", "claude-user", "codex-project", "claude-project"];
98
+ var SkillsRegistry = class {
99
+ constructor(userSkillsDir, defaultSource = "autohand-user") {
100
+ this.userSkillsDir = userSkillsDir;
101
+ this.skills = /* @__PURE__ */ new Map();
102
+ this.parser = new SkillParser();
103
+ this.workspaceRoot = null;
104
+ this.telemetryManager = null;
105
+ this.communityClient = null;
106
+ this.defaultSource = defaultSource;
107
+ }
108
+ /**
109
+ * Set the telemetry manager for tracking skill events
110
+ */
111
+ setTelemetryManager(telemetryManager) {
112
+ this.telemetryManager = telemetryManager;
113
+ }
114
+ /**
115
+ * Set the community skills client for backup/sync operations
116
+ */
117
+ setCommunityClient(client) {
118
+ this.communityClient = client;
119
+ }
120
+ /**
121
+ * Get the community skills client
122
+ */
123
+ getCommunityClient() {
124
+ return this.communityClient;
125
+ }
126
+ /**
127
+ * Check if any vendor skills (codex/claude) are loaded
128
+ */
129
+ hasVendorSkills() {
130
+ for (const skill of this.skills.values()) {
131
+ if (VENDOR_SOURCES.includes(skill.source)) {
132
+ return true;
133
+ }
134
+ }
135
+ return false;
136
+ }
137
+ /**
138
+ * Get all vendor skills (from codex/claude sources)
139
+ */
140
+ getVendorSkills() {
141
+ return Array.from(this.skills.values()).filter(
142
+ (skill) => VENDOR_SOURCES.includes(skill.source)
143
+ );
144
+ }
145
+ /**
146
+ * Import a community skill package and save to disk
147
+ */
148
+ async importCommunitySkill(pkg, targetDir) {
149
+ if (!pkg.name || !pkg.body) {
150
+ return { success: false, error: "Invalid skill package: missing name or body" };
151
+ }
152
+ const skillDir = path.join(targetDir, pkg.name);
153
+ const skillPath = path.join(skillDir, "SKILL.md");
154
+ if (await fs2.pathExists(skillPath)) {
155
+ return { success: false, skipped: true, error: "Skill already exists" };
156
+ }
157
+ try {
158
+ await fs2.ensureDir(skillDir);
159
+ await fs2.writeFile(skillPath, pkg.body, "utf-8");
160
+ const result = await this.parser.parseFile(skillPath, "community");
161
+ if (result.success && result.skill) {
162
+ this.skills.set(result.skill.name, result.skill);
163
+ return { success: true, path: skillPath };
164
+ }
165
+ return { success: false, error: "Failed to parse imported skill" };
166
+ } catch (err) {
167
+ const error = err instanceof Error ? err.message : "Unknown error";
168
+ return { success: false, error };
169
+ }
170
+ }
171
+ /**
172
+ * Import a community skill directory with multiple files
173
+ * Used for skills from GitHub that include templates, examples, etc.
174
+ *
175
+ * @param skillName - Name of the skill (used as directory name)
176
+ * @param files - Map of relative file paths to their contents
177
+ * @param targetDir - Target directory (user or project skills dir)
178
+ * @param force - Overwrite if skill already exists
179
+ */
180
+ async importCommunitySkillDirectory(skillName, files, targetDir, force = false) {
181
+ if (!files.has("SKILL.md")) {
182
+ return { success: false, error: "Missing required SKILL.md file" };
183
+ }
184
+ const skillDir = path.join(targetDir, skillName);
185
+ const skillPath = path.join(skillDir, "SKILL.md");
186
+ if (!force && await fs2.pathExists(skillPath)) {
187
+ return { success: false, skipped: true, error: "Skill already exists" };
188
+ }
189
+ try {
190
+ if (force && await fs2.pathExists(skillDir)) {
191
+ await fs2.remove(skillDir);
192
+ }
193
+ for (const [relativePath, content] of files) {
194
+ const fullPath = path.join(skillDir, relativePath);
195
+ await fs2.ensureDir(path.dirname(fullPath));
196
+ await fs2.writeFile(fullPath, content, "utf-8");
197
+ }
198
+ const result = await this.parser.parseFile(skillPath, "community");
199
+ if (result.success && result.skill) {
200
+ this.skills.set(result.skill.name, result.skill);
201
+ return { success: true, path: skillDir };
202
+ }
203
+ return { success: false, error: "Failed to parse imported skill" };
204
+ } catch (err) {
205
+ const error = err instanceof Error ? err.message : "Unknown error";
206
+ return { success: false, error };
207
+ }
208
+ }
209
+ /**
210
+ * Check if a skill is already installed
211
+ */
212
+ isSkillInstalled(skillName, targetDir) {
213
+ const skillPath = path.join(targetDir, skillName, "SKILL.md");
214
+ return fs2.pathExists(skillPath);
215
+ }
216
+ /**
217
+ * Get the user skills directory path
218
+ */
219
+ getUserSkillsDir() {
220
+ return this.userSkillsDir;
221
+ }
222
+ /**
223
+ * Initialize the registry by loading skills from the user directory
224
+ */
225
+ async initialize() {
226
+ await this.loadFromDirectory(this.userSkillsDir, this.defaultSource, true);
227
+ }
228
+ /**
229
+ * Set the workspace root and load project-level skills
230
+ */
231
+ async setWorkspace(workspaceRoot) {
232
+ this.workspaceRoot = workspaceRoot;
233
+ const claudeProjectSkillsDir = path.join(workspaceRoot, ".claude", "skills");
234
+ await this.loadFromDirectory(claudeProjectSkillsDir, "claude-project", false);
235
+ const autohandProjectSkillsDir = path.join(workspaceRoot, PROJECT_DIR_NAME, "skills");
236
+ await this.loadFromDirectory(autohandProjectSkillsDir, "autohand-project", true);
237
+ }
238
+ /**
239
+ * Add an additional skill location to search
240
+ */
241
+ async addLocation(directory, source, recursive = true) {
242
+ await this.loadFromDirectory(directory, source, recursive);
243
+ }
244
+ /**
245
+ * Add a skill location with auto-copy to autohand location
246
+ * Copies discovered skills to the target autohand directory, preserving structure
247
+ */
248
+ async addLocationWithAutoCopy(sourceDirectory, source, targetAutohandDir, recursive = true) {
249
+ const result = {
250
+ copiedCount: 0,
251
+ skippedCount: 0,
252
+ errorCount: 0,
253
+ copiedSkills: [],
254
+ skippedSkills: []
255
+ };
256
+ if (!await fs2.pathExists(sourceDirectory)) {
257
+ return result;
258
+ }
259
+ const skillFiles = await this.findSkillFiles(sourceDirectory, recursive);
260
+ for (const skillPath of skillFiles) {
261
+ const parseResult = await this.parser.parseFile(skillPath, source);
262
+ if (!parseResult.success || !parseResult.skill) {
263
+ continue;
264
+ }
265
+ const skill = parseResult.skill;
266
+ const skillName = skill.name;
267
+ const skillDir = path.dirname(skillPath);
268
+ const relativePath = path.relative(sourceDirectory, skillDir);
269
+ const targetSkillDir = path.join(targetAutohandDir, relativePath);
270
+ const targetSkillPath = path.join(targetSkillDir, "SKILL.md");
271
+ if (await fs2.pathExists(targetSkillPath)) {
272
+ result.skippedCount++;
273
+ result.skippedSkills.push(skillName);
274
+ } else {
275
+ try {
276
+ await fs2.ensureDir(targetSkillDir);
277
+ await fs2.copyFile(skillPath, targetSkillPath);
278
+ result.copiedCount++;
279
+ result.copiedSkills.push(skillName);
280
+ } catch {
281
+ result.errorCount++;
282
+ }
283
+ }
284
+ this.skills.set(skill.name, skill);
285
+ }
286
+ return result;
287
+ }
288
+ /**
289
+ * Set workspace with auto-copy from claude-project to autohand-project
290
+ */
291
+ async setWorkspaceWithAutoCopy(workspaceRoot) {
292
+ this.workspaceRoot = workspaceRoot;
293
+ const claudeProjectSkillsDir = path.join(workspaceRoot, ".claude", "skills");
294
+ const autohandProjectSkillsDir = path.join(workspaceRoot, PROJECT_DIR_NAME, "skills");
295
+ await this.addLocationWithAutoCopy(
296
+ claudeProjectSkillsDir,
297
+ "claude-project",
298
+ autohandProjectSkillsDir,
299
+ false
300
+ // Claude project is not recursive
301
+ );
302
+ await this.loadFromDirectory(autohandProjectSkillsDir, "autohand-project", true);
303
+ }
304
+ /**
305
+ * Add a skill location with auto-copy AND backup to community API
306
+ * Enhanced version that also backs up vendor skills to the API
307
+ */
308
+ async addLocationWithAutoCopyAndBackup(sourceDirectory, source, targetAutohandDir, recursive = true) {
309
+ const result = await this.addLocationWithAutoCopy(
310
+ sourceDirectory,
311
+ source,
312
+ targetAutohandDir,
313
+ recursive
314
+ );
315
+ if (this.communityClient && result.copiedCount > 0) {
316
+ const backupPayloads = [];
317
+ for (const skillName of result.copiedSkills) {
318
+ const skill = this.skills.get(skillName);
319
+ if (skill) {
320
+ backupPayloads.push({
321
+ name: skill.name,
322
+ description: skill.description,
323
+ body: skill.body,
324
+ allowedTools: skill["allowed-tools"],
325
+ originalSource: source,
326
+ originalPath: skill.path
327
+ });
328
+ }
329
+ }
330
+ if (backupPayloads.length > 0) {
331
+ await this.communityClient.backupAllSkills(backupPayloads);
332
+ }
333
+ }
334
+ return result;
335
+ }
336
+ /**
337
+ * Backup all vendor skills to community API
338
+ */
339
+ async backupAllVendorSkills() {
340
+ if (!this.communityClient) {
341
+ return { backed: 0, failed: 0 };
342
+ }
343
+ const vendorSkills = this.getVendorSkills();
344
+ if (vendorSkills.length === 0) {
345
+ return { backed: 0, failed: 0 };
346
+ }
347
+ const payloads = vendorSkills.map((skill) => ({
348
+ name: skill.name,
349
+ description: skill.description,
350
+ body: skill.body,
351
+ allowedTools: skill["allowed-tools"],
352
+ originalSource: skill.source,
353
+ originalPath: skill.path
354
+ }));
355
+ return this.communityClient.backupAllSkills(payloads);
356
+ }
357
+ /**
358
+ * Load skills from a directory
359
+ */
360
+ async loadFromDirectory(directory, source, recursive) {
361
+ if (!await fs2.pathExists(directory)) {
362
+ return;
363
+ }
364
+ const skillFiles = await this.findSkillFiles(directory, recursive);
365
+ for (const skillPath of skillFiles) {
366
+ const result = await this.parser.parseFile(skillPath, source);
367
+ if (result.success && result.skill) {
368
+ this.skills.set(result.skill.name, result.skill);
369
+ }
370
+ }
371
+ }
372
+ /**
373
+ * Find all SKILL.md files in a directory
374
+ */
375
+ async findSkillFiles(directory, recursive) {
376
+ const results = [];
377
+ if (!await fs2.pathExists(directory)) {
378
+ return results;
379
+ }
380
+ const entries = await fs2.readdir(directory, { withFileTypes: true });
381
+ for (const entry of entries) {
382
+ const fullPath = path.join(directory, entry.name);
383
+ if (entry.isDirectory()) {
384
+ const skillPath = path.join(fullPath, "SKILL.md");
385
+ if (await fs2.pathExists(skillPath)) {
386
+ results.push(skillPath);
387
+ }
388
+ if (recursive) {
389
+ const nested = await this.findSkillFiles(fullPath, true);
390
+ results.push(...nested);
391
+ }
392
+ }
393
+ }
394
+ return results;
395
+ }
396
+ /**
397
+ * List all available skills
398
+ */
399
+ listSkills() {
400
+ return Array.from(this.skills.values());
401
+ }
402
+ /**
403
+ * Get a specific skill by name
404
+ */
405
+ getSkill(name) {
406
+ return this.skills.get(name) ?? null;
407
+ }
408
+ /**
409
+ * Activate a skill by name
410
+ */
411
+ activateSkill(name) {
412
+ const skill = this.skills.get(name);
413
+ if (!skill) {
414
+ return false;
415
+ }
416
+ skill.isActive = true;
417
+ return true;
418
+ }
419
+ /**
420
+ * Deactivate a skill by name
421
+ */
422
+ deactivateSkill(name) {
423
+ const skill = this.skills.get(name);
424
+ if (!skill) {
425
+ return false;
426
+ }
427
+ skill.isActive = false;
428
+ return true;
429
+ }
430
+ /**
431
+ * Deactivate all active skills
432
+ */
433
+ deactivateAll() {
434
+ for (const skill of this.skills.values()) {
435
+ skill.isActive = false;
436
+ }
437
+ }
438
+ /**
439
+ * Get all currently active skills
440
+ */
441
+ getActiveSkills() {
442
+ return Array.from(this.skills.values()).filter((s) => s.isActive);
443
+ }
444
+ /**
445
+ * Find skills similar to a given query using Jaccard similarity
446
+ */
447
+ findSimilar(query, threshold = SIMILARITY_THRESHOLD) {
448
+ const queryTokens = this.tokenize(query);
449
+ const matches = [];
450
+ for (const skill of this.skills.values()) {
451
+ const skillText = `${skill.name} ${skill.description}`;
452
+ const skillTokens = this.tokenize(skillText);
453
+ const score = this.calculateJaccard(queryTokens, skillTokens);
454
+ if (score >= threshold) {
455
+ matches.push({ skill, score });
456
+ }
457
+ }
458
+ return matches.sort((a, b) => b.score - a.score);
459
+ }
460
+ /**
461
+ * Calculate Jaccard similarity between two token sets
462
+ */
463
+ calculateJaccard(a, b) {
464
+ if (a.size === 0 || b.size === 0) {
465
+ return 0;
466
+ }
467
+ const intersection = new Set([...a].filter((x) => b.has(x)));
468
+ const union = /* @__PURE__ */ new Set([...a, ...b]);
469
+ return intersection.size / union.size;
470
+ }
471
+ /**
472
+ * Tokenize text into a set of lowercase words
473
+ */
474
+ tokenize(text) {
475
+ return new Set(
476
+ text.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 2)
477
+ );
478
+ }
479
+ /**
480
+ * Get the number of loaded skills
481
+ */
482
+ get size() {
483
+ return this.skills.size;
484
+ }
485
+ /**
486
+ * Check if a skill exists by name
487
+ */
488
+ hasSkill(name) {
489
+ return this.skills.has(name);
490
+ }
491
+ /**
492
+ * Save a new skill to the user skills directory
493
+ */
494
+ async saveSkill(name, content) {
495
+ const skillDir = path.join(this.userSkillsDir, name);
496
+ const skillPath = path.join(skillDir, "SKILL.md");
497
+ try {
498
+ await fs2.ensureDir(skillDir);
499
+ await fs2.writeFile(skillPath, content, "utf-8");
500
+ const result = await this.parser.parseFile(skillPath, "autohand-user");
501
+ if (result.success && result.skill) {
502
+ this.skills.set(result.skill.name, result.skill);
503
+ return true;
504
+ }
505
+ return false;
506
+ } catch {
507
+ return false;
508
+ }
509
+ }
510
+ };
511
+
512
+ export {
513
+ SkillsRegistry
514
+ };
515
+ /**
516
+ * @license
517
+ * Copyright 2025 Autohand AI LLC
518
+ * SPDX-License-Identifier: Apache-2.0
519
+ *
520
+ * SkillParser - Parses SKILL.md files with YAML frontmatter
521
+ */
522
+ /**
523
+ * @license
524
+ * Copyright 2025 Autohand AI LLC
525
+ * SPDX-License-Identifier: Apache-2.0
526
+ *
527
+ * SkillsRegistry - Manages skill discovery, loading, and activation
528
+ */