Files
inkreach-teable-cli/dist/commands/resources.js
T
yeuimu 5fa843df05 feat: add inkreach CLI + inkreach-teable-ops skill
A self-built CLI and companion agent skill for operating a deployed
inkreach-teable instance — configuring tables, fields, records, views, and
the project's OWN automation engine.

Why not the official 'teable' CLI:
  inkreach-teable's automation is self-built with a different contract:
  - conditions (not filter), fieldIds (not watchFieldIds)
  - recordCondition (not recordMatchesConditions)
  - webhook route /api/webhook/{baseId}/{wfId} (not the /base//workflow/ form)
  - scheduledTime backend honors only timing.type + timing.interval
  The official CLI targets the upstream contract and 404s/misconfigures here.
  Table/field/record/view APIs ARE upstream-compatible, but automation is not.

inkreach CLI (packages/inkreach-cli/):
  - Node.js + commander, zero extra runtime deps
  - Uses node:http (NOT fetch — Node 24 fetch drops Authorization header)
  - Commands: auth (login/status/token), base/table/field/record/view,
    workflow (list/get/create/update/delete/activate/deactivate/test),
    trigger set (8 types, builds correct config keys), action add/update/delete
    (6 types), secret, runs (list/get/rerun), fire (button-click/webhook), sql
  - --json mode for agent parsing; GET->modify->PUT whole-graph for node edits
  - Verified end-to-end: create workflow -> set webhook trigger -> add script
    action -> activate -> fire webhook -> runs list (succeeded)

inkreach-teable-ops skill (.agents/skills/inkreach-teable-ops/):
  - SKILL.md entry: module map, routing rules, 11 key constraints (the real
    contract keys), execution rules, error recovery
  - guides/: cli-reference, automation-guide, tables-fields, instance-access,
    development (build/restart, known gotchas)
  - api-reference/: trigger-types (8 triggers, exact config), action-types
    (6 actions, sandbox globals), field-types (21 types incl. Button)
  - metadata.json + install.sh
2026-08-05 02:04:39 +08:00

463 lines
17 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.baseResourceCommands = void 0;
/**
* `inkreach base|table|field|record|view` — resource CRUD commands.
*
* These endpoints are compatible with official teable open-api.
*/
const commander_1 = require("commander");
const ctx_1 = require("../lib/ctx");
const output_1 = require("../lib/output");
function baseResourceCommands() {
return [baseCmd(), tableCmd(), fieldCmd(), recordCmd(), viewCmd()];
}
exports.baseResourceCommands = baseResourceCommands;
// ===================== base =====================
function baseCmd() {
const cmd = new commander_1.Command('base');
cmd.description('Manage bases (database containers)');
cmd
.command('list')
.description('List all accessible bases')
.action((0, ctx_1.run)(async (ctx) => {
const list = (await ctx.client.listBases());
if (ctx.json)
(0, output_1.printJson)(list);
else {
if (!list.length) {
process.stdout.write('No bases found.\n');
return;
}
(0, output_1.printTable)([
{ key: 'id', header: 'ID' },
{ key: 'name', header: 'NAME' },
], list.map((b) => ({ id: (0, output_1.truncate)(b.id), name: (0, output_1.truncate)(b.name, 30) })));
}
}));
cmd
.command('get <baseId>')
.description('Get base details')
.action((0, ctx_1.run)(async (ctx, args) => {
const baseId = args[0];
const base = await ctx.client.getBase(baseId);
if (ctx.json)
(0, output_1.printJson)(base);
else {
const b = base;
(0, output_1.printKv)(Object.entries(b).map(([k, v]) => [k, v]));
}
}));
return cmd;
}
// ===================== table =====================
function tableCmd() {
const cmd = new commander_1.Command('table');
cmd.description('Manage tables within a base');
cmd
.command('list')
.description('List tables in a base')
.option('--base-id <id>', 'Base id (overrides default)')
.action((0, ctx_1.run)(async (ctx, _args, o) => {
const opts = o;
const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId);
const list = (await ctx.client.listTables(baseId));
if (ctx.json)
(0, output_1.printJson)(list);
else {
if (!list.length) {
process.stdout.write('No tables found.\n');
return;
}
(0, output_1.printTable)([
{ key: 'id', header: 'ID' },
{ key: 'name', header: 'NAME' },
{ key: 'dbTableName', header: 'DB TABLE' },
], list.map((t) => ({
id: (0, output_1.truncate)(t.id),
name: (0, output_1.truncate)(t.name, 30),
dbTableName: (0, output_1.truncate)(t.dbTableName ?? ''),
})));
}
}));
cmd
.command('get <tableId>')
.description('Get table details')
.action((0, ctx_1.run)(async (ctx, args) => {
const tableId = args[0];
const table = await ctx.client.getTable(tableId);
if (ctx.json)
(0, output_1.printJson)(table);
else {
const t = table;
(0, output_1.printKv)(Object.entries(t).map(([k, v]) => [k, v]));
}
}));
cmd
.command('create')
.description('Create a new table')
.requiredOption('--name <name>', 'Table name')
.option('--base-id <id>', 'Base id (overrides default)')
.option('--fields <json>', 'Field definitions JSON array, e.g. \'[{"name":"Title","type":"singleLineText"}]\'')
.action((0, ctx_1.run)(async (ctx, _args, o) => {
const opts = o;
const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId);
const body = { name: opts.name };
if (opts.fields)
body.fields = JSON.parse(opts.fields);
const table = await ctx.client.createTable(baseId, body);
if (ctx.json)
(0, output_1.printJson)(table);
else {
const t = table;
(0, output_1.printKv)([['id', t.id], ['name', t.name]]);
}
}));
cmd
.command('update <tableId>')
.description('Update table (rename)')
.requiredOption('--name <name>', 'New table name')
.action((0, ctx_1.run)(async (ctx, args, o) => {
const tableId = args[0];
const opts = o;
const table = await ctx.client.updateTable(tableId, { name: opts.name });
if (ctx.json)
(0, output_1.printJson)(table);
else
process.stdout.write(`✓ Table ${tableId} updated\n`);
}));
cmd
.command('delete <tableId>')
.description('Delete a table')
.action((0, ctx_1.run)(async (ctx, args) => {
const tableId = args[0];
await ctx.client.deleteTable(tableId);
if (ctx.json)
(0, output_1.printJson)({ ok: true, id: tableId });
else
process.stdout.write(`✓ Table ${tableId} deleted\n`);
}));
return cmd;
}
// ===================== field =====================
function fieldCmd() {
const cmd = new commander_1.Command('field');
cmd.description('Manage fields (columns) in a table');
cmd
.command('list <tableId>')
.description('List fields in a table')
.action((0, ctx_1.run)(async (ctx, args) => {
const tableId = args[0];
const list = (await ctx.client.listFields(tableId));
if (ctx.json)
(0, output_1.printJson)(list);
else {
if (!list.length) {
process.stdout.write('No fields found.\n');
return;
}
(0, output_1.printTable)([
{ key: 'id', header: 'ID' },
{ key: 'name', header: 'NAME' },
{ key: 'type', header: 'TYPE' },
], list.map((f) => ({
id: (0, output_1.truncate)(f.id),
name: (0, output_1.truncate)(f.name, 30),
type: f.type,
})));
}
}));
cmd
.command('get <fieldId>')
.description('Get field details')
.action((0, ctx_1.run)(async (ctx, args) => {
const fieldId = args[0];
const field = await ctx.client.getField(fieldId);
if (ctx.json)
(0, output_1.printJson)(field);
else {
const f = field;
(0, output_1.printKv)(Object.entries(f).map(([k, v]) => [k, v]));
}
}));
cmd
.command('create')
.description('Create a new field')
.requiredOption('--table-id <id>', 'Table id')
.requiredOption('--name <name>', 'Field name')
.requiredOption('--type <type>', 'Field type (singleLineText, number, singleSelect, link, formula, etc.)')
.option('--options <json>', 'Field options JSON (type-specific)')
.action((0, ctx_1.run)(async (ctx, _args, o) => {
const opts = o;
const body = { name: opts.name, type: opts.type };
if (opts.options)
body.options = JSON.parse(opts.options);
const field = await ctx.client.createField(opts.tableId, body);
if (ctx.json)
(0, output_1.printJson)(field);
else {
const f = field;
(0, output_1.printKv)([['id', f.id], ['name', f.name], ['type', f.type]]);
}
}));
cmd
.command('update <fieldId>')
.description('Update a field')
.option('--name <name>', 'New field name')
.option('--options <json>', 'New field options JSON')
.action((0, ctx_1.run)(async (ctx, args, o) => {
const fieldId = args[0];
const opts = o;
const body = {};
if (opts.name)
body.name = opts.name;
if (opts.options)
body.options = JSON.parse(opts.options);
const field = await ctx.client.updateField(fieldId, body);
if (ctx.json)
(0, output_1.printJson)(field);
else
process.stdout.write(`✓ Field ${fieldId} updated\n`);
}));
cmd
.command('delete <fieldId>')
.description('Delete a field')
.action((0, ctx_1.run)(async (ctx, args) => {
const fieldId = args[0];
await ctx.client.deleteField(fieldId);
if (ctx.json)
(0, output_1.printJson)({ ok: true, id: fieldId });
else
process.stdout.write(`✓ Field ${fieldId} deleted\n`);
}));
return cmd;
}
// ===================== record =====================
function recordCmd() {
const cmd = new commander_1.Command('record');
cmd.description('Manage records (rows) in a table');
cmd
.command('get')
.description('Query records with filters, pagination, and search')
.requiredOption('--table-id <id>', 'Table id')
.option('--filter <json>', 'Filter conditions JSON')
.option('--projection <json>', 'Field projection JSON array')
.option('--page-size <n>', 'Records per page')
.option('--page <n>', 'Page number (1-based)')
.option('--search <kw>', 'Search keyword')
.option('--base-id <id>', 'Base id (overrides default)')
.action((0, ctx_1.run)(async (ctx, _args, o) => {
const opts = o;
// validate baseId is present (even though getRecords only needs tableId,
// we follow the convention that base-scoped commands require baseId config)
(0, ctx_1.requireBaseId)(ctx, opts.baseId);
const query = {};
if (opts.filter)
query.filter = JSON.parse(opts.filter);
if (opts.projection)
query.projection = JSON.parse(opts.projection);
if (opts.search)
query.search = opts.search;
const page = opts.page ? parseInt(opts.page, 10) : undefined;
const pageSize = opts.pageSize ? parseInt(opts.pageSize, 10) : undefined;
if (pageSize)
query.take = pageSize;
if (page) {
const take = pageSize || 100;
query.skip = (page - 1) * take;
if (!pageSize)
query.take = take;
}
const result = (await ctx.client.getRecords(opts.tableId, query));
if (ctx.json) {
(0, output_1.printJson)(result);
}
else {
if (!result.records?.length) {
process.stdout.write('No records found.\n');
return;
}
const firstFields = result.records[0].fields || {};
const fieldKeys = Object.keys(firstFields);
const columns = fieldKeys.map((k) => ({ key: k, header: k }));
(0, output_1.printTable)(columns, result.records.map((rec) => {
const f = rec.fields || {};
const row = { id: (0, output_1.truncate)(rec.id) };
for (const k of fieldKeys) {
row[k] = (0, output_1.truncate)(f[k], 30);
}
return row;
}));
if (result.total !== undefined) {
process.stdout.write(`\n${result.records.length} of ${result.total} record(s).\n`);
}
else {
process.stdout.write(`\n${result.records.length} record(s).\n`);
}
}
}));
cmd
.command('get-one <recordId>')
.description('Get a single record by id')
.action((0, ctx_1.run)(async (ctx, args) => {
const recordId = args[0];
const record = await ctx.client.getRecord(recordId);
if (ctx.json)
(0, output_1.printJson)(record);
else {
const rec = record;
const fields = rec.fields || {};
(0, output_1.printKv)([
['id', rec.id],
...Object.entries(fields).map(([k, v]) => [k, v]),
]);
}
}));
cmd
.command('create')
.description('Create record(s). Use --fields for a single record, --records for bulk.')
.requiredOption('--table-id <id>', 'Table id')
.option('--fields <json>', 'Fields object JSON, e.g. \'{"Title":"hello"}\'')
.option('--records <json>', 'Records array JSON for bulk, e.g. \'[{"fields":{"Title":"a"}}]\'')
.action((0, ctx_1.run)(async (ctx, _args, o) => {
const opts = o;
let body;
if (opts.records) {
body = { records: JSON.parse(opts.records) };
}
else if (opts.fields) {
body = { fields: JSON.parse(opts.fields) };
}
else {
throw Object.assign(new Error('Provide --fields or --records'), {
code: 'MISSING_FIELDS',
});
}
const result = await ctx.client.createRecord(opts.tableId, body);
if (ctx.json)
(0, output_1.printJson)(result);
else
process.stdout.write(`✓ Record(s) created\n`);
}));
cmd
.command('update')
.description('Batch update records')
.requiredOption('--table-id <id>', 'Table id')
.requiredOption('--records <json>', 'Records JSON, e.g. \'[{"id":"recX","fields":{"Title":"b"}}]\'')
.action((0, ctx_1.run)(async (ctx, _args, o) => {
const opts = o;
const body = { records: JSON.parse(opts.records) };
const result = await ctx.client.updateRecord(opts.tableId, body);
if (ctx.json)
(0, output_1.printJson)(result);
else
process.stdout.write(`✓ Records updated\n`);
}));
cmd
.command('delete')
.description('Batch delete records by ids')
.requiredOption('--table-id <id>', 'Table id')
.requiredOption('--record-ids <csv>', 'Record ids (comma-separated)')
.action((0, ctx_1.run)(async (ctx, _args, o) => {
const opts = o;
const ids = opts.recordIds
.split(',')
.map((s) => s.trim())
.filter(Boolean);
const body = { records: ids };
const result = await ctx.client.deleteRecord(opts.tableId, body);
if (ctx.json)
(0, output_1.printJson)(result);
else
process.stdout.write(`✓ ${ids.length} record(s) deleted\n`);
}));
return cmd;
}
// ===================== view =====================
function viewCmd() {
const cmd = new commander_1.Command('view');
cmd.description('Manage views in a table');
cmd
.command('list <tableId>')
.description('List views in a table')
.action((0, ctx_1.run)(async (ctx, args) => {
const tableId = args[0];
const list = (await ctx.client.listViews(tableId));
if (ctx.json)
(0, output_1.printJson)(list);
else {
if (!list.length) {
process.stdout.write('No views found.\n');
return;
}
(0, output_1.printTable)([
{ key: 'id', header: 'ID' },
{ key: 'name', header: 'NAME' },
{ key: 'type', header: 'TYPE' },
], list.map((v) => ({
id: (0, output_1.truncate)(v.id),
name: (0, output_1.truncate)(v.name, 30),
type: v.type,
})));
}
}));
cmd
.command('get <viewId>')
.description('Get view details')
.action((0, ctx_1.run)(async (ctx, args) => {
const viewId = args[0];
const view = await ctx.client.getView(viewId);
if (ctx.json)
(0, output_1.printJson)(view);
else {
const v = view;
(0, output_1.printKv)(Object.entries(v).map(([k, val]) => [k, val]));
}
}));
cmd
.command('create')
.description('Create a new view')
.requiredOption('--table-id <id>', 'Table id')
.requiredOption('--name <name>', 'View name')
.option('--type <type>', 'View type (grid, calendar, kanban, gallery, form)', 'grid')
.action((0, ctx_1.run)(async (ctx, _args, o) => {
const opts = o;
const body = { name: opts.name, type: opts.type };
const view = await ctx.client.createView(opts.tableId, body);
if (ctx.json)
(0, output_1.printJson)(view);
else {
const v = view;
(0, output_1.printKv)([['id', v.id], ['name', v.name], ['type', v.type]]);
}
}));
cmd
.command('update <viewId>')
.description('Update a view (rename)')
.option('--name <name>', 'New view name')
.action((0, ctx_1.run)(async (ctx, args, o) => {
const viewId = args[0];
const opts = o;
const body = {};
if (opts.name)
body.name = opts.name;
const view = await ctx.client.updateView(viewId, body);
if (ctx.json)
(0, output_1.printJson)(view);
else
process.stdout.write(`✓ View ${viewId} updated\n`);
}));
cmd
.command('delete <viewId>')
.description('Delete a view')
.action((0, ctx_1.run)(async (ctx, args) => {
const viewId = args[0];
await ctx.client.deleteView(viewId);
if (ctx.json)
(0, output_1.printJson)({ ok: true, id: viewId });
else
process.stdout.write(`✓ View ${viewId} deleted\n`);
}));
return cmd;
}