kenobi-pages 0.1.8 → 0.1.9
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/SKILL.md +65 -0
- package/bin/cli.mjs +12 -12
- package/package.json +5 -2
- package/rules/kenobi-pages-skill-compliance.mdc +21 -0
- package/skills/pages/SKILL.md +150 -0
- package/skills/run/SKILL.md +127 -0
- package/skills/workflows/SKILL.md +240 -0
package/SKILL.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kenobi-pages
|
|
3
|
+
description: Build personalized landing pages with AI-generated content from Kenobi workflows. Use when the user wants personalized pages, dynamic content per lead, post-call landing pages, or AI-powered content pipelines for their website.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# STOP — Read This Before Doing Anything
|
|
7
|
+
|
|
8
|
+
When this skill applies, your **very first response** to the user must be the discovery question below — and **nothing else**. No tool calls. No file reads. No exploring. No installing packages. No planning. No "let me check the project first." Your entire first message is:
|
|
9
|
+
|
|
10
|
+
> Before I start building, I need to know where you are:
|
|
11
|
+
>
|
|
12
|
+
> Do you already have a Kenobi workflow set up, or are we starting from scratch?
|
|
13
|
+
|
|
14
|
+
Send that message. Stop. Wait for the user's reply. Only after they answer do you proceed to Phase 2.
|
|
15
|
+
|
|
16
|
+
If you are tempted to "just get started" or "explore the project while asking" — don't. That is the single most common failure mode with this skill. The discovery question IS the first step. Everything else comes after.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Kenobi Pages
|
|
21
|
+
|
|
22
|
+
Kenobi Pages lets users create a single page template in their Next.js app that renders unique, AI-generated content for every lead. Each lead gets a URL like `/for/acme-corp` — the content is generated by a Kenobi workflow that pulls from CRM data, call transcripts, or other sources and uses AI to produce personalized copy and images.
|
|
23
|
+
|
|
24
|
+
The system has three parts: a **page** (Next.js dynamic route), a **workflow** (configured in Kenobi — wires data sources to AI generation to produce content), and **runs** (executing a workflow for a specific lead).
|
|
25
|
+
|
|
26
|
+
## Phase 2 — Route to the Right Sub-Skill
|
|
27
|
+
|
|
28
|
+
Based on the user's answer to the discovery question:
|
|
29
|
+
|
|
30
|
+
| User says | Sub-skill | Mode |
|
|
31
|
+
| --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
|
32
|
+
| "I have a workflow and want to build the page" | `skills/pages/SKILL.md` | Forward |
|
|
33
|
+
| "I have a page and want to make it personalized" | `skills/pages/SKILL.md` | Reverse |
|
|
34
|
+
| "I need to create a workflow" / "starting from scratch" | `skills/workflows/SKILL.md` | — |
|
|
35
|
+
| "I want to generate content for leads" / "run a workflow" | `skills/run/SKILL.md` | — |
|
|
36
|
+
| Not sure / vague | Ask: "Do you have an existing page you'd like to personalize, or should I design one from scratch?" Then route to pages sub-skill. | — |
|
|
37
|
+
|
|
38
|
+
## Phase 3 — Setup
|
|
39
|
+
|
|
40
|
+
Before writing any code, ensure the environment is ready:
|
|
41
|
+
|
|
42
|
+
1. Install: `pnpm add kenobi-pages`
|
|
43
|
+
2. Check if `KENOBI_PAGES_KEY` is already in `.env.local`. If not, ask the user to run `npx kenobi-pages init` in their terminal — this interactively prompts for their API key and saves it to `~/.kenobi/config.json` and `.env.local`.
|
|
44
|
+
3. Create `lib/kenobi.ts` (if it doesn't exist):
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { createKenobiPagesClient } from "kenobi-pages";
|
|
48
|
+
|
|
49
|
+
export const kenobi = createKenobiPagesClient({
|
|
50
|
+
apiKey: process.env.KENOBI_PAGES_KEY!,
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If the package is already installed and `lib/kenobi.ts` exists, skip this phase.
|
|
55
|
+
|
|
56
|
+
## Phase 4 — Implementation
|
|
57
|
+
|
|
58
|
+
Now read the sub-skill identified in Phase 2 and follow its instructions.
|
|
59
|
+
|
|
60
|
+
## Important Context
|
|
61
|
+
|
|
62
|
+
- The SDK (`kenobi-pages` npm package) does one thing: `getPage(workflowId, slug)` fetches content for a specific lead at runtime. That's it. Everything else — schema management, workflow CRUD, triggering runs — is done via the `npx kenobi-pages` CLI.
|
|
63
|
+
- A **workflow** is a pipeline configured in Kenobi that takes data from sources (Notion, HubSpot, etc.), runs AI generation, and stores personalized content keyed by a slug.
|
|
64
|
+
- A **slug** is the URL-friendly identifier for a lead (e.g., `acme-corp`). Every workflow run produces content for one slug.
|
|
65
|
+
- The CLI reads the API key from `KENOBI_PAGES_KEY` env var or `~/.kenobi/config.json`. All commands output JSON to stdout and status messages to stderr.
|
package/bin/cli.mjs
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
`)},"writeGlobalConfig"),
|
|
2
|
+
var F=Object.defineProperty;var t=(e,o)=>F(e,"name",{value:o,configurable:!0});import{existsSync as S,mkdirSync as $,readFileSync as P,writeFileSync as L,appendFileSync as G,cpSync as I,readdirSync as D}from"node:fs";import{readFile as B}from"node:fs/promises";import{homedir as W}from"node:os";import{join as g,resolve as C,dirname as O}from"node:path";import{createInterface as M}from"node:readline";import{fileURLToPath as Y}from"node:url";var H="https://kenobi.ai",_=g(W(),".kenobi"),v=g(_,"config.json"),y="KENOBI_PAGES_KEY",A=t(()=>{try{return JSON.parse(P(v,"utf-8"))}catch{return{}}},"readGlobalConfig"),q=t(e=>{$(_,{recursive:!0}),L(v,JSON.stringify(e,null,2)+`
|
|
3
|
+
`)},"writeGlobalConfig"),z=t(()=>{let e=process.env[y]||A().apiKey;return e||(console.error("Error: No API key found."),console.error(""),console.error("Run 'npx kenobi-pages init' to set up your API key."),console.error(""),console.error("Or set the KENOBI_PAGES_KEY environment variable directly."),process.exit(1)),e},"getApiKey"),V=t(()=>(process.env.KENOBI_BASE_URL??A().baseUrl??H).replace(/\/+$/,""),"getBaseUrl"),f=t(async(e,o={})=>{let n=`${V()}${e}`,s=await fetch(n,{method:o.method??"GET",headers:{"x-kenobi-key":z(),"Content-Type":"application/json"},body:o.body?JSON.stringify(o.body):void 0});if(!s.ok){let r=await s.text().catch(()=>"Unknown error"),i=s.status===401||s.status===403?4:s.status===404?3:1;console.error(`Error ${s.status}: ${r}`),process.exit(i)}return s.json()},"fetchKenobi"),E=t(e=>new Promise(o=>{let n=M({input:process.stdin,output:process.stderr});n.question(e,s=>{n.close(),o(s.trim())})}),"prompt"),R=t(()=>new Promise((e,o)=>{let n="";process.stdin.setEncoding("utf-8"),process.stdin.on("data",s=>{n+=s}),process.stdin.on("end",()=>e(n)),process.stdin.on("error",o),process.stdin.isTTY&&e("")}),"readStdin"),U=t(async e=>{let o=e.indexOf("--file");return o!==-1&&e[o+1]?B(e[o+1],"utf-8"):null},"parseFileArg"),X=t(()=>{let e=Y(import.meta.url),o=O(e);for(;o!=="/"&&o!==".";){if(S(g(o,"package.json"))&&JSON.parse(P(g(o,"package.json"),"utf-8")).name==="kenobi-pages")return o;o=O(o)}return O(e)},"resolvePackageRoot"),Z=t(()=>{let e=process.cwd(),o=X(),n=g(o,"SKILL.md"),s=g(o,"skills"),r=g(o,"rules");if(!S(n)){console.error("\u26A0 Could not find agent skills to install (SKILL.md missing from package).");return}let i=g(e,".agents","skills","kenobi-pages");if($(i,{recursive:!0}),I(n,g(i,"SKILL.md")),S(s)&&I(s,g(i,"skills"),{recursive:!0}),console.error("\u2713 Installed agent skills to .agents/skills/kenobi-pages/"),S(r)){let l=g(e,".cursor","rules");$(l,{recursive:!0});for(let a of D(r))I(g(r,a),g(l,a));console.error("\u2713 Installed cursor rules to .cursor/rules/")}},"installAgentAssets"),Q=t(async()=>{console.error("Kenobi Pages \u2014 Setup"),console.error(""),console.error("Find your Pages API key in the workflow builder:"),console.error(" https://kenobi.ai/testing/cortex"),console.error("");let e=await E("Paste your API key (pk_live_... or pk_test_...): ");e||(console.error("No key provided. Aborting."),process.exit(1)),!e.startsWith("pk_live_")&&!e.startsWith("pk_test_")&&(console.error(`Warning: "${e}" doesn't look like a Kenobi public key (expected pk_live_... or pk_test_...).`),(await E("Continue anyway? (y/N): ")).toLowerCase()!=="y"&&(console.error("Aborting."),process.exit(1)));let o=A();o.apiKey=e,q(o),console.error(`\u2713 Saved to ${v}`);let n=C(process.cwd(),".env.local"),s=C(process.cwd(),".env"),r=S(n)?n:S(s)?s:null;if(r){let i=P(r,"utf-8");if(i.includes(y))console.error(`\u2713 ${y} already present in ${r.split("/").pop()}`);else{let l=i.endsWith(`
|
|
4
4
|
`)?"":`
|
|
5
|
-
`;
|
|
6
|
-
`),console.error(`\u2713 Added ${
|
|
7
|
-
`),console.error(`\u2713 Created .env.local with ${
|
|
8
|
-
${
|
|
5
|
+
`;G(r,`${l}${y}="${e}"
|
|
6
|
+
`),console.error(`\u2713 Added ${y} to ${r.split("/").pop()}`)}}else(await E("No .env.local found. Create one with your API key? (Y/n): ")).toLowerCase()!=="n"&&(L(n,`${y}="${e}"
|
|
7
|
+
`),console.error(`\u2713 Created .env.local with ${y}`));Z(),console.error(""),console.error("Done! You can now use kenobi-pages commands."),console.error(` npx kenobi-pages schema push "My Page" '{"fields":{...}}'`),console.error(" npx kenobi-pages types <workflowId>")},"initCommand"),ee=t(async e=>{let o=e[0];o||(console.error("Usage: kenobi-pages schema get <workflowId>"),process.exit(2));let n=await f(`/api/v1/pages/${o}/schema`);console.log(JSON.stringify(n,null,2))},"schemaGet"),oe=t(async e=>{let o=e[0];o||(console.error("Usage: kenobi-pages schema push <name> '<json>'"),console.error(" kenobi-pages schema push <name> --file schema.json"),console.error(" echo '<json>' | kenobi-pages schema push <name>"),process.exit(2));let n=e.slice(1),s=await U(n),r=s?null:n.find(m=>m.startsWith("{")),i=!s&&!r?await R():null,l=s??r??i;(!l||l.trim().length===0)&&(console.error("Error: No schema provided."),console.error("Pass inline JSON, --file <path>, or pipe to stdin."),process.exit(2));let a;try{a=JSON.parse(l)}catch{console.error("Error: Invalid JSON."),process.exit(2)}let d=await f("/api/v1/pages/schema",{method:"POST",body:{name:o,schema:a}});console.log(JSON.stringify(d,null,2)),console.error(`Schema "${d.name}" pushed successfully.`),console.error(`Source key: ${d.sourceKey}`),console.error("You can now select this schema as an output target in the Kenobi workflow builder.")},"schemaPush"),ne=t(async e=>{let[o,n]=e;(!o||!n)&&(console.error("Usage: kenobi-pages page get <workflowId> <slug>"),process.exit(2));let s=await f(`/api/v1/pages/${o}/${n}`);console.log(JSON.stringify(s,null,2))},"pageGet"),se=t(async e=>{let o=e[0];o||(console.error("Usage: kenobi-pages types <workflowId>"),process.exit(2));let n=await f(`/api/v1/pages/${o}/schema`),s=n.schema;s?.fields||(console.error("Error: Workflow has no output schema with fields."),process.exit(1));let r=t(c=>" ".repeat(c),"indent"),i=t((c,w=1)=>{switch(c.type){case"string":case"url":return"string";case"number":return"number";case"boolean":return"boolean";case"enum":return c.values?.length?c.values.map(b=>`"${b}"`).join(" | "):"string";case"object":{let b=Object.entries(c.fields??{});return b.length===0?"Record<string, unknown>":`{
|
|
8
|
+
${b.map(([J,N])=>{let T=N.optional?"?":"";return`${r(w)}${J}${T}: ${i(N,w+1)}`}).join(`
|
|
9
9
|
`)}
|
|
10
|
-
${
|
|
11
|
-
`:""} ${
|
|
12
|
-
${
|
|
10
|
+
${r(w-1)}}`}case"array":return`Array<${i(c.items,w)}>`;default:return"unknown"}},"fieldToTs"),a=Object.entries(s.fields).map(([c,w])=>{let b=w.optional?"?":"";return`${w.description?` /** ${w.description} */
|
|
11
|
+
`:""} ${c}${b}: ${i(w,2)}`}),m=`export interface ${n.title?n.title.replace(/[^a-zA-Z0-9]+/g,""):"PageContent"} {
|
|
12
|
+
${a.join(`
|
|
13
13
|
`)}
|
|
14
14
|
}
|
|
15
|
-
`;console.log(
|
|
16
|
-
Run failed (${
|
|
15
|
+
`;console.log(m)},"typesGen"),re=new Set(["COMPLETED","FAILED","CANCELED","SYSTEM_FAILURE","CRASHED"]),te=t(e=>{if(typeof e=="string")return e;if(typeof e=="object"&&e!==null&&"message"in e){let o=e.message;if(typeof o=="string")return o}return"Unknown error"},"runStatusErrorMessage"),ie=t(e=>new Promise(o=>setTimeout(o,e)),"sleep"),h=t((e,o)=>{let n=e.indexOf(o);return n!==-1&&e[n+1]?e[n+1]:void 0},"parseFlag"),ae=t(async e=>{let o=e[0];o||(console.error("Usage: kenobi-pages run <workflowId> [--params '<json>'] [--context '<text>']"),process.exit(2));let n=e.slice(1),s={},r=h(n,"--params");if(r)try{s=JSON.parse(r)}catch{console.error("Error: Invalid JSON in --params"),process.exit(2)}let i=h(n,"--context"),l={params:s};i&&(l.context=i);let a=await f(`/api/v1/workflows/${o}/run`,{method:"POST",body:l});console.error(`Run triggered: ${a.runId}`),console.error("Polling for completion...");let d=2e3,m=15e3;for(;;){await ie(d);let c=await f(`/api/v1/workflows/${o}/runs/${a.runId}`);if(console.error(` Status: ${c.status}`),re.has(c.status)){if(c.status!=="COMPLETED"){let w=te(c.error);console.error(`
|
|
16
|
+
Run failed (${c.status}): ${w}`),console.log(JSON.stringify(c,null,2)),process.exit(1)}console.log(JSON.stringify(c,null,2));return}d=Math.min(d*1.5,m)}},"runWorkflow"),ce=t(async()=>{let e=await f("/api/v1/workflows");console.log(JSON.stringify(e,null,2))},"listWorkflows"),le=t(async()=>{let e=await f("/api/v1/sources");console.log(JSON.stringify(e,null,2))},"listSources"),pe=t(async e=>{let o=await f(`/api/v1/sources/${encodeURIComponent(e)}/sample`);console.log(JSON.stringify(o,null,2))},"sampleSource"),K=t(async e=>{let o=await U(e),n=o?null:e.find(i=>i.startsWith("{")),s=!o&&!n?await R():null,r=o??n??s;(!r||r.trim().length===0)&&(console.error("Error: No JSON provided."),console.error("Pass inline JSON, --file <path>, or pipe to stdin."),process.exit(2));try{return JSON.parse(r)}catch{console.error("Error: Invalid JSON."),process.exit(2)}},"readJsonInput"),ge=t(async e=>{let o=h(e,"--name"),n=h(e,"--slug");(!o||!n)&&(console.error("Usage: kenobi-pages workflow create --name '<name>' --slug '<slug>' --config '<json>'"),console.error(" kenobi-pages workflow create --name '<name>' --slug '<slug>' --file config.json"),process.exit(2));let s=h(e,"--description"),r=e.indexOf("--config"),i=r!==-1&&e[r+1]?[e[r+1]]:e,l=await K(i),a={name:o,slug:n,config:l};s&&(a.description=s);let d=await f("/api/v1/workflows",{method:"POST",body:a});console.log(JSON.stringify(d,null,2)),console.error(`Workflow "${o}" created.`)},"workflowCreate"),fe=t(async e=>{let o=e[0];o||(console.error("Usage: kenobi-pages workflow get <workflowId>"),process.exit(2));let n=await f(`/api/v1/workflows/${o}?full=true`);console.log(JSON.stringify(n,null,2))},"workflowGet"),ue=t(async e=>{let o=e[0];o||(console.error("Usage: kenobi-pages workflow update <workflowId> --config '<json>'"),console.error(" kenobi-pages workflow update <workflowId> --file config.json"),process.exit(2));let n=e.slice(1),s=h(n,"--name"),r=h(n,"--description"),i=n.indexOf("--config"),l=i!==-1&&n[i+1]?[n[i+1]]:n,a={};s&&(a.name=s),r!==void 0&&(a.description=r),(n.includes("--config")||n.includes("--file"))&&(a.config=await K(l)),Object.keys(a).length===0&&(console.error("Error: Nothing to update. Pass --name, --description, --config, or --file."),process.exit(2));let m=await f(`/api/v1/workflows/${o}`,{method:"PUT",body:a});console.log(JSON.stringify(m,null,2)),console.error("Workflow updated.")},"workflowUpdate"),de=t(async e=>{let o=e[0];o||(console.error("Usage: kenobi-pages workflow delete <workflowId>"),process.exit(2)),await f(`/api/v1/workflows/${o}`,{method:"DELETE"}),console.error("Workflow deleted.")},"workflowDelete"),we=`kenobi-pages \u2014 Kenobi Pages CLI
|
|
17
17
|
|
|
18
18
|
Commands:
|
|
19
19
|
init Set up your API key (interactive)
|
|
@@ -72,4 +72,4 @@ Environment:
|
|
|
72
72
|
Key resolution order:
|
|
73
73
|
1. KENOBI_PAGES_KEY environment variable
|
|
74
74
|
2. ~/.kenobi/config.json (written by 'init')
|
|
75
|
-
`,[,,...
|
|
75
|
+
`,[,,...x]=process.argv;(x.length===0||x[0]==="--help"||x[0]==="-h")&&(console.log(we),process.exit(0));var[u,p,...k]=x;if(u==="init")await Q();else if(u==="schema"&&p==="get")await ee(k);else if(u==="schema"&&p==="push")await oe(k);else if(u==="page"&&p==="get")await ne(k);else if(u==="types")await se([p,...k]);else if(u==="sources")if(p==="sample"){let e=k[0];e||(console.error("Usage: kenobi-pages sources sample <sourceKey>"),process.exit(2)),await pe(e)}else p===void 0||p==="list"?await le():(console.error(`Unknown sources subcommand: ${p}`),console.error("Run 'kenobi-pages --help' for usage."),process.exit(2));else u==="workflows"?await ce():u==="workflow"&&p==="create"?await ge(k):u==="workflow"&&p==="get"?await fe(k):u==="workflow"&&p==="update"?await ue(k):u==="workflow"&&p==="delete"?await de(k):u==="run"?await ae([p,...k].filter(Boolean)):(console.error(`Unknown command: ${x.join(" ")}`),console.error("Run 'kenobi-pages --help' for usage."),process.exit(1));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kenobi-pages",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Kenobi Pages SDK — fetch personalized page content from Kenobi workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"bin",
|
|
19
|
-
"dist"
|
|
19
|
+
"dist",
|
|
20
|
+
"skills",
|
|
21
|
+
"rules",
|
|
22
|
+
"SKILL.md"
|
|
20
23
|
],
|
|
21
24
|
"publishConfig": {
|
|
22
25
|
"access": "public"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Enforce the kenobi-pages skill's mandatory discovery phase. Applies whenever kenobi-pages skill is relevant.
|
|
3
|
+
globs:
|
|
4
|
+
alwaysApply: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Kenobi Pages — Skill Compliance
|
|
8
|
+
|
|
9
|
+
When the user's request matches the `kenobi-pages` skill (personalized pages, landing pages per lead, dynamic content, post-call pages, AI-generated content pipelines), you MUST follow this protocol:
|
|
10
|
+
|
|
11
|
+
1. **Your first response** must contain ONLY the discovery question from the skill — no tool calls, no file reads, no exploring, no package installs, no code. Ask:
|
|
12
|
+
|
|
13
|
+
> Before I start building, I need to know where you are:
|
|
14
|
+
>
|
|
15
|
+
> Do you already have a Kenobi workflow set up, or are we starting from scratch?
|
|
16
|
+
|
|
17
|
+
2. **Wait for the user's answer** before reading any sub-skill, running any command, or writing any code.
|
|
18
|
+
|
|
19
|
+
3. After the user answers, read the kenobi-pages `SKILL.md` and follow its phases in order.
|
|
20
|
+
|
|
21
|
+
This rule exists because agents consistently skip the discovery step and jump straight to implementation. That breaks the product flow. The question is not optional.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kenobi-pages/pages
|
|
3
|
+
description: Build a Next.js dynamic route that renders personalized, AI-generated content per lead. Use when creating landing pages, building pages from a Kenobi workflow schema, or personalizing an existing page with dynamic content.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Building Pages
|
|
7
|
+
|
|
8
|
+
This skill covers creating the Next.js page that renders personalized content fetched from Kenobi at runtime.
|
|
9
|
+
|
|
10
|
+
## Prerequisite — Do Not Skip
|
|
11
|
+
|
|
12
|
+
You must have already completed the discovery question and setup from the parent skill (`kenobi-pages/SKILL.md`). If the user hasn't been asked whether they have a workflow yet, STOP — go back to the parent skill and start from Phase 1. Do not continue reading this file.
|
|
13
|
+
|
|
14
|
+
## Determine the Mode
|
|
15
|
+
|
|
16
|
+
You should already know which mode to use from the discovery question in the parent skill. If not, ask:
|
|
17
|
+
|
|
18
|
+
> Do you already have a Kenobi workflow with a defined output schema, or do you have an existing page you'd like to personalize?
|
|
19
|
+
|
|
20
|
+
- **"I have a workflow"** → Forward mode (schema already exists, build the page around it)
|
|
21
|
+
- **"I have a page I want to personalize"** → Reverse mode (analyze the page, define a schema, push it to Kenobi)
|
|
22
|
+
- **"Neither" / unclear** → They probably need to set up a workflow first. Read the **workflows** sub-skill. Come back here once a workflow exists.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Forward Mode — Build a Page From an Existing Schema
|
|
27
|
+
|
|
28
|
+
The workflow already exists. You need to discover the schema, set up the types, and build the dynamic route.
|
|
29
|
+
|
|
30
|
+
### 1. Get the workflow ID
|
|
31
|
+
|
|
32
|
+
If you don't know it, list workflows:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx kenobi-pages workflows
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Ask the user which workflow this page is for.
|
|
39
|
+
|
|
40
|
+
### 2. Fetch the schema and generate types
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx kenobi-pages schema get <workflowId>
|
|
44
|
+
npx kenobi-pages types <workflowId> > lib/kenobi-types.ts
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Review the generated types. Tell the user what fields will be available on the page. For example:
|
|
48
|
+
|
|
49
|
+
> Your workflow produces these fields for each lead:
|
|
50
|
+
>
|
|
51
|
+
> - `headline` (string) — personalized headline
|
|
52
|
+
> - `value_proposition` (string) — why your product matters to them
|
|
53
|
+
> - `use_cases` (array) — 3-5 relevant use cases
|
|
54
|
+
> - `hero_image_url` (url) — AI-generated hero image
|
|
55
|
+
> - `cta_text` (string) — call-to-action button text
|
|
56
|
+
>
|
|
57
|
+
> I'll build a page that renders all of these. Sound good?
|
|
58
|
+
|
|
59
|
+
### 3. Build the dynamic route
|
|
60
|
+
|
|
61
|
+
Create `app/for/[slug]/page.tsx` (or whatever route the user prefers). The page should:
|
|
62
|
+
|
|
63
|
+
- Import and use the `kenobi` client from `lib/kenobi.ts`
|
|
64
|
+
- Call `kenobi.getPage(WORKFLOW_ID, slug)` in a server component
|
|
65
|
+
- Return `notFound()` if the page returns `null`
|
|
66
|
+
- Use `{ revalidate: 60 }` (or whatever caching the user wants) as the third argument
|
|
67
|
+
- Cast `page.content` to the generated type
|
|
68
|
+
|
|
69
|
+
The user's brand, layout preferences, and design system should drive the page design. Don't use a generic template — build it to match their project.
|
|
70
|
+
|
|
71
|
+
### 4. Placeholder content for development
|
|
72
|
+
|
|
73
|
+
If no workflow runs have completed yet, create a placeholder file with realistic mock data matching the type interface. Use it as a fallback in development only:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
if (!page && process.env.NODE_ENV === "development") return PLACEHOLDER_CONTENT;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Tell the user to remove the placeholder once real content is flowing.
|
|
80
|
+
|
|
81
|
+
### 5. Caching
|
|
82
|
+
|
|
83
|
+
Explain the caching options and ask what they want:
|
|
84
|
+
|
|
85
|
+
> How fresh should the content be? Options:
|
|
86
|
+
>
|
|
87
|
+
> - Revalidate every N seconds (ISR) — good default, e.g. `{ revalidate: 60 }`
|
|
88
|
+
> - Cache forever — `{ revalidate: false }`
|
|
89
|
+
> - Always fresh — `{ revalidate: 0 }`
|
|
90
|
+
> - On-demand — `{ tags: ["kenobi"] }` with `revalidateTag("kenobi")`
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Reverse Mode — Personalize an Existing Page
|
|
95
|
+
|
|
96
|
+
The user has an existing page with hardcoded content and wants to make parts of it dynamic.
|
|
97
|
+
|
|
98
|
+
### 1. Analyze the page
|
|
99
|
+
|
|
100
|
+
Read the page's JSX. Identify every piece of content that should change between leads — headings, descriptions, images, CTAs, lists, testimonials, etc.
|
|
101
|
+
|
|
102
|
+
### 2. Propose the schema to the user
|
|
103
|
+
|
|
104
|
+
Present your analysis and ask for confirmation:
|
|
105
|
+
|
|
106
|
+
> I've analyzed your page. Here are the parts I think should be personalized per lead:
|
|
107
|
+
>
|
|
108
|
+
> 1. `headline` (string) — the main H1
|
|
109
|
+
> 2. `subtitle` (string) — the description under the headline
|
|
110
|
+
> 3. `pain_points` (array of { title, description }) — the three pain points section
|
|
111
|
+
> 4. `cta_text` (string) — the button text
|
|
112
|
+
>
|
|
113
|
+
> Does this look right? Should I add or remove anything?
|
|
114
|
+
|
|
115
|
+
Do not proceed until the user confirms. They may want to add fields you missed or remove ones they want to keep static.
|
|
116
|
+
|
|
117
|
+
### 3. Push the schema
|
|
118
|
+
|
|
119
|
+
After confirmation, construct the schema JSON and push it:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npx kenobi-pages schema push "Page Name" '{"fields":{...}}'
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Tell the user what to do next:
|
|
126
|
+
|
|
127
|
+
> Schema pushed to Kenobi. Next steps:
|
|
128
|
+
>
|
|
129
|
+
> 1. Go to the Kenobi workflow builder
|
|
130
|
+
> 2. Select this schema as your output target
|
|
131
|
+
> 3. Connect your data sources and configure AI generation
|
|
132
|
+
> 4. Run the workflow for your leads
|
|
133
|
+
>
|
|
134
|
+
> Once a workflow is configured, I can refactor this page to fetch content dynamically. Want me to do that now with placeholder data, or wait until the workflow is ready?
|
|
135
|
+
|
|
136
|
+
### 4. Refactor the page
|
|
137
|
+
|
|
138
|
+
Replace hardcoded content with the same pattern as forward mode — `kenobi.getPage()` in a server component, with placeholders for development.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Error Handling
|
|
143
|
+
|
|
144
|
+
`getPage` returns `null` when content doesn't exist for a slug — it does not throw. Handle it with `notFound()`.
|
|
145
|
+
|
|
146
|
+
For API key issues, the SDK throws `KenobiUnauthorizedError`. Catch it if you want custom error UI, otherwise let it bubble to the error boundary.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { KenobiUnauthorizedError, KenobiPagesError } from "kenobi-pages";
|
|
150
|
+
```
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kenobi-pages/run
|
|
3
|
+
description: Trigger Kenobi workflow runs to generate personalized content for leads. Use when generating pages, running workflows for prospects, creating content after sales calls, or batch-generating landing pages.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Running Workflows
|
|
7
|
+
|
|
8
|
+
This skill covers triggering workflow runs, polling for completion, and verifying the generated content. A run executes a workflow for one lead and produces the content that the page will render.
|
|
9
|
+
|
|
10
|
+
## Prerequisite — Do Not Skip
|
|
11
|
+
|
|
12
|
+
You must have already completed the discovery question and setup from the parent skill (`kenobi-pages/SKILL.md`). If the user hasn't been asked whether they have a workflow yet, STOP — go back to the parent skill and start from Phase 1. Do not continue reading this file.
|
|
13
|
+
|
|
14
|
+
## Before Running
|
|
15
|
+
|
|
16
|
+
A workflow must already exist. If the user hasn't created one, read the **workflows** sub-skill first.
|
|
17
|
+
|
|
18
|
+
Check what's available:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx kenobi-pages workflows
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
If there are multiple workflows, ask which one to use. If there's only one, confirm it:
|
|
25
|
+
|
|
26
|
+
> I found a workflow called "Post-Call Landing Page" (ID: 42). It requires these parameters:
|
|
27
|
+
>
|
|
28
|
+
> - `slug` — URL slug for the page
|
|
29
|
+
> - `company_domain` — the lead's company domain
|
|
30
|
+
>
|
|
31
|
+
> Is this the right workflow?
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Single Run
|
|
36
|
+
|
|
37
|
+
### 1. Gather the inputs
|
|
38
|
+
|
|
39
|
+
Ask the user for the values needed. At minimum, every workflow needs a `slug`:
|
|
40
|
+
|
|
41
|
+
> What's the lead's company? I'll use that to generate the slug and look up their data.
|
|
42
|
+
|
|
43
|
+
Derive the slug from the company name (e.g., "Acme Corp" -> `acme-corp`).
|
|
44
|
+
|
|
45
|
+
### 2. Check for context
|
|
46
|
+
|
|
47
|
+
Ask if they have additional context for the AI:
|
|
48
|
+
|
|
49
|
+
> Do you have any context I should pass along? For example:
|
|
50
|
+
>
|
|
51
|
+
> - A call transcript or meeting notes
|
|
52
|
+
> - Specific talking points to emphasize
|
|
53
|
+
> - Tone or style preferences for this particular lead
|
|
54
|
+
>
|
|
55
|
+
> This is optional — the workflow will use its default instructions if you don't provide any.
|
|
56
|
+
|
|
57
|
+
Context is ephemeral — it's prepended to the AI generation prompts for this run only and isn't stored.
|
|
58
|
+
|
|
59
|
+
### 3. Confirm before triggering
|
|
60
|
+
|
|
61
|
+
Runs consume AI generation credits. Always confirm:
|
|
62
|
+
|
|
63
|
+
> I'm about to run the "Post-Call Landing Page" workflow for **acme-corp**. This will:
|
|
64
|
+
>
|
|
65
|
+
> - Look up Acme Corp's data from HubSpot
|
|
66
|
+
> - Generate personalized content using AI
|
|
67
|
+
> - Store it so the page at `/for/acme-corp` renders the content
|
|
68
|
+
>
|
|
69
|
+
> Go ahead?
|
|
70
|
+
|
|
71
|
+
### 4. Trigger
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npx kenobi-pages run <workflowId> --params '{"slug":"acme-corp","company_domain":"acme.com"}'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
With context:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npx kenobi-pages run <workflowId> \
|
|
81
|
+
--params '{"slug":"acme-corp","company_domain":"acme.com"}' \
|
|
82
|
+
--context "Call notes: They need faster onboarding. Emphasize self-serve setup."
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The CLI polls automatically and prints the final result to stdout when done.
|
|
86
|
+
|
|
87
|
+
### 5. Verify
|
|
88
|
+
|
|
89
|
+
Once the run completes, verify the content looks right:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx kenobi-pages page get <workflowId> acme-corp
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Share the key content with the user so they can sanity-check it before sharing the link.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Batch Runs
|
|
100
|
+
|
|
101
|
+
If the user wants to generate pages for multiple leads:
|
|
102
|
+
|
|
103
|
+
### 1. Get the list
|
|
104
|
+
|
|
105
|
+
Ask for all the leads:
|
|
106
|
+
|
|
107
|
+
> Which leads do you want to generate pages for? Give me company names and domains (or whatever parameters the workflow needs).
|
|
108
|
+
|
|
109
|
+
### 2. Confirm the batch
|
|
110
|
+
|
|
111
|
+
> I'll run the workflow for these 5 leads:
|
|
112
|
+
>
|
|
113
|
+
> 1. acme-corp (acme.com)
|
|
114
|
+
> 2. globex (globex.com)
|
|
115
|
+
> 3. initech (initech.com)
|
|
116
|
+
> 4. hooli (hooli.com)
|
|
117
|
+
> 5. piedpiper (piedpiper.com)
|
|
118
|
+
>
|
|
119
|
+
> Each run takes 30-60 seconds. Total estimated time: ~3-5 minutes. Proceed?
|
|
120
|
+
|
|
121
|
+
### 3. Run sequentially
|
|
122
|
+
|
|
123
|
+
Trigger each run one at a time. Report progress after each completion:
|
|
124
|
+
|
|
125
|
+
> Completed 2/5: acme-corp (success), globex (success). Running initech...
|
|
126
|
+
|
|
127
|
+
If a run fails, report it and continue with the remaining leads. Ask the user if they want to retry failures at the end.
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kenobi-pages/workflows
|
|
3
|
+
description: Create and modify Kenobi workflows that wire data sources to AI generation steps. Use when building a content pipeline, connecting CRM data to page generation, or configuring how AI produces personalized content.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Building Workflows
|
|
7
|
+
|
|
8
|
+
A workflow is a pipeline that takes data from external sources (CRM, databases, etc.), runs AI generation, and produces personalized content keyed by a slug. This skill covers creating and modifying workflows programmatically via the CLI.
|
|
9
|
+
|
|
10
|
+
## Prerequisite — Do Not Skip
|
|
11
|
+
|
|
12
|
+
You must have already completed the discovery question and setup from the parent skill (`kenobi-pages/SKILL.md`). If the user hasn't been asked whether they have a workflow yet, STOP — go back to the parent skill and start from Phase 1. Do not continue reading this file.
|
|
13
|
+
|
|
14
|
+
## First: Does a Workflow Already Exist?
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx kenobi-pages workflows
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If a workflow already exists and the user wants to modify it:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx kenobi-pages workflow get <id>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This returns the full config JSON. Edit it and push it back with `workflow update`.
|
|
27
|
+
|
|
28
|
+
If starting fresh, continue below.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Building a New Workflow
|
|
33
|
+
|
|
34
|
+
### 1. Understand what the user wants
|
|
35
|
+
|
|
36
|
+
Ask:
|
|
37
|
+
|
|
38
|
+
> What's this workflow for? For example: "personalized landing pages sent after sales calls" or "custom product pages for each prospect."
|
|
39
|
+
|
|
40
|
+
Then ask about their data:
|
|
41
|
+
|
|
42
|
+
> Where does the lead data come from? For example:
|
|
43
|
+
> - A Notion database (CRM contacts, company info)
|
|
44
|
+
> - HubSpot (contacts, deals)
|
|
45
|
+
> - Or will you pass everything as parameters when triggering a run?
|
|
46
|
+
|
|
47
|
+
### 2. Discover connected sources
|
|
48
|
+
|
|
49
|
+
If they have external data sources:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx kenobi-pages sources
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This lists all data sources connected to their Kenobi org with their schemas. Share what you find:
|
|
56
|
+
|
|
57
|
+
After you pick a source you plan to use in the workflow, preview real rows so lookup fields and formats are obvious:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx kenobi-pages sources sample <sourceKey>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Use the `sourceKey` from the list output (for example `notion:…` or `hubspot-crm:…`). Check values in columns you will match on — for example if the workflow uses lookup resolution on a domain field, confirm whether stored values look like `acme.com` or `https://acme.com`, and align the runtime param you pass accordingly.
|
|
64
|
+
|
|
65
|
+
> I can see these data sources connected to your Kenobi account:
|
|
66
|
+
> - "CRM Contacts" (Notion) — has fields: Name, Email, Domain, Company Size
|
|
67
|
+
> - "Deals" (HubSpot) — has fields: deal_name, amount, stage
|
|
68
|
+
>
|
|
69
|
+
> Which of these should feed into the workflow?
|
|
70
|
+
|
|
71
|
+
If they don't have sources connected, tell them:
|
|
72
|
+
|
|
73
|
+
> You'll need to connect your data sources in the Kenobi workflow builder UI first. Go to the workflow builder and use the integrations panel to connect Notion, HubSpot, or other services. Then come back and I can set up the workflow.
|
|
74
|
+
>
|
|
75
|
+
> Alternatively, you can build a workflow that only uses runtime parameters (values you pass in when triggering a run) — no external data sources needed.
|
|
76
|
+
|
|
77
|
+
### 3. Define the output schema
|
|
78
|
+
|
|
79
|
+
Ask:
|
|
80
|
+
|
|
81
|
+
> What content should be generated for each lead? For example:
|
|
82
|
+
> - A headline and value proposition
|
|
83
|
+
> - A list of relevant use cases
|
|
84
|
+
> - A hero image
|
|
85
|
+
> - A CTA button text
|
|
86
|
+
|
|
87
|
+
If a schema was already pushed via the pages skill (reverse mode), use that. Otherwise, define it together with the user.
|
|
88
|
+
|
|
89
|
+
### 4. Assemble the config
|
|
90
|
+
|
|
91
|
+
A workflow config is a JSON object. Here's the structure — fill in the values based on what you learned from the user:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"id": "<workflow-slug>",
|
|
96
|
+
"description": "<what this workflow does — this gets prepended to AI generation prompts>",
|
|
97
|
+
"params": [
|
|
98
|
+
{ "name": "slug", "description": "URL slug for the generated page" }
|
|
99
|
+
],
|
|
100
|
+
"sources": [],
|
|
101
|
+
"output": {
|
|
102
|
+
"provider": "kenobi-pages",
|
|
103
|
+
"dataSourceKey": "kenobi-pages:<schema-slug>",
|
|
104
|
+
"title": "<human-readable name>",
|
|
105
|
+
"schema": { "<field definitions>" }
|
|
106
|
+
},
|
|
107
|
+
"fields": {
|
|
108
|
+
"slug": { "strategy": "param", "paramName": "slug" }
|
|
109
|
+
},
|
|
110
|
+
"generationGroups": [],
|
|
111
|
+
"destinations": [
|
|
112
|
+
{ "id": "pages", "adapter": "kenobi-pages-store", "adapterConfig": {} }
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Key rules:
|
|
118
|
+
- `id` must match the workflow slug
|
|
119
|
+
- Every workflow must have a `slug` field bound to `{ "strategy": "param", "paramName": "slug" }`
|
|
120
|
+
- Each output field needs a binding: `param` (copy from runtime parameter), `passthrough` (copy from source field), `generate` (AI text), or `generate-image` (AI image)
|
|
121
|
+
- Fields bound to `generate` or `generate-image` reference a `generationGroups` entry by `groupId`
|
|
122
|
+
- A generation group has a `system` prompt, a `strategy` ("generate" or "generate-image"), and `contextSources` listing which source data the AI sees
|
|
123
|
+
- Source `resolution` determines how data is fetched: `single` (take first record), `lookup` (filter by a param value), or `collection` (fetch all, optionally with AI selection)
|
|
124
|
+
|
|
125
|
+
### 5. Confirm before creating
|
|
126
|
+
|
|
127
|
+
Show the user a summary of what you're about to create. Do not proceed without confirmation.
|
|
128
|
+
|
|
129
|
+
> Here's the workflow I'm about to create:
|
|
130
|
+
>
|
|
131
|
+
> **Name:** Post-Call Landing Page
|
|
132
|
+
> **Slug:** post-call-page
|
|
133
|
+
> **Sources:** HubSpot Contacts (lookup by company domain)
|
|
134
|
+
> **Output fields:** slug, headline, value_proposition, use_cases (array), cta_text
|
|
135
|
+
> **AI generation:** One text generation step using HubSpot contact data as context
|
|
136
|
+
> **Destination:** Kenobi Pages (stored as page content)
|
|
137
|
+
>
|
|
138
|
+
> Should I go ahead and create this?
|
|
139
|
+
|
|
140
|
+
### 6. Create
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npx kenobi-pages workflow create \
|
|
144
|
+
--name "<name>" \
|
|
145
|
+
--slug "<slug>" \
|
|
146
|
+
--description "<description>" \
|
|
147
|
+
--file workflow-config.json
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Or pass the config inline with `--config '<json>'`.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Full Example Config
|
|
155
|
+
|
|
156
|
+
A workflow that reads HubSpot contacts and generates landing page content:
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{
|
|
160
|
+
"id": "post-call-page",
|
|
161
|
+
"description": "Generate personalized post-call landing pages. Emphasize how our product solves the lead's specific pain points.",
|
|
162
|
+
"params": [
|
|
163
|
+
{ "name": "slug", "description": "URL slug for the page" },
|
|
164
|
+
{ "name": "company_domain", "description": "Lead company domain for CRM lookup" }
|
|
165
|
+
],
|
|
166
|
+
"sources": [
|
|
167
|
+
{
|
|
168
|
+
"id": "hubspot-contacts",
|
|
169
|
+
"provider": "hubspot-crm",
|
|
170
|
+
"dataSourceKey": "hubspot-crm:contacts",
|
|
171
|
+
"title": "HubSpot Contacts",
|
|
172
|
+
"schema": {
|
|
173
|
+
"firstname": { "type": "string" },
|
|
174
|
+
"lastname": { "type": "string" },
|
|
175
|
+
"company": { "type": "string" },
|
|
176
|
+
"website": { "type": "string" }
|
|
177
|
+
},
|
|
178
|
+
"resolution": { "kind": "lookup", "field": "website", "param": "company_domain" }
|
|
179
|
+
}
|
|
180
|
+
],
|
|
181
|
+
"output": {
|
|
182
|
+
"provider": "kenobi-pages",
|
|
183
|
+
"dataSourceKey": "kenobi-pages:post-call-page",
|
|
184
|
+
"title": "Post-Call Page",
|
|
185
|
+
"schema": {
|
|
186
|
+
"slug": { "type": "string", "description": "URL slug" },
|
|
187
|
+
"headline": { "type": "string", "description": "Attention-grabbing personalized headline" },
|
|
188
|
+
"value_proposition": { "type": "string", "description": "Why our product matters to this lead" },
|
|
189
|
+
"use_cases": {
|
|
190
|
+
"type": "array",
|
|
191
|
+
"items": { "type": "object", "fields": { "title": { "type": "string" }, "description": { "type": "string" } } },
|
|
192
|
+
"min": 3, "max": 5,
|
|
193
|
+
"description": "Relevant use cases for the lead"
|
|
194
|
+
},
|
|
195
|
+
"cta_text": { "type": "string", "description": "Call-to-action button text" }
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
"fields": {
|
|
199
|
+
"slug": { "strategy": "param", "paramName": "slug" },
|
|
200
|
+
"headline": { "strategy": "generate", "groupId": "text-gen" },
|
|
201
|
+
"value_proposition": { "strategy": "generate", "groupId": "text-gen" },
|
|
202
|
+
"use_cases": { "strategy": "generate", "groupId": "text-gen" },
|
|
203
|
+
"cta_text": { "strategy": "generate", "groupId": "text-gen" }
|
|
204
|
+
},
|
|
205
|
+
"generationGroups": [
|
|
206
|
+
{
|
|
207
|
+
"id": "text-gen",
|
|
208
|
+
"strategy": "generate",
|
|
209
|
+
"system": "Write personalized landing page content for a B2B SaaS product. Use the lead's company data to craft compelling, specific copy. Be concise and action-oriented.",
|
|
210
|
+
"contextSources": [{ "sourceId": "hubspot-contacts" }]
|
|
211
|
+
}
|
|
212
|
+
],
|
|
213
|
+
"destinations": [
|
|
214
|
+
{ "id": "pages-dest", "adapter": "kenobi-pages-store", "adapterConfig": {} }
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Modifying Workflows
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
# Get current config
|
|
223
|
+
npx kenobi-pages workflow get <id>
|
|
224
|
+
|
|
225
|
+
# Update (partial — only send what changed)
|
|
226
|
+
npx kenobi-pages workflow update <id> --config '<json>'
|
|
227
|
+
npx kenobi-pages workflow update <id> --name "New Name"
|
|
228
|
+
npx kenobi-pages workflow update <id> --description "New description"
|
|
229
|
+
|
|
230
|
+
# Delete (confirm with user first — this is destructive)
|
|
231
|
+
npx kenobi-pages workflow delete <id>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Always use `workflow get` before updating to avoid overwriting changes made in the Kenobi UI.
|
|
235
|
+
|
|
236
|
+
## Tips
|
|
237
|
+
|
|
238
|
+
- Field `description` values matter — they're included in AI generation prompts, so be specific about what you want
|
|
239
|
+
- Test incrementally: create with a few fields first, trigger a test run, verify output, then add complexity
|
|
240
|
+
- The `description` on the workflow itself is prepended to every generation group's system prompt — use it for global instructions like tone, audience, or constraints
|