Skip to content

Commit 4c8de11

Browse files
committed
feat(cli): enhance GeminiAdapter to support markdown to TOML conversion
- Implemented markdown parsing to extract frontmatter and body content. - Added functionality to convert markdown files to TOML format for command storage. - Updated installation process to create commands directory and handle markdown files. - Introduced context consolidation for Vibe packages, generating a comprehensive GEMINI.md file. This update significantly improves the GeminiAdapter's capabilities for managing commands and context.
1 parent 356b559 commit 4c8de11

1 file changed

Lines changed: 104 additions & 4 deletions

File tree

apps/cli/src/adapters/gemini-adapter.ts

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import fs from 'node:fs/promises';
12
import { existsSync } from 'node:fs';
23
import path from 'node:path';
34
import os from 'node:os';
@@ -7,6 +8,65 @@ import { BaseAdapter, TargetPaths, VibePackage } from './base-adapter.js';
78

89
const execAsync = promisify(exec);
910

11+
interface MarkdownParsed {
12+
frontmatter: Record<string, string>;
13+
body: string;
14+
}
15+
16+
function parseMarkdown(content: string): MarkdownParsed {
17+
const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
18+
const match = content.match(frontmatterRegex);
19+
20+
if (!match) {
21+
return {
22+
frontmatter: {},
23+
body: content.trim()
24+
};
25+
}
26+
27+
const frontmatterText = match[1];
28+
const body = match[2].trim();
29+
30+
const frontmatter: Record<string, string> = {};
31+
const lines = frontmatterText.split('\n');
32+
33+
for (const line of lines) {
34+
const colonIndex = line.indexOf(':');
35+
if (colonIndex === -1) continue;
36+
37+
const key = line.substring(0, colonIndex).trim();
38+
const value = line.substring(colonIndex + 1).trim();
39+
40+
frontmatter[key] = value;
41+
}
42+
43+
return { frontmatter, body };
44+
}
45+
46+
function escapeToml(str: string): string {
47+
return str
48+
.replace(/\\/g, '\\\\')
49+
.replace(/"/g, '\\"')
50+
.replace(/\n/g, '\\n')
51+
.replace(/\t/g, '\\t');
52+
}
53+
54+
function mdToToml(mdContent: string): string {
55+
const { frontmatter, body } = parseMarkdown(mdContent);
56+
57+
const description = frontmatter.description || 'Custom command';
58+
59+
let prompt = body;
60+
prompt = prompt.replace(/\$ARGUMENTS/g, '{{args}}');
61+
prompt = prompt.replace(/^#{1,6}\s+/gm, '');
62+
63+
return `description = "${escapeToml(description)}"
64+
65+
prompt = """
66+
${prompt}
67+
"""`;
68+
}
69+
1070
export class GeminiAdapter extends BaseAdapter {
1171
getName(): string {
1272
return 'gemini';
@@ -24,18 +84,58 @@ export class GeminiAdapter extends BaseAdapter {
2484

2585
getTargetPaths(): TargetPaths {
2686
return {
27-
prompts: '.gemini/prompts'
87+
commands: '.gemini/commands'
2888
};
2989
}
3090

3191
async install(vibe: VibePackage, targetDir: string): Promise<void> {
32-
const geminiDir = path.join(targetDir, '.gemini/prompts');
33-
92+
const commandsDir = path.join(targetDir, '.gemini/commands');
3493
const cursorCommands = path.join(vibe.path, '.cursor/commands');
3594

95+
await fs.mkdir(commandsDir, { recursive: true });
96+
3697
if (existsSync(cursorCommands)) {
37-
await this.mergeDirectories(cursorCommands, geminiDir);
98+
const files = await fs.readdir(cursorCommands);
99+
100+
for (const file of files) {
101+
if (!file.endsWith('.md')) continue;
102+
103+
const mdPath = path.join(cursorCommands, file);
104+
const mdContent = await fs.readFile(mdPath, 'utf-8');
105+
106+
const tomlContent = mdToToml(mdContent);
107+
108+
const tomlFilename = file.replace(/\.md$/, '.toml');
109+
const tomlPath = path.join(commandsDir, tomlFilename);
110+
111+
await fs.writeFile(tomlPath, tomlContent);
112+
}
38113
}
114+
115+
await this.consolidateContext(vibe, targetDir);
116+
}
117+
118+
private async consolidateContext(
119+
vibe: VibePackage,
120+
targetDir: string
121+
): Promise<void> {
122+
const rulesDir = path.join(vibe.path, '.cursor/rules');
123+
124+
if (!existsSync(rulesDir)) return;
125+
126+
const contextPath = path.join(targetDir, '.gemini/GEMINI.md');
127+
let contextContent = `# Vibe Context: ${vibe.name}\n\n`;
128+
129+
const ruleFiles = await fs.readdir(rulesDir);
130+
131+
for (const file of ruleFiles) {
132+
const rulePath = path.join(rulesDir, file);
133+
const ruleContent = await fs.readFile(rulePath, 'utf-8');
134+
135+
contextContent += `## ${file}\n\n${ruleContent}\n\n`;
136+
}
137+
138+
await fs.writeFile(contextPath, contextContent);
39139
}
40140

41141
async uninstall(vibe: VibePackage): Promise<void> {

0 commit comments

Comments
 (0)