@synapsync/synk 1.0.0
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 +480 -0
- package/ThirdPartyNoticeText.txt +125 -0
- package/bin/cli.mjs +14 -0
- package/dist/_chunks/libs/@clack/core.mjs +767 -0
- package/dist/_chunks/libs/@clack/prompts.mjs +196 -0
- package/dist/_chunks/libs/@kwsites/file-exists.mjs +562 -0
- package/dist/_chunks/libs/@kwsites/promise-deferred.mjs +37 -0
- package/dist/_chunks/libs/ansi-regex.mjs +4 -0
- package/dist/_chunks/libs/chalk.mjs +399 -0
- package/dist/_chunks/libs/cli-cursor.mjs +257 -0
- package/dist/_chunks/libs/cli-spinners.mjs +1656 -0
- package/dist/_chunks/libs/esprima.mjs +5338 -0
- package/dist/_chunks/libs/extend-shallow.mjs +31 -0
- package/dist/_chunks/libs/get-east-asian-width.mjs +18 -0
- package/dist/_chunks/libs/gray-matter.mjs +2596 -0
- package/dist/_chunks/libs/is-interactive.mjs +4 -0
- package/dist/_chunks/libs/is-unicode-supported.mjs +8 -0
- package/dist/_chunks/libs/log-symbols.mjs +70 -0
- package/dist/_chunks/libs/ora.mjs +460 -0
- package/dist/_chunks/libs/simple-git.mjs +3584 -0
- package/dist/_chunks/libs/xdg-basedir.mjs +14 -0
- package/dist/_chunks/rolldown-runtime.mjs +24 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +4066 -0
- package/package.json +116 -0
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,4066 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { r as __toESM } from "./_chunks/rolldown-runtime.mjs";
|
|
3
|
+
import { d as require_picocolors, l as pD } from "./_chunks/libs/@clack/core.mjs";
|
|
4
|
+
import "./_chunks/libs/cli-cursor.mjs";
|
|
5
|
+
import { t as ora } from "./_chunks/libs/ora.mjs";
|
|
6
|
+
import { n as ve, r as ye, t as fe } from "./_chunks/libs/@clack/prompts.mjs";
|
|
7
|
+
import { t as xdgConfig } from "./_chunks/libs/xdg-basedir.mjs";
|
|
8
|
+
import "./_chunks/libs/@kwsites/file-exists.mjs";
|
|
9
|
+
import "./_chunks/libs/@kwsites/promise-deferred.mjs";
|
|
10
|
+
import { t as esm_default } from "./_chunks/libs/simple-git.mjs";
|
|
11
|
+
import { t as require_gray_matter } from "./_chunks/libs/gray-matter.mjs";
|
|
12
|
+
import "./_chunks/libs/extend-shallow.mjs";
|
|
13
|
+
import "./_chunks/libs/esprima.mjs";
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
15
|
+
import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "path";
|
|
16
|
+
import { fileURLToPath } from "url";
|
|
17
|
+
import { homedir, platform, tmpdir } from "os";
|
|
18
|
+
import { access, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, rm, stat, symlink, writeFile } from "fs/promises";
|
|
19
|
+
import "crypto";
|
|
20
|
+
import { execSync, spawnSync } from "child_process";
|
|
21
|
+
import * as readline from "readline";
|
|
22
|
+
import { Writable } from "stream";
|
|
23
|
+
var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
|
|
24
|
+
const logger = {
|
|
25
|
+
log(message = "") {
|
|
26
|
+
console.log(message);
|
|
27
|
+
},
|
|
28
|
+
line() {
|
|
29
|
+
console.log();
|
|
30
|
+
},
|
|
31
|
+
clear() {
|
|
32
|
+
console.clear();
|
|
33
|
+
},
|
|
34
|
+
info(message) {
|
|
35
|
+
console.log(`${import_picocolors.default.blue("ℹ")} ${message}`);
|
|
36
|
+
},
|
|
37
|
+
success(message) {
|
|
38
|
+
console.log(`${import_picocolors.default.green("✓")} ${message}`);
|
|
39
|
+
},
|
|
40
|
+
warning(message) {
|
|
41
|
+
console.log(`${import_picocolors.default.yellow("⚠")} ${message}`);
|
|
42
|
+
},
|
|
43
|
+
error(message) {
|
|
44
|
+
console.log(`${import_picocolors.default.red("✖")} ${message}`);
|
|
45
|
+
},
|
|
46
|
+
debug(message) {
|
|
47
|
+
if (process.env["DEBUG"]) console.log(`${import_picocolors.default.dim("⋯")} ${import_picocolors.default.dim(message)}`);
|
|
48
|
+
},
|
|
49
|
+
bold(message) {
|
|
50
|
+
console.log(import_picocolors.default.bold(message));
|
|
51
|
+
},
|
|
52
|
+
dim(message) {
|
|
53
|
+
console.log(import_picocolors.default.dim(message));
|
|
54
|
+
},
|
|
55
|
+
section(title) {
|
|
56
|
+
console.log();
|
|
57
|
+
console.log(import_picocolors.default.bold(import_picocolors.default.cyan(title)));
|
|
58
|
+
},
|
|
59
|
+
header(title) {
|
|
60
|
+
console.log();
|
|
61
|
+
console.log(import_picocolors.default.bold(import_picocolors.default.cyan(` ${title}`)));
|
|
62
|
+
console.log(import_picocolors.default.dim(" " + "─".repeat(title.length + 2)));
|
|
63
|
+
console.log();
|
|
64
|
+
},
|
|
65
|
+
list(items) {
|
|
66
|
+
items.forEach((item) => console.log(`${import_picocolors.default.dim(" •")} ${item}`));
|
|
67
|
+
},
|
|
68
|
+
label(key, value) {
|
|
69
|
+
console.log(`${import_picocolors.default.dim(`${key}:`)}${" ".repeat(Math.max(1, 10 - key.length))}${value}`);
|
|
70
|
+
},
|
|
71
|
+
hint(message) {
|
|
72
|
+
console.log(import_picocolors.default.dim(message));
|
|
73
|
+
},
|
|
74
|
+
command(cmd, description) {
|
|
75
|
+
console.log(` ${import_picocolors.default.dim("$")} ${import_picocolors.default.white(cmd)} ${import_picocolors.default.dim(description)}`);
|
|
76
|
+
},
|
|
77
|
+
gradient(line, color) {
|
|
78
|
+
console.log(`${color}${line}[0m`);
|
|
79
|
+
},
|
|
80
|
+
spinner(text) {
|
|
81
|
+
const s = ora({ stream: process.stdout });
|
|
82
|
+
if (text) s.start(text);
|
|
83
|
+
return s;
|
|
84
|
+
},
|
|
85
|
+
note(content, title) {
|
|
86
|
+
console.log();
|
|
87
|
+
if (title) {
|
|
88
|
+
console.log(` ${import_picocolors.default.bold(title)}`);
|
|
89
|
+
console.log();
|
|
90
|
+
}
|
|
91
|
+
for (const line of content.split("\n")) console.log(` ${line}`);
|
|
92
|
+
console.log();
|
|
93
|
+
},
|
|
94
|
+
intro(title) {
|
|
95
|
+
console.log();
|
|
96
|
+
console.log(import_picocolors.default.bgCyan(import_picocolors.default.black(` ${title} `)));
|
|
97
|
+
},
|
|
98
|
+
outro(message) {
|
|
99
|
+
console.log();
|
|
100
|
+
console.log(message);
|
|
101
|
+
console.log();
|
|
102
|
+
},
|
|
103
|
+
cancel(message) {
|
|
104
|
+
console.log(`${import_picocolors.default.yellow("◆")} ${message}`);
|
|
105
|
+
},
|
|
106
|
+
step(message) {
|
|
107
|
+
console.log(`${import_picocolors.default.cyan("◆")} ${message}`);
|
|
108
|
+
},
|
|
109
|
+
message(message) {
|
|
110
|
+
console.log(` ${message}`);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
function getOwnerRepo(parsed) {
|
|
114
|
+
if (parsed.type === "local") return null;
|
|
115
|
+
if (!parsed.url.startsWith("http://") && !parsed.url.startsWith("https://")) return null;
|
|
116
|
+
try {
|
|
117
|
+
let path = new URL(parsed.url).pathname.slice(1);
|
|
118
|
+
path = path.replace(/\.git$/, "");
|
|
119
|
+
if (path.includes("/")) return path;
|
|
120
|
+
} catch {}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
function parseOwnerRepo(ownerRepo) {
|
|
124
|
+
const match = ownerRepo.match(/^([^/]+)\/([^/]+)$/);
|
|
125
|
+
if (match) return {
|
|
126
|
+
owner: match[1],
|
|
127
|
+
repo: match[2]
|
|
128
|
+
};
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
async function isRepoPrivate(owner, repo) {
|
|
132
|
+
try {
|
|
133
|
+
const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`);
|
|
134
|
+
if (!res.ok) return null;
|
|
135
|
+
return (await res.json()).private === true;
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function isLocalPath(input) {
|
|
141
|
+
return isAbsolute(input) || input.startsWith("./") || input.startsWith("../") || input === "." || input === ".." || /^[a-zA-Z]:[/\\]/.test(input);
|
|
142
|
+
}
|
|
143
|
+
function isDirectSkillUrl(input) {
|
|
144
|
+
if (!input.startsWith("http://") && !input.startsWith("https://")) return false;
|
|
145
|
+
if (!input.toLowerCase().endsWith("/skill.md")) return false;
|
|
146
|
+
if (input.includes("github.com/") && !input.includes("raw.githubusercontent.com")) {
|
|
147
|
+
if (!input.includes("/blob/") && !input.includes("/raw/")) return false;
|
|
148
|
+
}
|
|
149
|
+
if (input.includes("gitlab.com/") && !input.includes("/-/raw/")) return false;
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
function parseSource(input) {
|
|
153
|
+
if (isLocalPath(input)) {
|
|
154
|
+
const resolvedPath = resolve(input);
|
|
155
|
+
return {
|
|
156
|
+
type: "local",
|
|
157
|
+
url: resolvedPath,
|
|
158
|
+
localPath: resolvedPath
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (isDirectSkillUrl(input)) return {
|
|
162
|
+
type: "direct-url",
|
|
163
|
+
url: input
|
|
164
|
+
};
|
|
165
|
+
const githubTreeWithPathMatch = input.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)/);
|
|
166
|
+
if (githubTreeWithPathMatch) {
|
|
167
|
+
const [, owner, repo, ref, subpath] = githubTreeWithPathMatch;
|
|
168
|
+
return {
|
|
169
|
+
type: "github",
|
|
170
|
+
url: `https://github.com/${owner}/${repo}.git`,
|
|
171
|
+
ref,
|
|
172
|
+
subpath
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const githubTreeMatch = input.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)$/);
|
|
176
|
+
if (githubTreeMatch) {
|
|
177
|
+
const [, owner, repo, ref] = githubTreeMatch;
|
|
178
|
+
return {
|
|
179
|
+
type: "github",
|
|
180
|
+
url: `https://github.com/${owner}/${repo}.git`,
|
|
181
|
+
ref
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const githubRepoMatch = input.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
185
|
+
if (githubRepoMatch) {
|
|
186
|
+
const [, owner, repo] = githubRepoMatch;
|
|
187
|
+
return {
|
|
188
|
+
type: "github",
|
|
189
|
+
url: `https://github.com/${owner}/${repo.replace(/\.git$/, "")}.git`
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const gitlabTreeWithPathMatch = input.match(/^(https?):\/\/([^/]+)\/(.+?)\/-\/tree\/([^/]+)\/(.+)/);
|
|
193
|
+
if (gitlabTreeWithPathMatch) {
|
|
194
|
+
const [, protocol, hostname, repoPath, ref, subpath] = gitlabTreeWithPathMatch;
|
|
195
|
+
if (hostname !== "github.com" && repoPath) return {
|
|
196
|
+
type: "gitlab",
|
|
197
|
+
url: `${protocol}://${hostname}/${repoPath.replace(/\.git$/, "")}.git`,
|
|
198
|
+
ref,
|
|
199
|
+
subpath
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const gitlabTreeMatch = input.match(/^(https?):\/\/([^/]+)\/(.+?)\/-\/tree\/([^/]+)$/);
|
|
203
|
+
if (gitlabTreeMatch) {
|
|
204
|
+
const [, protocol, hostname, repoPath, ref] = gitlabTreeMatch;
|
|
205
|
+
if (hostname !== "github.com" && repoPath) return {
|
|
206
|
+
type: "gitlab",
|
|
207
|
+
url: `${protocol}://${hostname}/${repoPath.replace(/\.git$/, "")}.git`,
|
|
208
|
+
ref
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const gitlabRepoMatch = input.match(/gitlab\.com\/(.+?)(?:\.git)?\/?$/);
|
|
212
|
+
if (gitlabRepoMatch) {
|
|
213
|
+
const repoPath = gitlabRepoMatch[1];
|
|
214
|
+
if (repoPath.includes("/")) return {
|
|
215
|
+
type: "gitlab",
|
|
216
|
+
url: `https://gitlab.com/${repoPath}.git`
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const atSkillMatch = input.match(/^([^/]+)\/([^/@]+)@(.+)$/);
|
|
220
|
+
if (atSkillMatch && !input.includes(":") && !input.startsWith(".") && !input.startsWith("/")) {
|
|
221
|
+
const [, owner, repo, skillFilter] = atSkillMatch;
|
|
222
|
+
return {
|
|
223
|
+
type: "github",
|
|
224
|
+
url: `https://github.com/${owner}/${repo}.git`,
|
|
225
|
+
skillFilter
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const shorthandMatch = input.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
|
|
229
|
+
if (shorthandMatch && !input.includes(":") && !input.startsWith(".") && !input.startsWith("/")) {
|
|
230
|
+
const [, owner, repo, subpath] = shorthandMatch;
|
|
231
|
+
return {
|
|
232
|
+
type: "github",
|
|
233
|
+
url: `https://github.com/${owner}/${repo}.git`,
|
|
234
|
+
subpath
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (isWellKnownUrl(input)) return {
|
|
238
|
+
type: "well-known",
|
|
239
|
+
url: input
|
|
240
|
+
};
|
|
241
|
+
return {
|
|
242
|
+
type: "git",
|
|
243
|
+
url: input
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function isWellKnownUrl(input) {
|
|
247
|
+
if (!input.startsWith("http://") && !input.startsWith("https://")) return false;
|
|
248
|
+
try {
|
|
249
|
+
const parsed = new URL(input);
|
|
250
|
+
if ([
|
|
251
|
+
"github.com",
|
|
252
|
+
"gitlab.com",
|
|
253
|
+
"huggingface.co",
|
|
254
|
+
"raw.githubusercontent.com"
|
|
255
|
+
].includes(parsed.hostname)) return false;
|
|
256
|
+
if (input.toLowerCase().endsWith("/skill.md")) return false;
|
|
257
|
+
if (input.endsWith(".git")) return false;
|
|
258
|
+
return true;
|
|
259
|
+
} catch {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const home = homedir();
|
|
264
|
+
const configHome = xdgConfig ?? join(home, ".config");
|
|
265
|
+
const codexHome = process.env.CODEX_HOME?.trim() || join(home, ".codex");
|
|
266
|
+
const claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude");
|
|
267
|
+
const agents = {
|
|
268
|
+
amp: {
|
|
269
|
+
name: "amp",
|
|
270
|
+
displayName: "Amp",
|
|
271
|
+
skillsDir: ".agents/skills",
|
|
272
|
+
globalSkillsDir: join(configHome, "agents/skills"),
|
|
273
|
+
agentsDir: ".agents/agents",
|
|
274
|
+
globalAgentsDir: join(configHome, "agents/agents"),
|
|
275
|
+
promptsDir: ".agents/prompts",
|
|
276
|
+
globalPromptsDir: join(configHome, "agents/prompts"),
|
|
277
|
+
detectInstalled: async () => {
|
|
278
|
+
return existsSync(join(configHome, "amp"));
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
antigravity: {
|
|
282
|
+
name: "antigravity",
|
|
283
|
+
displayName: "Antigravity",
|
|
284
|
+
skillsDir: ".agent/skills",
|
|
285
|
+
globalSkillsDir: join(home, ".gemini/antigravity/skills"),
|
|
286
|
+
agentsDir: ".agent/agents",
|
|
287
|
+
globalAgentsDir: join(home, ".gemini/antigravity/agents"),
|
|
288
|
+
promptsDir: ".agent/prompts",
|
|
289
|
+
globalPromptsDir: join(home, ".gemini/antigravity/prompts"),
|
|
290
|
+
detectInstalled: async () => {
|
|
291
|
+
return existsSync(join(process.cwd(), ".agent")) || existsSync(join(home, ".gemini/antigravity"));
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
augment: {
|
|
295
|
+
name: "augment",
|
|
296
|
+
displayName: "Augment",
|
|
297
|
+
skillsDir: ".augment/skills",
|
|
298
|
+
globalSkillsDir: join(home, ".augment/skills"),
|
|
299
|
+
agentsDir: ".augment/agents",
|
|
300
|
+
globalAgentsDir: join(home, ".augment/agents"),
|
|
301
|
+
promptsDir: ".augment/prompts",
|
|
302
|
+
globalPromptsDir: join(home, ".augment/prompts"),
|
|
303
|
+
detectInstalled: async () => {
|
|
304
|
+
return existsSync(join(home, ".augment"));
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
"claude-code": {
|
|
308
|
+
name: "claude-code",
|
|
309
|
+
displayName: "Claude Code",
|
|
310
|
+
skillsDir: ".claude/skills",
|
|
311
|
+
globalSkillsDir: join(claudeHome, "skills"),
|
|
312
|
+
agentsDir: ".claude/agents",
|
|
313
|
+
globalAgentsDir: join(claudeHome, "agents"),
|
|
314
|
+
promptsDir: ".claude/prompts",
|
|
315
|
+
globalPromptsDir: join(claudeHome, "prompts"),
|
|
316
|
+
detectInstalled: async () => {
|
|
317
|
+
return existsSync(claudeHome);
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
openclaw: {
|
|
321
|
+
name: "openclaw",
|
|
322
|
+
displayName: "OpenClaw",
|
|
323
|
+
skillsDir: "skills",
|
|
324
|
+
globalSkillsDir: existsSync(join(home, ".openclaw")) ? join(home, ".openclaw/skills") : existsSync(join(home, ".clawdbot")) ? join(home, ".clawdbot/skills") : join(home, ".moltbot/skills"),
|
|
325
|
+
agentsDir: "agents",
|
|
326
|
+
globalAgentsDir: existsSync(join(home, ".openclaw")) ? join(home, ".openclaw/agents") : existsSync(join(home, ".clawdbot")) ? join(home, ".clawdbot/agents") : join(home, ".moltbot/agents"),
|
|
327
|
+
promptsDir: "prompts",
|
|
328
|
+
globalPromptsDir: existsSync(join(home, ".openclaw")) ? join(home, ".openclaw/prompts") : existsSync(join(home, ".clawdbot")) ? join(home, ".clawdbot/prompts") : join(home, ".moltbot/prompts"),
|
|
329
|
+
detectInstalled: async () => {
|
|
330
|
+
return existsSync(join(home, ".openclaw")) || existsSync(join(home, ".clawdbot")) || existsSync(join(home, ".moltbot"));
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
cline: {
|
|
334
|
+
name: "cline",
|
|
335
|
+
displayName: "Cline",
|
|
336
|
+
skillsDir: ".cline/skills",
|
|
337
|
+
globalSkillsDir: join(home, ".cline/skills"),
|
|
338
|
+
agentsDir: ".cline/agents",
|
|
339
|
+
globalAgentsDir: join(home, ".cline/agents"),
|
|
340
|
+
promptsDir: ".cline/prompts",
|
|
341
|
+
globalPromptsDir: join(home, ".cline/prompts"),
|
|
342
|
+
detectInstalled: async () => {
|
|
343
|
+
return existsSync(join(home, ".cline"));
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
codebuddy: {
|
|
347
|
+
name: "codebuddy",
|
|
348
|
+
displayName: "CodeBuddy",
|
|
349
|
+
skillsDir: ".codebuddy/skills",
|
|
350
|
+
globalSkillsDir: join(home, ".codebuddy/skills"),
|
|
351
|
+
agentsDir: ".codebuddy/agents",
|
|
352
|
+
globalAgentsDir: join(home, ".codebuddy/agents"),
|
|
353
|
+
promptsDir: ".codebuddy/prompts",
|
|
354
|
+
globalPromptsDir: join(home, ".codebuddy/prompts"),
|
|
355
|
+
detectInstalled: async () => {
|
|
356
|
+
return existsSync(join(process.cwd(), ".codebuddy")) || existsSync(join(home, ".codebuddy"));
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
codex: {
|
|
360
|
+
name: "codex",
|
|
361
|
+
displayName: "Codex",
|
|
362
|
+
skillsDir: ".agents/skills",
|
|
363
|
+
globalSkillsDir: join(codexHome, "skills"),
|
|
364
|
+
agentsDir: ".agents/agents",
|
|
365
|
+
globalAgentsDir: join(codexHome, "agents"),
|
|
366
|
+
promptsDir: ".agents/prompts",
|
|
367
|
+
globalPromptsDir: join(codexHome, "prompts"),
|
|
368
|
+
detectInstalled: async () => {
|
|
369
|
+
return existsSync(codexHome) || existsSync("/etc/codex");
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
"command-code": {
|
|
373
|
+
name: "command-code",
|
|
374
|
+
displayName: "Command Code",
|
|
375
|
+
skillsDir: ".commandcode/skills",
|
|
376
|
+
globalSkillsDir: join(home, ".commandcode/skills"),
|
|
377
|
+
agentsDir: ".commandcode/agents",
|
|
378
|
+
globalAgentsDir: join(home, ".commandcode/agents"),
|
|
379
|
+
promptsDir: ".commandcode/prompts",
|
|
380
|
+
globalPromptsDir: join(home, ".commandcode/prompts"),
|
|
381
|
+
detectInstalled: async () => {
|
|
382
|
+
return existsSync(join(home, ".commandcode"));
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
continue: {
|
|
386
|
+
name: "continue",
|
|
387
|
+
displayName: "Continue",
|
|
388
|
+
skillsDir: ".continue/skills",
|
|
389
|
+
globalSkillsDir: join(home, ".continue/skills"),
|
|
390
|
+
agentsDir: ".continue/agents",
|
|
391
|
+
globalAgentsDir: join(home, ".continue/agents"),
|
|
392
|
+
promptsDir: ".continue/prompts",
|
|
393
|
+
globalPromptsDir: join(home, ".continue/prompts"),
|
|
394
|
+
detectInstalled: async () => {
|
|
395
|
+
return existsSync(join(process.cwd(), ".continue")) || existsSync(join(home, ".continue"));
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
crush: {
|
|
399
|
+
name: "crush",
|
|
400
|
+
displayName: "Crush",
|
|
401
|
+
skillsDir: ".crush/skills",
|
|
402
|
+
globalSkillsDir: join(home, ".config/crush/skills"),
|
|
403
|
+
agentsDir: ".crush/agents",
|
|
404
|
+
globalAgentsDir: join(home, ".config/crush/agents"),
|
|
405
|
+
promptsDir: ".crush/prompts",
|
|
406
|
+
globalPromptsDir: join(home, ".config/crush/prompts"),
|
|
407
|
+
detectInstalled: async () => {
|
|
408
|
+
return existsSync(join(home, ".config/crush"));
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
cursor: {
|
|
412
|
+
name: "cursor",
|
|
413
|
+
displayName: "Cursor",
|
|
414
|
+
skillsDir: ".cursor/skills",
|
|
415
|
+
globalSkillsDir: join(home, ".cursor/skills"),
|
|
416
|
+
agentsDir: ".cursor/agents",
|
|
417
|
+
globalAgentsDir: join(home, ".cursor/agents"),
|
|
418
|
+
promptsDir: ".cursor/prompts",
|
|
419
|
+
globalPromptsDir: join(home, ".cursor/prompts"),
|
|
420
|
+
detectInstalled: async () => {
|
|
421
|
+
return existsSync(join(home, ".cursor"));
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
droid: {
|
|
425
|
+
name: "droid",
|
|
426
|
+
displayName: "Droid",
|
|
427
|
+
skillsDir: ".factory/skills",
|
|
428
|
+
globalSkillsDir: join(home, ".factory/skills"),
|
|
429
|
+
agentsDir: ".factory/agents",
|
|
430
|
+
globalAgentsDir: join(home, ".factory/agents"),
|
|
431
|
+
promptsDir: ".factory/prompts",
|
|
432
|
+
globalPromptsDir: join(home, ".factory/prompts"),
|
|
433
|
+
detectInstalled: async () => {
|
|
434
|
+
return existsSync(join(home, ".factory"));
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
"gemini-cli": {
|
|
438
|
+
name: "gemini-cli",
|
|
439
|
+
displayName: "Gemini CLI",
|
|
440
|
+
skillsDir: ".agents/skills",
|
|
441
|
+
globalSkillsDir: join(home, ".gemini/skills"),
|
|
442
|
+
agentsDir: ".agents/agents",
|
|
443
|
+
globalAgentsDir: join(home, ".gemini/agents"),
|
|
444
|
+
promptsDir: ".agents/prompts",
|
|
445
|
+
globalPromptsDir: join(home, ".gemini/prompts"),
|
|
446
|
+
detectInstalled: async () => {
|
|
447
|
+
return existsSync(join(home, ".gemini"));
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
"github-copilot": {
|
|
451
|
+
name: "github-copilot",
|
|
452
|
+
displayName: "GitHub Copilot",
|
|
453
|
+
skillsDir: ".agents/skills",
|
|
454
|
+
globalSkillsDir: join(home, ".copilot/skills"),
|
|
455
|
+
agentsDir: ".agents/agents",
|
|
456
|
+
globalAgentsDir: join(home, ".copilot/agents"),
|
|
457
|
+
promptsDir: ".agents/prompts",
|
|
458
|
+
globalPromptsDir: join(home, ".copilot/prompts"),
|
|
459
|
+
detectInstalled: async () => {
|
|
460
|
+
return existsSync(join(process.cwd(), ".github")) || existsSync(join(home, ".copilot"));
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
goose: {
|
|
464
|
+
name: "goose",
|
|
465
|
+
displayName: "Goose",
|
|
466
|
+
skillsDir: ".goose/skills",
|
|
467
|
+
globalSkillsDir: join(configHome, "goose/skills"),
|
|
468
|
+
agentsDir: ".goose/agents",
|
|
469
|
+
globalAgentsDir: join(configHome, "goose/agents"),
|
|
470
|
+
promptsDir: ".goose/prompts",
|
|
471
|
+
globalPromptsDir: join(configHome, "goose/prompts"),
|
|
472
|
+
detectInstalled: async () => {
|
|
473
|
+
return existsSync(join(configHome, "goose"));
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
junie: {
|
|
477
|
+
name: "junie",
|
|
478
|
+
displayName: "Junie",
|
|
479
|
+
skillsDir: ".junie/skills",
|
|
480
|
+
globalSkillsDir: join(home, ".junie/skills"),
|
|
481
|
+
agentsDir: ".junie/agents",
|
|
482
|
+
globalAgentsDir: join(home, ".junie/agents"),
|
|
483
|
+
promptsDir: ".junie/prompts",
|
|
484
|
+
globalPromptsDir: join(home, ".junie/prompts"),
|
|
485
|
+
detectInstalled: async () => {
|
|
486
|
+
return existsSync(join(home, ".junie"));
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
"iflow-cli": {
|
|
490
|
+
name: "iflow-cli",
|
|
491
|
+
displayName: "iFlow CLI",
|
|
492
|
+
skillsDir: ".iflow/skills",
|
|
493
|
+
globalSkillsDir: join(home, ".iflow/skills"),
|
|
494
|
+
agentsDir: ".iflow/agents",
|
|
495
|
+
globalAgentsDir: join(home, ".iflow/agents"),
|
|
496
|
+
promptsDir: ".iflow/prompts",
|
|
497
|
+
globalPromptsDir: join(home, ".iflow/prompts"),
|
|
498
|
+
detectInstalled: async () => {
|
|
499
|
+
return existsSync(join(home, ".iflow"));
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
kilo: {
|
|
503
|
+
name: "kilo",
|
|
504
|
+
displayName: "Kilo Code",
|
|
505
|
+
skillsDir: ".kilocode/skills",
|
|
506
|
+
globalSkillsDir: join(home, ".kilocode/skills"),
|
|
507
|
+
agentsDir: ".kilocode/agents",
|
|
508
|
+
globalAgentsDir: join(home, ".kilocode/agents"),
|
|
509
|
+
promptsDir: ".kilocode/prompts",
|
|
510
|
+
globalPromptsDir: join(home, ".kilocode/prompts"),
|
|
511
|
+
detectInstalled: async () => {
|
|
512
|
+
return existsSync(join(home, ".kilocode"));
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
"kimi-cli": {
|
|
516
|
+
name: "kimi-cli",
|
|
517
|
+
displayName: "Kimi Code CLI",
|
|
518
|
+
skillsDir: ".agents/skills",
|
|
519
|
+
globalSkillsDir: join(home, ".config/agents/skills"),
|
|
520
|
+
agentsDir: ".agents/agents",
|
|
521
|
+
globalAgentsDir: join(home, ".config/agents/agents"),
|
|
522
|
+
promptsDir: ".agents/prompts",
|
|
523
|
+
globalPromptsDir: join(home, ".config/agents/prompts"),
|
|
524
|
+
detectInstalled: async () => {
|
|
525
|
+
return existsSync(join(home, ".kimi"));
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
"kiro-cli": {
|
|
529
|
+
name: "kiro-cli",
|
|
530
|
+
displayName: "Kiro CLI",
|
|
531
|
+
skillsDir: ".kiro/skills",
|
|
532
|
+
globalSkillsDir: join(home, ".kiro/skills"),
|
|
533
|
+
agentsDir: ".kiro/agents",
|
|
534
|
+
globalAgentsDir: join(home, ".kiro/agents"),
|
|
535
|
+
promptsDir: ".kiro/prompts",
|
|
536
|
+
globalPromptsDir: join(home, ".kiro/prompts"),
|
|
537
|
+
detectInstalled: async () => {
|
|
538
|
+
return existsSync(join(home, ".kiro"));
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
kode: {
|
|
542
|
+
name: "kode",
|
|
543
|
+
displayName: "Kode",
|
|
544
|
+
skillsDir: ".kode/skills",
|
|
545
|
+
globalSkillsDir: join(home, ".kode/skills"),
|
|
546
|
+
agentsDir: ".kode/agents",
|
|
547
|
+
globalAgentsDir: join(home, ".kode/agents"),
|
|
548
|
+
promptsDir: ".kode/prompts",
|
|
549
|
+
globalPromptsDir: join(home, ".kode/prompts"),
|
|
550
|
+
detectInstalled: async () => {
|
|
551
|
+
return existsSync(join(home, ".kode"));
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
mcpjam: {
|
|
555
|
+
name: "mcpjam",
|
|
556
|
+
displayName: "MCPJam",
|
|
557
|
+
skillsDir: ".mcpjam/skills",
|
|
558
|
+
globalSkillsDir: join(home, ".mcpjam/skills"),
|
|
559
|
+
agentsDir: ".mcpjam/agents",
|
|
560
|
+
globalAgentsDir: join(home, ".mcpjam/agents"),
|
|
561
|
+
promptsDir: ".mcpjam/prompts",
|
|
562
|
+
globalPromptsDir: join(home, ".mcpjam/prompts"),
|
|
563
|
+
detectInstalled: async () => {
|
|
564
|
+
return existsSync(join(home, ".mcpjam"));
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
"mistral-vibe": {
|
|
568
|
+
name: "mistral-vibe",
|
|
569
|
+
displayName: "Mistral Vibe",
|
|
570
|
+
skillsDir: ".vibe/skills",
|
|
571
|
+
globalSkillsDir: join(home, ".vibe/skills"),
|
|
572
|
+
agentsDir: ".vibe/agents",
|
|
573
|
+
globalAgentsDir: join(home, ".vibe/agents"),
|
|
574
|
+
promptsDir: ".vibe/prompts",
|
|
575
|
+
globalPromptsDir: join(home, ".vibe/prompts"),
|
|
576
|
+
detectInstalled: async () => {
|
|
577
|
+
return existsSync(join(home, ".vibe"));
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
mux: {
|
|
581
|
+
name: "mux",
|
|
582
|
+
displayName: "Mux",
|
|
583
|
+
skillsDir: ".mux/skills",
|
|
584
|
+
globalSkillsDir: join(home, ".mux/skills"),
|
|
585
|
+
agentsDir: ".mux/agents",
|
|
586
|
+
globalAgentsDir: join(home, ".mux/agents"),
|
|
587
|
+
promptsDir: ".mux/prompts",
|
|
588
|
+
globalPromptsDir: join(home, ".mux/prompts"),
|
|
589
|
+
detectInstalled: async () => {
|
|
590
|
+
return existsSync(join(home, ".mux"));
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
opencode: {
|
|
594
|
+
name: "opencode",
|
|
595
|
+
displayName: "OpenCode",
|
|
596
|
+
skillsDir: ".agents/skills",
|
|
597
|
+
globalSkillsDir: join(configHome, "opencode/skills"),
|
|
598
|
+
agentsDir: ".agents/agents",
|
|
599
|
+
globalAgentsDir: join(configHome, "opencode/agents"),
|
|
600
|
+
promptsDir: ".agents/prompts",
|
|
601
|
+
globalPromptsDir: join(configHome, "opencode/prompts"),
|
|
602
|
+
detectInstalled: async () => {
|
|
603
|
+
return existsSync(join(configHome, "opencode")) || existsSync(join(claudeHome, "skills"));
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
openhands: {
|
|
607
|
+
name: "openhands",
|
|
608
|
+
displayName: "OpenHands",
|
|
609
|
+
skillsDir: ".openhands/skills",
|
|
610
|
+
globalSkillsDir: join(home, ".openhands/skills"),
|
|
611
|
+
agentsDir: ".openhands/agents",
|
|
612
|
+
globalAgentsDir: join(home, ".openhands/agents"),
|
|
613
|
+
promptsDir: ".openhands/prompts",
|
|
614
|
+
globalPromptsDir: join(home, ".openhands/prompts"),
|
|
615
|
+
detectInstalled: async () => {
|
|
616
|
+
return existsSync(join(home, ".openhands"));
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
pi: {
|
|
620
|
+
name: "pi",
|
|
621
|
+
displayName: "Pi",
|
|
622
|
+
skillsDir: ".pi/skills",
|
|
623
|
+
globalSkillsDir: join(home, ".pi/agent/skills"),
|
|
624
|
+
agentsDir: ".pi/agents",
|
|
625
|
+
globalAgentsDir: join(home, ".pi/agent/agents"),
|
|
626
|
+
promptsDir: ".pi/prompts",
|
|
627
|
+
globalPromptsDir: join(home, ".pi/agent/prompts"),
|
|
628
|
+
detectInstalled: async () => {
|
|
629
|
+
return existsSync(join(home, ".pi/agent"));
|
|
630
|
+
}
|
|
631
|
+
},
|
|
632
|
+
qoder: {
|
|
633
|
+
name: "qoder",
|
|
634
|
+
displayName: "Qoder",
|
|
635
|
+
skillsDir: ".qoder/skills",
|
|
636
|
+
globalSkillsDir: join(home, ".qoder/skills"),
|
|
637
|
+
agentsDir: ".qoder/agents",
|
|
638
|
+
globalAgentsDir: join(home, ".qoder/agents"),
|
|
639
|
+
promptsDir: ".qoder/prompts",
|
|
640
|
+
globalPromptsDir: join(home, ".qoder/prompts"),
|
|
641
|
+
detectInstalled: async () => {
|
|
642
|
+
return existsSync(join(home, ".qoder"));
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
"qwen-code": {
|
|
646
|
+
name: "qwen-code",
|
|
647
|
+
displayName: "Qwen Code",
|
|
648
|
+
skillsDir: ".qwen/skills",
|
|
649
|
+
globalSkillsDir: join(home, ".qwen/skills"),
|
|
650
|
+
agentsDir: ".qwen/agents",
|
|
651
|
+
globalAgentsDir: join(home, ".qwen/agents"),
|
|
652
|
+
promptsDir: ".qwen/prompts",
|
|
653
|
+
globalPromptsDir: join(home, ".qwen/prompts"),
|
|
654
|
+
detectInstalled: async () => {
|
|
655
|
+
return existsSync(join(home, ".qwen"));
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
replit: {
|
|
659
|
+
name: "replit",
|
|
660
|
+
displayName: "Replit",
|
|
661
|
+
skillsDir: ".agents/skills",
|
|
662
|
+
globalSkillsDir: join(configHome, "agents/skills"),
|
|
663
|
+
agentsDir: ".agents/agents",
|
|
664
|
+
globalAgentsDir: join(configHome, "agents/agents"),
|
|
665
|
+
promptsDir: ".agents/prompts",
|
|
666
|
+
globalPromptsDir: join(configHome, "agents/prompts"),
|
|
667
|
+
showInUniversalList: false,
|
|
668
|
+
detectInstalled: async () => {
|
|
669
|
+
return existsSync(join(process.cwd(), ".agents"));
|
|
670
|
+
}
|
|
671
|
+
},
|
|
672
|
+
roo: {
|
|
673
|
+
name: "roo",
|
|
674
|
+
displayName: "Roo Code",
|
|
675
|
+
skillsDir: ".roo/skills",
|
|
676
|
+
globalSkillsDir: join(home, ".roo/skills"),
|
|
677
|
+
agentsDir: ".roo/agents",
|
|
678
|
+
globalAgentsDir: join(home, ".roo/agents"),
|
|
679
|
+
promptsDir: ".roo/prompts",
|
|
680
|
+
globalPromptsDir: join(home, ".roo/prompts"),
|
|
681
|
+
detectInstalled: async () => {
|
|
682
|
+
return existsSync(join(home, ".roo"));
|
|
683
|
+
}
|
|
684
|
+
},
|
|
685
|
+
trae: {
|
|
686
|
+
name: "trae",
|
|
687
|
+
displayName: "Trae",
|
|
688
|
+
skillsDir: ".trae/skills",
|
|
689
|
+
globalSkillsDir: join(home, ".trae/skills"),
|
|
690
|
+
agentsDir: ".trae/agents",
|
|
691
|
+
globalAgentsDir: join(home, ".trae/agents"),
|
|
692
|
+
promptsDir: ".trae/prompts",
|
|
693
|
+
globalPromptsDir: join(home, ".trae/prompts"),
|
|
694
|
+
detectInstalled: async () => {
|
|
695
|
+
return existsSync(join(home, ".trae"));
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
"trae-cn": {
|
|
699
|
+
name: "trae-cn",
|
|
700
|
+
displayName: "Trae CN",
|
|
701
|
+
skillsDir: ".trae/skills",
|
|
702
|
+
globalSkillsDir: join(home, ".trae-cn/skills"),
|
|
703
|
+
agentsDir: ".trae/agents",
|
|
704
|
+
globalAgentsDir: join(home, ".trae-cn/agents"),
|
|
705
|
+
promptsDir: ".trae/prompts",
|
|
706
|
+
globalPromptsDir: join(home, ".trae-cn/prompts"),
|
|
707
|
+
detectInstalled: async () => {
|
|
708
|
+
return existsSync(join(home, ".trae-cn"));
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
windsurf: {
|
|
712
|
+
name: "windsurf",
|
|
713
|
+
displayName: "Windsurf",
|
|
714
|
+
skillsDir: ".windsurf/skills",
|
|
715
|
+
globalSkillsDir: join(home, ".codeium/windsurf/skills"),
|
|
716
|
+
agentsDir: ".windsurf/agents",
|
|
717
|
+
globalAgentsDir: join(home, ".codeium/windsurf/agents"),
|
|
718
|
+
promptsDir: ".windsurf/prompts",
|
|
719
|
+
globalPromptsDir: join(home, ".codeium/windsurf/prompts"),
|
|
720
|
+
detectInstalled: async () => {
|
|
721
|
+
return existsSync(join(home, ".codeium/windsurf"));
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
zencoder: {
|
|
725
|
+
name: "zencoder",
|
|
726
|
+
displayName: "Zencoder",
|
|
727
|
+
skillsDir: ".zencoder/skills",
|
|
728
|
+
globalSkillsDir: join(home, ".zencoder/skills"),
|
|
729
|
+
agentsDir: ".zencoder/agents",
|
|
730
|
+
globalAgentsDir: join(home, ".zencoder/agents"),
|
|
731
|
+
promptsDir: ".zencoder/prompts",
|
|
732
|
+
globalPromptsDir: join(home, ".zencoder/prompts"),
|
|
733
|
+
detectInstalled: async () => {
|
|
734
|
+
return existsSync(join(home, ".zencoder"));
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
neovate: {
|
|
738
|
+
name: "neovate",
|
|
739
|
+
displayName: "Neovate",
|
|
740
|
+
skillsDir: ".neovate/skills",
|
|
741
|
+
globalSkillsDir: join(home, ".neovate/skills"),
|
|
742
|
+
agentsDir: ".neovate/agents",
|
|
743
|
+
globalAgentsDir: join(home, ".neovate/agents"),
|
|
744
|
+
promptsDir: ".neovate/prompts",
|
|
745
|
+
globalPromptsDir: join(home, ".neovate/prompts"),
|
|
746
|
+
detectInstalled: async () => {
|
|
747
|
+
return existsSync(join(home, ".neovate"));
|
|
748
|
+
}
|
|
749
|
+
},
|
|
750
|
+
pochi: {
|
|
751
|
+
name: "pochi",
|
|
752
|
+
displayName: "Pochi",
|
|
753
|
+
skillsDir: ".pochi/skills",
|
|
754
|
+
globalSkillsDir: join(home, ".pochi/skills"),
|
|
755
|
+
agentsDir: ".pochi/agents",
|
|
756
|
+
globalAgentsDir: join(home, ".pochi/agents"),
|
|
757
|
+
promptsDir: ".pochi/prompts",
|
|
758
|
+
globalPromptsDir: join(home, ".pochi/prompts"),
|
|
759
|
+
detectInstalled: async () => {
|
|
760
|
+
return existsSync(join(home, ".pochi"));
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
adal: {
|
|
764
|
+
name: "adal",
|
|
765
|
+
displayName: "AdaL",
|
|
766
|
+
skillsDir: ".adal/skills",
|
|
767
|
+
globalSkillsDir: join(home, ".adal/skills"),
|
|
768
|
+
agentsDir: ".adal/agents",
|
|
769
|
+
globalAgentsDir: join(home, ".adal/agents"),
|
|
770
|
+
promptsDir: ".adal/prompts",
|
|
771
|
+
globalPromptsDir: join(home, ".adal/prompts"),
|
|
772
|
+
detectInstalled: async () => {
|
|
773
|
+
return existsSync(join(home, ".adal"));
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
async function detectInstalledAgents() {
|
|
778
|
+
return (await Promise.all(Object.entries(agents).map(async ([type, config]) => ({
|
|
779
|
+
type,
|
|
780
|
+
installed: await config.detectInstalled()
|
|
781
|
+
})))).filter((r) => r.installed).map((r) => r.type);
|
|
782
|
+
}
|
|
783
|
+
const AGENTS_DIR$3 = ".agents";
|
|
784
|
+
const COGNITIVE_SUBDIRS = {
|
|
785
|
+
skill: "skills",
|
|
786
|
+
agent: "agents",
|
|
787
|
+
prompt: "prompts"
|
|
788
|
+
};
|
|
789
|
+
const COGNITIVE_FILE_NAMES = {
|
|
790
|
+
skill: "SKILL.md",
|
|
791
|
+
agent: "AGENT.md",
|
|
792
|
+
prompt: "PROMPT.md"
|
|
793
|
+
};
|
|
794
|
+
function getUniversalAgents() {
|
|
795
|
+
return Object.entries(agents).filter(([_, config]) => config.skillsDir === ".agents/skills" && config.showInUniversalList !== false).map(([type]) => type);
|
|
796
|
+
}
|
|
797
|
+
function getNonUniversalAgents() {
|
|
798
|
+
return Object.entries(agents).filter(([_, config]) => config.skillsDir !== ".agents/skills").map(([type]) => type);
|
|
799
|
+
}
|
|
800
|
+
function isUniversalAgent(type) {
|
|
801
|
+
return agents[type].skillsDir === ".agents/skills";
|
|
802
|
+
}
|
|
803
|
+
function getCognitiveDir(agentType, cognitiveType, scope) {
|
|
804
|
+
const agent = agents[agentType];
|
|
805
|
+
if (scope === "global") switch (cognitiveType) {
|
|
806
|
+
case "skill": return agent.globalSkillsDir;
|
|
807
|
+
case "agent": return agent.globalAgentsDir;
|
|
808
|
+
case "prompt": return agent.globalPromptsDir;
|
|
809
|
+
}
|
|
810
|
+
else switch (cognitiveType) {
|
|
811
|
+
case "skill": return agent.skillsDir;
|
|
812
|
+
case "agent": return agent.agentsDir;
|
|
813
|
+
case "prompt": return agent.promptsDir;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
function isUniversalForType(agentType, cognitiveType) {
|
|
817
|
+
const agent = agents[agentType];
|
|
818
|
+
const universalDir = `.agents/${COGNITIVE_SUBDIRS[cognitiveType]}`;
|
|
819
|
+
switch (cognitiveType) {
|
|
820
|
+
case "skill": return agent.skillsDir === universalDir;
|
|
821
|
+
case "agent": return agent.agentsDir === universalDir;
|
|
822
|
+
case "prompt": return agent.promptsDir === universalDir;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
function shortenPath$1(fullPath, cwd) {
|
|
826
|
+
const home = homedir();
|
|
827
|
+
if (fullPath === home || fullPath.startsWith(home + sep)) return "~" + fullPath.slice(home.length);
|
|
828
|
+
if (fullPath === cwd || fullPath.startsWith(cwd + sep)) return "." + fullPath.slice(cwd.length);
|
|
829
|
+
return fullPath;
|
|
830
|
+
}
|
|
831
|
+
function formatList$1(items, maxShow = 5) {
|
|
832
|
+
if (items.length <= maxShow) return items.join(", ");
|
|
833
|
+
const shown = items.slice(0, maxShow);
|
|
834
|
+
const remaining = items.length - maxShow;
|
|
835
|
+
return `${shown.join(", ")} +${remaining} more`;
|
|
836
|
+
}
|
|
837
|
+
function splitAgentsByType(agentTypes) {
|
|
838
|
+
const universal = [];
|
|
839
|
+
const symlinked = [];
|
|
840
|
+
for (const a of agentTypes) if (isUniversalAgent(a)) universal.push(agents[a].displayName);
|
|
841
|
+
else symlinked.push(agents[a].displayName);
|
|
842
|
+
return {
|
|
843
|
+
universal,
|
|
844
|
+
symlinked
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
function buildAgentSummaryLines(targetAgents, installMode) {
|
|
848
|
+
const lines = [];
|
|
849
|
+
const { universal, symlinked } = splitAgentsByType(targetAgents);
|
|
850
|
+
if (installMode === "symlink") {
|
|
851
|
+
if (universal.length > 0) lines.push(` ${import_picocolors.default.green("universal:")} ${formatList$1(universal)}`);
|
|
852
|
+
if (symlinked.length > 0) lines.push(` ${import_picocolors.default.dim("symlink →")} ${formatList$1(symlinked)}`);
|
|
853
|
+
} else {
|
|
854
|
+
const allNames = targetAgents.map((a) => agents[a].displayName);
|
|
855
|
+
lines.push(` ${import_picocolors.default.dim("copy →")} ${formatList$1(allNames)}`);
|
|
856
|
+
}
|
|
857
|
+
return lines;
|
|
858
|
+
}
|
|
859
|
+
function ensureUniversalAgents(targetAgents) {
|
|
860
|
+
const universalAgents = getUniversalAgents();
|
|
861
|
+
const result = [...targetAgents];
|
|
862
|
+
for (const ua of universalAgents) if (!result.includes(ua)) result.push(ua);
|
|
863
|
+
return result;
|
|
864
|
+
}
|
|
865
|
+
function buildResultLines(results, targetAgents) {
|
|
866
|
+
const lines = [];
|
|
867
|
+
const { universal, symlinked: symlinkAgents } = splitAgentsByType(targetAgents);
|
|
868
|
+
const successfulSymlinks = results.filter((r) => !r.symlinkFailed && !universal.includes(r.agent)).map((r) => r.agent);
|
|
869
|
+
const failedSymlinks = results.filter((r) => r.symlinkFailed).map((r) => r.agent);
|
|
870
|
+
if (universal.length > 0) lines.push(` ${import_picocolors.default.green("universal:")} ${formatList$1(universal)}`);
|
|
871
|
+
if (successfulSymlinks.length > 0) lines.push(` ${import_picocolors.default.dim("symlinked:")} ${formatList$1(successfulSymlinks)}`);
|
|
872
|
+
if (failedSymlinks.length > 0) lines.push(` ${import_picocolors.default.yellow("copied:")} ${formatList$1(failedSymlinks)}`);
|
|
873
|
+
return lines;
|
|
874
|
+
}
|
|
875
|
+
const AGENTS_DIR$2 = ".agents";
|
|
876
|
+
const LOCK_FILE$2 = ".skill-lock.json";
|
|
877
|
+
const CURRENT_VERSION = 4;
|
|
878
|
+
function getSkillLockPath$2() {
|
|
879
|
+
return join(homedir(), AGENTS_DIR$2, LOCK_FILE$2);
|
|
880
|
+
}
|
|
881
|
+
async function readSkillLock$2() {
|
|
882
|
+
const lockPath = getSkillLockPath$2();
|
|
883
|
+
try {
|
|
884
|
+
const content = await readFile(lockPath, "utf-8");
|
|
885
|
+
const parsed = JSON.parse(content);
|
|
886
|
+
if (typeof parsed.version !== "number" || !parsed.skills) return createEmptyLockFile();
|
|
887
|
+
if (parsed.version < CURRENT_VERSION) return createEmptyLockFile();
|
|
888
|
+
return parsed;
|
|
889
|
+
} catch (error) {
|
|
890
|
+
return createEmptyLockFile();
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
async function writeSkillLock(lock) {
|
|
894
|
+
const lockPath = getSkillLockPath$2();
|
|
895
|
+
await mkdir(dirname(lockPath), { recursive: true });
|
|
896
|
+
await writeFile(lockPath, JSON.stringify(lock, null, 2), "utf-8");
|
|
897
|
+
}
|
|
898
|
+
function getGitHubToken() {
|
|
899
|
+
if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;
|
|
900
|
+
if (process.env.GH_TOKEN) return process.env.GH_TOKEN;
|
|
901
|
+
try {
|
|
902
|
+
const token = execSync("gh auth token", {
|
|
903
|
+
encoding: "utf-8",
|
|
904
|
+
stdio: [
|
|
905
|
+
"pipe",
|
|
906
|
+
"pipe",
|
|
907
|
+
"pipe"
|
|
908
|
+
]
|
|
909
|
+
}).trim();
|
|
910
|
+
if (token) return token;
|
|
911
|
+
} catch {}
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
async function fetchSkillFolderHash(ownerRepo, skillPath, token) {
|
|
915
|
+
let folderPath = skillPath.replace(/\\/g, "/");
|
|
916
|
+
if (folderPath.endsWith("/SKILL.md")) folderPath = folderPath.slice(0, -9);
|
|
917
|
+
else if (folderPath.endsWith("SKILL.md")) folderPath = folderPath.slice(0, -8);
|
|
918
|
+
if (folderPath.endsWith("/")) folderPath = folderPath.slice(0, -1);
|
|
919
|
+
for (const branch of ["main", "master"]) try {
|
|
920
|
+
const url = `https://api.github.com/repos/${ownerRepo}/git/trees/${branch}?recursive=1`;
|
|
921
|
+
const headers = {
|
|
922
|
+
Accept: "application/vnd.github.v3+json",
|
|
923
|
+
"User-Agent": "skills-cli"
|
|
924
|
+
};
|
|
925
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
926
|
+
const response = await fetch(url, { headers });
|
|
927
|
+
if (!response.ok) continue;
|
|
928
|
+
const data = await response.json();
|
|
929
|
+
if (!folderPath) return data.sha;
|
|
930
|
+
const folderEntry = data.tree.find((entry) => entry.type === "tree" && entry.path === folderPath);
|
|
931
|
+
if (folderEntry) return folderEntry.sha;
|
|
932
|
+
} catch {
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
async function addCognitiveToLock(name, cognitiveType, entry) {
|
|
938
|
+
const lock = await readSkillLock$2();
|
|
939
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
940
|
+
const existingEntry = lock.skills[name];
|
|
941
|
+
lock.skills[name] = {
|
|
942
|
+
...entry,
|
|
943
|
+
cognitiveType,
|
|
944
|
+
installedAt: existingEntry?.installedAt ?? now,
|
|
945
|
+
updatedAt: now
|
|
946
|
+
};
|
|
947
|
+
await writeSkillLock(lock);
|
|
948
|
+
}
|
|
949
|
+
async function addSkillToLock(skillName, entry) {
|
|
950
|
+
return addCognitiveToLock(skillName, "skill", entry);
|
|
951
|
+
}
|
|
952
|
+
async function removeSkillFromLock(skillName) {
|
|
953
|
+
const lock = await readSkillLock$2();
|
|
954
|
+
if (!(skillName in lock.skills)) return false;
|
|
955
|
+
delete lock.skills[skillName];
|
|
956
|
+
await writeSkillLock(lock);
|
|
957
|
+
return true;
|
|
958
|
+
}
|
|
959
|
+
async function getSkillFromLock(skillName) {
|
|
960
|
+
return (await readSkillLock$2()).skills[skillName] ?? null;
|
|
961
|
+
}
|
|
962
|
+
function createEmptyLockFile() {
|
|
963
|
+
return {
|
|
964
|
+
version: CURRENT_VERSION,
|
|
965
|
+
skills: {},
|
|
966
|
+
dismissed: {}
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
async function isPromptDismissed(promptKey) {
|
|
970
|
+
return (await readSkillLock$2()).dismissed?.[promptKey] === true;
|
|
971
|
+
}
|
|
972
|
+
async function dismissPrompt(promptKey) {
|
|
973
|
+
const lock = await readSkillLock$2();
|
|
974
|
+
if (!lock.dismissed) lock.dismissed = {};
|
|
975
|
+
lock.dismissed[promptKey] = true;
|
|
976
|
+
await writeSkillLock(lock);
|
|
977
|
+
}
|
|
978
|
+
async function getLastSelectedAgents() {
|
|
979
|
+
return (await readSkillLock$2()).lastSelectedAgents;
|
|
980
|
+
}
|
|
981
|
+
async function saveSelectedAgents(agents) {
|
|
982
|
+
const lock = await readSkillLock$2();
|
|
983
|
+
lock.lastSelectedAgents = agents;
|
|
984
|
+
await writeSkillLock(lock);
|
|
985
|
+
}
|
|
986
|
+
const silentOutput = new Writable({ write(_chunk, _encoding, callback) {
|
|
987
|
+
callback();
|
|
988
|
+
} });
|
|
989
|
+
const S_STEP_ACTIVE = import_picocolors.default.green("◆");
|
|
990
|
+
const S_STEP_CANCEL = import_picocolors.default.red("■");
|
|
991
|
+
const S_STEP_SUBMIT = import_picocolors.default.green("◇");
|
|
992
|
+
const S_RADIO_ACTIVE = import_picocolors.default.green("●");
|
|
993
|
+
const S_RADIO_INACTIVE = import_picocolors.default.dim("○");
|
|
994
|
+
const S_CHECKBOX_LOCKED = import_picocolors.default.green("✓");
|
|
995
|
+
const S_BAR = import_picocolors.default.dim("│");
|
|
996
|
+
const S_BAR_H = import_picocolors.default.dim("─");
|
|
997
|
+
const cancelSymbol = Symbol("cancel");
|
|
998
|
+
async function searchMultiselect(options) {
|
|
999
|
+
const { message, items, maxVisible = 8, initialSelected = [], required = false, lockedSection } = options;
|
|
1000
|
+
return new Promise((resolve) => {
|
|
1001
|
+
const rl = readline.createInterface({
|
|
1002
|
+
input: process.stdin,
|
|
1003
|
+
output: silentOutput,
|
|
1004
|
+
terminal: false
|
|
1005
|
+
});
|
|
1006
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
1007
|
+
readline.emitKeypressEvents(process.stdin, rl);
|
|
1008
|
+
let query = "";
|
|
1009
|
+
let cursor = 0;
|
|
1010
|
+
const selected = new Set(initialSelected);
|
|
1011
|
+
let lastRenderHeight = 0;
|
|
1012
|
+
const lockedValues = lockedSection ? lockedSection.items.map((i) => i.value) : [];
|
|
1013
|
+
const filter = (item, q) => {
|
|
1014
|
+
if (!q) return true;
|
|
1015
|
+
const lowerQ = q.toLowerCase();
|
|
1016
|
+
return item.label.toLowerCase().includes(lowerQ) || String(item.value).toLowerCase().includes(lowerQ);
|
|
1017
|
+
};
|
|
1018
|
+
const getFiltered = () => {
|
|
1019
|
+
return items.filter((item) => filter(item, query));
|
|
1020
|
+
};
|
|
1021
|
+
const clearRender = () => {
|
|
1022
|
+
if (lastRenderHeight > 0) {
|
|
1023
|
+
process.stdout.write(`\x1b[${lastRenderHeight}A`);
|
|
1024
|
+
for (let i = 0; i < lastRenderHeight; i++) process.stdout.write("\x1B[2K\x1B[1B");
|
|
1025
|
+
process.stdout.write(`\x1b[${lastRenderHeight}A`);
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
const render = (state = "active") => {
|
|
1029
|
+
clearRender();
|
|
1030
|
+
const lines = [];
|
|
1031
|
+
const filtered = getFiltered();
|
|
1032
|
+
const icon = state === "active" ? S_STEP_ACTIVE : state === "cancel" ? S_STEP_CANCEL : S_STEP_SUBMIT;
|
|
1033
|
+
lines.push(`${icon} ${import_picocolors.default.bold(message)}`);
|
|
1034
|
+
if (state === "active") {
|
|
1035
|
+
if (lockedSection && lockedSection.items.length > 0) {
|
|
1036
|
+
lines.push(`${S_BAR}`);
|
|
1037
|
+
lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${import_picocolors.default.bold(lockedSection.title)} ${S_BAR_H.repeat(30)}`);
|
|
1038
|
+
for (const item of lockedSection.items) lines.push(`${S_BAR} ${S_CHECKBOX_LOCKED} ${item.label}`);
|
|
1039
|
+
lines.push(`${S_BAR}`);
|
|
1040
|
+
lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${import_picocolors.default.bold("Other agents")} ${S_BAR_H.repeat(34)}`);
|
|
1041
|
+
}
|
|
1042
|
+
const searchLine = `${S_BAR} ${import_picocolors.default.dim("Search:")} ${query}${import_picocolors.default.inverse(" ")}`;
|
|
1043
|
+
lines.push(searchLine);
|
|
1044
|
+
lines.push(`${S_BAR} ${import_picocolors.default.dim("↑↓ move, space select, enter confirm")}`);
|
|
1045
|
+
lines.push(`${S_BAR}`);
|
|
1046
|
+
const visibleStart = Math.max(0, Math.min(cursor - Math.floor(maxVisible / 2), filtered.length - maxVisible));
|
|
1047
|
+
const visibleEnd = Math.min(filtered.length, visibleStart + maxVisible);
|
|
1048
|
+
const visibleItems = filtered.slice(visibleStart, visibleEnd);
|
|
1049
|
+
if (filtered.length === 0) lines.push(`${S_BAR} ${import_picocolors.default.dim("No matches found")}`);
|
|
1050
|
+
else {
|
|
1051
|
+
for (let i = 0; i < visibleItems.length; i++) {
|
|
1052
|
+
const item = visibleItems[i];
|
|
1053
|
+
const actualIndex = visibleStart + i;
|
|
1054
|
+
const isSelected = selected.has(item.value);
|
|
1055
|
+
const isCursor = actualIndex === cursor;
|
|
1056
|
+
const radio = isSelected ? S_RADIO_ACTIVE : S_RADIO_INACTIVE;
|
|
1057
|
+
const label = isCursor ? import_picocolors.default.underline(item.label) : item.label;
|
|
1058
|
+
const hint = item.hint ? import_picocolors.default.dim(` (${item.hint})`) : "";
|
|
1059
|
+
const prefix = isCursor ? import_picocolors.default.cyan("❯") : " ";
|
|
1060
|
+
lines.push(`${S_BAR} ${prefix} ${radio} ${label}${hint}`);
|
|
1061
|
+
}
|
|
1062
|
+
const hiddenBefore = visibleStart;
|
|
1063
|
+
const hiddenAfter = filtered.length - visibleEnd;
|
|
1064
|
+
if (hiddenBefore > 0 || hiddenAfter > 0) {
|
|
1065
|
+
const parts = [];
|
|
1066
|
+
if (hiddenBefore > 0) parts.push(`↑ ${hiddenBefore} more`);
|
|
1067
|
+
if (hiddenAfter > 0) parts.push(`↓ ${hiddenAfter} more`);
|
|
1068
|
+
lines.push(`${S_BAR} ${import_picocolors.default.dim(parts.join(" "))}`);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
lines.push(`${S_BAR}`);
|
|
1072
|
+
const allSelectedLabels = [...lockedSection ? lockedSection.items.map((i) => i.label) : [], ...items.filter((item) => selected.has(item.value)).map((item) => item.label)];
|
|
1073
|
+
if (allSelectedLabels.length === 0) lines.push(`${S_BAR} ${import_picocolors.default.dim("Selected: (none)")}`);
|
|
1074
|
+
else {
|
|
1075
|
+
const summary = allSelectedLabels.length <= 3 ? allSelectedLabels.join(", ") : `${allSelectedLabels.slice(0, 3).join(", ")} +${allSelectedLabels.length - 3} more`;
|
|
1076
|
+
lines.push(`${S_BAR} ${import_picocolors.default.green("Selected:")} ${summary}`);
|
|
1077
|
+
}
|
|
1078
|
+
lines.push(`${import_picocolors.default.dim("└")}`);
|
|
1079
|
+
} else if (state === "submit") {
|
|
1080
|
+
const allSelectedLabels = [...lockedSection ? lockedSection.items.map((i) => i.label) : [], ...items.filter((item) => selected.has(item.value)).map((item) => item.label)];
|
|
1081
|
+
lines.push(`${S_BAR} ${import_picocolors.default.dim(allSelectedLabels.join(", "))}`);
|
|
1082
|
+
} else if (state === "cancel") lines.push(`${S_BAR} ${import_picocolors.default.strikethrough(import_picocolors.default.dim("Cancelled"))}`);
|
|
1083
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
1084
|
+
lastRenderHeight = lines.length;
|
|
1085
|
+
};
|
|
1086
|
+
const cleanup = () => {
|
|
1087
|
+
process.stdin.removeListener("keypress", keypressHandler);
|
|
1088
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
1089
|
+
rl.close();
|
|
1090
|
+
};
|
|
1091
|
+
const submit = () => {
|
|
1092
|
+
if (required && selected.size === 0 && lockedValues.length === 0) return;
|
|
1093
|
+
render("submit");
|
|
1094
|
+
cleanup();
|
|
1095
|
+
resolve([...lockedValues, ...Array.from(selected)]);
|
|
1096
|
+
};
|
|
1097
|
+
const cancel = () => {
|
|
1098
|
+
render("cancel");
|
|
1099
|
+
cleanup();
|
|
1100
|
+
resolve(cancelSymbol);
|
|
1101
|
+
};
|
|
1102
|
+
const keypressHandler = (_str, key) => {
|
|
1103
|
+
if (!key) return;
|
|
1104
|
+
const filtered = getFiltered();
|
|
1105
|
+
if (key.name === "return") {
|
|
1106
|
+
submit();
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
if (key.name === "escape" || key.ctrl && key.name === "c") {
|
|
1110
|
+
cancel();
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
if (key.name === "up") {
|
|
1114
|
+
cursor = Math.max(0, cursor - 1);
|
|
1115
|
+
render();
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
if (key.name === "down") {
|
|
1119
|
+
cursor = Math.min(filtered.length - 1, cursor + 1);
|
|
1120
|
+
render();
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
if (key.name === "space") {
|
|
1124
|
+
const item = filtered[cursor];
|
|
1125
|
+
if (item) if (selected.has(item.value)) selected.delete(item.value);
|
|
1126
|
+
else selected.add(item.value);
|
|
1127
|
+
render();
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
if (key.name === "backspace") {
|
|
1131
|
+
query = query.slice(0, -1);
|
|
1132
|
+
cursor = 0;
|
|
1133
|
+
render();
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
if (key.sequence && !key.ctrl && !key.meta && key.sequence.length === 1) {
|
|
1137
|
+
query += key.sequence;
|
|
1138
|
+
cursor = 0;
|
|
1139
|
+
render();
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
};
|
|
1143
|
+
process.stdin.on("keypress", keypressHandler);
|
|
1144
|
+
render();
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
const isCancelled = (value) => typeof value === "symbol";
|
|
1148
|
+
function multiselect(opts) {
|
|
1149
|
+
return fe({
|
|
1150
|
+
...opts,
|
|
1151
|
+
options: opts.options,
|
|
1152
|
+
message: `${opts.message} ${import_picocolors.default.dim("(space to toggle)")}`
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
async function promptForAgents(message, choices) {
|
|
1156
|
+
let lastSelected;
|
|
1157
|
+
try {
|
|
1158
|
+
lastSelected = await getLastSelectedAgents();
|
|
1159
|
+
} catch {}
|
|
1160
|
+
const validAgents = choices.map((c) => c.value);
|
|
1161
|
+
const defaultValues = [
|
|
1162
|
+
"claude-code",
|
|
1163
|
+
"opencode",
|
|
1164
|
+
"codex"
|
|
1165
|
+
].filter((a) => validAgents.includes(a));
|
|
1166
|
+
let initialValues = [];
|
|
1167
|
+
if (lastSelected && lastSelected.length > 0) initialValues = lastSelected.filter((a) => validAgents.includes(a));
|
|
1168
|
+
if (initialValues.length === 0) initialValues = defaultValues;
|
|
1169
|
+
const selected = await searchMultiselect({
|
|
1170
|
+
message,
|
|
1171
|
+
items: choices,
|
|
1172
|
+
initialSelected: initialValues,
|
|
1173
|
+
required: true
|
|
1174
|
+
});
|
|
1175
|
+
if (!isCancelled(selected)) try {
|
|
1176
|
+
await saveSelectedAgents(selected);
|
|
1177
|
+
} catch {}
|
|
1178
|
+
return selected;
|
|
1179
|
+
}
|
|
1180
|
+
async function selectAgentsInteractive(options) {
|
|
1181
|
+
const supportsGlobalFilter = (a) => !options.global || agents[a].globalSkillsDir;
|
|
1182
|
+
const universalAgents = getUniversalAgents().filter(supportsGlobalFilter);
|
|
1183
|
+
const otherAgents = getNonUniversalAgents().filter(supportsGlobalFilter);
|
|
1184
|
+
const universalSection = {
|
|
1185
|
+
title: "Universal (.agents/skills)",
|
|
1186
|
+
items: universalAgents.map((a) => ({
|
|
1187
|
+
value: a,
|
|
1188
|
+
label: agents[a].displayName
|
|
1189
|
+
}))
|
|
1190
|
+
};
|
|
1191
|
+
const otherChoices = otherAgents.map((a) => ({
|
|
1192
|
+
value: a,
|
|
1193
|
+
label: agents[a].displayName,
|
|
1194
|
+
hint: options.global ? agents[a].globalSkillsDir : agents[a].skillsDir
|
|
1195
|
+
}));
|
|
1196
|
+
let lastSelected;
|
|
1197
|
+
try {
|
|
1198
|
+
lastSelected = await getLastSelectedAgents();
|
|
1199
|
+
} catch {}
|
|
1200
|
+
const selected = await searchMultiselect({
|
|
1201
|
+
message: "Which agents do you want to install to?",
|
|
1202
|
+
items: otherChoices,
|
|
1203
|
+
initialSelected: lastSelected ? lastSelected.filter((a) => otherAgents.includes(a) && !universalAgents.includes(a)) : [],
|
|
1204
|
+
lockedSection: universalSection
|
|
1205
|
+
});
|
|
1206
|
+
if (!isCancelled(selected)) try {
|
|
1207
|
+
await saveSelectedAgents(selected);
|
|
1208
|
+
} catch {}
|
|
1209
|
+
return selected;
|
|
1210
|
+
}
|
|
1211
|
+
const CLONE_TIMEOUT_MS = 6e4;
|
|
1212
|
+
var GitCloneError = class extends Error {
|
|
1213
|
+
url;
|
|
1214
|
+
isTimeout;
|
|
1215
|
+
isAuthError;
|
|
1216
|
+
constructor(message, url, isTimeout = false, isAuthError = false) {
|
|
1217
|
+
super(message);
|
|
1218
|
+
this.name = "GitCloneError";
|
|
1219
|
+
this.url = url;
|
|
1220
|
+
this.isTimeout = isTimeout;
|
|
1221
|
+
this.isAuthError = isAuthError;
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
async function cloneRepo(url, ref) {
|
|
1225
|
+
const tempDir = await mkdtemp(join(tmpdir(), "skills-"));
|
|
1226
|
+
const git = esm_default({ timeout: { block: CLONE_TIMEOUT_MS } });
|
|
1227
|
+
const cloneOptions = ref ? [
|
|
1228
|
+
"--depth",
|
|
1229
|
+
"1",
|
|
1230
|
+
"--branch",
|
|
1231
|
+
ref
|
|
1232
|
+
] : ["--depth", "1"];
|
|
1233
|
+
try {
|
|
1234
|
+
await git.clone(url, tempDir, cloneOptions);
|
|
1235
|
+
return tempDir;
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
await rm(tempDir, {
|
|
1238
|
+
recursive: true,
|
|
1239
|
+
force: true
|
|
1240
|
+
}).catch(() => {});
|
|
1241
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1242
|
+
const isTimeout = errorMessage.includes("block timeout") || errorMessage.includes("timed out");
|
|
1243
|
+
const isAuthError = errorMessage.includes("Authentication failed") || errorMessage.includes("could not read Username") || errorMessage.includes("Permission denied") || errorMessage.includes("Repository not found");
|
|
1244
|
+
if (isTimeout) throw new GitCloneError("Clone timed out after 60s. This often happens with private repos that require authentication.\n Ensure you have access and your SSH keys or credentials are configured:\n - For SSH: ssh-add -l (to check loaded keys)\n - For HTTPS: gh auth status (if using GitHub CLI)", url, true, false);
|
|
1245
|
+
if (isAuthError) throw new GitCloneError(`Authentication failed for ${url}.\n - For private repos, ensure you have access\n - For SSH: Check your keys with 'ssh -T git@github.com'\n - For HTTPS: Run 'gh auth login' or configure git credentials`, url, false, true);
|
|
1246
|
+
throw new GitCloneError(`Failed to clone ${url}: ${errorMessage}`, url, false, false);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
async function cleanupTempDir(dir) {
|
|
1250
|
+
const normalizedDir = normalize(resolve(dir));
|
|
1251
|
+
const normalizedTmpDir = normalize(resolve(tmpdir()));
|
|
1252
|
+
if (!normalizedDir.startsWith(normalizedTmpDir + sep) && normalizedDir !== normalizedTmpDir) throw new Error("Attempted to clean up directory outside of temp directory");
|
|
1253
|
+
await rm(dir, {
|
|
1254
|
+
recursive: true,
|
|
1255
|
+
force: true
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
var import_gray_matter = /* @__PURE__ */ __toESM(require_gray_matter(), 1);
|
|
1259
|
+
function shouldInstallInternalSkills() {
|
|
1260
|
+
const envValue = process.env.INSTALL_INTERNAL_SKILLS;
|
|
1261
|
+
return envValue === "1" || envValue === "true";
|
|
1262
|
+
}
|
|
1263
|
+
async function hasCognitiveMd(dir, cognitiveType) {
|
|
1264
|
+
try {
|
|
1265
|
+
const fileName = COGNITIVE_FILE_NAMES[cognitiveType];
|
|
1266
|
+
return (await stat(join(dir, fileName))).isFile();
|
|
1267
|
+
} catch {
|
|
1268
|
+
return false;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
async function parseCognitiveMd(mdPath, cognitiveType, options) {
|
|
1272
|
+
try {
|
|
1273
|
+
const content = await readFile(mdPath, "utf-8");
|
|
1274
|
+
const { data } = (0, import_gray_matter.default)(content);
|
|
1275
|
+
if (!data.name || !data.description) return null;
|
|
1276
|
+
if (data.metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) return null;
|
|
1277
|
+
return {
|
|
1278
|
+
name: data.name,
|
|
1279
|
+
description: data.description,
|
|
1280
|
+
path: dirname(mdPath),
|
|
1281
|
+
rawContent: content,
|
|
1282
|
+
metadata: data.metadata,
|
|
1283
|
+
cognitiveType
|
|
1284
|
+
};
|
|
1285
|
+
} catch {
|
|
1286
|
+
return null;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
async function parseSkillMd(skillMdPath, options) {
|
|
1290
|
+
return parseCognitiveMd(skillMdPath, "skill", options);
|
|
1291
|
+
}
|
|
1292
|
+
function isContainedIn(targetPath, basePath) {
|
|
1293
|
+
const normalizedBase = normalize(resolve(basePath));
|
|
1294
|
+
const normalizedTarget = normalize(resolve(targetPath));
|
|
1295
|
+
return normalizedTarget.startsWith(normalizedBase + sep) || normalizedTarget === normalizedBase;
|
|
1296
|
+
}
|
|
1297
|
+
function isValidRelativePath(path) {
|
|
1298
|
+
return path.startsWith("./");
|
|
1299
|
+
}
|
|
1300
|
+
async function getPluginSkillPaths(basePath) {
|
|
1301
|
+
const searchDirs = [];
|
|
1302
|
+
const addPluginSkillPaths = (pluginBase, skills) => {
|
|
1303
|
+
if (!isContainedIn(pluginBase, basePath)) return;
|
|
1304
|
+
if (skills && skills.length > 0) for (const skillPath of skills) {
|
|
1305
|
+
if (!isValidRelativePath(skillPath)) continue;
|
|
1306
|
+
const skillDir = dirname(join(pluginBase, skillPath));
|
|
1307
|
+
if (isContainedIn(skillDir, basePath)) searchDirs.push(skillDir);
|
|
1308
|
+
}
|
|
1309
|
+
searchDirs.push(join(pluginBase, "skills"));
|
|
1310
|
+
};
|
|
1311
|
+
try {
|
|
1312
|
+
const content = await readFile(join(basePath, ".claude-plugin/marketplace.json"), "utf-8");
|
|
1313
|
+
const manifest = JSON.parse(content);
|
|
1314
|
+
const pluginRoot = manifest.metadata?.pluginRoot;
|
|
1315
|
+
if (pluginRoot === void 0 || isValidRelativePath(pluginRoot)) for (const plugin of manifest.plugins ?? []) {
|
|
1316
|
+
if (typeof plugin.source !== "string" && plugin.source !== void 0) continue;
|
|
1317
|
+
if (plugin.source !== void 0 && !isValidRelativePath(plugin.source)) continue;
|
|
1318
|
+
addPluginSkillPaths(join(basePath, pluginRoot ?? "", plugin.source ?? ""), plugin.skills);
|
|
1319
|
+
}
|
|
1320
|
+
} catch {}
|
|
1321
|
+
try {
|
|
1322
|
+
const content = await readFile(join(basePath, ".claude-plugin/plugin.json"), "utf-8");
|
|
1323
|
+
addPluginSkillPaths(basePath, JSON.parse(content).skills);
|
|
1324
|
+
} catch {}
|
|
1325
|
+
return searchDirs;
|
|
1326
|
+
}
|
|
1327
|
+
const SKIP_DIRS = [
|
|
1328
|
+
"node_modules",
|
|
1329
|
+
".git",
|
|
1330
|
+
"dist",
|
|
1331
|
+
"build",
|
|
1332
|
+
"__pycache__"
|
|
1333
|
+
];
|
|
1334
|
+
const ALL_COGNITIVE_TYPES = [
|
|
1335
|
+
"skill",
|
|
1336
|
+
"agent",
|
|
1337
|
+
"prompt"
|
|
1338
|
+
];
|
|
1339
|
+
const AGENT_DIR_PREFIXES = [
|
|
1340
|
+
".agent",
|
|
1341
|
+
".agents",
|
|
1342
|
+
".claude",
|
|
1343
|
+
".cline",
|
|
1344
|
+
".codebuddy",
|
|
1345
|
+
".codex",
|
|
1346
|
+
".commandcode",
|
|
1347
|
+
".continue",
|
|
1348
|
+
".cursor",
|
|
1349
|
+
".github",
|
|
1350
|
+
".goose",
|
|
1351
|
+
".iflow",
|
|
1352
|
+
".junie",
|
|
1353
|
+
".kilocode",
|
|
1354
|
+
".kiro",
|
|
1355
|
+
".mux",
|
|
1356
|
+
".neovate",
|
|
1357
|
+
".opencode",
|
|
1358
|
+
".openhands",
|
|
1359
|
+
".pi",
|
|
1360
|
+
".qoder",
|
|
1361
|
+
".roo",
|
|
1362
|
+
".trae",
|
|
1363
|
+
".windsurf",
|
|
1364
|
+
".zencoder"
|
|
1365
|
+
];
|
|
1366
|
+
async function findCognitiveDirs(dir, types = ALL_COGNITIVE_TYPES, depth = 0, maxDepth = 5) {
|
|
1367
|
+
if (depth > maxDepth) return [];
|
|
1368
|
+
try {
|
|
1369
|
+
const checks = types.map(async (ct) => ({
|
|
1370
|
+
type: ct,
|
|
1371
|
+
found: await hasCognitiveMd(dir, ct)
|
|
1372
|
+
}));
|
|
1373
|
+
const [results, entries] = await Promise.all([Promise.all(checks), readdir(dir, { withFileTypes: true }).catch(() => [])]);
|
|
1374
|
+
const currentDir = results.filter((r) => r.found).map((r) => ({
|
|
1375
|
+
dir,
|
|
1376
|
+
cognitiveType: r.type
|
|
1377
|
+
}));
|
|
1378
|
+
const subDirResults = await Promise.all(entries.filter((entry) => entry.isDirectory() && !SKIP_DIRS.includes(entry.name)).map((entry) => findCognitiveDirs(join(dir, entry.name), types, depth + 1, maxDepth)));
|
|
1379
|
+
return [...currentDir, ...subDirResults.flat()];
|
|
1380
|
+
} catch {
|
|
1381
|
+
return [];
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
function buildPrioritySearchDirs(searchPath, cognitiveType) {
|
|
1385
|
+
const subdir = COGNITIVE_SUBDIRS[cognitiveType];
|
|
1386
|
+
const dirs = [
|
|
1387
|
+
searchPath,
|
|
1388
|
+
join(searchPath, subdir),
|
|
1389
|
+
join(searchPath, `${subdir}/.curated`),
|
|
1390
|
+
join(searchPath, `${subdir}/.experimental`),
|
|
1391
|
+
join(searchPath, `${subdir}/.system`)
|
|
1392
|
+
];
|
|
1393
|
+
for (const prefix of AGENT_DIR_PREFIXES) dirs.push(join(searchPath, prefix, subdir));
|
|
1394
|
+
return dirs;
|
|
1395
|
+
}
|
|
1396
|
+
async function discoverCognitives(basePath, subpath, options) {
|
|
1397
|
+
const types = options?.types ?? ["skill"];
|
|
1398
|
+
const results = [];
|
|
1399
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
1400
|
+
const searchPath = subpath ? join(basePath, subpath) : basePath;
|
|
1401
|
+
const makeKey = (cognitiveType, name) => `${cognitiveType}::${name}`;
|
|
1402
|
+
const addResult = (skill) => {
|
|
1403
|
+
const key = makeKey(skill.cognitiveType ?? "skill", skill.name);
|
|
1404
|
+
if (seenKeys.has(key)) return false;
|
|
1405
|
+
seenKeys.add(key);
|
|
1406
|
+
results.push(skill);
|
|
1407
|
+
return true;
|
|
1408
|
+
};
|
|
1409
|
+
for (const ct of types) if (await hasCognitiveMd(searchPath, ct)) {
|
|
1410
|
+
const fileName = COGNITIVE_FILE_NAMES[ct];
|
|
1411
|
+
const cognitive = await parseCognitiveMd(join(searchPath, fileName), ct, options);
|
|
1412
|
+
if (cognitive) addResult(cognitive);
|
|
1413
|
+
}
|
|
1414
|
+
if (results.length > 0 && !options?.fullDepth) return results;
|
|
1415
|
+
const allPriorityDirs = /* @__PURE__ */ new Set();
|
|
1416
|
+
const dirTypeMap = /* @__PURE__ */ new Map();
|
|
1417
|
+
for (const ct of types) {
|
|
1418
|
+
const dirs = buildPrioritySearchDirs(searchPath, ct);
|
|
1419
|
+
if (ct === "skill") dirs.push(...await getPluginSkillPaths(searchPath));
|
|
1420
|
+
for (const dir of dirs) {
|
|
1421
|
+
allPriorityDirs.add(dir);
|
|
1422
|
+
if (!dirTypeMap.has(dir)) dirTypeMap.set(dir, /* @__PURE__ */ new Set());
|
|
1423
|
+
dirTypeMap.get(dir).add(ct);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
for (const dir of allPriorityDirs) {
|
|
1427
|
+
const typesForDir = dirTypeMap.get(dir);
|
|
1428
|
+
try {
|
|
1429
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
1430
|
+
for (const entry of entries) if (entry.isDirectory()) {
|
|
1431
|
+
const subDir = join(dir, entry.name);
|
|
1432
|
+
for (const ct of typesForDir) if (await hasCognitiveMd(subDir, ct)) {
|
|
1433
|
+
const fileName = COGNITIVE_FILE_NAMES[ct];
|
|
1434
|
+
const cognitive = await parseCognitiveMd(join(subDir, fileName), ct, options);
|
|
1435
|
+
if (cognitive) addResult(cognitive);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
} catch {}
|
|
1439
|
+
}
|
|
1440
|
+
if (results.length === 0 || options?.fullDepth) {
|
|
1441
|
+
const allCognitiveDirs = await findCognitiveDirs(searchPath, types);
|
|
1442
|
+
for (const { dir: cogDir, cognitiveType: ct } of allCognitiveDirs) {
|
|
1443
|
+
const fileName = COGNITIVE_FILE_NAMES[ct];
|
|
1444
|
+
const cognitive = await parseCognitiveMd(join(cogDir, fileName), ct, options);
|
|
1445
|
+
if (cognitive) addResult(cognitive);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
return results;
|
|
1449
|
+
}
|
|
1450
|
+
async function discoverSkills(basePath, subpath, options) {
|
|
1451
|
+
return discoverCognitives(basePath, subpath, {
|
|
1452
|
+
...options,
|
|
1453
|
+
types: options?.types ?? ["skill"]
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
function getSkillDisplayName(skill) {
|
|
1457
|
+
return skill.name || basename(skill.path);
|
|
1458
|
+
}
|
|
1459
|
+
function filterSkills(skills, inputNames) {
|
|
1460
|
+
const normalizedInputs = inputNames.map((n) => n.toLowerCase());
|
|
1461
|
+
return skills.filter((skill) => {
|
|
1462
|
+
const name = skill.name.toLowerCase();
|
|
1463
|
+
const displayName = getSkillDisplayName(skill).toLowerCase();
|
|
1464
|
+
return normalizedInputs.some((input) => input === name || input === displayName);
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
function sanitizeName(name) {
|
|
1468
|
+
return name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.\-]+|[.\-]+$/g, "").substring(0, 255) || "unnamed-skill";
|
|
1469
|
+
}
|
|
1470
|
+
function isPathSafe(basePath, targetPath) {
|
|
1471
|
+
const normalizedBase = normalize(resolve(basePath));
|
|
1472
|
+
const normalizedTarget = normalize(resolve(targetPath));
|
|
1473
|
+
return normalizedTarget.startsWith(normalizedBase + sep) || normalizedTarget === normalizedBase;
|
|
1474
|
+
}
|
|
1475
|
+
function getCanonicalDir(cognitiveType, global, cwd) {
|
|
1476
|
+
return join(global ? homedir() : cwd || process.cwd(), AGENTS_DIR$3, COGNITIVE_SUBDIRS[cognitiveType]);
|
|
1477
|
+
}
|
|
1478
|
+
function getInstallPath(skillName, agentType, options = {}) {
|
|
1479
|
+
const cognitiveType = options.cognitiveType ?? "skill";
|
|
1480
|
+
const cwd = options.cwd || process.cwd();
|
|
1481
|
+
const sanitized = sanitizeName(skillName);
|
|
1482
|
+
const globalDir = getCognitiveDir(agentType, cognitiveType, "global");
|
|
1483
|
+
const localDir = getCognitiveDir(agentType, cognitiveType, "local");
|
|
1484
|
+
const targetBase = options.global && globalDir !== void 0 ? globalDir : join(cwd, localDir);
|
|
1485
|
+
const installPath = join(targetBase, sanitized);
|
|
1486
|
+
if (!isPathSafe(targetBase, installPath)) throw new Error(`Invalid ${cognitiveType} name: potential path traversal detected`);
|
|
1487
|
+
return installPath;
|
|
1488
|
+
}
|
|
1489
|
+
function getCanonicalPath(skillName, options = {}) {
|
|
1490
|
+
const cognitiveType = options.cognitiveType ?? "skill";
|
|
1491
|
+
const sanitized = sanitizeName(skillName);
|
|
1492
|
+
const canonicalBase = getCanonicalDir(cognitiveType, options.global ?? false, options.cwd);
|
|
1493
|
+
const canonicalPath = join(canonicalBase, sanitized);
|
|
1494
|
+
if (!isPathSafe(canonicalBase, canonicalPath)) throw new Error(`Invalid ${cognitiveType} name: potential path traversal detected`);
|
|
1495
|
+
return canonicalPath;
|
|
1496
|
+
}
|
|
1497
|
+
const EXCLUDE_FILES = new Set(["README.md", "metadata.json"]);
|
|
1498
|
+
const EXCLUDE_DIRS = new Set([".git"]);
|
|
1499
|
+
const isExcluded = (name, isDirectory = false) => {
|
|
1500
|
+
if (EXCLUDE_FILES.has(name)) return true;
|
|
1501
|
+
if (name.startsWith("_")) return true;
|
|
1502
|
+
if (isDirectory && EXCLUDE_DIRS.has(name)) return true;
|
|
1503
|
+
return false;
|
|
1504
|
+
};
|
|
1505
|
+
async function copyDirectory(src, dest) {
|
|
1506
|
+
await mkdir(dest, { recursive: true });
|
|
1507
|
+
const entries = await readdir(src, { withFileTypes: true });
|
|
1508
|
+
await Promise.all(entries.filter((entry) => !isExcluded(entry.name, entry.isDirectory())).map(async (entry) => {
|
|
1509
|
+
const srcPath = join(src, entry.name);
|
|
1510
|
+
const destPath = join(dest, entry.name);
|
|
1511
|
+
if (entry.isDirectory()) await copyDirectory(srcPath, destPath);
|
|
1512
|
+
else await cp(srcPath, destPath, {
|
|
1513
|
+
dereference: true,
|
|
1514
|
+
recursive: true
|
|
1515
|
+
});
|
|
1516
|
+
}));
|
|
1517
|
+
}
|
|
1518
|
+
async function cleanAndCreateDirectory(path) {
|
|
1519
|
+
try {
|
|
1520
|
+
await rm(path, {
|
|
1521
|
+
recursive: true,
|
|
1522
|
+
force: true
|
|
1523
|
+
});
|
|
1524
|
+
} catch {}
|
|
1525
|
+
await mkdir(path, { recursive: true });
|
|
1526
|
+
}
|
|
1527
|
+
async function resolveParentSymlinks(path) {
|
|
1528
|
+
const resolved = resolve(path);
|
|
1529
|
+
const dir = dirname(resolved);
|
|
1530
|
+
const base = basename(resolved);
|
|
1531
|
+
try {
|
|
1532
|
+
const { realpath } = await import("fs/promises");
|
|
1533
|
+
return join(await realpath(dir), base);
|
|
1534
|
+
} catch {
|
|
1535
|
+
return resolved;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
function resolveSymlinkTarget(linkPath, linkTarget) {
|
|
1539
|
+
return resolve(dirname(linkPath), linkTarget);
|
|
1540
|
+
}
|
|
1541
|
+
async function createSymlink(target, linkPath) {
|
|
1542
|
+
try {
|
|
1543
|
+
const resolvedTarget = resolve(target);
|
|
1544
|
+
if (resolvedTarget === resolve(linkPath)) return true;
|
|
1545
|
+
if (await resolveParentSymlinks(target) === await resolveParentSymlinks(linkPath)) return true;
|
|
1546
|
+
try {
|
|
1547
|
+
if ((await lstat(linkPath)).isSymbolicLink()) {
|
|
1548
|
+
if (resolveSymlinkTarget(linkPath, await readlink(linkPath)) === resolvedTarget) return true;
|
|
1549
|
+
await rm(linkPath);
|
|
1550
|
+
} else await rm(linkPath, { recursive: true });
|
|
1551
|
+
} catch (err) {
|
|
1552
|
+
if (err && typeof err === "object" && "code" in err && err.code === "ELOOP") try {
|
|
1553
|
+
await rm(linkPath, { force: true });
|
|
1554
|
+
} catch {}
|
|
1555
|
+
}
|
|
1556
|
+
const linkDir = dirname(linkPath);
|
|
1557
|
+
await mkdir(linkDir, { recursive: true });
|
|
1558
|
+
await symlink(relative(await resolveParentSymlinks(linkDir), target), linkPath, platform() === "win32" ? "junction" : void 0);
|
|
1559
|
+
return true;
|
|
1560
|
+
} catch {
|
|
1561
|
+
return false;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
async function installCognitiveForAgent(cognitive, agentType, options = {}) {
|
|
1565
|
+
const cognitiveType = options.cognitiveType ?? "skill";
|
|
1566
|
+
const agent = agents[agentType];
|
|
1567
|
+
const isGlobal = options.global ?? false;
|
|
1568
|
+
const cwd = options.cwd || process.cwd();
|
|
1569
|
+
const globalDir = getCognitiveDir(agentType, cognitiveType, "global");
|
|
1570
|
+
if (isGlobal && globalDir === void 0) return {
|
|
1571
|
+
success: false,
|
|
1572
|
+
path: "",
|
|
1573
|
+
mode: options.mode ?? "symlink",
|
|
1574
|
+
error: `${agent.displayName} does not support global ${cognitiveType} installation`
|
|
1575
|
+
};
|
|
1576
|
+
const safeName = sanitizeName(cognitive.name || basename(cognitive.path));
|
|
1577
|
+
const canonicalBase = getCanonicalDir(cognitiveType, isGlobal, cwd);
|
|
1578
|
+
const canonicalDir = join(canonicalBase, safeName);
|
|
1579
|
+
const localDir = getCognitiveDir(agentType, cognitiveType, "local");
|
|
1580
|
+
const agentBase = isGlobal ? globalDir : join(cwd, localDir);
|
|
1581
|
+
const agentDir = join(agentBase, safeName);
|
|
1582
|
+
const installMode = options.mode ?? "symlink";
|
|
1583
|
+
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
1584
|
+
success: false,
|
|
1585
|
+
path: agentDir,
|
|
1586
|
+
mode: installMode,
|
|
1587
|
+
error: `Invalid ${cognitiveType} name: potential path traversal detected`
|
|
1588
|
+
};
|
|
1589
|
+
if (!isPathSafe(agentBase, agentDir)) return {
|
|
1590
|
+
success: false,
|
|
1591
|
+
path: agentDir,
|
|
1592
|
+
mode: installMode,
|
|
1593
|
+
error: `Invalid ${cognitiveType} name: potential path traversal detected`
|
|
1594
|
+
};
|
|
1595
|
+
try {
|
|
1596
|
+
if (installMode === "copy") {
|
|
1597
|
+
await cleanAndCreateDirectory(agentDir);
|
|
1598
|
+
await copyDirectory(cognitive.path, agentDir);
|
|
1599
|
+
return {
|
|
1600
|
+
success: true,
|
|
1601
|
+
path: agentDir,
|
|
1602
|
+
mode: "copy"
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
await cleanAndCreateDirectory(canonicalDir);
|
|
1606
|
+
await copyDirectory(cognitive.path, canonicalDir);
|
|
1607
|
+
if (isGlobal && isUniversalForType(agentType, cognitiveType)) return {
|
|
1608
|
+
success: true,
|
|
1609
|
+
path: canonicalDir,
|
|
1610
|
+
canonicalPath: canonicalDir,
|
|
1611
|
+
mode: "symlink"
|
|
1612
|
+
};
|
|
1613
|
+
if (!await createSymlink(canonicalDir, agentDir)) {
|
|
1614
|
+
await cleanAndCreateDirectory(agentDir);
|
|
1615
|
+
await copyDirectory(cognitive.path, agentDir);
|
|
1616
|
+
return {
|
|
1617
|
+
success: true,
|
|
1618
|
+
path: agentDir,
|
|
1619
|
+
canonicalPath: canonicalDir,
|
|
1620
|
+
mode: "symlink",
|
|
1621
|
+
symlinkFailed: true
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
return {
|
|
1625
|
+
success: true,
|
|
1626
|
+
path: agentDir,
|
|
1627
|
+
canonicalPath: canonicalDir,
|
|
1628
|
+
mode: "symlink"
|
|
1629
|
+
};
|
|
1630
|
+
} catch (error) {
|
|
1631
|
+
return {
|
|
1632
|
+
success: false,
|
|
1633
|
+
path: agentDir,
|
|
1634
|
+
mode: installMode,
|
|
1635
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
async function installRemoteSkillForAgent(skill, agentType, options = {}) {
|
|
1640
|
+
const cognitiveType = options.cognitiveType ?? skill.cognitiveType ?? "skill";
|
|
1641
|
+
const agent = agents[agentType];
|
|
1642
|
+
const isGlobal = options.global ?? false;
|
|
1643
|
+
const cwd = options.cwd || process.cwd();
|
|
1644
|
+
const installMode = options.mode ?? "symlink";
|
|
1645
|
+
const fileName = COGNITIVE_FILE_NAMES[cognitiveType];
|
|
1646
|
+
const globalDir = getCognitiveDir(agentType, cognitiveType, "global");
|
|
1647
|
+
if (isGlobal && globalDir === void 0) return {
|
|
1648
|
+
success: false,
|
|
1649
|
+
path: "",
|
|
1650
|
+
mode: installMode,
|
|
1651
|
+
error: `${agent.displayName} does not support global ${cognitiveType} installation`
|
|
1652
|
+
};
|
|
1653
|
+
const skillName = sanitizeName(skill.installName);
|
|
1654
|
+
const canonicalBase = getCanonicalDir(cognitiveType, isGlobal, cwd);
|
|
1655
|
+
const canonicalDir = join(canonicalBase, skillName);
|
|
1656
|
+
const localDir = getCognitiveDir(agentType, cognitiveType, "local");
|
|
1657
|
+
const agentBase = isGlobal ? globalDir : join(cwd, localDir);
|
|
1658
|
+
const agentDir = join(agentBase, skillName);
|
|
1659
|
+
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
1660
|
+
success: false,
|
|
1661
|
+
path: agentDir,
|
|
1662
|
+
mode: installMode,
|
|
1663
|
+
error: `Invalid ${cognitiveType} name: potential path traversal detected`
|
|
1664
|
+
};
|
|
1665
|
+
if (!isPathSafe(agentBase, agentDir)) return {
|
|
1666
|
+
success: false,
|
|
1667
|
+
path: agentDir,
|
|
1668
|
+
mode: installMode,
|
|
1669
|
+
error: `Invalid ${cognitiveType} name: potential path traversal detected`
|
|
1670
|
+
};
|
|
1671
|
+
try {
|
|
1672
|
+
if (installMode === "copy") {
|
|
1673
|
+
await cleanAndCreateDirectory(agentDir);
|
|
1674
|
+
await writeFile(join(agentDir, fileName), skill.content, "utf-8");
|
|
1675
|
+
return {
|
|
1676
|
+
success: true,
|
|
1677
|
+
path: agentDir,
|
|
1678
|
+
mode: "copy"
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
await cleanAndCreateDirectory(canonicalDir);
|
|
1682
|
+
await writeFile(join(canonicalDir, fileName), skill.content, "utf-8");
|
|
1683
|
+
if (isGlobal && isUniversalForType(agentType, cognitiveType)) return {
|
|
1684
|
+
success: true,
|
|
1685
|
+
path: canonicalDir,
|
|
1686
|
+
canonicalPath: canonicalDir,
|
|
1687
|
+
mode: "symlink"
|
|
1688
|
+
};
|
|
1689
|
+
if (!await createSymlink(canonicalDir, agentDir)) {
|
|
1690
|
+
await cleanAndCreateDirectory(agentDir);
|
|
1691
|
+
await writeFile(join(agentDir, fileName), skill.content, "utf-8");
|
|
1692
|
+
return {
|
|
1693
|
+
success: true,
|
|
1694
|
+
path: agentDir,
|
|
1695
|
+
canonicalPath: canonicalDir,
|
|
1696
|
+
mode: "symlink",
|
|
1697
|
+
symlinkFailed: true
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
return {
|
|
1701
|
+
success: true,
|
|
1702
|
+
path: agentDir,
|
|
1703
|
+
canonicalPath: canonicalDir,
|
|
1704
|
+
mode: "symlink"
|
|
1705
|
+
};
|
|
1706
|
+
} catch (error) {
|
|
1707
|
+
return {
|
|
1708
|
+
success: false,
|
|
1709
|
+
path: agentDir,
|
|
1710
|
+
mode: installMode,
|
|
1711
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
1716
|
+
const cognitiveType = options.cognitiveType ?? "skill";
|
|
1717
|
+
const agent = agents[agentType];
|
|
1718
|
+
const isGlobal = options.global ?? false;
|
|
1719
|
+
const cwd = options.cwd || process.cwd();
|
|
1720
|
+
const installMode = options.mode ?? "symlink";
|
|
1721
|
+
const globalDir = getCognitiveDir(agentType, cognitiveType, "global");
|
|
1722
|
+
if (isGlobal && globalDir === void 0) return {
|
|
1723
|
+
success: false,
|
|
1724
|
+
path: "",
|
|
1725
|
+
mode: installMode,
|
|
1726
|
+
error: `${agent.displayName} does not support global ${cognitiveType} installation`
|
|
1727
|
+
};
|
|
1728
|
+
const skillName = sanitizeName(skill.installName);
|
|
1729
|
+
const canonicalBase = getCanonicalDir(cognitiveType, isGlobal, cwd);
|
|
1730
|
+
const canonicalDir = join(canonicalBase, skillName);
|
|
1731
|
+
const localDir = getCognitiveDir(agentType, cognitiveType, "local");
|
|
1732
|
+
const agentBase = isGlobal ? globalDir : join(cwd, localDir);
|
|
1733
|
+
const agentDir = join(agentBase, skillName);
|
|
1734
|
+
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
1735
|
+
success: false,
|
|
1736
|
+
path: agentDir,
|
|
1737
|
+
mode: installMode,
|
|
1738
|
+
error: `Invalid ${cognitiveType} name: potential path traversal detected`
|
|
1739
|
+
};
|
|
1740
|
+
if (!isPathSafe(agentBase, agentDir)) return {
|
|
1741
|
+
success: false,
|
|
1742
|
+
path: agentDir,
|
|
1743
|
+
mode: installMode,
|
|
1744
|
+
error: `Invalid ${cognitiveType} name: potential path traversal detected`
|
|
1745
|
+
};
|
|
1746
|
+
async function writeSkillFiles(targetDir) {
|
|
1747
|
+
for (const [filePath, content] of skill.files) {
|
|
1748
|
+
const fullPath = join(targetDir, filePath);
|
|
1749
|
+
if (!isPathSafe(targetDir, fullPath)) continue;
|
|
1750
|
+
const parentDir = dirname(fullPath);
|
|
1751
|
+
if (parentDir !== targetDir) await mkdir(parentDir, { recursive: true });
|
|
1752
|
+
await writeFile(fullPath, content, "utf-8");
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
try {
|
|
1756
|
+
if (installMode === "copy") {
|
|
1757
|
+
await cleanAndCreateDirectory(agentDir);
|
|
1758
|
+
await writeSkillFiles(agentDir);
|
|
1759
|
+
return {
|
|
1760
|
+
success: true,
|
|
1761
|
+
path: agentDir,
|
|
1762
|
+
mode: "copy"
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
await cleanAndCreateDirectory(canonicalDir);
|
|
1766
|
+
await writeSkillFiles(canonicalDir);
|
|
1767
|
+
if (isGlobal && isUniversalForType(agentType, cognitiveType)) return {
|
|
1768
|
+
success: true,
|
|
1769
|
+
path: canonicalDir,
|
|
1770
|
+
canonicalPath: canonicalDir,
|
|
1771
|
+
mode: "symlink"
|
|
1772
|
+
};
|
|
1773
|
+
if (!await createSymlink(canonicalDir, agentDir)) {
|
|
1774
|
+
await cleanAndCreateDirectory(agentDir);
|
|
1775
|
+
await writeSkillFiles(agentDir);
|
|
1776
|
+
return {
|
|
1777
|
+
success: true,
|
|
1778
|
+
path: agentDir,
|
|
1779
|
+
canonicalPath: canonicalDir,
|
|
1780
|
+
mode: "symlink",
|
|
1781
|
+
symlinkFailed: true
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
return {
|
|
1785
|
+
success: true,
|
|
1786
|
+
path: agentDir,
|
|
1787
|
+
canonicalPath: canonicalDir,
|
|
1788
|
+
mode: "symlink"
|
|
1789
|
+
};
|
|
1790
|
+
} catch (error) {
|
|
1791
|
+
return {
|
|
1792
|
+
success: false,
|
|
1793
|
+
path: agentDir,
|
|
1794
|
+
mode: installMode,
|
|
1795
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
async function listInstalledCognitives(options = {}) {
|
|
1800
|
+
const cwd = options.cwd || process.cwd();
|
|
1801
|
+
const typesToScan = options.typeFilter ?? [
|
|
1802
|
+
"skill",
|
|
1803
|
+
"agent",
|
|
1804
|
+
"prompt"
|
|
1805
|
+
];
|
|
1806
|
+
const cognitivesMap = /* @__PURE__ */ new Map();
|
|
1807
|
+
const detectedAgents = await detectInstalledAgents();
|
|
1808
|
+
const agentFilter = options.agentFilter;
|
|
1809
|
+
const agentsToCheck = agentFilter ? detectedAgents.filter((a) => agentFilter.includes(a)) : detectedAgents;
|
|
1810
|
+
const scopeTypes = [];
|
|
1811
|
+
if (options.global === void 0) scopeTypes.push({ global: false }, { global: true });
|
|
1812
|
+
else scopeTypes.push({ global: options.global });
|
|
1813
|
+
for (const cognitiveType of typesToScan) {
|
|
1814
|
+
const fileName = COGNITIVE_FILE_NAMES[cognitiveType];
|
|
1815
|
+
const scopes = [];
|
|
1816
|
+
for (const { global: isGlobal } of scopeTypes) {
|
|
1817
|
+
scopes.push({
|
|
1818
|
+
global: isGlobal,
|
|
1819
|
+
path: getCanonicalDir(cognitiveType, isGlobal, cwd)
|
|
1820
|
+
});
|
|
1821
|
+
for (const agentType of agentsToCheck) {
|
|
1822
|
+
const globalDir = getCognitiveDir(agentType, cognitiveType, "global");
|
|
1823
|
+
if (isGlobal && globalDir === void 0) continue;
|
|
1824
|
+
const localDir = getCognitiveDir(agentType, cognitiveType, "local");
|
|
1825
|
+
const agentDir = isGlobal ? globalDir : join(cwd, localDir);
|
|
1826
|
+
if (!scopes.some((s) => s.path === agentDir && s.global === isGlobal)) scopes.push({
|
|
1827
|
+
global: isGlobal,
|
|
1828
|
+
path: agentDir,
|
|
1829
|
+
agentType
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
for (const scope of scopes) try {
|
|
1834
|
+
const entries = await readdir(scope.path, { withFileTypes: true });
|
|
1835
|
+
for (const entry of entries) {
|
|
1836
|
+
if (!entry.isDirectory()) continue;
|
|
1837
|
+
const cognitiveDir = join(scope.path, entry.name);
|
|
1838
|
+
const mdPath = join(cognitiveDir, fileName);
|
|
1839
|
+
try {
|
|
1840
|
+
await stat(mdPath);
|
|
1841
|
+
} catch {
|
|
1842
|
+
continue;
|
|
1843
|
+
}
|
|
1844
|
+
const parsed = cognitiveType === "skill" ? await parseSkillMd(mdPath) : await parseCognitiveMd(mdPath, cognitiveType);
|
|
1845
|
+
if (!parsed) continue;
|
|
1846
|
+
const scopeKey = scope.global ? "global" : "project";
|
|
1847
|
+
const cognitiveKey = `${scopeKey}:${cognitiveType}:${parsed.name}`;
|
|
1848
|
+
if (scope.agentType) {
|
|
1849
|
+
if (cognitivesMap.has(cognitiveKey)) {
|
|
1850
|
+
const existing = cognitivesMap.get(cognitiveKey);
|
|
1851
|
+
if (!existing.agents.includes(scope.agentType)) existing.agents.push(scope.agentType);
|
|
1852
|
+
} else cognitivesMap.set(cognitiveKey, {
|
|
1853
|
+
name: parsed.name,
|
|
1854
|
+
description: parsed.description,
|
|
1855
|
+
path: cognitiveDir,
|
|
1856
|
+
canonicalPath: cognitiveDir,
|
|
1857
|
+
scope: scopeKey,
|
|
1858
|
+
agents: [scope.agentType],
|
|
1859
|
+
cognitiveType
|
|
1860
|
+
});
|
|
1861
|
+
continue;
|
|
1862
|
+
}
|
|
1863
|
+
const sanitizedName = sanitizeName(parsed.name);
|
|
1864
|
+
const installedAgents = [];
|
|
1865
|
+
for (const agentType of agentsToCheck) {
|
|
1866
|
+
const globalDir = getCognitiveDir(agentType, cognitiveType, "global");
|
|
1867
|
+
if (scope.global && globalDir === void 0) continue;
|
|
1868
|
+
const localDir = getCognitiveDir(agentType, cognitiveType, "local");
|
|
1869
|
+
const agentBase = scope.global ? globalDir : join(cwd, localDir);
|
|
1870
|
+
let found = false;
|
|
1871
|
+
const possibleNames = Array.from(new Set([
|
|
1872
|
+
entry.name,
|
|
1873
|
+
sanitizedName,
|
|
1874
|
+
parsed.name.toLowerCase().replace(/\s+/g, "-").replace(/[\/\\:\0]/g, "")
|
|
1875
|
+
]));
|
|
1876
|
+
for (const possibleName of possibleNames) {
|
|
1877
|
+
const agentCognitiveDir = join(agentBase, possibleName);
|
|
1878
|
+
if (!isPathSafe(agentBase, agentCognitiveDir)) continue;
|
|
1879
|
+
try {
|
|
1880
|
+
await access(agentCognitiveDir);
|
|
1881
|
+
found = true;
|
|
1882
|
+
break;
|
|
1883
|
+
} catch {}
|
|
1884
|
+
}
|
|
1885
|
+
if (!found) try {
|
|
1886
|
+
const agentEntries = await readdir(agentBase, { withFileTypes: true });
|
|
1887
|
+
for (const agentEntry of agentEntries) {
|
|
1888
|
+
if (!agentEntry.isDirectory()) continue;
|
|
1889
|
+
const candidateDir = join(agentBase, agentEntry.name);
|
|
1890
|
+
if (!isPathSafe(agentBase, candidateDir)) continue;
|
|
1891
|
+
try {
|
|
1892
|
+
const candidateMdPath = join(candidateDir, fileName);
|
|
1893
|
+
await stat(candidateMdPath);
|
|
1894
|
+
const candidateParsed = cognitiveType === "skill" ? await parseSkillMd(candidateMdPath) : await parseCognitiveMd(candidateMdPath, cognitiveType);
|
|
1895
|
+
if (candidateParsed && candidateParsed.name === parsed.name) {
|
|
1896
|
+
found = true;
|
|
1897
|
+
break;
|
|
1898
|
+
}
|
|
1899
|
+
} catch {}
|
|
1900
|
+
}
|
|
1901
|
+
} catch {}
|
|
1902
|
+
if (found) installedAgents.push(agentType);
|
|
1903
|
+
}
|
|
1904
|
+
if (cognitivesMap.has(cognitiveKey)) {
|
|
1905
|
+
const existing = cognitivesMap.get(cognitiveKey);
|
|
1906
|
+
for (const agent of installedAgents) if (!existing.agents.includes(agent)) existing.agents.push(agent);
|
|
1907
|
+
} else cognitivesMap.set(cognitiveKey, {
|
|
1908
|
+
name: parsed.name,
|
|
1909
|
+
description: parsed.description,
|
|
1910
|
+
path: cognitiveDir,
|
|
1911
|
+
canonicalPath: cognitiveDir,
|
|
1912
|
+
scope: scopeKey,
|
|
1913
|
+
agents: installedAgents,
|
|
1914
|
+
cognitiveType
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
} catch {}
|
|
1918
|
+
}
|
|
1919
|
+
return Array.from(cognitivesMap.values());
|
|
1920
|
+
}
|
|
1921
|
+
async function listInstalledSkills(options = {}) {
|
|
1922
|
+
return listInstalledCognitives({
|
|
1923
|
+
...options,
|
|
1924
|
+
typeFilter: ["skill"]
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
async function isCognitiveInstalled(name, agentType, cognitiveType, options = {}) {
|
|
1928
|
+
const sanitized = sanitizeName(name);
|
|
1929
|
+
const globalDir = getCognitiveDir(agentType, cognitiveType, "global");
|
|
1930
|
+
if (options.global && globalDir === void 0) return false;
|
|
1931
|
+
const localDir = getCognitiveDir(agentType, cognitiveType, "local");
|
|
1932
|
+
const targetBase = options.global ? globalDir : join(options.cwd || process.cwd(), localDir);
|
|
1933
|
+
const cognitiveDir = join(targetBase, sanitized);
|
|
1934
|
+
if (!isPathSafe(targetBase, cognitiveDir)) return false;
|
|
1935
|
+
try {
|
|
1936
|
+
await access(cognitiveDir);
|
|
1937
|
+
return true;
|
|
1938
|
+
} catch {
|
|
1939
|
+
return false;
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
async function isSkillInstalled(skillName, agentType, options = {}) {
|
|
1943
|
+
return isCognitiveInstalled(skillName, agentType, options.cognitiveType ?? "skill", options);
|
|
1944
|
+
}
|
|
1945
|
+
const TELEMETRY_URL = "https://add-skill.vercel.sh/t";
|
|
1946
|
+
let cliVersion = null;
|
|
1947
|
+
function isCI() {
|
|
1948
|
+
return !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.CIRCLECI || process.env.TRAVIS || process.env.BUILDKITE || process.env.JENKINS_URL || process.env.TEAMCITY_VERSION);
|
|
1949
|
+
}
|
|
1950
|
+
function isEnabled() {
|
|
1951
|
+
return !process.env.DISABLE_TELEMETRY && !process.env.DO_NOT_TRACK;
|
|
1952
|
+
}
|
|
1953
|
+
function setVersion(version) {
|
|
1954
|
+
cliVersion = version;
|
|
1955
|
+
}
|
|
1956
|
+
function track(data) {
|
|
1957
|
+
if (!isEnabled()) return;
|
|
1958
|
+
try {
|
|
1959
|
+
const params = new URLSearchParams();
|
|
1960
|
+
if (cliVersion) params.set("v", cliVersion);
|
|
1961
|
+
if (isCI()) params.set("ci", "1");
|
|
1962
|
+
for (const [key, value] of Object.entries(data)) if (value !== void 0 && value !== null) params.set(key, String(value));
|
|
1963
|
+
fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {});
|
|
1964
|
+
} catch {}
|
|
1965
|
+
}
|
|
1966
|
+
var ProviderRegistryImpl = class {
|
|
1967
|
+
providers = [];
|
|
1968
|
+
register(provider) {
|
|
1969
|
+
if (this.providers.some((p) => p.id === provider.id)) throw new Error(`Provider with id "${provider.id}" already registered`);
|
|
1970
|
+
this.providers.push(provider);
|
|
1971
|
+
}
|
|
1972
|
+
findProvider(url) {
|
|
1973
|
+
for (const provider of this.providers) if (provider.match(url).matches) return provider;
|
|
1974
|
+
return null;
|
|
1975
|
+
}
|
|
1976
|
+
getProviders() {
|
|
1977
|
+
return [...this.providers];
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
const registry = new ProviderRegistryImpl();
|
|
1981
|
+
function registerProvider(provider) {
|
|
1982
|
+
registry.register(provider);
|
|
1983
|
+
}
|
|
1984
|
+
function findProvider(url) {
|
|
1985
|
+
return registry.findProvider(url);
|
|
1986
|
+
}
|
|
1987
|
+
var MintlifyProvider = class {
|
|
1988
|
+
id = "mintlify";
|
|
1989
|
+
displayName = "Mintlify";
|
|
1990
|
+
match(url) {
|
|
1991
|
+
if (!url.startsWith("http://") && !url.startsWith("https://")) return { matches: false };
|
|
1992
|
+
if (!url.toLowerCase().endsWith("/skill.md")) return { matches: false };
|
|
1993
|
+
if (url.includes("github.com") || url.includes("gitlab.com")) return { matches: false };
|
|
1994
|
+
if (url.includes("huggingface.co")) return { matches: false };
|
|
1995
|
+
return { matches: true };
|
|
1996
|
+
}
|
|
1997
|
+
async fetchSkill(url) {
|
|
1998
|
+
try {
|
|
1999
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(3e4) });
|
|
2000
|
+
if (!response.ok) return null;
|
|
2001
|
+
const content = await response.text();
|
|
2002
|
+
const { data } = (0, import_gray_matter.default)(content);
|
|
2003
|
+
const mintlifySite = data.metadata?.["mintlify-proj"];
|
|
2004
|
+
if (!mintlifySite) return null;
|
|
2005
|
+
if (!data.name || !data.description) return null;
|
|
2006
|
+
return {
|
|
2007
|
+
name: data.name,
|
|
2008
|
+
description: data.description,
|
|
2009
|
+
content,
|
|
2010
|
+
installName: mintlifySite,
|
|
2011
|
+
sourceUrl: url,
|
|
2012
|
+
metadata: data.metadata
|
|
2013
|
+
};
|
|
2014
|
+
} catch {
|
|
2015
|
+
return null;
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
toRawUrl(url) {
|
|
2019
|
+
return url;
|
|
2020
|
+
}
|
|
2021
|
+
getSourceIdentifier(url) {
|
|
2022
|
+
return "mintlify/com";
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
2025
|
+
const mintlifyProvider = new MintlifyProvider();
|
|
2026
|
+
var HuggingFaceProvider = class {
|
|
2027
|
+
id = "huggingface";
|
|
2028
|
+
displayName = "HuggingFace";
|
|
2029
|
+
HOST = "huggingface.co";
|
|
2030
|
+
match(url) {
|
|
2031
|
+
if (!url.startsWith("http://") && !url.startsWith("https://")) return { matches: false };
|
|
2032
|
+
try {
|
|
2033
|
+
if (new URL(url).hostname !== this.HOST) return { matches: false };
|
|
2034
|
+
} catch {
|
|
2035
|
+
return { matches: false };
|
|
2036
|
+
}
|
|
2037
|
+
if (!url.toLowerCase().endsWith("/skill.md")) return { matches: false };
|
|
2038
|
+
if (!url.includes("/spaces/")) return { matches: false };
|
|
2039
|
+
return { matches: true };
|
|
2040
|
+
}
|
|
2041
|
+
async fetchSkill(url) {
|
|
2042
|
+
try {
|
|
2043
|
+
const rawUrl = this.toRawUrl(url);
|
|
2044
|
+
const response = await fetch(rawUrl, { signal: AbortSignal.timeout(3e4) });
|
|
2045
|
+
if (!response.ok) return null;
|
|
2046
|
+
const content = await response.text();
|
|
2047
|
+
const { data } = (0, import_gray_matter.default)(content);
|
|
2048
|
+
if (!data.name || !data.description) return null;
|
|
2049
|
+
const parsed = this.parseUrl(url);
|
|
2050
|
+
if (!parsed) return null;
|
|
2051
|
+
const installName = data.metadata?.["install-name"] || parsed.repo;
|
|
2052
|
+
return {
|
|
2053
|
+
name: data.name,
|
|
2054
|
+
description: data.description,
|
|
2055
|
+
content,
|
|
2056
|
+
installName,
|
|
2057
|
+
sourceUrl: url,
|
|
2058
|
+
metadata: data.metadata
|
|
2059
|
+
};
|
|
2060
|
+
} catch {
|
|
2061
|
+
return null;
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
toRawUrl(url) {
|
|
2065
|
+
return url.replace("/blob/", "/raw/");
|
|
2066
|
+
}
|
|
2067
|
+
getSourceIdentifier(url) {
|
|
2068
|
+
const parsed = this.parseUrl(url);
|
|
2069
|
+
if (!parsed) return "huggingface/unknown";
|
|
2070
|
+
return `huggingface/${parsed.owner}/${parsed.repo}`;
|
|
2071
|
+
}
|
|
2072
|
+
parseUrl(url) {
|
|
2073
|
+
const match = url.match(/\/spaces\/([^/]+)\/([^/]+)/);
|
|
2074
|
+
if (!match || !match[1] || !match[2]) return null;
|
|
2075
|
+
return {
|
|
2076
|
+
owner: match[1],
|
|
2077
|
+
repo: match[2]
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
};
|
|
2081
|
+
const huggingFaceProvider = new HuggingFaceProvider();
|
|
2082
|
+
var WellKnownProvider = class {
|
|
2083
|
+
id = "well-known";
|
|
2084
|
+
displayName = "Well-Known Skills";
|
|
2085
|
+
WELL_KNOWN_PATH = ".well-known/skills";
|
|
2086
|
+
INDEX_FILE = "index.json";
|
|
2087
|
+
match(url) {
|
|
2088
|
+
if (!url.startsWith("http://") && !url.startsWith("https://")) return { matches: false };
|
|
2089
|
+
try {
|
|
2090
|
+
const parsed = new URL(url);
|
|
2091
|
+
if ([
|
|
2092
|
+
"github.com",
|
|
2093
|
+
"gitlab.com",
|
|
2094
|
+
"huggingface.co"
|
|
2095
|
+
].includes(parsed.hostname)) return { matches: false };
|
|
2096
|
+
return {
|
|
2097
|
+
matches: true,
|
|
2098
|
+
sourceIdentifier: `wellknown/${parsed.hostname}`
|
|
2099
|
+
};
|
|
2100
|
+
} catch {
|
|
2101
|
+
return { matches: false };
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
async fetchIndex(baseUrl) {
|
|
2105
|
+
try {
|
|
2106
|
+
const parsed = new URL(baseUrl);
|
|
2107
|
+
const basePath = parsed.pathname.replace(/\/$/, "");
|
|
2108
|
+
const urlsToTry = [{
|
|
2109
|
+
indexUrl: `${parsed.protocol}//${parsed.host}${basePath}/${this.WELL_KNOWN_PATH}/${this.INDEX_FILE}`,
|
|
2110
|
+
baseUrl: `${parsed.protocol}//${parsed.host}${basePath}`
|
|
2111
|
+
}];
|
|
2112
|
+
if (basePath && basePath !== "") urlsToTry.push({
|
|
2113
|
+
indexUrl: `${parsed.protocol}//${parsed.host}/${this.WELL_KNOWN_PATH}/${this.INDEX_FILE}`,
|
|
2114
|
+
baseUrl: `${parsed.protocol}//${parsed.host}`
|
|
2115
|
+
});
|
|
2116
|
+
for (const { indexUrl, baseUrl: resolvedBase } of urlsToTry) try {
|
|
2117
|
+
const response = await fetch(indexUrl);
|
|
2118
|
+
if (!response.ok) continue;
|
|
2119
|
+
const index = await response.json();
|
|
2120
|
+
if (!index.skills || !Array.isArray(index.skills)) continue;
|
|
2121
|
+
let allValid = true;
|
|
2122
|
+
for (const entry of index.skills) if (!this.isValidSkillEntry(entry)) {
|
|
2123
|
+
allValid = false;
|
|
2124
|
+
break;
|
|
2125
|
+
}
|
|
2126
|
+
if (allValid) return {
|
|
2127
|
+
index,
|
|
2128
|
+
resolvedBaseUrl: resolvedBase
|
|
2129
|
+
};
|
|
2130
|
+
} catch {
|
|
2131
|
+
continue;
|
|
2132
|
+
}
|
|
2133
|
+
return null;
|
|
2134
|
+
} catch {
|
|
2135
|
+
return null;
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
isValidSkillEntry(entry) {
|
|
2139
|
+
if (!entry || typeof entry !== "object") return false;
|
|
2140
|
+
const e = entry;
|
|
2141
|
+
if (typeof e.name !== "string" || !e.name) return false;
|
|
2142
|
+
if (typeof e.description !== "string" || !e.description) return false;
|
|
2143
|
+
if (!Array.isArray(e.files) || e.files.length === 0) return false;
|
|
2144
|
+
if (!/^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$/.test(e.name) && e.name.length > 1) {
|
|
2145
|
+
if (e.name.length === 1 && !/^[a-z0-9]$/.test(e.name)) return false;
|
|
2146
|
+
}
|
|
2147
|
+
for (const file of e.files) {
|
|
2148
|
+
if (typeof file !== "string") return false;
|
|
2149
|
+
if (file.startsWith("/") || file.startsWith("\\") || file.includes("..")) return false;
|
|
2150
|
+
}
|
|
2151
|
+
if (!e.files.some((f) => typeof f === "string" && f.toLowerCase() === "skill.md")) return false;
|
|
2152
|
+
return true;
|
|
2153
|
+
}
|
|
2154
|
+
async fetchSkill(url) {
|
|
2155
|
+
try {
|
|
2156
|
+
const parsed = new URL(url);
|
|
2157
|
+
const result = await this.fetchIndex(url);
|
|
2158
|
+
if (!result) return null;
|
|
2159
|
+
const { index, resolvedBaseUrl } = result;
|
|
2160
|
+
let skillName = null;
|
|
2161
|
+
const pathMatch = parsed.pathname.match(/\/.well-known\/skills\/([^/]+)\/?$/);
|
|
2162
|
+
if (pathMatch && pathMatch[1] && pathMatch[1] !== "index.json") skillName = pathMatch[1];
|
|
2163
|
+
else if (index.skills.length === 1) skillName = index.skills[0].name;
|
|
2164
|
+
if (!skillName) return null;
|
|
2165
|
+
const skillEntry = index.skills.find((s) => s.name === skillName);
|
|
2166
|
+
if (!skillEntry) return null;
|
|
2167
|
+
return this.fetchSkillByEntry(resolvedBaseUrl, skillEntry);
|
|
2168
|
+
} catch {
|
|
2169
|
+
return null;
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
async fetchSkillByEntry(baseUrl, entry) {
|
|
2173
|
+
try {
|
|
2174
|
+
const skillBaseUrl = `${baseUrl.replace(/\/$/, "")}/${this.WELL_KNOWN_PATH}/${entry.name}`;
|
|
2175
|
+
const skillMdUrl = `${skillBaseUrl}/SKILL.md`;
|
|
2176
|
+
const response = await fetch(skillMdUrl);
|
|
2177
|
+
if (!response.ok) return null;
|
|
2178
|
+
const content = await response.text();
|
|
2179
|
+
const { data } = (0, import_gray_matter.default)(content);
|
|
2180
|
+
if (!data.name || !data.description) return null;
|
|
2181
|
+
const files = /* @__PURE__ */ new Map();
|
|
2182
|
+
files.set("SKILL.md", content);
|
|
2183
|
+
const filePromises = entry.files.filter((f) => f.toLowerCase() !== "skill.md").map(async (filePath) => {
|
|
2184
|
+
try {
|
|
2185
|
+
const fileUrl = `${skillBaseUrl}/${filePath}`;
|
|
2186
|
+
const fileResponse = await fetch(fileUrl);
|
|
2187
|
+
if (fileResponse.ok) return {
|
|
2188
|
+
path: filePath,
|
|
2189
|
+
content: await fileResponse.text()
|
|
2190
|
+
};
|
|
2191
|
+
} catch {}
|
|
2192
|
+
return null;
|
|
2193
|
+
});
|
|
2194
|
+
const fileResults = await Promise.all(filePromises);
|
|
2195
|
+
for (const result of fileResults) if (result) files.set(result.path, result.content);
|
|
2196
|
+
return {
|
|
2197
|
+
name: data.name,
|
|
2198
|
+
description: data.description,
|
|
2199
|
+
content,
|
|
2200
|
+
installName: entry.name,
|
|
2201
|
+
sourceUrl: skillMdUrl,
|
|
2202
|
+
metadata: data.metadata,
|
|
2203
|
+
files,
|
|
2204
|
+
indexEntry: entry
|
|
2205
|
+
};
|
|
2206
|
+
} catch {
|
|
2207
|
+
return null;
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
async fetchAllSkills(url) {
|
|
2211
|
+
try {
|
|
2212
|
+
const result = await this.fetchIndex(url);
|
|
2213
|
+
if (!result) return [];
|
|
2214
|
+
const { index, resolvedBaseUrl } = result;
|
|
2215
|
+
const skillPromises = index.skills.map((entry) => this.fetchSkillByEntry(resolvedBaseUrl, entry));
|
|
2216
|
+
return (await Promise.all(skillPromises)).filter((s) => s !== null);
|
|
2217
|
+
} catch {
|
|
2218
|
+
return [];
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
toRawUrl(url) {
|
|
2222
|
+
try {
|
|
2223
|
+
const parsed = new URL(url);
|
|
2224
|
+
if (url.toLowerCase().endsWith("/skill.md")) return url;
|
|
2225
|
+
const pathMatch = parsed.pathname.match(/\/.well-known\/skills\/([^/]+)\/?$/);
|
|
2226
|
+
if (pathMatch && pathMatch[1]) {
|
|
2227
|
+
const basePath = parsed.pathname.replace(/\/.well-known\/skills\/.*$/, "");
|
|
2228
|
+
return `${parsed.protocol}//${parsed.host}${basePath}/${this.WELL_KNOWN_PATH}/${pathMatch[1]}/SKILL.md`;
|
|
2229
|
+
}
|
|
2230
|
+
const basePath = parsed.pathname.replace(/\/$/, "");
|
|
2231
|
+
return `${parsed.protocol}//${parsed.host}${basePath}/${this.WELL_KNOWN_PATH}/${this.INDEX_FILE}`;
|
|
2232
|
+
} catch {
|
|
2233
|
+
return url;
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
getSourceIdentifier(url) {
|
|
2237
|
+
try {
|
|
2238
|
+
const parsed = new URL(url);
|
|
2239
|
+
const hostParts = parsed.hostname.split(".");
|
|
2240
|
+
if (hostParts.length >= 2) {
|
|
2241
|
+
const tld = hostParts[hostParts.length - 1];
|
|
2242
|
+
return `${hostParts[hostParts.length - 2]}/${tld}`;
|
|
2243
|
+
}
|
|
2244
|
+
return parsed.hostname.replace(".", "/");
|
|
2245
|
+
} catch {
|
|
2246
|
+
return "unknown/unknown";
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
async hasSkillsIndex(url) {
|
|
2250
|
+
return await this.fetchIndex(url) !== null;
|
|
2251
|
+
}
|
|
2252
|
+
};
|
|
2253
|
+
const wellKnownProvider = new WellKnownProvider();
|
|
2254
|
+
registerProvider(mintlifyProvider);
|
|
2255
|
+
registerProvider(huggingFaceProvider);
|
|
2256
|
+
async function fetchMintlifySkill(url) {
|
|
2257
|
+
try {
|
|
2258
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(3e4) });
|
|
2259
|
+
if (!response.ok) return null;
|
|
2260
|
+
const content = await response.text();
|
|
2261
|
+
const { data } = (0, import_gray_matter.default)(content);
|
|
2262
|
+
const mintlifySite = data.metadata?.["mintlify-proj"];
|
|
2263
|
+
if (!mintlifySite) return null;
|
|
2264
|
+
if (!data.name || !data.description) return null;
|
|
2265
|
+
return {
|
|
2266
|
+
name: data.name,
|
|
2267
|
+
description: data.description,
|
|
2268
|
+
content,
|
|
2269
|
+
mintlifySite,
|
|
2270
|
+
sourceUrl: url
|
|
2271
|
+
};
|
|
2272
|
+
} catch {
|
|
2273
|
+
return null;
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
var version$1 = "1.0.0";
|
|
2277
|
+
function initTelemetry(version) {
|
|
2278
|
+
setVersion(version);
|
|
2279
|
+
}
|
|
2280
|
+
setVersion(version$1);
|
|
2281
|
+
async function selectTargetAgents(options, spinner, cleanup, useUniversalAgents = false) {
|
|
2282
|
+
const validAgents = Object.keys(agents);
|
|
2283
|
+
const universalAgents = getUniversalAgents();
|
|
2284
|
+
if (options.agent?.includes("*")) {
|
|
2285
|
+
const all = validAgents;
|
|
2286
|
+
logger.info(`Installing to all ${all.length} agents`);
|
|
2287
|
+
return all;
|
|
2288
|
+
}
|
|
2289
|
+
if (options.agent && options.agent.length > 0) {
|
|
2290
|
+
const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
|
|
2291
|
+
if (invalidAgents.length > 0) {
|
|
2292
|
+
logger.error(`Invalid agents: ${invalidAgents.join(", ")}`);
|
|
2293
|
+
logger.info(`Valid agents: ${validAgents.join(", ")}`);
|
|
2294
|
+
await cleanup?.();
|
|
2295
|
+
process.exit(1);
|
|
2296
|
+
}
|
|
2297
|
+
if (useUniversalAgents) return ensureUniversalAgents(options.agent);
|
|
2298
|
+
return options.agent;
|
|
2299
|
+
}
|
|
2300
|
+
spinner.start("Loading agents...");
|
|
2301
|
+
const installedAgents = await detectInstalledAgents();
|
|
2302
|
+
const totalAgents = Object.keys(agents).length;
|
|
2303
|
+
spinner.succeed(`${totalAgents} agents`);
|
|
2304
|
+
if (installedAgents.length === 0) {
|
|
2305
|
+
if (options.yes) {
|
|
2306
|
+
if (useUniversalAgents) {
|
|
2307
|
+
logger.info(`Installing to universal agents`);
|
|
2308
|
+
return universalAgents;
|
|
2309
|
+
}
|
|
2310
|
+
const all = validAgents;
|
|
2311
|
+
logger.info("Installing to all agents");
|
|
2312
|
+
return all;
|
|
2313
|
+
}
|
|
2314
|
+
if (useUniversalAgents) {
|
|
2315
|
+
const selected = await selectAgentsInteractive({ global: options.global });
|
|
2316
|
+
if (pD(selected)) {
|
|
2317
|
+
logger.cancel("Installation cancelled");
|
|
2318
|
+
await cleanup?.();
|
|
2319
|
+
process.exit(0);
|
|
2320
|
+
}
|
|
2321
|
+
return selected;
|
|
2322
|
+
}
|
|
2323
|
+
logger.info("Select agents to install skills to");
|
|
2324
|
+
const selected = await promptForAgents("Which agents do you want to install to?", Object.entries(agents).map(([key, config]) => ({
|
|
2325
|
+
value: key,
|
|
2326
|
+
label: config.displayName
|
|
2327
|
+
})));
|
|
2328
|
+
if (pD(selected)) {
|
|
2329
|
+
logger.cancel("Installation cancelled");
|
|
2330
|
+
await cleanup?.();
|
|
2331
|
+
process.exit(0);
|
|
2332
|
+
}
|
|
2333
|
+
return selected;
|
|
2334
|
+
}
|
|
2335
|
+
if (installedAgents.length === 1 || options.yes) {
|
|
2336
|
+
if (useUniversalAgents) {
|
|
2337
|
+
const target = ensureUniversalAgents(installedAgents);
|
|
2338
|
+
const { universal, symlinked } = splitAgentsByType(target);
|
|
2339
|
+
if (symlinked.length > 0) logger.info(`Installing to: ${import_picocolors.default.green("universal")} + ${symlinked.map((a) => import_picocolors.default.cyan(a)).join(", ")}`);
|
|
2340
|
+
else logger.info(`Installing to: ${import_picocolors.default.green("universal agents")}`);
|
|
2341
|
+
return target;
|
|
2342
|
+
}
|
|
2343
|
+
if (installedAgents.length === 1) {
|
|
2344
|
+
const firstAgent = installedAgents[0];
|
|
2345
|
+
logger.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
|
|
2346
|
+
} else logger.info(`Installing to: ${installedAgents.map((a) => import_picocolors.default.cyan(agents[a].displayName)).join(", ")}`);
|
|
2347
|
+
return installedAgents;
|
|
2348
|
+
}
|
|
2349
|
+
const selected = await selectAgentsInteractive({ global: options.global });
|
|
2350
|
+
if (pD(selected)) {
|
|
2351
|
+
logger.cancel("Installation cancelled");
|
|
2352
|
+
await cleanup?.();
|
|
2353
|
+
process.exit(0);
|
|
2354
|
+
}
|
|
2355
|
+
return selected;
|
|
2356
|
+
}
|
|
2357
|
+
async function selectInstallScope(options, targetAgents, cleanup) {
|
|
2358
|
+
let installGlobally = options.global ?? false;
|
|
2359
|
+
const supportsGlobal = targetAgents.some((a) => agents[a].globalSkillsDir !== void 0);
|
|
2360
|
+
if (options.global === void 0 && !options.yes && supportsGlobal) {
|
|
2361
|
+
const scope = await ve({
|
|
2362
|
+
message: "Installation scope",
|
|
2363
|
+
options: [{
|
|
2364
|
+
value: false,
|
|
2365
|
+
label: "Project",
|
|
2366
|
+
hint: "Install in current directory (committed with your project)"
|
|
2367
|
+
}, {
|
|
2368
|
+
value: true,
|
|
2369
|
+
label: "Global",
|
|
2370
|
+
hint: "Install in home directory (available across all projects)"
|
|
2371
|
+
}]
|
|
2372
|
+
});
|
|
2373
|
+
if (pD(scope)) {
|
|
2374
|
+
logger.cancel("Installation cancelled");
|
|
2375
|
+
await cleanup?.();
|
|
2376
|
+
process.exit(0);
|
|
2377
|
+
}
|
|
2378
|
+
installGlobally = scope;
|
|
2379
|
+
}
|
|
2380
|
+
return installGlobally;
|
|
2381
|
+
}
|
|
2382
|
+
async function selectInstallMode(options, cleanup) {
|
|
2383
|
+
if (options.yes) return "symlink";
|
|
2384
|
+
const modeChoice = await ve({
|
|
2385
|
+
message: "Installation method",
|
|
2386
|
+
options: [{
|
|
2387
|
+
value: "symlink",
|
|
2388
|
+
label: "Symlink (Recommended)",
|
|
2389
|
+
hint: "Single source of truth, easy updates"
|
|
2390
|
+
}, {
|
|
2391
|
+
value: "copy",
|
|
2392
|
+
label: "Copy to all agents",
|
|
2393
|
+
hint: "Independent copies for each agent"
|
|
2394
|
+
}]
|
|
2395
|
+
});
|
|
2396
|
+
if (pD(modeChoice)) {
|
|
2397
|
+
logger.cancel("Installation cancelled");
|
|
2398
|
+
await cleanup?.();
|
|
2399
|
+
process.exit(0);
|
|
2400
|
+
}
|
|
2401
|
+
return modeChoice;
|
|
2402
|
+
}
|
|
2403
|
+
async function isSourcePrivate(source) {
|
|
2404
|
+
const ownerRepo = parseOwnerRepo(source);
|
|
2405
|
+
if (!ownerRepo) return false;
|
|
2406
|
+
return isRepoPrivate(ownerRepo.owner, ownerRepo.repo);
|
|
2407
|
+
}
|
|
2408
|
+
async function executeInstallFlow(prepared, options, spinner, cleanup) {
|
|
2409
|
+
const { items, targetAgents, installGlobally, installMode, cognitiveType } = prepared;
|
|
2410
|
+
const cwd = process.cwd();
|
|
2411
|
+
const overwriteChecks = await Promise.all(items.flatMap((item) => targetAgents.map(async (agent) => ({
|
|
2412
|
+
skillName: item.installName,
|
|
2413
|
+
agent,
|
|
2414
|
+
installed: cognitiveType === "skill" ? await isSkillInstalled(item.installName, agent, { global: installGlobally }) : await isCognitiveInstalled(item.installName, agent, cognitiveType, { global: installGlobally })
|
|
2415
|
+
}))));
|
|
2416
|
+
const overwriteStatus = /* @__PURE__ */ new Map();
|
|
2417
|
+
for (const { skillName, agent, installed } of overwriteChecks) {
|
|
2418
|
+
if (!overwriteStatus.has(skillName)) overwriteStatus.set(skillName, /* @__PURE__ */ new Map());
|
|
2419
|
+
overwriteStatus.get(skillName).set(agent, installed);
|
|
2420
|
+
}
|
|
2421
|
+
const summaryLines = [];
|
|
2422
|
+
for (const item of items) {
|
|
2423
|
+
if (summaryLines.length > 0) summaryLines.push("");
|
|
2424
|
+
const shortCanonical = shortenPath$1(getCanonicalPath(item.installName, {
|
|
2425
|
+
global: installGlobally,
|
|
2426
|
+
cognitiveType
|
|
2427
|
+
}), cwd);
|
|
2428
|
+
summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
|
|
2429
|
+
summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
|
|
2430
|
+
if (item.fileCount && item.fileCount > 1) summaryLines.push(` ${import_picocolors.default.dim("files:")} ${item.fileCount}`);
|
|
2431
|
+
const skillOverwrites = overwriteStatus.get(item.installName);
|
|
2432
|
+
const overwriteAgents = targetAgents.filter((a) => skillOverwrites?.get(a)).map((a) => agents[a].displayName);
|
|
2433
|
+
if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList$1(overwriteAgents)}`);
|
|
2434
|
+
}
|
|
2435
|
+
logger.note(summaryLines.join("\n"), "Installation Summary");
|
|
2436
|
+
if (!options.yes) {
|
|
2437
|
+
const confirmed = await ye({ message: "Proceed with installation?" });
|
|
2438
|
+
if (pD(confirmed) || !confirmed) {
|
|
2439
|
+
logger.cancel("Installation cancelled");
|
|
2440
|
+
await cleanup?.();
|
|
2441
|
+
process.exit(0);
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
const label = items.length === 1 ? "Installing skill..." : "Installing skills...";
|
|
2445
|
+
spinner.start(label);
|
|
2446
|
+
const results = [];
|
|
2447
|
+
for (const item of items) for (const agent of targetAgents) {
|
|
2448
|
+
const result = await item.installFn(agent, {
|
|
2449
|
+
global: installGlobally,
|
|
2450
|
+
mode: installMode
|
|
2451
|
+
});
|
|
2452
|
+
results.push({
|
|
2453
|
+
skill: item.installName,
|
|
2454
|
+
agent: agents[agent].displayName,
|
|
2455
|
+
...result
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
spinner.succeed("Installation complete");
|
|
2459
|
+
logger.line();
|
|
2460
|
+
const successful = results.filter((r) => r.success);
|
|
2461
|
+
const failed = results.filter((r) => !r.success);
|
|
2462
|
+
const { telemetry } = prepared;
|
|
2463
|
+
if (telemetry.source) {
|
|
2464
|
+
let shouldTrack = true;
|
|
2465
|
+
if (telemetry.checkPrivacy) {
|
|
2466
|
+
if (await isSourcePrivate(telemetry.source) === true) shouldTrack = false;
|
|
2467
|
+
}
|
|
2468
|
+
if (shouldTrack) track({
|
|
2469
|
+
event: "install",
|
|
2470
|
+
source: telemetry.source,
|
|
2471
|
+
skills: items.map((i) => i.installName).join(","),
|
|
2472
|
+
agents: targetAgents.join(","),
|
|
2473
|
+
...installGlobally && { global: "1" },
|
|
2474
|
+
skillFiles: JSON.stringify(telemetry.skillFiles),
|
|
2475
|
+
...telemetry.sourceType && { sourceType: telemetry.sourceType }
|
|
2476
|
+
});
|
|
2477
|
+
}
|
|
2478
|
+
if (successful.length > 0 && installGlobally) {
|
|
2479
|
+
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
2480
|
+
for (const entry of prepared.lockEntries) if (successfulSkillNames.has(entry.name)) try {
|
|
2481
|
+
let skillFolderHash = entry.skillFolderHash;
|
|
2482
|
+
if (!skillFolderHash && entry.sourceType === "github" && entry.skillPath) {
|
|
2483
|
+
const hash = await fetchSkillFolderHash(entry.source, entry.skillPath);
|
|
2484
|
+
if (hash) skillFolderHash = hash;
|
|
2485
|
+
}
|
|
2486
|
+
if (entry.isCognitive) await addCognitiveToLock(entry.name, entry.cognitiveType, {
|
|
2487
|
+
source: entry.source,
|
|
2488
|
+
sourceType: entry.sourceType,
|
|
2489
|
+
sourceUrl: entry.sourceUrl,
|
|
2490
|
+
skillPath: entry.skillPath,
|
|
2491
|
+
skillFolderHash
|
|
2492
|
+
});
|
|
2493
|
+
else await addSkillToLock(entry.name, {
|
|
2494
|
+
source: entry.source,
|
|
2495
|
+
sourceType: entry.sourceType,
|
|
2496
|
+
sourceUrl: entry.sourceUrl,
|
|
2497
|
+
skillFolderHash
|
|
2498
|
+
});
|
|
2499
|
+
} catch {}
|
|
2500
|
+
}
|
|
2501
|
+
if (successful.length > 0) {
|
|
2502
|
+
const bySkill = /* @__PURE__ */ new Map();
|
|
2503
|
+
for (const r of successful) {
|
|
2504
|
+
const skillResults = bySkill.get(r.skill) || [];
|
|
2505
|
+
skillResults.push(r);
|
|
2506
|
+
bySkill.set(r.skill, skillResults);
|
|
2507
|
+
}
|
|
2508
|
+
const skillCount = bySkill.size;
|
|
2509
|
+
const resultLines = [];
|
|
2510
|
+
for (const [skillName, skillResults] of bySkill) {
|
|
2511
|
+
const firstResult = skillResults[0];
|
|
2512
|
+
if (firstResult.mode === "copy") {
|
|
2513
|
+
resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim("(copied)")}`);
|
|
2514
|
+
for (const r of skillResults) {
|
|
2515
|
+
const shortPath = shortenPath$1(r.path, cwd);
|
|
2516
|
+
resultLines.push(` ${import_picocolors.default.dim("→")} ${shortPath}`);
|
|
2517
|
+
}
|
|
2518
|
+
} else {
|
|
2519
|
+
if (firstResult.canonicalPath) {
|
|
2520
|
+
const shortPath = shortenPath$1(firstResult.canonicalPath, cwd);
|
|
2521
|
+
resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
|
|
2522
|
+
} else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName}`);
|
|
2523
|
+
resultLines.push(...buildResultLines(skillResults, targetAgents));
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
const title = import_picocolors.default.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
|
|
2527
|
+
logger.note(resultLines.join("\n"), title);
|
|
2528
|
+
const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
|
|
2529
|
+
if (symlinkFailures.length > 0) {
|
|
2530
|
+
const copiedAgentNames = symlinkFailures.map((r) => r.agent);
|
|
2531
|
+
logger.warning(import_picocolors.default.yellow(`Symlinks failed for: ${formatList$1(copiedAgentNames)}`));
|
|
2532
|
+
logger.message(import_picocolors.default.dim("Files were copied instead. On Windows, enable Developer Mode for symlink support."));
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
if (failed.length > 0) {
|
|
2536
|
+
logger.line();
|
|
2537
|
+
logger.error(import_picocolors.default.red(`Failed to install ${failed.length}`));
|
|
2538
|
+
for (const r of failed) logger.message(`${import_picocolors.default.red("✗")} ${r.skill} → ${r.agent}: ${import_picocolors.default.dim(r.error)}`);
|
|
2539
|
+
}
|
|
2540
|
+
logger.outro(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Review skills before use; they run with full agent permissions."));
|
|
2541
|
+
await promptForFindSkills(options, targetAgents);
|
|
2542
|
+
}
|
|
2543
|
+
async function selectSkillItems(items, options, cleanup) {
|
|
2544
|
+
if (options.skill?.includes("*")) {
|
|
2545
|
+
logger.info(`Installing all ${items.length} skills`);
|
|
2546
|
+
return items;
|
|
2547
|
+
}
|
|
2548
|
+
if (options.skill && options.skill.length > 0) {
|
|
2549
|
+
const selected = items.filter((s) => options.skill.some((name) => "installName" in s && s.installName?.toLowerCase() === name.toLowerCase() || s.name.toLowerCase() === name.toLowerCase()));
|
|
2550
|
+
if (selected.length === 0) {
|
|
2551
|
+
logger.error(`No matching skills found for: ${options.skill.join(", ")}`);
|
|
2552
|
+
logger.info("Available skills:");
|
|
2553
|
+
for (const s of items) logger.message(`- ${"installName" in s ? s.installName : s.name}`);
|
|
2554
|
+
await cleanup?.();
|
|
2555
|
+
process.exit(1);
|
|
2556
|
+
}
|
|
2557
|
+
logger.info(`Selected ${selected.length} skill${selected.length !== 1 ? "s" : ""}: ${selected.map((s) => import_picocolors.default.cyan("installName" in s ? s.installName : s.name)).join(", ")}`);
|
|
2558
|
+
return selected;
|
|
2559
|
+
}
|
|
2560
|
+
if (items.length === 1) {
|
|
2561
|
+
const first = items[0];
|
|
2562
|
+
logger.info(`Skill: ${import_picocolors.default.cyan("installName" in first ? first.installName : first.name)}`);
|
|
2563
|
+
return items;
|
|
2564
|
+
}
|
|
2565
|
+
if (options.yes) {
|
|
2566
|
+
logger.info(`Installing all ${items.length} skills`);
|
|
2567
|
+
return items;
|
|
2568
|
+
}
|
|
2569
|
+
const selected = await multiselect({
|
|
2570
|
+
message: "Select skills to install",
|
|
2571
|
+
options: items.map((s) => ({
|
|
2572
|
+
value: s,
|
|
2573
|
+
label: "installName" in s ? s.installName : s.name,
|
|
2574
|
+
hint: s.description.length > 60 ? s.description.slice(0, 57) + "..." : s.description
|
|
2575
|
+
})),
|
|
2576
|
+
required: true
|
|
2577
|
+
});
|
|
2578
|
+
if (pD(selected)) {
|
|
2579
|
+
logger.cancel("Installation cancelled");
|
|
2580
|
+
await cleanup?.();
|
|
2581
|
+
process.exit(0);
|
|
2582
|
+
}
|
|
2583
|
+
return selected;
|
|
2584
|
+
}
|
|
2585
|
+
async function resolveRemoteSkill(source, url, options, spinner) {
|
|
2586
|
+
const provider = findProvider(url);
|
|
2587
|
+
if (!provider) return resolveDirectUrlLegacy(source, url, options, spinner);
|
|
2588
|
+
spinner.start(`Fetching skill.md from ${provider.displayName}...`);
|
|
2589
|
+
const providerSkill = await provider.fetchSkill(url);
|
|
2590
|
+
if (!providerSkill) {
|
|
2591
|
+
spinner.fail("Invalid skill");
|
|
2592
|
+
logger.outro(import_picocolors.default.red("Could not fetch skill.md or missing required frontmatter (name, description)."));
|
|
2593
|
+
process.exit(1);
|
|
2594
|
+
}
|
|
2595
|
+
const remoteSkill = {
|
|
2596
|
+
name: providerSkill.name,
|
|
2597
|
+
description: providerSkill.description,
|
|
2598
|
+
content: providerSkill.content,
|
|
2599
|
+
installName: providerSkill.installName,
|
|
2600
|
+
sourceUrl: providerSkill.sourceUrl,
|
|
2601
|
+
providerId: provider.id,
|
|
2602
|
+
sourceIdentifier: provider.getSourceIdentifier(url),
|
|
2603
|
+
metadata: providerSkill.metadata
|
|
2604
|
+
};
|
|
2605
|
+
spinner.succeed(`Found skill: ${import_picocolors.default.cyan(remoteSkill.installName)}`);
|
|
2606
|
+
logger.info(`Skill: ${import_picocolors.default.cyan(remoteSkill.name)}`);
|
|
2607
|
+
logger.message(import_picocolors.default.dim(remoteSkill.description));
|
|
2608
|
+
logger.message(import_picocolors.default.dim(`Source: ${remoteSkill.sourceIdentifier}`));
|
|
2609
|
+
if (options.list) {
|
|
2610
|
+
logger.line();
|
|
2611
|
+
logger.step(import_picocolors.default.bold("Skill Details"));
|
|
2612
|
+
logger.message(`${import_picocolors.default.cyan("Name:")} ${remoteSkill.name}`);
|
|
2613
|
+
logger.message(`${import_picocolors.default.cyan("Install as:")} ${remoteSkill.installName}`);
|
|
2614
|
+
logger.message(`${import_picocolors.default.cyan("Provider:")} ${provider.displayName}`);
|
|
2615
|
+
logger.message(`${import_picocolors.default.cyan("Description:")} ${remoteSkill.description}`);
|
|
2616
|
+
logger.outro("Run without --list to install");
|
|
2617
|
+
process.exit(0);
|
|
2618
|
+
}
|
|
2619
|
+
const targetAgents = await selectTargetAgents(options, spinner, void 0, true);
|
|
2620
|
+
const installGlobally = await selectInstallScope(options, targetAgents);
|
|
2621
|
+
const installMode = await selectInstallMode(options);
|
|
2622
|
+
return {
|
|
2623
|
+
items: [{
|
|
2624
|
+
installName: remoteSkill.installName,
|
|
2625
|
+
displayName: remoteSkill.name,
|
|
2626
|
+
description: remoteSkill.description,
|
|
2627
|
+
sourceIdentifier: remoteSkill.sourceIdentifier,
|
|
2628
|
+
providerId: remoteSkill.providerId,
|
|
2629
|
+
sourceUrl: url,
|
|
2630
|
+
installFn: (agent, opts) => installRemoteSkillForAgent(remoteSkill, agent, opts)
|
|
2631
|
+
}],
|
|
2632
|
+
targetAgents,
|
|
2633
|
+
installGlobally,
|
|
2634
|
+
installMode,
|
|
2635
|
+
cognitiveType: "skill",
|
|
2636
|
+
lockEntries: [{
|
|
2637
|
+
name: remoteSkill.installName,
|
|
2638
|
+
source: remoteSkill.sourceIdentifier,
|
|
2639
|
+
sourceType: remoteSkill.providerId,
|
|
2640
|
+
sourceUrl: url,
|
|
2641
|
+
skillFolderHash: "",
|
|
2642
|
+
cognitiveType: "skill",
|
|
2643
|
+
isCognitive: false
|
|
2644
|
+
}],
|
|
2645
|
+
telemetry: {
|
|
2646
|
+
source: remoteSkill.sourceIdentifier,
|
|
2647
|
+
sourceType: remoteSkill.providerId,
|
|
2648
|
+
skillFiles: { [remoteSkill.installName]: url },
|
|
2649
|
+
checkPrivacy: true
|
|
2650
|
+
}
|
|
2651
|
+
};
|
|
2652
|
+
}
|
|
2653
|
+
async function resolveDirectUrlLegacy(source, url, options, spinner) {
|
|
2654
|
+
spinner.start("Fetching skill.md...");
|
|
2655
|
+
const mintlifySkill = await fetchMintlifySkill(url);
|
|
2656
|
+
if (!mintlifySkill) {
|
|
2657
|
+
spinner.fail("Invalid skill");
|
|
2658
|
+
logger.outro(import_picocolors.default.red("Could not fetch skill.md or missing required frontmatter (name, description, mintlify-proj)."));
|
|
2659
|
+
process.exit(1);
|
|
2660
|
+
}
|
|
2661
|
+
const remoteSkill = {
|
|
2662
|
+
name: mintlifySkill.name,
|
|
2663
|
+
description: mintlifySkill.description,
|
|
2664
|
+
content: mintlifySkill.content,
|
|
2665
|
+
installName: mintlifySkill.mintlifySite,
|
|
2666
|
+
sourceUrl: mintlifySkill.sourceUrl,
|
|
2667
|
+
providerId: "mintlify",
|
|
2668
|
+
sourceIdentifier: "mintlify/com"
|
|
2669
|
+
};
|
|
2670
|
+
spinner.succeed(`Found skill: ${import_picocolors.default.cyan(remoteSkill.installName)}`);
|
|
2671
|
+
logger.info(`Skill: ${import_picocolors.default.cyan(remoteSkill.name)}`);
|
|
2672
|
+
logger.message(import_picocolors.default.dim(remoteSkill.description));
|
|
2673
|
+
if (options.list) {
|
|
2674
|
+
logger.line();
|
|
2675
|
+
logger.step(import_picocolors.default.bold("Skill Details"));
|
|
2676
|
+
logger.message(`${import_picocolors.default.cyan("Name:")} ${remoteSkill.name}`);
|
|
2677
|
+
logger.message(`${import_picocolors.default.cyan("Site:")} ${remoteSkill.installName}`);
|
|
2678
|
+
logger.message(`${import_picocolors.default.cyan("Description:")} ${remoteSkill.description}`);
|
|
2679
|
+
logger.outro("Run without --list to install");
|
|
2680
|
+
process.exit(0);
|
|
2681
|
+
}
|
|
2682
|
+
const targetAgents = await selectTargetAgents(options, spinner);
|
|
2683
|
+
const installGlobally = await selectInstallScope(options, targetAgents);
|
|
2684
|
+
return {
|
|
2685
|
+
items: [{
|
|
2686
|
+
installName: remoteSkill.installName,
|
|
2687
|
+
displayName: remoteSkill.name,
|
|
2688
|
+
description: remoteSkill.description,
|
|
2689
|
+
sourceIdentifier: "mintlify/com",
|
|
2690
|
+
providerId: "mintlify",
|
|
2691
|
+
sourceUrl: url,
|
|
2692
|
+
installFn: (agent, opts) => installRemoteSkillForAgent(remoteSkill, agent, opts)
|
|
2693
|
+
}],
|
|
2694
|
+
targetAgents,
|
|
2695
|
+
installGlobally,
|
|
2696
|
+
installMode: "symlink",
|
|
2697
|
+
cognitiveType: "skill",
|
|
2698
|
+
lockEntries: [{
|
|
2699
|
+
name: remoteSkill.installName,
|
|
2700
|
+
source: `mintlify/${remoteSkill.installName}`,
|
|
2701
|
+
sourceType: "mintlify",
|
|
2702
|
+
sourceUrl: url,
|
|
2703
|
+
skillFolderHash: "",
|
|
2704
|
+
cognitiveType: "skill",
|
|
2705
|
+
isCognitive: false
|
|
2706
|
+
}],
|
|
2707
|
+
telemetry: {
|
|
2708
|
+
source: "mintlify/com",
|
|
2709
|
+
sourceType: "mintlify",
|
|
2710
|
+
skillFiles: { [remoteSkill.installName]: url },
|
|
2711
|
+
checkPrivacy: false
|
|
2712
|
+
}
|
|
2713
|
+
};
|
|
2714
|
+
}
|
|
2715
|
+
async function resolveWellKnownSkills(source, url, options, spinner) {
|
|
2716
|
+
spinner.start("Discovering skills from well-known endpoint...");
|
|
2717
|
+
const skills = await wellKnownProvider.fetchAllSkills(url);
|
|
2718
|
+
if (skills.length === 0) {
|
|
2719
|
+
spinner.fail("No skills found");
|
|
2720
|
+
logger.outro(import_picocolors.default.red("No skills found at this URL. Make sure the server has a /.well-known/skills/index.json file."));
|
|
2721
|
+
process.exit(1);
|
|
2722
|
+
}
|
|
2723
|
+
spinner.succeed(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
|
|
2724
|
+
for (const skill of skills) {
|
|
2725
|
+
logger.info(`Skill: ${import_picocolors.default.cyan(skill.installName)}`);
|
|
2726
|
+
logger.message(import_picocolors.default.dim(skill.description));
|
|
2727
|
+
if (skill.files.size > 1) logger.message(import_picocolors.default.dim(` Files: ${Array.from(skill.files.keys()).join(", ")}`));
|
|
2728
|
+
}
|
|
2729
|
+
if (options.list) {
|
|
2730
|
+
logger.line();
|
|
2731
|
+
logger.step(import_picocolors.default.bold("Available Skills"));
|
|
2732
|
+
for (const skill of skills) {
|
|
2733
|
+
logger.message(`${import_picocolors.default.cyan(skill.installName)}`);
|
|
2734
|
+
logger.message(` ${import_picocolors.default.dim(skill.description)}`);
|
|
2735
|
+
if (skill.files.size > 1) logger.message(` ${import_picocolors.default.dim(`Files: ${skill.files.size}`)}`);
|
|
2736
|
+
}
|
|
2737
|
+
logger.outro("Run without --list to install");
|
|
2738
|
+
process.exit(0);
|
|
2739
|
+
}
|
|
2740
|
+
const selectedSkills = await selectSkillItems(skills, options);
|
|
2741
|
+
const targetAgents = await selectTargetAgents(options, spinner);
|
|
2742
|
+
const installGlobally = await selectInstallScope(options, targetAgents);
|
|
2743
|
+
const installMode = await selectInstallMode(options);
|
|
2744
|
+
const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
|
|
2745
|
+
const items = selectedSkills.map((skill) => ({
|
|
2746
|
+
installName: skill.installName,
|
|
2747
|
+
displayName: skill.name,
|
|
2748
|
+
description: skill.description,
|
|
2749
|
+
sourceIdentifier,
|
|
2750
|
+
providerId: "well-known",
|
|
2751
|
+
sourceUrl: skill.sourceUrl,
|
|
2752
|
+
fileCount: skill.files.size,
|
|
2753
|
+
installFn: (agent, opts) => installWellKnownSkillForAgent(skill, agent, opts)
|
|
2754
|
+
}));
|
|
2755
|
+
const skillFiles = {};
|
|
2756
|
+
const lockEntries = [];
|
|
2757
|
+
for (const skill of selectedSkills) {
|
|
2758
|
+
skillFiles[skill.installName] = skill.sourceUrl;
|
|
2759
|
+
lockEntries.push({
|
|
2760
|
+
name: skill.installName,
|
|
2761
|
+
source: sourceIdentifier,
|
|
2762
|
+
sourceType: "well-known",
|
|
2763
|
+
sourceUrl: skill.sourceUrl,
|
|
2764
|
+
skillFolderHash: "",
|
|
2765
|
+
cognitiveType: "skill",
|
|
2766
|
+
isCognitive: false
|
|
2767
|
+
});
|
|
2768
|
+
}
|
|
2769
|
+
return {
|
|
2770
|
+
items,
|
|
2771
|
+
targetAgents,
|
|
2772
|
+
installGlobally,
|
|
2773
|
+
installMode,
|
|
2774
|
+
cognitiveType: "skill",
|
|
2775
|
+
lockEntries,
|
|
2776
|
+
telemetry: {
|
|
2777
|
+
source: sourceIdentifier,
|
|
2778
|
+
sourceType: "well-known",
|
|
2779
|
+
skillFiles,
|
|
2780
|
+
checkPrivacy: true
|
|
2781
|
+
}
|
|
2782
|
+
};
|
|
2783
|
+
}
|
|
2784
|
+
async function resolveGitRepoSkills(source, parsed, options, spinner, skillsDir, tempDir) {
|
|
2785
|
+
const cleanupFn = async () => {
|
|
2786
|
+
await cleanupDir(tempDir);
|
|
2787
|
+
};
|
|
2788
|
+
const cognitiveType = options.type ?? "skill";
|
|
2789
|
+
const cognitiveLabel = cognitiveType === "skill" ? "skills" : `${cognitiveType}s`;
|
|
2790
|
+
const cognitiveFile = cognitiveType === "skill" ? "SKILL.md" : cognitiveType === "agent" ? "AGENT.md" : "PROMPT.md";
|
|
2791
|
+
const includeInternal = !!(options.skill && options.skill.length > 0);
|
|
2792
|
+
spinner.start(`Discovering ${cognitiveLabel}...`);
|
|
2793
|
+
const skills = options.type ? await discoverCognitives(skillsDir, parsed.subpath, {
|
|
2794
|
+
includeInternal,
|
|
2795
|
+
fullDepth: options.fullDepth,
|
|
2796
|
+
types: [options.type]
|
|
2797
|
+
}) : await discoverSkills(skillsDir, parsed.subpath, {
|
|
2798
|
+
includeInternal,
|
|
2799
|
+
fullDepth: options.fullDepth
|
|
2800
|
+
});
|
|
2801
|
+
if (skills.length === 0) {
|
|
2802
|
+
spinner.fail(`No ${cognitiveLabel} found`);
|
|
2803
|
+
logger.outro(import_picocolors.default.red(`No valid ${cognitiveLabel} found. They require a ${cognitiveFile} with name and description.`));
|
|
2804
|
+
await cleanupFn();
|
|
2805
|
+
process.exit(1);
|
|
2806
|
+
}
|
|
2807
|
+
spinner.succeed(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
|
|
2808
|
+
if (options.list) {
|
|
2809
|
+
logger.line();
|
|
2810
|
+
logger.step(import_picocolors.default.bold("Available Skills"));
|
|
2811
|
+
for (const skill of skills) {
|
|
2812
|
+
logger.message(`${import_picocolors.default.cyan(getSkillDisplayName(skill))}`);
|
|
2813
|
+
logger.message(` ${import_picocolors.default.dim(skill.description)}`);
|
|
2814
|
+
}
|
|
2815
|
+
logger.outro("Use --skill <name> to install specific skills");
|
|
2816
|
+
await cleanupFn();
|
|
2817
|
+
process.exit(0);
|
|
2818
|
+
}
|
|
2819
|
+
let selectedSkills;
|
|
2820
|
+
if (options.skill?.includes("*")) {
|
|
2821
|
+
selectedSkills = skills;
|
|
2822
|
+
logger.info(`Installing all ${skills.length} skills`);
|
|
2823
|
+
} else if (options.skill && options.skill.length > 0) {
|
|
2824
|
+
selectedSkills = filterSkills(skills, options.skill);
|
|
2825
|
+
if (selectedSkills.length === 0) {
|
|
2826
|
+
logger.error(`No matching skills found for: ${options.skill.join(", ")}`);
|
|
2827
|
+
logger.info("Available skills:");
|
|
2828
|
+
for (const s of skills) logger.message(`- ${getSkillDisplayName(s)}`);
|
|
2829
|
+
await cleanupFn();
|
|
2830
|
+
process.exit(1);
|
|
2831
|
+
}
|
|
2832
|
+
logger.info(`Selected ${selectedSkills.length} skill${selectedSkills.length !== 1 ? "s" : ""}: ${selectedSkills.map((s) => import_picocolors.default.cyan(getSkillDisplayName(s))).join(", ")}`);
|
|
2833
|
+
} else if (skills.length === 1) {
|
|
2834
|
+
selectedSkills = skills;
|
|
2835
|
+
const firstSkill = skills[0];
|
|
2836
|
+
logger.info(`Skill: ${import_picocolors.default.cyan(getSkillDisplayName(firstSkill))}`);
|
|
2837
|
+
logger.message(import_picocolors.default.dim(firstSkill.description));
|
|
2838
|
+
} else if (options.yes) {
|
|
2839
|
+
selectedSkills = skills;
|
|
2840
|
+
logger.info(`Installing all ${skills.length} skills`);
|
|
2841
|
+
} else {
|
|
2842
|
+
const selected = await multiselect({
|
|
2843
|
+
message: "Select skills to install",
|
|
2844
|
+
options: skills.map((s) => ({
|
|
2845
|
+
value: s,
|
|
2846
|
+
label: getSkillDisplayName(s),
|
|
2847
|
+
hint: s.description.length > 60 ? s.description.slice(0, 57) + "..." : s.description
|
|
2848
|
+
})),
|
|
2849
|
+
required: true
|
|
2850
|
+
});
|
|
2851
|
+
if (pD(selected)) {
|
|
2852
|
+
logger.cancel("Installation cancelled");
|
|
2853
|
+
await cleanupFn();
|
|
2854
|
+
process.exit(0);
|
|
2855
|
+
}
|
|
2856
|
+
selectedSkills = selected;
|
|
2857
|
+
}
|
|
2858
|
+
const targetAgents = await selectTargetAgents(options, spinner, cleanupFn);
|
|
2859
|
+
const installGlobally = await selectInstallScope(options, targetAgents, cleanupFn);
|
|
2860
|
+
const installMode = await selectInstallMode(options, cleanupFn);
|
|
2861
|
+
const normalizedSource = getOwnerRepo(parsed);
|
|
2862
|
+
const skillFiles = {};
|
|
2863
|
+
for (const skill of selectedSkills) {
|
|
2864
|
+
let relativePath;
|
|
2865
|
+
if (tempDir && skill.path === tempDir) relativePath = cognitiveFile;
|
|
2866
|
+
else if (tempDir && skill.path.startsWith(tempDir + sep)) relativePath = skill.path.slice(tempDir.length + 1).split(sep).join("/") + `/${cognitiveFile}`;
|
|
2867
|
+
else continue;
|
|
2868
|
+
skillFiles[skill.name] = relativePath;
|
|
2869
|
+
}
|
|
2870
|
+
const items = selectedSkills.map((skill) => ({
|
|
2871
|
+
installName: skill.name,
|
|
2872
|
+
displayName: getSkillDisplayName(skill),
|
|
2873
|
+
description: skill.description,
|
|
2874
|
+
sourceIdentifier: normalizedSource ?? source,
|
|
2875
|
+
providerId: parsed.type,
|
|
2876
|
+
sourceUrl: parsed.url,
|
|
2877
|
+
installFn: (agent, opts) => installCognitiveForAgent(skill, agent, {
|
|
2878
|
+
...opts,
|
|
2879
|
+
cognitiveType
|
|
2880
|
+
})
|
|
2881
|
+
}));
|
|
2882
|
+
const lockEntries = [];
|
|
2883
|
+
if (normalizedSource) for (const skill of selectedSkills) lockEntries.push({
|
|
2884
|
+
name: getSkillDisplayName(skill),
|
|
2885
|
+
source: normalizedSource,
|
|
2886
|
+
sourceType: parsed.type,
|
|
2887
|
+
sourceUrl: parsed.url,
|
|
2888
|
+
skillPath: skillFiles[skill.name],
|
|
2889
|
+
skillFolderHash: "",
|
|
2890
|
+
cognitiveType,
|
|
2891
|
+
isCognitive: true
|
|
2892
|
+
});
|
|
2893
|
+
let telemetrySource = normalizedSource ?? "";
|
|
2894
|
+
if (normalizedSource) {
|
|
2895
|
+
const ownerRepo = parseOwnerRepo(normalizedSource);
|
|
2896
|
+
if (ownerRepo) if (await isRepoPrivate(ownerRepo.owner, ownerRepo.repo) === false) {} else telemetrySource = "";
|
|
2897
|
+
}
|
|
2898
|
+
return {
|
|
2899
|
+
items,
|
|
2900
|
+
targetAgents,
|
|
2901
|
+
installGlobally,
|
|
2902
|
+
installMode,
|
|
2903
|
+
cognitiveType,
|
|
2904
|
+
lockEntries,
|
|
2905
|
+
telemetry: {
|
|
2906
|
+
source: telemetrySource,
|
|
2907
|
+
skillFiles,
|
|
2908
|
+
checkPrivacy: false
|
|
2909
|
+
}
|
|
2910
|
+
};
|
|
2911
|
+
}
|
|
2912
|
+
async function runAdd(args, options = {}) {
|
|
2913
|
+
const source = args[0];
|
|
2914
|
+
let installTipShown = false;
|
|
2915
|
+
const showInstallTip = () => {
|
|
2916
|
+
if (installTipShown) return;
|
|
2917
|
+
logger.message(import_picocolors.default.dim("Tip: use the --yes (-y) and --global (-g) flags to install without prompts."));
|
|
2918
|
+
installTipShown = true;
|
|
2919
|
+
};
|
|
2920
|
+
if (!source) {
|
|
2921
|
+
logger.line();
|
|
2922
|
+
logger.log(import_picocolors.default.bgRed(import_picocolors.default.white(import_picocolors.default.bold(" ERROR "))) + " " + import_picocolors.default.red("Missing required argument: source"));
|
|
2923
|
+
logger.line();
|
|
2924
|
+
logger.dim(" Usage:");
|
|
2925
|
+
logger.log(` ${import_picocolors.default.cyan("npx synk add")} ${import_picocolors.default.yellow("<source>")} ${import_picocolors.default.dim("[options]")}`);
|
|
2926
|
+
logger.line();
|
|
2927
|
+
logger.dim(" Example:");
|
|
2928
|
+
logger.log(` ${import_picocolors.default.cyan("npx synk add")} ${import_picocolors.default.yellow("vercel-labs/agent-skills")}`);
|
|
2929
|
+
logger.line();
|
|
2930
|
+
process.exit(1);
|
|
2931
|
+
}
|
|
2932
|
+
if (options.all) {
|
|
2933
|
+
options.skill = ["*"];
|
|
2934
|
+
options.agent = ["*"];
|
|
2935
|
+
options.yes = true;
|
|
2936
|
+
}
|
|
2937
|
+
logger.intro(" synk ");
|
|
2938
|
+
if (!process.stdin.isTTY) showInstallTip();
|
|
2939
|
+
let tempDir = null;
|
|
2940
|
+
try {
|
|
2941
|
+
const spinner = logger.spinner();
|
|
2942
|
+
spinner.start("Parsing source...");
|
|
2943
|
+
const parsed = parseSource(source);
|
|
2944
|
+
spinner.succeed(`Source: ${parsed.type === "local" ? parsed.localPath : parsed.url}${parsed.ref ? ` @ ${import_picocolors.default.yellow(parsed.ref)}` : ""}${parsed.subpath ? ` (${parsed.subpath})` : ""}${parsed.skillFilter ? ` ${import_picocolors.default.dim("@")}${import_picocolors.default.cyan(parsed.skillFilter)}` : ""}`);
|
|
2945
|
+
if (parsed.type === "direct-url") {
|
|
2946
|
+
await executeInstallFlow(await resolveRemoteSkill(source, parsed.url, options, spinner), options, spinner);
|
|
2947
|
+
return;
|
|
2948
|
+
}
|
|
2949
|
+
if (parsed.type === "well-known") {
|
|
2950
|
+
await executeInstallFlow(await resolveWellKnownSkills(source, parsed.url, options, spinner), options, spinner);
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
let skillsDir;
|
|
2954
|
+
if (parsed.type === "local") {
|
|
2955
|
+
spinner.start("Validating local path...");
|
|
2956
|
+
if (!existsSync(parsed.localPath)) {
|
|
2957
|
+
spinner.fail("Path not found");
|
|
2958
|
+
logger.outro(import_picocolors.default.red(`Local path does not exist: ${parsed.localPath}`));
|
|
2959
|
+
process.exit(1);
|
|
2960
|
+
}
|
|
2961
|
+
skillsDir = parsed.localPath;
|
|
2962
|
+
spinner.succeed("Local path validated");
|
|
2963
|
+
} else {
|
|
2964
|
+
spinner.start("Cloning repository...");
|
|
2965
|
+
tempDir = await cloneRepo(parsed.url, parsed.ref);
|
|
2966
|
+
skillsDir = tempDir;
|
|
2967
|
+
spinner.succeed("Repository cloned");
|
|
2968
|
+
}
|
|
2969
|
+
if (parsed.skillFilter) {
|
|
2970
|
+
options.skill = options.skill || [];
|
|
2971
|
+
if (!options.skill.includes(parsed.skillFilter)) options.skill.push(parsed.skillFilter);
|
|
2972
|
+
}
|
|
2973
|
+
const prepared = await resolveGitRepoSkills(source, parsed, options, spinner, skillsDir, tempDir);
|
|
2974
|
+
const cleanupFn = async () => {
|
|
2975
|
+
await cleanupDir(tempDir);
|
|
2976
|
+
};
|
|
2977
|
+
await executeInstallFlow(prepared, options, spinner, cleanupFn);
|
|
2978
|
+
} catch (error) {
|
|
2979
|
+
if (error instanceof GitCloneError) {
|
|
2980
|
+
logger.error(import_picocolors.default.red("Failed to clone repository"));
|
|
2981
|
+
for (const line of error.message.split("\n")) logger.message(import_picocolors.default.dim(line));
|
|
2982
|
+
} else logger.error(error instanceof Error ? error.message : "Unknown error occurred");
|
|
2983
|
+
showInstallTip();
|
|
2984
|
+
logger.outro(import_picocolors.default.red("Installation failed"));
|
|
2985
|
+
process.exit(1);
|
|
2986
|
+
} finally {
|
|
2987
|
+
await cleanupDir(tempDir);
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
async function cleanupDir(tempDir) {
|
|
2991
|
+
if (tempDir) try {
|
|
2992
|
+
await cleanupTempDir(tempDir);
|
|
2993
|
+
} catch {}
|
|
2994
|
+
}
|
|
2995
|
+
async function promptForFindSkills(options, targetAgents) {
|
|
2996
|
+
if (!process.stdin.isTTY) return;
|
|
2997
|
+
if (options?.yes) return;
|
|
2998
|
+
try {
|
|
2999
|
+
if (await isPromptDismissed("findSkillsPrompt")) return;
|
|
3000
|
+
if (await isSkillInstalled("find-skills", "claude-code", { global: true })) {
|
|
3001
|
+
await dismissPrompt("findSkillsPrompt");
|
|
3002
|
+
return;
|
|
3003
|
+
}
|
|
3004
|
+
logger.line();
|
|
3005
|
+
logger.message(import_picocolors.default.dim("One-time prompt - you won't be asked again if you dismiss."));
|
|
3006
|
+
const install = await ye({ message: `Install the ${import_picocolors.default.cyan("find-skills")} skill? It helps your agent discover and suggest skills.` });
|
|
3007
|
+
if (pD(install)) {
|
|
3008
|
+
await dismissPrompt("findSkillsPrompt");
|
|
3009
|
+
return;
|
|
3010
|
+
}
|
|
3011
|
+
if (install) {
|
|
3012
|
+
await dismissPrompt("findSkillsPrompt");
|
|
3013
|
+
const findSkillsAgents = targetAgents?.filter((a) => a !== "replit");
|
|
3014
|
+
if (!findSkillsAgents || findSkillsAgents.length === 0) return;
|
|
3015
|
+
logger.line();
|
|
3016
|
+
logger.step("Installing find-skills skill...");
|
|
3017
|
+
try {
|
|
3018
|
+
await runAdd(["vercel-labs/skills"], {
|
|
3019
|
+
skill: ["find-skills"],
|
|
3020
|
+
global: true,
|
|
3021
|
+
yes: true,
|
|
3022
|
+
agent: findSkillsAgents
|
|
3023
|
+
});
|
|
3024
|
+
} catch {
|
|
3025
|
+
logger.warning("Failed to install find-skills. You can try again with:");
|
|
3026
|
+
logger.message(import_picocolors.default.dim(" npx synk add vercel-labs/skills@find-skills -g -y --all"));
|
|
3027
|
+
}
|
|
3028
|
+
} else {
|
|
3029
|
+
await dismissPrompt("findSkillsPrompt");
|
|
3030
|
+
logger.message(import_picocolors.default.dim("You can install it later with: npx synk add vercel-labs/skills@find-skills"));
|
|
3031
|
+
}
|
|
3032
|
+
} catch {}
|
|
3033
|
+
}
|
|
3034
|
+
function parseAddOptions(args) {
|
|
3035
|
+
const options = {};
|
|
3036
|
+
const source = [];
|
|
3037
|
+
for (let i = 0; i < args.length; i++) {
|
|
3038
|
+
const arg = args[i];
|
|
3039
|
+
if (arg === "-g" || arg === "--global") options.global = true;
|
|
3040
|
+
else if (arg === "-y" || arg === "--yes") options.yes = true;
|
|
3041
|
+
else if (arg === "-l" || arg === "--list") options.list = true;
|
|
3042
|
+
else if (arg === "--all") options.all = true;
|
|
3043
|
+
else if (arg === "-a" || arg === "--agent") {
|
|
3044
|
+
options.agent = options.agent || [];
|
|
3045
|
+
i++;
|
|
3046
|
+
let nextArg = args[i];
|
|
3047
|
+
while (i < args.length && nextArg && !nextArg.startsWith("-")) {
|
|
3048
|
+
options.agent.push(nextArg);
|
|
3049
|
+
i++;
|
|
3050
|
+
nextArg = args[i];
|
|
3051
|
+
}
|
|
3052
|
+
i--;
|
|
3053
|
+
} else if (arg === "-s" || arg === "--skill") {
|
|
3054
|
+
options.skill = options.skill || [];
|
|
3055
|
+
i++;
|
|
3056
|
+
let nextArg = args[i];
|
|
3057
|
+
while (i < args.length && nextArg && !nextArg.startsWith("-")) {
|
|
3058
|
+
options.skill.push(nextArg);
|
|
3059
|
+
i++;
|
|
3060
|
+
nextArg = args[i];
|
|
3061
|
+
}
|
|
3062
|
+
i--;
|
|
3063
|
+
} else if (arg === "-t" || arg === "--type") {
|
|
3064
|
+
i++;
|
|
3065
|
+
const typeVal = args[i];
|
|
3066
|
+
if (typeVal === "skill" || typeVal === "agent" || typeVal === "prompt") options.type = typeVal;
|
|
3067
|
+
} else if (arg === "--full-depth") options.fullDepth = true;
|
|
3068
|
+
else if (arg && !arg.startsWith("-")) source.push(arg);
|
|
3069
|
+
}
|
|
3070
|
+
return {
|
|
3071
|
+
source,
|
|
3072
|
+
options
|
|
3073
|
+
};
|
|
3074
|
+
}
|
|
3075
|
+
const SEARCH_API_BASE = process.env.SKILLS_API_URL || "https://skills.sh";
|
|
3076
|
+
async function searchSkillsAPI(query) {
|
|
3077
|
+
try {
|
|
3078
|
+
const url = `${SEARCH_API_BASE}/api/search?q=${encodeURIComponent(query)}&limit=10`;
|
|
3079
|
+
const res = await fetch(url);
|
|
3080
|
+
if (!res.ok) return [];
|
|
3081
|
+
return (await res.json()).skills.map((skill) => ({
|
|
3082
|
+
name: skill.name,
|
|
3083
|
+
slug: skill.id,
|
|
3084
|
+
source: skill.source || "",
|
|
3085
|
+
installs: skill.installs
|
|
3086
|
+
}));
|
|
3087
|
+
} catch {
|
|
3088
|
+
return [];
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
const HIDE_CURSOR = "\x1B[?25l";
|
|
3092
|
+
const SHOW_CURSOR = "\x1B[?25h";
|
|
3093
|
+
const CLEAR_DOWN = "\x1B[J";
|
|
3094
|
+
const MOVE_UP = (n) => `\x1b[${n}A`;
|
|
3095
|
+
const MOVE_TO_COL = (n) => `\x1b[${n}G`;
|
|
3096
|
+
async function runSearchPrompt(initialQuery = "") {
|
|
3097
|
+
let results = [];
|
|
3098
|
+
let selectedIndex = 0;
|
|
3099
|
+
let query = initialQuery;
|
|
3100
|
+
let loading = false;
|
|
3101
|
+
let debounceTimer = null;
|
|
3102
|
+
let lastRenderedLines = 0;
|
|
3103
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
3104
|
+
readline.emitKeypressEvents(process.stdin);
|
|
3105
|
+
process.stdin.resume();
|
|
3106
|
+
process.stdout.write(HIDE_CURSOR);
|
|
3107
|
+
function render() {
|
|
3108
|
+
if (lastRenderedLines > 0) process.stdout.write(MOVE_UP(lastRenderedLines) + MOVE_TO_COL(1));
|
|
3109
|
+
process.stdout.write(CLEAR_DOWN);
|
|
3110
|
+
const lines = [];
|
|
3111
|
+
const cursor = `${import_picocolors.default.bold("_")}`;
|
|
3112
|
+
lines.push(`Search skills: ${query}${cursor}`);
|
|
3113
|
+
lines.push("");
|
|
3114
|
+
if (!query || query.length < 2) lines.push(import_picocolors.default.dim("Start typing to search (min 2 chars)"));
|
|
3115
|
+
else if (results.length === 0 && loading) lines.push(import_picocolors.default.dim("Searching..."));
|
|
3116
|
+
else if (results.length === 0) lines.push(import_picocolors.default.dim("No skills found"));
|
|
3117
|
+
else {
|
|
3118
|
+
const visible = results.slice(0, 8);
|
|
3119
|
+
for (let i = 0; i < visible.length; i++) {
|
|
3120
|
+
const skill = visible[i];
|
|
3121
|
+
const isSelected = i === selectedIndex;
|
|
3122
|
+
const arrow = isSelected ? import_picocolors.default.bold(">") : " ";
|
|
3123
|
+
const name = isSelected ? import_picocolors.default.bold(skill.name) : skill.name;
|
|
3124
|
+
const source = skill.source ? ` ${import_picocolors.default.dim(skill.source)}` : "";
|
|
3125
|
+
const loadingIndicator = loading && i === 0 ? ` ${import_picocolors.default.dim("...")}` : "";
|
|
3126
|
+
lines.push(` ${arrow} ${name}${source}${loadingIndicator}`);
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
lines.push("");
|
|
3130
|
+
lines.push(import_picocolors.default.dim("up/down navigate | enter select | esc cancel"));
|
|
3131
|
+
for (const line of lines) process.stdout.write(line + "\n");
|
|
3132
|
+
lastRenderedLines = lines.length;
|
|
3133
|
+
}
|
|
3134
|
+
function triggerSearch(q) {
|
|
3135
|
+
if (debounceTimer) {
|
|
3136
|
+
clearTimeout(debounceTimer);
|
|
3137
|
+
debounceTimer = null;
|
|
3138
|
+
}
|
|
3139
|
+
loading = false;
|
|
3140
|
+
if (!q || q.length < 2) {
|
|
3141
|
+
results = [];
|
|
3142
|
+
selectedIndex = 0;
|
|
3143
|
+
render();
|
|
3144
|
+
return;
|
|
3145
|
+
}
|
|
3146
|
+
loading = true;
|
|
3147
|
+
render();
|
|
3148
|
+
const debounceMs = Math.max(150, 350 - q.length * 50);
|
|
3149
|
+
debounceTimer = setTimeout(async () => {
|
|
3150
|
+
try {
|
|
3151
|
+
results = await searchSkillsAPI(q);
|
|
3152
|
+
selectedIndex = 0;
|
|
3153
|
+
} catch {
|
|
3154
|
+
results = [];
|
|
3155
|
+
} finally {
|
|
3156
|
+
loading = false;
|
|
3157
|
+
debounceTimer = null;
|
|
3158
|
+
render();
|
|
3159
|
+
}
|
|
3160
|
+
}, debounceMs);
|
|
3161
|
+
}
|
|
3162
|
+
if (initialQuery) triggerSearch(initialQuery);
|
|
3163
|
+
render();
|
|
3164
|
+
return new Promise((resolve) => {
|
|
3165
|
+
function cleanup() {
|
|
3166
|
+
process.stdin.removeListener("keypress", handleKeypress);
|
|
3167
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
3168
|
+
process.stdout.write(SHOW_CURSOR);
|
|
3169
|
+
process.stdin.pause();
|
|
3170
|
+
}
|
|
3171
|
+
function handleKeypress(_ch, key) {
|
|
3172
|
+
if (!key) return;
|
|
3173
|
+
if (key.name === "escape" || key.ctrl && key.name === "c") {
|
|
3174
|
+
cleanup();
|
|
3175
|
+
resolve(null);
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
if (key.name === "return") {
|
|
3179
|
+
cleanup();
|
|
3180
|
+
resolve(results[selectedIndex] || null);
|
|
3181
|
+
return;
|
|
3182
|
+
}
|
|
3183
|
+
if (key.name === "up") {
|
|
3184
|
+
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
3185
|
+
render();
|
|
3186
|
+
return;
|
|
3187
|
+
}
|
|
3188
|
+
if (key.name === "down") {
|
|
3189
|
+
selectedIndex = Math.min(Math.max(0, results.length - 1), selectedIndex + 1);
|
|
3190
|
+
render();
|
|
3191
|
+
return;
|
|
3192
|
+
}
|
|
3193
|
+
if (key.name === "backspace") {
|
|
3194
|
+
if (query.length > 0) {
|
|
3195
|
+
query = query.slice(0, -1);
|
|
3196
|
+
triggerSearch(query);
|
|
3197
|
+
}
|
|
3198
|
+
return;
|
|
3199
|
+
}
|
|
3200
|
+
if (key.sequence && !key.ctrl && !key.meta && key.sequence.length === 1) {
|
|
3201
|
+
const char = key.sequence;
|
|
3202
|
+
if (char >= " " && char <= "~") {
|
|
3203
|
+
query += char;
|
|
3204
|
+
triggerSearch(query);
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
process.stdin.on("keypress", handleKeypress);
|
|
3209
|
+
});
|
|
3210
|
+
}
|
|
3211
|
+
function getOwnerRepoFromString(pkg) {
|
|
3212
|
+
const atIndex = pkg.lastIndexOf("@");
|
|
3213
|
+
const match = (atIndex > 0 ? pkg.slice(0, atIndex) : pkg).match(/^([^/]+)\/([^/]+)$/);
|
|
3214
|
+
if (match) return {
|
|
3215
|
+
owner: match[1],
|
|
3216
|
+
repo: match[2]
|
|
3217
|
+
};
|
|
3218
|
+
return null;
|
|
3219
|
+
}
|
|
3220
|
+
async function isRepoPublic(owner, repo) {
|
|
3221
|
+
return await isRepoPrivate(owner, repo) === false;
|
|
3222
|
+
}
|
|
3223
|
+
async function runFind(args) {
|
|
3224
|
+
const query = args.join(" ");
|
|
3225
|
+
const isNonInteractive = !process.stdin.isTTY;
|
|
3226
|
+
const agentTip = `${import_picocolors.default.dim("Tip: if running in a coding agent, follow these steps:")}
|
|
3227
|
+
${import_picocolors.default.dim(" 1) npx synk find [query]")}
|
|
3228
|
+
${import_picocolors.default.dim(" 2) npx synk add <owner/repo@skill>")}`;
|
|
3229
|
+
if (query) {
|
|
3230
|
+
const results = await searchSkillsAPI(query);
|
|
3231
|
+
track({
|
|
3232
|
+
event: "find",
|
|
3233
|
+
query,
|
|
3234
|
+
resultCount: String(results.length)
|
|
3235
|
+
});
|
|
3236
|
+
if (results.length === 0) {
|
|
3237
|
+
logger.dim(`No skills found for "${query}"`);
|
|
3238
|
+
return;
|
|
3239
|
+
}
|
|
3240
|
+
logger.log(`${import_picocolors.default.dim("Install with")} npx synk add <owner/repo@skill>`);
|
|
3241
|
+
logger.line();
|
|
3242
|
+
for (const skill of results.slice(0, 6)) {
|
|
3243
|
+
const pkg = skill.source || skill.slug;
|
|
3244
|
+
logger.log(`${pkg}@${skill.name}`);
|
|
3245
|
+
logger.dim(`\u2514 https://skills.sh/${skill.slug}`);
|
|
3246
|
+
logger.line();
|
|
3247
|
+
}
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3250
|
+
if (isNonInteractive) {
|
|
3251
|
+
logger.log(agentTip);
|
|
3252
|
+
logger.line();
|
|
3253
|
+
}
|
|
3254
|
+
const selected = await runSearchPrompt();
|
|
3255
|
+
track({
|
|
3256
|
+
event: "find",
|
|
3257
|
+
query: "",
|
|
3258
|
+
resultCount: selected ? "1" : "0",
|
|
3259
|
+
interactive: "1"
|
|
3260
|
+
});
|
|
3261
|
+
if (!selected) {
|
|
3262
|
+
logger.dim("Search cancelled");
|
|
3263
|
+
logger.line();
|
|
3264
|
+
return;
|
|
3265
|
+
}
|
|
3266
|
+
const pkg = selected.source || selected.slug;
|
|
3267
|
+
const skillName = selected.name;
|
|
3268
|
+
logger.line();
|
|
3269
|
+
logger.log(`Installing ${import_picocolors.default.bold(skillName)} from ${import_picocolors.default.dim(pkg)}...`);
|
|
3270
|
+
logger.line();
|
|
3271
|
+
const { source, options } = parseAddOptions([
|
|
3272
|
+
pkg,
|
|
3273
|
+
"--skill",
|
|
3274
|
+
skillName
|
|
3275
|
+
]);
|
|
3276
|
+
await runAdd(source, options);
|
|
3277
|
+
logger.line();
|
|
3278
|
+
const info = getOwnerRepoFromString(pkg);
|
|
3279
|
+
if (info && await isRepoPublic(info.owner, info.repo)) logger.log(`${import_picocolors.default.dim("View the skill at")} https://skills.sh/${selected.slug}`);
|
|
3280
|
+
else logger.log(`${import_picocolors.default.dim("Discover more skills at")} https://skills.sh`);
|
|
3281
|
+
logger.line();
|
|
3282
|
+
}
|
|
3283
|
+
function shortenPath(fullPath, cwd) {
|
|
3284
|
+
const home = homedir();
|
|
3285
|
+
if (fullPath.startsWith(home)) return fullPath.replace(home, "~");
|
|
3286
|
+
if (fullPath.startsWith(cwd)) return "." + fullPath.slice(cwd.length);
|
|
3287
|
+
return fullPath;
|
|
3288
|
+
}
|
|
3289
|
+
function formatList(items, maxShow = 5) {
|
|
3290
|
+
if (items.length <= maxShow) return items.join(", ");
|
|
3291
|
+
const shown = items.slice(0, maxShow);
|
|
3292
|
+
const remaining = items.length - maxShow;
|
|
3293
|
+
return `${shown.join(", ")} +${remaining} more`;
|
|
3294
|
+
}
|
|
3295
|
+
function parseListOptions(args) {
|
|
3296
|
+
const options = {};
|
|
3297
|
+
for (let i = 0; i < args.length; i++) {
|
|
3298
|
+
const arg = args[i];
|
|
3299
|
+
if (arg === "-g" || arg === "--global") options.global = true;
|
|
3300
|
+
else if (arg === "-a" || arg === "--agent") {
|
|
3301
|
+
options.agent = options.agent || [];
|
|
3302
|
+
while (i + 1 < args.length && !args[i + 1].startsWith("-")) options.agent.push(args[++i]);
|
|
3303
|
+
} else if (arg === "-t" || arg === "--type") {
|
|
3304
|
+
i++;
|
|
3305
|
+
const typeVal = args[i];
|
|
3306
|
+
if (typeVal === "skill" || typeVal === "agent" || typeVal === "prompt") options.type = typeVal;
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
return options;
|
|
3310
|
+
}
|
|
3311
|
+
async function runList(args) {
|
|
3312
|
+
const options = parseListOptions(args);
|
|
3313
|
+
const scope = options.global === true ? true : false;
|
|
3314
|
+
let agentFilter;
|
|
3315
|
+
if (options.agent && options.agent.length > 0) {
|
|
3316
|
+
const validAgents = Object.keys(agents);
|
|
3317
|
+
const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
|
|
3318
|
+
if (invalidAgents.length > 0) {
|
|
3319
|
+
logger.warning(`Invalid agents: ${invalidAgents.join(", ")}`);
|
|
3320
|
+
logger.dim(`Valid agents: ${validAgents.join(", ")}`);
|
|
3321
|
+
process.exit(1);
|
|
3322
|
+
}
|
|
3323
|
+
agentFilter = options.agent;
|
|
3324
|
+
}
|
|
3325
|
+
const installedSkills = await listInstalledSkills({
|
|
3326
|
+
global: scope,
|
|
3327
|
+
agentFilter
|
|
3328
|
+
});
|
|
3329
|
+
const filteredSkills = options.type ? installedSkills.filter((s) => (s.cognitiveType || "skill") === options.type) : installedSkills;
|
|
3330
|
+
const cwd = process.cwd();
|
|
3331
|
+
const scopeLabel = scope ? "Global" : "Project";
|
|
3332
|
+
const typeLabel = options.type ? ` ${options.type}s` : "";
|
|
3333
|
+
if (filteredSkills.length === 0) {
|
|
3334
|
+
logger.dim(`No ${scopeLabel.toLowerCase()}${typeLabel} found.`);
|
|
3335
|
+
if (scope) logger.dim("Try listing project cognitives without -g");
|
|
3336
|
+
else logger.dim("Try listing global cognitives with -g");
|
|
3337
|
+
return;
|
|
3338
|
+
}
|
|
3339
|
+
function getTypeLabel(skill) {
|
|
3340
|
+
const ct = skill.cognitiveType || "skill";
|
|
3341
|
+
if (ct === "agent") return ` ${import_picocolors.default.dim("[AGENT]")}`;
|
|
3342
|
+
if (ct === "prompt") return ` ${import_picocolors.default.dim("[PROMPT]")}`;
|
|
3343
|
+
return "";
|
|
3344
|
+
}
|
|
3345
|
+
function printSkill(skill) {
|
|
3346
|
+
const shortPath = shortenPath(skill.canonicalPath, cwd);
|
|
3347
|
+
const agentNames = skill.agents.map((a) => agents[a].displayName);
|
|
3348
|
+
const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : import_picocolors.default.yellow("not linked");
|
|
3349
|
+
logger.log(`${import_picocolors.default.cyan(skill.name)}${getTypeLabel(skill)} ${import_picocolors.default.dim(shortPath)}`);
|
|
3350
|
+
logger.log(` ${import_picocolors.default.dim("Agents:")} ${agentInfo}`);
|
|
3351
|
+
}
|
|
3352
|
+
logger.bold(`${scopeLabel}${typeLabel ? typeLabel.charAt(0).toUpperCase() + typeLabel.slice(1) : " Cognitives"}`);
|
|
3353
|
+
logger.line();
|
|
3354
|
+
for (const skill of filteredSkills) printSkill(skill);
|
|
3355
|
+
logger.line();
|
|
3356
|
+
}
|
|
3357
|
+
async function removeCommand(skillNames, options) {
|
|
3358
|
+
const isGlobal = options.global ?? false;
|
|
3359
|
+
const cwd = process.cwd();
|
|
3360
|
+
const spinner = logger.spinner("Scanning for installed skills...");
|
|
3361
|
+
const skillNamesSet = /* @__PURE__ */ new Set();
|
|
3362
|
+
const scanDir = async (dir) => {
|
|
3363
|
+
try {
|
|
3364
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
3365
|
+
for (const entry of entries) if (entry.isDirectory()) skillNamesSet.add(entry.name);
|
|
3366
|
+
} catch (err) {
|
|
3367
|
+
if (err instanceof Error && err.code !== "ENOENT") logger.warning(`Could not scan directory ${dir}: ${err.message}`);
|
|
3368
|
+
}
|
|
3369
|
+
};
|
|
3370
|
+
const typesToScan = options.type ? [options.type] : [
|
|
3371
|
+
"skill",
|
|
3372
|
+
"agent",
|
|
3373
|
+
"prompt"
|
|
3374
|
+
];
|
|
3375
|
+
for (const cogType of typesToScan) if (isGlobal) {
|
|
3376
|
+
await scanDir(getCanonicalDir(cogType, true, cwd));
|
|
3377
|
+
for (const agent of Object.values(agents)) {
|
|
3378
|
+
const dir = cogType === "skill" ? agent.globalSkillsDir : cogType === "agent" ? agent.globalAgentsDir : agent.globalPromptsDir;
|
|
3379
|
+
if (dir !== void 0) await scanDir(dir);
|
|
3380
|
+
}
|
|
3381
|
+
} else {
|
|
3382
|
+
await scanDir(getCanonicalDir(cogType, false, cwd));
|
|
3383
|
+
for (const agent of Object.values(agents)) await scanDir(join(cwd, cogType === "skill" ? agent.skillsDir : cogType === "agent" ? agent.agentsDir : agent.promptsDir));
|
|
3384
|
+
}
|
|
3385
|
+
const installedSkills = Array.from(skillNamesSet).sort();
|
|
3386
|
+
spinner.succeed(`Found ${installedSkills.length} unique installed skill(s)`);
|
|
3387
|
+
if (installedSkills.length === 0) {
|
|
3388
|
+
logger.outro(import_picocolors.default.yellow("No skills found to remove."));
|
|
3389
|
+
return;
|
|
3390
|
+
}
|
|
3391
|
+
if (options.agent && options.agent.length > 0) {
|
|
3392
|
+
const validAgents = Object.keys(agents);
|
|
3393
|
+
const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
|
|
3394
|
+
if (invalidAgents.length > 0) {
|
|
3395
|
+
logger.error(`Invalid agents: ${invalidAgents.join(", ")}`);
|
|
3396
|
+
logger.info(`Valid agents: ${validAgents.join(", ")}`);
|
|
3397
|
+
process.exit(1);
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
let selectedSkills = [];
|
|
3401
|
+
if (options.all) selectedSkills = installedSkills;
|
|
3402
|
+
else if (skillNames.length > 0) {
|
|
3403
|
+
selectedSkills = installedSkills.filter((s) => skillNames.some((name) => name.toLowerCase() === s.toLowerCase()));
|
|
3404
|
+
if (selectedSkills.length === 0) {
|
|
3405
|
+
logger.error(`No matching skills found for: ${skillNames.join(", ")}`);
|
|
3406
|
+
return;
|
|
3407
|
+
}
|
|
3408
|
+
} else {
|
|
3409
|
+
const choices = installedSkills.map((s) => ({
|
|
3410
|
+
value: s,
|
|
3411
|
+
label: s
|
|
3412
|
+
}));
|
|
3413
|
+
const selected = await fe({
|
|
3414
|
+
message: `Select skills to remove ${import_picocolors.default.dim("(space to toggle)")}`,
|
|
3415
|
+
options: choices,
|
|
3416
|
+
required: true
|
|
3417
|
+
});
|
|
3418
|
+
if (pD(selected)) {
|
|
3419
|
+
logger.cancel("Removal cancelled");
|
|
3420
|
+
process.exit(0);
|
|
3421
|
+
}
|
|
3422
|
+
selectedSkills = selected;
|
|
3423
|
+
}
|
|
3424
|
+
let targetAgents;
|
|
3425
|
+
if (options.agent && options.agent.length > 0) targetAgents = options.agent;
|
|
3426
|
+
else {
|
|
3427
|
+
spinner.start("Detecting installed agents...");
|
|
3428
|
+
targetAgents = await detectInstalledAgents();
|
|
3429
|
+
if (targetAgents.length === 0) targetAgents = Object.keys(agents);
|
|
3430
|
+
spinner.succeed(`Targeting ${targetAgents.length} installed agent(s)`);
|
|
3431
|
+
}
|
|
3432
|
+
if (!options.yes) {
|
|
3433
|
+
logger.line();
|
|
3434
|
+
logger.info("Skills to remove:");
|
|
3435
|
+
for (const skill of selectedSkills) logger.message(`${import_picocolors.default.red("•")} ${skill}`);
|
|
3436
|
+
logger.line();
|
|
3437
|
+
const confirmed = await ye({ message: `Are you sure you want to uninstall ${selectedSkills.length} skill(s)?` });
|
|
3438
|
+
if (pD(confirmed) || !confirmed) {
|
|
3439
|
+
logger.cancel("Removal cancelled");
|
|
3440
|
+
process.exit(0);
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
spinner.start("Removing skills...");
|
|
3444
|
+
const results = [];
|
|
3445
|
+
for (const skillName of selectedSkills) try {
|
|
3446
|
+
for (const agentKey of targetAgents) {
|
|
3447
|
+
const agent = agents[agentKey];
|
|
3448
|
+
const skillPath = getInstallPath(skillName, agentKey, {
|
|
3449
|
+
global: isGlobal,
|
|
3450
|
+
cwd
|
|
3451
|
+
});
|
|
3452
|
+
try {
|
|
3453
|
+
if (await lstat(skillPath).catch(() => null)) await rm(skillPath, {
|
|
3454
|
+
recursive: true,
|
|
3455
|
+
force: true
|
|
3456
|
+
});
|
|
3457
|
+
} catch (err) {
|
|
3458
|
+
logger.warning(`Could not remove skill from ${agent.displayName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
for (const cogType of typesToScan) await rm(getCanonicalPath(skillName, {
|
|
3462
|
+
global: isGlobal,
|
|
3463
|
+
cwd,
|
|
3464
|
+
cognitiveType: cogType
|
|
3465
|
+
}), {
|
|
3466
|
+
recursive: true,
|
|
3467
|
+
force: true
|
|
3468
|
+
});
|
|
3469
|
+
const lockEntry = isGlobal ? await getSkillFromLock(skillName) : null;
|
|
3470
|
+
const effectiveSource = lockEntry?.source || "local";
|
|
3471
|
+
const effectiveSourceType = lockEntry?.sourceType || "local";
|
|
3472
|
+
if (isGlobal) await removeSkillFromLock(skillName);
|
|
3473
|
+
results.push({
|
|
3474
|
+
skill: skillName,
|
|
3475
|
+
success: true,
|
|
3476
|
+
source: effectiveSource,
|
|
3477
|
+
sourceType: effectiveSourceType
|
|
3478
|
+
});
|
|
3479
|
+
} catch (err) {
|
|
3480
|
+
results.push({
|
|
3481
|
+
skill: skillName,
|
|
3482
|
+
success: false,
|
|
3483
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3484
|
+
});
|
|
3485
|
+
}
|
|
3486
|
+
spinner.succeed("Removal process complete");
|
|
3487
|
+
const successful = results.filter((r) => r.success);
|
|
3488
|
+
const failed = results.filter((r) => !r.success);
|
|
3489
|
+
if (successful.length > 0) {
|
|
3490
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
3491
|
+
for (const r of successful) {
|
|
3492
|
+
const source = r.source || "local";
|
|
3493
|
+
const existing = bySource.get(source) || { skills: [] };
|
|
3494
|
+
existing.skills.push(r.skill);
|
|
3495
|
+
existing.sourceType = r.sourceType;
|
|
3496
|
+
bySource.set(source, existing);
|
|
3497
|
+
}
|
|
3498
|
+
for (const [source, data] of bySource) track({
|
|
3499
|
+
event: "remove",
|
|
3500
|
+
source,
|
|
3501
|
+
skills: data.skills.join(","),
|
|
3502
|
+
agents: targetAgents.join(","),
|
|
3503
|
+
...isGlobal && { global: "1" },
|
|
3504
|
+
sourceType: data.sourceType
|
|
3505
|
+
});
|
|
3506
|
+
}
|
|
3507
|
+
if (successful.length > 0) logger.success(import_picocolors.default.green(`Successfully removed ${successful.length} skill(s)`));
|
|
3508
|
+
if (failed.length > 0) {
|
|
3509
|
+
logger.error(import_picocolors.default.red(`Failed to remove ${failed.length} skill(s)`));
|
|
3510
|
+
for (const r of failed) logger.message(`${import_picocolors.default.red("✗")} ${r.skill}: ${r.error}`);
|
|
3511
|
+
}
|
|
3512
|
+
logger.line();
|
|
3513
|
+
logger.outro(import_picocolors.default.green("Done!"));
|
|
3514
|
+
}
|
|
3515
|
+
function parseRemoveOptions(args) {
|
|
3516
|
+
const options = {};
|
|
3517
|
+
const skills = [];
|
|
3518
|
+
for (let i = 0; i < args.length; i++) {
|
|
3519
|
+
const arg = args[i];
|
|
3520
|
+
if (arg === "-g" || arg === "--global") options.global = true;
|
|
3521
|
+
else if (arg === "-y" || arg === "--yes") options.yes = true;
|
|
3522
|
+
else if (arg === "--all") options.all = true;
|
|
3523
|
+
else if (arg === "-a" || arg === "--agent") {
|
|
3524
|
+
options.agent = options.agent || [];
|
|
3525
|
+
i++;
|
|
3526
|
+
let nextArg = args[i];
|
|
3527
|
+
while (i < args.length && nextArg && !nextArg.startsWith("-")) {
|
|
3528
|
+
options.agent.push(nextArg);
|
|
3529
|
+
i++;
|
|
3530
|
+
nextArg = args[i];
|
|
3531
|
+
}
|
|
3532
|
+
i--;
|
|
3533
|
+
} else if (arg === "-t" || arg === "--type") {
|
|
3534
|
+
i++;
|
|
3535
|
+
const typeVal = args[i];
|
|
3536
|
+
if (typeVal === "skill" || typeVal === "agent" || typeVal === "prompt") options.type = typeVal;
|
|
3537
|
+
} else if (arg && !arg.startsWith("-")) skills.push(arg);
|
|
3538
|
+
}
|
|
3539
|
+
return {
|
|
3540
|
+
skills,
|
|
3541
|
+
options
|
|
3542
|
+
};
|
|
3543
|
+
}
|
|
3544
|
+
function runInit(args) {
|
|
3545
|
+
const cwd = process.cwd();
|
|
3546
|
+
let cognitiveType = "skill";
|
|
3547
|
+
const filteredArgs = [];
|
|
3548
|
+
for (let i = 0; i < args.length; i++) {
|
|
3549
|
+
const arg = args[i];
|
|
3550
|
+
if ((arg === "-t" || arg === "--type") && i + 1 < args.length) {
|
|
3551
|
+
const typeVal = args[i + 1];
|
|
3552
|
+
if (typeVal === "skill" || typeVal === "agent" || typeVal === "prompt") cognitiveType = typeVal;
|
|
3553
|
+
i++;
|
|
3554
|
+
} else if (arg && !arg.startsWith("-")) filteredArgs.push(arg);
|
|
3555
|
+
}
|
|
3556
|
+
const itemName = filteredArgs[0] || basename(cwd);
|
|
3557
|
+
const hasName = filteredArgs[0] !== void 0;
|
|
3558
|
+
const fileName = {
|
|
3559
|
+
skill: "SKILL.md",
|
|
3560
|
+
agent: "AGENT.md",
|
|
3561
|
+
prompt: "PROMPT.md"
|
|
3562
|
+
}[cognitiveType];
|
|
3563
|
+
const itemDir = hasName ? join(cwd, itemName) : cwd;
|
|
3564
|
+
const itemFile = join(itemDir, fileName);
|
|
3565
|
+
const displayPath = hasName ? `${itemName}/${fileName}` : fileName;
|
|
3566
|
+
if (existsSync(itemFile)) {
|
|
3567
|
+
logger.log(`${cognitiveType} already exists at ${import_picocolors.default.dim(displayPath)}`);
|
|
3568
|
+
return;
|
|
3569
|
+
}
|
|
3570
|
+
if (hasName) mkdirSync(itemDir, { recursive: true });
|
|
3571
|
+
let content;
|
|
3572
|
+
if (cognitiveType === "agent") content = `---
|
|
3573
|
+
name: ${itemName}
|
|
3574
|
+
description: A brief description of this agent
|
|
3575
|
+
---
|
|
3576
|
+
|
|
3577
|
+
# ${itemName}
|
|
3578
|
+
|
|
3579
|
+
Agent instructions here.
|
|
3580
|
+
|
|
3581
|
+
## Role
|
|
3582
|
+
|
|
3583
|
+
Describe the agent's role and capabilities.
|
|
3584
|
+
|
|
3585
|
+
## Instructions
|
|
3586
|
+
|
|
3587
|
+
1. First step
|
|
3588
|
+
2. Second step
|
|
3589
|
+
3. Additional steps as needed
|
|
3590
|
+
`;
|
|
3591
|
+
else if (cognitiveType === "prompt") content = `---
|
|
3592
|
+
name: ${itemName}
|
|
3593
|
+
description: A brief description of this prompt
|
|
3594
|
+
---
|
|
3595
|
+
|
|
3596
|
+
# ${itemName}
|
|
3597
|
+
|
|
3598
|
+
Prompt template content here.
|
|
3599
|
+
|
|
3600
|
+
## Context
|
|
3601
|
+
|
|
3602
|
+
Describe when this prompt should be used.
|
|
3603
|
+
|
|
3604
|
+
## Template
|
|
3605
|
+
|
|
3606
|
+
Your prompt template goes here.
|
|
3607
|
+
`;
|
|
3608
|
+
else content = `---
|
|
3609
|
+
name: ${itemName}
|
|
3610
|
+
description: A brief description of what this skill does
|
|
3611
|
+
---
|
|
3612
|
+
|
|
3613
|
+
# ${itemName}
|
|
3614
|
+
|
|
3615
|
+
Instructions for the agent to follow when this skill is activated.
|
|
3616
|
+
|
|
3617
|
+
## When to use
|
|
3618
|
+
|
|
3619
|
+
Describe when this skill should be used.
|
|
3620
|
+
|
|
3621
|
+
## Instructions
|
|
3622
|
+
|
|
3623
|
+
1. First step
|
|
3624
|
+
2. Second step
|
|
3625
|
+
3. Additional steps as needed
|
|
3626
|
+
`;
|
|
3627
|
+
writeFileSync(itemFile, content);
|
|
3628
|
+
logger.log(`Initialized ${cognitiveType}: ${import_picocolors.default.dim(itemName)}`);
|
|
3629
|
+
logger.line();
|
|
3630
|
+
logger.dim("Created:");
|
|
3631
|
+
logger.log(` ${displayPath}`);
|
|
3632
|
+
logger.line();
|
|
3633
|
+
logger.dim("Next steps:");
|
|
3634
|
+
logger.log(` 1. Edit ${import_picocolors.default.cyan(displayPath)} to define your ${cognitiveType} instructions`);
|
|
3635
|
+
logger.log(` 2. Update the ${import_picocolors.default.cyan("name")} and ${import_picocolors.default.cyan("description")} in the frontmatter`);
|
|
3636
|
+
logger.line();
|
|
3637
|
+
logger.dim("Publishing:");
|
|
3638
|
+
logger.log(` ${import_picocolors.default.dim("GitHub:")} Push to a repo, then ${import_picocolors.default.cyan(`npx synk add <owner>/<repo>`)}`);
|
|
3639
|
+
logger.log(` ${import_picocolors.default.dim("URL:")} Host the file, then ${import_picocolors.default.cyan(`npx synk add https://example.com/${displayPath}`)}`);
|
|
3640
|
+
logger.line();
|
|
3641
|
+
}
|
|
3642
|
+
const AGENTS_DIR$1 = ".agents";
|
|
3643
|
+
const LOCK_FILE$1 = ".skill-lock.json";
|
|
3644
|
+
const CURRENT_LOCK_VERSION$1 = 4;
|
|
3645
|
+
function getSkillLockPath$1() {
|
|
3646
|
+
return join(homedir(), AGENTS_DIR$1, LOCK_FILE$1);
|
|
3647
|
+
}
|
|
3648
|
+
function readSkillLock$1() {
|
|
3649
|
+
const lockPath = getSkillLockPath$1();
|
|
3650
|
+
try {
|
|
3651
|
+
const content = readFileSync(lockPath, "utf-8");
|
|
3652
|
+
const parsed = JSON.parse(content);
|
|
3653
|
+
if (typeof parsed.version !== "number" || !parsed.skills) return {
|
|
3654
|
+
version: CURRENT_LOCK_VERSION$1,
|
|
3655
|
+
skills: {}
|
|
3656
|
+
};
|
|
3657
|
+
if (parsed.version < CURRENT_LOCK_VERSION$1) return {
|
|
3658
|
+
version: CURRENT_LOCK_VERSION$1,
|
|
3659
|
+
skills: {}
|
|
3660
|
+
};
|
|
3661
|
+
return parsed;
|
|
3662
|
+
} catch {
|
|
3663
|
+
return {
|
|
3664
|
+
version: CURRENT_LOCK_VERSION$1,
|
|
3665
|
+
skills: {}
|
|
3666
|
+
};
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
async function runCheck(args = []) {
|
|
3670
|
+
logger.log(`Checking for skill updates...`);
|
|
3671
|
+
logger.line();
|
|
3672
|
+
const lock = readSkillLock$1();
|
|
3673
|
+
const skillNames = Object.keys(lock.skills);
|
|
3674
|
+
if (skillNames.length === 0) {
|
|
3675
|
+
logger.dim("No skills tracked in lock file.");
|
|
3676
|
+
logger.log(`${import_picocolors.default.dim("Install skills with")} npx synk add <package>`);
|
|
3677
|
+
return;
|
|
3678
|
+
}
|
|
3679
|
+
const token = getGitHubToken();
|
|
3680
|
+
const skillsBySource = /* @__PURE__ */ new Map();
|
|
3681
|
+
let skippedCount = 0;
|
|
3682
|
+
for (const skillName of skillNames) {
|
|
3683
|
+
const entry = lock.skills[skillName];
|
|
3684
|
+
if (!entry) continue;
|
|
3685
|
+
if (entry.sourceType !== "github" || !entry.skillFolderHash || !entry.skillPath) {
|
|
3686
|
+
skippedCount++;
|
|
3687
|
+
continue;
|
|
3688
|
+
}
|
|
3689
|
+
const existing = skillsBySource.get(entry.source) || [];
|
|
3690
|
+
existing.push({
|
|
3691
|
+
name: skillName,
|
|
3692
|
+
entry
|
|
3693
|
+
});
|
|
3694
|
+
skillsBySource.set(entry.source, existing);
|
|
3695
|
+
}
|
|
3696
|
+
const totalSkills = skillNames.length - skippedCount;
|
|
3697
|
+
if (totalSkills === 0) {
|
|
3698
|
+
logger.dim("No GitHub skills to check.");
|
|
3699
|
+
return;
|
|
3700
|
+
}
|
|
3701
|
+
logger.dim(`Checking ${totalSkills} skill(s) for updates...`);
|
|
3702
|
+
const updates = [];
|
|
3703
|
+
const errors = [];
|
|
3704
|
+
for (const [source, skills] of skillsBySource) for (const { name, entry } of skills) try {
|
|
3705
|
+
const latestHash = await fetchSkillFolderHash(source, entry.skillPath, token);
|
|
3706
|
+
if (!latestHash) {
|
|
3707
|
+
errors.push({
|
|
3708
|
+
name,
|
|
3709
|
+
source,
|
|
3710
|
+
error: "Could not fetch from GitHub"
|
|
3711
|
+
});
|
|
3712
|
+
continue;
|
|
3713
|
+
}
|
|
3714
|
+
if (latestHash !== entry.skillFolderHash) updates.push({
|
|
3715
|
+
name,
|
|
3716
|
+
source
|
|
3717
|
+
});
|
|
3718
|
+
} catch (err) {
|
|
3719
|
+
errors.push({
|
|
3720
|
+
name,
|
|
3721
|
+
source,
|
|
3722
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
3723
|
+
});
|
|
3724
|
+
}
|
|
3725
|
+
logger.line();
|
|
3726
|
+
if (updates.length === 0) logger.success("All skills are up to date");
|
|
3727
|
+
else {
|
|
3728
|
+
logger.log(`${updates.length} update(s) available:`);
|
|
3729
|
+
logger.line();
|
|
3730
|
+
for (const update of updates) {
|
|
3731
|
+
logger.log(` ${import_picocolors.default.cyan("↑")} ${update.name}`);
|
|
3732
|
+
logger.dim(` source: ${update.source}`);
|
|
3733
|
+
}
|
|
3734
|
+
logger.line();
|
|
3735
|
+
logger.log(`${import_picocolors.default.dim("Run")} npx synk update ${import_picocolors.default.dim("to update all skills")}`);
|
|
3736
|
+
}
|
|
3737
|
+
if (errors.length > 0) {
|
|
3738
|
+
logger.line();
|
|
3739
|
+
logger.dim(`Could not check ${errors.length} skill(s) (may need reinstall)`);
|
|
3740
|
+
}
|
|
3741
|
+
track({
|
|
3742
|
+
event: "check",
|
|
3743
|
+
skillCount: String(totalSkills),
|
|
3744
|
+
updatesAvailable: String(updates.length)
|
|
3745
|
+
});
|
|
3746
|
+
logger.line();
|
|
3747
|
+
}
|
|
3748
|
+
const AGENTS_DIR = ".agents";
|
|
3749
|
+
const LOCK_FILE = ".skill-lock.json";
|
|
3750
|
+
const CURRENT_LOCK_VERSION = 4;
|
|
3751
|
+
function getSkillLockPath() {
|
|
3752
|
+
return join(homedir(), AGENTS_DIR, LOCK_FILE);
|
|
3753
|
+
}
|
|
3754
|
+
function readSkillLock() {
|
|
3755
|
+
const lockPath = getSkillLockPath();
|
|
3756
|
+
try {
|
|
3757
|
+
const content = readFileSync(lockPath, "utf-8");
|
|
3758
|
+
const parsed = JSON.parse(content);
|
|
3759
|
+
if (typeof parsed.version !== "number" || !parsed.skills) return {
|
|
3760
|
+
version: CURRENT_LOCK_VERSION,
|
|
3761
|
+
skills: {}
|
|
3762
|
+
};
|
|
3763
|
+
if (parsed.version < CURRENT_LOCK_VERSION) return {
|
|
3764
|
+
version: CURRENT_LOCK_VERSION,
|
|
3765
|
+
skills: {}
|
|
3766
|
+
};
|
|
3767
|
+
return parsed;
|
|
3768
|
+
} catch {
|
|
3769
|
+
return {
|
|
3770
|
+
version: CURRENT_LOCK_VERSION,
|
|
3771
|
+
skills: {}
|
|
3772
|
+
};
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
async function runUpdate() {
|
|
3776
|
+
logger.log("Checking for skill updates...");
|
|
3777
|
+
logger.line();
|
|
3778
|
+
const lock = readSkillLock();
|
|
3779
|
+
const skillNames = Object.keys(lock.skills);
|
|
3780
|
+
if (skillNames.length === 0) {
|
|
3781
|
+
logger.dim("No skills tracked in lock file.");
|
|
3782
|
+
logger.log(`${import_picocolors.default.dim("Install skills with")} npx synk add <package>`);
|
|
3783
|
+
return;
|
|
3784
|
+
}
|
|
3785
|
+
const token = getGitHubToken();
|
|
3786
|
+
const updates = [];
|
|
3787
|
+
let checkedCount = 0;
|
|
3788
|
+
for (const skillName of skillNames) {
|
|
3789
|
+
const entry = lock.skills[skillName];
|
|
3790
|
+
if (!entry) continue;
|
|
3791
|
+
if (entry.sourceType !== "github" || !entry.skillFolderHash || !entry.skillPath) continue;
|
|
3792
|
+
checkedCount++;
|
|
3793
|
+
try {
|
|
3794
|
+
const latestHash = await fetchSkillFolderHash(entry.source, entry.skillPath, token);
|
|
3795
|
+
if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
|
|
3796
|
+
name: skillName,
|
|
3797
|
+
source: entry.source,
|
|
3798
|
+
entry
|
|
3799
|
+
});
|
|
3800
|
+
} catch {}
|
|
3801
|
+
}
|
|
3802
|
+
if (checkedCount === 0) {
|
|
3803
|
+
logger.dim("No skills to check.");
|
|
3804
|
+
return;
|
|
3805
|
+
}
|
|
3806
|
+
if (updates.length === 0) {
|
|
3807
|
+
logger.success("All skills are up to date");
|
|
3808
|
+
logger.line();
|
|
3809
|
+
return;
|
|
3810
|
+
}
|
|
3811
|
+
logger.log(`Found ${updates.length} update(s)`);
|
|
3812
|
+
logger.line();
|
|
3813
|
+
let successCount = 0;
|
|
3814
|
+
let failCount = 0;
|
|
3815
|
+
for (const update of updates) {
|
|
3816
|
+
logger.log(`Updating ${update.name}...`);
|
|
3817
|
+
let installUrl = update.entry.sourceUrl;
|
|
3818
|
+
if (update.entry.skillPath) {
|
|
3819
|
+
let skillFolder = update.entry.skillPath;
|
|
3820
|
+
if (skillFolder.endsWith("/SKILL.md")) skillFolder = skillFolder.slice(0, -9);
|
|
3821
|
+
else if (skillFolder.endsWith("SKILL.md")) skillFolder = skillFolder.slice(0, -8);
|
|
3822
|
+
if (skillFolder.endsWith("/")) skillFolder = skillFolder.slice(0, -1);
|
|
3823
|
+
installUrl = update.entry.sourceUrl.replace(/\.git$/, "").replace(/\/$/, "");
|
|
3824
|
+
installUrl = `${installUrl}/tree/main/${skillFolder}`;
|
|
3825
|
+
}
|
|
3826
|
+
if (spawnSync("npx", [
|
|
3827
|
+
"-y",
|
|
3828
|
+
"skills",
|
|
3829
|
+
"add",
|
|
3830
|
+
installUrl,
|
|
3831
|
+
"-g",
|
|
3832
|
+
"-y"
|
|
3833
|
+
], { stdio: [
|
|
3834
|
+
"inherit",
|
|
3835
|
+
"pipe",
|
|
3836
|
+
"pipe"
|
|
3837
|
+
] }).status === 0) {
|
|
3838
|
+
successCount++;
|
|
3839
|
+
logger.success(`Updated ${update.name}`);
|
|
3840
|
+
} else {
|
|
3841
|
+
failCount++;
|
|
3842
|
+
logger.dim(`\u2717 Failed to update ${update.name}`);
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
logger.line();
|
|
3846
|
+
if (successCount > 0) logger.success(`Updated ${successCount} skill(s)`);
|
|
3847
|
+
if (failCount > 0) logger.dim(`Failed to update ${failCount} skill(s)`);
|
|
3848
|
+
track({
|
|
3849
|
+
event: "update",
|
|
3850
|
+
skillCount: String(updates.length),
|
|
3851
|
+
successCount: String(successCount),
|
|
3852
|
+
failCount: String(failCount)
|
|
3853
|
+
});
|
|
3854
|
+
logger.line();
|
|
3855
|
+
}
|
|
3856
|
+
const LOGO_LINES = [
|
|
3857
|
+
"███████╗██╗ ██╗███╗ ██╗██╗ ██╗",
|
|
3858
|
+
"██╔════╝╚██╗ ██╔╝████╗ ██║██║ ██╔╝",
|
|
3859
|
+
"███████╗ ╚████╔╝ ██╔██╗ ██║█████╔╝ ",
|
|
3860
|
+
"╚════██║ ╚██╔╝ ██║╚██╗██║██╔═██╗ ",
|
|
3861
|
+
"███████║ ██║ ██║ ╚████║██║ ██╗",
|
|
3862
|
+
"╚══════╝ ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝"
|
|
3863
|
+
];
|
|
3864
|
+
const GRAYS = [
|
|
3865
|
+
"\x1B[38;5;250m",
|
|
3866
|
+
"\x1B[38;5;248m",
|
|
3867
|
+
"\x1B[38;5;245m",
|
|
3868
|
+
"\x1B[38;5;243m",
|
|
3869
|
+
"\x1B[38;5;240m",
|
|
3870
|
+
"\x1B[38;5;238m"
|
|
3871
|
+
];
|
|
3872
|
+
function showLogo() {
|
|
3873
|
+
logger.line();
|
|
3874
|
+
LOGO_LINES.forEach((line, i) => {
|
|
3875
|
+
logger.gradient(line, GRAYS[i]);
|
|
3876
|
+
});
|
|
3877
|
+
}
|
|
3878
|
+
function showBanner() {
|
|
3879
|
+
showLogo();
|
|
3880
|
+
logger.line();
|
|
3881
|
+
logger.dim("The open cognitive ecosystem for AI agents");
|
|
3882
|
+
logger.line();
|
|
3883
|
+
logger.command("npx synk add <package>", "Install skills, agents, or prompts");
|
|
3884
|
+
logger.command("npx synk list", "List installed cognitives");
|
|
3885
|
+
logger.command("npx synk find [query]", "Search for skills");
|
|
3886
|
+
logger.command("npx synk check", "Check for updates");
|
|
3887
|
+
logger.command("npx synk update", "Update all cognitives");
|
|
3888
|
+
logger.command("npx synk remove", "Remove installed cognitives");
|
|
3889
|
+
logger.command("npx synk init [name]", "Create a new skill, agent, or prompt");
|
|
3890
|
+
logger.line();
|
|
3891
|
+
logger.log(`${import_picocolors.default.dim("try:")} npx synk add vercel-labs/agent-skills`);
|
|
3892
|
+
logger.line();
|
|
3893
|
+
}
|
|
3894
|
+
function showHelp() {
|
|
3895
|
+
logger.log(`
|
|
3896
|
+
${import_picocolors.default.bold("Usage:")} synk <command> [options]
|
|
3897
|
+
|
|
3898
|
+
${import_picocolors.default.bold("Commands:")}
|
|
3899
|
+
add <package> Add skills, agents, or prompts from a package
|
|
3900
|
+
e.g. vercel-labs/agent-skills
|
|
3901
|
+
https://github.com/vercel-labs/agent-skills
|
|
3902
|
+
remove [names] Remove installed cognitives
|
|
3903
|
+
list, ls List installed cognitives
|
|
3904
|
+
find [query] Search for skills interactively
|
|
3905
|
+
init [name] Initialize a cognitive (creates SKILL.md, AGENT.md, or PROMPT.md)
|
|
3906
|
+
check Check for available updates
|
|
3907
|
+
update Update all cognitives to latest versions
|
|
3908
|
+
|
|
3909
|
+
${import_picocolors.default.bold("Add Options:")}
|
|
3910
|
+
-g, --global Install globally (user-level) instead of project-level
|
|
3911
|
+
-a, --agent <agents> Specify agents to install to (use '*' for all agents)
|
|
3912
|
+
-s, --skill <skills> Specify skill names to install (use '*' for all skills)
|
|
3913
|
+
-t, --type <type> Filter by cognitive type: skill, agent, or prompt
|
|
3914
|
+
-l, --list List available cognitives in the repository without installing
|
|
3915
|
+
-y, --yes Skip confirmation prompts
|
|
3916
|
+
--all Shorthand for --skill '*' --agent '*' -y
|
|
3917
|
+
--full-depth Search all subdirectories even when a root SKILL.md exists
|
|
3918
|
+
|
|
3919
|
+
${import_picocolors.default.bold("Remove Options:")}
|
|
3920
|
+
-g, --global Remove from global scope
|
|
3921
|
+
-a, --agent <agents> Remove from specific agents (use '*' for all agents)
|
|
3922
|
+
-s, --skill <skills> Specify skills to remove (use '*' for all skills)
|
|
3923
|
+
-t, --type <type> Filter by cognitive type: skill, agent, or prompt
|
|
3924
|
+
-y, --yes Skip confirmation prompts
|
|
3925
|
+
--all Shorthand for --skill '*' --agent '*' -y
|
|
3926
|
+
|
|
3927
|
+
${import_picocolors.default.bold("List Options:")}
|
|
3928
|
+
-g, --global List global cognitives (default: project)
|
|
3929
|
+
-a, --agent <agents> Filter by specific agents
|
|
3930
|
+
-t, --type <type> Filter by cognitive type: skill, agent, or prompt
|
|
3931
|
+
|
|
3932
|
+
${import_picocolors.default.bold("Init Options:")}
|
|
3933
|
+
-t, --type <type> Type to create: skill (default), agent, or prompt
|
|
3934
|
+
|
|
3935
|
+
${import_picocolors.default.bold("Options:")}
|
|
3936
|
+
--help, -h Show this help message
|
|
3937
|
+
--version, -v Show version number
|
|
3938
|
+
|
|
3939
|
+
${import_picocolors.default.bold("Examples:")}
|
|
3940
|
+
${import_picocolors.default.dim("$")} synk add vercel-labs/agent-skills
|
|
3941
|
+
${import_picocolors.default.dim("$")} synk add vercel-labs/agent-skills -g
|
|
3942
|
+
${import_picocolors.default.dim("$")} synk add vercel-labs/agent-skills --type agent
|
|
3943
|
+
${import_picocolors.default.dim("$")} synk add vercel-labs/agent-skills --agent claude-code cursor
|
|
3944
|
+
${import_picocolors.default.dim("$")} synk add vercel-labs/agent-skills --skill pr-review commit
|
|
3945
|
+
${import_picocolors.default.dim("$")} synk remove ${import_picocolors.default.dim("# interactive remove")}
|
|
3946
|
+
${import_picocolors.default.dim("$")} synk remove web-design ${import_picocolors.default.dim("# remove by name")}
|
|
3947
|
+
${import_picocolors.default.dim("$")} synk rm --global frontend-design
|
|
3948
|
+
${import_picocolors.default.dim("$")} synk list ${import_picocolors.default.dim("# list all installed cognitives")}
|
|
3949
|
+
${import_picocolors.default.dim("$")} synk ls -g ${import_picocolors.default.dim("# list global cognitives only")}
|
|
3950
|
+
${import_picocolors.default.dim("$")} synk ls --type agent ${import_picocolors.default.dim("# list agents only")}
|
|
3951
|
+
${import_picocolors.default.dim("$")} synk find ${import_picocolors.default.dim("# interactive search")}
|
|
3952
|
+
${import_picocolors.default.dim("$")} synk find typescript ${import_picocolors.default.dim("# search by keyword")}
|
|
3953
|
+
${import_picocolors.default.dim("$")} synk init my-skill
|
|
3954
|
+
${import_picocolors.default.dim("$")} synk init --type agent my-agent
|
|
3955
|
+
${import_picocolors.default.dim("$")} synk init --type prompt my-prompt
|
|
3956
|
+
${import_picocolors.default.dim("$")} synk check
|
|
3957
|
+
${import_picocolors.default.dim("$")} synk update
|
|
3958
|
+
`);
|
|
3959
|
+
}
|
|
3960
|
+
function showRemoveHelp() {
|
|
3961
|
+
logger.log(`
|
|
3962
|
+
${import_picocolors.default.bold("Usage:")} synk remove [names...] [options]
|
|
3963
|
+
|
|
3964
|
+
${import_picocolors.default.bold("Description:")}
|
|
3965
|
+
Remove installed cognitives from agents. If no names are provided,
|
|
3966
|
+
an interactive selection menu will be shown.
|
|
3967
|
+
|
|
3968
|
+
${import_picocolors.default.bold("Arguments:")}
|
|
3969
|
+
names Optional names to remove (space-separated)
|
|
3970
|
+
|
|
3971
|
+
${import_picocolors.default.bold("Options:")}
|
|
3972
|
+
-g, --global Remove from global scope (~/) instead of project scope
|
|
3973
|
+
-a, --agent Remove from specific agents (use '*' for all agents)
|
|
3974
|
+
-s, --skill Specify skills to remove (use '*' for all skills)
|
|
3975
|
+
-t, --type Filter by cognitive type: skill, agent, or prompt
|
|
3976
|
+
-y, --yes Skip confirmation prompts
|
|
3977
|
+
--all Shorthand for --skill '*' --agent '*' -y
|
|
3978
|
+
|
|
3979
|
+
${import_picocolors.default.bold("Examples:")}
|
|
3980
|
+
${import_picocolors.default.dim("$")} synk remove ${import_picocolors.default.dim("# interactive selection")}
|
|
3981
|
+
${import_picocolors.default.dim("$")} synk remove my-skill ${import_picocolors.default.dim("# remove specific skill")}
|
|
3982
|
+
${import_picocolors.default.dim("$")} synk remove skill1 skill2 -y ${import_picocolors.default.dim("# remove multiple")}
|
|
3983
|
+
${import_picocolors.default.dim("$")} synk remove --global my-agent ${import_picocolors.default.dim("# remove from global scope")}
|
|
3984
|
+
${import_picocolors.default.dim("$")} synk rm --agent claude-code my-skill ${import_picocolors.default.dim("# remove from specific agent")}
|
|
3985
|
+
${import_picocolors.default.dim("$")} synk remove --all ${import_picocolors.default.dim("# remove all")}
|
|
3986
|
+
${import_picocolors.default.dim("$")} synk remove --type agent ${import_picocolors.default.dim("# remove only agents")}
|
|
3987
|
+
`);
|
|
3988
|
+
}
|
|
3989
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
3990
|
+
function getVersion() {
|
|
3991
|
+
try {
|
|
3992
|
+
const pkgPath = join(__dirname, "..", "package.json");
|
|
3993
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8")).version;
|
|
3994
|
+
} catch {
|
|
3995
|
+
return "0.0.0";
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
const VERSION = getVersion();
|
|
3999
|
+
initTelemetry(VERSION);
|
|
4000
|
+
async function main() {
|
|
4001
|
+
const args = process.argv.slice(2);
|
|
4002
|
+
if (args.length === 0) {
|
|
4003
|
+
showBanner();
|
|
4004
|
+
return;
|
|
4005
|
+
}
|
|
4006
|
+
const command = args[0];
|
|
4007
|
+
const restArgs = args.slice(1);
|
|
4008
|
+
switch (command) {
|
|
4009
|
+
case "find":
|
|
4010
|
+
case "search":
|
|
4011
|
+
case "f":
|
|
4012
|
+
case "s":
|
|
4013
|
+
showLogo();
|
|
4014
|
+
logger.line();
|
|
4015
|
+
await runFind(restArgs);
|
|
4016
|
+
break;
|
|
4017
|
+
case "init":
|
|
4018
|
+
showLogo();
|
|
4019
|
+
logger.line();
|
|
4020
|
+
runInit(restArgs);
|
|
4021
|
+
break;
|
|
4022
|
+
case "i":
|
|
4023
|
+
case "install":
|
|
4024
|
+
case "a":
|
|
4025
|
+
case "add": {
|
|
4026
|
+
showLogo();
|
|
4027
|
+
const { source, options } = parseAddOptions(restArgs);
|
|
4028
|
+
await runAdd(source, options);
|
|
4029
|
+
break;
|
|
4030
|
+
}
|
|
4031
|
+
case "remove":
|
|
4032
|
+
case "rm":
|
|
4033
|
+
case "r":
|
|
4034
|
+
if (restArgs.includes("--help") || restArgs.includes("-h")) {
|
|
4035
|
+
showRemoveHelp();
|
|
4036
|
+
break;
|
|
4037
|
+
}
|
|
4038
|
+
const { skills, options: removeOptions } = parseRemoveOptions(restArgs);
|
|
4039
|
+
await removeCommand(skills, removeOptions);
|
|
4040
|
+
break;
|
|
4041
|
+
case "list":
|
|
4042
|
+
case "ls":
|
|
4043
|
+
await runList(restArgs);
|
|
4044
|
+
break;
|
|
4045
|
+
case "check":
|
|
4046
|
+
runCheck(restArgs);
|
|
4047
|
+
break;
|
|
4048
|
+
case "update":
|
|
4049
|
+
case "upgrade":
|
|
4050
|
+
runUpdate();
|
|
4051
|
+
break;
|
|
4052
|
+
case "--help":
|
|
4053
|
+
case "-h":
|
|
4054
|
+
showHelp();
|
|
4055
|
+
break;
|
|
4056
|
+
case "--version":
|
|
4057
|
+
case "-v":
|
|
4058
|
+
logger.log(VERSION);
|
|
4059
|
+
break;
|
|
4060
|
+
default:
|
|
4061
|
+
logger.error(`Unknown command: ${command}`);
|
|
4062
|
+
logger.log(`Run ${import_picocolors.default.bold("synk --help")} for usage.`);
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
main();
|
|
4066
|
+
export {};
|