@@ -66,6 +66,21 @@ function getDirName(runtime) {
6666 return '.claude' ;
6767}
6868
69+ // Centralized path replacement for all install targets.
70+ // Handles ~/.claude/, $HOME/.claude/, ./.claude/, and runtime-specific variants.
71+ function replacePathPatterns ( content , pathPrefix , runtime ) {
72+ const dirName = getDirName ( runtime ) ;
73+ content = content . replace ( / ~ \/ \. c l a u d e \/ / g, pathPrefix ) ;
74+ content = content . replace ( / \$ H O M E \/ \. c l a u d e \/ / g, pathPrefix ) ;
75+ content = content . replace ( / \. \/ \. c l a u d e \/ / g, `./${ dirName } /` ) ;
76+ if ( runtime === 'opencode' ) {
77+ content = content . replace ( / ~ \/ \. o p e n c o d e \/ / g, pathPrefix ) ;
78+ } else if ( runtime === 'codex' ) {
79+ content = content . replace ( / ~ \/ \. c o d e x \/ / g, pathPrefix ) ;
80+ }
81+ return content ;
82+ }
83+
6984/**
7085 * Get the config directory path relative to home directory for a runtime
7186 * Used for templating hooks that use path.join(homeDir, '<configDir>', ...)
@@ -983,12 +998,7 @@ function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) {
983998 const destPath = path . join ( destDir , destName ) ;
984999
9851000 let content = fs . readFileSync ( srcPath , 'utf8' ) ;
986- const globalClaudeRegex = / ~ \/ \. c l a u d e \/ / g;
987- const localClaudeRegex = / \. \/ \. c l a u d e \/ / g;
988- const opencodeDirRegex = / ~ \/ \. o p e n c o d e \/ / g;
989- content = content . replace ( globalClaudeRegex , pathPrefix ) ;
990- content = content . replace ( localClaudeRegex , `./${ getDirName ( runtime ) } /` ) ;
991- content = content . replace ( opencodeDirRegex , pathPrefix ) ;
1001+ content = replacePathPatterns ( content , pathPrefix , runtime ) ;
9921002 content = processAttribution ( content , getCommitAttribution ( runtime ) ) ;
9931003 content = convertClaudeToOpencodeFrontmatter ( content ) ;
9941004
@@ -1042,12 +1052,7 @@ function copyCommandsAsCodexSkills(srcDir, skillsDir, prefix, pathPrefix, runtim
10421052 fs . mkdirSync ( skillDir , { recursive : true } ) ;
10431053
10441054 let content = fs . readFileSync ( srcPath , 'utf8' ) ;
1045- const globalClaudeRegex = / ~ \/ \. c l a u d e \/ / g;
1046- const localClaudeRegex = / \. \/ \. c l a u d e \/ / g;
1047- const codexDirRegex = / ~ \/ \. c o d e x \/ / g;
1048- content = content . replace ( globalClaudeRegex , pathPrefix ) ;
1049- content = content . replace ( localClaudeRegex , `./${ getDirName ( runtime ) } /` ) ;
1050- content = content . replace ( codexDirRegex , pathPrefix ) ;
1055+ content = replacePathPatterns ( content , pathPrefix , runtime ) ;
10511056 content = processAttribution ( content , getCommitAttribution ( runtime ) ) ;
10521057 content = convertClaudeCommandToCodexSkill ( content , skillName ) ;
10531058
@@ -1069,7 +1074,6 @@ function copyCommandsAsCodexSkills(srcDir, skillsDir, prefix, pathPrefix, runtim
10691074function copyWithPathReplacement ( srcDir , destDir , pathPrefix , runtime , isCommand = false ) {
10701075 const isOpencode = runtime === 'opencode' ;
10711076 const isCodex = runtime === 'codex' ;
1072- const dirName = getDirName ( runtime ) ;
10731077
10741078 // Clean install: remove existing destination to prevent orphaned files
10751079 if ( fs . existsSync ( destDir ) ) {
@@ -1086,12 +1090,9 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
10861090 if ( entry . isDirectory ( ) ) {
10871091 copyWithPathReplacement ( srcPath , destPath , pathPrefix , runtime , isCommand ) ;
10881092 } else if ( entry . name . endsWith ( '.md' ) ) {
1089- // Replace ~/.claude/ and ./.claude/ with runtime-appropriate paths
1093+ // Replace ~/.claude/, $HOME/.claude/, ./.claude/ with runtime-appropriate paths
10901094 let content = fs . readFileSync ( srcPath , 'utf8' ) ;
1091- const globalClaudeRegex = / ~ \/ \. c l a u d e \/ / g;
1092- const localClaudeRegex = / \. \/ \. c l a u d e \/ / g;
1093- content = content . replace ( globalClaudeRegex , pathPrefix ) ;
1094- content = content . replace ( localClaudeRegex , `./${ dirName } /` ) ;
1095+ content = replacePathPatterns ( content , pathPrefix , runtime ) ;
10951096 content = processAttribution ( content , getCommitAttribution ( runtime ) ) ;
10961097
10971098 // Convert frontmatter for opencode compatibility
@@ -1929,9 +1930,7 @@ function install(isGlobal, runtime = 'claude') {
19291930 for ( const entry of agentEntries ) {
19301931 if ( entry . isFile ( ) && entry . name . endsWith ( '.md' ) ) {
19311932 let content = fs . readFileSync ( path . join ( agentsSrc , entry . name ) , 'utf8' ) ;
1932- // Always replace ~/.claude/ as it is the source of truth in the repo
1933- const dirRegex = / ~ \/ \. c l a u d e \/ / g;
1934- content = content . replace ( dirRegex , pathPrefix ) ;
1933+ content = replacePathPatterns ( content , pathPrefix , runtime ) ;
19351934 content = processAttribution ( content , getCommitAttribution ( runtime ) ) ;
19361935 // Convert frontmatter for runtime compatibility
19371936 if ( isOpencode ) {
@@ -2333,6 +2332,7 @@ if (process.env.GSD_TEST_MODE) {
23332332 convertClaudeCommandToCodexSkill,
23342333 GSD_CODEX_MARKER ,
23352334 CODEX_AGENT_SANDBOX ,
2335+ replacePathPatterns,
23362336 } ;
23372337} else {
23382338
0 commit comments