66 * Interactive setup that detects existing config, prompts, and merges.
77 * Supports Claude Code and Cursor IDE.
88 *
9- * Usage: npx gitmem-mcp init [--yes] [--dry-run] [--project <name>] [--client <claude|cursor>]
9+ * Usage: npx gitmem-mcp init [--yes] [--dry-run] [--project <name>] [--client <claude|cursor|vscode|windsurf|generic >]
1010 */
1111
1212import {
@@ -35,11 +35,15 @@ const clientFlag = clientIdx !== -1 ? args[clientIdx + 1]?.toLowerCase() : null;
3535
3636// ── Client Configuration ──
3737
38+ // Resolve user home directory for clients that use user-level config
39+ const homeDir = process . env . HOME || process . env . USERPROFILE || "~" ;
40+
3841const CLIENT_CONFIGS = {
3942 claude : {
4043 name : "Claude Code" ,
4144 mcpConfigPath : join ( cwd , ".mcp.json" ) ,
4245 mcpConfigName : ".mcp.json" ,
46+ mcpConfigScope : "project" ,
4347 instructionsFile : join ( cwd , "CLAUDE.md" ) ,
4448 instructionsName : "CLAUDE.md" ,
4549 templateFile : join ( __dirname , ".." , "CLAUDE.md.template" ) ,
@@ -50,12 +54,14 @@ const CLIENT_CONFIGS = {
5054 settingsLocalFile : join ( cwd , ".claude" , "settings.local.json" ) ,
5155 hasPermissions : true ,
5256 hooksInSettings : true ,
57+ hasHooks : true ,
5358 completionMsg : "Setup complete! Start Claude Code \u2014 memory is active." ,
5459 } ,
5560 cursor : {
5661 name : "Cursor" ,
5762 mcpConfigPath : join ( cwd , ".cursor" , "mcp.json" ) ,
5863 mcpConfigName : ".cursor/mcp.json" ,
64+ mcpConfigScope : "project" ,
5965 instructionsFile : join ( cwd , ".cursorrules" ) ,
6066 instructionsName : ".cursorrules" ,
6167 templateFile : join ( __dirname , ".." , "cursorrules.template" ) ,
@@ -66,10 +72,66 @@ const CLIENT_CONFIGS = {
6672 settingsLocalFile : null ,
6773 hasPermissions : false ,
6874 hooksInSettings : false ,
75+ hasHooks : true ,
6976 hooksFile : join ( cwd , ".cursor" , "hooks.json" ) ,
7077 hooksFileName : ".cursor/hooks.json" ,
7178 completionMsg : "Setup complete! Open Cursor (Agent mode) \u2014 memory is active." ,
7279 } ,
80+ vscode : {
81+ name : "VS Code (Copilot)" ,
82+ mcpConfigPath : join ( cwd , ".vscode" , "mcp.json" ) ,
83+ mcpConfigName : ".vscode/mcp.json" ,
84+ mcpConfigScope : "project" ,
85+ instructionsFile : join ( cwd , ".github" , "copilot-instructions.md" ) ,
86+ instructionsName : ".github/copilot-instructions.md" ,
87+ templateFile : join ( __dirname , ".." , "copilot-instructions.template" ) ,
88+ startMarker : "<!-- gitmem:start -->" ,
89+ endMarker : "<!-- gitmem:end -->" ,
90+ configDir : join ( cwd , ".vscode" ) ,
91+ settingsFile : null ,
92+ settingsLocalFile : null ,
93+ hasPermissions : false ,
94+ hooksInSettings : false ,
95+ hasHooks : false ,
96+ completionMsg : "Setup complete! Open VS Code \u2014 memory is active via Copilot." ,
97+ } ,
98+ windsurf : {
99+ name : "Windsurf" ,
100+ mcpConfigPath : join ( homeDir , ".codeium" , "windsurf" , "mcp_config.json" ) ,
101+ mcpConfigName : "~/.codeium/windsurf/mcp_config.json" ,
102+ mcpConfigScope : "user" ,
103+ instructionsFile : join ( cwd , ".windsurfrules" ) ,
104+ instructionsName : ".windsurfrules" ,
105+ templateFile : join ( __dirname , ".." , "windsurfrules.template" ) ,
106+ startMarker : "# --- gitmem:start ---" ,
107+ endMarker : "# --- gitmem:end ---" ,
108+ configDir : null ,
109+ settingsFile : null ,
110+ settingsLocalFile : null ,
111+ hasPermissions : false ,
112+ hooksInSettings : false ,
113+ hasHooks : false ,
114+ completionMsg : "Setup complete! Open Windsurf \u2014 memory is active." ,
115+ } ,
116+ generic : {
117+ name : "Generic MCP Client" ,
118+ mcpConfigPath : join ( cwd , ".mcp.json" ) ,
119+ mcpConfigName : ".mcp.json" ,
120+ mcpConfigScope : "project" ,
121+ instructionsFile : join ( cwd , "CLAUDE.md" ) ,
122+ instructionsName : "CLAUDE.md" ,
123+ templateFile : join ( __dirname , ".." , "CLAUDE.md.template" ) ,
124+ startMarker : "<!-- gitmem:start -->" ,
125+ endMarker : "<!-- gitmem:end -->" ,
126+ configDir : null ,
127+ settingsFile : null ,
128+ settingsLocalFile : null ,
129+ hasPermissions : false ,
130+ hooksInSettings : false ,
131+ hasHooks : false ,
132+ completionMsg :
133+ "Setup complete! Configure your MCP client to use the gitmem server from .mcp.json." ,
134+ } ,
73135} ;
74136
75137// Shared paths (client-agnostic)
@@ -84,33 +146,49 @@ let cc; // shorthand for CLIENT_CONFIGS[client]
84146
85147// ── Client Detection ──
86148
149+ const VALID_CLIENTS = Object . keys ( CLIENT_CONFIGS ) ;
150+
87151function detectClient ( ) {
88152 // Explicit flag takes priority
89153 if ( clientFlag ) {
90- if ( clientFlag !== "claude" && clientFlag !== "cursor" ) {
91- console . error ( ` Error: Unknown client "${ clientFlag } ". Use --client claude or --client cursor .` ) ;
154+ if ( ! VALID_CLIENTS . includes ( clientFlag ) ) {
155+ console . error ( ` Error: Unknown client "${ clientFlag } ". Use --client ${ VALID_CLIENTS . join ( "|" ) } .` ) ;
92156 process . exit ( 1 ) ;
93157 }
94158 return clientFlag ;
95159 }
96160
97- // Auto-detect based on directory presence
161+ // Auto-detect based on directory/file presence
98162 const hasCursorDir = existsSync ( join ( cwd , ".cursor" ) ) ;
99163 const hasClaudeDir = existsSync ( join ( cwd , ".claude" ) ) ;
100164 const hasMcpJson = existsSync ( join ( cwd , ".mcp.json" ) ) ;
101165 const hasClaudeMd = existsSync ( join ( cwd , "CLAUDE.md" ) ) ;
102166 const hasCursorRules = existsSync ( join ( cwd , ".cursorrules" ) ) ;
103167 const hasCursorMcp = existsSync ( join ( cwd , ".cursor" , "mcp.json" ) ) ;
168+ const hasVscodeDir = existsSync ( join ( cwd , ".vscode" ) ) ;
169+ const hasVscodeMcp = existsSync ( join ( cwd , ".vscode" , "mcp.json" ) ) ;
170+ const hasCopilotInstructions = existsSync ( join ( cwd , ".github" , "copilot-instructions.md" ) ) ;
171+ const hasWindsurfRules = existsSync ( join ( cwd , ".windsurfrules" ) ) ;
172+ const hasWindsurfMcp = existsSync (
173+ join ( homeDir , ".codeium" , "windsurf" , "mcp_config.json" )
174+ ) ;
104175
105176 // Strong Cursor signals
106177 if ( hasCursorDir && ! hasClaudeDir && ! hasMcpJson && ! hasClaudeMd ) return "cursor" ;
107- if ( hasCursorRules && ! hasClaudeMd ) return "cursor" ;
108- if ( hasCursorMcp && ! hasMcpJson ) return "cursor" ;
178+ if ( hasCursorRules && ! hasClaudeMd && ! hasCopilotInstructions ) return "cursor" ;
179+ if ( hasCursorMcp && ! hasMcpJson && ! hasVscodeMcp ) return "cursor" ;
109180
110181 // Strong Claude signals
111- if ( hasClaudeDir && ! hasCursorDir ) return "claude" ;
112- if ( hasMcpJson && ! hasCursorMcp ) return "claude" ;
113- if ( hasClaudeMd && ! hasCursorRules ) return "claude" ;
182+ if ( hasClaudeDir && ! hasCursorDir && ! hasVscodeDir ) return "claude" ;
183+ if ( hasMcpJson && ! hasCursorMcp && ! hasVscodeMcp ) return "claude" ;
184+ if ( hasClaudeMd && ! hasCursorRules && ! hasCopilotInstructions ) return "claude" ;
185+
186+ // VS Code signals
187+ if ( hasVscodeMcp && ! hasMcpJson && ! hasCursorMcp ) return "vscode" ;
188+ if ( hasCopilotInstructions && ! hasClaudeMd && ! hasCursorRules ) return "vscode" ;
189+
190+ // Windsurf signals
191+ if ( hasWindsurfRules && ! hasClaudeMd && ! hasCursorRules && ! hasCopilotInstructions ) return "windsurf" ;
114192
115193 // Default to Claude Code (most common)
116194 return "claude" ;
@@ -439,6 +517,7 @@ async function stepMemoryStore() {
439517async function stepMcpServer ( ) {
440518 const mcpPath = cc . mcpConfigPath ;
441519 const mcpName = cc . mcpConfigName ;
520+ const isUserLevel = cc . mcpConfigScope === "user" ;
442521
443522 const existing = readJson ( mcpPath ) ;
444523 const hasGitmem =
@@ -453,21 +532,22 @@ async function stepMcpServer() {
453532 ? Object . keys ( existing . mcpServers ) . length
454533 : 0 ;
455534 const tierLabel = process . env . SUPABASE_URL ? "pro" : "free" ;
535+ const scopeNote = isUserLevel ? " (user-level config)" : "" ;
456536 const prompt = existing
457- ? `Add gitmem to ${ mcpName } ? (${ serverCount } existing server${ serverCount !== 1 ? "s" : "" } preserved)`
458- : `Create ${ mcpName } with gitmem server?` ;
537+ ? `Add gitmem to ${ mcpName } ?${ scopeNote } (${ serverCount } existing server${ serverCount !== 1 ? "s" : "" } preserved)`
538+ : `Create ${ mcpName } with gitmem server?${ scopeNote } ` ;
459539
460540 if ( ! ( await confirm ( prompt ) ) ) {
461541 console . log ( " Skipped." ) ;
462542 return ;
463543 }
464544
465545 if ( dryRun ) {
466- console . log ( ` [dry-run] Would add gitmem entry to ${ mcpName } (${ tierLabel } tier)` ) ;
546+ console . log ( ` [dry-run] Would add gitmem entry to ${ mcpName } (${ tierLabel } tier${ scopeNote } )` ) ;
467547 return ;
468548 }
469549
470- // Ensure parent directory exists (for .cursor/mcp.json)
550+ // Ensure parent directory exists (for .cursor/mcp.json, .vscode/mcp.json, ~/.codeium/windsurf/ )
471551 const parentDir = dirname ( mcpPath ) ;
472552 if ( ! existsSync ( parentDir ) ) {
473553 mkdirSync ( parentDir , { recursive : true } ) ;
@@ -481,7 +561,8 @@ async function stepMcpServer() {
481561 console . log (
482562 ` Added gitmem entry to ${ mcpName } (${ tierLabel } tier` +
483563 ( process . env . SUPABASE_URL ? " \u2014 Supabase detected" : " \u2014 local storage" ) +
484- ")"
564+ ")" +
565+ ( isUserLevel ? " [user-level]" : "" )
485566 ) ;
486567}
487568
@@ -525,6 +606,12 @@ async function stepInstructions() {
525606 block = `${ cc . startMarker } \n${ block } \n${ cc . endMarker } ` ;
526607 }
527608
609+ // Ensure parent directory exists (for .github/copilot-instructions.md)
610+ const instrParentDir = dirname ( instrPath ) ;
611+ if ( ! existsSync ( instrParentDir ) ) {
612+ mkdirSync ( instrParentDir , { recursive : true } ) ;
613+ }
614+
528615 if ( exists ) {
529616 content = content . trimEnd ( ) + "\n\n" + block + "\n" ;
530617 } else {
@@ -598,6 +685,11 @@ function copyHookScripts() {
598685}
599686
600687async function stepHooks ( ) {
688+ if ( ! cc . hasHooks ) {
689+ console . log ( ` ${ cc . name } does not support lifecycle hooks. Skipping.` ) ;
690+ console . log ( " Enforcement relies on system prompt instructions instead." ) ;
691+ return ;
692+ }
601693 if ( cc . hooksInSettings ) {
602694 return stepHooksClaude ( ) ;
603695 }
@@ -821,7 +913,7 @@ async function main() {
821913 ) ;
822914 }
823915
824- if ( ! cc . hooksInSettings && cc . hooksFile && existsSync ( cc . hooksFile ) ) {
916+ if ( ! cc . hooksInSettings && cc . hasHooks && cc . hooksFile && existsSync ( cc . hooksFile ) ) {
825917 const hooks = readJson ( cc . hooksFile ) ;
826918 const hookCount = hooks ?. hooks
827919 ? Object . values ( hooks . hooks ) . flat ( ) . length
@@ -850,8 +942,10 @@ async function main() {
850942 ) ;
851943 console . log ( "" ) ;
852944
853- // Run steps — step count depends on client
854- const stepCount = cc . hasPermissions ? 6 : 5 ;
945+ // Run steps — step count depends on client capabilities
946+ let stepCount = 4 ; // memory store + mcp server + instructions + gitignore
947+ if ( cc . hasPermissions ) stepCount ++ ;
948+ if ( cc . hasHooks ) stepCount ++ ;
855949 let step = 1 ;
856950
857951 console . log ( ` Step ${ step } /${ stepCount } \u2014 Memory Store` ) ;
@@ -876,10 +970,12 @@ async function main() {
876970 step ++ ;
877971 }
878972
879- console . log ( ` Step ${ step } /${ stepCount } \u2014 Lifecycle Hooks` ) ;
880- await stepHooks ( ) ;
881- console . log ( "" ) ;
882- step ++ ;
973+ if ( cc . hasHooks ) {
974+ console . log ( ` Step ${ step } /${ stepCount } \u2014 Lifecycle Hooks` ) ;
975+ await stepHooks ( ) ;
976+ console . log ( "" ) ;
977+ step ++ ;
978+ }
883979
884980 console . log ( ` Step ${ step } /${ stepCount } \u2014 Gitignore` ) ;
885981 await stepGitignore ( ) ;
0 commit comments