@codemation/agent-skills 0.4.0 → 0.5.2

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/dist/metadata.json +358 -48
  3. package/package.json +3 -1
  4. package/skills/builder/ai-agent/SKILL.md +314 -0
  5. package/skills/builder/ai-agent/references/anti-patterns.md +24 -0
  6. package/skills/{codemation-cli → builder/cli}/SKILL.md +1 -8
  7. package/skills/builder/connect-external-systems/SKILL.md +191 -0
  8. package/skills/builder/credential-development/SKILL.md +86 -0
  9. package/skills/{codemation-credential-development → builder/credential-development}/references/credential-patterns.md +3 -3
  10. package/skills/builder/custom-node-development/SKILL.md +61 -0
  11. package/skills/builder/custom-node-development/references/credential-aware-nodes.md +52 -0
  12. package/skills/builder/custom-node-development/references/define-batch-node.md +54 -0
  13. package/skills/{codemation-custom-node-development → builder/custom-node-development}/references/define-node-per-item.md +14 -14
  14. package/skills/{codemation-custom-node-development → builder/custom-node-development}/references/node-patterns.md +33 -49
  15. package/skills/builder/document-ai/SKILL.md +167 -0
  16. package/skills/builder/execution-context/SKILL.md +436 -0
  17. package/skills/{codemation-framework-concepts → builder/framework-concepts}/SKILL.md +18 -18
  18. package/skills/builder/gmail/SKILL.md +327 -0
  19. package/skills/builder/human-in-the-loop/SKILL.md +82 -0
  20. package/skills/{codemation-mcp-capabilities → builder/mcp-capabilities}/SKILL.md +5 -12
  21. package/skills/builder/mcp-capabilities/references/agent-with-mcp.ts +24 -0
  22. package/skills/builder/msgraph/SKILL.md +338 -0
  23. package/skills/builder/odoo/SKILL.md +498 -0
  24. package/skills/{codemation-plugin-development → builder/plugin-development}/SKILL.md +4 -7
  25. package/skills/{codemation-plugin-development → builder/plugin-development}/references/plugin-anatomy.md +36 -15
  26. package/skills/{codemation-plugin-development → builder/plugin-development}/references/plugin-structure.md +2 -2
  27. package/skills/builder/rest-node/SKILL.md +148 -0
  28. package/skills/builder/testing/SKILL.md +142 -0
  29. package/skills/builder/workflow-dsl/SKILL.md +492 -0
  30. package/skills/builder/workspace-files/SKILL.md +191 -0
  31. package/skills/concierge/credentials/SKILL.md +91 -0
  32. package/skills/concierge/intake-automation-playbook/SKILL.md +78 -0
  33. package/skills/concierge/scenario-invoice-to-accounting/SKILL.md +48 -0
  34. package/skills/concierge/scenario-procurement-intake/SKILL.md +58 -0
  35. package/skills/codemation-ai-agent-node/SKILL.md +0 -66
  36. package/skills/codemation-ai-agent-node/references/anti-patterns.md +0 -11
  37. package/skills/codemation-credential-development/SKILL.md +0 -57
  38. package/skills/codemation-custom-node-development/SKILL.md +0 -61
  39. package/skills/codemation-custom-node-development/references/credential-aware-nodes.md +0 -38
  40. package/skills/codemation-custom-node-development/references/define-batch-node.md +0 -38
  41. package/skills/codemation-document-scanner/SKILL.md +0 -136
  42. package/skills/codemation-mcp-capabilities/references/agent-with-mcp.ts +0 -44
  43. package/skills/codemation-workflow-dsl/SKILL.md +0 -78
  44. package/skills/codemation-workflow-dsl/references/builder-patterns.md +0 -120
  45. package/skills/codemation-workflow-dsl/references/complete-example.md +0 -263
  46. package/skills/codemation-workflow-dsl/references/workflow-testing.md +0 -194
  47. package/skills/codemation-workspace-files/SKILL.md +0 -142
  48. /package/skills/{codemation-cli → builder/cli}/references/command-map.md +0 -0
  49. /package/skills/{codemation-framework-concepts → builder/framework-concepts}/references/architecture-map.md +0 -0
@@ -0,0 +1,338 @@
1
+ ---
2
+ name: msgraph
3
+ description: Builds a Codemation workflow against Microsoft 365 via Microsoft Graph — trigger on new Outlook mail, reply, patch (read-state/categories/move), and reach OneDrive/Excel. Use whenever a workflow reads from or writes to a Microsoft 365 mailbox, OneDrive, or Excel workbook.
4
+ compatibility: Requires @codemation/core-nodes-msgraph. A Microsoft Graph OAuth credential binds on slot "auth".
5
+ tags: msgraph, microsoft, outlook, email, onedrive, excel, integration
6
+ uses: "@codemation/core-nodes-msgraph, @codemation/core-nodes, @codemation/core"
7
+ ---
8
+
9
+ # Codemation Microsoft Graph
10
+
11
+ `@codemation/core-nodes-msgraph` covers three Microsoft 365 surfaces, each binding a Graph OAuth
12
+ credential on slot **`"auth"`**:
13
+
14
+ - **Outlook mail** — trigger on new mail, plus reply / send / patch / get / attachment-download / folder-resolve.
15
+ - **OneDrive** — resolve, list, get, download, upload, copy, list-drives.
16
+ - **Excel** — open/close workbook, list/add sheets, read/write/style ranges.
17
+
18
+ Mail and Excel-on-mailbox bind the **`msgraph-mail-oauth`** credential type; OneDrive/Excel-on-drive
19
+ bind **`msgraph-drive-oauth`**. The two differ only in granted scopes — pick by surface.
20
+
21
+ These nodes are `defineNode` / `definePollingTrigger` definitions: instantiate with
22
+ **`node.create(config, label?, id?)`**, not `new`. Config fields accept per-item expressions via
23
+ `itemExpr` (type the expression with the item shape, e.g. `itemExpr<string, MsGraphMailItem>`).
24
+
25
+ **Discipline:** author straight from this file, then run `verify_workflow` and fix only what it flags.
26
+ Use `workflow-dsl` for the surrounding builder, trigger, and flow-control surface.
27
+
28
+ ## The default flow: trigger on new mail → reply
29
+
30
+ `onNewMsGraphMailTrigger` polls a folder and emits one `MsGraphMailItem` per new message.
31
+ `outlookMessageReplyNode` replies by `messageId`. Type the `.create<MsGraphMailItem>` call so the
32
+ `itemExpr` callbacks see the trigger payload.
33
+
34
+ ```typescript
35
+ import { createWorkflowBuilder } from "@codemation/core-nodes";
36
+ import { itemExpr } from "@codemation/core";
37
+ import { onNewMsGraphMailTrigger, outlookMessageReplyNode, type MsGraphMailItem } from "@codemation/core-nodes-msgraph";
38
+
39
+ export default createWorkflowBuilder({ id: "wf.msgraph.acknowledge", name: "Acknowledge inbound mail" })
40
+ .trigger(
41
+ // Polls the mailbox folder; default filter is "isRead eq false". Slot "auth" must be bound.
42
+ onNewMsGraphMailTrigger.create(
43
+ { mailbox: "me", folderId: "inbox", filter: "isRead eq false" },
44
+ "On new mail",
45
+ "mail-trigger",
46
+ ),
47
+ )
48
+ .then(
49
+ outlookMessageReplyNode.create<MsGraphMailItem>(
50
+ {
51
+ mailbox: "me",
52
+ messageId: itemExpr<string, MsGraphMailItem>(({ item }) => item.json.messageId),
53
+ body: itemExpr<string, MsGraphMailItem>(
54
+ ({ item }) => `Thanks — we received "${item.json.subject ?? "your email"}" and are processing it.`,
55
+ ),
56
+ bodyType: "text", // or "html"
57
+ // replyAll: true / forward: true (forward requires `to`) / draftOnly: true to save without sending
58
+ },
59
+ "Reply",
60
+ "reply",
61
+ ),
62
+ )
63
+ .build();
64
+ ```
65
+
66
+ ## Mail item shape
67
+
68
+ `onNewMsGraphMailTrigger` emits `MsGraphMailItem` — import the type, do not redefine it:
69
+
70
+ ```text
71
+ MsGraphMailItem = {
72
+ messageId: string; // target for reply / patch / get
73
+ conversationId?: string;
74
+ receivedDateTime: string;
75
+ internetMessageId?: string;
76
+ replyToMessageId?: string;
77
+ from?: { name?: string; address?: string };
78
+ to: { name?: string; address?: string }[];
79
+ cc?, bcc?: same shape;
80
+ subject?: string;
81
+ bodyText?: string; // prefer this for an LLM step
82
+ bodyHtml?: string;
83
+ attachments?: MsGraphMailAttachment[]; // metadata only — see below
84
+ headers?: Record<string, string>;
85
+ skippedAttachments?: { name; size; reason: "size-cap" }[];
86
+ }
87
+ ```
88
+
89
+ Trigger options: `{ mailbox; folderId?="inbox"; filter?; downloadAttachments?; attachmentSizeCapBytes?; pollIntervalMs? }`.
90
+ `mailbox: "me"` watches the credential owner's inbox; a UPN watches a shared mailbox (needs
91
+ `Mail.Read.Shared`). `filter` is a raw Graph OData `$filter` (e.g. `"hasAttachments eq true"`).
92
+
93
+ ## Patch a message (mark read, categorize, move)
94
+
95
+ `outlookMessagePatchNode` is the common "tidy up after handling" step. Chain it directly off the
96
+ trigger.
97
+
98
+ ```typescript
99
+ import { createWorkflowBuilder } from "@codemation/core-nodes";
100
+ import { itemExpr } from "@codemation/core";
101
+ import { onNewMsGraphMailTrigger, outlookMessagePatchNode, type MsGraphMailItem } from "@codemation/core-nodes-msgraph";
102
+
103
+ export default createWorkflowBuilder({ id: "wf.msgraph.triage", name: "Triage inbound mail" })
104
+ .trigger(onNewMsGraphMailTrigger.create({ mailbox: "me" }, "On new mail", "trigger"))
105
+ .then(
106
+ outlookMessagePatchNode.create<MsGraphMailItem>(
107
+ {
108
+ mailbox: "me",
109
+ messageId: itemExpr<string, MsGraphMailItem>(({ item }) => item.json.messageId),
110
+ isRead: true,
111
+ categories: ["Processed"],
112
+ move: { folderId: "Archive" }, // a well-known name or a folder id from outlookFolderResolveNode
113
+ },
114
+ "Mark processed",
115
+ "mark-processed",
116
+ ),
117
+ )
118
+ .build();
119
+ ```
120
+
121
+ ## Attachments → binary
122
+
123
+ Set the trigger's `downloadAttachments: true` and the trigger's `execute` step stores each attachment's
124
+ bytes via `ctx.binary` under a slot named after the attachment (`name`, or `inline:{contentId}` for
125
+ embedded images). `item.json.attachments` carries metadata only
126
+ (`{ id, name, contentType, size, isInline?, contentId? }`) — never the payload. To pull one attachment
127
+ on demand later, use `outlookAttachmentDownloadNode` (`{ mailbox?, messageId, attachmentId, binarySlot?="attachment" }`).
128
+ Read the stored bytes off `item.binary[slot]`; feed them to `document-ai` for OCR.
129
+
130
+ ## Node catalog
131
+
132
+ Instantiate each with `node.create(config, label?, id?)`. Bind `"auth"` to the matching credential type.
133
+
134
+ ```text
135
+ Outlook mail (msgraph-mail-oauth)
136
+ onNewMsGraphMailTrigger { mailbox; folderId?; filter?; downloadAttachments?; pollIntervalMs? }
137
+ outlookMessageReplyNode { mailbox; messageId; body; bodyType; replyAll?; forward?; to?; cc?; bcc?; draftOnly?; attachments? }
138
+ outlookMessageSendNode { mailbox; to; subject; body; bodyType; cc?; bcc?; attachments?; draftOnly? }
139
+ outlookMessagePatchNode { mailbox; messageId; isRead?; categories?; move?: { folderId } }
140
+ outlookMessageGetNode { mailbox; messageId; expandAttachments? }
141
+ outlookAttachmentDownloadNode{ mailbox?; messageId; attachmentId; binarySlot?; sizeCapBytes? }
142
+ outlookFolderResolveNode { mailbox; folderPath } → { folderId, path, mailbox }
143
+
144
+ OneDrive (msgraph-drive-oauth)
145
+ driveResolveNode · driveListChildrenNode · driveItemGetNode · driveDownloadNode
146
+ driveUploadNode · driveCopyNode · driveListMyDrivesNode · driveListSharedWithMeNode
147
+
148
+ Excel (msgraph-drive-oauth; open a workbook first, pass the handle downstream)
149
+ excelOpenWorkbookNode · excelCloseWorkbookNode · excelListWorksheetsNode · excelAddSheetNode
150
+ excelReadRangeNode · excelWriteRangeNode · excelStyleRangeNode
151
+ ```
152
+
153
+ For a OneDrive/Excel case not covered, resolve `ctx.getCredential<MsGraphSession>("auth")` in a
154
+ `Callback` and call `createGraphClient(session)` (exported from `@codemation/core-nodes-msgraph`) to
155
+ reach the raw Graph SDK.
156
+
157
+ ## Gotchas
158
+
159
+ - **`.create(...)`, never `new`.** These are `defineNode` definitions — `new outlookMessageReplyNode(...)`
160
+ does not exist.
161
+ - **Type the `itemExpr`.** `.create<MsGraphMailItem>` alone leaves a bare `itemExpr` callback's
162
+ `item.json` as `unknown`. Write `itemExpr<string, MsGraphMailItem>(({ item }) => item.json.messageId)`.
163
+ - **Pick the right credential type on `"auth"`.** Mail nodes → `msgraph-mail-oauth`; OneDrive/Excel →
164
+ `msgraph-drive-oauth`. Bind every node's slot before activation.
165
+ - **`filter` is server-side OData.** It is a raw `$filter` string — malformed expressions fail the poll.
166
+ The trigger establishes a baseline on its first cycle (emits nothing) so existing mail doesn't replay.
167
+ - **Attachment bytes need `downloadAttachments: true`** (or a later `outlookAttachmentDownloadNode`);
168
+ the item JSON only ever carries attachment metadata.
169
+
170
+ ## Testing an Outlook workflow on real mail (not fabricated fixtures)
171
+
172
+ Use `OutlookFolderTestSource` (a `TestTriggerNodeConfig`) to run the persistent Tests-tab suite on
173
+ real emails from a **designated test folder** in the same mailbox. Each message becomes one test
174
+ case and yields items in the **identical `MsGraphMailItem` shape** the live trigger emits — so OCR,
175
+ extraction, and matching all run downstream in tests too.
176
+
177
+ **The designated test folder is communicated to the builder by the concierge as part of the build
178
+ task.** If the folder id/name is absent from the task, call `report_flag({ kind: "gap" })` and note
179
+ that the test folder must be provided. Prefer declaring the folder id as a named constant the owner
180
+ fills (e.g. `const TEST_FOLDER = "TODO(setup): paste the test folder id here"`) rather than leaving
181
+ an unexplained placeholder string buried inside the node config. NEVER fabricate a folder id or fall
182
+ back to hard-coded fixture data.
183
+
184
+ The topology that matters:
185
+
186
+ ```text
187
+ trigger → [shared processing: OCR / extraction / matching] → IsTestRun → {
188
+ true (test): Assertion — checks the EXTRACTION OUTCOME, not the raw email
189
+ false (live): ALL side-effects — reply + patch (mark read/categorize) + ERP write, all gated together
190
+ }
191
+ ```
192
+
193
+ `IsTestRun` must go **after** the shared processing and **just before** the side-effects. If it
194
+ forks before the extraction, the test path asserts raw trigger bytes and proves nothing.
195
+
196
+ ```typescript
197
+ import { z } from "zod";
198
+ import {
199
+ createWorkflowBuilder,
200
+ AIAgent,
201
+ IsTestRun,
202
+ Assertion,
203
+ CodemationChatModelConfig,
204
+ } from "@codemation/core-nodes";
205
+ import { itemExpr } from "@codemation/core";
206
+ import {
207
+ onNewMsGraphMailTrigger,
208
+ OutlookFolderTestSource,
209
+ outlookMessageReplyNode,
210
+ outlookMessagePatchNode,
211
+ type MsGraphMailItem,
212
+ } from "@codemation/core-nodes-msgraph";
213
+
214
+ // ── output schema for the extraction step ─────────────────────────────────
215
+ // messageId is passed through so the side-effect nodes can address the message after extraction.
216
+ const ExtractionSchema = z.object({
217
+ messageId: z.string(), // pass-through from trigger — needed by reply/patch nodes
218
+ customer: z.string(), // recognised company/customer name; empty string when unrecognised
219
+ lineItems: z.array(z.object({ description: z.string(), amount: z.number() })),
220
+ });
221
+ type ExtractionOutput = z.infer<typeof ExtractionSchema>;
222
+
223
+ // ── test folder ───────────────────────────────────────────────────────────
224
+ // Declare the folder id as a named constant the owner fills.
225
+ // If the concierge did not provide it → call report_flag({ kind: "gap" }) instead.
226
+ const TEST_FOLDER = "TODO(setup): paste the test folder id here";
227
+
228
+ // SEPARATE top-level export — the Tests tab discovers it. NOT a second .trigger().
229
+ // `new`, not `.create(...)`: the test source is a class (like GmailLabelTestSource), not a defineNode.
230
+ export const testSource = new OutlookFolderTestSource(
231
+ "Outlook test cases",
232
+ { mailbox: "me", folderId: TEST_FOLDER, maxResults: 20 },
233
+ "outlook-test-source",
234
+ );
235
+
236
+ export default createWorkflowBuilder({ id: "wf.msgraph.procurement", name: "Procurement intake" })
237
+ .trigger(onNewMsGraphMailTrigger.create({ mailbox: "me", folderId: "inbox" }, "New email", "mail-trigger"))
238
+ // ── shared processing (runs in BOTH test and live paths) ────────────────
239
+ // Extract structured fields from the email body. Include messageId so
240
+ // downstream side-effect nodes (reply, patch, ERP) can address the message.
241
+ .then(
242
+ new AIAgent<MsGraphMailItem, ExtractionOutput>({
243
+ name: "Extract procurement data",
244
+ id: "extract-procurement-data",
245
+ messages: [
246
+ {
247
+ role: "system",
248
+ content:
249
+ "Extract the supplier/customer name and line items from the email. " +
250
+ "Include the messageId field verbatim from the input. " +
251
+ 'Reply with strict JSON matching {"messageId","customer","lineItems":[{"description","amount"}]}.',
252
+ },
253
+ {
254
+ role: "user",
255
+ content: ({ item }) => `messageId: ${item.json.messageId}\n\n${item.json.bodyText ?? ""}`,
256
+ },
257
+ ],
258
+ chatModel: new CodemationChatModelConfig("Managed AI", "low"),
259
+ outputSchema: ExtractionSchema,
260
+ guardrails: { maxTurns: 1 },
261
+ }),
262
+ )
263
+ // ── IsTestRun: AFTER all shared processing, JUST BEFORE side-effects ───
264
+ .then(new IsTestRun<ExtractionOutput>("Is this a test run?", "is-test-run"))
265
+ .when({
266
+ // true = test path: assert the EXTRACTION OUTCOME, not the raw email fields
267
+ true: [
268
+ new Assertion<ExtractionOutput>({
269
+ name: "Extraction outcome",
270
+ id: "assert-extraction-outcome",
271
+ assertions: (item) => [
272
+ {
273
+ // Did the model recognise a company/customer?
274
+ name: "customer recognised",
275
+ score: item.json.customer.trim().length > 0 ? 1 : 0,
276
+ actual: item.json.customer,
277
+ },
278
+ {
279
+ // Did the model extract at least one line item?
280
+ name: "line items extracted",
281
+ score: item.json.lineItems.length > 0 ? 1 : 0,
282
+ actual: item.json.lineItems.length,
283
+ },
284
+ ],
285
+ }),
286
+ ],
287
+ // false = live path: EVERY side-effect is gated here together
288
+ // (ERP write / create-order call belongs here too — never outside this branch)
289
+ false: [
290
+ outlookMessageReplyNode.create<ExtractionOutput>(
291
+ {
292
+ mailbox: "me",
293
+ messageId: itemExpr<string, ExtractionOutput>(({ item }) => item.json.messageId),
294
+ body: itemExpr<string, ExtractionOutput>(
295
+ ({ item }) =>
296
+ `Thank you — we received your procurement request and are processing it.` +
297
+ (item.json.customer ? ` Customer: ${item.json.customer}.` : ""),
298
+ ),
299
+ bodyType: "text",
300
+ // draftOnly is NOT a test switch — see the note below. Sending is gated by IsTestRun.
301
+ },
302
+ "Acknowledge receipt",
303
+ "reply",
304
+ ),
305
+ outlookMessagePatchNode.create<ExtractionOutput>(
306
+ {
307
+ mailbox: "me",
308
+ messageId: itemExpr<string, ExtractionOutput>(({ item }) => item.json.messageId),
309
+ isRead: true,
310
+ categories: ["Processed"],
311
+ // move: { folderId: "Archive" } — a well-known name or an id from outlookFolderResolveNode
312
+ },
313
+ "Mark processed",
314
+ "mark-processed",
315
+ ),
316
+ // → Add the ERP write node here (e.g. odooCreateSaleOrderNode) before marking processed.
317
+ ],
318
+ })
319
+ .build();
320
+ ```
321
+
322
+ **Key rules:**
323
+
324
+ - `OutlookFolderTestSource` is a **separate `export const`**, never a second `.trigger(...)` on the chain. It is a class (mirroring `GmailLabelTestSource`) — instantiate with `new`, not `.create(...)`.
325
+ - **Place `IsTestRun` after all shared processing and just before the side-effects.** Processing steps (OCR, extraction, matching) must be upstream of `IsTestRun` so both the test path and the live path exercise the same logic. An `IsTestRun` placed right after the trigger asserts nothing useful.
326
+ - **Assert the processing outcome, not the raw email.** The `Assertion` on the `true` branch must check the extractor's structured output (recognised customer, extracted line items) — not `subject`, `from`, or other raw trigger fields. Asserting `subject.length > 0` proves only that an email arrived; asserting `lineItems.length > 0` proves the extraction worked.
327
+ - **Gate EVERY side-effect on the `false` branch.** Reply, patch (mark read / categorize / move), and ERP writes all belong on the `false` branch — together. A single gated patch while the reply or ERP write runs unconditionally defeats the purpose. If you add a node that writes to an external system, it must live on the `false` branch.
328
+ - **`draftOnly` is NOT the test mechanism.** Gate the reply behind `IsTestRun` (it lives on the `false`/live branch, so a test run never reaches it and no mail is sent). Do not reach for `outlookMessageReplyNode`'s `draftOnly: true` to make a run "safe": `draftOnly` saves a draft on the live account, it is not a test-mode switch.
329
+ - **Absent test folder → `report_flag` + declare a required input, never a silent placeholder.** If the concierge did not provide the test folder, call `report_flag({ kind: "gap" })`. When a placeholder is unavoidable, declare it as a named constant at the top of the file (as `TEST_FOLDER` above) so the owner knows exactly what to fill in — never bury an opaque string inside a node config.
330
+ - `OutlookFolderTestSource` yields items in the same raw `MsGraphMailItem` shape as the live `onNewMsGraphMailTrigger` — no pre-processing, no canonicalization. That's what makes the test meaningful.
331
+ - The `caseLabel` on `OutlookFolderTestSource` defaults to the email subject so each test-case row in the Tests tab is human-readable.
332
+
333
+ ## Read next when needed
334
+
335
+ - `workflow-dsl` — builder, triggers, flow control, the per-item contract.
336
+ - `document-ai` — OCR an attachment's bytes once they're in `ctx.binary`.
337
+ - `ai-agent` — summarize or triage `item.json.bodyText` with an LLM before replying.
338
+ - `testing` — the full `IsTestRun` + `Assertion` + `TestTrigger` pattern (trigger-agnostic).