refactor: move skill to skills/inkreach-teable (from .agents/skills/)
git mv rename (history preserved); install.sh + README updated. The skill is consumed via user-level install (~/.zcode/skills), so in-repo .agents discovery was unnecessary.
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
# Automation Guide
|
||||
|
||||
Automations (workflows) are event-driven: a **trigger** fires, then **actions** run in topological order. inkreach-teable's automation engine is self-built — use the `inkreach-teable` CLI (not official `teable`).
|
||||
|
||||
> **Required reading before configuring triggers**: [trigger-types.md](../api-reference/trigger-types.md)
|
||||
> **Required reading before writing script actions**: [action-types.md](../api-reference/action-types.md)
|
||||
|
||||
## Mental Model
|
||||
|
||||
```
|
||||
[Trigger] → [Action 1] → [Action 2] → ... → [triggerEnd]
|
||||
```
|
||||
|
||||
- A workflow has **exactly one trigger node** (type `trigger`) and N action nodes (type `action`).
|
||||
- Nodes connect via **edges** (linear chain; each node has at most one upstream — no parallel branches).
|
||||
- **Activating** freezes a snapshot; execution runs against the snapshot, not the draft.
|
||||
- `hasDraft: true` = unactivated changes exist; re-`activate` to apply.
|
||||
|
||||
## Available Commands
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `workflow list` | List all workflows in the base |
|
||||
| `workflow get <id>` | Detail (nodes + edges + config) |
|
||||
| `workflow create --name <n>` | Create (auto-adds a recordCreated trigger) |
|
||||
| `workflow update <id> [--name] [--description]` | Rename |
|
||||
| `workflow delete <id>` | Soft-delete |
|
||||
| `workflow activate <id>` | Snapshot + register trigger + create auto-token |
|
||||
| `workflow deactivate <id>` | Unregister trigger |
|
||||
| `workflow test <id>` | Mock-input test run (no trigger, no queue) |
|
||||
| `trigger set <id> --type <T> ...` | Set trigger type + config |
|
||||
| `action add <id> --type <T> ...` | Add an action node |
|
||||
| `action update <id> <nodeId> ...` | Update an action node |
|
||||
| `action delete <id> <nodeId>` | Remove an action node |
|
||||
| `secret set/list/delete` | Manage script secrets |
|
||||
| `runs list/get/rerun` | Execution history |
|
||||
| `fire button-click/webhook` | Manually trigger for testing |
|
||||
|
||||
## Trigger Selection
|
||||
|
||||
| User scenario | `--type` | Key options |
|
||||
|--------------|----------|-------------|
|
||||
| React to new records | `recordCreated` | `--table-id` |
|
||||
| React to record changes | `recordUpdated` | `--table-id`, `--field-ids` (empty = all) |
|
||||
| React when a condition matches | `recordCondition` | `--table-id`, `--conditions` (required) |
|
||||
| React to create OR update | `recordCreatedOrUpdated` | `--table-id`, `--conditions` (optional) |
|
||||
| Form submission | `formSubmitted` | `--table-id`, `--form-ids <csv>` |
|
||||
| Schedule (only type+interval work) | `scheduledTime` | `--schedule '{"timing":{"type":"minutes","interval":5}}'` |
|
||||
| Button field click | `buttonClick` | `--table-id`, `--field-ids` |
|
||||
| External HTTP POST | `webhook` | (none; auth backend-ignored) |
|
||||
|
||||
**Batch processing** (record triggers only): add `--batch '{"enabled":true,"batchWindow":2000,"batchSize":100}'` to merge events within a window into a single run. The script receives an array of records.
|
||||
|
||||
Full config details: [trigger-types.md](../api-reference/trigger-types.md).
|
||||
|
||||
## Action Selection
|
||||
|
||||
| Goal | `--type` | Key options |
|
||||
|------|----------|-------------|
|
||||
| Run JS code (transform, call APIs, compute) | `script` | `--code '<js>'` or `--code-file <path>`, `--timeout <ms>` |
|
||||
| Send email | `sendEmail` | `--to`, `--subject`, `--body <html>` |
|
||||
| Call external HTTP | `httpRequest` | `--url`, `--method`, `--headers <json>`, `--req-body <json>` |
|
||||
| Create records | `createRecord` | `--target-table <id>`, `--records <json>` |
|
||||
| Update records | `updateRecord` | `--target-table <id>`, `--records <json>` |
|
||||
| Query records | `getRecords` | `--target-table <id>`, `--query <json>` |
|
||||
| Send system notification (站内信) | `sendNotification` | `--to-user <usrId>`, `--title`, `--message`, `--severity`, `--path` |
|
||||
|
||||
Full config details: [action-types.md](../api-reference/action-types.md).
|
||||
|
||||
> ⚠️ `action update --config '<json>'` takes the **flat action config** (e.g. `{"code":"...","timeout":120000}`), NOT `{"config":{...}}`. A wrapped config deploys an empty script that "succeeds" in ~1ms with `result: {}` and breaks downstream `{{nodeId.result.*}}` references. After any `--config` deploy, verify with `workflow get --json` that `nodes[].config.code` is non-empty. Details: [action-types.md § script](../api-reference/action-types.md).
|
||||
|
||||
## Creation Workflow
|
||||
|
||||
```bash
|
||||
# 1. Gather IDs
|
||||
inkreach-teable table get tblXXX # confirm table
|
||||
inkreach-teable field list tblXXX # find field ids for triggers
|
||||
|
||||
# 2. Create workflow
|
||||
inkreach-teable workflow create --name "My automation"
|
||||
# → returns wflXXX
|
||||
|
||||
# 3. Set trigger
|
||||
inkreach-teable trigger set wflXXX --type recordUpdated \
|
||||
--table-id tblXXX --field-ids '["fldStatus"]'
|
||||
|
||||
# 4. Add action(s)
|
||||
inkreach-teable action add wflXXX --type script \
|
||||
--code 'return { status: triggerInput.record.fields.Status }'
|
||||
|
||||
# 5. (Optional) add secrets the script needs
|
||||
inkreach-teable secret set wflXXX --name API_KEY --value sk_xxx
|
||||
|
||||
# 6. Activate
|
||||
inkreach-teable workflow activate wflXXX
|
||||
# → returns snapshotId
|
||||
|
||||
# 7. Test
|
||||
inkreach-teable fire button-click tblXXX recXXX fldXXX # for buttonClick
|
||||
# or just perform the triggering action (create/update a record, POST webhook...)
|
||||
inkreach-teable runs list wflXXX
|
||||
```
|
||||
|
||||
## Editing an Existing Workflow
|
||||
|
||||
```bash
|
||||
# Change trigger config
|
||||
inkreach-teable trigger set wflXXX --type buttonClick --table-id tblXXX --field-ids '["fldNew"]'
|
||||
# Add another action
|
||||
inkreach-teable action add wflXXX --type sendEmail --to a@b.com --subject Done --body '<p>ok</p>'
|
||||
# Remove an action
|
||||
inkreach-teable action delete wflXXX node-12345
|
||||
# RE-ACTIVATE to snapshot the changes
|
||||
inkreach-teable workflow activate wflXXX
|
||||
```
|
||||
|
||||
`trigger set` / `action add/update/delete` internally do GET→modify→PUT the whole graph, so they're safe. But you **must re-activate** for changes to take effect at runtime.
|
||||
|
||||
## Secrets
|
||||
|
||||
Scripts often need API keys. Never hardcode — use secrets.
|
||||
|
||||
```bash
|
||||
inkreach-teable secret set wflXXX --name OPENAI_KEY --value sk-xxx --description "for AI calls"
|
||||
inkreach-teable secret list wflXXX # names only — value never returned
|
||||
inkreach-teable secret update wflXXX secXXX --value sk-new
|
||||
inkreach-teable secret delete wflXXX secXXX
|
||||
```
|
||||
|
||||
In a script action, read via `process.env.OPENAI_KEY`. Secrets are AES-encrypted at rest.
|
||||
|
||||
## Testing / Triggering
|
||||
|
||||
Most triggers fire from real events (record create/update, form submit, schedule). Two triggers are manually fireable for testing:
|
||||
|
||||
```bash
|
||||
# Button field click
|
||||
inkreach-teable fire button-click <tableId> <recordId> <fieldId>
|
||||
|
||||
# Webhook (no auth needed)
|
||||
inkreach-teable fire webhook <baseId> <workflowId> --body '{"event":"test"}'
|
||||
```
|
||||
|
||||
For other triggers, test by performing the real action (create/update a record, submit the form). Then inspect:
|
||||
|
||||
```bash
|
||||
inkreach-teable runs list wflXXX --status failed
|
||||
inkreach-teable runs get wflXXX <executionId>
|
||||
# rerun a failed execution
|
||||
inkreach-teable runs rerun wflXXX <executionId>
|
||||
```
|
||||
|
||||
### `workflow test` vs real fire
|
||||
|
||||
`workflow test <id>` runs actions synchronously with **mock input** (a fake
|
||||
`{record:{id:'rec_test',fields:{}}, user:{...}}`). It does NOT fire the
|
||||
trigger or use the queue. Two caveats:
|
||||
|
||||
- **Upstream outputs are absent in test mode** — if a script reads
|
||||
`triggerInput.record.fields.<X>`, it gets `{}` because the mock record has
|
||||
no fields. Test only validates that the script *runs without throwing*, not
|
||||
that it produces correct data.
|
||||
- For data-dependent validation, do a **real fire** (button-click / webhook)
|
||||
or perform the real triggering action, then check `runs list`.
|
||||
|
||||
## Variable References (cross-node data)
|
||||
|
||||
An action can reference upstream nodes' output in string fields via `{{nodeId.path}}` (or the compatible `{{index.path}}` positional form):
|
||||
|
||||
- `{{n0.record.id}}` — trigger record's id (n0 = trigger node)
|
||||
- `{{n0.record.fields.Status}}` — trigger record's Status field
|
||||
- `{{2.result}}` — output of the first action (numeric index form; index = topological execution order, trigger is always 1)
|
||||
|
||||
Both forms resolve to the same value — `{{n0.record.id}}` ≡ `{{1.record.id}}`. Use `{{nodeId.path}}` in new workflows for clarity.
|
||||
|
||||
> **Empty-value semantics**: if a variable resolves to an empty string, the node sees the literal empty value. For `sendNotification`, an empty `toUserId` makes the node **silently skip** (status `succeeded`, `sentCount: 0`) instead of failing — so a script action that returns an empty recipient implements "notify only when X" conditional patterns. An empty `message` still fails with `Missing required fields: message`.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Notify on status change
|
||||
```bash
|
||||
inkreach-teable workflow create --name "Status→email"
|
||||
inkreach-teable trigger set wflXXX --type recordUpdated --table-id tblXXX --field-ids '["fldStatus"]'
|
||||
inkreach-teable action add wflXXX --type sendEmail \
|
||||
--to ops@x.com --subject 'Status changed' \
|
||||
--body '{{n0.record.fields.Status}}'
|
||||
inkreach-teable workflow activate wflXXX
|
||||
```
|
||||
|
||||
### Daily digest
|
||||
```bash
|
||||
inkreach-teable trigger set wflXXX --type scheduledTime \
|
||||
--schedule '{"timing":{"type":"hours","interval":24}}'
|
||||
```
|
||||
|
||||
### Button → script → update another table
|
||||
```bash
|
||||
inkreach-teable trigger set wflXXX --type buttonClick --table-id tblA --field-ids '["fldGo"]'
|
||||
inkreach-teable action add wflXXX --type script --code '
|
||||
const r = triggerInput.record;
|
||||
// call back into the instance API using the auto-injected token
|
||||
const res = await fetch(triggerInput._apiBaseUrl + "/api/table/tblB/record", {
|
||||
method: "POST",
|
||||
headers: { Authorization: "Bearer " + triggerInput._accessToken, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ fields: { Source: r.id } })
|
||||
});
|
||||
return { created: await res.json() };
|
||||
'
|
||||
```
|
||||
Reference in New Issue
Block a user