diff --git a/dist/commands/plugin.js b/dist/commands/plugin.js index 30b4627..a076b9f 100644 --- a/dist/commands/plugin.js +++ b/dist/commands/plugin.js @@ -9,9 +9,98 @@ const ctx_1 = require("../lib/ctx"); const output_1 = require("../lib/output"); const request_1 = require("../lib/request"); void ctx_1.requireBaseId; +// ---- developer whitelist subcommands (Feature 4) ---- +function addDevCommands(cmd) { + const dev = cmd.command('dev').description('Manage plugin developer whitelist (admin)'); + dev + .command('list') + .description('List all whitelisted developers') + .action((0, ctx_1.run)(async (ctx) => { + const list = (await ctx.client.listDevelopers()); + if (ctx.json) + (0, output_1.printJson)(list); + else if (!list.length) + process.stdout.write('(no developers)\n'); + else + (0, output_1.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((0, ctx_1.run)(async (ctx, args) => { + const userIdOrEmail = args[0]; + 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((0, ctx_1.run)(async (ctx, args) => { + const userIdOrEmail = args[0]; + await ctx.client.removeDeveloper(userIdOrEmail); + process.stdout.write(`✓ Developer revoked: ${userIdOrEmail}\n`); + })); +} +// ---- install / uninstall subcommands (Feature 1) ---- +function addInstallCommands(cmd) { + 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((0, ctx_1.run)(async (ctx, _args, o) => { + const opts = o; + const body = { + 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)); + if (ctx.json) + (0, output_1.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((0, ctx_1.run)(async (ctx, _args, o) => { + const opts = o; + 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((0, ctx_1.run)(async (ctx) => { + const list = (await ctx.client.listInstalls(ctx.baseId)); + if (ctx.json) + (0, output_1.printJson)(list); + else if (!list.length) + process.stdout.write('(no installs)\n'); + else + (0, output_1.printTable)([ + { key: 'id', header: 'INSTALL_ID' }, + { key: 'pluginId', header: 'PLUGIN' }, + { key: 'name', header: 'NAME' }, + { key: 'position', header: 'POSITION' }, + ], list); + })); +} function pluginCommand() { const cmd = new commander_1.Command('plugin'); - cmd.description('Manage plugins (list, register, config)'); + cmd.description('Manage plugins (list, register, config, create, install, dev)'); cmd .command('list') .description('List registered plugins') @@ -27,7 +116,12 @@ function pluginCommand() { { 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' }))); + ], list.map((p) => ({ + id: p.id, + name: p.name, + status: p.status, + runtimeType: p.runtimeType ?? 'url', + }))); } } catch (e) { @@ -120,6 +214,60 @@ function pluginCommand() { }); process.stdout.write(`✓ Plugin ${opts.plugin} disabled${opts.table ? ` for table ${opts.table}` : ' base-wide'}\n`); })); + // ---- CSP apiOrigins management ---- + const csp = cmd.command('csp').description('Manage plugin CSP apiOrigins'); + csp + .command('list') + .description('Show the union of all plugins\' apiOrigins (effective CSP connect-src extras)') + .action((0, ctx_1.run)(async (ctx) => { + const { origins } = await ctx.client.getCspOrigins(); + if (ctx.json) + (0, output_1.printJson)(origins); + else if (!origins.length) + process.stdout.write('(no apiOrigins declared by any plugin)\n'); + else + origins.forEach((o) => process.stdout.write(`${o}\n`)); + })); + csp + .command('set ') + .description('Set the external API origins a plugin may fetch (bundle plugins included)') + .requiredOption('--origins ', 'JSON array of origins, e.g. \'["http://192.168.124.92:8001"]\'') + .option('--clear', 'Remove all origins (pass "[]" works too)') + .action((0, ctx_1.run)(async (ctx, args, o) => { + const opts = o; + const pluginId = args[0]; + let origins; + try { + const parsed = JSON.parse(opts.origins); + if (!Array.isArray(parsed) || parsed.some((x) => typeof x !== 'string')) { + throw new Error('not a string array'); + } + origins = parsed; + } + catch { + process.stderr.write(`✗ --origins must be a JSON string array, got: ${opts.origins}\n`); + process.exitCode = 1; + return; + } + if (opts.clear) + origins = []; + // updatePlugin requires name + positions (full-vo update): fetch current values. + const cur = await ctx.client.getPlugin(pluginId); + await ctx.client.updatePlugin(pluginId, { + name: cur.name, + positions: cur.positions, + description: cur.description, + detailDesc: cur.detailDesc, + url: cur.url, + logo: cur.logo, + helpUrl: cur.helpUrl, + apiOrigins: origins, + }); + process.stdout.write(`✓ apiOrigins for ${pluginId} set to: ${JSON.stringify(origins)}\n`); + })); + // Wire feature subcommands + addDevCommands(cmd); + addInstallCommands(cmd); return cmd; } exports.pluginCommand = pluginCommand; diff --git a/dist/lib/client.js b/dist/lib/client.js index 5b42378..77cfbde 100644 --- a/dist/lib/client.js +++ b/dist/lib/client.js @@ -218,6 +218,15 @@ class Client { getPlugins() { return this.call('GET', '/api/plugin'); } + getPlugin(pluginId) { + return this.call('GET', `/api/plugin/${encodeURIComponent(pluginId)}`); + } + updatePlugin(pluginId, body) { + return this.call('PUT', `/api/plugin/${encodeURIComponent(pluginId)}`, body); + } + getCspOrigins() { + return this.call('GET', '/api/plugin/csp-origins'); + } registerPlugin(body) { return this.call('POST', '/api/plugin', body); } @@ -227,5 +236,28 @@ class Client { upsertPluginConfig(baseId, pluginId, body) { return this.call('PUT', `/api/base/${baseId}/plugin-config/${pluginId}`, body); } + // ---- plugin installs ---- + listInstalls(baseId) { + return this.call('GET', `/api/base/${baseId}/plugin-installs`); + } + createInstall(baseId, body) { + return this.call('POST', `/api/base/${baseId}/plugin-installs`, body); + } + deleteInstall(baseId, installId) { + return this.call('DELETE', `/api/base/${baseId}/plugin-installs/${installId}`); + } + // ---- plugin developers ---- + listDevelopers() { + return this.call('GET', '/api/plugin-developers'); + } + addDeveloper(userIdOrEmail) { + // Backend reads @Body('userId') or @Body('email'). Send whichever looks right. + const isEmail = userIdOrEmail.includes('@'); + const body = isEmail ? { email: userIdOrEmail } : { userId: userIdOrEmail }; + return this.call('POST', '/api/plugin-developers', body); + } + removeDeveloper(userId) { + return this.call('DELETE', `/api/plugin-developers/${encodeURIComponent(userId)}`); + } } exports.Client = Client;