@nicmeriano/agent-skills 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.mjs +270 -0
- package/package.json +30 -0
package/index.mjs
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
// ── Config ──────────────────────────────────────────────────────────
|
|
7
|
+
const REPO = "nicmeriano/agent-skills";
|
|
8
|
+
const BRANCH = "main";
|
|
9
|
+
const BASE_URL = `https://raw.githubusercontent.com/${REPO}/${BRANCH}`;
|
|
10
|
+
const API_URL = `https://api.github.com/repos/${REPO}/contents/bundles?ref=${BRANCH}`;
|
|
11
|
+
|
|
12
|
+
// ── Parse args ──────────────────────────────────────────────────────
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
const subcommand = args[0] === "install" ? args.shift() : null;
|
|
15
|
+
|
|
16
|
+
const flags = {
|
|
17
|
+
yes: false,
|
|
18
|
+
dryRun: false,
|
|
19
|
+
scope: "global",
|
|
20
|
+
agents: "claude-code",
|
|
21
|
+
method: "symlink",
|
|
22
|
+
bundle: null,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < args.length; i++) {
|
|
26
|
+
switch (args[i]) {
|
|
27
|
+
case "-y":
|
|
28
|
+
flags.yes = true;
|
|
29
|
+
break;
|
|
30
|
+
case "-g":
|
|
31
|
+
flags.scope = "global";
|
|
32
|
+
break;
|
|
33
|
+
case "-p":
|
|
34
|
+
flags.scope = "project";
|
|
35
|
+
break;
|
|
36
|
+
case "-a":
|
|
37
|
+
flags.agents = args[++i];
|
|
38
|
+
break;
|
|
39
|
+
case "--copy":
|
|
40
|
+
flags.method = "copy";
|
|
41
|
+
break;
|
|
42
|
+
case "--dry-run":
|
|
43
|
+
flags.dryRun = true;
|
|
44
|
+
break;
|
|
45
|
+
case "--bundle":
|
|
46
|
+
flags.bundle = args[++i];
|
|
47
|
+
break;
|
|
48
|
+
case "-h":
|
|
49
|
+
case "--help":
|
|
50
|
+
console.log(`Usage: npx @nicmeriano/agent-skills install [options]
|
|
51
|
+
|
|
52
|
+
Install agent skills from a remote manifest.
|
|
53
|
+
|
|
54
|
+
Options:
|
|
55
|
+
-y Non-interactive, use defaults
|
|
56
|
+
-g Global scope (default)
|
|
57
|
+
-p Project scope
|
|
58
|
+
-a AGENTS Comma-separated agents (default: claude-code)
|
|
59
|
+
--copy Copy instead of symlink
|
|
60
|
+
--dry-run Show what would be installed
|
|
61
|
+
--bundle NAME Install from a specific bundle
|
|
62
|
+
-h, --help Show this help
|
|
63
|
+
|
|
64
|
+
Examples:
|
|
65
|
+
npx @nicmeriano/agent-skills install # Interactive
|
|
66
|
+
npx @nicmeriano/agent-skills install -y # All skills, defaults
|
|
67
|
+
npx @nicmeriano/agent-skills install --bundle frontend
|
|
68
|
+
npx @nicmeriano/agent-skills install --dry-run`);
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
74
|
+
async function fetchJSON(url) {
|
|
75
|
+
const res = await fetch(url);
|
|
76
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
77
|
+
return res.json();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseSkills(manifest) {
|
|
81
|
+
return (manifest.skills ?? []).map((entry) => {
|
|
82
|
+
if (typeof entry === "string") {
|
|
83
|
+
return { source: entry, skill: null, label: entry };
|
|
84
|
+
}
|
|
85
|
+
const label = entry.skill
|
|
86
|
+
? `${entry.source} :: ${entry.skill}`
|
|
87
|
+
: entry.source;
|
|
88
|
+
return { source: entry.source, skill: entry.skill ?? null, label };
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function buildCmd(entry) {
|
|
93
|
+
const parts = ["npx", "-y", "skills", "add", entry.source, "--yes"];
|
|
94
|
+
if (flags.scope === "global") parts.push("--global");
|
|
95
|
+
if (flags.method === "copy") parts.push("--copy");
|
|
96
|
+
for (const agent of flags.agents.split(",")) {
|
|
97
|
+
parts.push("--agent", agent.trim());
|
|
98
|
+
}
|
|
99
|
+
if (entry.skill) parts.push("--skill", entry.skill);
|
|
100
|
+
return parts.join(" ");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function cancel(msg = "Cancelled.") {
|
|
104
|
+
p.cancel(msg);
|
|
105
|
+
process.exit(0);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── Main ────────────────────────────────────────────────────────────
|
|
109
|
+
p.intro("agent-skills");
|
|
110
|
+
const s = p.spinner();
|
|
111
|
+
|
|
112
|
+
// ── 1. Discover bundles ─────────────────────────────────────────────
|
|
113
|
+
let bundleNames = [];
|
|
114
|
+
|
|
115
|
+
if (!flags.bundle && !flags.yes) {
|
|
116
|
+
s.start("Discovering bundles");
|
|
117
|
+
try {
|
|
118
|
+
const contents = await fetchJSON(API_URL);
|
|
119
|
+
bundleNames = contents
|
|
120
|
+
.filter((f) => f.name.endsWith(".json"))
|
|
121
|
+
.map((f) => f.name.replace(/\.json$/, ""));
|
|
122
|
+
s.stop(`Found ${bundleNames.length} bundle(s)`);
|
|
123
|
+
} catch {
|
|
124
|
+
s.stop("No bundles found");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── 2. Pick bundle (interactive) ────────────────────────────────────
|
|
129
|
+
let manifestPath = "skills.json";
|
|
130
|
+
|
|
131
|
+
if (!flags.yes && !flags.dryRun && !flags.bundle) {
|
|
132
|
+
const bundleOptions = [
|
|
133
|
+
{ value: null, label: "All skills", hint: "skills.json" },
|
|
134
|
+
...bundleNames.map((name) => ({
|
|
135
|
+
value: name,
|
|
136
|
+
label: name,
|
|
137
|
+
hint: `bundles/${name}.json`,
|
|
138
|
+
})),
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
if (bundleOptions.length > 1) {
|
|
142
|
+
const picked = await p.select({
|
|
143
|
+
message: "Install from:",
|
|
144
|
+
options: bundleOptions,
|
|
145
|
+
});
|
|
146
|
+
if (p.isCancel(picked)) cancel();
|
|
147
|
+
if (picked) manifestPath = `bundles/${picked}.json`;
|
|
148
|
+
}
|
|
149
|
+
} else if (flags.bundle) {
|
|
150
|
+
manifestPath = `bundles/${flags.bundle}.json`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── 3. Fetch manifest ───────────────────────────────────────────────
|
|
154
|
+
s.start(`Fetching ${manifestPath}`);
|
|
155
|
+
|
|
156
|
+
let skillEntries;
|
|
157
|
+
try {
|
|
158
|
+
const manifest = await fetchJSON(`${BASE_URL}/${manifestPath}`);
|
|
159
|
+
skillEntries = parseSkills(manifest);
|
|
160
|
+
} catch {
|
|
161
|
+
s.stop(`Failed to fetch ${manifestPath}`);
|
|
162
|
+
cancel("Could not fetch manifest. Check that the repo is public.");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (skillEntries.length === 0) {
|
|
166
|
+
s.stop("No skills found");
|
|
167
|
+
cancel("Manifest is empty.");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
s.stop(`${skillEntries.length} skill(s) available`);
|
|
171
|
+
|
|
172
|
+
// ── 4. Pick skills (interactive) ────────────────────────────────────
|
|
173
|
+
let selected = skillEntries;
|
|
174
|
+
|
|
175
|
+
if (!flags.yes && !flags.dryRun) {
|
|
176
|
+
const picked = await p.multiselect({
|
|
177
|
+
message: "Select skills to install:",
|
|
178
|
+
options: skillEntries.map((e, i) => ({
|
|
179
|
+
value: i,
|
|
180
|
+
label: e.label,
|
|
181
|
+
})),
|
|
182
|
+
initialValues: skillEntries.map((_, i) => i),
|
|
183
|
+
required: true,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (p.isCancel(picked)) cancel();
|
|
187
|
+
selected = picked.map((i) => skillEntries[i]);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── 5. Configure options (interactive) ──────────────────────────────
|
|
191
|
+
if (!flags.yes && !flags.dryRun) {
|
|
192
|
+
const options = await p.group({
|
|
193
|
+
scope: () =>
|
|
194
|
+
p.select({
|
|
195
|
+
message: "Scope:",
|
|
196
|
+
options: [
|
|
197
|
+
{ value: "global", label: "global" },
|
|
198
|
+
{ value: "project", label: "project" },
|
|
199
|
+
],
|
|
200
|
+
initialValue: flags.scope,
|
|
201
|
+
}),
|
|
202
|
+
method: () =>
|
|
203
|
+
p.select({
|
|
204
|
+
message: "Method:",
|
|
205
|
+
options: [
|
|
206
|
+
{ value: "symlink", label: "symlink" },
|
|
207
|
+
{ value: "copy", label: "copy" },
|
|
208
|
+
],
|
|
209
|
+
initialValue: flags.method,
|
|
210
|
+
}),
|
|
211
|
+
agents: () =>
|
|
212
|
+
p.text({
|
|
213
|
+
message: "Agents:",
|
|
214
|
+
initialValue: flags.agents,
|
|
215
|
+
validate: (v) => (!v ? "At least one agent is required" : undefined),
|
|
216
|
+
}),
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (p.isCancel(options)) cancel();
|
|
220
|
+
flags.scope = options.scope;
|
|
221
|
+
flags.method = options.method;
|
|
222
|
+
flags.agents = options.agents;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ── 6. Install ──────────────────────────────────────────────────────
|
|
226
|
+
const total = selected.length;
|
|
227
|
+
let succeeded = 0;
|
|
228
|
+
const failed = [];
|
|
229
|
+
|
|
230
|
+
if (flags.dryRun) {
|
|
231
|
+
p.note(
|
|
232
|
+
selected.map((e) => `${e.label}\n → ${buildCmd(e)}`).join("\n\n"),
|
|
233
|
+
`Dry run — ${total} skill(s)`
|
|
234
|
+
);
|
|
235
|
+
} else {
|
|
236
|
+
s.start(`[1/${total}] ${selected[0].label}`);
|
|
237
|
+
|
|
238
|
+
for (let i = 0; i < total; i++) {
|
|
239
|
+
const entry = selected[i];
|
|
240
|
+
const cmd = buildCmd(entry);
|
|
241
|
+
const prefix = `[${i + 1}/${total}]`;
|
|
242
|
+
|
|
243
|
+
s.message = `${prefix} ${entry.label}`;
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
execSync(cmd, { stdio: "pipe", timeout: 120_000 });
|
|
247
|
+
succeeded++;
|
|
248
|
+
} catch {
|
|
249
|
+
failed.push(entry.label);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (failed.length === 0) {
|
|
254
|
+
s.stop(`${succeeded}/${total} skill(s) installed`);
|
|
255
|
+
} else {
|
|
256
|
+
s.stop(`${succeeded}/${total} installed, ${failed.length} failed`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ── 7. Summary ──────────────────────────────────────────────────────
|
|
261
|
+
if (flags.dryRun) {
|
|
262
|
+
p.outro(`${total} skill(s) would be installed.`);
|
|
263
|
+
} else if (failed.length > 0) {
|
|
264
|
+
p.log.warn(
|
|
265
|
+
`Failed:\n${failed.map((f) => ` - ${f}`).join("\n")}`
|
|
266
|
+
);
|
|
267
|
+
p.outro(`${succeeded}/${total} skill(s) installed.`);
|
|
268
|
+
} else {
|
|
269
|
+
p.outro(`All ${total} skill(s) installed successfully.`);
|
|
270
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nicmeriano/agent-skills",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Install agent skills from a remote manifest",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"agent-skills": "./index.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.mjs"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@clack/prompts": "^0.10.0"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/nicmeriano/agent-skills.git"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"agent",
|
|
25
|
+
"skills",
|
|
26
|
+
"claude",
|
|
27
|
+
"ai",
|
|
28
|
+
"cli"
|
|
29
|
+
]
|
|
30
|
+
}
|