Compare commits

..
4 Commits
Author SHA1 Message Date
yeuimu 69f354c833 chore: v0.1.1 —— 同步 SKILL.md/README 安装文档进 npm 包 2026-09-29 02:43:45 +00:00
yeuimu 87490e9c6f feat: 发布 npm 公共包 @inkreach/inkreach-teable
- 包名 @inkreach/teable-cli → @inkreach/inkreach-teable(npm i -g 即装即用)
- bin 路径规范化 ./dist/bin.js → dist/bin.js(npm 11 发布校验要求)
- README / SKILL.md 安装命令改为 npm 包优先,gitea git+ 与离线方式降为备选

已发布:https://www.npmjs.com/package/@inkreach/inkreach-teable (v0.1.0)
验证:tarball 99KB/38 文件,registry 全新安装 → bin/版本/真实端点鉴权全通过
2026-09-29 02:22:19 +00:00
yeuimu abdf71a816 feat(skill): inkreach-teable-plugin 按 skill-creator 评估流程迭代优化
- 导入规则精确化:@teable/* 禁运行时值导入(import type 可);
  react 仅在 webpack externals 映射到宿主全局时允许
- 新增效率守则:信任技能文档、知识题不读宿主源码、不上传演示插件验证
- 修复失效引用:hr_admin 死账号、404 测试基地 → 改为一次性沙箱基地模式
- 补充认证提示:管理类路由(如 DELETE /api/plugin)PAT 403 需 session 回退
- deployment.md 部署脚本改用 PAT 鉴权与必填 BASE_ID

评估:6 题 × 2 轮 24 次运行(含真实上传/清理链路);description 经
触发评估循环验证为原版最优,保持不变
2026-09-29 01:47:54 +00:00
yeuimu f2c2c9e399 feat(skill): inkreach-teable 按 skill-creator 评估流程迭代优化
- 路由表新增 select 字段写入规则(先查 choices/新值先加选项,typecast 静默失败)
- scheduledTime 期望管理:用户要具体时刻/星期须提前告知后端不支持
- 常见错误表补充 mutationApplied=false 静默失败条目
- 快速语法块补充 webhook 调用 URL 形状
- description 按触发评估循环结果重写(20 题评估集,保留测试集分数胜出)
- evals.json 更新为 11 题评估集(沙箱隔离、含 secrets/依赖/选项更新覆盖)

评估:Pi + opencode-go/deepseek-v4.1-flash,两轮迭代 28 次运行,
改进版 8 类任务近满分且耗时 -23%
2026-09-29 01:47:48 +00:00
6 changed files with 90 additions and 59 deletions
+3 -2
View File
@@ -4,8 +4,9 @@
```text
请安装并用 inkreach-teable CLI 操作 inkreach-teable:
1. 安装 cli:
npm i -g git+https://gitea.inkreach.cc/inkreachhub/inkreach-teable-cli.git
1. 安装 cli(npm 公共包):
npm i -g @inkreach/inkreach-teable
(内网/离线备选:npm i -g git+https://gitea.inkreach.cc/inkreachhub/inkreach-teable-cli.git)
2. 安装技能:
npx -y skills add https://gitea.inkreach.cc/inkreachhub/inkreach-teable-cli.git -g -y --copy
3. 鉴权(PAT 从 Web UI Settings → Personal Access Tokens 获取):
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@inkreach/teable-cli",
"version": "0.1.0",
"name": "@inkreach/inkreach-teable",
"version": "0.1.1",
"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",
@@ -12,7 +12,7 @@
"node": ">=18"
},
"bin": {
"inkreach-teable": "./dist/bin.js"
"inkreach-teable": "dist/bin.js"
},
"main": "dist/bin.js",
"files": [
+29 -10
View File
@@ -13,7 +13,7 @@ description: >-
"I need a dashboard widget" — or wants the native form embedded or
extended inside a plugin (原生表单, 表单注入, 表单虚拟字段, custom form
layout, NativeForm, NativeFormScope, form panel) — load this skill.
Reference example at plugins/batch-edit-pro/.
Reference example at `plugins/batch-edit-pro/` (on the deployment host: `/root/deployments/inkreach-teable/plugins/batch-edit-pro/`).
---
# inkreach-teable dynamic plugin development
@@ -28,13 +28,27 @@ JS that executes in the host page and accesses host APIs through
> `POST /api/plugin/:id/upload-bundle`. It runs in the same React tree as the host
> and has full access to the host's SDK, UI library, and OpenAPI helpers.
>
> **Critical constraint**: NEVER `import` from `@teable/sdk`, `@teable/ui-lib`,
> `@teable/openapi` or `react` in plugin code. These are mapped to
> `window.__TEABLE_HOST__.*` by webpack externals at build time — importing
> them produces `undefined` at runtime. Always read from the host global:
> **Critical constraint**: no RUNTIME imports from `@teable/sdk`, `@teable/ui-lib`,
> `@teable/openapi` — webpack maps them to `window.__TEABLE_HOST__.*` externals, so a
> value import compiles to `undefined` at runtime. `import type { X } from '@teable/...'`
> IS fine (erased at compile time). `react`/`react-dom` may be imported ONLY when your
> webpack `externals` maps them to `window.__TEABLE_HOST__.React/ReactDOM` (see the
> batch-edit-pro webpack config). For everything else, read from the host global:
> `const { useFields } = window.__TEABLE_HOST__.SDK`. See
> `dynamic-plugins/troubleshooting.md` Pitfall 1 for details and symptoms.
## 0. Two efficiency rules (read first)
- **Trust these docs.** The `dynamic-plugins/` documents are the verified contract
for this deployment. Answer plugin capability/behavior questions directly from
them — do NOT re-derive behavior by spelunking `nestjs-backend/src` or the host
source tree; that burns minutes and adds nothing. Source diving is only for
debugging a concrete discrepancy the docs can't explain.
- **Don't upload demo plugins to verify knowledge.** For how-to/consultation
questions, a written answer (with code) is the deliverable. Only upload a bundle
when the user actually asked to deploy — test uploads belong in a throwaway
sandbox base, never the production plugin registry.
## 1. When to use this skill
- User says "写个插件 / 做个插件 / make a plugin" — yes.
@@ -73,13 +87,18 @@ JS that executes in the host page and accesses host APIs through
- Endpoint: `http://localhost:3003` (backend; `:3000` is taken by an unrelated
docker container on this host — see AGENTS.md), frontend `http://localhost:3002`.
- Test account: create a throwaway via `POST /api/auth/signup` — the old
`hr_admin@xingchen.tech` account no longer exists on this instance. Plugin
upload additionally requires a `plugin_developer` row for the uploading user
- Plugin upload requires a `plugin_developer` row for the uploading user
(`INSERT INTO plugin_developer (id, user_id, email, created_time, created_by)
VALUES ('plgd<name>', '<userId>', '<email>', now(), '<userId>')`).
- Test base: `bseAi6Hazmw7jPWZbfO`, test table: `tbldtYd4ZhvkcyAaada`.
- Auth: session cookie `auth_session`, or `inkreach-teable auth login`.
- **Sandbox base for any hands-on test**: create a throwaway base
(`POST /api/base` with `{"spaceId":"<spaceId>","name":"Sandbox(可删)"}`),
do all table/install testing inside it, and delete it (`DELETE /api/base/:id`)
when done. Never hardcode base/table IDs here — they rot; resolve them at
runtime (`inkreach-teable base list`) instead.
- Auth: PAT (`Authorization: Bearer <PAT>`; upload-bundle supports it) or
session cookie `auth_session`. Note: some admin-gated routes (e.g.
`DELETE /api/plugin/:id`) pass with an admin session but 403 with a plain PAT
even for the same user — fall back to the session for those.
## 5. Start here
@@ -144,16 +144,15 @@ VALUES (gen_random_uuid(), '<userId>', '<email>', '<adminUserId>');
## Getting a session cookie
Prefer a PAT (upload-bundle accepts `Authorization: Bearer <PAT>`). For a
session cookie, sign in with any real account created for the purpose
(`POST /api/auth/signup`), then add its `plugin_developer` row before uploading:
```bash
curl -s -c /tmp/cookie_hr.txt http://localhost:3003/api/auth/signin \
curl -s -c /tmp/cookie.txt http://localhost:3003/api/auth/signin \
-X POST -H 'Content-Type: application/json' \
-d '{"email":"hr_admin@xingchen.tech","password":"Xingchen@2026"}'
> Account note (2026-08): `hr_admin@xingchen.tech` no longer exists on the
> deployed instance. Create a throwaway user via `POST /api/auth/signup`
> and add a `plugin_developer` row for it before uploading (see SKILL.md).
SESSION=$(grep auth_session /tmp/cookie_hr.txt | awk '{print $NF}')
-d '{"email":"<real-account>","password":"<password>"}'
SESSION=$(grep auth_session /tmp/cookie.txt | awk '{print $NF}')
```
## Full deploy script (copy-paste)
@@ -163,22 +162,20 @@ SESSION=$(grep auth_session /tmp/cookie_hr.txt | awk '{print $NF}')
set -e
DIR="${1:-.}"
UPLOAD_URL="${UPLOAD_URL:-http://localhost:3003}"
BASE_ID="${BASE_ID:-bseAi6Hazmw7jPWZbfO}"
BASE_ID="${BASE_ID:?set to a throwaway sandbox base, never a production base}"
# Read plugin ID from manifest
PLUGIN_ID=$(python3 -c "import json; print(json.load(open('$DIR/plugin.json'))['id'])")
# Get session
SESSION=$(grep auth_session /tmp/cookie_hr.txt 2>/dev/null | awk '{print $NF}')
if [ -z "$SESSION" ]; then
curl -s -c /tmp/cookie_hr.txt "$UPLOAD_URL/api/auth/signin" \
-X POST -H 'Content-Type: application/json' \
-d '{"email":"hr_admin@xingchen.tech","password":"Xingchen@2026"}' > /dev/null
SESSION=$(grep auth_session /tmp/cookie_hr.txt | awk '{print $NF}')
fi
# PAT preferred (upload-bundle accepts Bearer auth):
AUTH=(-H "Authorization: Bearer ${PAT:?set PAT env}")
# ...or session:
SESSION=$(grep auth_session /tmp/cookie.txt 2>/dev/null | awk '{print $NF}')
AUTH=(-b "auth_session=$SESSION")
# Upload (URL includes pluginId from manifest)
curl -s -b "auth_session=$SESSION" \
curl -s "${AUTH[@]}" \
-F "js=@$DIR/dist/main.js" \
-F "css=@$DIR/dist/styles.css" \
-F "manifest=@$DIR/plugin.json" \
@@ -186,7 +183,7 @@ curl -s -b "auth_session=$SESSION" \
echo "Uploaded: $PLUGIN_ID"
# Enable
curl -s -b "auth_session=$SESSION" -X PUT \
curl -s "${AUTH[@]}" -X PUT \
"$UPLOAD_URL/api/base/$BASE_ID/plugin-config/$PLUGIN_ID" \
-H 'Content-Type: application/json' \
-d '{"enabled":true,"tableIds":[]}' > /dev/null
+6 -12
View File
@@ -1,16 +1,7 @@
---
name: inkreach-teable
description: >-
Operate a deployed inkreach-teable instance (a Teable fork with a self-built
automation engine) via the inkreach-teable CLI: configure tables, fields,
records, views, and automations (8 triggers incl. recordCreated/Updated/
Condition, formSubmitted, scheduledTime, buttonClick, webhook; 7 actions),
inspect field constraints (select choices, visibilityFilter conditions),
manage secrets, execution history, plugins, and record subscriptions. Use
when the user references Teable-style IDs (bseXXX, tblXXX, fldXXX, recXXX,
viwXXX, wflXXX) or wants to configure tables/automations/plugins on the
deployed instance — even without saying "inkreach-teable". The official
teable CLI does NOT work here (different automation contract).
Use this skill for hands-on work on a deployed inkreach-teable instance (a self-hosted Teable fork with its own automation engine — not teable cloud): adding or altering fields and views, cleaning or bulk-updating records, aggregating table data (monthly counts, group-bys), building automations that react to record changes, form submissions, buttons, or schedules and that can call external APIs, storing API keys as secrets so they aren't hardcoded, checking run history, and configuring change-summary email subscriptions. Trigger whenever the user names a business table (工单表/订单表/成员表, or any bse/tbl/fld/rec/viw/wfl ID) and wants to configure, query, script, automate, or subscribe to it — even if they never say "teable". Also use for this instance's SQL/REST access. Don't use for teable cloud pricing/comparisons, generic SQL/MySQL tuning, standalone Python/npm/CLI scripting, or unrelated webhook integrations.
---
# inkreach-teable operations
@@ -24,7 +15,7 @@ Respond in the user's language. Keep answers concise and action-oriented.
## 1. Prerequisites
- Install the CLI if missing: `npm i -g git+https://gitea.inkreach.cc/inkreachhub/inkreach-teable-cli.git` (the `git+` prefix is REQUIRED for npm/npx; ad-hoc without installing: `npx git+https://gitea.inkreach.cc/inkreachhub/inkreach-teable-cli.git <cmd>`). Offline fallback: `bash scripts/install.sh` relative to this skill dir in a repo clone.
- Install the CLI if missing: `npm i -g @inkreach/inkreach-teable` (public npm package). Alternatives: `npm i -g git+https://gitea.inkreach.cc/inkreachhub/inkreach-teable-cli.git` (the `git+` prefix is REQUIRED for npm/npx; ad-hoc without installing: `npx git+https://gitea.inkreach.cc/inkreachhub/inkreach-teable-cli.git <cmd>`). Offline fallback: `bash scripts/install.sh` relative to this skill dir in a repo clone.
- **Configure once**: `inkreach-teable auth login --endpoint https://table.inkreach.cc --token <PAT> [--base-id bseXXX]`; verify with `auth status`. PAT comes from `auth token create --name x` (full token shown **once**) or the web UI (Settings → Personal Access Tokens).
- `--json` on any command returns raw JSON (for agent parsing). Fallback for unknown commands: `inkreach-teable <cmd> --help`.
- **Read on demand**: consult the guide linked for your module when doing non-trivial work — no need to read everything up front.
@@ -58,10 +49,11 @@ Full command syntax: [cli-reference.md](guides/cli-reference.md). Instance acces
| React to a condition matching | Trigger `recordCondition` (NOT recordMatchesConditions) | Official-teable key names |
| Button click starts a workflow | `buttonClick` trigger + Button field (`options.workflow` object) | Other trigger types |
| Receive external HTTP | `webhook`; test via `fire webhook` | Expecting auth (backend ignores it) |
| Schedule recurring job | `scheduledTime` (only `timing.type` + `interval` honored) | triggerTime/weekdays (ignored) |
| Schedule recurring job | `scheduledTime` (only `timing.type` + `interval` honored). If the user asks for specific clock times or weekdays, say up front that the backend ignores them and propose an interval instead | triggerTime/weekdays (ignored silently — configuring them looks fine but never fires as intended) |
| Per-row transformation | `script` action reading `triggerInput` | Row-by-row record API |
| Send notification | `sendNotification` (in-app) | `sendEmail` (needs SMTP) unless email required |
| **Inspect select-choice constraints** | `field list --with-options`, `field get <tbl> <name>`, `field visible-choices` | `--json` + manual filtering |
| **Write a select-field value** | Verify the value exists in choices first (`field list --with-options`); need a new value → `field update --options` to add the choice, then write | Blind `record update` (400, or typecast silently resolves to null / mutationApplied=false — it never creates choices) |
| Read records for later writes | `record get` (returns record IDs) | `sql` (no record IDs) |
| Analytics / JOIN | `sql` with GROUP BY | Fetch all records + compute |
| Store an API key for scripts | `secret set` → `process.env.NAME` | Hardcoding |
@@ -95,6 +87,7 @@ inkreach-teable trigger set wflXXX --type buttonClick --table-id tblXXX --field-
inkreach-teable action add wflXXX --type script --code 'return triggerInput.record.fields'
inkreach-teable workflow activate wflXXX
inkreach-teable fire button-click tblXXX recXXX fldXXX && inkreach-teable runs list wflXXX
# webhook 触发器的外部调用地址(无鉴权,激活后生效): {endpoint}/api/webhook/{baseId}/{workflowId} —— 用 fire webhook <baseId> <wflXXX> 本地测试
```
## 5. Common Errors & Recovery
@@ -110,3 +103,4 @@ inkreach-teable fire button-click tblXXX recXXX fldXXX && inkreach-teable runs l
| `runs list` shows failed | Action error | `runs get <wfId> <execId>` → `nodeResults[].error` |
| Script deps fail `spawn npm ENOENT` | `npm` not on service PATH | See Key Constraint 11 |
| Record write 400 "does not meet the current row conditions" | Value violates choice `visibilityFilter` | `field visible-choices` to see allowed options |
| Select-field write "succeeds" but value unchanged (or becomes null) | Value not in choices — without typecast the API 400s; with typecast it resolves to null and no-ops (`mutationApplied=false`), never creates the choice | `field list --with-options` to confirm; `field update --options` to add the choice, then write |
+34 -14
View File
@@ -4,30 +4,50 @@
{
"id": 1,
"name": "button-click-automation",
"prompt": "帮我在 order_logs 表里加一个按钮字段叫「催单」,点这个按钮的时候触发一个自动化,自动化里跑一段脚本,脚本内容是返回当前记录的订单号字段。然后激活它,点一下按钮测试看看执行结果。",
"expected_output": "应创建 button 字段(options.workflow.id 指向新 workflow)、创建 workflow、设 buttonClick 触发器、加 script action(读取 triggerInput.record.fields)、激活、fire button-click 测试、runs list 看到 succeeded。关键:button options 用 workflow 对象结构而非 workflowId 字符串。",
"files": []
"prompt": "帮我在 order_logs 表里加一个按钮字段(名字用:催单按钮{TAG}),点这个按钮的时候触发一个自动化,自动化里跑一段脚本,脚本返回当前记录的订单号。然后激活它,创建一条新记录并点一下按钮测试,确认执行结果。",
"expected_output": "创建 button 字段(options.workflow 为对象 {id,...})、workflow、buttonClick 触发器、读取 triggerInput 的 script action、activate、fire button-click 测试、runs 确认 succeeded。"
},
{
"id": 2,
"name": "scheduled-digest",
"prompt": "我想每天定时跑一次自动化,去查一下 order_logs 表里所有记录,把数量统计出来。帮我配一个定时自动化。",
"expected_output": "应创建 workflow、设 scheduledTime 触发器(timing.type + interval,不配后端忽略的字段如 triggerTime/weekdays)、加 getRecords action、加 script action 统计、激活。关键:知道 scheduledTime 后端只认 type+interval。",
"files": []
"prompt": "我想要每天定时跑一次自动化(工作流名字用:每日统计{TAG}),统计 order_logs 表里的记录数并输出。帮我配好。",
"expected_output": "scheduledTime 触发器只配 timing.type + timing.interval(后端忽略其他时间字段);script/getRecords action;activate。"
},
{
"id": 3,
"name": "record-condition-trigger",
"prompt": "当 order_logs 表里某条记录的「结果」字段变成「失败」的时候,自动发一封邮件通知到 ops@xingchen.tech。帮我配这个自动化。",
"expected_output": "应创建 workflow、设 recordCondition 触发器(用 conditions 而非 filter,category 是 recordCondition 而非 recordMatchesConditions)、加 sendEmail action、激活。关键:用正确的自研契约键名 conditions。",
"files": []
"name": "record-condition-email",
"prompt": "当 order_logs 表里有记录的「结果」字段变成「失败」时,自动发邮件通知 ops@xingchen.tech(工作流名字用:失败告警{TAG})。帮我配好。",
"expected_output": "recordCondition 触发器(分类是 recordCondition,条件键名是 conditions 而非 filter);sendEmail action 收件人 ops@xingchen.tech;activate。"
},
{
"id": 4,
"name": "debug-failed-run",
"prompt": "我有个自动化 wfl5F8dQRDloo28w909,最近好像没正常跑,帮我看看是什么情况。",
"expected_output": "应先 workflow get 查 isActive、再 runs list --status failed 查失败记录、runs get 看具体错误。关键:用排查流程而非盲目重新激活。",
"files": []
"prompt": "我有个自动化 wfl5F8dQRDloo28w909……不对,ID 是 {WFID},名字叫 nightly-sync,最近好像没正常跑,帮我看看是什么情况,别急着改,先诊断清楚再说。",
"expected_output": "先 workflow get 查 isActive → runs list --status failed → runs get 看 nodeResults 错误;指出脚本抛错(boom: upstream sync timeout);未盲目重新激活。"
},
{
"id": 5,
"name": "select-constraint-write",
"prompt": "在 {TBL5} 表里,把订单号为 {ORD} 的那条记录的「结果」改成「已完成」。另外告诉我这个「结果」字段目前一共有哪些可选值。",
"expected_output": "先查约束发现「已完成」不在 choices;要么说明并停下,要么先加选项再写入;不发生静默失败。"
},
{
"id": 6,
"name": "official-cli-near-miss",
"prompt": "用 teable 的 CLI 工具帮我列出这个基地里都有哪些表。",
"expected_output": "路由到 inkreach-teable CLI(官方 teable CLI 在该实例自动化契约不兼容);列出 order_logs;未安装/调用官方 teable CLI 打实例。"
},
{
"id": 7,
"name": "webhook-receiver",
"prompt": "帮我建一个自动化(名字:回单通知{TAG}),用 webhook 触发:外部系统会 POST 调用它。建好激活后,把可以调用的完整 URL 给我,并实际测试一次确认能通。",
"expected_output": "webhook 触发器;URL 形如 {endpoint}/api/webhook/{baseId}/{workflowId};用 fire webhook 或 curl 实测;已激活。"
},
{
"id": 8,
"name": "weekday-schedule-trap",
"prompt": "我要一个自动化(名字:工作日盘点{TAG}),每个工作日下午5点跑一次,统计 order_logs 表的记录条数就行。帮我配好。",
"expected_output": "知道后端只认 timing.type+interval:要么明确告知『具体时刻/星期』不被支持并给出周期间隔替代方案;绝不悄悄配 weekdays/triggerTime 假装生效。timing 配置无被忽略字段。"
}
]
}
}