@@ -212,48 +212,15 @@ const guardConfigSchema = z.object({
212212 options : z . record ( z . string ( ) , z . unknown ( ) ) . default ( { } ) ,
213213} )
214214
215- const ccxtAccountSchema = z . object ( {
215+ export const accountConfigSchema = z . object ( {
216216 id : z . string ( ) ,
217217 label : z . string ( ) . optional ( ) ,
218- type : z . literal ( 'ccxt' ) ,
219- exchange : z . string ( ) ,
220- sandbox : z . boolean ( ) . default ( false ) ,
221- demoTrading : z . boolean ( ) . default ( false ) ,
222- options : z . record ( z . string ( ) , z . unknown ( ) ) . optional ( ) ,
223- apiKey : z . string ( ) . optional ( ) ,
224- apiSecret : z . string ( ) . optional ( ) ,
225- password : z . string ( ) . optional ( ) ,
226- guards : z . array ( guardConfigSchema ) . default ( [ ] ) ,
227- } ) . passthrough ( )
228-
229- const alpacaAccountSchema = z . object ( {
230- id : z . string ( ) ,
231- label : z . string ( ) . optional ( ) ,
232- type : z . literal ( 'alpaca' ) ,
233- paper : z . boolean ( ) . default ( true ) ,
234- apiKey : z . string ( ) . optional ( ) ,
235- apiSecret : z . string ( ) . optional ( ) ,
236- guards : z . array ( guardConfigSchema ) . default ( [ ] ) ,
237- } )
238-
239- const ibkrAccountSchema = z . object ( {
240- id : z . string ( ) ,
241- label : z . string ( ) . optional ( ) ,
242- type : z . literal ( 'ibkr' ) ,
243- host : z . string ( ) . default ( '127.0.0.1' ) ,
244- port : z . number ( ) . int ( ) . default ( 7497 ) ,
245- clientId : z . number ( ) . int ( ) . default ( 0 ) ,
246- accountId : z . string ( ) . optional ( ) ,
247- paper : z . boolean ( ) . default ( true ) ,
218+ type : z . string ( ) ,
219+ enabled : z . boolean ( ) . default ( true ) ,
248220 guards : z . array ( guardConfigSchema ) . default ( [ ] ) ,
221+ brokerConfig : z . record ( z . string ( ) , z . unknown ( ) ) . default ( { } ) ,
249222} )
250223
251- export const accountConfigSchema = z . discriminatedUnion ( 'type' , [
252- ccxtAccountSchema ,
253- alpacaAccountSchema ,
254- ibkrAccountSchema ,
255- ] )
256-
257224export const accountsFileSchema = z . array ( accountConfigSchema )
258225
259226export type AccountConfig = z . infer < typeof accountConfigSchema >
@@ -374,6 +341,28 @@ export async function loadConfig(): Promise<Config> {
374341
375342// ==================== Account Config Loader ====================
376343
344+ /** Common fields that live at the top level, not inside brokerConfig. */
345+ const BASE_FIELDS = new Set ( [ 'id' , 'label' , 'type' , 'guards' , 'brokerConfig' ] )
346+
347+ /**
348+ * Migrate flat account config (legacy) to nested brokerConfig format.
349+ * Any field not in BASE_FIELDS gets moved into brokerConfig.
350+ */
351+ function migrateAccountConfig ( raw : Record < string , unknown > ) : Record < string , unknown > {
352+ if ( raw . brokerConfig ) return raw // already migrated
353+ const migrated : Record < string , unknown > = { }
354+ const brokerConfig : Record < string , unknown > = { }
355+ for ( const [ k , v ] of Object . entries ( raw ) ) {
356+ if ( BASE_FIELDS . has ( k ) ) {
357+ migrated [ k ] = v
358+ } else {
359+ brokerConfig [ k ] = v
360+ }
361+ }
362+ migrated . brokerConfig = brokerConfig
363+ return migrated
364+ }
365+
377366export async function readAccountsConfig ( ) : Promise < AccountConfig [ ] > {
378367 const raw = await loadJsonFile ( 'accounts.json' )
379368 if ( raw === undefined ) {
@@ -382,7 +371,9 @@ export async function readAccountsConfig(): Promise<AccountConfig[]> {
382371 await writeFile ( resolve ( CONFIG_DIR , 'accounts.json' ) , '[]\n' )
383372 return [ ]
384373 }
385- return accountsFileSchema . parse ( raw )
374+ // Migrate legacy flat format → nested brokerConfig
375+ const migrated = ( raw as unknown [ ] ) . map ( ( item ) => migrateAccountConfig ( item as Record < string , unknown > ) )
376+ return accountsFileSchema . parse ( migrated )
386377}
387378
388379export async function writeAccountsConfig ( accounts : AccountConfig [ ] ) : Promise < void > {
0 commit comments