/** * `inkreach-teable plugin` — plugin management commands. */ import { Command } from 'commander'; import { run, requireBaseId } from '../lib/ctx'; import { printJson, printTable } from '../lib/output'; import { ApiError } from '../lib/request'; void requireBaseId; // ---- developer whitelist subcommands (Feature 4) ---- function addDevCommands(cmd: Command) { const dev = cmd.command('dev').description('Manage plugin developer whitelist (admin)'); dev .command('list') .description('List all whitelisted developers') .action( run(async (ctx) => { const list = (await ctx.client.listDevelopers()) as Array<{ userId: string; email: string; createdBy: string; }>; if (ctx.json) printJson(list); else if (!list.length) process.stdout.write('(no developers)\n'); else printTable( [ { key: 'userId', header: 'USER_ID' }, { key: 'email', header: 'EMAIL' }, { key: 'createdBy', header: 'ADDED_BY' }, ], list ); }) ); dev .command('allow') .description('Add a user to the developer whitelist (userId or email)') .argument('', 'User ID or email') .action( run(async (ctx, args) => { const userIdOrEmail = args[0] as string; await ctx.client.addDeveloper(userIdOrEmail); process.stdout.write(`✓ Developer added: ${userIdOrEmail}\n`); }) ); dev .command('revoke') .description('Remove a user from the developer whitelist (userId or email)') .argument('', 'User ID or email') .action( run(async (ctx, args) => { const userIdOrEmail = args[0] as string; await ctx.client.removeDeveloper(userIdOrEmail); process.stdout.write(`✓ Developer revoked: ${userIdOrEmail}\n`); }) ); } // ---- install / uninstall subcommands (Feature 1) ---- function addInstallCommands(cmd: Command) { cmd .command('install') .description('Install a plugin into the current base (creates a node)') .requiredOption('--plugin ', 'Plugin ID') .requiredOption('--name ', 'Display name for this node') .requiredOption( '--position ', 'Mount position: base | panel | view | dashboard | contextMenu', 'base' ) .option('--storage ', 'Initial storage JSON passed to the plugin') .action( run(async (ctx, _args, o) => { const opts = o as { plugin: string; name: string; position: string; storage?: string }; const body: Record = { pluginId: opts.plugin, name: opts.name, position: opts.position, }; if (opts.storage) body.storage = opts.storage; const result = (await ctx.client.createInstall(ctx.baseId!, body)) as { id: string }; if (ctx.json) printJson(result); else process.stdout.write( `✓ Plugin installed: ${result.id} (${opts.name} → ${opts.position})\n` ); }) ); cmd .command('uninstall') .description('Remove a plugin install by its install ID') .requiredOption('--install ', 'Install ID') .action( run(async (ctx, _args, o) => { const opts = o as { install: string }; await ctx.client.deleteInstall(ctx.baseId!, opts.install); process.stdout.write(`✓ Plugin install removed: ${opts.install}\n`); }) ); cmd .command('installs') .description('List plugin installs for the current base') .action( run(async (ctx) => { const list = (await ctx.client.listInstalls(ctx.baseId!)) as Array<{ id: string; pluginId: string; name: string; position: string; }>; if (ctx.json) printJson(list); else if (!list.length) process.stdout.write('(no installs)\n'); else printTable( [ { key: 'id', header: 'INSTALL_ID' }, { key: 'pluginId', header: 'PLUGIN' }, { key: 'name', header: 'NAME' }, { key: 'position', header: 'POSITION' }, ], list ); }) ); } export function pluginCommand(): Command { const cmd = new Command('plugin'); cmd.description('Manage plugins (list, register, config, create, install, dev)'); cmd .command('list') .description('List registered plugins') .action( run(async (ctx) => { try { const list = (await ctx.client.getPlugins()) as Array<{ id: string; name: string; status: string; runtimeType?: string; }>; if (ctx.json) { printJson(list); } else { printTable( [ { key: 'id', header: 'ID' }, { key: 'name', header: 'NAME' }, { key: 'status', header: 'STATUS' }, { key: 'runtimeType', header: 'RUNTIME' }, ], list.map((p) => ({ id: p.id, name: p.name, status: p.status, runtimeType: p.runtimeType ?? 'url', })) ); } } catch (e) { if (e instanceof ApiError && e.status === 401) { process.stderr.write('Unauthorized — log in via "auth login" or provide --token\n'); process.exit(1); } throw e; } }) ); cmd .command('register') .description('Register a plugin by URL') .requiredOption('--url ', 'Plugin URL') .requiredOption('--name ', 'Plugin name') .option('--description ', 'Plugin description') .option('--logo ', 'Logo URL') .option('--positions ', 'Positions array, e.g. \'["panel"]\'', '["panel"]') .action( run(async (ctx, _args, o) => { const opts = o as { url: string; name: string; description?: string; logo?: string; positions: string; }; const positions = JSON.parse(opts.positions); const created = await ctx.client.registerPlugin({ name: opts.name, url: opts.url, description: opts.description, logo: opts.logo ?? '', positions, status: 'published', }); if (ctx.json) printJson(created); else process.stdout.write(`✓ Plugin registered: ${(created as { id?: string }).id}\n`); }) ); const config = cmd.command('config').description('Manage per-table plugin enablement'); config .command('list') .description('List plugin configs for the base') .action( run(async (ctx) => { const list = await ctx.client.listPluginConfigs(ctx.baseId!); if (ctx.json) printJson(list); else { printTable( [ { key: 'pluginId', header: 'PLUGIN' }, { key: 'tableId', header: 'TABLE' }, { key: 'enabled', header: 'ON' }, { key: 'runtimeForm', header: 'FORM' }, { key: 'writeEnabled', header: 'WRITE' }, ], (list as Array>).map((r) => ({ pluginId: r.pluginId, tableId: r.tableId || '-', enabled: r.enabled, runtimeForm: r.runtimeForm, writeEnabled: r.writeEnabled, })) ); } }) ); config .command('enable') .description('Enable a plugin for a table (or base-wide if no --table)') .requiredOption('--plugin ', 'Plugin ID') .option('--table ', 'Table ID (omit for base-wide)') .option('--write', 'Allow write-back') .action( run(async (ctx, _args, o) => { const opts = o as { plugin: string; table?: string; write?: boolean }; const result = await ctx.client.upsertPluginConfig(ctx.baseId!, opts.plugin, { enabled: true, runtimeForm: 'panel', writeEnabled: !!opts.write, tableIds: opts.table ? [opts.table] : [], }); if (ctx.json) printJson(result); else process.stdout.write( `✓ Plugin ${opts.plugin} enabled${opts.table ? ` for table ${opts.table}` : ' base-wide'}\n` ); }) ); config .command('disable') .description('Disable a plugin for a table (or base-wide if no --table)') .requiredOption('--plugin ', 'Plugin ID') .option('--table ', 'Table ID') .action( run(async (ctx, _args, o) => { const opts = o as { plugin: string; table?: string }; await ctx.client.upsertPluginConfig(ctx.baseId!, opts.plugin, { enabled: false, runtimeForm: 'panel', writeEnabled: false, tableIds: opts.table ? [opts.table] : [], }); process.stdout.write( `✓ Plugin ${opts.plugin} disabled${opts.table ? ` for table ${opts.table}` : ' base-wide'}\n` ); }) ); // Wire feature subcommands addDevCommands(cmd); addInstallCommands(cmd); return cmd; }