@ikyyofc/gemini-cli 3.0.4 → 3.0.5
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/package.json +1 -1
- package/src/skills.js +120 -121
package/package.json
CHANGED
package/src/skills.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
// src/skills.js — Skills manager via npx skills CLI
|
|
2
|
-
// All
|
|
3
|
-
// which is the official skills.sh CLI (github.com/vercel-labs/skills)
|
|
4
|
-
//
|
|
5
|
-
// Skills directories (Gemini agent convention from skills.sh):
|
|
6
|
-
// Project: ./.gemini/skills/ (committed with project)
|
|
7
|
-
// Global: ~/.gemini/skills/ (across all projects)
|
|
8
|
-
// Custom: ./.agents/ (manual / local skills)
|
|
2
|
+
// All skills are consolidated into a single path: ~/.agents/
|
|
9
3
|
import fs from "fs";
|
|
10
4
|
import path from "path";
|
|
11
5
|
import os from "os";
|
|
@@ -14,29 +8,54 @@ import { exec } from "child_process";
|
|
|
14
8
|
|
|
15
9
|
const execAsync = promisify(exec);
|
|
16
10
|
|
|
17
|
-
//
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
// Single canonical location for ALL skills
|
|
12
|
+
export const AGENTS_DIR = path.join(os.homedir(), ".agents");
|
|
13
|
+
|
|
14
|
+
// Agent dirs that npx skills may create (we scan after install to find new files)
|
|
15
|
+
const KNOWN_AGENT_DIRS = [
|
|
16
|
+
".gemini", ".gemini/skills",
|
|
17
|
+
".claude", ".claude/skills",
|
|
18
|
+
".cursor", ".cursor/rules",
|
|
19
|
+
".codex", ".opencode",
|
|
20
|
+
".cline", ".aider",
|
|
21
|
+
".amp", ".copilot",
|
|
22
|
+
];
|
|
21
23
|
|
|
22
24
|
export function ensureSkillsDirs() {
|
|
23
|
-
fs.mkdirSync(
|
|
25
|
+
fs.mkdirSync(AGENTS_DIR, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─────────────────────────────────────────────────────────────────
|
|
29
|
+
// Scan a dir tree for SKILL.md files
|
|
30
|
+
// ─────────────────────────────────────────────────────────────────
|
|
31
|
+
function scanForSkillMds(baseDir, maxDepth = 5) {
|
|
32
|
+
const results = [];
|
|
33
|
+
const SKIP = new Set(["node_modules", ".git", "dist", "build"]);
|
|
34
|
+
const walk = (dir, depth) => {
|
|
35
|
+
if (depth > maxDepth || !fs.existsSync(dir)) return;
|
|
36
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
37
|
+
if (SKIP.has(entry.name)) continue;
|
|
38
|
+
const full = path.join(dir, entry.name);
|
|
39
|
+
if (entry.isDirectory()) walk(full, depth + 1);
|
|
40
|
+
else if (entry.name === "SKILL.md") results.push(full);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
walk(baseDir, 0);
|
|
44
|
+
return results;
|
|
24
45
|
}
|
|
25
46
|
|
|
26
47
|
// ─────────────────────────────────────────────────────────────────
|
|
27
|
-
// Run npx skills
|
|
48
|
+
// Run npx skills — exposes real stderr on failure
|
|
28
49
|
// ─────────────────────────────────────────────────────────────────
|
|
29
50
|
async function npxSkills(args, cwd = process.cwd(), timeout = 120_000) {
|
|
30
51
|
const cmd = `npx --yes skills ${args}`;
|
|
31
52
|
try {
|
|
32
53
|
const { stdout, stderr } = await execAsync(cmd, {
|
|
33
|
-
cwd,
|
|
34
|
-
timeout,
|
|
54
|
+
cwd, timeout,
|
|
35
55
|
env: { ...process.env, NO_COLOR: "1", CI: "1" },
|
|
36
56
|
});
|
|
37
57
|
return { stdout: stdout.trim(), stderr: stderr.trim() };
|
|
38
58
|
} catch (err) {
|
|
39
|
-
// Expose actual npx output in the thrown error message
|
|
40
59
|
const detail = [err.stdout?.trim(), err.stderr?.trim()]
|
|
41
60
|
.filter(Boolean).join("\n") || err.message;
|
|
42
61
|
throw new Error(detail);
|
|
@@ -44,161 +63,141 @@ async function npxSkills(args, cwd = process.cwd(), timeout = 120_000) {
|
|
|
44
63
|
}
|
|
45
64
|
|
|
46
65
|
// ─────────────────────────────────────────────────────────────────
|
|
47
|
-
// Parse
|
|
48
|
-
// e.g. "anthropics/skills@frontend-design"
|
|
49
|
-
// → source="anthropics/skills", skill="frontend-design"
|
|
66
|
+
// Parse "owner/repo@skill-name" shorthand
|
|
50
67
|
// ─────────────────────────────────────────────────────────────────
|
|
51
68
|
function parseSource(raw) {
|
|
52
69
|
const atIdx = raw.indexOf("@");
|
|
53
70
|
if (atIdx > 0 && !raw.startsWith("http") && !raw.startsWith("git@")) {
|
|
54
|
-
return {
|
|
55
|
-
source: raw.slice(0, atIdx),
|
|
56
|
-
skill: raw.slice(atIdx + 1),
|
|
57
|
-
};
|
|
71
|
+
return { source: raw.slice(0, atIdx), skill: raw.slice(atIdx + 1) };
|
|
58
72
|
}
|
|
59
73
|
return { source: raw, skill: null };
|
|
60
74
|
}
|
|
61
75
|
|
|
62
76
|
// ─────────────────────────────────────────────────────────────────
|
|
63
|
-
//
|
|
77
|
+
// After npx install: copy new SKILL.md files → ~/.agents/<slug>/
|
|
78
|
+
// ─────────────────────────────────────────────────────────────────
|
|
79
|
+
function consolidate(cwd, beforeSnapshot) {
|
|
80
|
+
const copied = [];
|
|
81
|
+
const dest = AGENTS_DIR;
|
|
82
|
+
|
|
83
|
+
// 1. Check known agent dirs first
|
|
84
|
+
for (const rel of KNOWN_AGENT_DIRS) {
|
|
85
|
+
for (const f of scanForSkillMds(path.join(cwd, rel), 3)) {
|
|
86
|
+
if (beforeSnapshot.has(f)) continue;
|
|
87
|
+
const slug = path.basename(path.dirname(f));
|
|
88
|
+
const dir = path.join(dest, slug);
|
|
89
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
90
|
+
fs.copyFileSync(f, path.join(dir, "SKILL.md"));
|
|
91
|
+
copied.push(slug);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 2. Broader scan of cwd for anything missed
|
|
96
|
+
if (copied.length === 0) {
|
|
97
|
+
for (const f of scanForSkillMds(cwd, 5)) {
|
|
98
|
+
if (beforeSnapshot.has(f)) continue;
|
|
99
|
+
// Skip anything already inside ~/.agents
|
|
100
|
+
if (f.startsWith(dest)) continue;
|
|
101
|
+
const slug = path.basename(path.dirname(f));
|
|
102
|
+
const dir = path.join(dest, slug);
|
|
103
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
104
|
+
fs.copyFileSync(f, path.join(dir, "SKILL.md"));
|
|
105
|
+
copied.push(slug);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return [...new Set(copied)]; // deduplicate
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ─────────────────────────────────────────────────────────────────
|
|
113
|
+
// Install
|
|
114
|
+
// 1. Snapshot existing SKILL.md paths
|
|
115
|
+
// 2. npx skills add (wherever it installs)
|
|
116
|
+
// 3. Find new SKILL.md files and copy ALL → ~/.agents/
|
|
64
117
|
// ─────────────────────────────────────────────────────────────────
|
|
65
118
|
export async function installSkill(rawSource, opts = {}) {
|
|
66
|
-
const {
|
|
119
|
+
const { skill: explicitSkill = null, all = false } = opts;
|
|
67
120
|
const { source, skill: parsedSkill } = parseSource(rawSource);
|
|
68
121
|
const skillName = explicitSkill ?? parsedSkill;
|
|
69
122
|
|
|
70
|
-
|
|
123
|
+
const cwd = process.cwd();
|
|
124
|
+
const before = new Set(
|
|
125
|
+
KNOWN_AGENT_DIRS.flatMap(rel => scanForSkillMds(path.join(cwd, rel), 3))
|
|
126
|
+
);
|
|
127
|
+
|
|
71
128
|
const parts = ["add", source, "-y"];
|
|
72
|
-
if (global) parts.push("-g");
|
|
73
129
|
if (skillName) parts.push(`--skill "${skillName}"`);
|
|
74
130
|
if (all) parts.push("--skill '*'");
|
|
75
131
|
|
|
76
132
|
const { stdout, stderr } = await npxSkills(parts.join(" "));
|
|
77
|
-
|
|
133
|
+
const installed = consolidate(cwd, before);
|
|
134
|
+
|
|
135
|
+
return { output: stdout || stderr, installed };
|
|
78
136
|
}
|
|
79
137
|
|
|
80
138
|
// ─────────────────────────────────────────────────────────────────
|
|
81
|
-
//
|
|
139
|
+
// Remove — delete from ~/.agents/ then best-effort npx remove
|
|
82
140
|
// ─────────────────────────────────────────────────────────────────
|
|
83
|
-
export async function
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
141
|
+
export async function removeSkillNpx(slug) {
|
|
142
|
+
const skillDir = path.join(AGENTS_DIR, slug);
|
|
143
|
+
if (!fs.existsSync(skillDir)) throw new Error(`"${slug}" not found in ~/.agents/`);
|
|
144
|
+
fs.rmSync(skillDir, { recursive: true, force: true });
|
|
145
|
+
try { await npxSkills(`remove ${slug} -y`); } catch {}
|
|
146
|
+
return { output: `Removed ~/.agents/${slug}` };
|
|
87
147
|
}
|
|
88
148
|
|
|
89
149
|
// ─────────────────────────────────────────────────────────────────
|
|
90
|
-
//
|
|
150
|
+
// Delegate to npx for search / list / update / init
|
|
91
151
|
// ─────────────────────────────────────────────────────────────────
|
|
92
152
|
export async function findSkills(query) {
|
|
93
153
|
const { stdout } = await npxSkills(`find ${JSON.stringify(query)}`);
|
|
94
154
|
return stdout;
|
|
95
155
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// ─────────────────────────────────────────────────────────────────
|
|
100
|
-
export async function removeSkillNpx(slug) {
|
|
101
|
-
const { stdout, stderr } = await npxSkills(`remove ${slug} -y`);
|
|
102
|
-
return { output: stdout || stderr };
|
|
156
|
+
export async function listNpxSkills() {
|
|
157
|
+
const { stdout } = await npxSkills("list");
|
|
158
|
+
return stdout;
|
|
103
159
|
}
|
|
104
|
-
|
|
105
|
-
// ─────────────────────────────────────────────────────────────────
|
|
106
|
-
// Update — npx skills update [slug]
|
|
107
|
-
// ─────────────────────────────────────────────────────────────────
|
|
108
160
|
export async function updateSkill(slug = "") {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
return { output: stdout || stderr };
|
|
161
|
+
const { stdout } = await npxSkills(slug ? `update ${slug} -y` : "update -y");
|
|
162
|
+
return stdout;
|
|
112
163
|
}
|
|
113
|
-
|
|
114
|
-
// ─────────────────────────────────────────────────────────────────
|
|
115
|
-
// Init — npx skills init [name] (creates a SKILL.md template)
|
|
116
|
-
// ─────────────────────────────────────────────────────────────────
|
|
117
164
|
export async function initSkill(name = "") {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
return { output: stdout || stderr };
|
|
165
|
+
const { stdout } = await npxSkills(name ? `init "${name}"` : "init");
|
|
166
|
+
return stdout;
|
|
121
167
|
}
|
|
122
168
|
|
|
123
169
|
// ─────────────────────────────────────────────────────────────────
|
|
124
|
-
// Load skills for agent context injection
|
|
125
|
-
// Scans all skill directories and reads SKILL.md files
|
|
170
|
+
// Load all skills from ~/.agents/ for agent context injection
|
|
126
171
|
// ─────────────────────────────────────────────────────────────────
|
|
127
172
|
export function loadSkills() {
|
|
128
173
|
const skills = [];
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
if (!
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const content = fs.readFileSync(skillMd, "utf8");
|
|
147
|
-
|
|
148
|
-
// Parse optional frontmatter name from SKILL.md
|
|
149
|
-
const nameMatch = content.match(/^#\s+(.+)$/m);
|
|
150
|
-
const name = nameMatch?.[1]?.trim() ?? entry.name;
|
|
151
|
-
|
|
152
|
-
skills.push({ name, slug: entry.name, content, scope, path: skillDir });
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
// Priority: project (.gemini/skills/) > custom (.agents/) > global (~/.gemini/skills/)
|
|
157
|
-
scanDir(PROJECT_SKILLS_DIR(), "project");
|
|
158
|
-
scanDir(CUSTOM_SKILLS_DIR(), "custom");
|
|
159
|
-
scanDir(GLOBAL_SKILLS_DIR, "global");
|
|
160
|
-
|
|
174
|
+
if (!fs.existsSync(AGENTS_DIR)) return skills;
|
|
175
|
+
|
|
176
|
+
for (const entry of fs.readdirSync(AGENTS_DIR, { withFileTypes: true })) {
|
|
177
|
+
if (!entry.isDirectory()) continue;
|
|
178
|
+
const skillMd = path.join(AGENTS_DIR, entry.name, "SKILL.md");
|
|
179
|
+
if (!fs.existsSync(skillMd)) continue;
|
|
180
|
+
|
|
181
|
+
const content = fs.readFileSync(skillMd, "utf8");
|
|
182
|
+
const nameMatch = content.match(/^#\s+(.+)$/m);
|
|
183
|
+
skills.push({
|
|
184
|
+
name: nameMatch?.[1]?.trim() ?? entry.name,
|
|
185
|
+
slug: entry.name,
|
|
186
|
+
content,
|
|
187
|
+
path: path.join(AGENTS_DIR, entry.name),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
161
190
|
return skills;
|
|
162
191
|
}
|
|
163
192
|
|
|
164
|
-
// ─────────────────────────────────────────────────────────────────
|
|
165
|
-
// Build skills section injected into agent system prompt
|
|
166
|
-
// ─────────────────────────────────────────────────────────────────
|
|
167
193
|
export function buildSkillsPrompt(skills) {
|
|
168
194
|
if (!skills.length) return null;
|
|
169
|
-
|
|
170
195
|
const sections = skills.map(s =>
|
|
171
196
|
`### Skill: ${s.name}\n${s.content.trim()}`
|
|
172
197
|
).join("\n\n---\n\n");
|
|
173
|
-
|
|
174
|
-
return `## INSTALLED SKILLS\n\nApply the following skills when relevant to the current task:\n\n${sections}`;
|
|
198
|
+
return `## INSTALLED SKILLS\n\nApply the following skills when relevant:\n\n${sections}`;
|
|
175
199
|
}
|
|
176
200
|
|
|
177
|
-
// ─────────────────────────────────────────────────────────────────
|
|
178
|
-
// Quick disk check — list installed skills without running npx
|
|
179
|
-
// ─────────────────────────────────────────────────────────────────
|
|
180
201
|
export function listInstalledSkills() {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const scanDir = (dir, isGlobal) => {
|
|
184
|
-
if (!fs.existsSync(dir)) return;
|
|
185
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
186
|
-
if (!entry.isDirectory()) continue;
|
|
187
|
-
const skillDir = path.join(dir, entry.name);
|
|
188
|
-
if (!fs.existsSync(path.join(skillDir, "SKILL.md"))) continue;
|
|
189
|
-
const content = fs.readFileSync(path.join(skillDir, "SKILL.md"), "utf8");
|
|
190
|
-
const nameMatch = content.match(/^#\s+(.+)$/m);
|
|
191
|
-
result.push({
|
|
192
|
-
slug: entry.name,
|
|
193
|
-
name: nameMatch?.[1]?.trim() ?? entry.name,
|
|
194
|
-
global: isGlobal,
|
|
195
|
-
dir: skillDir,
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
scanDir(PROJECT_SKILLS_DIR(), false);
|
|
201
|
-
scanDir(CUSTOM_SKILLS_DIR(), false);
|
|
202
|
-
scanDir(GLOBAL_SKILLS_DIR, true);
|
|
203
|
-
return result;
|
|
202
|
+
return loadSkills().map(s => ({ ...s, global: true, dir: s.path }));
|
|
204
203
|
}
|