@mhmdreza-rafiei/agentry 0.3.0 → 0.3.2
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 +11 -1
- package/dist/_chunks/discovery.mjs +614 -0
- package/dist/_chunks/theme2.mjs +16 -20
- package/dist/_chunks/web.mjs +111 -0
- package/dist/cli.mjs +57 -613
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { DEFAULT_IGNORES, createIgnoreMatcher, isExplicitSource, isOwnerRepoShape, listAll, listKind, parseSource, resolveSource, select, selectAll, sourcesEqual } from "./_chunks/discovery.mjs";
|
|
2
3
|
import { animateLogo, badge, delay, printHelp, sky, theme } from "./_chunks/theme2.mjs";
|
|
3
4
|
import { execFileSync } from "node:child_process";
|
|
4
5
|
import https from "node:https";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { xdgCache, xdgConfig } from "xdg-basedir";
|
|
6
|
+
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
9
|
+
import { xdgConfig } from "xdg-basedir";
|
|
10
10
|
import { parse, stringify } from "yaml";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import * as clack from "@clack/prompts";
|
|
@@ -16,7 +16,7 @@ import { determineAgent } from "@vercel/detect-agent";
|
|
|
16
16
|
import { stripVTControlCharacters } from "node:util";
|
|
17
17
|
import { Writable } from "node:stream";
|
|
18
18
|
var name = "@mhmdreza-rafiei/agentry";
|
|
19
|
-
var version = "0.3.
|
|
19
|
+
var version = "0.3.2";
|
|
20
20
|
const ARTIFACT_KINDS = [
|
|
21
21
|
"skill",
|
|
22
22
|
"rule",
|
|
@@ -24,601 +24,6 @@ const ARTIFACT_KINDS = [
|
|
|
24
24
|
"profile",
|
|
25
25
|
"script"
|
|
26
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
|
-
const SAFE_REF$1 = /^[a-zA-Z0-9][\w./-]*$/;
|
|
38
|
-
function parseGithubTreeUrl(url) {
|
|
39
|
-
const m = url.match(/^https?:\/\/github\.com\/([\w.-]+\/[\w.-]+?)\/tree\/([^/]+)\/(.+)$/);
|
|
40
|
-
if (!m) return null;
|
|
41
|
-
const ref = m[2];
|
|
42
|
-
if (!SAFE_REF$1.test(ref)) throw new Error(`Invalid git ref in source URL: "${ref}"`);
|
|
43
|
-
return {
|
|
44
|
-
ownerRepo: m[1],
|
|
45
|
-
ref,
|
|
46
|
-
subpath: m[3].replace(/\/+$/, "")
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
function redactUrl(url) {
|
|
50
|
-
return url.replace(/:\/\/([^/@]+)@/g, "://***@");
|
|
51
|
-
}
|
|
52
|
-
function parseSource(source) {
|
|
53
|
-
if (!source || typeof source !== "string") throw new Error("Empty source.");
|
|
54
|
-
const raw = source;
|
|
55
|
-
if (isLocalPath(source)) return {
|
|
56
|
-
kind: "local",
|
|
57
|
-
raw,
|
|
58
|
-
url: null,
|
|
59
|
-
subpath: "",
|
|
60
|
-
local: resolve(source.replace(/^~/, homedir()))
|
|
61
|
-
};
|
|
62
|
-
const tree = parseGithubTreeUrl(source);
|
|
63
|
-
if (tree) return {
|
|
64
|
-
kind: "github-url",
|
|
65
|
-
raw,
|
|
66
|
-
url: `https://github.com/${tree.ownerRepo}.git`,
|
|
67
|
-
subpath: tree.subpath,
|
|
68
|
-
ref: tree.ref,
|
|
69
|
-
local: null
|
|
70
|
-
};
|
|
71
|
-
let m = source.match(/^https?:\/\/github\.com\/([\w.-]+\/[\w.-]+?)(?:\.git)?\/?$/);
|
|
72
|
-
if (m) return {
|
|
73
|
-
kind: "github-url",
|
|
74
|
-
raw,
|
|
75
|
-
url: `https://github.com/${m[1]}.git`,
|
|
76
|
-
subpath: "",
|
|
77
|
-
local: null
|
|
78
|
-
};
|
|
79
|
-
m = source.match(/^https?:\/\/gitlab\.com\/([\w./-]+?)(?:\.git)?\/?$/);
|
|
80
|
-
if (m) return {
|
|
81
|
-
kind: "gitlab-url",
|
|
82
|
-
raw,
|
|
83
|
-
url: `https://gitlab.com/${m[1]}.git`,
|
|
84
|
-
subpath: "",
|
|
85
|
-
local: null
|
|
86
|
-
};
|
|
87
|
-
if (source.startsWith("git@") || /^https?:\/\/.*\.git$/.test(source)) return {
|
|
88
|
-
kind: "git-url",
|
|
89
|
-
raw,
|
|
90
|
-
url: source,
|
|
91
|
-
subpath: "",
|
|
92
|
-
local: null
|
|
93
|
-
};
|
|
94
|
-
if (/^[\w.-]+\/[\w.-]+$/.test(source)) return {
|
|
95
|
-
kind: "github-shorthand",
|
|
96
|
-
raw,
|
|
97
|
-
url: `https://github.com/${source}.git`,
|
|
98
|
-
subpath: "",
|
|
99
|
-
local: null
|
|
100
|
-
};
|
|
101
|
-
throw new Error(`Cannot parse source "${source}". Use author/repo, a GitHub/GitLab URL, a git URL, or a local path.`);
|
|
102
|
-
}
|
|
103
|
-
function sourceIdentity(source) {
|
|
104
|
-
try {
|
|
105
|
-
const p = parseSource(source);
|
|
106
|
-
if (p.kind === "local" && p.local) return `local:${resolve(p.local).replace(/\\/g, "/").toLowerCase()}`;
|
|
107
|
-
return `git:${(p.url || "").replace(/\.git$/i, "").replace(/\/+$/, "").toLowerCase()}${p.subpath ? `/${p.subpath.replace(/^\/+|\/+$/g, "")}` : ""}${p.ref ? `@${p.ref}` : ""}`;
|
|
108
|
-
} catch {
|
|
109
|
-
return `raw:${source.trim().toLowerCase()}`;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
function sourcesEqual(a, b) {
|
|
113
|
-
if (!a || !b) return false;
|
|
114
|
-
if (a === b) return true;
|
|
115
|
-
return sourceIdentity(a) === sourceIdentity(b);
|
|
116
|
-
}
|
|
117
|
-
function isExplicitSource(arg) {
|
|
118
|
-
if (!arg) return false;
|
|
119
|
-
if (isLocalPath(arg)) return true;
|
|
120
|
-
if (/^https?:\/\//i.test(arg)) return true;
|
|
121
|
-
if (arg.startsWith("git@")) return true;
|
|
122
|
-
if (/^https?:\/\/.*\.git$/i.test(arg)) return true;
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
function isOwnerRepoShape(arg) {
|
|
126
|
-
return /^[\w.-]+\/[\w.-]+$/.test(arg);
|
|
127
|
-
}
|
|
128
|
-
function cacheRoot() {
|
|
129
|
-
return join(xdgCache ?? join(homedir(), ".cache"), "agentry", "repos");
|
|
130
|
-
}
|
|
131
|
-
function cacheKey(parsed) {
|
|
132
|
-
const id = sourceIdentity(parsed.raw);
|
|
133
|
-
return createHash("sha256").update(id).digest("hex").slice(0, 20);
|
|
134
|
-
}
|
|
135
|
-
function cacheDirFor(parsed) {
|
|
136
|
-
return join(cacheRoot(), cacheKey(parsed));
|
|
137
|
-
}
|
|
138
|
-
function saveToCache(parsed, clonedRoot) {
|
|
139
|
-
const dest = cacheDirFor(parsed);
|
|
140
|
-
try {
|
|
141
|
-
rmSync(dest, {
|
|
142
|
-
recursive: true,
|
|
143
|
-
force: true
|
|
144
|
-
});
|
|
145
|
-
mkdirSync(join(dest, ".."), { recursive: true });
|
|
146
|
-
cpSync(clonedRoot, dest, { recursive: true });
|
|
147
|
-
} catch {}
|
|
148
|
-
}
|
|
149
|
-
const SAFE_REF = /^[a-zA-Z0-9][\w./-]*$/;
|
|
150
|
-
function cloneTo(url, dest, ref) {
|
|
151
|
-
if (ref && !SAFE_REF.test(ref)) throw new Error(`Invalid git ref: "${ref}"`);
|
|
152
|
-
execFileSync("git", [
|
|
153
|
-
"clone",
|
|
154
|
-
"--quiet",
|
|
155
|
-
...ref ? [
|
|
156
|
-
"--branch",
|
|
157
|
-
ref,
|
|
158
|
-
"--depth",
|
|
159
|
-
"1"
|
|
160
|
-
] : ["--depth", "1"],
|
|
161
|
-
url,
|
|
162
|
-
dest
|
|
163
|
-
], { stdio: [
|
|
164
|
-
"ignore",
|
|
165
|
-
"ignore",
|
|
166
|
-
"pipe"
|
|
167
|
-
] });
|
|
168
|
-
}
|
|
169
|
-
function assertInsideRoot(root, candidate) {
|
|
170
|
-
const base = resolve(root);
|
|
171
|
-
const resolved = resolve(candidate);
|
|
172
|
-
const prefix = base.endsWith(sep) ? base : base + sep;
|
|
173
|
-
const a = resolved.toLowerCase();
|
|
174
|
-
const b = base.toLowerCase();
|
|
175
|
-
const p = prefix.toLowerCase();
|
|
176
|
-
if (a !== b && !a.startsWith(p)) throw new Error(`Path escapes source root: "${candidate}"`);
|
|
177
|
-
return resolved;
|
|
178
|
-
}
|
|
179
|
-
function withSubpath(root, subpath, cleanupRoot) {
|
|
180
|
-
const cleanup = () => rmSync(cleanupRoot, {
|
|
181
|
-
recursive: true,
|
|
182
|
-
force: true
|
|
183
|
-
});
|
|
184
|
-
if (!subpath) return {
|
|
185
|
-
root,
|
|
186
|
-
cleanup
|
|
187
|
-
};
|
|
188
|
-
let sub;
|
|
189
|
-
try {
|
|
190
|
-
sub = assertInsideRoot(root, join(root, subpath));
|
|
191
|
-
} catch (e) {
|
|
192
|
-
cleanup();
|
|
193
|
-
throw e;
|
|
194
|
-
}
|
|
195
|
-
if (!existsSync(sub)) {
|
|
196
|
-
cleanup();
|
|
197
|
-
throw new Error(`Subpath "${subpath}" not found.`);
|
|
198
|
-
}
|
|
199
|
-
return {
|
|
200
|
-
root: sub,
|
|
201
|
-
cleanup
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
async function resolveSource(source, opts = {}) {
|
|
205
|
-
const parsed = typeof source === "string" ? parseSource(source) : source;
|
|
206
|
-
if (parsed.kind === "local" && parsed.local) {
|
|
207
|
-
if (!existsSync(parsed.local)) throw new Error(`Path not found: ${parsed.raw}`);
|
|
208
|
-
return {
|
|
209
|
-
root: parsed.local,
|
|
210
|
-
cleanup: () => {}
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
const url = parsed.url;
|
|
214
|
-
if (!url) throw new Error(`Cannot resolve source "${parsed.raw}".`);
|
|
215
|
-
const safeUrl = redactUrl(url);
|
|
216
|
-
const tmp = mkdtempSync(join(tmpdir(), "agentry-src-"));
|
|
217
|
-
try {
|
|
218
|
-
cloneTo(url, tmp, parsed.ref);
|
|
219
|
-
} catch (e) {
|
|
220
|
-
rmSync(tmp, {
|
|
221
|
-
recursive: true,
|
|
222
|
-
force: true
|
|
223
|
-
});
|
|
224
|
-
const detail = e.stderr ? e.stderr.toString().trim() : e.message;
|
|
225
|
-
const cached = cacheDirFor(parsed);
|
|
226
|
-
const cacheOk = existsSync(cached) && existsSync(join(cached, ".git"));
|
|
227
|
-
if (cacheOk && opts.confirmCache) {
|
|
228
|
-
if (await opts.confirmCache({
|
|
229
|
-
url: safeUrl,
|
|
230
|
-
cacheDir: cached,
|
|
231
|
-
error: detail
|
|
232
|
-
})) {
|
|
233
|
-
const work = mkdtempSync(join(tmpdir(), "agentry-src-"));
|
|
234
|
-
cpSync(cached, work, { recursive: true });
|
|
235
|
-
return {
|
|
236
|
-
...withSubpath(work, parsed.subpath, work),
|
|
237
|
-
fromCache: true
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
throw new Error(`git clone failed for ${safeUrl}\n${detail}${cacheOk ? "\n(A local cache exists — re-run interactively to load it, or pass -y to auto-use cache.)" : ""}`);
|
|
242
|
-
}
|
|
243
|
-
saveToCache(parsed, tmp);
|
|
244
|
-
return withSubpath(tmp, parsed.subpath, tmp);
|
|
245
|
-
}
|
|
246
|
-
const DEFAULT_IGNORES = [
|
|
247
|
-
".git",
|
|
248
|
-
".github",
|
|
249
|
-
"node_modules",
|
|
250
|
-
".agents",
|
|
251
|
-
".agentry",
|
|
252
|
-
".cursor",
|
|
253
|
-
".claude",
|
|
254
|
-
".vscode",
|
|
255
|
-
"dist",
|
|
256
|
-
"build",
|
|
257
|
-
".DS_Store"
|
|
258
|
-
];
|
|
259
|
-
function parseIgnorePattern(pattern) {
|
|
260
|
-
const trimmed = pattern.trim();
|
|
261
|
-
if (!trimmed || trimmed.startsWith("#")) return null;
|
|
262
|
-
const isDirOnly = trimmed.endsWith("/");
|
|
263
|
-
let p = isDirOnly ? trimmed.slice(0, -1) : trimmed;
|
|
264
|
-
const isRootAnchored = p.startsWith("/");
|
|
265
|
-
if (isRootAnchored) p = p.slice(1);
|
|
266
|
-
let regexStr = "";
|
|
267
|
-
for (let i = 0; i < p.length; i++) {
|
|
268
|
-
const char = p[i];
|
|
269
|
-
if (char === "*") if (p[i + 1] === "*") {
|
|
270
|
-
regexStr += ".*";
|
|
271
|
-
i++;
|
|
272
|
-
if (p[i + 1] === "/") i++;
|
|
273
|
-
} else regexStr += "[^/]*";
|
|
274
|
-
else if (char === "?") regexStr += "[^/]";
|
|
275
|
-
else if ([
|
|
276
|
-
".",
|
|
277
|
-
"+",
|
|
278
|
-
"^",
|
|
279
|
-
"$",
|
|
280
|
-
"{",
|
|
281
|
-
"}",
|
|
282
|
-
"(",
|
|
283
|
-
")",
|
|
284
|
-
"|",
|
|
285
|
-
"[",
|
|
286
|
-
"]",
|
|
287
|
-
"\\"
|
|
288
|
-
].includes(char)) regexStr += "\\" + char;
|
|
289
|
-
else regexStr += char;
|
|
290
|
-
}
|
|
291
|
-
let regex;
|
|
292
|
-
if (isRootAnchored) regex = new RegExp(`^${regexStr}(/.*)?$`, "i");
|
|
293
|
-
else if (p.includes("/")) regex = new RegExp(`(^|/)${regexStr}(/.*)?$`, "i");
|
|
294
|
-
else regex = new RegExp(`(^|/)${regexStr}($|/)`, "i");
|
|
295
|
-
return {
|
|
296
|
-
raw: trimmed,
|
|
297
|
-
isDirOnly,
|
|
298
|
-
regex
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
function parseIgnoreFile(content) {
|
|
302
|
-
return content.split(/\r?\n/).map((line) => parseIgnorePattern(line)).filter((r) => r !== null);
|
|
303
|
-
}
|
|
304
|
-
function loadIgnoreRules(root) {
|
|
305
|
-
const candidates = [".agentryignore", ".agentry.ignore"];
|
|
306
|
-
const rules = [];
|
|
307
|
-
for (const name of candidates) {
|
|
308
|
-
const file = join(root, name);
|
|
309
|
-
if (existsSync(file)) try {
|
|
310
|
-
const text = readFileSync(file, "utf8");
|
|
311
|
-
rules.push(...parseIgnoreFile(text));
|
|
312
|
-
} catch {}
|
|
313
|
-
}
|
|
314
|
-
return rules;
|
|
315
|
-
}
|
|
316
|
-
function isDefaultIgnored(relPath) {
|
|
317
|
-
return relPath.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "").split("/").some((s) => DEFAULT_IGNORES.includes(s));
|
|
318
|
-
}
|
|
319
|
-
function createIgnoreMatcher(root) {
|
|
320
|
-
const fileRules = loadIgnoreRules(root);
|
|
321
|
-
return (relPath, isDirectory = false) => {
|
|
322
|
-
if (!relPath || relPath === "." || relPath === "./") return false;
|
|
323
|
-
const normalized = relPath.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
324
|
-
if (!normalized) return false;
|
|
325
|
-
if (isDefaultIgnored(normalized)) return true;
|
|
326
|
-
for (const rule of fileRules) {
|
|
327
|
-
if (rule.isDirOnly && !isDirectory) {
|
|
328
|
-
if (rule.regex.test(normalized + "/")) return true;
|
|
329
|
-
continue;
|
|
330
|
-
}
|
|
331
|
-
if (rule.regex.test(normalized)) return true;
|
|
332
|
-
}
|
|
333
|
-
return false;
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
const SKILL_MARKER = "SKILL.md";
|
|
337
|
-
const AGENT_MARKER = "AGENT.md";
|
|
338
|
-
const RULE_MARKER = "RULE.md";
|
|
339
|
-
const CURSORRULES_MARKER = ".cursorrules";
|
|
340
|
-
const FILE_EXT = ".mdc";
|
|
341
|
-
const PROFILE_EXTS = [".yaml", ".yml"];
|
|
342
|
-
const KIND_DIRS = {
|
|
343
|
-
skill: "skills",
|
|
344
|
-
rule: "rules",
|
|
345
|
-
agent: "agents",
|
|
346
|
-
profile: "profiles",
|
|
347
|
-
script: "scripts"
|
|
348
|
-
};
|
|
349
|
-
const ROOT_SKIP = /* @__PURE__ */ new Set([
|
|
350
|
-
".",
|
|
351
|
-
"node_modules",
|
|
352
|
-
"skills",
|
|
353
|
-
"rules",
|
|
354
|
-
"agents",
|
|
355
|
-
"profiles",
|
|
356
|
-
"scripts"
|
|
357
|
-
]);
|
|
358
|
-
function entries(dir) {
|
|
359
|
-
try {
|
|
360
|
-
return readdirSync(dir, { withFileTypes: true });
|
|
361
|
-
} catch {
|
|
362
|
-
return [];
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
function firstHeading(md) {
|
|
366
|
-
const m = md.match(/^#\s+(.+)$/m);
|
|
367
|
-
return m ? m[1].trim() : "";
|
|
368
|
-
}
|
|
369
|
-
function frontmatterName(md) {
|
|
370
|
-
const fm = md.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
371
|
-
if (!fm) return "";
|
|
372
|
-
const m = fm[1].match(/^name:\s*(.+)$/m);
|
|
373
|
-
return m ? m[1].trim().replace(/^['"]|['"]$/g, "") : "";
|
|
374
|
-
}
|
|
375
|
-
function frontmatterDescription(md) {
|
|
376
|
-
const fm = md.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
377
|
-
if (!fm) return "";
|
|
378
|
-
const lines = fm[1].split(/\r?\n/);
|
|
379
|
-
for (let i = 0; i < lines.length; i++) {
|
|
380
|
-
const dm = lines[i].match(/^description:\s*(.*)$/);
|
|
381
|
-
if (!dm) continue;
|
|
382
|
-
let val = dm[1].trim();
|
|
383
|
-
if (val === "" || val === ">" || val === "|" || val === ">-" || val === "|-") {
|
|
384
|
-
const collected = [];
|
|
385
|
-
for (let j = i + 1; j < lines.length; j++) if (/^\s+\S/.test(lines[j])) collected.push(lines[j].trim());
|
|
386
|
-
else break;
|
|
387
|
-
val = collected.join(" ");
|
|
388
|
-
}
|
|
389
|
-
return val.trim();
|
|
390
|
-
}
|
|
391
|
-
return "";
|
|
392
|
-
}
|
|
393
|
-
function readDescription(filePath) {
|
|
394
|
-
try {
|
|
395
|
-
const md = readFileSync(filePath, "utf8");
|
|
396
|
-
return frontmatterDescription(md) || firstHeading(md);
|
|
397
|
-
} catch {
|
|
398
|
-
return "";
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
function folderDescription(dir) {
|
|
402
|
-
for (const f of [SKILL_MARKER, "README.md"]) {
|
|
403
|
-
const p = join(dir, f);
|
|
404
|
-
if (existsSync(p)) {
|
|
405
|
-
const d = readDescription(p);
|
|
406
|
-
if (d) return d;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
return "";
|
|
410
|
-
}
|
|
411
|
-
function makeArtifact(kind, category, name, srcPath, isFile) {
|
|
412
|
-
return {
|
|
413
|
-
kind,
|
|
414
|
-
category,
|
|
415
|
-
name,
|
|
416
|
-
id: category ? `${category}/${name}` : name,
|
|
417
|
-
dir: srcPath,
|
|
418
|
-
isFile,
|
|
419
|
-
description: isFile ? readDescription(srcPath) : folderDescription(srcPath)
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
function rel(root, fullPath) {
|
|
423
|
-
return relative(root, fullPath).replace(/\\/g, "/");
|
|
424
|
-
}
|
|
425
|
-
function collectSkills(root, base, out, matcher) {
|
|
426
|
-
for (const child of entries(base)) {
|
|
427
|
-
if (!child.isDirectory()) continue;
|
|
428
|
-
const childDir = join(base, child.name);
|
|
429
|
-
if (matcher(rel(root, childDir), true)) continue;
|
|
430
|
-
if (existsSync(join(childDir, SKILL_MARKER))) out.push(makeArtifact("skill", null, child.name, childDir, false));
|
|
431
|
-
else for (const item of entries(childDir)) {
|
|
432
|
-
if (!item.isDirectory()) continue;
|
|
433
|
-
const itemDir = join(childDir, item.name);
|
|
434
|
-
if (matcher(rel(root, itemDir), true)) continue;
|
|
435
|
-
if (existsSync(join(itemDir, SKILL_MARKER))) out.push(makeArtifact("skill", child.name, item.name, itemDir, false));
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
function collectMdc(root, kind, base, out, matcher) {
|
|
440
|
-
for (const child of entries(base)) {
|
|
441
|
-
const childPath = join(base, child.name);
|
|
442
|
-
if (child.isFile() && child.name.endsWith(FILE_EXT)) {
|
|
443
|
-
if (matcher(rel(root, childPath), false)) continue;
|
|
444
|
-
out.push(makeArtifact(kind, null, child.name.slice(0, -4), childPath, true));
|
|
445
|
-
} else if (child.isDirectory()) {
|
|
446
|
-
if (matcher(rel(root, childPath), true)) continue;
|
|
447
|
-
for (const item of entries(childPath)) {
|
|
448
|
-
const itemPath = join(childPath, item.name);
|
|
449
|
-
if (item.isFile() && item.name.endsWith(FILE_EXT)) {
|
|
450
|
-
if (matcher(rel(root, itemPath), false)) continue;
|
|
451
|
-
out.push(makeArtifact(kind, child.name, item.name.slice(0, -4), itemPath, true));
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
function collectProfiles(root, base, out, matcher) {
|
|
458
|
-
for (const child of entries(base)) {
|
|
459
|
-
const childPath = join(base, child.name);
|
|
460
|
-
if (child.isFile() && PROFILE_EXTS.some((e) => child.name.endsWith(e))) {
|
|
461
|
-
if (matcher(rel(root, childPath), false)) continue;
|
|
462
|
-
const stem = child.name.replace(/\.(yaml|yml)$/, "");
|
|
463
|
-
out.push(makeArtifact("profile", null, stem, childPath, true));
|
|
464
|
-
} else if (child.isDirectory()) {
|
|
465
|
-
if (matcher(rel(root, childPath), true)) continue;
|
|
466
|
-
for (const item of entries(childPath)) {
|
|
467
|
-
const itemPath = join(childPath, item.name);
|
|
468
|
-
if (item.isFile() && PROFILE_EXTS.some((e) => item.name.endsWith(e))) {
|
|
469
|
-
if (matcher(rel(root, itemPath), false)) continue;
|
|
470
|
-
const stem = item.name.replace(/\.(yaml|yml)$/, "");
|
|
471
|
-
out.push(makeArtifact("profile", child.name, stem, itemPath, true));
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
function collectScripts(root, base, out, matcher) {
|
|
478
|
-
for (const child of entries(base)) {
|
|
479
|
-
if (!child.isDirectory()) continue;
|
|
480
|
-
const childDir = join(base, child.name);
|
|
481
|
-
if (matcher(rel(root, childDir), true)) continue;
|
|
482
|
-
if (entries(childDir).some((e) => e.isFile())) out.push(makeArtifact("script", null, child.name, childDir, false));
|
|
483
|
-
else for (const item of entries(childDir)) {
|
|
484
|
-
const itemDir = join(childDir, item.name);
|
|
485
|
-
if (!item.isDirectory() || matcher(rel(root, itemDir), true)) continue;
|
|
486
|
-
if (entries(itemDir).some((e) => e.isFile())) out.push(makeArtifact("script", child.name, item.name, join(childDir, item.name), false));
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
function collectRootSkills(root, out, matcher) {
|
|
491
|
-
const rootSkillFile = join(root, SKILL_MARKER);
|
|
492
|
-
if (existsSync(rootSkillFile) && !matcher(SKILL_MARKER, false)) {
|
|
493
|
-
let name = "";
|
|
494
|
-
try {
|
|
495
|
-
name = frontmatterName(readFileSync(rootSkillFile, "utf8"));
|
|
496
|
-
} catch {}
|
|
497
|
-
if (!name) name = basename(root) || "skill";
|
|
498
|
-
out.push(makeArtifact("skill", null, name, root, false));
|
|
499
|
-
}
|
|
500
|
-
for (const child of entries(root)) {
|
|
501
|
-
if (!child.isDirectory() || child.name.startsWith(".") || ROOT_SKIP.has(child.name)) continue;
|
|
502
|
-
const childDir = join(root, child.name);
|
|
503
|
-
if (matcher(child.name, true)) continue;
|
|
504
|
-
if (existsSync(join(childDir, SKILL_MARKER))) out.push(makeArtifact("skill", null, child.name, childDir, false));
|
|
505
|
-
else for (const item of entries(childDir)) {
|
|
506
|
-
if (!item.isDirectory()) continue;
|
|
507
|
-
const itemDir = join(childDir, item.name);
|
|
508
|
-
if (matcher(rel(root, itemDir), true)) continue;
|
|
509
|
-
if (existsSync(join(itemDir, SKILL_MARKER))) out.push(makeArtifact("skill", child.name, item.name, itemDir, false));
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
function collectRootMdc(kind, root, out, matcher) {
|
|
514
|
-
if (kind === "agent") {
|
|
515
|
-
const rootAgent = join(root, AGENT_MARKER);
|
|
516
|
-
if (existsSync(rootAgent) && !matcher(AGENT_MARKER, false)) {
|
|
517
|
-
let name = "";
|
|
518
|
-
try {
|
|
519
|
-
name = frontmatterName(readFileSync(rootAgent, "utf8"));
|
|
520
|
-
} catch {}
|
|
521
|
-
out.push(makeArtifact("agent", null, name || "agent", rootAgent, true));
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
if (kind === "rule") {
|
|
525
|
-
const rootRule = join(root, RULE_MARKER);
|
|
526
|
-
if (existsSync(rootRule) && !matcher(RULE_MARKER, false)) {
|
|
527
|
-
let name = "";
|
|
528
|
-
try {
|
|
529
|
-
name = frontmatterName(readFileSync(rootRule, "utf8"));
|
|
530
|
-
} catch {}
|
|
531
|
-
out.push(makeArtifact("rule", null, name || "rule", rootRule, true));
|
|
532
|
-
}
|
|
533
|
-
const cursorRules = join(root, CURSORRULES_MARKER);
|
|
534
|
-
if (existsSync(cursorRules) && !matcher(CURSORRULES_MARKER, false)) out.push(makeArtifact("rule", null, "cursorrules", cursorRules, true));
|
|
535
|
-
}
|
|
536
|
-
for (const child of entries(root)) {
|
|
537
|
-
const childPath = join(root, child.name);
|
|
538
|
-
if (child.isFile() && child.name.endsWith(FILE_EXT) && !child.name.startsWith(".")) {
|
|
539
|
-
if (matcher(child.name, false)) continue;
|
|
540
|
-
out.push(makeArtifact(kind, null, child.name.slice(0, -4), childPath, true));
|
|
541
|
-
} else if (child.isDirectory() && !child.name.startsWith(".") && !ROOT_SKIP.has(child.name)) {
|
|
542
|
-
if (matcher(child.name, true)) continue;
|
|
543
|
-
for (const item of entries(childPath)) {
|
|
544
|
-
const itemPath = join(childPath, item.name);
|
|
545
|
-
if (item.isFile() && item.name.endsWith(FILE_EXT)) {
|
|
546
|
-
if (matcher(rel(root, itemPath), false)) continue;
|
|
547
|
-
out.push(makeArtifact(kind, child.name, item.name.slice(0, -4), itemPath, true));
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
function collectRootProfiles(root, out, matcher) {
|
|
554
|
-
const localProfileDir = join(root, "profile");
|
|
555
|
-
if (existsSync(localProfileDir) && !matcher("profile", true)) collectProfiles(root, localProfileDir, out, matcher);
|
|
556
|
-
for (const child of entries(root)) {
|
|
557
|
-
if (!child.isFile()) continue;
|
|
558
|
-
if (matcher(child.name, false)) continue;
|
|
559
|
-
const lower = child.name.toLowerCase();
|
|
560
|
-
if (lower === "profile.yaml" || lower === "profile.yml" || lower.endsWith(".profile.yaml") || lower.endsWith(".profile.yml")) {
|
|
561
|
-
const stem = child.name.replace(/\.(profile\.)?(yaml|yml)$/i, "");
|
|
562
|
-
out.push(makeArtifact("profile", null, stem, join(root, child.name), true));
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
function collectRootScripts(root, out, matcher) {
|
|
567
|
-
const localScriptDir = join(root, "script");
|
|
568
|
-
if (existsSync(localScriptDir) && !matcher("script", true)) collectScripts(root, localScriptDir, out, matcher);
|
|
569
|
-
if (!existsSync(join(root, SKILL_MARKER))) {
|
|
570
|
-
for (const sf of [
|
|
571
|
-
"run.sh",
|
|
572
|
-
"script.sh",
|
|
573
|
-
"run.js",
|
|
574
|
-
"main.py"
|
|
575
|
-
]) if (existsSync(join(root, sf)) && !matcher(sf, false)) {
|
|
576
|
-
const name = basename(root) || "script";
|
|
577
|
-
out.push(makeArtifact("script", null, name, root, false));
|
|
578
|
-
break;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
function listKind(root, kind) {
|
|
583
|
-
const out = [];
|
|
584
|
-
const matcher = createIgnoreMatcher(root);
|
|
585
|
-
const base = join(root, KIND_DIRS[kind]);
|
|
586
|
-
if (existsSync(base) && !matcher(KIND_DIRS[kind], true)) if (kind === "skill") collectSkills(root, base, out, matcher);
|
|
587
|
-
else if (kind === "profile") collectProfiles(root, base, out, matcher);
|
|
588
|
-
else if (kind === "script") collectScripts(root, base, out, matcher);
|
|
589
|
-
else collectMdc(root, kind, base, out, matcher);
|
|
590
|
-
if (kind === "skill") collectRootSkills(root, out, matcher);
|
|
591
|
-
else if (kind === "agent" || kind === "rule") collectRootMdc(kind, root, out, matcher);
|
|
592
|
-
else if (kind === "profile") collectRootProfiles(root, out, matcher);
|
|
593
|
-
else if (kind === "script") collectRootScripts(root, out, matcher);
|
|
594
|
-
const seen = /* @__PURE__ */ new Set();
|
|
595
|
-
return out.filter((a) => seen.has(a.id) ? false : seen.add(a.id)).sort((a, b) => a.id.localeCompare(b.id));
|
|
596
|
-
}
|
|
597
|
-
function listAll(root) {
|
|
598
|
-
return [
|
|
599
|
-
"skill",
|
|
600
|
-
"rule",
|
|
601
|
-
"agent",
|
|
602
|
-
"profile",
|
|
603
|
-
"script"
|
|
604
|
-
].flatMap((k) => listKind(root, k));
|
|
605
|
-
}
|
|
606
|
-
function select(root, kind, selector) {
|
|
607
|
-
const items = listKind(root, kind);
|
|
608
|
-
if (!selector) return items;
|
|
609
|
-
if (selector.includes("/")) return items.filter((a) => a.id === selector);
|
|
610
|
-
const byCategory = items.filter((a) => a.category === selector);
|
|
611
|
-
if (byCategory.length) return byCategory;
|
|
612
|
-
return items.filter((a) => a.name === selector);
|
|
613
|
-
}
|
|
614
|
-
function selectAll(root, selector) {
|
|
615
|
-
const items = listAll(root);
|
|
616
|
-
if (!selector) return items;
|
|
617
|
-
if (selector.includes("/")) return items.filter((a) => a.id === selector);
|
|
618
|
-
const byCategory = items.filter((a) => a.category === selector);
|
|
619
|
-
if (byCategory.length) return byCategory;
|
|
620
|
-
return items.filter((a) => a.name === selector);
|
|
621
|
-
}
|
|
622
27
|
const home$1 = homedir();
|
|
623
28
|
const configHome = xdgConfig ?? join(home$1, ".config");
|
|
624
29
|
const codexHome = process.env.CODEX_HOME?.trim() || join(home$1, ".codex");
|
|
@@ -2094,6 +1499,24 @@ async function selectAgents(message, universal, others, detectedNames = []) {
|
|
|
2094
1499
|
for (const u of universal) if (!out.some((a) => a.name === u.name)) out.unshift(u);
|
|
2095
1500
|
return out;
|
|
2096
1501
|
}
|
|
1502
|
+
function showFoundArtifacts(artifacts, title = artifacts.length === 1 ? "Found artifact" : `Found ${artifacts.length} artifacts`) {
|
|
1503
|
+
blank();
|
|
1504
|
+
const bar = c.dim("│");
|
|
1505
|
+
const corner = c.dim("└");
|
|
1506
|
+
log(`${sky("◆")} ${c.bold(title)}`);
|
|
1507
|
+
log(bar);
|
|
1508
|
+
const maxItems = 12;
|
|
1509
|
+
const shown = artifacts.slice(0, maxItems);
|
|
1510
|
+
for (const a of shown) {
|
|
1511
|
+
const desc = (a.description || "").replace(/\s+/g, " ").trim().slice(0, 56);
|
|
1512
|
+
log(`${bar} ${sky("•")} ${c.bold(`${a.kind}/${a.id}`)}`);
|
|
1513
|
+
if (desc) log(`${bar} ${c.dim(desc)}`);
|
|
1514
|
+
}
|
|
1515
|
+
if (artifacts.length > maxItems) log(`${bar} ${c.dim(`… +${artifacts.length - maxItems} more artifacts`)}`);
|
|
1516
|
+
log(bar);
|
|
1517
|
+
log(`${corner} ${c.dim(`${artifacts.length} artifact${artifacts.length === 1 ? "" : "s"} ready`)}`);
|
|
1518
|
+
blank();
|
|
1519
|
+
}
|
|
2097
1520
|
function installSummary(artifacts, agents, scopeLabel) {
|
|
2098
1521
|
blank();
|
|
2099
1522
|
const bar = c.dim("│");
|
|
@@ -2121,9 +1544,8 @@ function installSummary(artifacts, agents, scopeLabel) {
|
|
|
2121
1544
|
log(`${bar} ${c.bold("Artifacts")} ${artifacts.length} selected`);
|
|
2122
1545
|
log(bar);
|
|
2123
1546
|
for (const a of artifacts) {
|
|
2124
|
-
|
|
1547
|
+
(a.description || "").replace(/\s+/g, " ").trim().slice(0, 52);
|
|
2125
1548
|
log(`${bar} ${sky("•")} ${c.bold(`${a.kind}/${a.id}`)}`);
|
|
2126
|
-
if (desc) log(`${bar} ${c.dim(desc)}`);
|
|
2127
1549
|
}
|
|
2128
1550
|
log(bar);
|
|
2129
1551
|
log(`${corner} ${c.dim("Review before use — artifacts run with full agent permissions.")}`);
|
|
@@ -2166,8 +1588,9 @@ async function cmdAdd(kind, source, selector, opts) {
|
|
|
2166
1588
|
}
|
|
2167
1589
|
await delayQuiet(320);
|
|
2168
1590
|
blank();
|
|
1591
|
+
showFoundArtifacts(artifacts);
|
|
2169
1592
|
if (opts.dryRun) {
|
|
2170
|
-
preview(artifacts, await resolveAgentList(opts, "install"));
|
|
1593
|
+
preview(artifacts, await resolveAgentList(opts, "install", true));
|
|
2171
1594
|
return;
|
|
2172
1595
|
}
|
|
2173
1596
|
let chosen = artifacts;
|
|
@@ -2210,8 +1633,6 @@ async function cmdAddProfile(name, source, opts) {
|
|
|
2210
1633
|
const { file, profile } = loadProfile(name);
|
|
2211
1634
|
const repo = source || (profile.artifacts.skills[0]?.source ?? profile.artifacts.rules[0]?.source);
|
|
2212
1635
|
if (!repo) throw new Error(`No source given and ${file} lists none. Try: agentry add profile ${name} author/repo`);
|
|
2213
|
-
const agentList = await resolveAgentList(opts, "install");
|
|
2214
|
-
if (!await ensureInstallScope(opts, "install")) return;
|
|
2215
1636
|
const { root, cleanup } = await resolveSourceWithUi(repo, opts);
|
|
2216
1637
|
try {
|
|
2217
1638
|
const artifacts = [];
|
|
@@ -2228,16 +1649,19 @@ async function cmdAddProfile(name, source, opts) {
|
|
|
2228
1649
|
if (ref.source) src.cleanup();
|
|
2229
1650
|
}
|
|
2230
1651
|
}
|
|
1652
|
+
showFoundArtifacts(artifacts, `Profile: ${name}`);
|
|
2231
1653
|
if (opts.dryRun) {
|
|
2232
|
-
preview(artifacts,
|
|
1654
|
+
preview(artifacts, await resolveAgentList(opts, "install", true));
|
|
2233
1655
|
return;
|
|
2234
1656
|
}
|
|
1657
|
+
const agentList = await resolveAgentList(opts, "install");
|
|
1658
|
+
if (!await ensureInstallScope(opts, "install")) return;
|
|
2235
1659
|
installAll(artifacts, opts, agentList, repo);
|
|
2236
1660
|
} finally {
|
|
2237
1661
|
cleanup();
|
|
2238
1662
|
}
|
|
2239
1663
|
}
|
|
2240
|
-
async function resolveAgentList(opts, action = "install") {
|
|
1664
|
+
async function resolveAgentList(opts, action = "install", nonInteractive = false) {
|
|
2241
1665
|
if (opts.all) return listAgents();
|
|
2242
1666
|
if (opts.agents.length) {
|
|
2243
1667
|
const { agents, unknown } = resolveAgents(opts.agents);
|
|
@@ -2249,7 +1673,7 @@ async function resolveAgentList(opts, action = "install") {
|
|
|
2249
1673
|
const visibleUniversal = getVisibleUniversalAgents();
|
|
2250
1674
|
const others = getNonUniversalAgents();
|
|
2251
1675
|
const detectedOthers = detected.filter((n) => others.some((o) => o.name === n));
|
|
2252
|
-
if (isQuiet() || opts.yes) {
|
|
1676
|
+
if (isQuiet() || opts.yes || nonInteractive) {
|
|
2253
1677
|
const picked = detectedOthers.length ? detectedOthers : ["cursor"];
|
|
2254
1678
|
return [...universal, ...others.filter((o) => picked.includes(o.name))];
|
|
2255
1679
|
}
|
|
@@ -2474,6 +1898,7 @@ async function cmdUpdateAssets(kind, arg1, arg2, opts) {
|
|
|
2474
1898
|
warn(`Nothing found in ${source}.`);
|
|
2475
1899
|
return;
|
|
2476
1900
|
}
|
|
1901
|
+
showFoundArtifacts(artifacts, "Found artifacts to update");
|
|
2477
1902
|
let chosen = artifacts;
|
|
2478
1903
|
if (!selector && artifacts.length > 1 && !opts.all && !opts.yes && !isQuiet()) {
|
|
2479
1904
|
const noun = kind === "all" ? "artifacts" : `${kind}s`;
|
|
@@ -2899,6 +2324,13 @@ async function main() {
|
|
|
2899
2324
|
await cmdUpdateAssets(kind, positional[2], positional[3], opts);
|
|
2900
2325
|
return;
|
|
2901
2326
|
}
|
|
2327
|
+
if (action === "from") {
|
|
2328
|
+
const source = positional[1];
|
|
2329
|
+
if (!source) throw new Error("Missing URL. e.g. agentry from https://awesomeskill.ai/skill/...");
|
|
2330
|
+
const selector = positional[2];
|
|
2331
|
+
await cmdAdd("all", source, selector, opts);
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2902
2334
|
if (action === "list") {
|
|
2903
2335
|
const a1 = positional[1];
|
|
2904
2336
|
const a2 = positional[2];
|
|
@@ -2919,16 +2351,28 @@ async function main() {
|
|
|
2919
2351
|
}
|
|
2920
2352
|
if (action === "add") {
|
|
2921
2353
|
const a1 = positional[1];
|
|
2922
|
-
if (!a1) throw new Error("Missing source or kind. e.g. agentry add author/repo or agentry add skills author/repo");
|
|
2354
|
+
if (!a1) throw new Error("Missing source or kind. e.g. agentry add author/repo, agentry add from <url>, or agentry add skills author/repo");
|
|
2355
|
+
if (a1 === "from") {
|
|
2356
|
+
const source = positional[2];
|
|
2357
|
+
if (!source) throw new Error("Missing URL after \"from\". e.g. agentry add from https://awesomeskill.ai/skill/...");
|
|
2358
|
+
const selector = positional[3];
|
|
2359
|
+
await cmdAdd("all", source, selector, opts);
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2923
2362
|
const k1 = normalizeKind(a1);
|
|
2924
2363
|
if (k1 === "profile") {
|
|
2925
2364
|
await cmdAddProfile(positional[2], positional[3], opts);
|
|
2926
2365
|
return;
|
|
2927
2366
|
}
|
|
2928
2367
|
if (k1 === "all" || isKind(k1)) {
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2368
|
+
let source = positional[2];
|
|
2369
|
+
let selector = positional[3];
|
|
2370
|
+
if (source === "from") {
|
|
2371
|
+
source = positional[3];
|
|
2372
|
+
selector = positional[4];
|
|
2373
|
+
}
|
|
2374
|
+
if (!source) throw new Error(`Missing source. e.g. agentry add ${k1} author/repo or agentry add ${k1} from <url>`);
|
|
2375
|
+
await cmdAdd(k1, source, selector, opts);
|
|
2932
2376
|
return;
|
|
2933
2377
|
}
|
|
2934
2378
|
const source = a1;
|