// Name: n8n Pin Data Generator // Description: Generate n8n pinned data items for debugging from JSON via paste, file, or clipboard. // Author: tayiorbeii // GitHub: tayiorbeii import "@johnlindquist/kit" type N8nItem = { json?: any; binary?: any } const source = await arg("Choose input source", [ { name: "Paste JSON in Editor", value: "paste" }, { name: "Use Clipboard Text", value: "clipboard" }, { name: "Select JSON File", value: "file" }, ]) let raw = "" if (source === "paste") { raw = await editor({ value: "", language: "json", hint: "Paste JSON (object or array). On submit we'll convert to n8n items", footer: "Hit cmd+s to continue", }) } else if (source === "clipboard") { raw = await clipboard.readText() if (!raw?.trim()) { await div(md("Clipboard is empty or not text.")) exit() } } else { const filePath = await selectFile("Select a JSON file") if (!filePath) exit() raw = await readFile(filePath, "utf8") } const tryParse = (input: string): any => { let parsed: any try { parsed = JSON.parse(input) return parsed } catch { // Try NDJSON (one JSON object per line) const lines = input .split(/\r?\n/) .map(l => l.trim()) .filter(Boolean) if (!lines.length) throw new Error("No content to parse") const arr = [] for (const line of lines) { try { arr.push(JSON.parse(line)) } catch { // Not NDJSON throw new Error("Invalid JSON and not NDJSON") } } return arr } } let data: any try { data = tryParse(raw) } catch (err: any) { await editor({ value: raw, language: "json", hint: `Failed to parse JSON: ${err?.message || err}`, footer: "Fix the JSON and hit cmd+s to retry", }) try { data = tryParse(await editor()) } catch (err2: any) { await div(md(`Still invalid JSON: ${err2?.message || err2}`)) exit() } } const toItems = (value: any): N8nItem[] => { // Already n8n items? if (Array.isArray(value) && value.every(v => v && typeof v === "object" && ("json" in v || "binary" in v))) { return value as N8nItem[] } // Array of objects/primitives -> wrap as { json: ... } if (Array.isArray(value)) { return value.map(v => { if (v && typeof v === "object" && ("json" in v || "binary" in v)) return v as N8nItem return { json: v } }) } // Single object -> { json: object } if (value !== null && typeof value === "object") { return [{ json: value }] } // Primitive -> { json: { value: primitive } } return [{ json: { value } }] } let items = toItems(data) // Optional limit const limitInput = await micro({ placeholder: "Max items to include (optional). Leave blank for all.", strict: false, }) const limit = Number(limitInput) if (!Number.isNaN(limit) && limit > 0) { items = items.slice(0, limit) } const output = JSON.stringify(items, null, 2) const result = await editor( { value: output, language: "json", hint: "This is the n8n 'Pinned data' format (array of items).", footer: "cmd+s to Copy, cmd+o to Save as file", }, [ { name: "Copy", shortcut: `${cmd}+s`, bar: "right", onAction: async () => submit("copy"), }, { name: "Save As...", shortcut: `${cmd}+o`, bar: "right", onAction: async () => submit("save"), }, ] ) if (result === "save") { const outPath = await path({ placeholder: "Choose where to save (end with .json)", strict: false }) const finalPath = outPath.endsWith(".json") ? outPath : `${outPath}.json` await writeFile(finalPath, output, "utf8") await revealFile(finalPath) await notify({ title: "Saved", body: finalPath }) } else { await clipboard.writeText(output) await toast("Pinned data copied to clipboard") }// Name: n8n Mock Pinned Nodes // Description: Clone an n8n workflow and replace pinned nodes with Code nodes returning the pinned items. // Author: tayiorbeii // GitHub: tayiorbeii import '@johnlindquist/kit' const BASE = (await env('N8N_BASE_URL', async () => { return await arg('Enter N8N base URL', 'http://localhost:5678') })).replace(/\/+$/, '') const API_KEY = await env('N8N_API_KEY', { secret: true, placeholder: 'Enter your n8n API key', }) let workflowId = args[0] || (await arg('Enter n8n Workflow ID')) const severUpstream = Boolean(flag?.['sever-upstream']) || Boolean(flag?.severUpstream) || Boolean(flag?.sever) let onlyCSV = typeof flag?.only === 'string' ? (flag.only as string) : '' let suffix = typeof flag?.suffix === 'string' ? (flag.suffix as string) : ' (Mocked)' const onlyList = onlyCSV .split(',') .map(s => s.trim()) .filter(Boolean) type AnyObj = Record<string, any> async function apiGet<T = AnyObj>(path: string): Promise<T> { const res = await get(`${BASE}/api/v1${path}`, { headers: { 'Content-Type': 'application/json', 'X-N8N-API-KEY': API_KEY, }, }) return res.data as T } async function apiPost<T = AnyObj>(path: string, body: any): Promise<T> { const res = await post(`${BASE}/api/v1${path}`, body, { headers: { 'Content-Type': 'application/json', 'X-N8N-API-KEY': API_KEY, }, }) return res.data as T } function clone<T>(obj: T): T { return JSON.parse(JSON.stringify(obj)) } function toSet(node: AnyObj, pinnedItems: any[]): AnyObj { return { ...node, type: 'n8n-nodes-base.code', typeVersion: 2, parameters: { jsCode: `// Auto-generated from pinned data for "${node.name}" return ${JSON.stringify(pinnedItems, null, 2)};`, }, credentials: undefined, webhookId: undefined, alwaysOutputData: true, } } function removeInboundEdges(workflow: AnyObj, targetName: string) { const connections = workflow.connections || {} for (const key of Object.keys(connections)) { const connDef = connections[key] if (!connDef || !connDef.main) continue connDef.main = connDef.main.map((edges: any[]) => (edges || []).filter(edge => edge?.node !== targetName) ) } } try { const original = await apiGet<AnyObj>(`/workflows/${encodeURIComponent(workflowId)}`) const wf = clone(original) const pinned: AnyObj = wf.pinData || {} const byName = new Map<string, AnyObj>() for (const n of wf.nodes || []) byName.set(n.name, n) const pinnedNodeNames = Object.keys(pinned) const targetNames = onlyList.length > 0 ? pinnedNodeNames.filter(n => onlyList.includes(n)) : pinnedNodeNames if (targetNames.length === 0) { await div(md('No pinned nodes found to mock (or none match --only). Aborting.')) exit(1) } for (const name of targetNames) { const node = byName.get(name) const items = pinned[name] if (!node || !Array.isArray(items)) continue const replaced = toSet(node, items) Object.assign(node, replaced) delete pinned[name] if (severUpstream) removeInboundEdges(wf, name) } const newBody: AnyObj = { name: `${wf.name || 'Workflow'}${suffix}`, nodes: wf.nodes || [], connections: wf.connections || {}, settings: wf.settings || { executionOrder: 'v1' }, pinData: pinned, active: false, tags: wf.tags || [], } const created = await apiPost<AnyObj>(`/workflows`, newBody) const createdId = created.id || created.workflow?.id || 'unknown' const createdName = created.name || created.workflow?.name || 'Mocked Workflow' const uiUrl = `${BASE}/workflow/${createdId}` await notify(`Created mocked workflow: ${createdName} (id: ${createdId})`) await div( md(` ## β Created Mocked Workflow - Name: ${createdName} - ID: ${createdId} [Open in n8n](${uiUrl}) `) ) } catch (err: any) { await div(md(`Failed: ${err?.message || err}`)) exit(1) }