@@ -15,6 +15,8 @@ const API_HEADERS = {
1515 "User-Agent" : "myclaude-npx" ,
1616 Accept : "application/vnd.github+json" ,
1717} ;
18+ const WRAPPER_REQUIRED_MODULES = new Set ( [ "do" , "omo" ] ) ;
19+ const WRAPPER_REQUIRED_SKILLS = new Set ( [ "dev" ] ) ;
1820
1921function parseArgs ( argv ) {
2022 const out = {
@@ -499,9 +501,19 @@ async function updateInstalledModules(installDir, tag, config, dryRun) {
499501 }
500502
501503 await fs . promises . mkdir ( installDir , { recursive : true } ) ;
504+ const installState = { wrapperInstalled : false } ;
505+
506+ async function ensureWrapperInstalled ( ) {
507+ if ( installState . wrapperInstalled ) return ;
508+ process . stdout . write ( "Installing codeagent-wrapper...\n" ) ;
509+ await runInstallSh ( repoRoot , installDir , tag ) ;
510+ installState . wrapperInstalled = true ;
511+ }
512+
502513 for ( const name of toUpdate ) {
514+ if ( WRAPPER_REQUIRED_MODULES . has ( name ) ) await ensureWrapperInstalled ( ) ;
503515 process . stdout . write ( `Updating module: ${ name } \n` ) ;
504- const r = await applyModule ( name , config , repoRoot , installDir , true , tag ) ;
516+ const r = await applyModule ( name , config , repoRoot , installDir , true , tag , installState ) ;
505517 upsertModuleStatus ( installDir , r ) ;
506518 }
507519 } finally {
@@ -777,7 +789,57 @@ async function rmTree(p) {
777789 await fs . promises . rmdir ( p , { recursive : true } ) ;
778790}
779791
780- async function applyModule ( moduleName , config , repoRoot , installDir , force , tag ) {
792+ function defaultModelsConfig ( ) {
793+ return {
794+ default_backend : "codex" ,
795+ default_model : "gpt-4.1" ,
796+ backends : { } ,
797+ agents : { } ,
798+ } ;
799+ }
800+
801+ function mergeModuleAgentsToModels ( moduleName , mod , repoRoot ) {
802+ const moduleAgents = mod && mod . agents ;
803+ if ( ! isPlainObject ( moduleAgents ) || ! Object . keys ( moduleAgents ) . length ) return false ;
804+
805+ const modelsPath = path . join ( os . homedir ( ) , ".codeagent" , "models.json" ) ;
806+ fs . mkdirSync ( path . dirname ( modelsPath ) , { recursive : true } ) ;
807+
808+ let models ;
809+ if ( fs . existsSync ( modelsPath ) ) {
810+ models = JSON . parse ( fs . readFileSync ( modelsPath , "utf8" ) ) ;
811+ } else {
812+ const templatePath = path . join ( repoRoot , "templates" , "models.json.example" ) ;
813+ if ( fs . existsSync ( templatePath ) ) {
814+ models = JSON . parse ( fs . readFileSync ( templatePath , "utf8" ) ) ;
815+ if ( ! isPlainObject ( models ) ) models = defaultModelsConfig ( ) ;
816+ models . agents = { } ;
817+ } else {
818+ models = defaultModelsConfig ( ) ;
819+ }
820+ }
821+
822+ if ( ! isPlainObject ( models ) ) models = defaultModelsConfig ( ) ;
823+ if ( ! isPlainObject ( models . agents ) ) models . agents = { } ;
824+
825+ let modified = false ;
826+ for ( const [ agentName , agentCfg ] of Object . entries ( moduleAgents ) ) {
827+ if ( ! isPlainObject ( agentCfg ) ) continue ;
828+ const existing = models . agents [ agentName ] ;
829+ const canOverwrite = ! isPlainObject ( existing ) || Object . prototype . hasOwnProperty . call ( existing , "__module__" ) ;
830+ if ( ! canOverwrite ) continue ;
831+ const next = { ...agentCfg , __module__ : moduleName } ;
832+ if ( ! deepEqual ( existing , next ) ) {
833+ models . agents [ agentName ] = next ;
834+ modified = true ;
835+ }
836+ }
837+
838+ if ( modified ) fs . writeFileSync ( modelsPath , JSON . stringify ( models , null , 2 ) + "\n" , "utf8" ) ;
839+ return modified ;
840+ }
841+
842+ async function applyModule ( moduleName , config , repoRoot , installDir , force , tag , installState ) {
781843 const mod = config && config . modules && config . modules [ moduleName ] ;
782844 if ( ! mod ) throw new Error ( `Unknown module: ${ moduleName } ` ) ;
783845 const ops = Array . isArray ( mod . operations ) ? mod . operations : [ ] ;
@@ -803,7 +865,12 @@ async function applyModule(moduleName, config, repoRoot, installDir, force, tag)
803865 if ( cmd !== "bash install.sh" ) {
804866 throw new Error ( `Refusing run_command: ${ cmd || "(empty)" } ` ) ;
805867 }
868+ if ( installState && installState . wrapperInstalled ) {
869+ result . operations . push ( { type, status : "success" , skipped : true } ) ;
870+ continue ;
871+ }
806872 await runInstallSh ( repoRoot , installDir , tag ) ;
873+ if ( installState ) installState . wrapperInstalled = true ;
807874 } else {
808875 throw new Error ( `Unsupported operation type: ${ type } ` ) ;
809876 }
@@ -834,6 +901,19 @@ async function applyModule(moduleName, config, repoRoot, installDir, force, tag)
834901 } ) ;
835902 }
836903
904+ try {
905+ if ( mergeModuleAgentsToModels ( moduleName , mod , repoRoot ) ) {
906+ result . has_agents = true ;
907+ result . operations . push ( { type : "merge_agents" , status : "success" } ) ;
908+ }
909+ } catch ( err ) {
910+ result . operations . push ( {
911+ type : "merge_agents" ,
912+ status : "failed" ,
913+ error : err && err . message ? err . message : String ( err ) ,
914+ } ) ;
915+ }
916+
837917 return result ;
838918}
839919
@@ -1023,20 +1103,37 @@ async function installSelected(picks, tag, config, installDir, force, dryRun) {
10231103 }
10241104
10251105 await fs . promises . mkdir ( installDir , { recursive : true } ) ;
1106+ const installState = { wrapperInstalled : false } ;
1107+
1108+ async function ensureWrapperInstalled ( ) {
1109+ if ( installState . wrapperInstalled ) return ;
1110+ process . stdout . write ( "Installing codeagent-wrapper...\n" ) ;
1111+ await runInstallSh ( repoRoot , installDir , tag ) ;
1112+ installState . wrapperInstalled = true ;
1113+ }
10261114
10271115 for ( const p of picks ) {
10281116 if ( p . kind === "wrapper" ) {
1029- process . stdout . write ( "Installing codeagent-wrapper...\n" ) ;
1030- await runInstallSh ( repoRoot , installDir , tag ) ;
1117+ await ensureWrapperInstalled ( ) ;
10311118 continue ;
10321119 }
10331120 if ( p . kind === "module" ) {
1121+ if ( WRAPPER_REQUIRED_MODULES . has ( p . moduleName ) ) await ensureWrapperInstalled ( ) ;
10341122 process . stdout . write ( `Installing module: ${ p . moduleName } \n` ) ;
1035- const r = await applyModule ( p . moduleName , config , repoRoot , installDir , force , tag ) ;
1123+ const r = await applyModule (
1124+ p . moduleName ,
1125+ config ,
1126+ repoRoot ,
1127+ installDir ,
1128+ force ,
1129+ tag ,
1130+ installState
1131+ ) ;
10361132 upsertModuleStatus ( installDir , r ) ;
10371133 continue ;
10381134 }
10391135 if ( p . kind === "skill" ) {
1136+ if ( WRAPPER_REQUIRED_SKILLS . has ( p . skillName ) ) await ensureWrapperInstalled ( ) ;
10401137 process . stdout . write ( `Installing skill: ${ p . skillName } \n` ) ;
10411138 await copyDirRecursive (
10421139 path . join ( repoRoot , "skills" , p . skillName ) ,
0 commit comments