feat(cli): add inkreach-teable plugin commands

plugin list: list registered plugins (id/name/status/runtimeType)
plugin register: register by URL (--name --url --positions)
plugin config list: list base configs (pluginId/tableId/enabled/form/write)
plugin config enable/disable: enable/disable per table or base-wide

Added 5 client methods: getPlugins, registerPlugin, listPluginConfigs,
upsertPluginConfig for CRUD.
This commit is contained in:
yeuimu
2026-08-06 09:03:04 +08:00
parent 584b75ba69
commit 0891bda134
6 changed files with 294 additions and 0 deletions
Vendored
+2
View File
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
*/ */
const commander_1 = require("commander"); const commander_1 = require("commander");
const package_json_1 = require("../package.json"); const package_json_1 = require("../package.json");
const plugin_1 = require("./commands/plugin");
const auth_1 = require("./commands/auth"); const auth_1 = require("./commands/auth");
const workflow_1 = require("./commands/workflow"); const workflow_1 = require("./commands/workflow");
const resources_1 = require("./commands/resources"); const resources_1 = require("./commands/resources");
@@ -27,6 +28,7 @@ program
.option('--debug', 'Print requests/responses to stderr (verbose)'); .option('--debug', 'Print requests/responses to stderr (verbose)');
// Command groups // Command groups
program.addCommand((0, auth_1.authCommand)()); program.addCommand((0, auth_1.authCommand)());
program.addCommand((0, plugin_1.pluginCommand)());
for (const c of (0, resources_1.baseResourceCommands)()) for (const c of (0, resources_1.baseResourceCommands)())
program.addCommand(c); program.addCommand(c);
program.addCommand((0, workflow_1.workflowCommand)()); program.addCommand((0, workflow_1.workflowCommand)());
+125
View File
@@ -0,0 +1,125 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.pluginCommand = void 0;
/**
* `inkreach-teable plugin` — plugin management commands.
*/
const commander_1 = require("commander");
const ctx_1 = require("../lib/ctx");
const output_1 = require("../lib/output");
const request_1 = require("../lib/request");
void ctx_1.requireBaseId;
function pluginCommand() {
const cmd = new commander_1.Command('plugin');
cmd.description('Manage plugins (list, register, config)');
cmd
.command('list')
.description('List registered plugins')
.action((0, ctx_1.run)(async (ctx) => {
try {
const list = (await ctx.client.getPlugins());
if (ctx.json) {
(0, output_1.printJson)(list);
}
else {
(0, output_1.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 request_1.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 <url>', 'Plugin URL')
.requiredOption('--name <name>', 'Plugin name')
.option('--description <desc>', 'Plugin description')
.option('--logo <url>', 'Logo URL')
.option('--positions <json>', 'Positions array, e.g. \'["panel"]\'', '["panel"]')
.action((0, ctx_1.run)(async (ctx, _args, o) => {
const opts = o;
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)
(0, output_1.printJson)(created);
else
process.stdout.write(`✓ Plugin registered: ${created.id}\n`);
}));
const config = cmd.command('config').description('Manage per-table plugin enablement');
config
.command('list')
.description('List plugin configs for the base')
.action((0, ctx_1.run)(async (ctx) => {
const list = await ctx.client.listPluginConfigs(ctx.baseId);
if (ctx.json)
(0, output_1.printJson)(list);
else {
(0, output_1.printTable)([
{ key: 'pluginId', header: 'PLUGIN' },
{ key: 'tableId', header: 'TABLE' },
{ key: 'enabled', header: 'ON' },
{ key: 'runtimeForm', header: 'FORM' },
{ key: 'writeEnabled', header: 'WRITE' },
], list.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 <id>', 'Plugin ID')
.option('--table <id>', 'Table ID (omit for base-wide)')
.option('--write', 'Allow write-back')
.action((0, ctx_1.run)(async (ctx, _args, o) => {
const opts = o;
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)
(0, output_1.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 <id>', 'Plugin ID')
.option('--table <id>', 'Table ID')
.action((0, ctx_1.run)(async (ctx, _args, o) => {
const opts = o;
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`);
}));
return cmd;
}
exports.pluginCommand = pluginCommand;
+13
View File
@@ -207,5 +207,18 @@ class Client {
sql(baseId, sql) { sql(baseId, sql) {
return this.call('POST', `/api/base/${baseId}/sql`, { sql }); return this.call('POST', `/api/base/${baseId}/sql`, { sql });
} }
// ---- plugin ----
getPlugins() {
return this.call('GET', '/api/plugin');
}
registerPlugin(body) {
return this.call('POST', '/api/plugin', body);
}
listPluginConfigs(baseId) {
return this.call('GET', `/api/base/${baseId}/plugin-config`);
}
upsertPluginConfig(baseId, pluginId, body) {
return this.call('PUT', `/api/base/${baseId}/plugin-config/${pluginId}`, body);
}
} }
exports.Client = Client; exports.Client = Client;
+2
View File
@@ -6,6 +6,7 @@
*/ */
import { Command } from 'commander'; import { Command } from 'commander';
import { version } from '../package.json'; import { version } from '../package.json';
import { pluginCommand } from './commands/plugin';
import { authCommand } from './commands/auth'; import { authCommand } from './commands/auth';
import { workflowCommand, triggerCommand, actionCommand } from './commands/workflow'; import { workflowCommand, triggerCommand, actionCommand } from './commands/workflow';
import { baseResourceCommands } from './commands/resources'; import { baseResourceCommands } from './commands/resources';
@@ -31,6 +32,7 @@ program
// Command groups // Command groups
program.addCommand(authCommand()); program.addCommand(authCommand());
program.addCommand(pluginCommand());
for (const c of baseResourceCommands()) program.addCommand(c); for (const c of baseResourceCommands()) program.addCommand(c);
program.addCommand(workflowCommand()); program.addCommand(workflowCommand());
program.addCommand(triggerCommand()); program.addCommand(triggerCommand());
+138
View File
@@ -0,0 +1,138 @@
/**
* `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;
export function pluginCommand(): Command {
const cmd = new Command('plugin');
cmd.description('Manage plugins (list, register, config)');
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 <url>', 'Plugin URL')
.requiredOption('--name <name>', 'Plugin name')
.option('--description <desc>', 'Plugin description')
.option('--logo <url>', 'Logo URL')
.option('--positions <json>', '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<Record<string, unknown>>).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 <id>', 'Plugin ID')
.option('--table <id>', '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 <id>', 'Plugin ID')
.option('--table <id>', '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`);
})
);
return cmd;
}
+14
View File
@@ -224,6 +224,20 @@ export class Client {
sql(baseId: string, sql: string) { sql(baseId: string, sql: string) {
return this.call<unknown[]>('POST', `/api/base/${baseId}/sql`, { sql }); return this.call<unknown[]>('POST', `/api/base/${baseId}/sql`, { sql });
} }
// ---- plugin ----
getPlugins() {
return this.call<unknown[]>('GET', '/api/plugin');
}
registerPlugin(body: Record<string, unknown>) {
return this.call<unknown>('POST', '/api/plugin', body);
}
listPluginConfigs(baseId: string) {
return this.call<unknown[]>('GET', `/api/base/${baseId}/plugin-config`);
}
upsertPluginConfig(baseId: string, pluginId: string, body: Record<string, unknown>) {
return this.call<unknown>('PUT', `/api/base/${baseId}/plugin-config/${pluginId}`, body);
}
} }
// ---- shared types (mirror backend workflow_node / workflow_edge shapes) ---- // ---- shared types (mirror backend workflow_node / workflow_edge shapes) ----