@mhmdreza-rafiei/agentry 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -195
- package/dist/_chunks/theme2.mjs +42 -16
- package/dist/cli.mjs +1013 -177
- package/package.json +1 -1
- package/dist/_chunks/source_parser.mjs +0 -2
- package/dist/_chunks/source_parser2.mjs +0 -74
package/dist/cli.mjs
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { animateLogo, badge, delay, printHelp, theme } from "./_chunks/theme2.mjs";
|
|
2
|
+
import { animateLogo, badge, delay, printHelp, sky, theme } from "./_chunks/theme2.mjs";
|
|
4
3
|
import { execFileSync, execSync } from "node:child_process";
|
|
5
4
|
import https from "node:https";
|
|
6
|
-
import {
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { homedir, tmpdir } from "node:os";
|
|
8
|
-
import { join } from "node:path";
|
|
9
|
-
import { xdgConfig } from "xdg-basedir";
|
|
10
|
-
import { parse } from "yaml";
|
|
8
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
9
|
+
import { xdgCache, xdgConfig } from "xdg-basedir";
|
|
10
|
+
import { parse, stringify } from "yaml";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import * as clack from "@clack/prompts";
|
|
13
|
+
import * as readline from "node:readline";
|
|
13
14
|
import c from "picocolors";
|
|
14
15
|
import { determineAgent } from "@vercel/detect-agent";
|
|
15
|
-
import * as readline from "node:readline";
|
|
16
16
|
import { stripVTControlCharacters } from "node:util";
|
|
17
17
|
import { Writable } from "node:stream";
|
|
18
18
|
var name = "@mhmdreza-rafiei/agentry";
|
|
@@ -24,7 +24,165 @@ const ARTIFACT_KINDS = [
|
|
|
24
24
|
"profile",
|
|
25
25
|
"script"
|
|
26
26
|
];
|
|
27
|
-
function
|
|
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
|
+
function parseGithubTreeUrl(url) {
|
|
38
|
+
const m = url.match(/^https?:\/\/github\.com\/([\w.-]+\/[\w.-]+?)\/tree\/([^/]+)\/(.+)$/);
|
|
39
|
+
if (!m) return null;
|
|
40
|
+
return {
|
|
41
|
+
ownerRepo: m[1],
|
|
42
|
+
ref: m[2],
|
|
43
|
+
subpath: m[3].replace(/\/+$/, "")
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function parseSource(source) {
|
|
47
|
+
if (!source || typeof source !== "string") throw new Error("Empty source.");
|
|
48
|
+
const raw = source;
|
|
49
|
+
if (isLocalPath(source)) return {
|
|
50
|
+
kind: "local",
|
|
51
|
+
raw,
|
|
52
|
+
url: null,
|
|
53
|
+
subpath: "",
|
|
54
|
+
local: resolve(source.replace(/^~/, homedir()))
|
|
55
|
+
};
|
|
56
|
+
const tree = parseGithubTreeUrl(source);
|
|
57
|
+
if (tree) return {
|
|
58
|
+
kind: "github-url",
|
|
59
|
+
raw,
|
|
60
|
+
url: `https://github.com/${tree.ownerRepo}.git`,
|
|
61
|
+
subpath: tree.subpath,
|
|
62
|
+
ref: tree.ref,
|
|
63
|
+
local: null
|
|
64
|
+
};
|
|
65
|
+
let m = source.match(/^https?:\/\/github\.com\/([\w.-]+\/[\w.-]+?)(?:\.git)?\/?$/);
|
|
66
|
+
if (m) return {
|
|
67
|
+
kind: "github-url",
|
|
68
|
+
raw,
|
|
69
|
+
url: `https://github.com/${m[1]}.git`,
|
|
70
|
+
subpath: "",
|
|
71
|
+
local: null
|
|
72
|
+
};
|
|
73
|
+
m = source.match(/^https?:\/\/gitlab\.com\/([\w./-]+?)(?:\.git)?\/?$/);
|
|
74
|
+
if (m) return {
|
|
75
|
+
kind: "gitlab-url",
|
|
76
|
+
raw,
|
|
77
|
+
url: `https://gitlab.com/${m[1]}.git`,
|
|
78
|
+
subpath: "",
|
|
79
|
+
local: null
|
|
80
|
+
};
|
|
81
|
+
if (source.startsWith("git@") || /^https?:\/\/.*\.git$/.test(source)) return {
|
|
82
|
+
kind: "git-url",
|
|
83
|
+
raw,
|
|
84
|
+
url: source,
|
|
85
|
+
subpath: "",
|
|
86
|
+
local: null
|
|
87
|
+
};
|
|
88
|
+
if (/^[\w.-]+\/[\w.-]+$/.test(source)) return {
|
|
89
|
+
kind: "github-shorthand",
|
|
90
|
+
raw,
|
|
91
|
+
url: `https://github.com/${source}.git`,
|
|
92
|
+
subpath: "",
|
|
93
|
+
local: null
|
|
94
|
+
};
|
|
95
|
+
throw new Error(`Cannot parse source "${source}". Use author/repo, a GitHub/GitLab URL, a git URL, or a local path.`);
|
|
96
|
+
}
|
|
97
|
+
function sourceIdentity(source) {
|
|
98
|
+
try {
|
|
99
|
+
const p = parseSource(source);
|
|
100
|
+
if (p.kind === "local" && p.local) return `local:${resolve(p.local).replace(/\\/g, "/").toLowerCase()}`;
|
|
101
|
+
return `git:${(p.url || "").replace(/\.git$/i, "").replace(/\/+$/, "").toLowerCase()}${p.subpath ? `/${p.subpath.replace(/^\/+|\/+$/g, "")}` : ""}${p.ref ? `@${p.ref}` : ""}`;
|
|
102
|
+
} catch {
|
|
103
|
+
return `raw:${source.trim().toLowerCase()}`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function sourcesEqual(a, b) {
|
|
107
|
+
if (!a || !b) return false;
|
|
108
|
+
if (a === b) return true;
|
|
109
|
+
return sourceIdentity(a) === sourceIdentity(b);
|
|
110
|
+
}
|
|
111
|
+
function isExplicitSource(arg) {
|
|
112
|
+
if (!arg) return false;
|
|
113
|
+
if (isLocalPath(arg)) return true;
|
|
114
|
+
if (/^https?:\/\//i.test(arg)) return true;
|
|
115
|
+
if (arg.startsWith("git@")) return true;
|
|
116
|
+
if (/^https?:\/\/.*\.git$/i.test(arg)) return true;
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
function isOwnerRepoShape(arg) {
|
|
120
|
+
return /^[\w.-]+\/[\w.-]+$/.test(arg);
|
|
121
|
+
}
|
|
122
|
+
function cacheRoot() {
|
|
123
|
+
return join(xdgCache ?? join(homedir(), ".cache"), "agentry", "repos");
|
|
124
|
+
}
|
|
125
|
+
function cacheKey(parsed) {
|
|
126
|
+
const id = sourceIdentity(parsed.raw);
|
|
127
|
+
return createHash("sha256").update(id).digest("hex").slice(0, 20);
|
|
128
|
+
}
|
|
129
|
+
function cacheDirFor(parsed) {
|
|
130
|
+
return join(cacheRoot(), cacheKey(parsed));
|
|
131
|
+
}
|
|
132
|
+
function saveToCache(parsed, clonedRoot) {
|
|
133
|
+
const dest = cacheDirFor(parsed);
|
|
134
|
+
try {
|
|
135
|
+
rmSync(dest, {
|
|
136
|
+
recursive: true,
|
|
137
|
+
force: true
|
|
138
|
+
});
|
|
139
|
+
mkdirSync(join(dest, ".."), { recursive: true });
|
|
140
|
+
cpSync(clonedRoot, dest, { recursive: true });
|
|
141
|
+
} catch {}
|
|
142
|
+
}
|
|
143
|
+
function cloneTo(url, dest, ref) {
|
|
144
|
+
execFileSync("git", [
|
|
145
|
+
"clone",
|
|
146
|
+
"--quiet",
|
|
147
|
+
...ref ? [
|
|
148
|
+
"--branch",
|
|
149
|
+
ref,
|
|
150
|
+
"--depth",
|
|
151
|
+
"1"
|
|
152
|
+
] : ["--depth", "1"],
|
|
153
|
+
url,
|
|
154
|
+
dest
|
|
155
|
+
], { stdio: [
|
|
156
|
+
"ignore",
|
|
157
|
+
"ignore",
|
|
158
|
+
"pipe"
|
|
159
|
+
] });
|
|
160
|
+
}
|
|
161
|
+
function withSubpath(root, subpath, cleanupRoot) {
|
|
162
|
+
if (!subpath) return {
|
|
163
|
+
root,
|
|
164
|
+
cleanup: () => rmSync(cleanupRoot, {
|
|
165
|
+
recursive: true,
|
|
166
|
+
force: true
|
|
167
|
+
})
|
|
168
|
+
};
|
|
169
|
+
const sub = join(root, subpath);
|
|
170
|
+
if (!existsSync(sub)) {
|
|
171
|
+
rmSync(cleanupRoot, {
|
|
172
|
+
recursive: true,
|
|
173
|
+
force: true
|
|
174
|
+
});
|
|
175
|
+
throw new Error(`Subpath "${subpath}" not found.`);
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
root: sub,
|
|
179
|
+
cleanup: () => rmSync(cleanupRoot, {
|
|
180
|
+
recursive: true,
|
|
181
|
+
force: true
|
|
182
|
+
})
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
async function resolveSource(source, opts = {}) {
|
|
28
186
|
const parsed = typeof source === "string" ? parseSource(source) : source;
|
|
29
187
|
if (parsed.kind === "local" && parsed.local) {
|
|
30
188
|
if (!existsSync(parsed.local)) throw new Error(`Path not found: ${parsed.raw}`);
|
|
@@ -37,54 +195,33 @@ function resolveSource(source) {
|
|
|
37
195
|
if (!url) throw new Error(`Cannot resolve source "${parsed.raw}".`);
|
|
38
196
|
const tmp = mkdtempSync(join(tmpdir(), "agentry-src-"));
|
|
39
197
|
try {
|
|
40
|
-
|
|
41
|
-
"clone",
|
|
42
|
-
"--quiet",
|
|
43
|
-
...parsed.ref ? [
|
|
44
|
-
"--branch",
|
|
45
|
-
parsed.ref,
|
|
46
|
-
"--depth",
|
|
47
|
-
"1"
|
|
48
|
-
] : ["--depth", "1"],
|
|
49
|
-
url,
|
|
50
|
-
tmp
|
|
51
|
-
], { stdio: [
|
|
52
|
-
"ignore",
|
|
53
|
-
"ignore",
|
|
54
|
-
"pipe"
|
|
55
|
-
] });
|
|
198
|
+
cloneTo(url, tmp, parsed.ref);
|
|
56
199
|
} catch (e) {
|
|
57
200
|
rmSync(tmp, {
|
|
58
201
|
recursive: true,
|
|
59
202
|
force: true
|
|
60
203
|
});
|
|
61
204
|
const detail = e.stderr ? e.stderr.toString().trim() : e.message;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
205
|
+
const cached = cacheDirFor(parsed);
|
|
206
|
+
const cacheOk = existsSync(cached) && existsSync(join(cached, ".git"));
|
|
207
|
+
if (cacheOk && opts.confirmCache) {
|
|
208
|
+
if (await opts.confirmCache({
|
|
209
|
+
url,
|
|
210
|
+
cacheDir: cached,
|
|
211
|
+
error: detail
|
|
212
|
+
})) {
|
|
213
|
+
const work = mkdtempSync(join(tmpdir(), "agentry-src-"));
|
|
214
|
+
cpSync(cached, work, { recursive: true });
|
|
215
|
+
return {
|
|
216
|
+
...withSubpath(work, parsed.subpath, work),
|
|
217
|
+
fromCache: true
|
|
218
|
+
};
|
|
219
|
+
}
|
|
72
220
|
}
|
|
73
|
-
|
|
74
|
-
root: sub,
|
|
75
|
-
cleanup: () => rmSync(tmp, {
|
|
76
|
-
recursive: true,
|
|
77
|
-
force: true
|
|
78
|
-
})
|
|
79
|
-
};
|
|
221
|
+
throw new Error(`git clone failed for ${url}\n${detail}${cacheOk ? "\n(A local cache exists — re-run interactively to load it, or pass -y to auto-use cache.)" : ""}`);
|
|
80
222
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
cleanup: () => rmSync(tmp, {
|
|
84
|
-
recursive: true,
|
|
85
|
-
force: true
|
|
86
|
-
})
|
|
87
|
-
};
|
|
223
|
+
saveToCache(parsed, tmp);
|
|
224
|
+
return withSubpath(tmp, parsed.subpath, tmp);
|
|
88
225
|
}
|
|
89
226
|
const SKILL_MARKER = "SKILL.md";
|
|
90
227
|
const FILE_EXT = ".mdc";
|
|
@@ -458,10 +595,74 @@ function removeLegacyLockIfPresent(opts) {
|
|
|
458
595
|
}
|
|
459
596
|
function writeLock(opts, lock) {
|
|
460
597
|
const file = lockPath(opts);
|
|
598
|
+
if (!Object.keys(lock.items).length) {
|
|
599
|
+
const dir = join(lockBase(opts), ".agentry");
|
|
600
|
+
if (existsSync(dir)) rmSync(dir, {
|
|
601
|
+
recursive: true,
|
|
602
|
+
force: true
|
|
603
|
+
});
|
|
604
|
+
removeLegacyLockIfPresent(opts);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
461
607
|
mkdirSync(join(file, ".."), { recursive: true });
|
|
462
608
|
writeFileSync(file, JSON.stringify(lock, null, 2) + "\n");
|
|
463
609
|
removeLegacyLockIfPresent(opts);
|
|
464
610
|
}
|
|
611
|
+
function isDirTreeEmpty(dir) {
|
|
612
|
+
if (!existsSync(dir)) return true;
|
|
613
|
+
let entries;
|
|
614
|
+
try {
|
|
615
|
+
entries = readdirSync(dir);
|
|
616
|
+
} catch {
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
for (const name of entries) {
|
|
620
|
+
const p = join(dir, name);
|
|
621
|
+
let st;
|
|
622
|
+
try {
|
|
623
|
+
st = statSync(p);
|
|
624
|
+
} catch {
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (st.isDirectory()) {
|
|
628
|
+
if (!isDirTreeEmpty(p)) return false;
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
if (st.isFile()) {
|
|
632
|
+
if (name === "lock.json") {
|
|
633
|
+
try {
|
|
634
|
+
const data = JSON.parse(readFileSync(p, "utf8"));
|
|
635
|
+
if (data && typeof data === "object" && Object.keys(data.items || {}).length === 0) continue;
|
|
636
|
+
} catch {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return true;
|
|
645
|
+
}
|
|
646
|
+
function pruneEmptyAncestors(dir, stopAt, removedPaths = []) {
|
|
647
|
+
const stop = resolve(stopAt);
|
|
648
|
+
let cur = resolve(dir);
|
|
649
|
+
while (cur.length > stop.length) {
|
|
650
|
+
const prefix = stop.endsWith(sep) ? stop : stop + sep;
|
|
651
|
+
if (!cur.toLowerCase().startsWith(prefix.toLowerCase()) && cur.toLowerCase() !== stop.toLowerCase()) break;
|
|
652
|
+
if (!existsSync(cur)) {
|
|
653
|
+
cur = dirname(cur);
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
if (!isDirTreeEmpty(cur)) break;
|
|
657
|
+
rmSync(cur, {
|
|
658
|
+
recursive: true,
|
|
659
|
+
force: true
|
|
660
|
+
});
|
|
661
|
+
removedPaths.push(cur);
|
|
662
|
+
cur = dirname(cur);
|
|
663
|
+
}
|
|
664
|
+
return removedPaths;
|
|
665
|
+
}
|
|
465
666
|
function listInstalled(opts) {
|
|
466
667
|
return Object.entries(readLock(opts).items).map(([id, v]) => ({
|
|
467
668
|
id,
|
|
@@ -489,12 +690,7 @@ function installOne(artifact, opts, agentList, source) {
|
|
|
489
690
|
force: true
|
|
490
691
|
});
|
|
491
692
|
mkdirSync(join(canonical, ".."), { recursive: true });
|
|
492
|
-
|
|
493
|
-
else try {
|
|
494
|
-
symlinkSync(artifact.dir, canonical, "junction");
|
|
495
|
-
} catch {
|
|
496
|
-
cpSync(artifact.dir, canonical, { recursive: true });
|
|
497
|
-
}
|
|
693
|
+
cpSync(artifact.dir, canonical, { recursive: true });
|
|
498
694
|
for (const a of list) if (a.skillsDir === ".agents/skills") dests.push({
|
|
499
695
|
agent: a.name,
|
|
500
696
|
dir: canonical
|
|
@@ -534,12 +730,7 @@ function installOne(artifact, opts, agentList, source) {
|
|
|
534
730
|
force: true
|
|
535
731
|
});
|
|
536
732
|
mkdirSync(join(dir, ".."), { recursive: true });
|
|
537
|
-
|
|
538
|
-
else try {
|
|
539
|
-
symlinkSync(artifact.dir, dir, "junction");
|
|
540
|
-
} catch {
|
|
541
|
-
cpSync(artifact.dir, dir, { recursive: true });
|
|
542
|
-
}
|
|
733
|
+
cpSync(artifact.dir, dir, { recursive: true });
|
|
543
734
|
dests.push({
|
|
544
735
|
agent: a.name,
|
|
545
736
|
dir
|
|
@@ -563,37 +754,115 @@ function agentDir(artifact, a, base) {
|
|
|
563
754
|
}
|
|
564
755
|
return join(base, a.configDir || a.skillsDir, pluralKind(artifact.kind), artifact.id);
|
|
565
756
|
}
|
|
566
|
-
function removeSelection(kind, selector, opts, agentList) {
|
|
757
|
+
function removeSelection(kind, selector, opts, agentList, sourceFilter) {
|
|
567
758
|
const base = lockBase(opts);
|
|
568
759
|
const list = agentList.length ? agentList : [getAgent("cursor")];
|
|
569
760
|
const rel = [kind, selector].filter(Boolean).join("/");
|
|
570
761
|
const removedKeys = [];
|
|
762
|
+
const removedPaths = [];
|
|
571
763
|
const lock = readLock(opts);
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
if (kind === "
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
if (existsSync(dir)) rmSync(dir, {
|
|
764
|
+
const matchingKeys = Object.keys(lock.items).filter((key) => {
|
|
765
|
+
const entry = lock.items[key];
|
|
766
|
+
if (sourceFilter && !sourcesEqual(entry.source, sourceFilter)) return false;
|
|
767
|
+
if (kind === "all") return true;
|
|
768
|
+
if (key === rel || key.startsWith(rel + "/")) return true;
|
|
769
|
+
if (!selector && key.startsWith(kind + "/")) return true;
|
|
770
|
+
return false;
|
|
771
|
+
});
|
|
772
|
+
const rmPath = (p) => {
|
|
773
|
+
if (!p || !existsSync(p)) return;
|
|
774
|
+
const parent = dirname(p);
|
|
775
|
+
rmSync(p, {
|
|
585
776
|
recursive: true,
|
|
586
777
|
force: true
|
|
587
778
|
});
|
|
588
|
-
|
|
589
|
-
|
|
779
|
+
removedPaths.push(p);
|
|
780
|
+
pruneEmptyAncestors(parent, base, removedPaths);
|
|
781
|
+
};
|
|
782
|
+
const agentsFor = (entry) => {
|
|
783
|
+
const fromEntry = entry.agents.map((n) => getAgent(n)).filter(Boolean);
|
|
784
|
+
const byName = /* @__PURE__ */ new Map();
|
|
785
|
+
for (const a of [...fromEntry, ...list]) byName.set(a.name, a);
|
|
786
|
+
return [...byName.values()];
|
|
787
|
+
};
|
|
788
|
+
const removeFilesForKey = (key, entry) => {
|
|
789
|
+
const parts = key.split("/");
|
|
790
|
+
const k = parts[0];
|
|
791
|
+
const sel = parts.slice(1).join("/") || void 0;
|
|
792
|
+
if (!sel && k === "skill") return;
|
|
793
|
+
const agents = agentsFor(entry);
|
|
794
|
+
if (k === "skill" && sel) {
|
|
795
|
+
rmPath(join(base, ".agents", "skills", sel));
|
|
796
|
+
for (const a of agents) {
|
|
797
|
+
if (a.skillsDir === ".agents/skills") continue;
|
|
798
|
+
rmPath(join(base, a.skillsDir, sel));
|
|
799
|
+
}
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
for (const a of agents) if (k === "agent" || k === "rule" || k === "profile") {
|
|
803
|
+
const ext = k === "profile" ? ".yaml" : ".mdc";
|
|
804
|
+
if (!sel) rmPath(join(base, a.configDir || a.skillsDir, pluralKind(k)));
|
|
805
|
+
else if (sel.includes("/")) {
|
|
806
|
+
const [cat, name] = sel.split("/");
|
|
807
|
+
rmPath(join(base, a.configDir || a.skillsDir, pluralKind(k), cat, name + ext));
|
|
808
|
+
} else rmPath(join(base, a.configDir || a.skillsDir, pluralKind(k), sel + ext));
|
|
809
|
+
} else if (sel) rmPath(join(base, a.configDir || a.skillsDir, pluralKind(k), sel));
|
|
810
|
+
};
|
|
811
|
+
for (const key of matchingKeys) {
|
|
812
|
+
const entry = lock.items[key];
|
|
813
|
+
removeFilesForKey(key, entry);
|
|
590
814
|
delete lock.items[key];
|
|
591
815
|
removedKeys.push(key);
|
|
592
816
|
}
|
|
817
|
+
if (!removedKeys.length && !sourceFilter) {
|
|
818
|
+
if (kind === "skill") rmPath(join(base, ".agents", "skills", selector || ""));
|
|
819
|
+
for (const a of list) {
|
|
820
|
+
let dir;
|
|
821
|
+
if (kind === "skill") {
|
|
822
|
+
if (a.skillsDir === ".agents/skills") continue;
|
|
823
|
+
dir = join(base, a.skillsDir, selector || "");
|
|
824
|
+
} else if (kind === "agent" || kind === "rule" || kind === "profile") {
|
|
825
|
+
const ext = kind === "profile" ? ".yaml" : ".mdc";
|
|
826
|
+
if (!selector) dir = join(base, a.configDir || a.skillsDir, pluralKind(kind));
|
|
827
|
+
else if (selector.includes("/")) {
|
|
828
|
+
const [cat, name] = selector.split("/");
|
|
829
|
+
dir = join(base, a.configDir || a.skillsDir, pluralKind(kind), cat, name + ext);
|
|
830
|
+
} else dir = join(base, a.configDir || a.skillsDir, pluralKind(kind), selector + ext);
|
|
831
|
+
} else dir = join(base, a.configDir || a.skillsDir, pluralKind(kind), selector || "");
|
|
832
|
+
if (existsSync(dir)) {
|
|
833
|
+
rmPath(dir);
|
|
834
|
+
if (!removedKeys.includes(rel)) removedKeys.push(rel);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
for (const key of Object.keys(lock.items)) if (key === rel || key.startsWith(rel + "/")) {
|
|
838
|
+
delete lock.items[key];
|
|
839
|
+
if (!removedKeys.includes(key)) removedKeys.push(key);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
593
842
|
writeLock(opts, lock);
|
|
843
|
+
const sweepRoots = /* @__PURE__ */ new Set([
|
|
844
|
+
join(base, ".agents", "skills"),
|
|
845
|
+
join(base, ".agents"),
|
|
846
|
+
join(base, ".agentry")
|
|
847
|
+
]);
|
|
848
|
+
for (const a of list) {
|
|
849
|
+
sweepRoots.add(join(base, a.skillsDir));
|
|
850
|
+
if (a.configDir) {
|
|
851
|
+
sweepRoots.add(join(base, a.configDir));
|
|
852
|
+
for (const pk of [
|
|
853
|
+
"skills",
|
|
854
|
+
"agents",
|
|
855
|
+
"rules",
|
|
856
|
+
"profiles",
|
|
857
|
+
"scripts"
|
|
858
|
+
]) sweepRoots.add(join(base, a.configDir, pk));
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
for (const root of sweepRoots) if (existsSync(root) && isDirTreeEmpty(root)) pruneEmptyAncestors(root, base, removedPaths);
|
|
594
862
|
return {
|
|
595
|
-
existed: removedKeys.length > 0,
|
|
596
|
-
removedKeys
|
|
863
|
+
existed: removedKeys.length > 0 || removedPaths.length > 0,
|
|
864
|
+
removedKeys,
|
|
865
|
+
removedPaths
|
|
597
866
|
};
|
|
598
867
|
}
|
|
599
868
|
const ProfileArtifactRefSchema = z.object({
|
|
@@ -636,6 +905,98 @@ function loadProfile(name, baseDir) {
|
|
|
636
905
|
profile: loadProfileFile(file)
|
|
637
906
|
};
|
|
638
907
|
}
|
|
908
|
+
function ensureDir(file) {
|
|
909
|
+
mkdirSync(join(file, ".."), { recursive: true });
|
|
910
|
+
}
|
|
911
|
+
function writeNew(file, content, force = false) {
|
|
912
|
+
if (existsSync(file) && !force) throw new Error(`Already exists: ${file}`);
|
|
913
|
+
ensureDir(file);
|
|
914
|
+
writeFileSync(file, content);
|
|
915
|
+
return file;
|
|
916
|
+
}
|
|
917
|
+
function scaffoldSkill(opts) {
|
|
918
|
+
const dir = join(opts.baseDir || process.cwd(), opts.category ? join("skills", opts.category, opts.name) : join("skills", opts.name));
|
|
919
|
+
const id = opts.category ? `${opts.category}/${opts.name}` : opts.name;
|
|
920
|
+
const desc = opts.description || `${opts.name} skill`;
|
|
921
|
+
const skillMd = `---
|
|
922
|
+
name: ${opts.name}
|
|
923
|
+
description: ${desc}
|
|
924
|
+
---
|
|
925
|
+
|
|
926
|
+
# ${opts.name}
|
|
927
|
+
|
|
928
|
+
${desc}
|
|
929
|
+
|
|
930
|
+
## When to use
|
|
931
|
+
|
|
932
|
+
Describe when this skill should run.
|
|
933
|
+
|
|
934
|
+
## Instructions
|
|
935
|
+
|
|
936
|
+
1. …
|
|
937
|
+
`;
|
|
938
|
+
const paths = [writeNew(join(dir, "SKILL.md"), skillMd)];
|
|
939
|
+
if (opts.reference !== false) paths.push(writeNew(join(dir, "references", "TEMPLATE.md"), `# ${opts.name} — reference
|
|
940
|
+
|
|
941
|
+
Fill in details here. Keep SKILL.md lean; put depth in references/.
|
|
942
|
+
`));
|
|
943
|
+
return {
|
|
944
|
+
paths,
|
|
945
|
+
id
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
function scaffoldMdc(opts) {
|
|
949
|
+
const base = opts.baseDir || process.cwd();
|
|
950
|
+
const folder = opts.kind === "agent" ? "agents" : "rules";
|
|
951
|
+
const rel = opts.category ? join(folder, opts.category, `${opts.name}.mdc`) : join(folder, `${opts.name}.mdc`);
|
|
952
|
+
const id = opts.category ? `${opts.category}/${opts.name}` : opts.name;
|
|
953
|
+
const desc = opts.description || `${opts.name} ${opts.kind}`;
|
|
954
|
+
const always = opts.kind === "rule" && opts.alwaysApply ? "alwaysApply: true\n" : opts.kind === "rule" ? "alwaysApply: false\n" : "";
|
|
955
|
+
const body = `---
|
|
956
|
+
name: ${opts.name}
|
|
957
|
+
description: ${desc}
|
|
958
|
+
${always}---
|
|
959
|
+
|
|
960
|
+
# ${opts.name}
|
|
961
|
+
|
|
962
|
+
${desc}
|
|
963
|
+
`;
|
|
964
|
+
return {
|
|
965
|
+
paths: [writeNew(join(base, rel), body)],
|
|
966
|
+
id
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
function scaffoldScript(opts) {
|
|
970
|
+
const dir = join(opts.baseDir || process.cwd(), opts.category ? join("scripts", opts.category, opts.name) : join("scripts", opts.name));
|
|
971
|
+
const id = opts.category ? `${opts.category}/${opts.name}` : opts.name;
|
|
972
|
+
const desc = opts.description || `${opts.name} script`;
|
|
973
|
+
return {
|
|
974
|
+
paths: [writeNew(join(dir, "README.md"), `# ${opts.name}
|
|
975
|
+
|
|
976
|
+
${desc}
|
|
977
|
+
|
|
978
|
+
Place runnable scripts for this use case in this folder.
|
|
979
|
+
`)],
|
|
980
|
+
id
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
function scaffoldProfile(opts) {
|
|
984
|
+
return {
|
|
985
|
+
paths: [writeNew(join(opts.baseDir || process.cwd(), opts.localConfig ? "profile" : "profiles", `${opts.name}.yaml`), stringify({
|
|
986
|
+
name: opts.name,
|
|
987
|
+
description: opts.description || `${opts.name} profile`,
|
|
988
|
+
scope: "project",
|
|
989
|
+
targets: { agents: opts.agents?.length ? opts.agents : ["cursor"] },
|
|
990
|
+
artifacts: {
|
|
991
|
+
skills: opts.skills ?? [],
|
|
992
|
+
rules: opts.rules ?? [],
|
|
993
|
+
agents: opts.agentsArtifacts ?? [],
|
|
994
|
+
scripts: opts.scripts ?? []
|
|
995
|
+
}
|
|
996
|
+
}))],
|
|
997
|
+
id: opts.name
|
|
998
|
+
};
|
|
999
|
+
}
|
|
639
1000
|
let runningInsideAgent = false;
|
|
640
1001
|
try {
|
|
641
1002
|
const result = await determineAgent();
|
|
@@ -651,17 +1012,17 @@ const silentOutput = new Writable({ write(_chunk, _encoding, callback) {
|
|
|
651
1012
|
} });
|
|
652
1013
|
const cancelSymbol = Symbol("cancel");
|
|
653
1014
|
const ALL_VALUE = "__all__";
|
|
654
|
-
const BLUE =
|
|
655
|
-
const BLUE_BOLD = (s) => c.bold(
|
|
656
|
-
const S_STEP_ACTIVE =
|
|
1015
|
+
const BLUE = sky;
|
|
1016
|
+
const BLUE_BOLD = (s) => c.bold(sky(s));
|
|
1017
|
+
const S_STEP_ACTIVE = sky("◆");
|
|
657
1018
|
const S_STEP_CANCEL = c.red("■");
|
|
658
|
-
const S_STEP_SUBMIT =
|
|
659
|
-
const S_RADIO_ACTIVE =
|
|
1019
|
+
const S_STEP_SUBMIT = sky("◇");
|
|
1020
|
+
const S_RADIO_ACTIVE = sky("●");
|
|
660
1021
|
const S_RADIO_INACTIVE = c.dim("○");
|
|
661
1022
|
const S_RADIO_DISABLED = c.dim("◌");
|
|
662
|
-
const S_BULLET =
|
|
1023
|
+
const S_BULLET = sky("•");
|
|
663
1024
|
const S_BAR = c.dim("│");
|
|
664
|
-
|
|
1025
|
+
c.dim("─");
|
|
665
1026
|
const S_CORNER = c.dim("└");
|
|
666
1027
|
const S_BOX_TL = c.dim("┌");
|
|
667
1028
|
const S_BOX_TR = c.dim("┐");
|
|
@@ -764,7 +1125,7 @@ function renderInfoBox(fields, innerWidth, rows) {
|
|
|
764
1125
|
});
|
|
765
1126
|
const body = expanded.map((r) => {
|
|
766
1127
|
if (!r.label && !r.value) return `${S_BOX_V} ${" ".repeat(contentWidth)} ${S_BOX_V}`;
|
|
767
|
-
const content = `${r.label ? `${
|
|
1128
|
+
const content = `${r.label ? `${sky(padPlain(r.label, labelW))} ` : `${" ".repeat(labelW)} `}${r.cont ? c.dim(r.value) : c.bold(r.value)}`;
|
|
768
1129
|
const plainW = approxStringWidth(stripVTControlCharacters(content));
|
|
769
1130
|
const pad = Math.max(0, contentWidth - plainW);
|
|
770
1131
|
return `${S_BOX_V} ${content}${" ".repeat(pad)} ${S_BOX_V}`;
|
|
@@ -859,6 +1220,7 @@ async function searchMultiselect(options) {
|
|
|
859
1220
|
});
|
|
860
1221
|
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
861
1222
|
readline.emitKeypressEvents(process.stdin, rl);
|
|
1223
|
+
if (process.stdout.isTTY) process.stdout.write("\x1B[?25l");
|
|
862
1224
|
let query = "";
|
|
863
1225
|
let cursor = 0;
|
|
864
1226
|
const selected = new Set(initialSelected.filter((v) => String(v) !== ALL_VALUE));
|
|
@@ -873,6 +1235,32 @@ async function searchMultiselect(options) {
|
|
|
873
1235
|
const matched = rest.filter((item) => item.label.toLowerCase().includes(lowerQ) || String(item.value).toLowerCase().includes(lowerQ) || item.hint && item.hint.toLowerCase().includes(lowerQ));
|
|
874
1236
|
return allItem && (allItem.label.toLowerCase().includes(lowerQ) || "all".includes(lowerQ)) && allItem ? [allItem, ...matched] : matched;
|
|
875
1237
|
};
|
|
1238
|
+
const lockedTotal = lockedSection ? lockedSection.items.length + (lockedSection.hiddenCount ?? 0) : 0;
|
|
1239
|
+
const selectedSummaryLine = (allOn) => {
|
|
1240
|
+
if (allOn) {
|
|
1241
|
+
const extra = lockedTotal ? `Universal (${lockedTotal}) + All additional (${uniqueItems.length})` : `All ${uniqueItems.length}`;
|
|
1242
|
+
return `${S_BAR} ${BLUE("Selected:")} ${c.bold(extra)}`;
|
|
1243
|
+
}
|
|
1244
|
+
const labels = uniqueItems.filter((item) => selected.has(item.value)).map((item) => item.label);
|
|
1245
|
+
if (labels.length === 0) {
|
|
1246
|
+
if (requiredFlash) return `${S_BAR} ${c.red("Selected: pick at least one")}`;
|
|
1247
|
+
if (lockedTotal > 0) return `${S_BAR} ${BLUE("Selected:")} ${c.bold(`Universal (${lockedTotal})`)}`;
|
|
1248
|
+
return `${S_BAR} ${c.dim("Selected: (none)")}`;
|
|
1249
|
+
}
|
|
1250
|
+
const prefix = lockedTotal > 0 ? `Universal (${lockedTotal}) + ` : "";
|
|
1251
|
+
const summary = labels.length <= 3 ? labels.join(", ") : `${labels.slice(0, 3).join(", ")} +${labels.length - 3} more`;
|
|
1252
|
+
return `${S_BAR} ${BLUE("Selected:")} ${prefix}${summary}`;
|
|
1253
|
+
};
|
|
1254
|
+
const submitSummary = (allOn) => {
|
|
1255
|
+
if (allOn) return lockedTotal ? `Universal (${lockedTotal}) + All additional (${uniqueItems.length})` : `All (${uniqueItems.length})`;
|
|
1256
|
+
const labels = uniqueItems.filter((item) => selected.has(item.value)).map((item) => item.label);
|
|
1257
|
+
if (labels.length === 0) return lockedTotal > 0 ? `Universal (${lockedTotal})` : "none";
|
|
1258
|
+
return (lockedTotal > 0 ? `Universal (${lockedTotal}) + ` : "") + (labels.length <= 5 ? labels.join(", ") : `${labels.slice(0, 5).join(", ")} +${labels.length - 5}`);
|
|
1259
|
+
};
|
|
1260
|
+
const clearPrevious = (rows) => {
|
|
1261
|
+
if (rows <= 0) return;
|
|
1262
|
+
for (let i = 0; i < rows; i++) process.stdout.write("\x1B[1A\x1B[2K");
|
|
1263
|
+
};
|
|
876
1264
|
const render = (state = "active") => {
|
|
877
1265
|
const lines = [];
|
|
878
1266
|
const filtered = getFiltered();
|
|
@@ -881,14 +1269,9 @@ async function searchMultiselect(options) {
|
|
|
881
1269
|
lines.push(`${icon} ${c.bold(message)}`);
|
|
882
1270
|
lines.push(`${S_BAR}`);
|
|
883
1271
|
if (state === "active") {
|
|
884
|
-
if (lockedSection &&
|
|
885
|
-
|
|
886
|
-
lines.push(`${S_BAR} ${
|
|
887
|
-
for (const item of lockedSection.items.slice(0, 3)) lines.push(`${S_BAR} ${S_BULLET} ${c.bold(item.label)}`);
|
|
888
|
-
const more = (lockedSection.hiddenCount ?? 0) + Math.max(0, lockedSection.items.length - 3);
|
|
889
|
-
if (more > 0) lines.push(`${S_BAR} ${c.dim(`…and ${more} more`)}`);
|
|
890
|
-
lines.push(`${S_BAR}`);
|
|
891
|
-
lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${c.bold("Additional")} ${S_BAR_H.repeat(26)}`);
|
|
1272
|
+
if (lockedSection && lockedTotal > 0) {
|
|
1273
|
+
lines.push(`${S_BAR} ${S_BULLET} ${c.bold(lockedSection.title)} ${c.dim(`· ${lockedTotal} always included`)}`);
|
|
1274
|
+
lines.push(`${S_BAR} ${c.bold("Additional")}`);
|
|
892
1275
|
lines.push(`${S_BAR}`);
|
|
893
1276
|
}
|
|
894
1277
|
if (searchable) {
|
|
@@ -938,28 +1321,20 @@ async function searchMultiselect(options) {
|
|
|
938
1321
|
}
|
|
939
1322
|
if (showSelectedSummary) {
|
|
940
1323
|
lines.push(`${S_BAR}`);
|
|
941
|
-
|
|
942
|
-
else {
|
|
943
|
-
const labels = uniqueItems.filter((item) => selected.has(item.value)).map((item) => item.label);
|
|
944
|
-
if (labels.length === 0) lines.push(`${S_BAR} ${requiredFlash ? c.red("Selected: pick at least one") : c.dim("Selected: (none)")}`);
|
|
945
|
-
else {
|
|
946
|
-
const summary = labels.length <= 3 ? labels.join(", ") : `${labels.slice(0, 3).join(", ")} +${labels.length - 3} more`;
|
|
947
|
-
lines.push(`${S_BAR} ${BLUE("Selected:")} ${summary}`);
|
|
948
|
-
}
|
|
949
|
-
}
|
|
1324
|
+
lines.push(selectedSummaryLine(allOn));
|
|
950
1325
|
}
|
|
951
1326
|
lines.push(`${S_CORNER}`);
|
|
952
|
-
} else if (state === "submit") {
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
process.stdout.write(clearPreviousFrame + lines.join("\n") + "\n");
|
|
1327
|
+
} else if (state === "submit") lines.push(`${S_BAR} ${c.dim(submitSummary(allOn))}`);
|
|
1328
|
+
else lines.push(`${S_BAR} ${c.strikethrough(c.dim("Cancelled"))}`);
|
|
1329
|
+
clearPrevious(lastRenderHeight);
|
|
1330
|
+
const out = lines.join("\n") + "\n";
|
|
1331
|
+
process.stdout.write(out);
|
|
958
1332
|
lastRenderHeight = countVisualRowsForLines(lines, process.stdout.columns);
|
|
959
1333
|
};
|
|
960
1334
|
const cleanup = () => {
|
|
961
1335
|
process.stdin.removeListener("keypress", keypressHandler);
|
|
962
1336
|
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
1337
|
+
if (process.stdout.isTTY) process.stdout.write("\x1B[?25h");
|
|
963
1338
|
rl.close();
|
|
964
1339
|
};
|
|
965
1340
|
const submit = () => {
|
|
@@ -1037,6 +1412,11 @@ async function searchMultiselect(options) {
|
|
|
1037
1412
|
render();
|
|
1038
1413
|
});
|
|
1039
1414
|
}
|
|
1415
|
+
function agentsPrompt(action) {
|
|
1416
|
+
if (action === "remove") return "Which agents do you want to remove from?";
|
|
1417
|
+
if (action === "update") return "Which agents do you want to update for?";
|
|
1418
|
+
return "Which agents do you want to install to?";
|
|
1419
|
+
}
|
|
1040
1420
|
function isQuiet() {
|
|
1041
1421
|
return isCI() || !process.stdout.isTTY;
|
|
1042
1422
|
}
|
|
@@ -1054,6 +1434,79 @@ function outro(message) {
|
|
|
1054
1434
|
}
|
|
1055
1435
|
clack.outro(message);
|
|
1056
1436
|
}
|
|
1437
|
+
async function confirm(message) {
|
|
1438
|
+
if (isQuiet()) return true;
|
|
1439
|
+
return new Promise((resolve) => {
|
|
1440
|
+
if (!process.stdin.isTTY) {
|
|
1441
|
+
resolve(true);
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
const rl = readline.createInterface({
|
|
1445
|
+
input: process.stdin,
|
|
1446
|
+
output: process.stdout
|
|
1447
|
+
});
|
|
1448
|
+
process.stdin.setRawMode(true);
|
|
1449
|
+
readline.emitKeypressEvents(process.stdin, rl);
|
|
1450
|
+
let value = true;
|
|
1451
|
+
let lastH = 0;
|
|
1452
|
+
const paint = (done) => {
|
|
1453
|
+
const lines = [];
|
|
1454
|
+
if (done === "cancel") {
|
|
1455
|
+
lines.push(`${sky("■")} ${c.bold(message)}`);
|
|
1456
|
+
lines.push(`${c.dim("│")} ${c.strikethrough(c.dim("Cancelled"))}`);
|
|
1457
|
+
} else if (done === "yes" || done === "no") {
|
|
1458
|
+
lines.push(`${sky("◇")} ${c.bold(message)}`);
|
|
1459
|
+
lines.push(`${c.dim("│")} ${c.dim(done === "yes" ? "Yes" : "No")}`);
|
|
1460
|
+
} else {
|
|
1461
|
+
lines.push(`${sky("◆")} ${c.bold(message)}`);
|
|
1462
|
+
lines.push(`${c.dim("│")}`);
|
|
1463
|
+
const yes = value ? sky(c.bold("● Yes")) : c.dim("○ Yes");
|
|
1464
|
+
const no = !value ? sky(c.bold("● No")) : c.dim("○ No");
|
|
1465
|
+
lines.push(`${c.dim("│")} ${yes} / ${no}`);
|
|
1466
|
+
lines.push(`${c.dim("└")} ${c.dim("←/→ toggle · enter confirm")}`);
|
|
1467
|
+
}
|
|
1468
|
+
if (lastH > 0) for (let i = 0; i < lastH; i++) process.stdout.write("\x1B[1A\x1B[2K");
|
|
1469
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
1470
|
+
lastH = lines.length;
|
|
1471
|
+
};
|
|
1472
|
+
const cleanup = () => {
|
|
1473
|
+
process.stdin.removeListener("keypress", onKey);
|
|
1474
|
+
process.stdin.setRawMode(false);
|
|
1475
|
+
rl.close();
|
|
1476
|
+
};
|
|
1477
|
+
const onKey = (_s, key) => {
|
|
1478
|
+
if (!key) return;
|
|
1479
|
+
if (key.name === "left" || key.name === "right" || key.name === "tab") {
|
|
1480
|
+
value = !value;
|
|
1481
|
+
paint();
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
if (key.name === "y") {
|
|
1485
|
+
value = true;
|
|
1486
|
+
paint();
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
if (key.name === "n") {
|
|
1490
|
+
value = false;
|
|
1491
|
+
paint();
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
if (key.name === "return") {
|
|
1495
|
+
paint(value ? "yes" : "no");
|
|
1496
|
+
cleanup();
|
|
1497
|
+
resolve(value);
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
if (key.name === "escape" || key.ctrl && key.name === "c") {
|
|
1501
|
+
paint("cancel");
|
|
1502
|
+
cleanup();
|
|
1503
|
+
resolve(false);
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
process.stdin.on("keypress", onKey);
|
|
1507
|
+
paint();
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1057
1510
|
function log(message) {
|
|
1058
1511
|
process.stdout.write(message + "\n");
|
|
1059
1512
|
}
|
|
@@ -1063,7 +1516,7 @@ function blank() {
|
|
|
1063
1516
|
async function sectionTransition(doneLabel, nextLabel) {
|
|
1064
1517
|
if (isQuiet()) return;
|
|
1065
1518
|
blank();
|
|
1066
|
-
log(`${
|
|
1519
|
+
log(`${sky("◇")} ${c.dim("Done:")} ${c.bold(doneLabel)}`);
|
|
1067
1520
|
await delay(320);
|
|
1068
1521
|
for (const ch of [
|
|
1069
1522
|
"│",
|
|
@@ -1074,15 +1527,15 @@ async function sectionTransition(doneLabel, nextLabel) {
|
|
|
1074
1527
|
await delay(140);
|
|
1075
1528
|
}
|
|
1076
1529
|
await delay(200);
|
|
1077
|
-
log(`${
|
|
1530
|
+
log(`${sky("◆")} ${c.bold("Next:")} ${nextLabel}`);
|
|
1078
1531
|
await delay(420);
|
|
1079
1532
|
blank();
|
|
1080
1533
|
}
|
|
1081
1534
|
function step(message) {
|
|
1082
|
-
process.stdout.write(` ${
|
|
1535
|
+
process.stdout.write(` ${sky("→")} ${message}\n`);
|
|
1083
1536
|
}
|
|
1084
1537
|
function ok(message) {
|
|
1085
|
-
process.stdout.write(`${
|
|
1538
|
+
process.stdout.write(`${sky("✓")} ${message}\n`);
|
|
1086
1539
|
}
|
|
1087
1540
|
function err(message) {
|
|
1088
1541
|
process.stdout.write(`${c.red("✗")} ${message}\n`);
|
|
@@ -1091,13 +1544,13 @@ function warn(message) {
|
|
|
1091
1544
|
process.stdout.write(`${c.yellow("⚠")} ${message}\n`);
|
|
1092
1545
|
}
|
|
1093
1546
|
function info(message) {
|
|
1094
|
-
process.stdout.write(`${
|
|
1547
|
+
process.stdout.write(`${sky("ℹ")} ${message}\n`);
|
|
1095
1548
|
}
|
|
1096
1549
|
async function spinner(startMsg, fn, stopWith, minMs = 480) {
|
|
1097
1550
|
if (isQuiet()) {
|
|
1098
1551
|
process.stdout.write(c.dim(`• ${startMsg}...`) + "\n");
|
|
1099
1552
|
const r = await fn();
|
|
1100
|
-
if (stopWith) process.stdout.write(`${
|
|
1553
|
+
if (stopWith) process.stdout.write(`${sky("◇")} ${stopWith(r)}\n`);
|
|
1101
1554
|
return r;
|
|
1102
1555
|
}
|
|
1103
1556
|
const frames = [
|
|
@@ -1114,11 +1567,11 @@ async function spinner(startMsg, fn, stopWith, minMs = 480) {
|
|
|
1114
1567
|
];
|
|
1115
1568
|
let i = 0;
|
|
1116
1569
|
let alive = true;
|
|
1117
|
-
process.stdout.write(`${
|
|
1570
|
+
process.stdout.write(`${sky(frames[0])} ${startMsg}`);
|
|
1118
1571
|
const tick = setInterval(() => {
|
|
1119
1572
|
if (!alive) return;
|
|
1120
1573
|
i = (i + 1) % frames.length;
|
|
1121
|
-
process.stdout.write(`\r${
|
|
1574
|
+
process.stdout.write(`\r${sky(frames[i])} ${startMsg} `);
|
|
1122
1575
|
}, 80);
|
|
1123
1576
|
const started = Date.now();
|
|
1124
1577
|
try {
|
|
@@ -1128,7 +1581,7 @@ async function spinner(startMsg, fn, stopWith, minMs = 480) {
|
|
|
1128
1581
|
alive = false;
|
|
1129
1582
|
clearInterval(tick);
|
|
1130
1583
|
const msg = stopWith ? stopWith(result) : startMsg;
|
|
1131
|
-
process.stdout.write(`\r\x1b[K${
|
|
1584
|
+
process.stdout.write(`\r\x1b[K${sky("◇")} ${msg}\n`);
|
|
1132
1585
|
return result;
|
|
1133
1586
|
} catch (e) {
|
|
1134
1587
|
alive = false;
|
|
@@ -1235,23 +1688,23 @@ async function selectAgents(message, universal, others, detectedNames = []) {
|
|
|
1235
1688
|
}));
|
|
1236
1689
|
const locked = universal.length > 0 ? {
|
|
1237
1690
|
title: "Universal",
|
|
1238
|
-
items: universal.
|
|
1691
|
+
items: universal.map((a) => ({
|
|
1239
1692
|
value: a.name,
|
|
1240
1693
|
label: a.displayName,
|
|
1241
1694
|
fields: agentFields(a, true)
|
|
1242
1695
|
})),
|
|
1243
|
-
|
|
1696
|
+
compact: true
|
|
1244
1697
|
} : void 0;
|
|
1245
1698
|
const res = await searchMultiselect({
|
|
1246
1699
|
message,
|
|
1247
1700
|
items,
|
|
1248
|
-
maxVisible:
|
|
1701
|
+
maxVisible: 5,
|
|
1249
1702
|
required: false,
|
|
1250
1703
|
initialSelected: detected.map((d) => d.name),
|
|
1251
1704
|
lockedSection: locked,
|
|
1252
1705
|
searchable: true,
|
|
1253
1706
|
showDetail: true,
|
|
1254
|
-
detailLines:
|
|
1707
|
+
detailLines: 4,
|
|
1255
1708
|
showSelectedSummary: true,
|
|
1256
1709
|
includeAllOption: true,
|
|
1257
1710
|
allLabel: `All additional (${uniqueOthers.length})`,
|
|
@@ -1273,12 +1726,12 @@ function installSummary(artifacts, agents, scopeLabel) {
|
|
|
1273
1726
|
blank();
|
|
1274
1727
|
const bar = c.dim("│");
|
|
1275
1728
|
const corner = c.dim("└");
|
|
1276
|
-
log(`${
|
|
1729
|
+
log(`${sky("◆")} ${c.bold("Install summary")}`);
|
|
1277
1730
|
log(bar);
|
|
1278
|
-
log(`${bar} ${
|
|
1731
|
+
log(`${bar} ${sky("Scope")} ${scopeLabel}`);
|
|
1279
1732
|
const universalCount = agents.filter((a) => a.skillsDir === ".agents/skills").length;
|
|
1280
1733
|
const otherCount = agents.length - universalCount;
|
|
1281
|
-
log(`${bar} ${
|
|
1734
|
+
log(`${bar} ${sky("Targets")} ${agents.length} agent${agents.length === 1 ? "" : "s"}` + c.dim(` · ${universalCount} universal` + (otherCount ? ` · ${otherCount} additional` : "")));
|
|
1282
1735
|
const names = agents.map((a) => a.displayName);
|
|
1283
1736
|
const chunks = [];
|
|
1284
1737
|
let line = "";
|
|
@@ -1297,7 +1750,7 @@ function installSummary(artifacts, agents, scopeLabel) {
|
|
|
1297
1750
|
log(bar);
|
|
1298
1751
|
for (const a of artifacts) {
|
|
1299
1752
|
const desc = (a.description || "").replace(/\s+/g, " ").trim().slice(0, 52);
|
|
1300
|
-
log(`${bar} ${
|
|
1753
|
+
log(`${bar} ${sky("•")} ${c.bold(`${a.kind}/${a.id}`)}`);
|
|
1301
1754
|
if (desc) log(`${bar} ${c.dim(desc)}`);
|
|
1302
1755
|
}
|
|
1303
1756
|
log(bar);
|
|
@@ -1307,16 +1760,29 @@ function installSummary(artifacts, agents, scopeLabel) {
|
|
|
1307
1760
|
function preview(artifacts, agents) {
|
|
1308
1761
|
installSummary(artifacts, agents, "preview (dry-run)");
|
|
1309
1762
|
}
|
|
1763
|
+
async function resolveSourceWithUi(source, opts) {
|
|
1764
|
+
const resolved = await resolveSource(source, { confirmCache: async ({ url, error }) => {
|
|
1765
|
+
warn(`git clone failed for ${url}`);
|
|
1766
|
+
if (error) step(c.dim(error.split("\n")[0] || error));
|
|
1767
|
+
if (opts.yes || isQuiet()) {
|
|
1768
|
+
info("Using cached copy.");
|
|
1769
|
+
return true;
|
|
1770
|
+
}
|
|
1771
|
+
return confirm("Load from cache instead?");
|
|
1772
|
+
} });
|
|
1773
|
+
if (resolved.fromCache) info("Loaded source from cache.");
|
|
1774
|
+
return resolved;
|
|
1775
|
+
}
|
|
1310
1776
|
async function cmdAdd(kind, source, selector, opts) {
|
|
1311
1777
|
if (!source) throw new Error(`Missing source. e.g. agentry add ${kind} author/repo`);
|
|
1312
1778
|
intro();
|
|
1313
1779
|
await delayQuiet(180);
|
|
1314
1780
|
await spinner("Parsing source…", () => parseSourceForUi(source), (p) => {
|
|
1315
1781
|
const where = p.url || p.local || source;
|
|
1316
|
-
return `${
|
|
1782
|
+
return `${theme.primary("Source:")} ${where}${p.ref ? c.dim(` @ ${p.ref}`) : ""}${p.subpath ? c.dim(` (${p.subpath})`) : ""}`;
|
|
1317
1783
|
}, 520);
|
|
1318
1784
|
await delayQuiet(280);
|
|
1319
|
-
const { root, cleanup } =
|
|
1785
|
+
const { root, cleanup } = await resolveSourceWithUi(source, opts);
|
|
1320
1786
|
try {
|
|
1321
1787
|
const artifacts = await spinner("Discovering…", () => kind === "all" ? listAll(root) : select(root, kind, selector), (a) => {
|
|
1322
1788
|
const noun = kind === "all" ? "artifact" : kind;
|
|
@@ -1329,7 +1795,7 @@ async function cmdAdd(kind, source, selector, opts) {
|
|
|
1329
1795
|
await delayQuiet(320);
|
|
1330
1796
|
blank();
|
|
1331
1797
|
if (opts.dryRun) {
|
|
1332
|
-
preview(artifacts, await resolveAgentList(opts));
|
|
1798
|
+
preview(artifacts, await resolveAgentList(opts, "install"));
|
|
1333
1799
|
return;
|
|
1334
1800
|
}
|
|
1335
1801
|
let chosen = artifacts;
|
|
@@ -1347,7 +1813,7 @@ async function cmdAdd(kind, source, selector, opts) {
|
|
|
1347
1813
|
await delayQuiet(200);
|
|
1348
1814
|
blank();
|
|
1349
1815
|
}
|
|
1350
|
-
const agentList = await resolveAgentList(opts);
|
|
1816
|
+
const agentList = await resolveAgentList(opts, "install");
|
|
1351
1817
|
if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition(`${agentList.length} agent${agentList.length === 1 ? "" : "s"} selected`, "install summary");
|
|
1352
1818
|
installAll(chosen, opts, agentList, source);
|
|
1353
1819
|
} finally {
|
|
@@ -1360,14 +1826,14 @@ async function delayQuiet(ms) {
|
|
|
1360
1826
|
await delay(ms);
|
|
1361
1827
|
}
|
|
1362
1828
|
function parseSourceForUi(source) {
|
|
1363
|
-
return
|
|
1829
|
+
return parseSource(source);
|
|
1364
1830
|
}
|
|
1365
1831
|
async function cmdAddProfile(name, source, opts) {
|
|
1366
1832
|
const { file, profile } = loadProfile(name);
|
|
1367
1833
|
const repo = source || (profile.artifacts.skills[0]?.source ?? profile.artifacts.rules[0]?.source);
|
|
1368
1834
|
if (!repo) throw new Error(`No source given and ${file} lists none. Try: agentry add profile ${name} author/repo`);
|
|
1369
|
-
const agentList = await resolveAgentList(opts);
|
|
1370
|
-
const { root, cleanup } =
|
|
1835
|
+
const agentList = await resolveAgentList(opts, "install");
|
|
1836
|
+
const { root, cleanup } = await resolveSourceWithUi(repo, opts);
|
|
1371
1837
|
try {
|
|
1372
1838
|
const artifacts = [];
|
|
1373
1839
|
const plural = (k) => k === "skill" ? "skills" : k === "rule" ? "rules" : k === "script" ? "scripts" : "agents";
|
|
@@ -1375,7 +1841,7 @@ async function cmdAddProfile(name, source, opts) {
|
|
|
1375
1841
|
if (k === "profile") continue;
|
|
1376
1842
|
const refs = profile.artifacts[plural(k)] ?? [];
|
|
1377
1843
|
for (const ref of refs) {
|
|
1378
|
-
const src = ref.source ?
|
|
1844
|
+
const src = ref.source ? await resolveSourceWithUi(ref.source, opts) : {
|
|
1379
1845
|
root,
|
|
1380
1846
|
cleanup: () => {}
|
|
1381
1847
|
};
|
|
@@ -1392,7 +1858,7 @@ async function cmdAddProfile(name, source, opts) {
|
|
|
1392
1858
|
cleanup();
|
|
1393
1859
|
}
|
|
1394
1860
|
}
|
|
1395
|
-
async function resolveAgentList(opts) {
|
|
1861
|
+
async function resolveAgentList(opts, action = "install") {
|
|
1396
1862
|
if (opts.all) return listAgents();
|
|
1397
1863
|
if (opts.agents.length) {
|
|
1398
1864
|
const { agents, unknown } = resolveAgents(opts.agents);
|
|
@@ -1408,7 +1874,7 @@ async function resolveAgentList(opts) {
|
|
|
1408
1874
|
const picked = detectedOthers.length ? detectedOthers : ["cursor"];
|
|
1409
1875
|
return [...universal, ...others.filter((o) => picked.includes(o.name))];
|
|
1410
1876
|
}
|
|
1411
|
-
const selected = await selectAgents(
|
|
1877
|
+
const selected = await selectAgents(agentsPrompt(action), visibleUniversal, others, detectedOthers);
|
|
1412
1878
|
if (isCancelLike(selected)) throw new Error("Cancelled.");
|
|
1413
1879
|
return selected;
|
|
1414
1880
|
}
|
|
@@ -1430,74 +1896,204 @@ function toInstallOpts(opts) {
|
|
|
1430
1896
|
dryRun: opts.dryRun
|
|
1431
1897
|
};
|
|
1432
1898
|
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1899
|
+
function resolveSourceOrSelector(arg1, arg2, opts) {
|
|
1900
|
+
if (arg1 && arg2) return {
|
|
1901
|
+
source: arg1,
|
|
1902
|
+
selector: arg2
|
|
1903
|
+
};
|
|
1904
|
+
if (!arg1) return {};
|
|
1905
|
+
if (isExplicitSource(arg1)) return { source: arg1 };
|
|
1906
|
+
if (isOwnerRepoShape(arg1)) {
|
|
1907
|
+
const lock = readLock(toInstallOpts(opts));
|
|
1908
|
+
if (Object.values(lock.items).some((e) => e.source && sourcesEqual(e.source, arg1))) return { source: arg1 };
|
|
1909
|
+
}
|
|
1910
|
+
return { selector: arg1 };
|
|
1911
|
+
}
|
|
1912
|
+
async function cmdRemove(kind, arg1, arg2, opts) {
|
|
1913
|
+
intro();
|
|
1914
|
+
const { source, selector } = resolveSourceOrSelector(arg1, arg2, opts);
|
|
1915
|
+
if (source) await spinner("Resolving source…", () => parseSource(source), (p) => {
|
|
1916
|
+
const where = p.url || p.local || source;
|
|
1917
|
+
return `${theme.primary("Source:")} ${where}${p.ref ? c.dim(` @ ${p.ref}`) : ""}`;
|
|
1918
|
+
}, 420);
|
|
1919
|
+
if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition("prepare remove", "choose agents to remove from");
|
|
1920
|
+
const agentList = await resolveAgentList(opts, "remove");
|
|
1435
1921
|
const installOpts = toInstallOpts(opts);
|
|
1436
|
-
const scope = [
|
|
1922
|
+
const scope = [
|
|
1923
|
+
kind,
|
|
1924
|
+
source,
|
|
1925
|
+
selector
|
|
1926
|
+
].filter(Boolean).join(" / ");
|
|
1437
1927
|
if (kind === "all") {
|
|
1438
1928
|
let count = 0;
|
|
1439
|
-
|
|
1440
|
-
|
|
1929
|
+
let paths = 0;
|
|
1930
|
+
for (const t of ARTIFACT_KINDS) {
|
|
1931
|
+
const r = removeSelection(t, void 0, installOpts, agentList, source);
|
|
1932
|
+
count += r.removedKeys.length;
|
|
1933
|
+
paths += r.removedPaths.length;
|
|
1934
|
+
}
|
|
1935
|
+
ok(`Removed ${count} item(s)${paths ? c.dim(` · ${paths} path(s)`) : ""}${source ? c.dim(` from ${source}`) : ""}.`);
|
|
1441
1936
|
return;
|
|
1442
1937
|
}
|
|
1443
|
-
const r = removeSelection(kind, selector, installOpts, agentList);
|
|
1938
|
+
const r = removeSelection(kind, selector, installOpts, agentList, source);
|
|
1444
1939
|
if (!r.existed) {
|
|
1445
|
-
warn(`Nothing installed
|
|
1940
|
+
warn(`Nothing installed matching "${scope}".`);
|
|
1446
1941
|
return;
|
|
1447
1942
|
}
|
|
1448
1943
|
for (const k of r.removedKeys) step(`removed ${k}`);
|
|
1449
|
-
|
|
1944
|
+
for (const p of r.removedPaths.slice(0, 12)) step(c.dim(`deleted ${p}`));
|
|
1945
|
+
if (r.removedPaths.length > 12) step(c.dim(`…and ${r.removedPaths.length - 12} more paths`));
|
|
1946
|
+
ok(`Removed ${r.removedKeys.length} item(s)${r.removedPaths.length ? c.dim(` · ${r.removedPaths.length} path(s)`) : ""}${source ? c.dim(` from ${source}`) : ""}.`);
|
|
1450
1947
|
}
|
|
1451
|
-
function cmdList(source, kind) {
|
|
1452
|
-
if (!source)
|
|
1453
|
-
|
|
1948
|
+
async function cmdList(source, kind, selector, opts) {
|
|
1949
|
+
if (!source) {
|
|
1950
|
+
cmdListInstalled(opts, kind, selector);
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
intro();
|
|
1954
|
+
await spinner("Parsing source…", () => parseSource(source), (p) => {
|
|
1955
|
+
const where = p.url || p.local || source;
|
|
1956
|
+
return `${theme.primary("Source:")} ${where}${p.ref ? c.dim(` @ ${p.ref}`) : ""}${p.subpath ? c.dim(` (${p.subpath})`) : ""}`;
|
|
1957
|
+
}, 420);
|
|
1958
|
+
const { root, cleanup } = await resolveSourceWithUi(source, opts);
|
|
1454
1959
|
try {
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
for (const it of items) {
|
|
1463
|
-
const desc = (it.description || "").replace(/\s+/g, " ").slice(0, 80);
|
|
1464
|
-
step(`${it.id}${desc ? " - " + desc : ""}`);
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1960
|
+
printArtifactList(await spinner("Discovering…", () => {
|
|
1961
|
+
if (kind) return select(root, kind, selector);
|
|
1962
|
+
return listAll(root);
|
|
1963
|
+
}, (a) => `Found ${a.length} artifact${a.length === 1 ? "" : "s"}`, 520), {
|
|
1964
|
+
title: "Available in source",
|
|
1965
|
+
subtitle: source
|
|
1966
|
+
});
|
|
1467
1967
|
} finally {
|
|
1468
1968
|
cleanup();
|
|
1469
1969
|
}
|
|
1470
1970
|
}
|
|
1471
|
-
function cmdListInstalled(opts) {
|
|
1472
|
-
|
|
1971
|
+
function cmdListInstalled(opts, kind, selector) {
|
|
1972
|
+
intro();
|
|
1973
|
+
let installed = listInstalled(toInstallOpts(opts));
|
|
1974
|
+
if (kind) installed = installed.filter((it) => it.kind === kind || it.id.startsWith(kind + "/"));
|
|
1975
|
+
if (selector && kind) {
|
|
1976
|
+
const rel = `${kind}/${selector}`;
|
|
1977
|
+
installed = installed.filter((it) => it.id === rel || it.id.startsWith(rel + "/") || it.id.endsWith("/" + selector) || it.id === `${kind}/${selector}`);
|
|
1978
|
+
} else if (selector) installed = installed.filter((it) => it.id.includes(selector));
|
|
1473
1979
|
if (!installed.length) {
|
|
1474
|
-
info("Nothing installed.");
|
|
1980
|
+
info(kind || selector ? `Nothing installed matching that filter.` : "Nothing installed.");
|
|
1981
|
+
step(c.dim("Try: agentry add skills mhmdreza-rafiei/agent-tools"));
|
|
1475
1982
|
return;
|
|
1476
1983
|
}
|
|
1477
|
-
|
|
1984
|
+
const bar = c.dim("│");
|
|
1985
|
+
const corner = c.dim("└");
|
|
1986
|
+
blank();
|
|
1987
|
+
log(`${sky("◆")} ${c.bold("Installed")}`);
|
|
1988
|
+
log(bar);
|
|
1989
|
+
log(`${bar} ${sky("Scope")} ${opts.scope === "global" ? "global (~)" : `project (${opts.dir || process.cwd()})`}`);
|
|
1990
|
+
log(`${bar} ${sky("Total")} ${installed.length} artifact${installed.length === 1 ? "" : "s"}`);
|
|
1991
|
+
log(bar);
|
|
1992
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
1993
|
+
for (const it of installed) {
|
|
1994
|
+
const k = it.kind || it.id.split("/")[0];
|
|
1995
|
+
const arr = byKind.get(k) || [];
|
|
1996
|
+
arr.push(it);
|
|
1997
|
+
byKind.set(k, arr);
|
|
1998
|
+
}
|
|
1999
|
+
for (const k of ARTIFACT_KINDS) {
|
|
2000
|
+
const items = byKind.get(k);
|
|
2001
|
+
if (!items?.length) continue;
|
|
2002
|
+
log(`${bar} ${c.bold(k + "s")} ${c.dim(`(${items.length})`)}`);
|
|
2003
|
+
for (const it of items) {
|
|
2004
|
+
const id = it.id.includes("/") ? it.id.slice(it.id.indexOf("/") + 1) : it.id;
|
|
2005
|
+
log(`${bar} ${sky("•")} ${c.bold(id)}`);
|
|
2006
|
+
const agents = it.agents?.length ? it.agents.join(", ") : "—";
|
|
2007
|
+
log(`${bar} ${c.dim(`agents: ${agents}`)}`);
|
|
2008
|
+
if (it.source) log(`${bar} ${c.dim(`source: ${it.source}`)}`);
|
|
2009
|
+
}
|
|
2010
|
+
log(bar);
|
|
2011
|
+
}
|
|
2012
|
+
log(`${corner} ${c.dim("agentry remove <kind> [source] [selector] · agentry update <kind> [source]")}`);
|
|
2013
|
+
blank();
|
|
1478
2014
|
}
|
|
1479
|
-
|
|
2015
|
+
function printArtifactList(artifacts, meta) {
|
|
2016
|
+
const bar = c.dim("│");
|
|
2017
|
+
const corner = c.dim("└");
|
|
2018
|
+
blank();
|
|
2019
|
+
log(`${sky("◆")} ${c.bold(meta.title)}`);
|
|
2020
|
+
log(bar);
|
|
2021
|
+
if (meta.subtitle) log(`${bar} ${sky("From")} ${meta.subtitle}`);
|
|
2022
|
+
log(`${bar} ${sky("Total")} ${artifacts.length}`);
|
|
2023
|
+
log(bar);
|
|
2024
|
+
if (!artifacts.length) {
|
|
2025
|
+
log(`${bar} ${c.dim("(none)")}`);
|
|
2026
|
+
log(bar);
|
|
2027
|
+
log(`${corner}`);
|
|
2028
|
+
blank();
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
2032
|
+
for (const a of artifacts) {
|
|
2033
|
+
const arr = byKind.get(a.kind) || [];
|
|
2034
|
+
arr.push(a);
|
|
2035
|
+
byKind.set(a.kind, arr);
|
|
2036
|
+
}
|
|
2037
|
+
for (const k of ARTIFACT_KINDS) {
|
|
2038
|
+
const items = byKind.get(k);
|
|
2039
|
+
if (!items?.length) continue;
|
|
2040
|
+
log(`${bar} ${c.bold(k + "s")} ${c.dim(`(${items.length})`)}`);
|
|
2041
|
+
for (const it of items) {
|
|
2042
|
+
const desc = (it.description || "").replace(/\s+/g, " ").trim().slice(0, 56);
|
|
2043
|
+
log(`${bar} ${sky("•")} ${c.bold(it.id)}`);
|
|
2044
|
+
if (desc) log(`${bar} ${c.dim(desc)}`);
|
|
2045
|
+
}
|
|
2046
|
+
log(bar);
|
|
2047
|
+
}
|
|
2048
|
+
log(`${corner} ${c.dim("agentry add <kind> <source> [selector]")}`);
|
|
2049
|
+
blank();
|
|
2050
|
+
}
|
|
2051
|
+
async function cmdUpdateAssets(kind, arg1, arg2, opts) {
|
|
2052
|
+
intro();
|
|
2053
|
+
const { source, selector } = resolveSourceOrSelector(arg1, arg2, opts);
|
|
1480
2054
|
const installOpts = toInstallOpts(opts);
|
|
1481
|
-
const agentList = await resolveAgentList(opts);
|
|
1482
2055
|
if (source) {
|
|
1483
|
-
|
|
2056
|
+
await spinner("Parsing source…", () => parseSource(source), (p) => {
|
|
2057
|
+
const where = p.url || p.local || source;
|
|
2058
|
+
return `${theme.primary("Source:")} ${where}${p.ref ? c.dim(` @ ${p.ref}`) : ""}`;
|
|
2059
|
+
}, 520);
|
|
2060
|
+
const { root, cleanup } = await resolveSourceWithUi(source, opts);
|
|
1484
2061
|
try {
|
|
1485
|
-
const artifacts = kind === "all" ? listAll(root) : select(root, kind, selector);
|
|
2062
|
+
const artifacts = await spinner("Discovering…", () => kind === "all" ? listAll(root) : select(root, kind, selector), (a) => `Found ${a.length} to update`, 620);
|
|
1486
2063
|
if (!artifacts.length) {
|
|
1487
2064
|
warn(`Nothing found in ${source}.`);
|
|
1488
2065
|
return;
|
|
1489
2066
|
}
|
|
1490
|
-
|
|
2067
|
+
let chosen = artifacts;
|
|
2068
|
+
if (!selector && kind !== "all" && artifacts.length > 1 && !opts.all && !opts.yes && !isQuiet()) {
|
|
2069
|
+
await sectionTransition("source discovery", `select ${kind}s to update`);
|
|
2070
|
+
const picked = await selectArtifacts(`Select ${kind}s to update`, artifacts);
|
|
2071
|
+
if (isCancelLike(picked)) {
|
|
2072
|
+
outro("Cancelled.");
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
chosen = picked;
|
|
2076
|
+
await sectionTransition(`${chosen.length} ${kind}${chosen.length === 1 ? "" : "s"} selected`, "choose target agents");
|
|
2077
|
+
} else if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition(`${chosen.length} ${kind}${chosen.length === 1 ? "" : "s"} ready`, "choose target agents");
|
|
2078
|
+
const agentList = await resolveAgentList(opts, "update");
|
|
2079
|
+
if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition(`${agentList.length} agent${agentList.length === 1 ? "" : "s"} selected`, "update summary");
|
|
2080
|
+
installAll(chosen, opts, agentList, source);
|
|
1491
2081
|
} finally {
|
|
1492
2082
|
cleanup();
|
|
1493
2083
|
}
|
|
1494
2084
|
return;
|
|
1495
2085
|
}
|
|
1496
|
-
|
|
2086
|
+
let installed = listInstalled(installOpts).filter((it) => kind === "all" || it.id.startsWith(kind + "/"));
|
|
2087
|
+
if (selector) {
|
|
2088
|
+
const rel = `${kind}/${selector}`;
|
|
2089
|
+
installed = installed.filter((it) => it.id === rel || it.id.startsWith(rel + "/") || it.id.endsWith("/" + selector));
|
|
2090
|
+
}
|
|
1497
2091
|
if (!installed.length) {
|
|
1498
2092
|
warn(`No installed ${kind} recorded in lockfile. Provide a source.`);
|
|
1499
2093
|
return;
|
|
1500
2094
|
}
|
|
2095
|
+
if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition("lockfile sources", "choose agents to update for");
|
|
2096
|
+
const agentList = await resolveAgentList(opts, "update");
|
|
1501
2097
|
const bySource = /* @__PURE__ */ new Map();
|
|
1502
2098
|
for (const it of installed) if (it.source) {
|
|
1503
2099
|
const arr = bySource.get(it.source) || [];
|
|
@@ -1509,14 +2105,206 @@ async function cmdUpdateAssets(kind, source, selector, opts) {
|
|
|
1509
2105
|
return;
|
|
1510
2106
|
}
|
|
1511
2107
|
for (const [s, ids] of bySource) {
|
|
1512
|
-
|
|
2108
|
+
info(`Updating from ${s}…`);
|
|
2109
|
+
const { root, cleanup } = await resolveSourceWithUi(s, opts);
|
|
1513
2110
|
try {
|
|
1514
|
-
installAll(ids.map((id) =>
|
|
2111
|
+
installAll(ids.map((id) => {
|
|
2112
|
+
const [k, ...rest] = id.split("/");
|
|
2113
|
+
return select(root, k, rest.join("/") || void 0);
|
|
2114
|
+
}).flat(), opts, agentList, s);
|
|
1515
2115
|
} finally {
|
|
1516
2116
|
cleanup();
|
|
1517
2117
|
}
|
|
1518
2118
|
}
|
|
1519
2119
|
}
|
|
2120
|
+
async function promptText(message, initial) {
|
|
2121
|
+
if (isQuiet()) return initial || null;
|
|
2122
|
+
const res = await clack.text({
|
|
2123
|
+
message,
|
|
2124
|
+
initialValue: initial || "",
|
|
2125
|
+
placeholder: initial
|
|
2126
|
+
});
|
|
2127
|
+
if (clack.isCancel(res)) return null;
|
|
2128
|
+
return String(res).trim() || null;
|
|
2129
|
+
}
|
|
2130
|
+
async function pickFromSource(kind, source, message, opts) {
|
|
2131
|
+
const { root, cleanup } = await resolveSourceWithUi(source, opts);
|
|
2132
|
+
try {
|
|
2133
|
+
const artifacts = listKind(root, kind);
|
|
2134
|
+
if (!artifacts.length) {
|
|
2135
|
+
warn(`No ${kind}s in ${source}.`);
|
|
2136
|
+
return [];
|
|
2137
|
+
}
|
|
2138
|
+
if (isQuiet() || artifacts.length === 1) return artifacts.map((a) => ({
|
|
2139
|
+
id: a.id,
|
|
2140
|
+
source
|
|
2141
|
+
}));
|
|
2142
|
+
const picked = await selectArtifacts(message, artifacts);
|
|
2143
|
+
if (isCancelLike(picked) || !picked) return [];
|
|
2144
|
+
return picked.map((a) => ({
|
|
2145
|
+
id: a.id,
|
|
2146
|
+
source
|
|
2147
|
+
}));
|
|
2148
|
+
} finally {
|
|
2149
|
+
cleanup();
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
async function cmdInit(kind, nameArg, categoryArg, opts) {
|
|
2153
|
+
intro();
|
|
2154
|
+
const baseDir = opts.dir || process.cwd();
|
|
2155
|
+
let name = nameArg;
|
|
2156
|
+
let category = categoryArg || null;
|
|
2157
|
+
if (!name && !isQuiet()) name = await promptText(`Name for the new ${kind}`) || void 0;
|
|
2158
|
+
if (!name) throw new Error(`Missing name. e.g. agentry init ${kind} my-${kind}`);
|
|
2159
|
+
let description = opts.description;
|
|
2160
|
+
if (!description && !opts.yes && !isQuiet()) description = await promptText("Description (optional)", `${name} ${kind}`) || `${name} ${kind}`;
|
|
2161
|
+
description = description || `${name} ${kind}`;
|
|
2162
|
+
if (kind === "skill") {
|
|
2163
|
+
const reference = opts.reference !== false;
|
|
2164
|
+
if (opts.dryRun) {
|
|
2165
|
+
info(`Would create skills/${category ? category + "/" : ""}${name}/SKILL.md${reference ? " + references/TEMPLATE.md" : ""}`);
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
const r = scaffoldSkill({
|
|
2169
|
+
name,
|
|
2170
|
+
category,
|
|
2171
|
+
description,
|
|
2172
|
+
reference,
|
|
2173
|
+
baseDir
|
|
2174
|
+
});
|
|
2175
|
+
for (const p of r.paths) step(p);
|
|
2176
|
+
ok(`Initialized skill ${r.id}`);
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
if (kind === "agent" || kind === "rule") {
|
|
2180
|
+
if (opts.dryRun) {
|
|
2181
|
+
info(`Would create ${kind}s/${category ? category + "/" : ""}${name}.mdc`);
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
const r = scaffoldMdc({
|
|
2185
|
+
kind,
|
|
2186
|
+
name,
|
|
2187
|
+
category,
|
|
2188
|
+
description,
|
|
2189
|
+
alwaysApply: opts.alwaysApply,
|
|
2190
|
+
baseDir
|
|
2191
|
+
});
|
|
2192
|
+
for (const p of r.paths) step(p);
|
|
2193
|
+
ok(`Initialized ${kind} ${r.id}`);
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
if (kind === "script") {
|
|
2197
|
+
if (opts.dryRun) {
|
|
2198
|
+
info(`Would create scripts/${category ? category + "/" : ""}${name}/`);
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2201
|
+
const r = scaffoldScript({
|
|
2202
|
+
name,
|
|
2203
|
+
category,
|
|
2204
|
+
description,
|
|
2205
|
+
baseDir
|
|
2206
|
+
});
|
|
2207
|
+
for (const p of r.paths) step(p);
|
|
2208
|
+
ok(`Initialized script ${r.id}`);
|
|
2209
|
+
return;
|
|
2210
|
+
}
|
|
2211
|
+
if (kind === "profile") {
|
|
2212
|
+
await initProfile(name, description, opts, baseDir);
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
throw new Error(`Cannot init kind: ${kind}`);
|
|
2216
|
+
}
|
|
2217
|
+
async function initProfile(name, description, opts, baseDir) {
|
|
2218
|
+
const initKinds = opts.initKinds || {};
|
|
2219
|
+
const wantAll = opts.all;
|
|
2220
|
+
let wantSkills = wantAll || initKinds.skills !== void 0;
|
|
2221
|
+
let wantAgents = wantAll || initKinds.agents !== void 0;
|
|
2222
|
+
let wantRules = wantAll || initKinds.rules !== void 0;
|
|
2223
|
+
let wantScripts = wantAll || initKinds.scripts !== void 0;
|
|
2224
|
+
if (!wantSkills && !wantAgents && !wantRules && !wantScripts && !opts.yes && !isQuiet()) {
|
|
2225
|
+
const res = await clack.multiselect({
|
|
2226
|
+
message: "What should this profile include?",
|
|
2227
|
+
options: [
|
|
2228
|
+
{
|
|
2229
|
+
value: "skills",
|
|
2230
|
+
label: "Skills"
|
|
2231
|
+
},
|
|
2232
|
+
{
|
|
2233
|
+
value: "agents",
|
|
2234
|
+
label: "Agents"
|
|
2235
|
+
},
|
|
2236
|
+
{
|
|
2237
|
+
value: "rules",
|
|
2238
|
+
label: "Rules"
|
|
2239
|
+
},
|
|
2240
|
+
{
|
|
2241
|
+
value: "scripts",
|
|
2242
|
+
label: "Scripts"
|
|
2243
|
+
}
|
|
2244
|
+
],
|
|
2245
|
+
required: false
|
|
2246
|
+
});
|
|
2247
|
+
if (clack.isCancel(res)) {
|
|
2248
|
+
outro("Cancelled.");
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2251
|
+
const set = new Set(res);
|
|
2252
|
+
wantSkills = set.has("skills");
|
|
2253
|
+
wantAgents = set.has("agents");
|
|
2254
|
+
wantRules = set.has("rules");
|
|
2255
|
+
wantScripts = set.has("scripts");
|
|
2256
|
+
}
|
|
2257
|
+
const agentList = opts.agents.length ? (await resolveAgentList(opts, "install")).map((a) => a.name) : ["cursor"];
|
|
2258
|
+
const skills = [];
|
|
2259
|
+
const rules = [];
|
|
2260
|
+
const agentsArtifacts = [];
|
|
2261
|
+
const scripts = [];
|
|
2262
|
+
async function fill(flag, enabled, k, into) {
|
|
2263
|
+
if (!enabled) return;
|
|
2264
|
+
const src = typeof flag === "string" ? flag : void 0;
|
|
2265
|
+
if (src) {
|
|
2266
|
+
const picked = await pickFromSource(k, src, `Select ${k}s for profile`, opts);
|
|
2267
|
+
into.push(...picked);
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
if (isQuiet() || opts.yes) return;
|
|
2271
|
+
const ask = await promptText(`Source for ${k}s (author/repo, URL, or local path — empty to skip)`);
|
|
2272
|
+
if (!ask) return;
|
|
2273
|
+
const picked = await pickFromSource(k, ask, `Select ${k}s for profile`, opts);
|
|
2274
|
+
into.push(...picked);
|
|
2275
|
+
}
|
|
2276
|
+
await fill(initKinds.skills, wantSkills, "skill", skills);
|
|
2277
|
+
await fill(initKinds.agents, wantAgents, "agent", agentsArtifacts);
|
|
2278
|
+
await fill(initKinds.rules, wantRules, "rule", rules);
|
|
2279
|
+
await fill(initKinds.scripts, wantScripts, "script", scripts);
|
|
2280
|
+
if (opts.dryRun) {
|
|
2281
|
+
info(`Would write profiles/${name}.yaml with ${skills.length} skills, ${agentsArtifacts.length} agents, ${rules.length} rules, ${scripts.length} scripts`);
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
const r = scaffoldProfile({
|
|
2285
|
+
name,
|
|
2286
|
+
description,
|
|
2287
|
+
agents: agentList,
|
|
2288
|
+
skills,
|
|
2289
|
+
rules,
|
|
2290
|
+
agentsArtifacts,
|
|
2291
|
+
scripts,
|
|
2292
|
+
baseDir
|
|
2293
|
+
});
|
|
2294
|
+
for (const p of r.paths) step(p);
|
|
2295
|
+
ok(`Initialized profile ${r.id}`);
|
|
2296
|
+
}
|
|
2297
|
+
function takeOptValue(argv, i) {
|
|
2298
|
+
const next = argv[i + 1];
|
|
2299
|
+
if (next && !next.startsWith("-")) return {
|
|
2300
|
+
value: next,
|
|
2301
|
+
next: i + 1
|
|
2302
|
+
};
|
|
2303
|
+
return {
|
|
2304
|
+
value: true,
|
|
2305
|
+
next: i
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
1520
2308
|
function parseArgs(argv) {
|
|
1521
2309
|
const opts = {
|
|
1522
2310
|
scope: "project",
|
|
@@ -1524,7 +2312,8 @@ function parseArgs(argv) {
|
|
|
1524
2312
|
copy: false,
|
|
1525
2313
|
dryRun: false,
|
|
1526
2314
|
yes: false,
|
|
1527
|
-
all: false
|
|
2315
|
+
all: false,
|
|
2316
|
+
initKinds: {}
|
|
1528
2317
|
};
|
|
1529
2318
|
const positional = [];
|
|
1530
2319
|
const flags = {};
|
|
@@ -1554,6 +2343,38 @@ function parseArgs(argv) {
|
|
|
1554
2343
|
} else if (a === "--all") {
|
|
1555
2344
|
opts.all = true;
|
|
1556
2345
|
flags.all = true;
|
|
2346
|
+
} else if (a === "--description") {
|
|
2347
|
+
opts.description = argv[++i];
|
|
2348
|
+
flags.description = opts.description;
|
|
2349
|
+
} else if (a === "--alwaysApply" || a === "--always-apply") {
|
|
2350
|
+
opts.alwaysApply = true;
|
|
2351
|
+
flags.alwaysApply = true;
|
|
2352
|
+
} else if (a === "--reference") {
|
|
2353
|
+
opts.reference = true;
|
|
2354
|
+
flags.reference = true;
|
|
2355
|
+
} else if (a === "--no-reference") {
|
|
2356
|
+
opts.reference = false;
|
|
2357
|
+
flags.reference = false;
|
|
2358
|
+
} else if (a === "--skills") {
|
|
2359
|
+
const { value, next } = takeOptValue(argv, i);
|
|
2360
|
+
opts.initKinds.skills = value;
|
|
2361
|
+
i = next;
|
|
2362
|
+
flags.skills = true;
|
|
2363
|
+
} else if (a === "--agents") {
|
|
2364
|
+
const { value, next } = takeOptValue(argv, i);
|
|
2365
|
+
opts.initKinds.agents = value;
|
|
2366
|
+
i = next;
|
|
2367
|
+
flags.agents = true;
|
|
2368
|
+
} else if (a === "--rules") {
|
|
2369
|
+
const { value, next } = takeOptValue(argv, i);
|
|
2370
|
+
opts.initKinds.rules = value;
|
|
2371
|
+
i = next;
|
|
2372
|
+
flags.rules = true;
|
|
2373
|
+
} else if (a === "--scripts") {
|
|
2374
|
+
const { value, next } = takeOptValue(argv, i);
|
|
2375
|
+
opts.initKinds.scripts = value;
|
|
2376
|
+
i = next;
|
|
2377
|
+
flags.scripts = true;
|
|
1557
2378
|
} else if (a === "-v" || a === "--version") flags.version = true;
|
|
1558
2379
|
else if (a === "-h" || a === "--help") flags.help = true;
|
|
1559
2380
|
else positional.push(a);
|
|
@@ -1650,12 +2471,21 @@ async function main() {
|
|
|
1650
2471
|
return;
|
|
1651
2472
|
}
|
|
1652
2473
|
if (action === "list") {
|
|
1653
|
-
|
|
1654
|
-
|
|
2474
|
+
const a1 = positional[1];
|
|
2475
|
+
const a2 = positional[2];
|
|
2476
|
+
const a3 = positional[3];
|
|
2477
|
+
if (!a1) {
|
|
2478
|
+
await cmdList(void 0, void 0, void 0, opts);
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
const k1 = normalizeKind(a1);
|
|
2482
|
+
if (isKind(k1) && !isExplicitSource(a1) && !/^[\w.-]+\/[\w.-]+$/.test(a1)) {
|
|
2483
|
+
await cmdList(void 0, k1, a2, opts);
|
|
1655
2484
|
return;
|
|
1656
2485
|
}
|
|
1657
|
-
const
|
|
1658
|
-
|
|
2486
|
+
const kind = a2 ? normalizeKind(a2) : void 0;
|
|
2487
|
+
if (kind && kind !== "all" && !isKind(kind)) throw new Error(`Unknown kind: ${kind}`);
|
|
2488
|
+
await cmdList(a1, kind && isKind(kind) ? kind : void 0, a3, opts);
|
|
1659
2489
|
return;
|
|
1660
2490
|
}
|
|
1661
2491
|
if (action === "add") {
|
|
@@ -1673,7 +2503,13 @@ async function main() {
|
|
|
1673
2503
|
const kind = positional[1] ? normalizeKind(positional[1]) : void 0;
|
|
1674
2504
|
if (!kind) throw new Error("Missing kind. e.g. agentry remove skills enhance-prompt");
|
|
1675
2505
|
if (kind !== "all" && !isKind(kind)) throw new Error(`Unknown kind: ${kind}`);
|
|
1676
|
-
await cmdRemove(kind, positional[2], opts);
|
|
2506
|
+
await cmdRemove(kind, positional[2], positional[3], opts);
|
|
2507
|
+
return;
|
|
2508
|
+
}
|
|
2509
|
+
if (action === "init") {
|
|
2510
|
+
const kind = positional[1] ? normalizeKind(positional[1]) : void 0;
|
|
2511
|
+
if (!kind || !isKind(kind)) throw new Error("Missing kind. e.g. agentry init skill my-skill [category]");
|
|
2512
|
+
await cmdInit(kind, positional[2], positional[3], opts);
|
|
1677
2513
|
return;
|
|
1678
2514
|
}
|
|
1679
2515
|
err(`Unknown action: ${action}`);
|