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
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Doctor command - Health check
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { resolveScope, merkleHash, loadSkillLock } from "../helpers.js";
|
|
8
|
+
import { step, stepLine, S, c } from "../ui.js";
|
|
9
|
+
import { STRICT, FIX, DRY, cwd } from "../config.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Run health check on installed skills
|
|
13
|
+
*/
|
|
14
|
+
export async function run() {
|
|
15
|
+
const scope = resolveScope();
|
|
16
|
+
|
|
17
|
+
if (!fs.existsSync(scope)) {
|
|
18
|
+
stepLine();
|
|
19
|
+
step("No skills directory found", S.diamond);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
stepLine();
|
|
24
|
+
step(c.bold("Health Check"), S.diamondFilled, "cyan");
|
|
25
|
+
stepLine();
|
|
26
|
+
|
|
27
|
+
let errors = 0, warnings = 0;
|
|
28
|
+
const lock = fs.existsSync(path.join(cwd, ".agent", "skill-lock.json")) ? loadSkillLock() : null;
|
|
29
|
+
|
|
30
|
+
for (const name of fs.readdirSync(scope)) {
|
|
31
|
+
const dir = path.join(scope, name);
|
|
32
|
+
if (!fs.statSync(dir).isDirectory()) continue;
|
|
33
|
+
|
|
34
|
+
// Check SKILL.md
|
|
35
|
+
if (!fs.existsSync(path.join(dir, "SKILL.md"))) {
|
|
36
|
+
step(`${name}: ${c.red("missing SKILL.md")}`, S.cross, "red");
|
|
37
|
+
errors++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check metadata
|
|
42
|
+
const mf = path.join(dir, ".skill-source.json");
|
|
43
|
+
if (!fs.existsSync(mf)) {
|
|
44
|
+
step(`${name}: ${c.red("missing metadata")}`, S.cross, "red");
|
|
45
|
+
errors++;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const m = JSON.parse(fs.readFileSync(mf, "utf-8"));
|
|
50
|
+
const actual = merkleHash(dir);
|
|
51
|
+
|
|
52
|
+
// Check checksum
|
|
53
|
+
if (actual !== m.checksum) {
|
|
54
|
+
if (FIX && !DRY) {
|
|
55
|
+
m.checksum = actual;
|
|
56
|
+
fs.writeFileSync(mf, JSON.stringify(m, null, 2));
|
|
57
|
+
step(`${name}: ${c.yellow("checksum fixed")}`, S.diamond, "yellow");
|
|
58
|
+
} else {
|
|
59
|
+
step(`${name}: ${c.yellow("checksum drift")}`, S.diamond, "yellow");
|
|
60
|
+
STRICT ? errors++ : warnings++;
|
|
61
|
+
}
|
|
62
|
+
} else if (lock && !lock.skills[name]) {
|
|
63
|
+
step(`${name}: ${c.yellow("not in lock")}`, S.diamond, "yellow");
|
|
64
|
+
STRICT ? errors++ : warnings++;
|
|
65
|
+
} else {
|
|
66
|
+
step(`${name}: ${c.green("healthy")}`, S.check, "green");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
stepLine();
|
|
71
|
+
console.log(` ${c.gray(S.branch)} Errors: ${errors}, Warnings: ${warnings}`);
|
|
72
|
+
stepLine();
|
|
73
|
+
|
|
74
|
+
if (STRICT && errors) process.exit(1);
|
|
75
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Help command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { c } from "../ui.js";
|
|
6
|
+
import { VERSION } from "../config.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Show help
|
|
10
|
+
*/
|
|
11
|
+
export async function run() {
|
|
12
|
+
console.log(`
|
|
13
|
+
${c.bold("add-skill")} ${c.dim("v" + VERSION)}
|
|
14
|
+
|
|
15
|
+
${c.bold("Usage")}
|
|
16
|
+
$ add-skill <command> [options]
|
|
17
|
+
|
|
18
|
+
${c.bold("Commands")}
|
|
19
|
+
<org/repo> Install all skills from repository
|
|
20
|
+
<org/repo#skill> Install specific skill
|
|
21
|
+
list List installed skills
|
|
22
|
+
uninstall <skill> Remove a skill
|
|
23
|
+
update <skill> Update a skill
|
|
24
|
+
verify Verify checksums
|
|
25
|
+
doctor Check health
|
|
26
|
+
lock Generate skill-lock.json
|
|
27
|
+
init Initialize skills directory
|
|
28
|
+
validate [skill] Validate against Antigravity spec
|
|
29
|
+
analyze <skill> Analyze skill structure
|
|
30
|
+
cache [info|clear] Manage cache
|
|
31
|
+
info <skill> Show skill info
|
|
32
|
+
|
|
33
|
+
${c.bold("Options")}
|
|
34
|
+
--global, -g Use global scope
|
|
35
|
+
--force, -f Force operation
|
|
36
|
+
--strict Fail on violations
|
|
37
|
+
--fix Auto-fix issues
|
|
38
|
+
--dry-run Preview only
|
|
39
|
+
--verbose, -v Detailed output
|
|
40
|
+
--json JSON output
|
|
41
|
+
`);
|
|
42
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Info command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { resolveScope, formatDate } from "../helpers.js";
|
|
8
|
+
import { step, stepLine, S, c, fatal } from "../ui.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Show skill info
|
|
12
|
+
* @param {string} name
|
|
13
|
+
*/
|
|
14
|
+
export async function run(name) {
|
|
15
|
+
if (!name) fatal("Missing skill name");
|
|
16
|
+
|
|
17
|
+
const scope = resolveScope();
|
|
18
|
+
const localDir = path.join(scope, name);
|
|
19
|
+
|
|
20
|
+
stepLine();
|
|
21
|
+
|
|
22
|
+
if (fs.existsSync(localDir)) {
|
|
23
|
+
step(`${c.bold(name)} ${c.green("(installed)")}`, S.diamondFilled, "cyan");
|
|
24
|
+
console.log(` ${c.gray(S.branch)} ${c.dim("Path: " + localDir)}`);
|
|
25
|
+
|
|
26
|
+
const mf = path.join(localDir, ".skill-source.json");
|
|
27
|
+
if (fs.existsSync(mf)) {
|
|
28
|
+
const m = JSON.parse(fs.readFileSync(mf, "utf-8"));
|
|
29
|
+
console.log(` ${c.gray(S.branch)} Repo: ${m.repo || "local"}`);
|
|
30
|
+
console.log(` ${c.gray(S.branch)} Installed: ${formatDate(m.installedAt)}`);
|
|
31
|
+
}
|
|
32
|
+
stepLine();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
step(`Skill not installed: ${name}`, S.diamond, "yellow");
|
|
37
|
+
stepLine();
|
|
38
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Init command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import { step, stepLine, S, success } from "../ui.js";
|
|
7
|
+
import { GLOBAL, GLOBAL_DIR, WORKSPACE, DRY, cwd } from "../config.js";
|
|
8
|
+
import path from "path";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Initialize skills directory
|
|
12
|
+
*/
|
|
13
|
+
export async function run() {
|
|
14
|
+
stepLine();
|
|
15
|
+
|
|
16
|
+
const targetDir = GLOBAL ? GLOBAL_DIR : WORKSPACE;
|
|
17
|
+
|
|
18
|
+
if (fs.existsSync(targetDir)) {
|
|
19
|
+
step(`Skills directory already exists: ${targetDir}`, S.check, "green");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (DRY) {
|
|
24
|
+
step(`Would create: ${targetDir}`, S.diamond);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
29
|
+
|
|
30
|
+
// Create .gitignore if workspace
|
|
31
|
+
if (!GLOBAL) {
|
|
32
|
+
const gi = path.join(cwd, ".agent", ".gitignore");
|
|
33
|
+
if (!fs.existsSync(gi)) {
|
|
34
|
+
fs.writeFileSync(gi, "# Skill caches\nskills/*/.skill-source.json\n");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
success(`Initialized: ${targetDir}`);
|
|
39
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Install command - Interactive skill installation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import os from "os";
|
|
8
|
+
import { execSync } from "child_process";
|
|
9
|
+
import prompts from "prompts";
|
|
10
|
+
import ora from "ora";
|
|
11
|
+
import boxen from "boxen";
|
|
12
|
+
import { parseSkillSpec, merkleHash } from "../helpers.js";
|
|
13
|
+
import { parseSkillMdFrontmatter } from "../skills.js";
|
|
14
|
+
import { step, stepLine, S, c, fatal } from "../ui.js";
|
|
15
|
+
import { WORKSPACE, GLOBAL_DIR } from "../config.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Install skills from repository
|
|
19
|
+
* @param {string} spec - Skill spec (org/repo or org/repo#skill)
|
|
20
|
+
*/
|
|
21
|
+
export async function run(spec) {
|
|
22
|
+
if (!spec) {
|
|
23
|
+
fatal("Missing skill spec. Usage: add-skill <org/repo>");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { org, repo, skill: singleSkill, ref } = parseSkillSpec(spec);
|
|
28
|
+
|
|
29
|
+
if (!org || !repo) {
|
|
30
|
+
fatal("Invalid spec. Format: org/repo or org/repo#skill");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const url = `https://github.com/${org}/${repo}.git`;
|
|
35
|
+
|
|
36
|
+
stepLine();
|
|
37
|
+
step("Source: " + c.cyan(url), S.diamond);
|
|
38
|
+
|
|
39
|
+
const spinner = ora({
|
|
40
|
+
text: "Cloning repository",
|
|
41
|
+
prefixText: ` ${c.gray(S.branch)} ${c.cyan(S.diamondFilled)} `,
|
|
42
|
+
color: "cyan"
|
|
43
|
+
}).start();
|
|
44
|
+
|
|
45
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "add-skill-"));
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
execSync(`git clone --depth=1 ${url} "${tmp}"`, { stdio: "pipe" });
|
|
49
|
+
if (ref) execSync(`git -C "${tmp}" checkout ${ref}`, { stdio: "pipe" });
|
|
50
|
+
} catch {
|
|
51
|
+
spinner.stop();
|
|
52
|
+
step(c.red("Failed to clone"), S.cross, "red");
|
|
53
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
spinner.stop();
|
|
58
|
+
step("Repository cloned", S.check, "green");
|
|
59
|
+
|
|
60
|
+
// Find skills in repo
|
|
61
|
+
const skillsInRepo = [];
|
|
62
|
+
for (const e of fs.readdirSync(tmp)) {
|
|
63
|
+
const sp = path.join(tmp, e);
|
|
64
|
+
if (fs.statSync(sp).isDirectory() && fs.existsSync(path.join(sp, "SKILL.md"))) {
|
|
65
|
+
const m = parseSkillMdFrontmatter(path.join(sp, "SKILL.md"));
|
|
66
|
+
skillsInRepo.push({
|
|
67
|
+
title: e + (m.description ? c.dim(` (${m.description.substring(0, 40)}...)`) : ""),
|
|
68
|
+
value: e,
|
|
69
|
+
selected: singleSkill ? e === singleSkill : true
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (skillsInRepo.length === 0) {
|
|
75
|
+
step(c.yellow("No valid skills found"), S.diamond, "yellow");
|
|
76
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
stepLine();
|
|
81
|
+
step(`Found ${skillsInRepo.length} skill${skillsInRepo.length > 1 ? "s" : ""}`, S.diamond);
|
|
82
|
+
|
|
83
|
+
let selectedSkills = [];
|
|
84
|
+
|
|
85
|
+
// If single skill specified via #skill_name, auto-select it
|
|
86
|
+
if (singleSkill) {
|
|
87
|
+
const found = skillsInRepo.find(s => s.value === singleSkill);
|
|
88
|
+
if (!found) {
|
|
89
|
+
stepLine();
|
|
90
|
+
step(c.red(`Skill '${singleSkill}' not found in repository`), S.cross, "red");
|
|
91
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
selectedSkills = [singleSkill];
|
|
95
|
+
stepLine();
|
|
96
|
+
step(`Auto-selected: ${c.cyan(singleSkill)}`, S.check, "green");
|
|
97
|
+
} else {
|
|
98
|
+
// Show selection prompt for multiple skills
|
|
99
|
+
stepLine();
|
|
100
|
+
step("Select skills to install", S.diamond, "cyan");
|
|
101
|
+
|
|
102
|
+
// Display choices with custom formatting
|
|
103
|
+
const skillsR = await prompts({
|
|
104
|
+
type: "multiselect",
|
|
105
|
+
name: "skills",
|
|
106
|
+
message: "Select skills to install",
|
|
107
|
+
choices: skillsInRepo.map(s => ({
|
|
108
|
+
title: s.title,
|
|
109
|
+
value: s.value,
|
|
110
|
+
selected: true // Pre-select all by default
|
|
111
|
+
})),
|
|
112
|
+
hint: "- Space to toggle. Return to submit",
|
|
113
|
+
instructions: false
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (!skillsR.skills || skillsR.skills.length === 0) {
|
|
117
|
+
console.log(`\n ${c.yellow("Cancelled.")}`);
|
|
118
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
selectedSkills = skillsR.skills;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Scope selection
|
|
126
|
+
stepLine();
|
|
127
|
+
const scopeR = await prompts({
|
|
128
|
+
type: "select",
|
|
129
|
+
name: "scope",
|
|
130
|
+
message: "Installation scope",
|
|
131
|
+
choices: [
|
|
132
|
+
{ title: "Project (current directory)", value: "project" },
|
|
133
|
+
{ title: "Global", value: "global" }
|
|
134
|
+
],
|
|
135
|
+
initial: 0
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const targetScope = scopeR.scope === "global" ? GLOBAL_DIR : WORKSPACE;
|
|
139
|
+
|
|
140
|
+
// Summary
|
|
141
|
+
stepLine();
|
|
142
|
+
step("Installation Summary", S.diamond);
|
|
143
|
+
stepLine();
|
|
144
|
+
|
|
145
|
+
let boxContent = "";
|
|
146
|
+
for (const sn of selectedSkills) boxContent += `${c.cyan(sn)}\n ${c.dim(targetScope)}\n\n`;
|
|
147
|
+
const box = boxen(boxContent.trim(), { padding: 1, borderStyle: "round", borderColor: "gray", dimBorder: true });
|
|
148
|
+
box.split("\n").forEach(l => console.log(` ${c.gray(S.branch)} ${l}`));
|
|
149
|
+
|
|
150
|
+
stepLine();
|
|
151
|
+
|
|
152
|
+
// Confirmation
|
|
153
|
+
const confirm = await prompts({ type: "confirm", name: "value", message: "Proceed?", initial: true });
|
|
154
|
+
if (!confirm.value) {
|
|
155
|
+
console.log(`\n ${c.yellow("Cancelled.")}`);
|
|
156
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Install
|
|
161
|
+
stepLine();
|
|
162
|
+
fs.mkdirSync(targetScope, { recursive: true });
|
|
163
|
+
|
|
164
|
+
for (const sn of selectedSkills) {
|
|
165
|
+
const src = path.join(tmp, sn);
|
|
166
|
+
const dest = path.join(targetScope, sn);
|
|
167
|
+
|
|
168
|
+
if (fs.existsSync(dest)) fs.rmSync(dest, { recursive: true, force: true });
|
|
169
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
170
|
+
|
|
171
|
+
const hash = merkleHash(dest);
|
|
172
|
+
fs.writeFileSync(path.join(dest, ".skill-source.json"), JSON.stringify({
|
|
173
|
+
repo: `${org}/${repo}`,
|
|
174
|
+
skill: sn,
|
|
175
|
+
ref: ref || null,
|
|
176
|
+
checksum: hash,
|
|
177
|
+
installedAt: new Date().toISOString()
|
|
178
|
+
}, null, 2));
|
|
179
|
+
|
|
180
|
+
step(`Installed: ${c.bold(sn)}`, S.check, "green");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
184
|
+
|
|
185
|
+
stepLine();
|
|
186
|
+
console.log(` ${c.cyan("Done!")}`);
|
|
187
|
+
console.log();
|
|
188
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview List command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getInstalledSkills } from "../skills.js";
|
|
6
|
+
import { resolveScope, formatBytes } from "../helpers.js";
|
|
7
|
+
import { step, stepLine, S, c, outputJSON } from "../ui.js";
|
|
8
|
+
import { VERBOSE, JSON_OUTPUT } from "../config.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* List installed skills
|
|
12
|
+
*/
|
|
13
|
+
export async function run() {
|
|
14
|
+
stepLine();
|
|
15
|
+
step(c.bold("Installed Skills"), S.diamondFilled, "cyan");
|
|
16
|
+
console.log(` ${c.gray(S.branch)} ${c.dim("Location: " + resolveScope())}`);
|
|
17
|
+
stepLine();
|
|
18
|
+
|
|
19
|
+
const skills = getInstalledSkills();
|
|
20
|
+
|
|
21
|
+
if (skills.length === 0) {
|
|
22
|
+
step(c.dim("No skills installed."), S.diamond);
|
|
23
|
+
stepLine();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (JSON_OUTPUT) {
|
|
28
|
+
outputJSON({ skills }, true);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const s of skills) {
|
|
33
|
+
const icon = s.hasSkillMd ? c.green(S.check) : c.yellow(S.diamond);
|
|
34
|
+
console.log(` ${c.gray(S.branch)} ${icon} ${c.bold(s.name)} ${c.dim("v" + s.version)} ${c.dim("(" + formatBytes(s.size) + ")")}`);
|
|
35
|
+
if (s.description && VERBOSE) {
|
|
36
|
+
console.log(` ${c.gray(S.branch)} ${c.dim(s.description.substring(0, 60))}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
stepLine();
|
|
41
|
+
console.log(` ${c.gray(S.branch)} ${c.dim("Total: " + skills.length + " skill(s)")}`);
|
|
42
|
+
stepLine();
|
|
43
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Lock command - Generate skill-lock.json
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { step, stepLine, success, fatal, outputJSON } from "../ui.js";
|
|
8
|
+
import { WORKSPACE, DRY, cwd, VERSION } from "../config.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate skill-lock.json
|
|
12
|
+
*/
|
|
13
|
+
export async function run() {
|
|
14
|
+
if (!fs.existsSync(WORKSPACE)) {
|
|
15
|
+
fatal("No .agent/skills directory");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
stepLine();
|
|
20
|
+
|
|
21
|
+
const skills = {};
|
|
22
|
+
for (const name of fs.readdirSync(WORKSPACE)) {
|
|
23
|
+
const dir = path.join(WORKSPACE, name);
|
|
24
|
+
if (!fs.statSync(dir).isDirectory()) continue;
|
|
25
|
+
|
|
26
|
+
const mf = path.join(dir, ".skill-source.json");
|
|
27
|
+
if (!fs.existsSync(mf)) continue;
|
|
28
|
+
|
|
29
|
+
const m = JSON.parse(fs.readFileSync(mf, "utf-8"));
|
|
30
|
+
skills[name] = {
|
|
31
|
+
repo: m.repo,
|
|
32
|
+
skill: m.skill,
|
|
33
|
+
ref: m.ref,
|
|
34
|
+
checksum: `sha256:${m.checksum}`,
|
|
35
|
+
publisher: m.publisher || null
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const lock = {
|
|
40
|
+
lockVersion: 1,
|
|
41
|
+
generatedAt: new Date().toISOString(),
|
|
42
|
+
generator: `@dataguruin/add-skill@${VERSION}`,
|
|
43
|
+
skills
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
if (DRY) {
|
|
47
|
+
step("Would generate skill-lock.json");
|
|
48
|
+
outputJSON(lock, true);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fs.mkdirSync(path.join(cwd, ".agent"), { recursive: true });
|
|
53
|
+
fs.writeFileSync(path.join(cwd, ".agent", "skill-lock.json"), JSON.stringify(lock, null, 2));
|
|
54
|
+
|
|
55
|
+
success("skill-lock.json generated");
|
|
56
|
+
stepLine();
|
|
57
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Uninstall command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import prompts from "prompts";
|
|
8
|
+
import { resolveScope, createBackup } from "../helpers.js";
|
|
9
|
+
import { step, stepLine, success, fatal, c } from "../ui.js";
|
|
10
|
+
import { DRY } from "../config.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Remove a skill
|
|
14
|
+
* @param {string} skillName
|
|
15
|
+
*/
|
|
16
|
+
export async function run(skillName) {
|
|
17
|
+
if (!skillName) fatal("Missing skill name");
|
|
18
|
+
|
|
19
|
+
const scope = resolveScope();
|
|
20
|
+
const targetDir = path.join(scope, skillName);
|
|
21
|
+
|
|
22
|
+
if (!fs.existsSync(targetDir)) fatal(`Skill not found: ${skillName}`);
|
|
23
|
+
|
|
24
|
+
stepLine();
|
|
25
|
+
|
|
26
|
+
const confirm = await prompts({
|
|
27
|
+
type: "confirm",
|
|
28
|
+
name: "value",
|
|
29
|
+
message: `Remove skill "${skillName}"?`,
|
|
30
|
+
initial: false
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!confirm.value) {
|
|
34
|
+
step("Cancelled");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (DRY) {
|
|
39
|
+
step(`Would remove: ${targetDir}`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
createBackup(targetDir, skillName);
|
|
44
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
45
|
+
|
|
46
|
+
success(`Removed: ${skillName}`);
|
|
47
|
+
stepLine();
|
|
48
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Update command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import ora from "ora";
|
|
8
|
+
import { resolveScope, createBackup } from "../helpers.js";
|
|
9
|
+
import { step, stepLine, S, c, fatal } from "../ui.js";
|
|
10
|
+
import { DRY } from "../config.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Update a skill
|
|
14
|
+
* @param {string} skillName
|
|
15
|
+
*/
|
|
16
|
+
export async function run(skillName) {
|
|
17
|
+
if (!skillName) fatal("Missing skill name");
|
|
18
|
+
|
|
19
|
+
const scope = resolveScope();
|
|
20
|
+
const targetDir = path.join(scope, skillName);
|
|
21
|
+
|
|
22
|
+
if (!fs.existsSync(targetDir)) fatal(`Skill not found: ${skillName}`);
|
|
23
|
+
|
|
24
|
+
const metaFile = path.join(targetDir, ".skill-source.json");
|
|
25
|
+
if (!fs.existsSync(metaFile)) fatal("Skill metadata not found");
|
|
26
|
+
|
|
27
|
+
const meta = JSON.parse(fs.readFileSync(metaFile, "utf-8"));
|
|
28
|
+
if (!meta.repo || meta.repo === "local") fatal("Cannot update local skill");
|
|
29
|
+
|
|
30
|
+
stepLine();
|
|
31
|
+
|
|
32
|
+
const spinner = ora({
|
|
33
|
+
text: `Updating ${skillName}`,
|
|
34
|
+
prefixText: ` ${c.gray(S.branch)} ${c.cyan(S.diamondFilled)} `,
|
|
35
|
+
color: "cyan"
|
|
36
|
+
}).start();
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
if (!DRY) {
|
|
40
|
+
createBackup(targetDir, skillName);
|
|
41
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const spec = `${meta.repo}#${meta.skill}${meta.ref ? "@" + meta.ref : ""}`;
|
|
45
|
+
|
|
46
|
+
if (DRY) {
|
|
47
|
+
spinner.stop();
|
|
48
|
+
step(`Would update: ${skillName}`);
|
|
49
|
+
} else {
|
|
50
|
+
// Dynamically import install command
|
|
51
|
+
const { run: install } = await import("./install.js");
|
|
52
|
+
await install(spec);
|
|
53
|
+
}
|
|
54
|
+
} catch (err) {
|
|
55
|
+
spinner.stop();
|
|
56
|
+
step(`Failed: ${err.message}`, S.cross, "red");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Validate command - Antigravity spec validation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { getInstalledSkills, parseSkillMdFrontmatter } from "../skills.js";
|
|
8
|
+
import { resolveScope } from "../helpers.js";
|
|
9
|
+
import { step, stepLine, S, c, fatal } from "../ui.js";
|
|
10
|
+
import { VERBOSE, STRICT } from "../config.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validate skills against Antigravity spec
|
|
14
|
+
* @param {string} [skillName] - Optional specific skill
|
|
15
|
+
*/
|
|
16
|
+
export async function run(skillName) {
|
|
17
|
+
const scope = resolveScope();
|
|
18
|
+
let skillsToValidate = [];
|
|
19
|
+
|
|
20
|
+
if (skillName) {
|
|
21
|
+
const sd = path.join(scope, skillName);
|
|
22
|
+
if (!fs.existsSync(sd)) fatal(`Skill not found: ${skillName}`);
|
|
23
|
+
skillsToValidate = [{ name: skillName, path: sd }];
|
|
24
|
+
} else {
|
|
25
|
+
skillsToValidate = getInstalledSkills();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (skillsToValidate.length === 0) {
|
|
29
|
+
stepLine();
|
|
30
|
+
step("No skills to validate", S.diamond);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
stepLine();
|
|
35
|
+
step(c.bold("Antigravity Validation"), S.diamondFilled, "cyan");
|
|
36
|
+
stepLine();
|
|
37
|
+
|
|
38
|
+
let totalErrors = 0, totalWarnings = 0;
|
|
39
|
+
|
|
40
|
+
for (const skill of skillsToValidate) {
|
|
41
|
+
const errors = [], warnings = [];
|
|
42
|
+
const smp = path.join(skill.path, "SKILL.md");
|
|
43
|
+
|
|
44
|
+
if (!fs.existsSync(smp)) {
|
|
45
|
+
errors.push("Missing SKILL.md");
|
|
46
|
+
} else {
|
|
47
|
+
const m = parseSkillMdFrontmatter(smp);
|
|
48
|
+
if (!m.description) errors.push("Missing description");
|
|
49
|
+
else if (m.description.length < 50) warnings.push("Description too short");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const status = errors.length > 0 ? c.red("FAIL") : warnings.length > 0 ? c.yellow("WARN") : c.green("PASS");
|
|
53
|
+
console.log(` ${c.gray(S.branch)} ${status} ${c.bold(skill.name)}`);
|
|
54
|
+
|
|
55
|
+
if (VERBOSE || errors.length || warnings.length) {
|
|
56
|
+
errors.forEach(e => console.log(` ${c.gray(S.branch)} ${c.red("ERROR: " + e)}`));
|
|
57
|
+
warnings.forEach(w => console.log(` ${c.gray(S.branch)} ${c.yellow("WARN: " + w)}`));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
totalErrors += errors.length;
|
|
61
|
+
totalWarnings += warnings.length;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
stepLine();
|
|
65
|
+
console.log(` ${c.gray(S.branch)} Total: ${skillsToValidate.length}, Errors: ${totalErrors}, Warnings: ${totalWarnings}`);
|
|
66
|
+
stepLine();
|
|
67
|
+
|
|
68
|
+
if (STRICT && totalErrors > 0) process.exit(1);
|
|
69
|
+
}
|