gemkit-cli 0.2.3 → 0.3.1
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.md +141 -7
- package/dist/commands/agent/index.d.ts +9 -0
- package/dist/commands/agent/index.js +1329 -0
- package/dist/commands/cache/index.d.ts +5 -0
- package/dist/commands/cache/index.js +43 -0
- package/dist/commands/catalog/index.d.ts +2 -0
- package/dist/commands/catalog/index.js +57 -0
- package/dist/commands/config/index.d.ts +7 -0
- package/dist/commands/config/index.js +122 -0
- package/dist/commands/convert/index.d.ts +8 -0
- package/dist/commands/convert/index.js +391 -0
- package/dist/commands/doctor/index.d.ts +2 -0
- package/dist/commands/doctor/index.js +243 -0
- package/dist/commands/extension/index.d.ts +5 -0
- package/dist/commands/extension/index.js +52 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.js +37 -0
- package/dist/commands/init/index.d.ts +6 -0
- package/dist/commands/init/index.js +345 -0
- package/dist/commands/new/index.d.ts +5 -0
- package/dist/commands/new/index.js +49 -0
- package/dist/commands/office/index.d.ts +5 -0
- package/dist/commands/office/index.js +283 -0
- package/dist/commands/paste/index.d.ts +10 -0
- package/dist/commands/paste/index.js +533 -0
- package/dist/commands/plan/index.d.ts +8 -0
- package/dist/commands/plan/index.js +247 -0
- package/dist/commands/session/index.d.ts +8 -0
- package/dist/commands/session/index.js +289 -0
- package/dist/commands/tokens/index.d.ts +6 -0
- package/dist/commands/tokens/index.js +148 -0
- package/dist/commands/update/index.d.ts +26 -0
- package/dist/commands/update/index.js +199 -0
- package/dist/commands/versions/index.d.ts +5 -0
- package/dist/commands/versions/index.js +39 -0
- package/dist/domains/agent/index.d.ts +8 -0
- package/dist/domains/agent/index.js +8 -0
- package/dist/domains/agent/mappings.d.ts +32 -0
- package/dist/domains/agent/mappings.js +164 -0
- package/dist/domains/agent/profile.d.ts +26 -0
- package/dist/domains/agent/profile.js +225 -0
- package/dist/domains/agent/pty-context.d.ts +11 -0
- package/dist/domains/agent/pty-context.js +83 -0
- package/dist/domains/agent/pty-providers.d.ts +18 -0
- package/dist/domains/agent/pty-providers.js +66 -0
- package/dist/domains/agent/pty-session.d.ts +33 -0
- package/dist/domains/agent/pty-session.js +82 -0
- package/dist/domains/agent/pty-types.d.ts +127 -0
- package/dist/domains/agent/pty-types.js +4 -0
- package/dist/domains/agent/search.d.ts +45 -0
- package/dist/domains/agent/search.js +614 -0
- package/dist/domains/agent/types.d.ts +78 -0
- package/dist/domains/agent/types.js +5 -0
- package/dist/domains/agent-office/documents-scanner.d.ts +9 -0
- package/dist/domains/agent-office/documents-scanner.js +143 -0
- package/dist/domains/agent-office/event-emitter.d.ts +43 -0
- package/dist/domains/agent-office/event-emitter.js +86 -0
- package/dist/domains/agent-office/file-watcher.d.ts +40 -0
- package/dist/domains/agent-office/file-watcher.js +173 -0
- package/dist/domains/agent-office/icons.d.ts +11 -0
- package/dist/domains/agent-office/icons.js +36 -0
- package/dist/domains/agent-office/index.d.ts +12 -0
- package/dist/domains/agent-office/index.js +20 -0
- package/dist/domains/agent-office/renderer/web/assets.d.ts +11 -0
- package/dist/domains/agent-office/renderer/web/assets.js +3419 -0
- package/dist/domains/agent-office/renderer/web/server.d.ts +42 -0
- package/dist/domains/agent-office/renderer/web/server.js +228 -0
- package/dist/domains/agent-office/renderer/web.d.ts +30 -0
- package/dist/domains/agent-office/renderer/web.js +111 -0
- package/dist/domains/agent-office/session-bridge.d.ts +23 -0
- package/dist/domains/agent-office/session-bridge.js +171 -0
- package/dist/domains/agent-office/state-machine.d.ts +5 -0
- package/dist/domains/agent-office/state-machine.js +82 -0
- package/dist/domains/agent-office/types.d.ts +91 -0
- package/dist/domains/agent-office/types.js +4 -0
- package/dist/domains/cache/index.d.ts +1 -0
- package/dist/domains/cache/index.js +1 -0
- package/dist/domains/cache/manager.d.ts +22 -0
- package/dist/domains/cache/manager.js +84 -0
- package/dist/domains/config/index.d.ts +5 -0
- package/dist/domains/config/index.js +5 -0
- package/dist/domains/config/manager.d.ts +24 -0
- package/dist/domains/config/manager.js +85 -0
- package/dist/domains/config/schema.d.ts +17 -0
- package/dist/domains/config/schema.js +96 -0
- package/dist/domains/convert/converter.d.ts +78 -0
- package/dist/domains/convert/converter.js +471 -0
- package/dist/domains/convert/index.d.ts +5 -0
- package/dist/domains/convert/index.js +5 -0
- package/dist/domains/convert/types.d.ts +88 -0
- package/dist/domains/convert/types.js +18 -0
- package/dist/domains/github/download.d.ts +12 -0
- package/dist/domains/github/download.js +51 -0
- package/dist/domains/github/index.d.ts +2 -0
- package/dist/domains/github/index.js +2 -0
- package/dist/domains/github/releases.d.ts +16 -0
- package/dist/domains/github/releases.js +68 -0
- package/dist/domains/installation/conflict.d.ts +13 -0
- package/dist/domains/installation/conflict.js +38 -0
- package/dist/domains/installation/file-sync.d.ts +16 -0
- package/dist/domains/installation/file-sync.js +77 -0
- package/dist/domains/installation/index.d.ts +3 -0
- package/dist/domains/installation/index.js +3 -0
- package/dist/domains/installation/metadata.d.ts +20 -0
- package/dist/domains/installation/metadata.js +52 -0
- package/dist/domains/plan/index.d.ts +2 -0
- package/dist/domains/plan/index.js +2 -0
- package/dist/domains/plan/resolver.d.ts +24 -0
- package/dist/domains/plan/resolver.js +164 -0
- package/dist/domains/plan/types.d.ts +13 -0
- package/dist/domains/plan/types.js +4 -0
- package/dist/domains/session/env.d.ts +51 -0
- package/dist/domains/session/env.js +118 -0
- package/dist/domains/session/index.d.ts +8 -0
- package/dist/domains/session/index.js +8 -0
- package/dist/domains/session/manager.d.ts +56 -0
- package/dist/domains/session/manager.js +205 -0
- package/dist/domains/session/paths.d.ts +6 -0
- package/dist/domains/session/paths.js +6 -0
- package/dist/domains/session/types.d.ts +121 -0
- package/dist/domains/session/types.js +5 -0
- package/dist/domains/session/writer.d.ts +82 -0
- package/dist/domains/session/writer.js +431 -0
- package/dist/domains/tokens/index.d.ts +5 -0
- package/dist/domains/tokens/index.js +5 -0
- package/dist/domains/tokens/pricing.d.ts +38 -0
- package/dist/domains/tokens/pricing.js +129 -0
- package/dist/domains/tokens/scanner.d.ts +42 -0
- package/dist/domains/tokens/scanner.js +168 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +90 -59
- package/dist/services/aipty.d.ts +76 -0
- package/dist/services/aipty.js +276 -0
- package/dist/services/archive.d.ts +22 -0
- package/dist/services/archive.js +53 -0
- package/dist/services/auto-update.d.ts +26 -0
- package/dist/services/auto-update.js +117 -0
- package/dist/services/hash.d.ts +36 -0
- package/dist/services/hash.js +63 -0
- package/dist/services/logger.d.ts +28 -0
- package/dist/services/logger.js +102 -0
- package/dist/services/music.d.ts +67 -0
- package/dist/services/music.js +290 -0
- package/dist/services/npm.d.ts +22 -0
- package/dist/services/npm.js +65 -0
- package/dist/services/pty-client.d.ts +66 -0
- package/dist/services/pty-client.js +154 -0
- package/dist/services/pty-server.d.ts +102 -0
- package/dist/services/pty-server.js +613 -0
- package/dist/types/index.d.ts +155 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/colors.d.ts +43 -0
- package/dist/utils/colors.js +98 -0
- package/dist/utils/errors.d.ts +24 -0
- package/dist/utils/errors.js +56 -0
- package/dist/utils/paths.d.ts +46 -0
- package/dist/utils/paths.js +89 -0
- package/dist/utils/platform.d.ts +11 -0
- package/dist/utils/platform.js +31 -0
- package/package.json +55 -54
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill to Extension Converter
|
|
3
|
+
* Converts .claude/skills to .gemini/extensions
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync, readdirSync, mkdirSync, writeFileSync, copyFileSync, statSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { MODEL_MAPPING } from './types.js';
|
|
8
|
+
// Default paths
|
|
9
|
+
const CLAUDE_SKILLS_DIR = '.claude/skills';
|
|
10
|
+
const GEMINI_EXTENSIONS_DIR = '.gemini/extensions';
|
|
11
|
+
const CLAUDE_AGENTS_DIR = '.claude/agents';
|
|
12
|
+
const GEMINI_AGENTS_DIR = '.gemini/agents';
|
|
13
|
+
/**
|
|
14
|
+
* Parse YAML-like frontmatter from markdown file
|
|
15
|
+
*/
|
|
16
|
+
export function parseFrontmatter(content) {
|
|
17
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
18
|
+
if (!match)
|
|
19
|
+
return null;
|
|
20
|
+
const frontmatter = { name: '', description: '' };
|
|
21
|
+
const lines = match[1].split(/\r?\n/);
|
|
22
|
+
for (const line of lines) {
|
|
23
|
+
const colonIndex = line.indexOf(':');
|
|
24
|
+
if (colonIndex === -1)
|
|
25
|
+
continue;
|
|
26
|
+
const key = line.slice(0, colonIndex).trim();
|
|
27
|
+
let value = line.slice(colonIndex + 1).trim();
|
|
28
|
+
// Remove quotes if present
|
|
29
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
30
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
31
|
+
value = value.slice(1, -1);
|
|
32
|
+
}
|
|
33
|
+
frontmatter[key] = value;
|
|
34
|
+
}
|
|
35
|
+
return frontmatter;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get skills directory path
|
|
39
|
+
*/
|
|
40
|
+
export function getSkillsDir(projectDir = process.cwd()) {
|
|
41
|
+
return join(projectDir, CLAUDE_SKILLS_DIR);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get extensions directory path
|
|
45
|
+
*/
|
|
46
|
+
export function getExtensionsDir(projectDir = process.cwd()) {
|
|
47
|
+
return join(projectDir, GEMINI_EXTENSIONS_DIR);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* List all available Claude skills
|
|
51
|
+
*/
|
|
52
|
+
export function listSkills(projectDir = process.cwd()) {
|
|
53
|
+
const skillsDir = getSkillsDir(projectDir);
|
|
54
|
+
if (!existsSync(skillsDir)) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
const skills = [];
|
|
58
|
+
try {
|
|
59
|
+
const dirs = readdirSync(skillsDir, { withFileTypes: true })
|
|
60
|
+
.filter(d => d.isDirectory())
|
|
61
|
+
.map(d => d.name);
|
|
62
|
+
for (const name of dirs) {
|
|
63
|
+
const skillPath = join(skillsDir, name);
|
|
64
|
+
const skillMdPath = join(skillPath, 'SKILL.md');
|
|
65
|
+
const referencesPath = join(skillPath, 'references');
|
|
66
|
+
const hasSkillMd = existsSync(skillMdPath);
|
|
67
|
+
const hasReferences = existsSync(referencesPath) && statSync(referencesPath).isDirectory();
|
|
68
|
+
let description;
|
|
69
|
+
if (hasSkillMd) {
|
|
70
|
+
try {
|
|
71
|
+
const content = readFileSync(skillMdPath, 'utf-8');
|
|
72
|
+
const frontmatter = parseFrontmatter(content);
|
|
73
|
+
description = frontmatter?.description;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Ignore parse errors
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
skills.push({
|
|
80
|
+
name,
|
|
81
|
+
path: skillPath,
|
|
82
|
+
hasSkillMd,
|
|
83
|
+
hasReferences,
|
|
84
|
+
description
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Return empty on error
|
|
90
|
+
}
|
|
91
|
+
return skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Check if an extension already exists
|
|
95
|
+
*/
|
|
96
|
+
export function extensionExists(name, projectDir = process.cwd()) {
|
|
97
|
+
const extPath = join(getExtensionsDir(projectDir), name);
|
|
98
|
+
return existsSync(extPath);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Generate gemini-extension.json content
|
|
102
|
+
*/
|
|
103
|
+
export function generateExtensionJson(frontmatter, hasReferences) {
|
|
104
|
+
const contextFileName = ['SKILL.md'];
|
|
105
|
+
if (hasReferences) {
|
|
106
|
+
contextFileName.push('references/*.md');
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
name: frontmatter.name || '',
|
|
110
|
+
version: '1.0.0',
|
|
111
|
+
description: frontmatter.description || '',
|
|
112
|
+
license: 'MIT',
|
|
113
|
+
contextFileName
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Copy directory recursively
|
|
118
|
+
*/
|
|
119
|
+
function copyDirRecursive(src, dest, created) {
|
|
120
|
+
if (!existsSync(dest)) {
|
|
121
|
+
mkdirSync(dest, { recursive: true });
|
|
122
|
+
created.push(dest);
|
|
123
|
+
}
|
|
124
|
+
const entries = readdirSync(src, { withFileTypes: true });
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
const srcPath = join(src, entry.name);
|
|
127
|
+
const destPath = join(dest, entry.name);
|
|
128
|
+
if (entry.isDirectory()) {
|
|
129
|
+
copyDirRecursive(srcPath, destPath, created);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
copyFileSync(srcPath, destPath);
|
|
133
|
+
created.push(destPath);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Convert a single skill to extension
|
|
139
|
+
*/
|
|
140
|
+
export function convertSkill(skillName, options = {}, projectDir = process.cwd()) {
|
|
141
|
+
const result = {
|
|
142
|
+
skill: skillName,
|
|
143
|
+
success: false,
|
|
144
|
+
created: [],
|
|
145
|
+
skipped: [],
|
|
146
|
+
errors: []
|
|
147
|
+
};
|
|
148
|
+
const skillsDir = getSkillsDir(projectDir);
|
|
149
|
+
const extensionsDir = getExtensionsDir(projectDir);
|
|
150
|
+
const skillPath = join(skillsDir, skillName);
|
|
151
|
+
const extPath = join(extensionsDir, skillName);
|
|
152
|
+
// Check if skill exists
|
|
153
|
+
if (!existsSync(skillPath)) {
|
|
154
|
+
result.errors.push(`Skill not found: ${skillName}`);
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
// Check if SKILL.md exists
|
|
158
|
+
const skillMdPath = join(skillPath, 'SKILL.md');
|
|
159
|
+
if (!existsSync(skillMdPath)) {
|
|
160
|
+
result.errors.push(`SKILL.md not found in ${skillName}`);
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
// Check if extension already exists
|
|
164
|
+
if (extensionExists(skillName, projectDir) && !options.force) {
|
|
165
|
+
result.skipped.push(`Extension already exists: ${skillName} (use --force to overwrite)`);
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
// Parse frontmatter
|
|
169
|
+
let frontmatter = null;
|
|
170
|
+
try {
|
|
171
|
+
const content = readFileSync(skillMdPath, 'utf-8');
|
|
172
|
+
frontmatter = parseFrontmatter(content);
|
|
173
|
+
}
|
|
174
|
+
catch (e) {
|
|
175
|
+
result.errors.push(`Failed to read SKILL.md: ${e instanceof Error ? e.message : String(e)}`);
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
if (!frontmatter || !frontmatter.name) {
|
|
179
|
+
// Try to use directory name if no frontmatter
|
|
180
|
+
frontmatter = {
|
|
181
|
+
name: skillName,
|
|
182
|
+
description: ''
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
// Check for references
|
|
186
|
+
const referencesPath = join(skillPath, 'references');
|
|
187
|
+
const hasReferences = existsSync(referencesPath) && statSync(referencesPath).isDirectory();
|
|
188
|
+
// Generate extension JSON
|
|
189
|
+
const extensionJson = generateExtensionJson(frontmatter, hasReferences);
|
|
190
|
+
if (options.dryRun) {
|
|
191
|
+
result.created.push(`[DRY RUN] Would create: ${extPath}`);
|
|
192
|
+
result.created.push(`[DRY RUN] Would create: ${join(extPath, 'gemini-extension.json')}`);
|
|
193
|
+
result.created.push(`[DRY RUN] Would copy: SKILL.md`);
|
|
194
|
+
if (hasReferences) {
|
|
195
|
+
result.created.push(`[DRY RUN] Would copy: references/`);
|
|
196
|
+
}
|
|
197
|
+
result.success = true;
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
// Create extension directory
|
|
202
|
+
if (!existsSync(extPath)) {
|
|
203
|
+
mkdirSync(extPath, { recursive: true });
|
|
204
|
+
result.created.push(extPath);
|
|
205
|
+
}
|
|
206
|
+
// Write gemini-extension.json
|
|
207
|
+
const jsonPath = join(extPath, 'gemini-extension.json');
|
|
208
|
+
writeFileSync(jsonPath, JSON.stringify(extensionJson, null, 2) + '\n');
|
|
209
|
+
result.created.push(jsonPath);
|
|
210
|
+
// Copy SKILL.md
|
|
211
|
+
const destSkillMd = join(extPath, 'SKILL.md');
|
|
212
|
+
copyFileSync(skillMdPath, destSkillMd);
|
|
213
|
+
result.created.push(destSkillMd);
|
|
214
|
+
// Copy references if exists
|
|
215
|
+
if (hasReferences) {
|
|
216
|
+
const destReferences = join(extPath, 'references');
|
|
217
|
+
copyDirRecursive(referencesPath, destReferences, result.created);
|
|
218
|
+
}
|
|
219
|
+
// Copy any other files (assets, scripts, etc.)
|
|
220
|
+
const entries = readdirSync(skillPath, { withFileTypes: true });
|
|
221
|
+
for (const entry of entries) {
|
|
222
|
+
if (entry.name === 'SKILL.md' || entry.name === 'references')
|
|
223
|
+
continue;
|
|
224
|
+
const srcPath = join(skillPath, entry.name);
|
|
225
|
+
const destPath = join(extPath, entry.name);
|
|
226
|
+
if (entry.isDirectory()) {
|
|
227
|
+
copyDirRecursive(srcPath, destPath, result.created);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
copyFileSync(srcPath, destPath);
|
|
231
|
+
result.created.push(destPath);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
result.success = true;
|
|
235
|
+
}
|
|
236
|
+
catch (e) {
|
|
237
|
+
result.errors.push(`Failed to convert: ${e instanceof Error ? e.message : String(e)}`);
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Convert all skills to extensions
|
|
243
|
+
*/
|
|
244
|
+
export function convertAllSkills(options = {}, projectDir = process.cwd()) {
|
|
245
|
+
const skills = listSkills(projectDir);
|
|
246
|
+
const results = [];
|
|
247
|
+
for (const skill of skills) {
|
|
248
|
+
const result = convertSkill(skill.name, options, projectDir);
|
|
249
|
+
results.push(result);
|
|
250
|
+
}
|
|
251
|
+
return results;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get conversion summary
|
|
255
|
+
*/
|
|
256
|
+
export function getConversionSummary(results) {
|
|
257
|
+
return {
|
|
258
|
+
total: results.length,
|
|
259
|
+
success: results.filter(r => r.success && r.errors.length === 0 && r.skipped.length === 0).length,
|
|
260
|
+
skipped: results.filter(r => r.skipped.length > 0).length,
|
|
261
|
+
failed: results.filter(r => r.errors.length > 0).length
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
265
|
+
// AGENT CONVERSION
|
|
266
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
267
|
+
/**
|
|
268
|
+
* Get Claude agents directory path
|
|
269
|
+
*/
|
|
270
|
+
export function getClaudeAgentsDir(projectDir = process.cwd()) {
|
|
271
|
+
return join(projectDir, CLAUDE_AGENTS_DIR);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Get Gemini agents directory path
|
|
275
|
+
*/
|
|
276
|
+
export function getGeminiAgentsDir(projectDir = process.cwd()) {
|
|
277
|
+
return join(projectDir, GEMINI_AGENTS_DIR);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Parse agent frontmatter
|
|
281
|
+
*/
|
|
282
|
+
export function parseAgentFrontmatter(content) {
|
|
283
|
+
const frontmatter = parseFrontmatter(content);
|
|
284
|
+
if (!frontmatter)
|
|
285
|
+
return null;
|
|
286
|
+
return {
|
|
287
|
+
name: frontmatter.name || '',
|
|
288
|
+
description: frontmatter.description || '',
|
|
289
|
+
model: frontmatter.model,
|
|
290
|
+
skills: frontmatter.skills
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* List all available Claude agents
|
|
295
|
+
*/
|
|
296
|
+
export function listAgents(projectDir = process.cwd()) {
|
|
297
|
+
const agentsDir = getClaudeAgentsDir(projectDir);
|
|
298
|
+
if (!existsSync(agentsDir)) {
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
const agents = [];
|
|
302
|
+
try {
|
|
303
|
+
const files = readdirSync(agentsDir, { withFileTypes: true })
|
|
304
|
+
.filter(f => f.isFile() && f.name.endsWith('.md'))
|
|
305
|
+
.map(f => f.name);
|
|
306
|
+
for (const file of files) {
|
|
307
|
+
const filePath = join(agentsDir, file);
|
|
308
|
+
const name = file.replace(/\.md$/, '');
|
|
309
|
+
let description;
|
|
310
|
+
let model;
|
|
311
|
+
let skills;
|
|
312
|
+
try {
|
|
313
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
314
|
+
const frontmatter = parseAgentFrontmatter(content);
|
|
315
|
+
if (frontmatter) {
|
|
316
|
+
description = frontmatter.description;
|
|
317
|
+
model = frontmatter.model;
|
|
318
|
+
skills = frontmatter.skills;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
// Ignore parse errors
|
|
323
|
+
}
|
|
324
|
+
agents.push({
|
|
325
|
+
name,
|
|
326
|
+
path: filePath,
|
|
327
|
+
description,
|
|
328
|
+
model,
|
|
329
|
+
skills
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
// Return empty on error
|
|
335
|
+
}
|
|
336
|
+
return agents.sort((a, b) => a.name.localeCompare(b.name));
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Check if an agent already exists in Gemini
|
|
340
|
+
*/
|
|
341
|
+
export function geminiAgentExists(name, projectDir = process.cwd()) {
|
|
342
|
+
const agentPath = join(getGeminiAgentsDir(projectDir), `${name}.md`);
|
|
343
|
+
return existsSync(agentPath);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Map Claude model to Gemini model
|
|
347
|
+
*/
|
|
348
|
+
export function mapModel(model) {
|
|
349
|
+
if (!model)
|
|
350
|
+
return undefined;
|
|
351
|
+
return MODEL_MAPPING[model] || model;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Update frontmatter in markdown content
|
|
355
|
+
*/
|
|
356
|
+
function updateFrontmatter(content, updates) {
|
|
357
|
+
const match = content.match(/^(---\r?\n)([\s\S]*?)(\r?\n---)/);
|
|
358
|
+
if (!match)
|
|
359
|
+
return content;
|
|
360
|
+
const [fullMatch, startDelim, frontmatterContent, endDelim] = match;
|
|
361
|
+
const lines = frontmatterContent.split(/\r?\n/);
|
|
362
|
+
const newLines = [];
|
|
363
|
+
const processedKeys = new Set();
|
|
364
|
+
for (const line of lines) {
|
|
365
|
+
const colonIndex = line.indexOf(':');
|
|
366
|
+
if (colonIndex === -1) {
|
|
367
|
+
newLines.push(line);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const key = line.slice(0, colonIndex).trim();
|
|
371
|
+
if (key in updates) {
|
|
372
|
+
processedKeys.add(key);
|
|
373
|
+
if (updates[key] !== undefined) {
|
|
374
|
+
newLines.push(`${key}: ${updates[key]}`);
|
|
375
|
+
}
|
|
376
|
+
// If undefined, skip the line (remove the field)
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
newLines.push(line);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// Add any new keys that weren't in the original
|
|
383
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
384
|
+
if (!processedKeys.has(key) && value !== undefined) {
|
|
385
|
+
newLines.push(`${key}: ${value}`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
const newFrontmatter = startDelim + newLines.join('\n') + endDelim;
|
|
389
|
+
return content.replace(fullMatch, newFrontmatter);
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Convert a single agent from Claude to Gemini
|
|
393
|
+
*/
|
|
394
|
+
export function convertAgent(agentName, options = {}, projectDir = process.cwd()) {
|
|
395
|
+
const result = {
|
|
396
|
+
skill: agentName, // Reusing skill field for agent name
|
|
397
|
+
success: false,
|
|
398
|
+
created: [],
|
|
399
|
+
skipped: [],
|
|
400
|
+
errors: []
|
|
401
|
+
};
|
|
402
|
+
const claudeAgentsDir = getClaudeAgentsDir(projectDir);
|
|
403
|
+
const geminiAgentsDir = getGeminiAgentsDir(projectDir);
|
|
404
|
+
const srcPath = join(claudeAgentsDir, `${agentName}.md`);
|
|
405
|
+
const destPath = join(geminiAgentsDir, `${agentName}.md`);
|
|
406
|
+
// Check if source agent exists
|
|
407
|
+
if (!existsSync(srcPath)) {
|
|
408
|
+
result.errors.push(`Agent not found: ${agentName}`);
|
|
409
|
+
return result;
|
|
410
|
+
}
|
|
411
|
+
// Check if destination already exists
|
|
412
|
+
if (geminiAgentExists(agentName, projectDir) && !options.force) {
|
|
413
|
+
result.skipped.push(`Agent already exists in .gemini/agents: ${agentName} (use --force to overwrite)`);
|
|
414
|
+
return result;
|
|
415
|
+
}
|
|
416
|
+
// Read source content
|
|
417
|
+
let content;
|
|
418
|
+
try {
|
|
419
|
+
content = readFileSync(srcPath, 'utf-8');
|
|
420
|
+
}
|
|
421
|
+
catch (e) {
|
|
422
|
+
result.errors.push(`Failed to read agent file: ${e instanceof Error ? e.message : String(e)}`);
|
|
423
|
+
return result;
|
|
424
|
+
}
|
|
425
|
+
// Parse frontmatter to get model
|
|
426
|
+
const frontmatter = parseAgentFrontmatter(content);
|
|
427
|
+
const originalModel = frontmatter?.model;
|
|
428
|
+
const mappedModel = mapModel(originalModel);
|
|
429
|
+
// Update model if mapping exists and is different
|
|
430
|
+
let finalContent = content;
|
|
431
|
+
if (originalModel && mappedModel && originalModel !== mappedModel) {
|
|
432
|
+
finalContent = updateFrontmatter(content, { model: mappedModel });
|
|
433
|
+
}
|
|
434
|
+
if (options.dryRun) {
|
|
435
|
+
result.created.push(`[DRY RUN] Would create: ${destPath}`);
|
|
436
|
+
if (originalModel && mappedModel && originalModel !== mappedModel) {
|
|
437
|
+
result.created.push(`[DRY RUN] Would map model: ${originalModel} -> ${mappedModel}`);
|
|
438
|
+
}
|
|
439
|
+
result.success = true;
|
|
440
|
+
return result;
|
|
441
|
+
}
|
|
442
|
+
try {
|
|
443
|
+
// Ensure destination directory exists
|
|
444
|
+
if (!existsSync(geminiAgentsDir)) {
|
|
445
|
+
mkdirSync(geminiAgentsDir, { recursive: true });
|
|
446
|
+
}
|
|
447
|
+
// Write converted agent
|
|
448
|
+
writeFileSync(destPath, finalContent);
|
|
449
|
+
result.created.push(destPath);
|
|
450
|
+
if (originalModel && mappedModel && originalModel !== mappedModel) {
|
|
451
|
+
result.created.push(`Model mapped: ${originalModel} -> ${mappedModel}`);
|
|
452
|
+
}
|
|
453
|
+
result.success = true;
|
|
454
|
+
}
|
|
455
|
+
catch (e) {
|
|
456
|
+
result.errors.push(`Failed to convert: ${e instanceof Error ? e.message : String(e)}`);
|
|
457
|
+
}
|
|
458
|
+
return result;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Convert all agents from Claude to Gemini
|
|
462
|
+
*/
|
|
463
|
+
export function convertAllAgents(options = {}, projectDir = process.cwd()) {
|
|
464
|
+
const agents = listAgents(projectDir);
|
|
465
|
+
const results = [];
|
|
466
|
+
for (const agent of agents) {
|
|
467
|
+
const result = convertAgent(agent.name, options, projectDir);
|
|
468
|
+
results.push(result);
|
|
469
|
+
}
|
|
470
|
+
return results;
|
|
471
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for skill/extension conversion
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Claude skill frontmatter
|
|
6
|
+
*/
|
|
7
|
+
export interface SkillFrontmatter {
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Gemini extension configuration
|
|
14
|
+
*/
|
|
15
|
+
export interface GeminiExtension {
|
|
16
|
+
name: string;
|
|
17
|
+
version: string;
|
|
18
|
+
description: string;
|
|
19
|
+
license: string;
|
|
20
|
+
contextFileName: string[];
|
|
21
|
+
author?: string;
|
|
22
|
+
tools?: GeminiTool[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Gemini extension tool definition
|
|
26
|
+
*/
|
|
27
|
+
export interface GeminiTool {
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
command: string;
|
|
31
|
+
inputSchema: {
|
|
32
|
+
type: string;
|
|
33
|
+
properties: Record<string, unknown>;
|
|
34
|
+
required?: string[];
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Skill info for listing
|
|
39
|
+
*/
|
|
40
|
+
export interface SkillInfo {
|
|
41
|
+
name: string;
|
|
42
|
+
path: string;
|
|
43
|
+
hasSkillMd: boolean;
|
|
44
|
+
hasReferences: boolean;
|
|
45
|
+
description?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Conversion result
|
|
49
|
+
*/
|
|
50
|
+
export interface ConversionResult {
|
|
51
|
+
skill: string;
|
|
52
|
+
success: boolean;
|
|
53
|
+
created: string[];
|
|
54
|
+
skipped: string[];
|
|
55
|
+
errors: string[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Conversion options
|
|
59
|
+
*/
|
|
60
|
+
export interface ConvertOptions {
|
|
61
|
+
force?: boolean;
|
|
62
|
+
dryRun?: boolean;
|
|
63
|
+
verbose?: boolean;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Agent frontmatter
|
|
67
|
+
*/
|
|
68
|
+
export interface AgentFrontmatter {
|
|
69
|
+
name: string;
|
|
70
|
+
description: string;
|
|
71
|
+
model?: string;
|
|
72
|
+
skills?: string;
|
|
73
|
+
[key: string]: unknown;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Agent info for listing
|
|
77
|
+
*/
|
|
78
|
+
export interface AgentInfo {
|
|
79
|
+
name: string;
|
|
80
|
+
path: string;
|
|
81
|
+
description?: string;
|
|
82
|
+
model?: string;
|
|
83
|
+
skills?: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Model mapping for conversion (Claude -> Gemini)
|
|
87
|
+
*/
|
|
88
|
+
export declare const MODEL_MAPPING: Record<string, string>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for skill/extension conversion
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Model mapping for conversion (Claude -> Gemini)
|
|
6
|
+
*/
|
|
7
|
+
export const MODEL_MAPPING = {
|
|
8
|
+
// Claude models to Gemini equivalents
|
|
9
|
+
'claude-3-opus': 'gemini-2.5-pro',
|
|
10
|
+
'claude-3-sonnet': 'gemini-2.5-flash',
|
|
11
|
+
'claude-3-haiku': 'gemini-2.5-flash',
|
|
12
|
+
'claude-3.5-sonnet': 'gemini-2.5-pro',
|
|
13
|
+
'claude-3.5-haiku': 'gemini-2.5-flash',
|
|
14
|
+
// Keep Gemini models as-is
|
|
15
|
+
'gemini-2.5-pro': 'gemini-2.5-pro',
|
|
16
|
+
'gemini-2.5-flash': 'gemini-2.5-flash',
|
|
17
|
+
'gemini-3-flash-preview': 'gemini-3-flash-preview',
|
|
18
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub download utilities
|
|
3
|
+
*/
|
|
4
|
+
import type { Release, ReleaseAsset } from '../../types/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Download release asset
|
|
7
|
+
*/
|
|
8
|
+
export declare function downloadAsset(asset: ReleaseAsset, destDir?: string): Promise<string>;
|
|
9
|
+
/**
|
|
10
|
+
* Download release tarball
|
|
11
|
+
*/
|
|
12
|
+
export declare function downloadRelease(release: Release): Promise<string>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub download utilities
|
|
3
|
+
*/
|
|
4
|
+
import { createWriteStream, existsSync, mkdirSync } from 'fs';
|
|
5
|
+
import { pipeline } from 'stream/promises';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { GEMKIT_CACHE_DIR } from '../../utils/paths.js';
|
|
8
|
+
import { GitHubError } from '../../utils/errors.js';
|
|
9
|
+
/**
|
|
10
|
+
* Download release asset
|
|
11
|
+
*/
|
|
12
|
+
export async function downloadAsset(asset, destDir = GEMKIT_CACHE_DIR) {
|
|
13
|
+
if (!existsSync(destDir)) {
|
|
14
|
+
mkdirSync(destDir, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
const destPath = join(destDir, asset.name);
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(asset.downloadUrl, {
|
|
19
|
+
headers: {
|
|
20
|
+
'User-Agent': 'gemkit-cli',
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new GitHubError(`Failed to download asset: ${response.status}`);
|
|
25
|
+
}
|
|
26
|
+
if (!response.body) {
|
|
27
|
+
throw new GitHubError('No response body');
|
|
28
|
+
}
|
|
29
|
+
const fileStream = createWriteStream(destPath);
|
|
30
|
+
// @ts-ignore - Node.js stream compatibility
|
|
31
|
+
await pipeline(response.body, fileStream);
|
|
32
|
+
return destPath;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error instanceof GitHubError) {
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
throw new GitHubError(`Download failed: ${error}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Download release tarball
|
|
43
|
+
*/
|
|
44
|
+
export async function downloadRelease(release) {
|
|
45
|
+
// Find tarball asset
|
|
46
|
+
const tarball = release.assets.find(a => a.name.endsWith('.tar.gz') || a.name.endsWith('.tgz'));
|
|
47
|
+
if (!tarball) {
|
|
48
|
+
throw new GitHubError('No tarball asset found in release');
|
|
49
|
+
}
|
|
50
|
+
return downloadAsset(tarball);
|
|
51
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub releases API - Public repos only, no auth
|
|
3
|
+
*/
|
|
4
|
+
import type { Release } from '../../types/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Fetch releases from GitHub
|
|
7
|
+
*/
|
|
8
|
+
export declare function fetchReleases(limit?: number): Promise<Release[]>;
|
|
9
|
+
/**
|
|
10
|
+
* Get latest release
|
|
11
|
+
*/
|
|
12
|
+
export declare function getLatestRelease(): Promise<Release | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Get release by version
|
|
15
|
+
*/
|
|
16
|
+
export declare function getReleaseByVersion(version: string): Promise<Release | null>;
|