agentic-awesome-skills 14.0.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.
@@ -0,0 +1,755 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawnSync } = require("child_process");
4
+ const path = require("path");
5
+ const fs = require("fs");
6
+ const os = require("os");
7
+ const sanitizeFilename = require("sanitize-filename");
8
+ const { getRealPath, isPathInside, resolveSafeRealPath } = require("../lib/symlink-safety");
9
+ const { listSkillIdsRecursive, readSkill } = require("../lib/skill-utils");
10
+ const packageMetadata = require("../../package.json");
11
+
12
+ const REPO = "https://github.com/sickn33/agentic-awesome-skills.git";
13
+ const HOME = process.env.HOME || process.env.USERPROFILE || "";
14
+ const INSTALL_MANIFEST_FILE = ".antigravity-install-manifest.json";
15
+ const DEFAULT_RELEASE_REF = packageMetadata.version ? `v${packageMetadata.version}` : null;
16
+
17
+ function resolveDir(p) {
18
+ if (!p) return null;
19
+ const s = p.replace(/^~($|\/)/, HOME + "$1");
20
+ const root = path.isAbsolute(s) ? path.parse(path.resolve(s)).root : process.cwd();
21
+ const sanitizedSegments = path
22
+ .resolve(s)
23
+ .slice(path.parse(path.resolve(s)).root.length)
24
+ .split(path.sep)
25
+ .filter(Boolean)
26
+ .map((segment) => {
27
+ const sanitized = sanitizeFilename(segment);
28
+ if (sanitized !== segment || !sanitized) {
29
+ throw new Error(`Unsafe path segment: ${segment}`);
30
+ }
31
+ return sanitized;
32
+ });
33
+ return path.resolve(root, ...sanitizedSegments);
34
+ }
35
+
36
+ function parseArgs() {
37
+ const a = process.argv.slice(2);
38
+ let pathArg = null;
39
+ let versionArg = null;
40
+ let tagArg = null;
41
+ let riskArg = null;
42
+ let categoryArg = null;
43
+ let tagsArg = null;
44
+ let cursor = false,
45
+ claude = false,
46
+ gemini = false,
47
+ codex = false,
48
+ antigravity = false,
49
+ agy = false,
50
+ kiro = false;
51
+
52
+ for (let i = 0; i < a.length; i++) {
53
+ if (a[i] === "--help" || a[i] === "-h") return { help: true };
54
+ if (a[i] === "--path" && a[i + 1]) {
55
+ pathArg = a[++i];
56
+ continue;
57
+ }
58
+ if (a[i] === "--version" && a[i + 1]) {
59
+ versionArg = a[++i];
60
+ continue;
61
+ }
62
+ if (a[i] === "--tag" && a[i + 1]) {
63
+ tagArg = a[++i];
64
+ continue;
65
+ }
66
+ if (a[i] === "--risk" && a[i + 1]) {
67
+ riskArg = a[++i];
68
+ continue;
69
+ }
70
+ if (a[i] === "--category" && a[i + 1]) {
71
+ categoryArg = a[++i];
72
+ continue;
73
+ }
74
+ if (a[i] === "--tags" && a[i + 1]) {
75
+ tagsArg = a[++i];
76
+ continue;
77
+ }
78
+ if (a[i] === "--cursor") {
79
+ cursor = true;
80
+ continue;
81
+ }
82
+ if (a[i] === "--claude") {
83
+ claude = true;
84
+ continue;
85
+ }
86
+ if (a[i] === "--gemini") {
87
+ gemini = true;
88
+ continue;
89
+ }
90
+ if (a[i] === "--codex") {
91
+ codex = true;
92
+ continue;
93
+ }
94
+ if (a[i] === "--antigravity") {
95
+ antigravity = true;
96
+ continue;
97
+ }
98
+ if (a[i] === "--agy") {
99
+ agy = true;
100
+ continue;
101
+ }
102
+ if (a[i] === "--kiro") {
103
+ kiro = true;
104
+ continue;
105
+ }
106
+ if (a[i] === "install") continue;
107
+ }
108
+
109
+ return {
110
+ pathArg,
111
+ versionArg,
112
+ tagArg,
113
+ riskArg,
114
+ categoryArg,
115
+ tagsArg,
116
+ cursor,
117
+ claude,
118
+ gemini,
119
+ codex,
120
+ antigravity,
121
+ agy,
122
+ kiro,
123
+ };
124
+ }
125
+
126
+ function getTargets(opts) {
127
+ const targets = [];
128
+ if (opts.pathArg) {
129
+ return [{ name: "Custom", path: resolveDir(opts.pathArg) }];
130
+ }
131
+ if (opts.cursor) {
132
+ targets.push({ name: "Cursor", path: path.join(HOME, ".cursor", "skills") });
133
+ }
134
+ if (opts.claude) {
135
+ targets.push({ name: "Claude Code", path: path.join(HOME, ".claude", "skills") });
136
+ }
137
+ if (opts.gemini) {
138
+ targets.push({ name: "Gemini CLI", path: path.join(HOME, ".gemini", "skills") });
139
+ }
140
+ if (opts.codex) {
141
+ const codexHome = process.env.CODEX_HOME;
142
+ const codexPath = codexHome
143
+ ? path.join(codexHome, "skills")
144
+ : path.join(HOME, ".codex", "skills");
145
+ targets.push({ name: "Codex CLI", path: codexPath });
146
+ }
147
+ if (opts.kiro) {
148
+ targets.push({ name: "Kiro", path: path.join(HOME, ".kiro", "skills") });
149
+ }
150
+ if (opts.antigravity) {
151
+ targets.push({ name: "Antigravity", path: path.join(HOME, ".agents", "skills") });
152
+ }
153
+ if (opts.agy) {
154
+ targets.push({
155
+ name: "Antigravity CLI",
156
+ path: path.join(HOME, ".gemini", "antigravity-cli", "skills"),
157
+ });
158
+ }
159
+ if (targets.length === 0) {
160
+ targets.push({ name: "Antigravity", path: path.join(HOME, ".agents", "skills") });
161
+ }
162
+ return targets;
163
+ }
164
+
165
+ function printHelp() {
166
+ console.log(`
167
+ agentic-awesome-skills — installer
168
+
169
+ npx agentic-awesome-skills [install] [options]
170
+
171
+ Shallow-clones the skills repo into your agent's skills directory.
172
+
173
+ Options:
174
+ --cursor Install to ~/.cursor/skills (Cursor)
175
+ --claude Install to ~/.claude/skills (Claude Code)
176
+ --gemini Install to ~/.gemini/skills (Gemini CLI)
177
+ --codex Install to ~/.codex/skills (Codex CLI)
178
+ --kiro Install to ~/.kiro/skills (Kiro CLI)
179
+ --antigravity Install to ~/.agents/skills (Antigravity IDE / OpenCode-style layout)
180
+ --agy Install to ~/.gemini/antigravity-cli/skills (Antigravity CLI slash commands)
181
+ --path <dir> Install to <dir> (default: ~/.agents/skills)
182
+ --risk <csv> Install only skills matching these risk labels
183
+ --category <csv> Install only skills matching these categories
184
+ --tags <csv> Install only skills matching these tags
185
+ --version <ver> Clone tag v<ver> (e.g. 4.6.0 -> v4.6.0)
186
+ --tag <tag> Clone this tag or branch (e.g. v4.6.0, main)
187
+
188
+ Examples:
189
+ npx agentic-awesome-skills
190
+ npx agentic-awesome-skills --cursor
191
+ npx agentic-awesome-skills --kiro
192
+ npx agentic-awesome-skills --antigravity
193
+ npx agentic-awesome-skills --agy
194
+ npx agentic-awesome-skills --path .agents/skills --category development,backend --risk safe,none
195
+ npx agentic-awesome-skills --path .agents/skills --tags debugging,typescript-legacy-
196
+ npx agentic-awesome-skills --version 4.6.0
197
+ npx agentic-awesome-skills --path ./my-skills
198
+ npx agentic-awesome-skills --claude --codex Install to multiple targets
199
+ `);
200
+ }
201
+
202
+ function normalizeFilterValue(value) {
203
+ return typeof value === "string" ? value.trim().toLowerCase() : "";
204
+ }
205
+
206
+ function uniqueValues(values) {
207
+ return [...new Set(values.filter(Boolean))];
208
+ }
209
+
210
+ function parseSelectorArg(raw) {
211
+ const include = [];
212
+ const exclude = [];
213
+
214
+ if (typeof raw !== "string" || !raw.trim()) {
215
+ return { include, exclude };
216
+ }
217
+
218
+ for (const token of raw.split(",")) {
219
+ const normalized = normalizeFilterValue(token);
220
+ if (!normalized) continue;
221
+ if (normalized.endsWith("-") && normalized.length > 1) {
222
+ exclude.push(normalized.slice(0, -1));
223
+ continue;
224
+ }
225
+ include.push(normalized);
226
+ }
227
+
228
+ const excludeValues = uniqueValues(exclude);
229
+ return {
230
+ include: uniqueValues(include).filter((value) => !excludeValues.includes(value)),
231
+ exclude: excludeValues,
232
+ };
233
+ }
234
+
235
+ function hasActiveSelector(selector) {
236
+ return selector.include.length > 0 || selector.exclude.length > 0;
237
+ }
238
+
239
+ function buildInstallSelectors(opts) {
240
+ return {
241
+ risk: parseSelectorArg(opts.riskArg),
242
+ category: parseSelectorArg(opts.categoryArg),
243
+ tags: parseSelectorArg(opts.tagsArg),
244
+ };
245
+ }
246
+
247
+ function hasInstallSelectors(selectors) {
248
+ return Object.values(selectors).some(hasActiveSelector);
249
+ }
250
+
251
+ function matchesScalarSelector(value, selector) {
252
+ const normalized = normalizeFilterValue(value);
253
+ if (normalized && selector.exclude.includes(normalized)) {
254
+ return false;
255
+ }
256
+ if (selector.include.length === 0) {
257
+ return true;
258
+ }
259
+ if (!normalized) {
260
+ return false;
261
+ }
262
+ return selector.include.includes(normalized);
263
+ }
264
+
265
+ function matchesArraySelector(values, selector) {
266
+ const normalizedValues = uniqueValues(
267
+ (Array.isArray(values) ? values : []).map((value) => normalizeFilterValue(value)),
268
+ );
269
+
270
+ if (normalizedValues.some((value) => selector.exclude.includes(value))) {
271
+ return false;
272
+ }
273
+ if (selector.include.length === 0) {
274
+ return true;
275
+ }
276
+ return normalizedValues.some((value) => selector.include.includes(value));
277
+ }
278
+
279
+ function matchesInstallSelectors(skill, selectors) {
280
+ return (
281
+ matchesScalarSelector(skill.risk, selectors.risk) &&
282
+ matchesScalarSelector(skill.category, selectors.category) &&
283
+ matchesArraySelector(skill.tags, selectors.tags)
284
+ );
285
+ }
286
+
287
+ function assertSafeDestinationPath(dest, destRoot) {
288
+ const rootPath = path.resolve(destRoot);
289
+ const destPath = path.resolve(dest);
290
+ const relative = path.relative(rootPath, destPath);
291
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
292
+ throw new Error(`Refusing destination outside install root: ${dest}`);
293
+ }
294
+
295
+ if (fs.existsSync(rootPath) && fs.lstatSync(rootPath).isSymbolicLink()) {
296
+ throw new Error(`Refusing symlinked install root: ${destRoot}`);
297
+ }
298
+
299
+ const realRoot = fs.existsSync(rootPath) ? getRealPath(rootPath) : rootPath;
300
+ let current = rootPath;
301
+ for (const part of relative.split(path.sep).filter(Boolean)) {
302
+ current = path.join(current, part);
303
+ if (!fs.existsSync(current)) {
304
+ break;
305
+ }
306
+ if (fs.lstatSync(current).isSymbolicLink()) {
307
+ throw new Error(`Refusing unsafe destination symlink component: ${current}`);
308
+ }
309
+ if (!isPathInside(realRoot, getRealPath(current))) {
310
+ throw new Error(`Refusing destination outside install root: ${current}`);
311
+ }
312
+ }
313
+ }
314
+
315
+ function copyRecursiveSync(src, dest, rootDir = src, skipGit = true, destRoot = dest) {
316
+ const stats = fs.lstatSync(src);
317
+ const resolvedSource = stats.isSymbolicLink()
318
+ ? resolveSafeRealPath(rootDir, src)
319
+ : src;
320
+
321
+ if (!resolvedSource) {
322
+ console.warn(` Skipping symlink outside cloned skills root: ${src}`);
323
+ return;
324
+ }
325
+
326
+ const resolvedStats = fs.statSync(resolvedSource);
327
+ if (fs.existsSync(dest) && fs.lstatSync(dest).isSymbolicLink()) {
328
+ throw new Error(`Skipping unsafe destination symlink: ${dest}`);
329
+ }
330
+ assertSafeDestinationPath(dest, destRoot);
331
+ if (resolvedStats.isDirectory()) {
332
+ if (!fs.existsSync(dest)) {
333
+ fs.mkdirSync(dest, { recursive: true });
334
+ }
335
+ const dir = fs.opendirSync(resolvedSource);
336
+ try {
337
+ for (;;) {
338
+ const child = dir.readSync();
339
+ if (!child) break;
340
+ if (skipGit && child.name === ".git") continue;
341
+ copyRecursiveSync(
342
+ path.join(resolvedSource, child.name),
343
+ path.join(dest, child.name),
344
+ rootDir,
345
+ skipGit,
346
+ destRoot,
347
+ );
348
+ }
349
+ } finally {
350
+ dir.closeSync();
351
+ }
352
+ } else {
353
+ fs.cpSync(resolvedSource, dest);
354
+ }
355
+ }
356
+
357
+ /** Copy contents of repo's skills/ into target so each skill is target/skill-name/ (for Claude Code etc.). */
358
+ function getInstallEntries(tempDir, selectors = buildInstallSelectors({})) {
359
+ const repoSkills = path.join(tempDir, "skills");
360
+ if (!fs.existsSync(repoSkills)) {
361
+ console.error("Cloned repo has no skills/ directory.");
362
+ process.exit(1);
363
+ }
364
+
365
+ const skillEntries = listSkillIdsRecursive(repoSkills);
366
+ const filteredEntries = hasInstallSelectors(selectors)
367
+ ? skillEntries.filter((skillId) => matchesInstallSelectors(readSkill(repoSkills, skillId), selectors))
368
+ : skillEntries;
369
+
370
+ if (hasInstallSelectors(selectors) && filteredEntries.length === 0) {
371
+ console.error("No skills matched the requested --risk/--category/--tags filters.");
372
+ process.exit(1);
373
+ }
374
+
375
+ const entries = [...filteredEntries];
376
+ if (fs.existsSync(path.join(tempDir, "docs"))) {
377
+ entries.push("docs");
378
+ }
379
+ return entries;
380
+ }
381
+
382
+ function installSkillsIntoTarget(tempDir, target, installEntries) {
383
+ const repoSkills = path.join(tempDir, "skills");
384
+ installEntries.forEach((name) => {
385
+ const destName = normalizeInstallEntry(name);
386
+ if (destName === "docs") {
387
+ const repoDocs = path.join(tempDir, "docs");
388
+ const docsDest = path.join(target, "docs");
389
+ if (!fs.existsSync(docsDest)) fs.mkdirSync(docsDest, { recursive: true });
390
+ copyRecursiveSync(repoDocs, docsDest, repoDocs, true, target);
391
+ return;
392
+ }
393
+ const src = path.join(repoSkills, normalizeSourceEntry(name));
394
+ const dest = path.join(target, destName);
395
+ copyRecursiveSync(src, dest, repoSkills, true, target);
396
+ });
397
+ }
398
+
399
+ function normalizeInstallEntry(entry) {
400
+ if (entry === "docs") {
401
+ return entry;
402
+ }
403
+ const normalized = typeof entry === "string" && entry.startsWith("skills/")
404
+ ? entry.slice("skills/".length)
405
+ : entry;
406
+ if (typeof normalized !== "string") {
407
+ return normalized;
408
+ }
409
+ const parts = normalized.split(/[\\/]+/);
410
+ if (
411
+ path.isAbsolute(normalized) ||
412
+ /[\\/]{2,}/.test(normalized) ||
413
+ parts.some((part) => !part || part === "." || part === "..")
414
+ ) {
415
+ throw new Error(`Unsafe install entry: ${entry}`);
416
+ }
417
+ return parts.join(path.sep);
418
+ }
419
+
420
+ function normalizeSourceEntry(entry) {
421
+ if (entry === "docs") {
422
+ return entry;
423
+ }
424
+ if (typeof entry !== "string") {
425
+ return entry;
426
+ }
427
+ const parts = entry.split(/[\\/]+/);
428
+ if (
429
+ path.isAbsolute(entry) ||
430
+ /[\\/]{2,}/.test(entry) ||
431
+ parts.some((part) => !part || part === "." || part === "..")
432
+ ) {
433
+ throw new Error(`Unsafe install source entry: ${entry}`);
434
+ }
435
+ return parts.join(path.sep);
436
+ }
437
+
438
+ function getManagedEntries(installEntries, target = {}) {
439
+ return installEntries.map(normalizeInstallEntry);
440
+ }
441
+
442
+ function resolveManagedPath(targetPath, entry) {
443
+ const normalizedEntry = normalizeInstallEntry(entry);
444
+ const resolvedTargetPath = path.resolve(targetPath);
445
+ const candidate = path.resolve(targetPath, normalizedEntry);
446
+ const relative = path.relative(resolvedTargetPath, candidate);
447
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
448
+ return null;
449
+ }
450
+ return candidate;
451
+ }
452
+
453
+ function resolveInstallManifestPath(targetPath) {
454
+ const manifestPath = path.join(targetPath, INSTALL_MANIFEST_FILE);
455
+ assertSafeDestinationPath(manifestPath, targetPath);
456
+ return manifestPath;
457
+ }
458
+
459
+ function readInstallManifest(targetPath) {
460
+ const manifestPath = resolveInstallManifestPath(targetPath);
461
+ if (!fs.existsSync(manifestPath)) {
462
+ return [];
463
+ }
464
+ let fd = null;
465
+ try {
466
+ fd = fs.openSync(manifestPath, "r");
467
+ const parsed = JSON.parse(fs.readFileSync(fd, "utf8"));
468
+ if (!parsed || !Array.isArray(parsed.entries)) {
469
+ return [];
470
+ }
471
+ return parsed.entries.filter((entry) => typeof entry === "string");
472
+ } catch (error) {
473
+ console.warn(` Ignoring invalid install manifest at ${manifestPath}`);
474
+ return [];
475
+ } finally {
476
+ if (fd !== null) {
477
+ fs.closeSync(fd);
478
+ }
479
+ }
480
+ }
481
+
482
+ function writeInstallManifest(targetPath, installEntries) {
483
+ const manifestPath = resolveInstallManifestPath(targetPath);
484
+ const normalizedEntries = [...new Set(installEntries.map(normalizeInstallEntry).filter(Boolean))].sort();
485
+ const manifest = JSON.stringify(
486
+ {
487
+ schemaVersion: 1,
488
+ updatedAt: new Date().toISOString(),
489
+ entries: normalizedEntries,
490
+ },
491
+ null,
492
+ 2,
493
+ ) + "\n";
494
+ const fd = fs.openSync(manifestPath, "w", 0o600);
495
+ try {
496
+ fs.writeFileSync(fd, manifest, "utf8");
497
+ } finally {
498
+ fs.closeSync(fd);
499
+ }
500
+ }
501
+
502
+ function pruneRemovedEntries(targetPath, previousEntries, installEntries) {
503
+ const next = new Set(installEntries.map(normalizeInstallEntry));
504
+ for (const entry of previousEntries) {
505
+ const normalizedEntry = normalizeInstallEntry(entry);
506
+ if (next.has(normalizedEntry)) {
507
+ continue;
508
+ }
509
+ const candidate = resolveManagedPath(targetPath, entry);
510
+ if (!candidate) {
511
+ console.warn(` Skipping unsafe managed entry path from manifest: ${entry}`);
512
+ continue;
513
+ }
514
+ assertSafeDestinationPath(candidate, targetPath);
515
+ fs.rmSync(candidate, { recursive: true, force: true });
516
+ console.log(` Removed stale managed entry: ${normalizedEntry}`);
517
+ }
518
+ }
519
+
520
+ function ensureTargetIsDirectory(targetPath) {
521
+ if (!fs.existsSync(targetPath)) {
522
+ return;
523
+ }
524
+ const stats = fs.lstatSync(targetPath);
525
+ if (stats.isDirectory()) {
526
+ return;
527
+ }
528
+ if (stats.isSymbolicLink()) {
529
+ try {
530
+ if (fs.statSync(targetPath).isDirectory()) {
531
+ return;
532
+ }
533
+ } catch (error) {
534
+ // Fall through to the error below for dangling links or non-directory targets.
535
+ }
536
+ }
537
+ console.error(` Install path exists but is not a directory: ${targetPath}`);
538
+ process.exit(1);
539
+ }
540
+
541
+ function isSafeGitRef(ref) {
542
+ return (
543
+ typeof ref === "string" &&
544
+ ref.length > 0 &&
545
+ ref.length <= 128 &&
546
+ /^[A-Za-z0-9._/-]+$/.test(ref) &&
547
+ !ref.startsWith("-") &&
548
+ !ref.startsWith("/") &&
549
+ !ref.endsWith("/") &&
550
+ !ref.endsWith(".") &&
551
+ !ref.includes("..") &&
552
+ !ref.includes("//") &&
553
+ !ref.includes("@{") &&
554
+ ref !== "@" &&
555
+ !ref.split("/").some((part) => part.endsWith(".lock"))
556
+ );
557
+ }
558
+
559
+ function assertSafeGitRef(ref) {
560
+ if (!isSafeGitRef(ref)) {
561
+ throw new Error(`Unsafe git ref: ${ref}`);
562
+ }
563
+ }
564
+
565
+ function run(cmd, args, opts = {}) {
566
+ const r = spawnSync(cmd, args, { stdio: "inherit", ...opts });
567
+ if (r.status !== 0) process.exit(r.status == null ? 1 : r.status);
568
+ }
569
+
570
+ function buildCloneArgs(repo, tempDir, ref = null) {
571
+ const args = ["clone", "--depth", "1"];
572
+ if (ref) {
573
+ assertSafeGitRef(ref);
574
+ args.push("--branch", ref);
575
+ }
576
+ args.push(repo, tempDir);
577
+ return args;
578
+ }
579
+
580
+ function resolveInstallRef(opts) {
581
+ if (opts.tagArg) {
582
+ return opts.tagArg;
583
+ }
584
+ if (opts.versionArg) {
585
+ return opts.versionArg.startsWith("v") ? opts.versionArg : `v${opts.versionArg}`;
586
+ }
587
+ return DEFAULT_RELEASE_REF;
588
+ }
589
+
590
+ function installForTarget(tempDir, target, selectors = buildInstallSelectors({})) {
591
+ if (fs.existsSync(target.path)) {
592
+ ensureTargetIsDirectory(target.path);
593
+ const targetStats = fs.lstatSync(target.path);
594
+ if (targetStats.isSymbolicLink()) {
595
+ console.error(
596
+ ` Refusing to migrate or update through symlinked target: ${target.path}`,
597
+ );
598
+ console.error(" Choose a real directory path, or replace the symlink manually before retrying.");
599
+ process.exit(1);
600
+ }
601
+ const gitDir = path.join(target.path, ".git");
602
+ if (fs.existsSync(gitDir)) {
603
+ console.log(` Migrating from full-repo install to skills-only layout…`);
604
+ const backupPath = `${target.path}_backup_${Date.now()}`;
605
+ try {
606
+ fs.cpSync(target.path, backupPath, { recursive: true });
607
+ fs.rmSync(target.path, { recursive: true, force: true });
608
+ console.log(` ⚠️ Safety Backup created at: ${backupPath}`);
609
+ fs.mkdirSync(target.path, { recursive: true, mode: targetStats.mode });
610
+ } catch (err) {
611
+ console.error(` Migration Error: ${err.message}`);
612
+ process.exit(1);
613
+ }
614
+ } else {
615
+ console.log(` Updating existing install at ${target.path}…`);
616
+ }
617
+ } else {
618
+ const parent = path.dirname(target.path);
619
+ if (!fs.existsSync(parent)) {
620
+ try {
621
+ fs.mkdirSync(parent, { recursive: true });
622
+ } catch (e) {
623
+ console.error(` Cannot create parent directory: ${parent}`, e.message);
624
+ process.exit(1);
625
+ }
626
+ }
627
+ fs.mkdirSync(target.path, { recursive: true });
628
+ }
629
+
630
+ const installEntries = getInstallEntries(tempDir, selectors);
631
+ const managedEntries = getManagedEntries(installEntries, target);
632
+ const previousEntries = readInstallManifest(target.path);
633
+ pruneRemovedEntries(target.path, previousEntries, managedEntries);
634
+ installSkillsIntoTarget(tempDir, target.path, installEntries);
635
+ writeInstallManifest(target.path, managedEntries);
636
+ console.log(` ✓ Installed to ${target.path}`);
637
+ }
638
+
639
+ function isOpenCodeStylePath(targetPath) {
640
+ const normalizedPath = path.normalize(targetPath);
641
+ return normalizedPath.endsWith(path.join(".agents", "skills"));
642
+ }
643
+
644
+ function getPostInstallMessages(targets, selectors = buildInstallSelectors({})) {
645
+ const messages = [
646
+ "Pick a bundle in docs/users/bundles.md and use @skill-name in your AI assistant.",
647
+ ];
648
+
649
+ if (targets.some((target) => target.name === "Antigravity")) {
650
+ messages.push(
651
+ "If Antigravity hits context/truncation limits, see docs/users/agent-overload-recovery.md",
652
+ );
653
+ messages.push(
654
+ "For the agy CLI slash-command menu, install the Antigravity CLI layout with --agy.",
655
+ );
656
+ messages.push(
657
+ "For clone-based installs, use scripts/activate-skills.sh or scripts/activate-skills.bat",
658
+ );
659
+ }
660
+
661
+ if (targets.some((target) => target.name === "Antigravity CLI")) {
662
+ messages.push(
663
+ "Restart agy and type /skills or /<skill-name> to load installed Antigravity CLI skills.",
664
+ );
665
+ }
666
+
667
+ if (targets.some((target) => isOpenCodeStylePath(target.path))) {
668
+ const baseMessage =
669
+ "For Antigravity 2.0, OpenCode, or other .agents/skills installs, prefer a reduced install with --risk, --category, or --tags to avoid context overload.";
670
+ messages.push(baseMessage);
671
+ if (!hasInstallSelectors(selectors)) {
672
+ messages.push(
673
+ "Example: npx agentic-awesome-skills --path .agents/skills --category development,backend --risk safe,none",
674
+ );
675
+ }
676
+ }
677
+
678
+ return messages;
679
+ }
680
+
681
+ function main() {
682
+ const opts = parseArgs();
683
+ const selectors = buildInstallSelectors(opts);
684
+ const ref = resolveInstallRef(opts);
685
+
686
+ if (opts.help) {
687
+ printHelp();
688
+ return;
689
+ }
690
+
691
+ const targets = getTargets(opts);
692
+ if (!targets.length || !HOME) {
693
+ console.error(
694
+ "Could not resolve home directory. Use --path <absolute-path>.",
695
+ );
696
+ process.exit(1);
697
+ }
698
+
699
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ag-skills-"));
700
+
701
+ try {
702
+ console.log("Cloning repository…");
703
+ if (ref) {
704
+ console.log(`Cloning repository at ${ref}…`);
705
+ }
706
+ run("git", buildCloneArgs(REPO, tempDir, ref));
707
+
708
+ console.log(`\nInstalling for ${targets.length} target(s):`);
709
+ for (const target of targets) {
710
+ console.log(`\n${target.name}:`);
711
+ installForTarget(tempDir, target, selectors);
712
+ }
713
+
714
+ for (const message of getPostInstallMessages(targets, selectors)) {
715
+ console.log(`\n${message}`);
716
+ }
717
+ } finally {
718
+ try {
719
+ if (fs.existsSync(tempDir)) {
720
+ if (fs.rmSync) {
721
+ fs.rmSync(tempDir, { recursive: true, force: true });
722
+ } else {
723
+ fs.rmdirSync(tempDir, { recursive: true });
724
+ }
725
+ }
726
+ } catch (e) {
727
+ // ignore cleanup errors
728
+ }
729
+ }
730
+ }
731
+
732
+ if (require.main === module) {
733
+ main();
734
+ }
735
+
736
+ module.exports = {
737
+ copyRecursiveSync,
738
+ getPostInstallMessages,
739
+ buildCloneArgs,
740
+ buildInstallSelectors,
741
+ getInstallEntries,
742
+ getManagedEntries,
743
+ installSkillsIntoTarget,
744
+ installForTarget,
745
+ isSafeGitRef,
746
+ isOpenCodeStylePath,
747
+ main,
748
+ matchesInstallSelectors,
749
+ normalizeInstallEntry,
750
+ parseSelectorArg,
751
+ pruneRemovedEntries,
752
+ readInstallManifest,
753
+ resolveInstallRef,
754
+ writeInstallManifest,
755
+ };