@mhmdreza-rafiei/agentry 0.2.1 → 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.
- package/LICENSE +21 -21
- package/README.md +280 -198
- package/dist/_chunks/theme2.mjs +3 -3
- package/dist/cli.mjs +529 -88
- package/package.json +61 -53
package/dist/cli.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { animateLogo, badge, delay, printHelp, sky, theme } from "./_chunks/theme2.mjs";
|
|
3
|
-
import { execFileSync
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
4
|
import https from "node:https";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
|
-
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
|
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.
|
|
19
|
+
var version = "0.3.0";
|
|
20
20
|
const ARTIFACT_KINDS = [
|
|
21
21
|
"skill",
|
|
22
22
|
"rule",
|
|
@@ -34,15 +34,21 @@ function isLocalPath(source) {
|
|
|
34
34
|
return false;
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
+
const SAFE_REF$1 = /^[a-zA-Z0-9][\w./-]*$/;
|
|
37
38
|
function parseGithubTreeUrl(url) {
|
|
38
39
|
const m = url.match(/^https?:\/\/github\.com\/([\w.-]+\/[\w.-]+?)\/tree\/([^/]+)\/(.+)$/);
|
|
39
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}"`);
|
|
40
43
|
return {
|
|
41
44
|
ownerRepo: m[1],
|
|
42
|
-
ref
|
|
45
|
+
ref,
|
|
43
46
|
subpath: m[3].replace(/\/+$/, "")
|
|
44
47
|
};
|
|
45
48
|
}
|
|
49
|
+
function redactUrl(url) {
|
|
50
|
+
return url.replace(/:\/\/([^/@]+)@/g, "://***@");
|
|
51
|
+
}
|
|
46
52
|
function parseSource(source) {
|
|
47
53
|
if (!source || typeof source !== "string") throw new Error("Empty source.");
|
|
48
54
|
const raw = source;
|
|
@@ -140,7 +146,9 @@ function saveToCache(parsed, clonedRoot) {
|
|
|
140
146
|
cpSync(clonedRoot, dest, { recursive: true });
|
|
141
147
|
} catch {}
|
|
142
148
|
}
|
|
149
|
+
const SAFE_REF = /^[a-zA-Z0-9][\w./-]*$/;
|
|
143
150
|
function cloneTo(url, dest, ref) {
|
|
151
|
+
if (ref && !SAFE_REF.test(ref)) throw new Error(`Invalid git ref: "${ref}"`);
|
|
144
152
|
execFileSync("git", [
|
|
145
153
|
"clone",
|
|
146
154
|
"--quiet",
|
|
@@ -158,28 +166,39 @@ function cloneTo(url, dest, ref) {
|
|
|
158
166
|
"pipe"
|
|
159
167
|
] });
|
|
160
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
|
+
}
|
|
161
179
|
function withSubpath(root, subpath, cleanupRoot) {
|
|
180
|
+
const cleanup = () => rmSync(cleanupRoot, {
|
|
181
|
+
recursive: true,
|
|
182
|
+
force: true
|
|
183
|
+
});
|
|
162
184
|
if (!subpath) return {
|
|
163
185
|
root,
|
|
164
|
-
cleanup
|
|
165
|
-
recursive: true,
|
|
166
|
-
force: true
|
|
167
|
-
})
|
|
186
|
+
cleanup
|
|
168
187
|
};
|
|
169
|
-
|
|
188
|
+
let sub;
|
|
189
|
+
try {
|
|
190
|
+
sub = assertInsideRoot(root, join(root, subpath));
|
|
191
|
+
} catch (e) {
|
|
192
|
+
cleanup();
|
|
193
|
+
throw e;
|
|
194
|
+
}
|
|
170
195
|
if (!existsSync(sub)) {
|
|
171
|
-
|
|
172
|
-
recursive: true,
|
|
173
|
-
force: true
|
|
174
|
-
});
|
|
196
|
+
cleanup();
|
|
175
197
|
throw new Error(`Subpath "${subpath}" not found.`);
|
|
176
198
|
}
|
|
177
199
|
return {
|
|
178
200
|
root: sub,
|
|
179
|
-
cleanup
|
|
180
|
-
recursive: true,
|
|
181
|
-
force: true
|
|
182
|
-
})
|
|
201
|
+
cleanup
|
|
183
202
|
};
|
|
184
203
|
}
|
|
185
204
|
async function resolveSource(source, opts = {}) {
|
|
@@ -193,6 +212,7 @@ async function resolveSource(source, opts = {}) {
|
|
|
193
212
|
}
|
|
194
213
|
const url = parsed.url;
|
|
195
214
|
if (!url) throw new Error(`Cannot resolve source "${parsed.raw}".`);
|
|
215
|
+
const safeUrl = redactUrl(url);
|
|
196
216
|
const tmp = mkdtempSync(join(tmpdir(), "agentry-src-"));
|
|
197
217
|
try {
|
|
198
218
|
cloneTo(url, tmp, parsed.ref);
|
|
@@ -206,7 +226,7 @@ async function resolveSource(source, opts = {}) {
|
|
|
206
226
|
const cacheOk = existsSync(cached) && existsSync(join(cached, ".git"));
|
|
207
227
|
if (cacheOk && opts.confirmCache) {
|
|
208
228
|
if (await opts.confirmCache({
|
|
209
|
-
url,
|
|
229
|
+
url: safeUrl,
|
|
210
230
|
cacheDir: cached,
|
|
211
231
|
error: detail
|
|
212
232
|
})) {
|
|
@@ -218,12 +238,105 @@ async function resolveSource(source, opts = {}) {
|
|
|
218
238
|
};
|
|
219
239
|
}
|
|
220
240
|
}
|
|
221
|
-
throw new Error(`git clone failed for ${
|
|
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.)" : ""}`);
|
|
222
242
|
}
|
|
223
243
|
saveToCache(parsed, tmp);
|
|
224
244
|
return withSubpath(tmp, parsed.subpath, tmp);
|
|
225
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
|
+
}
|
|
226
336
|
const SKILL_MARKER = "SKILL.md";
|
|
337
|
+
const AGENT_MARKER = "AGENT.md";
|
|
338
|
+
const RULE_MARKER = "RULE.md";
|
|
339
|
+
const CURSORRULES_MARKER = ".cursorrules";
|
|
227
340
|
const FILE_EXT = ".mdc";
|
|
228
341
|
const PROFILE_EXTS = [".yaml", ".yml"];
|
|
229
342
|
const KIND_DIRS = {
|
|
@@ -253,6 +366,12 @@ function firstHeading(md) {
|
|
|
253
366
|
const m = md.match(/^#\s+(.+)$/m);
|
|
254
367
|
return m ? m[1].trim() : "";
|
|
255
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
|
+
}
|
|
256
375
|
function frontmatterDescription(md) {
|
|
257
376
|
const fm = md.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
258
377
|
if (!fm) return "";
|
|
@@ -300,69 +419,178 @@ function makeArtifact(kind, category, name, srcPath, isFile) {
|
|
|
300
419
|
description: isFile ? readDescription(srcPath) : folderDescription(srcPath)
|
|
301
420
|
};
|
|
302
421
|
}
|
|
303
|
-
function
|
|
422
|
+
function rel(root, fullPath) {
|
|
423
|
+
return relative(root, fullPath).replace(/\\/g, "/");
|
|
424
|
+
}
|
|
425
|
+
function collectSkills(root, base, out, matcher) {
|
|
304
426
|
for (const child of entries(base)) {
|
|
305
427
|
if (!child.isDirectory()) continue;
|
|
306
428
|
const childDir = join(base, child.name);
|
|
429
|
+
if (matcher(rel(root, childDir), true)) continue;
|
|
307
430
|
if (existsSync(join(childDir, SKILL_MARKER))) out.push(makeArtifact("skill", null, child.name, childDir, false));
|
|
308
431
|
else for (const item of entries(childDir)) {
|
|
309
432
|
if (!item.isDirectory()) continue;
|
|
310
433
|
const itemDir = join(childDir, item.name);
|
|
434
|
+
if (matcher(rel(root, itemDir), true)) continue;
|
|
311
435
|
if (existsSync(join(itemDir, SKILL_MARKER))) out.push(makeArtifact("skill", child.name, item.name, itemDir, false));
|
|
312
436
|
}
|
|
313
437
|
}
|
|
314
438
|
}
|
|
315
|
-
function collectMdc(kind, base, out) {
|
|
316
|
-
for (const child of entries(base))
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
+
}
|
|
320
455
|
}
|
|
321
456
|
}
|
|
322
|
-
function collectProfiles(base, out) {
|
|
323
|
-
for (const child of entries(base))
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
+
}
|
|
331
474
|
}
|
|
332
475
|
}
|
|
333
476
|
}
|
|
334
|
-
function collectScripts(base, out) {
|
|
477
|
+
function collectScripts(root, base, out, matcher) {
|
|
335
478
|
for (const child of entries(base)) {
|
|
336
479
|
if (!child.isDirectory()) continue;
|
|
337
480
|
const childDir = join(base, child.name);
|
|
481
|
+
if (matcher(rel(root, childDir), true)) continue;
|
|
338
482
|
if (entries(childDir).some((e) => e.isFile())) out.push(makeArtifact("script", null, child.name, childDir, false));
|
|
339
|
-
else for (const item of entries(childDir))
|
|
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
|
+
}
|
|
340
488
|
}
|
|
341
489
|
}
|
|
342
|
-
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
|
+
}
|
|
343
500
|
for (const child of entries(root)) {
|
|
344
501
|
if (!child.isDirectory() || child.name.startsWith(".") || ROOT_SKIP.has(child.name)) continue;
|
|
345
502
|
const childDir = join(root, child.name);
|
|
503
|
+
if (matcher(child.name, true)) continue;
|
|
346
504
|
if (existsSync(join(childDir, SKILL_MARKER))) out.push(makeArtifact("skill", null, child.name, childDir, false));
|
|
347
|
-
else for (const item of entries(childDir))
|
|
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
|
+
}
|
|
348
511
|
}
|
|
349
512
|
}
|
|
350
|
-
function collectRootMdc(kind, root, out) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
+
}
|
|
355
580
|
}
|
|
356
581
|
}
|
|
357
582
|
function listKind(root, kind) {
|
|
358
583
|
const out = [];
|
|
584
|
+
const matcher = createIgnoreMatcher(root);
|
|
359
585
|
const base = join(root, KIND_DIRS[kind]);
|
|
360
|
-
if (existsSync(base)) if (kind === "skill") collectSkills(base, out);
|
|
361
|
-
else if (kind === "profile") collectProfiles(base, out);
|
|
362
|
-
else if (kind === "script") collectScripts(base, out);
|
|
363
|
-
else collectMdc(kind, base, out);
|
|
364
|
-
if (kind === "skill") collectRootSkills(root, out);
|
|
365
|
-
else if (kind === "agent" || kind === "rule") collectRootMdc(kind, root, out);
|
|
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);
|
|
366
594
|
const seen = /* @__PURE__ */ new Set();
|
|
367
595
|
return out.filter((a) => seen.has(a.id) ? false : seen.add(a.id)).sort((a, b) => a.id.localeCompare(b.id));
|
|
368
596
|
}
|
|
@@ -371,7 +599,8 @@ function listAll(root) {
|
|
|
371
599
|
"skill",
|
|
372
600
|
"rule",
|
|
373
601
|
"agent",
|
|
374
|
-
"profile"
|
|
602
|
+
"profile",
|
|
603
|
+
"script"
|
|
375
604
|
].flatMap((k) => listKind(root, k));
|
|
376
605
|
}
|
|
377
606
|
function select(root, kind, selector) {
|
|
@@ -382,6 +611,14 @@ function select(root, kind, selector) {
|
|
|
382
611
|
if (byCategory.length) return byCategory;
|
|
383
612
|
return items.filter((a) => a.name === selector);
|
|
384
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
|
+
}
|
|
385
622
|
const home$1 = homedir();
|
|
386
623
|
const configHome = xdgConfig ?? join(home$1, ".config");
|
|
387
624
|
const codexHome = process.env.CODEX_HOME?.trim() || join(home$1, ".codex");
|
|
@@ -563,6 +800,48 @@ function getNonUniversalAgents() {
|
|
|
563
800
|
return listAgents().filter((a) => a.skillsDir !== ".agents/skills");
|
|
564
801
|
}
|
|
565
802
|
const home = homedir();
|
|
803
|
+
function copyTreeDereferenced(src, dest, seen = /* @__PURE__ */ new Set(), filter, baseSrc = src) {
|
|
804
|
+
const st = lstatSync(src);
|
|
805
|
+
if (st.isSymbolicLink()) {
|
|
806
|
+
let real;
|
|
807
|
+
try {
|
|
808
|
+
real = realpathSync(src);
|
|
809
|
+
} catch {
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
if (seen.has(real)) return;
|
|
813
|
+
seen.add(real);
|
|
814
|
+
copyTreeDereferenced(real, dest, seen, filter, baseSrc);
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
if (st.isDirectory()) {
|
|
818
|
+
let real;
|
|
819
|
+
try {
|
|
820
|
+
real = realpathSync(src);
|
|
821
|
+
} catch {
|
|
822
|
+
real = resolve(src);
|
|
823
|
+
}
|
|
824
|
+
if (seen.has(real)) return;
|
|
825
|
+
seen.add(real);
|
|
826
|
+
mkdirSync(dest, { recursive: true });
|
|
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
|
+
}
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
if (st.isFile()) {
|
|
841
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
842
|
+
copyFileSync(src, dest);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
566
845
|
function lockBase(opts) {
|
|
567
846
|
return opts.scope === "global" ? home : opts.dir || process.cwd();
|
|
568
847
|
}
|
|
@@ -683,6 +962,7 @@ function installOne(artifact, opts, agentList, source) {
|
|
|
683
962
|
});
|
|
684
963
|
return dests;
|
|
685
964
|
}
|
|
965
|
+
const ignoreFilter = artifact.isFile ? void 0 : createIgnoreMatcher(artifact.dir);
|
|
686
966
|
if (artifact.kind === "skill") {
|
|
687
967
|
const canonical = join(base, ".agents", "skills", artifact.id);
|
|
688
968
|
rmSync(canonical, {
|
|
@@ -690,7 +970,7 @@ function installOne(artifact, opts, agentList, source) {
|
|
|
690
970
|
force: true
|
|
691
971
|
});
|
|
692
972
|
mkdirSync(join(canonical, ".."), { recursive: true });
|
|
693
|
-
|
|
973
|
+
copyTreeDereferenced(artifact.dir, canonical, /* @__PURE__ */ new Set(), ignoreFilter);
|
|
694
974
|
for (const a of list) if (a.skillsDir === ".agents/skills") dests.push({
|
|
695
975
|
agent: a.name,
|
|
696
976
|
dir: canonical
|
|
@@ -703,7 +983,7 @@ function installOne(artifact, opts, agentList, source) {
|
|
|
703
983
|
});
|
|
704
984
|
mkdirSync(join(dir, ".."), { recursive: true });
|
|
705
985
|
if (opts.copy) {
|
|
706
|
-
|
|
986
|
+
copyTreeDereferenced(canonical, dir);
|
|
707
987
|
dests.push({
|
|
708
988
|
agent: a.name,
|
|
709
989
|
dir
|
|
@@ -716,7 +996,7 @@ function installOne(artifact, opts, agentList, source) {
|
|
|
716
996
|
symlinked: true
|
|
717
997
|
});
|
|
718
998
|
} catch {
|
|
719
|
-
|
|
999
|
+
copyTreeDereferenced(canonical, dir);
|
|
720
1000
|
dests.push({
|
|
721
1001
|
agent: a.name,
|
|
722
1002
|
dir
|
|
@@ -730,7 +1010,7 @@ function installOne(artifact, opts, agentList, source) {
|
|
|
730
1010
|
force: true
|
|
731
1011
|
});
|
|
732
1012
|
mkdirSync(join(dir, ".."), { recursive: true });
|
|
733
|
-
|
|
1013
|
+
copyTreeDereferenced(artifact.dir, dir, /* @__PURE__ */ new Set(), ignoreFilter);
|
|
734
1014
|
dests.push({
|
|
735
1015
|
agent: a.name,
|
|
736
1016
|
dir
|
|
@@ -1417,6 +1697,98 @@ function agentsPrompt(action) {
|
|
|
1417
1697
|
if (action === "update") return "Which agents do you want to update for?";
|
|
1418
1698
|
return "Which agents do you want to install to?";
|
|
1419
1699
|
}
|
|
1700
|
+
function scopePrompt(action) {
|
|
1701
|
+
if (action === "remove") return "Removal scope";
|
|
1702
|
+
if (action === "update") return "Update scope";
|
|
1703
|
+
return "Installation scope";
|
|
1704
|
+
}
|
|
1705
|
+
async function selectInstallScope(message, paths) {
|
|
1706
|
+
if (isQuiet()) return "project";
|
|
1707
|
+
return new Promise((resolve) => {
|
|
1708
|
+
if (!process.stdin.isTTY) {
|
|
1709
|
+
resolve("project");
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
const rl = readline.createInterface({
|
|
1713
|
+
input: process.stdin,
|
|
1714
|
+
output: process.stdout
|
|
1715
|
+
});
|
|
1716
|
+
process.stdin.setRawMode(true);
|
|
1717
|
+
readline.emitKeypressEvents(process.stdin, rl);
|
|
1718
|
+
let value = "project";
|
|
1719
|
+
let lastH = 0;
|
|
1720
|
+
const joinRel = (base, rel) => {
|
|
1721
|
+
const sep = base.includes("\\") ? "\\" : "/";
|
|
1722
|
+
return `${base.replace(/[\\/]+$/, "")}${sep}${rel.replace(/\//g, sep)}`;
|
|
1723
|
+
};
|
|
1724
|
+
const optionBlock = (key, selected, done) => {
|
|
1725
|
+
const active = done ? done === key : selected;
|
|
1726
|
+
const bullet = active ? sky(c.bold("●")) : c.dim("○");
|
|
1727
|
+
const title = key === "project" ? "Project" : "Global";
|
|
1728
|
+
const hint = key === "project" ? "Install in the current directory (committed with your project)" : "Install in your home directory (available across all projects)";
|
|
1729
|
+
const path = key === "project" ? paths.projectDir : paths.homeDir;
|
|
1730
|
+
const lock = key === "project" ? joinRel(paths.projectDir, ".agentry/lock.json") : joinRel(paths.homeDir, ".agentry/lock.json");
|
|
1731
|
+
const lines = [];
|
|
1732
|
+
if (done === "cancel") lines.push(`${c.dim("│")} ${c.strikethrough(c.dim(title))}`);
|
|
1733
|
+
else {
|
|
1734
|
+
lines.push(`${c.dim("│")} ${bullet} ${active ? c.bold(title) : title}`);
|
|
1735
|
+
lines.push(`${c.dim("│")} ${c.dim(hint)}`);
|
|
1736
|
+
lines.push(`${c.dim("│")} ${sky("→")} ${c.dim(path)}`);
|
|
1737
|
+
lines.push(`${c.dim("│")} ${c.dim(`lock ${lock}`)}`);
|
|
1738
|
+
}
|
|
1739
|
+
return lines;
|
|
1740
|
+
};
|
|
1741
|
+
const paint = (done) => {
|
|
1742
|
+
const lines = [];
|
|
1743
|
+
if (done === "cancel") {
|
|
1744
|
+
lines.push(`${sky("■")} ${c.bold(message)}`);
|
|
1745
|
+
lines.push(`${c.dim("│")} ${c.strikethrough(c.dim("Cancelled"))}`);
|
|
1746
|
+
} else if (done === "project" || done === "global") {
|
|
1747
|
+
lines.push(`${sky("◇")} ${c.bold(message)}`);
|
|
1748
|
+
lines.push(...optionBlock(done, true, done).slice(0, 2));
|
|
1749
|
+
lines.push(`${c.dim("│")} ${sky("→")} ${c.dim(done === "project" ? paths.projectDir : paths.homeDir)}`);
|
|
1750
|
+
} else {
|
|
1751
|
+
lines.push(`${sky("◆")} ${c.bold(message)}`);
|
|
1752
|
+
lines.push(`${c.dim("│")}`);
|
|
1753
|
+
lines.push(...optionBlock("project", value === "project"));
|
|
1754
|
+
lines.push(`${c.dim("│")}`);
|
|
1755
|
+
lines.push(...optionBlock("global", value === "global"));
|
|
1756
|
+
lines.push(`${c.dim("└")} ${c.dim("↑/↓ select · enter confirm")}`);
|
|
1757
|
+
}
|
|
1758
|
+
if (lastH > 0) for (let i = 0; i < lastH; i++) process.stdout.write("\x1B[1A\x1B[2K");
|
|
1759
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
1760
|
+
lastH = lines.length;
|
|
1761
|
+
};
|
|
1762
|
+
const cleanup = () => {
|
|
1763
|
+
process.stdin.removeListener("keypress", onKey);
|
|
1764
|
+
process.stdin.setRawMode(false);
|
|
1765
|
+
rl.close();
|
|
1766
|
+
};
|
|
1767
|
+
const onKey = (_s, key) => {
|
|
1768
|
+
if (!key) return;
|
|
1769
|
+
if (key.name === "up" || key.name === "down" || key.name === "tab") {
|
|
1770
|
+
value = value === "project" ? "global" : "project";
|
|
1771
|
+
paint();
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
if (key.name === "return") {
|
|
1775
|
+
paint(value);
|
|
1776
|
+
cleanup();
|
|
1777
|
+
blank();
|
|
1778
|
+
resolve(value);
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
if (key.name === "escape" || key.ctrl && key.name === "c") {
|
|
1782
|
+
paint("cancel");
|
|
1783
|
+
cleanup();
|
|
1784
|
+
resolve(null);
|
|
1785
|
+
}
|
|
1786
|
+
};
|
|
1787
|
+
process.stdin.on("keypress", onKey);
|
|
1788
|
+
blank();
|
|
1789
|
+
paint();
|
|
1790
|
+
});
|
|
1791
|
+
}
|
|
1420
1792
|
function isQuiet() {
|
|
1421
1793
|
return isCI() || !process.stdout.isTTY;
|
|
1422
1794
|
}
|
|
@@ -1784,7 +2156,7 @@ async function cmdAdd(kind, source, selector, opts) {
|
|
|
1784
2156
|
await delayQuiet(280);
|
|
1785
2157
|
const { root, cleanup } = await resolveSourceWithUi(source, opts);
|
|
1786
2158
|
try {
|
|
1787
|
-
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) => {
|
|
1788
2160
|
const noun = kind === "all" ? "artifact" : kind;
|
|
1789
2161
|
return `Found ${a.length} ${noun}${a.length === 1 ? "" : "s"}`;
|
|
1790
2162
|
}, 620);
|
|
@@ -1799,22 +2171,28 @@ async function cmdAdd(kind, source, selector, opts) {
|
|
|
1799
2171
|
return;
|
|
1800
2172
|
}
|
|
1801
2173
|
let chosen = artifacts;
|
|
1802
|
-
if (!selector &&
|
|
1803
|
-
|
|
1804
|
-
|
|
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);
|
|
1805
2178
|
if (isCancelLike(picked)) {
|
|
1806
2179
|
outro("Cancelled.");
|
|
1807
2180
|
return;
|
|
1808
2181
|
}
|
|
1809
2182
|
chosen = picked;
|
|
1810
|
-
await sectionTransition(`${chosen.length} ${
|
|
1811
|
-
} else if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes)
|
|
1812
|
-
|
|
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 {
|
|
1813
2188
|
await delayQuiet(200);
|
|
1814
2189
|
blank();
|
|
1815
2190
|
}
|
|
1816
2191
|
const agentList = await resolveAgentList(opts, "install");
|
|
1817
|
-
|
|
2192
|
+
const askScope = !opts.scopeExplicit && !opts.yes && !isQuiet();
|
|
2193
|
+
if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition(`${agentList.length} agent${agentList.length === 1 ? "" : "s"} selected`, askScope ? "installation scope" : "install summary");
|
|
2194
|
+
if (!await ensureInstallScope(opts, "install")) return;
|
|
2195
|
+
if (askScope) await sectionTransition("scope set", "install summary");
|
|
1818
2196
|
installAll(chosen, opts, agentList, source);
|
|
1819
2197
|
} finally {
|
|
1820
2198
|
cleanup();
|
|
@@ -1833,6 +2211,7 @@ async function cmdAddProfile(name, source, opts) {
|
|
|
1833
2211
|
const repo = source || (profile.artifacts.skills[0]?.source ?? profile.artifacts.rules[0]?.source);
|
|
1834
2212
|
if (!repo) throw new Error(`No source given and ${file} lists none. Try: agentry add profile ${name} author/repo`);
|
|
1835
2213
|
const agentList = await resolveAgentList(opts, "install");
|
|
2214
|
+
if (!await ensureInstallScope(opts, "install")) return;
|
|
1836
2215
|
const { root, cleanup } = await resolveSourceWithUi(repo, opts);
|
|
1837
2216
|
try {
|
|
1838
2217
|
const artifacts = [];
|
|
@@ -1878,6 +2257,23 @@ async function resolveAgentList(opts, action = "install") {
|
|
|
1878
2257
|
if (isCancelLike(selected)) throw new Error("Cancelled.");
|
|
1879
2258
|
return selected;
|
|
1880
2259
|
}
|
|
2260
|
+
async function ensureInstallScope(opts, action) {
|
|
2261
|
+
if (opts.scopeExplicit) return true;
|
|
2262
|
+
if (opts.yes || isQuiet()) return true;
|
|
2263
|
+
const { homedir } = await import("node:os");
|
|
2264
|
+
if (!isQuiet()) await sectionTransition(`${action === "install" ? "agents" : action} ready`, scopePrompt(action).toLowerCase());
|
|
2265
|
+
const picked = await selectInstallScope(scopePrompt(action), {
|
|
2266
|
+
projectDir: opts.dir || process.cwd(),
|
|
2267
|
+
homeDir: homedir()
|
|
2268
|
+
});
|
|
2269
|
+
if (picked == null || isCancelLike(picked)) {
|
|
2270
|
+
outro("Cancelled.");
|
|
2271
|
+
return false;
|
|
2272
|
+
}
|
|
2273
|
+
opts.scope = picked;
|
|
2274
|
+
opts.scopeExplicit = true;
|
|
2275
|
+
return true;
|
|
2276
|
+
}
|
|
1881
2277
|
function installAll(artifacts, opts, agentList, source) {
|
|
1882
2278
|
if (!artifacts.length) {
|
|
1883
2279
|
warn("Nothing matched - nothing installed.");
|
|
@@ -1904,8 +2300,22 @@ function resolveSourceOrSelector(arg1, arg2, opts) {
|
|
|
1904
2300
|
if (!arg1) return {};
|
|
1905
2301
|
if (isExplicitSource(arg1)) return { source: arg1 };
|
|
1906
2302
|
if (isOwnerRepoShape(arg1)) {
|
|
1907
|
-
const
|
|
1908
|
-
|
|
2303
|
+
const bases = [{
|
|
2304
|
+
scope: "project",
|
|
2305
|
+
dir: opts.dir,
|
|
2306
|
+
agents: opts.agents,
|
|
2307
|
+
copy: opts.copy,
|
|
2308
|
+
dryRun: opts.dryRun
|
|
2309
|
+
}, {
|
|
2310
|
+
scope: "global",
|
|
2311
|
+
agents: opts.agents,
|
|
2312
|
+
copy: opts.copy,
|
|
2313
|
+
dryRun: opts.dryRun
|
|
2314
|
+
}];
|
|
2315
|
+
for (const base of bases) {
|
|
2316
|
+
const lock = readLock(base);
|
|
2317
|
+
if (Object.values(lock.items).some((e) => e.source && sourcesEqual(e.source, arg1))) return { source: arg1 };
|
|
2318
|
+
}
|
|
1909
2319
|
}
|
|
1910
2320
|
return { selector: arg1 };
|
|
1911
2321
|
}
|
|
@@ -1918,6 +2328,7 @@ async function cmdRemove(kind, arg1, arg2, opts) {
|
|
|
1918
2328
|
}, 420);
|
|
1919
2329
|
if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition("prepare remove", "choose agents to remove from");
|
|
1920
2330
|
const agentList = await resolveAgentList(opts, "remove");
|
|
2331
|
+
if (!await ensureInstallScope(opts, "remove")) return;
|
|
1921
2332
|
const installOpts = toInstallOpts(opts);
|
|
1922
2333
|
const scope = [
|
|
1923
2334
|
kind,
|
|
@@ -1928,7 +2339,7 @@ async function cmdRemove(kind, arg1, arg2, opts) {
|
|
|
1928
2339
|
let count = 0;
|
|
1929
2340
|
let paths = 0;
|
|
1930
2341
|
for (const t of ARTIFACT_KINDS) {
|
|
1931
|
-
const r = removeSelection(t,
|
|
2342
|
+
const r = removeSelection(t, selector, installOpts, agentList, source);
|
|
1932
2343
|
count += r.removedKeys.length;
|
|
1933
2344
|
paths += r.removedPaths.length;
|
|
1934
2345
|
}
|
|
@@ -1959,7 +2370,7 @@ async function cmdList(source, kind, selector, opts) {
|
|
|
1959
2370
|
try {
|
|
1960
2371
|
printArtifactList(await spinner("Discovering…", () => {
|
|
1961
2372
|
if (kind) return select(root, kind, selector);
|
|
1962
|
-
return listAll(root);
|
|
2373
|
+
return selector ? selectAll(root, selector) : listAll(root);
|
|
1963
2374
|
}, (a) => `Found ${a.length} artifact${a.length === 1 ? "" : "s"}`, 520), {
|
|
1964
2375
|
title: "Available in source",
|
|
1965
2376
|
subtitle: source
|
|
@@ -2051,7 +2462,6 @@ function printArtifactList(artifacts, meta) {
|
|
|
2051
2462
|
async function cmdUpdateAssets(kind, arg1, arg2, opts) {
|
|
2052
2463
|
intro();
|
|
2053
2464
|
const { source, selector } = resolveSourceOrSelector(arg1, arg2, opts);
|
|
2054
|
-
const installOpts = toInstallOpts(opts);
|
|
2055
2465
|
if (source) {
|
|
2056
2466
|
await spinner("Parsing source…", () => parseSource(source), (p) => {
|
|
2057
2467
|
const where = p.url || p.local || source;
|
|
@@ -2059,23 +2469,28 @@ async function cmdUpdateAssets(kind, arg1, arg2, opts) {
|
|
|
2059
2469
|
}, 520);
|
|
2060
2470
|
const { root, cleanup } = await resolveSourceWithUi(source, opts);
|
|
2061
2471
|
try {
|
|
2062
|
-
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);
|
|
2063
2473
|
if (!artifacts.length) {
|
|
2064
2474
|
warn(`Nothing found in ${source}.`);
|
|
2065
2475
|
return;
|
|
2066
2476
|
}
|
|
2067
2477
|
let chosen = artifacts;
|
|
2068
|
-
if (!selector &&
|
|
2069
|
-
|
|
2070
|
-
|
|
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);
|
|
2071
2482
|
if (isCancelLike(picked)) {
|
|
2072
2483
|
outro("Cancelled.");
|
|
2073
2484
|
return;
|
|
2074
2485
|
}
|
|
2075
2486
|
chosen = picked;
|
|
2076
|
-
await sectionTransition(`${chosen.length} ${
|
|
2077
|
-
} else if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes)
|
|
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
|
+
}
|
|
2078
2492
|
const agentList = await resolveAgentList(opts, "update");
|
|
2493
|
+
if (!await ensureInstallScope(opts, "update")) return;
|
|
2079
2494
|
if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition(`${agentList.length} agent${agentList.length === 1 ? "" : "s"} selected`, "update summary");
|
|
2080
2495
|
installAll(chosen, opts, agentList, source);
|
|
2081
2496
|
} finally {
|
|
@@ -2083,7 +2498,8 @@ async function cmdUpdateAssets(kind, arg1, arg2, opts) {
|
|
|
2083
2498
|
}
|
|
2084
2499
|
return;
|
|
2085
2500
|
}
|
|
2086
|
-
|
|
2501
|
+
if (!await ensureInstallScope(opts, "update")) return;
|
|
2502
|
+
let installed = listInstalled(toInstallOpts(opts)).filter((it) => kind === "all" || it.id.startsWith(kind + "/"));
|
|
2087
2503
|
if (selector) {
|
|
2088
2504
|
const rel = `${kind}/${selector}`;
|
|
2089
2505
|
installed = installed.filter((it) => it.id === rel || it.id.startsWith(rel + "/") || it.id.endsWith("/" + selector));
|
|
@@ -2308,6 +2724,7 @@ function takeOptValue(argv, i) {
|
|
|
2308
2724
|
function parseArgs(argv) {
|
|
2309
2725
|
const opts = {
|
|
2310
2726
|
scope: "project",
|
|
2727
|
+
scopeExplicit: false,
|
|
2311
2728
|
agents: [],
|
|
2312
2729
|
copy: false,
|
|
2313
2730
|
dryRun: false,
|
|
@@ -2321,15 +2738,19 @@ function parseArgs(argv) {
|
|
|
2321
2738
|
const a = argv[i];
|
|
2322
2739
|
if (a === "-g" || a === "--global") {
|
|
2323
2740
|
opts.scope = "global";
|
|
2741
|
+
opts.scopeExplicit = true;
|
|
2324
2742
|
flags.global = true;
|
|
2325
|
-
} else if (a === "--project") {
|
|
2743
|
+
} else if (a === "-p" || a === "--project") {
|
|
2326
2744
|
opts.scope = "project";
|
|
2745
|
+
opts.scopeExplicit = true;
|
|
2327
2746
|
flags.project = true;
|
|
2328
2747
|
} else if (a === "-y" || a === "--yes") {
|
|
2329
2748
|
opts.yes = true;
|
|
2330
2749
|
flags.yes = true;
|
|
2331
|
-
} else if (a === "--dir") {
|
|
2750
|
+
} else if (a === "-d" || a === "--dir") {
|
|
2332
2751
|
opts.dir = argv[++i];
|
|
2752
|
+
opts.scope = "project";
|
|
2753
|
+
opts.scopeExplicit = true;
|
|
2333
2754
|
flags.dir = opts.dir;
|
|
2334
2755
|
} else if (a === "-a" || a === "--agent") {
|
|
2335
2756
|
opts.agents.push(argv[++i]);
|
|
@@ -2420,7 +2841,11 @@ async function runUpdateSelf() {
|
|
|
2420
2841
|
function runNpmInstall(spec) {
|
|
2421
2842
|
spinner(`Updating agentry CLI`, () => {
|
|
2422
2843
|
try {
|
|
2423
|
-
|
|
2844
|
+
execFileSync("npm", [
|
|
2845
|
+
"install",
|
|
2846
|
+
"-g",
|
|
2847
|
+
spec
|
|
2848
|
+
], { stdio: "inherit" });
|
|
2424
2849
|
} catch {
|
|
2425
2850
|
err("Update failed. Once published, run: npm install -g " + spec);
|
|
2426
2851
|
process.exit(1);
|
|
@@ -2430,7 +2855,11 @@ function runNpmInstall(spec) {
|
|
|
2430
2855
|
function runUninstall() {
|
|
2431
2856
|
spinner("Uninstalling agentry CLI", () => {
|
|
2432
2857
|
try {
|
|
2433
|
-
|
|
2858
|
+
execFileSync("npm", [
|
|
2859
|
+
"uninstall",
|
|
2860
|
+
"-g",
|
|
2861
|
+
name
|
|
2862
|
+
], { stdio: "inherit" });
|
|
2434
2863
|
} catch {
|
|
2435
2864
|
err("Uninstall failed. Try: npm uninstall -g " + name);
|
|
2436
2865
|
process.exit(1);
|
|
@@ -2489,21 +2918,33 @@ async function main() {
|
|
|
2489
2918
|
return;
|
|
2490
2919
|
}
|
|
2491
2920
|
if (action === "add") {
|
|
2492
|
-
const
|
|
2493
|
-
if (!
|
|
2494
|
-
|
|
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") {
|
|
2495
2925
|
await cmdAddProfile(positional[2], positional[3], opts);
|
|
2496
2926
|
return;
|
|
2497
2927
|
}
|
|
2498
|
-
if (
|
|
2499
|
-
|
|
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);
|
|
2500
2937
|
return;
|
|
2501
2938
|
}
|
|
2502
2939
|
if (action === "remove" || action === "rm") {
|
|
2503
|
-
const
|
|
2504
|
-
if (!
|
|
2505
|
-
|
|
2506
|
-
|
|
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);
|
|
2507
2948
|
return;
|
|
2508
2949
|
}
|
|
2509
2950
|
if (action === "init") {
|