@codemation/agent-skills 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +165 -0
- package/dist/metadata.json +358 -48
- package/package.json +3 -1
- package/skills/builder/ai-agent/SKILL.md +314 -0
- package/skills/builder/ai-agent/references/anti-patterns.md +24 -0
- package/skills/{codemation-cli → builder/cli}/SKILL.md +1 -8
- package/skills/builder/connect-external-systems/SKILL.md +191 -0
- package/skills/builder/credential-development/SKILL.md +86 -0
- package/skills/{codemation-credential-development → builder/credential-development}/references/credential-patterns.md +3 -3
- package/skills/builder/custom-node-development/SKILL.md +61 -0
- package/skills/builder/custom-node-development/references/credential-aware-nodes.md +52 -0
- package/skills/builder/custom-node-development/references/define-batch-node.md +54 -0
- package/skills/{codemation-custom-node-development → builder/custom-node-development}/references/define-node-per-item.md +14 -14
- package/skills/{codemation-custom-node-development → builder/custom-node-development}/references/node-patterns.md +33 -49
- package/skills/builder/document-ai/SKILL.md +167 -0
- package/skills/builder/execution-context/SKILL.md +436 -0
- package/skills/{codemation-framework-concepts → builder/framework-concepts}/SKILL.md +10 -18
- package/skills/builder/gmail/SKILL.md +327 -0
- package/skills/builder/human-in-the-loop/SKILL.md +82 -0
- package/skills/{codemation-mcp-capabilities → builder/mcp-capabilities}/SKILL.md +4 -11
- package/skills/{codemation-mcp-capabilities → builder/mcp-capabilities}/references/agent-with-mcp.ts +1 -1
- package/skills/builder/msgraph/SKILL.md +338 -0
- package/skills/builder/odoo/SKILL.md +498 -0
- package/skills/{codemation-plugin-development → builder/plugin-development}/SKILL.md +4 -7
- package/skills/{codemation-plugin-development → builder/plugin-development}/references/plugin-anatomy.md +36 -15
- package/skills/{codemation-plugin-development → builder/plugin-development}/references/plugin-structure.md +2 -2
- package/skills/builder/rest-node/SKILL.md +148 -0
- package/skills/builder/testing/SKILL.md +142 -0
- package/skills/builder/workflow-dsl/SKILL.md +493 -0
- package/skills/builder/workspace-files/SKILL.md +191 -0
- package/skills/concierge/credentials/SKILL.md +91 -0
- package/skills/concierge/intake-automation-playbook/SKILL.md +78 -0
- package/skills/concierge/scenario-invoice-to-accounting/SKILL.md +48 -0
- package/skills/concierge/scenario-procurement-intake/SKILL.md +58 -0
- package/skills/codemation-ai-agent-node/SKILL.md +0 -66
- package/skills/codemation-ai-agent-node/references/anti-patterns.md +0 -11
- package/skills/codemation-credential-development/SKILL.md +0 -57
- package/skills/codemation-custom-node-development/SKILL.md +0 -61
- package/skills/codemation-custom-node-development/references/credential-aware-nodes.md +0 -38
- package/skills/codemation-custom-node-development/references/define-batch-node.md +0 -38
- package/skills/codemation-document-scanner/SKILL.md +0 -136
- package/skills/codemation-workflow-dsl/SKILL.md +0 -78
- package/skills/codemation-workflow-dsl/references/builder-patterns.md +0 -120
- package/skills/codemation-workflow-dsl/references/complete-example.md +0 -263
- package/skills/codemation-workflow-dsl/references/workflow-testing.md +0 -194
- package/skills/codemation-workspace-files/SKILL.md +0 -142
- /package/skills/{codemation-cli → builder/cli}/references/command-map.md +0 -0
- /package/skills/{codemation-framework-concepts → builder/framework-concepts}/references/architecture-map.md +0 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: workspace-files
|
|
3
|
+
description: Reads and writes files in the platform's shared workspace pool from inside a running workflow — listWorkspaceFilesNode, readWorkspaceFileNode, writeWorkspaceFileNode. Use this whenever an automation needs a file the platform manages (a lookup table, a config sheet, a concierge-derived JSON) or produces a file back into the pool.
|
|
4
|
+
compatibility: Codemation core-nodes-workspace-files. Requires WORKSPACE_ID and BLOB_STORAGE_* env vars.
|
|
5
|
+
tags: workspace, files, pool, binary, storage, read, write, list, csv, json, lookup
|
|
6
|
+
uses: "@codemation/core-nodes-workspace-files"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Codemation Workspace Files
|
|
10
|
+
|
|
11
|
+
## The lifecycle — how an automation gets at a platform-managed file
|
|
12
|
+
|
|
13
|
+
The workspace **file pool** is shared storage (S3-backed) that the platform manages. A user does not
|
|
14
|
+
hand a file to a workflow directly. The real flow is:
|
|
15
|
+
|
|
16
|
+
1. A user **uploads a heavy file** (e.g. an Excel product catalogue) through the platform.
|
|
17
|
+
2. The **concierge** has the coding agent **transform** it into a workflow-friendly shape (e.g. a flat
|
|
18
|
+
JSON keyed by SKU) and **stores the result** in the pool under a known filename.
|
|
19
|
+
3. A **running automation reads that stored file** by filename (or pinned id) via the nodes below — and
|
|
20
|
+
does its work with the data.
|
|
21
|
+
|
|
22
|
+
The automation is **decoupled** from the upload: it reads whatever the current version of that filename
|
|
23
|
+
is, every run. Two concrete use cases:
|
|
24
|
+
|
|
25
|
+
- **Product database for lookups.** A nightly order workflow reads `products.json` from the pool to
|
|
26
|
+
enrich each line item with price and description — no external API call, the sheet is the source.
|
|
27
|
+
- **Leads router.** An inbound-lead webhook reads a `zipcode-regions.json` table from the pool, maps the
|
|
28
|
+
lead's zipcode to a region, and routes it to the right regional manager.
|
|
29
|
+
|
|
30
|
+
Workflows can also **write** files back into the pool (`writeWorkspaceFileNode`) when a step produces a
|
|
31
|
+
derived artifact other runs or the concierge should pick up — but **read is the headline pattern**.
|
|
32
|
+
|
|
33
|
+
**Bytes always flow through `item.binary`**, never as base64 on `item.json`. Read nodes stream a file's
|
|
34
|
+
bytes into a binary slot; you parse them in the next step. Use `workflow-dsl` for the
|
|
35
|
+
surrounding builder.
|
|
36
|
+
|
|
37
|
+
## A complete read-and-use workflow
|
|
38
|
+
|
|
39
|
+
The concierge stored `products.json` in the pool. This webhook-triggered workflow reads it (latest-wins),
|
|
40
|
+
parses the JSON straight off the binary slot with `ctx.binary.getJson`, and looks a SKU up.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { createWorkflowBuilder, WebhookTrigger, Callback } from "@codemation/core-nodes";
|
|
44
|
+
import type { NodeExecutionContext } from "@codemation/core";
|
|
45
|
+
import { readWorkspaceFileNode } from "@codemation/core-nodes-workspace-files";
|
|
46
|
+
|
|
47
|
+
type FileMeta = {
|
|
48
|
+
fileId: string;
|
|
49
|
+
filename: string;
|
|
50
|
+
contentType: string;
|
|
51
|
+
size: number;
|
|
52
|
+
lastModified: string;
|
|
53
|
+
binarySlot: string;
|
|
54
|
+
};
|
|
55
|
+
type Product = { sku: string; name: string; price: number };
|
|
56
|
+
|
|
57
|
+
export default createWorkflowBuilder({ id: "wf.product-lookup", name: "Look up product from pool" })
|
|
58
|
+
.trigger(new WebhookTrigger("Order in", { endpointKey: "order", methods: ["POST"] }))
|
|
59
|
+
// Read the newest "products.json" — a fresher upload is picked up on the next run.
|
|
60
|
+
.then(
|
|
61
|
+
readWorkspaceFileNode.create(
|
|
62
|
+
{ filename: "products.json", binarySlot: "data" },
|
|
63
|
+
"Read product DB",
|
|
64
|
+
"read-product-db",
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
// Parse the bytes from the binary slot — never base64 on item.json.
|
|
68
|
+
.then(
|
|
69
|
+
new Callback<FileMeta, { count: number; first?: Product }>(
|
|
70
|
+
"Parse products",
|
|
71
|
+
async (items, ctx: NodeExecutionContext) => {
|
|
72
|
+
const results: Array<{ json: { count: number; first?: Product } }> = [];
|
|
73
|
+
for (const item of items) {
|
|
74
|
+
const attachment = item.binary?.["data"];
|
|
75
|
+
if (!attachment) throw new Error('No binary at slot "data"');
|
|
76
|
+
const products = await ctx.binary.getJson<Product[]>(attachment);
|
|
77
|
+
results.push({ json: { count: products.length, first: products[0] } });
|
|
78
|
+
}
|
|
79
|
+
return results;
|
|
80
|
+
},
|
|
81
|
+
{ id: "parse-products" },
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
.build();
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Resolution modes (read)
|
|
88
|
+
|
|
89
|
+
| Mode | Config | Behaviour |
|
|
90
|
+
| ------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| **latest-wins** (default) | `filename: "products.json"` | Reads the **newest** file with that name. Next upload of the same name is what the next run reads. |
|
|
92
|
+
| **pinned fileId** | `fileId: "abc123..."` | Reads that exact, immutable version forever — a new upload never changes it. Takes precedence over `filename`. |
|
|
93
|
+
|
|
94
|
+
Use **latest-wins** for "always use the current sheet". Use a **pinned fileId** for reproducible /
|
|
95
|
+
auditable runs. Discover ids with `listWorkspaceFilesNode`.
|
|
96
|
+
|
|
97
|
+
## Binary slot handoff
|
|
98
|
+
|
|
99
|
+
`readWorkspaceFileNode` streams the file's bytes into `item.binary[binarySlot]` (default `"data"`) and
|
|
100
|
+
emits this metadata on `item.json`:
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
{
|
|
104
|
+
fileId: string;
|
|
105
|
+
filename: string;
|
|
106
|
+
contentType: string;
|
|
107
|
+
size: number; // bytes
|
|
108
|
+
lastModified: string; // ISO 8601
|
|
109
|
+
binarySlot: string; // e.g. "data"
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Read the bytes downstream via `ctx.binary.getJson<T>(attachment)` (bounded read + decode + parse, the
|
|
114
|
+
clean default for JSON), `ctx.binary.getBytes(attachment)` (raw `Uint8Array`), or
|
|
115
|
+
`ctx.binary.openReadStream(attachment)` for streaming. The bytes are never on `item.json`.
|
|
116
|
+
|
|
117
|
+
## Node reference
|
|
118
|
+
|
|
119
|
+
Import every node from `@codemation/core-nodes-workspace-files`.
|
|
120
|
+
|
|
121
|
+
### `listWorkspaceFilesNode` — discover what's in the pool
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { listWorkspaceFilesNode } from "@codemation/core-nodes-workspace-files";
|
|
125
|
+
|
|
126
|
+
const list = listWorkspaceFilesNode.create(
|
|
127
|
+
{ filenameFilter: "products" }, // optional case-insensitive substring; omit to list all
|
|
128
|
+
"List product files",
|
|
129
|
+
"list-product-files",
|
|
130
|
+
);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Emits one item per file (newest-first): `{ fileId, filename, contentType, size, lastModified }`. Useful
|
|
134
|
+
to drive a fan-out (one item per file) or to resolve a fileId for pinned reads.
|
|
135
|
+
|
|
136
|
+
### `readWorkspaceFileNode` — read a file's bytes
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { readWorkspaceFileNode } from "@codemation/core-nodes-workspace-files";
|
|
140
|
+
|
|
141
|
+
const read = readWorkspaceFileNode.create(
|
|
142
|
+
{
|
|
143
|
+
filename: "products.json", // latest-wins; or use fileId for a pinned version
|
|
144
|
+
binarySlot: "data", // where the bytes land on item.binary — default "data"
|
|
145
|
+
maxBytes: 5 * 1024 * 1024, // cap before streaming — default 100 MiB
|
|
146
|
+
},
|
|
147
|
+
"Read products",
|
|
148
|
+
"read-products",
|
|
149
|
+
);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Either `filename` or `fileId` must be set (`fileId` wins). Output: the metadata JSON above + bytes in
|
|
153
|
+
`item.binary[binarySlot]`.
|
|
154
|
+
|
|
155
|
+
### `writeWorkspaceFileNode` — produce a file back into the pool
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { writeWorkspaceFileNode } from "@codemation/core-nodes-workspace-files";
|
|
159
|
+
|
|
160
|
+
const write = writeWorkspaceFileNode.create(
|
|
161
|
+
{
|
|
162
|
+
filename: "daily-summary.json", // required — the name in the pool / concierge view
|
|
163
|
+
binarySlot: "data", // slot holding the bytes to write — default "data"
|
|
164
|
+
contentType: "application/json", // stamped on the stored object — default application/octet-stream
|
|
165
|
+
},
|
|
166
|
+
"Write daily summary",
|
|
167
|
+
"write-daily-summary",
|
|
168
|
+
);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Reads bytes from `item.binary[binarySlot]` and writes them under a new fileId. Output:
|
|
172
|
+
`{ fileId, filename, contentType, size, key, registered }`. In managed (CP-paired) mode it also registers
|
|
173
|
+
the file so the concierge can list/read it; until the CP register endpoint ships, `registered` is `false`
|
|
174
|
+
while the bytes are always written.
|
|
175
|
+
|
|
176
|
+
## Gotchas
|
|
177
|
+
|
|
178
|
+
- **Bytes go through `item.binary`, never base64 on `item.json`.** Read nodes attach to a slot; write
|
|
179
|
+
nodes read from a slot. Attach bytes with `ctx.binary` (or an upstream node) before writing.
|
|
180
|
+
- **Read the derived file, not the raw upload.** Point workflows at the concierge-produced JSON/CSV in
|
|
181
|
+
the pool — not at a user's raw PDF/Excel. The transform is the concierge's job.
|
|
182
|
+
- **`WORKSPACE_ID` / `BLOB_STORAGE_*` must be set.** Outside a configured workspace (e.g. plain local dev
|
|
183
|
+
without CP integration) the storage adapter is unavailable and the node throws — guard if a workflow
|
|
184
|
+
may run in that mode.
|
|
185
|
+
- **Stable node ids.** Set an explicit `nodeId`; the engine slugifies the label otherwise, so renaming
|
|
186
|
+
re-keys the node.
|
|
187
|
+
|
|
188
|
+
## Read next when needed
|
|
189
|
+
|
|
190
|
+
- `workflow-dsl` — builder, triggers, flow control, the per-item contract.
|
|
191
|
+
- `document-ai` — scan a file's bytes (e.g. read a PDF, then scan it).
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: credentials
|
|
3
|
+
description: Explains how the concierge connects a credential — the slot/type/instance model and how it presents and binds API-key, OAuth2, and MCP credentials. Use whenever a workflow needs an external account connected (an API key, an OAuth login, an MCP server) before it can run.
|
|
4
|
+
compatibility: Concierge planning skill — conversation + binding guidance, no code.
|
|
5
|
+
tags: audience:concierge, credentials, oauth, mcp, api-key, binding
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Codemation Credentials
|
|
9
|
+
|
|
10
|
+
## Mental model
|
|
11
|
+
|
|
12
|
+
Four words run through everything here:
|
|
13
|
+
|
|
14
|
+
- **Credential TYPE** — a schema for one kind of connection (e.g. "Odoo API", "Gmail").
|
|
15
|
+
It declares `publicFields` (config, e.g. base URL) and `secretFields` (secret material, e.g.
|
|
16
|
+
an API key or token).
|
|
17
|
+
- **Credential INSTANCE** — one concrete connection the owner has set up against a type (their
|
|
18
|
+
actual Odoo, their actual mailbox). A type can have several instances (personal vs work Gmail).
|
|
19
|
+
- **SLOT** — a credential requirement a node declares. The binding key is
|
|
20
|
+
`(workflowId, nodeId, slotKey)`.
|
|
21
|
+
- **BIND** — pointing a slot at an instance, so the node authenticates with that connection at run time.
|
|
22
|
+
|
|
23
|
+
The division of labour: **the builder DECLARES and USES** slots (a node says "I need an Odoo
|
|
24
|
+
credential"); **the concierge BINDS** an instance into each slot. A workflow can't be activated while
|
|
25
|
+
any required slot has no healthy bound instance — so binding is the concierge's job to close out.
|
|
26
|
+
|
|
27
|
+
## Forms render themselves — you collect and bind
|
|
28
|
+
|
|
29
|
+
You do **not** hand-build credential forms. The control-plane UI and the credential tools render the
|
|
30
|
+
fields automatically from the type's `publicFields` / `secretFields` schema served by the host. Each
|
|
31
|
+
field carries its own presentation: `type` (`string` / `password` / `textarea` / `json` / `boolean`),
|
|
32
|
+
whether it's `required`, and `visibility: "advanced"` (renders in a collapsed section for power-user
|
|
33
|
+
fields). You pick the right credential type, walk the owner through the rendered fields, and bind.
|
|
34
|
+
|
|
35
|
+
**Secrets go straight to the host, never into the chat.** When the owner types an API key or password,
|
|
36
|
+
it is submitted directly to the host as secret material — you never echo it, store it in the
|
|
37
|
+
conversation, or pass it through a tool argument. Public config (a base URL, a database name) is fine
|
|
38
|
+
in conversation; secret material is not.
|
|
39
|
+
|
|
40
|
+
## The three presentation patterns
|
|
41
|
+
|
|
42
|
+
These are three _shapes the form takes_, not three different credential systems. Only OAuth2 is a
|
|
43
|
+
distinct auth flow under the hood — API-key and MCP both reduce to "render the schema, then bind".
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
API-key / basic-auth credential
|
|
47
|
+
- the type declares plain publicFields + secretFields (e.g. baseUrl + apiKey, or username + password)
|
|
48
|
+
- you: surface the form → owner fills it → secrets go to the host → test → bind to the slot
|
|
49
|
+
- the host's test() probes the real service; a "failing" result blocks activation, so resolve it first
|
|
50
|
+
|
|
51
|
+
OAuth2 credential (redirect flow)
|
|
52
|
+
- the type carries an oauth2 auth definition (provider, scopes, authorize/token URLs)
|
|
53
|
+
- there is NO secret field to type — the token comes from the redirect, not from chat
|
|
54
|
+
- you: surface the connection → owner clicks Connect → redirected to the provider → grants access
|
|
55
|
+
→ the host stores the returned token as material → bind the resulting instance to the slot
|
|
56
|
+
- if a client id / secret field is part of publicFields, those are config, not the user's secret
|
|
57
|
+
|
|
58
|
+
MCP server credential
|
|
59
|
+
- an MCP server (from the control-plane registry) declares acceptedCredentialTypes — usually an
|
|
60
|
+
OAuth connection (e.g. Gmail). MCP is NOT its own credential kind; you bind an instance of the
|
|
61
|
+
accepted type exactly as above
|
|
62
|
+
- one instance can serve both a node (e.g. a Gmail trigger) and the MCP server in the same workflow
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## How a binding gets done
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
1. find the workflow's open credential slots (the builder declared them on specific nodes)
|
|
69
|
+
2. for each slot, identify the credential type it accepts
|
|
70
|
+
3. is there already a healthy instance of that type? → reuse it; otherwise set one up:
|
|
71
|
+
- API-key/basic: present the form → collect → secrets to host → test
|
|
72
|
+
- OAuth2 (incl. most MCP): present Connect → owner completes the redirect
|
|
73
|
+
4. bind the instance to the slot
|
|
74
|
+
5. confirm every required slot is bound and healthy → only then can the workflow activate
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## The things owners forget — surface them
|
|
78
|
+
|
|
79
|
+
- **Connect before they expect to.** A workflow that "looks built" still won't run until its slots are
|
|
80
|
+
bound. Drive the owner through connecting accounts as part of finishing, not after they hit an error.
|
|
81
|
+
- **Reuse, don't re-ask.** If a healthy instance of the right type already exists, bind it rather than
|
|
82
|
+
making the owner reconnect.
|
|
83
|
+
- **A failing test is a blocker, not a warning.** The host tests a credential on Connect and before
|
|
84
|
+
activation. Treat a "failing" status as work to resolve (wrong key, bad URL), not something to skip.
|
|
85
|
+
- **One real test before live.** After binding, run the workflow once on real input so the owner sees
|
|
86
|
+
the connection actually working end to end.
|
|
87
|
+
|
|
88
|
+
## Read next when needed
|
|
89
|
+
|
|
90
|
+
- `credential-development` — the builder side: declaring a credential type and node slots.
|
|
91
|
+
- `mcp-capabilities` — discover MCP server ids and their accepted credential types.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: intake-automation-playbook
|
|
3
|
+
description: Scopes an INTAKE automation (something arrives → gets understood → an action happens) and proactively surfaces the dimensions owners forget — traceability, duplicate-handling, human-review, testing on real data. Use whenever an owner wants to automate handling of incoming emails, messages, orders, leads, or form submissions.
|
|
4
|
+
compatibility: Concierge planning skill — plain-language conversation guidance, no code.
|
|
5
|
+
tags: audience:concierge, audience:concierge-only, intake, automation, planning
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Intake automation playbook
|
|
9
|
+
|
|
10
|
+
## Mental model
|
|
11
|
+
|
|
12
|
+
Most automations a business owner asks for are the same shape: an **intake automation**. Something
|
|
13
|
+
arrives → it gets understood → an action happens → the loop is closed. "Process my order emails", "new
|
|
14
|
+
leads into my CRM", "contact-form submissions into a spreadsheet" are all this one pattern.
|
|
15
|
+
|
|
16
|
+
Your job is not to transcribe what the owner asks — it's to recognise the pattern and **proactively fill
|
|
17
|
+
in the parts they don't know to ask for**. A non-technical owner describes the happy path; they rarely
|
|
18
|
+
mention what happens when the same email arrives twice, when the AI misreads a total, or how they'll
|
|
19
|
+
know it works on _their_ data. You supply that missing input.
|
|
20
|
+
|
|
21
|
+
## When to use
|
|
22
|
+
|
|
23
|
+
The moment a request looks like intake: an incoming thing (email / message / order / lead / form /
|
|
24
|
+
document) that should be processed into an action. Walk the owner through the dimensions below,
|
|
25
|
+
**suggesting** the ones they didn't raise — as recommendations, not a checklist. Don't use this for
|
|
26
|
+
one-off tasks, reports with no incoming trigger, or pure data lookups.
|
|
27
|
+
|
|
28
|
+
## The nine dimensions
|
|
29
|
+
|
|
30
|
+
The owner usually volunteers 1, 2, and 7. **You** raise the rest.
|
|
31
|
+
|
|
32
|
+
1. **Trigger — what starts it.** Confirm the exact source (which inbox / label / endpoint).
|
|
33
|
+
2. **Ingest / understand — turn messy input into clean data.** Read PDFs and free text, pull out the fields.
|
|
34
|
+
3. **Match + link + report.** The one most underestimated: each company / contact / product must be
|
|
35
|
+
matched to the record in their system and linked — and whatever can't be matched is reported, not
|
|
36
|
+
silently dropped.
|
|
37
|
+
4. **Human-review — a person checks before it commits, when it matters.** Pause on low-confidence or
|
|
38
|
+
unusual cases for the owner's OK.
|
|
39
|
+
5. **Traceability / audit trail — keep the original** so a human can reconstruct what happened. This is
|
|
40
|
+
what makes them trust it.
|
|
41
|
+
6. **Idempotency — don't process the same thing twice.** A re-sent or forwarded item must not double-enter.
|
|
42
|
+
7. **Act — the actual output.** Be concrete: create the sale order, print the ticket, add the contact.
|
|
43
|
+
8. **Notify / close the loop.** Reply to confirm or send a summary; mark the item handled.
|
|
44
|
+
9. **Testability — the owner verifies on THEIR real data, no code.** Let them drop ~5 real items into a
|
|
45
|
+
test path and see exactly what it would do before switching it on. For a mailbox workflow this means a
|
|
46
|
+
**designated test folder** — ask for the folder/label name, resolve its id (via MCP), and pass it to
|
|
47
|
+
the builder. Fabricating fixtures defeats the point: the test must exercise the real processing on the
|
|
48
|
+
real input shape. See the `testing` skill for the principle and the integration skill (e.g. `gmail`)
|
|
49
|
+
for the concrete mechanism.
|
|
50
|
+
|
|
51
|
+
## Per-vertical extensions
|
|
52
|
+
|
|
53
|
+
The nine are the universal core; each vertical adds its own concerns. Pull the matching scenario skill
|
|
54
|
+
if one exists (e.g. `scenario-procurement-intake`,
|
|
55
|
+
`scenario-invoice-to-accounting`), or reason from the same shape: what's the domain's
|
|
56
|
+
system-of-record (3), what's domain-specific to verify before committing (4), what counts as a
|
|
57
|
+
duplicate (6)? If no scenario skill exists, the nine still apply.
|
|
58
|
+
|
|
59
|
+
## Using it in conversation — not an interrogation
|
|
60
|
+
|
|
61
|
+
- Lead with the owner's goal; weave the forgotten dimensions in as **suggestions you recommend**. Two or
|
|
62
|
+
three well-placed "I'd also set it up to…" beats nine blunt questions.
|
|
63
|
+
- Prioritise the high-value forgotten ones — **traceability, duplicate-handling, human-review, and a way
|
|
64
|
+
to test on real data**. These are what owners are most grateful you raised.
|
|
65
|
+
- Fold them into the plan you present, so it visibly covers them — that plan becomes the brief the
|
|
66
|
+
builder works from.
|
|
67
|
+
|
|
68
|
+
## Anti-patterns
|
|
69
|
+
|
|
70
|
+
- **Mirroring the naive input** — building exactly the happy path, omitting traceability / dedup /
|
|
71
|
+
review / testing. That's the failure this skill exists to prevent.
|
|
72
|
+
- **Interrogating** — firing all nine as questions. Suggest and recommend instead.
|
|
73
|
+
- **Recognise-without-link** — "the AI read the order" isn't done. If matched entities aren't linked into
|
|
74
|
+
the system of record and the unmatched aren't reported, a human has to redo it.
|
|
75
|
+
- **Silent skips on the hard cases** — low-confidence or unmatched items must surface, never be quietly
|
|
76
|
+
guessed or dropped.
|
|
77
|
+
- **Leaking how it's built** — keep it plain-language outcomes; never mention nodes, tools, or code to
|
|
78
|
+
the owner.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scenario-invoice-to-accounting
|
|
3
|
+
description: Plans an invoice-to-accounting automation — an incoming invoice is scanned, its vendor/amount/line-items extracted, and posted to an accounting system, with low-confidence ones held for review. Use when an owner wants supplier invoices read off and pushed into their accounting system automatically.
|
|
4
|
+
tags: scenario, invoice, accounting, ocr, document, confidence, audience:concierge
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Invoice → accounting — scenario guidance
|
|
8
|
+
|
|
9
|
+
A business receives supplier invoices and wants the key fields read off and pushed into their
|
|
10
|
+
accounting system automatically — with the uncertain ones held back for a person to check. This is the
|
|
11
|
+
shape to plan from; build each step from the skills listed below.
|
|
12
|
+
|
|
13
|
+
## The flow
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
on invoice arriving (webhook / upload / email)
|
|
17
|
+
→ scan the document (invoice analyzer, per-field confidence)
|
|
18
|
+
→ normalise the fields (vendor, invoice number, date, total, tax, line items)
|
|
19
|
+
→ if any key field's confidence is below threshold → human review, else continue
|
|
20
|
+
→ post the structured invoice to the accounting API
|
|
21
|
+
→ keep the original document + the posted result audited
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## What to ask the owner first
|
|
25
|
+
|
|
26
|
+
- How do invoices arrive — a webhook/upload, an email attachment, a watched folder?
|
|
27
|
+
- Which accounting system, and what's its API for creating a bill/expense? Which fields does it need?
|
|
28
|
+
- What confidence is "safe to auto-post" vs "send to a human"? (the scanner returns per-field confidence)
|
|
29
|
+
- Which fields matter — vendor, invoice number, date, total, tax, line items?
|
|
30
|
+
- How will they test it before going live — which ~5 real invoices?
|
|
31
|
+
|
|
32
|
+
## Build it from these skills
|
|
33
|
+
|
|
34
|
+
- `workflow-dsl` — the builder, trigger, and flow-control spine. Start here.
|
|
35
|
+
- `workflow-dsl` — receive the invoice by webhook/upload; or `gmail` if it arrives by email.
|
|
36
|
+
- `document-ai` — the invoice analyzer with `includeConfidence` for per-field confidence.
|
|
37
|
+
- `workflow-dsl` — normalise the extracted fields into the shape the accounting API wants.
|
|
38
|
+
- `human-in-the-loop` — the confidence gate that routes uncertain invoices to review.
|
|
39
|
+
- `workflow-dsl` — post to the accounting API.
|
|
40
|
+
- `credentials` — connect the accounting API and bind it to the slot the builder declared.
|
|
41
|
+
- `testing` — let them dry-run on real invoices before it goes live.
|
|
42
|
+
|
|
43
|
+
## The things owners forget — surface them
|
|
44
|
+
|
|
45
|
+
- **Gate on confidence** — don't auto-post a low-confidence amount; route it to review.
|
|
46
|
+
- **Idempotency** — the same invoice arriving twice must not post twice (dedupe on invoice number).
|
|
47
|
+
- **Traceability** — keep the original document and a record of what was posted.
|
|
48
|
+
- **Testability is part of done** — let them dry-run on a handful of real invoices before going live.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scenario-procurement-intake
|
|
3
|
+
description: Plans an order-intake automation — order emails are read, matched into the customer's ERP, and turned into sales orders, with a human in the loop for the uncertain ones. Use when an owner wants incoming purchase orders processed into their ERP (Odoo, AFAS, Salesforce, …) automatically.
|
|
4
|
+
tags: scenario, procurement, order-intake, erp, gmail, ocr, audience:concierge
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Procurement order-intake — scenario guidance
|
|
8
|
+
|
|
9
|
+
A dealer or distributor receives purchase orders by email — usually a PDF — and wants them turned into
|
|
10
|
+
sales orders in their ERP automatically and safely, with a human checking the uncertain ones. The ERP
|
|
11
|
+
is **whatever the owner uses** (Odoo, AFAS, Salesforce, …) — ask, don't assume. This is the shape to
|
|
12
|
+
plan from; build each step from the skills listed below.
|
|
13
|
+
|
|
14
|
+
## The flow
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
on new mail @ "orders" label
|
|
18
|
+
→ OCR the attachment + read the body
|
|
19
|
+
→ [agent] router : is this really an order? (order / not-order / needs-human)
|
|
20
|
+
→ [agent] extractor : company, buyer, PO number, line items (description, code, qty) as structured output
|
|
21
|
+
→ erp-sync : MATCH company + contacts + products in the customer's ERP, LINK them into a new
|
|
22
|
+
sales order, REPORT anything unmatched — never silently drop it
|
|
23
|
+
→ if low-confidence or unusual → human review gate, else continue
|
|
24
|
+
→ create the sales order (header + lines for the matched items)
|
|
25
|
+
→ reply to confirm + label the mail processed
|
|
26
|
+
→ keep the original email + PDF audited throughout
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## What to ask the owner first
|
|
30
|
+
|
|
31
|
+
- Which Gmail label do order emails land on? Are orders a PDF attachment, inline text, or both?
|
|
32
|
+
- **Which ERP / business system do they use** (Odoo, AFAS, Salesforce, …)? products matched by code/SKU?
|
|
33
|
+
existing customers to match? which company owns the order?
|
|
34
|
+
- The buyer: is it the sender, or a buyer address inside the mail? any company to exclude (their own)?
|
|
35
|
+
default country / currency?
|
|
36
|
+
- How will they test it before going live — which ~5 real order emails?
|
|
37
|
+
|
|
38
|
+
## Build it from these skills
|
|
39
|
+
|
|
40
|
+
- `workflow-dsl` — the builder, trigger, and flow-control spine. Start here.
|
|
41
|
+
- `gmail` — trigger on the label; reply and label the mail processed.
|
|
42
|
+
- `document-ai` — OCR the attachment (managed, no Azure key).
|
|
43
|
+
- `ai-agent` — the router and the extractor (with an output schema). The agents classify and extract;
|
|
44
|
+
the ERP sync itself is deterministic integration work, not an agent.
|
|
45
|
+
- `connect-external-systems` — **how to reach the ERP**: once you know which ERP the owner uses, follow
|
|
46
|
+
the routing rule — a specialized node if one exists (e.g. `odoo`), else an MCP-backed agent, else
|
|
47
|
+
`defineRestNode`. Never hand-roll the ERP's HTTP/JSON-RPC in a Callback.
|
|
48
|
+
- `human-in-the-loop` — the review gate, on the live path, not beside a NoOp.
|
|
49
|
+
- `testing` — TestTrigger reads a label, IsTestRun guards live writes, assertions.
|
|
50
|
+
- `credential-development` — author or bind the ERP credential the integration nodes declare.
|
|
51
|
+
|
|
52
|
+
## The things owners forget — surface them
|
|
53
|
+
|
|
54
|
+
- **Match → link → report** every entity (company, contacts, products): recognise it, match the ERP
|
|
55
|
+
record, link it, and report what didn't match for review. Recognise-only isn't enough.
|
|
56
|
+
- **Traceability** — keep the original email + PDF attached and audited.
|
|
57
|
+
- **Idempotency** — the same mail processed twice must not create a duplicate sales order.
|
|
58
|
+
- **Testability is part of done** — the workflow is tested on **real order emails from a designated test folder**, not fabricated data. Ask the owner for the test-folder name, resolve its Gmail label id (via MCP), and pass it to the builder in the build task. The builder will `report_flag` a gap if it's missing. See the `testing` and `gmail` skills for the mechanism.
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: codemation-ai-agent-node
|
|
3
|
-
description: AIAgent constructor, message shape, managed and BYOK chatModel configs, outputSchema, mcpServers. Read before writing any workflow step that calls an LLM.
|
|
4
|
-
compatibility: Codemation core-nodes. Requires @codemation/core-nodes import.
|
|
5
|
-
tags: agent, llm, ai
|
|
6
|
-
uses: "@codemation/core-nodes"
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# Codemation AI Agent Node
|
|
10
|
-
|
|
11
|
-
## Mental model
|
|
12
|
-
|
|
13
|
-
`AIAgent` is the single building block for any LLM step in a workflow. It receives items, runs a chat completion per item using the configured model and messages, and emits `{ output: string }` (or a parsed object when `outputSchema` is set) on its `main` port. The `chatModel` field determines whether the run consumes Codemation-managed quota (no credential needed) or a BYOK key the operator supplies. Every AIAgent emits exactly one output item per input item — it never fans out or filters.
|
|
14
|
-
|
|
15
|
-
## When to use / when NOT
|
|
16
|
-
|
|
17
|
-
Use `AIAgent` when a workflow step needs an LLM call: classification, extraction, summarisation, drafting, or decision.
|
|
18
|
-
Use a plain `Callback` instead when the logic is deterministic code — no LLM needed.
|
|
19
|
-
Use `mcpServers` (see `codemation-mcp-capabilities`) when the agent needs tool access to external services.
|
|
20
|
-
Read `codemation-workflow-dsl` for the surrounding workflow structure.
|
|
21
|
-
|
|
22
|
-
## Quickstart
|
|
23
|
-
|
|
24
|
-
```ts
|
|
25
|
-
import { AIAgent, CodemationChatModelConfig } from "@codemation/core-nodes";
|
|
26
|
-
|
|
27
|
-
new AIAgent({
|
|
28
|
-
name: "Classify email",
|
|
29
|
-
messages: [
|
|
30
|
-
{ role: "system", content: "Classify the email as spam or not-spam." },
|
|
31
|
-
{ role: "user", content: (args) => args.item.json.body as string },
|
|
32
|
-
],
|
|
33
|
-
chatModel: new CodemationChatModelConfig("Claude Haiku", "anthropic/claude-haiku-4-5-20251001"),
|
|
34
|
-
});
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
For full patterns — BYOK (`OpenAIChatModelConfig`), `outputSchema`, tools, multi-step pipelines, and gmail classification — use your harness's example-discovery tool: `find_examples({ query: "AIAgent" })`.
|
|
38
|
-
|
|
39
|
-
## Decision branches & gotchas
|
|
40
|
-
|
|
41
|
-
**Managed mode (default — no API key needed):** use `CodemationChatModelConfig(label, modelId)`. In managed mode the LLM broker **auto-authenticates via the workspace HMAC pairing** — no API key, no credential slot, no user setup required. This is the correct default for all managed-mode workflows. Do NOT tell managed users to "get an API key" — the broker handles authentication transparently.
|
|
42
|
-
|
|
43
|
-
```ts
|
|
44
|
-
chatModel: new CodemationChatModelConfig("Claude Haiku", "anthropic/claude-haiku-4-5-20251001");
|
|
45
|
-
// No credential slot created. Discover live model ids:
|
|
46
|
-
// GET <CONTROL_PLANE_URL>/api/llm/managed-models
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
**BYOK (self-hosted / non-managed only):** use `OpenAIChatModelConfig(label, modelId, slotKey)` — it creates a credential slot the operator must bind with an API key. Only use this in self-hosted deployments where no managed broker is available.
|
|
50
|
-
|
|
51
|
-
**Messages:** `content` is a plain string or a function `(args: { item, itemIndex, items, ctx }) => string`. Put instructions in the `system` message, per-item data in the `user` message. Use `"assistant"` role only for few-shot examples.
|
|
52
|
-
|
|
53
|
-
**Structured output:** add `outputSchema: z.object({...})` to validate and parse the response. Without it, `item.json.output` is always a plain string.
|
|
54
|
-
|
|
55
|
-
**Stable node id:** if the node has a credential binding (BYOK), set an explicit `id:` on the constructor. Without it the id derives from the `name` label — renaming the label orphans the binding. See `codemation-workflow-dsl` for the full id-stability rule.
|
|
56
|
-
|
|
57
|
-
**Downstream access:** the next node sees `item.json.output` as the agent's text response. Cast it via a typed `Callback<{ output: string }>`.
|
|
58
|
-
|
|
59
|
-
## Anti-patterns
|
|
60
|
-
|
|
61
|
-
- Do not tell managed users to get an API key — use `CodemationChatModelConfig`; the broker authenticates automatically.
|
|
62
|
-
- Do not use `OpenAIChatModelConfig` in managed mode — it creates an unnecessary credential slot and will prompt the user to bind a key they don't need.
|
|
63
|
-
- Do not use `AIAgent` for deterministic logic; use `Callback` instead (cheaper, faster, no LLM billing).
|
|
64
|
-
- Do not attempt to return multiple items from a single `AIAgent` step — it emits exactly one output per input.
|
|
65
|
-
|
|
66
|
-
See `references/anti-patterns.md` for version-specific gotchas (managed model id churn, chatModel string shorthand trap).
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# AIAgent anti-patterns (version-specific)
|
|
2
|
-
|
|
3
|
-
## Managed model ids change between releases
|
|
4
|
-
|
|
5
|
-
Do NOT hard-code managed model ids sourced from training data. The allowlisted set changes with each release.
|
|
6
|
-
Always discover the live list via `GET <CONTROL_PLANE_URL>/api/llm/managed-models` before committing a model id.
|
|
7
|
-
|
|
8
|
-
## `chatModel` string shorthand is not supported on AIAgent
|
|
9
|
-
|
|
10
|
-
`AIAgent` does not accept a plain string for `chatModel` — only `CodemationChatModelConfig` or `OpenAIChatModelConfig` instances.
|
|
11
|
-
(The string shorthand `model: "openai:gpt-4o-mini"` works on the `.agent(...)` fluent DSL helper only.)
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: codemation-credential-development
|
|
3
|
-
description: Guides Codemation custom credential development with `defineCredential(...)`, typed sessions, credential testing, and node credential slots. Use when creating or updating custom credentials, credential registrations, or credential-aware custom nodes.
|
|
4
|
-
compatibility: Designed for Codemation apps and plugins that register typed credentials.
|
|
5
|
-
tags: credential, oauth, plugin
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Codemation Credential Development
|
|
9
|
-
|
|
10
|
-
## Mental model
|
|
11
|
-
|
|
12
|
-
A credential type is a schema + runtime adapter: it declares `public` config (e.g. OAuth client id), `secret` material (e.g. tokens), a `createSession(...)` factory that returns the typed object nodes consume, and a `test(...)` function for pre-activation validation. Nodes declare named credential slots; operators bind concrete instances to those slots in the UI. The binding key is `(workflowId, nodeId, slotKey)`.
|
|
13
|
-
|
|
14
|
-
## When to use / when NOT
|
|
15
|
-
|
|
16
|
-
Use this skill for defining new credential types, wiring them into apps or plugins, and teaching nodes to request typed credential sessions.
|
|
17
|
-
Do not use for general workflow authoring unless credential slots or runtime sessions are the core problem.
|
|
18
|
-
|
|
19
|
-
## Quickstart
|
|
20
|
-
|
|
21
|
-
No standalone snippet — the full `defineCredential(...)` shape is in `references/credential-patterns.md`. Use your harness's example-discovery tool for runnable examples: `find_examples({ query: "defineCredential api-key test" })` or `find_examples({ query: "credential slot" })`.
|
|
22
|
-
|
|
23
|
-
## What `test()` does and why it matters
|
|
24
|
-
|
|
25
|
-
Every credential type must implement `test(args)`. It is called:
|
|
26
|
-
|
|
27
|
-
- When the operator clicks **Connect** in the credential dialog (validates before saving).
|
|
28
|
-
- Before a workflow activates (blocks activation on failing credentials).
|
|
29
|
-
|
|
30
|
-
`test()` receives the same `{ publicConfig, material }` args as `createSession()`. It must return `{ status: "healthy" | "failing", message, testedAt }`. A "failing" result blocks workflow activation and surfaces the `message` to the operator — use it to give actionable guidance ("API key is empty", "Endpoint returned 401 — key is invalid").
|
|
31
|
-
|
|
32
|
-
Implement `test()` as a cheap probe against the real service when possible (e.g. a `/health` or `/me` call). At minimum, validate that required secret fields are non-empty. Do NOT call `createSession()` from inside `test()` — test independently so credential issues are caught before runtime.
|
|
33
|
-
|
|
34
|
-
See the `define-credential-api-key` example for a concrete `test()` implementation: `find_examples({ query: "defineCredential api-key test" })`.
|
|
35
|
-
|
|
36
|
-
## Authoring rules
|
|
37
|
-
|
|
38
|
-
1. Start with `defineCredential(...)`.
|
|
39
|
-
2. Keep `public` versus `secret` fields intentional.
|
|
40
|
-
3. Make `createSession(...)` return the typed runtime object the node actually needs.
|
|
41
|
-
4. Implement `test(...)` so failure states are explicit before workflow activation.
|
|
42
|
-
5. Register credential types at the app or plugin boundary, not inside random workflow files.
|
|
43
|
-
|
|
44
|
-
## Decision branches & gotchas
|
|
45
|
-
|
|
46
|
-
**Node integration:** helper-defined nodes declare credentials directly in the `credentials` field; class-based nodes use lower-level credential requirement APIs when needed.
|
|
47
|
-
|
|
48
|
-
**Binding stability:** the `nodeId` defaults to a slug of the node's `name` label. Renaming a credential-using node's label silently changes its id and orphans the binding in the UI. To prevent this, set an explicit `id:` on credential-using node configs so the id is decoupled from the label.
|
|
49
|
-
|
|
50
|
-
## Anti-patterns
|
|
51
|
-
|
|
52
|
-
- Do not hard-code secrets in node implementation — use credential slots.
|
|
53
|
-
- Do not register credential types inside workflow files — use the app or plugin composition root.
|
|
54
|
-
|
|
55
|
-
## Read next when needed
|
|
56
|
-
|
|
57
|
-
- Read `references/credential-patterns.md` for schema, registration, and slot guidance.
|