@@ -69,6 +69,21 @@ function getDirName(runtime) {
6969 return '.claude' ;
7070}
7171
72+ // Centralized path replacement for all install targets.
73+ // Handles ~/.claude/, $HOME/.claude/, ./.claude/, and runtime-specific variants.
74+ function replacePathPatterns ( content , pathPrefix , runtime ) {
75+ const dirName = getDirName ( runtime ) ;
76+ content = content . replace ( / ~ \/ \. c l a u d e \/ / g, pathPrefix ) ;
77+ content = content . replace ( / \$ H O M E \/ \. c l a u d e \/ / g, pathPrefix ) ;
78+ content = content . replace ( / \. \/ \. c l a u d e \/ / g, `./${ dirName } /` ) ;
79+ if ( runtime === 'opencode' ) {
80+ content = content . replace ( / ~ \/ \. o p e n c o d e \/ / g, pathPrefix ) ;
81+ } else if ( runtime === 'codex' ) {
82+ content = content . replace ( / ~ \/ \. c o d e x \/ / g, pathPrefix ) ;
83+ }
84+ return content ;
85+ }
86+
7287/**
7388 * Get the config directory path relative to home directory for a runtime
7489 * Used for templating hooks that use path.join(homeDir, '<configDir>', ...)
@@ -1116,12 +1131,7 @@ function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) {
11161131 const destPath = path . join ( destDir , destName ) ;
11171132
11181133 let content = fs . readFileSync ( srcPath , 'utf8' ) ;
1119- const globalClaudeRegex = / ~ \/ \. c l a u d e \/ / g;
1120- const localClaudeRegex = / \. \/ \. c l a u d e \/ / g;
1121- const opencodeDirRegex = / ~ \/ \. o p e n c o d e \/ / g;
1122- content = content . replace ( globalClaudeRegex , pathPrefix ) ;
1123- content = content . replace ( localClaudeRegex , `./${ getDirName ( runtime ) } /` ) ;
1124- content = content . replace ( opencodeDirRegex , pathPrefix ) ;
1134+ content = replacePathPatterns ( content , pathPrefix , runtime ) ;
11251135 content = processAttribution ( content , getCommitAttribution ( runtime ) ) ;
11261136 content = convertClaudeToOpencodeFrontmatter ( content ) ;
11271137
@@ -1175,12 +1185,7 @@ function copyCommandsAsCodexSkills(srcDir, skillsDir, prefix, pathPrefix, runtim
11751185 fs . mkdirSync ( skillDir , { recursive : true } ) ;
11761186
11771187 let content = fs . readFileSync ( srcPath , 'utf8' ) ;
1178- const globalClaudeRegex = / ~ \/ \. c l a u d e \/ / g;
1179- const localClaudeRegex = / \. \/ \. c l a u d e \/ / g;
1180- const codexDirRegex = / ~ \/ \. c o d e x \/ / g;
1181- content = content . replace ( globalClaudeRegex , pathPrefix ) ;
1182- content = content . replace ( localClaudeRegex , `./${ getDirName ( runtime ) } /` ) ;
1183- content = content . replace ( codexDirRegex , pathPrefix ) ;
1188+ content = replacePathPatterns ( content , pathPrefix , runtime ) ;
11841189 content = processAttribution ( content , getCommitAttribution ( runtime ) ) ;
11851190 content = convertClaudeCommandToCodexSkill ( content , skillName ) ;
11861191
@@ -1220,8 +1225,7 @@ function copyCommandsAsKimiSkills(srcDir, skillsDir, prefix, pathPrefix, runtime
12201225 fs . mkdirSync ( skillDir , { recursive : true } ) ;
12211226
12221227 let content = fs . readFileSync ( srcPath , 'utf8' ) ;
1223- content = content . replace ( / ~ \/ \. c l a u d e \/ / g, pathPrefix ) ;
1224- content = content . replace ( / \. \/ \. c l a u d e \/ / g, `./${ getDirName ( runtime ) } /` ) ;
1228+ content = replacePathPatterns ( content , pathPrefix , runtime ) ;
12251229 content = processAttribution ( content , getCommitAttribution ( runtime ) ) ;
12261230 content = convertClaudeToKimiSkill ( content , skillName ) ;
12271231
@@ -1243,7 +1247,6 @@ function copyCommandsAsKimiSkills(srcDir, skillsDir, prefix, pathPrefix, runtime
12431247function copyWithPathReplacement ( srcDir , destDir , pathPrefix , runtime , isCommand = false ) {
12441248 const isOpencode = runtime === 'opencode' ;
12451249 const isCodex = runtime === 'codex' ;
1246- const dirName = getDirName ( runtime ) ;
12471250
12481251 // Clean install: remove existing destination to prevent orphaned files
12491252 if ( fs . existsSync ( destDir ) ) {
@@ -1260,12 +1263,9 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
12601263 if ( entry . isDirectory ( ) ) {
12611264 copyWithPathReplacement ( srcPath , destPath , pathPrefix , runtime , isCommand ) ;
12621265 } else if ( entry . name . endsWith ( '.md' ) ) {
1263- // Replace ~/.claude/ and ./.claude/ with runtime-appropriate paths
1266+ // Replace ~/.claude/, $HOME/.claude/, ./.claude/ with runtime-appropriate paths
12641267 let content = fs . readFileSync ( srcPath , 'utf8' ) ;
1265- const globalClaudeRegex = / ~ \/ \. c l a u d e \/ / g;
1266- const localClaudeRegex = / \. \/ \. c l a u d e \/ / g;
1267- content = content . replace ( globalClaudeRegex , pathPrefix ) ;
1268- content = content . replace ( localClaudeRegex , `./${ dirName } /` ) ;
1268+ content = replacePathPatterns ( content , pathPrefix , runtime ) ;
12691269 content = processAttribution ( content , getCommitAttribution ( runtime ) ) ;
12701270
12711271 // Convert frontmatter for opencode compatibility
@@ -2145,9 +2145,7 @@ function install(isGlobal, runtime = 'claude') {
21452145 for ( const entry of agentEntries ) {
21462146 if ( entry . isFile ( ) && entry . name . endsWith ( '.md' ) ) {
21472147 let content = fs . readFileSync ( path . join ( agentsSrc , entry . name ) , 'utf8' ) ;
2148- // Always replace ~/.claude/ as it is the source of truth in the repo
2149- const dirRegex = / ~ \/ \. c l a u d e \/ / g;
2150- content = content . replace ( dirRegex , pathPrefix ) ;
2148+ content = replacePathPatterns ( content , pathPrefix , runtime ) ;
21512149 content = processAttribution ( content , getCommitAttribution ( runtime ) ) ;
21522150 // Convert frontmatter for runtime compatibility
21532151 if ( isOpencode ) {
@@ -2577,6 +2575,7 @@ if (process.env.GSD_TEST_MODE) {
25772575 convertClaudeToKimiAgent,
25782576 convertKimiToolName,
25792577 copyCommandsAsKimiSkills,
2578+ replacePathPatterns,
25802579 } ;
25812580} else {
25822581
0 commit comments