@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/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 { createHash } from "node:crypto";
6
- import { copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
7
- import { homedir, tmpdir } from "node:os";
8
- import { basename, dirname, join, relative, resolve, sep } from "node:path";
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.0";
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
- const desc = (a.description || "").replace(/\s+/g, " ").trim().slice(0, 52);
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, agentList);
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
- const source = positional[2];
2930
- if (!source) throw new Error(`Missing source. e.g. agentry add ${k1} author/repo`);
2931
- await cmdAdd(k1, source, positional[3], opts);
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;