From 2a186c9accd92de5e4b449619dbac13b56aa7a16 Mon Sep 17 00:00:00 2001 From: yeuimu <2197651308@qq.com> Date: Wed, 23 Sep 2026 04:01:22 +0000 Subject: [PATCH] chore: standalone repo bootstrap after split from inkreach-teable monorepo - untrack dist/, add .gitignore, add pnpm-lock.yaml - inline tsconfig.base.json options (no monorepo extends) - install.sh: CLI now at repo root; install skill to ~/.zcode/skills (user scope) - fix stale paths in skill docs; 7 actions in help text; README --- .../api-reference/field-types.md | 2 +- .../inkreach-teable/guides/development.md | 5 + .../skills/inkreach-teable/scripts/install.sh | 42 +- .gitignore | 3 + README.md | 50 + dist/bin.js | 46 - dist/commands/auth.js | 139 --- dist/commands/extras.js | 288 ----- dist/commands/plugin.js | 273 ----- dist/commands/resources.js | 739 ------------ dist/commands/subscription.js | 323 ----- dist/commands/workflow.js | 636 ---------- dist/lib/client.js | 311 ----- dist/lib/config.js | 73 -- dist/lib/ctx.js | 72 -- dist/lib/field-display.js | 136 --- dist/lib/field-normalize.js | 289 ----- dist/lib/output.js | 34 - dist/lib/request.js | 124 -- package.json | 10 +- pnpm-lock.yaml | 1069 +++++++++++++++++ src/bin.ts | 2 +- tsconfig.build.json | 10 +- 23 files changed, 1174 insertions(+), 3502 deletions(-) create mode 100644 .gitignore create mode 100644 README.md delete mode 100755 dist/bin.js delete mode 100644 dist/commands/auth.js delete mode 100644 dist/commands/extras.js delete mode 100644 dist/commands/plugin.js delete mode 100644 dist/commands/resources.js delete mode 100644 dist/commands/subscription.js delete mode 100644 dist/commands/workflow.js delete mode 100644 dist/lib/client.js delete mode 100644 dist/lib/config.js delete mode 100644 dist/lib/ctx.js delete mode 100644 dist/lib/field-display.js delete mode 100644 dist/lib/field-normalize.js delete mode 100644 dist/lib/output.js delete mode 100644 dist/lib/request.js create mode 100644 pnpm-lock.yaml diff --git a/.agents/skills/inkreach-teable/api-reference/field-types.md b/.agents/skills/inkreach-teable/api-reference/field-types.md index 3bb1a75..fa6701a 100644 --- a/.agents/skills/inkreach-teable/api-reference/field-types.md +++ b/.agents/skills/inkreach-teable/api-reference/field-types.md @@ -4,7 +4,7 @@ Complete reference of all Teable field types, aliases, option shapes, and smart > ✅ **Verified 2026-08**: aliases, string choices, compact `--fields` format, smart > inference, and name resolution (`foreignTableName`/`linkFieldName`/`lookupFieldName`) -> are implemented in the CLI (`packages/inkreach-cli/src/lib/field-normalize.ts`) +> are implemented in the CLI (`src/lib/field-normalize.ts`, repo root) > and covered by unit tests (`field-normalize.spec.ts`, 20 cases). --- diff --git a/.agents/skills/inkreach-teable/guides/development.md b/.agents/skills/inkreach-teable/guides/development.md index 78bde34..96a1753 100644 --- a/.agents/skills/inkreach-teable/guides/development.md +++ b/.agents/skills/inkreach-teable/guides/development.md @@ -1,5 +1,10 @@ # Development & Deployment +> This guide documents the **inkreach-teable application repo** +> (`ssh://git@gitea.inkreach.cc:2222/inkreachhub/inkreach-teable.git`) — the +> deployed instance this CLI operates on — not this CLI repo. Paths like +> `apps/nestjs-backend/` refer to that repo's checkout. + How to build, restart, and debug this inkreach-teable instance. Covers the dual-process architecture, known gotchas, and test infrastructure. For daily operations (tables/automation), use the `inkreach-teable` CLI instead — this guide is diff --git a/.agents/skills/inkreach-teable/scripts/install.sh b/.agents/skills/inkreach-teable/scripts/install.sh index fa0e762..1fa83bf 100644 --- a/.agents/skills/inkreach-teable/scripts/install.sh +++ b/.agents/skills/inkreach-teable/scripts/install.sh @@ -1,12 +1,18 @@ #!/bin/bash -# Install/upgrade the inkreach-teable CLI from the local monorepo source. -# Run from this skill's directory. Builds the CLI package and links it globally. +# Install/upgrade the inkreach-teable CLI + agent skill from this standalone repo. +# Run from this skill's directory (or anywhere; paths resolve from this file). +# What it does: +# 1. Builds the CLI (this repo's root IS the package). +# 2. Links the `inkreach-teable` command globally. +# 3. Installs the skill to ~/.zcode/skills/inkreach-teable (user scope — +# available in every ZCode workspace, shadows same-named workspace skills). +# Re-run this script after pulling changes to refresh both the command and the skill. set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# Resolve the project root: skill lives at /.agents/skills/inkreach-teable/ -PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" -CLI_DIR="$PROJECT_ROOT/packages/inkreach-cli" +# Resolve the repo root: skill lives at /.agents/skills/inkreach-teable/ +REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" +SKILL_DIR="$REPO_ROOT/.agents/skills/inkreach-teable" echo "=== inkreach-teable CLI installation ===" @@ -22,16 +28,16 @@ if [ "$NODE_VERSION" -lt 18 ]; then fi echo "✓ Node.js $(node -v)" -# Check CLI source exists -if [ ! -d "$CLI_DIR" ]; then - echo "ERROR: CLI source not found at $CLI_DIR" - echo " Ensure the inkreach-teable repo is checked out at $PROJECT_ROOT" +# Check repo layout +if [ ! -f "$REPO_ROOT/package.json" ] || [ ! -d "$REPO_ROOT/src" ]; then + echo "ERROR: CLI source not found at $REPO_ROOT" + echo " Ensure the inkreach-teable-cli repo is checked out at $REPO_ROOT" exit 1 fi # Build -echo "Building CLI from $CLI_DIR ..." -cd "$CLI_DIR" +echo "Building CLI from $REPO_ROOT ..." +cd "$REPO_ROOT" if command -v pnpm &> /dev/null; then pnpm install --prefer-offline 2>/dev/null || true pnpm build @@ -49,14 +55,22 @@ elif command -v npm &> /dev/null && npm link 2>/dev/null; then LINKED=1 fi # Ensure a usable wrapper exists on PATH even if global bin isn't configured. -BIN_DIR="${PNPM_GLOBAL_BIN:-$(npm bin -g 2>/dev/null):-${HOME}/.local/bin}" mkdir -p "$HOME/.local/bin" cat > "$HOME/.local/bin/inkreach-teable" < /dev/null; then echo " inkreach-teable auth token create --name my-cli" else echo "⚠ 'inkreach-teable' not on PATH. Add pnpm/npm global bin to your PATH, or run directly:" - echo " node $CLI_DIR/dist/bin.js " + echo " node $REPO_ROOT/dist/bin.js " fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f4e2c6d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +*.tsbuildinfo diff --git a/README.md b/README.md new file mode 100644 index 0000000..65916b3 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# inkreach-teable-cli + +操作 [inkreach-teable](ssh://git@gitea.inkreach.cc:2222/inkreachhub/inkreach-teable.git) +实例(Teable fork + 自建自动化引擎)的独立工具项目,包含两部分: + +| 内容 | 位置 | 说明 | +|---|---|---| +| **CLI**(命令 `inkreach-teable`) | 仓库根(`src/`,包即仓库) | 表、字段、记录、视图、自动化(8 触发器 + 7 动作)、密钥、执行历史、插件、订阅等全部 REST API 封装 | +| **Agent Skill** | `.agents/skills/inkreach-teable/` | ZCode/Claude 技能:模块地图、路由规则、API 参考、实例运维指南 | + +设计文档见 [`docs/cli-skill-design.md`](docs/cli-skill-design.md)。 + +> ⚠️ 官方 `teable` CLI 与本实例的自动化契约不兼容(`conditions` 而非 `filter`、 +> `fieldIds` 而非 `watchFieldIds` 等),务必使用 `inkreach-teable`。 + +## 安装 + +```bash +git clone ssh://git@gitea.inkreach.cc:2222/inkreachhub/inkreach-teable-cli.git +cd inkreach-teable-cli +bash .agents/skills/inkreach-teable/scripts/install.sh +``` + +安装脚本会:构建 CLI → 全局链接 `inkreach-teable` 命令 → 把 skill 安装到 +`~/.zcode/skills/inkreach-teable`(用户级,任意工作区可用)。更新后重跑一次即可。 + +## 配置 + +```bash +inkreach-teable auth login --endpoint http://localhost:3003 --token --base-id bseXXX +inkreach-teable auth status +``` + +PAT 从 Web UI(Settings → Personal Access Tokens)或 `auth token create` 获取。 + +## 开发 + +```bash +pnpm install +pnpm build # tsc → dist/ +pnpm test # vitest 单测(field-normalize / field-display / resources) +pnpm typecheck +``` + +## 来源 + +2026-09-23 从 inkreach-teable monorepo 拆分独立(原路径 `packages/inkreach-cli/` + +`.agents/skills/inkreach-teable/`),git 历史完整保留。应用本体的开发/部署文档见 +skill 内 [development.md](.agents/skills/inkreach-teable/guides/development.md)(描述的是 +inkreach-teable 应用仓库,不是本仓库)。 diff --git a/dist/bin.js b/dist/bin.js deleted file mode 100755 index 2c1520e..0000000 --- a/dist/bin.js +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env node -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -/** - * inkreach-teable — CLI for operating inkreach-teable. - * - * Entry point. Sets up the global commander program and mounts command groups. - */ -const commander_1 = require("commander"); -const package_json_1 = require("../package.json"); -const plugin_1 = require("./commands/plugin"); -const auth_1 = require("./commands/auth"); -const workflow_1 = require("./commands/workflow"); -const resources_1 = require("./commands/resources"); -const extras_1 = require("./commands/extras"); -const subscription_1 = require("./commands/subscription"); -const program = new commander_1.Command(); -program - .name('inkreach-teable') - .description('Operate inkreach-teable: tables, fields, records, views, and the self-built automation engine (8 triggers + 6 actions).\n' + - 'Use --json for machine-readable output (agent parsing).') - .version(`inkreach-teable ${package_json_1.version}`, '-v, --version'); -// Global options (highest precedence over config file + env). -program - .option('--endpoint ', 'Backend URL (default http://localhost:3000)') - .option('--token ', 'Personal Access Token') - .option('--base-id ', 'Default base id') - .option('--json', 'Output raw JSON for agent parsing') - .option('--debug', 'Print requests/responses to stderr (verbose)'); -// Command groups -program.addCommand((0, auth_1.authCommand)()); -program.addCommand((0, plugin_1.pluginCommand)()); -for (const c of (0, resources_1.baseResourceCommands)()) - program.addCommand(c); -program.addCommand((0, workflow_1.workflowCommand)()); -program.addCommand((0, workflow_1.triggerCommand)()); -program.addCommand((0, workflow_1.actionCommand)()); -program.addCommand((0, subscription_1.subscriptionCommand)()); -program.addCommand((0, extras_1.secretCommand)()); -program.addCommand((0, extras_1.runsCommand)()); -program.addCommand((0, extras_1.fireCommand)()); -program.addCommand((0, extras_1.sqlCommand)()); -program.parseAsync(process.argv).catch((err) => { - process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}\n`); - process.exit(1); -}); diff --git a/dist/commands/auth.js b/dist/commands/auth.js deleted file mode 100644 index 2fe1881..0000000 --- a/dist/commands/auth.js +++ /dev/null @@ -1,139 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.authCommand = void 0; -/** - * `inkreach-teable auth` — authentication & config commands. - */ -const commander_1 = require("commander"); -const ctx_1 = require("../lib/ctx"); -const config_1 = require("../lib/config"); -const output_1 = require("../lib/output"); -void ctx_1.requireBaseId; -function authCommand() { - const cmd = new commander_1.Command('auth'); - cmd.description('Manage authentication and CLI config'); - cmd - .command('status') - .description('Verify the configured token by calling GET /api/user/me') - .action((0, ctx_1.run)(async (ctx) => { - const me = (await ctx.client.getMe()); - const cfg = (0, config_1.readConfigFile)(); - if (ctx.json) { - (0, output_1.printJson)({ config: cfg, user: me }); - } - else { - (0, output_1.printKv)([ - ['config path', (0, config_1.configPath)()], - ['endpoint', cfg.endpoint || config_1.DEFAULT_ENDPOINT], - ['token set', cfg.token ? `yes (${cfg.token.slice(0, 14)}…)` : 'no'], - ['base id', cfg.baseId || '(none)'], - ['', ''], - ['user id', me?.id || 'unknown'], - ['name', me?.name || 'unknown'], - ['email', me?.email || 'unknown'], - ]); - } - })); - cmd - .command('login') - .description('Configure endpoint, token, and default base id') - .option('--endpoint ', 'Backend URL (default http://localhost:3000)') - .option('--token ', 'Personal Access Token (format: teable_xxx_yyy)') - .option('--base-id ', 'Default base id for subsequent commands') - .action((0, ctx_1.run)(async (ctx, _args, o) => { - const opts = o; - // Merge global flags (--token/--endpoint/--base-id on the program) with - // this subcommand's own --endpoint/--token/--base-id options. - const g = ctx.program.opts(); - const patch = {}; - const endpoint = opts.endpoint || g.endpoint; - const tokenVal = opts.token || g.token; - const baseId = opts.baseId || g.baseId; - if (endpoint) - patch.endpoint = endpoint; - if (tokenVal) - patch.token = tokenVal; - if (baseId) - patch.baseId = baseId; - (0, config_1.writeConfigFile)(patch); - const cfg = (0, config_1.readConfigFile)(); - if (ctx.json) { - (0, output_1.printJson)({ ok: true, config: cfg }); - } - else { - process.stdout.write('✓ Configuration saved:\n'); - (0, output_1.printKv)([ - ['endpoint', cfg.endpoint || config_1.DEFAULT_ENDPOINT], - ['token', cfg.token ? `set (${cfg.token.slice(0, 14)}…)` : '(not set)'], - ['base id', cfg.baseId || '(none)'], - ['path', (0, config_1.configPath)()], - ]); - } - })); - const token = cmd.command('token').description('Manage Personal Access Tokens (PAT)'); - token - .command('list') - .description('List your access tokens') - .action((0, ctx_1.run)(async (ctx) => { - const list = (await ctx.client.listAccessTokens()); - if (ctx.json) - (0, output_1.printJson)(list); - else - (0, output_1.printTable)([ - { key: 'id', header: 'ID' }, - { key: 'name', header: 'NAME' }, - { key: 'scopes', header: 'SCOPES' }, - { key: 'expiredTime', header: 'EXPIRES' }, - ], list.map((t) => ({ ...t, scopes: (t.scopes || []).join(',') }))); - })); - token - .command('create') - .description('Create a PAT. The full token is returned ONLY ONCE.') - .requiredOption('--name ', 'Token name') - .option('--scopes ', 'Comma-separated scopes', 'automation|read,automation|create,automation|update,automation|delete,table|read,record|read,record|create,record|update') - .option('--base-ids ', 'Restrict to base ids (comma-separated). Omit for full access') - .option('--full-access', 'Grant full access') - .option('--expires ', 'Expiry date YYYY-MM-DD (default 2099-12-31)', '2099-12-31') - .action((0, ctx_1.run)(async (ctx, _args, o) => { - // commander stores --kebab-case flags camelCased (--base-ids -> - // opts.baseIds, --full-access -> opts.fullAccess); accept both. - const opts = o; - const scopes = opts.scopes.split(',').map((s) => s.trim()).filter(Boolean); - const rawBaseIds = opts['base-ids'] ?? opts.baseIds; - const baseIds = rawBaseIds - ? rawBaseIds.split(',').map((s) => s.trim()) - : undefined; - const fullAccess = Boolean(opts['full-access'] ?? opts.fullAccess); - const created = (await ctx.client.createAccessToken({ - name: opts.name, - scopes, - baseIds, - hasFullAccess: fullAccess, - expiredTime: opts.expires, - })); - if (ctx.json) - (0, output_1.printJson)(created); - else { - (0, output_1.printKv)([ - ['id', created.id], - ['token', created.token], - ['', ''], - ['note', 'Save this token now — it cannot be retrieved again.'], - ['use', 'inkreach-teable auth login --token '], - ]); - } - })); - token - .command('delete ') - .description('Revoke an access token') - .action((0, ctx_1.run)(async (ctx, args) => { - const id = args[0]; - await ctx.client.deleteAccessToken(id); - if (ctx.json) - (0, output_1.printJson)({ ok: true, id }); - else - process.stdout.write(`✓ Token ${id} revoked\n`); - })); - return cmd; -} -exports.authCommand = authCommand; diff --git a/dist/commands/extras.js b/dist/commands/extras.js deleted file mode 100644 index 7d688c2..0000000 --- a/dist/commands/extras.js +++ /dev/null @@ -1,288 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.sqlCommand = exports.fireCommand = exports.runsCommand = exports.secretCommand = void 0; -/** - * `inkreach-teable secret` — manage workflow automation secrets. - * `inkreach-teable runs` — execution history. - * `inkreach-teable fire` — manual trigger testing (button-click / webhook). - * `inkreach-teable sql` — read-only SQL queries. - */ -const commander_1 = require("commander"); -const ctx_1 = require("../lib/ctx"); -const output_1 = require("../lib/output"); -// ===================== secret ===================== -function secretCommand() { - const cmd = new commander_1.Command('secret'); - cmd.description('Manage automation script secrets'); - cmd - .command('list ') - .description('List secrets for a workflow. Values are never returned.') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const workflowId = args[0]; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - const list = (await ctx.client.listSecrets(baseId, workflowId)); - if (ctx.json) - (0, output_1.printJson)(list); - else { - if (!list.length) { - process.stdout.write('No secrets found.\n'); - return; - } - (0, output_1.printTable)([ - { key: 'id', header: 'ID' }, - { key: 'name', header: 'NAME' }, - { key: 'description', header: 'DESCRIPTION' }, - ], list.map((s) => ({ - id: s.id, - name: (0, output_1.truncate)(s.name, 30), - description: (0, output_1.truncate)(s.description, 50), - }))); - } - })); - cmd - .command('set ') - .description('Create a new secret') - .requiredOption('--name ', 'Secret name') - .requiredOption('--value ', 'Secret value') - .option('--description ', 'Secret description') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const workflowId = args[0]; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - const created = await ctx.client.createSecret(baseId, workflowId, { - name: opts.name, - value: opts.value, - description: opts.description, - }); - const s = created; - if (ctx.json) - (0, output_1.printJson)(created); - else { - (0, output_1.printKv)([ - ['id', s.id], - ['name', s.name], - ['', ''], - ['note', 'Secret value is never returned. Update it if needed.'], - ]); - } - })); - cmd - .command('update ') - .description('Update a secret') - .option('--name ', 'New name') - .option('--value ', 'New value') - .option('--description ', 'New description') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const [workflowId, secretId] = args; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - await ctx.client.updateSecret(baseId, workflowId, secretId, { - name: opts.name, - value: opts.value, - description: opts.description, - }); - if (ctx.json) - (0, output_1.printJson)({ ok: true, id: secretId }); - else - process.stdout.write(`✓ Secret ${secretId} updated\n`); - })); - cmd - .command('delete ') - .description('Delete a secret') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const [workflowId, secretId] = args; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - await ctx.client.deleteSecret(baseId, workflowId, secretId); - if (ctx.json) - (0, output_1.printJson)({ ok: true, id: secretId }); - else - process.stdout.write(`✓ Secret ${secretId} deleted\n`); - })); - return cmd; -} -exports.secretCommand = secretCommand; -// ===================== runs ===================== -function runsCommand() { - const cmd = new commander_1.Command('runs'); - cmd.description('Execution history for automations'); - cmd - .command('list ') - .description('List execution history') - .option('--status ', 'Filter by status: failed, succeeded, or running') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const workflowId = args[0]; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - let list = (await ctx.client.listExecutions(baseId, workflowId)); - if (opts.status) { - list = list.filter((e) => e.status === opts.status); - } - if (ctx.json) - (0, output_1.printJson)(list); - else { - if (!list.length) { - process.stdout.write('No executions found.\n'); - return; - } - (0, output_1.printTable)([ - { key: 'id', header: 'ID' }, - { key: 'status', header: 'STATUS' }, - { key: 'triggerType', header: 'TRIGGER' }, - { key: 'startedAt', header: 'STARTED' }, - { key: 'finishedAt', header: 'FINISHED' }, - ], list.map((e) => ({ - id: e.id, - status: e.status, - triggerType: e.triggerType || '', - startedAt: (0, output_1.truncate)(e.startedAt, 25), - finishedAt: (0, output_1.truncate)(e.finishedAt, 25), - }))); - } - })); - cmd - .command('get ') - .description('Get execution detail with node results') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const [workflowId, executionId] = args; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - const exec = await ctx.client.getExecution(baseId, workflowId, executionId); - const ex = exec; - if (ctx.json) - (0, output_1.printJson)(exec); - else { - (0, output_1.printKv)([ - ['id', ex.id], - ['status', ex.status], - ['trigger', ex.triggerType || ''], - ['started', ex.startedAt || ''], - ['finished', ex.finishedAt || ''], - ]); - if (ex.nodeResults?.length) { - process.stdout.write('\nNODE RESULTS:\n'); - (0, output_1.printTable)([ - { key: 'nodeId', header: 'NODE' }, - { key: 'status', header: 'STATUS' }, - { key: 'error', header: 'ERROR' }, - ], ex.nodeResults.map((r) => ({ - nodeId: r.nodeId, - status: r.status, - error: (0, output_1.truncate)(r.error, 50), - }))); - } - } - })); - cmd - .command('rerun ') - .description('Rerun a failed execution') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const [workflowId, executionId] = args; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - const res = (await ctx.client.rerunExecution(baseId, workflowId, executionId)); - if (ctx.json) - (0, output_1.printJson)(res); - else { - const pairs = [ - ['executionId', executionId], - ['status', 'rerun requested'], - ]; - if (res.id) - pairs.push(['newExecutionId', res.id]); - (0, output_1.printKv)(pairs); - } - })); - return cmd; -} -exports.runsCommand = runsCommand; -// ===================== fire ===================== -function fireCommand() { - const cmd = new commander_1.Command('fire'); - cmd.description('Manually fire triggers for testing'); - cmd - .command('button-click ') - .description('Simulate a button field click') - .action((0, ctx_1.run)(async (ctx, args) => { - const [tableId, recordId, fieldId] = args; - const res = await ctx.client.fireButtonClick(tableId, recordId, fieldId); - if (ctx.json) - (0, output_1.printJson)(res); - else { - (0, output_1.printKv)([ - ['runId', res.runId], - ['tableId', res.tableId], - ['fieldId', res.fieldId], - ]); - } - })); - cmd - .command('webhook ') - .description('Simulate a webhook call') - .option('--body ', 'JSON body to send') - .option('--method ', 'HTTP method (informational only)', 'POST') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const [baseId, workflowId] = args; - const opts = o; - const body = opts.body ? JSON.parse(opts.body) : undefined; - const res = await ctx.client.fireWebhook(baseId, workflowId, body); - if (ctx.json) - (0, output_1.printJson)(res); - else { - process.stdout.write(`✓ Webhook fired for workflow ${workflowId}\n`); - if (typeof res === 'object' && res !== null) { - (0, output_1.printJson)(res); - } - } - })); - return cmd; -} -exports.fireCommand = fireCommand; -// ===================== sql ===================== -function sqlCommand() { - const cmd = new commander_1.Command('sql'); - cmd.description('Execute a read-only SQL query (SELECT only)'); - cmd.option('--base-id ', 'Base id (overrides default)'); - cmd.argument('', 'SQL SELECT statement'); - cmd.action((0, ctx_1.run)(async (ctx, args, o) => { - const sql = args[0]; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - const rows = (await ctx.client.sql(baseId, sql)); - if (ctx.json) { - (0, output_1.printJson)(rows); - } - else { - if (!rows.length) { - process.stdout.write('No rows returned.\n'); - return; - } - const first = rows[0]; - if (typeof first === 'object' && first !== null && !Array.isArray(first)) { - const keys = Object.keys(first); - (0, output_1.printTable)(keys.map((k) => ({ key: k, header: k })), rows.map((r) => { - const obj = r; - const row = {}; - for (const k of keys) { - row[k] = (0, output_1.truncate)(obj[k], 40); - } - return row; - })); - process.stdout.write(`\n${rows.length} row(s).\n`); - } - else { - rows.forEach((r) => process.stdout.write(JSON.stringify(r) + '\n')); - } - } - })); - return cmd; -} -exports.sqlCommand = sqlCommand; diff --git a/dist/commands/plugin.js b/dist/commands/plugin.js deleted file mode 100644 index a076b9f..0000000 --- a/dist/commands/plugin.js +++ /dev/null @@ -1,273 +0,0 @@ -"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; -// ---- 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, create, install, dev)'); - 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 ', 'Plugin URL') - .requiredOption('--name ', 'Plugin name') - .option('--description ', 'Plugin description') - .option('--logo ', 'Logo URL') - .option('--positions ', '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 ', 'Plugin ID') - .option('--table ', '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 ', 'Plugin ID') - .option('--table ', '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`); - })); - // ---- 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/commands/resources.js b/dist/commands/resources.js deleted file mode 100644 index d24d86e..0000000 --- a/dist/commands/resources.js +++ /dev/null @@ -1,739 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.presetConfigFromOptions = exports.baseResourceCommands = void 0; -/** - * `inkreach-teable 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"); -const client_1 = require("../lib/client"); -const field_normalize_1 = require("../lib/field-normalize"); -const field_display_1 = require("../lib/field-display"); -/** - * Fetch a field by ID or display name. A direct ID hit needs no extra call; - * a 400/404 miss (the API rejects non-ID paths) falls back to resolving the - * arg against the table's field list. Unresolvable input rethrows the - * original error so the user sees the underlying cause. - */ -async function getFieldFlexible(client, tableId, fieldIdOrName) { - try { - return (await client.getField(tableId, fieldIdOrName)); - } - catch (err) { - if (err instanceof client_1.ApiError && (err.status === 400 || err.status === 404)) { - const list = (await client.listFields(tableId)); - const hit = (0, field_normalize_1.findFieldByNameOrId)(list, fieldIdOrName); - if (hit) - return (await client.getField(tableId, hit.id)); - } - throw err; - } -} -/** fieldId → display-name map of a table, to humanize visibilityFilter references. */ -async function fieldNameMap(client, tableId) { - const list = (await client.listFields(tableId)); - return Object.fromEntries(list.map((f) => [f.id, f.name])); -} -function baseResourceCommands() { - return [baseCmd(), tableCmd(), fieldCmd(), recordCmd(), viewCmd(), presetCmd()]; -} -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 ') - .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 ', '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 ') - .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 ', 'Table name') - .option('--base-id ', 'Base id (overrides default)') - .option('--fields ', '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 = (0, field_normalize_1.normalizeFieldRoArray)(JSON.parse(opts.fields)); - // Resolve foreignTableName references against existing tables in the base. - const fields = body.fields; - const tables = await ctx.client.listTables(baseId).catch(() => []); - for (const f of fields) { - const options = f?.options; - if (options && typeof options.foreignTableName === 'string' && !options.foreignTableId) { - const t = (0, field_normalize_1.findTableByNameOrId)(tables || [], options.foreignTableName); - if (t) - options.foreignTableId = t.id; - } - } - } - 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 ') - .description('Update table (rename)') - .requiredOption('--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 ') - .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 ') - .description('List fields in a table') - .option('--with-options', 'Add an OPTIONS summary column and expand select choices with their visibility conditions') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const opts = o; - 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; - } - if (opts.withOptions) { - const nameMap = Object.fromEntries(list.map((f) => [String(f.id), String(f.name)])); - (0, output_1.printTable)([ - { key: 'id', header: 'ID' }, - { key: 'name', header: 'NAME' }, - { key: 'type', header: 'TYPE' }, - { key: 'options', header: 'OPTIONS' }, - ], list.map((f) => ({ - id: (0, output_1.truncate)(f.id), - name: (0, output_1.truncate)(f.name, 30), - type: f.type, - options: (0, field_display_1.summarizeFieldOptions)(f), - }))); - const selectFields = list.filter((f) => Array.isArray(f.options?.choices)); - for (const f of selectFields) { - const rows = (0, field_display_1.choiceRows)(f, nameMap); - const conditional = rows.filter((r) => r.when !== '(always)').length; - process.stdout.write(`\n◇ ${f.name} (${f.id}) — ${rows.length} choices · ${conditional} conditional\n`); - for (const r of rows) { - const color = r.color ? ` [${r.color}]` : ''; - process.stdout.write(` ${r.name}${color}\n`); - if (r.when !== '(always)') { - process.stdout.write(` when: ${r.when}\n`); - } - } - } - } - else { - (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 ') - .description('Get field details by id or name (full options; select choices rendered with visibility conditions)') - .action((0, ctx_1.run)(async (ctx, args) => { - const [tableId, fieldIdOrName] = args; - const field = await getFieldFlexible(ctx.client, tableId, fieldIdOrName); - if (ctx.json) - (0, output_1.printJson)(field); - else { - const choices = field.options?.choices; - const names = Array.isArray(choices) - ? await fieldNameMap(ctx.client, tableId) - : undefined; - process.stdout.write((0, field_display_1.formatFieldDetail)(field, names) + '\n'); - } - })); - cmd - .command('create') - .description('Create a new field') - .requiredOption('--table-id ', 'Table id') - .requiredOption('--name ', 'Field name') - .option('--type ', 'Field type — full value (singleLineText, singleSelect, link…) or alias (text, sel, num…). Omitted: inferred from --options shape / --name') - .option('--options ', 'Field options JSON (type-specific; select choices accept plain strings)') - .option('--description ', 'Field description') - .option('--lookup-options ', 'Lookup options JSON (names like linkFieldName/lookupFieldName are resolved to IDs)') - .option('--is-lookup', 'Mark the field as a lookup field') - .action((0, ctx_1.run)(async (ctx, _args, o) => { - const opts = o; - const body = { name: opts.name }; - if (opts.options) - body.options = JSON.parse(opts.options); - if (opts.description) - body.description = opts.description; - if (opts.lookupOptions) - body.lookupOptions = JSON.parse(opts.lookupOptions); - if (opts.isLookup) - body.isLookup = true; - body.type = opts.type - ? (0, field_normalize_1.resolveFieldType)(opts.type) - : (0, field_normalize_1.inferFieldType)(opts.name, body.options); - (0, field_normalize_1.normalizeFieldRo)(body); - const errors = await (0, field_normalize_1.resolveFieldReferences)(body, { - listTables: (baseId) => ctx.client.listTables(baseId), - listFields: (tableId) => ctx.client.listFields(tableId), - baseId: ctx.baseId, - tableId: opts.tableId, - }); - if (errors.length) - throw new Error(errors.join('; ')); - 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 ') - .description('Update a field (by id or name). --name renames (PATCH); --options rewrites options via the convert API (the update API does not accept options)') - .option('--name ', 'New field name') - .option('--options ', 'New field options JSON (written via convert API)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const [tableId, fieldIdOrName] = args; - const opts = o; - if (!opts.name && !opts.options) { - throw new Error('Nothing to update: pass --name and/or --options'); - } - if (opts.name && !opts.options) { - const current = (await getFieldFlexible(ctx.client, tableId, fieldIdOrName)); - const field = await ctx.client.updateField(tableId, current.id, { name: opts.name }); - if (ctx.json) - (0, output_1.printJson)(field); - else - process.stdout.write(`✓ Field ${current.id} renamed\n`); - return; - } - // Options (choices / visibilityFilter / ...) must go through convert. - // Fetch the current field to reuse its type; merge name if given. - const current = (await getFieldFlexible(ctx.client, tableId, fieldIdOrName)); - const body = { - type: current.type, - options: JSON.parse(opts.options), - }; - if (opts.name) - body.name = opts.name; - (0, field_normalize_1.normalizeFieldRo)(body); - const field = await ctx.client.convertField(tableId, current.id, body); - if (ctx.json) - (0, output_1.printJson)(field); - else - process.stdout.write(`✓ Field ${current.id} options updated (via convert)\n`); - })); - cmd - .command('visible-choices ') - .description('Evaluate select-choice visibility for a record (respects choice visibilityFilter dependencies)') - .action((0, ctx_1.run)(async (ctx, args) => { - const [tableId, recordId, fieldIdOrName] = args; - const field = await getFieldFlexible(ctx.client, tableId, fieldIdOrName); - const res = await ctx.client.getVisibleChoices(tableId, recordId, String(field.id)); - if (ctx.json) - (0, output_1.printJson)(res); - else { - (0, output_1.printTable)([ - { key: 'id', header: 'ID' }, - { key: 'name', header: 'NAME' }, - { key: 'color', header: 'COLOR' }, - ], res.visibleChoices.map((c) => ({ ...c, color: c.color ?? '' }))); - } - })); - cmd - .command('delete ') - .description('Delete a field') - .action((0, ctx_1.run)(async (ctx, args) => { - const [tableId, fieldId] = args; - await ctx.client.deleteField(tableId, 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 ', 'Table id') - .option('--filter ', 'Filter conditions JSON') - .option('--projection ', 'Field projection JSON array') - .option('--page-size ', 'Records per page') - .option('--page ', 'Page number (1-based)') - .option('--search ', 'Search keyword') - .option('--base-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 ') - .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 ', 'Table id') - .option('--fields ', 'Fields object JSON, e.g. \'{"Title":"hello"}\'') - .option('--records ', '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 ', 'Table id') - .requiredOption('--records ', '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 ', 'Table id') - .requiredOption('--record-ids ', '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 result = await ctx.client.deleteRecords(opts.tableId, ids); - 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 ') - .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 ') - .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 ', 'Table id') - .requiredOption('--name ', 'View name') - .option('--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 ') - .description('Update a view (rename)') - .option('--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 ') - .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; -} -// ===================== view preset ===================== -/** Parse a --filter/--sort/... JSON option; empty/absent → undefined. */ -function parseJsonOption(value, optionName) { - if (value == null || value === '') - return undefined; - try { - return JSON.parse(value); - } - catch (err) { - throw new Error(`Invalid JSON for ${optionName}: ${err.message}`); - } -} -/** Collect preset config keys from options; absent keys stay out of the config. */ -function presetConfigFromOptions(opts) { - const config = {}; - const entries = [ - ['filter', '--filter'], - ['sort', '--sort'], - ['group', '--group'], - ['options', '--options'], - ['columnMeta', '--column-meta'], - ]; - for (const [key, optionName] of entries) { - const parsed = parseJsonOption(opts[key], optionName); - if (parsed !== undefined) - config[key] = parsed; - } - return config; -} -exports.presetConfigFromOptions = presetConfigFromOptions; -function presetCmd() { - const cmd = new commander_1.Command('preset'); - cmd.description('Manage preset view configurations (named filter/sort/group bundles, shared with all collaborators)'); - cmd - .command('list ') - .description('List view presets of a table (pass --view-id to scope to one view)') - .option('--view-id ', 'Only presets bound to this view') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const tableId = args[0]; - const opts = o; - const list = (await ctx.client.listViewPresets(tableId, opts.viewId)); - if (ctx.json) - (0, output_1.printJson)(list); - else { - if (!list.length) { - process.stdout.write('No view presets found.\n'); - return; - } - (0, output_1.printTable)([ - { key: 'id', header: 'ID' }, - { key: 'name', header: 'NAME' }, - { key: 'config', header: 'CONFIG' }, - ], list.map((p) => { - const config = (p.config ?? {}); - const keys = Object.keys(config).filter((k) => config[k] != null); - return { - id: (0, output_1.truncate)(p.id), - name: (0, output_1.truncate)(p.name, 30), - config: keys.join(',') || '(empty)', - }; - })); - } - })); - cmd - .command('create') - .description('Create a view preset bound to one view (keys not passed stay unset — they keep the view values on apply)') - .requiredOption('--table-id ', 'Table id') - .requiredOption('--view-id ', 'View id the preset belongs to') - .requiredOption('--name ', 'Preset name') - .option('--description ', 'Preset description') - .option('--filter ', 'Filter config, e.g. \'{"conjunction":"and","filterSet":[{"fieldId":"fldX","operator":"is","value":"Pending"}]}\'') - .option('--sort ', 'Sort config, e.g. \'{"sortObjs":[{"fieldId":"fldX","order":"asc"}]}\'') - .option('--group ', 'Group config (JSON array of group items)') - .option('--options ', 'View options (JSON object)') - .option('--column-meta ', 'Column meta (field visibility/order, JSON object)') - .action((0, ctx_1.run)(async (ctx, _args, o) => { - const opts = o; - const body = { - name: opts.name, - viewId: opts.viewId, - config: presetConfigFromOptions(opts), - }; - if (opts.description) - body.description = opts.description; - const preset = (await ctx.client.createViewPreset(opts.tableId, body)); - if (ctx.json) - (0, output_1.printJson)(preset); - else { - (0, output_1.printKv)([ - ['id', preset.id], - ['name', preset.name], - ['order', preset.order], - ]); - } - })); - cmd - .command('update ') - .description('Update a view preset (rename and/or replace config keys)') - .requiredOption('--table-id ', 'Table id') - .option('--name ', 'New preset name') - .option('--description ', 'New preset description') - .option('--filter ', 'Replace filter config (JSON)') - .option('--sort ', 'Replace sort config (JSON)') - .option('--group ', 'Replace group config (JSON)') - .option('--options ', 'Replace view options (JSON object)') - .option('--column-meta ', 'Replace column meta (JSON object)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const presetId = args[0]; - const opts = o; - const body = {}; - if (opts.name) - body.name = opts.name; - if (opts.description !== undefined) - body.description = opts.description; - const config = presetConfigFromOptions(opts); - if (Object.keys(config).length) - body.config = config; - await ctx.client.updateViewPreset(opts.tableId, presetId, body); - if (ctx.json) - (0, output_1.printJson)({ ok: true, id: presetId }); - else - process.stdout.write(`✓ Preset ${presetId} updated\n`); - })); - cmd - .command('delete ') - .description('Delete a view preset') - .requiredOption('--table-id ', 'Table id') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const presetId = args[0]; - const opts = o; - await ctx.client.deleteViewPreset(opts.tableId, presetId); - if (ctx.json) - (0, output_1.printJson)({ ok: true, id: presetId }); - else - process.stdout.write(`✓ Preset ${presetId} deleted\n`); - })); - return cmd; -} diff --git a/dist/commands/subscription.js b/dist/commands/subscription.js deleted file mode 100644 index 755fc14..0000000 --- a/dist/commands/subscription.js +++ /dev/null @@ -1,323 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.subscriptionCommand = void 0; -/** - * `inkreach-teable subscription` — 订阅中心全配置项 CRUD + 事件查看 + 立即发送。 - * - * 覆盖订阅中心全部配置:name/table/watchCreated/watchUpdated/fieldIds/recordIds/ - * displayFieldId/attachCsv/filter(原生 IFilter JSON)/triggerOnEnter(进入条件语义)/ - * sendMode/recipients/notifyInApp/scheduleType/scheduleTime/scheduleWeekday/ - * scheduleInterval/isActive。 - * - * filter 示例(与表格视图筛选同构): - * --filter '{"conjunction":"and","filterSet":[{"fieldId":"fldX","operator":"isExactly","value":"已完成"}]}' - * 日期筛选值结构:{"mode":"exactDate","exactDate":"2026-08-21","timeZone":"Asia/Shanghai"} - * 用户筛选值:["usrXXX"]('Me' 由后端解析为订阅创建者)。 - */ -const commander_1 = require("commander"); -const ctx_1 = require("../lib/ctx"); -const output_1 = require("../lib/output"); -function parseJsonFlag(raw, flagName) { - if (raw === undefined) - return undefined; - try { - return JSON.parse(raw); - } - catch (err) { - throw new Error(`--${flagName} is not valid JSON: ${err.message}\n got: ${raw}`); - } -} -/** 'a@x.com,b@y.com' | '["a@x.com"]' → string[] */ -function parseRecipients(raw) { - if (raw === undefined) - return undefined; - const trimmed = raw.trim(); - if (trimmed.startsWith('[')) { - return parseJsonFlag(raw, 'recipients'); - } - return trimmed - .split(',') - .map((s) => s.trim()) - .filter(Boolean); -} -/** 从 CLI 选项构建 RO body;create 时 name/tableId 必填。 */ -function buildRo(opts, forCreate) { - const body = {}; - if (opts.name !== undefined) - body.name = opts.name; - if (opts.tableId !== undefined) - body.tableId = opts.tableId; - if (forCreate) { - if (!body.name) - throw new Error('--name is required for create'); - if (!body.tableId) - throw new Error('--table-id is required for create'); - } - if (opts.watchCreated !== undefined) - body.watchCreated = opts.watchCreated; - if (opts.watchUpdated !== undefined) - body.watchUpdated = opts.watchUpdated; - const fieldIds = parseJsonFlag(opts.fieldIds, 'field-ids'); - if (fieldIds !== undefined) - body.fieldIds = fieldIds; - const recordIds = parseJsonFlag(opts.recordIds, 'record-ids'); - if (recordIds !== undefined) - body.recordIds = recordIds; - if (opts.displayFieldId !== undefined) { - body.displayFieldId = opts.displayFieldId === '' ? null : opts.displayFieldId; - } - if (opts.attachCsv !== undefined) - body.attachCsv = opts.attachCsv; - const filter = parseJsonFlag(opts.filter, 'filter'); - if (filter !== undefined) - body.filter = filter; - if (opts.triggerOnEnter !== undefined) - body.triggerOnEnter = opts.triggerOnEnter; - if (opts.sendMode !== undefined) { - if (opts.sendMode !== 'digest' && opts.sendMode !== 'instant') { - throw new Error(`--send-mode must be digest|instant, got: ${opts.sendMode}`); - } - body.sendMode = opts.sendMode; - } - const recipients = parseRecipients(opts.recipients); - if (recipients !== undefined) - body.recipients = recipients; - if (opts.notifyInApp !== undefined) - body.notifyInApp = opts.notifyInApp; - if (opts.scheduleType !== undefined) { - const t = opts.scheduleType; - if (!['daily', 'weekly', 'hours', 'minutes'].includes(t)) { - throw new Error(`--schedule-type must be daily|weekly|hours|minutes, got: ${t}`); - } - body.scheduleType = t; - } - if (opts.scheduleTime !== undefined) - body.scheduleTime = opts.scheduleTime; - if (opts.scheduleWeekday !== undefined) { - const n = Number(opts.scheduleWeekday); - if (!Number.isInteger(n) || n < 0 || n > 6) { - throw new Error(`--schedule-weekday must be 0-6 (0=Sunday), got: ${opts.scheduleWeekday}`); - } - body.scheduleWeekday = n; - } - if (opts.scheduleInterval !== undefined) { - const n = Number(opts.scheduleInterval); - if (!Number.isInteger(n) || n < 1) { - throw new Error(`--schedule-interval must be a positive integer, got: ${opts.scheduleInterval}`); - } - body.scheduleInterval = n; - } - if (opts.isActive !== undefined) - body.isActive = opts.isActive; - return body; -} -function addConfigOptions(cmd) { - return cmd - .option('--base-id ', 'Base id (overrides default)') - .option('--name ', 'Subscription name') - .option('--table-id ', 'Watched table id (tblXXX)') - .option('--no-watch-created', 'Disable watching record creation (default: on)') - .option('--no-watch-updated', 'Disable watching record updates (default: on)') - .option('--field-ids ', 'Watched field ids as JSON array, e.g. \'["fldA","fldB"]\' (omit = all fields)') - .option('--record-ids ', 'Record-level subscription: tracked record ids as JSON array (omit = all records)') - .option('--display-field-id ', 'Field shown as record title in emails ("" = primary field)') - .option('--attach-csv', 'Attach CSV of changes to digest emails (needs export permission)') - .option('--no-attach-csv', 'Do not attach CSV') - .option('--filter ', 'Native IFilter JSON (same shape as view filters); null/omit = all records') - .option('--trigger-on-enter', 'Only notify on the transition INTO the filter condition (requires --filter)') - .option('--no-trigger-on-enter', 'Notify on every matching change (state semantics)') - .option('--send-mode ', 'digest | instant') - .option('--recipients ', 'Recipient emails: comma-separated or JSON array (default = creator)') - .option('--notify-in-app', 'Also send in-app notifications (default)') - .option('--no-notify-in-app', 'Disable in-app notifications') - .option('--schedule-type ', 'daily | weekly | hours | minutes') - .option('--schedule-time ', 'Send time for daily/weekly, e.g. 09:00') - .option('--schedule-weekday ', '0-6 (0=Sunday), for weekly') - .option('--schedule-interval ', 'Interval for hours/minutes') - .option('--active', 'Subscription active (default)') - .option('--inactive', 'Pause the subscription'); -} -function sendModeBadge(s) { - return s.sendMode === 'instant' ? 'instant' : 'digest'; -} -function subscriptionCommand() { - const cmd = new commander_1.Command('subscription'); - cmd.description('Manage the subscription center (record subscriptions) — full config support'); - cmd - .command('list') - .description('List subscriptions in the base') - .option('--table-id ', 'Filter by table') - .option('--base-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.listSubscriptions(baseId, opts.tableId); - if (ctx.json) - (0, output_1.printJson)(list); - else { - if (!list.length) { - process.stdout.write('No subscriptions found.\n'); - return; - } - (0, output_1.printTable)([ - { key: 'id', header: 'ID' }, - { key: 'name', header: 'NAME' }, - { key: 'tableId', header: 'TABLE' }, - { key: 'mode', header: 'MODE' }, - { key: 'onEnter', header: 'ON-ENTER' }, - { key: 'schedule', header: 'SCHEDULE' }, - { key: 'pending', header: 'PENDING' }, - { key: 'active', header: 'ACTIVE' }, - ], list.map((s) => ({ - id: s.id, - name: (0, output_1.truncate)(s.name, 24), - tableId: s.tableId, - mode: sendModeBadge(s), - onEnter: s.triggerOnEnter ? '✓' : '', - schedule: s.sendMode === 'instant' ? '-' : s.scheduleType, - pending: s.pendingCount ?? 0, - active: s.isActive ? '✓' : '', - }))); - process.stdout.write(`\n${list.length} subscription(s). Use 'subscription get ' for details.\n`); - } - })); - const getCmd = cmd.command('get ').description('Show full subscription config'); - getCmd.option('--base-id ', 'Base id (overrides default)').action((0, ctx_1.run)(async (ctx, args, o) => { - const id = args[0]; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - const list = await ctx.client.listSubscriptions(baseId); - const sub = list.find((s) => s.id === id); - if (!sub) - throw new Error(`Subscription ${id} not found in base ${baseId}`); - if (ctx.json) - (0, output_1.printJson)(sub); - else { - (0, output_1.printKv)([ - ['id', sub.id], - ['name', sub.name], - ['tableId', sub.tableId], - ['watchCreated', String(sub.watchCreated)], - ['watchUpdated', String(sub.watchUpdated)], - ['fieldIds', sub.fieldIds?.length ? sub.fieldIds.join(', ') : '(all fields)'], - [ - 'recordIds', - sub.recordIds?.length ? `${sub.recordIds.length} record(s)` : '(all records)', - ], - ['displayFieldId', sub.displayFieldId ?? '(primary)'], - ['attachCsv', String(sub.attachCsv)], - ['filter', sub.filter ? JSON.stringify(sub.filter) : '(none)'], - ['triggerOnEnter', String(sub.triggerOnEnter)], - ['sendMode', sendModeBadge(sub)], - ['recipients', (sub.recipients ?? []).join(', ')], - ['notifyInApp', String(sub.notifyInApp)], - ['scheduleType', sub.scheduleType], - ['scheduleTime', sub.scheduleTime ?? ''], - ['scheduleWeekday', sub.scheduleWeekday != null ? String(sub.scheduleWeekday) : ''], - ['scheduleInterval', sub.scheduleInterval != null ? String(sub.scheduleInterval) : ''], - ['isActive', String(sub.isActive)], - ['pendingCount', String(sub.pendingCount ?? 0)], - ['lastSentTime', sub.lastSentTime ?? ''], - ]); - } - })); - const createCmd = cmd - .command('create') - .description('Create a subscription (--name and --table-id required)'); - addConfigOptions(createCmd).action((0, ctx_1.run)(async (ctx, _args, o) => { - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - const body = buildRo(opts, true); - const vo = await ctx.client.createSubscription(baseId, body); - if (ctx.json) - (0, output_1.printJson)(vo); - else - (0, output_1.printKv)([ - ['id', vo.id], - ['name', vo.name], - ['created', '✓'], - ]); - })); - const updateCmd = cmd - .command('update ') - .description('Update a subscription (only provided options change)'); - addConfigOptions(updateCmd).action((0, ctx_1.run)(async (ctx, args, o) => { - const id = args[0]; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - const body = buildRo(opts, false); - if (Object.keys(body).length === 0) { - throw new Error('No options provided — nothing to update'); - } - const vo = await ctx.client.updateSubscription(baseId, id, body); - if (ctx.json) - (0, output_1.printJson)(vo); - else - (0, output_1.printKv)([ - ['id', vo.id], - ['updated', '✓'], - ]); - })); - cmd - .command('delete ') - .description('Delete a subscription') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const id = args[0]; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - await ctx.client.deleteSubscription(baseId, id); - if (ctx.json) - (0, output_1.printJson)({ id }); - else - process.stdout.write(`Deleted ${id}\n`); - })); - cmd - .command('events ') - .description('List captured events (pending + sent) of a subscription') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const id = args[0]; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - const events = (await ctx.client.getSubscriptionEvents(baseId, id)); - if (ctx.json) - (0, output_1.printJson)(events); - else { - if (!events.length) { - process.stdout.write('No events captured.\n'); - return; - } - (0, output_1.printTable)([ - { key: 'recordId', header: 'RECORD' }, - { key: 'changeType', header: 'CHANGE' }, - { key: 'createdTime', header: 'CAPTURED' }, - { key: 'sentTime', header: 'SENT' }, - ], events.map((e) => ({ - recordId: e.recordId, - changeType: e.changeType, - createdTime: e.createdTime, - sentTime: e.sentTime ?? '(pending)', - }))); - } - })); - cmd - .command('send ') - .description('Send pending digest events immediately (flush now)') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const id = args[0]; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - const result = await ctx.client.sendSubscriptionNow(baseId, id); - if (ctx.json) - (0, output_1.printJson)(result ?? { id }); - else - (0, output_1.printKv)([ - ['id', id], - ['sent', '✓'], - ]); - })); - return cmd; -} -exports.subscriptionCommand = subscriptionCommand; diff --git a/dist/commands/workflow.js b/dist/commands/workflow.js deleted file mode 100644 index 54b60e6..0000000 --- a/dist/commands/workflow.js +++ /dev/null @@ -1,636 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.buildActionConfig = exports.actionCommand = exports.buildTriggerConfig = exports.triggerCommand = exports.workflowCommand = void 0; -/** - * `inkreach-teable workflow` — workflow CRUD + lifecycle. - * `inkreach-teable trigger` — set the trigger node's type & config. - * `inkreach-teable action` — add/update/delete action nodes. - * - * All node-mutating commands operate via PUT /node (whole-graph replacement), - * so they internally GET the full graph, mutate, then PUT it back. - */ -const commander_1 = require("commander"); -const ctx_1 = require("../lib/ctx"); -const output_1 = require("../lib/output"); -// ===================== workflow ===================== -function workflowCommand() { - const cmd = new commander_1.Command('workflow'); - cmd.description('Manage automations (workflows) — the self-built automation engine'); - cmd - .command('list') - .description('List all workflows in the base') - .option('--base-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.listWorkflows(baseId)); - if (ctx.json) - (0, output_1.printJson)(list); - else { - if (!list.length) { - process.stdout.write('No workflows found.\n'); - return; - } - (0, output_1.printTable)([ - { key: 'id', header: 'ID' }, - { key: 'name', header: 'NAME' }, - { key: 'isActive', header: 'ACTIVE' }, - { key: 'hasDraft', header: 'DRAFT' }, - { key: 'nodes', header: 'NODES' }, - ], list.map((w) => ({ - id: w.id, - name: (0, output_1.truncate)(w.name, 30), - isActive: w.isActive ? '✓' : '', - hasDraft: w.hasDraft ? '*' : '', - nodes: (w.nodes || []).length, - }))); - process.stdout.write(`\n${list.length} workflow(s). Use 'workflow get ' for details.\n`); - } - })); - cmd - .command('get ') - .description('Get workflow detail (nodes + edges)') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const workflowId = args[0]; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - const wf = await ctx.client.getWorkflowInBase(baseId, workflowId); - if (ctx.json) - (0, output_1.printJson)(wf); - else { - (0, output_1.printKv)([ - ['id', wf.id], - ['name', wf.name], - ['description', wf.description || ''], - ['active', wf.isActive ? 'yes' : 'no'], - ['hasDraft', wf.hasDraft ? 'yes' : 'no'], - ]); - process.stdout.write('\nNODES:\n'); - (0, output_1.printTable)([ - { key: 'id', header: 'ID' }, - { key: 'type', header: 'TYPE' }, - { key: 'category', header: 'CATEGORY' }, - { key: 'config', header: 'CONFIG' }, - ], (wf.nodes || []).map((n) => ({ - id: n.id, - type: n.type, - category: n.category, - config: (0, output_1.truncate)(JSON.stringify(n.config), 40), - }))); - if (wf.edges?.length) { - process.stdout.write('\nEDGES:\n'); - (0, output_1.printTable)([ - { key: 'id', header: 'ID' }, - { key: 'sourceNodeId', header: 'SOURCE' }, - { key: 'targetNodeId', header: 'TARGET' }, - ], (wf.edges || []).map((e) => ({ ...e }))); - } - } - })); - cmd - .command('create') - .description('Create a workflow (auto-includes a recordCreated trigger node)') - .requiredOption('--name ', 'Workflow name') - .option('--description ', 'Workflow description') - .option('--base-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 wf = await ctx.client.createWorkflow(baseId, { - name: opts.name, - description: opts.description, - }); - if (ctx.json) - (0, output_1.printJson)(wf); - else { - (0, output_1.printKv)([ - ['id', wf.id], - ['name', wf.name], - ['baseId', wf.baseId], - ['', ''], - ['next', `inkreach-teable trigger set ${wf.id} --type ...`], - ]); - } - })); - cmd - .command('update ') - .description('Update workflow name / description') - .option('--name ', 'New name') - .option('--description ', 'New description') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const workflowId = args[0]; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - const wf = await ctx.client.updateWorkflow(baseId, workflowId, { - name: opts.name, - description: opts.description, - }); - if (ctx.json) - (0, output_1.printJson)(wf); - else - process.stdout.write(`✓ Workflow ${workflowId} updated\n`); - })); - cmd - .command('delete ') - .description('Soft-delete a workflow (cascades: base_node, auto-token, trigger unregister)') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const workflowId = args[0]; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - await ctx.client.deleteWorkflow(baseId, workflowId); - if (ctx.json) - (0, output_1.printJson)({ ok: true, id: workflowId }); - else - process.stdout.write(`✓ Workflow ${workflowId} deleted\n`); - })); - cmd - .command('activate ') - .description('Activate: snapshot + register trigger + create auto access token') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const workflowId = args[0]; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - const res = await ctx.client.activateWorkflow(baseId, workflowId); - if (ctx.json) - (0, output_1.printJson)({ ok: true, ...res }); - else - (0, output_1.printKv)([ - ['snapshotId', res.snapshotId], - ['status', 'activated'], - ]); - })); - cmd - .command('deactivate ') - .description('Deactivate: unregister trigger') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const workflowId = args[0]; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - await ctx.client.deactivateWorkflow(baseId, workflowId); - if (ctx.json) - (0, output_1.printJson)({ ok: true, id: workflowId }); - else - process.stdout.write(`✓ Workflow ${workflowId} deactivated\n`); - })); - cmd - .command('test ') - .description('Test-run: execute action nodes with mock input (no trigger, no queue)') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const workflowId = args[0]; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - const res = await ctx.client.testWorkflow(baseId, workflowId); - if (ctx.json) - (0, output_1.printJson)(res); - else { - process.stdout.write('Test results:\n'); - (0, output_1.printTable)([ - { key: 'nodeId', header: 'NODE' }, - { key: 'status', header: 'STATUS' }, - { key: 'error', header: 'ERROR' }, - ], (res.nodeResults || []).map((r) => { - const row = r; - return { - nodeId: row.nodeId, - status: row.status, - error: (0, output_1.truncate)(row.error, 50), - }; - })); - } - })); - return cmd; -} -exports.workflowCommand = workflowCommand; -// ===================== trigger ===================== -const TRIGGER_TYPES = [ - 'recordCreated', - 'recordUpdated', - 'recordCondition', - 'recordCreatedOrUpdated', - 'formSubmitted', - 'scheduledTime', - 'buttonClick', - 'webhook', -]; -function triggerCommand() { - const cmd = new commander_1.Command('trigger'); - cmd.description('Set the trigger node (type + config). Builds correct config keys for you.'); - cmd - .command('set ') - .description('Set the trigger type and config (replaces the existing trigger node)') - .requiredOption('--type ', `Trigger type: ${TRIGGER_TYPES.join('|')}`) - .option('--table-id ', 'Table id (most record/button triggers)') - .option('--field-ids ', 'Field ids JSON array, e.g. \'["fldXXX"]\' (recordUpdated, buttonClick)') - .option('--form-id ', 'Form view id (formSubmitted, legacy single). Prefer --form-ids.') - .option('--form-ids ', 'Comma-separated form view ids (formSubmitted, multi-select)') - .option('--conditions ', 'Filter conditions JSON (recordCondition/recordCreated)') - .option('--schedule ', 'Schedule JSON {timing:{type,interval}} (scheduledTime)') - .option('--batch ', 'Batch config JSON {enabled,batchWindow,batchSize}') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const workflowId = args[0]; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - if (!TRIGGER_TYPES.includes(opts.type)) { - throw Object.assign(new Error(`Invalid trigger type: ${opts.type}`), { - code: 'INVALID_TYPE', - }); - } - const config = buildTriggerConfig(opts.type, { - tableId: opts.tableId, - fieldIds: opts.fieldIds, - formId: opts.formId, - formIds: opts.formIds, - conditions: opts.conditions, - schedule: opts.schedule, - batch: opts.batch, - }); - const wf = await ctx.client.getWorkflowInBase(baseId, workflowId); - const nodes = (wf.nodes || []).slice(); - // Find the trigger node (there is exactly one) and replace its category + config. - const triggerIdx = nodes.findIndex((n) => n.type === 'trigger'); - const newTrigger = { - ...nodes[triggerIdx >= 0 ? triggerIdx : 0], - type: 'trigger', - category: opts.type, - config, - }; - if (triggerIdx >= 0) - nodes[triggerIdx] = newTrigger; - else - nodes.unshift(newTrigger); - const updated = await ctx.client.replaceNodes(baseId, workflowId, { - nodes, - edges: wf.edges || [], - }); - if (ctx.json) - (0, output_1.printJson)(updated); - else { - (0, output_1.printKv)([ - ['workflowId', workflowId], - ['triggerType', opts.type], - ['config', JSON.stringify(config)], - ['', ''], - ['next', `inkreach-teable action add ${workflowId} --type script ...`], - ]); - } - })); - return cmd; -} -exports.triggerCommand = triggerCommand; -/** Build the trigger node config JSON using the SELF-BUILT contract keys. */ -function buildTriggerConfig(type, opts) { - const parse = (s) => (s ? JSON.parse(s) : undefined); - const config = {}; - switch (type) { - case 'recordCreated': - case 'recordCreatedOrUpdated': { - if (opts.tableId) - config.tableId = opts.tableId; - if (opts.conditions) - config.conditions = parse(opts.conditions); - break; - } - case 'recordUpdated': { - if (opts.tableId) - config.tableId = opts.tableId; - // NOTE: key is `fieldIds` (NOT watchFieldIds). [] = watch all fields. - config.fieldIds = opts.fieldIds ? parse(opts.fieldIds) : []; - break; - } - case 'recordCondition': { - if (opts.tableId) - config.tableId = opts.tableId; - // conditions is required for recordCondition - config.conditions = opts.conditions - ? parse(opts.conditions) - : { conjunction: 'and', filterSet: [] }; - break; - } - case 'formSubmitted': { - if (opts.tableId) - config.tableId = opts.tableId; - // Prefer --form-ids (csv) which populates formIds:string[]. Fall back to - // legacy --form-id (single string) for backward compatibility. - if (opts.formIds) { - config.formIds = opts.formIds - .split(',') - .map((s) => s.trim()) - .filter((s) => s.length > 0); - } - else if (opts.formId) { - config.formId = opts.formId; - } - break; - } - case 'scheduledTime': { - // Backend ONLY honors timing.type + timing.interval (rest is ignored). - if (opts.schedule) { - const sched = parse(opts.schedule); - if (sched.timing) - config.timing = sched.timing; - } - break; - } - case 'buttonClick': { - if (opts.tableId) - config.tableId = opts.tableId; - config.fieldIds = opts.fieldIds ? parse(opts.fieldIds) : []; - break; - } - case 'webhook': { - // authorization is UI-only; backend does NOT validate it. - config.authorization = { type: 'none' }; - break; - } - default: - break; - } - if (opts.batch && - ['recordCreated', 'recordUpdated', 'recordCondition', 'recordCreatedOrUpdated'].includes(type)) { - config.batch = parse(opts.batch); - } - return config; -} -exports.buildTriggerConfig = buildTriggerConfig; -// ===================== action ===================== -const ACTION_TYPES = [ - 'script', - 'sendEmail', - 'httpRequest', - 'createRecord', - 'updateRecord', - 'getRecords', - 'sendNotification', -]; -function actionCommand() { - const cmd = new commander_1.Command('action'); - cmd.description('Add/update/delete action nodes'); - cmd - .command('add ') - .description('Add an action node after the last existing node') - .requiredOption('--type ', `Action type: ${ACTION_TYPES.join('|')}`) - .option('--config ', 'Full action config JSON (overrides the granular flags below)') - .option('--code ', 'Script JS code (script)') - .option('--code-file ', 'Read script code from a file (script)') - .option('--timeout ', 'Script timeout ms (script, default 60000)') - .option('--dependencies ', 'npm packages JSON array for script, e.g. \'[{"name":"axios","version":"1.6.0"}]\' (script)') - .option('--to ', 'Recipient (sendEmail)') - .option('--subject ', 'Subject (sendEmail)') - .option('--body ', 'HTML body (sendEmail)') - .option('--url ', 'Request URL (httpRequest)') - .option('--method ', 'HTTP method (httpRequest, default GET)') - .option('--headers ', 'Request headers JSON (httpRequest)') - .option('--req-body ', 'Request body JSON (httpRequest)') - .option('--target-table ', 'Target table id (createRecord/updateRecord/getRecords)') - .option('--records ', 'Records JSON array (createRecord/updateRecord)') - .option('--query ', 'Query JSON (getRecords)') - .option('--to-user ', 'Recipient user id (sendNotification)') - .option('--title ', 'Notification title (sendNotification)') - .option('--message ', 'Notification message (sendNotification)') - .option('--severity ', 'Notification severity info|warning|critical (sendNotification)') - .option('--path ', 'Click-through path, e.g. /base/xx/table/yy (sendNotification)') - .option('--name ', 'Optional node display name') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const workflowId = args[0]; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - if (!ACTION_TYPES.includes(opts.type)) { - throw Object.assign(new Error(`Invalid action type: ${opts.type}`), { - code: 'INVALID_TYPE', - }); - } - const config = opts.config ? JSON.parse(opts.config) : buildActionConfig(opts); - const wf = await ctx.client.getWorkflowInBase(baseId, workflowId); - const nodes = (wf.nodes || []).slice(); - const edges = (wf.edges || []).slice(); - const newNodeId = `node-${Date.now()}`; - const newNode = { - id: newNodeId, - type: 'action', - category: opts.type, - config, - name: opts.name || null, - }; - // Chain after the last node (trigger → ... → new). - const lastNode = nodes[nodes.length - 1]; - nodes.push(newNode); - if (lastNode) { - edges.push({ - id: `edge-${lastNode.id}-${newNodeId}`, - sourceNodeId: lastNode.id, - targetNodeId: newNodeId, - }); - } - const updated = await ctx.client.replaceNodes(baseId, workflowId, { nodes, edges }); - if (ctx.json) - (0, output_1.printJson)(updated); - else { - (0, output_1.printKv)([ - ['workflowId', workflowId], - ['nodeId', newNodeId], - ['type', opts.type], - ['config', JSON.stringify(config)], - ['', ''], - ['next', `inkreach-teable workflow activate ${workflowId}`], - ]); - } - })); - cmd - .command('update ') - .description('Update an existing action node config') - .option('--config ', 'Full replacement config JSON') - .option('--code ', 'Script JS code (script)') - .option('--code-file ', 'Read script code from a file (script)') - .option('--timeout ', 'Script timeout ms (script)') - .option('--dependencies ', 'npm packages JSON array for script, e.g. \'[{"name":"axios","version":"1.6.0"}]\' (script; replaces existing deps)') - .option('--name ', 'New node display name') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const [workflowId, nodeId] = args; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - const wf = await ctx.client.getWorkflowInBase(baseId, workflowId); - const nodes = (wf.nodes || []).slice(); - const idx = nodes.findIndex((n) => n.id === nodeId); - if (idx < 0) - throw Object.assign(new Error(`Node ${nodeId} not found`), { code: 'NOT_FOUND' }); - const node = nodes[idx]; - let config = node.config || {}; - if (opts.config) - config = JSON.parse(opts.config); - else { - // Granular update: merge only the flags the user actually passed. - config = { ...config }; - // commander stores --kebab-case flags camelCased (--code-file -> - // opts.codeFile); accept both forms like buildActionConfig's pick(). - const codeFile = opts['code-file'] ?? opts.codeFile; - if (opts.code !== undefined || codeFile !== undefined) { - config.code = codeFile ? readFile(codeFile) : opts.code; - } - if (opts.timeout !== undefined) - config.timeout = Number(opts.timeout); - if (opts.dependencies !== undefined) { - config.dependencies = parseDependencies(opts.dependencies); - } - } - nodes[idx] = { ...node, config, name: opts.name ?? node.name }; - const updated = await ctx.client.replaceNodes(baseId, workflowId, { - nodes, - edges: wf.edges || [], - }); - if (ctx.json) - (0, output_1.printJson)(updated); - else - process.stdout.write(`✓ Node ${nodeId} updated\n`); - })); - cmd - .command('delete ') - .description('Delete an action node and its connected edges') - .option('--base-id ', 'Base id (overrides default)') - .action((0, ctx_1.run)(async (ctx, args, o) => { - const [workflowId, nodeId] = args; - const opts = o; - const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId); - const wf = await ctx.client.getWorkflowInBase(baseId, workflowId); - const nodes = (wf.nodes || []).filter((n) => n.id !== nodeId); - const edges = (wf.edges || []).filter((e) => e.sourceNodeId !== nodeId && e.targetNodeId !== nodeId); - const updated = await ctx.client.replaceNodes(baseId, workflowId, { nodes, edges }); - if (ctx.json) - (0, output_1.printJson)(updated); - else - process.stdout.write(`✓ Node ${nodeId} deleted\n`); - })); - return cmd; -} -exports.actionCommand = actionCommand; -function readFile(p) { - // inline require to keep imports clean - // eslint-disable-next-line @typescript-eslint/no-var-requires - return require('node:fs').readFileSync(p, 'utf8'); -} -/** - * Parse `--dependencies` into the ScriptDependency[] shape the backend expects. - * Accepts: - * - full JSON: '[{"name":"axios","version":"1.6.0"}]' - * - name list: 'axios,lodash' (version defaults to 'latest') - * - name@ver: 'axios@1.6.0,lodash@4.17.21' - * - empty '[]' or '' to clear deps - */ -function parseDependencies(raw) { - const trimmed = raw.trim(); - if (!trimmed || trimmed === '[]') - return []; - // JSON form - if (trimmed[0] === '[') { - const parsed = JSON.parse(trimmed); - if (!Array.isArray(parsed)) - throw new Error('--dependencies JSON must be an array'); - return parsed.map((d) => { - if (typeof d === 'string') - return { name: d }; - if (d && typeof d === 'object' && typeof d.name === 'string') { - return { name: d.name, ...(d.version ? { version: String(d.version) } : {}) }; - } - throw new Error('--dependencies entries must be strings or {name,version?} objects'); - }); - } - // Comma list form: axios@1.6.0,lodash - return trimmed - .split(',') - .map((s) => s.trim()) - .filter(Boolean) - .map((spec) => { - const at = spec.lastIndexOf('@'); - if (at > 0) - return { name: spec.slice(0, at), version: spec.slice(at + 1) }; - return { name: spec }; - }); -} -/** Build action config from granular flags. */ -function buildActionConfig(opts) { - const type = opts.type; - // Helper: commander stores --kebab-case flags as camelCase in opts (e.g. - // --target-table -> opts.targetTable). Accept both forms for safety. - const pick = (k) => (opts[k] ?? opts[toCamel(k)]); - const config = {}; - switch (type) { - case 'script': { - const codeFile = pick('code-file'); - if (codeFile) - config.code = readFile(codeFile); - else if (opts.code) - config.code = opts.code; - if (opts.timeout) - config.timeout = Number(opts.timeout); - if (opts.dependencies) - config.dependencies = parseDependencies(opts.dependencies); - break; - } - case 'sendEmail': { - if (opts.to) - config.to = opts.to; - if (opts.subject) - config.subject = opts.subject; - if (opts.body) - config.body = opts.body; - break; - } - case 'httpRequest': { - if (opts.url) - config.url = opts.url; - if (opts.method) - config.method = opts.method; - if (opts.headers) - config.headers = JSON.parse(opts.headers); - const reqBody = pick('req-body'); - if (reqBody) - config.body = JSON.parse(reqBody); - break; - } - case 'createRecord': - case 'updateRecord': { - const tt = pick('target-table'); - if (tt) - config.tableId = tt; - if (opts.records) - config.records = JSON.parse(opts.records); - break; - } - case 'getRecords': { - const tt = pick('target-table'); - if (tt) - config.tableId = tt; - if (opts.query) - config.query = JSON.parse(opts.query); - break; - } - case 'sendNotification': { - if (opts.toUser) - config.toUserId = opts.toUser; - if (opts.title) - config.title = opts.title; - if (opts.message) - config.message = opts.message; - if (opts.severity) - config.severity = opts.severity; - if (opts.path) - config.path = opts.path; - break; - } - default: - break; - } - return config; -} -exports.buildActionConfig = buildActionConfig; -function toCamel(s) { - return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase()); -} diff --git a/dist/lib/client.js b/dist/lib/client.js deleted file mode 100644 index b4d88f7..0000000 --- a/dist/lib/client.js +++ /dev/null @@ -1,311 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ApiError = exports.Client = void 0; -/** - * High-level API client. Each method maps to one REST endpoint of the - * inkreach-teable backend, using the SELF-BUILT contract (not official teable). - * - * Base/table/field/record/view endpoints are compatible with official teable - * open-api; workflow/automation endpoints use the project's own contract. - */ -const request_1 = require("./request"); -Object.defineProperty(exports, "ApiError", { enumerable: true, get: function () { return request_1.ApiError; } }); -class Client { - cfg; - constructor(cfg) { - this.cfg = cfg; - } - get headers() { - return this.cfg.token ? { Authorization: `Bearer ${this.cfg.token}` } : {}; - } - async call(method, p, body) { - const res = await (0, request_1.request)(this.cfg.endpoint, p, { method, headers: this.headers, body }); - return res.data; - } - // ---- auth / user ---- - getMe() { - // GET /api/auth/user — @TokenAccess() enabled, works with PAT. - // (/api/auth/user/me is session-only and 403s for PAT.) - return this.call('GET', '/api/auth/user'); - } - // ---- access tokens (PAT) ---- - listAccessTokens() { - return this.call('GET', '/api/access-token'); - } - createAccessToken(body) { - return this.call('POST', '/api/access-token', body); - } - deleteAccessToken(id) { - return this.call('DELETE', `/api/access-token/${id}`); - } - // ---- base ---- - listBases() { - // GET /api/base/access/all — lists bases the user can access. - return this.call('GET', '/api/base/access/all'); - } - getBase(baseId) { - return this.call('GET', `/api/base/${baseId}`); - } - // ---- table ---- - listTables(baseId) { - return this.call('GET', `/api/base/${baseId}/table`); - } - getTable(tableId) { - return this.call('GET', `/api/table/${tableId}`); - } - createTable(baseId, body) { - return this.call('POST', `/api/base/${baseId}/table`, body); - } - updateTable(tableId, body) { - return this.call('PUT', `/api/table/${tableId}`, body); - } - deleteTable(tableId, baseId) { - const base = baseId ?? this.cfg.baseId; - return this.call('DELETE', `/api/base/${base}/table/${tableId}`); - } - // ---- field ---- - listFields(tableId) { - return this.call('GET', `/api/table/${tableId}/field`); - } - getField(tableId, fieldId) { - return this.call('GET', `/api/table/${tableId}/field/${fieldId}`); - } - createField(tableId, body) { - return this.call('POST', `/api/table/${tableId}/field`, body); - } - /** Rename/describe. NOTE: the update API accepts name/description only — - * options (incl. select choice visibilityFilter) must go through convertField. */ - updateField(tableId, fieldId, body) { - return this.call('PATCH', `/api/table/${tableId}/field/${fieldId}`, body); - } - /** PUT convert: the ONLY API path that persists field options (choices, - * visibilityFilter dependency config, etc.). Requires the field's current type. */ - convertField(tableId, fieldId, body) { - return this.call('PUT', `/api/table/${tableId}/field/${fieldId}/convert`, body); - } - deleteField(tableId, fieldId) { - return this.call('DELETE', `/api/table/${tableId}/field/${fieldId}`); - } - /** Evaluate select-choice visibility dependency for one record+field. - * GET /api/table/:tableId/select-visibility/:recordId/:fieldId */ - getVisibleChoices(tableId, recordId, fieldId) { - return this.call('GET', `/api/table/${tableId}/select-visibility/${recordId}/${fieldId}`); - } - // ---- record ---- - getRecords(tableId, query) { - const qs = query ? '?' + new URLSearchParams(this.flattenQuery(query)).toString() : ''; - return this.call('GET', `/api/table/${tableId}/record${qs}`); - } - /** Flatten search[] tuple arrays into repeated query keys (Teable expects search[]=...). */ - flattenQuery(query) { - const out = {}; - for (const [k, v] of Object.entries(query)) { - if (v === undefined || v === null) - continue; - if (Array.isArray(v)) { - v.forEach((item) => { - out[`${k}[]`] = typeof item === 'object' ? JSON.stringify(item) : String(item); - }); - } - else if (typeof v === 'object') { - out[k] = JSON.stringify(v); - } - else if (k === 'search' && typeof v === 'string') { - // The records list API expects `search[]=value` (tuple), not a plain - // `search=value` string. https://github.com/colinhacks/zod - out['search[]'] = v; - } - else { - out[k] = String(v); - } - } - return out; - } - getRecord(recordId, query) { - const qs = query ? '?' + new URLSearchParams(this.flattenQuery(query)).toString() : ''; - return this.call('GET', `/api/record/${recordId}${qs}`); - } - createRecord(tableId, body) { - return this.call('POST', `/api/table/${tableId}/record`, body); - } - updateRecord(tableId, body) { - return this.call('PATCH', `/api/table/${tableId}/record`, body); - } - updateRecordOne(recordId, body) { - return this.call('PATCH', `/api/record/${recordId}`, body); - } - deleteRecords(tableId, recordIds) { - const qs = recordIds.map((id) => `recordIds=${encodeURIComponent(id)}`).join('&'); - return this.call('DELETE', `/api/table/${tableId}/record?${qs}`); - } - // ---- view ---- - listViews(tableId) { - return this.call('GET', `/api/table/${tableId}/view`); - } - getView(viewId) { - return this.call('GET', `/api/view/${viewId}`); - } - createView(tableId, body) { - return this.call('POST', `/api/table/${tableId}/view`, body); - } - updateView(viewId, body) { - return this.call('PATCH', `/api/view/${viewId}`, body); - } - deleteView(viewId) { - return this.call('DELETE', `/api/view/${viewId}`); - } - // ---- view preset (self-built contract) ---- - // Preset view configurations: VIEW-scoped named bundles of - // filter/sort/group(/options/columnMeta) shared with all collaborators. - listViewPresets(tableId, viewId) { - return this.call('GET', `/api/table/${tableId}/view-preset${viewId ? `?viewId=${encodeURIComponent(viewId)}` : ''}`); - } - createViewPreset(tableId, body) { - return this.call('POST', `/api/table/${tableId}/view-preset`, body); - } - updateViewPreset(tableId, presetId, body) { - return this.call('PUT', `/api/table/${tableId}/view-preset/${presetId}`, body); - } - deleteViewPreset(tableId, presetId) { - return this.call('DELETE', `/api/table/${tableId}/view-preset/${presetId}`); - } - // ---- workflow (self-built contract) ---- - listWorkflows(baseId) { - return this.call('GET', `/api/base/${baseId}/workflow`); - } - getWorkflow(workflowId) { - return this.call('GET', `/api/base/any/workflow/${workflowId}`); - } - /** - * Get workflow by explicit baseId + workflowId. getWorkflow() above hits a - * route that resolves base from the workflow itself, but some auth/permission - * paths need the base-scoped route. Prefer this when baseId is known. - */ - getWorkflowInBase(baseId, workflowId) { - return this.call('GET', `/api/base/${baseId}/workflow/${workflowId}`); - } - createWorkflow(baseId, body) { - return this.call('POST', `/api/base/${baseId}/workflow`, body); - } - updateWorkflow(baseId, workflowId, body) { - return this.call('PATCH', `/api/base/${baseId}/workflow/${workflowId}`, body); - } - deleteWorkflow(baseId, workflowId) { - return this.call('DELETE', `/api/base/${baseId}/workflow/${workflowId}`); - } - activateWorkflow(baseId, workflowId) { - return this.call('POST', `/api/base/${baseId}/workflow/${workflowId}/activate`); - } - deactivateWorkflow(baseId, workflowId) { - return this.call('POST', `/api/base/${baseId}/workflow/${workflowId}/deactivate`); - } - testWorkflow(baseId, workflowId) { - return this.call('POST', `/api/base/${baseId}/workflow/${workflowId}/test`); - } - /** PUT /node — whole-graph replacement (delete-all + create-all in a txn). */ - replaceNodes(baseId, workflowId, body) { - return this.call('PUT', `/api/base/${baseId}/workflow/${workflowId}/node`, body); - } - // ---- secrets ---- - listSecrets(baseId, workflowId) { - return this.call('GET', `/api/base/${baseId}/workflow/${workflowId}/secret`); - } - createSecret(baseId, workflowId, body) { - return this.call('POST', `/api/base/${baseId}/workflow/${workflowId}/secret`, body); - } - updateSecret(baseId, workflowId, secretId, body) { - return this.call('PATCH', `/api/base/${baseId}/workflow/${workflowId}/secret/${secretId}`, body); - } - deleteSecret(baseId, workflowId, secretId) { - return this.call('DELETE', `/api/base/${baseId}/workflow/${workflowId}/secret/${secretId}`); - } - // ---- execution history ---- - listExecutions(baseId, workflowId) { - return this.call('GET', `/api/base/${baseId}/workflow/${workflowId}/execution`); - } - getExecution(baseId, workflowId, executionId) { - return this.call('GET', `/api/base/${baseId}/workflow/${workflowId}/execution/${executionId}`); - } - rerunExecution(baseId, workflowId, executionId) { - return this.call('POST', `/api/base/${baseId}/workflow/${workflowId}/execution/${executionId}/rerun`); - } - // ---- trigger (manual fire for testing) ---- - fireButtonClick(tableId, recordId, fieldId) { - return this.call('POST', `/api/table/${tableId}/record/${recordId}/${fieldId}/button-click`); - } - fireWebhook(baseId, workflowId, body) { - // NOTE: real backend route is /api/webhook/{baseId}/{workflowId} (no /base/ /workflow/ segments) - return this.call('POST', `/api/webhook/${baseId}/${workflowId}`, body); - } - // ---- SQL (read-only) ---- - sql(baseId, sql) { - return this.call('POST', `/api/base/${baseId}/sql`, { sql }); - } - // ---- plugin ---- - 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); - } - 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); - } - // ---- 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)}`); - } - // ---- subscription center (record subscriptions) ---- - listSubscriptions(baseId, tableId) { - const q = tableId ? `?tableId=${encodeURIComponent(tableId)}` : ''; - return this.call('GET', `/api/base/${baseId}/subscription${q}`); - } - getSubscriptionEvents(baseId, subscriptionId) { - return this.call('GET', `/api/base/${baseId}/subscription/${subscriptionId}/event`); - } - createSubscription(baseId, body) { - return this.call('POST', `/api/base/${baseId}/subscription`, body); - } - updateSubscription(baseId, subscriptionId, body) { - return this.call('PATCH', `/api/base/${baseId}/subscription/${subscriptionId}`, body); - } - deleteSubscription(baseId, subscriptionId) { - return this.call('DELETE', `/api/base/${baseId}/subscription/${subscriptionId}`); - } - /** 立即发送(flush pending digest events) */ - sendSubscriptionNow(baseId, subscriptionId) { - return this.call('POST', `/api/base/${baseId}/subscription/${subscriptionId}/send`); - } -} -exports.Client = Client; diff --git a/dist/lib/config.js b/dist/lib/config.js deleted file mode 100644 index 618d94c..0000000 --- a/dist/lib/config.js +++ /dev/null @@ -1,73 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.configPath = exports.resolveConfig = exports.writeConfigFile = exports.readConfigFile = exports.DEFAULT_ENDPOINT = void 0; -/** - * Config storage for inkreach-teable CLI. - * - * Stores endpoint + token at ~/.inkreach-teable/config.json, with environment variable - * overrides INKREACH_TEABLE_ENDPOINT / INKREACH_TEABLE_TOKEN. The --endpoint / --token / --base-id - * global flags take the highest precedence (handled by resolveOptions()). - */ -const fs = __importStar(require("node:fs")); -const path = __importStar(require("node:path")); -const os = __importStar(require("node:os")); -const CONFIG_DIR = path.join(os.homedir(), '.inkreach-teable'); -const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json'); -exports.DEFAULT_ENDPOINT = 'http://localhost:3003'; -function readConfigFile() { - try { - const raw = fs.readFileSync(CONFIG_FILE, 'utf8'); - return JSON.parse(raw); - } - catch { - return {}; - } -} -exports.readConfigFile = readConfigFile; -function writeConfigFile(cfg) { - if (!fs.existsSync(CONFIG_DIR)) { - fs.mkdirSync(CONFIG_DIR, { recursive: true }); - } - const merged = { ...readConfigFile(), ...cfg }; - fs.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + '\n', { mode: 0o600 }); -} -exports.writeConfigFile = writeConfigFile; -function resolveConfig(overrides) { - const file = readConfigFile(); - return { - endpoint: overrides?.endpoint || - process.env.INKREACH_TEABLE_ENDPOINT || - file.endpoint || - exports.DEFAULT_ENDPOINT, - token: overrides?.token || process.env.INKREACH_TEABLE_TOKEN || file.token || '', - baseId: overrides?.baseId || process.env.INKREACH_TEABLE_BASE_ID || file.baseId, - }; -} -exports.resolveConfig = resolveConfig; -function configPath() { - return CONFIG_FILE; -} -exports.configPath = configPath; diff --git a/dist/lib/ctx.js b/dist/lib/ctx.js deleted file mode 100644 index df58a5c..0000000 --- a/dist/lib/ctx.js +++ /dev/null @@ -1,72 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.requireBaseId = exports.run = exports.makeCtx = void 0; -const config_1 = require("./config"); -const client_1 = require("./client"); -const request_1 = require("./request"); -/** - * Build a Ctx. `command` is the subcommand being executed (so .opts() returns - * its local options); `program` is the top-level program (for global options). - */ -function makeCtx(program, command) { - const g = program.opts(); - const cfg = (0, config_1.resolveConfig)({ - endpoint: g.endpoint, - token: g.token, - baseId: g.baseId, - }); - return { - client: new client_1.Client(cfg), - json: !!g.json, - baseId: cfg.baseId, - command, - program, - }; -} -exports.makeCtx = makeCtx; -/** - * Wrap an async command action with normalized error handling. - * - * The wrapped fn receives (ctx, positionalArgs[], options). commander passes - * positional args before the trailing Command; we detect the trailing Command - * and forward everything else as positional, plus parsed options. - */ -function run(fn) { - return async function (...cmdArgs) { - // commander convention: last arg is the Command instance (the subcommand). - const command = cmdArgs[cmdArgs.length - 1]; - const program = command?.parent ? findRoot(command) : command; - const positional = cmdArgs.slice(0, -1); - const options = (command?.opts() || {}); - const ctx = makeCtx(program, command); - try { - await fn(ctx, positional, options); - } - catch (err) { - const { status, code, message } = (0, request_1.describeError)(err); - if (ctx.json) { - process.stdout.write(JSON.stringify({ error: { status, code, message } }) + '\n'); - } - else { - process.stderr.write(`✗ ${code}${status ? ` (${status})` : ''}: ${message}\n`); - } - process.exitCode = 1; - } - }; -} -exports.run = run; -function findRoot(cmd) { - let c = cmd; - while (c.parent) - c = c.parent; - return c; -} -/** Require a baseId: from flag, else ctx default, else throw with guidance. */ -function requireBaseId(ctx, flagVal) { - const baseId = flagVal || ctx.baseId; - if (!baseId) { - throw new client_1.ApiError(0, 'MISSING_BASE_ID', 'No base id. Pass --base-id , set a default with `inkreach-teable auth login --base-id `, or export INKREACH_TEABLE_BASE_ID.'); - } - return baseId; -} -exports.requireBaseId = requireBaseId; diff --git a/dist/lib/field-display.js b/dist/lib/field-display.js deleted file mode 100644 index 654d771..0000000 --- a/dist/lib/field-display.js +++ /dev/null @@ -1,136 +0,0 @@ -"use strict"; -/** - * Human-mode display helpers for field options — make select-choice - * constraints (choices, colors, per-choice visibilityFilter) readable - * without `--json` post-processing. - */ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.formatFieldDetail = exports.choiceRows = exports.describeChoiceCondition = exports.summarizeFieldOptions = void 0; -const CONDITION_MAX_LEN = 90; -/** Summarize a field's options for the `field list --with-options` column. */ -function summarizeFieldOptions(field) { - const options = field.options; - const choices = options?.choices; - if (Array.isArray(choices) && choices.length) { - const conditional = choices.filter((c) => c && typeof c === 'object' && c.visibilityFilter).length; - return conditional - ? `${choices.length} choices · ${conditional} conditional` - : `${choices.length} choices`; - } - if (!options || typeof options !== 'object') - return '—'; - const type = field.type; - if (type === 'rating') { - return [ - `max=${options.max ?? 5}`, - typeof options.icon === 'string' ? `icon=${options.icon}` : '', - ] - .filter(Boolean) - .join(' '); - } - const formatting = options.formatting; - if (type === 'number' && formatting) { - return `precision=${formatting.precision ?? 0}`; - } - if ((type === 'date' || type === 'createdTime' || type === 'lastModifiedTime') && formatting) { - return [formatting.date, formatting.time && formatting.time !== 'None' ? formatting.time : ''] - .filter(Boolean) - .join(' '); - } - if (typeof options.expression === 'string') { - const e = options.expression; - return `expr: ${e.length > 40 ? e.slice(0, 39) + '…' : e}`; - } - return '—'; -} -exports.summarizeFieldOptions = summarizeFieldOptions; -function formatValue(value) { - if (value === null || value === undefined) - return ''; - if (Array.isArray(value)) - return value.map((v) => String(v)).join('|'); - if (typeof value === 'object') - return JSON.stringify(value); - return String(value); -} -/** One-line human summary of a choice's visibilityFilter; `(always)` when absent. */ -function describeChoiceCondition(vf, fieldNames) { - if (!vf || typeof vf !== 'object') - return '(always)'; - const { conjunction, filterSet } = vf; - if (!Array.isArray(filterSet) || !filterSet.length) - return '(always)'; - const joiner = conjunction === 'or' ? ' OR ' : ' AND '; - const rules = filterSet.map(({ fieldId, operator, value }) => { - const label = (fieldId && fieldNames?.[fieldId]) || fieldId || '?'; - const v = formatValue(value); - return v ? `${label} ${operator ?? '?'} ${v}` : `${label} ${operator ?? '?'}`; - }); - let text = ''; - let kept = 0; - for (const rule of rules) { - const candidate = text ? text + joiner + rule : rule; - if (candidate.length > CONDITION_MAX_LEN && kept > 0) - break; - text = candidate; - kept++; - } - const omitted = rules.length - kept; - return omitted > 0 ? `${text}… +${omitted} more` : text; -} -exports.describeChoiceCondition = describeChoiceCondition; -/** Choices of a select field as display rows; `[]` when the field has none. */ -function choiceRows(field, fieldNames) { - const choices = field.options?.choices; - if (!Array.isArray(choices)) - return []; - return choices - .filter((c) => !!c && typeof c === 'object') - .map((c) => ({ - name: String(c.name ?? ''), - color: typeof c.color === 'string' ? c.color : '', - when: describeChoiceCondition(c.visibilityFilter, fieldNames), - })); -} -exports.choiceRows = choiceRows; -function kvLine(key, value, keyWidth) { - const val = typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value ?? ''); - return `${key.padEnd(keyWidth)} ${val}`; -} -/** Full human-mode rendering for `field get`: kv lines, with select choices - * expanded into a per-choice condition listing instead of one JSON blob. */ -function formatFieldDetail(field, fieldNames) { - const lines = []; - const options = field.options; - const choices = options?.choices; - const restEntries = Object.entries({ ...field }); - const keyWidth = Math.max(...restEntries.map(([k]) => k.length), 'defaultValue'.length); - for (const [k, v] of restEntries) { - if (k === 'options') - continue; - lines.push(kvLine(k, v, keyWidth)); - } - if (Array.isArray(choices) && choices.length) { - const conditional = choices.filter((c) => c && typeof c === 'object' && c.visibilityFilter).length; - lines.push(''); - lines.push(`choices (${choices.length} · ${conditional} conditional):`); - for (const row of choiceRows(field, fieldNames)) { - const color = row.color ? ` [${row.color}]` : ''; - lines.push(` ${row.name}${color}`); - lines.push(` when: ${row.when}`); - } - const otherOpts = Object.fromEntries(Object.entries(options ?? {}).filter(([k]) => k !== 'choices')); - if (Object.keys(otherOpts).length) { - lines.push(''); - lines.push(kvLine('options (other)', otherOpts, keyWidth)); - } - } - else if (options && Object.keys(options).length) { - lines.push(kvLine('options', options, keyWidth)); - } - else { - lines.push(kvLine('options', undefined, keyWidth)); - } - return lines.join('\n'); -} -exports.formatFieldDetail = formatFieldDetail; diff --git a/dist/lib/field-normalize.js b/dist/lib/field-normalize.js deleted file mode 100644 index 6b9e1d5..0000000 --- a/dist/lib/field-normalize.js +++ /dev/null @@ -1,289 +0,0 @@ -"use strict"; -/** - * Field-definition normalization for `field create` / `table create --fields`. - * - * The raw API is strict: `type` must be the full enum value and select - * `choices` must be objects (`{name, ...}`) — plain strings 400. These helpers - * accept the friendlier documented forms (aliases like `sel`, string choices, - * `foreignTableName` / `linkFieldName` / `lookupFieldName` to be resolved to - * IDs) so the examples in the skill docs actually work. - */ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.resolveFieldReferences = exports.findFieldByNameOrId = exports.findTableByNameOrId = exports.inferFieldType = exports.expandCompactFieldSpec = exports.normalizeFieldRoArray = exports.normalizeFieldRo = exports.resolveFieldType = void 0; -const TYPE_ALIASES = { - text: 'singleLineText', - str: 'singleLineText', - long: 'longText', - num: 'number', - int: 'number', - check: 'checkbox', - bool: 'checkbox', - sel: 'singleSelect', - select: 'singleSelect', - multi: 'multipleSelect', - tags: 'multipleSelect', - rate: 'rating', - star: 'rating', - user: 'user', - person: 'user', - file: 'attachment', - attach: 'attachment', - mul: 'multipleSelect', - link: 'link', - rel: 'link', - rollup: 'rollup', - condrollup: 'conditionalRollup', - formula: 'formula', - auto: 'autoNumber', - created: 'createdTime', - modified: 'lastModifiedTime', - createdby: 'createdBy', - modifiedby: 'lastModifiedBy', - button: 'button', -}; -/** Resolve a type alias (`sel`) to its full value (`singleSelect`). Unknown values pass through untouched. */ -function resolveFieldType(type) { - return TYPE_ALIASES[type.toLowerCase()] ?? type; -} -exports.resolveFieldType = resolveFieldType; -const SELECT_TYPES = new Set(['singleSelect', 'multipleSelect']); -/** Required-option defaults for types whose Ro schema has none — applied only - * when the user supplied an options object that is missing required keys. */ -function applyOptionDefaults(type, options) { - if (type === 'rating') { - if (options.icon === undefined) - options.icon = 'star'; - if (options.color === undefined) - options.color = 'yellowBright'; - if (options.max === undefined) - options.max = 5; - } - else if (type === 'number') { - if (options.formatting === undefined) { - options.formatting = { precision: 0, type: 'decimal' }; - } - } - else if (type === 'button') { - if (options.label === undefined) - options.label = 'Button'; - if (options.color === undefined) - options.color = 'teal'; - } -} -/** - * Normalize one field body in place: - * - resolve `type` aliases - * - convert select `options.choices` string entries to `{name}` objects - * - backfill required-option defaults for rating/number/button - */ -function normalizeFieldRo(body) { - if (typeof body.type === 'string') { - body.type = resolveFieldType(body.type); - } - const type = body.type; - if (type && - body.options && - typeof body.options === 'object' && - !Array.isArray(body.options)) { - const options = body.options; - if (SELECT_TYPES.has(type) && Array.isArray(options.choices)) { - options.choices = options.choices.map((c) => typeof c === 'string' ? { name: c } : c); - } - applyOptionDefaults(type, options); - } - return body; -} -exports.normalizeFieldRo = normalizeFieldRo; -/** Apply normalizeFieldRo to every entry of a `--fields` array (invalid input passes through so the API reports it). */ -function normalizeFieldRoArray(fields) { - if (!Array.isArray(fields)) - return fields; - return fields.map((f) => typeof f === 'string' - ? expandCompactFieldSpec(f) - : f && typeof f === 'object' && !Array.isArray(f) - ? normalizeFieldRo({ ...f }) - : f); -} -exports.normalizeFieldRoArray = normalizeFieldRoArray; -/** - * Expand the compact `--fields` string form "Name[:type[:choice,choice,…]]" - * (e.g. "Status:sel:Todo,Done", "Notes:long", "Title") into a field body. - * Bare names become singleLineText; a bare type token is resolved through - * the alias map; a third segment lists select choices (strings). - */ -function expandCompactFieldSpec(spec) { - const parts = spec.split(':').map((p) => p.trim()); - const name = parts[0]; - const type = parts[1] ? resolveFieldType(parts[1]) : inferFieldType(name, undefined); - const body = { name, type }; - if (parts.length > 2 && parts[2]) { - const choices = parts[2].split(',').map((c) => c.trim()).filter(Boolean); - body.options = { choices: choices.map((c) => ({ name: c })) }; - } - return body; -} -exports.expandCompactFieldSpec = expandCompactFieldSpec; -const NAME_INFER_RULES = [ - [/^(status|priority|category|type|stage|level)$/i, 'singleSelect'], - [/^(tags?|labels?)$/i, 'multipleSelect'], - [/^(description|notes|comment|content|body)$/i, 'longText'], - [/^(count|amount|price|quantity|total|number|age)$/i, 'number'], - [/^(rating|score|stars?)$/i, 'rating'], - [/^(is.+|has.+|active|enabled|done|completed)$/i, 'checkbox'], - [/^(assignee|owner|user)$/i, 'user'], - [/^(attachment|file|image|photo|document)$/i, 'attachment'], - [/^(email|url)$/i, 'singleLineText'], - [/^(created( time)?|created)$/i, 'createdTime'], - [/^(last modified|updated)$/i, 'lastModifiedTime'], - [/^(created by|author)$/i, 'createdBy'], - [/^(modified by|updated by)$/i, 'lastModifiedBy'], -]; -/** - * Infer the field type when `--type` is omitted: first from options shape - * (choices/max/foreignTableName/expression/workflow), then from the field - * name, falling back to singleLineText. - */ -function inferFieldType(name, options) { - if (options && typeof options === 'object' && !Array.isArray(options)) { - const o = options; - if (Array.isArray(o.choices)) - return 'singleSelect'; - if (typeof o.max === 'number') - return 'rating'; - if (typeof o.foreignTableName === 'string' || typeof o.foreignTableId === 'string') { - return 'link'; - } - if (typeof o.expression === 'string') { - // conditionalRollup when combined with link + condition; plain formula otherwise - return typeof o.linkFieldId === 'string' && o.condition ? 'conditionalRollup' : 'formula'; - } - if (o.workflow && typeof o.workflow === 'object') - return 'button'; - } - if (name) { - for (const [re, type] of NAME_INFER_RULES) { - if (re.test(name)) - return type; - } - } - return 'singleLineText'; -} -exports.inferFieldType = inferFieldType; -/** Find a table by ID or (exact, then case-insensitive) name. Returns undefined when not found. */ -function findTableByNameOrId(tables, nameOrId) { - return (tables.find((t) => t.id === nameOrId) ?? - tables.find((t) => t.name === nameOrId) ?? - tables.find((t) => t.name.toLowerCase() === nameOrId.toLowerCase())); -} -exports.findTableByNameOrId = findTableByNameOrId; -/** Find a field by ID or (exact, then case-insensitive) name. Returns undefined when not found. */ -function findFieldByNameOrId(fields, nameOrId) { - return (fields.find((f) => f.id === nameOrId) ?? - fields.find((f) => f.name === nameOrId) ?? - fields.find((f) => f.name.toLowerCase() === nameOrId.toLowerCase())); -} -exports.findFieldByNameOrId = findFieldByNameOrId; -/** - * Resolve name references in link `options` and `lookupOptions` to IDs: - * - options.foreignTableName → options.foreignTableId - * - lookupOptions.linkFieldName → linkFieldId (fields of THIS table) - * - lookupOptions.lookupFieldName → lookupFieldId (fields of the foreign table) - * - * `listTables(baseId)`, `listFields(tableId)` are client calls; only invoked when - * a *Name key is present. Existing IDs are left untouched. Returns error strings - * (not throws) so the caller can decide to fail with a helpful message. - */ -async function resolveFieldReferences(body, deps) { - const errors = []; - const options = (body.options && typeof body.options === 'object' && !Array.isArray(body.options) - ? body.options - : undefined); - if (options && typeof options.foreignTableName === 'string' && !options.foreignTableId) { - const tables = await deps.listTables(deps.baseId); - const t = findTableByNameOrId(tables, options.foreignTableName); - if (!t) { - errors.push(`foreignTableName "${options.foreignTableName}" not found in base ${deps.baseId}`); - } - else { - options.foreignTableId = t.id; - delete options.foreignTableName; // API rejects unknown keys - } - } - const lookup = body.lookupOptions && - typeof body.lookupOptions === 'object' && - !Array.isArray(body.lookupOptions) - ? body.lookupOptions - : undefined; - if (lookup) { - // For lookup fields the foreign table is the target of the LINK field - // referenced by linkFieldId — there is no options.foreignTableId on the - // lookup field itself. Resolve the link field first, then use its options. - let linkForeignTableId; - const linkRef = typeof lookup.linkFieldId === 'string' - ? lookup.linkFieldId - : typeof lookup.linkFieldName === 'string' - ? lookup.linkFieldName - : undefined; - const hasNameHints = typeof lookup.linkFieldName === 'string' || typeof lookup.lookupFieldName === 'string'; - const needForeignFromLink = hasNameHints && - typeof lookup.foreignTableId !== 'string' && - !(options && typeof options.foreignTableId === 'string'); - if (linkRef && deps.tableId && (hasNameHints || needForeignFromLink)) { - const fields = (await deps.listFields(deps.tableId)) ?? []; - const linkField = findFieldByNameOrId(fields, linkRef); - if (!linkField && typeof lookup.linkFieldId !== 'string') { - errors.push(`linkFieldName "${lookup.linkFieldName}" not found in table ${deps.tableId}`); - } - if (linkField) { - lookup.linkFieldId = linkField.id; - const linkOptions = linkField - .options; - const fid = linkOptions && typeof linkOptions.foreignTableId === 'string' - ? linkOptions.foreignTableId - : undefined; - if (fid) - linkForeignTableId = fid; - } - } - const foreignTableId = (options && typeof options.foreignTableId === 'string' && options.foreignTableId) || - (typeof lookup.foreignTableId === 'string' && lookup.foreignTableId) || - linkForeignTableId || - (typeof lookup.linkFieldId === 'string' && lookup.linkFieldId); // conditional rollup: foreign table only - // Backfill foreignTableId (Ro schema requires it even for plain lookups, - // where it is derived from the link field) once resolved. - if (linkForeignTableId && typeof lookup.foreignTableId !== 'string') { - lookup.foreignTableId = linkForeignTableId; - } - if (typeof lookup.linkFieldName === 'string' && !lookup.linkFieldId) { - if (!deps.tableId) { - errors.push('linkFieldName resolution requires --table-id (creating a field)'); - } - // (resolution already attempted above; if it failed an error was pushed) - } - if (typeof lookup.lookupFieldName === 'string' && !lookup.lookupFieldId) { - const foreign = (options && typeof options.foreignTableId === 'string' && options.foreignTableId) || - foreignTableId; - if (!foreign) { - errors.push('lookupFieldName resolution requires foreignTableId (or foreignTableName)'); - } - else { - const fields = await deps.listFields(foreign); - const f = findFieldByNameOrId(fields, lookup.lookupFieldName); - if (!f) { - errors.push(`lookupFieldName "${lookup.lookupFieldName}" not found in foreign table ${foreign}`); - } - else { - lookup.lookupFieldId = f.id; - } - } - } - // The API rejects unknown keys — strip the name hints (only when resolved; - // unresolved ones produced errors above and the caller will abort). - if (lookup.linkFieldId) - delete lookup.linkFieldName; - if (lookup.lookupFieldId) - delete lookup.lookupFieldName; - } - return errors; -} -exports.resolveFieldReferences = resolveFieldReferences; diff --git a/dist/lib/output.js b/dist/lib/output.js deleted file mode 100644 index c114faf..0000000 --- a/dist/lib/output.js +++ /dev/null @@ -1,34 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.truncate = exports.printKv = exports.printTable = exports.printJson = void 0; -/** Print as JSON (compact). Safe for agent parsing. */ -function printJson(value) { - process.stdout.write(JSON.stringify(value) + '\n'); -} -exports.printJson = printJson; -/** Print a human-readable table. columns: [{key, header, width?}]; rows: object[]. */ -function printTable(columns, rows) { - const headers = columns.map((c) => c.header); - const widths = columns.map((c) => Math.max(c.header.length, ...rows.map((r) => String(r[c.key] ?? '').length))); - const sep = widths.map((w) => '-'.repeat(w)).join(' '); - const fmtRow = (r) => columns.map((c, i) => String(r[c.key] ?? '').padEnd(widths[i])).join(' '); - process.stdout.write(headers.map((h, i) => h.padEnd(widths[i])).join(' ') + '\n'); - process.stdout.write(sep + '\n'); - rows.forEach((r) => process.stdout.write(fmtRow(r) + '\n')); -} -exports.printTable = printTable; -/** Print key: value pairs vertically. */ -function printKv(pairs) { - const width = Math.max(...pairs.map(([k]) => k.length)); - pairs.forEach(([k, v]) => { - const val = typeof v === 'object' && v !== null ? JSON.stringify(v) : String(v ?? ''); - process.stdout.write(`${k.padEnd(width)} ${val}\n`); - }); -} -exports.printKv = printKv; -/** Truncate a long string to width, appending ellipsis. */ -function truncate(s, width = 40) { - const str = String(s ?? ''); - return str.length > width ? str.slice(0, width - 1) + '…' : str; -} -exports.truncate = truncate; diff --git a/dist/lib/request.js b/dist/lib/request.js deleted file mode 100644 index 79e3d14..0000000 --- a/dist/lib/request.js +++ /dev/null @@ -1,124 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.describeError = exports.request = exports.ApiError = void 0; -/** - * HTTP request helper using node:http / node:https. - * - * IMPORTANT: we deliberately avoid global fetch() on Node.js — the Node 24 - * undici fetch drops the Authorization header in some redirect/proxy scenarios - * (confirmed bug in this project). Using the built-in http(s) module keeps the - * Authorization: Bearer header intact end-to-end. - */ -const http = __importStar(require("node:http")); -const https = __importStar(require("node:https")); -class ApiError extends Error { - status; - body; - constructor(status, message, body) { - super(message); - this.name = 'ApiError'; - this.status = status; - this.body = body; - } -} -exports.ApiError = ApiError; -async function request(baseUrl, pathName, options = {}) { - const url = new URL(pathName.startsWith('http') ? pathName : pathName.replace(/^\//, ''), baseUrl.endsWith('/') ? baseUrl : baseUrl + '/'); - const isHttps = url.protocol === 'https:'; - const lib = isHttps ? https : http; - const method = options.method || 'GET'; - const headers = { - Accept: 'application/json', - ...(options.headers || {}), - }; - let bodyData; - if (options.body !== undefined && options.body !== null) { - bodyData = JSON.stringify(options.body); - headers['Content-Type'] = headers['Content-Type'] || 'application/json'; - headers['Content-Length'] = Buffer.byteLength(bodyData).toString(); - } - return new Promise((resolve, reject) => { - const req = lib.request({ - hostname: url.hostname, - port: url.port || (isHttps ? 443 : 80), - path: url.pathname + url.search, - method, - headers, - }, (res) => { - const chunks = []; - res.on('data', (chunk) => chunks.push(chunk)); - res.on('end', () => { - const buf = Buffer.concat(chunks); - const text = buf.toString('utf8'); - let data = text; - const ct = res.headers['content-type'] || ''; - if (ct.includes('application/json') || text.trim().startsWith('{') || text.trim().startsWith('[')) { - try { - data = text ? JSON.parse(text) : null; - } - catch { - data = text; - } - } - const status = res.statusCode || 200; - if (status >= 200 && status < 300) { - resolve({ status, headers: res.headers, data: data }); - } - else { - const errBody = data; - const msg = (errBody && typeof errBody === 'object' && errBody.error?.message) || - (typeof errBody === 'object' && errBody.message) || - `HTTP ${status}`; - reject(new ApiError(status, String(msg), errBody)); - } - }); - }); - if (options.signal) { - options.signal.addEventListener('abort', () => req.destroy(new Error('aborted'))); - } - req.on('error', reject); - if (bodyData !== undefined) - req.write(bodyData); - req.end(); - }); -} -exports.request = request; -/** Extract `{ code, message }` from an ApiError body for display. */ -function describeError(err) { - if (err instanceof ApiError) { - const b = err.body; - return { - status: err.status, - code: b?.error?.code || `HTTP_${err.status}`, - message: b?.error?.message || err.message, - }; - } - if (err instanceof Error) { - return { status: 0, code: 'ERROR', message: err.message }; - } - return { status: 0, code: 'UNKNOWN', message: String(err) }; -} -exports.describeError = describeError; diff --git a/package.json b/package.json index 864a199..02ee42b 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,16 @@ { "name": "@inkreach/teable-cli", "version": "0.1.0", - "description": "CLI (command: inkreach-teable) for operating inkreach-teable: tables, fields, records, views, and the self-built automation engine (8 triggers + 6 actions).", + "description": "CLI (command: inkreach-teable) for operating inkreach-teable: tables, fields, records, views, and the self-built automation engine (8 triggers + 7 actions).", "license": "MIT", "type": "commonjs", + "repository": { + "type": "git", + "url": "ssh://git@gitea.inkreach.cc:2222/inkreachhub/inkreach-teable-cli.git" + }, + "engines": { + "node": ">=18" + }, "bin": { "inkreach-teable": "./dist/bin.js" }, @@ -12,6 +19,7 @@ "build": "tsc -p tsconfig.build.json", "dev": "tsc -p tsconfig.build.json --watch", "typecheck": "tsc -p tsconfig.json --noEmit", + "test": "vitest run", "clean": "rimraf dist tsconfig.build.tsbuildinfo" }, "dependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..11aa068 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1069 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + commander: + specifier: ^12.1.0 + version: 12.1.0 + devDependencies: + '@types/node': + specifier: ^22.18.0 + version: 22.20.1 + rimraf: + specifier: ^5.0.5 + version: 5.0.5 + typescript: + specifier: ^5.4.3 + version: 5.4.3 + vitest: + specifier: 4.1.10 + version: 4.1.10(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)) + +packages: + + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@oxc-project/types@0.139.0': + resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rolldown/binding-android-arm64@1.1.5': + resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.1.5': + resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.1.5': + resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.1.5': + resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.1.5': + resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.1.5': + resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.1.5': + resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.1.5': + resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.1.5': + resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.1.5': + resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.1.5': + resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.1.5': + resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.1.5': + resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.1.5': + resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/node@22.20.1': + resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==} + + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} + + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} + + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} + + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} + + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + balanced-match@1.0.0: + resolution: {integrity: sha512-9Y0g0Q8rmSt+H33DfKv7FOc3v+iRI+o1lbzt8jGcIosYW37IIW/2XVYq5NPdmaD5NQ59Nk26Kl/vZbwW9Fr8vg==} + + brace-expansion@2.1.2: + resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + es-module-lexer@2.3.0: + resolution: {integrity: sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + foreground-child@3.1.0: + resolution: {integrity: sha512-lXeSPRCndWPaipZbtI4CkvTZpF6OPsy19dkvf7+5AHeJD+w+iAKPc9Q78xWBmX4SdR+8xrtY9jTXs/YDv8q+Ug==} + engines: {node: '>=14'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.1.2: + resolution: {integrity: sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==} + engines: {node: '>=14'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lru-cache@10.2.0: + resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} + engines: {node: 14 || >=16.14} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + nanoid@3.3.15: + resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + obug@2.1.3: + resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} + engines: {node: '>=12.20.0'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + postcss@8.5.17: + resolution: {integrity: sha512-J7EF+8X+CzRPaJPOv9Ck2wNWJvGnnl3PcNPAdGg6GTLjyVpyQ0yATMSXRFRV01BviT/9Gwuc3rjEyJbDJG9a4w==} + engines: {node: ^10 || ^12 || >=14} + + rimraf@5.0.5: + resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} + engines: {node: '>=14'} + hasBin: true + + rolldown@1.1.5: + resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + + string-width@4.2.0: + resolution: {integrity: sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.4.3: + resolution: {integrity: sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + vite@8.1.4: + resolution: {integrity: sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.3.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + which@2.0.1: + resolution: {integrity: sha512-N7GBZOTswtB9lkQBZA4+zAXrjEIWAUOB93AvzUiudRzRxhUdLURQ7D/gAIMY1gatT/LTbmbcv8SiYazy3eYB7w==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + +snapshots: + + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.0 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@oxc-project/types@0.139.0': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rolldown/binding-android-arm64@1.1.5': + optional: true + + '@rolldown/binding-darwin-arm64@1.1.5': + optional: true + + '@rolldown/binding-darwin-x64@1.1.5': + optional: true + + '@rolldown/binding-freebsd-x64@1.1.5': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.1.5': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-x64-musl@1.1.5': + optional: true + + '@rolldown/binding-openharmony-arm64@1.1.5': + optional: true + + '@rolldown/binding-wasm32-wasi@1.1.5': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.1.5': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.1.5': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@standard-schema/spec@1.1.0': {} + + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} + + '@types/node@22.20.1': + dependencies: + undici-types: 6.21.0 + + '@vitest/expect@4.1.10': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.10(vite@8.1.4(@types/node@22.20.1))': + dependencies: + '@vitest/spy': 4.1.10 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.1.4(@types/node@22.20.1) + + '@vitest/pretty-format@4.1.10': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.10': + dependencies: + '@vitest/utils': 4.1.10 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.10': {} + + '@vitest/utils@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + assertion-error@2.0.1: {} + + balanced-match@1.0.0: {} + + brace-expansion@2.1.2: + dependencies: + balanced-match: 1.0.0 + + chai@6.2.2: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@12.1.0: {} + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.1 + + detect-libc@2.1.2: {} + + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + es-module-lexer@2.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + expect-type@1.4.0: {} + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + + foreground-child@3.1.0: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fsevents@2.3.3: + optional: true + + glob@10.5.0: + dependencies: + foreground-child: 3.1.0 + jackspeak: 3.1.2 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + is-fullwidth-code-point@3.0.0: {} + + isexe@2.0.0: {} + + jackspeak@3.1.2: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lru-cache@10.2.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.2 + + minipass@7.1.3: {} + + nanoid@3.3.15: {} + + obug@2.1.3: {} + + package-json-from-dist@1.0.1: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.2.0 + minipass: 7.1.3 + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.5: {} + + postcss@8.5.17: + dependencies: + nanoid: 3.3.15 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + rimraf@5.0.5: + dependencies: + glob: 10.5.0 + + rolldown@1.1.5: + dependencies: + '@oxc-project/types': 0.139.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.1.5 + '@rolldown/binding-darwin-arm64': 1.1.5 + '@rolldown/binding-darwin-x64': 1.1.5 + '@rolldown/binding-freebsd-x64': 1.1.5 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.5 + '@rolldown/binding-linux-arm64-gnu': 1.1.5 + '@rolldown/binding-linux-arm64-musl': 1.1.5 + '@rolldown/binding-linux-ppc64-gnu': 1.1.5 + '@rolldown/binding-linux-s390x-gnu': 1.1.5 + '@rolldown/binding-linux-x64-gnu': 1.1.5 + '@rolldown/binding-linux-x64-musl': 1.1.5 + '@rolldown/binding-openharmony-arm64': 1.1.5 + '@rolldown/binding-wasm32-wasi': 1.1.5 + '@rolldown/binding-win32-arm64-msvc': 1.1.5 + '@rolldown/binding-win32-x64-msvc': 1.1.5 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@4.2.0: {} + + string-width@4.2.0: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + tinybench@2.9.0: {} + + tinyexec@1.2.4: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + tinyrainbow@3.1.0: {} + + tslib@2.8.1: + optional: true + + typescript@5.4.3: {} + + undici-types@6.21.0: {} + + vite@8.1.4(@types/node@22.20.1): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.5 + postcss: 8.5.17 + rolldown: 1.1.5 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 22.20.1 + fsevents: 2.3.3 + + vitest@4.1.10(@types/node@22.20.1)(vite@8.1.4(@types/node@22.20.1)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@8.1.4(@types/node@22.20.1)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.0 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.3 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 8.1.4(@types/node@22.20.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.20.1 + transitivePeerDependencies: + - msw + + which@2.0.1: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.0 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 diff --git a/src/bin.ts b/src/bin.ts index f5b1d7a..7df462f 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -18,7 +18,7 @@ const program = new Command(); program .name('inkreach-teable') .description( - 'Operate inkreach-teable: tables, fields, records, views, and the self-built automation engine (8 triggers + 6 actions).\n' + + 'Operate inkreach-teable: tables, fields, records, views, and the self-built automation engine (8 triggers + 7 actions).\n' + 'Use --json for machine-readable output (agent parsing).' ) .version(`inkreach-teable ${version}`, '-v, --version'); diff --git a/tsconfig.build.json b/tsconfig.build.json index 7a63caf..781ffba 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,5 +1,4 @@ { - "extends": "../../tsconfig.base.json", "compilerOptions": { "module": "commonjs", "target": "es2022", @@ -12,7 +11,14 @@ "resolveJsonModule": true, "esModuleInterop": true, "skipLibCheck": true, - "strict": true + "strict": true, + "useUnknownInCatchVariables": true, + "allowJs": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "isolatedModules": true, + "incremental": true, + "newLine": "lf" }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "**/*.spec.ts"]