@empjs/skill 1.0.5 → 1.0.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/README.md +19 -5
- package/dist/index.cjs +472 -390
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +471 -389
- package/dist/index.js.map +1 -1
- package/package.json +2 -6
package/dist/index.js
CHANGED
|
@@ -3,80 +3,20 @@
|
|
|
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 fs2 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
|
|
15
|
+
import fs from "fs";
|
|
77
16
|
import os from "os";
|
|
78
17
|
import path from "path";
|
|
79
18
|
var HOME = os.homedir();
|
|
19
|
+
var CONFIG_HOME = process.env.XDG_CONFIG_HOME || path.join(HOME, ".config");
|
|
80
20
|
function getAgentSkillsDirs(agent, cwd) {
|
|
81
21
|
if (agent.skillsDirs) {
|
|
82
22
|
if (typeof agent.skillsDirs === "function") {
|
|
@@ -91,21 +31,44 @@ function getAgentSkillsDirs(agent, cwd) {
|
|
|
91
31
|
}
|
|
92
32
|
var AGENTS = [
|
|
93
33
|
{
|
|
94
|
-
name: "
|
|
95
|
-
displayName: "
|
|
96
|
-
|
|
34
|
+
name: "amp",
|
|
35
|
+
displayName: "AMP",
|
|
36
|
+
skillsDirs: (cwd) => {
|
|
37
|
+
const dirs = [path.join(CONFIG_HOME, "agents", "skills")];
|
|
38
|
+
if (cwd) dirs.push(path.join(cwd, ".agents", "skills"));
|
|
39
|
+
return dirs;
|
|
40
|
+
},
|
|
97
41
|
enabled: true
|
|
98
42
|
},
|
|
99
43
|
{
|
|
100
|
-
name: "
|
|
101
|
-
displayName: "
|
|
102
|
-
|
|
44
|
+
name: "antigravity",
|
|
45
|
+
displayName: "Antigravity",
|
|
46
|
+
skillsDirs: (cwd) => {
|
|
47
|
+
const dirs = [path.join(HOME, ".gemini", "antigravity", "skills")];
|
|
48
|
+
if (cwd) dirs.push(path.join(cwd, ".agent", "skills"));
|
|
49
|
+
if (cwd) dirs.push(path.join(cwd, ".shared", "skills"));
|
|
50
|
+
return dirs;
|
|
51
|
+
},
|
|
103
52
|
enabled: true
|
|
104
53
|
},
|
|
105
54
|
{
|
|
106
|
-
name: "
|
|
107
|
-
displayName: "
|
|
108
|
-
skillsDir: path.join(HOME, ".
|
|
55
|
+
name: "claude",
|
|
56
|
+
displayName: "Claude Code",
|
|
57
|
+
skillsDir: path.join(process.env.CLAUDE_CONFIG_DIR?.trim() || path.join(HOME, ".claude"), "skills"),
|
|
58
|
+
enabled: true
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "clawdbot",
|
|
62
|
+
displayName: "ClawdBot",
|
|
63
|
+
skillsDirs: () => {
|
|
64
|
+
const openclaw = path.join(HOME, ".openclaw", "skills");
|
|
65
|
+
const clawdbot = path.join(HOME, ".clawdbot", "skills");
|
|
66
|
+
const moltbot = path.join(HOME, ".moltbot", "skills");
|
|
67
|
+
if (fs.existsSync(path.join(HOME, ".openclaw"))) return [openclaw];
|
|
68
|
+
if (fs.existsSync(path.join(HOME, ".clawdbot"))) return [clawdbot];
|
|
69
|
+
if (fs.existsSync(path.join(HOME, ".moltbot"))) return [moltbot];
|
|
70
|
+
return [openclaw];
|
|
71
|
+
},
|
|
109
72
|
enabled: true
|
|
110
73
|
},
|
|
111
74
|
{
|
|
@@ -114,9 +77,27 @@ var AGENTS = [
|
|
|
114
77
|
skillsDir: path.join(HOME, ".cline", "skills"),
|
|
115
78
|
enabled: true
|
|
116
79
|
},
|
|
80
|
+
{
|
|
81
|
+
name: "codex",
|
|
82
|
+
displayName: "Codex",
|
|
83
|
+
skillsDir: path.join(process.env.CODEX_HOME?.trim() || path.join(HOME, ".codex"), "skills"),
|
|
84
|
+
enabled: true
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "cursor",
|
|
88
|
+
displayName: "Cursor",
|
|
89
|
+
skillsDir: path.join(HOME, ".cursor", "skills"),
|
|
90
|
+
enabled: true
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "droid",
|
|
94
|
+
displayName: "Droid",
|
|
95
|
+
skillsDir: path.join(HOME, ".factory", "skills"),
|
|
96
|
+
enabled: true
|
|
97
|
+
},
|
|
117
98
|
{
|
|
118
99
|
name: "gemini",
|
|
119
|
-
displayName: "Gemini
|
|
100
|
+
displayName: "Gemini",
|
|
120
101
|
skillsDir: path.join(HOME, ".gemini", "skills"),
|
|
121
102
|
enabled: true
|
|
122
103
|
},
|
|
@@ -127,47 +108,31 @@ var AGENTS = [
|
|
|
127
108
|
enabled: true
|
|
128
109
|
},
|
|
129
110
|
{
|
|
130
|
-
name: "
|
|
131
|
-
displayName: "
|
|
132
|
-
skillsDir: path.join(
|
|
111
|
+
name: "goose",
|
|
112
|
+
displayName: "Goose",
|
|
113
|
+
skillsDir: path.join(CONFIG_HOME, "goose", "skills"),
|
|
133
114
|
enabled: true
|
|
134
115
|
},
|
|
135
116
|
{
|
|
136
|
-
name: "
|
|
137
|
-
displayName: "
|
|
138
|
-
|
|
139
|
-
const dirs = [];
|
|
140
|
-
dirs.push(path.join(HOME, ".gemini", "antigravity", "skills"));
|
|
141
|
-
if (cwd) {
|
|
142
|
-
dirs.push(path.join(cwd, ".agent", "skills"));
|
|
143
|
-
}
|
|
144
|
-
if (cwd) {
|
|
145
|
-
dirs.push(path.join(cwd, ".shared", "skills"));
|
|
146
|
-
}
|
|
147
|
-
return dirs;
|
|
148
|
-
},
|
|
117
|
+
name: "kilo",
|
|
118
|
+
displayName: "Kilo Code",
|
|
119
|
+
skillsDir: path.join(HOME, ".kilocode", "skills"),
|
|
149
120
|
enabled: true
|
|
150
121
|
},
|
|
151
122
|
{
|
|
152
123
|
name: "kiro",
|
|
153
|
-
displayName: "Kiro",
|
|
124
|
+
displayName: "Kiro CLI",
|
|
154
125
|
skillsDir: path.join(HOME, ".kiro", "skills"),
|
|
155
126
|
enabled: true
|
|
156
127
|
},
|
|
157
128
|
{
|
|
158
|
-
name: "
|
|
159
|
-
displayName: "
|
|
160
|
-
skillsDir: path.join(
|
|
161
|
-
enabled: true
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
name: "qoder",
|
|
165
|
-
displayName: "Qoder",
|
|
166
|
-
skillsDir: path.join(HOME, ".qoder", "skills"),
|
|
129
|
+
name: "opencode",
|
|
130
|
+
displayName: "OpenCode",
|
|
131
|
+
skillsDir: path.join(CONFIG_HOME, "opencode", "skills"),
|
|
167
132
|
enabled: true
|
|
168
133
|
},
|
|
169
134
|
{
|
|
170
|
-
name: "
|
|
135
|
+
name: "roo",
|
|
171
136
|
displayName: "Roo Code",
|
|
172
137
|
skillsDir: path.join(HOME, ".roo", "skills"),
|
|
173
138
|
enabled: true
|
|
@@ -178,18 +143,246 @@ var AGENTS = [
|
|
|
178
143
|
skillsDir: path.join(HOME, ".trae", "skills"),
|
|
179
144
|
enabled: true
|
|
180
145
|
},
|
|
146
|
+
{
|
|
147
|
+
name: "windsurf",
|
|
148
|
+
displayName: "Windsurf",
|
|
149
|
+
skillsDirs: () => {
|
|
150
|
+
const dirs = [];
|
|
151
|
+
dirs.push(path.join(HOME, ".windsurf", "skills"));
|
|
152
|
+
dirs.push(path.join(HOME, ".codeium", "windsurf", "skills"));
|
|
153
|
+
return dirs;
|
|
154
|
+
},
|
|
155
|
+
enabled: true
|
|
156
|
+
},
|
|
157
|
+
// Additional agents
|
|
158
|
+
{
|
|
159
|
+
name: "qoder",
|
|
160
|
+
displayName: "Qoder",
|
|
161
|
+
skillsDir: path.join(HOME, ".qoder", "skills"),
|
|
162
|
+
enabled: true
|
|
163
|
+
},
|
|
181
164
|
{
|
|
182
165
|
name: "continue",
|
|
183
166
|
displayName: "Continue",
|
|
184
167
|
skillsDir: path.join(HOME, ".continue", "skills"),
|
|
185
168
|
enabled: true
|
|
186
169
|
}
|
|
187
|
-
];
|
|
188
|
-
var SHARED_SKILLS_DIR = path.join(HOME, ".emp-agent", "skills");
|
|
189
|
-
var CONFIG_FILE = path.join(HOME, ".emp-agent", "config.json");
|
|
190
|
-
var CACHE_DIR = path.join(HOME, ".emp-agent", "cache");
|
|
170
|
+
];
|
|
171
|
+
var SHARED_SKILLS_DIR = path.join(HOME, ".emp-agent", "skills");
|
|
172
|
+
var CONFIG_FILE = path.join(HOME, ".emp-agent", "config.json");
|
|
173
|
+
var CACHE_DIR = path.join(HOME, ".emp-agent", "cache");
|
|
174
|
+
|
|
175
|
+
// src/commands/agents.ts
|
|
176
|
+
function agents() {
|
|
177
|
+
const cwd = process.cwd();
|
|
178
|
+
console.log(chalk.bold("\n\u{1F4CB} Supported AI Agents:\n"));
|
|
179
|
+
for (const agent of AGENTS) {
|
|
180
|
+
const skillsDirs = getAgentSkillsDirs(agent, cwd);
|
|
181
|
+
const dirsInfo = skillsDirs.length > 1 ? ` (${skillsDirs.length} directories)` : "";
|
|
182
|
+
console.log(chalk.bold(agent.displayName));
|
|
183
|
+
console.log(chalk.gray(` Name: ${agent.name}`));
|
|
184
|
+
if (skillsDirs.length === 0) {
|
|
185
|
+
console.log(chalk.yellow(" \u26A0\uFE0F No directories configured"));
|
|
186
|
+
} else {
|
|
187
|
+
console.log(chalk.gray(` Directories${dirsInfo}:`));
|
|
188
|
+
for (const dir of skillsDirs) {
|
|
189
|
+
const exists = fs2.existsSync(dir);
|
|
190
|
+
const status = exists ? chalk.green("\u2713") : chalk.gray("\u25CB");
|
|
191
|
+
const pathColor = exists ? chalk.white : chalk.gray;
|
|
192
|
+
console.log(` ${status} ${pathColor(dir)}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
console.log("");
|
|
196
|
+
}
|
|
197
|
+
console.log(chalk.bold("\u{1F4E6} Shared Skills Directory:"));
|
|
198
|
+
const sharedExists = fs2.existsSync(SHARED_SKILLS_DIR);
|
|
199
|
+
const sharedStatus = sharedExists ? chalk.green("\u2713") : chalk.gray("\u25CB");
|
|
200
|
+
const sharedPathColor = sharedExists ? chalk.white : chalk.gray;
|
|
201
|
+
console.log(` ${sharedStatus} ${sharedPathColor(SHARED_SKILLS_DIR)}`);
|
|
202
|
+
console.log("");
|
|
203
|
+
const home = os2.homedir();
|
|
204
|
+
if (cwd !== home) {
|
|
205
|
+
console.log(chalk.gray(`Current working directory: ${cwd}`));
|
|
206
|
+
console.log(chalk.gray("(Project-level directories are shown above)\n"));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/commands/install.ts
|
|
211
|
+
import { exec as exec2 } from "child_process";
|
|
212
|
+
import fs6 from "fs";
|
|
213
|
+
import os3 from "os";
|
|
214
|
+
import path5 from "path";
|
|
215
|
+
import { promisify as promisify2 } from "util";
|
|
216
|
+
|
|
217
|
+
// src/utils/git.ts
|
|
218
|
+
function parseGitUrl(url) {
|
|
219
|
+
const githubPatterns = [
|
|
220
|
+
// https://github.com/owner/repo/tree/branch/path/to/dir
|
|
221
|
+
/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)(?:\/(.+))?$/,
|
|
222
|
+
// https://github.com/owner/repo
|
|
223
|
+
/^https?:\/\/github\.com\/([^/]+)\/([^/]+)(?:\/)?$/,
|
|
224
|
+
// git@github.com:owner/repo.git
|
|
225
|
+
/^git@github\.com:([^/]+)\/([^/]+)(?:\.git)?$/
|
|
226
|
+
];
|
|
227
|
+
const gitlabPatterns = [
|
|
228
|
+
// https://host/group/repo/-/tree/branch/path (self-hosted + gitlab.com)
|
|
229
|
+
/^https?:\/\/([^/]+)\/(.+)\/-\/tree\/([^/]+)(?:\/(.+))?$/,
|
|
230
|
+
// https://gitlab.com/owner/repo
|
|
231
|
+
/^https?:\/\/gitlab\.com\/([^/]+)\/([^/]+)(?:\/)?$/,
|
|
232
|
+
// git@gitlab.com:owner/repo.git or git@host:group/repo.git
|
|
233
|
+
/^git@([^:]+):(.+)(?:\.git)?$/
|
|
234
|
+
];
|
|
235
|
+
for (let i = 0; i < githubPatterns.length; i++) {
|
|
236
|
+
const pattern = githubPatterns[i];
|
|
237
|
+
const match = url.match(pattern);
|
|
238
|
+
if (match) {
|
|
239
|
+
const owner = match[1];
|
|
240
|
+
const repo = match[2].replace(/\.git$/, "");
|
|
241
|
+
let branch;
|
|
242
|
+
let path7;
|
|
243
|
+
if (i === 0) {
|
|
244
|
+
branch = match[3];
|
|
245
|
+
path7 = match[4];
|
|
246
|
+
} else if (i === 1) {
|
|
247
|
+
branch = "main";
|
|
248
|
+
} else {
|
|
249
|
+
branch = "main";
|
|
250
|
+
}
|
|
251
|
+
const gitUrl = `https://github.com/${owner}/${repo}.git`;
|
|
252
|
+
return {
|
|
253
|
+
type: "github",
|
|
254
|
+
owner,
|
|
255
|
+
repo,
|
|
256
|
+
branch,
|
|
257
|
+
path: path7,
|
|
258
|
+
gitUrl,
|
|
259
|
+
installUrl: gitUrl
|
|
260
|
+
// Will be used for git clone
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
for (let i = 0; i < gitlabPatterns.length; i++) {
|
|
265
|
+
const pattern = gitlabPatterns[i];
|
|
266
|
+
const match = url.match(pattern);
|
|
267
|
+
if (match) {
|
|
268
|
+
let owner;
|
|
269
|
+
let repo;
|
|
270
|
+
let branch;
|
|
271
|
+
let path7;
|
|
272
|
+
let gitUrl;
|
|
273
|
+
if (i === 0) {
|
|
274
|
+
const host = match[1];
|
|
275
|
+
const repoPath = match[2].replace(/\.git$/, "");
|
|
276
|
+
branch = match[3];
|
|
277
|
+
path7 = match[4];
|
|
278
|
+
const parts = repoPath.split("/");
|
|
279
|
+
repo = parts.pop() || repoPath;
|
|
280
|
+
owner = parts.join("/") || repo;
|
|
281
|
+
gitUrl = `https://${host}/${repoPath}.git`;
|
|
282
|
+
} else if (i === 1) {
|
|
283
|
+
owner = match[1];
|
|
284
|
+
repo = match[2].replace(/\.git$/, "");
|
|
285
|
+
branch = "main";
|
|
286
|
+
gitUrl = `https://gitlab.com/${owner}/${repo}.git`;
|
|
287
|
+
} else {
|
|
288
|
+
const host = match[1];
|
|
289
|
+
const repoPath = match[2].replace(/\.git$/, "");
|
|
290
|
+
branch = "main";
|
|
291
|
+
const parts = repoPath.split("/");
|
|
292
|
+
repo = parts.pop() || repoPath;
|
|
293
|
+
owner = parts.join("/") || repo;
|
|
294
|
+
gitUrl = `https://${host}/${repoPath}.git`;
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
type: "gitlab",
|
|
298
|
+
owner,
|
|
299
|
+
repo,
|
|
300
|
+
branch,
|
|
301
|
+
path: path7,
|
|
302
|
+
gitUrl,
|
|
303
|
+
installUrl: gitUrl
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (url.startsWith("git+") || url.startsWith("git://")) {
|
|
308
|
+
return {
|
|
309
|
+
type: "other",
|
|
310
|
+
owner: "",
|
|
311
|
+
repo: "",
|
|
312
|
+
gitUrl: url.replace(/^git\+/, ""),
|
|
313
|
+
installUrl: url.startsWith("git+") ? url : `git+${url}`
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
function isGitUrl(str) {
|
|
319
|
+
return str.includes("github.com") || str.includes("gitlab.com") || str.includes("/-/tree/") || // GitLab web URL pattern (self-hosted)
|
|
320
|
+
str.startsWith("git@") || str.startsWith("git+") || str.startsWith("git://");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/utils/logger.ts
|
|
324
|
+
import chalk2 from "chalk";
|
|
325
|
+
import ora from "ora";
|
|
326
|
+
var Logger = class {
|
|
327
|
+
spinner = null;
|
|
328
|
+
info(message) {
|
|
329
|
+
if (this.spinner) this.spinner.stop();
|
|
330
|
+
console.log(chalk2.blue("\u2139"), message);
|
|
331
|
+
}
|
|
332
|
+
success(message) {
|
|
333
|
+
if (this.spinner) this.spinner.stop();
|
|
334
|
+
console.log(chalk2.green("\u2713"), message);
|
|
335
|
+
}
|
|
336
|
+
warn(message) {
|
|
337
|
+
if (this.spinner) this.spinner.stop();
|
|
338
|
+
console.log(chalk2.yellow("\u26A0"), message);
|
|
339
|
+
}
|
|
340
|
+
error(message) {
|
|
341
|
+
if (this.spinner) this.spinner.stop();
|
|
342
|
+
console.log(chalk2.red("\u2717"), message);
|
|
343
|
+
}
|
|
344
|
+
dim(message) {
|
|
345
|
+
if (this.spinner) this.spinner.stop();
|
|
346
|
+
console.log(chalk2.dim(" " + message));
|
|
347
|
+
}
|
|
348
|
+
start(message) {
|
|
349
|
+
if (this.spinner) this.spinner.stop();
|
|
350
|
+
this.spinner = ora(message).start();
|
|
351
|
+
return this.spinner;
|
|
352
|
+
}
|
|
353
|
+
stopSpinner() {
|
|
354
|
+
if (this.spinner) {
|
|
355
|
+
this.spinner.stop();
|
|
356
|
+
this.spinner = null;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Update spinner text without stopping it
|
|
361
|
+
*/
|
|
362
|
+
updateSpinner(message) {
|
|
363
|
+
if (this.spinner) {
|
|
364
|
+
this.spinner.text = message;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Log info without stopping spinner (for background info)
|
|
369
|
+
*/
|
|
370
|
+
infoWithoutStop(message) {
|
|
371
|
+
if (this.spinner) {
|
|
372
|
+
const currentText = this.spinner.text;
|
|
373
|
+
this.spinner.stop();
|
|
374
|
+
console.log(chalk2.blue("\u2139"), message);
|
|
375
|
+
this.spinner.start(currentText);
|
|
376
|
+
} else {
|
|
377
|
+
console.log(chalk2.blue("\u2139"), message);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
var logger = new Logger();
|
|
191
382
|
|
|
192
383
|
// src/utils/paths.ts
|
|
384
|
+
import fs3 from "fs";
|
|
385
|
+
import path2 from "path";
|
|
193
386
|
function getSharedSkillPath(skillName) {
|
|
194
387
|
return path2.join(SHARED_SKILLS_DIR, skillName);
|
|
195
388
|
}
|
|
@@ -202,8 +395,8 @@ function getAgentSkillPaths(agentName, skillName, cwd) {
|
|
|
202
395
|
return skillsDirs.map((dir) => path2.join(dir, skillName));
|
|
203
396
|
}
|
|
204
397
|
function ensureSharedDir() {
|
|
205
|
-
if (!
|
|
206
|
-
|
|
398
|
+
if (!fs3.existsSync(SHARED_SKILLS_DIR)) {
|
|
399
|
+
fs3.mkdirSync(SHARED_SKILLS_DIR, { recursive: true });
|
|
207
400
|
}
|
|
208
401
|
}
|
|
209
402
|
function detectInstalledAgents(cwd) {
|
|
@@ -211,7 +404,7 @@ function detectInstalledAgents(cwd) {
|
|
|
211
404
|
try {
|
|
212
405
|
const skillsDirs = getAgentSkillsDirs(agent, cwd);
|
|
213
406
|
return skillsDirs.some((dir) => {
|
|
214
|
-
return
|
|
407
|
+
return fs3.existsSync(dir) || fs3.existsSync(path2.dirname(dir));
|
|
215
408
|
});
|
|
216
409
|
} catch {
|
|
217
410
|
return false;
|
|
@@ -230,32 +423,93 @@ function extractSkillName(nameOrPath) {
|
|
|
230
423
|
return path2.basename(nameOrPath);
|
|
231
424
|
}
|
|
232
425
|
|
|
233
|
-
// src/utils/
|
|
234
|
-
import
|
|
426
|
+
// src/utils/registry.ts
|
|
427
|
+
import { exec } from "child_process";
|
|
428
|
+
import fs4 from "fs";
|
|
235
429
|
import path3 from "path";
|
|
430
|
+
import { promisify } from "util";
|
|
431
|
+
var execAsync = promisify(exec);
|
|
432
|
+
function findNpmrc(startDir) {
|
|
433
|
+
let currentDir = path3.resolve(startDir);
|
|
434
|
+
while (currentDir !== path3.dirname(currentDir)) {
|
|
435
|
+
const npmrcPath = path3.join(currentDir, ".npmrc");
|
|
436
|
+
if (fs4.existsSync(npmrcPath)) {
|
|
437
|
+
return npmrcPath;
|
|
438
|
+
}
|
|
439
|
+
currentDir = path3.dirname(currentDir);
|
|
440
|
+
}
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
function parseNpmrc(npmrcPath) {
|
|
444
|
+
try {
|
|
445
|
+
const content = fs4.readFileSync(npmrcPath, "utf-8");
|
|
446
|
+
const lines = content.split("\n");
|
|
447
|
+
for (const line of lines) {
|
|
448
|
+
const trimmed = line.trim();
|
|
449
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
const registryMatch = trimmed.match(/^(?:@[^:]+:)?registry\s*=\s*(.+)$/);
|
|
453
|
+
if (registryMatch) {
|
|
454
|
+
return registryMatch[1].trim();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
} catch (error) {
|
|
458
|
+
}
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
async function getGlobalRegistry() {
|
|
462
|
+
try {
|
|
463
|
+
const { stdout } = await execAsync("npm config get registry");
|
|
464
|
+
const registry = stdout.trim();
|
|
465
|
+
if (registry && registry !== "undefined") {
|
|
466
|
+
return registry;
|
|
467
|
+
}
|
|
468
|
+
} catch (error) {
|
|
469
|
+
}
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
async function getRegistry(cwd = process.cwd()) {
|
|
473
|
+
const npmrcPath = findNpmrc(cwd);
|
|
474
|
+
if (npmrcPath) {
|
|
475
|
+
const registry = parseNpmrc(npmrcPath);
|
|
476
|
+
if (registry) {
|
|
477
|
+
return registry;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
const globalRegistry = await getGlobalRegistry();
|
|
481
|
+
if (globalRegistry) {
|
|
482
|
+
return globalRegistry;
|
|
483
|
+
}
|
|
484
|
+
return "https://registry.npmjs.org/";
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/utils/symlink.ts
|
|
488
|
+
import fs5 from "fs";
|
|
489
|
+
import path4 from "path";
|
|
236
490
|
function createSymlink(skillName, agent, cwd) {
|
|
237
491
|
const source = getSharedSkillPath(skillName);
|
|
238
|
-
if (!
|
|
492
|
+
if (!fs5.existsSync(source)) {
|
|
239
493
|
logger.error(`Skill not found: ${source}`);
|
|
240
494
|
return false;
|
|
241
495
|
}
|
|
242
496
|
const targets = getAgentSkillPaths(agent.name, skillName, cwd);
|
|
243
497
|
let successCount = 0;
|
|
244
498
|
for (const target of targets) {
|
|
245
|
-
const targetDir =
|
|
246
|
-
if (!
|
|
499
|
+
const targetDir = path4.dirname(target);
|
|
500
|
+
if (!fs5.existsSync(targetDir)) {
|
|
247
501
|
try {
|
|
248
|
-
|
|
502
|
+
fs5.mkdirSync(targetDir, { recursive: true });
|
|
249
503
|
} catch (error) {
|
|
250
504
|
logger.error(`Failed to create directory ${targetDir}: ${error.message}`);
|
|
251
505
|
continue;
|
|
252
506
|
}
|
|
253
507
|
}
|
|
254
|
-
if (
|
|
508
|
+
if (fs5.existsSync(target)) {
|
|
255
509
|
try {
|
|
256
|
-
const stats =
|
|
510
|
+
const stats = fs5.lstatSync(target);
|
|
257
511
|
if (stats.isSymbolicLink()) {
|
|
258
|
-
|
|
512
|
+
fs5.unlinkSync(target);
|
|
259
513
|
} else {
|
|
260
514
|
logger.warn(`Target exists but is not a symlink, skipping: ${target}`);
|
|
261
515
|
continue;
|
|
@@ -266,7 +520,7 @@ function createSymlink(skillName, agent, cwd) {
|
|
|
266
520
|
}
|
|
267
521
|
}
|
|
268
522
|
try {
|
|
269
|
-
|
|
523
|
+
fs5.symlinkSync(source, target, "dir");
|
|
270
524
|
successCount++;
|
|
271
525
|
} catch (error) {
|
|
272
526
|
logger.error(`Failed to create symlink at ${target}: ${error.message}`);
|
|
@@ -283,13 +537,13 @@ function removeSymlink(skillName, agent, cwd) {
|
|
|
283
537
|
const targets = getAgentSkillPaths(agent.name, skillName, cwd);
|
|
284
538
|
let removedCount = 0;
|
|
285
539
|
for (const target of targets) {
|
|
286
|
-
if (!
|
|
540
|
+
if (!fs5.existsSync(target)) {
|
|
287
541
|
continue;
|
|
288
542
|
}
|
|
289
543
|
try {
|
|
290
|
-
const stats =
|
|
544
|
+
const stats = fs5.lstatSync(target);
|
|
291
545
|
if (stats.isSymbolicLink()) {
|
|
292
|
-
|
|
546
|
+
fs5.unlinkSync(target);
|
|
293
547
|
removedCount++;
|
|
294
548
|
} else {
|
|
295
549
|
logger.warn(`Not a symlink: ${target}`);
|
|
@@ -307,7 +561,7 @@ function removeSymlink(skillName, agent, cwd) {
|
|
|
307
561
|
}
|
|
308
562
|
function isSymlink(filePath) {
|
|
309
563
|
try {
|
|
310
|
-
const stats =
|
|
564
|
+
const stats = fs5.lstatSync(filePath);
|
|
311
565
|
return stats.isSymbolicLink();
|
|
312
566
|
} catch {
|
|
313
567
|
return false;
|
|
@@ -315,164 +569,12 @@ function isSymlink(filePath) {
|
|
|
315
569
|
}
|
|
316
570
|
function readSymlink(filePath) {
|
|
317
571
|
try {
|
|
318
|
-
return
|
|
572
|
+
return fs5.readlinkSync(filePath);
|
|
319
573
|
} catch {
|
|
320
574
|
return null;
|
|
321
575
|
}
|
|
322
576
|
}
|
|
323
577
|
|
|
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
578
|
// src/commands/install.ts
|
|
477
579
|
var execAsync2 = promisify2(exec2);
|
|
478
580
|
async function execWithTimeout(command, timeout = 12e4, env) {
|
|
@@ -484,10 +586,7 @@ async function execWithTimeout(command, timeout = 12e4, env) {
|
|
|
484
586
|
});
|
|
485
587
|
try {
|
|
486
588
|
const execOptions = env ? { env } : {};
|
|
487
|
-
const result = await Promise.race([
|
|
488
|
-
execAsync2(command, execOptions),
|
|
489
|
-
timeoutPromise
|
|
490
|
-
]);
|
|
589
|
+
const result = await Promise.race([execAsync2(command, execOptions), timeoutPromise]);
|
|
491
590
|
if (timeoutId) {
|
|
492
591
|
clearTimeout(timeoutId);
|
|
493
592
|
}
|
|
@@ -499,12 +598,25 @@ async function execWithTimeout(command, timeout = 12e4, env) {
|
|
|
499
598
|
throw error;
|
|
500
599
|
}
|
|
501
600
|
}
|
|
601
|
+
function shortenPath(p) {
|
|
602
|
+
const home = os3.homedir();
|
|
603
|
+
return p.startsWith(home) ? p.replace(home, "~") : p;
|
|
604
|
+
}
|
|
502
605
|
async function install(skillNameOrPath, options = {}) {
|
|
503
|
-
|
|
606
|
+
const isGit = isGitUrl(skillNameOrPath);
|
|
607
|
+
if (isGit) {
|
|
608
|
+
logger.info("Installing from Git URL");
|
|
609
|
+
logger.dim(skillNameOrPath);
|
|
610
|
+
} else {
|
|
611
|
+
logger.info(`Installing skill: ${skillNameOrPath}`);
|
|
612
|
+
}
|
|
504
613
|
ensureSharedDir();
|
|
505
614
|
let skillPath;
|
|
506
615
|
let skillName;
|
|
507
|
-
if (
|
|
616
|
+
if (process.env.DEBUG_ESKILL) {
|
|
617
|
+
logger.dim(`[DEBUG] isGitUrl=${isGit}, parseGitUrl=${parseGitUrl(skillNameOrPath) ? "ok" : "null"}`);
|
|
618
|
+
}
|
|
619
|
+
if (isGit) {
|
|
508
620
|
const gitInfo = parseGitUrl(skillNameOrPath);
|
|
509
621
|
if (!gitInfo) {
|
|
510
622
|
logger.error(`Invalid git URL: ${skillNameOrPath}`);
|
|
@@ -516,21 +628,17 @@ async function install(skillNameOrPath, options = {}) {
|
|
|
516
628
|
const cloneDir = path5.join(tempDir, "repo");
|
|
517
629
|
try {
|
|
518
630
|
const timeout = options.timeout || 12e4;
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
if (gitInfo.
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
logger.infoWithoutStop(`Path: ${gitInfo.path}`);
|
|
526
|
-
}
|
|
527
|
-
logger.infoWithoutStop(`Timeout: ${timeout / 1e3}s`);
|
|
528
|
-
fs4.mkdirSync(tempDir, { recursive: true });
|
|
631
|
+
const gitDetails = [`${gitInfo.gitUrl}`];
|
|
632
|
+
if (gitInfo.branch) gitDetails.push(`branch: ${gitInfo.branch}`);
|
|
633
|
+
if (gitInfo.path) gitDetails.push(`path: ${gitInfo.path}`);
|
|
634
|
+
logger.dim(gitDetails.join(" \xB7 "));
|
|
635
|
+
const spinner = logger.start(`Cloning...`);
|
|
636
|
+
fs6.mkdirSync(tempDir, { recursive: true });
|
|
529
637
|
const branchFlag = gitInfo.branch ? `-b ${gitInfo.branch}` : "";
|
|
530
638
|
const cloneCommand = branchFlag ? `git clone ${branchFlag} ${gitInfo.gitUrl} ${cloneDir} --depth 1 --quiet` : `git clone ${gitInfo.gitUrl} ${cloneDir} --depth 1 --quiet`;
|
|
531
639
|
try {
|
|
532
640
|
await execWithTimeout(cloneCommand, timeout);
|
|
533
|
-
spinner.succeed(`
|
|
641
|
+
spinner.succeed(`Cloned successfully`);
|
|
534
642
|
} catch (error) {
|
|
535
643
|
spinner.fail("Clone failed");
|
|
536
644
|
if (error.message.includes("timeout")) {
|
|
@@ -547,7 +655,7 @@ async function install(skillNameOrPath, options = {}) {
|
|
|
547
655
|
}
|
|
548
656
|
if (gitInfo.path) {
|
|
549
657
|
skillPath = path5.join(cloneDir, gitInfo.path);
|
|
550
|
-
if (!
|
|
658
|
+
if (!fs6.existsSync(skillPath)) {
|
|
551
659
|
logger.error(`Path not found in repository: ${gitInfo.path}`);
|
|
552
660
|
logger.info(`Repository cloned to: ${cloneDir}`);
|
|
553
661
|
process.exit(1);
|
|
@@ -556,7 +664,7 @@ async function install(skillNameOrPath, options = {}) {
|
|
|
556
664
|
skillPath = cloneDir;
|
|
557
665
|
}
|
|
558
666
|
const skillMdPath = path5.join(skillPath, "SKILL.md");
|
|
559
|
-
if (!
|
|
667
|
+
if (!fs6.existsSync(skillMdPath)) {
|
|
560
668
|
logger.warn(`Warning: SKILL.md not found in ${skillPath}`);
|
|
561
669
|
logger.info("The directory may not be a valid skill package");
|
|
562
670
|
}
|
|
@@ -578,16 +686,16 @@ Tried to clone: ${gitInfo.gitUrl}`);
|
|
|
578
686
|
}
|
|
579
687
|
process.exit(1);
|
|
580
688
|
}
|
|
581
|
-
} else if (options.link ||
|
|
689
|
+
} else if (options.link || fs6.existsSync(skillNameOrPath)) {
|
|
582
690
|
skillPath = path5.resolve(skillNameOrPath);
|
|
583
|
-
if (!
|
|
691
|
+
if (!fs6.existsSync(skillPath)) {
|
|
584
692
|
logger.error(`Path not found: ${skillPath}`);
|
|
585
693
|
process.exit(1);
|
|
586
694
|
}
|
|
587
695
|
const pkgPath = path5.join(skillPath, "package.json");
|
|
588
|
-
if (
|
|
696
|
+
if (fs6.existsSync(pkgPath)) {
|
|
589
697
|
try {
|
|
590
|
-
const pkg = JSON.parse(
|
|
698
|
+
const pkg = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
|
|
591
699
|
skillName = extractSkillName(pkg.name);
|
|
592
700
|
} catch {
|
|
593
701
|
skillName = extractSkillName(path5.basename(skillPath));
|
|
@@ -604,12 +712,12 @@ Tried to clone: ${gitInfo.gitUrl}`);
|
|
|
604
712
|
const spinner = logger.start(`Installing ${skillNameOrPath}...`);
|
|
605
713
|
logger.infoWithoutStop(`Registry: ${registry}`);
|
|
606
714
|
logger.infoWithoutStop(`Timeout: ${timeout / 1e3}s`);
|
|
607
|
-
|
|
715
|
+
fs6.mkdirSync(tempDir, { recursive: true });
|
|
608
716
|
logger.updateSpinner(`Downloading ${skillNameOrPath} from ${registry}...`);
|
|
609
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE ||
|
|
717
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || os3.homedir();
|
|
610
718
|
const npmCacheDir = path5.join(homeDir, ".npm");
|
|
611
719
|
const npmConfigPrefix = path5.join(homeDir, ".npm-global");
|
|
612
|
-
|
|
720
|
+
fs6.mkdirSync(npmCacheDir, { recursive: true });
|
|
613
721
|
const installCommand = `npm install ${skillNameOrPath} --prefix ${tempDir} --registry=${registry} --no-save --silent --no-bin-links --prefer-offline`;
|
|
614
722
|
const env = {
|
|
615
723
|
...process.env,
|
|
@@ -634,7 +742,9 @@ Tried to clone: ${gitInfo.gitUrl}`);
|
|
|
634
742
|
logger.info("");
|
|
635
743
|
logger.info("Suggestions:");
|
|
636
744
|
logger.info(` - Try again: eskill add ${skillNameOrPath}`);
|
|
637
|
-
logger.info(
|
|
745
|
+
logger.info(
|
|
746
|
+
` - Use a different registry: eskill add ${skillNameOrPath} --registry=https://registry.npmjs.org/`
|
|
747
|
+
);
|
|
638
748
|
logger.info(` - Increase timeout: eskill add ${skillNameOrPath} --timeout=300000`);
|
|
639
749
|
} else if (errorMessage.includes("EACCES") || errorMessage.includes("permission denied") || errorMessage.includes("Permission denied")) {
|
|
640
750
|
logger.error("Permission denied error detected");
|
|
@@ -673,7 +783,9 @@ Tried to clone: ${gitInfo.gitUrl}`);
|
|
|
673
783
|
logger.info("Please check:");
|
|
674
784
|
logger.info(" - Your internet connection");
|
|
675
785
|
logger.info(` - Registry accessibility: ${registry}`);
|
|
676
|
-
logger.info(
|
|
786
|
+
logger.info(
|
|
787
|
+
` - Try a different registry: eskill add ${skillNameOrPath} --registry=https://registry.npmjs.org/`
|
|
788
|
+
);
|
|
677
789
|
} else {
|
|
678
790
|
logger.error(`Failed to download: ${errorMessage}`);
|
|
679
791
|
logger.info("");
|
|
@@ -686,7 +798,7 @@ Tried to clone: ${gitInfo.gitUrl}`);
|
|
|
686
798
|
process.exit(1);
|
|
687
799
|
}
|
|
688
800
|
skillPath = path5.join(tempDir, "node_modules", skillNameOrPath);
|
|
689
|
-
if (!
|
|
801
|
+
if (!fs6.existsSync(skillPath)) {
|
|
690
802
|
logger.error(`Failed to download package: ${skillNameOrPath}`);
|
|
691
803
|
process.exit(1);
|
|
692
804
|
}
|
|
@@ -696,18 +808,19 @@ Tried to clone: ${gitInfo.gitUrl}`);
|
|
|
696
808
|
}
|
|
697
809
|
}
|
|
698
810
|
const targetPath = getSharedSkillPath(skillName);
|
|
699
|
-
if (
|
|
811
|
+
if (fs6.existsSync(targetPath)) {
|
|
700
812
|
if (options.force) {
|
|
701
813
|
logger.warn("Removing existing installation...");
|
|
702
|
-
|
|
814
|
+
fs6.rmSync(targetPath, { recursive: true, force: true });
|
|
703
815
|
} else {
|
|
704
|
-
logger.error(`Skill already exists
|
|
705
|
-
logger.
|
|
816
|
+
logger.error(`Skill already exists`);
|
|
817
|
+
logger.dim(`Location: ${shortenPath(targetPath)}`);
|
|
818
|
+
logger.dim(`Tip: Add --force to overwrite`);
|
|
706
819
|
process.exit(1);
|
|
707
820
|
}
|
|
708
821
|
}
|
|
709
822
|
if (options.link) {
|
|
710
|
-
|
|
823
|
+
fs6.symlinkSync(skillPath, targetPath, "dir");
|
|
711
824
|
logger.success(`Linked to shared directory (dev mode)`);
|
|
712
825
|
} else {
|
|
713
826
|
copyDir(skillPath, targetPath);
|
|
@@ -721,21 +834,25 @@ Tried to clone: ${gitInfo.gitUrl}`);
|
|
|
721
834
|
logger.info("Skill installed to shared directory, but not linked to any agent");
|
|
722
835
|
logger.info("");
|
|
723
836
|
logger.info("Supported agents:");
|
|
724
|
-
logger.info("
|
|
725
|
-
logger.info("
|
|
726
|
-
logger.info("
|
|
837
|
+
logger.info(" AMP, Antigravity, Claude Code, ClawdBot, Cline, Codex, Cursor, Droid,");
|
|
838
|
+
logger.info(" Gemini, GitHub Copilot, Goose, Kilo, Kiro CLI, OpenCode, Roo, Trae, Windsurf");
|
|
839
|
+
logger.info("");
|
|
840
|
+
logger.info('Run "eskill agents" to see all agent directories');
|
|
727
841
|
return;
|
|
728
842
|
}
|
|
729
843
|
let targetAgents = installedAgents;
|
|
730
844
|
if (options.agent && options.agent !== "all") {
|
|
731
|
-
const agent =
|
|
845
|
+
const agent = AGENTS.find((a) => a.name === options.agent && a.enabled);
|
|
732
846
|
if (!agent) {
|
|
733
|
-
logger.error(`
|
|
847
|
+
logger.error(`Unknown agent: ${options.agent}`);
|
|
734
848
|
logger.info(`
|
|
735
|
-
|
|
849
|
+
Supported agents: ${AGENTS.filter((a) => a.enabled).map((a) => a.name).join(", ")}`);
|
|
736
850
|
process.exit(1);
|
|
737
851
|
}
|
|
738
852
|
targetAgents = [agent];
|
|
853
|
+
if (!installedAgents.find((a) => a.name === options.agent)) {
|
|
854
|
+
logger.info(`Note: ${agent.displayName} directory will be created if not exists`);
|
|
855
|
+
}
|
|
739
856
|
}
|
|
740
857
|
logger.info("\nCreating symlinks...");
|
|
741
858
|
let successCount = 0;
|
|
@@ -753,8 +870,8 @@ Linked to ${successCount}/${targetAgents.length} agents`);
|
|
|
753
870
|
}
|
|
754
871
|
}
|
|
755
872
|
function copyDir(src, dest) {
|
|
756
|
-
|
|
757
|
-
const entries =
|
|
873
|
+
fs6.mkdirSync(dest, { recursive: true });
|
|
874
|
+
const entries = fs6.readdirSync(src, { withFileTypes: true });
|
|
758
875
|
for (const entry of entries) {
|
|
759
876
|
const srcPath = path5.join(src, entry.name);
|
|
760
877
|
const destPath = path5.join(dest, entry.name);
|
|
@@ -764,44 +881,44 @@ function copyDir(src, dest) {
|
|
|
764
881
|
if (entry.isDirectory()) {
|
|
765
882
|
copyDir(srcPath, destPath);
|
|
766
883
|
} else {
|
|
767
|
-
|
|
884
|
+
fs6.copyFileSync(srcPath, destPath);
|
|
768
885
|
}
|
|
769
886
|
}
|
|
770
887
|
}
|
|
771
888
|
|
|
772
889
|
// src/commands/list.ts
|
|
773
|
-
import
|
|
890
|
+
import fs7 from "fs";
|
|
774
891
|
import path6 from "path";
|
|
775
|
-
import
|
|
892
|
+
import chalk3 from "chalk";
|
|
776
893
|
function list() {
|
|
777
|
-
if (!
|
|
894
|
+
if (!fs7.existsSync(SHARED_SKILLS_DIR)) {
|
|
778
895
|
logger.info("No skills installed");
|
|
779
896
|
logger.info(`
|
|
780
|
-
To install a skill, run: ${
|
|
897
|
+
To install a skill, run: ${chalk3.cyan("eskill install <skill-name>")}`);
|
|
781
898
|
return;
|
|
782
899
|
}
|
|
783
|
-
const skills =
|
|
900
|
+
const skills = fs7.readdirSync(SHARED_SKILLS_DIR);
|
|
784
901
|
if (skills.length === 0) {
|
|
785
902
|
logger.info("No skills installed");
|
|
786
903
|
logger.info(`
|
|
787
|
-
To install a skill, run: ${
|
|
904
|
+
To install a skill, run: ${chalk3.cyan("eskill install <skill-name>")}`);
|
|
788
905
|
return;
|
|
789
906
|
}
|
|
790
|
-
console.log(
|
|
907
|
+
console.log(chalk3.bold(`
|
|
791
908
|
Installed skills in ${SHARED_SKILLS_DIR}:
|
|
792
909
|
`));
|
|
793
910
|
for (const skill of skills) {
|
|
794
911
|
const skillPath = path6.join(SHARED_SKILLS_DIR, skill);
|
|
795
912
|
try {
|
|
796
|
-
const stats =
|
|
913
|
+
const stats = fs7.lstatSync(skillPath);
|
|
797
914
|
if (!stats.isDirectory() && !stats.isSymbolicLink()) {
|
|
798
915
|
continue;
|
|
799
916
|
}
|
|
800
917
|
let version2 = "unknown";
|
|
801
918
|
const pkgPath = path6.join(skillPath, "package.json");
|
|
802
|
-
if (
|
|
919
|
+
if (fs7.existsSync(pkgPath)) {
|
|
803
920
|
try {
|
|
804
|
-
const pkgContent =
|
|
921
|
+
const pkgContent = fs7.readFileSync(pkgPath, "utf-8");
|
|
805
922
|
const pkg = JSON.parse(pkgContent);
|
|
806
923
|
if (pkg.version && typeof pkg.version === "string") {
|
|
807
924
|
version2 = pkg.version;
|
|
@@ -811,9 +928,9 @@ Installed skills in ${SHARED_SKILLS_DIR}:
|
|
|
811
928
|
}
|
|
812
929
|
if (version2 === "unknown") {
|
|
813
930
|
const skillMdPath = path6.join(skillPath, "SKILL.md");
|
|
814
|
-
if (
|
|
931
|
+
if (fs7.existsSync(skillMdPath)) {
|
|
815
932
|
try {
|
|
816
|
-
const skillMdContent =
|
|
933
|
+
const skillMdContent = fs7.readFileSync(skillMdPath, "utf-8");
|
|
817
934
|
const frontmatterMatch = skillMdContent.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
818
935
|
if (frontmatterMatch) {
|
|
819
936
|
const frontmatter = frontmatterMatch[1];
|
|
@@ -831,8 +948,8 @@ Installed skills in ${SHARED_SKILLS_DIR}:
|
|
|
831
948
|
const targetPath = readSymlink(skillPath);
|
|
832
949
|
if (targetPath) {
|
|
833
950
|
const targetPkgPath = path6.join(targetPath, "package.json");
|
|
834
|
-
if (
|
|
835
|
-
const pkg = JSON.parse(
|
|
951
|
+
if (fs7.existsSync(targetPkgPath)) {
|
|
952
|
+
const pkg = JSON.parse(fs7.readFileSync(targetPkgPath, "utf-8"));
|
|
836
953
|
if (pkg.version && typeof pkg.version === "string") {
|
|
837
954
|
version2 = pkg.version;
|
|
838
955
|
}
|
|
@@ -842,16 +959,16 @@ Installed skills in ${SHARED_SKILLS_DIR}:
|
|
|
842
959
|
}
|
|
843
960
|
}
|
|
844
961
|
const isDev = isSymlink(skillPath);
|
|
845
|
-
const devTag = isDev ?
|
|
846
|
-
const versionDisplay = version2 !== "unknown" ?
|
|
847
|
-
console.log(
|
|
962
|
+
const devTag = isDev ? chalk3.yellow(" (dev)") : "";
|
|
963
|
+
const versionDisplay = version2 !== "unknown" ? chalk3.gray(`(v${version2})`) : "";
|
|
964
|
+
console.log(chalk3.green("\u{1F4E6}") + ` ${chalk3.bold(skill)}${versionDisplay ? " " + versionDisplay : ""}${devTag}`);
|
|
848
965
|
const cwd = process.cwd();
|
|
849
966
|
const linkedAgents = [];
|
|
850
967
|
for (const agent of AGENTS) {
|
|
851
968
|
const skillsDirs = getAgentSkillsDirs(agent, cwd);
|
|
852
969
|
const hasSymlink = skillsDirs.some((dir) => {
|
|
853
970
|
const agentSkillPath = path6.join(dir, skill);
|
|
854
|
-
if (
|
|
971
|
+
if (fs7.existsSync(agentSkillPath) && isSymlink(agentSkillPath)) {
|
|
855
972
|
const target = readSymlink(agentSkillPath);
|
|
856
973
|
return target === skillPath;
|
|
857
974
|
}
|
|
@@ -862,29 +979,28 @@ Installed skills in ${SHARED_SKILLS_DIR}:
|
|
|
862
979
|
}
|
|
863
980
|
}
|
|
864
981
|
if (linkedAgents.length > 0) {
|
|
865
|
-
console.log(
|
|
982
|
+
console.log(chalk3.gray(` \u2192 Linked to: ${linkedAgents.join(", ")}`));
|
|
866
983
|
} else {
|
|
867
|
-
console.log(
|
|
984
|
+
console.log(chalk3.yellow(` \u2192 Not linked to any agent`));
|
|
868
985
|
}
|
|
869
986
|
if (isDev) {
|
|
870
987
|
const target = readSymlink(skillPath);
|
|
871
988
|
if (target) {
|
|
872
|
-
console.log(
|
|
989
|
+
console.log(chalk3.gray(` \u2192 Source: ${target}`));
|
|
873
990
|
}
|
|
874
991
|
}
|
|
875
992
|
console.log("");
|
|
876
993
|
} catch (error) {
|
|
877
|
-
continue;
|
|
878
994
|
}
|
|
879
995
|
}
|
|
880
996
|
}
|
|
881
997
|
|
|
882
998
|
// src/commands/remove.ts
|
|
883
|
-
import
|
|
999
|
+
import fs8 from "fs";
|
|
884
1000
|
async function remove(skillName, options = {}) {
|
|
885
1001
|
const extractedName = extractSkillName(skillName);
|
|
886
1002
|
const sharedPath = getSharedSkillPath(extractedName);
|
|
887
|
-
const skillExists =
|
|
1003
|
+
const skillExists = fs8.existsSync(sharedPath);
|
|
888
1004
|
if (!skillExists) {
|
|
889
1005
|
logger.error(`Skill not found: ${extractedName}`);
|
|
890
1006
|
logger.info(`Location: ${sharedPath}`);
|
|
@@ -894,11 +1010,11 @@ async function remove(skillName, options = {}) {
|
|
|
894
1010
|
const installedAgents = detectInstalledAgents(cwd);
|
|
895
1011
|
let targetAgents = installedAgents;
|
|
896
1012
|
if (options.agent && options.agent !== "all") {
|
|
897
|
-
const agent =
|
|
1013
|
+
const agent = AGENTS.find((a) => a.name === options.agent && a.enabled);
|
|
898
1014
|
if (!agent) {
|
|
899
|
-
logger.error(`
|
|
1015
|
+
logger.error(`Unknown agent: ${options.agent}`);
|
|
900
1016
|
logger.info(`
|
|
901
|
-
|
|
1017
|
+
Supported agents: ${AGENTS.filter((a) => a.enabled).map((a) => a.name).join(", ")}`);
|
|
902
1018
|
process.exit(1);
|
|
903
1019
|
}
|
|
904
1020
|
targetAgents = [agent];
|
|
@@ -920,12 +1036,12 @@ Installed agents: ${installedAgents.map((a) => a.name).join(", ")}`);
|
|
|
920
1036
|
}
|
|
921
1037
|
logger.info("Removing skill from shared directory...");
|
|
922
1038
|
try {
|
|
923
|
-
const stats =
|
|
1039
|
+
const stats = fs8.lstatSync(sharedPath);
|
|
924
1040
|
if (stats.isSymbolicLink()) {
|
|
925
|
-
|
|
1041
|
+
fs8.unlinkSync(sharedPath);
|
|
926
1042
|
logger.success("Removed symlink from shared directory");
|
|
927
1043
|
} else {
|
|
928
|
-
|
|
1044
|
+
fs8.rmSync(sharedPath, { recursive: true, force: true });
|
|
929
1045
|
logger.success("Removed skill from shared directory");
|
|
930
1046
|
}
|
|
931
1047
|
} catch (error) {
|
|
@@ -936,7 +1052,7 @@ Installed agents: ${installedAgents.map((a) => a.name).join(", ")}`);
|
|
|
936
1052
|
for (const agent of AGENTS) {
|
|
937
1053
|
const agentSkillPaths = getAgentSkillPaths(agent.name, extractedName, cwd);
|
|
938
1054
|
const hasSymlink = agentSkillPaths.some((path7) => {
|
|
939
|
-
return
|
|
1055
|
+
return fs8.existsSync(path7) && isSymlink(path7);
|
|
940
1056
|
});
|
|
941
1057
|
if (hasSymlink) {
|
|
942
1058
|
remainingSymlinks.push(agent.displayName);
|
|
@@ -954,44 +1070,6 @@ Installed agents: ${installedAgents.map((a) => a.name).join(", ")}`);
|
|
|
954
1070
|
}
|
|
955
1071
|
}
|
|
956
1072
|
|
|
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
1073
|
// src/index.ts
|
|
996
1074
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
997
1075
|
var __dirname2 = dirname(__filename2);
|
|
@@ -1000,7 +1078,11 @@ var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
|
1000
1078
|
var version = packageJson.version;
|
|
1001
1079
|
var program = new Command();
|
|
1002
1080
|
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(
|
|
1081
|
+
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(
|
|
1082
|
+
"-t, --timeout <ms>",
|
|
1083
|
+
"Timeout in milliseconds (default: 180000 for npm, 120000 for git)",
|
|
1084
|
+
(value) => Number.parseInt(value, 10)
|
|
1085
|
+
).action(async (skill, options) => {
|
|
1004
1086
|
try {
|
|
1005
1087
|
await install(skill, options);
|
|
1006
1088
|
} catch (error) {
|