@mhmdreza-rafiei/agentry 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +51 -20
  2. package/dist/cli.mjs +309 -62
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -24,11 +24,15 @@ npx @mhmdreza-rafiei/agentry@latest --help
24
24
  ## Quick start
25
25
 
26
26
  ```bash
27
- # Browse + install skills from a repo
27
+ # Install a standalone single-skill repository (root SKILL.md)
28
+ agentry add author/my-skill
29
+
30
+ # Browse + install from a catalog repo (kind is optional or explicit)
31
+ agentry add mhmdreza-rafiei/agent-tools
28
32
  agentry add skills mhmdreza-rafiei/agent-tools
29
33
 
30
34
  # Install one skill to Cursor
31
- agentry add skills mhmdreza-rafiei/agent-tools enhance-prompt -a cursor
35
+ agentry add mhmdreza-rafiei/agent-tools enhance-prompt -a cursor
32
36
 
33
37
  # See what is installed
34
38
  agentry list
@@ -90,36 +94,37 @@ agentry list skills
90
94
 
91
95
  | Command | Description |
92
96
  | --- | --- |
93
- | `agentry add <kind> <source> [selector]` | Discover and install artifacts |
97
+ | `agentry add [kind] <source> [selector]` | Discover and install artifacts (kind is optional) |
94
98
  | `agentry add profile <name> [source]` | Apply a local `profile/<name>.yaml` bundle |
95
99
  | `agentry list` | List installed artifacts |
96
100
  | `agentry list <kind>` | List installed artifacts of one kind |
97
101
  | `agentry list <source> [kind] [selector]` | List what a repo/path contains |
98
- | `agentry remove <kind> [source] [selector]` | Remove installs (files + lock) |
102
+ | `agentry remove [kind] [source] [selector]` | Remove installs (files + lock) |
99
103
  | `agentry update [kind] [source] [selector]` | Re-install from source or lockfile |
100
104
  | `agentry init <kind> [name] [category]` | Scaffold a new artifact |
101
105
  | `agentry update` | Update the Agentry CLI itself |
102
106
  | `agentry uninstall` | Uninstall the Agentry CLI |
103
107
 
104
- `<kind>` is `skill | rule | agent | profile | script` (plurals accepted).
108
+ `<kind>` is `skill | rule | agent | profile | script` (plurals accepted; omit to match all).
105
109
 
106
110
  ### Source formats
107
111
 
108
112
  ```bash
109
- # GitHub shorthand
113
+ # GitHub shorthand (single-skill repo or catalog)
114
+ agentry add mhmdreza-rafiei/agent-tools
110
115
  agentry add skills mhmdreza-rafiei/agent-tools
111
116
 
112
117
  # Full GitHub URL
113
- agentry add skills https://github.com/mhmdreza-rafiei/agent-tools
118
+ agentry add https://github.com/mhmdreza-rafiei/agent-tools
114
119
 
115
120
  # Tree URL (subdir + ref)
116
- agentry add skills https://github.com/mhmdreza-rafiei/agent-tools/tree/main/skills
121
+ agentry add https://github.com/mhmdreza-rafiei/agent-tools/tree/main/skills
117
122
 
118
123
  # Local path
119
- agentry add skills ./my-skills
124
+ agentry add ./my-skills
120
125
 
121
126
  # Any git URL
122
- agentry add skills git@github.com:mhmdreza-rafiei/agent-tools.git
127
+ agentry add git@github.com:mhmdreza-rafiei/agent-tools.git
123
128
  ```
124
129
 
125
130
  ### Options
@@ -145,10 +150,10 @@ agentry add skills git@github.com:mhmdreza-rafiei/agent-tools.git
145
150
  # List everything in a catalog repo
146
151
  agentry list mhmdreza-rafiei/agent-tools
147
152
  agentry list mhmdreza-rafiei/agent-tools skills
148
- agentry list mhmdreza-rafiei/agent-tools skills enhance-prompt
153
+ agentry list mhmdreza-rafiei/agent-tools enhance-prompt
149
154
 
150
155
  # Install
151
- agentry add skills mhmdreza-rafiei/agent-tools
156
+ agentry add mhmdreza-rafiei/agent-tools
152
157
  agentry add skills mhmdreza-rafiei/agent-tools enhance-prompt -a cursor -a claude-code
153
158
  agentry add rules mhmdreza-rafiei/agent-tools ask-dont-guess
154
159
  agentry add agents mhmdreza-rafiei/agent-tools frontend-developer
@@ -159,7 +164,7 @@ agentry list skills
159
164
 
160
165
  # Update / remove by selector or by install source
161
166
  agentry update skills mhmdreza-rafiei/agent-tools
162
- agentry remove skills enhance-prompt
167
+ agentry remove enhance-prompt
163
168
  agentry remove skills mhmdreza-rafiei/agent-tools
164
169
 
165
170
  # Scaffold
@@ -173,18 +178,33 @@ Example catalog used throughout this README: [`mhmdreza-rafiei/agent-tools`](htt
173
178
  ## How it works
174
179
 
175
180
  1. Resolve a source (local path or shallow git clone).
176
- 2. Discover artifacts under `<kind>s/` **and** root markers (`SKILL.md`, `.mdc`).
177
- 3. Let you pick what to install (searchable list + `All`).
178
- 4. Install once to `.agents/skills/<id>` for universal providers; symlink (or `--copy`) for others.
179
- 5. Track installs in `.agentry/lock.json` for remove/update.
181
+ 2. Discover artifacts under `<kind>s/` **and** root markers:
182
+ - Root `SKILL.md` (standalone single-skill repo)
183
+ - Root `AGENT.md` / `RULE.md` / `.cursorrules` / `.mdc` files
184
+ - Root `profile/<name>.yaml` / `*.profile.yaml` / `profile.yaml`
185
+ - Root scripts (`run.sh`, `script.sh`, `run.js`)
186
+ 3. Filter out directories/files ignored by `.agentryignore` or `.agentry.ignore` (and built-in VCS ignores like `.git`).
187
+ 4. Let you pick what to install (searchable list + `All`).
188
+ 5. Install once to `.agents/skills/<id>` for universal providers; symlink (or `--copy`) for others.
189
+ 6. Track installs in `.agentry/lock.json` for remove/update.
180
190
 
181
191
  If a clone fails and a prior cache exists, Agentry asks whether to load from cache (`~/.cache/agentry/repos`).
182
192
 
183
193
  ## Artifact layouts
184
194
 
185
- Author installable tools in a source repo like this:
195
+ Author installable tools in a source repo:
186
196
 
187
- ### Skills
197
+ ### Single-skill repo (Root layout)
198
+
199
+ ```text
200
+ SKILL.md
201
+ references/
202
+ GUIDE.md
203
+ scripts/
204
+ tool.sh
205
+ ```
206
+
207
+ ### Multi-skill catalog
188
208
 
189
209
  ```text
190
210
  skills/
@@ -216,7 +236,18 @@ profiles/
216
236
  frontend.yaml
217
237
  ```
218
238
 
219
- Local install profiles (consumed by `agentry add profile`) live at `profile/<name>.yaml`.
239
+ Local install profiles live at `profile/<name>.yaml`.
240
+
241
+ ### Excluding development assets (`.agentryignore`)
242
+
243
+ Place `.agentryignore` (or `.agentry.ignore`) in the root of your repo to prevent internal tools from being discovered or installed:
244
+
245
+ ```gitignore
246
+ # Exclude internal dev skills
247
+ dev-skills/
248
+ skills/internal-*
249
+ *.draft.mdc
250
+ ```
220
251
 
221
252
  ## Supported agents
222
253
 
package/dist/cli.mjs CHANGED
@@ -5,7 +5,7 @@ import https from "node:https";
5
5
  import { createHash } from "node:crypto";
6
6
  import { copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
7
7
  import { homedir, tmpdir } from "node:os";
8
- import { dirname, join, resolve, sep } from "node:path";
8
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
9
9
  import { xdgCache, xdgConfig } from "xdg-basedir";
10
10
  import { parse, stringify } from "yaml";
11
11
  import { z } from "zod";
@@ -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.2.2";
19
+ var version = "0.3.0";
20
20
  const ARTIFACT_KINDS = [
21
21
  "skill",
22
22
  "rule",
@@ -243,7 +243,100 @@ async function resolveSource(source, opts = {}) {
243
243
  saveToCache(parsed, tmp);
244
244
  return withSubpath(tmp, parsed.subpath, tmp);
245
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
+ }
246
336
  const SKILL_MARKER = "SKILL.md";
337
+ const AGENT_MARKER = "AGENT.md";
338
+ const RULE_MARKER = "RULE.md";
339
+ const CURSORRULES_MARKER = ".cursorrules";
247
340
  const FILE_EXT = ".mdc";
248
341
  const PROFILE_EXTS = [".yaml", ".yml"];
249
342
  const KIND_DIRS = {
@@ -273,6 +366,12 @@ function firstHeading(md) {
273
366
  const m = md.match(/^#\s+(.+)$/m);
274
367
  return m ? m[1].trim() : "";
275
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
+ }
276
375
  function frontmatterDescription(md) {
277
376
  const fm = md.match(/^---\r?\n([\s\S]*?)\r?\n---/);
278
377
  if (!fm) return "";
@@ -320,69 +419,178 @@ function makeArtifact(kind, category, name, srcPath, isFile) {
320
419
  description: isFile ? readDescription(srcPath) : folderDescription(srcPath)
321
420
  };
322
421
  }
323
- function collectSkills(base, out) {
422
+ function rel(root, fullPath) {
423
+ return relative(root, fullPath).replace(/\\/g, "/");
424
+ }
425
+ function collectSkills(root, base, out, matcher) {
324
426
  for (const child of entries(base)) {
325
427
  if (!child.isDirectory()) continue;
326
428
  const childDir = join(base, child.name);
429
+ if (matcher(rel(root, childDir), true)) continue;
327
430
  if (existsSync(join(childDir, SKILL_MARKER))) out.push(makeArtifact("skill", null, child.name, childDir, false));
328
431
  else for (const item of entries(childDir)) {
329
432
  if (!item.isDirectory()) continue;
330
433
  const itemDir = join(childDir, item.name);
434
+ if (matcher(rel(root, itemDir), true)) continue;
331
435
  if (existsSync(join(itemDir, SKILL_MARKER))) out.push(makeArtifact("skill", child.name, item.name, itemDir, false));
332
436
  }
333
437
  }
334
438
  }
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));
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
+ }
340
455
  }
341
456
  }
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));
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
+ }
351
474
  }
352
475
  }
353
476
  }
354
- function collectScripts(base, out) {
477
+ function collectScripts(root, base, out, matcher) {
355
478
  for (const child of entries(base)) {
356
479
  if (!child.isDirectory()) continue;
357
480
  const childDir = join(base, child.name);
481
+ if (matcher(rel(root, childDir), true)) continue;
358
482
  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));
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
+ }
360
488
  }
361
489
  }
362
- function collectRootSkills(root, out) {
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
+ }
363
500
  for (const child of entries(root)) {
364
501
  if (!child.isDirectory() || child.name.startsWith(".") || ROOT_SKIP.has(child.name)) continue;
365
502
  const childDir = join(root, child.name);
503
+ if (matcher(child.name, true)) continue;
366
504
  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));
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
+ }
368
511
  }
369
512
  }
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));
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
+ }
375
580
  }
376
581
  }
377
582
  function listKind(root, kind) {
378
583
  const out = [];
584
+ const matcher = createIgnoreMatcher(root);
379
585
  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);
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);
386
594
  const seen = /* @__PURE__ */ new Set();
387
595
  return out.filter((a) => seen.has(a.id) ? false : seen.add(a.id)).sort((a, b) => a.id.localeCompare(b.id));
388
596
  }
@@ -391,7 +599,8 @@ function listAll(root) {
391
599
  "skill",
392
600
  "rule",
393
601
  "agent",
394
- "profile"
602
+ "profile",
603
+ "script"
395
604
  ].flatMap((k) => listKind(root, k));
396
605
  }
397
606
  function select(root, kind, selector) {
@@ -402,6 +611,14 @@ function select(root, kind, selector) {
402
611
  if (byCategory.length) return byCategory;
403
612
  return items.filter((a) => a.name === selector);
404
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
+ }
405
622
  const home$1 = homedir();
406
623
  const configHome = xdgConfig ?? join(home$1, ".config");
407
624
  const codexHome = process.env.CODEX_HOME?.trim() || join(home$1, ".codex");
@@ -583,7 +800,7 @@ function getNonUniversalAgents() {
583
800
  return listAgents().filter((a) => a.skillsDir !== ".agents/skills");
584
801
  }
585
802
  const home = homedir();
586
- function copyTreeDereferenced(src, dest, seen = /* @__PURE__ */ new Set()) {
803
+ function copyTreeDereferenced(src, dest, seen = /* @__PURE__ */ new Set(), filter, baseSrc = src) {
587
804
  const st = lstatSync(src);
588
805
  if (st.isSymbolicLink()) {
589
806
  let real;
@@ -594,7 +811,7 @@ function copyTreeDereferenced(src, dest, seen = /* @__PURE__ */ new Set()) {
594
811
  }
595
812
  if (seen.has(real)) return;
596
813
  seen.add(real);
597
- copyTreeDereferenced(real, dest, seen);
814
+ copyTreeDereferenced(real, dest, seen, filter, baseSrc);
598
815
  return;
599
816
  }
600
817
  if (st.isDirectory()) {
@@ -607,7 +824,17 @@ function copyTreeDereferenced(src, dest, seen = /* @__PURE__ */ new Set()) {
607
824
  if (seen.has(real)) return;
608
825
  seen.add(real);
609
826
  mkdirSync(dest, { recursive: true });
610
- for (const name of readdirSync(src)) copyTreeDereferenced(join(src, name), join(dest, name), seen);
827
+ for (const name of readdirSync(src)) {
828
+ const childSrc = join(src, name);
829
+ const relPath = relative(baseSrc, childSrc).replace(/\\/g, "/");
830
+ let isDir = false;
831
+ try {
832
+ isDir = statSync(childSrc).isDirectory();
833
+ } catch {}
834
+ if (DEFAULT_IGNORES.includes(name) || name === ".agentryignore" || name === ".agentry.ignore") continue;
835
+ if (filter && filter(relPath, isDir)) continue;
836
+ copyTreeDereferenced(childSrc, join(dest, name), seen, filter, baseSrc);
837
+ }
611
838
  return;
612
839
  }
613
840
  if (st.isFile()) {
@@ -735,6 +962,7 @@ function installOne(artifact, opts, agentList, source) {
735
962
  });
736
963
  return dests;
737
964
  }
965
+ const ignoreFilter = artifact.isFile ? void 0 : createIgnoreMatcher(artifact.dir);
738
966
  if (artifact.kind === "skill") {
739
967
  const canonical = join(base, ".agents", "skills", artifact.id);
740
968
  rmSync(canonical, {
@@ -742,7 +970,7 @@ function installOne(artifact, opts, agentList, source) {
742
970
  force: true
743
971
  });
744
972
  mkdirSync(join(canonical, ".."), { recursive: true });
745
- copyTreeDereferenced(artifact.dir, canonical);
973
+ copyTreeDereferenced(artifact.dir, canonical, /* @__PURE__ */ new Set(), ignoreFilter);
746
974
  for (const a of list) if (a.skillsDir === ".agents/skills") dests.push({
747
975
  agent: a.name,
748
976
  dir: canonical
@@ -782,7 +1010,7 @@ function installOne(artifact, opts, agentList, source) {
782
1010
  force: true
783
1011
  });
784
1012
  mkdirSync(join(dir, ".."), { recursive: true });
785
- copyTreeDereferenced(artifact.dir, dir);
1013
+ copyTreeDereferenced(artifact.dir, dir, /* @__PURE__ */ new Set(), ignoreFilter);
786
1014
  dests.push({
787
1015
  agent: a.name,
788
1016
  dir
@@ -1928,7 +2156,7 @@ async function cmdAdd(kind, source, selector, opts) {
1928
2156
  await delayQuiet(280);
1929
2157
  const { root, cleanup } = await resolveSourceWithUi(source, opts);
1930
2158
  try {
1931
- const artifacts = await spinner("Discovering…", () => kind === "all" ? listAll(root) : select(root, kind, selector), (a) => {
2159
+ const artifacts = await spinner("Discovering…", () => kind === "all" ? selector ? selectAll(root, selector) : listAll(root) : select(root, kind, selector), (a) => {
1932
2160
  const noun = kind === "all" ? "artifact" : kind;
1933
2161
  return `Found ${a.length} ${noun}${a.length === 1 ? "" : "s"}`;
1934
2162
  }, 620);
@@ -1943,17 +2171,20 @@ async function cmdAdd(kind, source, selector, opts) {
1943
2171
  return;
1944
2172
  }
1945
2173
  let chosen = artifacts;
1946
- if (!selector && kind !== "all" && artifacts.length > 1 && !opts.all && !opts.yes && !isQuiet()) {
1947
- await sectionTransition("source discovery", `select ${kind}s to install`);
1948
- const picked = await selectArtifacts(`Select ${kind}s to install`, artifacts);
2174
+ if (!selector && artifacts.length > 1 && !opts.all && !opts.yes && !isQuiet()) {
2175
+ const noun = kind === "all" ? "artifacts" : `${kind}s`;
2176
+ await sectionTransition("source discovery", `select ${noun} to install`);
2177
+ const picked = await selectArtifacts(`Select ${noun} to install`, artifacts);
1949
2178
  if (isCancelLike(picked)) {
1950
2179
  outro("Cancelled.");
1951
2180
  return;
1952
2181
  }
1953
2182
  chosen = picked;
1954
- await sectionTransition(`${chosen.length} ${kind}${chosen.length === 1 ? "" : "s"} selected`, "choose target agents");
1955
- } else if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition(`${chosen.length} ${kind}${chosen.length === 1 ? "" : "s"} ready`, "choose target agents");
1956
- else {
2183
+ await sectionTransition(`${chosen.length} ${chosen.length === 1 ? "artifact" : "artifacts"} selected`, "choose target agents");
2184
+ } else if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) {
2185
+ const noun = kind === "all" ? chosen.length === 1 ? "artifact" : "artifacts" : `${kind}${chosen.length === 1 ? "" : "s"}`;
2186
+ await sectionTransition(`${chosen.length} ${noun} ready`, "choose target agents");
2187
+ } else {
1957
2188
  await delayQuiet(200);
1958
2189
  blank();
1959
2190
  }
@@ -2108,7 +2339,7 @@ async function cmdRemove(kind, arg1, arg2, opts) {
2108
2339
  let count = 0;
2109
2340
  let paths = 0;
2110
2341
  for (const t of ARTIFACT_KINDS) {
2111
- const r = removeSelection(t, void 0, installOpts, agentList, source);
2342
+ const r = removeSelection(t, selector, installOpts, agentList, source);
2112
2343
  count += r.removedKeys.length;
2113
2344
  paths += r.removedPaths.length;
2114
2345
  }
@@ -2139,7 +2370,7 @@ async function cmdList(source, kind, selector, opts) {
2139
2370
  try {
2140
2371
  printArtifactList(await spinner("Discovering…", () => {
2141
2372
  if (kind) return select(root, kind, selector);
2142
- return listAll(root);
2373
+ return selector ? selectAll(root, selector) : listAll(root);
2143
2374
  }, (a) => `Found ${a.length} artifact${a.length === 1 ? "" : "s"}`, 520), {
2144
2375
  title: "Available in source",
2145
2376
  subtitle: source
@@ -2238,22 +2469,26 @@ async function cmdUpdateAssets(kind, arg1, arg2, opts) {
2238
2469
  }, 520);
2239
2470
  const { root, cleanup } = await resolveSourceWithUi(source, opts);
2240
2471
  try {
2241
- const artifacts = await spinner("Discovering…", () => kind === "all" ? listAll(root) : select(root, kind, selector), (a) => `Found ${a.length} to update`, 620);
2472
+ 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
2473
  if (!artifacts.length) {
2243
2474
  warn(`Nothing found in ${source}.`);
2244
2475
  return;
2245
2476
  }
2246
2477
  let chosen = artifacts;
2247
- if (!selector && kind !== "all" && artifacts.length > 1 && !opts.all && !opts.yes && !isQuiet()) {
2248
- await sectionTransition("source discovery", `select ${kind}s to update`);
2249
- const picked = await selectArtifacts(`Select ${kind}s to update`, artifacts);
2478
+ if (!selector && artifacts.length > 1 && !opts.all && !opts.yes && !isQuiet()) {
2479
+ const noun = kind === "all" ? "artifacts" : `${kind}s`;
2480
+ await sectionTransition("source discovery", `select ${noun} to update`);
2481
+ const picked = await selectArtifacts(`Select ${noun} to update`, artifacts);
2250
2482
  if (isCancelLike(picked)) {
2251
2483
  outro("Cancelled.");
2252
2484
  return;
2253
2485
  }
2254
2486
  chosen = picked;
2255
- await sectionTransition(`${chosen.length} ${kind}${chosen.length === 1 ? "" : "s"} selected`, "choose target agents");
2256
- } else if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition(`${chosen.length} ${kind}${chosen.length === 1 ? "" : "s"} ready`, "choose target agents");
2487
+ await sectionTransition(`${chosen.length} ${chosen.length === 1 ? "artifact" : "artifacts"} selected`, "choose target agents");
2488
+ } else if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) {
2489
+ const noun = kind === "all" ? chosen.length === 1 ? "artifact" : "artifacts" : `${kind}${chosen.length === 1 ? "" : "s"}`;
2490
+ await sectionTransition(`${chosen.length} ${noun} ready`, "choose target agents");
2491
+ }
2257
2492
  const agentList = await resolveAgentList(opts, "update");
2258
2493
  if (!await ensureInstallScope(opts, "update")) return;
2259
2494
  if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition(`${agentList.length} agent${agentList.length === 1 ? "" : "s"} selected`, "update summary");
@@ -2683,21 +2918,33 @@ async function main() {
2683
2918
  return;
2684
2919
  }
2685
2920
  if (action === "add") {
2686
- const kind = positional[1] ? normalizeKind(positional[1]) : void 0;
2687
- if (!kind) throw new Error("Missing kind. e.g. agentry add skills author/repo");
2688
- if (kind === "profile") {
2921
+ 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");
2923
+ const k1 = normalizeKind(a1);
2924
+ if (k1 === "profile") {
2689
2925
  await cmdAddProfile(positional[2], positional[3], opts);
2690
2926
  return;
2691
2927
  }
2692
- if (kind !== "all" && !isKind(kind)) throw new Error(`Unknown kind: ${kind}`);
2693
- await cmdAdd(kind, positional[2], positional[3], opts);
2928
+ 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);
2932
+ return;
2933
+ }
2934
+ const source = a1;
2935
+ const selector = positional[2];
2936
+ await cmdAdd("all", source, selector, opts);
2694
2937
  return;
2695
2938
  }
2696
2939
  if (action === "remove" || action === "rm") {
2697
- const kind = positional[1] ? normalizeKind(positional[1]) : void 0;
2698
- if (!kind) throw new Error("Missing kind. e.g. agentry remove skills enhance-prompt");
2699
- if (kind !== "all" && !isKind(kind)) throw new Error(`Unknown kind: ${kind}`);
2700
- await cmdRemove(kind, positional[2], positional[3], opts);
2940
+ const a1 = positional[1];
2941
+ if (!a1) throw new Error("Missing kind or selector. e.g. agentry remove skills enhance-prompt");
2942
+ const k1 = normalizeKind(a1);
2943
+ if (k1 === "all" || isKind(k1)) {
2944
+ await cmdRemove(k1, positional[2], positional[3], opts);
2945
+ return;
2946
+ }
2947
+ await cmdRemove("all", positional[1], positional[2], opts);
2701
2948
  return;
2702
2949
  }
2703
2950
  if (action === "init") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhmdreza-rafiei/agentry",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "One CLI to install agents, skills, rules, and profiles onto AI coding agents — portable AI/agent tooling.",
5
5
  "type": "module",
6
6
  "bin": {