// Instance-level shell verbs. Intentionally separate from pluginsCommand's bus transport. import { readdirSync } from 'node:fs'; import { join } from 'node:path'; import { CliError } from './errors.mjs'; import { refuseInsideAgent } from './transport.mjs'; import { configDir } from '../../organization/src/organization-config.mjs'; import { assertAccountsOwner } from '../../organization/src/accounts.mjs'; import { withRegistry } from '../../organization/src/org-registry.mjs'; import { readJsonFile } from '../../organization/src/util.mjs'; import { installPlugin, removePlugin } from '../../plugins/src/install.mjs'; import { readPluginLock } from '../../plugins/src/pinned.mjs'; export async function pluginInstallCommand(args,io,{system,dir=configDir(io.env)}) { refuseInsideAgent(io.env); try { assertAccountsOwner(dir); const [verb,value,...rest]=args; if(!['install','list','remove'].includes(verb))throw new CliError('mosaic plugins install SOURCE --sha256 HASH [--commit FULL_SHA] | list | remove NAME',4); const opts={}; if(verb==='install') { for(let i=0;iio.stdout.write(line+'\n')}); else result=await withRegistry({configDir:dir,instance:system.instance ?? dir.split('/').pop().replace(/^mosaic-/, '')},()=>{ // All organization files, including archived organizations, must be readable. The registry // lock prevents a simultaneous create from disappearing from this check. let files=[];try{files=readdirSync(join(dir,'businesses'));}catch(e){if(e.code!=='ENOENT')throw e;} return removePlugin({dataRoot:system.dataRoot,name:value,enabled:name=>files.filter(n=>n.endsWith('.json')).some(n=>{ const doc=readJsonFile(join(dir,'businesses',n),'organization file'); return Object.values(doc.plugins ?? {}).flat().includes(name); })}); },{write:false}); io.stdout.write(JSON.stringify(result,null,2)+'\n'); }catch(e){if(e instanceof CliError)throw e;throw new CliError(`plugin management refused: ${/^[a-z-]+$/.test(e.code ?? '')?e.code:'plugin-refused'}`,3);} }