add-skill-kit 3.2.6 → 3.2.8

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.
@@ -1,164 +1,164 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { homedir } from "os";
4
- import { GLOBAL_DIR } from "./config.js";
5
- import { merkleHash } from "./helpers.js";
6
- import { AGENTS } from "./agents.js";
7
-
8
- const home = homedir();
9
-
10
- /**
11
- * Install a skill to the destination using the specified method.
12
- * @param {string} src - Source directory (temp)
13
- * @param {string} dest - Destination directory (project)
14
- * @param {string} method - 'symlink' or 'copy'
15
- * @param {Object} metadata - Metadata for .skill-source.json
16
- */
17
- export async function installSkill(src, dest, method, metadata) {
18
- if (fs.existsSync(dest)) fs.rmSync(dest, { recursive: true, force: true });
19
-
20
- if (method === "symlink") {
21
- // For symlink: Move to global persistent storage first
22
- // Storage path: ~/.gemini/antigravity/skills/storage/<org>/<repo>/<skill>
23
- // Metadata must contain org, repo, skill
24
- const { repo: repoStr, skill } = metadata;
25
- const [org, repo] = repoStr.split("/");
26
-
27
- const storageBase = path.join(GLOBAL_DIR, "storage", org, repo, skill);
28
-
29
- // Ensure fresh copy in storage
30
- if (fs.existsSync(storageBase)) fs.rmSync(storageBase, { recursive: true, force: true });
31
- fs.mkdirSync(path.dirname(storageBase), { recursive: true });
32
-
33
- // Copy from tmp to storage
34
- await fs.promises.cp(src, storageBase, { recursive: true });
35
-
36
- // Create junction
37
- fs.symlinkSync(storageBase, dest, "junction");
38
- } else {
39
- // Copy directly
40
- await fs.promises.cp(src, dest, { recursive: true });
41
- }
42
-
43
- // Write metadata
44
- const hash = merkleHash(dest);
45
- const metaFile = path.join(dest, ".skill-source.json");
46
-
47
- fs.writeFileSync(metaFile, JSON.stringify({
48
- ...metadata,
49
- checksum: hash,
50
- installedAt: new Date().toISOString(),
51
- method: method
52
- }, null, 2));
53
- }
54
-
55
- /**
56
- * Install a skill to multiple agents
57
- * @param {string} src - Source directory containing the skill
58
- * @param {string} skillName - Name of the skill
59
- * @param {Array<{name: string, displayName: string, skillsDir: string, globalSkillsDir: string}>} agents - Agents to install to
60
- * @param {Object} options - Installation options
61
- * @param {string} options.method - 'symlink' or 'copy'
62
- * @param {string} options.scope - 'project' or 'global'
63
- * @param {Object} options.metadata - Metadata for tracking
64
- * @returns {Promise<{success: Array, failed: Array}>}
65
- */
66
- export async function installSkillForAgents(src, skillName, agents, options = {}) {
67
- const { method = "symlink", scope = "project", metadata = {} } = options;
68
- const results = { success: [], failed: [] };
69
-
70
- // For symlink mode: first copy to canonical location
71
- let canonicalPath = null;
72
-
73
- if (method === "symlink") {
74
- // Canonical: .agents/skills/<skill-name> or ~/.agents/skills/<skill-name>
75
- const baseDir = scope === "global" ? home : process.cwd();
76
- canonicalPath = path.join(baseDir, ".agents", "skills", skillName);
77
-
78
- // Ensure fresh copy in canonical
79
- if (fs.existsSync(canonicalPath)) {
80
- fs.rmSync(canonicalPath, { recursive: true, force: true });
81
- }
82
- fs.mkdirSync(path.dirname(canonicalPath), { recursive: true });
83
-
84
- try {
85
- await fs.promises.cp(src, canonicalPath, { recursive: true });
86
-
87
- // Write metadata to canonical location
88
- const hash = merkleHash(canonicalPath);
89
- const metaFile = path.join(canonicalPath, ".skill-source.json");
90
- fs.writeFileSync(metaFile, JSON.stringify({
91
- ...metadata,
92
- skillName,
93
- checksum: hash,
94
- installedAt: new Date().toISOString(),
95
- method: method,
96
- scope: scope,
97
- agents: agents.map(a => a.name)
98
- }, null, 2));
99
- } catch (err) {
100
- // If canonical copy fails, abort
101
- return {
102
- success: [],
103
- failed: agents.map(a => ({ agent: a.displayName, error: `Canonical copy failed: ${err.message}` }))
104
- };
105
- }
106
- }
107
-
108
- // Install to each agent
109
- for (const agent of agents) {
110
- const agentConfig = AGENTS[agent.name];
111
- if (!agentConfig) {
112
- results.failed.push({ agent: agent.displayName, error: "Unknown agent" });
113
- continue;
114
- }
115
-
116
- // Determine destination path
117
- const baseDir = scope === "global" ? agentConfig.globalSkillsDir : path.join(process.cwd(), agentConfig.skillsDir);
118
- const destPath = path.join(baseDir, skillName);
119
-
120
- try {
121
- // Ensure parent directory exists
122
- fs.mkdirSync(path.dirname(destPath), { recursive: true });
123
-
124
- // Remove existing if any
125
- if (fs.existsSync(destPath)) {
126
- fs.rmSync(destPath, { recursive: true, force: true });
127
- }
128
-
129
- if (method === "symlink" && canonicalPath) {
130
- // Create symlink to canonical location
131
- try {
132
- fs.symlinkSync(canonicalPath, destPath, "junction");
133
- results.success.push({ agent: agent.displayName, path: destPath, mode: "symlink" });
134
- } catch (symlinkErr) {
135
- // Fallback to copy if symlink fails (Windows permissions)
136
- await fs.promises.cp(canonicalPath, destPath, { recursive: true });
137
- results.success.push({ agent: agent.displayName, path: destPath, mode: "copy (symlink failed)" });
138
- }
139
- } else {
140
- // Direct copy
141
- await fs.promises.cp(src, destPath, { recursive: true });
142
-
143
- // Write metadata
144
- const hash = merkleHash(destPath);
145
- const metaFile = path.join(destPath, ".skill-source.json");
146
- fs.writeFileSync(metaFile, JSON.stringify({
147
- ...metadata,
148
- skillName,
149
- checksum: hash,
150
- installedAt: new Date().toISOString(),
151
- method: "copy",
152
- scope: scope
153
- }, null, 2));
154
-
155
- results.success.push({ agent: agent.displayName, path: destPath, mode: "copy" });
156
- }
157
- } catch (err) {
158
- results.failed.push({ agent: agent.displayName, error: err.message });
159
- }
160
- }
161
-
162
- return results;
163
- }
164
-
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { homedir } from "os";
4
+ import { GLOBAL_DIR } from "./config.js";
5
+ import { merkleHash } from "./helpers.js";
6
+ import { AGENTS } from "./agents.js";
7
+
8
+ const home = homedir();
9
+
10
+ /**
11
+ * Install a skill to the destination using the specified method.
12
+ * @param {string} src - Source directory (temp)
13
+ * @param {string} dest - Destination directory (project)
14
+ * @param {string} method - 'symlink' or 'copy'
15
+ * @param {Object} metadata - Metadata for .skill-source.json
16
+ */
17
+ export async function installSkill(src, dest, method, metadata) {
18
+ if (fs.existsSync(dest)) fs.rmSync(dest, { recursive: true, force: true });
19
+
20
+ if (method === "symlink") {
21
+ // For symlink: Move to global persistent storage first
22
+ // Storage path: ~/.gemini/antigravity/skills/storage/<org>/<repo>/<skill>
23
+ // Metadata must contain org, repo, skill
24
+ const { repo: repoStr, skill } = metadata;
25
+ const [org, repo] = repoStr.split("/");
26
+
27
+ const storageBase = path.join(GLOBAL_DIR, "storage", org, repo, skill);
28
+
29
+ // Ensure fresh copy in storage
30
+ if (fs.existsSync(storageBase)) fs.rmSync(storageBase, { recursive: true, force: true });
31
+ fs.mkdirSync(path.dirname(storageBase), { recursive: true });
32
+
33
+ // Copy from tmp to storage
34
+ await fs.promises.cp(src, storageBase, { recursive: true });
35
+
36
+ // Create junction
37
+ fs.symlinkSync(storageBase, dest, "junction");
38
+ } else {
39
+ // Copy directly
40
+ await fs.promises.cp(src, dest, { recursive: true });
41
+ }
42
+
43
+ // Write metadata
44
+ const hash = merkleHash(dest);
45
+ const metaFile = path.join(dest, ".skill-source.json");
46
+
47
+ fs.writeFileSync(metaFile, JSON.stringify({
48
+ ...metadata,
49
+ checksum: hash,
50
+ installedAt: new Date().toISOString(),
51
+ method: method
52
+ }, null, 2));
53
+ }
54
+
55
+ /**
56
+ * Install a skill to multiple agents
57
+ * @param {string} src - Source directory containing the skill
58
+ * @param {string} skillName - Name of the skill
59
+ * @param {Array<{name: string, displayName: string, skillsDir: string, globalSkillsDir: string}>} agents - Agents to install to
60
+ * @param {Object} options - Installation options
61
+ * @param {string} options.method - 'symlink' or 'copy'
62
+ * @param {string} options.scope - 'project' or 'global'
63
+ * @param {Object} options.metadata - Metadata for tracking
64
+ * @returns {Promise<{success: Array, failed: Array}>}
65
+ */
66
+ export async function installSkillForAgents(src, skillName, agents, options = {}) {
67
+ const { method = "symlink", scope = "project", metadata = {} } = options;
68
+ const results = { success: [], failed: [] };
69
+
70
+ // For symlink mode: first copy to canonical location
71
+ let canonicalPath = null;
72
+
73
+ if (method === "symlink") {
74
+ // Canonical: .agents/skills/<skill-name> or ~/.agents/skills/<skill-name>
75
+ const baseDir = scope === "global" ? home : process.cwd();
76
+ canonicalPath = path.join(baseDir, ".agents", "skills", skillName);
77
+
78
+ // Ensure fresh copy in canonical
79
+ if (fs.existsSync(canonicalPath)) {
80
+ fs.rmSync(canonicalPath, { recursive: true, force: true });
81
+ }
82
+ fs.mkdirSync(path.dirname(canonicalPath), { recursive: true });
83
+
84
+ try {
85
+ await fs.promises.cp(src, canonicalPath, { recursive: true });
86
+
87
+ // Write metadata to canonical location
88
+ const hash = merkleHash(canonicalPath);
89
+ const metaFile = path.join(canonicalPath, ".skill-source.json");
90
+ fs.writeFileSync(metaFile, JSON.stringify({
91
+ ...metadata,
92
+ skillName,
93
+ checksum: hash,
94
+ installedAt: new Date().toISOString(),
95
+ method: method,
96
+ scope: scope,
97
+ agents: agents.map(a => a.name)
98
+ }, null, 2));
99
+ } catch (err) {
100
+ // If canonical copy fails, abort
101
+ return {
102
+ success: [],
103
+ failed: agents.map(a => ({ agent: a.displayName, error: `Canonical copy failed: ${err.message}` }))
104
+ };
105
+ }
106
+ }
107
+
108
+ // Install to each agent
109
+ for (const agent of agents) {
110
+ const agentConfig = AGENTS[agent.name];
111
+ if (!agentConfig) {
112
+ results.failed.push({ agent: agent.displayName, error: "Unknown agent" });
113
+ continue;
114
+ }
115
+
116
+ // Determine destination path
117
+ const baseDir = scope === "global" ? agentConfig.globalSkillsDir : path.join(process.cwd(), agentConfig.skillsDir);
118
+ const destPath = path.join(baseDir, skillName);
119
+
120
+ try {
121
+ // Ensure parent directory exists
122
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
123
+
124
+ // Remove existing if any
125
+ if (fs.existsSync(destPath)) {
126
+ fs.rmSync(destPath, { recursive: true, force: true });
127
+ }
128
+
129
+ if (method === "symlink" && canonicalPath) {
130
+ // Create symlink to canonical location
131
+ try {
132
+ fs.symlinkSync(canonicalPath, destPath, "junction");
133
+ results.success.push({ agent: agent.displayName, path: destPath, mode: "symlink" });
134
+ } catch (symlinkErr) {
135
+ // Fallback to copy if symlink fails (Windows permissions)
136
+ await fs.promises.cp(canonicalPath, destPath, { recursive: true });
137
+ results.success.push({ agent: agent.displayName, path: destPath, mode: "copy (symlink failed)" });
138
+ }
139
+ } else {
140
+ // Direct copy
141
+ await fs.promises.cp(src, destPath, { recursive: true });
142
+
143
+ // Write metadata
144
+ const hash = merkleHash(destPath);
145
+ const metaFile = path.join(destPath, ".skill-source.json");
146
+ fs.writeFileSync(metaFile, JSON.stringify({
147
+ ...metadata,
148
+ skillName,
149
+ checksum: hash,
150
+ installedAt: new Date().toISOString(),
151
+ method: "copy",
152
+ scope: scope
153
+ }, null, 2));
154
+
155
+ results.success.push({ agent: agent.displayName, path: destPath, mode: "copy" });
156
+ }
157
+ } catch (err) {
158
+ results.failed.push({ agent: agent.displayName, error: err.message });
159
+ }
160
+ }
161
+
162
+ return results;
163
+ }
164
+
package/bin/lib/skills.js CHANGED
@@ -1,119 +1,119 @@
1
- /**
2
- * @fileoverview Skill detection and parsing
3
- */
4
-
5
- import fs from "fs";
6
- import path from "path";
7
- import { resolveScope } from "./helpers.js";
8
- import { getDirSize } from "./helpers.js";
9
-
10
- /**
11
- * Parse SKILL.md YAML frontmatter
12
- * @param {string} p - Path to SKILL.md
13
- * @returns {import('./types.js').SkillMeta}
14
- */
15
- export function parseSkillMdFrontmatter(p) {
16
- try {
17
- const content = fs.readFileSync(p, "utf-8");
18
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
19
- if (!match) return {};
20
-
21
- /** @type {import('./types.js').SkillMeta} */
22
- const meta = {};
23
-
24
- for (const line of match[1].split(/\r?\n/)) {
25
- const i = line.indexOf(":");
26
- if (i === -1) continue;
27
- const key = line.substring(0, i).trim();
28
- const val = line.substring(i + 1).trim();
29
- if (key === "tags") meta.tags = val.split(",").map(t => t.trim()).filter(Boolean);
30
- else if (key && val) meta[key] = val;
31
- }
32
- return meta;
33
- } catch (err) {
34
- if (process.env.DEBUG) console.error(`parseSkillMdFrontmatter error: ${err.message}`);
35
- return {};
36
- }
37
- }
38
-
39
- /**
40
- * Detect skill directory structure
41
- * @param {string} dir - Skill directory
42
- * @returns {import('./types.js').SkillStructure}
43
- */
44
- export function detectSkillStructure(dir) {
45
- /** @type {import('./types.js').SkillStructure} */
46
- const s = {
47
- hasResources: false,
48
- hasExamples: false,
49
- hasScripts: false,
50
- hasConstitution: false,
51
- hasDoctrines: false,
52
- hasEnforcement: false,
53
- hasProposals: false,
54
- directories: [],
55
- files: []
56
- };
57
-
58
- try {
59
- for (const item of fs.readdirSync(dir)) {
60
- const full = path.join(dir, item);
61
- if (fs.statSync(full).isDirectory()) {
62
- s.directories.push(item);
63
- const l = item.toLowerCase();
64
- if (l === "resources") s.hasResources = true;
65
- if (l === "examples") s.hasExamples = true;
66
- if (l === "scripts") s.hasScripts = true;
67
- if (l === "constitution") s.hasConstitution = true;
68
- if (l === "doctrines") s.hasDoctrines = true;
69
- if (l === "enforcement") s.hasEnforcement = true;
70
- if (l === "proposals") s.hasProposals = true;
71
- } else {
72
- s.files.push(item);
73
- }
74
- }
75
- } catch (err) {
76
- if (process.env.DEBUG) console.error(`detectSkillStructure error: ${err.message}`);
77
- }
78
- return s;
79
- }
80
-
81
- /**
82
- * Get all installed skills
83
- * @returns {import('./types.js').Skill[]}
84
- */
85
- export function getInstalledSkills() {
86
- const scope = resolveScope();
87
- /** @type {import('./types.js').Skill[]} */
88
- const skills = [];
89
-
90
- if (!fs.existsSync(scope)) return skills;
91
-
92
- for (const name of fs.readdirSync(scope)) {
93
- const dir = path.join(scope, name);
94
- if (!fs.statSync(dir).isDirectory()) continue;
95
-
96
- const metaFile = path.join(dir, ".skill-source.json");
97
- const skillFile = path.join(dir, "SKILL.md");
98
-
99
- if (fs.existsSync(metaFile) || fs.existsSync(skillFile)) {
100
- const meta = fs.existsSync(metaFile) ? JSON.parse(fs.readFileSync(metaFile, "utf-8")) : {};
101
- const hasSkillMd = fs.existsSync(skillFile);
102
- const skillMeta = hasSkillMd ? parseSkillMdFrontmatter(skillFile) : {};
103
-
104
- skills.push({
105
- name,
106
- path: dir,
107
- ...meta,
108
- hasSkillMd,
109
- description: skillMeta.description || meta.description || "",
110
- tags: skillMeta.tags || [],
111
- author: skillMeta.author || meta.publisher || "",
112
- version: skillMeta.version || meta.ref || "unknown",
113
- structure: detectSkillStructure(dir),
114
- size: getDirSize(dir)
115
- });
116
- }
117
- }
118
- return skills;
119
- }
1
+ /**
2
+ * @fileoverview Skill detection and parsing
3
+ */
4
+
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import { resolveScope } from "./helpers.js";
8
+ import { getDirSize } from "./helpers.js";
9
+
10
+ /**
11
+ * Parse SKILL.md YAML frontmatter
12
+ * @param {string} p - Path to SKILL.md
13
+ * @returns {import('./types.js').SkillMeta}
14
+ */
15
+ export function parseSkillMdFrontmatter(p) {
16
+ try {
17
+ const content = fs.readFileSync(p, "utf-8");
18
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
19
+ if (!match) return {};
20
+
21
+ /** @type {import('./types.js').SkillMeta} */
22
+ const meta = {};
23
+
24
+ for (const line of match[1].split(/\r?\n/)) {
25
+ const i = line.indexOf(":");
26
+ if (i === -1) continue;
27
+ const key = line.substring(0, i).trim();
28
+ const val = line.substring(i + 1).trim();
29
+ if (key === "tags") meta.tags = val.split(",").map(t => t.trim()).filter(Boolean);
30
+ else if (key && val) meta[key] = val;
31
+ }
32
+ return meta;
33
+ } catch (err) {
34
+ if (process.env.DEBUG) console.error(`parseSkillMdFrontmatter error: ${err.message}`);
35
+ return {};
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Detect skill directory structure
41
+ * @param {string} dir - Skill directory
42
+ * @returns {import('./types.js').SkillStructure}
43
+ */
44
+ export function detectSkillStructure(dir) {
45
+ /** @type {import('./types.js').SkillStructure} */
46
+ const s = {
47
+ hasResources: false,
48
+ hasExamples: false,
49
+ hasScripts: false,
50
+ hasConstitution: false,
51
+ hasDoctrines: false,
52
+ hasEnforcement: false,
53
+ hasProposals: false,
54
+ directories: [],
55
+ files: []
56
+ };
57
+
58
+ try {
59
+ for (const item of fs.readdirSync(dir)) {
60
+ const full = path.join(dir, item);
61
+ if (fs.statSync(full).isDirectory()) {
62
+ s.directories.push(item);
63
+ const l = item.toLowerCase();
64
+ if (l === "resources") s.hasResources = true;
65
+ if (l === "examples") s.hasExamples = true;
66
+ if (l === "scripts") s.hasScripts = true;
67
+ if (l === "constitution") s.hasConstitution = true;
68
+ if (l === "doctrines") s.hasDoctrines = true;
69
+ if (l === "enforcement") s.hasEnforcement = true;
70
+ if (l === "proposals") s.hasProposals = true;
71
+ } else {
72
+ s.files.push(item);
73
+ }
74
+ }
75
+ } catch (err) {
76
+ if (process.env.DEBUG) console.error(`detectSkillStructure error: ${err.message}`);
77
+ }
78
+ return s;
79
+ }
80
+
81
+ /**
82
+ * Get all installed skills
83
+ * @returns {import('./types.js').Skill[]}
84
+ */
85
+ export function getInstalledSkills() {
86
+ const scope = resolveScope();
87
+ /** @type {import('./types.js').Skill[]} */
88
+ const skills = [];
89
+
90
+ if (!fs.existsSync(scope)) return skills;
91
+
92
+ for (const name of fs.readdirSync(scope)) {
93
+ const dir = path.join(scope, name);
94
+ if (!fs.statSync(dir).isDirectory()) continue;
95
+
96
+ const metaFile = path.join(dir, ".skill-source.json");
97
+ const skillFile = path.join(dir, "SKILL.md");
98
+
99
+ if (fs.existsSync(metaFile) || fs.existsSync(skillFile)) {
100
+ const meta = fs.existsSync(metaFile) ? JSON.parse(fs.readFileSync(metaFile, "utf-8")) : {};
101
+ const hasSkillMd = fs.existsSync(skillFile);
102
+ const skillMeta = hasSkillMd ? parseSkillMdFrontmatter(skillFile) : {};
103
+
104
+ skills.push({
105
+ name,
106
+ path: dir,
107
+ ...meta,
108
+ hasSkillMd,
109
+ description: skillMeta.description || meta.description || "",
110
+ tags: skillMeta.tags || [],
111
+ author: skillMeta.author || meta.publisher || "",
112
+ version: skillMeta.version || meta.ref || "unknown",
113
+ structure: detectSkillStructure(dir),
114
+ size: getDirSize(dir)
115
+ });
116
+ }
117
+ }
118
+ return skills;
119
+ }