1+ import fs from 'node:fs/promises' ;
12import { existsSync } from 'node:fs' ;
23import path from 'node:path' ;
34import os from 'node:os' ;
@@ -7,6 +8,65 @@ import { BaseAdapter, TargetPaths, VibePackage } from './base-adapter.js';
78
89const 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 ( / \$ A R G U M E N T S / g, '{{args}}' ) ;
61+ prompt = prompt . replace ( / ^ # { 1 , 6 } \s + / gm, '' ) ;
62+
63+ return `description = "${ escapeToml ( description ) } "
64+
65+ prompt = """
66+ ${ prompt }
67+ """` ;
68+ }
69+
1070export 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 ( / \. m d $ / , '.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