@mhmdreza-rafiei/agentry 0.1.0 → 0.2.1
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 +161 -58
- package/dist/_chunks/theme.mjs +2 -0
- package/dist/_chunks/theme2.mjs +149 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +2523 -0
- package/package.json +30 -11
- package/bin/agentry.js +0 -257
- package/examples/agents/frontend/frontend-developer/AGENT.md +0 -17
- package/examples/agents/frontend/frontend-developer/agentry.json +0 -5
- package/examples/rules/workflow/ask-dont-guess/RULE.md +0 -15
- package/examples/rules/workflow/ask-dont-guess/agentry.json +0 -5
- package/examples/scripts/env/doctor/agentry.json +0 -5
- package/examples/scripts/env/doctor/run.sh +0 -4
- package/examples/skills/prompt/enhance-prompt/SKILL.md +0 -186
- package/examples/skills/prompt/enhance-prompt/references/TEMPLATE.md +0 -182
- package/profile/backend.json +0 -5
- package/profile/frontend.json +0 -5
- package/src/commands.js +0 -64
- package/src/profile.js +0 -21
- package/src/registry.js +0 -145
- package/src/source.js +0 -52
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,2523 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { animateLogo, badge, delay, printHelp, sky, theme } from "./_chunks/theme2.mjs";
|
|
3
|
+
import { execFileSync, execSync } from "node:child_process";
|
|
4
|
+
import https from "node:https";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { homedir, tmpdir } from "node:os";
|
|
8
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
9
|
+
import { xdgCache, xdgConfig } from "xdg-basedir";
|
|
10
|
+
import { parse, stringify } from "yaml";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import * as clack from "@clack/prompts";
|
|
13
|
+
import * as readline from "node:readline";
|
|
14
|
+
import c from "picocolors";
|
|
15
|
+
import { determineAgent } from "@vercel/detect-agent";
|
|
16
|
+
import { stripVTControlCharacters } from "node:util";
|
|
17
|
+
import { Writable } from "node:stream";
|
|
18
|
+
var name = "@mhmdreza-rafiei/agentry";
|
|
19
|
+
var version = "0.2.0";
|
|
20
|
+
const ARTIFACT_KINDS = [
|
|
21
|
+
"skill",
|
|
22
|
+
"rule",
|
|
23
|
+
"agent",
|
|
24
|
+
"profile",
|
|
25
|
+
"script"
|
|
26
|
+
];
|
|
27
|
+
function isLocalPath(source) {
|
|
28
|
+
if (!source) return false;
|
|
29
|
+
if (source === "." || source === "..") return true;
|
|
30
|
+
if (source.startsWith("./") || source.startsWith("../") || source.startsWith("/") || source.startsWith("~")) return true;
|
|
31
|
+
try {
|
|
32
|
+
return existsSync(source) && statSync(source).isDirectory();
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function parseGithubTreeUrl(url) {
|
|
38
|
+
const m = url.match(/^https?:\/\/github\.com\/([\w.-]+\/[\w.-]+?)\/tree\/([^/]+)\/(.+)$/);
|
|
39
|
+
if (!m) return null;
|
|
40
|
+
return {
|
|
41
|
+
ownerRepo: m[1],
|
|
42
|
+
ref: m[2],
|
|
43
|
+
subpath: m[3].replace(/\/+$/, "")
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function parseSource(source) {
|
|
47
|
+
if (!source || typeof source !== "string") throw new Error("Empty source.");
|
|
48
|
+
const raw = source;
|
|
49
|
+
if (isLocalPath(source)) return {
|
|
50
|
+
kind: "local",
|
|
51
|
+
raw,
|
|
52
|
+
url: null,
|
|
53
|
+
subpath: "",
|
|
54
|
+
local: resolve(source.replace(/^~/, homedir()))
|
|
55
|
+
};
|
|
56
|
+
const tree = parseGithubTreeUrl(source);
|
|
57
|
+
if (tree) return {
|
|
58
|
+
kind: "github-url",
|
|
59
|
+
raw,
|
|
60
|
+
url: `https://github.com/${tree.ownerRepo}.git`,
|
|
61
|
+
subpath: tree.subpath,
|
|
62
|
+
ref: tree.ref,
|
|
63
|
+
local: null
|
|
64
|
+
};
|
|
65
|
+
let m = source.match(/^https?:\/\/github\.com\/([\w.-]+\/[\w.-]+?)(?:\.git)?\/?$/);
|
|
66
|
+
if (m) return {
|
|
67
|
+
kind: "github-url",
|
|
68
|
+
raw,
|
|
69
|
+
url: `https://github.com/${m[1]}.git`,
|
|
70
|
+
subpath: "",
|
|
71
|
+
local: null
|
|
72
|
+
};
|
|
73
|
+
m = source.match(/^https?:\/\/gitlab\.com\/([\w./-]+?)(?:\.git)?\/?$/);
|
|
74
|
+
if (m) return {
|
|
75
|
+
kind: "gitlab-url",
|
|
76
|
+
raw,
|
|
77
|
+
url: `https://gitlab.com/${m[1]}.git`,
|
|
78
|
+
subpath: "",
|
|
79
|
+
local: null
|
|
80
|
+
};
|
|
81
|
+
if (source.startsWith("git@") || /^https?:\/\/.*\.git$/.test(source)) return {
|
|
82
|
+
kind: "git-url",
|
|
83
|
+
raw,
|
|
84
|
+
url: source,
|
|
85
|
+
subpath: "",
|
|
86
|
+
local: null
|
|
87
|
+
};
|
|
88
|
+
if (/^[\w.-]+\/[\w.-]+$/.test(source)) return {
|
|
89
|
+
kind: "github-shorthand",
|
|
90
|
+
raw,
|
|
91
|
+
url: `https://github.com/${source}.git`,
|
|
92
|
+
subpath: "",
|
|
93
|
+
local: null
|
|
94
|
+
};
|
|
95
|
+
throw new Error(`Cannot parse source "${source}". Use author/repo, a GitHub/GitLab URL, a git URL, or a local path.`);
|
|
96
|
+
}
|
|
97
|
+
function sourceIdentity(source) {
|
|
98
|
+
try {
|
|
99
|
+
const p = parseSource(source);
|
|
100
|
+
if (p.kind === "local" && p.local) return `local:${resolve(p.local).replace(/\\/g, "/").toLowerCase()}`;
|
|
101
|
+
return `git:${(p.url || "").replace(/\.git$/i, "").replace(/\/+$/, "").toLowerCase()}${p.subpath ? `/${p.subpath.replace(/^\/+|\/+$/g, "")}` : ""}${p.ref ? `@${p.ref}` : ""}`;
|
|
102
|
+
} catch {
|
|
103
|
+
return `raw:${source.trim().toLowerCase()}`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function sourcesEqual(a, b) {
|
|
107
|
+
if (!a || !b) return false;
|
|
108
|
+
if (a === b) return true;
|
|
109
|
+
return sourceIdentity(a) === sourceIdentity(b);
|
|
110
|
+
}
|
|
111
|
+
function isExplicitSource(arg) {
|
|
112
|
+
if (!arg) return false;
|
|
113
|
+
if (isLocalPath(arg)) return true;
|
|
114
|
+
if (/^https?:\/\//i.test(arg)) return true;
|
|
115
|
+
if (arg.startsWith("git@")) return true;
|
|
116
|
+
if (/^https?:\/\/.*\.git$/i.test(arg)) return true;
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
function isOwnerRepoShape(arg) {
|
|
120
|
+
return /^[\w.-]+\/[\w.-]+$/.test(arg);
|
|
121
|
+
}
|
|
122
|
+
function cacheRoot() {
|
|
123
|
+
return join(xdgCache ?? join(homedir(), ".cache"), "agentry", "repos");
|
|
124
|
+
}
|
|
125
|
+
function cacheKey(parsed) {
|
|
126
|
+
const id = sourceIdentity(parsed.raw);
|
|
127
|
+
return createHash("sha256").update(id).digest("hex").slice(0, 20);
|
|
128
|
+
}
|
|
129
|
+
function cacheDirFor(parsed) {
|
|
130
|
+
return join(cacheRoot(), cacheKey(parsed));
|
|
131
|
+
}
|
|
132
|
+
function saveToCache(parsed, clonedRoot) {
|
|
133
|
+
const dest = cacheDirFor(parsed);
|
|
134
|
+
try {
|
|
135
|
+
rmSync(dest, {
|
|
136
|
+
recursive: true,
|
|
137
|
+
force: true
|
|
138
|
+
});
|
|
139
|
+
mkdirSync(join(dest, ".."), { recursive: true });
|
|
140
|
+
cpSync(clonedRoot, dest, { recursive: true });
|
|
141
|
+
} catch {}
|
|
142
|
+
}
|
|
143
|
+
function cloneTo(url, dest, ref) {
|
|
144
|
+
execFileSync("git", [
|
|
145
|
+
"clone",
|
|
146
|
+
"--quiet",
|
|
147
|
+
...ref ? [
|
|
148
|
+
"--branch",
|
|
149
|
+
ref,
|
|
150
|
+
"--depth",
|
|
151
|
+
"1"
|
|
152
|
+
] : ["--depth", "1"],
|
|
153
|
+
url,
|
|
154
|
+
dest
|
|
155
|
+
], { stdio: [
|
|
156
|
+
"ignore",
|
|
157
|
+
"ignore",
|
|
158
|
+
"pipe"
|
|
159
|
+
] });
|
|
160
|
+
}
|
|
161
|
+
function withSubpath(root, subpath, cleanupRoot) {
|
|
162
|
+
if (!subpath) return {
|
|
163
|
+
root,
|
|
164
|
+
cleanup: () => rmSync(cleanupRoot, {
|
|
165
|
+
recursive: true,
|
|
166
|
+
force: true
|
|
167
|
+
})
|
|
168
|
+
};
|
|
169
|
+
const sub = join(root, subpath);
|
|
170
|
+
if (!existsSync(sub)) {
|
|
171
|
+
rmSync(cleanupRoot, {
|
|
172
|
+
recursive: true,
|
|
173
|
+
force: true
|
|
174
|
+
});
|
|
175
|
+
throw new Error(`Subpath "${subpath}" not found.`);
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
root: sub,
|
|
179
|
+
cleanup: () => rmSync(cleanupRoot, {
|
|
180
|
+
recursive: true,
|
|
181
|
+
force: true
|
|
182
|
+
})
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
async function resolveSource(source, opts = {}) {
|
|
186
|
+
const parsed = typeof source === "string" ? parseSource(source) : source;
|
|
187
|
+
if (parsed.kind === "local" && parsed.local) {
|
|
188
|
+
if (!existsSync(parsed.local)) throw new Error(`Path not found: ${parsed.raw}`);
|
|
189
|
+
return {
|
|
190
|
+
root: parsed.local,
|
|
191
|
+
cleanup: () => {}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const url = parsed.url;
|
|
195
|
+
if (!url) throw new Error(`Cannot resolve source "${parsed.raw}".`);
|
|
196
|
+
const tmp = mkdtempSync(join(tmpdir(), "agentry-src-"));
|
|
197
|
+
try {
|
|
198
|
+
cloneTo(url, tmp, parsed.ref);
|
|
199
|
+
} catch (e) {
|
|
200
|
+
rmSync(tmp, {
|
|
201
|
+
recursive: true,
|
|
202
|
+
force: true
|
|
203
|
+
});
|
|
204
|
+
const detail = e.stderr ? e.stderr.toString().trim() : e.message;
|
|
205
|
+
const cached = cacheDirFor(parsed);
|
|
206
|
+
const cacheOk = existsSync(cached) && existsSync(join(cached, ".git"));
|
|
207
|
+
if (cacheOk && opts.confirmCache) {
|
|
208
|
+
if (await opts.confirmCache({
|
|
209
|
+
url,
|
|
210
|
+
cacheDir: cached,
|
|
211
|
+
error: detail
|
|
212
|
+
})) {
|
|
213
|
+
const work = mkdtempSync(join(tmpdir(), "agentry-src-"));
|
|
214
|
+
cpSync(cached, work, { recursive: true });
|
|
215
|
+
return {
|
|
216
|
+
...withSubpath(work, parsed.subpath, work),
|
|
217
|
+
fromCache: true
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
throw new Error(`git clone failed for ${url}\n${detail}${cacheOk ? "\n(A local cache exists — re-run interactively to load it, or pass -y to auto-use cache.)" : ""}`);
|
|
222
|
+
}
|
|
223
|
+
saveToCache(parsed, tmp);
|
|
224
|
+
return withSubpath(tmp, parsed.subpath, tmp);
|
|
225
|
+
}
|
|
226
|
+
const SKILL_MARKER = "SKILL.md";
|
|
227
|
+
const FILE_EXT = ".mdc";
|
|
228
|
+
const PROFILE_EXTS = [".yaml", ".yml"];
|
|
229
|
+
const KIND_DIRS = {
|
|
230
|
+
skill: "skills",
|
|
231
|
+
rule: "rules",
|
|
232
|
+
agent: "agents",
|
|
233
|
+
profile: "profiles",
|
|
234
|
+
script: "scripts"
|
|
235
|
+
};
|
|
236
|
+
const ROOT_SKIP = /* @__PURE__ */ new Set([
|
|
237
|
+
".",
|
|
238
|
+
"node_modules",
|
|
239
|
+
"skills",
|
|
240
|
+
"rules",
|
|
241
|
+
"agents",
|
|
242
|
+
"profiles",
|
|
243
|
+
"scripts"
|
|
244
|
+
]);
|
|
245
|
+
function entries(dir) {
|
|
246
|
+
try {
|
|
247
|
+
return readdirSync(dir, { withFileTypes: true });
|
|
248
|
+
} catch {
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function firstHeading(md) {
|
|
253
|
+
const m = md.match(/^#\s+(.+)$/m);
|
|
254
|
+
return m ? m[1].trim() : "";
|
|
255
|
+
}
|
|
256
|
+
function frontmatterDescription(md) {
|
|
257
|
+
const fm = md.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
258
|
+
if (!fm) return "";
|
|
259
|
+
const lines = fm[1].split(/\r?\n/);
|
|
260
|
+
for (let i = 0; i < lines.length; i++) {
|
|
261
|
+
const dm = lines[i].match(/^description:\s*(.*)$/);
|
|
262
|
+
if (!dm) continue;
|
|
263
|
+
let val = dm[1].trim();
|
|
264
|
+
if (val === "" || val === ">" || val === "|" || val === ">-" || val === "|-") {
|
|
265
|
+
const collected = [];
|
|
266
|
+
for (let j = i + 1; j < lines.length; j++) if (/^\s+\S/.test(lines[j])) collected.push(lines[j].trim());
|
|
267
|
+
else break;
|
|
268
|
+
val = collected.join(" ");
|
|
269
|
+
}
|
|
270
|
+
return val.trim();
|
|
271
|
+
}
|
|
272
|
+
return "";
|
|
273
|
+
}
|
|
274
|
+
function readDescription(filePath) {
|
|
275
|
+
try {
|
|
276
|
+
const md = readFileSync(filePath, "utf8");
|
|
277
|
+
return frontmatterDescription(md) || firstHeading(md);
|
|
278
|
+
} catch {
|
|
279
|
+
return "";
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function folderDescription(dir) {
|
|
283
|
+
for (const f of [SKILL_MARKER, "README.md"]) {
|
|
284
|
+
const p = join(dir, f);
|
|
285
|
+
if (existsSync(p)) {
|
|
286
|
+
const d = readDescription(p);
|
|
287
|
+
if (d) return d;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return "";
|
|
291
|
+
}
|
|
292
|
+
function makeArtifact(kind, category, name, srcPath, isFile) {
|
|
293
|
+
return {
|
|
294
|
+
kind,
|
|
295
|
+
category,
|
|
296
|
+
name,
|
|
297
|
+
id: category ? `${category}/${name}` : name,
|
|
298
|
+
dir: srcPath,
|
|
299
|
+
isFile,
|
|
300
|
+
description: isFile ? readDescription(srcPath) : folderDescription(srcPath)
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function collectSkills(base, out) {
|
|
304
|
+
for (const child of entries(base)) {
|
|
305
|
+
if (!child.isDirectory()) continue;
|
|
306
|
+
const childDir = join(base, child.name);
|
|
307
|
+
if (existsSync(join(childDir, SKILL_MARKER))) out.push(makeArtifact("skill", null, child.name, childDir, false));
|
|
308
|
+
else for (const item of entries(childDir)) {
|
|
309
|
+
if (!item.isDirectory()) continue;
|
|
310
|
+
const itemDir = join(childDir, item.name);
|
|
311
|
+
if (existsSync(join(itemDir, SKILL_MARKER))) out.push(makeArtifact("skill", child.name, item.name, itemDir, false));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function collectMdc(kind, base, out) {
|
|
316
|
+
for (const child of entries(base)) if (child.isFile() && child.name.endsWith(FILE_EXT)) out.push(makeArtifact(kind, null, child.name.slice(0, -4), join(base, child.name), true));
|
|
317
|
+
else if (child.isDirectory()) {
|
|
318
|
+
const subDir = join(base, child.name);
|
|
319
|
+
for (const item of entries(subDir)) if (item.isFile() && item.name.endsWith(FILE_EXT)) out.push(makeArtifact(kind, child.name, item.name.slice(0, -4), join(subDir, item.name), true));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function collectProfiles(base, out) {
|
|
323
|
+
for (const child of entries(base)) if (child.isFile() && PROFILE_EXTS.some((e) => child.name.endsWith(e))) {
|
|
324
|
+
const stem = child.name.replace(/\.(yaml|yml)$/, "");
|
|
325
|
+
out.push(makeArtifact("profile", null, stem, join(base, child.name), true));
|
|
326
|
+
} else if (child.isDirectory()) {
|
|
327
|
+
const subDir = join(base, child.name);
|
|
328
|
+
for (const item of entries(subDir)) if (item.isFile() && PROFILE_EXTS.some((e) => item.name.endsWith(e))) {
|
|
329
|
+
const stem = item.name.replace(/\.(yaml|yml)$/, "");
|
|
330
|
+
out.push(makeArtifact("profile", child.name, stem, join(subDir, item.name), true));
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function collectScripts(base, out) {
|
|
335
|
+
for (const child of entries(base)) {
|
|
336
|
+
if (!child.isDirectory()) continue;
|
|
337
|
+
const childDir = join(base, child.name);
|
|
338
|
+
if (entries(childDir).some((e) => e.isFile())) out.push(makeArtifact("script", null, child.name, childDir, false));
|
|
339
|
+
else for (const item of entries(childDir)) if (item.isDirectory() && entries(join(childDir, item.name)).some((e) => e.isFile())) out.push(makeArtifact("script", child.name, item.name, join(childDir, item.name), false));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function collectRootSkills(root, out) {
|
|
343
|
+
for (const child of entries(root)) {
|
|
344
|
+
if (!child.isDirectory() || child.name.startsWith(".") || ROOT_SKIP.has(child.name)) continue;
|
|
345
|
+
const childDir = join(root, child.name);
|
|
346
|
+
if (existsSync(join(childDir, SKILL_MARKER))) out.push(makeArtifact("skill", null, child.name, childDir, false));
|
|
347
|
+
else for (const item of entries(childDir)) if (item.isDirectory() && existsSync(join(childDir, item.name, SKILL_MARKER))) out.push(makeArtifact("skill", child.name, item.name, join(childDir, item.name), false));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function collectRootMdc(kind, root, out) {
|
|
351
|
+
for (const child of entries(root)) if (child.isFile() && child.name.endsWith(FILE_EXT) && !child.name.startsWith(".")) out.push(makeArtifact(kind, null, child.name.slice(0, -4), join(root, child.name), true));
|
|
352
|
+
else if (child.isDirectory() && !child.name.startsWith(".") && !ROOT_SKIP.has(child.name)) {
|
|
353
|
+
const subDir = join(root, child.name);
|
|
354
|
+
for (const item of entries(subDir)) if (item.isFile() && item.name.endsWith(FILE_EXT)) out.push(makeArtifact(kind, child.name, item.name.slice(0, -4), join(subDir, item.name), true));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function listKind(root, kind) {
|
|
358
|
+
const out = [];
|
|
359
|
+
const base = join(root, KIND_DIRS[kind]);
|
|
360
|
+
if (existsSync(base)) if (kind === "skill") collectSkills(base, out);
|
|
361
|
+
else if (kind === "profile") collectProfiles(base, out);
|
|
362
|
+
else if (kind === "script") collectScripts(base, out);
|
|
363
|
+
else collectMdc(kind, base, out);
|
|
364
|
+
if (kind === "skill") collectRootSkills(root, out);
|
|
365
|
+
else if (kind === "agent" || kind === "rule") collectRootMdc(kind, root, out);
|
|
366
|
+
const seen = /* @__PURE__ */ new Set();
|
|
367
|
+
return out.filter((a) => seen.has(a.id) ? false : seen.add(a.id)).sort((a, b) => a.id.localeCompare(b.id));
|
|
368
|
+
}
|
|
369
|
+
function listAll(root) {
|
|
370
|
+
return [
|
|
371
|
+
"skill",
|
|
372
|
+
"rule",
|
|
373
|
+
"agent",
|
|
374
|
+
"profile"
|
|
375
|
+
].flatMap((k) => listKind(root, k));
|
|
376
|
+
}
|
|
377
|
+
function select(root, kind, selector) {
|
|
378
|
+
const items = listKind(root, kind);
|
|
379
|
+
if (!selector) return items;
|
|
380
|
+
if (selector.includes("/")) return items.filter((a) => a.id === selector);
|
|
381
|
+
const byCategory = items.filter((a) => a.category === selector);
|
|
382
|
+
if (byCategory.length) return byCategory;
|
|
383
|
+
return items.filter((a) => a.name === selector);
|
|
384
|
+
}
|
|
385
|
+
const home$1 = homedir();
|
|
386
|
+
const configHome = xdgConfig ?? join(home$1, ".config");
|
|
387
|
+
const codexHome = process.env.CODEX_HOME?.trim() || join(home$1, ".codex");
|
|
388
|
+
const claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home$1, ".claude");
|
|
389
|
+
const vibeHome = process.env.VIBE_HOME?.trim() || join(home$1, ".vibe");
|
|
390
|
+
const hermesHome = process.env.HERMES_HOME?.trim() || join(home$1, ".hermes");
|
|
391
|
+
const autohandHome = process.env.AUTOHAND_HOME?.trim() || join(home$1, ".autohand");
|
|
392
|
+
const grokHome = process.env.GROK_HOME?.trim() || join(home$1, ".grok");
|
|
393
|
+
const zedAppDataHome = process.env.APPDATA?.trim();
|
|
394
|
+
const zedFlatpakConfigHome = process.env.FLATPAK_XDG_CONFIG_HOME?.trim();
|
|
395
|
+
function packageJsonHasDependency(packageJsonPath, dependencyName) {
|
|
396
|
+
try {
|
|
397
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
398
|
+
return !!(pkg.dependencies?.[dependencyName] || pkg.devDependencies?.[dependencyName]);
|
|
399
|
+
} catch {
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function getOpenClawGlobalSkillsDir(homeDir = home$1) {
|
|
404
|
+
if (existsSync(join(homeDir, ".openclaw"))) return join(homeDir, ".openclaw/skills");
|
|
405
|
+
if (existsSync(join(homeDir, ".clawdbot"))) return join(homeDir, ".clawdbot/skills");
|
|
406
|
+
if (existsSync(join(homeDir, ".moltbot"))) return join(homeDir, ".moltbot/skills");
|
|
407
|
+
return join(homeDir, ".openclaw/skills");
|
|
408
|
+
}
|
|
409
|
+
function isZCodeInstalled(homeDir = home$1) {
|
|
410
|
+
return existsSync(join(homeDir, ".zcode")) || existsSync("/Applications/ZCode.app");
|
|
411
|
+
}
|
|
412
|
+
function isKimchiInstalled(homeDir = home$1) {
|
|
413
|
+
return existsSync(join(homeDir, ".config", "kimchi"));
|
|
414
|
+
}
|
|
415
|
+
function make(name, displayName, skillsDir, globalSkillsDir, detect, flags = {}) {
|
|
416
|
+
return {
|
|
417
|
+
name,
|
|
418
|
+
displayName,
|
|
419
|
+
skillsDir,
|
|
420
|
+
globalSkillsDir,
|
|
421
|
+
configDir: deriveConfigDir(skillsDir),
|
|
422
|
+
globalConfigDir: deriveConfigDir(globalSkillsDir || skillsDir),
|
|
423
|
+
detectInstalled: detect,
|
|
424
|
+
showInUniversalList: flags.showInUniversalList ?? true,
|
|
425
|
+
showInUniversalPrompt: flags.showInUniversalPrompt ?? true
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function deriveConfigDir(dir) {
|
|
429
|
+
if (!dir) return dir;
|
|
430
|
+
const p = dir.replace(/\\/g, "/");
|
|
431
|
+
if (p === "skills" || p.endsWith("/skills")) {
|
|
432
|
+
const i = p.lastIndexOf("/skills");
|
|
433
|
+
return i < 0 ? "" : p.slice(0, i);
|
|
434
|
+
}
|
|
435
|
+
return p;
|
|
436
|
+
}
|
|
437
|
+
const ex = (p) => {
|
|
438
|
+
try {
|
|
439
|
+
return existsSync(p);
|
|
440
|
+
} catch {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
const AGENTS = {};
|
|
445
|
+
AGENTS["aider-desk"] = make("aider-desk", "AiderDesk", ".aider-desk/skills", join(home$1, ".aider-desk/skills"), () => ex(join(home$1, ".aider-desk")));
|
|
446
|
+
AGENTS["amp"] = make("amp", "Amp", ".agents/skills", join(configHome, "agents/skills"), () => ex(join(configHome, "amp")));
|
|
447
|
+
AGENTS["antigravity"] = make("antigravity", "Antigravity", ".agents/skills", join(home$1, ".gemini/antigravity/skills"), () => ex(join(home$1, ".gemini/antigravity")));
|
|
448
|
+
AGENTS["antigravity-cli"] = make("antigravity-cli", "Antigravity CLI", ".agents/skills", join(home$1, ".gemini/antigravity-cli/skills"), () => ex(join(home$1, ".gemini/antigravity-cli")));
|
|
449
|
+
AGENTS["astrbot"] = make("astrbot", "AstrBot", "data/skills", join(home$1, ".astrbot/data/skills"), () => ex(join(process.cwd(), "data/skills")) || ex(join(home$1, ".astrbot")));
|
|
450
|
+
AGENTS["autohand-code"] = make("autohand-code", "Autohand Code CLI", ".autohand/skills", join(autohandHome, "skills"), () => ex(autohandHome));
|
|
451
|
+
AGENTS["augment"] = make("augment", "Augment", ".augment/skills", join(home$1, ".augment/skills"), () => ex(join(home$1, ".augment")));
|
|
452
|
+
AGENTS["bob"] = make("bob", "IBM Bob", ".bob/skills", join(home$1, ".bob/skills"), () => ex(join(home$1, ".bob")));
|
|
453
|
+
AGENTS["claude-code"] = make("claude-code", "Claude Code", ".claude/skills", join(claudeHome, "skills"), () => ex(claudeHome));
|
|
454
|
+
AGENTS["openclaw"] = make("openclaw", "OpenClaw", "skills", getOpenClawGlobalSkillsDir(), () => ex(join(home$1, ".openclaw")) || ex(join(home$1, ".clawdbot")) || ex(join(home$1, ".moltbot")));
|
|
455
|
+
AGENTS["cline"] = make("cline", "Cline", ".agents/skills", join(home$1, ".agents", "skills"), () => ex(join(home$1, ".cline")));
|
|
456
|
+
AGENTS["codearts-agent"] = make("codearts-agent", "CodeArts Agent", ".codeartsdoer/skills", join(home$1, ".codeartsdoer/skills"), () => ex(join(home$1, ".codeartsdoer")));
|
|
457
|
+
AGENTS["codebuddy"] = make("codebuddy", "CodeBuddy", ".codebuddy/skills", join(home$1, ".codebuddy/skills"), () => ex(join(process.cwd(), ".codebuddy")) || ex(join(home$1, ".codebuddy")));
|
|
458
|
+
AGENTS["codemaker"] = make("codemaker", "Codemaker", ".codemaker/skills", join(home$1, ".codemaker/skills"), () => ex(join(home$1, ".codemaker")));
|
|
459
|
+
AGENTS["codestudio"] = make("codestudio", "Code Studio", ".codestudio/skills", join(home$1, ".codestudio/skills"), () => ex(join(home$1, ".codestudio")));
|
|
460
|
+
AGENTS["codex"] = make("codex", "Codex", ".agents/skills", join(codexHome, "skills"), () => ex(codexHome) || ex("/etc/codex"));
|
|
461
|
+
AGENTS["command-code"] = make("command-code", "Command Code", ".commandcode/skills", join(home$1, ".commandcode/skills"), () => ex(join(home$1, ".commandcode")));
|
|
462
|
+
AGENTS["continue"] = make("continue", "Continue", ".continue/skills", join(home$1, ".continue/skills"), () => ex(join(process.cwd(), ".continue")) || ex(join(home$1, ".continue")));
|
|
463
|
+
AGENTS["cortex"] = make("cortex", "Cortex Code", ".cortex/skills", join(home$1, ".snowflake/cortex/skills"), () => ex(join(home$1, ".snowflake/cortex")));
|
|
464
|
+
AGENTS["crush"] = make("crush", "Crush", ".crush/skills", join(home$1, ".config/crush/skills"), () => ex(join(home$1, ".config/crush")));
|
|
465
|
+
AGENTS["cursor"] = make("cursor", "Cursor", ".agents/skills", join(home$1, ".cursor/skills"), () => ex(join(home$1, ".cursor")));
|
|
466
|
+
AGENTS["deepagents"] = make("deepagents", "Deep Agents", ".agents/skills", join(home$1, ".deepagents/agent/skills"), () => ex(join(home$1, ".deepagents")));
|
|
467
|
+
AGENTS["devin"] = make("devin", "Devin for Terminal", ".devin/skills", join(configHome, "devin/skills"), () => ex(join(configHome, "devin")));
|
|
468
|
+
AGENTS["dexto"] = make("dexto", "Dexto", ".agents/skills", join(home$1, ".agents/skills"), () => ex(join(home$1, ".dexto")), { showInUniversalPrompt: false });
|
|
469
|
+
AGENTS["droid"] = make("droid", "Droid", ".factory/skills", join(home$1, ".factory/skills"), () => ex(join(home$1, ".factory")));
|
|
470
|
+
AGENTS["eve"] = make("eve", "Eve", "agent/skills", null, () => ex(join(process.cwd(), "agent")) && packageJsonHasDependency(join(process.cwd(), "package.json"), "eve"));
|
|
471
|
+
AGENTS["firebender"] = make("firebender", "Firebender", ".agents/skills", join(home$1, ".firebender/skills"), () => ex(join(home$1, ".firebender")), { showInUniversalPrompt: false });
|
|
472
|
+
AGENTS["forgecode"] = make("forgecode", "ForgeCode", ".forge/skills", join(home$1, ".forge/skills"), () => ex(join(home$1, ".forge")));
|
|
473
|
+
AGENTS["gemini-cli"] = make("gemini-cli", "Gemini CLI", ".agents/skills", join(home$1, ".gemini/skills"), () => ex(join(home$1, ".gemini")));
|
|
474
|
+
AGENTS["github-copilot"] = make("github-copilot", "GitHub Copilot", ".agents/skills", join(home$1, ".copilot/skills"), () => ex(join(home$1, ".copilot")));
|
|
475
|
+
AGENTS["goose"] = make("goose", "Goose", ".goose/skills", join(configHome, "goose/skills"), () => ex(join(configHome, "goose")));
|
|
476
|
+
AGENTS["grok"] = make("grok", "Grok Build", ".grok/skills", join(grokHome, "skills"), () => ex(grokHome));
|
|
477
|
+
AGENTS["hermes-agent"] = make("hermes-agent", "Hermes Agent", ".hermes/skills", join(hermesHome, "skills"), () => ex(hermesHome));
|
|
478
|
+
AGENTS["inference-sh"] = make("inference-sh", "inference.sh", ".inferencesh/skills", join(home$1, ".inferencesh/skills"), () => ex(join(home$1, ".inferencesh")));
|
|
479
|
+
AGENTS["jazz"] = make("jazz", "Jazz", ".jazz/skills", join(home$1, ".jazz/skills"), () => ex(join(home$1, ".jazz")) || ex(join(process.cwd(), ".jazz")));
|
|
480
|
+
AGENTS["junie"] = make("junie", "Junie", ".junie/skills", join(home$1, ".junie/skills"), () => ex(join(home$1, ".junie")));
|
|
481
|
+
AGENTS["iflow-cli"] = make("iflow-cli", "iFlow CLI", ".iflow/skills", join(home$1, ".iflow/skills"), () => ex(join(home$1, ".iflow")));
|
|
482
|
+
AGENTS["kilo"] = make("kilo", "Kilo Code", ".kilocode/skills", join(home$1, ".kilocode/skills"), () => ex(join(home$1, ".kilocode")));
|
|
483
|
+
AGENTS["kimchi"] = make("kimchi", "Kimchi", ".kimchi/skills", join(home$1, ".config", "kimchi", "harness", "skills"), () => isKimchiInstalled());
|
|
484
|
+
AGENTS["kimi-code-cli"] = make("kimi-code-cli", "Kimi Code CLI", ".agents/skills", join(home$1, ".agents/skills"), () => ex(join(home$1, ".kimi-code")) || ex(join(home$1, ".kimi")));
|
|
485
|
+
AGENTS["kiro-cli"] = make("kiro-cli", "Kiro CLI", ".kiro/skills", join(home$1, ".kiro/skills"), () => ex(join(home$1, ".kiro")));
|
|
486
|
+
AGENTS["kode"] = make("kode", "Kode", ".kode/skills", join(home$1, ".kode/skills"), () => ex(join(home$1, ".kode")));
|
|
487
|
+
AGENTS["lingma"] = make("lingma", "Lingma", ".lingma/skills", join(home$1, ".lingma/skills"), () => ex(join(home$1, ".lingma")));
|
|
488
|
+
AGENTS["loaf"] = make("loaf", "Loaf", ".agents/skills", join(home$1, ".agents/skills"), () => ex(join(home$1, ".loaf")), { showInUniversalPrompt: false });
|
|
489
|
+
AGENTS["mcpjam"] = make("mcpjam", "MCPJam", ".mcpjam/skills", join(home$1, ".mcpjam/skills"), () => ex(join(home$1, ".mcpjam")));
|
|
490
|
+
AGENTS["mistral-vibe"] = make("mistral-vibe", "Mistral Vibe", ".vibe/skills", join(vibeHome, "skills"), () => ex(vibeHome));
|
|
491
|
+
AGENTS["moxby"] = make("moxby", "Moxby", ".moxby/skills", join(home$1, ".moxby/skills"), () => ex(join(home$1, ".moxby")));
|
|
492
|
+
AGENTS["mux"] = make("mux", "Mux", ".mux/skills", join(home$1, ".mux/skills"), () => ex(join(home$1, ".mux")));
|
|
493
|
+
AGENTS["opencode"] = make("opencode", "OpenCode", ".agents/skills", join(configHome, "opencode/skills"), () => ex(join(configHome, "opencode")));
|
|
494
|
+
AGENTS["openhands"] = make("openhands", "OpenHands", ".openhands/skills", join(home$1, ".openhands/skills"), () => ex(join(home$1, ".openhands")));
|
|
495
|
+
AGENTS["ona"] = make("ona", "Ona", ".ona/skills", join(home$1, ".ona/skills"), () => ex(join(home$1, ".ona")));
|
|
496
|
+
AGENTS["pi"] = make("pi", "Pi", ".pi/skills", join(home$1, ".pi/agent/skills"), () => ex(join(home$1, ".pi/agent")));
|
|
497
|
+
AGENTS["qoder"] = make("qoder", "Qoder", ".qoder/skills", join(home$1, ".qoder/skills"), () => ex(join(home$1, ".qoder")));
|
|
498
|
+
AGENTS["qoder-cn"] = make("qoder-cn", "Qoder CN", ".qoder/skills", join(home$1, ".qoder-cn/skills"), () => ex(join(home$1, ".qoder-cn")));
|
|
499
|
+
AGENTS["qwen-code"] = make("qwen-code", "Qwen Code", ".qwen/skills", join(home$1, ".qwen/skills"), () => ex(join(home$1, ".qwen")));
|
|
500
|
+
AGENTS["replit"] = make("replit", "Replit", ".agents/skills", join(configHome, "agents/skills"), () => ex(join(process.cwd(), ".replit")), { showInUniversalList: false });
|
|
501
|
+
AGENTS["reasonix"] = make("reasonix", "Reasonix", ".reasonix/skills", join(home$1, ".reasonix/skills"), () => ex(join(home$1, ".reasonix")));
|
|
502
|
+
AGENTS["rovodev"] = make("rovodev", "Rovo Dev", ".rovodev/skills", join(home$1, ".rovodev/skills"), () => ex(join(home$1, ".rovodev")));
|
|
503
|
+
AGENTS["roo"] = make("roo", "Roo Code", ".roo/skills", join(home$1, ".roo/skills"), () => ex(join(home$1, ".roo")));
|
|
504
|
+
AGENTS["tabnine-cli"] = make("tabnine-cli", "Tabnine CLI", ".tabnine/agent/skills", join(home$1, ".tabnine/agent/skills"), () => ex(join(home$1, ".tabnine")));
|
|
505
|
+
AGENTS["terramind"] = make("terramind", "Terramind", ".terramind/skills", join(home$1, ".terramind/skills"), () => ex(join(home$1, ".terramind")));
|
|
506
|
+
AGENTS["tinycloud"] = make("tinycloud", "Tinycloud", ".tinycloud/skills", join(home$1, ".tinycloud/skills"), () => ex(join(home$1, ".tinycloud")));
|
|
507
|
+
AGENTS["trae"] = make("trae", "Trae", ".trae/skills", join(home$1, ".trae/skills"), () => ex(join(home$1, ".trae")));
|
|
508
|
+
AGENTS["trae-cn"] = make("trae-cn", "Trae CN", ".trae/skills", join(home$1, ".trae-cn/skills"), () => ex(join(home$1, ".trae-cn")));
|
|
509
|
+
AGENTS["warp"] = make("warp", "Warp", ".agents/skills", join(home$1, ".agents/skills"), () => ex(join(home$1, ".warp")));
|
|
510
|
+
AGENTS["windsurf"] = make("windsurf", "Windsurf", ".windsurf/skills", join(home$1, ".codeium/windsurf/skills"), () => ex(join(home$1, ".codeium/windsurf")));
|
|
511
|
+
AGENTS["zed"] = make("zed", "Zed", ".agents/skills", join(home$1, ".agents/skills"), () => ex(join(configHome, "zed")) || !!zedAppDataHome && ex(join(zedAppDataHome, "Zed")) || !!zedFlatpakConfigHome && ex(join(zedFlatpakConfigHome, "zed")));
|
|
512
|
+
AGENTS["zcode"] = make("zcode", "ZCode", ".zcode/skills", join(home$1, ".zcode/skills"), () => isZCodeInstalled());
|
|
513
|
+
AGENTS["zencoder"] = make("zencoder", "Zencoder", ".zencoder/skills", join(home$1, ".zencoder/skills"), () => ex(join(home$1, ".zencoder")));
|
|
514
|
+
AGENTS["zenflow"] = make("zenflow", "Zenflow", ".zencoder/skills", join(home$1, ".zencoder/skills"), () => ex(join(home$1, ".zencoder")));
|
|
515
|
+
AGENTS["neovate"] = make("neovate", "Neovate", ".neovate/skills", join(home$1, ".neovate/skills"), () => ex(join(home$1, ".neovate")));
|
|
516
|
+
AGENTS["pochi"] = make("pochi", "Pochi", ".pochi/skills", join(home$1, ".pochi/skills"), () => ex(join(home$1, ".pochi")));
|
|
517
|
+
AGENTS["promptscript"] = make("promptscript", "PromptScript", ".agents/skills", null, () => ex(join(process.cwd(), ".promptscript")) || ex(join(process.cwd(), "promptscript.yaml")), { showInUniversalPrompt: false });
|
|
518
|
+
AGENTS["adal"] = make("adal", "AdaL", ".adal/skills", join(home$1, ".adal/skills"), () => ex(join(home$1, ".adal")));
|
|
519
|
+
AGENTS["universal"] = make("universal", "Universal", ".agents/skills", join(configHome, "agents/skills"), () => false, { showInUniversalList: false });
|
|
520
|
+
function getAgent(name) {
|
|
521
|
+
return AGENTS[name] ?? null;
|
|
522
|
+
}
|
|
523
|
+
function listAgents() {
|
|
524
|
+
return Object.values(AGENTS);
|
|
525
|
+
}
|
|
526
|
+
function detectInstalledAgents() {
|
|
527
|
+
return listAgents().filter((a) => {
|
|
528
|
+
try {
|
|
529
|
+
return a.detectInstalled();
|
|
530
|
+
} catch {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
function resolveAgents(names) {
|
|
536
|
+
if (!names || names.length === 0) return {
|
|
537
|
+
agents: [],
|
|
538
|
+
unknown: []
|
|
539
|
+
};
|
|
540
|
+
if (names.includes("*")) return {
|
|
541
|
+
agents: listAgents(),
|
|
542
|
+
unknown: []
|
|
543
|
+
};
|
|
544
|
+
const agents = [];
|
|
545
|
+
const unknown = [];
|
|
546
|
+
for (const n of names) {
|
|
547
|
+
const a = getAgent(n);
|
|
548
|
+
if (a) agents.push(a);
|
|
549
|
+
else unknown.push(n);
|
|
550
|
+
}
|
|
551
|
+
return {
|
|
552
|
+
agents,
|
|
553
|
+
unknown
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function getUniversalAgents() {
|
|
557
|
+
return listAgents().filter((a) => a.skillsDir === ".agents/skills" && a.showInUniversalList !== false);
|
|
558
|
+
}
|
|
559
|
+
function getVisibleUniversalAgents() {
|
|
560
|
+
return listAgents().filter((a) => a.skillsDir === ".agents/skills" && a.showInUniversalList !== false && a.showInUniversalPrompt !== false);
|
|
561
|
+
}
|
|
562
|
+
function getNonUniversalAgents() {
|
|
563
|
+
return listAgents().filter((a) => a.skillsDir !== ".agents/skills");
|
|
564
|
+
}
|
|
565
|
+
const home = homedir();
|
|
566
|
+
function lockBase(opts) {
|
|
567
|
+
return opts.scope === "global" ? home : opts.dir || process.cwd();
|
|
568
|
+
}
|
|
569
|
+
function lockPath(opts) {
|
|
570
|
+
return join(lockBase(opts), ".agentry", "lock.json");
|
|
571
|
+
}
|
|
572
|
+
function legacyLockPath(opts) {
|
|
573
|
+
return join(lockBase(opts), ".cursor", "agentry.lock.json");
|
|
574
|
+
}
|
|
575
|
+
function tryReadLockFile(file) {
|
|
576
|
+
try {
|
|
577
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
578
|
+
} catch {
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function readLock(opts) {
|
|
583
|
+
const fromCurrent = tryReadLockFile(lockPath(opts));
|
|
584
|
+
if (fromCurrent) return fromCurrent;
|
|
585
|
+
const fromLegacy = tryReadLockFile(legacyLockPath(opts));
|
|
586
|
+
if (fromLegacy) return fromLegacy;
|
|
587
|
+
return {
|
|
588
|
+
version: 1,
|
|
589
|
+
items: {}
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function removeLegacyLockIfPresent(opts) {
|
|
593
|
+
const legacy = legacyLockPath(opts);
|
|
594
|
+
if (existsSync(legacy)) rmSync(legacy, { force: true });
|
|
595
|
+
}
|
|
596
|
+
function writeLock(opts, lock) {
|
|
597
|
+
const file = lockPath(opts);
|
|
598
|
+
if (!Object.keys(lock.items).length) {
|
|
599
|
+
const dir = join(lockBase(opts), ".agentry");
|
|
600
|
+
if (existsSync(dir)) rmSync(dir, {
|
|
601
|
+
recursive: true,
|
|
602
|
+
force: true
|
|
603
|
+
});
|
|
604
|
+
removeLegacyLockIfPresent(opts);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
mkdirSync(join(file, ".."), { recursive: true });
|
|
608
|
+
writeFileSync(file, JSON.stringify(lock, null, 2) + "\n");
|
|
609
|
+
removeLegacyLockIfPresent(opts);
|
|
610
|
+
}
|
|
611
|
+
function isDirTreeEmpty(dir) {
|
|
612
|
+
if (!existsSync(dir)) return true;
|
|
613
|
+
let entries;
|
|
614
|
+
try {
|
|
615
|
+
entries = readdirSync(dir);
|
|
616
|
+
} catch {
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
for (const name of entries) {
|
|
620
|
+
const p = join(dir, name);
|
|
621
|
+
let st;
|
|
622
|
+
try {
|
|
623
|
+
st = statSync(p);
|
|
624
|
+
} catch {
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (st.isDirectory()) {
|
|
628
|
+
if (!isDirTreeEmpty(p)) return false;
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
if (st.isFile()) {
|
|
632
|
+
if (name === "lock.json") {
|
|
633
|
+
try {
|
|
634
|
+
const data = JSON.parse(readFileSync(p, "utf8"));
|
|
635
|
+
if (data && typeof data === "object" && Object.keys(data.items || {}).length === 0) continue;
|
|
636
|
+
} catch {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return true;
|
|
645
|
+
}
|
|
646
|
+
function pruneEmptyAncestors(dir, stopAt, removedPaths = []) {
|
|
647
|
+
const stop = resolve(stopAt);
|
|
648
|
+
let cur = resolve(dir);
|
|
649
|
+
while (cur.length > stop.length) {
|
|
650
|
+
const prefix = stop.endsWith(sep) ? stop : stop + sep;
|
|
651
|
+
if (!cur.toLowerCase().startsWith(prefix.toLowerCase()) && cur.toLowerCase() !== stop.toLowerCase()) break;
|
|
652
|
+
if (!existsSync(cur)) {
|
|
653
|
+
cur = dirname(cur);
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
if (!isDirTreeEmpty(cur)) break;
|
|
657
|
+
rmSync(cur, {
|
|
658
|
+
recursive: true,
|
|
659
|
+
force: true
|
|
660
|
+
});
|
|
661
|
+
removedPaths.push(cur);
|
|
662
|
+
cur = dirname(cur);
|
|
663
|
+
}
|
|
664
|
+
return removedPaths;
|
|
665
|
+
}
|
|
666
|
+
function listInstalled(opts) {
|
|
667
|
+
return Object.entries(readLock(opts).items).map(([id, v]) => ({
|
|
668
|
+
id,
|
|
669
|
+
...v
|
|
670
|
+
}));
|
|
671
|
+
}
|
|
672
|
+
function pluralKind(kind) {
|
|
673
|
+
return kind === "skill" ? "skills" : kind === "rule" ? "rules" : kind === "agent" ? "agents" : kind === "script" ? "scripts" : "profiles";
|
|
674
|
+
}
|
|
675
|
+
function installOne(artifact, opts, agentList, source) {
|
|
676
|
+
const list = agentList.length ? agentList : [getAgent("cursor")];
|
|
677
|
+
const base = lockBase(opts);
|
|
678
|
+
const dests = [];
|
|
679
|
+
if (opts.dryRun) {
|
|
680
|
+
for (const a of list) dests.push({
|
|
681
|
+
agent: a.name,
|
|
682
|
+
dir: agentDir(artifact, a, base)
|
|
683
|
+
});
|
|
684
|
+
return dests;
|
|
685
|
+
}
|
|
686
|
+
if (artifact.kind === "skill") {
|
|
687
|
+
const canonical = join(base, ".agents", "skills", artifact.id);
|
|
688
|
+
rmSync(canonical, {
|
|
689
|
+
recursive: true,
|
|
690
|
+
force: true
|
|
691
|
+
});
|
|
692
|
+
mkdirSync(join(canonical, ".."), { recursive: true });
|
|
693
|
+
cpSync(artifact.dir, canonical, { recursive: true });
|
|
694
|
+
for (const a of list) if (a.skillsDir === ".agents/skills") dests.push({
|
|
695
|
+
agent: a.name,
|
|
696
|
+
dir: canonical
|
|
697
|
+
});
|
|
698
|
+
else {
|
|
699
|
+
const dir = join(base, a.skillsDir, artifact.id);
|
|
700
|
+
rmSync(dir, {
|
|
701
|
+
recursive: true,
|
|
702
|
+
force: true
|
|
703
|
+
});
|
|
704
|
+
mkdirSync(join(dir, ".."), { recursive: true });
|
|
705
|
+
if (opts.copy) {
|
|
706
|
+
cpSync(canonical, dir, { recursive: true });
|
|
707
|
+
dests.push({
|
|
708
|
+
agent: a.name,
|
|
709
|
+
dir
|
|
710
|
+
});
|
|
711
|
+
} else try {
|
|
712
|
+
symlinkSync(canonical, dir, "junction");
|
|
713
|
+
dests.push({
|
|
714
|
+
agent: a.name,
|
|
715
|
+
dir,
|
|
716
|
+
symlinked: true
|
|
717
|
+
});
|
|
718
|
+
} catch {
|
|
719
|
+
cpSync(canonical, dir, { recursive: true });
|
|
720
|
+
dests.push({
|
|
721
|
+
agent: a.name,
|
|
722
|
+
dir
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
} else for (const a of list) {
|
|
727
|
+
const dir = agentDir(artifact, a, base);
|
|
728
|
+
rmSync(dir, {
|
|
729
|
+
recursive: true,
|
|
730
|
+
force: true
|
|
731
|
+
});
|
|
732
|
+
mkdirSync(join(dir, ".."), { recursive: true });
|
|
733
|
+
cpSync(artifact.dir, dir, { recursive: true });
|
|
734
|
+
dests.push({
|
|
735
|
+
agent: a.name,
|
|
736
|
+
dir
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
const lock = readLock(opts);
|
|
740
|
+
lock.items[`${artifact.kind}/${artifact.id}`] = {
|
|
741
|
+
source: source || null,
|
|
742
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
743
|
+
agents: list.map((a) => a.name),
|
|
744
|
+
kind: artifact.kind
|
|
745
|
+
};
|
|
746
|
+
writeLock(opts, lock);
|
|
747
|
+
return dests;
|
|
748
|
+
}
|
|
749
|
+
function agentDir(artifact, a, base) {
|
|
750
|
+
if (artifact.kind === "skill") return join(base, a.skillsDir, artifact.id);
|
|
751
|
+
if (artifact.isFile) {
|
|
752
|
+
const ext = artifact.kind === "profile" ? ".yaml" : ".mdc";
|
|
753
|
+
return join(base, a.configDir || a.skillsDir, pluralKind(artifact.kind), artifact.name + ext);
|
|
754
|
+
}
|
|
755
|
+
return join(base, a.configDir || a.skillsDir, pluralKind(artifact.kind), artifact.id);
|
|
756
|
+
}
|
|
757
|
+
function removeSelection(kind, selector, opts, agentList, sourceFilter) {
|
|
758
|
+
const base = lockBase(opts);
|
|
759
|
+
const list = agentList.length ? agentList : [getAgent("cursor")];
|
|
760
|
+
const rel = [kind, selector].filter(Boolean).join("/");
|
|
761
|
+
const removedKeys = [];
|
|
762
|
+
const removedPaths = [];
|
|
763
|
+
const lock = readLock(opts);
|
|
764
|
+
const matchingKeys = Object.keys(lock.items).filter((key) => {
|
|
765
|
+
const entry = lock.items[key];
|
|
766
|
+
if (sourceFilter && !sourcesEqual(entry.source, sourceFilter)) return false;
|
|
767
|
+
if (kind === "all") return true;
|
|
768
|
+
if (key === rel || key.startsWith(rel + "/")) return true;
|
|
769
|
+
if (!selector && key.startsWith(kind + "/")) return true;
|
|
770
|
+
return false;
|
|
771
|
+
});
|
|
772
|
+
const rmPath = (p) => {
|
|
773
|
+
if (!p || !existsSync(p)) return;
|
|
774
|
+
const parent = dirname(p);
|
|
775
|
+
rmSync(p, {
|
|
776
|
+
recursive: true,
|
|
777
|
+
force: true
|
|
778
|
+
});
|
|
779
|
+
removedPaths.push(p);
|
|
780
|
+
pruneEmptyAncestors(parent, base, removedPaths);
|
|
781
|
+
};
|
|
782
|
+
const agentsFor = (entry) => {
|
|
783
|
+
const fromEntry = entry.agents.map((n) => getAgent(n)).filter(Boolean);
|
|
784
|
+
const byName = /* @__PURE__ */ new Map();
|
|
785
|
+
for (const a of [...fromEntry, ...list]) byName.set(a.name, a);
|
|
786
|
+
return [...byName.values()];
|
|
787
|
+
};
|
|
788
|
+
const removeFilesForKey = (key, entry) => {
|
|
789
|
+
const parts = key.split("/");
|
|
790
|
+
const k = parts[0];
|
|
791
|
+
const sel = parts.slice(1).join("/") || void 0;
|
|
792
|
+
if (!sel && k === "skill") return;
|
|
793
|
+
const agents = agentsFor(entry);
|
|
794
|
+
if (k === "skill" && sel) {
|
|
795
|
+
rmPath(join(base, ".agents", "skills", sel));
|
|
796
|
+
for (const a of agents) {
|
|
797
|
+
if (a.skillsDir === ".agents/skills") continue;
|
|
798
|
+
rmPath(join(base, a.skillsDir, sel));
|
|
799
|
+
}
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
for (const a of agents) if (k === "agent" || k === "rule" || k === "profile") {
|
|
803
|
+
const ext = k === "profile" ? ".yaml" : ".mdc";
|
|
804
|
+
if (!sel) rmPath(join(base, a.configDir || a.skillsDir, pluralKind(k)));
|
|
805
|
+
else if (sel.includes("/")) {
|
|
806
|
+
const [cat, name] = sel.split("/");
|
|
807
|
+
rmPath(join(base, a.configDir || a.skillsDir, pluralKind(k), cat, name + ext));
|
|
808
|
+
} else rmPath(join(base, a.configDir || a.skillsDir, pluralKind(k), sel + ext));
|
|
809
|
+
} else if (sel) rmPath(join(base, a.configDir || a.skillsDir, pluralKind(k), sel));
|
|
810
|
+
};
|
|
811
|
+
for (const key of matchingKeys) {
|
|
812
|
+
const entry = lock.items[key];
|
|
813
|
+
removeFilesForKey(key, entry);
|
|
814
|
+
delete lock.items[key];
|
|
815
|
+
removedKeys.push(key);
|
|
816
|
+
}
|
|
817
|
+
if (!removedKeys.length && !sourceFilter) {
|
|
818
|
+
if (kind === "skill") rmPath(join(base, ".agents", "skills", selector || ""));
|
|
819
|
+
for (const a of list) {
|
|
820
|
+
let dir;
|
|
821
|
+
if (kind === "skill") {
|
|
822
|
+
if (a.skillsDir === ".agents/skills") continue;
|
|
823
|
+
dir = join(base, a.skillsDir, selector || "");
|
|
824
|
+
} else if (kind === "agent" || kind === "rule" || kind === "profile") {
|
|
825
|
+
const ext = kind === "profile" ? ".yaml" : ".mdc";
|
|
826
|
+
if (!selector) dir = join(base, a.configDir || a.skillsDir, pluralKind(kind));
|
|
827
|
+
else if (selector.includes("/")) {
|
|
828
|
+
const [cat, name] = selector.split("/");
|
|
829
|
+
dir = join(base, a.configDir || a.skillsDir, pluralKind(kind), cat, name + ext);
|
|
830
|
+
} else dir = join(base, a.configDir || a.skillsDir, pluralKind(kind), selector + ext);
|
|
831
|
+
} else dir = join(base, a.configDir || a.skillsDir, pluralKind(kind), selector || "");
|
|
832
|
+
if (existsSync(dir)) {
|
|
833
|
+
rmPath(dir);
|
|
834
|
+
if (!removedKeys.includes(rel)) removedKeys.push(rel);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
for (const key of Object.keys(lock.items)) if (key === rel || key.startsWith(rel + "/")) {
|
|
838
|
+
delete lock.items[key];
|
|
839
|
+
if (!removedKeys.includes(key)) removedKeys.push(key);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
writeLock(opts, lock);
|
|
843
|
+
const sweepRoots = /* @__PURE__ */ new Set([
|
|
844
|
+
join(base, ".agents", "skills"),
|
|
845
|
+
join(base, ".agents"),
|
|
846
|
+
join(base, ".agentry")
|
|
847
|
+
]);
|
|
848
|
+
for (const a of list) {
|
|
849
|
+
sweepRoots.add(join(base, a.skillsDir));
|
|
850
|
+
if (a.configDir) {
|
|
851
|
+
sweepRoots.add(join(base, a.configDir));
|
|
852
|
+
for (const pk of [
|
|
853
|
+
"skills",
|
|
854
|
+
"agents",
|
|
855
|
+
"rules",
|
|
856
|
+
"profiles",
|
|
857
|
+
"scripts"
|
|
858
|
+
]) sweepRoots.add(join(base, a.configDir, pk));
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
for (const root of sweepRoots) if (existsSync(root) && isDirTreeEmpty(root)) pruneEmptyAncestors(root, base, removedPaths);
|
|
862
|
+
return {
|
|
863
|
+
existed: removedKeys.length > 0 || removedPaths.length > 0,
|
|
864
|
+
removedKeys,
|
|
865
|
+
removedPaths
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
const ProfileArtifactRefSchema = z.object({
|
|
869
|
+
source: z.string().optional(),
|
|
870
|
+
id: z.string()
|
|
871
|
+
});
|
|
872
|
+
const ProfileSchema = z.object({
|
|
873
|
+
name: z.string(),
|
|
874
|
+
description: z.string().optional(),
|
|
875
|
+
scope: z.enum(["global", "project"]).default("project"),
|
|
876
|
+
targets: z.object({ agents: z.array(z.string()).default([]) }).default({ agents: [] }),
|
|
877
|
+
artifacts: z.object({
|
|
878
|
+
skills: z.array(ProfileArtifactRefSchema).default([]),
|
|
879
|
+
rules: z.array(ProfileArtifactRefSchema).default([]),
|
|
880
|
+
agents: z.array(ProfileArtifactRefSchema).default([]),
|
|
881
|
+
scripts: z.array(ProfileArtifactRefSchema).default([])
|
|
882
|
+
}).default({})
|
|
883
|
+
});
|
|
884
|
+
function loadProfileFile(file) {
|
|
885
|
+
let raw;
|
|
886
|
+
try {
|
|
887
|
+
raw = readFileSync(file, "utf8");
|
|
888
|
+
} catch {
|
|
889
|
+
throw new Error(`Profile not found: ${file}`);
|
|
890
|
+
}
|
|
891
|
+
let data;
|
|
892
|
+
try {
|
|
893
|
+
data = parse(raw);
|
|
894
|
+
} catch (e) {
|
|
895
|
+
throw new Error(`Invalid YAML in ${file}: ${e.message}`);
|
|
896
|
+
}
|
|
897
|
+
const result = ProfileSchema.safeParse(data);
|
|
898
|
+
if (!result.success) throw new Error(`Invalid profile ${file}: ${result.error.message}`);
|
|
899
|
+
return result.data;
|
|
900
|
+
}
|
|
901
|
+
function loadProfile(name, baseDir) {
|
|
902
|
+
const file = join(baseDir || process.cwd(), "profile", `${name}.yaml`);
|
|
903
|
+
return {
|
|
904
|
+
file,
|
|
905
|
+
profile: loadProfileFile(file)
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
function ensureDir(file) {
|
|
909
|
+
mkdirSync(join(file, ".."), { recursive: true });
|
|
910
|
+
}
|
|
911
|
+
function writeNew(file, content, force = false) {
|
|
912
|
+
if (existsSync(file) && !force) throw new Error(`Already exists: ${file}`);
|
|
913
|
+
ensureDir(file);
|
|
914
|
+
writeFileSync(file, content);
|
|
915
|
+
return file;
|
|
916
|
+
}
|
|
917
|
+
function scaffoldSkill(opts) {
|
|
918
|
+
const dir = join(opts.baseDir || process.cwd(), opts.category ? join("skills", opts.category, opts.name) : join("skills", opts.name));
|
|
919
|
+
const id = opts.category ? `${opts.category}/${opts.name}` : opts.name;
|
|
920
|
+
const desc = opts.description || `${opts.name} skill`;
|
|
921
|
+
const skillMd = `---
|
|
922
|
+
name: ${opts.name}
|
|
923
|
+
description: ${desc}
|
|
924
|
+
---
|
|
925
|
+
|
|
926
|
+
# ${opts.name}
|
|
927
|
+
|
|
928
|
+
${desc}
|
|
929
|
+
|
|
930
|
+
## When to use
|
|
931
|
+
|
|
932
|
+
Describe when this skill should run.
|
|
933
|
+
|
|
934
|
+
## Instructions
|
|
935
|
+
|
|
936
|
+
1. …
|
|
937
|
+
`;
|
|
938
|
+
const paths = [writeNew(join(dir, "SKILL.md"), skillMd)];
|
|
939
|
+
if (opts.reference !== false) paths.push(writeNew(join(dir, "references", "TEMPLATE.md"), `# ${opts.name} — reference
|
|
940
|
+
|
|
941
|
+
Fill in details here. Keep SKILL.md lean; put depth in references/.
|
|
942
|
+
`));
|
|
943
|
+
return {
|
|
944
|
+
paths,
|
|
945
|
+
id
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
function scaffoldMdc(opts) {
|
|
949
|
+
const base = opts.baseDir || process.cwd();
|
|
950
|
+
const folder = opts.kind === "agent" ? "agents" : "rules";
|
|
951
|
+
const rel = opts.category ? join(folder, opts.category, `${opts.name}.mdc`) : join(folder, `${opts.name}.mdc`);
|
|
952
|
+
const id = opts.category ? `${opts.category}/${opts.name}` : opts.name;
|
|
953
|
+
const desc = opts.description || `${opts.name} ${opts.kind}`;
|
|
954
|
+
const always = opts.kind === "rule" && opts.alwaysApply ? "alwaysApply: true\n" : opts.kind === "rule" ? "alwaysApply: false\n" : "";
|
|
955
|
+
const body = `---
|
|
956
|
+
name: ${opts.name}
|
|
957
|
+
description: ${desc}
|
|
958
|
+
${always}---
|
|
959
|
+
|
|
960
|
+
# ${opts.name}
|
|
961
|
+
|
|
962
|
+
${desc}
|
|
963
|
+
`;
|
|
964
|
+
return {
|
|
965
|
+
paths: [writeNew(join(base, rel), body)],
|
|
966
|
+
id
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
function scaffoldScript(opts) {
|
|
970
|
+
const dir = join(opts.baseDir || process.cwd(), opts.category ? join("scripts", opts.category, opts.name) : join("scripts", opts.name));
|
|
971
|
+
const id = opts.category ? `${opts.category}/${opts.name}` : opts.name;
|
|
972
|
+
const desc = opts.description || `${opts.name} script`;
|
|
973
|
+
return {
|
|
974
|
+
paths: [writeNew(join(dir, "README.md"), `# ${opts.name}
|
|
975
|
+
|
|
976
|
+
${desc}
|
|
977
|
+
|
|
978
|
+
Place runnable scripts for this use case in this folder.
|
|
979
|
+
`)],
|
|
980
|
+
id
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
function scaffoldProfile(opts) {
|
|
984
|
+
return {
|
|
985
|
+
paths: [writeNew(join(opts.baseDir || process.cwd(), opts.localConfig ? "profile" : "profiles", `${opts.name}.yaml`), stringify({
|
|
986
|
+
name: opts.name,
|
|
987
|
+
description: opts.description || `${opts.name} profile`,
|
|
988
|
+
scope: "project",
|
|
989
|
+
targets: { agents: opts.agents?.length ? opts.agents : ["cursor"] },
|
|
990
|
+
artifacts: {
|
|
991
|
+
skills: opts.skills ?? [],
|
|
992
|
+
rules: opts.rules ?? [],
|
|
993
|
+
agents: opts.agentsArtifacts ?? [],
|
|
994
|
+
scripts: opts.scripts ?? []
|
|
995
|
+
}
|
|
996
|
+
}))],
|
|
997
|
+
id: opts.name
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
let runningInsideAgent = false;
|
|
1001
|
+
try {
|
|
1002
|
+
const result = await determineAgent();
|
|
1003
|
+
runningInsideAgent = !!(result && result.isAgent);
|
|
1004
|
+
} catch {}
|
|
1005
|
+
function isCI() {
|
|
1006
|
+
if (process.env.CI === "true" || process.env.CI === "1") return true;
|
|
1007
|
+
if (!process.stdout.isTTY) return true;
|
|
1008
|
+
return runningInsideAgent;
|
|
1009
|
+
}
|
|
1010
|
+
const silentOutput = new Writable({ write(_chunk, _encoding, callback) {
|
|
1011
|
+
callback();
|
|
1012
|
+
} });
|
|
1013
|
+
const cancelSymbol = Symbol("cancel");
|
|
1014
|
+
const ALL_VALUE = "__all__";
|
|
1015
|
+
const BLUE = sky;
|
|
1016
|
+
const BLUE_BOLD = (s) => c.bold(sky(s));
|
|
1017
|
+
const S_STEP_ACTIVE = sky("◆");
|
|
1018
|
+
const S_STEP_CANCEL = c.red("■");
|
|
1019
|
+
const S_STEP_SUBMIT = sky("◇");
|
|
1020
|
+
const S_RADIO_ACTIVE = sky("●");
|
|
1021
|
+
const S_RADIO_INACTIVE = c.dim("○");
|
|
1022
|
+
const S_RADIO_DISABLED = c.dim("◌");
|
|
1023
|
+
const S_BULLET = sky("•");
|
|
1024
|
+
const S_BAR = c.dim("│");
|
|
1025
|
+
c.dim("─");
|
|
1026
|
+
const S_CORNER = c.dim("└");
|
|
1027
|
+
const S_BOX_TL = c.dim("┌");
|
|
1028
|
+
const S_BOX_TR = c.dim("┐");
|
|
1029
|
+
const S_BOX_BL = c.dim("└");
|
|
1030
|
+
const S_BOX_BR = c.dim("┘");
|
|
1031
|
+
const S_BOX_H = c.dim("─");
|
|
1032
|
+
const S_BOX_V = c.dim("│");
|
|
1033
|
+
function approxStringWidth(plain) {
|
|
1034
|
+
let width = 0;
|
|
1035
|
+
for (const ch of plain) {
|
|
1036
|
+
const code = ch.codePointAt(0);
|
|
1037
|
+
if (code === 0) continue;
|
|
1038
|
+
width += code >= 4352 && code <= 4447 || code >= 11904 && code <= 42191 && code !== 12351 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65280 && code <= 65376 || code >= 126976 && code <= 129535 ? 2 : 1;
|
|
1039
|
+
}
|
|
1040
|
+
return width;
|
|
1041
|
+
}
|
|
1042
|
+
function visualRowsForLine(line, columns) {
|
|
1043
|
+
const plain = stripVTControlCharacters(line);
|
|
1044
|
+
const cols = Math.max(1, columns);
|
|
1045
|
+
return Math.max(1, Math.ceil(approxStringWidth(plain) / cols));
|
|
1046
|
+
}
|
|
1047
|
+
function countVisualRowsForLines(lines, columns) {
|
|
1048
|
+
const cols = columns !== void 0 && columns > 0 ? columns : process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
|
|
1049
|
+
return lines.reduce((sum, line) => sum + visualRowsForLine(line, cols), 0);
|
|
1050
|
+
}
|
|
1051
|
+
function truncateToWidth(text, width) {
|
|
1052
|
+
let truncated = "";
|
|
1053
|
+
for (const char of text) {
|
|
1054
|
+
if (approxStringWidth(truncated + char) > width) break;
|
|
1055
|
+
truncated += char;
|
|
1056
|
+
}
|
|
1057
|
+
return truncated;
|
|
1058
|
+
}
|
|
1059
|
+
function padPlain(text, width) {
|
|
1060
|
+
const w = approxStringWidth(text);
|
|
1061
|
+
if (w >= width) return truncateToWidth(text, width);
|
|
1062
|
+
return text + " ".repeat(width - w);
|
|
1063
|
+
}
|
|
1064
|
+
function wrapPlain(text, width, maxLines) {
|
|
1065
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
1066
|
+
if (!normalized) return [""];
|
|
1067
|
+
const lines = [];
|
|
1068
|
+
let remaining = normalized;
|
|
1069
|
+
while (remaining && lines.length < maxLines) {
|
|
1070
|
+
if (approxStringWidth(remaining) <= width) {
|
|
1071
|
+
lines.push(remaining);
|
|
1072
|
+
remaining = "";
|
|
1073
|
+
break;
|
|
1074
|
+
}
|
|
1075
|
+
const candidate = truncateToWidth(remaining, width);
|
|
1076
|
+
const breakAt = candidate.lastIndexOf(" ");
|
|
1077
|
+
if (breakAt > 0) {
|
|
1078
|
+
lines.push(candidate.slice(0, breakAt).trimEnd());
|
|
1079
|
+
remaining = remaining.slice(breakAt).trimStart();
|
|
1080
|
+
} else {
|
|
1081
|
+
lines.push(candidate);
|
|
1082
|
+
remaining = remaining.slice(candidate.length).trimStart();
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
if (remaining && lines.length > 0) {
|
|
1086
|
+
const last = lines.length - 1;
|
|
1087
|
+
lines[last] = `${truncateToWidth(lines[last], Math.max(0, width - 1)).trimEnd()}…`;
|
|
1088
|
+
}
|
|
1089
|
+
return lines.length ? lines : [""];
|
|
1090
|
+
}
|
|
1091
|
+
function renderInfoBox(fields, innerWidth, rows) {
|
|
1092
|
+
const title = " Info ";
|
|
1093
|
+
const topPad = Math.max(0, innerWidth - approxStringWidth(title));
|
|
1094
|
+
const top = `${S_BOX_TL}${S_BOX_H}${title}${S_BOX_H.repeat(topPad)}${S_BOX_TR}`;
|
|
1095
|
+
const bottom = `${S_BOX_BL}${S_BOX_H.repeat(innerWidth + 1)}${S_BOX_BR}`;
|
|
1096
|
+
const contentWidth = Math.max(12, innerWidth - 1);
|
|
1097
|
+
const labelW = 10;
|
|
1098
|
+
const valueW = Math.max(8, contentWidth - labelW - 1);
|
|
1099
|
+
const longLabels = /* @__PURE__ */ new Set([
|
|
1100
|
+
"about",
|
|
1101
|
+
"description",
|
|
1102
|
+
"desc",
|
|
1103
|
+
"note",
|
|
1104
|
+
"action"
|
|
1105
|
+
]);
|
|
1106
|
+
const expanded = [];
|
|
1107
|
+
for (const f of fields) {
|
|
1108
|
+
if (!f.label && !f.value) continue;
|
|
1109
|
+
if (longLabels.has(f.label.toLowerCase())) wrapPlain(f.value || "—", valueW, 4).forEach((line, idx) => {
|
|
1110
|
+
expanded.push({
|
|
1111
|
+
label: idx === 0 ? f.label : "",
|
|
1112
|
+
value: line,
|
|
1113
|
+
cont: idx > 0
|
|
1114
|
+
});
|
|
1115
|
+
});
|
|
1116
|
+
else expanded.push({
|
|
1117
|
+
label: f.label,
|
|
1118
|
+
value: truncateToWidth((f.value || "—").replace(/\s+/g, " ").trim(), valueW)
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
while (expanded.length > rows) expanded.pop();
|
|
1122
|
+
while (expanded.length < rows) expanded.push({
|
|
1123
|
+
label: "",
|
|
1124
|
+
value: ""
|
|
1125
|
+
});
|
|
1126
|
+
const body = expanded.map((r) => {
|
|
1127
|
+
if (!r.label && !r.value) return `${S_BOX_V} ${" ".repeat(contentWidth)} ${S_BOX_V}`;
|
|
1128
|
+
const content = `${r.label ? `${sky(padPlain(r.label, labelW))} ` : `${" ".repeat(labelW)} `}${r.cont ? c.dim(r.value) : c.bold(r.value)}`;
|
|
1129
|
+
const plainW = approxStringWidth(stripVTControlCharacters(content));
|
|
1130
|
+
const pad = Math.max(0, contentWidth - plainW);
|
|
1131
|
+
return `${S_BOX_V} ${content}${" ".repeat(pad)} ${S_BOX_V}`;
|
|
1132
|
+
});
|
|
1133
|
+
return [
|
|
1134
|
+
`${S_BAR} ${top}`,
|
|
1135
|
+
...body.map((b) => `${S_BAR} ${b}`),
|
|
1136
|
+
`${S_BAR} ${bottom}`
|
|
1137
|
+
];
|
|
1138
|
+
}
|
|
1139
|
+
function fieldsForItem(item, allSelected, total) {
|
|
1140
|
+
if (!item) return [{
|
|
1141
|
+
label: "Name",
|
|
1142
|
+
value: "—"
|
|
1143
|
+
}, {
|
|
1144
|
+
label: "About",
|
|
1145
|
+
value: "No item highlighted"
|
|
1146
|
+
}];
|
|
1147
|
+
if (String(item.value) === "__all__") return [
|
|
1148
|
+
{
|
|
1149
|
+
label: "Name",
|
|
1150
|
+
value: item.label
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
label: "About",
|
|
1154
|
+
value: allSelected ? `All ${total} items selected. Individual rows are locked until you turn All off.` : `Select every item in the list (${total}). Space toggles All; Enter confirms.`
|
|
1155
|
+
},
|
|
1156
|
+
{
|
|
1157
|
+
label: "Count",
|
|
1158
|
+
value: String(total)
|
|
1159
|
+
}
|
|
1160
|
+
];
|
|
1161
|
+
if (item.fields && item.fields.length) return item.fields;
|
|
1162
|
+
const desc = (item.detail || item.hint || "").replace(/\s+/g, " ").trim();
|
|
1163
|
+
return [
|
|
1164
|
+
{
|
|
1165
|
+
label: "Name",
|
|
1166
|
+
value: item.label
|
|
1167
|
+
},
|
|
1168
|
+
{
|
|
1169
|
+
label: "Id",
|
|
1170
|
+
value: String(item.value)
|
|
1171
|
+
},
|
|
1172
|
+
{
|
|
1173
|
+
label: "About",
|
|
1174
|
+
value: desc || "—"
|
|
1175
|
+
}
|
|
1176
|
+
];
|
|
1177
|
+
}
|
|
1178
|
+
async function searchMultiselect(options) {
|
|
1179
|
+
const { message, items, maxVisible = 8, initialSelected = [], required = false, lockedSection, searchable = true, showDetail = true, detailLines = 8, showSelectedSummary = true, includeAllOption = false, allLabel, allHint = "select every item · disables others" } = options;
|
|
1180
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1181
|
+
const uniqueItems = items.filter((item) => {
|
|
1182
|
+
const key = String(item.value);
|
|
1183
|
+
if (seen.has(key) || key === "__all__") return false;
|
|
1184
|
+
seen.add(key);
|
|
1185
|
+
return true;
|
|
1186
|
+
});
|
|
1187
|
+
const allItem = includeAllOption ? {
|
|
1188
|
+
value: ALL_VALUE,
|
|
1189
|
+
label: allLabel || `All (${uniqueItems.length})`,
|
|
1190
|
+
hint: allHint,
|
|
1191
|
+
fields: [
|
|
1192
|
+
{
|
|
1193
|
+
label: "Name",
|
|
1194
|
+
value: allLabel || `All (${uniqueItems.length})`
|
|
1195
|
+
},
|
|
1196
|
+
{
|
|
1197
|
+
label: "Action",
|
|
1198
|
+
value: "Install every listed item"
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
label: "Count",
|
|
1202
|
+
value: String(uniqueItems.length)
|
|
1203
|
+
},
|
|
1204
|
+
{
|
|
1205
|
+
label: "Note",
|
|
1206
|
+
value: "When All is on, other rows are disabled"
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
label: "",
|
|
1210
|
+
value: ""
|
|
1211
|
+
}
|
|
1212
|
+
]
|
|
1213
|
+
} : null;
|
|
1214
|
+
allItem && [...uniqueItems];
|
|
1215
|
+
return new Promise((resolve) => {
|
|
1216
|
+
const rl = readline.createInterface({
|
|
1217
|
+
input: process.stdin,
|
|
1218
|
+
output: silentOutput,
|
|
1219
|
+
terminal: false
|
|
1220
|
+
});
|
|
1221
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
1222
|
+
readline.emitKeypressEvents(process.stdin, rl);
|
|
1223
|
+
if (process.stdout.isTTY) process.stdout.write("\x1B[?25l");
|
|
1224
|
+
let query = "";
|
|
1225
|
+
let cursor = 0;
|
|
1226
|
+
const selected = new Set(initialSelected.filter((v) => String(v) !== ALL_VALUE));
|
|
1227
|
+
let lastRenderHeight = 0;
|
|
1228
|
+
let requiredFlash = false;
|
|
1229
|
+
const lockedValues = lockedSection ? lockedSection.items.map((i) => i.value) : [];
|
|
1230
|
+
const isAllOn = () => selected.has(ALL_VALUE);
|
|
1231
|
+
const getFiltered = () => {
|
|
1232
|
+
const rest = uniqueItems;
|
|
1233
|
+
if (!query) return allItem ? [allItem, ...rest] : rest;
|
|
1234
|
+
const lowerQ = query.toLowerCase();
|
|
1235
|
+
const matched = rest.filter((item) => item.label.toLowerCase().includes(lowerQ) || String(item.value).toLowerCase().includes(lowerQ) || item.hint && item.hint.toLowerCase().includes(lowerQ));
|
|
1236
|
+
return allItem && (allItem.label.toLowerCase().includes(lowerQ) || "all".includes(lowerQ)) && allItem ? [allItem, ...matched] : matched;
|
|
1237
|
+
};
|
|
1238
|
+
const lockedTotal = lockedSection ? lockedSection.items.length + (lockedSection.hiddenCount ?? 0) : 0;
|
|
1239
|
+
const selectedSummaryLine = (allOn) => {
|
|
1240
|
+
if (allOn) {
|
|
1241
|
+
const extra = lockedTotal ? `Universal (${lockedTotal}) + All additional (${uniqueItems.length})` : `All ${uniqueItems.length}`;
|
|
1242
|
+
return `${S_BAR} ${BLUE("Selected:")} ${c.bold(extra)}`;
|
|
1243
|
+
}
|
|
1244
|
+
const labels = uniqueItems.filter((item) => selected.has(item.value)).map((item) => item.label);
|
|
1245
|
+
if (labels.length === 0) {
|
|
1246
|
+
if (requiredFlash) return `${S_BAR} ${c.red("Selected: pick at least one")}`;
|
|
1247
|
+
if (lockedTotal > 0) return `${S_BAR} ${BLUE("Selected:")} ${c.bold(`Universal (${lockedTotal})`)}`;
|
|
1248
|
+
return `${S_BAR} ${c.dim("Selected: (none)")}`;
|
|
1249
|
+
}
|
|
1250
|
+
const prefix = lockedTotal > 0 ? `Universal (${lockedTotal}) + ` : "";
|
|
1251
|
+
const summary = labels.length <= 3 ? labels.join(", ") : `${labels.slice(0, 3).join(", ")} +${labels.length - 3} more`;
|
|
1252
|
+
return `${S_BAR} ${BLUE("Selected:")} ${prefix}${summary}`;
|
|
1253
|
+
};
|
|
1254
|
+
const submitSummary = (allOn) => {
|
|
1255
|
+
if (allOn) return lockedTotal ? `Universal (${lockedTotal}) + All additional (${uniqueItems.length})` : `All (${uniqueItems.length})`;
|
|
1256
|
+
const labels = uniqueItems.filter((item) => selected.has(item.value)).map((item) => item.label);
|
|
1257
|
+
if (labels.length === 0) return lockedTotal > 0 ? `Universal (${lockedTotal})` : "none";
|
|
1258
|
+
return (lockedTotal > 0 ? `Universal (${lockedTotal}) + ` : "") + (labels.length <= 5 ? labels.join(", ") : `${labels.slice(0, 5).join(", ")} +${labels.length - 5}`);
|
|
1259
|
+
};
|
|
1260
|
+
const clearPrevious = (rows) => {
|
|
1261
|
+
if (rows <= 0) return;
|
|
1262
|
+
for (let i = 0; i < rows; i++) process.stdout.write("\x1B[1A\x1B[2K");
|
|
1263
|
+
};
|
|
1264
|
+
const render = (state = "active") => {
|
|
1265
|
+
const lines = [];
|
|
1266
|
+
const filtered = getFiltered();
|
|
1267
|
+
const allOn = isAllOn();
|
|
1268
|
+
const icon = state === "active" ? S_STEP_ACTIVE : state === "cancel" ? S_STEP_CANCEL : S_STEP_SUBMIT;
|
|
1269
|
+
lines.push(`${icon} ${c.bold(message)}`);
|
|
1270
|
+
lines.push(`${S_BAR}`);
|
|
1271
|
+
if (state === "active") {
|
|
1272
|
+
if (lockedSection && lockedTotal > 0) {
|
|
1273
|
+
lines.push(`${S_BAR} ${S_BULLET} ${c.bold(lockedSection.title)} ${c.dim(`· ${lockedTotal} always included`)}`);
|
|
1274
|
+
lines.push(`${S_BAR} ${c.bold("Additional")}`);
|
|
1275
|
+
lines.push(`${S_BAR}`);
|
|
1276
|
+
}
|
|
1277
|
+
if (searchable) {
|
|
1278
|
+
lines.push(`${S_BAR} ${c.dim("Search:")} ${query}${c.inverse(" ")}`);
|
|
1279
|
+
lines.push(`${S_BAR} ${c.dim("↑↓ move · space select · enter confirm")}`);
|
|
1280
|
+
lines.push(`${S_BAR}`);
|
|
1281
|
+
}
|
|
1282
|
+
const visibleStart = Math.max(0, Math.min(cursor - Math.floor(maxVisible / 2), Math.max(0, filtered.length - maxVisible)));
|
|
1283
|
+
const visibleEnd = Math.min(filtered.length, visibleStart + maxVisible);
|
|
1284
|
+
const visible = filtered.slice(visibleStart, visibleEnd);
|
|
1285
|
+
if (filtered.length === 0) lines.push(`${S_BAR} ${c.dim("No matches found")}`);
|
|
1286
|
+
else {
|
|
1287
|
+
for (let i = 0; i < visible.length; i++) {
|
|
1288
|
+
const item = visible[i];
|
|
1289
|
+
const isCursor = visibleStart + i === cursor;
|
|
1290
|
+
const isAllRow = String(item.value) === ALL_VALUE;
|
|
1291
|
+
const disabled = allOn && !isAllRow;
|
|
1292
|
+
const isSelected = isAllRow ? allOn : allOn ? true : selected.has(item.value);
|
|
1293
|
+
let radio;
|
|
1294
|
+
if (disabled) radio = S_RADIO_DISABLED;
|
|
1295
|
+
else if (isSelected) radio = S_RADIO_ACTIVE;
|
|
1296
|
+
else radio = S_RADIO_INACTIVE;
|
|
1297
|
+
let label;
|
|
1298
|
+
if (disabled) label = c.dim(item.label);
|
|
1299
|
+
else if (isCursor) label = c.underline(BLUE_BOLD(item.label));
|
|
1300
|
+
else if (isAllRow) label = c.bold(item.label);
|
|
1301
|
+
else label = item.label;
|
|
1302
|
+
const hint = disabled || !item.hint ? "" : c.dim(` · ${item.hint.slice(0, 36)}`);
|
|
1303
|
+
const prefix = isCursor ? BLUE("❯") : " ";
|
|
1304
|
+
lines.push(`${S_BAR} ${prefix} ${radio} ${label}${hint}`);
|
|
1305
|
+
}
|
|
1306
|
+
const hiddenBefore = visibleStart;
|
|
1307
|
+
const hiddenAfter = filtered.length - visibleEnd;
|
|
1308
|
+
if (hiddenBefore > 0 || hiddenAfter > 0) {
|
|
1309
|
+
const parts = [];
|
|
1310
|
+
if (hiddenBefore > 0) parts.push(`↑ ${hiddenBefore} more`);
|
|
1311
|
+
if (hiddenAfter > 0) parts.push(`↓ ${hiddenAfter} more`);
|
|
1312
|
+
lines.push(`${S_BAR} ${c.dim(parts.join(" "))}`);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
if (showDetail) {
|
|
1316
|
+
const columns = process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
|
|
1317
|
+
const boxInner = Math.min(72, Math.max(48, columns - 6));
|
|
1318
|
+
const fields = fieldsForItem(filtered[cursor], allOn, uniqueItems.length);
|
|
1319
|
+
lines.push(`${S_BAR}`);
|
|
1320
|
+
lines.push(...renderInfoBox(fields, boxInner, detailLines));
|
|
1321
|
+
}
|
|
1322
|
+
if (showSelectedSummary) {
|
|
1323
|
+
lines.push(`${S_BAR}`);
|
|
1324
|
+
lines.push(selectedSummaryLine(allOn));
|
|
1325
|
+
}
|
|
1326
|
+
lines.push(`${S_CORNER}`);
|
|
1327
|
+
} else if (state === "submit") lines.push(`${S_BAR} ${c.dim(submitSummary(allOn))}`);
|
|
1328
|
+
else lines.push(`${S_BAR} ${c.strikethrough(c.dim("Cancelled"))}`);
|
|
1329
|
+
clearPrevious(lastRenderHeight);
|
|
1330
|
+
const out = lines.join("\n") + "\n";
|
|
1331
|
+
process.stdout.write(out);
|
|
1332
|
+
lastRenderHeight = countVisualRowsForLines(lines, process.stdout.columns);
|
|
1333
|
+
};
|
|
1334
|
+
const cleanup = () => {
|
|
1335
|
+
process.stdin.removeListener("keypress", keypressHandler);
|
|
1336
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
1337
|
+
if (process.stdout.isTTY) process.stdout.write("\x1B[?25h");
|
|
1338
|
+
rl.close();
|
|
1339
|
+
};
|
|
1340
|
+
const submit = () => {
|
|
1341
|
+
const allOn = isAllOn();
|
|
1342
|
+
if (required && !allOn && selected.size === 0 && lockedValues.length === 0) {
|
|
1343
|
+
requiredFlash = true;
|
|
1344
|
+
render();
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
render("submit");
|
|
1348
|
+
cleanup();
|
|
1349
|
+
if (allOn) resolve([...lockedValues, ...uniqueItems.map((i) => i.value)]);
|
|
1350
|
+
else resolve([...lockedValues, ...Array.from(selected).filter((v) => String(v) !== ALL_VALUE)]);
|
|
1351
|
+
};
|
|
1352
|
+
const cancel = () => {
|
|
1353
|
+
render("cancel");
|
|
1354
|
+
cleanup();
|
|
1355
|
+
resolve(cancelSymbol);
|
|
1356
|
+
};
|
|
1357
|
+
const keypressHandler = (_str, key) => {
|
|
1358
|
+
if (!key) return;
|
|
1359
|
+
const filtered = getFiltered();
|
|
1360
|
+
if (key.name === "return") {
|
|
1361
|
+
submit();
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
if (key.name === "escape" || key.ctrl && key.name === "c") {
|
|
1365
|
+
cancel();
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
if (key.name === "up") {
|
|
1369
|
+
cursor = Math.max(0, cursor - 1);
|
|
1370
|
+
requiredFlash = false;
|
|
1371
|
+
render();
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
if (key.name === "down") {
|
|
1375
|
+
cursor = Math.min(Math.max(0, filtered.length - 1), cursor + 1);
|
|
1376
|
+
requiredFlash = false;
|
|
1377
|
+
render();
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
if (key.name === "space") {
|
|
1381
|
+
const item = filtered[cursor];
|
|
1382
|
+
if (!item) return;
|
|
1383
|
+
if (String(item.value) === "__all__") if (isAllOn()) selected.delete(ALL_VALUE);
|
|
1384
|
+
else {
|
|
1385
|
+
selected.clear();
|
|
1386
|
+
selected.add(ALL_VALUE);
|
|
1387
|
+
}
|
|
1388
|
+
else if (isAllOn()) {
|
|
1389
|
+
requiredFlash = false;
|
|
1390
|
+
render();
|
|
1391
|
+
return;
|
|
1392
|
+
} else if (selected.has(item.value)) selected.delete(item.value);
|
|
1393
|
+
else selected.add(item.value);
|
|
1394
|
+
requiredFlash = false;
|
|
1395
|
+
render();
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
if (key.name === "backspace") {
|
|
1399
|
+
query = query.slice(0, -1);
|
|
1400
|
+
cursor = 0;
|
|
1401
|
+
render();
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
if (searchable && key.sequence && !key.ctrl && !key.meta && key.sequence.length === 1) {
|
|
1405
|
+
query += key.sequence;
|
|
1406
|
+
cursor = 0;
|
|
1407
|
+
render();
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
};
|
|
1411
|
+
process.stdin.on("keypress", keypressHandler);
|
|
1412
|
+
render();
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
function agentsPrompt(action) {
|
|
1416
|
+
if (action === "remove") return "Which agents do you want to remove from?";
|
|
1417
|
+
if (action === "update") return "Which agents do you want to update for?";
|
|
1418
|
+
return "Which agents do you want to install to?";
|
|
1419
|
+
}
|
|
1420
|
+
function isQuiet() {
|
|
1421
|
+
return isCI() || !process.stdout.isTTY;
|
|
1422
|
+
}
|
|
1423
|
+
function isCancelLike(res) {
|
|
1424
|
+
return res == null || res === cancelSymbol || clack.isCancel(res);
|
|
1425
|
+
}
|
|
1426
|
+
function intro() {
|
|
1427
|
+
if (isQuiet()) return;
|
|
1428
|
+
clack.intro(badge());
|
|
1429
|
+
}
|
|
1430
|
+
function outro(message) {
|
|
1431
|
+
if (isQuiet()) {
|
|
1432
|
+
process.stdout.write(message + "\n");
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
clack.outro(message);
|
|
1436
|
+
}
|
|
1437
|
+
async function confirm(message) {
|
|
1438
|
+
if (isQuiet()) return true;
|
|
1439
|
+
return new Promise((resolve) => {
|
|
1440
|
+
if (!process.stdin.isTTY) {
|
|
1441
|
+
resolve(true);
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
const rl = readline.createInterface({
|
|
1445
|
+
input: process.stdin,
|
|
1446
|
+
output: process.stdout
|
|
1447
|
+
});
|
|
1448
|
+
process.stdin.setRawMode(true);
|
|
1449
|
+
readline.emitKeypressEvents(process.stdin, rl);
|
|
1450
|
+
let value = true;
|
|
1451
|
+
let lastH = 0;
|
|
1452
|
+
const paint = (done) => {
|
|
1453
|
+
const lines = [];
|
|
1454
|
+
if (done === "cancel") {
|
|
1455
|
+
lines.push(`${sky("■")} ${c.bold(message)}`);
|
|
1456
|
+
lines.push(`${c.dim("│")} ${c.strikethrough(c.dim("Cancelled"))}`);
|
|
1457
|
+
} else if (done === "yes" || done === "no") {
|
|
1458
|
+
lines.push(`${sky("◇")} ${c.bold(message)}`);
|
|
1459
|
+
lines.push(`${c.dim("│")} ${c.dim(done === "yes" ? "Yes" : "No")}`);
|
|
1460
|
+
} else {
|
|
1461
|
+
lines.push(`${sky("◆")} ${c.bold(message)}`);
|
|
1462
|
+
lines.push(`${c.dim("│")}`);
|
|
1463
|
+
const yes = value ? sky(c.bold("● Yes")) : c.dim("○ Yes");
|
|
1464
|
+
const no = !value ? sky(c.bold("● No")) : c.dim("○ No");
|
|
1465
|
+
lines.push(`${c.dim("│")} ${yes} / ${no}`);
|
|
1466
|
+
lines.push(`${c.dim("└")} ${c.dim("←/→ toggle · enter confirm")}`);
|
|
1467
|
+
}
|
|
1468
|
+
if (lastH > 0) for (let i = 0; i < lastH; i++) process.stdout.write("\x1B[1A\x1B[2K");
|
|
1469
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
1470
|
+
lastH = lines.length;
|
|
1471
|
+
};
|
|
1472
|
+
const cleanup = () => {
|
|
1473
|
+
process.stdin.removeListener("keypress", onKey);
|
|
1474
|
+
process.stdin.setRawMode(false);
|
|
1475
|
+
rl.close();
|
|
1476
|
+
};
|
|
1477
|
+
const onKey = (_s, key) => {
|
|
1478
|
+
if (!key) return;
|
|
1479
|
+
if (key.name === "left" || key.name === "right" || key.name === "tab") {
|
|
1480
|
+
value = !value;
|
|
1481
|
+
paint();
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
if (key.name === "y") {
|
|
1485
|
+
value = true;
|
|
1486
|
+
paint();
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
if (key.name === "n") {
|
|
1490
|
+
value = false;
|
|
1491
|
+
paint();
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
if (key.name === "return") {
|
|
1495
|
+
paint(value ? "yes" : "no");
|
|
1496
|
+
cleanup();
|
|
1497
|
+
resolve(value);
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
if (key.name === "escape" || key.ctrl && key.name === "c") {
|
|
1501
|
+
paint("cancel");
|
|
1502
|
+
cleanup();
|
|
1503
|
+
resolve(false);
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
process.stdin.on("keypress", onKey);
|
|
1507
|
+
paint();
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
function log(message) {
|
|
1511
|
+
process.stdout.write(message + "\n");
|
|
1512
|
+
}
|
|
1513
|
+
function blank() {
|
|
1514
|
+
process.stdout.write("\n");
|
|
1515
|
+
}
|
|
1516
|
+
async function sectionTransition(doneLabel, nextLabel) {
|
|
1517
|
+
if (isQuiet()) return;
|
|
1518
|
+
blank();
|
|
1519
|
+
log(`${sky("◇")} ${c.dim("Done:")} ${c.bold(doneLabel)}`);
|
|
1520
|
+
await delay(320);
|
|
1521
|
+
for (const ch of [
|
|
1522
|
+
"│",
|
|
1523
|
+
"│",
|
|
1524
|
+
"↓"
|
|
1525
|
+
]) {
|
|
1526
|
+
process.stdout.write(`${c.dim(` ${ch}`)}\n`);
|
|
1527
|
+
await delay(140);
|
|
1528
|
+
}
|
|
1529
|
+
await delay(200);
|
|
1530
|
+
log(`${sky("◆")} ${c.bold("Next:")} ${nextLabel}`);
|
|
1531
|
+
await delay(420);
|
|
1532
|
+
blank();
|
|
1533
|
+
}
|
|
1534
|
+
function step(message) {
|
|
1535
|
+
process.stdout.write(` ${sky("→")} ${message}\n`);
|
|
1536
|
+
}
|
|
1537
|
+
function ok(message) {
|
|
1538
|
+
process.stdout.write(`${sky("✓")} ${message}\n`);
|
|
1539
|
+
}
|
|
1540
|
+
function err(message) {
|
|
1541
|
+
process.stdout.write(`${c.red("✗")} ${message}\n`);
|
|
1542
|
+
}
|
|
1543
|
+
function warn(message) {
|
|
1544
|
+
process.stdout.write(`${c.yellow("⚠")} ${message}\n`);
|
|
1545
|
+
}
|
|
1546
|
+
function info(message) {
|
|
1547
|
+
process.stdout.write(`${sky("ℹ")} ${message}\n`);
|
|
1548
|
+
}
|
|
1549
|
+
async function spinner(startMsg, fn, stopWith, minMs = 480) {
|
|
1550
|
+
if (isQuiet()) {
|
|
1551
|
+
process.stdout.write(c.dim(`• ${startMsg}...`) + "\n");
|
|
1552
|
+
const r = await fn();
|
|
1553
|
+
if (stopWith) process.stdout.write(`${sky("◇")} ${stopWith(r)}\n`);
|
|
1554
|
+
return r;
|
|
1555
|
+
}
|
|
1556
|
+
const frames = [
|
|
1557
|
+
"⠋",
|
|
1558
|
+
"⠙",
|
|
1559
|
+
"⠹",
|
|
1560
|
+
"⠸",
|
|
1561
|
+
"⠼",
|
|
1562
|
+
"⠴",
|
|
1563
|
+
"⠦",
|
|
1564
|
+
"⠧",
|
|
1565
|
+
"⠇",
|
|
1566
|
+
"⠏"
|
|
1567
|
+
];
|
|
1568
|
+
let i = 0;
|
|
1569
|
+
let alive = true;
|
|
1570
|
+
process.stdout.write(`${sky(frames[0])} ${startMsg}`);
|
|
1571
|
+
const tick = setInterval(() => {
|
|
1572
|
+
if (!alive) return;
|
|
1573
|
+
i = (i + 1) % frames.length;
|
|
1574
|
+
process.stdout.write(`\r${sky(frames[i])} ${startMsg} `);
|
|
1575
|
+
}, 80);
|
|
1576
|
+
const started = Date.now();
|
|
1577
|
+
try {
|
|
1578
|
+
const result = await fn();
|
|
1579
|
+
const wait = Math.max(0, minMs - (Date.now() - started));
|
|
1580
|
+
if (wait) await delay(wait);
|
|
1581
|
+
alive = false;
|
|
1582
|
+
clearInterval(tick);
|
|
1583
|
+
const msg = stopWith ? stopWith(result) : startMsg;
|
|
1584
|
+
process.stdout.write(`\r\x1b[K${sky("◇")} ${msg}\n`);
|
|
1585
|
+
return result;
|
|
1586
|
+
} catch (e) {
|
|
1587
|
+
alive = false;
|
|
1588
|
+
clearInterval(tick);
|
|
1589
|
+
process.stdout.write(`\r\x1b[K${c.red("■")} ${startMsg}\n`);
|
|
1590
|
+
throw e;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
function artifactFields(a) {
|
|
1594
|
+
return [
|
|
1595
|
+
{
|
|
1596
|
+
label: "Name",
|
|
1597
|
+
value: a.name
|
|
1598
|
+
},
|
|
1599
|
+
{
|
|
1600
|
+
label: "Kind",
|
|
1601
|
+
value: a.kind
|
|
1602
|
+
},
|
|
1603
|
+
{
|
|
1604
|
+
label: "Id",
|
|
1605
|
+
value: a.id
|
|
1606
|
+
},
|
|
1607
|
+
{
|
|
1608
|
+
label: "About",
|
|
1609
|
+
value: a.description ? a.description.replace(/\s+/g, " ").trim() : "No description provided."
|
|
1610
|
+
},
|
|
1611
|
+
{
|
|
1612
|
+
label: "Path",
|
|
1613
|
+
value: a.dir
|
|
1614
|
+
}
|
|
1615
|
+
];
|
|
1616
|
+
}
|
|
1617
|
+
function agentFields(a, installed) {
|
|
1618
|
+
return [
|
|
1619
|
+
{
|
|
1620
|
+
label: "Name",
|
|
1621
|
+
value: a.displayName
|
|
1622
|
+
},
|
|
1623
|
+
{
|
|
1624
|
+
label: "Id",
|
|
1625
|
+
value: a.name
|
|
1626
|
+
},
|
|
1627
|
+
{
|
|
1628
|
+
label: "Project",
|
|
1629
|
+
value: a.skillsDir
|
|
1630
|
+
},
|
|
1631
|
+
{
|
|
1632
|
+
label: "Global",
|
|
1633
|
+
value: a.globalSkillsDir || "—"
|
|
1634
|
+
},
|
|
1635
|
+
{
|
|
1636
|
+
label: "About",
|
|
1637
|
+
value: [
|
|
1638
|
+
a.skillsDir === ".agents/skills" ? "Universal provider (.agents/skills)." : "Provider-specific install path.",
|
|
1639
|
+
installed ? "Detected on this machine." : "Not detected on this machine.",
|
|
1640
|
+
a.globalSkillsDir ? `Global skills: ${a.globalSkillsDir}` : ""
|
|
1641
|
+
].filter(Boolean).join(" ")
|
|
1642
|
+
}
|
|
1643
|
+
];
|
|
1644
|
+
}
|
|
1645
|
+
async function selectArtifacts(message, artifacts) {
|
|
1646
|
+
if (isQuiet() || artifacts.length <= 1) return artifacts;
|
|
1647
|
+
blank();
|
|
1648
|
+
const res = await searchMultiselect({
|
|
1649
|
+
message,
|
|
1650
|
+
items: artifacts.map((a) => ({
|
|
1651
|
+
value: a.id,
|
|
1652
|
+
label: a.id,
|
|
1653
|
+
hint: a.description ? a.description.replace(/\s+/g, " ").slice(0, 42) : a.kind,
|
|
1654
|
+
fields: artifactFields(a)
|
|
1655
|
+
})),
|
|
1656
|
+
maxVisible: 8,
|
|
1657
|
+
required: true,
|
|
1658
|
+
searchable: true,
|
|
1659
|
+
showDetail: true,
|
|
1660
|
+
detailLines: 8,
|
|
1661
|
+
showSelectedSummary: true,
|
|
1662
|
+
includeAllOption: true,
|
|
1663
|
+
allLabel: `All (${artifacts.length})`,
|
|
1664
|
+
allHint: "every skill below · disables others"
|
|
1665
|
+
});
|
|
1666
|
+
if (res === cancelSymbol) return null;
|
|
1667
|
+
blank();
|
|
1668
|
+
const ids = new Set(res);
|
|
1669
|
+
return artifacts.filter((a) => ids.has(a.id));
|
|
1670
|
+
}
|
|
1671
|
+
async function selectAgents(message, universal, others, detectedNames = []) {
|
|
1672
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1673
|
+
const uniqueOthers = others.filter((o) => {
|
|
1674
|
+
if (seen.has(o.name)) return false;
|
|
1675
|
+
seen.add(o.name);
|
|
1676
|
+
return true;
|
|
1677
|
+
});
|
|
1678
|
+
const detected = uniqueOthers.filter((o) => detectedNames.includes(o.name));
|
|
1679
|
+
if (isQuiet()) return [...universal, ...detected];
|
|
1680
|
+
if (!uniqueOthers.length) return universal;
|
|
1681
|
+
blank();
|
|
1682
|
+
const installedSet = new Set(detectedNames);
|
|
1683
|
+
const items = uniqueOthers.map((a) => ({
|
|
1684
|
+
value: a.name,
|
|
1685
|
+
label: a.displayName,
|
|
1686
|
+
hint: installedSet.has(a.name) ? `${a.skillsDir} · detected` : a.skillsDir,
|
|
1687
|
+
fields: agentFields(a, installedSet.has(a.name))
|
|
1688
|
+
}));
|
|
1689
|
+
const locked = universal.length > 0 ? {
|
|
1690
|
+
title: "Universal",
|
|
1691
|
+
items: universal.map((a) => ({
|
|
1692
|
+
value: a.name,
|
|
1693
|
+
label: a.displayName,
|
|
1694
|
+
fields: agentFields(a, true)
|
|
1695
|
+
})),
|
|
1696
|
+
compact: true
|
|
1697
|
+
} : void 0;
|
|
1698
|
+
const res = await searchMultiselect({
|
|
1699
|
+
message,
|
|
1700
|
+
items,
|
|
1701
|
+
maxVisible: 5,
|
|
1702
|
+
required: false,
|
|
1703
|
+
initialSelected: detected.map((d) => d.name),
|
|
1704
|
+
lockedSection: locked,
|
|
1705
|
+
searchable: true,
|
|
1706
|
+
showDetail: true,
|
|
1707
|
+
detailLines: 4,
|
|
1708
|
+
showSelectedSummary: true,
|
|
1709
|
+
includeAllOption: true,
|
|
1710
|
+
allLabel: `All additional (${uniqueOthers.length})`,
|
|
1711
|
+
allHint: "every provider below · disables others"
|
|
1712
|
+
});
|
|
1713
|
+
if (res === cancelSymbol) return null;
|
|
1714
|
+
blank();
|
|
1715
|
+
const picked = new Set(res);
|
|
1716
|
+
const byName = new Map([...universal, ...uniqueOthers].map((a) => [a.name, a]));
|
|
1717
|
+
const out = [];
|
|
1718
|
+
for (const name of picked) {
|
|
1719
|
+
const a = byName.get(name);
|
|
1720
|
+
if (a) out.push(a);
|
|
1721
|
+
}
|
|
1722
|
+
for (const u of universal) if (!out.some((a) => a.name === u.name)) out.unshift(u);
|
|
1723
|
+
return out;
|
|
1724
|
+
}
|
|
1725
|
+
function installSummary(artifacts, agents, scopeLabel) {
|
|
1726
|
+
blank();
|
|
1727
|
+
const bar = c.dim("│");
|
|
1728
|
+
const corner = c.dim("└");
|
|
1729
|
+
log(`${sky("◆")} ${c.bold("Install summary")}`);
|
|
1730
|
+
log(bar);
|
|
1731
|
+
log(`${bar} ${sky("Scope")} ${scopeLabel}`);
|
|
1732
|
+
const universalCount = agents.filter((a) => a.skillsDir === ".agents/skills").length;
|
|
1733
|
+
const otherCount = agents.length - universalCount;
|
|
1734
|
+
log(`${bar} ${sky("Targets")} ${agents.length} agent${agents.length === 1 ? "" : "s"}` + c.dim(` · ${universalCount} universal` + (otherCount ? ` · ${otherCount} additional` : "")));
|
|
1735
|
+
const names = agents.map((a) => a.displayName);
|
|
1736
|
+
const chunks = [];
|
|
1737
|
+
let line = "";
|
|
1738
|
+
for (const n of names) {
|
|
1739
|
+
const next = line ? `${line}, ${n}` : n;
|
|
1740
|
+
if (next.length > 56) {
|
|
1741
|
+
if (line) chunks.push(line);
|
|
1742
|
+
line = n;
|
|
1743
|
+
} else line = next;
|
|
1744
|
+
}
|
|
1745
|
+
if (line) chunks.push(line);
|
|
1746
|
+
for (const ch of chunks.slice(0, 3)) log(`${bar} ${c.dim(ch)}`);
|
|
1747
|
+
if (chunks.length > 3) log(`${bar} ${c.dim(`… +${chunks.length - 3} more lines`)}`);
|
|
1748
|
+
log(bar);
|
|
1749
|
+
log(`${bar} ${c.bold("Artifacts")} ${artifacts.length} selected`);
|
|
1750
|
+
log(bar);
|
|
1751
|
+
for (const a of artifacts) {
|
|
1752
|
+
const desc = (a.description || "").replace(/\s+/g, " ").trim().slice(0, 52);
|
|
1753
|
+
log(`${bar} ${sky("•")} ${c.bold(`${a.kind}/${a.id}`)}`);
|
|
1754
|
+
if (desc) log(`${bar} ${c.dim(desc)}`);
|
|
1755
|
+
}
|
|
1756
|
+
log(bar);
|
|
1757
|
+
log(`${corner} ${c.dim("Review before use — artifacts run with full agent permissions.")}`);
|
|
1758
|
+
blank();
|
|
1759
|
+
}
|
|
1760
|
+
function preview(artifacts, agents) {
|
|
1761
|
+
installSummary(artifacts, agents, "preview (dry-run)");
|
|
1762
|
+
}
|
|
1763
|
+
async function resolveSourceWithUi(source, opts) {
|
|
1764
|
+
const resolved = await resolveSource(source, { confirmCache: async ({ url, error }) => {
|
|
1765
|
+
warn(`git clone failed for ${url}`);
|
|
1766
|
+
if (error) step(c.dim(error.split("\n")[0] || error));
|
|
1767
|
+
if (opts.yes || isQuiet()) {
|
|
1768
|
+
info("Using cached copy.");
|
|
1769
|
+
return true;
|
|
1770
|
+
}
|
|
1771
|
+
return confirm("Load from cache instead?");
|
|
1772
|
+
} });
|
|
1773
|
+
if (resolved.fromCache) info("Loaded source from cache.");
|
|
1774
|
+
return resolved;
|
|
1775
|
+
}
|
|
1776
|
+
async function cmdAdd(kind, source, selector, opts) {
|
|
1777
|
+
if (!source) throw new Error(`Missing source. e.g. agentry add ${kind} author/repo`);
|
|
1778
|
+
intro();
|
|
1779
|
+
await delayQuiet(180);
|
|
1780
|
+
await spinner("Parsing source…", () => parseSourceForUi(source), (p) => {
|
|
1781
|
+
const where = p.url || p.local || source;
|
|
1782
|
+
return `${theme.primary("Source:")} ${where}${p.ref ? c.dim(` @ ${p.ref}`) : ""}${p.subpath ? c.dim(` (${p.subpath})`) : ""}`;
|
|
1783
|
+
}, 520);
|
|
1784
|
+
await delayQuiet(280);
|
|
1785
|
+
const { root, cleanup } = await resolveSourceWithUi(source, opts);
|
|
1786
|
+
try {
|
|
1787
|
+
const artifacts = await spinner("Discovering…", () => kind === "all" ? listAll(root) : select(root, kind, selector), (a) => {
|
|
1788
|
+
const noun = kind === "all" ? "artifact" : kind;
|
|
1789
|
+
return `Found ${a.length} ${noun}${a.length === 1 ? "" : "s"}`;
|
|
1790
|
+
}, 620);
|
|
1791
|
+
if (!artifacts.length) {
|
|
1792
|
+
outro(c.red(`Nothing found for "${kind}${selector ? " " + selector : ""}" in ${source}.`));
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
await delayQuiet(320);
|
|
1796
|
+
blank();
|
|
1797
|
+
if (opts.dryRun) {
|
|
1798
|
+
preview(artifacts, await resolveAgentList(opts, "install"));
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
let chosen = artifacts;
|
|
1802
|
+
if (!selector && kind !== "all" && artifacts.length > 1 && !opts.all && !opts.yes && !isQuiet()) {
|
|
1803
|
+
await sectionTransition("source discovery", `select ${kind}s to install`);
|
|
1804
|
+
const picked = await selectArtifacts(`Select ${kind}s to install`, artifacts);
|
|
1805
|
+
if (isCancelLike(picked)) {
|
|
1806
|
+
outro("Cancelled.");
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
chosen = picked;
|
|
1810
|
+
await sectionTransition(`${chosen.length} ${kind}${chosen.length === 1 ? "" : "s"} selected`, "choose target agents");
|
|
1811
|
+
} else if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition(`${chosen.length} ${kind}${chosen.length === 1 ? "" : "s"} ready`, "choose target agents");
|
|
1812
|
+
else {
|
|
1813
|
+
await delayQuiet(200);
|
|
1814
|
+
blank();
|
|
1815
|
+
}
|
|
1816
|
+
const agentList = await resolveAgentList(opts, "install");
|
|
1817
|
+
if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition(`${agentList.length} agent${agentList.length === 1 ? "" : "s"} selected`, "install summary");
|
|
1818
|
+
installAll(chosen, opts, agentList, source);
|
|
1819
|
+
} finally {
|
|
1820
|
+
cleanup();
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
async function delayQuiet(ms) {
|
|
1824
|
+
if (isQuiet()) return;
|
|
1825
|
+
const { delay } = await import("./_chunks/theme.mjs");
|
|
1826
|
+
await delay(ms);
|
|
1827
|
+
}
|
|
1828
|
+
function parseSourceForUi(source) {
|
|
1829
|
+
return parseSource(source);
|
|
1830
|
+
}
|
|
1831
|
+
async function cmdAddProfile(name, source, opts) {
|
|
1832
|
+
const { file, profile } = loadProfile(name);
|
|
1833
|
+
const repo = source || (profile.artifacts.skills[0]?.source ?? profile.artifacts.rules[0]?.source);
|
|
1834
|
+
if (!repo) throw new Error(`No source given and ${file} lists none. Try: agentry add profile ${name} author/repo`);
|
|
1835
|
+
const agentList = await resolveAgentList(opts, "install");
|
|
1836
|
+
const { root, cleanup } = await resolveSourceWithUi(repo, opts);
|
|
1837
|
+
try {
|
|
1838
|
+
const artifacts = [];
|
|
1839
|
+
const plural = (k) => k === "skill" ? "skills" : k === "rule" ? "rules" : k === "script" ? "scripts" : "agents";
|
|
1840
|
+
for (const k of ARTIFACT_KINDS) {
|
|
1841
|
+
if (k === "profile") continue;
|
|
1842
|
+
const refs = profile.artifacts[plural(k)] ?? [];
|
|
1843
|
+
for (const ref of refs) {
|
|
1844
|
+
const src = ref.source ? await resolveSourceWithUi(ref.source, opts) : {
|
|
1845
|
+
root,
|
|
1846
|
+
cleanup: () => {}
|
|
1847
|
+
};
|
|
1848
|
+
artifacts.push(...select(src.root, k, ref.id));
|
|
1849
|
+
if (ref.source) src.cleanup();
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
if (opts.dryRun) {
|
|
1853
|
+
preview(artifacts, agentList);
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
installAll(artifacts, opts, agentList, repo);
|
|
1857
|
+
} finally {
|
|
1858
|
+
cleanup();
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
async function resolveAgentList(opts, action = "install") {
|
|
1862
|
+
if (opts.all) return listAgents();
|
|
1863
|
+
if (opts.agents.length) {
|
|
1864
|
+
const { agents, unknown } = resolveAgents(opts.agents);
|
|
1865
|
+
if (unknown.length) throw new Error(`Unknown agent(s): ${unknown.join(", ")}. See --help for supported agents.`);
|
|
1866
|
+
return agents;
|
|
1867
|
+
}
|
|
1868
|
+
const detected = detectInstalledAgents().map((a) => a.name);
|
|
1869
|
+
const universal = getUniversalAgents();
|
|
1870
|
+
const visibleUniversal = getVisibleUniversalAgents();
|
|
1871
|
+
const others = getNonUniversalAgents();
|
|
1872
|
+
const detectedOthers = detected.filter((n) => others.some((o) => o.name === n));
|
|
1873
|
+
if (isQuiet() || opts.yes) {
|
|
1874
|
+
const picked = detectedOthers.length ? detectedOthers : ["cursor"];
|
|
1875
|
+
return [...universal, ...others.filter((o) => picked.includes(o.name))];
|
|
1876
|
+
}
|
|
1877
|
+
const selected = await selectAgents(agentsPrompt(action), visibleUniversal, others, detectedOthers);
|
|
1878
|
+
if (isCancelLike(selected)) throw new Error("Cancelled.");
|
|
1879
|
+
return selected;
|
|
1880
|
+
}
|
|
1881
|
+
function installAll(artifacts, opts, agentList, source) {
|
|
1882
|
+
if (!artifacts.length) {
|
|
1883
|
+
warn("Nothing matched - nothing installed.");
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1886
|
+
const where = opts.scope === "global" ? "global (~)" : `project (${opts.dir || process.cwd()})`;
|
|
1887
|
+
for (const a of artifacts) installOne(a, toInstallOpts(opts), agentList, source);
|
|
1888
|
+
installSummary(artifacts, agentList, where);
|
|
1889
|
+
}
|
|
1890
|
+
function toInstallOpts(opts) {
|
|
1891
|
+
return {
|
|
1892
|
+
scope: opts.scope,
|
|
1893
|
+
dir: opts.dir,
|
|
1894
|
+
agents: opts.agents,
|
|
1895
|
+
copy: opts.copy,
|
|
1896
|
+
dryRun: opts.dryRun
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
function resolveSourceOrSelector(arg1, arg2, opts) {
|
|
1900
|
+
if (arg1 && arg2) return {
|
|
1901
|
+
source: arg1,
|
|
1902
|
+
selector: arg2
|
|
1903
|
+
};
|
|
1904
|
+
if (!arg1) return {};
|
|
1905
|
+
if (isExplicitSource(arg1)) return { source: arg1 };
|
|
1906
|
+
if (isOwnerRepoShape(arg1)) {
|
|
1907
|
+
const lock = readLock(toInstallOpts(opts));
|
|
1908
|
+
if (Object.values(lock.items).some((e) => e.source && sourcesEqual(e.source, arg1))) return { source: arg1 };
|
|
1909
|
+
}
|
|
1910
|
+
return { selector: arg1 };
|
|
1911
|
+
}
|
|
1912
|
+
async function cmdRemove(kind, arg1, arg2, opts) {
|
|
1913
|
+
intro();
|
|
1914
|
+
const { source, selector } = resolveSourceOrSelector(arg1, arg2, opts);
|
|
1915
|
+
if (source) await spinner("Resolving source…", () => parseSource(source), (p) => {
|
|
1916
|
+
const where = p.url || p.local || source;
|
|
1917
|
+
return `${theme.primary("Source:")} ${where}${p.ref ? c.dim(` @ ${p.ref}`) : ""}`;
|
|
1918
|
+
}, 420);
|
|
1919
|
+
if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition("prepare remove", "choose agents to remove from");
|
|
1920
|
+
const agentList = await resolveAgentList(opts, "remove");
|
|
1921
|
+
const installOpts = toInstallOpts(opts);
|
|
1922
|
+
const scope = [
|
|
1923
|
+
kind,
|
|
1924
|
+
source,
|
|
1925
|
+
selector
|
|
1926
|
+
].filter(Boolean).join(" / ");
|
|
1927
|
+
if (kind === "all") {
|
|
1928
|
+
let count = 0;
|
|
1929
|
+
let paths = 0;
|
|
1930
|
+
for (const t of ARTIFACT_KINDS) {
|
|
1931
|
+
const r = removeSelection(t, void 0, installOpts, agentList, source);
|
|
1932
|
+
count += r.removedKeys.length;
|
|
1933
|
+
paths += r.removedPaths.length;
|
|
1934
|
+
}
|
|
1935
|
+
ok(`Removed ${count} item(s)${paths ? c.dim(` · ${paths} path(s)`) : ""}${source ? c.dim(` from ${source}`) : ""}.`);
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
const r = removeSelection(kind, selector, installOpts, agentList, source);
|
|
1939
|
+
if (!r.existed) {
|
|
1940
|
+
warn(`Nothing installed matching "${scope}".`);
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
for (const k of r.removedKeys) step(`removed ${k}`);
|
|
1944
|
+
for (const p of r.removedPaths.slice(0, 12)) step(c.dim(`deleted ${p}`));
|
|
1945
|
+
if (r.removedPaths.length > 12) step(c.dim(`…and ${r.removedPaths.length - 12} more paths`));
|
|
1946
|
+
ok(`Removed ${r.removedKeys.length} item(s)${r.removedPaths.length ? c.dim(` · ${r.removedPaths.length} path(s)`) : ""}${source ? c.dim(` from ${source}`) : ""}.`);
|
|
1947
|
+
}
|
|
1948
|
+
async function cmdList(source, kind, selector, opts) {
|
|
1949
|
+
if (!source) {
|
|
1950
|
+
cmdListInstalled(opts, kind, selector);
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
intro();
|
|
1954
|
+
await spinner("Parsing source…", () => parseSource(source), (p) => {
|
|
1955
|
+
const where = p.url || p.local || source;
|
|
1956
|
+
return `${theme.primary("Source:")} ${where}${p.ref ? c.dim(` @ ${p.ref}`) : ""}${p.subpath ? c.dim(` (${p.subpath})`) : ""}`;
|
|
1957
|
+
}, 420);
|
|
1958
|
+
const { root, cleanup } = await resolveSourceWithUi(source, opts);
|
|
1959
|
+
try {
|
|
1960
|
+
printArtifactList(await spinner("Discovering…", () => {
|
|
1961
|
+
if (kind) return select(root, kind, selector);
|
|
1962
|
+
return listAll(root);
|
|
1963
|
+
}, (a) => `Found ${a.length} artifact${a.length === 1 ? "" : "s"}`, 520), {
|
|
1964
|
+
title: "Available in source",
|
|
1965
|
+
subtitle: source
|
|
1966
|
+
});
|
|
1967
|
+
} finally {
|
|
1968
|
+
cleanup();
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
function cmdListInstalled(opts, kind, selector) {
|
|
1972
|
+
intro();
|
|
1973
|
+
let installed = listInstalled(toInstallOpts(opts));
|
|
1974
|
+
if (kind) installed = installed.filter((it) => it.kind === kind || it.id.startsWith(kind + "/"));
|
|
1975
|
+
if (selector && kind) {
|
|
1976
|
+
const rel = `${kind}/${selector}`;
|
|
1977
|
+
installed = installed.filter((it) => it.id === rel || it.id.startsWith(rel + "/") || it.id.endsWith("/" + selector) || it.id === `${kind}/${selector}`);
|
|
1978
|
+
} else if (selector) installed = installed.filter((it) => it.id.includes(selector));
|
|
1979
|
+
if (!installed.length) {
|
|
1980
|
+
info(kind || selector ? `Nothing installed matching that filter.` : "Nothing installed.");
|
|
1981
|
+
step(c.dim("Try: agentry add skills mhmdreza-rafiei/agent-tools"));
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
const bar = c.dim("│");
|
|
1985
|
+
const corner = c.dim("└");
|
|
1986
|
+
blank();
|
|
1987
|
+
log(`${sky("◆")} ${c.bold("Installed")}`);
|
|
1988
|
+
log(bar);
|
|
1989
|
+
log(`${bar} ${sky("Scope")} ${opts.scope === "global" ? "global (~)" : `project (${opts.dir || process.cwd()})`}`);
|
|
1990
|
+
log(`${bar} ${sky("Total")} ${installed.length} artifact${installed.length === 1 ? "" : "s"}`);
|
|
1991
|
+
log(bar);
|
|
1992
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
1993
|
+
for (const it of installed) {
|
|
1994
|
+
const k = it.kind || it.id.split("/")[0];
|
|
1995
|
+
const arr = byKind.get(k) || [];
|
|
1996
|
+
arr.push(it);
|
|
1997
|
+
byKind.set(k, arr);
|
|
1998
|
+
}
|
|
1999
|
+
for (const k of ARTIFACT_KINDS) {
|
|
2000
|
+
const items = byKind.get(k);
|
|
2001
|
+
if (!items?.length) continue;
|
|
2002
|
+
log(`${bar} ${c.bold(k + "s")} ${c.dim(`(${items.length})`)}`);
|
|
2003
|
+
for (const it of items) {
|
|
2004
|
+
const id = it.id.includes("/") ? it.id.slice(it.id.indexOf("/") + 1) : it.id;
|
|
2005
|
+
log(`${bar} ${sky("•")} ${c.bold(id)}`);
|
|
2006
|
+
const agents = it.agents?.length ? it.agents.join(", ") : "—";
|
|
2007
|
+
log(`${bar} ${c.dim(`agents: ${agents}`)}`);
|
|
2008
|
+
if (it.source) log(`${bar} ${c.dim(`source: ${it.source}`)}`);
|
|
2009
|
+
}
|
|
2010
|
+
log(bar);
|
|
2011
|
+
}
|
|
2012
|
+
log(`${corner} ${c.dim("agentry remove <kind> [source] [selector] · agentry update <kind> [source]")}`);
|
|
2013
|
+
blank();
|
|
2014
|
+
}
|
|
2015
|
+
function printArtifactList(artifacts, meta) {
|
|
2016
|
+
const bar = c.dim("│");
|
|
2017
|
+
const corner = c.dim("└");
|
|
2018
|
+
blank();
|
|
2019
|
+
log(`${sky("◆")} ${c.bold(meta.title)}`);
|
|
2020
|
+
log(bar);
|
|
2021
|
+
if (meta.subtitle) log(`${bar} ${sky("From")} ${meta.subtitle}`);
|
|
2022
|
+
log(`${bar} ${sky("Total")} ${artifacts.length}`);
|
|
2023
|
+
log(bar);
|
|
2024
|
+
if (!artifacts.length) {
|
|
2025
|
+
log(`${bar} ${c.dim("(none)")}`);
|
|
2026
|
+
log(bar);
|
|
2027
|
+
log(`${corner}`);
|
|
2028
|
+
blank();
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
2032
|
+
for (const a of artifacts) {
|
|
2033
|
+
const arr = byKind.get(a.kind) || [];
|
|
2034
|
+
arr.push(a);
|
|
2035
|
+
byKind.set(a.kind, arr);
|
|
2036
|
+
}
|
|
2037
|
+
for (const k of ARTIFACT_KINDS) {
|
|
2038
|
+
const items = byKind.get(k);
|
|
2039
|
+
if (!items?.length) continue;
|
|
2040
|
+
log(`${bar} ${c.bold(k + "s")} ${c.dim(`(${items.length})`)}`);
|
|
2041
|
+
for (const it of items) {
|
|
2042
|
+
const desc = (it.description || "").replace(/\s+/g, " ").trim().slice(0, 56);
|
|
2043
|
+
log(`${bar} ${sky("•")} ${c.bold(it.id)}`);
|
|
2044
|
+
if (desc) log(`${bar} ${c.dim(desc)}`);
|
|
2045
|
+
}
|
|
2046
|
+
log(bar);
|
|
2047
|
+
}
|
|
2048
|
+
log(`${corner} ${c.dim("agentry add <kind> <source> [selector]")}`);
|
|
2049
|
+
blank();
|
|
2050
|
+
}
|
|
2051
|
+
async function cmdUpdateAssets(kind, arg1, arg2, opts) {
|
|
2052
|
+
intro();
|
|
2053
|
+
const { source, selector } = resolveSourceOrSelector(arg1, arg2, opts);
|
|
2054
|
+
const installOpts = toInstallOpts(opts);
|
|
2055
|
+
if (source) {
|
|
2056
|
+
await spinner("Parsing source…", () => parseSource(source), (p) => {
|
|
2057
|
+
const where = p.url || p.local || source;
|
|
2058
|
+
return `${theme.primary("Source:")} ${where}${p.ref ? c.dim(` @ ${p.ref}`) : ""}`;
|
|
2059
|
+
}, 520);
|
|
2060
|
+
const { root, cleanup } = await resolveSourceWithUi(source, opts);
|
|
2061
|
+
try {
|
|
2062
|
+
const artifacts = await spinner("Discovering…", () => kind === "all" ? listAll(root) : select(root, kind, selector), (a) => `Found ${a.length} to update`, 620);
|
|
2063
|
+
if (!artifacts.length) {
|
|
2064
|
+
warn(`Nothing found in ${source}.`);
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
let chosen = artifacts;
|
|
2068
|
+
if (!selector && kind !== "all" && artifacts.length > 1 && !opts.all && !opts.yes && !isQuiet()) {
|
|
2069
|
+
await sectionTransition("source discovery", `select ${kind}s to update`);
|
|
2070
|
+
const picked = await selectArtifacts(`Select ${kind}s to update`, artifacts);
|
|
2071
|
+
if (isCancelLike(picked)) {
|
|
2072
|
+
outro("Cancelled.");
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
chosen = picked;
|
|
2076
|
+
await sectionTransition(`${chosen.length} ${kind}${chosen.length === 1 ? "" : "s"} selected`, "choose target agents");
|
|
2077
|
+
} else if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition(`${chosen.length} ${kind}${chosen.length === 1 ? "" : "s"} ready`, "choose target agents");
|
|
2078
|
+
const agentList = await resolveAgentList(opts, "update");
|
|
2079
|
+
if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition(`${agentList.length} agent${agentList.length === 1 ? "" : "s"} selected`, "update summary");
|
|
2080
|
+
installAll(chosen, opts, agentList, source);
|
|
2081
|
+
} finally {
|
|
2082
|
+
cleanup();
|
|
2083
|
+
}
|
|
2084
|
+
return;
|
|
2085
|
+
}
|
|
2086
|
+
let installed = listInstalled(installOpts).filter((it) => kind === "all" || it.id.startsWith(kind + "/"));
|
|
2087
|
+
if (selector) {
|
|
2088
|
+
const rel = `${kind}/${selector}`;
|
|
2089
|
+
installed = installed.filter((it) => it.id === rel || it.id.startsWith(rel + "/") || it.id.endsWith("/" + selector));
|
|
2090
|
+
}
|
|
2091
|
+
if (!installed.length) {
|
|
2092
|
+
warn(`No installed ${kind} recorded in lockfile. Provide a source.`);
|
|
2093
|
+
return;
|
|
2094
|
+
}
|
|
2095
|
+
if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition("lockfile sources", "choose agents to update for");
|
|
2096
|
+
const agentList = await resolveAgentList(opts, "update");
|
|
2097
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
2098
|
+
for (const it of installed) if (it.source) {
|
|
2099
|
+
const arr = bySource.get(it.source) || [];
|
|
2100
|
+
arr.push(it.id);
|
|
2101
|
+
bySource.set(it.source, arr);
|
|
2102
|
+
}
|
|
2103
|
+
if (!bySource.size) {
|
|
2104
|
+
warn(`Installed ${kind} artifacts have no recorded source. Provide a source.`);
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
for (const [s, ids] of bySource) {
|
|
2108
|
+
info(`Updating from ${s}…`);
|
|
2109
|
+
const { root, cleanup } = await resolveSourceWithUi(s, opts);
|
|
2110
|
+
try {
|
|
2111
|
+
installAll(ids.map((id) => {
|
|
2112
|
+
const [k, ...rest] = id.split("/");
|
|
2113
|
+
return select(root, k, rest.join("/") || void 0);
|
|
2114
|
+
}).flat(), opts, agentList, s);
|
|
2115
|
+
} finally {
|
|
2116
|
+
cleanup();
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
async function promptText(message, initial) {
|
|
2121
|
+
if (isQuiet()) return initial || null;
|
|
2122
|
+
const res = await clack.text({
|
|
2123
|
+
message,
|
|
2124
|
+
initialValue: initial || "",
|
|
2125
|
+
placeholder: initial
|
|
2126
|
+
});
|
|
2127
|
+
if (clack.isCancel(res)) return null;
|
|
2128
|
+
return String(res).trim() || null;
|
|
2129
|
+
}
|
|
2130
|
+
async function pickFromSource(kind, source, message, opts) {
|
|
2131
|
+
const { root, cleanup } = await resolveSourceWithUi(source, opts);
|
|
2132
|
+
try {
|
|
2133
|
+
const artifacts = listKind(root, kind);
|
|
2134
|
+
if (!artifacts.length) {
|
|
2135
|
+
warn(`No ${kind}s in ${source}.`);
|
|
2136
|
+
return [];
|
|
2137
|
+
}
|
|
2138
|
+
if (isQuiet() || artifacts.length === 1) return artifacts.map((a) => ({
|
|
2139
|
+
id: a.id,
|
|
2140
|
+
source
|
|
2141
|
+
}));
|
|
2142
|
+
const picked = await selectArtifacts(message, artifacts);
|
|
2143
|
+
if (isCancelLike(picked) || !picked) return [];
|
|
2144
|
+
return picked.map((a) => ({
|
|
2145
|
+
id: a.id,
|
|
2146
|
+
source
|
|
2147
|
+
}));
|
|
2148
|
+
} finally {
|
|
2149
|
+
cleanup();
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
async function cmdInit(kind, nameArg, categoryArg, opts) {
|
|
2153
|
+
intro();
|
|
2154
|
+
const baseDir = opts.dir || process.cwd();
|
|
2155
|
+
let name = nameArg;
|
|
2156
|
+
let category = categoryArg || null;
|
|
2157
|
+
if (!name && !isQuiet()) name = await promptText(`Name for the new ${kind}`) || void 0;
|
|
2158
|
+
if (!name) throw new Error(`Missing name. e.g. agentry init ${kind} my-${kind}`);
|
|
2159
|
+
let description = opts.description;
|
|
2160
|
+
if (!description && !opts.yes && !isQuiet()) description = await promptText("Description (optional)", `${name} ${kind}`) || `${name} ${kind}`;
|
|
2161
|
+
description = description || `${name} ${kind}`;
|
|
2162
|
+
if (kind === "skill") {
|
|
2163
|
+
const reference = opts.reference !== false;
|
|
2164
|
+
if (opts.dryRun) {
|
|
2165
|
+
info(`Would create skills/${category ? category + "/" : ""}${name}/SKILL.md${reference ? " + references/TEMPLATE.md" : ""}`);
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
const r = scaffoldSkill({
|
|
2169
|
+
name,
|
|
2170
|
+
category,
|
|
2171
|
+
description,
|
|
2172
|
+
reference,
|
|
2173
|
+
baseDir
|
|
2174
|
+
});
|
|
2175
|
+
for (const p of r.paths) step(p);
|
|
2176
|
+
ok(`Initialized skill ${r.id}`);
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
if (kind === "agent" || kind === "rule") {
|
|
2180
|
+
if (opts.dryRun) {
|
|
2181
|
+
info(`Would create ${kind}s/${category ? category + "/" : ""}${name}.mdc`);
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
const r = scaffoldMdc({
|
|
2185
|
+
kind,
|
|
2186
|
+
name,
|
|
2187
|
+
category,
|
|
2188
|
+
description,
|
|
2189
|
+
alwaysApply: opts.alwaysApply,
|
|
2190
|
+
baseDir
|
|
2191
|
+
});
|
|
2192
|
+
for (const p of r.paths) step(p);
|
|
2193
|
+
ok(`Initialized ${kind} ${r.id}`);
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
if (kind === "script") {
|
|
2197
|
+
if (opts.dryRun) {
|
|
2198
|
+
info(`Would create scripts/${category ? category + "/" : ""}${name}/`);
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2201
|
+
const r = scaffoldScript({
|
|
2202
|
+
name,
|
|
2203
|
+
category,
|
|
2204
|
+
description,
|
|
2205
|
+
baseDir
|
|
2206
|
+
});
|
|
2207
|
+
for (const p of r.paths) step(p);
|
|
2208
|
+
ok(`Initialized script ${r.id}`);
|
|
2209
|
+
return;
|
|
2210
|
+
}
|
|
2211
|
+
if (kind === "profile") {
|
|
2212
|
+
await initProfile(name, description, opts, baseDir);
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
throw new Error(`Cannot init kind: ${kind}`);
|
|
2216
|
+
}
|
|
2217
|
+
async function initProfile(name, description, opts, baseDir) {
|
|
2218
|
+
const initKinds = opts.initKinds || {};
|
|
2219
|
+
const wantAll = opts.all;
|
|
2220
|
+
let wantSkills = wantAll || initKinds.skills !== void 0;
|
|
2221
|
+
let wantAgents = wantAll || initKinds.agents !== void 0;
|
|
2222
|
+
let wantRules = wantAll || initKinds.rules !== void 0;
|
|
2223
|
+
let wantScripts = wantAll || initKinds.scripts !== void 0;
|
|
2224
|
+
if (!wantSkills && !wantAgents && !wantRules && !wantScripts && !opts.yes && !isQuiet()) {
|
|
2225
|
+
const res = await clack.multiselect({
|
|
2226
|
+
message: "What should this profile include?",
|
|
2227
|
+
options: [
|
|
2228
|
+
{
|
|
2229
|
+
value: "skills",
|
|
2230
|
+
label: "Skills"
|
|
2231
|
+
},
|
|
2232
|
+
{
|
|
2233
|
+
value: "agents",
|
|
2234
|
+
label: "Agents"
|
|
2235
|
+
},
|
|
2236
|
+
{
|
|
2237
|
+
value: "rules",
|
|
2238
|
+
label: "Rules"
|
|
2239
|
+
},
|
|
2240
|
+
{
|
|
2241
|
+
value: "scripts",
|
|
2242
|
+
label: "Scripts"
|
|
2243
|
+
}
|
|
2244
|
+
],
|
|
2245
|
+
required: false
|
|
2246
|
+
});
|
|
2247
|
+
if (clack.isCancel(res)) {
|
|
2248
|
+
outro("Cancelled.");
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2251
|
+
const set = new Set(res);
|
|
2252
|
+
wantSkills = set.has("skills");
|
|
2253
|
+
wantAgents = set.has("agents");
|
|
2254
|
+
wantRules = set.has("rules");
|
|
2255
|
+
wantScripts = set.has("scripts");
|
|
2256
|
+
}
|
|
2257
|
+
const agentList = opts.agents.length ? (await resolveAgentList(opts, "install")).map((a) => a.name) : ["cursor"];
|
|
2258
|
+
const skills = [];
|
|
2259
|
+
const rules = [];
|
|
2260
|
+
const agentsArtifacts = [];
|
|
2261
|
+
const scripts = [];
|
|
2262
|
+
async function fill(flag, enabled, k, into) {
|
|
2263
|
+
if (!enabled) return;
|
|
2264
|
+
const src = typeof flag === "string" ? flag : void 0;
|
|
2265
|
+
if (src) {
|
|
2266
|
+
const picked = await pickFromSource(k, src, `Select ${k}s for profile`, opts);
|
|
2267
|
+
into.push(...picked);
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
if (isQuiet() || opts.yes) return;
|
|
2271
|
+
const ask = await promptText(`Source for ${k}s (author/repo, URL, or local path — empty to skip)`);
|
|
2272
|
+
if (!ask) return;
|
|
2273
|
+
const picked = await pickFromSource(k, ask, `Select ${k}s for profile`, opts);
|
|
2274
|
+
into.push(...picked);
|
|
2275
|
+
}
|
|
2276
|
+
await fill(initKinds.skills, wantSkills, "skill", skills);
|
|
2277
|
+
await fill(initKinds.agents, wantAgents, "agent", agentsArtifacts);
|
|
2278
|
+
await fill(initKinds.rules, wantRules, "rule", rules);
|
|
2279
|
+
await fill(initKinds.scripts, wantScripts, "script", scripts);
|
|
2280
|
+
if (opts.dryRun) {
|
|
2281
|
+
info(`Would write profiles/${name}.yaml with ${skills.length} skills, ${agentsArtifacts.length} agents, ${rules.length} rules, ${scripts.length} scripts`);
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
const r = scaffoldProfile({
|
|
2285
|
+
name,
|
|
2286
|
+
description,
|
|
2287
|
+
agents: agentList,
|
|
2288
|
+
skills,
|
|
2289
|
+
rules,
|
|
2290
|
+
agentsArtifacts,
|
|
2291
|
+
scripts,
|
|
2292
|
+
baseDir
|
|
2293
|
+
});
|
|
2294
|
+
for (const p of r.paths) step(p);
|
|
2295
|
+
ok(`Initialized profile ${r.id}`);
|
|
2296
|
+
}
|
|
2297
|
+
function takeOptValue(argv, i) {
|
|
2298
|
+
const next = argv[i + 1];
|
|
2299
|
+
if (next && !next.startsWith("-")) return {
|
|
2300
|
+
value: next,
|
|
2301
|
+
next: i + 1
|
|
2302
|
+
};
|
|
2303
|
+
return {
|
|
2304
|
+
value: true,
|
|
2305
|
+
next: i
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
function parseArgs(argv) {
|
|
2309
|
+
const opts = {
|
|
2310
|
+
scope: "project",
|
|
2311
|
+
agents: [],
|
|
2312
|
+
copy: false,
|
|
2313
|
+
dryRun: false,
|
|
2314
|
+
yes: false,
|
|
2315
|
+
all: false,
|
|
2316
|
+
initKinds: {}
|
|
2317
|
+
};
|
|
2318
|
+
const positional = [];
|
|
2319
|
+
const flags = {};
|
|
2320
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2321
|
+
const a = argv[i];
|
|
2322
|
+
if (a === "-g" || a === "--global") {
|
|
2323
|
+
opts.scope = "global";
|
|
2324
|
+
flags.global = true;
|
|
2325
|
+
} else if (a === "--project") {
|
|
2326
|
+
opts.scope = "project";
|
|
2327
|
+
flags.project = true;
|
|
2328
|
+
} else if (a === "-y" || a === "--yes") {
|
|
2329
|
+
opts.yes = true;
|
|
2330
|
+
flags.yes = true;
|
|
2331
|
+
} else if (a === "--dir") {
|
|
2332
|
+
opts.dir = argv[++i];
|
|
2333
|
+
flags.dir = opts.dir;
|
|
2334
|
+
} else if (a === "-a" || a === "--agent") {
|
|
2335
|
+
opts.agents.push(argv[++i]);
|
|
2336
|
+
flags.agent = true;
|
|
2337
|
+
} else if (a === "-l" || a === "--list") {
|
|
2338
|
+
opts.dryRun = true;
|
|
2339
|
+
flags.list = true;
|
|
2340
|
+
} else if (a === "--copy") {
|
|
2341
|
+
opts.copy = true;
|
|
2342
|
+
flags.copy = true;
|
|
2343
|
+
} else if (a === "--all") {
|
|
2344
|
+
opts.all = true;
|
|
2345
|
+
flags.all = true;
|
|
2346
|
+
} else if (a === "--description") {
|
|
2347
|
+
opts.description = argv[++i];
|
|
2348
|
+
flags.description = opts.description;
|
|
2349
|
+
} else if (a === "--alwaysApply" || a === "--always-apply") {
|
|
2350
|
+
opts.alwaysApply = true;
|
|
2351
|
+
flags.alwaysApply = true;
|
|
2352
|
+
} else if (a === "--reference") {
|
|
2353
|
+
opts.reference = true;
|
|
2354
|
+
flags.reference = true;
|
|
2355
|
+
} else if (a === "--no-reference") {
|
|
2356
|
+
opts.reference = false;
|
|
2357
|
+
flags.reference = false;
|
|
2358
|
+
} else if (a === "--skills") {
|
|
2359
|
+
const { value, next } = takeOptValue(argv, i);
|
|
2360
|
+
opts.initKinds.skills = value;
|
|
2361
|
+
i = next;
|
|
2362
|
+
flags.skills = true;
|
|
2363
|
+
} else if (a === "--agents") {
|
|
2364
|
+
const { value, next } = takeOptValue(argv, i);
|
|
2365
|
+
opts.initKinds.agents = value;
|
|
2366
|
+
i = next;
|
|
2367
|
+
flags.agents = true;
|
|
2368
|
+
} else if (a === "--rules") {
|
|
2369
|
+
const { value, next } = takeOptValue(argv, i);
|
|
2370
|
+
opts.initKinds.rules = value;
|
|
2371
|
+
i = next;
|
|
2372
|
+
flags.rules = true;
|
|
2373
|
+
} else if (a === "--scripts") {
|
|
2374
|
+
const { value, next } = takeOptValue(argv, i);
|
|
2375
|
+
opts.initKinds.scripts = value;
|
|
2376
|
+
i = next;
|
|
2377
|
+
flags.scripts = true;
|
|
2378
|
+
} else if (a === "-v" || a === "--version") flags.version = true;
|
|
2379
|
+
else if (a === "-h" || a === "--help") flags.help = true;
|
|
2380
|
+
else positional.push(a);
|
|
2381
|
+
}
|
|
2382
|
+
return {
|
|
2383
|
+
opts,
|
|
2384
|
+
positional,
|
|
2385
|
+
flags
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
function fetchJson(url) {
|
|
2389
|
+
return new Promise((resolve, reject) => {
|
|
2390
|
+
https.get(url, (res) => {
|
|
2391
|
+
let body = "";
|
|
2392
|
+
res.on("data", (c) => body += c);
|
|
2393
|
+
res.on("end", () => {
|
|
2394
|
+
try {
|
|
2395
|
+
resolve(JSON.parse(body));
|
|
2396
|
+
} catch (e) {
|
|
2397
|
+
reject(e);
|
|
2398
|
+
}
|
|
2399
|
+
});
|
|
2400
|
+
}).on("error", reject);
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
async function runUpdateSelf() {
|
|
2404
|
+
log(theme.info("Checking agentry version..."));
|
|
2405
|
+
let latest;
|
|
2406
|
+
try {
|
|
2407
|
+
latest = (await fetchJson(`https://registry.npmjs.org/${encodeURIComponent(name)}/latest`))?.version;
|
|
2408
|
+
} catch {
|
|
2409
|
+
warn("Could not reach npm registry; running self-update anyway.");
|
|
2410
|
+
runNpmInstall(`${name}@latest`);
|
|
2411
|
+
return;
|
|
2412
|
+
}
|
|
2413
|
+
if (latest && latest === version) {
|
|
2414
|
+
ok(`Already up to date (v${version}).`);
|
|
2415
|
+
return;
|
|
2416
|
+
}
|
|
2417
|
+
log(`Current v${version} -> latest ${latest || "unknown"}`);
|
|
2418
|
+
runNpmInstall(`${name}@latest`);
|
|
2419
|
+
}
|
|
2420
|
+
function runNpmInstall(spec) {
|
|
2421
|
+
spinner(`Updating agentry CLI`, () => {
|
|
2422
|
+
try {
|
|
2423
|
+
execSync(`npm install -g ${spec}`, { stdio: "inherit" });
|
|
2424
|
+
} catch {
|
|
2425
|
+
err("Update failed. Once published, run: npm install -g " + spec);
|
|
2426
|
+
process.exit(1);
|
|
2427
|
+
}
|
|
2428
|
+
});
|
|
2429
|
+
}
|
|
2430
|
+
function runUninstall() {
|
|
2431
|
+
spinner("Uninstalling agentry CLI", () => {
|
|
2432
|
+
try {
|
|
2433
|
+
execSync(`npm uninstall -g ${name}`, { stdio: "inherit" });
|
|
2434
|
+
} catch {
|
|
2435
|
+
err("Uninstall failed. Try: npm uninstall -g " + name);
|
|
2436
|
+
process.exit(1);
|
|
2437
|
+
}
|
|
2438
|
+
});
|
|
2439
|
+
}
|
|
2440
|
+
function isKind(s) {
|
|
2441
|
+
return ARTIFACT_KINDS.includes(s);
|
|
2442
|
+
}
|
|
2443
|
+
function normalizeKind(s) {
|
|
2444
|
+
return s.endsWith("s") ? s.slice(0, -1) : s;
|
|
2445
|
+
}
|
|
2446
|
+
async function main() {
|
|
2447
|
+
const { opts, positional, flags } = parseArgs(process.argv.slice(2));
|
|
2448
|
+
if (flags.version) {
|
|
2449
|
+
log(version);
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
if (flags.help || positional.length === 0) {
|
|
2453
|
+
if (!isQuiet()) await animateLogo();
|
|
2454
|
+
printHelp();
|
|
2455
|
+
return;
|
|
2456
|
+
}
|
|
2457
|
+
const [action] = positional;
|
|
2458
|
+
if (!isQuiet()) await animateLogo();
|
|
2459
|
+
if (action === "uninstall") {
|
|
2460
|
+
runUninstall();
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
if (action === "update") {
|
|
2464
|
+
if (positional.length === 1) {
|
|
2465
|
+
await runUpdateSelf();
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
const kind = normalizeKind(positional[1]);
|
|
2469
|
+
if (kind !== "all" && !isKind(kind)) throw new Error(`Unknown kind: ${kind}`);
|
|
2470
|
+
await cmdUpdateAssets(kind, positional[2], positional[3], opts);
|
|
2471
|
+
return;
|
|
2472
|
+
}
|
|
2473
|
+
if (action === "list") {
|
|
2474
|
+
const a1 = positional[1];
|
|
2475
|
+
const a2 = positional[2];
|
|
2476
|
+
const a3 = positional[3];
|
|
2477
|
+
if (!a1) {
|
|
2478
|
+
await cmdList(void 0, void 0, void 0, opts);
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
const k1 = normalizeKind(a1);
|
|
2482
|
+
if (isKind(k1) && !isExplicitSource(a1) && !/^[\w.-]+\/[\w.-]+$/.test(a1)) {
|
|
2483
|
+
await cmdList(void 0, k1, a2, opts);
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
const kind = a2 ? normalizeKind(a2) : void 0;
|
|
2487
|
+
if (kind && kind !== "all" && !isKind(kind)) throw new Error(`Unknown kind: ${kind}`);
|
|
2488
|
+
await cmdList(a1, kind && isKind(kind) ? kind : void 0, a3, opts);
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
if (action === "add") {
|
|
2492
|
+
const kind = positional[1] ? normalizeKind(positional[1]) : void 0;
|
|
2493
|
+
if (!kind) throw new Error("Missing kind. e.g. agentry add skills author/repo");
|
|
2494
|
+
if (kind === "profile") {
|
|
2495
|
+
await cmdAddProfile(positional[2], positional[3], opts);
|
|
2496
|
+
return;
|
|
2497
|
+
}
|
|
2498
|
+
if (kind !== "all" && !isKind(kind)) throw new Error(`Unknown kind: ${kind}`);
|
|
2499
|
+
await cmdAdd(kind, positional[2], positional[3], opts);
|
|
2500
|
+
return;
|
|
2501
|
+
}
|
|
2502
|
+
if (action === "remove" || action === "rm") {
|
|
2503
|
+
const kind = positional[1] ? normalizeKind(positional[1]) : void 0;
|
|
2504
|
+
if (!kind) throw new Error("Missing kind. e.g. agentry remove skills enhance-prompt");
|
|
2505
|
+
if (kind !== "all" && !isKind(kind)) throw new Error(`Unknown kind: ${kind}`);
|
|
2506
|
+
await cmdRemove(kind, positional[2], positional[3], opts);
|
|
2507
|
+
return;
|
|
2508
|
+
}
|
|
2509
|
+
if (action === "init") {
|
|
2510
|
+
const kind = positional[1] ? normalizeKind(positional[1]) : void 0;
|
|
2511
|
+
if (!kind || !isKind(kind)) throw new Error("Missing kind. e.g. agentry init skill my-skill [category]");
|
|
2512
|
+
await cmdInit(kind, positional[2], positional[3], opts);
|
|
2513
|
+
return;
|
|
2514
|
+
}
|
|
2515
|
+
err(`Unknown action: ${action}`);
|
|
2516
|
+
printHelp();
|
|
2517
|
+
process.exit(1);
|
|
2518
|
+
}
|
|
2519
|
+
main().catch((e) => {
|
|
2520
|
+
err(e.message || String(e));
|
|
2521
|
+
process.exit(1);
|
|
2522
|
+
});
|
|
2523
|
+
export {};
|