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,680 @@
|
|
|
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(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
var _chunk3HPUOQJNcjs = require('./chunk-3HPUOQJN.cjs');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
var _chunkXTHHDIBGcjs = require('./chunk-XTHHDIBG.cjs');
|
|
9
|
+
require('./chunk-N254NRHT.cjs');
|
|
10
|
+
|
|
11
|
+
// src/commands/skills-install.ts
|
|
12
|
+
var _path = require('path'); var _path2 = _interopRequireDefault(_path);
|
|
13
|
+
var _chalk = require('chalk'); var _chalk2 = _interopRequireDefault(_chalk);
|
|
14
|
+
|
|
15
|
+
// src/skills/GitHubRegistryFetcher.ts
|
|
16
|
+
var DEFAULT_REPO = "autohandai/community-skills";
|
|
17
|
+
var DEFAULT_BRANCH = "main";
|
|
18
|
+
var GitHubRegistryFetcher = class {
|
|
19
|
+
constructor(config = {}) {
|
|
20
|
+
const repo = config.repo || DEFAULT_REPO;
|
|
21
|
+
const branch = config.branch || DEFAULT_BRANCH;
|
|
22
|
+
this.baseUrl = `https://raw.githubusercontent.com/${repo}/${branch}`;
|
|
23
|
+
this.timeout = config.timeout || 15e3;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Fetch the registry.json index file
|
|
27
|
+
*/
|
|
28
|
+
async fetchRegistry() {
|
|
29
|
+
const url = `${this.baseUrl}/registry.json`;
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch(url, {
|
|
34
|
+
headers: {
|
|
35
|
+
Accept: "application/json",
|
|
36
|
+
"User-Agent": "autohand-cli"
|
|
37
|
+
},
|
|
38
|
+
signal: controller.signal
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
throw new Error(`Failed to fetch registry: HTTP ${response.status}`);
|
|
42
|
+
}
|
|
43
|
+
const data = await response.json();
|
|
44
|
+
return this.validateRegistry(data);
|
|
45
|
+
} finally {
|
|
46
|
+
clearTimeout(timeoutId);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Fetch a single file from a skill directory
|
|
51
|
+
*/
|
|
52
|
+
async fetchSkillFile(skillDirectory, filePath) {
|
|
53
|
+
const url = `${this.baseUrl}/${skillDirectory}/${filePath}`;
|
|
54
|
+
const controller = new AbortController();
|
|
55
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch(url, {
|
|
58
|
+
headers: {
|
|
59
|
+
"User-Agent": "autohand-cli"
|
|
60
|
+
},
|
|
61
|
+
signal: controller.signal
|
|
62
|
+
});
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
throw new Error(`Failed to fetch ${filePath}: HTTP ${response.status}`);
|
|
65
|
+
}
|
|
66
|
+
return response.text();
|
|
67
|
+
} finally {
|
|
68
|
+
clearTimeout(timeoutId);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Fetch all files for a skill directory
|
|
73
|
+
* Returns a Map of relative file paths to their contents
|
|
74
|
+
*/
|
|
75
|
+
async fetchSkillDirectory(skill) {
|
|
76
|
+
const contents = /* @__PURE__ */ new Map();
|
|
77
|
+
const errors = [];
|
|
78
|
+
const concurrencyLimit = 5;
|
|
79
|
+
const files = [...skill.files];
|
|
80
|
+
for (let i = 0; i < files.length; i += concurrencyLimit) {
|
|
81
|
+
const batch = files.slice(i, i + concurrencyLimit);
|
|
82
|
+
const results = await Promise.allSettled(
|
|
83
|
+
batch.map(async (file) => {
|
|
84
|
+
const content = await this.fetchSkillFile(skill.directory, file);
|
|
85
|
+
return { file, content };
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
for (const result of results) {
|
|
89
|
+
if (result.status === "fulfilled") {
|
|
90
|
+
contents.set(result.value.file, result.value.content);
|
|
91
|
+
} else {
|
|
92
|
+
errors.push(_optionalChain([result, 'access', _ => _.reason, 'optionalAccess', _2 => _2.message]) || "Unknown error");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (!contents.has("SKILL.md")) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Failed to fetch SKILL.md for ${skill.name}: ${errors.join(", ")}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return contents;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Validate and normalize the registry data
|
|
105
|
+
*/
|
|
106
|
+
validateRegistry(data) {
|
|
107
|
+
if (!data || typeof data !== "object") {
|
|
108
|
+
throw new Error("Invalid registry: expected object");
|
|
109
|
+
}
|
|
110
|
+
const registry = data;
|
|
111
|
+
if (!Array.isArray(registry.skills)) {
|
|
112
|
+
throw new Error("Invalid registry: missing skills array");
|
|
113
|
+
}
|
|
114
|
+
if (!Array.isArray(registry.categories)) {
|
|
115
|
+
throw new Error("Invalid registry: missing categories array");
|
|
116
|
+
}
|
|
117
|
+
const validatedSkills = [];
|
|
118
|
+
for (const skill of registry.skills) {
|
|
119
|
+
if (this.isValidSkill(skill)) {
|
|
120
|
+
validatedSkills.push(skill);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
version: String(registry.version || "1.0.0"),
|
|
125
|
+
updatedAt: String(registry.updatedAt || (/* @__PURE__ */ new Date()).toISOString()),
|
|
126
|
+
skills: validatedSkills,
|
|
127
|
+
categories: registry.categories
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Type guard for valid skill objects
|
|
132
|
+
*/
|
|
133
|
+
isValidSkill(skill) {
|
|
134
|
+
if (!skill || typeof skill !== "object") return false;
|
|
135
|
+
const s = skill;
|
|
136
|
+
return typeof s.id === "string" && typeof s.name === "string" && typeof s.description === "string" && typeof s.directory === "string" && Array.isArray(s.files) && s.files.includes("SKILL.md");
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Search skills by query (client-side filtering)
|
|
140
|
+
*/
|
|
141
|
+
filterSkills(skills, query) {
|
|
142
|
+
if (!query.trim()) return skills;
|
|
143
|
+
const lowerQuery = query.toLowerCase();
|
|
144
|
+
return skills.filter((skill) => {
|
|
145
|
+
const searchText = [
|
|
146
|
+
skill.name,
|
|
147
|
+
skill.description,
|
|
148
|
+
skill.category,
|
|
149
|
+
...skill.tags || [],
|
|
150
|
+
...skill.languages || [],
|
|
151
|
+
...skill.frameworks || []
|
|
152
|
+
].join(" ").toLowerCase();
|
|
153
|
+
return searchText.includes(lowerQuery);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get skills by category
|
|
158
|
+
*/
|
|
159
|
+
getSkillsByCategory(skills, categoryId) {
|
|
160
|
+
return skills.filter((skill) => skill.category === categoryId);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Get featured skills
|
|
164
|
+
*/
|
|
165
|
+
getFeaturedSkills(skills) {
|
|
166
|
+
return skills.filter((skill) => skill.isFeatured);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Find a skill by name or ID
|
|
170
|
+
*/
|
|
171
|
+
findSkill(skills, nameOrId) {
|
|
172
|
+
const lower = nameOrId.toLowerCase();
|
|
173
|
+
return skills.find(
|
|
174
|
+
(s) => s.id.toLowerCase() === lower || s.name.toLowerCase() === lower
|
|
175
|
+
) || null;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Get similar skills based on simple string matching
|
|
179
|
+
*/
|
|
180
|
+
findSimilarSkills(skills, query, limit = 5) {
|
|
181
|
+
const lower = query.toLowerCase();
|
|
182
|
+
const scored = skills.map((skill) => {
|
|
183
|
+
let score = 0;
|
|
184
|
+
if (skill.name.toLowerCase().includes(lower)) {
|
|
185
|
+
score += 10;
|
|
186
|
+
}
|
|
187
|
+
if (skill.description.toLowerCase().includes(lower)) {
|
|
188
|
+
score += 5;
|
|
189
|
+
}
|
|
190
|
+
if (_optionalChain([skill, 'access', _3 => _3.tags, 'optionalAccess', _4 => _4.some, 'call', _5 => _5((t) => t.toLowerCase().includes(lower))])) {
|
|
191
|
+
score += 3;
|
|
192
|
+
}
|
|
193
|
+
return { skill, score };
|
|
194
|
+
});
|
|
195
|
+
return scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map((s) => s.skill);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// src/skills/CommunitySkillsCache.ts
|
|
200
|
+
var _fsextra = require('fs-extra'); var _fsextra2 = _interopRequireDefault(_fsextra);
|
|
201
|
+
|
|
202
|
+
var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
203
|
+
var DEFAULT_MAX_SKILLS_CACHE = 50;
|
|
204
|
+
var CommunitySkillsCache = class {
|
|
205
|
+
constructor(config = {}) {
|
|
206
|
+
this.cacheDir = config.cacheDir || _path2.default.join(_chunkXTHHDIBGcjs.AUTOHAND_HOME, "community-skills", "cache");
|
|
207
|
+
this.ttlMs = _nullishCoalesce(config.ttlMs, () => ( DEFAULT_TTL_MS));
|
|
208
|
+
this.maxSkillsCache = _nullishCoalesce(config.maxSkillsCache, () => ( DEFAULT_MAX_SKILLS_CACHE));
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Get the cache directory path
|
|
212
|
+
*/
|
|
213
|
+
getCacheDir() {
|
|
214
|
+
return this.cacheDir;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get cached registry if it exists and is not expired
|
|
218
|
+
*/
|
|
219
|
+
async getRegistry() {
|
|
220
|
+
const cached = await this.readCachedRegistry();
|
|
221
|
+
if (!cached) return null;
|
|
222
|
+
if (Date.now() - cached.fetchedAt >= this.ttlMs) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
return cached.registry;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get cached registry ignoring TTL (for offline fallback)
|
|
229
|
+
*/
|
|
230
|
+
async getRegistryIgnoreTTL() {
|
|
231
|
+
const cached = await this.readCachedRegistry();
|
|
232
|
+
return _optionalChain([cached, 'optionalAccess', _6 => _6.registry]) || null;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Get cache age in milliseconds, or null if no cache exists
|
|
236
|
+
*/
|
|
237
|
+
async getCacheAge() {
|
|
238
|
+
const cached = await this.readCachedRegistry();
|
|
239
|
+
if (!cached) return null;
|
|
240
|
+
return Date.now() - cached.fetchedAt;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Check if cache is expired
|
|
244
|
+
*/
|
|
245
|
+
async isExpired() {
|
|
246
|
+
const age = await this.getCacheAge();
|
|
247
|
+
if (age === null) return true;
|
|
248
|
+
return age >= this.ttlMs;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Save registry to cache
|
|
252
|
+
*/
|
|
253
|
+
async setRegistry(registry, etag) {
|
|
254
|
+
const cached = {
|
|
255
|
+
registry,
|
|
256
|
+
fetchedAt: Date.now(),
|
|
257
|
+
etag
|
|
258
|
+
};
|
|
259
|
+
await _fsextra2.default.ensureDir(this.cacheDir);
|
|
260
|
+
await _fsextra2.default.writeJson(this.registryPath, cached, { spaces: 2 });
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Get a cached skill body by skill ID
|
|
264
|
+
*/
|
|
265
|
+
async getSkillBody(skillId) {
|
|
266
|
+
const skillPath = this.getSkillBodyPath(skillId);
|
|
267
|
+
if (await _fsextra2.default.pathExists(skillPath)) {
|
|
268
|
+
try {
|
|
269
|
+
return await _fsextra2.default.readFile(skillPath, "utf-8");
|
|
270
|
+
} catch (e) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Cache a skill body
|
|
278
|
+
*/
|
|
279
|
+
async setSkillBody(skillId, body) {
|
|
280
|
+
const skillsDir = _path2.default.join(this.cacheDir, "skills");
|
|
281
|
+
await _fsextra2.default.ensureDir(skillsDir);
|
|
282
|
+
await this.enforceMaxSkillsCache();
|
|
283
|
+
await _fsextra2.default.writeFile(this.getSkillBodyPath(skillId), body, "utf-8");
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Get cached skill directory files
|
|
287
|
+
* Returns Map of relative paths to contents, or null if not cached
|
|
288
|
+
*/
|
|
289
|
+
async getSkillDirectory(skillId) {
|
|
290
|
+
const skillCacheDir = _path2.default.join(this.cacheDir, "skills", skillId);
|
|
291
|
+
if (!await _fsextra2.default.pathExists(skillCacheDir)) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
const files = /* @__PURE__ */ new Map();
|
|
296
|
+
await this.readDirRecursive(skillCacheDir, skillCacheDir, files);
|
|
297
|
+
return files.size > 0 ? files : null;
|
|
298
|
+
} catch (e2) {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Cache a skill directory with all its files
|
|
304
|
+
*/
|
|
305
|
+
async setSkillDirectory(skillId, files) {
|
|
306
|
+
const skillCacheDir = _path2.default.join(this.cacheDir, "skills", skillId);
|
|
307
|
+
await this.enforceMaxSkillsCache();
|
|
308
|
+
await _fsextra2.default.remove(skillCacheDir);
|
|
309
|
+
for (const [relativePath, content] of files) {
|
|
310
|
+
const fullPath = _path2.default.join(skillCacheDir, relativePath);
|
|
311
|
+
await _fsextra2.default.ensureDir(_path2.default.dirname(fullPath));
|
|
312
|
+
await _fsextra2.default.writeFile(fullPath, content, "utf-8");
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Clear all cached data
|
|
317
|
+
*/
|
|
318
|
+
async clear() {
|
|
319
|
+
await _fsextra2.default.remove(this.cacheDir);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Clear only the registry cache (keep skill bodies)
|
|
323
|
+
*/
|
|
324
|
+
async clearRegistry() {
|
|
325
|
+
await _fsextra2.default.remove(this.registryPath);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Get cache statistics
|
|
329
|
+
*/
|
|
330
|
+
async getStats() {
|
|
331
|
+
const skillsDir = _path2.default.join(this.cacheDir, "skills");
|
|
332
|
+
let cachedSkillCount = 0;
|
|
333
|
+
if (await _fsextra2.default.pathExists(skillsDir)) {
|
|
334
|
+
const entries = await _fsextra2.default.readdir(skillsDir);
|
|
335
|
+
cachedSkillCount = entries.length;
|
|
336
|
+
}
|
|
337
|
+
const cached = await this.readCachedRegistry();
|
|
338
|
+
const registryAge = cached ? Date.now() - cached.fetchedAt : null;
|
|
339
|
+
return {
|
|
340
|
+
hasRegistry: cached !== null,
|
|
341
|
+
registryAge,
|
|
342
|
+
isExpired: registryAge !== null ? registryAge >= this.ttlMs : true,
|
|
343
|
+
cachedSkillCount
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
// ========== Private Methods ==========
|
|
347
|
+
get registryPath() {
|
|
348
|
+
return _path2.default.join(this.cacheDir, "registry.json");
|
|
349
|
+
}
|
|
350
|
+
getSkillBodyPath(skillId) {
|
|
351
|
+
return _path2.default.join(this.cacheDir, "skills", `${skillId}.md`);
|
|
352
|
+
}
|
|
353
|
+
async readCachedRegistry() {
|
|
354
|
+
if (!await _fsextra2.default.pathExists(this.registryPath)) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
try {
|
|
358
|
+
const data = await _fsextra2.default.readJson(this.registryPath);
|
|
359
|
+
if (typeof data === "object" && data !== null && typeof data.fetchedAt === "number" && data.registry && Array.isArray(data.registry.skills)) {
|
|
360
|
+
return data;
|
|
361
|
+
}
|
|
362
|
+
return null;
|
|
363
|
+
} catch (e3) {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Recursively read directory contents into a Map
|
|
369
|
+
*/
|
|
370
|
+
async readDirRecursive(baseDir, currentDir, files) {
|
|
371
|
+
const entries = await _fsextra2.default.readdir(currentDir, { withFileTypes: true });
|
|
372
|
+
for (const entry of entries) {
|
|
373
|
+
const fullPath = _path2.default.join(currentDir, entry.name);
|
|
374
|
+
const relativePath = _path2.default.relative(baseDir, fullPath);
|
|
375
|
+
if (entry.isDirectory()) {
|
|
376
|
+
await this.readDirRecursive(baseDir, fullPath, files);
|
|
377
|
+
} else if (entry.isFile()) {
|
|
378
|
+
const content = await _fsextra2.default.readFile(fullPath, "utf-8");
|
|
379
|
+
files.set(relativePath, content);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Enforce maximum number of cached skills by removing oldest
|
|
385
|
+
*/
|
|
386
|
+
async enforceMaxSkillsCache() {
|
|
387
|
+
const skillsDir = _path2.default.join(this.cacheDir, "skills");
|
|
388
|
+
if (!await _fsextra2.default.pathExists(skillsDir)) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const entries = await _fsextra2.default.readdir(skillsDir);
|
|
392
|
+
if (entries.length < this.maxSkillsCache) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const withStats = await Promise.all(
|
|
396
|
+
entries.map(async (name) => {
|
|
397
|
+
const entryPath = _path2.default.join(skillsDir, name);
|
|
398
|
+
try {
|
|
399
|
+
const stat = await _fsextra2.default.stat(entryPath);
|
|
400
|
+
return { name, path: entryPath, mtime: stat.mtime.getTime() };
|
|
401
|
+
} catch (e4) {
|
|
402
|
+
return { name, path: entryPath, mtime: 0 };
|
|
403
|
+
}
|
|
404
|
+
})
|
|
405
|
+
);
|
|
406
|
+
withStats.sort((a, b) => a.mtime - b.mtime);
|
|
407
|
+
const toRemove = withStats.slice(0, entries.length - this.maxSkillsCache + 1);
|
|
408
|
+
for (const entry of toRemove) {
|
|
409
|
+
await _fsextra2.default.remove(entry.path);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// src/commands/skills-install.ts
|
|
415
|
+
var metadata = {
|
|
416
|
+
command: "/skills install",
|
|
417
|
+
description: "browse and install community skills",
|
|
418
|
+
implemented: true
|
|
419
|
+
};
|
|
420
|
+
async function skillsInstall(ctx, skillName) {
|
|
421
|
+
const { skillsRegistry, workspaceRoot } = ctx;
|
|
422
|
+
if (!skillsRegistry) {
|
|
423
|
+
console.log(_chalk2.default.red("Skills registry not available."));
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
const cache = new CommunitySkillsCache();
|
|
427
|
+
const fetcher = new GitHubRegistryFetcher();
|
|
428
|
+
let registry;
|
|
429
|
+
try {
|
|
430
|
+
const cached = await cache.getRegistry();
|
|
431
|
+
if (cached) {
|
|
432
|
+
registry = cached;
|
|
433
|
+
} else {
|
|
434
|
+
console.log(_chalk2.default.cyan("Fetching community skills registry..."));
|
|
435
|
+
registry = await fetcher.fetchRegistry();
|
|
436
|
+
await cache.setRegistry(registry);
|
|
437
|
+
}
|
|
438
|
+
} catch (error) {
|
|
439
|
+
const stale = await cache.getRegistryIgnoreTTL();
|
|
440
|
+
if (stale) {
|
|
441
|
+
console.log(_chalk2.default.yellow("Using cached skills (offline mode)"));
|
|
442
|
+
registry = stale;
|
|
443
|
+
} else {
|
|
444
|
+
console.log(_chalk2.default.red("Failed to fetch community skills. Please check your internet connection."));
|
|
445
|
+
console.log(_chalk2.default.gray(error instanceof Error ? error.message : "Unknown error"));
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (skillName) {
|
|
450
|
+
return directInstall(ctx, registry, fetcher, cache, skillName);
|
|
451
|
+
}
|
|
452
|
+
return interactiveBrowser(ctx, registry, fetcher, cache);
|
|
453
|
+
}
|
|
454
|
+
async function directInstall(ctx, registry, fetcher, cache, skillName) {
|
|
455
|
+
const { skillsRegistry, workspaceRoot } = ctx;
|
|
456
|
+
const skill = fetcher.findSkill(registry.skills, skillName);
|
|
457
|
+
if (!skill) {
|
|
458
|
+
console.log(_chalk2.default.red(`Skill not found: ${skillName}`));
|
|
459
|
+
const similar = fetcher.findSimilarSkills(registry.skills, skillName, 3);
|
|
460
|
+
if (similar.length > 0) {
|
|
461
|
+
console.log(_chalk2.default.gray("Did you mean:"));
|
|
462
|
+
for (const s of similar) {
|
|
463
|
+
console.log(_chalk2.default.gray(` - ${s.name}: ${s.description}`));
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
const scope = await promptInstallScope();
|
|
469
|
+
if (!scope) {
|
|
470
|
+
console.log(_chalk2.default.gray("Installation cancelled."));
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
return installSkill(ctx, fetcher, cache, skill, scope);
|
|
474
|
+
}
|
|
475
|
+
async function interactiveBrowser(ctx, registry, fetcher, cache) {
|
|
476
|
+
console.log();
|
|
477
|
+
console.log(_chalk2.default.bold.cyan("Community Skills Marketplace"));
|
|
478
|
+
console.log(_chalk2.default.gray("\u2500".repeat(50)));
|
|
479
|
+
console.log(_chalk2.default.gray(`${registry.skills.length} skills available`));
|
|
480
|
+
console.log();
|
|
481
|
+
console.log(_chalk2.default.bold("Categories:"));
|
|
482
|
+
for (const cat of registry.categories) {
|
|
483
|
+
console.log(_chalk2.default.gray(` ${cat.name} (${cat.count})`));
|
|
484
|
+
}
|
|
485
|
+
console.log();
|
|
486
|
+
const featured = fetcher.getFeaturedSkills(registry.skills);
|
|
487
|
+
if (featured.length > 0) {
|
|
488
|
+
console.log(_chalk2.default.bold.yellow("Featured Skills:"));
|
|
489
|
+
for (const skill of featured.slice(0, 5)) {
|
|
490
|
+
const rating = skill.rating ? `\u2605 ${skill.rating.toFixed(1)}` : "";
|
|
491
|
+
const downloads = skill.downloadCount ? `\u2193${formatDownloads(skill.downloadCount)}` : "";
|
|
492
|
+
console.log(` ${_chalk2.default.green("\u25CF")} ${_chalk2.default.bold(skill.name)} ${_chalk2.default.gray(rating)} ${_chalk2.default.gray(downloads)}`);
|
|
493
|
+
console.log(_chalk2.default.gray(` ${skill.description}`));
|
|
494
|
+
}
|
|
495
|
+
console.log();
|
|
496
|
+
}
|
|
497
|
+
const choices = registry.skills.map((skill) => ({
|
|
498
|
+
name: skill.id,
|
|
499
|
+
message: formatSkillChoice(skill),
|
|
500
|
+
value: skill.id
|
|
501
|
+
}));
|
|
502
|
+
const answer = await _chunk3HPUOQJNcjs.safePrompt.call(void 0, [
|
|
503
|
+
{
|
|
504
|
+
type: "autocomplete",
|
|
505
|
+
name: "skill",
|
|
506
|
+
message: "Select a skill to install (type to search)",
|
|
507
|
+
choices
|
|
508
|
+
}
|
|
509
|
+
// Cast to any to allow autocomplete-specific options
|
|
510
|
+
]);
|
|
511
|
+
if (!_optionalChain([answer, 'optionalAccess', _7 => _7.skill])) {
|
|
512
|
+
console.log(_chalk2.default.gray("No skill selected."));
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
const selectedSkill = registry.skills.find((s) => s.id === answer.skill);
|
|
516
|
+
if (!selectedSkill) {
|
|
517
|
+
console.log(_chalk2.default.red("Skill not found."));
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
console.log();
|
|
521
|
+
console.log(_chalk2.default.bold.cyan(`Skill: ${selectedSkill.name}`));
|
|
522
|
+
console.log(_chalk2.default.gray("\u2500".repeat(50)));
|
|
523
|
+
console.log(_chalk2.default.white("Description: ") + selectedSkill.description);
|
|
524
|
+
console.log(_chalk2.default.white("Category: ") + selectedSkill.category);
|
|
525
|
+
if (_optionalChain([selectedSkill, 'access', _8 => _8.tags, 'optionalAccess', _9 => _9.length])) {
|
|
526
|
+
console.log(_chalk2.default.white("Tags: ") + selectedSkill.tags.join(", "));
|
|
527
|
+
}
|
|
528
|
+
if (selectedSkill.rating) {
|
|
529
|
+
console.log(_chalk2.default.white("Rating: ") + `\u2605 ${selectedSkill.rating.toFixed(1)}`);
|
|
530
|
+
}
|
|
531
|
+
if (selectedSkill.downloadCount) {
|
|
532
|
+
console.log(_chalk2.default.white("Downloads: ") + formatDownloads(selectedSkill.downloadCount));
|
|
533
|
+
}
|
|
534
|
+
if (selectedSkill.files.length > 1) {
|
|
535
|
+
console.log(_chalk2.default.white("Files: ") + selectedSkill.files.length + " files");
|
|
536
|
+
for (const file of selectedSkill.files.slice(0, 5)) {
|
|
537
|
+
console.log(_chalk2.default.gray(` - ${file}`));
|
|
538
|
+
}
|
|
539
|
+
if (selectedSkill.files.length > 5) {
|
|
540
|
+
console.log(_chalk2.default.gray(` ... and ${selectedSkill.files.length - 5} more`));
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
console.log();
|
|
544
|
+
const scope = await promptInstallScope();
|
|
545
|
+
if (!scope) {
|
|
546
|
+
console.log(_chalk2.default.gray("Installation cancelled."));
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
return installSkill(ctx, fetcher, cache, selectedSkill, scope);
|
|
550
|
+
}
|
|
551
|
+
async function promptInstallScope() {
|
|
552
|
+
const answer = await _chunk3HPUOQJNcjs.safePrompt.call(void 0, [
|
|
553
|
+
{
|
|
554
|
+
type: "select",
|
|
555
|
+
name: "scope",
|
|
556
|
+
message: "Install location",
|
|
557
|
+
choices: [
|
|
558
|
+
{
|
|
559
|
+
name: "user",
|
|
560
|
+
message: "User (~/.autohand/skills/) - Available in all projects"
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: "project",
|
|
564
|
+
message: "Project (.autohand/skills/) - Only this project"
|
|
565
|
+
}
|
|
566
|
+
]
|
|
567
|
+
}
|
|
568
|
+
]);
|
|
569
|
+
return _optionalChain([answer, 'optionalAccess', _10 => _10.scope]) || null;
|
|
570
|
+
}
|
|
571
|
+
async function installSkill(ctx, fetcher, cache, skill, scope) {
|
|
572
|
+
const { skillsRegistry, workspaceRoot } = ctx;
|
|
573
|
+
const targetDir = scope === "project" ? _path2.default.join(workspaceRoot, _chunkXTHHDIBGcjs.PROJECT_DIR_NAME, "skills") : _chunkXTHHDIBGcjs.AUTOHAND_PATHS.skills;
|
|
574
|
+
const isInstalled = await skillsRegistry.isSkillInstalled(skill.name, targetDir);
|
|
575
|
+
if (isInstalled) {
|
|
576
|
+
const confirm = await _chunk3HPUOQJNcjs.safePrompt.call(void 0, [
|
|
577
|
+
{
|
|
578
|
+
type: "confirm",
|
|
579
|
+
name: "overwrite",
|
|
580
|
+
message: `Skill "${skill.name}" already exists. Overwrite?`,
|
|
581
|
+
initial: false
|
|
582
|
+
}
|
|
583
|
+
]);
|
|
584
|
+
if (!_optionalChain([confirm, 'optionalAccess', _11 => _11.overwrite])) {
|
|
585
|
+
console.log(_chalk2.default.gray("Installation cancelled."));
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
console.log(_chalk2.default.cyan(`Installing ${skill.name}...`));
|
|
590
|
+
try {
|
|
591
|
+
let files = await cache.getSkillDirectory(skill.id);
|
|
592
|
+
if (!files) {
|
|
593
|
+
console.log(_chalk2.default.gray(`Fetching ${skill.files.length} files...`));
|
|
594
|
+
files = await fetcher.fetchSkillDirectory(skill);
|
|
595
|
+
await cache.setSkillDirectory(skill.id, files);
|
|
596
|
+
}
|
|
597
|
+
const result = await skillsRegistry.importCommunitySkillDirectory(
|
|
598
|
+
skill.name,
|
|
599
|
+
files,
|
|
600
|
+
targetDir,
|
|
601
|
+
isInstalled
|
|
602
|
+
// force if overwriting
|
|
603
|
+
);
|
|
604
|
+
if (result.success) {
|
|
605
|
+
console.log(_chalk2.default.green(`\u2713 Installed ${skill.name} to ${scope} skills`));
|
|
606
|
+
console.log(_chalk2.default.gray(` Path: ${result.path}`));
|
|
607
|
+
console.log();
|
|
608
|
+
console.log(_chalk2.default.gray("To activate this skill, run:"));
|
|
609
|
+
console.log(_chalk2.default.gray(` /skills use ${skill.name}`));
|
|
610
|
+
return `Skill "${skill.name}" installed successfully.`;
|
|
611
|
+
} else {
|
|
612
|
+
console.log(_chalk2.default.red(`Failed to install: ${result.error}`));
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
} catch (error) {
|
|
616
|
+
console.log(_chalk2.default.red("Installation failed."));
|
|
617
|
+
console.log(_chalk2.default.gray(error instanceof Error ? error.message : "Unknown error"));
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
function formatDownloads(count) {
|
|
622
|
+
if (count >= 1e6) return `${(count / 1e6).toFixed(1)}M`;
|
|
623
|
+
if (count >= 1e3) return `${(count / 1e3).toFixed(1)}K`;
|
|
624
|
+
return String(count);
|
|
625
|
+
}
|
|
626
|
+
function formatSkillChoice(skill) {
|
|
627
|
+
const parts = [];
|
|
628
|
+
if (skill.isFeatured) {
|
|
629
|
+
parts.push(_chalk2.default.yellow("\u2605"));
|
|
630
|
+
} else if (skill.isCurated) {
|
|
631
|
+
parts.push(_chalk2.default.green("\u2713"));
|
|
632
|
+
} else {
|
|
633
|
+
parts.push(" ");
|
|
634
|
+
}
|
|
635
|
+
parts.push(_chalk2.default.bold(skill.name.padEnd(30)));
|
|
636
|
+
if (skill.rating) {
|
|
637
|
+
parts.push(_chalk2.default.gray(`${skill.rating.toFixed(1)}`));
|
|
638
|
+
}
|
|
639
|
+
parts.push(_chalk2.default.gray(skill.description.slice(0, 40)));
|
|
640
|
+
return parts.join(" ");
|
|
641
|
+
}
|
|
642
|
+
async function refreshCache() {
|
|
643
|
+
const cache = new CommunitySkillsCache();
|
|
644
|
+
const fetcher = new GitHubRegistryFetcher();
|
|
645
|
+
console.log(_chalk2.default.cyan("Refreshing community skills cache..."));
|
|
646
|
+
try {
|
|
647
|
+
const registry = await fetcher.fetchRegistry();
|
|
648
|
+
await cache.setRegistry(registry);
|
|
649
|
+
console.log(_chalk2.default.green(`\u2713 Cached ${registry.skills.length} skills`));
|
|
650
|
+
} catch (error) {
|
|
651
|
+
console.log(_chalk2.default.red("Failed to refresh cache."));
|
|
652
|
+
console.log(_chalk2.default.gray(error instanceof Error ? error.message : "Unknown error"));
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
exports.metadata = metadata; exports.refreshCache = refreshCache; exports.skillsInstall = skillsInstall;
|
|
660
|
+
/**
|
|
661
|
+
* @license
|
|
662
|
+
* Copyright 2025 Autohand AI LLC
|
|
663
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
664
|
+
*
|
|
665
|
+
* GitHubRegistryFetcher - Fetches community skills registry from GitHub
|
|
666
|
+
*/
|
|
667
|
+
/**
|
|
668
|
+
* @license
|
|
669
|
+
* Copyright 2025 Autohand AI LLC
|
|
670
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
671
|
+
*
|
|
672
|
+
* CommunitySkillsCache - TTL-based caching for community skills registry
|
|
673
|
+
*/
|
|
674
|
+
/**
|
|
675
|
+
* @license
|
|
676
|
+
* Copyright 2025 Autohand AI LLC
|
|
677
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
678
|
+
*
|
|
679
|
+
* Skills install command - Browse and install community skills from GitHub
|
|
680
|
+
*/
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
var _chunkLUKMRIKJcjs = require('./chunk-LUKMRIKJ.cjs');
|
|
5
|
+
require('./chunk-JHGIWNHL.cjs');
|
|
6
|
+
require('./chunk-3HPUOQJN.cjs');
|
|
7
|
+
require('./chunk-XTHHDIBG.cjs');
|
|
8
|
+
require('./chunk-N254NRHT.cjs');
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
exports.createSkill = _chunkLUKMRIKJcjs.createSkill; exports.metadata = _chunkLUKMRIKJcjs.metadata;
|