@empjs/skill 1.0.5 → 1.0.6
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/index.cjs +345 -326
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +344 -325
- package/dist/index.js.map +1 -1
- package/package.json +2 -6
package/dist/index.js
CHANGED
|
@@ -3,75 +3,13 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { readFileSync } from "fs";
|
|
6
|
-
import { fileURLToPath } from "url";
|
|
7
6
|
import { dirname, join } from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
8
|
|
|
9
|
-
// src/commands/
|
|
10
|
-
import
|
|
11
|
-
import { promisify as promisify2 } from "util";
|
|
12
|
-
import fs4 from "fs";
|
|
13
|
-
import path5 from "path";
|
|
9
|
+
// src/commands/agents.ts
|
|
10
|
+
import fs from "fs";
|
|
14
11
|
import os2 from "os";
|
|
15
|
-
|
|
16
|
-
// src/utils/logger.ts
|
|
17
12
|
import chalk from "chalk";
|
|
18
|
-
import ora from "ora";
|
|
19
|
-
var Logger = class {
|
|
20
|
-
spinner = null;
|
|
21
|
-
info(message) {
|
|
22
|
-
if (this.spinner) this.spinner.stop();
|
|
23
|
-
console.log(chalk.blue("\u2139"), message);
|
|
24
|
-
}
|
|
25
|
-
success(message) {
|
|
26
|
-
if (this.spinner) this.spinner.stop();
|
|
27
|
-
console.log(chalk.green("\u2713"), message);
|
|
28
|
-
}
|
|
29
|
-
warn(message) {
|
|
30
|
-
if (this.spinner) this.spinner.stop();
|
|
31
|
-
console.log(chalk.yellow("\u26A0"), message);
|
|
32
|
-
}
|
|
33
|
-
error(message) {
|
|
34
|
-
if (this.spinner) this.spinner.stop();
|
|
35
|
-
console.log(chalk.red("\u2717"), message);
|
|
36
|
-
}
|
|
37
|
-
start(message) {
|
|
38
|
-
if (this.spinner) this.spinner.stop();
|
|
39
|
-
this.spinner = ora(message).start();
|
|
40
|
-
return this.spinner;
|
|
41
|
-
}
|
|
42
|
-
stopSpinner() {
|
|
43
|
-
if (this.spinner) {
|
|
44
|
-
this.spinner.stop();
|
|
45
|
-
this.spinner = null;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Update spinner text without stopping it
|
|
50
|
-
*/
|
|
51
|
-
updateSpinner(message) {
|
|
52
|
-
if (this.spinner) {
|
|
53
|
-
this.spinner.text = message;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Log info without stopping spinner (for background info)
|
|
58
|
-
*/
|
|
59
|
-
infoWithoutStop(message) {
|
|
60
|
-
if (this.spinner) {
|
|
61
|
-
const currentText = this.spinner.text;
|
|
62
|
-
this.spinner.stop();
|
|
63
|
-
console.log(chalk.blue("\u2139"), message);
|
|
64
|
-
this.spinner.start(currentText);
|
|
65
|
-
} else {
|
|
66
|
-
console.log(chalk.blue("\u2139"), message);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
var logger = new Logger();
|
|
71
|
-
|
|
72
|
-
// src/utils/paths.ts
|
|
73
|
-
import fs from "fs";
|
|
74
|
-
import path2 from "path";
|
|
75
13
|
|
|
76
14
|
// src/config/agents.ts
|
|
77
15
|
import os from "os";
|
|
@@ -189,7 +127,213 @@ var SHARED_SKILLS_DIR = path.join(HOME, ".emp-agent", "skills");
|
|
|
189
127
|
var CONFIG_FILE = path.join(HOME, ".emp-agent", "config.json");
|
|
190
128
|
var CACHE_DIR = path.join(HOME, ".emp-agent", "cache");
|
|
191
129
|
|
|
130
|
+
// src/commands/agents.ts
|
|
131
|
+
function agents() {
|
|
132
|
+
const cwd = process.cwd();
|
|
133
|
+
console.log(chalk.bold("\n\u{1F4CB} Supported AI Agents:\n"));
|
|
134
|
+
for (const agent of AGENTS) {
|
|
135
|
+
const skillsDirs = getAgentSkillsDirs(agent, cwd);
|
|
136
|
+
const dirsInfo = skillsDirs.length > 1 ? ` (${skillsDirs.length} directories)` : "";
|
|
137
|
+
console.log(chalk.bold(agent.displayName));
|
|
138
|
+
console.log(chalk.gray(` Name: ${agent.name}`));
|
|
139
|
+
if (skillsDirs.length === 0) {
|
|
140
|
+
console.log(chalk.yellow(" \u26A0\uFE0F No directories configured"));
|
|
141
|
+
} else {
|
|
142
|
+
console.log(chalk.gray(` Directories${dirsInfo}:`));
|
|
143
|
+
for (const dir of skillsDirs) {
|
|
144
|
+
const exists = fs.existsSync(dir);
|
|
145
|
+
const status = exists ? chalk.green("\u2713") : chalk.gray("\u25CB");
|
|
146
|
+
const pathColor = exists ? chalk.white : chalk.gray;
|
|
147
|
+
console.log(` ${status} ${pathColor(dir)}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
console.log("");
|
|
151
|
+
}
|
|
152
|
+
console.log(chalk.bold("\u{1F4E6} Shared Skills Directory:"));
|
|
153
|
+
const sharedExists = fs.existsSync(SHARED_SKILLS_DIR);
|
|
154
|
+
const sharedStatus = sharedExists ? chalk.green("\u2713") : chalk.gray("\u25CB");
|
|
155
|
+
const sharedPathColor = sharedExists ? chalk.white : chalk.gray;
|
|
156
|
+
console.log(` ${sharedStatus} ${sharedPathColor(SHARED_SKILLS_DIR)}`);
|
|
157
|
+
console.log("");
|
|
158
|
+
const home = os2.homedir();
|
|
159
|
+
if (cwd !== home) {
|
|
160
|
+
console.log(chalk.gray(`Current working directory: ${cwd}`));
|
|
161
|
+
console.log(chalk.gray("(Project-level directories are shown above)\n"));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/commands/install.ts
|
|
166
|
+
import { exec as exec2 } from "child_process";
|
|
167
|
+
import fs5 from "fs";
|
|
168
|
+
import os3 from "os";
|
|
169
|
+
import path5 from "path";
|
|
170
|
+
import { promisify as promisify2 } from "util";
|
|
171
|
+
|
|
172
|
+
// src/utils/git.ts
|
|
173
|
+
function parseGitUrl(url) {
|
|
174
|
+
const githubPatterns = [
|
|
175
|
+
// https://github.com/owner/repo/tree/branch/path/to/dir
|
|
176
|
+
/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)(?:\/(.+))?$/,
|
|
177
|
+
// https://github.com/owner/repo
|
|
178
|
+
/^https?:\/\/github\.com\/([^/]+)\/([^/]+)(?:\/)?$/,
|
|
179
|
+
// git@github.com:owner/repo.git
|
|
180
|
+
/^git@github\.com:([^/]+)\/([^/]+)(?:\.git)?$/
|
|
181
|
+
];
|
|
182
|
+
const gitlabPatterns = [
|
|
183
|
+
// https://host/group/repo/-/tree/branch/path (self-hosted + gitlab.com)
|
|
184
|
+
/^https?:\/\/([^/]+)\/(.+)\/-\/tree\/([^/]+)(?:\/(.+))?$/,
|
|
185
|
+
// https://gitlab.com/owner/repo
|
|
186
|
+
/^https?:\/\/gitlab\.com\/([^/]+)\/([^/]+)(?:\/)?$/,
|
|
187
|
+
// git@gitlab.com:owner/repo.git or git@host:group/repo.git
|
|
188
|
+
/^git@([^:]+):(.+)(?:\.git)?$/
|
|
189
|
+
];
|
|
190
|
+
for (let i = 0; i < githubPatterns.length; i++) {
|
|
191
|
+
const pattern = githubPatterns[i];
|
|
192
|
+
const match = url.match(pattern);
|
|
193
|
+
if (match) {
|
|
194
|
+
const owner = match[1];
|
|
195
|
+
const repo = match[2].replace(/\.git$/, "");
|
|
196
|
+
let branch;
|
|
197
|
+
let path7;
|
|
198
|
+
if (i === 0) {
|
|
199
|
+
branch = match[3];
|
|
200
|
+
path7 = match[4];
|
|
201
|
+
} else if (i === 1) {
|
|
202
|
+
branch = "main";
|
|
203
|
+
} else {
|
|
204
|
+
branch = "main";
|
|
205
|
+
}
|
|
206
|
+
const gitUrl = `https://github.com/${owner}/${repo}.git`;
|
|
207
|
+
return {
|
|
208
|
+
type: "github",
|
|
209
|
+
owner,
|
|
210
|
+
repo,
|
|
211
|
+
branch,
|
|
212
|
+
path: path7,
|
|
213
|
+
gitUrl,
|
|
214
|
+
installUrl: gitUrl
|
|
215
|
+
// Will be used for git clone
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
for (let i = 0; i < gitlabPatterns.length; i++) {
|
|
220
|
+
const pattern = gitlabPatterns[i];
|
|
221
|
+
const match = url.match(pattern);
|
|
222
|
+
if (match) {
|
|
223
|
+
let owner;
|
|
224
|
+
let repo;
|
|
225
|
+
let branch;
|
|
226
|
+
let path7;
|
|
227
|
+
let gitUrl;
|
|
228
|
+
if (i === 0) {
|
|
229
|
+
const host = match[1];
|
|
230
|
+
const repoPath = match[2].replace(/\.git$/, "");
|
|
231
|
+
branch = match[3];
|
|
232
|
+
path7 = match[4];
|
|
233
|
+
const parts = repoPath.split("/");
|
|
234
|
+
repo = parts.pop() || repoPath;
|
|
235
|
+
owner = parts.join("/") || repo;
|
|
236
|
+
gitUrl = `https://${host}/${repoPath}.git`;
|
|
237
|
+
} else if (i === 1) {
|
|
238
|
+
owner = match[1];
|
|
239
|
+
repo = match[2].replace(/\.git$/, "");
|
|
240
|
+
branch = "main";
|
|
241
|
+
gitUrl = `https://gitlab.com/${owner}/${repo}.git`;
|
|
242
|
+
} else {
|
|
243
|
+
const host = match[1];
|
|
244
|
+
const repoPath = match[2].replace(/\.git$/, "");
|
|
245
|
+
branch = "main";
|
|
246
|
+
const parts = repoPath.split("/");
|
|
247
|
+
repo = parts.pop() || repoPath;
|
|
248
|
+
owner = parts.join("/") || repo;
|
|
249
|
+
gitUrl = `https://${host}/${repoPath}.git`;
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
type: "gitlab",
|
|
253
|
+
owner,
|
|
254
|
+
repo,
|
|
255
|
+
branch,
|
|
256
|
+
path: path7,
|
|
257
|
+
gitUrl,
|
|
258
|
+
installUrl: gitUrl
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (url.startsWith("git+") || url.startsWith("git://")) {
|
|
263
|
+
return {
|
|
264
|
+
type: "other",
|
|
265
|
+
owner: "",
|
|
266
|
+
repo: "",
|
|
267
|
+
gitUrl: url.replace(/^git\+/, ""),
|
|
268
|
+
installUrl: url.startsWith("git+") ? url : `git+${url}`
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
function isGitUrl(str) {
|
|
274
|
+
return str.includes("github.com") || str.includes("gitlab.com") || str.includes("/-/tree/") || // GitLab web URL pattern (self-hosted)
|
|
275
|
+
str.startsWith("git@") || str.startsWith("git+") || str.startsWith("git://");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/utils/logger.ts
|
|
279
|
+
import chalk2 from "chalk";
|
|
280
|
+
import ora from "ora";
|
|
281
|
+
var Logger = class {
|
|
282
|
+
spinner = null;
|
|
283
|
+
info(message) {
|
|
284
|
+
if (this.spinner) this.spinner.stop();
|
|
285
|
+
console.log(chalk2.blue("\u2139"), message);
|
|
286
|
+
}
|
|
287
|
+
success(message) {
|
|
288
|
+
if (this.spinner) this.spinner.stop();
|
|
289
|
+
console.log(chalk2.green("\u2713"), message);
|
|
290
|
+
}
|
|
291
|
+
warn(message) {
|
|
292
|
+
if (this.spinner) this.spinner.stop();
|
|
293
|
+
console.log(chalk2.yellow("\u26A0"), message);
|
|
294
|
+
}
|
|
295
|
+
error(message) {
|
|
296
|
+
if (this.spinner) this.spinner.stop();
|
|
297
|
+
console.log(chalk2.red("\u2717"), message);
|
|
298
|
+
}
|
|
299
|
+
start(message) {
|
|
300
|
+
if (this.spinner) this.spinner.stop();
|
|
301
|
+
this.spinner = ora(message).start();
|
|
302
|
+
return this.spinner;
|
|
303
|
+
}
|
|
304
|
+
stopSpinner() {
|
|
305
|
+
if (this.spinner) {
|
|
306
|
+
this.spinner.stop();
|
|
307
|
+
this.spinner = null;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Update spinner text without stopping it
|
|
312
|
+
*/
|
|
313
|
+
updateSpinner(message) {
|
|
314
|
+
if (this.spinner) {
|
|
315
|
+
this.spinner.text = message;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Log info without stopping spinner (for background info)
|
|
320
|
+
*/
|
|
321
|
+
infoWithoutStop(message) {
|
|
322
|
+
if (this.spinner) {
|
|
323
|
+
const currentText = this.spinner.text;
|
|
324
|
+
this.spinner.stop();
|
|
325
|
+
console.log(chalk2.blue("\u2139"), message);
|
|
326
|
+
this.spinner.start(currentText);
|
|
327
|
+
} else {
|
|
328
|
+
console.log(chalk2.blue("\u2139"), message);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
var logger = new Logger();
|
|
333
|
+
|
|
192
334
|
// src/utils/paths.ts
|
|
335
|
+
import fs2 from "fs";
|
|
336
|
+
import path2 from "path";
|
|
193
337
|
function getSharedSkillPath(skillName) {
|
|
194
338
|
return path2.join(SHARED_SKILLS_DIR, skillName);
|
|
195
339
|
}
|
|
@@ -202,8 +346,8 @@ function getAgentSkillPaths(agentName, skillName, cwd) {
|
|
|
202
346
|
return skillsDirs.map((dir) => path2.join(dir, skillName));
|
|
203
347
|
}
|
|
204
348
|
function ensureSharedDir() {
|
|
205
|
-
if (!
|
|
206
|
-
|
|
349
|
+
if (!fs2.existsSync(SHARED_SKILLS_DIR)) {
|
|
350
|
+
fs2.mkdirSync(SHARED_SKILLS_DIR, { recursive: true });
|
|
207
351
|
}
|
|
208
352
|
}
|
|
209
353
|
function detectInstalledAgents(cwd) {
|
|
@@ -211,7 +355,7 @@ function detectInstalledAgents(cwd) {
|
|
|
211
355
|
try {
|
|
212
356
|
const skillsDirs = getAgentSkillsDirs(agent, cwd);
|
|
213
357
|
return skillsDirs.some((dir) => {
|
|
214
|
-
return
|
|
358
|
+
return fs2.existsSync(dir) || fs2.existsSync(path2.dirname(dir));
|
|
215
359
|
});
|
|
216
360
|
} catch {
|
|
217
361
|
return false;
|
|
@@ -230,32 +374,93 @@ function extractSkillName(nameOrPath) {
|
|
|
230
374
|
return path2.basename(nameOrPath);
|
|
231
375
|
}
|
|
232
376
|
|
|
233
|
-
// src/utils/
|
|
234
|
-
import
|
|
377
|
+
// src/utils/registry.ts
|
|
378
|
+
import { exec } from "child_process";
|
|
379
|
+
import fs3 from "fs";
|
|
235
380
|
import path3 from "path";
|
|
381
|
+
import { promisify } from "util";
|
|
382
|
+
var execAsync = promisify(exec);
|
|
383
|
+
function findNpmrc(startDir) {
|
|
384
|
+
let currentDir = path3.resolve(startDir);
|
|
385
|
+
while (currentDir !== path3.dirname(currentDir)) {
|
|
386
|
+
const npmrcPath = path3.join(currentDir, ".npmrc");
|
|
387
|
+
if (fs3.existsSync(npmrcPath)) {
|
|
388
|
+
return npmrcPath;
|
|
389
|
+
}
|
|
390
|
+
currentDir = path3.dirname(currentDir);
|
|
391
|
+
}
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
function parseNpmrc(npmrcPath) {
|
|
395
|
+
try {
|
|
396
|
+
const content = fs3.readFileSync(npmrcPath, "utf-8");
|
|
397
|
+
const lines = content.split("\n");
|
|
398
|
+
for (const line of lines) {
|
|
399
|
+
const trimmed = line.trim();
|
|
400
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
const registryMatch = trimmed.match(/^(?:@[^:]+:)?registry\s*=\s*(.+)$/);
|
|
404
|
+
if (registryMatch) {
|
|
405
|
+
return registryMatch[1].trim();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
} catch (error) {
|
|
409
|
+
}
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
async function getGlobalRegistry() {
|
|
413
|
+
try {
|
|
414
|
+
const { stdout } = await execAsync("npm config get registry");
|
|
415
|
+
const registry = stdout.trim();
|
|
416
|
+
if (registry && registry !== "undefined") {
|
|
417
|
+
return registry;
|
|
418
|
+
}
|
|
419
|
+
} catch (error) {
|
|
420
|
+
}
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
async function getRegistry(cwd = process.cwd()) {
|
|
424
|
+
const npmrcPath = findNpmrc(cwd);
|
|
425
|
+
if (npmrcPath) {
|
|
426
|
+
const registry = parseNpmrc(npmrcPath);
|
|
427
|
+
if (registry) {
|
|
428
|
+
return registry;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
const globalRegistry = await getGlobalRegistry();
|
|
432
|
+
if (globalRegistry) {
|
|
433
|
+
return globalRegistry;
|
|
434
|
+
}
|
|
435
|
+
return "https://registry.npmjs.org/";
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/utils/symlink.ts
|
|
439
|
+
import fs4 from "fs";
|
|
440
|
+
import path4 from "path";
|
|
236
441
|
function createSymlink(skillName, agent, cwd) {
|
|
237
442
|
const source = getSharedSkillPath(skillName);
|
|
238
|
-
if (!
|
|
443
|
+
if (!fs4.existsSync(source)) {
|
|
239
444
|
logger.error(`Skill not found: ${source}`);
|
|
240
445
|
return false;
|
|
241
446
|
}
|
|
242
447
|
const targets = getAgentSkillPaths(agent.name, skillName, cwd);
|
|
243
448
|
let successCount = 0;
|
|
244
449
|
for (const target of targets) {
|
|
245
|
-
const targetDir =
|
|
246
|
-
if (!
|
|
450
|
+
const targetDir = path4.dirname(target);
|
|
451
|
+
if (!fs4.existsSync(targetDir)) {
|
|
247
452
|
try {
|
|
248
|
-
|
|
453
|
+
fs4.mkdirSync(targetDir, { recursive: true });
|
|
249
454
|
} catch (error) {
|
|
250
455
|
logger.error(`Failed to create directory ${targetDir}: ${error.message}`);
|
|
251
456
|
continue;
|
|
252
457
|
}
|
|
253
458
|
}
|
|
254
|
-
if (
|
|
459
|
+
if (fs4.existsSync(target)) {
|
|
255
460
|
try {
|
|
256
|
-
const stats =
|
|
461
|
+
const stats = fs4.lstatSync(target);
|
|
257
462
|
if (stats.isSymbolicLink()) {
|
|
258
|
-
|
|
463
|
+
fs4.unlinkSync(target);
|
|
259
464
|
} else {
|
|
260
465
|
logger.warn(`Target exists but is not a symlink, skipping: ${target}`);
|
|
261
466
|
continue;
|
|
@@ -266,7 +471,7 @@ function createSymlink(skillName, agent, cwd) {
|
|
|
266
471
|
}
|
|
267
472
|
}
|
|
268
473
|
try {
|
|
269
|
-
|
|
474
|
+
fs4.symlinkSync(source, target, "dir");
|
|
270
475
|
successCount++;
|
|
271
476
|
} catch (error) {
|
|
272
477
|
logger.error(`Failed to create symlink at ${target}: ${error.message}`);
|
|
@@ -283,13 +488,13 @@ function removeSymlink(skillName, agent, cwd) {
|
|
|
283
488
|
const targets = getAgentSkillPaths(agent.name, skillName, cwd);
|
|
284
489
|
let removedCount = 0;
|
|
285
490
|
for (const target of targets) {
|
|
286
|
-
if (!
|
|
491
|
+
if (!fs4.existsSync(target)) {
|
|
287
492
|
continue;
|
|
288
493
|
}
|
|
289
494
|
try {
|
|
290
|
-
const stats =
|
|
495
|
+
const stats = fs4.lstatSync(target);
|
|
291
496
|
if (stats.isSymbolicLink()) {
|
|
292
|
-
|
|
497
|
+
fs4.unlinkSync(target);
|
|
293
498
|
removedCount++;
|
|
294
499
|
} else {
|
|
295
500
|
logger.warn(`Not a symlink: ${target}`);
|
|
@@ -307,7 +512,7 @@ function removeSymlink(skillName, agent, cwd) {
|
|
|
307
512
|
}
|
|
308
513
|
function isSymlink(filePath) {
|
|
309
514
|
try {
|
|
310
|
-
const stats =
|
|
515
|
+
const stats = fs4.lstatSync(filePath);
|
|
311
516
|
return stats.isSymbolicLink();
|
|
312
517
|
} catch {
|
|
313
518
|
return false;
|
|
@@ -315,164 +520,12 @@ function isSymlink(filePath) {
|
|
|
315
520
|
}
|
|
316
521
|
function readSymlink(filePath) {
|
|
317
522
|
try {
|
|
318
|
-
return
|
|
523
|
+
return fs4.readlinkSync(filePath);
|
|
319
524
|
} catch {
|
|
320
525
|
return null;
|
|
321
526
|
}
|
|
322
527
|
}
|
|
323
528
|
|
|
324
|
-
// src/utils/registry.ts
|
|
325
|
-
import { exec } from "child_process";
|
|
326
|
-
import { promisify } from "util";
|
|
327
|
-
import fs3 from "fs";
|
|
328
|
-
import path4 from "path";
|
|
329
|
-
var execAsync = promisify(exec);
|
|
330
|
-
function findNpmrc(startDir) {
|
|
331
|
-
let currentDir = path4.resolve(startDir);
|
|
332
|
-
while (currentDir !== path4.dirname(currentDir)) {
|
|
333
|
-
const npmrcPath = path4.join(currentDir, ".npmrc");
|
|
334
|
-
if (fs3.existsSync(npmrcPath)) {
|
|
335
|
-
return npmrcPath;
|
|
336
|
-
}
|
|
337
|
-
currentDir = path4.dirname(currentDir);
|
|
338
|
-
}
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
function parseNpmrc(npmrcPath) {
|
|
342
|
-
try {
|
|
343
|
-
const content = fs3.readFileSync(npmrcPath, "utf-8");
|
|
344
|
-
const lines = content.split("\n");
|
|
345
|
-
for (const line of lines) {
|
|
346
|
-
const trimmed = line.trim();
|
|
347
|
-
if (!trimmed || trimmed.startsWith("#")) {
|
|
348
|
-
continue;
|
|
349
|
-
}
|
|
350
|
-
const registryMatch = trimmed.match(/^(?:@[^:]+:)?registry\s*=\s*(.+)$/);
|
|
351
|
-
if (registryMatch) {
|
|
352
|
-
return registryMatch[1].trim();
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
} catch (error) {
|
|
356
|
-
}
|
|
357
|
-
return null;
|
|
358
|
-
}
|
|
359
|
-
async function getGlobalRegistry() {
|
|
360
|
-
try {
|
|
361
|
-
const { stdout } = await execAsync("npm config get registry");
|
|
362
|
-
const registry = stdout.trim();
|
|
363
|
-
if (registry && registry !== "undefined") {
|
|
364
|
-
return registry;
|
|
365
|
-
}
|
|
366
|
-
} catch (error) {
|
|
367
|
-
}
|
|
368
|
-
return null;
|
|
369
|
-
}
|
|
370
|
-
async function getRegistry(cwd = process.cwd()) {
|
|
371
|
-
const npmrcPath = findNpmrc(cwd);
|
|
372
|
-
if (npmrcPath) {
|
|
373
|
-
const registry = parseNpmrc(npmrcPath);
|
|
374
|
-
if (registry) {
|
|
375
|
-
return registry;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
const globalRegistry = await getGlobalRegistry();
|
|
379
|
-
if (globalRegistry) {
|
|
380
|
-
return globalRegistry;
|
|
381
|
-
}
|
|
382
|
-
return "https://registry.npmjs.org/";
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// src/utils/git.ts
|
|
386
|
-
function parseGitUrl(url) {
|
|
387
|
-
const githubPatterns = [
|
|
388
|
-
// https://github.com/owner/repo/tree/branch/path/to/dir
|
|
389
|
-
/^https?:\/\/github\.com\/([^\/]+)\/([^\/]+)\/tree\/([^\/]+)(?:\/(.+))?$/,
|
|
390
|
-
// https://github.com/owner/repo
|
|
391
|
-
/^https?:\/\/github\.com\/([^\/]+)\/([^\/]+)(?:\/)?$/,
|
|
392
|
-
// git@github.com:owner/repo.git
|
|
393
|
-
/^git@github\.com:([^\/]+)\/([^\/]+)(?:\.git)?$/
|
|
394
|
-
];
|
|
395
|
-
const gitlabPatterns = [
|
|
396
|
-
// https://gitlab.com/owner/repo/-/tree/branch/path/to/dir
|
|
397
|
-
/^https?:\/\/gitlab\.com\/([^\/]+)\/([^\/]+)\/-\/tree\/([^\/]+)(?:\/(.+))?$/,
|
|
398
|
-
// https://gitlab.com/owner/repo
|
|
399
|
-
/^https?:\/\/gitlab\.com\/([^\/]+)\/([^\/]+)(?:\/)?$/,
|
|
400
|
-
// git@gitlab.com:owner/repo.git
|
|
401
|
-
/^git@gitlab\.com:([^\/]+)\/([^\/]+)(?:\.git)?$/
|
|
402
|
-
];
|
|
403
|
-
for (let i = 0; i < githubPatterns.length; i++) {
|
|
404
|
-
const pattern = githubPatterns[i];
|
|
405
|
-
const match = url.match(pattern);
|
|
406
|
-
if (match) {
|
|
407
|
-
const owner = match[1];
|
|
408
|
-
const repo = match[2].replace(/\.git$/, "");
|
|
409
|
-
let branch;
|
|
410
|
-
let path7;
|
|
411
|
-
if (i === 0) {
|
|
412
|
-
branch = match[3];
|
|
413
|
-
path7 = match[4];
|
|
414
|
-
} else if (i === 1) {
|
|
415
|
-
branch = "main";
|
|
416
|
-
} else {
|
|
417
|
-
branch = "main";
|
|
418
|
-
}
|
|
419
|
-
const gitUrl = `https://github.com/${owner}/${repo}.git`;
|
|
420
|
-
return {
|
|
421
|
-
type: "github",
|
|
422
|
-
owner,
|
|
423
|
-
repo,
|
|
424
|
-
branch,
|
|
425
|
-
path: path7,
|
|
426
|
-
gitUrl,
|
|
427
|
-
installUrl: gitUrl
|
|
428
|
-
// Will be used for git clone
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
for (let i = 0; i < gitlabPatterns.length; i++) {
|
|
433
|
-
const pattern = gitlabPatterns[i];
|
|
434
|
-
const match = url.match(pattern);
|
|
435
|
-
if (match) {
|
|
436
|
-
const owner = match[1];
|
|
437
|
-
const repo = match[2].replace(/\.git$/, "");
|
|
438
|
-
let branch;
|
|
439
|
-
let path7;
|
|
440
|
-
if (i === 0) {
|
|
441
|
-
branch = match[3];
|
|
442
|
-
path7 = match[4];
|
|
443
|
-
} else if (i === 1) {
|
|
444
|
-
branch = "main";
|
|
445
|
-
} else {
|
|
446
|
-
branch = "main";
|
|
447
|
-
}
|
|
448
|
-
const gitUrl = `https://gitlab.com/${owner}/${repo}.git`;
|
|
449
|
-
return {
|
|
450
|
-
type: "gitlab",
|
|
451
|
-
owner,
|
|
452
|
-
repo,
|
|
453
|
-
branch,
|
|
454
|
-
path: path7,
|
|
455
|
-
gitUrl,
|
|
456
|
-
installUrl: gitUrl
|
|
457
|
-
// Will be used for git clone
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
if (url.startsWith("git+") || url.startsWith("git://")) {
|
|
462
|
-
return {
|
|
463
|
-
type: "other",
|
|
464
|
-
owner: "",
|
|
465
|
-
repo: "",
|
|
466
|
-
gitUrl: url.replace(/^git\+/, ""),
|
|
467
|
-
installUrl: url.startsWith("git+") ? url : `git+${url}`
|
|
468
|
-
};
|
|
469
|
-
}
|
|
470
|
-
return null;
|
|
471
|
-
}
|
|
472
|
-
function isGitUrl(str) {
|
|
473
|
-
return str.includes("github.com") || str.includes("gitlab.com") || str.startsWith("git@") || str.startsWith("git+") || str.startsWith("git://");
|
|
474
|
-
}
|
|
475
|
-
|
|
476
529
|
// src/commands/install.ts
|
|
477
530
|
var execAsync2 = promisify2(exec2);
|
|
478
531
|
async function execWithTimeout(command, timeout = 12e4, env) {
|
|
@@ -484,10 +537,7 @@ async function execWithTimeout(command, timeout = 12e4, env) {
|
|
|
484
537
|
});
|
|
485
538
|
try {
|
|
486
539
|
const execOptions = env ? { env } : {};
|
|
487
|
-
const result = await Promise.race([
|
|
488
|
-
execAsync2(command, execOptions),
|
|
489
|
-
timeoutPromise
|
|
490
|
-
]);
|
|
540
|
+
const result = await Promise.race([execAsync2(command, execOptions), timeoutPromise]);
|
|
491
541
|
if (timeoutId) {
|
|
492
542
|
clearTimeout(timeoutId);
|
|
493
543
|
}
|
|
@@ -525,7 +575,7 @@ async function install(skillNameOrPath, options = {}) {
|
|
|
525
575
|
logger.infoWithoutStop(`Path: ${gitInfo.path}`);
|
|
526
576
|
}
|
|
527
577
|
logger.infoWithoutStop(`Timeout: ${timeout / 1e3}s`);
|
|
528
|
-
|
|
578
|
+
fs5.mkdirSync(tempDir, { recursive: true });
|
|
529
579
|
const branchFlag = gitInfo.branch ? `-b ${gitInfo.branch}` : "";
|
|
530
580
|
const cloneCommand = branchFlag ? `git clone ${branchFlag} ${gitInfo.gitUrl} ${cloneDir} --depth 1 --quiet` : `git clone ${gitInfo.gitUrl} ${cloneDir} --depth 1 --quiet`;
|
|
531
581
|
try {
|
|
@@ -547,7 +597,7 @@ async function install(skillNameOrPath, options = {}) {
|
|
|
547
597
|
}
|
|
548
598
|
if (gitInfo.path) {
|
|
549
599
|
skillPath = path5.join(cloneDir, gitInfo.path);
|
|
550
|
-
if (!
|
|
600
|
+
if (!fs5.existsSync(skillPath)) {
|
|
551
601
|
logger.error(`Path not found in repository: ${gitInfo.path}`);
|
|
552
602
|
logger.info(`Repository cloned to: ${cloneDir}`);
|
|
553
603
|
process.exit(1);
|
|
@@ -556,7 +606,7 @@ async function install(skillNameOrPath, options = {}) {
|
|
|
556
606
|
skillPath = cloneDir;
|
|
557
607
|
}
|
|
558
608
|
const skillMdPath = path5.join(skillPath, "SKILL.md");
|
|
559
|
-
if (!
|
|
609
|
+
if (!fs5.existsSync(skillMdPath)) {
|
|
560
610
|
logger.warn(`Warning: SKILL.md not found in ${skillPath}`);
|
|
561
611
|
logger.info("The directory may not be a valid skill package");
|
|
562
612
|
}
|
|
@@ -578,16 +628,16 @@ Tried to clone: ${gitInfo.gitUrl}`);
|
|
|
578
628
|
}
|
|
579
629
|
process.exit(1);
|
|
580
630
|
}
|
|
581
|
-
} else if (options.link ||
|
|
631
|
+
} else if (options.link || fs5.existsSync(skillNameOrPath)) {
|
|
582
632
|
skillPath = path5.resolve(skillNameOrPath);
|
|
583
|
-
if (!
|
|
633
|
+
if (!fs5.existsSync(skillPath)) {
|
|
584
634
|
logger.error(`Path not found: ${skillPath}`);
|
|
585
635
|
process.exit(1);
|
|
586
636
|
}
|
|
587
637
|
const pkgPath = path5.join(skillPath, "package.json");
|
|
588
|
-
if (
|
|
638
|
+
if (fs5.existsSync(pkgPath)) {
|
|
589
639
|
try {
|
|
590
|
-
const pkg = JSON.parse(
|
|
640
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
591
641
|
skillName = extractSkillName(pkg.name);
|
|
592
642
|
} catch {
|
|
593
643
|
skillName = extractSkillName(path5.basename(skillPath));
|
|
@@ -604,12 +654,12 @@ Tried to clone: ${gitInfo.gitUrl}`);
|
|
|
604
654
|
const spinner = logger.start(`Installing ${skillNameOrPath}...`);
|
|
605
655
|
logger.infoWithoutStop(`Registry: ${registry}`);
|
|
606
656
|
logger.infoWithoutStop(`Timeout: ${timeout / 1e3}s`);
|
|
607
|
-
|
|
657
|
+
fs5.mkdirSync(tempDir, { recursive: true });
|
|
608
658
|
logger.updateSpinner(`Downloading ${skillNameOrPath} from ${registry}...`);
|
|
609
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE ||
|
|
659
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || os3.homedir();
|
|
610
660
|
const npmCacheDir = path5.join(homeDir, ".npm");
|
|
611
661
|
const npmConfigPrefix = path5.join(homeDir, ".npm-global");
|
|
612
|
-
|
|
662
|
+
fs5.mkdirSync(npmCacheDir, { recursive: true });
|
|
613
663
|
const installCommand = `npm install ${skillNameOrPath} --prefix ${tempDir} --registry=${registry} --no-save --silent --no-bin-links --prefer-offline`;
|
|
614
664
|
const env = {
|
|
615
665
|
...process.env,
|
|
@@ -634,7 +684,9 @@ Tried to clone: ${gitInfo.gitUrl}`);
|
|
|
634
684
|
logger.info("");
|
|
635
685
|
logger.info("Suggestions:");
|
|
636
686
|
logger.info(` - Try again: eskill add ${skillNameOrPath}`);
|
|
637
|
-
logger.info(
|
|
687
|
+
logger.info(
|
|
688
|
+
` - Use a different registry: eskill add ${skillNameOrPath} --registry=https://registry.npmjs.org/`
|
|
689
|
+
);
|
|
638
690
|
logger.info(` - Increase timeout: eskill add ${skillNameOrPath} --timeout=300000`);
|
|
639
691
|
} else if (errorMessage.includes("EACCES") || errorMessage.includes("permission denied") || errorMessage.includes("Permission denied")) {
|
|
640
692
|
logger.error("Permission denied error detected");
|
|
@@ -673,7 +725,9 @@ Tried to clone: ${gitInfo.gitUrl}`);
|
|
|
673
725
|
logger.info("Please check:");
|
|
674
726
|
logger.info(" - Your internet connection");
|
|
675
727
|
logger.info(` - Registry accessibility: ${registry}`);
|
|
676
|
-
logger.info(
|
|
728
|
+
logger.info(
|
|
729
|
+
` - Try a different registry: eskill add ${skillNameOrPath} --registry=https://registry.npmjs.org/`
|
|
730
|
+
);
|
|
677
731
|
} else {
|
|
678
732
|
logger.error(`Failed to download: ${errorMessage}`);
|
|
679
733
|
logger.info("");
|
|
@@ -686,7 +740,7 @@ Tried to clone: ${gitInfo.gitUrl}`);
|
|
|
686
740
|
process.exit(1);
|
|
687
741
|
}
|
|
688
742
|
skillPath = path5.join(tempDir, "node_modules", skillNameOrPath);
|
|
689
|
-
if (!
|
|
743
|
+
if (!fs5.existsSync(skillPath)) {
|
|
690
744
|
logger.error(`Failed to download package: ${skillNameOrPath}`);
|
|
691
745
|
process.exit(1);
|
|
692
746
|
}
|
|
@@ -696,10 +750,10 @@ Tried to clone: ${gitInfo.gitUrl}`);
|
|
|
696
750
|
}
|
|
697
751
|
}
|
|
698
752
|
const targetPath = getSharedSkillPath(skillName);
|
|
699
|
-
if (
|
|
753
|
+
if (fs5.existsSync(targetPath)) {
|
|
700
754
|
if (options.force) {
|
|
701
755
|
logger.warn("Removing existing installation...");
|
|
702
|
-
|
|
756
|
+
fs5.rmSync(targetPath, { recursive: true, force: true });
|
|
703
757
|
} else {
|
|
704
758
|
logger.error(`Skill already exists: ${targetPath}`);
|
|
705
759
|
logger.info("Use --force to overwrite");
|
|
@@ -707,7 +761,7 @@ Tried to clone: ${gitInfo.gitUrl}`);
|
|
|
707
761
|
}
|
|
708
762
|
}
|
|
709
763
|
if (options.link) {
|
|
710
|
-
|
|
764
|
+
fs5.symlinkSync(skillPath, targetPath, "dir");
|
|
711
765
|
logger.success(`Linked to shared directory (dev mode)`);
|
|
712
766
|
} else {
|
|
713
767
|
copyDir(skillPath, targetPath);
|
|
@@ -753,8 +807,8 @@ Linked to ${successCount}/${targetAgents.length} agents`);
|
|
|
753
807
|
}
|
|
754
808
|
}
|
|
755
809
|
function copyDir(src, dest) {
|
|
756
|
-
|
|
757
|
-
const entries =
|
|
810
|
+
fs5.mkdirSync(dest, { recursive: true });
|
|
811
|
+
const entries = fs5.readdirSync(src, { withFileTypes: true });
|
|
758
812
|
for (const entry of entries) {
|
|
759
813
|
const srcPath = path5.join(src, entry.name);
|
|
760
814
|
const destPath = path5.join(dest, entry.name);
|
|
@@ -764,44 +818,44 @@ function copyDir(src, dest) {
|
|
|
764
818
|
if (entry.isDirectory()) {
|
|
765
819
|
copyDir(srcPath, destPath);
|
|
766
820
|
} else {
|
|
767
|
-
|
|
821
|
+
fs5.copyFileSync(srcPath, destPath);
|
|
768
822
|
}
|
|
769
823
|
}
|
|
770
824
|
}
|
|
771
825
|
|
|
772
826
|
// src/commands/list.ts
|
|
773
|
-
import
|
|
827
|
+
import fs6 from "fs";
|
|
774
828
|
import path6 from "path";
|
|
775
|
-
import
|
|
829
|
+
import chalk3 from "chalk";
|
|
776
830
|
function list() {
|
|
777
|
-
if (!
|
|
831
|
+
if (!fs6.existsSync(SHARED_SKILLS_DIR)) {
|
|
778
832
|
logger.info("No skills installed");
|
|
779
833
|
logger.info(`
|
|
780
|
-
To install a skill, run: ${
|
|
834
|
+
To install a skill, run: ${chalk3.cyan("eskill install <skill-name>")}`);
|
|
781
835
|
return;
|
|
782
836
|
}
|
|
783
|
-
const skills =
|
|
837
|
+
const skills = fs6.readdirSync(SHARED_SKILLS_DIR);
|
|
784
838
|
if (skills.length === 0) {
|
|
785
839
|
logger.info("No skills installed");
|
|
786
840
|
logger.info(`
|
|
787
|
-
To install a skill, run: ${
|
|
841
|
+
To install a skill, run: ${chalk3.cyan("eskill install <skill-name>")}`);
|
|
788
842
|
return;
|
|
789
843
|
}
|
|
790
|
-
console.log(
|
|
844
|
+
console.log(chalk3.bold(`
|
|
791
845
|
Installed skills in ${SHARED_SKILLS_DIR}:
|
|
792
846
|
`));
|
|
793
847
|
for (const skill of skills) {
|
|
794
848
|
const skillPath = path6.join(SHARED_SKILLS_DIR, skill);
|
|
795
849
|
try {
|
|
796
|
-
const stats =
|
|
850
|
+
const stats = fs6.lstatSync(skillPath);
|
|
797
851
|
if (!stats.isDirectory() && !stats.isSymbolicLink()) {
|
|
798
852
|
continue;
|
|
799
853
|
}
|
|
800
854
|
let version2 = "unknown";
|
|
801
855
|
const pkgPath = path6.join(skillPath, "package.json");
|
|
802
|
-
if (
|
|
856
|
+
if (fs6.existsSync(pkgPath)) {
|
|
803
857
|
try {
|
|
804
|
-
const pkgContent =
|
|
858
|
+
const pkgContent = fs6.readFileSync(pkgPath, "utf-8");
|
|
805
859
|
const pkg = JSON.parse(pkgContent);
|
|
806
860
|
if (pkg.version && typeof pkg.version === "string") {
|
|
807
861
|
version2 = pkg.version;
|
|
@@ -811,9 +865,9 @@ Installed skills in ${SHARED_SKILLS_DIR}:
|
|
|
811
865
|
}
|
|
812
866
|
if (version2 === "unknown") {
|
|
813
867
|
const skillMdPath = path6.join(skillPath, "SKILL.md");
|
|
814
|
-
if (
|
|
868
|
+
if (fs6.existsSync(skillMdPath)) {
|
|
815
869
|
try {
|
|
816
|
-
const skillMdContent =
|
|
870
|
+
const skillMdContent = fs6.readFileSync(skillMdPath, "utf-8");
|
|
817
871
|
const frontmatterMatch = skillMdContent.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
818
872
|
if (frontmatterMatch) {
|
|
819
873
|
const frontmatter = frontmatterMatch[1];
|
|
@@ -831,8 +885,8 @@ Installed skills in ${SHARED_SKILLS_DIR}:
|
|
|
831
885
|
const targetPath = readSymlink(skillPath);
|
|
832
886
|
if (targetPath) {
|
|
833
887
|
const targetPkgPath = path6.join(targetPath, "package.json");
|
|
834
|
-
if (
|
|
835
|
-
const pkg = JSON.parse(
|
|
888
|
+
if (fs6.existsSync(targetPkgPath)) {
|
|
889
|
+
const pkg = JSON.parse(fs6.readFileSync(targetPkgPath, "utf-8"));
|
|
836
890
|
if (pkg.version && typeof pkg.version === "string") {
|
|
837
891
|
version2 = pkg.version;
|
|
838
892
|
}
|
|
@@ -842,16 +896,16 @@ Installed skills in ${SHARED_SKILLS_DIR}:
|
|
|
842
896
|
}
|
|
843
897
|
}
|
|
844
898
|
const isDev = isSymlink(skillPath);
|
|
845
|
-
const devTag = isDev ?
|
|
846
|
-
const versionDisplay = version2 !== "unknown" ?
|
|
847
|
-
console.log(
|
|
899
|
+
const devTag = isDev ? chalk3.yellow(" (dev)") : "";
|
|
900
|
+
const versionDisplay = version2 !== "unknown" ? chalk3.gray(`(v${version2})`) : "";
|
|
901
|
+
console.log(chalk3.green("\u{1F4E6}") + ` ${chalk3.bold(skill)}${versionDisplay ? " " + versionDisplay : ""}${devTag}`);
|
|
848
902
|
const cwd = process.cwd();
|
|
849
903
|
const linkedAgents = [];
|
|
850
904
|
for (const agent of AGENTS) {
|
|
851
905
|
const skillsDirs = getAgentSkillsDirs(agent, cwd);
|
|
852
906
|
const hasSymlink = skillsDirs.some((dir) => {
|
|
853
907
|
const agentSkillPath = path6.join(dir, skill);
|
|
854
|
-
if (
|
|
908
|
+
if (fs6.existsSync(agentSkillPath) && isSymlink(agentSkillPath)) {
|
|
855
909
|
const target = readSymlink(agentSkillPath);
|
|
856
910
|
return target === skillPath;
|
|
857
911
|
}
|
|
@@ -862,29 +916,28 @@ Installed skills in ${SHARED_SKILLS_DIR}:
|
|
|
862
916
|
}
|
|
863
917
|
}
|
|
864
918
|
if (linkedAgents.length > 0) {
|
|
865
|
-
console.log(
|
|
919
|
+
console.log(chalk3.gray(` \u2192 Linked to: ${linkedAgents.join(", ")}`));
|
|
866
920
|
} else {
|
|
867
|
-
console.log(
|
|
921
|
+
console.log(chalk3.yellow(` \u2192 Not linked to any agent`));
|
|
868
922
|
}
|
|
869
923
|
if (isDev) {
|
|
870
924
|
const target = readSymlink(skillPath);
|
|
871
925
|
if (target) {
|
|
872
|
-
console.log(
|
|
926
|
+
console.log(chalk3.gray(` \u2192 Source: ${target}`));
|
|
873
927
|
}
|
|
874
928
|
}
|
|
875
929
|
console.log("");
|
|
876
930
|
} catch (error) {
|
|
877
|
-
continue;
|
|
878
931
|
}
|
|
879
932
|
}
|
|
880
933
|
}
|
|
881
934
|
|
|
882
935
|
// src/commands/remove.ts
|
|
883
|
-
import
|
|
936
|
+
import fs7 from "fs";
|
|
884
937
|
async function remove(skillName, options = {}) {
|
|
885
938
|
const extractedName = extractSkillName(skillName);
|
|
886
939
|
const sharedPath = getSharedSkillPath(extractedName);
|
|
887
|
-
const skillExists =
|
|
940
|
+
const skillExists = fs7.existsSync(sharedPath);
|
|
888
941
|
if (!skillExists) {
|
|
889
942
|
logger.error(`Skill not found: ${extractedName}`);
|
|
890
943
|
logger.info(`Location: ${sharedPath}`);
|
|
@@ -920,12 +973,12 @@ Installed agents: ${installedAgents.map((a) => a.name).join(", ")}`);
|
|
|
920
973
|
}
|
|
921
974
|
logger.info("Removing skill from shared directory...");
|
|
922
975
|
try {
|
|
923
|
-
const stats =
|
|
976
|
+
const stats = fs7.lstatSync(sharedPath);
|
|
924
977
|
if (stats.isSymbolicLink()) {
|
|
925
|
-
|
|
978
|
+
fs7.unlinkSync(sharedPath);
|
|
926
979
|
logger.success("Removed symlink from shared directory");
|
|
927
980
|
} else {
|
|
928
|
-
|
|
981
|
+
fs7.rmSync(sharedPath, { recursive: true, force: true });
|
|
929
982
|
logger.success("Removed skill from shared directory");
|
|
930
983
|
}
|
|
931
984
|
} catch (error) {
|
|
@@ -936,7 +989,7 @@ Installed agents: ${installedAgents.map((a) => a.name).join(", ")}`);
|
|
|
936
989
|
for (const agent of AGENTS) {
|
|
937
990
|
const agentSkillPaths = getAgentSkillPaths(agent.name, extractedName, cwd);
|
|
938
991
|
const hasSymlink = agentSkillPaths.some((path7) => {
|
|
939
|
-
return
|
|
992
|
+
return fs7.existsSync(path7) && isSymlink(path7);
|
|
940
993
|
});
|
|
941
994
|
if (hasSymlink) {
|
|
942
995
|
remainingSymlinks.push(agent.displayName);
|
|
@@ -954,44 +1007,6 @@ Installed agents: ${installedAgents.map((a) => a.name).join(", ")}`);
|
|
|
954
1007
|
}
|
|
955
1008
|
}
|
|
956
1009
|
|
|
957
|
-
// src/commands/agents.ts
|
|
958
|
-
import chalk3 from "chalk";
|
|
959
|
-
import fs7 from "fs";
|
|
960
|
-
import os3 from "os";
|
|
961
|
-
function agents() {
|
|
962
|
-
const cwd = process.cwd();
|
|
963
|
-
console.log(chalk3.bold("\n\u{1F4CB} Supported AI Agents:\n"));
|
|
964
|
-
for (const agent of AGENTS) {
|
|
965
|
-
const skillsDirs = getAgentSkillsDirs(agent, cwd);
|
|
966
|
-
const dirsInfo = skillsDirs.length > 1 ? ` (${skillsDirs.length} directories)` : "";
|
|
967
|
-
console.log(chalk3.bold(agent.displayName));
|
|
968
|
-
console.log(chalk3.gray(` Name: ${agent.name}`));
|
|
969
|
-
if (skillsDirs.length === 0) {
|
|
970
|
-
console.log(chalk3.yellow(" \u26A0\uFE0F No directories configured"));
|
|
971
|
-
} else {
|
|
972
|
-
console.log(chalk3.gray(` Directories${dirsInfo}:`));
|
|
973
|
-
for (const dir of skillsDirs) {
|
|
974
|
-
const exists = fs7.existsSync(dir);
|
|
975
|
-
const status = exists ? chalk3.green("\u2713") : chalk3.gray("\u25CB");
|
|
976
|
-
const pathColor = exists ? chalk3.white : chalk3.gray;
|
|
977
|
-
console.log(` ${status} ${pathColor(dir)}`);
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
console.log("");
|
|
981
|
-
}
|
|
982
|
-
console.log(chalk3.bold("\u{1F4E6} Shared Skills Directory:"));
|
|
983
|
-
const sharedExists = fs7.existsSync(SHARED_SKILLS_DIR);
|
|
984
|
-
const sharedStatus = sharedExists ? chalk3.green("\u2713") : chalk3.gray("\u25CB");
|
|
985
|
-
const sharedPathColor = sharedExists ? chalk3.white : chalk3.gray;
|
|
986
|
-
console.log(` ${sharedStatus} ${sharedPathColor(SHARED_SKILLS_DIR)}`);
|
|
987
|
-
console.log("");
|
|
988
|
-
const home = os3.homedir();
|
|
989
|
-
if (cwd !== home) {
|
|
990
|
-
console.log(chalk3.gray(`Current working directory: ${cwd}`));
|
|
991
|
-
console.log(chalk3.gray("(Project-level directories are shown above)\n"));
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
|
|
995
1010
|
// src/index.ts
|
|
996
1011
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
997
1012
|
var __dirname2 = dirname(__filename2);
|
|
@@ -1000,7 +1015,11 @@ var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
|
1000
1015
|
var version = packageJson.version;
|
|
1001
1016
|
var program = new Command();
|
|
1002
1017
|
program.name("eskill").description("Unified CLI tool for managing AI agent skills").version(version);
|
|
1003
|
-
program.command("install <skill>").alias("add").description("Install a skill from NPM, Git URL, or local directory").option("-a, --agent <name>", "Install for specific agent (claude, cursor, windsurf, or all)").option("-l, --link", "Dev mode: symlink from local directory").option("-f, --force", "Force reinstall if already exists").option("-r, --registry <url>", "NPM registry URL (overrides .npmrc and global config)").option(
|
|
1018
|
+
program.command("install <skill>").alias("add").description("Install a skill from NPM, Git URL, or local directory").option("-a, --agent <name>", "Install for specific agent (claude, cursor, windsurf, or all)").option("-l, --link", "Dev mode: symlink from local directory").option("-f, --force", "Force reinstall if already exists").option("-r, --registry <url>", "NPM registry URL (overrides .npmrc and global config)").option(
|
|
1019
|
+
"-t, --timeout <ms>",
|
|
1020
|
+
"Timeout in milliseconds (default: 180000 for npm, 120000 for git)",
|
|
1021
|
+
(value) => Number.parseInt(value, 10)
|
|
1022
|
+
).action(async (skill, options) => {
|
|
1004
1023
|
try {
|
|
1005
1024
|
await install(skill, options);
|
|
1006
1025
|
} catch (error) {
|