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.
- package/dist/SkillsRegistry-LXDK73BL.cjs +9 -0
- package/dist/SkillsRegistry-SP5MX7OA.js +9 -0
- package/dist/agents-FH47ZMOI.cjs +10 -0
- package/dist/{agents-OJWYZN6X.js → agents-NB5VQN6H.js} +2 -2
- package/dist/{agents-new-WQLJOXSS.js → agents-new-M325HGWT.js} +3 -2
- package/dist/agents-new-XLEU26YI.cjs +11 -0
- package/dist/{chunk-DD2YPHP5.cjs → chunk-2OBNJCG6.cjs} +15 -13
- package/dist/{chunk-NWXYG5PQ.js → chunk-37NUB5KX.js} +1 -1
- package/dist/{chunk-AL4Z4WKG.cjs → chunk-3DPDLZYY.cjs} +9 -7
- package/dist/chunk-3HPUOQJN.cjs +23 -0
- package/dist/{chunk-G7SYGATA.cjs → chunk-3ZUWWML7.cjs} +2 -2
- package/dist/{chunk-3Y6G5DUX.cjs → chunk-53YDUYNS.cjs} +10 -4
- package/dist/{chunk-PRZTK2FX.js → chunk-5WKR4HIB.js} +7 -5
- package/dist/{chunk-ZTA2ASFW.cjs → chunk-6ZGNSZRG.cjs} +1 -1
- package/dist/chunk-7BYSXAKS.js +23 -0
- package/dist/{chunk-6FEZ6JAQ.js → chunk-7HB7GSQF.js} +1 -1
- package/dist/{chunk-UBGEAEKS.js → chunk-AY2XV7TH.js} +1 -1
- package/dist/{chunk-7RRX7H2X.cjs → chunk-B5N5UAMO.cjs} +20 -12
- package/dist/{chunk-KZ2UXXLH.js → chunk-BAHUKJJR.js} +7 -5
- package/dist/{chunk-KT55HW6V.js → chunk-CHQMK2ZG.js} +1 -1
- package/dist/chunk-CVYEUA3D.cjs +528 -0
- package/dist/{chunk-MRQV5HMC.js → chunk-DE7YC5MB.js} +8 -6
- package/dist/{chunk-AD4O67ZA.cjs → chunk-EJ77L3KT.cjs} +10 -10
- package/dist/{chunk-737A24RB.js → chunk-FUEL6BK7.js} +9 -1
- package/dist/{chunk-6MCXWSR3.js → chunk-H4RPZD6H.js} +1 -1
- package/dist/chunk-JHGIWNHL.cjs +46 -0
- package/dist/{chunk-27JNK5TE.cjs → chunk-LUKMRIKJ.cjs} +40 -61
- package/dist/{chunk-4H3B46YX.js → chunk-MWLAHCU7.js} +9 -3
- package/dist/{chunk-QCMC2WOC.cjs → chunk-N6ZOJI2M.cjs} +4 -4
- package/dist/{chunk-2TPGTNNY.js → chunk-NGSLABLS.js} +37 -58
- package/dist/chunk-QHPFA6OE.js +46 -0
- package/dist/{chunk-M4LKQQHU.cjs → chunk-REPKBECD.cjs} +2 -2
- package/dist/chunk-SKU4M27Z.js +528 -0
- package/dist/chunk-W4XTDUGT.js +267 -0
- package/dist/{chunk-RDEROLKA.cjs → chunk-XAM7SFVB.cjs} +8 -6
- package/dist/chunk-XF4EQ3IV.cjs +267 -0
- package/dist/{chunk-IHJDYAYJ.cjs → chunk-XTHHDIBG.cjs} +9 -1
- package/dist/{chunk-5N3QP5LJ.js → chunk-YDH2BMEN.js} +19 -11
- package/dist/constants-ICQLSGZN.cjs +19 -0
- package/dist/constants-N3I2FHCM.js +19 -0
- package/dist/feedback-TOGESBX7.cjs +11 -0
- package/dist/{feedback-3THCLEBE.js → feedback-YGSYBQEW.js} +3 -2
- package/dist/index.cjs +1463 -1146
- package/dist/index.js +987 -670
- package/dist/login-QVBS7KBK.cjs +13 -0
- package/dist/login-XUVEFKCR.js +13 -0
- package/dist/logout-75YLPOBK.js +13 -0
- package/dist/logout-FYYR5KCP.cjs +13 -0
- package/dist/{permissions-CYW62ZK3.js → permissions-3GS4ZWVA.js} +2 -1
- package/dist/permissions-E3MTIE7D.cjs +10 -0
- package/dist/{skills-HF4SAF5O.js → skills-3M26KASS.js} +3 -1
- package/dist/skills-BOFY5RQN.cjs +13 -0
- package/dist/skills-install-WJ2LDRQG.js +680 -0
- package/dist/skills-install-Z3W5PQWQ.cjs +680 -0
- package/dist/skills-new-KIBUN63X.js +12 -0
- package/dist/skills-new-XDYS24XW.cjs +12 -0
- package/dist/{status-SLYYTKXD.js → status-6FY6RKIS.js} +1 -1
- package/dist/status-DJHDT6QH.cjs +9 -0
- package/dist/theme-2UK74UWR.cjs +13 -0
- package/dist/{theme-THMQ5AIN.js → theme-XNZ2X6HE.js} +3 -3
- package/package.json +1 -1
- package/dist/agents-EHLYBJLK.cjs +0 -10
- package/dist/agents-new-7VPASCBV.cjs +0 -10
- package/dist/chunk-NEAJ2UWG.js +0 -191
- package/dist/chunk-Z6SGIQWH.cjs +0 -191
- package/dist/feedback-PATTKRH5.cjs +0 -10
- package/dist/login-QJROML5I.js +0 -12
- package/dist/login-X66DSV75.cjs +0 -12
- package/dist/logout-3Z7R3F7J.cjs +0 -12
- package/dist/logout-RJ5OAXRI.js +0 -12
- package/dist/permissions-NOC5DMOH.cjs +0 -9
- package/dist/skills-U6J6DFLK.cjs +0 -11
- package/dist/skills-new-QDTNEG3R.js +0 -10
- package/dist/skills-new-UPVBHIF2.cjs +0 -10
- package/dist/status-GR73LEEN.cjs +0 -9
- 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
|
+
*/
|