import { Command } from 'commander'; import chalk from 'chalk'; import ora from 'ora'; import { IndemnClient } from '../sdk/client.js'; import { printError, printJson } from './utils/output.js'; export function registerOrgCommands(program: Command): void { const org = program.command('org').description('Manage organizations'); org .command('list') .description('List organizations you have access to') .option('--json', 'Output as JSON') .action(async (options) => { try { const client = new IndemnClient(); const spinner = ora('Fetching organizations...').start(); // Get all project memberships const res = await fetch(client.getHosts().copilot + '/projects', { headers: { 'Authorization': `Bearer ${client.getApiKey()}`, 'Content-Type': 'application/json', }, }); if (!res.ok) throw new Error(`Failed to fetch projects (${res.status})`); const memberships = await res.json() as Array<{ id_organization?: string }>; // Get unique org IDs const orgIds = [...new Set(memberships.map(m => m.id_organization).filter(Boolean))] as string[]; // Fetch org details const orgs: Array<{ _id: string; name: string; active: boolean }> = []; for (const orgId of orgIds) { try { const orgRes = await fetch(client.getHosts().copilot + '/organization/' + orgId, { headers: { 'Authorization': `Bearer ${client.getApiKey()}`, 'Content-Type': 'application/json', }, }); if (orgRes.ok) { const data = await orgRes.json() as { organization: { _id: string; name: string; active: boolean } }; orgs.push(data.organization); } } catch { // Skip orgs we can't fetch } } spinner.stop(); if (options.json) { printJson(orgs.map(o => ({ id: o._id, name: o.name, active: o.active }))); return; } const currentOrgId = client.getOrgId(); // Opportunistically sync the cached org_name while we have fresh data. const activeOrg = orgs.find(o => o._id === currentOrgId); if (activeOrg) { IndemnClient.saveConfig({ org_name: activeOrg.name }); } console.log(chalk.bold('Organizations')); console.log(''); for (const o of orgs.sort((a, b) => a.name.localeCompare(b.name))) { const marker = o._id === currentOrgId ? chalk.green(' (active)') : ''; console.log(` ${o._id} ${o.name}${marker}`); } console.log(''); console.log(chalk.dim(`${orgs.length} organizations. Switch with: indemn org switch `)); } catch (err) { printError(err); process.exit(1); } }); org .command('switch') .description('Switch active organization') .argument('', 'Organization ID to switch to') .action(async (orgId: string) => { try { const client = new IndemnClient(); const spinner = ora('Switching organization...').start(); // Verify the user has access to this org const orgRes = await fetch(client.getHosts().copilot + '/organization/' + orgId, { headers: { 'Authorization': `Bearer ${client.getApiKey()}`, 'Content-Type': 'application/json', }, }); if (!orgRes.ok) { spinner.fail('Organization not found or you don\'t have access.'); process.exit(1); } const data = await orgRes.json() as { organization: { _id: string; name: string } }; const orgName = data.organization.name; // Update config — cache both org_id and org_name so the banner can render without a lookup. IndemnClient.saveConfig({ org_id: orgId, org_name: orgName }); spinner.succeed(`Switched to ${orgName} (${orgId})`); } catch (err) { printError(err); process.exit(1); } }); }