install-agent-skill 1.2.4 → 1.2.7

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,103 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * install-agent-skill
4
+ * Vercel-Style CLI - Modular Entry Point v2.1
5
+ */
6
+
7
+ import { c, step, fatal, intro, outro } from "./lib/ui.js";
8
+ import { command, params, flags, VERSION } from "./lib/config.js";
9
+
10
+ // --- MAIN ---
11
+ async function main() {
12
+ // Basic command routing
13
+ let cmdModule;
14
+
15
+ try {
16
+ switch (command) {
17
+ case "list":
18
+ case "ls":
19
+ cmdModule = await import("./lib/commands/list.js");
20
+ await cmdModule.run();
21
+ break;
22
+ case "init":
23
+ cmdModule = await import("./lib/commands/init.js");
24
+ await cmdModule.run();
25
+ break;
26
+ case "install":
27
+ case "add":
28
+ case "i":
29
+ cmdModule = await import("./lib/commands/install.js");
30
+ await cmdModule.run(params[0]);
31
+ break;
32
+ case "uninstall":
33
+ case "remove":
34
+ case "rm":
35
+ cmdModule = await import("./lib/commands/uninstall.js");
36
+ await cmdModule.run(params[0]);
37
+ break;
38
+ case "update":
39
+ cmdModule = await import("./lib/commands/update.js");
40
+ await cmdModule.run(params[0]);
41
+ break;
42
+ case "lock":
43
+ cmdModule = await import("./lib/commands/lock.js");
44
+ await cmdModule.run();
45
+ break;
46
+ case "verify":
47
+ cmdModule = await import("./lib/commands/verify.js");
48
+ await cmdModule.run();
49
+ break;
50
+ case "doctor":
51
+ cmdModule = await import("./lib/commands/doctor.js");
52
+ await cmdModule.run();
53
+ break;
54
+ case "cache":
55
+ cmdModule = await import("./lib/commands/cache.js");
56
+ await cmdModule.run(params[0]);
57
+ break;
58
+ case "validate":
59
+ case "check":
60
+ cmdModule = await import("./lib/commands/validate.js");
61
+ await cmdModule.run(params[0]);
62
+ break;
63
+ case "analyze":
64
+ cmdModule = await import("./lib/commands/analyze.js");
65
+ await cmdModule.run(params[0]);
66
+ break;
67
+ case "info":
68
+ case "show":
69
+ cmdModule = await import("./lib/commands/info.js");
70
+ await cmdModule.run(params[0]);
71
+ break;
72
+ case "help":
73
+ case "--help":
74
+ case "-h":
75
+ cmdModule = await import("./lib/commands/help.js");
76
+ await cmdModule.run();
77
+ break;
78
+ case "--version":
79
+ case "-V":
80
+ console.log(VERSION);
81
+ break;
82
+ default:
83
+ // Handle direct install via org/repo syntax
84
+ if (command.includes("/")) {
85
+ cmdModule = await import("./lib/commands/install.js");
86
+ await cmdModule.run(command);
87
+ } else {
88
+ console.log(`Unknown command: ${command}`);
89
+ cmdModule = await import("./lib/commands/help.js");
90
+ await cmdModule.run();
91
+ }
92
+ }
93
+ } catch (err) {
94
+ console.error(c.red("\nError: " + err.message));
95
+ if (process.env.DEBUG) console.error(err.stack);
96
+ process.exit(1);
97
+ }
98
+ }
99
+
100
+ main().catch(err => {
101
+ console.error(c.red("\nFatal Error: " + err.message));
102
+ process.exit(1);
103
+ });
@@ -10,9 +10,9 @@ import os from "os";
10
10
  import { execSync } from "child_process";
11
11
  import crypto from "crypto";
12
12
  import prompts from "prompts";
13
- import { multiselect, isCancel, cancel, intro, outro, select, confirm } from "@clack/prompts";
13
+ import { multiselect, isCancel, cancel, intro, outro, select, confirm, log, spinner } from "@clack/prompts";
14
14
  import kleur from "kleur";
15
- import ora from "ora";
15
+
16
16
  import boxen from "boxen";
17
17
  import { createRequire } from "module";
18
18
 
@@ -250,24 +250,54 @@ async function runInstall(spec) {
250
250
  if (!spec) { fatal("Missing skill spec. Usage: add-skill <org/repo>"); return; }
251
251
  const { org, repo, skill: singleSkill, ref } = parseSkillSpec(spec);
252
252
  if (!org || !repo) { fatal("Invalid spec. Format: org/repo or org/repo#skill"); return; }
253
+
254
+ // Start Clack UI session
255
+ intro(c.bgCyan(c.black(" add-skill ")));
256
+
253
257
  const url = `https://github.com/${org}/${repo}.git`;
254
- stepLine();
255
- console.log(` ${c.gray(S.branch)} ${c.bgCyan().black(" skills ")}`);
256
- stepLine();
257
- step(`Source: ${c.cyan(url)}`, S.diamond, "green");
258
- stepLine();
259
- const spinner = ora({ text: "Cloning repository", prefixText: ` ${c.gray(S.branch)} ${c.cyan(S.diamondFilled)} `, color: "cyan" }).start();
258
+ log.info(`Source: ${c.cyan(url)}`);
259
+
260
+ const s = spinner();
261
+ s.start("Cloning repository");
262
+
260
263
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "add-skill-"));
261
- try { execSync(`git clone --depth=1 ${url} "${tmp}"`, { stdio: "pipe" }); if (ref) execSync(`git -C "${tmp}" checkout ${ref}`, { stdio: "pipe" }); } catch { spinner.stop(); console.log(` ${c.gray(S.branch)} ${c.red(S.cross)} ${c.red("Failed to clone")}`); fs.rmSync(tmp, { recursive: true, force: true }); return; }
262
- spinner.stop(); step("Repository cloned", S.check, "green");
264
+ try {
265
+ execSync(`git clone --depth=1 ${url} "${tmp}"`, { stdio: "pipe" });
266
+ if (ref) execSync(`git -C "${tmp}" checkout ${ref}`, { stdio: "pipe" });
267
+ } catch {
268
+ s.stop("Failed to clone", 1);
269
+ log.error("Failed to clone repository");
270
+ fs.rmSync(tmp, { recursive: true, force: true });
271
+ cancel("Operation cancelled");
272
+ return;
273
+ }
274
+ s.stop("Repository cloned");
275
+
263
276
  const skillsInRepo = [];
264
- for (const e of fs.readdirSync(tmp)) { const sp = path.join(tmp, e); if (fs.statSync(sp).isDirectory() && fs.existsSync(path.join(sp, "SKILL.md"))) { const m = parseSkillMdFrontmatter(path.join(sp, "SKILL.md")); skillsInRepo.push({ title: e + (m.description ? c.dim(` (${m.description.substring(0, 40)}...)`) : ""), value: e, selected: singleSkill ? e === singleSkill : true }); } }
265
- if (skillsInRepo.length === 0) { step(c.yellow("No valid skills found"), S.diamond, "yellow"); fs.rmSync(tmp, { recursive: true, force: true }); return; }
266
- stepLine(); step(`Found ${skillsInRepo.length} skills`, S.diamond); stepLine();
277
+ for (const e of fs.readdirSync(tmp)) {
278
+ const sp = path.join(tmp, e);
279
+ if (fs.statSync(sp).isDirectory() && fs.existsSync(path.join(sp, "SKILL.md"))) {
280
+ const m = parseSkillMdFrontmatter(path.join(sp, "SKILL.md"));
281
+ skillsInRepo.push({
282
+ title: e + (m.description ? c.dim(` (${m.description.substring(0, 40)}...)`) : ""),
283
+ value: e,
284
+ selected: singleSkill ? e === singleSkill : true
285
+ });
286
+ }
287
+ }
288
+
289
+ if (skillsInRepo.length === 0) {
290
+ log.warn("No valid skills found in repository");
291
+ fs.rmSync(tmp, { recursive: true, force: true });
292
+ cancel("Operation cancelled");
293
+ return;
294
+ }
295
+
296
+ log.info(`Found ${skillsInRepo.length} skills`);
267
297
 
268
298
  // Use @clack/prompts for multiselect
269
299
  const skillsR = await multiselect({
270
- message: 'Select skills to install',
300
+ message: 'Select skills to install (Press Space to select, Enter to continue)',
271
301
  options: skillsInRepo.map(s => ({
272
302
  label: s.title,
273
303
  value: s.value,
@@ -284,46 +314,66 @@ async function runInstall(spec) {
284
314
  }
285
315
 
286
316
  const selectedSkills = skillsR;
287
- if (selectedSkills.length === 0) { console.log(`\n ${c.yellow("Cancelled.")}`); fs.rmSync(tmp, { recursive: true, force: true }); return; }
317
+ if (selectedSkills.length === 0) {
318
+ log.warn("No skills selected");
319
+ fs.rmSync(tmp, { recursive: true, force: true });
320
+ cancel("Operation cancelled");
321
+ return;
322
+ }
288
323
 
324
+ // Agent selection
289
325
  stepLine();
290
- // Re-use standard prompts for scope for now, or could switch to clack select too,
291
- // but focusing on the requested "green square" UI which is multiselect.
292
- // To match style, let's use clack select for scope too.
326
+ step("Detected 5 agents", S.diamond);
293
327
 
294
- const scopeR = await select({
295
- message: 'Installation scope',
328
+ const agentsR = await multiselect({
329
+ message: 'Select agents to install skills to',
296
330
  options: [
297
- { label: 'Project (current directory)', value: 'project' },
298
- { label: 'Global', value: 'global' }
331
+ { label: 'Antigravity (.agent/skills)', value: 'antigravity', selected: true },
332
+ { label: c.dim('Claude Code'), value: 'claude', hint: c.dim('(not supported)') },
333
+ { label: c.dim('Codex'), value: 'codex', hint: c.dim('(not supported)') },
334
+ { label: c.dim('Gemini CLI'), value: 'gemini', hint: c.dim('(not supported)') },
335
+ { label: c.dim('Windsurf'), value: 'windsurf', hint: c.dim('(not supported)') }
299
336
  ],
300
- initialValue: 'project'
337
+ required: true
301
338
  });
302
339
 
303
- if (isCancel(scopeR)) {
340
+ if (isCancel(agentsR)) {
304
341
  cancel('Operation cancelled.');
305
342
  fs.rmSync(tmp, { recursive: true, force: true });
306
343
  return;
307
344
  }
308
345
 
309
- const targetScope = scopeR === "global" ? GLOBAL_DIR : WORKSPACE;
346
+ const selectedAgents = agentsR;
347
+ if (selectedAgents.includes('antigravity') === false) {
348
+ log.warn("Antigravity is currently the only supported agent. Selecting it automatically.");
349
+ }
350
+
351
+ // For now, we only support Antigravity, effectively just checking if we continue
352
+ const targetScope = WORKSPACE;
310
353
 
311
- stepLine(); step("Installation Summary", S.diamond); stepLine();
312
- let boxContent = ""; for (const sn of selectedSkills) boxContent += `${c.cyan(sn)}\n ${c.dim(targetScope)}\n\n`;
313
- const box = boxen(boxContent.trim(), { padding: 1, borderStyle: "round", borderColor: "gray", dimBorder: true });
314
- box.split("\n").forEach(l => console.log(` ${c.gray(S.branch)} ${l}`));
354
+ log.info("Installing skills...");
355
+ fs.mkdirSync(targetScope, { recursive: true });
356
+
357
+ // Install loop
358
+ const sInstall = spinner();
359
+ sInstall.start("Copying files");
315
360
 
316
- stepLine(); fs.mkdirSync(targetScope, { recursive: true });
317
361
  for (const sn of selectedSkills) {
318
362
  const src = path.join(tmp, sn), dest = path.join(targetScope, sn);
319
363
  if (fs.existsSync(dest)) fs.rmSync(dest, { recursive: true, force: true });
320
364
  fs.cpSync(src, dest, { recursive: true });
321
365
  const hash = merkleHash(dest);
322
366
  fs.writeFileSync(path.join(dest, ".skill-source.json"), JSON.stringify({ repo: `${org}/${repo}`, skill: sn, ref: ref || null, checksum: hash, installedAt: new Date().toISOString() }, null, 2));
323
- step(`Installed: ${c.bold(sn)}`, S.check, "green");
324
367
  }
368
+ sInstall.stop("Files copied");
369
+
370
+ for (const sn of selectedSkills) {
371
+ log.success(`Installed ${c.bold(sn)}`);
372
+ }
373
+
325
374
  fs.rmSync(tmp, { recursive: true, force: true });
326
- stepLine(); console.log(` ${c.cyan("Done!")}`); console.log();
375
+
376
+ outro("Done!");
327
377
  }
328
378
 
329
379
  async function runUninstall(skillName) {
@@ -348,12 +398,13 @@ async function runUpdate(skillName) {
348
398
  const meta = JSON.parse(fs.readFileSync(metaFile, "utf-8"));
349
399
  if (!meta.repo || meta.repo === "local") fatal("Cannot update local skill");
350
400
  stepLine();
351
- const spinner = ora({ text: `Updating ${skillName}`, prefixText: ` ${c.gray(S.branch)} ${c.cyan(S.diamondFilled)} `, color: "cyan" }).start();
401
+ const s = spinner();
402
+ s.start(`Updating ${skillName}`);
352
403
  try {
353
404
  if (!DRY) { createBackup(targetDir, skillName); fs.rmSync(targetDir, { recursive: true, force: true }); }
354
405
  const spec = `${meta.repo}#${meta.skill}${meta.ref ? "@" + meta.ref : ""}`;
355
- if (DRY) { spinner.stop(); step(`Would update: ${skillName}`, S.diamond); } else { await runInstall(spec); }
356
- } catch (err) { spinner.stop(); step(`Failed: ${err.message}`, S.cross, "red"); }
406
+ if (DRY) { s.stop(); step(`Would update: ${skillName}`, S.diamond); } else { await runInstall(spec); s.stop(); }
407
+ } catch (err) { s.stop(`Failed: ${err.message}`, 1); step(`Failed: ${err.message}`, S.cross, "red"); }
357
408
  }
358
409
 
359
410
  function runLock() {
@@ -7,11 +7,11 @@ import path from "path";
7
7
  import os from "os";
8
8
  import { execSync } from "child_process";
9
9
  import prompts from "prompts";
10
- import ora from "ora";
10
+
11
11
  import boxen from "boxen";
12
12
  import { parseSkillSpec, merkleHash } from "../helpers.js";
13
13
  import { parseSkillMdFrontmatter } from "../skills.js";
14
- import { step, stepLine, S, c, fatal } from "../ui.js";
14
+ import { step, stepLine, S, c, fatal, spinner } from "../ui.js";
15
15
  import { WORKSPACE, GLOBAL_DIR } from "../config.js";
16
16
 
17
17
  /**
@@ -36,11 +36,8 @@ export async function run(spec) {
36
36
  stepLine();
37
37
  step("Source: " + c.cyan(url), S.diamond);
38
38
 
39
- const spinner = ora({
40
- text: "Cloning repository",
41
- prefixText: ` ${c.gray(S.branch)} ${c.cyan(S.diamondFilled)} `,
42
- color: "cyan"
43
- }).start();
39
+ const s = spinner();
40
+ s.start("Cloning repository");
44
41
 
45
42
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "add-skill-"));
46
43
 
@@ -48,14 +45,13 @@ export async function run(spec) {
48
45
  execSync(`git clone --depth=1 ${url} "${tmp}"`, { stdio: "pipe" });
49
46
  if (ref) execSync(`git -C "${tmp}" checkout ${ref}`, { stdio: "pipe" });
50
47
  } catch {
51
- spinner.stop();
48
+ s.stop("Failed to clone");
52
49
  step(c.red("Failed to clone"), S.cross, "red");
53
50
  fs.rmSync(tmp, { recursive: true, force: true });
54
51
  return;
55
52
  }
56
53
 
57
- spinner.stop();
58
- step("Repository cloned", S.check, "green");
54
+ s.stop("Repository cloned");
59
55
 
60
56
  // Find skills in repo
61
57
  const skillsInRepo = [];
@@ -109,7 +105,7 @@ export async function run(spec) {
109
105
  value: s.value,
110
106
  selected: true // Pre-select all by default
111
107
  })),
112
- hint: "- Space to toggle. Return to submit",
108
+ hint: "- Press Space to select, Enter to continue",
113
109
  instructions: false
114
110
  });
115
111
 
@@ -122,20 +118,32 @@ export async function run(spec) {
122
118
  selectedSkills = skillsR.skills;
123
119
  }
124
120
 
125
- // Scope selection
121
+ // Agent selection
126
122
  stepLine();
127
- const scopeR = await prompts({
128
- type: "select",
129
- name: "scope",
130
- message: "Installation scope",
123
+ step("Detected 5 agents", S.diamond);
124
+
125
+ const agentsR = await prompts({
126
+ type: "multiselect",
127
+ name: "agents",
128
+ message: "Select agents to install skills to",
131
129
  choices: [
132
- { title: "Project (current directory)", value: "project" },
133
- { title: "Global", value: "global" }
130
+ { title: "Antigravity (.agent/skills)", value: "antigravity", selected: true },
131
+ { title: c.dim("Claude Code"), value: "claude", disabled: true },
132
+ { title: c.dim("Codex"), value: "codex", disabled: true },
133
+ { title: c.dim("Gemini CLI"), value: "gemini", disabled: true },
134
+ { title: c.dim("Windsurf"), value: "windsurf", disabled: true }
134
135
  ],
135
- initial: 0
136
+ hint: "- Space to toggle. Enter to submit",
137
+ instructions: false
136
138
  });
137
139
 
138
- const targetScope = scopeR.scope === "global" ? GLOBAL_DIR : WORKSPACE;
140
+ if (!agentsR.agents || agentsR.agents.length === 0) {
141
+ console.log(`\n ${c.yellow("No agents selected.")}`);
142
+ fs.rmSync(tmp, { recursive: true, force: true });
143
+ return;
144
+ }
145
+
146
+ const targetScope = WORKSPACE;
139
147
 
140
148
  // Summary
141
149
  stepLine();
@@ -4,9 +4,9 @@
4
4
 
5
5
  import fs from "fs";
6
6
  import path from "path";
7
- import ora from "ora";
7
+
8
8
  import { resolveScope, createBackup } from "../helpers.js";
9
- import { step, stepLine, S, c, fatal } from "../ui.js";
9
+ import { step, stepLine, S, c, fatal, spinner } from "../ui.js";
10
10
  import { DRY } from "../config.js";
11
11
 
12
12
  /**
@@ -29,11 +29,8 @@ export async function run(skillName) {
29
29
 
30
30
  stepLine();
31
31
 
32
- const spinner = ora({
33
- text: `Updating ${skillName}`,
34
- prefixText: ` ${c.gray(S.branch)} ${c.cyan(S.diamondFilled)} `,
35
- color: "cyan"
36
- }).start();
32
+ const s = spinner();
33
+ s.start(`Updating ${skillName}`);
37
34
 
38
35
  try {
39
36
  if (!DRY) {
@@ -44,15 +41,15 @@ export async function run(skillName) {
44
41
  const spec = `${meta.repo}#${meta.skill}${meta.ref ? "@" + meta.ref : ""}`;
45
42
 
46
43
  if (DRY) {
47
- spinner.stop();
44
+ s.stop();
48
45
  step(`Would update: ${skillName}`);
49
46
  } else {
50
47
  // Dynamically import install command
51
48
  const { run: install } = await import("./install.js");
52
49
  await install(spec);
50
+ s.stop();
53
51
  }
54
52
  } catch (err) {
55
- spinner.stop();
56
- step(`Failed: ${err.message}`, S.cross, "red");
53
+ s.stop(`Failed: ${err.message}`);
57
54
  }
58
55
  }
package/bin/lib/ui.js CHANGED
@@ -3,6 +3,9 @@
3
3
  */
4
4
 
5
5
  import kleur from "kleur";
6
+ import { intro, outro, spinner, multiselect, select, confirm, isCancel, cancel, text } from "@clack/prompts";
7
+
8
+ export { intro, outro, spinner, multiselect, select, confirm, isCancel, cancel, text };
6
9
 
7
10
  // --- Symbols ---
8
11
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "install-agent-skill",
3
- "version": "1.2.4",
3
+ "version": "1.2.7",
4
4
  "description": "Enterprise-grade Agent Skill Manager with Antigravity Skills support, Progressive Disclosure detection, and semantic routing validation",
5
5
  "license": "MIT",
6
6
  "author": "DataGuruIn <contact@dataguruin.com>",
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "type": "module",
16
16
  "bin": {
17
- "add-skill": "./bin/add-skill.v2.js"
17
+ "add-skill": "./bin/add-skill.modular.js"
18
18
  },
19
19
  "files": [
20
20
  "bin/",