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
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
2
+
3
+ var _chunkJHGIWNHLcjs = require('./chunk-JHGIWNHL.cjs');
4
+
5
+
6
+ var _chunkXTHHDIBGcjs = require('./chunk-XTHHDIBG.cjs');
7
+
8
+ // src/skills/SkillsRegistry.ts
9
+ var _fsextra = require('fs-extra'); var _fsextra2 = _interopRequireDefault(_fsextra);
10
+ var _path = require('path'); var _path2 = _interopRequireDefault(_path);
11
+
12
+ // src/skills/SkillParser.ts
13
+
14
+ var _yaml = require('yaml'); var _yaml2 = _interopRequireDefault(_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 _fsextra2.default.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 = _yaml2.default.parse(extraction.frontmatter);
43
+ const validation = _chunkJHGIWNHLcjs.validateSkillFrontmatter.call(void 0, 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 = _path2.default.join(targetDir, pkg.name);
153
+ const skillPath = _path2.default.join(skillDir, "SKILL.md");
154
+ if (await _fsextra2.default.pathExists(skillPath)) {
155
+ return { success: false, skipped: true, error: "Skill already exists" };
156
+ }
157
+ try {
158
+ await _fsextra2.default.ensureDir(skillDir);
159
+ await _fsextra2.default.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 = _path2.default.join(targetDir, skillName);
185
+ const skillPath = _path2.default.join(skillDir, "SKILL.md");
186
+ if (!force && await _fsextra2.default.pathExists(skillPath)) {
187
+ return { success: false, skipped: true, error: "Skill already exists" };
188
+ }
189
+ try {
190
+ if (force && await _fsextra2.default.pathExists(skillDir)) {
191
+ await _fsextra2.default.remove(skillDir);
192
+ }
193
+ for (const [relativePath, content] of files) {
194
+ const fullPath = _path2.default.join(skillDir, relativePath);
195
+ await _fsextra2.default.ensureDir(_path2.default.dirname(fullPath));
196
+ await _fsextra2.default.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 = _path2.default.join(targetDir, skillName, "SKILL.md");
214
+ return _fsextra2.default.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 = _path2.default.join(workspaceRoot, ".claude", "skills");
234
+ await this.loadFromDirectory(claudeProjectSkillsDir, "claude-project", false);
235
+ const autohandProjectSkillsDir = _path2.default.join(workspaceRoot, _chunkXTHHDIBGcjs.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 _fsextra2.default.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 = _path2.default.dirname(skillPath);
268
+ const relativePath = _path2.default.relative(sourceDirectory, skillDir);
269
+ const targetSkillDir = _path2.default.join(targetAutohandDir, relativePath);
270
+ const targetSkillPath = _path2.default.join(targetSkillDir, "SKILL.md");
271
+ if (await _fsextra2.default.pathExists(targetSkillPath)) {
272
+ result.skippedCount++;
273
+ result.skippedSkills.push(skillName);
274
+ } else {
275
+ try {
276
+ await _fsextra2.default.ensureDir(targetSkillDir);
277
+ await _fsextra2.default.copyFile(skillPath, targetSkillPath);
278
+ result.copiedCount++;
279
+ result.copiedSkills.push(skillName);
280
+ } catch (e) {
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 = _path2.default.join(workspaceRoot, ".claude", "skills");
294
+ const autohandProjectSkillsDir = _path2.default.join(workspaceRoot, _chunkXTHHDIBGcjs.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 _fsextra2.default.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 _fsextra2.default.pathExists(directory)) {
378
+ return results;
379
+ }
380
+ const entries = await _fsextra2.default.readdir(directory, { withFileTypes: true });
381
+ for (const entry of entries) {
382
+ const fullPath = _path2.default.join(directory, entry.name);
383
+ if (entry.isDirectory()) {
384
+ const skillPath = _path2.default.join(fullPath, "SKILL.md");
385
+ if (await _fsextra2.default.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 _nullishCoalesce(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 = _path2.default.join(this.userSkillsDir, name);
496
+ const skillPath = _path2.default.join(skillDir, "SKILL.md");
497
+ try {
498
+ await _fsextra2.default.ensureDir(skillDir);
499
+ await _fsextra2.default.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 (e2) {
507
+ return false;
508
+ }
509
+ }
510
+ };
511
+
512
+
513
+
514
+ exports.SkillsRegistry = SkillsRegistry;
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
+ */
@@ -1,16 +1,18 @@
1
1
  import {
2
2
  getAuthClient
3
- } from "./chunk-KT55HW6V.js";
3
+ } from "./chunk-CHQMK2ZG.js";
4
4
  import {
5
5
  saveConfig
6
- } from "./chunk-UBGEAEKS.js";
6
+ } from "./chunk-AY2XV7TH.js";
7
+ import {
8
+ safePrompt
9
+ } from "./chunk-7BYSXAKS.js";
7
10
  import {
8
11
  AUTH_CONFIG
9
- } from "./chunk-737A24RB.js";
12
+ } from "./chunk-FUEL6BK7.js";
10
13
 
11
14
  // src/commands/login.ts
12
15
  import chalk from "chalk";
13
- import enquirer from "enquirer";
14
16
  var metadata = {
15
17
  command: "/login",
16
18
  description: "sign in to your Autohand account",
@@ -47,13 +49,13 @@ function sleep(ms) {
47
49
  async function login(ctx) {
48
50
  const config = ctx.config;
49
51
  if (config?.auth?.token && config?.auth?.user) {
50
- const { continueLogin } = await enquirer.prompt({
52
+ const result = await safePrompt({
51
53
  type: "confirm",
52
54
  name: "continueLogin",
53
55
  message: `Already logged in as ${chalk.cyan(config.auth.user.email)}. Log in with a different account?`,
54
56
  initial: false
55
57
  });
56
- if (!continueLogin) {
58
+ if (!result || !result.continueLogin) {
57
59
  console.log(chalk.gray("Login cancelled."));
58
60
  return null;
59
61
  }
@@ -5,18 +5,18 @@
5
5
 
6
6
 
7
7
 
8
- var _chunkQCMC2WOCcjs = require('./chunk-QCMC2WOC.cjs');
8
+ var _chunkN6ZOJI2Mcjs = require('./chunk-N6ZOJI2M.cjs');
9
9
 
10
10
  // src/commands/theme.ts
11
11
  var _chalk = require('chalk'); var _chalk2 = _interopRequireDefault(_chalk);
12
12
  var _enquirer = require('enquirer'); var _enquirer2 = _interopRequireDefault(_enquirer);
13
13
  async function theme(ctx) {
14
14
  const { Select } = _enquirer2.default;
15
- const themes = _chunkQCMC2WOCcjs.listAvailableThemes.call(void 0, );
16
- const currentTheme = _chunkQCMC2WOCcjs.isThemeInitialized.call(void 0, ) ? _chunkQCMC2WOCcjs.getTheme.call(void 0, ).name : _optionalChain([ctx, 'access', _ => _.config, 'access', _2 => _2.ui, 'optionalAccess', _3 => _3.theme]) || "dark";
15
+ const themes = _chunkN6ZOJI2Mcjs.listAvailableThemes.call(void 0, );
16
+ const currentTheme = _chunkN6ZOJI2Mcjs.isThemeInitialized.call(void 0, ) ? _chunkN6ZOJI2Mcjs.getTheme.call(void 0, ).name : _optionalChain([ctx, 'access', _ => _.config, 'access', _2 => _2.ui, 'optionalAccess', _3 => _3.theme]) || "dark";
17
17
  console.log(_chalk2.default.cyan("\n\u{1F3A8} Theme Selection\n"));
18
18
  console.log(_chalk2.default.gray(`Current theme: ${_chalk2.default.white(currentTheme)}`));
19
- console.log(_chalk2.default.gray(`Custom themes location: ${_chunkQCMC2WOCcjs.CUSTOM_THEMES_DIR}
19
+ console.log(_chalk2.default.gray(`Custom themes location: ${_chunkN6ZOJI2Mcjs.CUSTOM_THEMES_DIR}
20
20
  `));
21
21
  const choices = themes.map((name) => ({
22
22
  name,
@@ -35,12 +35,12 @@ async function theme(ctx) {
35
35
  console.log(_chalk2.default.gray("\nNo change made."));
36
36
  return null;
37
37
  }
38
- _chunkQCMC2WOCcjs.initTheme.call(void 0, selected);
38
+ _chunkN6ZOJI2Mcjs.initTheme.call(void 0, selected);
39
39
  ctx.config.ui = { ...ctx.config.ui, theme: selected };
40
- await _chunkQCMC2WOCcjs.saveConfig.call(void 0, ctx.config);
40
+ await _chunkN6ZOJI2Mcjs.saveConfig.call(void 0, ctx.config);
41
41
  console.log(_chalk2.default.green(`
42
42
  \u2713 Theme changed to '${selected}'`));
43
- const newTheme = _chunkQCMC2WOCcjs.getTheme.call(void 0, );
43
+ const newTheme = _chunkN6ZOJI2Mcjs.getTheme.call(void 0, );
44
44
  console.log("\nTheme preview:");
45
45
  console.log(` ${newTheme.fg("accent", "\u25CF accent")} ${newTheme.fg("success", "\u25CF success")} ${newTheme.fg("error", "\u25CF error")} ${newTheme.fg("warning", "\u25CF warning")}`);
46
46
  console.log(` ${newTheme.fg("muted", "\u25CF muted")} ${newTheme.fg("dim", "\u25CF dim")} ${newTheme.fg("text", "\u25CF text")}`);
@@ -52,15 +52,15 @@ async function theme(ctx) {
52
52
  }
53
53
  }
54
54
  async function themeInfo() {
55
- if (!_chunkQCMC2WOCcjs.isThemeInitialized.call(void 0, )) {
55
+ if (!_chunkN6ZOJI2Mcjs.isThemeInitialized.call(void 0, )) {
56
56
  console.log(_chalk2.default.yellow("Theme not initialized."));
57
57
  return null;
58
58
  }
59
- const currentTheme = _chunkQCMC2WOCcjs.getTheme.call(void 0, );
59
+ const currentTheme = _chunkN6ZOJI2Mcjs.getTheme.call(void 0, );
60
60
  console.log(_chalk2.default.cyan("\n\u{1F3A8} Current Theme Info\n"));
61
61
  console.log(_chalk2.default.gray(`Name: ${_chalk2.default.white(currentTheme.name)}`));
62
62
  console.log(_chalk2.default.gray(`Color mode: ${_chalk2.default.white(currentTheme.getColorMode())}`));
63
- console.log(_chalk2.default.gray(`Custom themes dir: ${_chunkQCMC2WOCcjs.CUSTOM_THEMES_DIR}`));
63
+ console.log(_chalk2.default.gray(`Custom themes dir: ${_chunkN6ZOJI2Mcjs.CUSTOM_THEMES_DIR}`));
64
64
  console.log();
65
65
  console.log("Color preview:");
66
66
  console.log(` ${currentTheme.fg("accent", "\u25CF accent")} ${currentTheme.fg("success", "\u25CF success")} ${currentTheme.fg("error", "\u25CF error")} ${currentTheme.fg("warning", "\u25CF warning")}`);
@@ -60,13 +60,21 @@ var SKILL_LOCATIONS = [
60
60
  { basePath: AUTOHAND_PATHS.skills, source: "autohand-user", recursive: true }
61
61
  // Project-level Autohand skills are resolved at runtime with workspaceRoot
62
62
  ];
63
+ function getProjectSkillLocations(workspaceRoot) {
64
+ return [
65
+ { basePath: path.join(workspaceRoot, ".claude", "skills"), source: "claude-project", recursive: false },
66
+ { basePath: path.join(workspaceRoot, PROJECT_DIR_NAME, "skills"), source: "autohand-project", recursive: true }
67
+ ];
68
+ }
63
69
 
64
70
  export {
65
71
  AUTOHAND_HOME,
66
72
  AUTOHAND_PATHS,
67
73
  AUTOHAND_FILES,
68
74
  PROJECT_DIR_NAME,
69
- AUTH_CONFIG
75
+ AUTH_CONFIG,
76
+ SKILL_LOCATIONS,
77
+ getProjectSkillLocations
70
78
  };
71
79
  /**
72
80
  * @license
@@ -5,7 +5,7 @@ import readline from "readline";
5
5
  // package.json
6
6
  var package_default = {
7
7
  name: "autohand-cli",
8
- version: "0.6.4",
8
+ version: "0.6.7",
9
9
  licenses: "SEE LICENSE IN LICENSE",
10
10
  description: "Autohand interactive coding agent CLI powered by LLMs.",
11
11
  type: "module",