@mhmdreza-rafiei/agentry 0.2.2 → 0.3.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 +62 -21
- 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 +84 -413
- 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.
|
|
19
|
+
var version = "0.3.1";
|
|
20
20
|
const ARTIFACT_KINDS = [
|
|
21
21
|
"skill",
|
|
22
22
|
"rule",
|
|
@@ -24,384 +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 SKILL_MARKER = "SKILL.md";
|
|
247
|
-
const FILE_EXT = ".mdc";
|
|
248
|
-
const PROFILE_EXTS = [".yaml", ".yml"];
|
|
249
|
-
const KIND_DIRS = {
|
|
250
|
-
skill: "skills",
|
|
251
|
-
rule: "rules",
|
|
252
|
-
agent: "agents",
|
|
253
|
-
profile: "profiles",
|
|
254
|
-
script: "scripts"
|
|
255
|
-
};
|
|
256
|
-
const ROOT_SKIP = /* @__PURE__ */ new Set([
|
|
257
|
-
".",
|
|
258
|
-
"node_modules",
|
|
259
|
-
"skills",
|
|
260
|
-
"rules",
|
|
261
|
-
"agents",
|
|
262
|
-
"profiles",
|
|
263
|
-
"scripts"
|
|
264
|
-
]);
|
|
265
|
-
function entries(dir) {
|
|
266
|
-
try {
|
|
267
|
-
return readdirSync(dir, { withFileTypes: true });
|
|
268
|
-
} catch {
|
|
269
|
-
return [];
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
function firstHeading(md) {
|
|
273
|
-
const m = md.match(/^#\s+(.+)$/m);
|
|
274
|
-
return m ? m[1].trim() : "";
|
|
275
|
-
}
|
|
276
|
-
function frontmatterDescription(md) {
|
|
277
|
-
const fm = md.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
278
|
-
if (!fm) return "";
|
|
279
|
-
const lines = fm[1].split(/\r?\n/);
|
|
280
|
-
for (let i = 0; i < lines.length; i++) {
|
|
281
|
-
const dm = lines[i].match(/^description:\s*(.*)$/);
|
|
282
|
-
if (!dm) continue;
|
|
283
|
-
let val = dm[1].trim();
|
|
284
|
-
if (val === "" || val === ">" || val === "|" || val === ">-" || val === "|-") {
|
|
285
|
-
const collected = [];
|
|
286
|
-
for (let j = i + 1; j < lines.length; j++) if (/^\s+\S/.test(lines[j])) collected.push(lines[j].trim());
|
|
287
|
-
else break;
|
|
288
|
-
val = collected.join(" ");
|
|
289
|
-
}
|
|
290
|
-
return val.trim();
|
|
291
|
-
}
|
|
292
|
-
return "";
|
|
293
|
-
}
|
|
294
|
-
function readDescription(filePath) {
|
|
295
|
-
try {
|
|
296
|
-
const md = readFileSync(filePath, "utf8");
|
|
297
|
-
return frontmatterDescription(md) || firstHeading(md);
|
|
298
|
-
} catch {
|
|
299
|
-
return "";
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
function folderDescription(dir) {
|
|
303
|
-
for (const f of [SKILL_MARKER, "README.md"]) {
|
|
304
|
-
const p = join(dir, f);
|
|
305
|
-
if (existsSync(p)) {
|
|
306
|
-
const d = readDescription(p);
|
|
307
|
-
if (d) return d;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
return "";
|
|
311
|
-
}
|
|
312
|
-
function makeArtifact(kind, category, name, srcPath, isFile) {
|
|
313
|
-
return {
|
|
314
|
-
kind,
|
|
315
|
-
category,
|
|
316
|
-
name,
|
|
317
|
-
id: category ? `${category}/${name}` : name,
|
|
318
|
-
dir: srcPath,
|
|
319
|
-
isFile,
|
|
320
|
-
description: isFile ? readDescription(srcPath) : folderDescription(srcPath)
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
function collectSkills(base, out) {
|
|
324
|
-
for (const child of entries(base)) {
|
|
325
|
-
if (!child.isDirectory()) continue;
|
|
326
|
-
const childDir = join(base, child.name);
|
|
327
|
-
if (existsSync(join(childDir, SKILL_MARKER))) out.push(makeArtifact("skill", null, child.name, childDir, false));
|
|
328
|
-
else for (const item of entries(childDir)) {
|
|
329
|
-
if (!item.isDirectory()) continue;
|
|
330
|
-
const itemDir = join(childDir, item.name);
|
|
331
|
-
if (existsSync(join(itemDir, SKILL_MARKER))) out.push(makeArtifact("skill", child.name, item.name, itemDir, false));
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
function collectMdc(kind, base, out) {
|
|
336
|
-
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));
|
|
337
|
-
else if (child.isDirectory()) {
|
|
338
|
-
const subDir = join(base, child.name);
|
|
339
|
-
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));
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
function collectProfiles(base, out) {
|
|
343
|
-
for (const child of entries(base)) if (child.isFile() && PROFILE_EXTS.some((e) => child.name.endsWith(e))) {
|
|
344
|
-
const stem = child.name.replace(/\.(yaml|yml)$/, "");
|
|
345
|
-
out.push(makeArtifact("profile", null, stem, join(base, child.name), true));
|
|
346
|
-
} else if (child.isDirectory()) {
|
|
347
|
-
const subDir = join(base, child.name);
|
|
348
|
-
for (const item of entries(subDir)) if (item.isFile() && PROFILE_EXTS.some((e) => item.name.endsWith(e))) {
|
|
349
|
-
const stem = item.name.replace(/\.(yaml|yml)$/, "");
|
|
350
|
-
out.push(makeArtifact("profile", child.name, stem, join(subDir, item.name), true));
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
function collectScripts(base, out) {
|
|
355
|
-
for (const child of entries(base)) {
|
|
356
|
-
if (!child.isDirectory()) continue;
|
|
357
|
-
const childDir = join(base, child.name);
|
|
358
|
-
if (entries(childDir).some((e) => e.isFile())) out.push(makeArtifact("script", null, child.name, childDir, false));
|
|
359
|
-
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));
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
function collectRootSkills(root, out) {
|
|
363
|
-
for (const child of entries(root)) {
|
|
364
|
-
if (!child.isDirectory() || child.name.startsWith(".") || ROOT_SKIP.has(child.name)) continue;
|
|
365
|
-
const childDir = join(root, child.name);
|
|
366
|
-
if (existsSync(join(childDir, SKILL_MARKER))) out.push(makeArtifact("skill", null, child.name, childDir, false));
|
|
367
|
-
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));
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
function collectRootMdc(kind, root, out) {
|
|
371
|
-
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));
|
|
372
|
-
else if (child.isDirectory() && !child.name.startsWith(".") && !ROOT_SKIP.has(child.name)) {
|
|
373
|
-
const subDir = join(root, child.name);
|
|
374
|
-
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));
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
function listKind(root, kind) {
|
|
378
|
-
const out = [];
|
|
379
|
-
const base = join(root, KIND_DIRS[kind]);
|
|
380
|
-
if (existsSync(base)) if (kind === "skill") collectSkills(base, out);
|
|
381
|
-
else if (kind === "profile") collectProfiles(base, out);
|
|
382
|
-
else if (kind === "script") collectScripts(base, out);
|
|
383
|
-
else collectMdc(kind, base, out);
|
|
384
|
-
if (kind === "skill") collectRootSkills(root, out);
|
|
385
|
-
else if (kind === "agent" || kind === "rule") collectRootMdc(kind, root, out);
|
|
386
|
-
const seen = /* @__PURE__ */ new Set();
|
|
387
|
-
return out.filter((a) => seen.has(a.id) ? false : seen.add(a.id)).sort((a, b) => a.id.localeCompare(b.id));
|
|
388
|
-
}
|
|
389
|
-
function listAll(root) {
|
|
390
|
-
return [
|
|
391
|
-
"skill",
|
|
392
|
-
"rule",
|
|
393
|
-
"agent",
|
|
394
|
-
"profile"
|
|
395
|
-
].flatMap((k) => listKind(root, k));
|
|
396
|
-
}
|
|
397
|
-
function select(root, kind, selector) {
|
|
398
|
-
const items = listKind(root, kind);
|
|
399
|
-
if (!selector) return items;
|
|
400
|
-
if (selector.includes("/")) return items.filter((a) => a.id === selector);
|
|
401
|
-
const byCategory = items.filter((a) => a.category === selector);
|
|
402
|
-
if (byCategory.length) return byCategory;
|
|
403
|
-
return items.filter((a) => a.name === selector);
|
|
404
|
-
}
|
|
405
27
|
const home$1 = homedir();
|
|
406
28
|
const configHome = xdgConfig ?? join(home$1, ".config");
|
|
407
29
|
const codexHome = process.env.CODEX_HOME?.trim() || join(home$1, ".codex");
|
|
@@ -583,7 +205,7 @@ function getNonUniversalAgents() {
|
|
|
583
205
|
return listAgents().filter((a) => a.skillsDir !== ".agents/skills");
|
|
584
206
|
}
|
|
585
207
|
const home = homedir();
|
|
586
|
-
function copyTreeDereferenced(src, dest, seen = /* @__PURE__ */ new Set()) {
|
|
208
|
+
function copyTreeDereferenced(src, dest, seen = /* @__PURE__ */ new Set(), filter, baseSrc = src) {
|
|
587
209
|
const st = lstatSync(src);
|
|
588
210
|
if (st.isSymbolicLink()) {
|
|
589
211
|
let real;
|
|
@@ -594,7 +216,7 @@ function copyTreeDereferenced(src, dest, seen = /* @__PURE__ */ new Set()) {
|
|
|
594
216
|
}
|
|
595
217
|
if (seen.has(real)) return;
|
|
596
218
|
seen.add(real);
|
|
597
|
-
copyTreeDereferenced(real, dest, seen);
|
|
219
|
+
copyTreeDereferenced(real, dest, seen, filter, baseSrc);
|
|
598
220
|
return;
|
|
599
221
|
}
|
|
600
222
|
if (st.isDirectory()) {
|
|
@@ -607,7 +229,17 @@ function copyTreeDereferenced(src, dest, seen = /* @__PURE__ */ new Set()) {
|
|
|
607
229
|
if (seen.has(real)) return;
|
|
608
230
|
seen.add(real);
|
|
609
231
|
mkdirSync(dest, { recursive: true });
|
|
610
|
-
for (const name of readdirSync(src))
|
|
232
|
+
for (const name of readdirSync(src)) {
|
|
233
|
+
const childSrc = join(src, name);
|
|
234
|
+
const relPath = relative(baseSrc, childSrc).replace(/\\/g, "/");
|
|
235
|
+
let isDir = false;
|
|
236
|
+
try {
|
|
237
|
+
isDir = statSync(childSrc).isDirectory();
|
|
238
|
+
} catch {}
|
|
239
|
+
if (DEFAULT_IGNORES.includes(name) || name === ".agentryignore" || name === ".agentry.ignore") continue;
|
|
240
|
+
if (filter && filter(relPath, isDir)) continue;
|
|
241
|
+
copyTreeDereferenced(childSrc, join(dest, name), seen, filter, baseSrc);
|
|
242
|
+
}
|
|
611
243
|
return;
|
|
612
244
|
}
|
|
613
245
|
if (st.isFile()) {
|
|
@@ -735,6 +367,7 @@ function installOne(artifact, opts, agentList, source) {
|
|
|
735
367
|
});
|
|
736
368
|
return dests;
|
|
737
369
|
}
|
|
370
|
+
const ignoreFilter = artifact.isFile ? void 0 : createIgnoreMatcher(artifact.dir);
|
|
738
371
|
if (artifact.kind === "skill") {
|
|
739
372
|
const canonical = join(base, ".agents", "skills", artifact.id);
|
|
740
373
|
rmSync(canonical, {
|
|
@@ -742,7 +375,7 @@ function installOne(artifact, opts, agentList, source) {
|
|
|
742
375
|
force: true
|
|
743
376
|
});
|
|
744
377
|
mkdirSync(join(canonical, ".."), { recursive: true });
|
|
745
|
-
copyTreeDereferenced(artifact.dir, canonical);
|
|
378
|
+
copyTreeDereferenced(artifact.dir, canonical, /* @__PURE__ */ new Set(), ignoreFilter);
|
|
746
379
|
for (const a of list) if (a.skillsDir === ".agents/skills") dests.push({
|
|
747
380
|
agent: a.name,
|
|
748
381
|
dir: canonical
|
|
@@ -782,7 +415,7 @@ function installOne(artifact, opts, agentList, source) {
|
|
|
782
415
|
force: true
|
|
783
416
|
});
|
|
784
417
|
mkdirSync(join(dir, ".."), { recursive: true });
|
|
785
|
-
copyTreeDereferenced(artifact.dir, dir);
|
|
418
|
+
copyTreeDereferenced(artifact.dir, dir, /* @__PURE__ */ new Set(), ignoreFilter);
|
|
786
419
|
dests.push({
|
|
787
420
|
agent: a.name,
|
|
788
421
|
dir
|
|
@@ -1928,7 +1561,7 @@ async function cmdAdd(kind, source, selector, opts) {
|
|
|
1928
1561
|
await delayQuiet(280);
|
|
1929
1562
|
const { root, cleanup } = await resolveSourceWithUi(source, opts);
|
|
1930
1563
|
try {
|
|
1931
|
-
const artifacts = await spinner("Discovering…", () => kind === "all" ? listAll(root) : select(root, kind, selector), (a) => {
|
|
1564
|
+
const artifacts = await spinner("Discovering…", () => kind === "all" ? selector ? selectAll(root, selector) : listAll(root) : select(root, kind, selector), (a) => {
|
|
1932
1565
|
const noun = kind === "all" ? "artifact" : kind;
|
|
1933
1566
|
return `Found ${a.length} ${noun}${a.length === 1 ? "" : "s"}`;
|
|
1934
1567
|
}, 620);
|
|
@@ -1943,17 +1576,20 @@ async function cmdAdd(kind, source, selector, opts) {
|
|
|
1943
1576
|
return;
|
|
1944
1577
|
}
|
|
1945
1578
|
let chosen = artifacts;
|
|
1946
|
-
if (!selector &&
|
|
1947
|
-
|
|
1948
|
-
|
|
1579
|
+
if (!selector && artifacts.length > 1 && !opts.all && !opts.yes && !isQuiet()) {
|
|
1580
|
+
const noun = kind === "all" ? "artifacts" : `${kind}s`;
|
|
1581
|
+
await sectionTransition("source discovery", `select ${noun} to install`);
|
|
1582
|
+
const picked = await selectArtifacts(`Select ${noun} to install`, artifacts);
|
|
1949
1583
|
if (isCancelLike(picked)) {
|
|
1950
1584
|
outro("Cancelled.");
|
|
1951
1585
|
return;
|
|
1952
1586
|
}
|
|
1953
1587
|
chosen = picked;
|
|
1954
|
-
await sectionTransition(`${chosen.length} ${
|
|
1955
|
-
} else if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes)
|
|
1956
|
-
|
|
1588
|
+
await sectionTransition(`${chosen.length} ${chosen.length === 1 ? "artifact" : "artifacts"} selected`, "choose target agents");
|
|
1589
|
+
} else if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) {
|
|
1590
|
+
const noun = kind === "all" ? chosen.length === 1 ? "artifact" : "artifacts" : `${kind}${chosen.length === 1 ? "" : "s"}`;
|
|
1591
|
+
await sectionTransition(`${chosen.length} ${noun} ready`, "choose target agents");
|
|
1592
|
+
} else {
|
|
1957
1593
|
await delayQuiet(200);
|
|
1958
1594
|
blank();
|
|
1959
1595
|
}
|
|
@@ -2108,7 +1744,7 @@ async function cmdRemove(kind, arg1, arg2, opts) {
|
|
|
2108
1744
|
let count = 0;
|
|
2109
1745
|
let paths = 0;
|
|
2110
1746
|
for (const t of ARTIFACT_KINDS) {
|
|
2111
|
-
const r = removeSelection(t,
|
|
1747
|
+
const r = removeSelection(t, selector, installOpts, agentList, source);
|
|
2112
1748
|
count += r.removedKeys.length;
|
|
2113
1749
|
paths += r.removedPaths.length;
|
|
2114
1750
|
}
|
|
@@ -2139,7 +1775,7 @@ async function cmdList(source, kind, selector, opts) {
|
|
|
2139
1775
|
try {
|
|
2140
1776
|
printArtifactList(await spinner("Discovering…", () => {
|
|
2141
1777
|
if (kind) return select(root, kind, selector);
|
|
2142
|
-
return listAll(root);
|
|
1778
|
+
return selector ? selectAll(root, selector) : listAll(root);
|
|
2143
1779
|
}, (a) => `Found ${a.length} artifact${a.length === 1 ? "" : "s"}`, 520), {
|
|
2144
1780
|
title: "Available in source",
|
|
2145
1781
|
subtitle: source
|
|
@@ -2238,22 +1874,26 @@ async function cmdUpdateAssets(kind, arg1, arg2, opts) {
|
|
|
2238
1874
|
}, 520);
|
|
2239
1875
|
const { root, cleanup } = await resolveSourceWithUi(source, opts);
|
|
2240
1876
|
try {
|
|
2241
|
-
const artifacts = await spinner("Discovering…", () => kind === "all" ? listAll(root) : select(root, kind, selector), (a) => `Found ${a.length} to update`, 620);
|
|
1877
|
+
const artifacts = await spinner("Discovering…", () => kind === "all" ? selector ? selectAll(root, selector) : listAll(root) : select(root, kind, selector), (a) => `Found ${a.length} to update`, 620);
|
|
2242
1878
|
if (!artifacts.length) {
|
|
2243
1879
|
warn(`Nothing found in ${source}.`);
|
|
2244
1880
|
return;
|
|
2245
1881
|
}
|
|
2246
1882
|
let chosen = artifacts;
|
|
2247
|
-
if (!selector &&
|
|
2248
|
-
|
|
2249
|
-
|
|
1883
|
+
if (!selector && artifacts.length > 1 && !opts.all && !opts.yes && !isQuiet()) {
|
|
1884
|
+
const noun = kind === "all" ? "artifacts" : `${kind}s`;
|
|
1885
|
+
await sectionTransition("source discovery", `select ${noun} to update`);
|
|
1886
|
+
const picked = await selectArtifacts(`Select ${noun} to update`, artifacts);
|
|
2250
1887
|
if (isCancelLike(picked)) {
|
|
2251
1888
|
outro("Cancelled.");
|
|
2252
1889
|
return;
|
|
2253
1890
|
}
|
|
2254
1891
|
chosen = picked;
|
|
2255
|
-
await sectionTransition(`${chosen.length} ${
|
|
2256
|
-
} else if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes)
|
|
1892
|
+
await sectionTransition(`${chosen.length} ${chosen.length === 1 ? "artifact" : "artifacts"} selected`, "choose target agents");
|
|
1893
|
+
} else if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) {
|
|
1894
|
+
const noun = kind === "all" ? chosen.length === 1 ? "artifact" : "artifacts" : `${kind}${chosen.length === 1 ? "" : "s"}`;
|
|
1895
|
+
await sectionTransition(`${chosen.length} ${noun} ready`, "choose target agents");
|
|
1896
|
+
}
|
|
2257
1897
|
const agentList = await resolveAgentList(opts, "update");
|
|
2258
1898
|
if (!await ensureInstallScope(opts, "update")) return;
|
|
2259
1899
|
if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition(`${agentList.length} agent${agentList.length === 1 ? "" : "s"} selected`, "update summary");
|
|
@@ -2664,6 +2304,13 @@ async function main() {
|
|
|
2664
2304
|
await cmdUpdateAssets(kind, positional[2], positional[3], opts);
|
|
2665
2305
|
return;
|
|
2666
2306
|
}
|
|
2307
|
+
if (action === "from") {
|
|
2308
|
+
const source = positional[1];
|
|
2309
|
+
if (!source) throw new Error("Missing URL. e.g. agentry from https://awesomeskill.ai/skill/...");
|
|
2310
|
+
const selector = positional[2];
|
|
2311
|
+
await cmdAdd("all", source, selector, opts);
|
|
2312
|
+
return;
|
|
2313
|
+
}
|
|
2667
2314
|
if (action === "list") {
|
|
2668
2315
|
const a1 = positional[1];
|
|
2669
2316
|
const a2 = positional[2];
|
|
@@ -2683,21 +2330,45 @@ async function main() {
|
|
|
2683
2330
|
return;
|
|
2684
2331
|
}
|
|
2685
2332
|
if (action === "add") {
|
|
2686
|
-
const
|
|
2687
|
-
if (!
|
|
2688
|
-
if (
|
|
2333
|
+
const a1 = positional[1];
|
|
2334
|
+
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");
|
|
2335
|
+
if (a1 === "from") {
|
|
2336
|
+
const source = positional[2];
|
|
2337
|
+
if (!source) throw new Error("Missing URL after \"from\". e.g. agentry add from https://awesomeskill.ai/skill/...");
|
|
2338
|
+
const selector = positional[3];
|
|
2339
|
+
await cmdAdd("all", source, selector, opts);
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
const k1 = normalizeKind(a1);
|
|
2343
|
+
if (k1 === "profile") {
|
|
2689
2344
|
await cmdAddProfile(positional[2], positional[3], opts);
|
|
2690
2345
|
return;
|
|
2691
2346
|
}
|
|
2692
|
-
if (
|
|
2693
|
-
|
|
2347
|
+
if (k1 === "all" || isKind(k1)) {
|
|
2348
|
+
let source = positional[2];
|
|
2349
|
+
let selector = positional[3];
|
|
2350
|
+
if (source === "from") {
|
|
2351
|
+
source = positional[3];
|
|
2352
|
+
selector = positional[4];
|
|
2353
|
+
}
|
|
2354
|
+
if (!source) throw new Error(`Missing source. e.g. agentry add ${k1} author/repo or agentry add ${k1} from <url>`);
|
|
2355
|
+
await cmdAdd(k1, source, selector, opts);
|
|
2356
|
+
return;
|
|
2357
|
+
}
|
|
2358
|
+
const source = a1;
|
|
2359
|
+
const selector = positional[2];
|
|
2360
|
+
await cmdAdd("all", source, selector, opts);
|
|
2694
2361
|
return;
|
|
2695
2362
|
}
|
|
2696
2363
|
if (action === "remove" || action === "rm") {
|
|
2697
|
-
const
|
|
2698
|
-
if (!
|
|
2699
|
-
|
|
2700
|
-
|
|
2364
|
+
const a1 = positional[1];
|
|
2365
|
+
if (!a1) throw new Error("Missing kind or selector. e.g. agentry remove skills enhance-prompt");
|
|
2366
|
+
const k1 = normalizeKind(a1);
|
|
2367
|
+
if (k1 === "all" || isKind(k1)) {
|
|
2368
|
+
await cmdRemove(k1, positional[2], positional[3], opts);
|
|
2369
|
+
return;
|
|
2370
|
+
}
|
|
2371
|
+
await cmdRemove("all", positional[1], positional[2], opts);
|
|
2701
2372
|
return;
|
|
2702
2373
|
}
|
|
2703
2374
|
if (action === "init") {
|
package/package.json
CHANGED