packages/cli/skills/pikku-n8n-addon-map/SKILL.md
Use when mapping n8n integration stubs (gmailTool, slackTool, googleSheetsTool, plain gmail/slack action nodes, etc.) emitted by @pikku/n8n-import to real `@pikku/addon-*` functions. Triggered when the user points at a `<workflow>.integrations.json` manifest produced by `pikku-n8n-import`, says 'map the n8n integrations', 'wire up the gmail/slack stubs', 'replace these stubs with addon refs', or opens a stub file generated from an n8n integration node (the stub's JSDoc says `STUB — generated from n8n node "..." (type "n8n-nodes-base.<service>...")`). For n8n **Code** node stubs use `pikku-n8n-code-translate` instead.
npx skillsauth add pikkujs/pikku pikku-n8n-addon-mapInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use this skill as an execution checklist, not reference material.
pikku-meta when available; otherwise run the relevant pikku meta ... --json command and inspect only the focused output you need..pikku, node_modules, vendored packages, or broad build artifacts.pikku-verify or pikku all when functions, wirings, schemas, or generated clients may have changed.You are translating n8n integration nodes (gmailTool, slackTool, googleSheetsTool, plain gmail / slack action nodes, etc.) that @pikku/n8n-import left as throwing stubs into real ref('<addonRpc>') references that point at functions in installed @pikku/addon-* packages.
This skill is per-stub mechanical. You do not invent business logic, you do not chain calls, you do not "improve" the workflow. You read one entry from a manifest, find the matching addon function, and rewrite the stub.
<workflow>.integrations.json next to the workflow .graph.ts. Each entry:
{
"rpcName": "agentGmailtool__sendAMessageInGmail",
"n8nType": "n8n-nodes-base.gmailTool",
"n8nName": "Send a message in Gmail",
"parameters": { "sendTo": "...", "message": "...", "subject": "..." },
"credentials": {
"gmailOAuth2": { "id": "...", "name": "Personal Gmail" },
},
"isAgentTool": true,
"agentName": "Inbox Assistant",
}
@pikku/addon-* in the project's package.json dependencies. The mapping is only allowed against installed packages. If the addon for a given n8n type is not installed, surface that — do not silently skip and do not pick a vaguely-named function from another addon.For each entry in the manifest:
Map n8nType to a @pikku/addon-* package by reading its source. Common shapes:
| n8n type prefix | typical addon package candidate(s) |
| ------------------------------------------ | ---------------------------------- |
| n8n-nodes-base.gmail / gmailTool | @pikku/addon-email-gmail |
| n8n-nodes-base.slack / slackTool | @pikku/addon-chat-slack |
| n8n-nodes-base.googleSheets / …Tool | @pikku/addon-sheets-google |
| n8n-nodes-base.notion / notionTool | @pikku/addon-docs-notion |
| n8n-nodes-base.telegram / telegramTool | @pikku/addon-chat-telegram |
These are guesses, not authoritative. Always verify by reading the installed addon's src/functions/** to confirm the exported function names exist. If you cannot find an installed addon that plausibly covers this n8n type, stop and tell the user — do not pick a wrong addon.
n8n integration nodes use a (resource, operation) pair to disambiguate. The mapping rubric:
resource defaults to the integration's primary noun if absent (gmail → message, slack → message, sheets → spreadsheet, etc.). Look at the addon's folder structure (messages/, drafts/, channels/) to see what nouns exist.operation is usually a verb (get, getAll, send, delete, addLabels, markAsRead, create).<resource><Verb> in camelCase, matching the file's export const (e.g. messageList for messages/list.function.ts, draftCreate for drafts/create.function.ts).Verify by grep -h "^export const" <addonPkg>/src/functions/**/*.ts and matching by name.
Conventions seen in @pikku/addon-email-gmail (use as a sanity reference, not as a fallback if the addon is something else):
getAll → <resource>Listget → <resource>Getsend → <resource>Senddelete → <resource>Deletereply → <resource>ReplyaddLabels → <resource>AddLabel (singular!)removeLabels → <resource>RemoveLabelmarkAsRead / markAsUnread → <resource>MarkRead / <resource>MarkUnreadcreate (drafts/labels) → <resource>CreateIf the addon has a node: block on the function, prefer matching on that block's category/displayName over guessing — read the source.
The stub file currently looks like:
import { z } from 'zod'
import { pikkuSessionlessFunc } from '#pikku'
export const AgentGmailtoolSendAMessageInGmailInput = z.object({
items: z.array(z.unknown()).optional(),
})
export const AgentGmailtoolSendAMessageInGmailOutput = z.object({
items: z.array(z.unknown()),
})
/** STUB — generated from n8n node "Send a message in Gmail" (type "n8n-nodes-base.gmailTool"). … */
export const agentGmailtool__sendAMessageInGmail = pikkuSessionlessFunc({
func: async () => {
throw new Error('agentGmailtool__sendAMessageInGmail — implement me')
},
})
There are two outcomes, depending on isAgentTool:
isAgentTool: true — the stub is consumed by an agent via ref()The agent file references the stub by its export name. The cleanest path is:
src/functions), update the agent's tools: [...] array — replace ref('agentGmailtool__sendAMessageInGmail') with ref('messageSend') (or whichever addon function name you resolved).node_modules/@pikku/addon-*).If you cannot delete the stub safely (e.g. it has multiple consumers, or the user wants to keep a thin wrapper for renaming), leave a one-line re-export wrapper instead:
import { messageSend } from '@pikku/addon-email-gmail'
export const agentGmailtool__sendAMessageInGmail = messageSend
But the default is delete + retarget. Wrappers add maintenance burden.
isAgentTool: false — the stub is part of the workflow graph proper.graph.ts file.nodes: { … } whose value is the stub's rpc name (e.g. 'agentGmailtool__sendAMessageInGmail').'messageSend').config: { <id>: { input: … } } block has an input expression that produces an { items } envelope, rewrite it to produce the addon's actual input schema (read the addon function's input: z.object({...}) to know the shape).n8n parameters fall into two camps:
"limit": 20, "labelIds": ["INBOX"], "sendTo": "[email protected]") — these were user choices in the n8n UI. Preserve them in the workflow's input expression (case B), or, for agent tools (case A), document them in the agent's tool list comment so the user knows what was lost. Agent tools cannot carry hardcoded params — the LLM fills the args at call time. If the user needs a hardcoded value baked in, they must keep a wrapper function. Surface this trade-off explicitly.$fromAI('Name', '', 'string') placeholders — these are LLM-filled. They map naturally to addon function input fields the LLM will populate via the pikkuAIAgent's tool-calling. No action needed beyond deleting the placeholder string; the addon's Zod schema becomes the tool schema.Each entry's credentials: { gmailOAuth2: { id, name } } is the n8n credential reference. Pikku addons typically expect a service (e.g. services.gmail) wired in services.ts. Do not attempt to auto-wire — append a one-line note for the user:
// TODO: wire services.gmail using credential "Personal Gmail" (n8n id: gmail_cred_1) — see @pikku/addon-email-gmail/README.md
…either at the top of the workflow file or printed in your final summary.
npm i @pikku/addon-<x> is required.@pikku/n8n-import. That package is intentionally addon-agnostic. All mapping logic lives here in this skill.Print a short summary to the user:
Mapped 3 of 5 stubs:
✓ agentGmailtool__sendAMessageInGmail → ref('messageSend') [agent tool: Inbox Assistant]
✓ agentGmailtool__getManyMessagesInGmail → ref('messageList') [agent tool: Inbox Assistant]
✓ workflow node 'updateRow' → 'sheetRowAppend' [graph node]
Unmapped:
✗ slackTool 'Post to channel' — @pikku/addon-chat-slack not installed (npm i @pikku/addon-chat-slack)
✗ notionTool 'Create page' — no `pageCreate` in @pikku/addon-docs-notion (only `pageGet`, `pageUpdate`)
Hardcoded params worth knowing:
• Get many messages in Gmail had limit=20, labelIds=["INBOX"] — agent tools cannot pin these; if you need them fixed, add a thin wrapper function.
Credential wiring TODOs added to the top of agentGmailtool.graph.ts.
Be terse. The user already knows the workflow context — they pointed you at the manifest.
documentation
Deprecated — use pikku-middleware instead. Tag middleware (addTagMiddleware) is now documented as a section within the pikku-middleware skill, alongside global HTTP middleware, execution order, and the service-to-service bearer auth pattern.
testing
Use when adding authorization checks to Pikku functions or routes — pikkuPermission, pikkuAuth, per-function permissions, pattern-based permissions, or understanding OR/AND permission logic. TRIGGER when: user wants to restrict who can call a function, check resource ownership, add role-based access, or understand where permission checks belong. DO NOT TRIGGER when: user asks about middleware or request interception (use pikku-middleware), authentication strategies (use pikku-security), or session management.
testing
Use when adding any middleware to a Pikku app — global HTTP middleware, tag-scoped middleware (including service-to-service bearer auth), per-route middleware, session-setting middleware, or understanding middleware execution order and priority. TRIGGER when: user wants middleware on some or all routes, machine-to-machine auth, tag-scoped cross-cutting concerns, global interceptors, or middleware priority/order questions. DO NOT TRIGGER when: user asks about permissions/authorization checks (use pikku-permissions), auth strategies like authBearer/authCookie (use pikku-security), or deployment.
documentation
Standard cleanup to run right after a Pikku template is cloned or scaffolded into a new project. TRIGGER when: a Pikku template was just cloned/scaffolded (via `pikku create`, `git clone <template>`, or the user says "I cloned the kanban template / starter / template"), or the working tree still looks like an untouched template (template README, placeholder `@project/*` name in package.json). DO NOT TRIGGER when: working in an established project mid-feature, or editing the template repo itself.