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.
Files changed (160) hide show
  1. package/README.md +141 -7
  2. package/dist/commands/agent/index.d.ts +9 -0
  3. package/dist/commands/agent/index.js +1329 -0
  4. package/dist/commands/cache/index.d.ts +5 -0
  5. package/dist/commands/cache/index.js +43 -0
  6. package/dist/commands/catalog/index.d.ts +2 -0
  7. package/dist/commands/catalog/index.js +57 -0
  8. package/dist/commands/config/index.d.ts +7 -0
  9. package/dist/commands/config/index.js +122 -0
  10. package/dist/commands/convert/index.d.ts +8 -0
  11. package/dist/commands/convert/index.js +391 -0
  12. package/dist/commands/doctor/index.d.ts +2 -0
  13. package/dist/commands/doctor/index.js +243 -0
  14. package/dist/commands/extension/index.d.ts +5 -0
  15. package/dist/commands/extension/index.js +52 -0
  16. package/dist/commands/index.d.ts +5 -0
  17. package/dist/commands/index.js +37 -0
  18. package/dist/commands/init/index.d.ts +6 -0
  19. package/dist/commands/init/index.js +345 -0
  20. package/dist/commands/new/index.d.ts +5 -0
  21. package/dist/commands/new/index.js +49 -0
  22. package/dist/commands/office/index.d.ts +5 -0
  23. package/dist/commands/office/index.js +283 -0
  24. package/dist/commands/paste/index.d.ts +10 -0
  25. package/dist/commands/paste/index.js +533 -0
  26. package/dist/commands/plan/index.d.ts +8 -0
  27. package/dist/commands/plan/index.js +247 -0
  28. package/dist/commands/session/index.d.ts +8 -0
  29. package/dist/commands/session/index.js +289 -0
  30. package/dist/commands/tokens/index.d.ts +6 -0
  31. package/dist/commands/tokens/index.js +148 -0
  32. package/dist/commands/update/index.d.ts +26 -0
  33. package/dist/commands/update/index.js +199 -0
  34. package/dist/commands/versions/index.d.ts +5 -0
  35. package/dist/commands/versions/index.js +39 -0
  36. package/dist/domains/agent/index.d.ts +8 -0
  37. package/dist/domains/agent/index.js +8 -0
  38. package/dist/domains/agent/mappings.d.ts +32 -0
  39. package/dist/domains/agent/mappings.js +164 -0
  40. package/dist/domains/agent/profile.d.ts +26 -0
  41. package/dist/domains/agent/profile.js +225 -0
  42. package/dist/domains/agent/pty-context.d.ts +11 -0
  43. package/dist/domains/agent/pty-context.js +83 -0
  44. package/dist/domains/agent/pty-providers.d.ts +18 -0
  45. package/dist/domains/agent/pty-providers.js +66 -0
  46. package/dist/domains/agent/pty-session.d.ts +33 -0
  47. package/dist/domains/agent/pty-session.js +82 -0
  48. package/dist/domains/agent/pty-types.d.ts +127 -0
  49. package/dist/domains/agent/pty-types.js +4 -0
  50. package/dist/domains/agent/search.d.ts +45 -0
  51. package/dist/domains/agent/search.js +614 -0
  52. package/dist/domains/agent/types.d.ts +78 -0
  53. package/dist/domains/agent/types.js +5 -0
  54. package/dist/domains/agent-office/documents-scanner.d.ts +9 -0
  55. package/dist/domains/agent-office/documents-scanner.js +143 -0
  56. package/dist/domains/agent-office/event-emitter.d.ts +43 -0
  57. package/dist/domains/agent-office/event-emitter.js +86 -0
  58. package/dist/domains/agent-office/file-watcher.d.ts +40 -0
  59. package/dist/domains/agent-office/file-watcher.js +173 -0
  60. package/dist/domains/agent-office/icons.d.ts +11 -0
  61. package/dist/domains/agent-office/icons.js +36 -0
  62. package/dist/domains/agent-office/index.d.ts +12 -0
  63. package/dist/domains/agent-office/index.js +20 -0
  64. package/dist/domains/agent-office/renderer/web/assets.d.ts +11 -0
  65. package/dist/domains/agent-office/renderer/web/assets.js +3419 -0
  66. package/dist/domains/agent-office/renderer/web/server.d.ts +42 -0
  67. package/dist/domains/agent-office/renderer/web/server.js +228 -0
  68. package/dist/domains/agent-office/renderer/web.d.ts +30 -0
  69. package/dist/domains/agent-office/renderer/web.js +111 -0
  70. package/dist/domains/agent-office/session-bridge.d.ts +23 -0
  71. package/dist/domains/agent-office/session-bridge.js +171 -0
  72. package/dist/domains/agent-office/state-machine.d.ts +5 -0
  73. package/dist/domains/agent-office/state-machine.js +82 -0
  74. package/dist/domains/agent-office/types.d.ts +91 -0
  75. package/dist/domains/agent-office/types.js +4 -0
  76. package/dist/domains/cache/index.d.ts +1 -0
  77. package/dist/domains/cache/index.js +1 -0
  78. package/dist/domains/cache/manager.d.ts +22 -0
  79. package/dist/domains/cache/manager.js +84 -0
  80. package/dist/domains/config/index.d.ts +5 -0
  81. package/dist/domains/config/index.js +5 -0
  82. package/dist/domains/config/manager.d.ts +24 -0
  83. package/dist/domains/config/manager.js +85 -0
  84. package/dist/domains/config/schema.d.ts +17 -0
  85. package/dist/domains/config/schema.js +96 -0
  86. package/dist/domains/convert/converter.d.ts +78 -0
  87. package/dist/domains/convert/converter.js +471 -0
  88. package/dist/domains/convert/index.d.ts +5 -0
  89. package/dist/domains/convert/index.js +5 -0
  90. package/dist/domains/convert/types.d.ts +88 -0
  91. package/dist/domains/convert/types.js +18 -0
  92. package/dist/domains/github/download.d.ts +12 -0
  93. package/dist/domains/github/download.js +51 -0
  94. package/dist/domains/github/index.d.ts +2 -0
  95. package/dist/domains/github/index.js +2 -0
  96. package/dist/domains/github/releases.d.ts +16 -0
  97. package/dist/domains/github/releases.js +68 -0
  98. package/dist/domains/installation/conflict.d.ts +13 -0
  99. package/dist/domains/installation/conflict.js +38 -0
  100. package/dist/domains/installation/file-sync.d.ts +16 -0
  101. package/dist/domains/installation/file-sync.js +77 -0
  102. package/dist/domains/installation/index.d.ts +3 -0
  103. package/dist/domains/installation/index.js +3 -0
  104. package/dist/domains/installation/metadata.d.ts +20 -0
  105. package/dist/domains/installation/metadata.js +52 -0
  106. package/dist/domains/plan/index.d.ts +2 -0
  107. package/dist/domains/plan/index.js +2 -0
  108. package/dist/domains/plan/resolver.d.ts +24 -0
  109. package/dist/domains/plan/resolver.js +164 -0
  110. package/dist/domains/plan/types.d.ts +13 -0
  111. package/dist/domains/plan/types.js +4 -0
  112. package/dist/domains/session/env.d.ts +51 -0
  113. package/dist/domains/session/env.js +118 -0
  114. package/dist/domains/session/index.d.ts +8 -0
  115. package/dist/domains/session/index.js +8 -0
  116. package/dist/domains/session/manager.d.ts +56 -0
  117. package/dist/domains/session/manager.js +205 -0
  118. package/dist/domains/session/paths.d.ts +6 -0
  119. package/dist/domains/session/paths.js +6 -0
  120. package/dist/domains/session/types.d.ts +121 -0
  121. package/dist/domains/session/types.js +5 -0
  122. package/dist/domains/session/writer.d.ts +82 -0
  123. package/dist/domains/session/writer.js +431 -0
  124. package/dist/domains/tokens/index.d.ts +5 -0
  125. package/dist/domains/tokens/index.js +5 -0
  126. package/dist/domains/tokens/pricing.d.ts +38 -0
  127. package/dist/domains/tokens/pricing.js +129 -0
  128. package/dist/domains/tokens/scanner.d.ts +42 -0
  129. package/dist/domains/tokens/scanner.js +168 -0
  130. package/dist/index.d.ts +5 -0
  131. package/dist/index.js +90 -59
  132. package/dist/services/aipty.d.ts +76 -0
  133. package/dist/services/aipty.js +276 -0
  134. package/dist/services/archive.d.ts +22 -0
  135. package/dist/services/archive.js +53 -0
  136. package/dist/services/auto-update.d.ts +26 -0
  137. package/dist/services/auto-update.js +117 -0
  138. package/dist/services/hash.d.ts +36 -0
  139. package/dist/services/hash.js +63 -0
  140. package/dist/services/logger.d.ts +28 -0
  141. package/dist/services/logger.js +102 -0
  142. package/dist/services/music.d.ts +67 -0
  143. package/dist/services/music.js +290 -0
  144. package/dist/services/npm.d.ts +22 -0
  145. package/dist/services/npm.js +65 -0
  146. package/dist/services/pty-client.d.ts +66 -0
  147. package/dist/services/pty-client.js +154 -0
  148. package/dist/services/pty-server.d.ts +102 -0
  149. package/dist/services/pty-server.js +613 -0
  150. package/dist/types/index.d.ts +155 -0
  151. package/dist/types/index.js +4 -0
  152. package/dist/utils/colors.d.ts +43 -0
  153. package/dist/utils/colors.js +98 -0
  154. package/dist/utils/errors.d.ts +24 -0
  155. package/dist/utils/errors.js +56 -0
  156. package/dist/utils/paths.d.ts +46 -0
  157. package/dist/utils/paths.js +89 -0
  158. package/dist/utils/platform.d.ts +11 -0
  159. package/dist/utils/platform.js +31 -0
  160. 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,5 @@
1
+ /**
2
+ * Convert domain exports
3
+ */
4
+ export * from './types.js';
5
+ export * from './converter.js';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Convert domain exports
3
+ */
4
+ export * from './types.js';
5
+ export * from './converter.js';
@@ -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,2 @@
1
+ export * from './releases.js';
2
+ export * from './download.js';
@@ -0,0 +1,2 @@
1
+ export * from './releases.js';
2
+ export * from './download.js';
@@ -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>;