@mhmdreza-rafiei/agentry 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +249 -259
- package/dist/_chunks/theme2.mjs +45 -19
- package/dist/cli.mjs +1217 -187
- package/package.json +61 -53
- package/dist/_chunks/source_parser.mjs +0 -2
- package/dist/_chunks/source_parser2.mjs +0 -74
package/dist/cli.mjs
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { execFileSync, execSync } from "node:child_process";
|
|
2
|
+
import { animateLogo, badge, delay, printHelp, sky, theme } from "./_chunks/theme2.mjs";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
5
4
|
import https from "node:https";
|
|
6
|
-
import {
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
|
7
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";
|
|
19
|
-
var version = "0.2.
|
|
19
|
+
var version = "0.2.2";
|
|
20
20
|
const ARTIFACT_KINDS = [
|
|
21
21
|
"skill",
|
|
22
22
|
"rule",
|
|
@@ -24,7 +24,184 @@ 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
|
+
const SAFE_REF$1 = /^[a-zA-Z0-9][\w./-]*$/;
|
|
38
|
+
function parseGithubTreeUrl(url) {
|
|
39
|
+
const m = url.match(/^https?:\/\/github\.com\/([\w.-]+\/[\w.-]+?)\/tree\/([^/]+)\/(.+)$/);
|
|
40
|
+
if (!m) return null;
|
|
41
|
+
const ref = m[2];
|
|
42
|
+
if (!SAFE_REF$1.test(ref)) throw new Error(`Invalid git ref in source URL: "${ref}"`);
|
|
43
|
+
return {
|
|
44
|
+
ownerRepo: m[1],
|
|
45
|
+
ref,
|
|
46
|
+
subpath: m[3].replace(/\/+$/, "")
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function redactUrl(url) {
|
|
50
|
+
return url.replace(/:\/\/([^/@]+)@/g, "://***@");
|
|
51
|
+
}
|
|
52
|
+
function parseSource(source) {
|
|
53
|
+
if (!source || typeof source !== "string") throw new Error("Empty source.");
|
|
54
|
+
const raw = source;
|
|
55
|
+
if (isLocalPath(source)) return {
|
|
56
|
+
kind: "local",
|
|
57
|
+
raw,
|
|
58
|
+
url: null,
|
|
59
|
+
subpath: "",
|
|
60
|
+
local: resolve(source.replace(/^~/, homedir()))
|
|
61
|
+
};
|
|
62
|
+
const tree = parseGithubTreeUrl(source);
|
|
63
|
+
if (tree) return {
|
|
64
|
+
kind: "github-url",
|
|
65
|
+
raw,
|
|
66
|
+
url: `https://github.com/${tree.ownerRepo}.git`,
|
|
67
|
+
subpath: tree.subpath,
|
|
68
|
+
ref: tree.ref,
|
|
69
|
+
local: null
|
|
70
|
+
};
|
|
71
|
+
let m = source.match(/^https?:\/\/github\.com\/([\w.-]+\/[\w.-]+?)(?:\.git)?\/?$/);
|
|
72
|
+
if (m) return {
|
|
73
|
+
kind: "github-url",
|
|
74
|
+
raw,
|
|
75
|
+
url: `https://github.com/${m[1]}.git`,
|
|
76
|
+
subpath: "",
|
|
77
|
+
local: null
|
|
78
|
+
};
|
|
79
|
+
m = source.match(/^https?:\/\/gitlab\.com\/([\w./-]+?)(?:\.git)?\/?$/);
|
|
80
|
+
if (m) return {
|
|
81
|
+
kind: "gitlab-url",
|
|
82
|
+
raw,
|
|
83
|
+
url: `https://gitlab.com/${m[1]}.git`,
|
|
84
|
+
subpath: "",
|
|
85
|
+
local: null
|
|
86
|
+
};
|
|
87
|
+
if (source.startsWith("git@") || /^https?:\/\/.*\.git$/.test(source)) return {
|
|
88
|
+
kind: "git-url",
|
|
89
|
+
raw,
|
|
90
|
+
url: source,
|
|
91
|
+
subpath: "",
|
|
92
|
+
local: null
|
|
93
|
+
};
|
|
94
|
+
if (/^[\w.-]+\/[\w.-]+$/.test(source)) return {
|
|
95
|
+
kind: "github-shorthand",
|
|
96
|
+
raw,
|
|
97
|
+
url: `https://github.com/${source}.git`,
|
|
98
|
+
subpath: "",
|
|
99
|
+
local: null
|
|
100
|
+
};
|
|
101
|
+
throw new Error(`Cannot parse source "${source}". Use author/repo, a GitHub/GitLab URL, a git URL, or a local path.`);
|
|
102
|
+
}
|
|
103
|
+
function sourceIdentity(source) {
|
|
104
|
+
try {
|
|
105
|
+
const p = parseSource(source);
|
|
106
|
+
if (p.kind === "local" && p.local) return `local:${resolve(p.local).replace(/\\/g, "/").toLowerCase()}`;
|
|
107
|
+
return `git:${(p.url || "").replace(/\.git$/i, "").replace(/\/+$/, "").toLowerCase()}${p.subpath ? `/${p.subpath.replace(/^\/+|\/+$/g, "")}` : ""}${p.ref ? `@${p.ref}` : ""}`;
|
|
108
|
+
} catch {
|
|
109
|
+
return `raw:${source.trim().toLowerCase()}`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function sourcesEqual(a, b) {
|
|
113
|
+
if (!a || !b) return false;
|
|
114
|
+
if (a === b) return true;
|
|
115
|
+
return sourceIdentity(a) === sourceIdentity(b);
|
|
116
|
+
}
|
|
117
|
+
function isExplicitSource(arg) {
|
|
118
|
+
if (!arg) return false;
|
|
119
|
+
if (isLocalPath(arg)) return true;
|
|
120
|
+
if (/^https?:\/\//i.test(arg)) return true;
|
|
121
|
+
if (arg.startsWith("git@")) return true;
|
|
122
|
+
if (/^https?:\/\/.*\.git$/i.test(arg)) return true;
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
function isOwnerRepoShape(arg) {
|
|
126
|
+
return /^[\w.-]+\/[\w.-]+$/.test(arg);
|
|
127
|
+
}
|
|
128
|
+
function cacheRoot() {
|
|
129
|
+
return join(xdgCache ?? join(homedir(), ".cache"), "agentry", "repos");
|
|
130
|
+
}
|
|
131
|
+
function cacheKey(parsed) {
|
|
132
|
+
const id = sourceIdentity(parsed.raw);
|
|
133
|
+
return createHash("sha256").update(id).digest("hex").slice(0, 20);
|
|
134
|
+
}
|
|
135
|
+
function cacheDirFor(parsed) {
|
|
136
|
+
return join(cacheRoot(), cacheKey(parsed));
|
|
137
|
+
}
|
|
138
|
+
function saveToCache(parsed, clonedRoot) {
|
|
139
|
+
const dest = cacheDirFor(parsed);
|
|
140
|
+
try {
|
|
141
|
+
rmSync(dest, {
|
|
142
|
+
recursive: true,
|
|
143
|
+
force: true
|
|
144
|
+
});
|
|
145
|
+
mkdirSync(join(dest, ".."), { recursive: true });
|
|
146
|
+
cpSync(clonedRoot, dest, { recursive: true });
|
|
147
|
+
} catch {}
|
|
148
|
+
}
|
|
149
|
+
const SAFE_REF = /^[a-zA-Z0-9][\w./-]*$/;
|
|
150
|
+
function cloneTo(url, dest, ref) {
|
|
151
|
+
if (ref && !SAFE_REF.test(ref)) throw new Error(`Invalid git ref: "${ref}"`);
|
|
152
|
+
execFileSync("git", [
|
|
153
|
+
"clone",
|
|
154
|
+
"--quiet",
|
|
155
|
+
...ref ? [
|
|
156
|
+
"--branch",
|
|
157
|
+
ref,
|
|
158
|
+
"--depth",
|
|
159
|
+
"1"
|
|
160
|
+
] : ["--depth", "1"],
|
|
161
|
+
url,
|
|
162
|
+
dest
|
|
163
|
+
], { stdio: [
|
|
164
|
+
"ignore",
|
|
165
|
+
"ignore",
|
|
166
|
+
"pipe"
|
|
167
|
+
] });
|
|
168
|
+
}
|
|
169
|
+
function assertInsideRoot(root, candidate) {
|
|
170
|
+
const base = resolve(root);
|
|
171
|
+
const resolved = resolve(candidate);
|
|
172
|
+
const prefix = base.endsWith(sep) ? base : base + sep;
|
|
173
|
+
const a = resolved.toLowerCase();
|
|
174
|
+
const b = base.toLowerCase();
|
|
175
|
+
const p = prefix.toLowerCase();
|
|
176
|
+
if (a !== b && !a.startsWith(p)) throw new Error(`Path escapes source root: "${candidate}"`);
|
|
177
|
+
return resolved;
|
|
178
|
+
}
|
|
179
|
+
function withSubpath(root, subpath, cleanupRoot) {
|
|
180
|
+
const cleanup = () => rmSync(cleanupRoot, {
|
|
181
|
+
recursive: true,
|
|
182
|
+
force: true
|
|
183
|
+
});
|
|
184
|
+
if (!subpath) return {
|
|
185
|
+
root,
|
|
186
|
+
cleanup
|
|
187
|
+
};
|
|
188
|
+
let sub;
|
|
189
|
+
try {
|
|
190
|
+
sub = assertInsideRoot(root, join(root, subpath));
|
|
191
|
+
} catch (e) {
|
|
192
|
+
cleanup();
|
|
193
|
+
throw e;
|
|
194
|
+
}
|
|
195
|
+
if (!existsSync(sub)) {
|
|
196
|
+
cleanup();
|
|
197
|
+
throw new Error(`Subpath "${subpath}" not found.`);
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
root: sub,
|
|
201
|
+
cleanup
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
async function resolveSource(source, opts = {}) {
|
|
28
205
|
const parsed = typeof source === "string" ? parseSource(source) : source;
|
|
29
206
|
if (parsed.kind === "local" && parsed.local) {
|
|
30
207
|
if (!existsSync(parsed.local)) throw new Error(`Path not found: ${parsed.raw}`);
|
|
@@ -35,56 +212,36 @@ function resolveSource(source) {
|
|
|
35
212
|
}
|
|
36
213
|
const url = parsed.url;
|
|
37
214
|
if (!url) throw new Error(`Cannot resolve source "${parsed.raw}".`);
|
|
215
|
+
const safeUrl = redactUrl(url);
|
|
38
216
|
const tmp = mkdtempSync(join(tmpdir(), "agentry-src-"));
|
|
39
217
|
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
|
-
] });
|
|
218
|
+
cloneTo(url, tmp, parsed.ref);
|
|
56
219
|
} catch (e) {
|
|
57
220
|
rmSync(tmp, {
|
|
58
221
|
recursive: true,
|
|
59
222
|
force: true
|
|
60
223
|
});
|
|
61
224
|
const detail = e.stderr ? e.stderr.toString().trim() : e.message;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
225
|
+
const cached = cacheDirFor(parsed);
|
|
226
|
+
const cacheOk = existsSync(cached) && existsSync(join(cached, ".git"));
|
|
227
|
+
if (cacheOk && opts.confirmCache) {
|
|
228
|
+
if (await opts.confirmCache({
|
|
229
|
+
url: safeUrl,
|
|
230
|
+
cacheDir: cached,
|
|
231
|
+
error: detail
|
|
232
|
+
})) {
|
|
233
|
+
const work = mkdtempSync(join(tmpdir(), "agentry-src-"));
|
|
234
|
+
cpSync(cached, work, { recursive: true });
|
|
235
|
+
return {
|
|
236
|
+
...withSubpath(work, parsed.subpath, work),
|
|
237
|
+
fromCache: true
|
|
238
|
+
};
|
|
239
|
+
}
|
|
72
240
|
}
|
|
73
|
-
|
|
74
|
-
root: sub,
|
|
75
|
-
cleanup: () => rmSync(tmp, {
|
|
76
|
-
recursive: true,
|
|
77
|
-
force: true
|
|
78
|
-
})
|
|
79
|
-
};
|
|
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.)" : ""}`);
|
|
80
242
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
cleanup: () => rmSync(tmp, {
|
|
84
|
-
recursive: true,
|
|
85
|
-
force: true
|
|
86
|
-
})
|
|
87
|
-
};
|
|
243
|
+
saveToCache(parsed, tmp);
|
|
244
|
+
return withSubpath(tmp, parsed.subpath, tmp);
|
|
88
245
|
}
|
|
89
246
|
const SKILL_MARKER = "SKILL.md";
|
|
90
247
|
const FILE_EXT = ".mdc";
|
|
@@ -426,6 +583,38 @@ function getNonUniversalAgents() {
|
|
|
426
583
|
return listAgents().filter((a) => a.skillsDir !== ".agents/skills");
|
|
427
584
|
}
|
|
428
585
|
const home = homedir();
|
|
586
|
+
function copyTreeDereferenced(src, dest, seen = /* @__PURE__ */ new Set()) {
|
|
587
|
+
const st = lstatSync(src);
|
|
588
|
+
if (st.isSymbolicLink()) {
|
|
589
|
+
let real;
|
|
590
|
+
try {
|
|
591
|
+
real = realpathSync(src);
|
|
592
|
+
} catch {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (seen.has(real)) return;
|
|
596
|
+
seen.add(real);
|
|
597
|
+
copyTreeDereferenced(real, dest, seen);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
if (st.isDirectory()) {
|
|
601
|
+
let real;
|
|
602
|
+
try {
|
|
603
|
+
real = realpathSync(src);
|
|
604
|
+
} catch {
|
|
605
|
+
real = resolve(src);
|
|
606
|
+
}
|
|
607
|
+
if (seen.has(real)) return;
|
|
608
|
+
seen.add(real);
|
|
609
|
+
mkdirSync(dest, { recursive: true });
|
|
610
|
+
for (const name of readdirSync(src)) copyTreeDereferenced(join(src, name), join(dest, name), seen);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (st.isFile()) {
|
|
614
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
615
|
+
copyFileSync(src, dest);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
429
618
|
function lockBase(opts) {
|
|
430
619
|
return opts.scope === "global" ? home : opts.dir || process.cwd();
|
|
431
620
|
}
|
|
@@ -458,10 +647,74 @@ function removeLegacyLockIfPresent(opts) {
|
|
|
458
647
|
}
|
|
459
648
|
function writeLock(opts, lock) {
|
|
460
649
|
const file = lockPath(opts);
|
|
650
|
+
if (!Object.keys(lock.items).length) {
|
|
651
|
+
const dir = join(lockBase(opts), ".agentry");
|
|
652
|
+
if (existsSync(dir)) rmSync(dir, {
|
|
653
|
+
recursive: true,
|
|
654
|
+
force: true
|
|
655
|
+
});
|
|
656
|
+
removeLegacyLockIfPresent(opts);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
461
659
|
mkdirSync(join(file, ".."), { recursive: true });
|
|
462
660
|
writeFileSync(file, JSON.stringify(lock, null, 2) + "\n");
|
|
463
661
|
removeLegacyLockIfPresent(opts);
|
|
464
662
|
}
|
|
663
|
+
function isDirTreeEmpty(dir) {
|
|
664
|
+
if (!existsSync(dir)) return true;
|
|
665
|
+
let entries;
|
|
666
|
+
try {
|
|
667
|
+
entries = readdirSync(dir);
|
|
668
|
+
} catch {
|
|
669
|
+
return true;
|
|
670
|
+
}
|
|
671
|
+
for (const name of entries) {
|
|
672
|
+
const p = join(dir, name);
|
|
673
|
+
let st;
|
|
674
|
+
try {
|
|
675
|
+
st = statSync(p);
|
|
676
|
+
} catch {
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
if (st.isDirectory()) {
|
|
680
|
+
if (!isDirTreeEmpty(p)) return false;
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
if (st.isFile()) {
|
|
684
|
+
if (name === "lock.json") {
|
|
685
|
+
try {
|
|
686
|
+
const data = JSON.parse(readFileSync(p, "utf8"));
|
|
687
|
+
if (data && typeof data === "object" && Object.keys(data.items || {}).length === 0) continue;
|
|
688
|
+
} catch {
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return true;
|
|
697
|
+
}
|
|
698
|
+
function pruneEmptyAncestors(dir, stopAt, removedPaths = []) {
|
|
699
|
+
const stop = resolve(stopAt);
|
|
700
|
+
let cur = resolve(dir);
|
|
701
|
+
while (cur.length > stop.length) {
|
|
702
|
+
const prefix = stop.endsWith(sep) ? stop : stop + sep;
|
|
703
|
+
if (!cur.toLowerCase().startsWith(prefix.toLowerCase()) && cur.toLowerCase() !== stop.toLowerCase()) break;
|
|
704
|
+
if (!existsSync(cur)) {
|
|
705
|
+
cur = dirname(cur);
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
if (!isDirTreeEmpty(cur)) break;
|
|
709
|
+
rmSync(cur, {
|
|
710
|
+
recursive: true,
|
|
711
|
+
force: true
|
|
712
|
+
});
|
|
713
|
+
removedPaths.push(cur);
|
|
714
|
+
cur = dirname(cur);
|
|
715
|
+
}
|
|
716
|
+
return removedPaths;
|
|
717
|
+
}
|
|
465
718
|
function listInstalled(opts) {
|
|
466
719
|
return Object.entries(readLock(opts).items).map(([id, v]) => ({
|
|
467
720
|
id,
|
|
@@ -489,12 +742,7 @@ function installOne(artifact, opts, agentList, source) {
|
|
|
489
742
|
force: true
|
|
490
743
|
});
|
|
491
744
|
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
|
-
}
|
|
745
|
+
copyTreeDereferenced(artifact.dir, canonical);
|
|
498
746
|
for (const a of list) if (a.skillsDir === ".agents/skills") dests.push({
|
|
499
747
|
agent: a.name,
|
|
500
748
|
dir: canonical
|
|
@@ -507,7 +755,7 @@ function installOne(artifact, opts, agentList, source) {
|
|
|
507
755
|
});
|
|
508
756
|
mkdirSync(join(dir, ".."), { recursive: true });
|
|
509
757
|
if (opts.copy) {
|
|
510
|
-
|
|
758
|
+
copyTreeDereferenced(canonical, dir);
|
|
511
759
|
dests.push({
|
|
512
760
|
agent: a.name,
|
|
513
761
|
dir
|
|
@@ -520,7 +768,7 @@ function installOne(artifact, opts, agentList, source) {
|
|
|
520
768
|
symlinked: true
|
|
521
769
|
});
|
|
522
770
|
} catch {
|
|
523
|
-
|
|
771
|
+
copyTreeDereferenced(canonical, dir);
|
|
524
772
|
dests.push({
|
|
525
773
|
agent: a.name,
|
|
526
774
|
dir
|
|
@@ -534,12 +782,7 @@ function installOne(artifact, opts, agentList, source) {
|
|
|
534
782
|
force: true
|
|
535
783
|
});
|
|
536
784
|
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
|
-
}
|
|
785
|
+
copyTreeDereferenced(artifact.dir, dir);
|
|
543
786
|
dests.push({
|
|
544
787
|
agent: a.name,
|
|
545
788
|
dir
|
|
@@ -563,37 +806,115 @@ function agentDir(artifact, a, base) {
|
|
|
563
806
|
}
|
|
564
807
|
return join(base, a.configDir || a.skillsDir, pluralKind(artifact.kind), artifact.id);
|
|
565
808
|
}
|
|
566
|
-
function removeSelection(kind, selector, opts, agentList) {
|
|
809
|
+
function removeSelection(kind, selector, opts, agentList, sourceFilter) {
|
|
567
810
|
const base = lockBase(opts);
|
|
568
811
|
const list = agentList.length ? agentList : [getAgent("cursor")];
|
|
569
812
|
const rel = [kind, selector].filter(Boolean).join("/");
|
|
570
813
|
const removedKeys = [];
|
|
814
|
+
const removedPaths = [];
|
|
571
815
|
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, {
|
|
816
|
+
const matchingKeys = Object.keys(lock.items).filter((key) => {
|
|
817
|
+
const entry = lock.items[key];
|
|
818
|
+
if (sourceFilter && !sourcesEqual(entry.source, sourceFilter)) return false;
|
|
819
|
+
if (kind === "all") return true;
|
|
820
|
+
if (key === rel || key.startsWith(rel + "/")) return true;
|
|
821
|
+
if (!selector && key.startsWith(kind + "/")) return true;
|
|
822
|
+
return false;
|
|
823
|
+
});
|
|
824
|
+
const rmPath = (p) => {
|
|
825
|
+
if (!p || !existsSync(p)) return;
|
|
826
|
+
const parent = dirname(p);
|
|
827
|
+
rmSync(p, {
|
|
585
828
|
recursive: true,
|
|
586
829
|
force: true
|
|
587
830
|
});
|
|
588
|
-
|
|
589
|
-
|
|
831
|
+
removedPaths.push(p);
|
|
832
|
+
pruneEmptyAncestors(parent, base, removedPaths);
|
|
833
|
+
};
|
|
834
|
+
const agentsFor = (entry) => {
|
|
835
|
+
const fromEntry = entry.agents.map((n) => getAgent(n)).filter(Boolean);
|
|
836
|
+
const byName = /* @__PURE__ */ new Map();
|
|
837
|
+
for (const a of [...fromEntry, ...list]) byName.set(a.name, a);
|
|
838
|
+
return [...byName.values()];
|
|
839
|
+
};
|
|
840
|
+
const removeFilesForKey = (key, entry) => {
|
|
841
|
+
const parts = key.split("/");
|
|
842
|
+
const k = parts[0];
|
|
843
|
+
const sel = parts.slice(1).join("/") || void 0;
|
|
844
|
+
if (!sel && k === "skill") return;
|
|
845
|
+
const agents = agentsFor(entry);
|
|
846
|
+
if (k === "skill" && sel) {
|
|
847
|
+
rmPath(join(base, ".agents", "skills", sel));
|
|
848
|
+
for (const a of agents) {
|
|
849
|
+
if (a.skillsDir === ".agents/skills") continue;
|
|
850
|
+
rmPath(join(base, a.skillsDir, sel));
|
|
851
|
+
}
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
for (const a of agents) if (k === "agent" || k === "rule" || k === "profile") {
|
|
855
|
+
const ext = k === "profile" ? ".yaml" : ".mdc";
|
|
856
|
+
if (!sel) rmPath(join(base, a.configDir || a.skillsDir, pluralKind(k)));
|
|
857
|
+
else if (sel.includes("/")) {
|
|
858
|
+
const [cat, name] = sel.split("/");
|
|
859
|
+
rmPath(join(base, a.configDir || a.skillsDir, pluralKind(k), cat, name + ext));
|
|
860
|
+
} else rmPath(join(base, a.configDir || a.skillsDir, pluralKind(k), sel + ext));
|
|
861
|
+
} else if (sel) rmPath(join(base, a.configDir || a.skillsDir, pluralKind(k), sel));
|
|
862
|
+
};
|
|
863
|
+
for (const key of matchingKeys) {
|
|
864
|
+
const entry = lock.items[key];
|
|
865
|
+
removeFilesForKey(key, entry);
|
|
590
866
|
delete lock.items[key];
|
|
591
867
|
removedKeys.push(key);
|
|
592
868
|
}
|
|
869
|
+
if (!removedKeys.length && !sourceFilter) {
|
|
870
|
+
if (kind === "skill") rmPath(join(base, ".agents", "skills", selector || ""));
|
|
871
|
+
for (const a of list) {
|
|
872
|
+
let dir;
|
|
873
|
+
if (kind === "skill") {
|
|
874
|
+
if (a.skillsDir === ".agents/skills") continue;
|
|
875
|
+
dir = join(base, a.skillsDir, selector || "");
|
|
876
|
+
} else if (kind === "agent" || kind === "rule" || kind === "profile") {
|
|
877
|
+
const ext = kind === "profile" ? ".yaml" : ".mdc";
|
|
878
|
+
if (!selector) dir = join(base, a.configDir || a.skillsDir, pluralKind(kind));
|
|
879
|
+
else if (selector.includes("/")) {
|
|
880
|
+
const [cat, name] = selector.split("/");
|
|
881
|
+
dir = join(base, a.configDir || a.skillsDir, pluralKind(kind), cat, name + ext);
|
|
882
|
+
} else dir = join(base, a.configDir || a.skillsDir, pluralKind(kind), selector + ext);
|
|
883
|
+
} else dir = join(base, a.configDir || a.skillsDir, pluralKind(kind), selector || "");
|
|
884
|
+
if (existsSync(dir)) {
|
|
885
|
+
rmPath(dir);
|
|
886
|
+
if (!removedKeys.includes(rel)) removedKeys.push(rel);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
for (const key of Object.keys(lock.items)) if (key === rel || key.startsWith(rel + "/")) {
|
|
890
|
+
delete lock.items[key];
|
|
891
|
+
if (!removedKeys.includes(key)) removedKeys.push(key);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
593
894
|
writeLock(opts, lock);
|
|
895
|
+
const sweepRoots = /* @__PURE__ */ new Set([
|
|
896
|
+
join(base, ".agents", "skills"),
|
|
897
|
+
join(base, ".agents"),
|
|
898
|
+
join(base, ".agentry")
|
|
899
|
+
]);
|
|
900
|
+
for (const a of list) {
|
|
901
|
+
sweepRoots.add(join(base, a.skillsDir));
|
|
902
|
+
if (a.configDir) {
|
|
903
|
+
sweepRoots.add(join(base, a.configDir));
|
|
904
|
+
for (const pk of [
|
|
905
|
+
"skills",
|
|
906
|
+
"agents",
|
|
907
|
+
"rules",
|
|
908
|
+
"profiles",
|
|
909
|
+
"scripts"
|
|
910
|
+
]) sweepRoots.add(join(base, a.configDir, pk));
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
for (const root of sweepRoots) if (existsSync(root) && isDirTreeEmpty(root)) pruneEmptyAncestors(root, base, removedPaths);
|
|
594
914
|
return {
|
|
595
|
-
existed: removedKeys.length > 0,
|
|
596
|
-
removedKeys
|
|
915
|
+
existed: removedKeys.length > 0 || removedPaths.length > 0,
|
|
916
|
+
removedKeys,
|
|
917
|
+
removedPaths
|
|
597
918
|
};
|
|
598
919
|
}
|
|
599
920
|
const ProfileArtifactRefSchema = z.object({
|
|
@@ -636,6 +957,98 @@ function loadProfile(name, baseDir) {
|
|
|
636
957
|
profile: loadProfileFile(file)
|
|
637
958
|
};
|
|
638
959
|
}
|
|
960
|
+
function ensureDir(file) {
|
|
961
|
+
mkdirSync(join(file, ".."), { recursive: true });
|
|
962
|
+
}
|
|
963
|
+
function writeNew(file, content, force = false) {
|
|
964
|
+
if (existsSync(file) && !force) throw new Error(`Already exists: ${file}`);
|
|
965
|
+
ensureDir(file);
|
|
966
|
+
writeFileSync(file, content);
|
|
967
|
+
return file;
|
|
968
|
+
}
|
|
969
|
+
function scaffoldSkill(opts) {
|
|
970
|
+
const dir = join(opts.baseDir || process.cwd(), opts.category ? join("skills", opts.category, opts.name) : join("skills", opts.name));
|
|
971
|
+
const id = opts.category ? `${opts.category}/${opts.name}` : opts.name;
|
|
972
|
+
const desc = opts.description || `${opts.name} skill`;
|
|
973
|
+
const skillMd = `---
|
|
974
|
+
name: ${opts.name}
|
|
975
|
+
description: ${desc}
|
|
976
|
+
---
|
|
977
|
+
|
|
978
|
+
# ${opts.name}
|
|
979
|
+
|
|
980
|
+
${desc}
|
|
981
|
+
|
|
982
|
+
## When to use
|
|
983
|
+
|
|
984
|
+
Describe when this skill should run.
|
|
985
|
+
|
|
986
|
+
## Instructions
|
|
987
|
+
|
|
988
|
+
1. …
|
|
989
|
+
`;
|
|
990
|
+
const paths = [writeNew(join(dir, "SKILL.md"), skillMd)];
|
|
991
|
+
if (opts.reference !== false) paths.push(writeNew(join(dir, "references", "TEMPLATE.md"), `# ${opts.name} — reference
|
|
992
|
+
|
|
993
|
+
Fill in details here. Keep SKILL.md lean; put depth in references/.
|
|
994
|
+
`));
|
|
995
|
+
return {
|
|
996
|
+
paths,
|
|
997
|
+
id
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
function scaffoldMdc(opts) {
|
|
1001
|
+
const base = opts.baseDir || process.cwd();
|
|
1002
|
+
const folder = opts.kind === "agent" ? "agents" : "rules";
|
|
1003
|
+
const rel = opts.category ? join(folder, opts.category, `${opts.name}.mdc`) : join(folder, `${opts.name}.mdc`);
|
|
1004
|
+
const id = opts.category ? `${opts.category}/${opts.name}` : opts.name;
|
|
1005
|
+
const desc = opts.description || `${opts.name} ${opts.kind}`;
|
|
1006
|
+
const always = opts.kind === "rule" && opts.alwaysApply ? "alwaysApply: true\n" : opts.kind === "rule" ? "alwaysApply: false\n" : "";
|
|
1007
|
+
const body = `---
|
|
1008
|
+
name: ${opts.name}
|
|
1009
|
+
description: ${desc}
|
|
1010
|
+
${always}---
|
|
1011
|
+
|
|
1012
|
+
# ${opts.name}
|
|
1013
|
+
|
|
1014
|
+
${desc}
|
|
1015
|
+
`;
|
|
1016
|
+
return {
|
|
1017
|
+
paths: [writeNew(join(base, rel), body)],
|
|
1018
|
+
id
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
function scaffoldScript(opts) {
|
|
1022
|
+
const dir = join(opts.baseDir || process.cwd(), opts.category ? join("scripts", opts.category, opts.name) : join("scripts", opts.name));
|
|
1023
|
+
const id = opts.category ? `${opts.category}/${opts.name}` : opts.name;
|
|
1024
|
+
const desc = opts.description || `${opts.name} script`;
|
|
1025
|
+
return {
|
|
1026
|
+
paths: [writeNew(join(dir, "README.md"), `# ${opts.name}
|
|
1027
|
+
|
|
1028
|
+
${desc}
|
|
1029
|
+
|
|
1030
|
+
Place runnable scripts for this use case in this folder.
|
|
1031
|
+
`)],
|
|
1032
|
+
id
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
function scaffoldProfile(opts) {
|
|
1036
|
+
return {
|
|
1037
|
+
paths: [writeNew(join(opts.baseDir || process.cwd(), opts.localConfig ? "profile" : "profiles", `${opts.name}.yaml`), stringify({
|
|
1038
|
+
name: opts.name,
|
|
1039
|
+
description: opts.description || `${opts.name} profile`,
|
|
1040
|
+
scope: "project",
|
|
1041
|
+
targets: { agents: opts.agents?.length ? opts.agents : ["cursor"] },
|
|
1042
|
+
artifacts: {
|
|
1043
|
+
skills: opts.skills ?? [],
|
|
1044
|
+
rules: opts.rules ?? [],
|
|
1045
|
+
agents: opts.agentsArtifacts ?? [],
|
|
1046
|
+
scripts: opts.scripts ?? []
|
|
1047
|
+
}
|
|
1048
|
+
}))],
|
|
1049
|
+
id: opts.name
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
639
1052
|
let runningInsideAgent = false;
|
|
640
1053
|
try {
|
|
641
1054
|
const result = await determineAgent();
|
|
@@ -651,17 +1064,17 @@ const silentOutput = new Writable({ write(_chunk, _encoding, callback) {
|
|
|
651
1064
|
} });
|
|
652
1065
|
const cancelSymbol = Symbol("cancel");
|
|
653
1066
|
const ALL_VALUE = "__all__";
|
|
654
|
-
const BLUE =
|
|
655
|
-
const BLUE_BOLD = (s) => c.bold(
|
|
656
|
-
const S_STEP_ACTIVE =
|
|
1067
|
+
const BLUE = sky;
|
|
1068
|
+
const BLUE_BOLD = (s) => c.bold(sky(s));
|
|
1069
|
+
const S_STEP_ACTIVE = sky("◆");
|
|
657
1070
|
const S_STEP_CANCEL = c.red("■");
|
|
658
|
-
const S_STEP_SUBMIT =
|
|
659
|
-
const S_RADIO_ACTIVE =
|
|
1071
|
+
const S_STEP_SUBMIT = sky("◇");
|
|
1072
|
+
const S_RADIO_ACTIVE = sky("●");
|
|
660
1073
|
const S_RADIO_INACTIVE = c.dim("○");
|
|
661
1074
|
const S_RADIO_DISABLED = c.dim("◌");
|
|
662
|
-
const S_BULLET =
|
|
1075
|
+
const S_BULLET = sky("•");
|
|
663
1076
|
const S_BAR = c.dim("│");
|
|
664
|
-
|
|
1077
|
+
c.dim("─");
|
|
665
1078
|
const S_CORNER = c.dim("└");
|
|
666
1079
|
const S_BOX_TL = c.dim("┌");
|
|
667
1080
|
const S_BOX_TR = c.dim("┐");
|
|
@@ -764,7 +1177,7 @@ function renderInfoBox(fields, innerWidth, rows) {
|
|
|
764
1177
|
});
|
|
765
1178
|
const body = expanded.map((r) => {
|
|
766
1179
|
if (!r.label && !r.value) return `${S_BOX_V} ${" ".repeat(contentWidth)} ${S_BOX_V}`;
|
|
767
|
-
const content = `${r.label ? `${
|
|
1180
|
+
const content = `${r.label ? `${sky(padPlain(r.label, labelW))} ` : `${" ".repeat(labelW)} `}${r.cont ? c.dim(r.value) : c.bold(r.value)}`;
|
|
768
1181
|
const plainW = approxStringWidth(stripVTControlCharacters(content));
|
|
769
1182
|
const pad = Math.max(0, contentWidth - plainW);
|
|
770
1183
|
return `${S_BOX_V} ${content}${" ".repeat(pad)} ${S_BOX_V}`;
|
|
@@ -859,6 +1272,7 @@ async function searchMultiselect(options) {
|
|
|
859
1272
|
});
|
|
860
1273
|
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
861
1274
|
readline.emitKeypressEvents(process.stdin, rl);
|
|
1275
|
+
if (process.stdout.isTTY) process.stdout.write("\x1B[?25l");
|
|
862
1276
|
let query = "";
|
|
863
1277
|
let cursor = 0;
|
|
864
1278
|
const selected = new Set(initialSelected.filter((v) => String(v) !== ALL_VALUE));
|
|
@@ -873,6 +1287,32 @@ async function searchMultiselect(options) {
|
|
|
873
1287
|
const matched = rest.filter((item) => item.label.toLowerCase().includes(lowerQ) || String(item.value).toLowerCase().includes(lowerQ) || item.hint && item.hint.toLowerCase().includes(lowerQ));
|
|
874
1288
|
return allItem && (allItem.label.toLowerCase().includes(lowerQ) || "all".includes(lowerQ)) && allItem ? [allItem, ...matched] : matched;
|
|
875
1289
|
};
|
|
1290
|
+
const lockedTotal = lockedSection ? lockedSection.items.length + (lockedSection.hiddenCount ?? 0) : 0;
|
|
1291
|
+
const selectedSummaryLine = (allOn) => {
|
|
1292
|
+
if (allOn) {
|
|
1293
|
+
const extra = lockedTotal ? `Universal (${lockedTotal}) + All additional (${uniqueItems.length})` : `All ${uniqueItems.length}`;
|
|
1294
|
+
return `${S_BAR} ${BLUE("Selected:")} ${c.bold(extra)}`;
|
|
1295
|
+
}
|
|
1296
|
+
const labels = uniqueItems.filter((item) => selected.has(item.value)).map((item) => item.label);
|
|
1297
|
+
if (labels.length === 0) {
|
|
1298
|
+
if (requiredFlash) return `${S_BAR} ${c.red("Selected: pick at least one")}`;
|
|
1299
|
+
if (lockedTotal > 0) return `${S_BAR} ${BLUE("Selected:")} ${c.bold(`Universal (${lockedTotal})`)}`;
|
|
1300
|
+
return `${S_BAR} ${c.dim("Selected: (none)")}`;
|
|
1301
|
+
}
|
|
1302
|
+
const prefix = lockedTotal > 0 ? `Universal (${lockedTotal}) + ` : "";
|
|
1303
|
+
const summary = labels.length <= 3 ? labels.join(", ") : `${labels.slice(0, 3).join(", ")} +${labels.length - 3} more`;
|
|
1304
|
+
return `${S_BAR} ${BLUE("Selected:")} ${prefix}${summary}`;
|
|
1305
|
+
};
|
|
1306
|
+
const submitSummary = (allOn) => {
|
|
1307
|
+
if (allOn) return lockedTotal ? `Universal (${lockedTotal}) + All additional (${uniqueItems.length})` : `All (${uniqueItems.length})`;
|
|
1308
|
+
const labels = uniqueItems.filter((item) => selected.has(item.value)).map((item) => item.label);
|
|
1309
|
+
if (labels.length === 0) return lockedTotal > 0 ? `Universal (${lockedTotal})` : "none";
|
|
1310
|
+
return (lockedTotal > 0 ? `Universal (${lockedTotal}) + ` : "") + (labels.length <= 5 ? labels.join(", ") : `${labels.slice(0, 5).join(", ")} +${labels.length - 5}`);
|
|
1311
|
+
};
|
|
1312
|
+
const clearPrevious = (rows) => {
|
|
1313
|
+
if (rows <= 0) return;
|
|
1314
|
+
for (let i = 0; i < rows; i++) process.stdout.write("\x1B[1A\x1B[2K");
|
|
1315
|
+
};
|
|
876
1316
|
const render = (state = "active") => {
|
|
877
1317
|
const lines = [];
|
|
878
1318
|
const filtered = getFiltered();
|
|
@@ -881,14 +1321,9 @@ async function searchMultiselect(options) {
|
|
|
881
1321
|
lines.push(`${icon} ${c.bold(message)}`);
|
|
882
1322
|
lines.push(`${S_BAR}`);
|
|
883
1323
|
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)}`);
|
|
1324
|
+
if (lockedSection && lockedTotal > 0) {
|
|
1325
|
+
lines.push(`${S_BAR} ${S_BULLET} ${c.bold(lockedSection.title)} ${c.dim(`· ${lockedTotal} always included`)}`);
|
|
1326
|
+
lines.push(`${S_BAR} ${c.bold("Additional")}`);
|
|
892
1327
|
lines.push(`${S_BAR}`);
|
|
893
1328
|
}
|
|
894
1329
|
if (searchable) {
|
|
@@ -938,28 +1373,20 @@ async function searchMultiselect(options) {
|
|
|
938
1373
|
}
|
|
939
1374
|
if (showSelectedSummary) {
|
|
940
1375
|
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
|
-
}
|
|
1376
|
+
lines.push(selectedSummaryLine(allOn));
|
|
950
1377
|
}
|
|
951
1378
|
lines.push(`${S_CORNER}`);
|
|
952
|
-
} else if (state === "submit") {
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
process.stdout.write(clearPreviousFrame + lines.join("\n") + "\n");
|
|
1379
|
+
} else if (state === "submit") lines.push(`${S_BAR} ${c.dim(submitSummary(allOn))}`);
|
|
1380
|
+
else lines.push(`${S_BAR} ${c.strikethrough(c.dim("Cancelled"))}`);
|
|
1381
|
+
clearPrevious(lastRenderHeight);
|
|
1382
|
+
const out = lines.join("\n") + "\n";
|
|
1383
|
+
process.stdout.write(out);
|
|
958
1384
|
lastRenderHeight = countVisualRowsForLines(lines, process.stdout.columns);
|
|
959
1385
|
};
|
|
960
1386
|
const cleanup = () => {
|
|
961
1387
|
process.stdin.removeListener("keypress", keypressHandler);
|
|
962
1388
|
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
1389
|
+
if (process.stdout.isTTY) process.stdout.write("\x1B[?25h");
|
|
963
1390
|
rl.close();
|
|
964
1391
|
};
|
|
965
1392
|
const submit = () => {
|
|
@@ -1037,6 +1464,103 @@ async function searchMultiselect(options) {
|
|
|
1037
1464
|
render();
|
|
1038
1465
|
});
|
|
1039
1466
|
}
|
|
1467
|
+
function agentsPrompt(action) {
|
|
1468
|
+
if (action === "remove") return "Which agents do you want to remove from?";
|
|
1469
|
+
if (action === "update") return "Which agents do you want to update for?";
|
|
1470
|
+
return "Which agents do you want to install to?";
|
|
1471
|
+
}
|
|
1472
|
+
function scopePrompt(action) {
|
|
1473
|
+
if (action === "remove") return "Removal scope";
|
|
1474
|
+
if (action === "update") return "Update scope";
|
|
1475
|
+
return "Installation scope";
|
|
1476
|
+
}
|
|
1477
|
+
async function selectInstallScope(message, paths) {
|
|
1478
|
+
if (isQuiet()) return "project";
|
|
1479
|
+
return new Promise((resolve) => {
|
|
1480
|
+
if (!process.stdin.isTTY) {
|
|
1481
|
+
resolve("project");
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
const rl = readline.createInterface({
|
|
1485
|
+
input: process.stdin,
|
|
1486
|
+
output: process.stdout
|
|
1487
|
+
});
|
|
1488
|
+
process.stdin.setRawMode(true);
|
|
1489
|
+
readline.emitKeypressEvents(process.stdin, rl);
|
|
1490
|
+
let value = "project";
|
|
1491
|
+
let lastH = 0;
|
|
1492
|
+
const joinRel = (base, rel) => {
|
|
1493
|
+
const sep = base.includes("\\") ? "\\" : "/";
|
|
1494
|
+
return `${base.replace(/[\\/]+$/, "")}${sep}${rel.replace(/\//g, sep)}`;
|
|
1495
|
+
};
|
|
1496
|
+
const optionBlock = (key, selected, done) => {
|
|
1497
|
+
const active = done ? done === key : selected;
|
|
1498
|
+
const bullet = active ? sky(c.bold("●")) : c.dim("○");
|
|
1499
|
+
const title = key === "project" ? "Project" : "Global";
|
|
1500
|
+
const hint = key === "project" ? "Install in the current directory (committed with your project)" : "Install in your home directory (available across all projects)";
|
|
1501
|
+
const path = key === "project" ? paths.projectDir : paths.homeDir;
|
|
1502
|
+
const lock = key === "project" ? joinRel(paths.projectDir, ".agentry/lock.json") : joinRel(paths.homeDir, ".agentry/lock.json");
|
|
1503
|
+
const lines = [];
|
|
1504
|
+
if (done === "cancel") lines.push(`${c.dim("│")} ${c.strikethrough(c.dim(title))}`);
|
|
1505
|
+
else {
|
|
1506
|
+
lines.push(`${c.dim("│")} ${bullet} ${active ? c.bold(title) : title}`);
|
|
1507
|
+
lines.push(`${c.dim("│")} ${c.dim(hint)}`);
|
|
1508
|
+
lines.push(`${c.dim("│")} ${sky("→")} ${c.dim(path)}`);
|
|
1509
|
+
lines.push(`${c.dim("│")} ${c.dim(`lock ${lock}`)}`);
|
|
1510
|
+
}
|
|
1511
|
+
return lines;
|
|
1512
|
+
};
|
|
1513
|
+
const paint = (done) => {
|
|
1514
|
+
const lines = [];
|
|
1515
|
+
if (done === "cancel") {
|
|
1516
|
+
lines.push(`${sky("■")} ${c.bold(message)}`);
|
|
1517
|
+
lines.push(`${c.dim("│")} ${c.strikethrough(c.dim("Cancelled"))}`);
|
|
1518
|
+
} else if (done === "project" || done === "global") {
|
|
1519
|
+
lines.push(`${sky("◇")} ${c.bold(message)}`);
|
|
1520
|
+
lines.push(...optionBlock(done, true, done).slice(0, 2));
|
|
1521
|
+
lines.push(`${c.dim("│")} ${sky("→")} ${c.dim(done === "project" ? paths.projectDir : paths.homeDir)}`);
|
|
1522
|
+
} else {
|
|
1523
|
+
lines.push(`${sky("◆")} ${c.bold(message)}`);
|
|
1524
|
+
lines.push(`${c.dim("│")}`);
|
|
1525
|
+
lines.push(...optionBlock("project", value === "project"));
|
|
1526
|
+
lines.push(`${c.dim("│")}`);
|
|
1527
|
+
lines.push(...optionBlock("global", value === "global"));
|
|
1528
|
+
lines.push(`${c.dim("└")} ${c.dim("↑/↓ select · enter confirm")}`);
|
|
1529
|
+
}
|
|
1530
|
+
if (lastH > 0) for (let i = 0; i < lastH; i++) process.stdout.write("\x1B[1A\x1B[2K");
|
|
1531
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
1532
|
+
lastH = lines.length;
|
|
1533
|
+
};
|
|
1534
|
+
const cleanup = () => {
|
|
1535
|
+
process.stdin.removeListener("keypress", onKey);
|
|
1536
|
+
process.stdin.setRawMode(false);
|
|
1537
|
+
rl.close();
|
|
1538
|
+
};
|
|
1539
|
+
const onKey = (_s, key) => {
|
|
1540
|
+
if (!key) return;
|
|
1541
|
+
if (key.name === "up" || key.name === "down" || key.name === "tab") {
|
|
1542
|
+
value = value === "project" ? "global" : "project";
|
|
1543
|
+
paint();
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
if (key.name === "return") {
|
|
1547
|
+
paint(value);
|
|
1548
|
+
cleanup();
|
|
1549
|
+
blank();
|
|
1550
|
+
resolve(value);
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
if (key.name === "escape" || key.ctrl && key.name === "c") {
|
|
1554
|
+
paint("cancel");
|
|
1555
|
+
cleanup();
|
|
1556
|
+
resolve(null);
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
process.stdin.on("keypress", onKey);
|
|
1560
|
+
blank();
|
|
1561
|
+
paint();
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1040
1564
|
function isQuiet() {
|
|
1041
1565
|
return isCI() || !process.stdout.isTTY;
|
|
1042
1566
|
}
|
|
@@ -1054,6 +1578,79 @@ function outro(message) {
|
|
|
1054
1578
|
}
|
|
1055
1579
|
clack.outro(message);
|
|
1056
1580
|
}
|
|
1581
|
+
async function confirm(message) {
|
|
1582
|
+
if (isQuiet()) return true;
|
|
1583
|
+
return new Promise((resolve) => {
|
|
1584
|
+
if (!process.stdin.isTTY) {
|
|
1585
|
+
resolve(true);
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
const rl = readline.createInterface({
|
|
1589
|
+
input: process.stdin,
|
|
1590
|
+
output: process.stdout
|
|
1591
|
+
});
|
|
1592
|
+
process.stdin.setRawMode(true);
|
|
1593
|
+
readline.emitKeypressEvents(process.stdin, rl);
|
|
1594
|
+
let value = true;
|
|
1595
|
+
let lastH = 0;
|
|
1596
|
+
const paint = (done) => {
|
|
1597
|
+
const lines = [];
|
|
1598
|
+
if (done === "cancel") {
|
|
1599
|
+
lines.push(`${sky("■")} ${c.bold(message)}`);
|
|
1600
|
+
lines.push(`${c.dim("│")} ${c.strikethrough(c.dim("Cancelled"))}`);
|
|
1601
|
+
} else if (done === "yes" || done === "no") {
|
|
1602
|
+
lines.push(`${sky("◇")} ${c.bold(message)}`);
|
|
1603
|
+
lines.push(`${c.dim("│")} ${c.dim(done === "yes" ? "Yes" : "No")}`);
|
|
1604
|
+
} else {
|
|
1605
|
+
lines.push(`${sky("◆")} ${c.bold(message)}`);
|
|
1606
|
+
lines.push(`${c.dim("│")}`);
|
|
1607
|
+
const yes = value ? sky(c.bold("● Yes")) : c.dim("○ Yes");
|
|
1608
|
+
const no = !value ? sky(c.bold("● No")) : c.dim("○ No");
|
|
1609
|
+
lines.push(`${c.dim("│")} ${yes} / ${no}`);
|
|
1610
|
+
lines.push(`${c.dim("└")} ${c.dim("←/→ toggle · enter confirm")}`);
|
|
1611
|
+
}
|
|
1612
|
+
if (lastH > 0) for (let i = 0; i < lastH; i++) process.stdout.write("\x1B[1A\x1B[2K");
|
|
1613
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
1614
|
+
lastH = lines.length;
|
|
1615
|
+
};
|
|
1616
|
+
const cleanup = () => {
|
|
1617
|
+
process.stdin.removeListener("keypress", onKey);
|
|
1618
|
+
process.stdin.setRawMode(false);
|
|
1619
|
+
rl.close();
|
|
1620
|
+
};
|
|
1621
|
+
const onKey = (_s, key) => {
|
|
1622
|
+
if (!key) return;
|
|
1623
|
+
if (key.name === "left" || key.name === "right" || key.name === "tab") {
|
|
1624
|
+
value = !value;
|
|
1625
|
+
paint();
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
if (key.name === "y") {
|
|
1629
|
+
value = true;
|
|
1630
|
+
paint();
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
if (key.name === "n") {
|
|
1634
|
+
value = false;
|
|
1635
|
+
paint();
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
if (key.name === "return") {
|
|
1639
|
+
paint(value ? "yes" : "no");
|
|
1640
|
+
cleanup();
|
|
1641
|
+
resolve(value);
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
if (key.name === "escape" || key.ctrl && key.name === "c") {
|
|
1645
|
+
paint("cancel");
|
|
1646
|
+
cleanup();
|
|
1647
|
+
resolve(false);
|
|
1648
|
+
}
|
|
1649
|
+
};
|
|
1650
|
+
process.stdin.on("keypress", onKey);
|
|
1651
|
+
paint();
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1057
1654
|
function log(message) {
|
|
1058
1655
|
process.stdout.write(message + "\n");
|
|
1059
1656
|
}
|
|
@@ -1063,7 +1660,7 @@ function blank() {
|
|
|
1063
1660
|
async function sectionTransition(doneLabel, nextLabel) {
|
|
1064
1661
|
if (isQuiet()) return;
|
|
1065
1662
|
blank();
|
|
1066
|
-
log(`${
|
|
1663
|
+
log(`${sky("◇")} ${c.dim("Done:")} ${c.bold(doneLabel)}`);
|
|
1067
1664
|
await delay(320);
|
|
1068
1665
|
for (const ch of [
|
|
1069
1666
|
"│",
|
|
@@ -1074,15 +1671,15 @@ async function sectionTransition(doneLabel, nextLabel) {
|
|
|
1074
1671
|
await delay(140);
|
|
1075
1672
|
}
|
|
1076
1673
|
await delay(200);
|
|
1077
|
-
log(`${
|
|
1674
|
+
log(`${sky("◆")} ${c.bold("Next:")} ${nextLabel}`);
|
|
1078
1675
|
await delay(420);
|
|
1079
1676
|
blank();
|
|
1080
1677
|
}
|
|
1081
1678
|
function step(message) {
|
|
1082
|
-
process.stdout.write(` ${
|
|
1679
|
+
process.stdout.write(` ${sky("→")} ${message}\n`);
|
|
1083
1680
|
}
|
|
1084
1681
|
function ok(message) {
|
|
1085
|
-
process.stdout.write(`${
|
|
1682
|
+
process.stdout.write(`${sky("✓")} ${message}\n`);
|
|
1086
1683
|
}
|
|
1087
1684
|
function err(message) {
|
|
1088
1685
|
process.stdout.write(`${c.red("✗")} ${message}\n`);
|
|
@@ -1091,13 +1688,13 @@ function warn(message) {
|
|
|
1091
1688
|
process.stdout.write(`${c.yellow("⚠")} ${message}\n`);
|
|
1092
1689
|
}
|
|
1093
1690
|
function info(message) {
|
|
1094
|
-
process.stdout.write(`${
|
|
1691
|
+
process.stdout.write(`${sky("ℹ")} ${message}\n`);
|
|
1095
1692
|
}
|
|
1096
1693
|
async function spinner(startMsg, fn, stopWith, minMs = 480) {
|
|
1097
1694
|
if (isQuiet()) {
|
|
1098
1695
|
process.stdout.write(c.dim(`• ${startMsg}...`) + "\n");
|
|
1099
1696
|
const r = await fn();
|
|
1100
|
-
if (stopWith) process.stdout.write(`${
|
|
1697
|
+
if (stopWith) process.stdout.write(`${sky("◇")} ${stopWith(r)}\n`);
|
|
1101
1698
|
return r;
|
|
1102
1699
|
}
|
|
1103
1700
|
const frames = [
|
|
@@ -1114,11 +1711,11 @@ async function spinner(startMsg, fn, stopWith, minMs = 480) {
|
|
|
1114
1711
|
];
|
|
1115
1712
|
let i = 0;
|
|
1116
1713
|
let alive = true;
|
|
1117
|
-
process.stdout.write(`${
|
|
1714
|
+
process.stdout.write(`${sky(frames[0])} ${startMsg}`);
|
|
1118
1715
|
const tick = setInterval(() => {
|
|
1119
1716
|
if (!alive) return;
|
|
1120
1717
|
i = (i + 1) % frames.length;
|
|
1121
|
-
process.stdout.write(`\r${
|
|
1718
|
+
process.stdout.write(`\r${sky(frames[i])} ${startMsg} `);
|
|
1122
1719
|
}, 80);
|
|
1123
1720
|
const started = Date.now();
|
|
1124
1721
|
try {
|
|
@@ -1128,7 +1725,7 @@ async function spinner(startMsg, fn, stopWith, minMs = 480) {
|
|
|
1128
1725
|
alive = false;
|
|
1129
1726
|
clearInterval(tick);
|
|
1130
1727
|
const msg = stopWith ? stopWith(result) : startMsg;
|
|
1131
|
-
process.stdout.write(`\r\x1b[K${
|
|
1728
|
+
process.stdout.write(`\r\x1b[K${sky("◇")} ${msg}\n`);
|
|
1132
1729
|
return result;
|
|
1133
1730
|
} catch (e) {
|
|
1134
1731
|
alive = false;
|
|
@@ -1235,23 +1832,23 @@ async function selectAgents(message, universal, others, detectedNames = []) {
|
|
|
1235
1832
|
}));
|
|
1236
1833
|
const locked = universal.length > 0 ? {
|
|
1237
1834
|
title: "Universal",
|
|
1238
|
-
items: universal.
|
|
1835
|
+
items: universal.map((a) => ({
|
|
1239
1836
|
value: a.name,
|
|
1240
1837
|
label: a.displayName,
|
|
1241
1838
|
fields: agentFields(a, true)
|
|
1242
1839
|
})),
|
|
1243
|
-
|
|
1840
|
+
compact: true
|
|
1244
1841
|
} : void 0;
|
|
1245
1842
|
const res = await searchMultiselect({
|
|
1246
1843
|
message,
|
|
1247
1844
|
items,
|
|
1248
|
-
maxVisible:
|
|
1845
|
+
maxVisible: 5,
|
|
1249
1846
|
required: false,
|
|
1250
1847
|
initialSelected: detected.map((d) => d.name),
|
|
1251
1848
|
lockedSection: locked,
|
|
1252
1849
|
searchable: true,
|
|
1253
1850
|
showDetail: true,
|
|
1254
|
-
detailLines:
|
|
1851
|
+
detailLines: 4,
|
|
1255
1852
|
showSelectedSummary: true,
|
|
1256
1853
|
includeAllOption: true,
|
|
1257
1854
|
allLabel: `All additional (${uniqueOthers.length})`,
|
|
@@ -1273,12 +1870,12 @@ function installSummary(artifacts, agents, scopeLabel) {
|
|
|
1273
1870
|
blank();
|
|
1274
1871
|
const bar = c.dim("│");
|
|
1275
1872
|
const corner = c.dim("└");
|
|
1276
|
-
log(`${
|
|
1873
|
+
log(`${sky("◆")} ${c.bold("Install summary")}`);
|
|
1277
1874
|
log(bar);
|
|
1278
|
-
log(`${bar} ${
|
|
1875
|
+
log(`${bar} ${sky("Scope")} ${scopeLabel}`);
|
|
1279
1876
|
const universalCount = agents.filter((a) => a.skillsDir === ".agents/skills").length;
|
|
1280
1877
|
const otherCount = agents.length - universalCount;
|
|
1281
|
-
log(`${bar} ${
|
|
1878
|
+
log(`${bar} ${sky("Targets")} ${agents.length} agent${agents.length === 1 ? "" : "s"}` + c.dim(` · ${universalCount} universal` + (otherCount ? ` · ${otherCount} additional` : "")));
|
|
1282
1879
|
const names = agents.map((a) => a.displayName);
|
|
1283
1880
|
const chunks = [];
|
|
1284
1881
|
let line = "";
|
|
@@ -1297,7 +1894,7 @@ function installSummary(artifacts, agents, scopeLabel) {
|
|
|
1297
1894
|
log(bar);
|
|
1298
1895
|
for (const a of artifacts) {
|
|
1299
1896
|
const desc = (a.description || "").replace(/\s+/g, " ").trim().slice(0, 52);
|
|
1300
|
-
log(`${bar} ${
|
|
1897
|
+
log(`${bar} ${sky("•")} ${c.bold(`${a.kind}/${a.id}`)}`);
|
|
1301
1898
|
if (desc) log(`${bar} ${c.dim(desc)}`);
|
|
1302
1899
|
}
|
|
1303
1900
|
log(bar);
|
|
@@ -1307,16 +1904,29 @@ function installSummary(artifacts, agents, scopeLabel) {
|
|
|
1307
1904
|
function preview(artifacts, agents) {
|
|
1308
1905
|
installSummary(artifacts, agents, "preview (dry-run)");
|
|
1309
1906
|
}
|
|
1907
|
+
async function resolveSourceWithUi(source, opts) {
|
|
1908
|
+
const resolved = await resolveSource(source, { confirmCache: async ({ url, error }) => {
|
|
1909
|
+
warn(`git clone failed for ${url}`);
|
|
1910
|
+
if (error) step(c.dim(error.split("\n")[0] || error));
|
|
1911
|
+
if (opts.yes || isQuiet()) {
|
|
1912
|
+
info("Using cached copy.");
|
|
1913
|
+
return true;
|
|
1914
|
+
}
|
|
1915
|
+
return confirm("Load from cache instead?");
|
|
1916
|
+
} });
|
|
1917
|
+
if (resolved.fromCache) info("Loaded source from cache.");
|
|
1918
|
+
return resolved;
|
|
1919
|
+
}
|
|
1310
1920
|
async function cmdAdd(kind, source, selector, opts) {
|
|
1311
1921
|
if (!source) throw new Error(`Missing source. e.g. agentry add ${kind} author/repo`);
|
|
1312
1922
|
intro();
|
|
1313
1923
|
await delayQuiet(180);
|
|
1314
1924
|
await spinner("Parsing source…", () => parseSourceForUi(source), (p) => {
|
|
1315
1925
|
const where = p.url || p.local || source;
|
|
1316
|
-
return `${
|
|
1926
|
+
return `${theme.primary("Source:")} ${where}${p.ref ? c.dim(` @ ${p.ref}`) : ""}${p.subpath ? c.dim(` (${p.subpath})`) : ""}`;
|
|
1317
1927
|
}, 520);
|
|
1318
1928
|
await delayQuiet(280);
|
|
1319
|
-
const { root, cleanup } =
|
|
1929
|
+
const { root, cleanup } = await resolveSourceWithUi(source, opts);
|
|
1320
1930
|
try {
|
|
1321
1931
|
const artifacts = await spinner("Discovering…", () => kind === "all" ? listAll(root) : select(root, kind, selector), (a) => {
|
|
1322
1932
|
const noun = kind === "all" ? "artifact" : kind;
|
|
@@ -1329,7 +1939,7 @@ async function cmdAdd(kind, source, selector, opts) {
|
|
|
1329
1939
|
await delayQuiet(320);
|
|
1330
1940
|
blank();
|
|
1331
1941
|
if (opts.dryRun) {
|
|
1332
|
-
preview(artifacts, await resolveAgentList(opts));
|
|
1942
|
+
preview(artifacts, await resolveAgentList(opts, "install"));
|
|
1333
1943
|
return;
|
|
1334
1944
|
}
|
|
1335
1945
|
let chosen = artifacts;
|
|
@@ -1347,8 +1957,11 @@ async function cmdAdd(kind, source, selector, opts) {
|
|
|
1347
1957
|
await delayQuiet(200);
|
|
1348
1958
|
blank();
|
|
1349
1959
|
}
|
|
1350
|
-
const agentList = await resolveAgentList(opts);
|
|
1351
|
-
|
|
1960
|
+
const agentList = await resolveAgentList(opts, "install");
|
|
1961
|
+
const askScope = !opts.scopeExplicit && !opts.yes && !isQuiet();
|
|
1962
|
+
if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition(`${agentList.length} agent${agentList.length === 1 ? "" : "s"} selected`, askScope ? "installation scope" : "install summary");
|
|
1963
|
+
if (!await ensureInstallScope(opts, "install")) return;
|
|
1964
|
+
if (askScope) await sectionTransition("scope set", "install summary");
|
|
1352
1965
|
installAll(chosen, opts, agentList, source);
|
|
1353
1966
|
} finally {
|
|
1354
1967
|
cleanup();
|
|
@@ -1360,14 +1973,15 @@ async function delayQuiet(ms) {
|
|
|
1360
1973
|
await delay(ms);
|
|
1361
1974
|
}
|
|
1362
1975
|
function parseSourceForUi(source) {
|
|
1363
|
-
return
|
|
1976
|
+
return parseSource(source);
|
|
1364
1977
|
}
|
|
1365
1978
|
async function cmdAddProfile(name, source, opts) {
|
|
1366
1979
|
const { file, profile } = loadProfile(name);
|
|
1367
1980
|
const repo = source || (profile.artifacts.skills[0]?.source ?? profile.artifacts.rules[0]?.source);
|
|
1368
1981
|
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
|
-
|
|
1982
|
+
const agentList = await resolveAgentList(opts, "install");
|
|
1983
|
+
if (!await ensureInstallScope(opts, "install")) return;
|
|
1984
|
+
const { root, cleanup } = await resolveSourceWithUi(repo, opts);
|
|
1371
1985
|
try {
|
|
1372
1986
|
const artifacts = [];
|
|
1373
1987
|
const plural = (k) => k === "skill" ? "skills" : k === "rule" ? "rules" : k === "script" ? "scripts" : "agents";
|
|
@@ -1375,7 +1989,7 @@ async function cmdAddProfile(name, source, opts) {
|
|
|
1375
1989
|
if (k === "profile") continue;
|
|
1376
1990
|
const refs = profile.artifacts[plural(k)] ?? [];
|
|
1377
1991
|
for (const ref of refs) {
|
|
1378
|
-
const src = ref.source ?
|
|
1992
|
+
const src = ref.source ? await resolveSourceWithUi(ref.source, opts) : {
|
|
1379
1993
|
root,
|
|
1380
1994
|
cleanup: () => {}
|
|
1381
1995
|
};
|
|
@@ -1392,7 +2006,7 @@ async function cmdAddProfile(name, source, opts) {
|
|
|
1392
2006
|
cleanup();
|
|
1393
2007
|
}
|
|
1394
2008
|
}
|
|
1395
|
-
async function resolveAgentList(opts) {
|
|
2009
|
+
async function resolveAgentList(opts, action = "install") {
|
|
1396
2010
|
if (opts.all) return listAgents();
|
|
1397
2011
|
if (opts.agents.length) {
|
|
1398
2012
|
const { agents, unknown } = resolveAgents(opts.agents);
|
|
@@ -1408,10 +2022,27 @@ async function resolveAgentList(opts) {
|
|
|
1408
2022
|
const picked = detectedOthers.length ? detectedOthers : ["cursor"];
|
|
1409
2023
|
return [...universal, ...others.filter((o) => picked.includes(o.name))];
|
|
1410
2024
|
}
|
|
1411
|
-
const selected = await selectAgents(
|
|
2025
|
+
const selected = await selectAgents(agentsPrompt(action), visibleUniversal, others, detectedOthers);
|
|
1412
2026
|
if (isCancelLike(selected)) throw new Error("Cancelled.");
|
|
1413
2027
|
return selected;
|
|
1414
2028
|
}
|
|
2029
|
+
async function ensureInstallScope(opts, action) {
|
|
2030
|
+
if (opts.scopeExplicit) return true;
|
|
2031
|
+
if (opts.yes || isQuiet()) return true;
|
|
2032
|
+
const { homedir } = await import("node:os");
|
|
2033
|
+
if (!isQuiet()) await sectionTransition(`${action === "install" ? "agents" : action} ready`, scopePrompt(action).toLowerCase());
|
|
2034
|
+
const picked = await selectInstallScope(scopePrompt(action), {
|
|
2035
|
+
projectDir: opts.dir || process.cwd(),
|
|
2036
|
+
homeDir: homedir()
|
|
2037
|
+
});
|
|
2038
|
+
if (picked == null || isCancelLike(picked)) {
|
|
2039
|
+
outro("Cancelled.");
|
|
2040
|
+
return false;
|
|
2041
|
+
}
|
|
2042
|
+
opts.scope = picked;
|
|
2043
|
+
opts.scopeExplicit = true;
|
|
2044
|
+
return true;
|
|
2045
|
+
}
|
|
1415
2046
|
function installAll(artifacts, opts, agentList, source) {
|
|
1416
2047
|
if (!artifacts.length) {
|
|
1417
2048
|
warn("Nothing matched - nothing installed.");
|
|
@@ -1430,74 +2061,220 @@ function toInstallOpts(opts) {
|
|
|
1430
2061
|
dryRun: opts.dryRun
|
|
1431
2062
|
};
|
|
1432
2063
|
}
|
|
1433
|
-
|
|
1434
|
-
|
|
2064
|
+
function resolveSourceOrSelector(arg1, arg2, opts) {
|
|
2065
|
+
if (arg1 && arg2) return {
|
|
2066
|
+
source: arg1,
|
|
2067
|
+
selector: arg2
|
|
2068
|
+
};
|
|
2069
|
+
if (!arg1) return {};
|
|
2070
|
+
if (isExplicitSource(arg1)) return { source: arg1 };
|
|
2071
|
+
if (isOwnerRepoShape(arg1)) {
|
|
2072
|
+
const bases = [{
|
|
2073
|
+
scope: "project",
|
|
2074
|
+
dir: opts.dir,
|
|
2075
|
+
agents: opts.agents,
|
|
2076
|
+
copy: opts.copy,
|
|
2077
|
+
dryRun: opts.dryRun
|
|
2078
|
+
}, {
|
|
2079
|
+
scope: "global",
|
|
2080
|
+
agents: opts.agents,
|
|
2081
|
+
copy: opts.copy,
|
|
2082
|
+
dryRun: opts.dryRun
|
|
2083
|
+
}];
|
|
2084
|
+
for (const base of bases) {
|
|
2085
|
+
const lock = readLock(base);
|
|
2086
|
+
if (Object.values(lock.items).some((e) => e.source && sourcesEqual(e.source, arg1))) return { source: arg1 };
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
return { selector: arg1 };
|
|
2090
|
+
}
|
|
2091
|
+
async function cmdRemove(kind, arg1, arg2, opts) {
|
|
2092
|
+
intro();
|
|
2093
|
+
const { source, selector } = resolveSourceOrSelector(arg1, arg2, opts);
|
|
2094
|
+
if (source) await spinner("Resolving source…", () => parseSource(source), (p) => {
|
|
2095
|
+
const where = p.url || p.local || source;
|
|
2096
|
+
return `${theme.primary("Source:")} ${where}${p.ref ? c.dim(` @ ${p.ref}`) : ""}`;
|
|
2097
|
+
}, 420);
|
|
2098
|
+
if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition("prepare remove", "choose agents to remove from");
|
|
2099
|
+
const agentList = await resolveAgentList(opts, "remove");
|
|
2100
|
+
if (!await ensureInstallScope(opts, "remove")) return;
|
|
1435
2101
|
const installOpts = toInstallOpts(opts);
|
|
1436
|
-
const scope = [
|
|
2102
|
+
const scope = [
|
|
2103
|
+
kind,
|
|
2104
|
+
source,
|
|
2105
|
+
selector
|
|
2106
|
+
].filter(Boolean).join(" / ");
|
|
1437
2107
|
if (kind === "all") {
|
|
1438
2108
|
let count = 0;
|
|
1439
|
-
|
|
1440
|
-
|
|
2109
|
+
let paths = 0;
|
|
2110
|
+
for (const t of ARTIFACT_KINDS) {
|
|
2111
|
+
const r = removeSelection(t, void 0, installOpts, agentList, source);
|
|
2112
|
+
count += r.removedKeys.length;
|
|
2113
|
+
paths += r.removedPaths.length;
|
|
2114
|
+
}
|
|
2115
|
+
ok(`Removed ${count} item(s)${paths ? c.dim(` · ${paths} path(s)`) : ""}${source ? c.dim(` from ${source}`) : ""}.`);
|
|
1441
2116
|
return;
|
|
1442
2117
|
}
|
|
1443
|
-
const r = removeSelection(kind, selector, installOpts, agentList);
|
|
2118
|
+
const r = removeSelection(kind, selector, installOpts, agentList, source);
|
|
1444
2119
|
if (!r.existed) {
|
|
1445
|
-
warn(`Nothing installed
|
|
2120
|
+
warn(`Nothing installed matching "${scope}".`);
|
|
1446
2121
|
return;
|
|
1447
2122
|
}
|
|
1448
2123
|
for (const k of r.removedKeys) step(`removed ${k}`);
|
|
1449
|
-
|
|
2124
|
+
for (const p of r.removedPaths.slice(0, 12)) step(c.dim(`deleted ${p}`));
|
|
2125
|
+
if (r.removedPaths.length > 12) step(c.dim(`…and ${r.removedPaths.length - 12} more paths`));
|
|
2126
|
+
ok(`Removed ${r.removedKeys.length} item(s)${r.removedPaths.length ? c.dim(` · ${r.removedPaths.length} path(s)`) : ""}${source ? c.dim(` from ${source}`) : ""}.`);
|
|
1450
2127
|
}
|
|
1451
|
-
function cmdList(source, kind) {
|
|
1452
|
-
if (!source)
|
|
1453
|
-
|
|
2128
|
+
async function cmdList(source, kind, selector, opts) {
|
|
2129
|
+
if (!source) {
|
|
2130
|
+
cmdListInstalled(opts, kind, selector);
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
intro();
|
|
2134
|
+
await spinner("Parsing source…", () => parseSource(source), (p) => {
|
|
2135
|
+
const where = p.url || p.local || source;
|
|
2136
|
+
return `${theme.primary("Source:")} ${where}${p.ref ? c.dim(` @ ${p.ref}`) : ""}${p.subpath ? c.dim(` (${p.subpath})`) : ""}`;
|
|
2137
|
+
}, 420);
|
|
2138
|
+
const { root, cleanup } = await resolveSourceWithUi(source, opts);
|
|
1454
2139
|
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
|
-
}
|
|
2140
|
+
printArtifactList(await spinner("Discovering…", () => {
|
|
2141
|
+
if (kind) return select(root, kind, selector);
|
|
2142
|
+
return listAll(root);
|
|
2143
|
+
}, (a) => `Found ${a.length} artifact${a.length === 1 ? "" : "s"}`, 520), {
|
|
2144
|
+
title: "Available in source",
|
|
2145
|
+
subtitle: source
|
|
2146
|
+
});
|
|
1467
2147
|
} finally {
|
|
1468
2148
|
cleanup();
|
|
1469
2149
|
}
|
|
1470
2150
|
}
|
|
1471
|
-
function cmdListInstalled(opts) {
|
|
1472
|
-
|
|
2151
|
+
function cmdListInstalled(opts, kind, selector) {
|
|
2152
|
+
intro();
|
|
2153
|
+
let installed = listInstalled(toInstallOpts(opts));
|
|
2154
|
+
if (kind) installed = installed.filter((it) => it.kind === kind || it.id.startsWith(kind + "/"));
|
|
2155
|
+
if (selector && kind) {
|
|
2156
|
+
const rel = `${kind}/${selector}`;
|
|
2157
|
+
installed = installed.filter((it) => it.id === rel || it.id.startsWith(rel + "/") || it.id.endsWith("/" + selector) || it.id === `${kind}/${selector}`);
|
|
2158
|
+
} else if (selector) installed = installed.filter((it) => it.id.includes(selector));
|
|
1473
2159
|
if (!installed.length) {
|
|
1474
|
-
info("Nothing installed.");
|
|
2160
|
+
info(kind || selector ? `Nothing installed matching that filter.` : "Nothing installed.");
|
|
2161
|
+
step(c.dim("Try: agentry add skills mhmdreza-rafiei/agent-tools"));
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
const bar = c.dim("│");
|
|
2165
|
+
const corner = c.dim("└");
|
|
2166
|
+
blank();
|
|
2167
|
+
log(`${sky("◆")} ${c.bold("Installed")}`);
|
|
2168
|
+
log(bar);
|
|
2169
|
+
log(`${bar} ${sky("Scope")} ${opts.scope === "global" ? "global (~)" : `project (${opts.dir || process.cwd()})`}`);
|
|
2170
|
+
log(`${bar} ${sky("Total")} ${installed.length} artifact${installed.length === 1 ? "" : "s"}`);
|
|
2171
|
+
log(bar);
|
|
2172
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
2173
|
+
for (const it of installed) {
|
|
2174
|
+
const k = it.kind || it.id.split("/")[0];
|
|
2175
|
+
const arr = byKind.get(k) || [];
|
|
2176
|
+
arr.push(it);
|
|
2177
|
+
byKind.set(k, arr);
|
|
2178
|
+
}
|
|
2179
|
+
for (const k of ARTIFACT_KINDS) {
|
|
2180
|
+
const items = byKind.get(k);
|
|
2181
|
+
if (!items?.length) continue;
|
|
2182
|
+
log(`${bar} ${c.bold(k + "s")} ${c.dim(`(${items.length})`)}`);
|
|
2183
|
+
for (const it of items) {
|
|
2184
|
+
const id = it.id.includes("/") ? it.id.slice(it.id.indexOf("/") + 1) : it.id;
|
|
2185
|
+
log(`${bar} ${sky("•")} ${c.bold(id)}`);
|
|
2186
|
+
const agents = it.agents?.length ? it.agents.join(", ") : "—";
|
|
2187
|
+
log(`${bar} ${c.dim(`agents: ${agents}`)}`);
|
|
2188
|
+
if (it.source) log(`${bar} ${c.dim(`source: ${it.source}`)}`);
|
|
2189
|
+
}
|
|
2190
|
+
log(bar);
|
|
2191
|
+
}
|
|
2192
|
+
log(`${corner} ${c.dim("agentry remove <kind> [source] [selector] · agentry update <kind> [source]")}`);
|
|
2193
|
+
blank();
|
|
2194
|
+
}
|
|
2195
|
+
function printArtifactList(artifacts, meta) {
|
|
2196
|
+
const bar = c.dim("│");
|
|
2197
|
+
const corner = c.dim("└");
|
|
2198
|
+
blank();
|
|
2199
|
+
log(`${sky("◆")} ${c.bold(meta.title)}`);
|
|
2200
|
+
log(bar);
|
|
2201
|
+
if (meta.subtitle) log(`${bar} ${sky("From")} ${meta.subtitle}`);
|
|
2202
|
+
log(`${bar} ${sky("Total")} ${artifacts.length}`);
|
|
2203
|
+
log(bar);
|
|
2204
|
+
if (!artifacts.length) {
|
|
2205
|
+
log(`${bar} ${c.dim("(none)")}`);
|
|
2206
|
+
log(bar);
|
|
2207
|
+
log(`${corner}`);
|
|
2208
|
+
blank();
|
|
1475
2209
|
return;
|
|
1476
2210
|
}
|
|
1477
|
-
|
|
2211
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
2212
|
+
for (const a of artifacts) {
|
|
2213
|
+
const arr = byKind.get(a.kind) || [];
|
|
2214
|
+
arr.push(a);
|
|
2215
|
+
byKind.set(a.kind, arr);
|
|
2216
|
+
}
|
|
2217
|
+
for (const k of ARTIFACT_KINDS) {
|
|
2218
|
+
const items = byKind.get(k);
|
|
2219
|
+
if (!items?.length) continue;
|
|
2220
|
+
log(`${bar} ${c.bold(k + "s")} ${c.dim(`(${items.length})`)}`);
|
|
2221
|
+
for (const it of items) {
|
|
2222
|
+
const desc = (it.description || "").replace(/\s+/g, " ").trim().slice(0, 56);
|
|
2223
|
+
log(`${bar} ${sky("•")} ${c.bold(it.id)}`);
|
|
2224
|
+
if (desc) log(`${bar} ${c.dim(desc)}`);
|
|
2225
|
+
}
|
|
2226
|
+
log(bar);
|
|
2227
|
+
}
|
|
2228
|
+
log(`${corner} ${c.dim("agentry add <kind> <source> [selector]")}`);
|
|
2229
|
+
blank();
|
|
1478
2230
|
}
|
|
1479
|
-
async function cmdUpdateAssets(kind,
|
|
1480
|
-
|
|
1481
|
-
const
|
|
2231
|
+
async function cmdUpdateAssets(kind, arg1, arg2, opts) {
|
|
2232
|
+
intro();
|
|
2233
|
+
const { source, selector } = resolveSourceOrSelector(arg1, arg2, opts);
|
|
1482
2234
|
if (source) {
|
|
1483
|
-
|
|
2235
|
+
await spinner("Parsing source…", () => parseSource(source), (p) => {
|
|
2236
|
+
const where = p.url || p.local || source;
|
|
2237
|
+
return `${theme.primary("Source:")} ${where}${p.ref ? c.dim(` @ ${p.ref}`) : ""}`;
|
|
2238
|
+
}, 520);
|
|
2239
|
+
const { root, cleanup } = await resolveSourceWithUi(source, opts);
|
|
1484
2240
|
try {
|
|
1485
|
-
const artifacts = kind === "all" ? listAll(root) : select(root, kind, selector);
|
|
2241
|
+
const artifacts = await spinner("Discovering…", () => kind === "all" ? listAll(root) : select(root, kind, selector), (a) => `Found ${a.length} to update`, 620);
|
|
1486
2242
|
if (!artifacts.length) {
|
|
1487
2243
|
warn(`Nothing found in ${source}.`);
|
|
1488
2244
|
return;
|
|
1489
2245
|
}
|
|
1490
|
-
|
|
2246
|
+
let chosen = artifacts;
|
|
2247
|
+
if (!selector && kind !== "all" && artifacts.length > 1 && !opts.all && !opts.yes && !isQuiet()) {
|
|
2248
|
+
await sectionTransition("source discovery", `select ${kind}s to update`);
|
|
2249
|
+
const picked = await selectArtifacts(`Select ${kind}s to update`, artifacts);
|
|
2250
|
+
if (isCancelLike(picked)) {
|
|
2251
|
+
outro("Cancelled.");
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
chosen = picked;
|
|
2255
|
+
await sectionTransition(`${chosen.length} ${kind}${chosen.length === 1 ? "" : "s"} selected`, "choose target agents");
|
|
2256
|
+
} else if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition(`${chosen.length} ${kind}${chosen.length === 1 ? "" : "s"} ready`, "choose target agents");
|
|
2257
|
+
const agentList = await resolveAgentList(opts, "update");
|
|
2258
|
+
if (!await ensureInstallScope(opts, "update")) return;
|
|
2259
|
+
if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition(`${agentList.length} agent${agentList.length === 1 ? "" : "s"} selected`, "update summary");
|
|
2260
|
+
installAll(chosen, opts, agentList, source);
|
|
1491
2261
|
} finally {
|
|
1492
2262
|
cleanup();
|
|
1493
2263
|
}
|
|
1494
2264
|
return;
|
|
1495
2265
|
}
|
|
1496
|
-
|
|
2266
|
+
if (!await ensureInstallScope(opts, "update")) return;
|
|
2267
|
+
let installed = listInstalled(toInstallOpts(opts)).filter((it) => kind === "all" || it.id.startsWith(kind + "/"));
|
|
2268
|
+
if (selector) {
|
|
2269
|
+
const rel = `${kind}/${selector}`;
|
|
2270
|
+
installed = installed.filter((it) => it.id === rel || it.id.startsWith(rel + "/") || it.id.endsWith("/" + selector));
|
|
2271
|
+
}
|
|
1497
2272
|
if (!installed.length) {
|
|
1498
2273
|
warn(`No installed ${kind} recorded in lockfile. Provide a source.`);
|
|
1499
2274
|
return;
|
|
1500
2275
|
}
|
|
2276
|
+
if (!isQuiet() && !opts.agents.length && !opts.all && !opts.yes) await sectionTransition("lockfile sources", "choose agents to update for");
|
|
2277
|
+
const agentList = await resolveAgentList(opts, "update");
|
|
1501
2278
|
const bySource = /* @__PURE__ */ new Map();
|
|
1502
2279
|
for (const it of installed) if (it.source) {
|
|
1503
2280
|
const arr = bySource.get(it.source) || [];
|
|
@@ -1509,22 +2286,216 @@ async function cmdUpdateAssets(kind, source, selector, opts) {
|
|
|
1509
2286
|
return;
|
|
1510
2287
|
}
|
|
1511
2288
|
for (const [s, ids] of bySource) {
|
|
1512
|
-
|
|
2289
|
+
info(`Updating from ${s}…`);
|
|
2290
|
+
const { root, cleanup } = await resolveSourceWithUi(s, opts);
|
|
1513
2291
|
try {
|
|
1514
|
-
installAll(ids.map((id) =>
|
|
2292
|
+
installAll(ids.map((id) => {
|
|
2293
|
+
const [k, ...rest] = id.split("/");
|
|
2294
|
+
return select(root, k, rest.join("/") || void 0);
|
|
2295
|
+
}).flat(), opts, agentList, s);
|
|
1515
2296
|
} finally {
|
|
1516
2297
|
cleanup();
|
|
1517
2298
|
}
|
|
1518
2299
|
}
|
|
1519
2300
|
}
|
|
2301
|
+
async function promptText(message, initial) {
|
|
2302
|
+
if (isQuiet()) return initial || null;
|
|
2303
|
+
const res = await clack.text({
|
|
2304
|
+
message,
|
|
2305
|
+
initialValue: initial || "",
|
|
2306
|
+
placeholder: initial
|
|
2307
|
+
});
|
|
2308
|
+
if (clack.isCancel(res)) return null;
|
|
2309
|
+
return String(res).trim() || null;
|
|
2310
|
+
}
|
|
2311
|
+
async function pickFromSource(kind, source, message, opts) {
|
|
2312
|
+
const { root, cleanup } = await resolveSourceWithUi(source, opts);
|
|
2313
|
+
try {
|
|
2314
|
+
const artifacts = listKind(root, kind);
|
|
2315
|
+
if (!artifacts.length) {
|
|
2316
|
+
warn(`No ${kind}s in ${source}.`);
|
|
2317
|
+
return [];
|
|
2318
|
+
}
|
|
2319
|
+
if (isQuiet() || artifacts.length === 1) return artifacts.map((a) => ({
|
|
2320
|
+
id: a.id,
|
|
2321
|
+
source
|
|
2322
|
+
}));
|
|
2323
|
+
const picked = await selectArtifacts(message, artifacts);
|
|
2324
|
+
if (isCancelLike(picked) || !picked) return [];
|
|
2325
|
+
return picked.map((a) => ({
|
|
2326
|
+
id: a.id,
|
|
2327
|
+
source
|
|
2328
|
+
}));
|
|
2329
|
+
} finally {
|
|
2330
|
+
cleanup();
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
async function cmdInit(kind, nameArg, categoryArg, opts) {
|
|
2334
|
+
intro();
|
|
2335
|
+
const baseDir = opts.dir || process.cwd();
|
|
2336
|
+
let name = nameArg;
|
|
2337
|
+
let category = categoryArg || null;
|
|
2338
|
+
if (!name && !isQuiet()) name = await promptText(`Name for the new ${kind}`) || void 0;
|
|
2339
|
+
if (!name) throw new Error(`Missing name. e.g. agentry init ${kind} my-${kind}`);
|
|
2340
|
+
let description = opts.description;
|
|
2341
|
+
if (!description && !opts.yes && !isQuiet()) description = await promptText("Description (optional)", `${name} ${kind}`) || `${name} ${kind}`;
|
|
2342
|
+
description = description || `${name} ${kind}`;
|
|
2343
|
+
if (kind === "skill") {
|
|
2344
|
+
const reference = opts.reference !== false;
|
|
2345
|
+
if (opts.dryRun) {
|
|
2346
|
+
info(`Would create skills/${category ? category + "/" : ""}${name}/SKILL.md${reference ? " + references/TEMPLATE.md" : ""}`);
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
const r = scaffoldSkill({
|
|
2350
|
+
name,
|
|
2351
|
+
category,
|
|
2352
|
+
description,
|
|
2353
|
+
reference,
|
|
2354
|
+
baseDir
|
|
2355
|
+
});
|
|
2356
|
+
for (const p of r.paths) step(p);
|
|
2357
|
+
ok(`Initialized skill ${r.id}`);
|
|
2358
|
+
return;
|
|
2359
|
+
}
|
|
2360
|
+
if (kind === "agent" || kind === "rule") {
|
|
2361
|
+
if (opts.dryRun) {
|
|
2362
|
+
info(`Would create ${kind}s/${category ? category + "/" : ""}${name}.mdc`);
|
|
2363
|
+
return;
|
|
2364
|
+
}
|
|
2365
|
+
const r = scaffoldMdc({
|
|
2366
|
+
kind,
|
|
2367
|
+
name,
|
|
2368
|
+
category,
|
|
2369
|
+
description,
|
|
2370
|
+
alwaysApply: opts.alwaysApply,
|
|
2371
|
+
baseDir
|
|
2372
|
+
});
|
|
2373
|
+
for (const p of r.paths) step(p);
|
|
2374
|
+
ok(`Initialized ${kind} ${r.id}`);
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
2377
|
+
if (kind === "script") {
|
|
2378
|
+
if (opts.dryRun) {
|
|
2379
|
+
info(`Would create scripts/${category ? category + "/" : ""}${name}/`);
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
const r = scaffoldScript({
|
|
2383
|
+
name,
|
|
2384
|
+
category,
|
|
2385
|
+
description,
|
|
2386
|
+
baseDir
|
|
2387
|
+
});
|
|
2388
|
+
for (const p of r.paths) step(p);
|
|
2389
|
+
ok(`Initialized script ${r.id}`);
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
if (kind === "profile") {
|
|
2393
|
+
await initProfile(name, description, opts, baseDir);
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2396
|
+
throw new Error(`Cannot init kind: ${kind}`);
|
|
2397
|
+
}
|
|
2398
|
+
async function initProfile(name, description, opts, baseDir) {
|
|
2399
|
+
const initKinds = opts.initKinds || {};
|
|
2400
|
+
const wantAll = opts.all;
|
|
2401
|
+
let wantSkills = wantAll || initKinds.skills !== void 0;
|
|
2402
|
+
let wantAgents = wantAll || initKinds.agents !== void 0;
|
|
2403
|
+
let wantRules = wantAll || initKinds.rules !== void 0;
|
|
2404
|
+
let wantScripts = wantAll || initKinds.scripts !== void 0;
|
|
2405
|
+
if (!wantSkills && !wantAgents && !wantRules && !wantScripts && !opts.yes && !isQuiet()) {
|
|
2406
|
+
const res = await clack.multiselect({
|
|
2407
|
+
message: "What should this profile include?",
|
|
2408
|
+
options: [
|
|
2409
|
+
{
|
|
2410
|
+
value: "skills",
|
|
2411
|
+
label: "Skills"
|
|
2412
|
+
},
|
|
2413
|
+
{
|
|
2414
|
+
value: "agents",
|
|
2415
|
+
label: "Agents"
|
|
2416
|
+
},
|
|
2417
|
+
{
|
|
2418
|
+
value: "rules",
|
|
2419
|
+
label: "Rules"
|
|
2420
|
+
},
|
|
2421
|
+
{
|
|
2422
|
+
value: "scripts",
|
|
2423
|
+
label: "Scripts"
|
|
2424
|
+
}
|
|
2425
|
+
],
|
|
2426
|
+
required: false
|
|
2427
|
+
});
|
|
2428
|
+
if (clack.isCancel(res)) {
|
|
2429
|
+
outro("Cancelled.");
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
const set = new Set(res);
|
|
2433
|
+
wantSkills = set.has("skills");
|
|
2434
|
+
wantAgents = set.has("agents");
|
|
2435
|
+
wantRules = set.has("rules");
|
|
2436
|
+
wantScripts = set.has("scripts");
|
|
2437
|
+
}
|
|
2438
|
+
const agentList = opts.agents.length ? (await resolveAgentList(opts, "install")).map((a) => a.name) : ["cursor"];
|
|
2439
|
+
const skills = [];
|
|
2440
|
+
const rules = [];
|
|
2441
|
+
const agentsArtifacts = [];
|
|
2442
|
+
const scripts = [];
|
|
2443
|
+
async function fill(flag, enabled, k, into) {
|
|
2444
|
+
if (!enabled) return;
|
|
2445
|
+
const src = typeof flag === "string" ? flag : void 0;
|
|
2446
|
+
if (src) {
|
|
2447
|
+
const picked = await pickFromSource(k, src, `Select ${k}s for profile`, opts);
|
|
2448
|
+
into.push(...picked);
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2451
|
+
if (isQuiet() || opts.yes) return;
|
|
2452
|
+
const ask = await promptText(`Source for ${k}s (author/repo, URL, or local path — empty to skip)`);
|
|
2453
|
+
if (!ask) return;
|
|
2454
|
+
const picked = await pickFromSource(k, ask, `Select ${k}s for profile`, opts);
|
|
2455
|
+
into.push(...picked);
|
|
2456
|
+
}
|
|
2457
|
+
await fill(initKinds.skills, wantSkills, "skill", skills);
|
|
2458
|
+
await fill(initKinds.agents, wantAgents, "agent", agentsArtifacts);
|
|
2459
|
+
await fill(initKinds.rules, wantRules, "rule", rules);
|
|
2460
|
+
await fill(initKinds.scripts, wantScripts, "script", scripts);
|
|
2461
|
+
if (opts.dryRun) {
|
|
2462
|
+
info(`Would write profiles/${name}.yaml with ${skills.length} skills, ${agentsArtifacts.length} agents, ${rules.length} rules, ${scripts.length} scripts`);
|
|
2463
|
+
return;
|
|
2464
|
+
}
|
|
2465
|
+
const r = scaffoldProfile({
|
|
2466
|
+
name,
|
|
2467
|
+
description,
|
|
2468
|
+
agents: agentList,
|
|
2469
|
+
skills,
|
|
2470
|
+
rules,
|
|
2471
|
+
agentsArtifacts,
|
|
2472
|
+
scripts,
|
|
2473
|
+
baseDir
|
|
2474
|
+
});
|
|
2475
|
+
for (const p of r.paths) step(p);
|
|
2476
|
+
ok(`Initialized profile ${r.id}`);
|
|
2477
|
+
}
|
|
2478
|
+
function takeOptValue(argv, i) {
|
|
2479
|
+
const next = argv[i + 1];
|
|
2480
|
+
if (next && !next.startsWith("-")) return {
|
|
2481
|
+
value: next,
|
|
2482
|
+
next: i + 1
|
|
2483
|
+
};
|
|
2484
|
+
return {
|
|
2485
|
+
value: true,
|
|
2486
|
+
next: i
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
1520
2489
|
function parseArgs(argv) {
|
|
1521
2490
|
const opts = {
|
|
1522
2491
|
scope: "project",
|
|
2492
|
+
scopeExplicit: false,
|
|
1523
2493
|
agents: [],
|
|
1524
2494
|
copy: false,
|
|
1525
2495
|
dryRun: false,
|
|
1526
2496
|
yes: false,
|
|
1527
|
-
all: false
|
|
2497
|
+
all: false,
|
|
2498
|
+
initKinds: {}
|
|
1528
2499
|
};
|
|
1529
2500
|
const positional = [];
|
|
1530
2501
|
const flags = {};
|
|
@@ -1532,15 +2503,19 @@ function parseArgs(argv) {
|
|
|
1532
2503
|
const a = argv[i];
|
|
1533
2504
|
if (a === "-g" || a === "--global") {
|
|
1534
2505
|
opts.scope = "global";
|
|
2506
|
+
opts.scopeExplicit = true;
|
|
1535
2507
|
flags.global = true;
|
|
1536
|
-
} else if (a === "--project") {
|
|
2508
|
+
} else if (a === "-p" || a === "--project") {
|
|
1537
2509
|
opts.scope = "project";
|
|
2510
|
+
opts.scopeExplicit = true;
|
|
1538
2511
|
flags.project = true;
|
|
1539
2512
|
} else if (a === "-y" || a === "--yes") {
|
|
1540
2513
|
opts.yes = true;
|
|
1541
2514
|
flags.yes = true;
|
|
1542
|
-
} else if (a === "--dir") {
|
|
2515
|
+
} else if (a === "-d" || a === "--dir") {
|
|
1543
2516
|
opts.dir = argv[++i];
|
|
2517
|
+
opts.scope = "project";
|
|
2518
|
+
opts.scopeExplicit = true;
|
|
1544
2519
|
flags.dir = opts.dir;
|
|
1545
2520
|
} else if (a === "-a" || a === "--agent") {
|
|
1546
2521
|
opts.agents.push(argv[++i]);
|
|
@@ -1554,6 +2529,38 @@ function parseArgs(argv) {
|
|
|
1554
2529
|
} else if (a === "--all") {
|
|
1555
2530
|
opts.all = true;
|
|
1556
2531
|
flags.all = true;
|
|
2532
|
+
} else if (a === "--description") {
|
|
2533
|
+
opts.description = argv[++i];
|
|
2534
|
+
flags.description = opts.description;
|
|
2535
|
+
} else if (a === "--alwaysApply" || a === "--always-apply") {
|
|
2536
|
+
opts.alwaysApply = true;
|
|
2537
|
+
flags.alwaysApply = true;
|
|
2538
|
+
} else if (a === "--reference") {
|
|
2539
|
+
opts.reference = true;
|
|
2540
|
+
flags.reference = true;
|
|
2541
|
+
} else if (a === "--no-reference") {
|
|
2542
|
+
opts.reference = false;
|
|
2543
|
+
flags.reference = false;
|
|
2544
|
+
} else if (a === "--skills") {
|
|
2545
|
+
const { value, next } = takeOptValue(argv, i);
|
|
2546
|
+
opts.initKinds.skills = value;
|
|
2547
|
+
i = next;
|
|
2548
|
+
flags.skills = true;
|
|
2549
|
+
} else if (a === "--agents") {
|
|
2550
|
+
const { value, next } = takeOptValue(argv, i);
|
|
2551
|
+
opts.initKinds.agents = value;
|
|
2552
|
+
i = next;
|
|
2553
|
+
flags.agents = true;
|
|
2554
|
+
} else if (a === "--rules") {
|
|
2555
|
+
const { value, next } = takeOptValue(argv, i);
|
|
2556
|
+
opts.initKinds.rules = value;
|
|
2557
|
+
i = next;
|
|
2558
|
+
flags.rules = true;
|
|
2559
|
+
} else if (a === "--scripts") {
|
|
2560
|
+
const { value, next } = takeOptValue(argv, i);
|
|
2561
|
+
opts.initKinds.scripts = value;
|
|
2562
|
+
i = next;
|
|
2563
|
+
flags.scripts = true;
|
|
1557
2564
|
} else if (a === "-v" || a === "--version") flags.version = true;
|
|
1558
2565
|
else if (a === "-h" || a === "--help") flags.help = true;
|
|
1559
2566
|
else positional.push(a);
|
|
@@ -1599,7 +2606,11 @@ async function runUpdateSelf() {
|
|
|
1599
2606
|
function runNpmInstall(spec) {
|
|
1600
2607
|
spinner(`Updating agentry CLI`, () => {
|
|
1601
2608
|
try {
|
|
1602
|
-
|
|
2609
|
+
execFileSync("npm", [
|
|
2610
|
+
"install",
|
|
2611
|
+
"-g",
|
|
2612
|
+
spec
|
|
2613
|
+
], { stdio: "inherit" });
|
|
1603
2614
|
} catch {
|
|
1604
2615
|
err("Update failed. Once published, run: npm install -g " + spec);
|
|
1605
2616
|
process.exit(1);
|
|
@@ -1609,7 +2620,11 @@ function runNpmInstall(spec) {
|
|
|
1609
2620
|
function runUninstall() {
|
|
1610
2621
|
spinner("Uninstalling agentry CLI", () => {
|
|
1611
2622
|
try {
|
|
1612
|
-
|
|
2623
|
+
execFileSync("npm", [
|
|
2624
|
+
"uninstall",
|
|
2625
|
+
"-g",
|
|
2626
|
+
name
|
|
2627
|
+
], { stdio: "inherit" });
|
|
1613
2628
|
} catch {
|
|
1614
2629
|
err("Uninstall failed. Try: npm uninstall -g " + name);
|
|
1615
2630
|
process.exit(1);
|
|
@@ -1650,12 +2665,21 @@ async function main() {
|
|
|
1650
2665
|
return;
|
|
1651
2666
|
}
|
|
1652
2667
|
if (action === "list") {
|
|
1653
|
-
|
|
1654
|
-
|
|
2668
|
+
const a1 = positional[1];
|
|
2669
|
+
const a2 = positional[2];
|
|
2670
|
+
const a3 = positional[3];
|
|
2671
|
+
if (!a1) {
|
|
2672
|
+
await cmdList(void 0, void 0, void 0, opts);
|
|
1655
2673
|
return;
|
|
1656
2674
|
}
|
|
1657
|
-
const
|
|
1658
|
-
|
|
2675
|
+
const k1 = normalizeKind(a1);
|
|
2676
|
+
if (isKind(k1) && !isExplicitSource(a1) && !/^[\w.-]+\/[\w.-]+$/.test(a1)) {
|
|
2677
|
+
await cmdList(void 0, k1, a2, opts);
|
|
2678
|
+
return;
|
|
2679
|
+
}
|
|
2680
|
+
const kind = a2 ? normalizeKind(a2) : void 0;
|
|
2681
|
+
if (kind && kind !== "all" && !isKind(kind)) throw new Error(`Unknown kind: ${kind}`);
|
|
2682
|
+
await cmdList(a1, kind && isKind(kind) ? kind : void 0, a3, opts);
|
|
1659
2683
|
return;
|
|
1660
2684
|
}
|
|
1661
2685
|
if (action === "add") {
|
|
@@ -1673,7 +2697,13 @@ async function main() {
|
|
|
1673
2697
|
const kind = positional[1] ? normalizeKind(positional[1]) : void 0;
|
|
1674
2698
|
if (!kind) throw new Error("Missing kind. e.g. agentry remove skills enhance-prompt");
|
|
1675
2699
|
if (kind !== "all" && !isKind(kind)) throw new Error(`Unknown kind: ${kind}`);
|
|
1676
|
-
await cmdRemove(kind, positional[2], opts);
|
|
2700
|
+
await cmdRemove(kind, positional[2], positional[3], opts);
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2703
|
+
if (action === "init") {
|
|
2704
|
+
const kind = positional[1] ? normalizeKind(positional[1]) : void 0;
|
|
2705
|
+
if (!kind || !isKind(kind)) throw new Error("Missing kind. e.g. agentry init skill my-skill [category]");
|
|
2706
|
+
await cmdInit(kind, positional[2], positional[3], opts);
|
|
1677
2707
|
return;
|
|
1678
2708
|
}
|
|
1679
2709
|
err(`Unknown action: ${action}`);
|