install-agent-skill 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.bigtech.md +191 -0
- package/README.md +271 -0
- package/bin/add-skill.js +889 -0
- package/bin/add-skill.v1.backup.js +1904 -0
- package/bin/add-skill.v2.js +512 -0
- package/bin/lib/commands/analyze.js +70 -0
- package/bin/lib/commands/cache.js +65 -0
- package/bin/lib/commands/doctor.js +75 -0
- package/bin/lib/commands/help.js +42 -0
- package/bin/lib/commands/info.js +38 -0
- package/bin/lib/commands/init.js +39 -0
- package/bin/lib/commands/install.js +188 -0
- package/bin/lib/commands/list.js +43 -0
- package/bin/lib/commands/lock.js +57 -0
- package/bin/lib/commands/uninstall.js +48 -0
- package/bin/lib/commands/update.js +58 -0
- package/bin/lib/commands/validate.js +69 -0
- package/bin/lib/commands/verify.js +56 -0
- package/bin/lib/config.js +80 -0
- package/bin/lib/helpers.js +155 -0
- package/bin/lib/skills.js +114 -0
- package/bin/lib/types.js +82 -0
- package/bin/lib/ui.js +77 -0
- package/package.json +56 -0
- package/specs/ADD_SKILL_SPEC.md +333 -0
- package/specs/REGISTRY_V2_SPEC.md +334 -0
package/bin/add-skill.js
ADDED
|
@@ -0,0 +1,889 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @dataguruin/add-skill
|
|
4
|
+
* Vercel-Style CLI - Exact Match with @clack/prompts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import os from "os";
|
|
10
|
+
import { execSync } from "child_process";
|
|
11
|
+
import crypto from "crypto";
|
|
12
|
+
import * as p from "@clack/prompts";
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
import { createRequire } from "module";
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const pkg = (() => {
|
|
18
|
+
try { return require("../package.json"); } catch { return { version: "4.1.0" }; }
|
|
19
|
+
})();
|
|
20
|
+
|
|
21
|
+
// --- CONFIG & CONSTANTS ---
|
|
22
|
+
const cwd = process.cwd();
|
|
23
|
+
const WORKSPACE = path.join(cwd, ".agent", "skills");
|
|
24
|
+
const GLOBAL_DIR = path.join(os.homedir(), ".gemini", "antigravity", "skills");
|
|
25
|
+
const CACHE_ROOT = process.env.ADD_SKILL_CACHE_DIR || path.join(os.homedir(), ".cache", "add-skill");
|
|
26
|
+
const BACKUP_DIR = path.join(CACHE_ROOT, "backups");
|
|
27
|
+
|
|
28
|
+
// Args
|
|
29
|
+
const args = process.argv.slice(2);
|
|
30
|
+
const command = args[0] || "help";
|
|
31
|
+
const flags = new Set(args.filter((a) => a.startsWith("--")));
|
|
32
|
+
const params = args.filter((a) => !a.startsWith("--")).slice(1);
|
|
33
|
+
|
|
34
|
+
const GLOBAL = flags.has("--global") || flags.has("-g");
|
|
35
|
+
const VERBOSE = flags.has("--verbose") || flags.has("-v");
|
|
36
|
+
const JSON_OUTPUT = flags.has("--json");
|
|
37
|
+
const FORCE = flags.has("--force") || flags.has("-f");
|
|
38
|
+
const DRY = flags.has("--dry-run");
|
|
39
|
+
const STRICT = flags.has("--strict");
|
|
40
|
+
const FIX = flags.has("--fix");
|
|
41
|
+
const YES = flags.has("--yes") || flags.has("-y");
|
|
42
|
+
|
|
43
|
+
// --- Helper Functions ---
|
|
44
|
+
function resolveScope() {
|
|
45
|
+
if (GLOBAL) return GLOBAL_DIR;
|
|
46
|
+
if (fs.existsSync(path.join(cwd, ".agent"))) return WORKSPACE;
|
|
47
|
+
return GLOBAL_DIR;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getDirSize(dir) {
|
|
51
|
+
let size = 0;
|
|
52
|
+
try {
|
|
53
|
+
const walk = (p) => {
|
|
54
|
+
for (const f of fs.readdirSync(p)) {
|
|
55
|
+
const full = path.join(p, f);
|
|
56
|
+
const stat = fs.statSync(full);
|
|
57
|
+
if (stat.isDirectory()) walk(full);
|
|
58
|
+
else size += stat.size;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
walk(dir);
|
|
62
|
+
} catch { }
|
|
63
|
+
return size;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function formatBytes(bytes) {
|
|
67
|
+
if (bytes < 1024) return bytes + " B";
|
|
68
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
|
|
69
|
+
return (bytes / 1024 / 1024).toFixed(1) + " MB";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function formatDate(iso) { return iso ? new Date(iso).toLocaleDateString() : "unknown"; }
|
|
73
|
+
|
|
74
|
+
function shortenPath(fullPath, fromCwd = cwd) {
|
|
75
|
+
const home = os.homedir();
|
|
76
|
+
if (fullPath.startsWith(home)) return fullPath.replace(home, "~");
|
|
77
|
+
if (fullPath.startsWith(fromCwd)) return "." + fullPath.slice(fromCwd.length);
|
|
78
|
+
return fullPath;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseSkillMdFrontmatter(filePath) {
|
|
82
|
+
try {
|
|
83
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
84
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
85
|
+
if (!match) return {};
|
|
86
|
+
const meta = {};
|
|
87
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
88
|
+
const i = line.indexOf(":");
|
|
89
|
+
if (i === -1) continue;
|
|
90
|
+
const key = line.substring(0, i).trim();
|
|
91
|
+
const val = line.substring(i + 1).trim();
|
|
92
|
+
if (key === "tags") meta[key] = val.split(",").map(t => t.trim()).filter(Boolean);
|
|
93
|
+
else if (key && val) meta[key] = val;
|
|
94
|
+
}
|
|
95
|
+
return meta;
|
|
96
|
+
} catch { return {}; }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function detectSkillStructure(dir) {
|
|
100
|
+
const s = { hasResources: false, hasExamples: false, hasScripts: false, hasConstitution: false, hasDoctrines: false, hasEnforcement: false, hasProposals: false, directories: [], files: [] };
|
|
101
|
+
try {
|
|
102
|
+
for (const item of fs.readdirSync(dir)) {
|
|
103
|
+
const full = path.join(dir, item);
|
|
104
|
+
if (fs.statSync(full).isDirectory()) {
|
|
105
|
+
s.directories.push(item);
|
|
106
|
+
const l = item.toLowerCase();
|
|
107
|
+
if (l === "resources") s.hasResources = true;
|
|
108
|
+
if (l === "examples") s.hasExamples = true;
|
|
109
|
+
if (l === "scripts") s.hasScripts = true;
|
|
110
|
+
if (l === "constitution") s.hasConstitution = true;
|
|
111
|
+
if (l === "doctrines") s.hasDoctrines = true;
|
|
112
|
+
if (l === "enforcement") s.hasEnforcement = true;
|
|
113
|
+
if (l === "proposals") s.hasProposals = true;
|
|
114
|
+
} else s.files.push(item);
|
|
115
|
+
}
|
|
116
|
+
} catch { }
|
|
117
|
+
return s;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getInstalledSkills() {
|
|
121
|
+
const scope = resolveScope();
|
|
122
|
+
const skills = [];
|
|
123
|
+
if (!fs.existsSync(scope)) return skills;
|
|
124
|
+
for (const name of fs.readdirSync(scope)) {
|
|
125
|
+
const dir = path.join(scope, name);
|
|
126
|
+
if (!fs.statSync(dir).isDirectory()) continue;
|
|
127
|
+
const metaFile = path.join(dir, ".skill-source.json");
|
|
128
|
+
const skillFile = path.join(dir, "SKILL.md");
|
|
129
|
+
if (fs.existsSync(metaFile) || fs.existsSync(skillFile)) {
|
|
130
|
+
const meta = fs.existsSync(metaFile) ? JSON.parse(fs.readFileSync(metaFile, "utf-8")) : {};
|
|
131
|
+
const hasSkillMd = fs.existsSync(skillFile);
|
|
132
|
+
const skillMeta = hasSkillMd ? parseSkillMdFrontmatter(skillFile) : {};
|
|
133
|
+
skills.push({ name, path: dir, ...meta, hasSkillMd, description: skillMeta.description || meta.description || "", tags: skillMeta.tags || [], author: skillMeta.author || meta.publisher || "", version: skillMeta.version || meta.ref || "unknown", structure: detectSkillStructure(dir), size: getDirSize(dir) });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return skills;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function merkleHash(dir) {
|
|
140
|
+
const files = [];
|
|
141
|
+
const walk = (p) => {
|
|
142
|
+
for (const f of fs.readdirSync(p)) {
|
|
143
|
+
if (f === ".skill-source.json") continue;
|
|
144
|
+
const full = path.join(p, f);
|
|
145
|
+
const stat = fs.statSync(full);
|
|
146
|
+
if (stat.isDirectory()) walk(full);
|
|
147
|
+
else { const h = crypto.createHash("sha256").update(fs.readFileSync(full)).digest("hex"); files.push(`${path.relative(dir, full)}:${h}`); }
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
walk(dir);
|
|
151
|
+
files.sort();
|
|
152
|
+
return crypto.createHash("sha256").update(files.join("|")).digest("hex");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function parseSkillSpec(spec) {
|
|
156
|
+
const [repoPart, skillPart] = spec.split("#");
|
|
157
|
+
const [org, repo] = repoPart.split("/");
|
|
158
|
+
const [skill, ref] = (skillPart || "").split("@");
|
|
159
|
+
return { org, repo, skill, ref };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function createBackup(skillDir, skillName) {
|
|
163
|
+
if (DRY) return null;
|
|
164
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
165
|
+
const bp = path.join(BACKUP_DIR, `${skillName}_${ts}`);
|
|
166
|
+
fs.mkdirSync(BACKUP_DIR, { recursive: true });
|
|
167
|
+
fs.cpSync(skillDir, bp, { recursive: true });
|
|
168
|
+
return bp;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function listBackups(skillName = null) {
|
|
172
|
+
if (!fs.existsSync(BACKUP_DIR)) return [];
|
|
173
|
+
const backups = [];
|
|
174
|
+
for (const name of fs.readdirSync(BACKUP_DIR)) {
|
|
175
|
+
if (skillName && !name.startsWith(skillName + "_")) continue;
|
|
176
|
+
const bp = path.join(BACKUP_DIR, name);
|
|
177
|
+
backups.push({ name, path: bp, createdAt: fs.statSync(bp).mtime, size: getDirSize(bp) });
|
|
178
|
+
}
|
|
179
|
+
return backups.sort((a, b) => b.createdAt - a.createdAt);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// --- COMMANDS ---
|
|
183
|
+
|
|
184
|
+
async function runInit() {
|
|
185
|
+
console.log();
|
|
186
|
+
p.intro(chalk.bgCyan.black(" add-skill "));
|
|
187
|
+
|
|
188
|
+
const targetDir = GLOBAL ? GLOBAL_DIR : WORKSPACE;
|
|
189
|
+
if (fs.existsSync(targetDir)) {
|
|
190
|
+
p.log.success(`Skills directory already exists: ${shortenPath(targetDir)}`);
|
|
191
|
+
p.outro(chalk.green("Done!"));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (DRY) {
|
|
196
|
+
p.log.info(`Would create: ${shortenPath(targetDir)}`);
|
|
197
|
+
p.outro(chalk.dim("Dry run complete"));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
202
|
+
if (!GLOBAL) {
|
|
203
|
+
const gi = path.join(cwd, ".agent", ".gitignore");
|
|
204
|
+
if (!fs.existsSync(gi)) fs.writeFileSync(gi, "# Skill caches\nskills/*/.skill-source.json\n");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
p.log.success(`Initialized skills directory: ${shortenPath(targetDir)}`);
|
|
208
|
+
p.outro(chalk.green("Done!"));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function runList() {
|
|
212
|
+
console.log();
|
|
213
|
+
p.intro(chalk.bgCyan.black(" add-skill "));
|
|
214
|
+
|
|
215
|
+
const skills = getInstalledSkills();
|
|
216
|
+
const scope = resolveScope();
|
|
217
|
+
|
|
218
|
+
p.log.info(`Location: ${chalk.dim(shortenPath(scope))}`);
|
|
219
|
+
|
|
220
|
+
if (skills.length === 0) {
|
|
221
|
+
p.log.message(chalk.dim("No skills installed."));
|
|
222
|
+
p.outro(chalk.dim("Run add-skill <org/repo> to install skills"));
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (JSON_OUTPUT) {
|
|
227
|
+
console.log(JSON.stringify({ skills }, null, 2));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const lines = [];
|
|
232
|
+
for (const s of skills) {
|
|
233
|
+
const icon = s.hasSkillMd ? chalk.green("✓") : chalk.yellow("○");
|
|
234
|
+
lines.push(`${icon} ${chalk.bold(s.name)} ${chalk.dim("v" + s.version)} ${chalk.dim("(" + formatBytes(s.size) + ")")}`);
|
|
235
|
+
if (s.description && VERBOSE) {
|
|
236
|
+
lines.push(` ${chalk.dim(s.description.substring(0, 60))}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
p.note(lines.join("\n"), `Installed Skills (${skills.length})`);
|
|
241
|
+
p.outro(chalk.green("Done!"));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function runInstall(spec) {
|
|
245
|
+
console.log();
|
|
246
|
+
p.intro(chalk.bgCyan.black(" skills "));
|
|
247
|
+
|
|
248
|
+
if (!spec) {
|
|
249
|
+
p.log.error("Missing skill spec. Usage: add-skill <org/repo>");
|
|
250
|
+
p.outro(chalk.red("Installation failed"));
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const { org, repo, skill: singleSkill, ref } = parseSkillSpec(spec);
|
|
255
|
+
if (!org || !repo) {
|
|
256
|
+
p.log.error("Invalid spec. Format: org/repo or org/repo#skill");
|
|
257
|
+
p.outro(chalk.red("Installation failed"));
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const url = `https://github.com/${org}/${repo}.git`;
|
|
262
|
+
const spinner = p.spinner();
|
|
263
|
+
|
|
264
|
+
spinner.start("Cloning repository");
|
|
265
|
+
|
|
266
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "add-skill-"));
|
|
267
|
+
try {
|
|
268
|
+
execSync(`git clone --depth=1 ${url} "${tmp}"`, { stdio: "pipe" });
|
|
269
|
+
if (ref) execSync(`git -C "${tmp}" checkout ${ref}`, { stdio: "pipe" });
|
|
270
|
+
} catch {
|
|
271
|
+
spinner.stop(chalk.red("Failed to clone repository"));
|
|
272
|
+
p.outro(chalk.red("Installation failed"));
|
|
273
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
spinner.stop(`Source: ${chalk.cyan(url)}`);
|
|
278
|
+
|
|
279
|
+
// Find skills in repo
|
|
280
|
+
const skillsInRepo = [];
|
|
281
|
+
for (const e of fs.readdirSync(tmp)) {
|
|
282
|
+
const sp = path.join(tmp, e);
|
|
283
|
+
if (fs.statSync(sp).isDirectory() && fs.existsSync(path.join(sp, "SKILL.md"))) {
|
|
284
|
+
const m = parseSkillMdFrontmatter(path.join(sp, "SKILL.md"));
|
|
285
|
+
skillsInRepo.push({
|
|
286
|
+
value: e,
|
|
287
|
+
label: e,
|
|
288
|
+
hint: m.description ? m.description.substring(0, 50) + "..." : undefined
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (skillsInRepo.length === 0) {
|
|
294
|
+
p.log.warn("No valid skills found in repository");
|
|
295
|
+
p.outro(chalk.yellow("No skills to install"));
|
|
296
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
p.log.success(`Repository cloned`);
|
|
301
|
+
p.log.info(`Found ${skillsInRepo.length} skills`);
|
|
302
|
+
|
|
303
|
+
// Select skills
|
|
304
|
+
let selectedSkills;
|
|
305
|
+
if (singleSkill) {
|
|
306
|
+
selectedSkills = [singleSkill];
|
|
307
|
+
} else if (YES) {
|
|
308
|
+
selectedSkills = skillsInRepo.map(s => s.value);
|
|
309
|
+
} else {
|
|
310
|
+
const selected = await p.multiselect({
|
|
311
|
+
message: "Select skills to install",
|
|
312
|
+
options: skillsInRepo,
|
|
313
|
+
required: true,
|
|
314
|
+
initialValues: skillsInRepo.map(s => s.value)
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
if (p.isCancel(selected)) {
|
|
318
|
+
p.cancel("Installation cancelled");
|
|
319
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
320
|
+
process.exit(0);
|
|
321
|
+
}
|
|
322
|
+
selectedSkills = selected;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Select scope
|
|
326
|
+
let installGlobally = GLOBAL;
|
|
327
|
+
if (!GLOBAL && !YES) {
|
|
328
|
+
const scope = await p.select({
|
|
329
|
+
message: "Installation scope",
|
|
330
|
+
options: [
|
|
331
|
+
{ value: false, label: "Project", hint: "Install in current directory" },
|
|
332
|
+
{ value: true, label: "Global", hint: "Install in home directory" }
|
|
333
|
+
]
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
if (p.isCancel(scope)) {
|
|
337
|
+
p.cancel("Installation cancelled");
|
|
338
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
339
|
+
process.exit(0);
|
|
340
|
+
}
|
|
341
|
+
installGlobally = scope;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const targetScope = installGlobally ? GLOBAL_DIR : WORKSPACE;
|
|
345
|
+
|
|
346
|
+
// Show summary
|
|
347
|
+
const summaryLines = [];
|
|
348
|
+
for (const sn of selectedSkills) {
|
|
349
|
+
const shortPath = shortenPath(path.join(targetScope, sn));
|
|
350
|
+
summaryLines.push(`${chalk.cyan(sn)}`);
|
|
351
|
+
summaryLines.push(` ${chalk.dim("→")} ${shortPath}`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
p.note(summaryLines.join("\n"), "Installation Summary");
|
|
355
|
+
|
|
356
|
+
// Confirm
|
|
357
|
+
if (!YES) {
|
|
358
|
+
const confirmed = await p.confirm({
|
|
359
|
+
message: "Proceed with installation?"
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
363
|
+
p.cancel("Installation cancelled");
|
|
364
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
365
|
+
process.exit(0);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Install
|
|
370
|
+
spinner.start("Installing skills...");
|
|
371
|
+
fs.mkdirSync(targetScope, { recursive: true });
|
|
372
|
+
|
|
373
|
+
const installed = [];
|
|
374
|
+
for (const sn of selectedSkills) {
|
|
375
|
+
const src = path.join(tmp, sn);
|
|
376
|
+
const dest = path.join(targetScope, sn);
|
|
377
|
+
if (fs.existsSync(dest)) fs.rmSync(dest, { recursive: true, force: true });
|
|
378
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
379
|
+
const hash = merkleHash(dest);
|
|
380
|
+
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));
|
|
381
|
+
installed.push(sn);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
spinner.stop("Installation complete");
|
|
385
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
386
|
+
|
|
387
|
+
// Show result
|
|
388
|
+
const resultLines = installed.map(s => `${chalk.green("✓")} ${s}`);
|
|
389
|
+
p.note(resultLines.join("\n"), chalk.green(`Installed ${installed.length} skill${installed.length !== 1 ? "s" : ""}`));
|
|
390
|
+
|
|
391
|
+
console.log();
|
|
392
|
+
p.outro(chalk.green("Done!"));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async function runUninstall(skillName) {
|
|
396
|
+
console.log();
|
|
397
|
+
p.intro(chalk.bgCyan.black(" add-skill "));
|
|
398
|
+
|
|
399
|
+
if (!skillName) {
|
|
400
|
+
p.log.error("Missing skill name");
|
|
401
|
+
p.outro(chalk.red("Uninstall failed"));
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const scope = resolveScope();
|
|
406
|
+
const targetDir = path.join(scope, skillName);
|
|
407
|
+
|
|
408
|
+
if (!fs.existsSync(targetDir)) {
|
|
409
|
+
p.log.error(`Skill not found: ${skillName}`);
|
|
410
|
+
p.outro(chalk.red("Uninstall failed"));
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (!YES) {
|
|
415
|
+
const confirmed = await p.confirm({
|
|
416
|
+
message: `Remove skill "${skillName}"?`,
|
|
417
|
+
initialValue: false
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
421
|
+
p.cancel("Uninstall cancelled");
|
|
422
|
+
process.exit(0);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (DRY) {
|
|
427
|
+
p.log.info(`Would remove: ${shortenPath(targetDir)}`);
|
|
428
|
+
p.outro(chalk.dim("Dry run complete"));
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
createBackup(targetDir, skillName);
|
|
433
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
434
|
+
|
|
435
|
+
p.log.success(`Removed: ${skillName}`);
|
|
436
|
+
p.outro(chalk.green("Done!"));
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async function runUpdate(skillName) {
|
|
440
|
+
console.log();
|
|
441
|
+
p.intro(chalk.bgCyan.black(" add-skill "));
|
|
442
|
+
|
|
443
|
+
if (!skillName) {
|
|
444
|
+
p.log.error("Missing skill name");
|
|
445
|
+
p.outro(chalk.red("Update failed"));
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const scope = resolveScope();
|
|
450
|
+
const targetDir = path.join(scope, skillName);
|
|
451
|
+
|
|
452
|
+
if (!fs.existsSync(targetDir)) {
|
|
453
|
+
p.log.error(`Skill not found: ${skillName}`);
|
|
454
|
+
p.outro(chalk.red("Update failed"));
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const metaFile = path.join(targetDir, ".skill-source.json");
|
|
459
|
+
if (!fs.existsSync(metaFile)) {
|
|
460
|
+
p.log.error("Skill metadata not found");
|
|
461
|
+
p.outro(chalk.red("Update failed"));
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const meta = JSON.parse(fs.readFileSync(metaFile, "utf-8"));
|
|
466
|
+
if (!meta.repo || meta.repo === "local") {
|
|
467
|
+
p.log.error("Cannot update local skill");
|
|
468
|
+
p.outro(chalk.red("Update failed"));
|
|
469
|
+
process.exit(1);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const spinner = p.spinner();
|
|
473
|
+
spinner.start(`Updating ${skillName}...`);
|
|
474
|
+
|
|
475
|
+
try {
|
|
476
|
+
if (!DRY) {
|
|
477
|
+
createBackup(targetDir, skillName);
|
|
478
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
479
|
+
}
|
|
480
|
+
const spec = `${meta.repo}#${meta.skill}${meta.ref ? "@" + meta.ref : ""}`;
|
|
481
|
+
spinner.stop(`Updated: ${skillName}`);
|
|
482
|
+
if (!DRY) await runInstall(spec);
|
|
483
|
+
} catch (err) {
|
|
484
|
+
spinner.stop(chalk.red(`Failed: ${err.message}`));
|
|
485
|
+
p.outro(chalk.red("Update failed"));
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function runLock() {
|
|
490
|
+
console.log();
|
|
491
|
+
p.intro(chalk.bgCyan.black(" add-skill "));
|
|
492
|
+
|
|
493
|
+
if (!fs.existsSync(WORKSPACE)) {
|
|
494
|
+
p.log.error("No .agent/skills directory");
|
|
495
|
+
p.outro(chalk.red("Lock failed"));
|
|
496
|
+
process.exit(1);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const skills = {};
|
|
500
|
+
for (const name of fs.readdirSync(WORKSPACE)) {
|
|
501
|
+
const dir = path.join(WORKSPACE, name);
|
|
502
|
+
if (!fs.statSync(dir).isDirectory()) continue;
|
|
503
|
+
const mf = path.join(dir, ".skill-source.json");
|
|
504
|
+
if (!fs.existsSync(mf)) continue;
|
|
505
|
+
const m = JSON.parse(fs.readFileSync(mf, "utf-8"));
|
|
506
|
+
skills[name] = { repo: m.repo, skill: m.skill, ref: m.ref, checksum: `sha256:${m.checksum}`, publisher: m.publisher || null };
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const lock = { lockVersion: 1, generatedAt: new Date().toISOString(), generator: `@dataguruin/add-skill@${pkg.version}`, skills };
|
|
510
|
+
|
|
511
|
+
if (DRY) {
|
|
512
|
+
p.log.info("Would generate skill-lock.json");
|
|
513
|
+
console.log(JSON.stringify(lock, null, 2));
|
|
514
|
+
p.outro(chalk.dim("Dry run complete"));
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
fs.mkdirSync(path.join(cwd, ".agent"), { recursive: true });
|
|
519
|
+
fs.writeFileSync(path.join(cwd, ".agent", "skill-lock.json"), JSON.stringify(lock, null, 2));
|
|
520
|
+
|
|
521
|
+
p.log.success("Generated skill-lock.json");
|
|
522
|
+
p.outro(chalk.green("Done!"));
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function runVerify() {
|
|
526
|
+
console.log();
|
|
527
|
+
p.intro(chalk.bgCyan.black(" add-skill "));
|
|
528
|
+
|
|
529
|
+
const scope = resolveScope();
|
|
530
|
+
if (!fs.existsSync(scope)) {
|
|
531
|
+
p.log.warn("No skills directory found");
|
|
532
|
+
p.outro(chalk.dim("Nothing to verify"));
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
p.log.step("Verifying skills...");
|
|
537
|
+
|
|
538
|
+
let issues = 0;
|
|
539
|
+
const results = [];
|
|
540
|
+
|
|
541
|
+
for (const name of fs.readdirSync(scope)) {
|
|
542
|
+
const dir = path.join(scope, name);
|
|
543
|
+
if (!fs.statSync(dir).isDirectory()) continue;
|
|
544
|
+
const mf = path.join(dir, ".skill-source.json");
|
|
545
|
+
if (!fs.existsSync(mf)) {
|
|
546
|
+
results.push(`${chalk.red("✗")} ${name}: ${chalk.red("missing metadata")}`);
|
|
547
|
+
issues++;
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
const m = JSON.parse(fs.readFileSync(mf, "utf-8"));
|
|
551
|
+
const actual = merkleHash(dir);
|
|
552
|
+
if (actual !== m.checksum) {
|
|
553
|
+
results.push(`${chalk.red("✗")} ${name}: ${chalk.red("checksum mismatch")}`);
|
|
554
|
+
issues++;
|
|
555
|
+
} else {
|
|
556
|
+
results.push(`${chalk.green("✓")} ${name}: ${chalk.green("OK")}`);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
p.note(results.join("\n"), "Verification Results");
|
|
561
|
+
|
|
562
|
+
if (issues > 0) {
|
|
563
|
+
p.log.error(`${issues} issue(s) found`);
|
|
564
|
+
if (STRICT) process.exit(1);
|
|
565
|
+
} else {
|
|
566
|
+
p.log.success("All skills verified");
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
p.outro(chalk.green("Done!"));
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function runDoctor() {
|
|
573
|
+
console.log();
|
|
574
|
+
p.intro(chalk.bgCyan.black(" add-skill "));
|
|
575
|
+
|
|
576
|
+
const scope = resolveScope();
|
|
577
|
+
if (!fs.existsSync(scope)) {
|
|
578
|
+
p.log.warn("No skills directory found");
|
|
579
|
+
p.outro(chalk.dim("Nothing to check"));
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
p.log.step("Running health check...");
|
|
584
|
+
|
|
585
|
+
let errors = 0, warnings = 0;
|
|
586
|
+
const results = [];
|
|
587
|
+
const lockFile = path.join(cwd, ".agent", "skill-lock.json");
|
|
588
|
+
const lock = fs.existsSync(lockFile) ? JSON.parse(fs.readFileSync(lockFile, "utf-8")) : null;
|
|
589
|
+
|
|
590
|
+
for (const name of fs.readdirSync(scope)) {
|
|
591
|
+
const dir = path.join(scope, name);
|
|
592
|
+
if (!fs.statSync(dir).isDirectory()) continue;
|
|
593
|
+
|
|
594
|
+
if (!fs.existsSync(path.join(dir, "SKILL.md"))) {
|
|
595
|
+
results.push(`${chalk.red("✗")} ${name}: ${chalk.red("missing SKILL.md")}`);
|
|
596
|
+
errors++;
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const mf = path.join(dir, ".skill-source.json");
|
|
601
|
+
if (!fs.existsSync(mf)) {
|
|
602
|
+
results.push(`${chalk.red("✗")} ${name}: ${chalk.red("missing metadata")}`);
|
|
603
|
+
errors++;
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const m = JSON.parse(fs.readFileSync(mf, "utf-8"));
|
|
608
|
+
const actual = merkleHash(dir);
|
|
609
|
+
|
|
610
|
+
if (actual !== m.checksum) {
|
|
611
|
+
if (FIX && !DRY) {
|
|
612
|
+
m.checksum = actual;
|
|
613
|
+
fs.writeFileSync(mf, JSON.stringify(m, null, 2));
|
|
614
|
+
results.push(`${chalk.yellow("○")} ${name}: ${chalk.yellow("checksum fixed")}`);
|
|
615
|
+
} else {
|
|
616
|
+
results.push(`${chalk.yellow("○")} ${name}: ${chalk.yellow("checksum drift")}`);
|
|
617
|
+
STRICT ? errors++ : warnings++;
|
|
618
|
+
}
|
|
619
|
+
} else if (lock && !lock.skills[name]) {
|
|
620
|
+
results.push(`${chalk.yellow("○")} ${name}: ${chalk.yellow("not in lock")}`);
|
|
621
|
+
STRICT ? errors++ : warnings++;
|
|
622
|
+
} else {
|
|
623
|
+
results.push(`${chalk.green("✓")} ${name}: ${chalk.green("healthy")}`);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
p.note(results.join("\n"), "Health Check Results");
|
|
628
|
+
p.log.info(`Errors: ${errors}, Warnings: ${warnings}`);
|
|
629
|
+
|
|
630
|
+
if (STRICT && errors) process.exit(1);
|
|
631
|
+
p.outro(chalk.green("Done!"));
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function runCache(sub) {
|
|
635
|
+
console.log();
|
|
636
|
+
p.intro(chalk.bgCyan.black(" add-skill "));
|
|
637
|
+
|
|
638
|
+
if (sub === "clear") {
|
|
639
|
+
if (DRY) {
|
|
640
|
+
p.log.info(`Would clear: ${shortenPath(CACHE_ROOT)}`);
|
|
641
|
+
p.outro(chalk.dim("Dry run complete"));
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (fs.existsSync(CACHE_ROOT)) {
|
|
645
|
+
const size = getDirSize(CACHE_ROOT);
|
|
646
|
+
fs.rmSync(CACHE_ROOT, { recursive: true, force: true });
|
|
647
|
+
p.log.success(`Cache cleared (${formatBytes(size)})`);
|
|
648
|
+
} else {
|
|
649
|
+
p.log.info("Cache already empty");
|
|
650
|
+
}
|
|
651
|
+
p.outro(chalk.green("Done!"));
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (sub === "info" || !sub) {
|
|
656
|
+
if (!fs.existsSync(CACHE_ROOT)) {
|
|
657
|
+
p.log.info("Cache is empty");
|
|
658
|
+
p.outro(chalk.dim("Nothing cached"));
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const bs = fs.existsSync(BACKUP_DIR) ? getDirSize(BACKUP_DIR) : 0;
|
|
662
|
+
const lines = [
|
|
663
|
+
`Location: ${shortenPath(CACHE_ROOT)}`,
|
|
664
|
+
`Backups: ${formatBytes(bs)}`,
|
|
665
|
+
`Total: ${formatBytes(getDirSize(CACHE_ROOT))}`
|
|
666
|
+
];
|
|
667
|
+
p.note(lines.join("\n"), "Cache Info");
|
|
668
|
+
p.outro(chalk.green("Done!"));
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (sub === "backups") {
|
|
673
|
+
const backups = listBackups();
|
|
674
|
+
if (backups.length === 0) {
|
|
675
|
+
p.log.info("No backups found");
|
|
676
|
+
p.outro(chalk.dim("Nothing backed up"));
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
const lines = backups.map(b => `${b.name} (${formatBytes(b.size)})`);
|
|
680
|
+
p.note(lines.join("\n"), `Backups (${backups.length})`);
|
|
681
|
+
p.outro(chalk.green("Done!"));
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
p.log.error(`Unknown cache subcommand: ${sub}`);
|
|
686
|
+
p.outro(chalk.red("Failed"));
|
|
687
|
+
process.exit(1);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function runValidate(skillName) {
|
|
691
|
+
console.log();
|
|
692
|
+
p.intro(chalk.bgCyan.black(" add-skill "));
|
|
693
|
+
|
|
694
|
+
const scope = resolveScope();
|
|
695
|
+
let skillsToValidate = [];
|
|
696
|
+
|
|
697
|
+
if (skillName) {
|
|
698
|
+
const sd = path.join(scope, skillName);
|
|
699
|
+
if (!fs.existsSync(sd)) {
|
|
700
|
+
p.log.error(`Skill not found: ${skillName}`);
|
|
701
|
+
p.outro(chalk.red("Validation failed"));
|
|
702
|
+
process.exit(1);
|
|
703
|
+
}
|
|
704
|
+
skillsToValidate = [{ name: skillName, path: sd }];
|
|
705
|
+
} else {
|
|
706
|
+
skillsToValidate = getInstalledSkills();
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (skillsToValidate.length === 0) {
|
|
710
|
+
p.log.warn("No skills to validate");
|
|
711
|
+
p.outro(chalk.dim("Nothing to validate"));
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
p.log.step("Running Antigravity validation...");
|
|
716
|
+
|
|
717
|
+
let totalErrors = 0, totalWarnings = 0;
|
|
718
|
+
const results = [];
|
|
719
|
+
|
|
720
|
+
for (const skill of skillsToValidate) {
|
|
721
|
+
const errors = [], warnings = [];
|
|
722
|
+
const smp = path.join(skill.path, "SKILL.md");
|
|
723
|
+
|
|
724
|
+
if (!fs.existsSync(smp)) {
|
|
725
|
+
errors.push("Missing SKILL.md");
|
|
726
|
+
} else {
|
|
727
|
+
const m = parseSkillMdFrontmatter(smp);
|
|
728
|
+
if (!m.description) errors.push("Missing description");
|
|
729
|
+
else if (m.description.length < 50) warnings.push("Description too short");
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const status = errors.length > 0 ? chalk.red("FAIL") : warnings.length > 0 ? chalk.yellow("WARN") : chalk.green("PASS");
|
|
733
|
+
results.push(`${status} ${chalk.bold(skill.name)}`);
|
|
734
|
+
|
|
735
|
+
if (VERBOSE || errors.length || warnings.length) {
|
|
736
|
+
errors.forEach(e => results.push(` ${chalk.red("ERROR: " + e)}`));
|
|
737
|
+
warnings.forEach(w => results.push(` ${chalk.yellow("WARN: " + w)}`));
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
totalErrors += errors.length;
|
|
741
|
+
totalWarnings += warnings.length;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
p.note(results.join("\n"), "Validation Results");
|
|
745
|
+
p.log.info(`Total: ${skillsToValidate.length}, Errors: ${totalErrors}, Warnings: ${totalWarnings}`);
|
|
746
|
+
|
|
747
|
+
if (STRICT && totalErrors > 0) process.exit(1);
|
|
748
|
+
p.outro(chalk.green("Done!"));
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function runAnalyze(skillName) {
|
|
752
|
+
console.log();
|
|
753
|
+
p.intro(chalk.bgCyan.black(" add-skill "));
|
|
754
|
+
|
|
755
|
+
if (!skillName) {
|
|
756
|
+
p.log.error("Missing skill name");
|
|
757
|
+
p.outro(chalk.red("Analysis failed"));
|
|
758
|
+
process.exit(1);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const scope = resolveScope();
|
|
762
|
+
const skillDir = path.join(scope, skillName);
|
|
763
|
+
|
|
764
|
+
if (!fs.existsSync(skillDir)) {
|
|
765
|
+
p.log.error(`Skill not found: ${skillName}`);
|
|
766
|
+
p.outro(chalk.red("Analysis failed"));
|
|
767
|
+
process.exit(1);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
p.log.info(`Path: ${chalk.dim(shortenPath(skillDir))}`);
|
|
771
|
+
|
|
772
|
+
const smp = path.join(skillDir, "SKILL.md");
|
|
773
|
+
const lines = [];
|
|
774
|
+
|
|
775
|
+
if (fs.existsSync(smp)) {
|
|
776
|
+
const m = parseSkillMdFrontmatter(smp);
|
|
777
|
+
lines.push(chalk.cyan("SKILL.md Frontmatter:"));
|
|
778
|
+
lines.push(` Name: ${m.name || chalk.dim("(not set)")}`);
|
|
779
|
+
lines.push(` Description: ${m.description ? m.description.substring(0, 60) : chalk.red("(MISSING)")}`);
|
|
780
|
+
if (m.tags) lines.push(` Tags: ${m.tags.join(", ")}`);
|
|
781
|
+
lines.push("");
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const structure = detectSkillStructure(skillDir);
|
|
785
|
+
lines.push(chalk.cyan("Structure:"));
|
|
786
|
+
const items = [["resources", structure.hasResources], ["examples", structure.hasExamples], ["scripts", structure.hasScripts], ["constitution", structure.hasConstitution], ["doctrines", structure.hasDoctrines]];
|
|
787
|
+
items.forEach(([n, has]) => lines.push(` ${has ? chalk.green("✓") : chalk.dim("○")} ${has ? chalk.bold(n) : chalk.dim(n)}`));
|
|
788
|
+
lines.push("");
|
|
789
|
+
|
|
790
|
+
let score = 0;
|
|
791
|
+
if (fs.existsSync(smp)) score += 20;
|
|
792
|
+
const m = parseSkillMdFrontmatter(smp);
|
|
793
|
+
if (m.description) score += 25;
|
|
794
|
+
if (m.tags && m.tags.length > 0) score += 10;
|
|
795
|
+
if (structure.hasResources || structure.hasExamples || structure.hasScripts) score += 20;
|
|
796
|
+
if (fs.existsSync(path.join(skillDir, ".skill-source.json"))) score += 10;
|
|
797
|
+
if (structure.hasConstitution || structure.hasDoctrines) score += 15;
|
|
798
|
+
|
|
799
|
+
const scoreColor = score >= 80 ? chalk.green : score >= 50 ? chalk.yellow : chalk.red;
|
|
800
|
+
lines.push(`${chalk.cyan("Antigravity Score:")} ${scoreColor(score + "/100")}`);
|
|
801
|
+
|
|
802
|
+
p.note(lines.join("\n"), `Skill Analysis: ${skillName}`);
|
|
803
|
+
p.outro(chalk.green("Done!"));
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function runInfo(name) {
|
|
807
|
+
console.log();
|
|
808
|
+
p.intro(chalk.bgCyan.black(" add-skill "));
|
|
809
|
+
|
|
810
|
+
if (!name) {
|
|
811
|
+
p.log.error("Missing skill name");
|
|
812
|
+
p.outro(chalk.red("Info failed"));
|
|
813
|
+
process.exit(1);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const scope = resolveScope();
|
|
817
|
+
const localDir = path.join(scope, name);
|
|
818
|
+
|
|
819
|
+
if (fs.existsSync(localDir)) {
|
|
820
|
+
const lines = [`Path: ${shortenPath(localDir)}`];
|
|
821
|
+
const mf = path.join(localDir, ".skill-source.json");
|
|
822
|
+
if (fs.existsSync(mf)) {
|
|
823
|
+
const m = JSON.parse(fs.readFileSync(mf, "utf-8"));
|
|
824
|
+
lines.push(`Repo: ${m.repo || "local"}`);
|
|
825
|
+
lines.push(`Installed: ${formatDate(m.installedAt)}`);
|
|
826
|
+
}
|
|
827
|
+
p.note(lines.join("\n"), `${name} ${chalk.green("(installed)")}`);
|
|
828
|
+
p.outro(chalk.green("Done!"));
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
p.log.warn(`Skill not installed: ${name}`);
|
|
833
|
+
p.outro(chalk.dim("Run add-skill <org/repo> to install"));
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
async function runHelp() {
|
|
837
|
+
console.log(`
|
|
838
|
+
${chalk.bold("add-skill")} ${chalk.dim("v" + pkg.version)}
|
|
839
|
+
|
|
840
|
+
${chalk.bold("Usage")}
|
|
841
|
+
$ add-skill <command> [options]
|
|
842
|
+
|
|
843
|
+
${chalk.bold("Commands")}
|
|
844
|
+
<org/repo> Install all skills from repository
|
|
845
|
+
<org/repo#skill> Install specific skill
|
|
846
|
+
list List installed skills
|
|
847
|
+
uninstall <skill> Remove a skill
|
|
848
|
+
update <skill> Update a skill
|
|
849
|
+
verify Verify checksums
|
|
850
|
+
doctor Check health
|
|
851
|
+
lock Generate skill-lock.json
|
|
852
|
+
init Initialize skills directory
|
|
853
|
+
validate [skill] Validate against Antigravity spec
|
|
854
|
+
analyze <skill> Analyze skill structure
|
|
855
|
+
cache [info|clear] Manage cache
|
|
856
|
+
|
|
857
|
+
${chalk.bold("Options")}
|
|
858
|
+
--global, -g Use global scope
|
|
859
|
+
--force, -f Force operation
|
|
860
|
+
--strict Fail on violations
|
|
861
|
+
--fix Auto-fix issues
|
|
862
|
+
--dry-run Preview only
|
|
863
|
+
--verbose, -v Detailed output
|
|
864
|
+
--json JSON output
|
|
865
|
+
--yes, -y Skip confirmation prompts
|
|
866
|
+
`);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// --- MAIN ---
|
|
870
|
+
async function main() {
|
|
871
|
+
if (command === "list" || command === "ls") await runList();
|
|
872
|
+
else if (command === "init") await runInit();
|
|
873
|
+
else if (command === "install" || command === "add" || command === "i") await runInstall(params[0]);
|
|
874
|
+
else if (command === "uninstall" || command === "remove" || command === "rm") await runUninstall(params[0]);
|
|
875
|
+
else if (command === "update") await runUpdate(params[0]);
|
|
876
|
+
else if (command === "lock") runLock();
|
|
877
|
+
else if (command === "verify") runVerify();
|
|
878
|
+
else if (command === "doctor") runDoctor();
|
|
879
|
+
else if (command === "cache") runCache(params[0]);
|
|
880
|
+
else if (command === "validate" || command === "check") runValidate(params[0]);
|
|
881
|
+
else if (command === "analyze") runAnalyze(params[0]);
|
|
882
|
+
else if (command === "info" || command === "show") runInfo(params[0]);
|
|
883
|
+
else if (command === "help" || command === "--help" || command === "-h") await runHelp();
|
|
884
|
+
else if (command === "--version" || command === "-V") console.log(pkg.version);
|
|
885
|
+
else if (command.includes("/")) await runInstall(command);
|
|
886
|
+
else { console.log(`Unknown command: ${command}`); await runHelp(); }
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
main().catch(err => { console.error(chalk.red("\nError: " + err.message)); process.exit(1); });
|