@@ -22,6 +22,103 @@ import { ConfigMarkdown } from './markdown';
2222export namespace Config {
2323 const log = Log . create ( { service : 'config' } ) ;
2424
25+ /**
26+ * Automatically migrate .opencode directories to .link-assistant-agent
27+ * This ensures a smooth transition for both file system configs and environment variables.
28+ * Once .link-assistant-agent exists, we stop reading from .opencode.
29+ */
30+ async function migrateConfigDirectories ( ) {
31+ // Find all .opencode and .link-assistant-agent directories in the project hierarchy
32+ const allDirs = await Array . fromAsync (
33+ Filesystem . up ( {
34+ targets : [ '.link-assistant-agent' , '.opencode' ] ,
35+ start : Instance . directory ,
36+ stop : Instance . worktree ,
37+ } )
38+ ) ;
39+
40+ const newConfigDirs = allDirs . filter ( ( d ) =>
41+ d . endsWith ( '.link-assistant-agent' )
42+ ) ;
43+ const oldConfigDirs = allDirs . filter ( ( d ) => d . endsWith ( '.opencode' ) ) ;
44+
45+ // For each old config directory, check if there's a corresponding new one
46+ for ( const oldDir of oldConfigDirs ) {
47+ const parentDir = path . dirname ( oldDir ) ;
48+ const newDir = path . join ( parentDir , '.link-assistant-agent' ) ;
49+
50+ // Check if the new directory already exists at the same level
51+ const newDirExists = newConfigDirs . includes ( newDir ) ;
52+
53+ if ( ! newDirExists ) {
54+ try {
55+ // Perform migration by copying the entire directory
56+ log . info (
57+ `Migrating config from ${ oldDir } to ${ newDir } for smooth transition`
58+ ) ;
59+
60+ // Use fs-extra style recursive copy
61+ await copyDirectory ( oldDir , newDir ) ;
62+
63+ log . info ( `Successfully migrated config to ${ newDir } ` ) ;
64+ } catch ( error ) {
65+ log . error ( `Failed to migrate config from ${ oldDir } :` , error ) ;
66+ // Don't throw - allow the app to continue with the old config
67+ }
68+ }
69+ }
70+
71+ // Also migrate global config if needed
72+ const oldGlobalPath = path . join ( os . homedir ( ) , '.config' , 'opencode' ) ;
73+ const newGlobalPath = Global . Path . config ;
74+
75+ try {
76+ const oldGlobalExists = await fs
77+ . stat ( oldGlobalPath )
78+ . then ( ( ) => true )
79+ . catch ( ( ) => false ) ;
80+ const newGlobalExists = await fs
81+ . stat ( newGlobalPath )
82+ . then ( ( ) => true )
83+ . catch ( ( ) => false ) ;
84+
85+ if ( oldGlobalExists && ! newGlobalExists ) {
86+ log . info (
87+ `Migrating global config from ${ oldGlobalPath } to ${ newGlobalPath } `
88+ ) ;
89+ await copyDirectory ( oldGlobalPath , newGlobalPath ) ;
90+ log . info ( `Successfully migrated global config to ${ newGlobalPath } ` ) ;
91+ }
92+ } catch ( error ) {
93+ log . error ( 'Failed to migrate global config:' , error ) ;
94+ // Don't throw - allow the app to continue
95+ }
96+ }
97+
98+ /**
99+ * Recursively copy a directory and all its contents
100+ */
101+ async function copyDirectory ( src : string , dest : string ) {
102+ // Create destination directory
103+ await fs . mkdir ( dest , { recursive : true } ) ;
104+
105+ // Read all entries in source directory
106+ const entries = await fs . readdir ( src , { withFileTypes : true } ) ;
107+
108+ for ( const entry of entries ) {
109+ const srcPath = path . join ( src , entry . name ) ;
110+ const destPath = path . join ( dest , entry . name ) ;
111+
112+ if ( entry . isDirectory ( ) ) {
113+ // Recursively copy subdirectories
114+ await copyDirectory ( srcPath , destPath ) ;
115+ } else if ( entry . isFile ( ) || entry . isSymbolicLink ( ) ) {
116+ // Copy files
117+ await fs . copyFile ( srcPath , destPath ) ;
118+ }
119+ }
120+ }
121+
25122 export const state = Instance . state ( async ( ) => {
26123 const auth = await Auth . all ( ) ;
27124 let result = await global ( ) ;
@@ -64,20 +161,43 @@ export namespace Config {
64161 result . agent = result . agent || { } ;
65162 result . mode = result . mode || { } ;
66163
67- const directories = [
68- Global . Path . config ,
69- ...( await Array . fromAsync (
70- Filesystem . up ( {
71- targets : [ '.opencode' ] ,
72- start : Instance . directory ,
73- stop : Instance . worktree ,
74- } )
75- ) ) ,
76- ] ;
164+ // Perform automatic migration from .opencode to .link-assistant-agent if needed
165+ await migrateConfigDirectories ( ) ;
166+
167+ // Find all config directories
168+ const foundDirs = await Array . fromAsync (
169+ Filesystem . up ( {
170+ targets : [ '.link-assistant-agent' , '.opencode' ] ,
171+ start : Instance . directory ,
172+ stop : Instance . worktree ,
173+ } )
174+ ) ;
175+
176+ // Check if any .link-assistant-agent directory exists
177+ const hasNewConfig = foundDirs . some ( ( d ) =>
178+ d . endsWith ( '.link-assistant-agent' )
179+ ) ;
180+
181+ // Filter out .opencode directories if .link-assistant-agent exists
182+ const filteredDirs = foundDirs . filter ( ( dir ) => {
183+ // If .link-assistant-agent exists, exclude .opencode directories
184+ if ( hasNewConfig && dir . endsWith ( '.opencode' ) ) {
185+ log . debug (
186+ 'Skipping .opencode directory (using .link-assistant-agent):' ,
187+ {
188+ path : dir ,
189+ }
190+ ) ;
191+ return false ;
192+ }
193+ return true ;
194+ } ) ;
195+
196+ const directories = [ Global . Path . config , ...filteredDirs ] ;
77197
78198 if ( Flag . OPENCODE_CONFIG_DIR ) {
79199 directories . push ( Flag . OPENCODE_CONFIG_DIR ) ;
80- log . debug ( 'loading config from OPENCODE_CONFIG_DIR ' , {
200+ log . debug ( 'loading config from LINK_ASSISTANT_AGENT_CONFIG_DIR ' , {
81201 path : Flag . OPENCODE_CONFIG_DIR ,
82202 } ) ;
83203 }
@@ -86,7 +206,11 @@ export namespace Config {
86206 for ( const dir of directories ) {
87207 await assertValid ( dir ) ;
88208
89- if ( dir . endsWith ( '.opencode' ) || dir === Flag . OPENCODE_CONFIG_DIR ) {
209+ if (
210+ dir . endsWith ( '.link-assistant-agent' ) ||
211+ dir . endsWith ( '.opencode' ) ||
212+ dir === Flag . OPENCODE_CONFIG_DIR
213+ ) {
90214 for ( const file of [ 'opencode.jsonc' , 'opencode.json' ] ) {
91215 log . debug ( `loading config from ${ path . join ( dir , file ) } ` ) ;
92216 result = mergeDeep ( result , await loadFile ( path . join ( dir , file ) ) ) ;
@@ -162,7 +286,11 @@ export namespace Config {
162286 if ( ! md . data ) continue ;
163287
164288 const name = ( ( ) => {
165- const patterns = [ '/.opencode/command/' , '/command/' ] ;
289+ const patterns = [
290+ '/.link-assistant-agent/command/' ,
291+ '/.opencode/command/' ,
292+ '/command/' ,
293+ ] ;
166294 const pattern = patterns . find ( ( p ) => item . includes ( p ) ) ;
167295
168296 if ( pattern ) {
@@ -202,11 +330,13 @@ export namespace Config {
202330
203331 // Extract relative path from agent folder for nested agents
204332 let agentName = path . basename ( item , '.md' ) ;
205- const agentFolderPath = item . includes ( '/.opencode/agent/' )
206- ? item . split ( '/.opencode/agent/' ) [ 1 ]
207- : item . includes ( '/agent/' )
208- ? item . split ( '/agent/' ) [ 1 ]
209- : agentName + '.md' ;
333+ const agentFolderPath = item . includes ( '/.link-assistant-agent/agent/' )
334+ ? item . split ( '/.link-assistant-agent/agent/' ) [ 1 ]
335+ : item . includes ( '/.opencode/agent/' )
336+ ? item . split ( '/.opencode/agent/' ) [ 1 ]
337+ : item . includes ( '/agent/' )
338+ ? item . split ( '/agent/' ) [ 1 ]
339+ : agentName + '.md' ;
210340
211341 // If agent is in a subfolder, include folder path in name
212342 if ( agentFolderPath . includes ( '/' ) ) {
0 commit comments