feat(cli): 错误提示行动 hint + 发布门禁 + 分发收尾 (0.1.7)
- 错误输出附 hint: 404 Invalid tableId / Workflow not found / 路由不存在 / 401 / 403 / 非法选项值 / visibilityFilter 等 9 类模式,JSON 模式含 hint 字段 - prepublishOnly 门禁: build + test + 全命令冒烟(81 用例) - skills/ 移除 evals/(判分标准不随技能分发) + scripts/sync-skills.sh 一键同步 - 冒烟建库固定在 agent-playground 专用空间(生产保护红线)
This commit is contained in:
+3
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@inkreach/inkreach-teable",
|
"name": "@inkreach/inkreach-teable",
|
||||||
"version": "0.1.6",
|
"version": "0.1.7",
|
||||||
"description": "CLI (command: inkreach-teable) for operating inkreach-teable: tables, fields, records, views, and the self-built automation engine (8 triggers + 7 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",
|
"license": "MIT",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
@@ -26,7 +26,8 @@
|
|||||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"clean": "rimraf dist tsconfig.build.tsbuildinfo",
|
"clean": "rimraf dist tsconfig.build.tsbuildinfo",
|
||||||
"prepare": "npm run build"
|
"prepare": "npm run build",
|
||||||
|
"prepublishOnly": "npm run build && npm test && bash scripts/smoke-commands.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^12.1.0"
|
"commander": "^12.1.0"
|
||||||
|
|||||||
@@ -40,7 +40,10 @@ smoke() { # smoke <label> <cmd...>
|
|||||||
j() { python3 -c "import sys,json;d=json.load(sys.stdin);print(eval(sys.argv[1]))" "$1"; }
|
j() { python3 -c "import sys,json;d=json.load(sys.stdin);print(eval(sys.argv[1]))" "$1"; }
|
||||||
|
|
||||||
# ---------- sandbox ----------
|
# ---------- sandbox ----------
|
||||||
SPACE=$(curl -s -H "Authorization: Bearer $TOK" "$EP/api/space" | j "d[0]['id']")
|
SPACE=$(curl -s -H "Authorization: Bearer $TOK" "$EP/api/space" | python3 -c "
|
||||||
|
import sys,json
|
||||||
|
for sp in json.load(sys.stdin):
|
||||||
|
if 'agent-playground' in sp.get('name',''): print(sp['id']); break")
|
||||||
BASE=$(curl -s -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' \
|
BASE=$(curl -s -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' \
|
||||||
-X POST "$EP/api/base" -d "{\"spaceId\":\"$SPACE\",\"name\":\"cli-smoke-$(date +%s)\"}" | j "d['id']")
|
-X POST "$EP/api/base" -d "{\"spaceId\":\"$SPACE\",\"name\":\"cli-smoke-$(date +%s)\"}" | j "d['id']")
|
||||||
export INKREACH_TEABLE_BASE_ID="$BASE"
|
export INKREACH_TEABLE_BASE_ID="$BASE"
|
||||||
@@ -171,6 +174,16 @@ fi
|
|||||||
# ---------- plugin (reads only; register/install need a real bundle) ----------
|
# ---------- plugin (reads only; register/install need a real bundle) ----------
|
||||||
smoke "plugin list" $CLI plugin list
|
smoke "plugin list" $CLI plugin list
|
||||||
smoke "plugin installs" $CLI plugin installs
|
smoke "plugin installs" $CLI plugin installs
|
||||||
|
smoke "plugin csp list" $CLI plugin csp list
|
||||||
|
|
||||||
|
# ---------- auth token create/delete (self-cleaning PAT) ----------
|
||||||
|
PATID=$($CLI auth token create --name cli-smoke --json 2>/dev/null | j "d['id']") || PATID=""
|
||||||
|
if [ -n "${PATID:-}" ]; then
|
||||||
|
smoke "auth token create" $CLI auth token list
|
||||||
|
smoke "auth token delete" $CLI auth token delete "$PATID"
|
||||||
|
else
|
||||||
|
echo "warn auth token create returned no id (may need scopes) — check manually"
|
||||||
|
fi
|
||||||
|
|
||||||
# ---------- table delete (destructive, scratch only) ----------
|
# ---------- table delete (destructive, scratch only) ----------
|
||||||
smoke "table delete" $CLI table delete "$TBL"
|
smoke "table delete" $CLI table delete "$TBL"
|
||||||
|
|||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Sync the repo's skills/ to every installed agent skill dir on this host.
|
||||||
|
# Single source of truth: this repo's skills/ directory.
|
||||||
|
set -eu
|
||||||
|
REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
for d in ~/.zcode/skills ~/.agents/skills ~/.pi/agent/skills; do
|
||||||
|
[ -d "$d" ] || continue
|
||||||
|
for s in inkreach-teable inkreach-teable-plugin; do
|
||||||
|
rm -rf "$d/$s"
|
||||||
|
cp -r "$REPO/skills/$s" "$d/"
|
||||||
|
done
|
||||||
|
echo "synced -> $d"
|
||||||
|
done
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
{
|
|
||||||
"skill_name": "inkreach-teable",
|
|
||||||
"evals": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"name": "button-click-automation",
|
|
||||||
"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": "我想要每天定时跑一次自动化(工作流名字用:每日统计{TAG}),统计 order_logs 表里的记录数并输出。帮我配好。",
|
|
||||||
"expected_output": "scheduledTime 触发器只配 timing.type + timing.interval(后端忽略其他时间字段);script/getRecords action;activate。"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"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……不对,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 配置无被忽略字段。"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
+29
-1
@@ -7,6 +7,32 @@ import { resolveConfig } from './config';
|
|||||||
import { Client, ApiError } from './client';
|
import { Client, ApiError } from './client';
|
||||||
import { describeError } from './request';
|
import { describeError } from './request';
|
||||||
|
|
||||||
|
/** Actionable hints for recurring backend error shapes — the goal is that an
|
||||||
|
* agent (or human) reading the error knows the next command to try, instead
|
||||||
|
* of spending a round-trip re-deriving the cause. */
|
||||||
|
export function buildHint(status: number, message: string): string | undefined {
|
||||||
|
const m = message || '';
|
||||||
|
if (/Invalid tableId/i.test(m))
|
||||||
|
return '表不存在或不属于当前 base —— 用 `inkreach-teable table list` 核对 ID;确认 --base-id / INKREACH_TEABLE_BASE_ID 指向正确的基地。';
|
||||||
|
if (/Workflow not found/i.test(m))
|
||||||
|
return '工作流不存在(或已被软删除)—— 用 `inkreach-teable workflow list` 核对 ID;workflow delete 是软删除,列表里可能已不可见。';
|
||||||
|
if (/Cannot (GET|POST|PUT|PATCH|DELETE)/i.test(m))
|
||||||
|
return '路由不存在 —— CLI 与后端版本可能不匹配,升级 CLI:`npm i -g @inkreach/inkreach-teable`;仍失败则用 --debug 反馈。';
|
||||||
|
if (status === 401)
|
||||||
|
return '鉴权失败 —— `inkreach-teable auth status` 检查 token;必要时到 Web UI Settings → Personal Access Tokens 重新签发并 `auth login`。';
|
||||||
|
if (status === 403 || /restricted_resource|Forbidden/i.test(m))
|
||||||
|
return '权限不足 —— 该操作需要更高角色(base owner/creator)或 PAT 授权范围不含此 base;插件上传还需 plugin_developer 白名单。';
|
||||||
|
if (/Invalid value for field|Invalid option/i.test(m))
|
||||||
|
return '值不在字段合法选项内 —— `inkreach-teable field list <tableId> --with-options` 查看可选值;新增选项用 `field update <tableId> <field> --options`。';
|
||||||
|
if (/does not meet the current row conditions/i.test(m))
|
||||||
|
return '违反了字段的 visibilityFilter(行级条件)—— `inkreach-teable field visible-choices <tableId> <recordId> <field>` 查看该行可见选项。';
|
||||||
|
if (/expected array, received/i.test(m))
|
||||||
|
return '参数形状不符 —— 批量接口要数组;单条可改用 --fields,批量用 --records。';
|
||||||
|
if (/VALIDATION|Validation error/i.test(m) && status === 400)
|
||||||
|
return '请求校验失败 —— 用 --json 查看完整错误;对照 `<cmd> --help` 的参数示例。';
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Ctx {
|
export interface Ctx {
|
||||||
client: Client;
|
client: Client;
|
||||||
json: boolean;
|
json: boolean;
|
||||||
@@ -62,10 +88,12 @@ export function run<TArgs extends unknown[] = unknown[]>(
|
|||||||
await fn(ctx, positional, options);
|
await fn(ctx, positional, options);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const { status, code, message } = describeError(err);
|
const { status, code, message } = describeError(err);
|
||||||
|
const hint = status ? buildHint(status, message) : undefined;
|
||||||
if (ctx.json) {
|
if (ctx.json) {
|
||||||
process.stdout.write(JSON.stringify({ error: { status, code, message } }) + '\n');
|
process.stdout.write(JSON.stringify({ error: { status, code, message, ...(hint ? { hint } : {}) } }) + '\n');
|
||||||
} else {
|
} else {
|
||||||
process.stderr.write(`✗ ${code}${status ? ` (${status})` : ''}: ${message}\n`);
|
process.stderr.write(`✗ ${code}${status ? ` (${status})` : ''}: ${message}\n`);
|
||||||
|
if (hint) process.stderr.write(` hint: ${hint}\n`);
|
||||||
}
|
}
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user