kenobi-pages 0.1.1 → 0.1.3

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/bin/cli.mjs CHANGED
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
- var C=Object.defineProperty;var t=(e,o)=>C(e,"name",{value:o,configurable:!0});import{existsSync as P,mkdirSync as F,readFileSync as E,writeFileSync as I,appendFileSync as G}from"node:fs";import{readFile as j}from"node:fs/promises";import{homedir as L}from"node:os";import{join as _,resolve as x}from"node:path";import{createInterface as R}from"node:readline";var U="https://kenobi.ai",v=_(L(),".kenobi"),S=_(v,"config.json"),l="KENOBI_PAGES_KEY",$=t(()=>{try{return JSON.parse(E(S,"utf-8"))}catch{return{}}},"readGlobalConfig"),B=t(e=>{F(v,{recursive:!0}),I(S,JSON.stringify(e,null,2)+`
3
- `)},"writeGlobalConfig"),J=t(()=>{let e=process.env[l]||$().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"),T=t(()=>(process.env.KENOBI_BASE_URL??$().baseUrl??U).replace(/\/+$/,""),"getBaseUrl"),k=t(async(e,o={})=>{let n=`${T()}${e}`,s=await fetch(n,{method:o.method??"GET",headers:{"x-kenobi-key":J(),"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"),w=t(e=>new Promise(o=>{let n=R({input:process.stdin,output:process.stderr});n.question(e,s=>{n.close(),o(s.trim())})}),"prompt"),Y=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"),W=t(async e=>{let o=e.indexOf("--file");return o!==-1&&e[o+1]?j(e[o+1],"utf-8"):null},"parseFileArg"),D=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 w("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 w("Continue anyway? (y/N): ")).toLowerCase()!=="y"&&(console.error("Aborting."),process.exit(1)));let o=$();o.apiKey=e,B(o),console.error(`\u2713 Saved to ${S}`);let n=x(process.cwd(),".env.local"),s=x(process.cwd(),".env"),r=P(n)?n:P(s)?s:null;if(r){let i=E(r,"utf-8");if(i.includes(l))console.error(`\u2713 ${l} already present in ${r.split("/").pop()}`);else{let p=i.endsWith(`
2
+ var K=Object.defineProperty;var t=(e,o)=>K(e,"name",{value:o,configurable:!0});import{existsSync as O,mkdirSync as L,readFileSync as A,writeFileSync as x,appendFileSync as F}from"node:fs";import{readFile as R}from"node:fs/promises";import{homedir as G}from"node:os";import{join as P,resolve as $}from"node:path";import{createInterface as T}from"node:readline";var j="https://kenobi.ai",v=P(G(),".kenobi"),S=P(v,"config.json"),g="KENOBI_PAGES_KEY",E=t(()=>{try{return JSON.parse(A(S,"utf-8"))}catch{return{}}},"readGlobalConfig"),U=t(e=>{L(v,{recursive:!0}),x(S,JSON.stringify(e,null,2)+`
3
+ `)},"writeGlobalConfig"),J=t(()=>{let e=process.env[g]||E().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"),B=t(()=>(process.env.KENOBI_BASE_URL??E().baseUrl??j).replace(/\/+$/,""),"getBaseUrl"),d=t(async(e,o={})=>{let n=`${B()}${e}`,s=await fetch(n,{method:o.method??"GET",headers:{"x-kenobi-key":J(),"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"),b=t(e=>new Promise(o=>{let n=T({input:process.stdin,output:process.stderr});n.question(e,s=>{n.close(),o(s.trim())})}),"prompt"),Y=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"),D=t(async e=>{let o=e.indexOf("--file");return o!==-1&&e[o+1]?R(e[o+1],"utf-8"):null},"parseFileArg"),M=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 b("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 b("Continue anyway? (y/N): ")).toLowerCase()!=="y"&&(console.error("Aborting."),process.exit(1)));let o=E();o.apiKey=e,U(o),console.error(`\u2713 Saved to ${S}`);let n=$(process.cwd(),".env.local"),s=$(process.cwd(),".env"),r=O(n)?n:O(s)?s:null;if(r){let i=A(r,"utf-8");if(i.includes(g))console.error(`\u2713 ${g} already present in ${r.split("/").pop()}`);else{let a=i.endsWith(`
4
4
  `)?"":`
5
- `;G(r,`${p}${l}="${e}"
6
- `),console.error(`\u2713 Added ${l} to ${r.split("/").pop()}`)}}else(await w("No .env.local found. Create one with your API key? (Y/n): ")).toLowerCase()!=="n"&&(I(n,`${l}="${e}"
7
- `),console.error(`\u2713 Created .env.local with ${l}`));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"),M=t(async e=>{let o=e[0];o||(console.error("Usage: kenobi-pages schema get <workflowId>"),process.exit(2));let n=await k(`/api/v1/pages/${o}/schema`);console.log(JSON.stringify(n,null,2))},"schemaGet"),q=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 W(n),r=s?null:n.find(b=>b.startsWith("{")),i=!s&&!r?await Y():null,p=s??r??i;(!p||p.trim().length===0)&&(console.error("Error: No schema provided."),console.error("Pass inline JSON, --file <path>, or pipe to stdin."),process.exit(2));let f;try{f=JSON.parse(p)}catch{console.error("Error: Invalid JSON."),process.exit(2)}let m=await k("/api/v1/pages/schema",{method:"POST",body:{name:o,schema:f}});console.log(JSON.stringify(m,null,2)),console.error(`Schema "${m.name}" pushed successfully.`),console.error(`Source key: ${m.sourceKey}`),console.error("You can now select this schema as an output target in the Kenobi workflow builder.")},"schemaPush"),z=t(async e=>{let[o,n]=e;(!o||!n)&&(console.error("Usage: kenobi-pages page get <workflowId> <slug>"),process.exit(2));let s=await k(`/api/v1/pages/${o}/${n}`);console.log(JSON.stringify(s,null,2))},"pageGet"),H=t(async e=>{let o=e[0];o||(console.error("Usage: kenobi-pages types <workflowId>"),process.exit(2));let n=await k(`/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(a=>" ".repeat(a),"indent"),i=t((a,c=1)=>{switch(a.type){case"string":case"url":return"string";case"number":return"number";case"boolean":return"boolean";case"enum":return a.values?.length?a.values.map(u=>`"${u}"`).join(" | "):"string";case"object":{let u=Object.entries(a.fields??{});return u.length===0?"Record<string, unknown>":`{
8
- ${u.map(([N,O])=>{let K=O.optional?"?":"";return`${r(c)}${N}${K}: ${i(O,c+1)}`}).join(`
5
+ `;F(r,`${a}${g}="${e}"
6
+ `),console.error(`\u2713 Added ${g} to ${r.split("/").pop()}`)}}else(await b("No .env.local found. Create one with your API key? (Y/n): ")).toLowerCase()!=="n"&&(x(n,`${g}="${e}"
7
+ `),console.error(`\u2713 Created .env.local with ${g}`));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"),W=t(async e=>{let o=e[0];o||(console.error("Usage: kenobi-pages schema get <workflowId>"),process.exit(2));let n=await d(`/api/v1/pages/${o}/schema`);console.log(JSON.stringify(n,null,2))},"schemaGet"),H=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 D(n),r=s?null:n.find(y=>y.startsWith("{")),i=!s&&!r?await Y():null,a=s??r??i;(!a||a.trim().length===0)&&(console.error("Error: No schema provided."),console.error("Pass inline JSON, --file <path>, or pipe to stdin."),process.exit(2));let f;try{f=JSON.parse(a)}catch{console.error("Error: Invalid JSON."),process.exit(2)}let c=await d("/api/v1/pages/schema",{method:"POST",body:{name:o,schema:f}});console.log(JSON.stringify(c,null,2)),console.error(`Schema "${c.name}" pushed successfully.`),console.error(`Source key: ${c.sourceKey}`),console.error("You can now select this schema as an output target in the Kenobi workflow builder.")},"schemaPush"),q=t(async e=>{let[o,n]=e;(!o||!n)&&(console.error("Usage: kenobi-pages page get <workflowId> <slug>"),process.exit(2));let s=await d(`/api/v1/pages/${o}/${n}`);console.log(JSON.stringify(s,null,2))},"pageGet"),z=t(async e=>{let o=e[0];o||(console.error("Usage: kenobi-pages types <workflowId>"),process.exit(2));let n=await d(`/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(l=>" ".repeat(l),"indent"),i=t((l,p=1)=>{switch(l.type){case"string":case"url":return"string";case"number":return"number";case"boolean":return"boolean";case"enum":return l.values?.length?l.values.map(m=>`"${m}"`).join(" | "):"string";case"object":{let m=Object.entries(l.fields??{});return m.length===0?"Record<string, unknown>":`{
8
+ ${m.map(([N,I])=>{let C=I.optional?"?":"";return`${r(p)}${N}${C}: ${i(I,p+1)}`}).join(`
9
9
  `)}
10
- ${r(c-1)}}`}case"array":return`Array<${i(a.items,c)}>`;default:return"unknown"}},"fieldToTs"),f=Object.entries(s.fields).map(([a,c])=>{let u=c.optional?"?":"";return`${c.description?` /** ${c.description} */
11
- `:""} ${a}${u}: ${i(c,2)}`}),b=`export interface ${n.title?n.title.replace(/[^a-zA-Z0-9]+/g,""):"PageContent"} {
10
+ ${r(p-1)}}`}case"array":return`Array<${i(l.items,p)}>`;default:return"unknown"}},"fieldToTs"),f=Object.entries(s.fields).map(([l,p])=>{let m=p.optional?"?":"";return`${p.description?` /** ${p.description} */
11
+ `:""} ${l}${m}: ${i(p,2)}`}),y=`export interface ${n.title?n.title.replace(/[^a-zA-Z0-9]+/g,""):"PageContent"} {
12
12
  ${f.join(`
13
13
  `)}
14
14
  }
15
- `;console.log(b)},"typesGen"),V=`kenobi-pages \u2014 Kenobi Pages CLI
15
+ `;console.log(y)},"typesGen"),V=new Set(["COMPLETED","FAILED","CANCELED","SYSTEM_FAILURE","CRASHED"]),X=t(e=>new Promise(o=>setTimeout(o,e)),"sleep"),Z=t(async e=>{let o=e[0];o||(console.error("Usage: kenobi-pages run <workflowId> [--params '<json>']"),process.exit(2));let n=e.slice(1),s=n.indexOf("--params"),r={};if(s!==-1&&n[s+1])try{r=JSON.parse(n[s+1])}catch{console.error("Error: Invalid JSON in --params"),process.exit(2)}let i=await d(`/api/v1/workflows/${o}/run`,{method:"POST",body:{params:r}});console.error(`Run triggered: ${i.runId}`),console.error("Polling for completion...");let a=2e3,f=15e3;for(;;){await X(a);let c=await d(`/api/v1/workflows/${o}/runs/${i.runId}`);if(console.error(` Status: ${c.status}`),V.has(c.status)){console.log(JSON.stringify(c,null,2)),c.status!=="COMPLETED"&&process.exit(1);return}a=Math.min(a*1.5,f)}},"runWorkflow"),Q=t(async()=>{let e=await d("/api/v1/workflows");console.log(JSON.stringify(e,null,2))},"listWorkflows"),ee=`kenobi-pages \u2014 Kenobi Pages CLI
16
16
 
17
17
  Commands:
18
18
  kenobi-pages init Set up your API key (interactive)
@@ -21,6 +21,8 @@ Commands:
21
21
  kenobi-pages schema push <name> --file f Push a schema to Kenobi (from file)
22
22
  kenobi-pages page get <workflowId> <slug> Fetch page content for a specific lead
23
23
  kenobi-pages types <workflowId> Generate TypeScript interface from schema
24
+ kenobi-pages workflows List workflows for your org
25
+ kenobi-pages run <workflowId> [--params '{}'] Trigger a workflow run and poll to completion
24
26
 
25
27
  Examples:
26
28
  npx kenobi-pages init
@@ -28,6 +30,8 @@ Examples:
28
30
  npx kenobi-pages schema push "My Page" '{"fields":{"headline":{"type":"string"}}}'
29
31
  npx kenobi-pages types 42 > lib/kenobi-types.ts
30
32
  npx kenobi-pages page get 42 acme-corp
33
+ npx kenobi-pages workflows
34
+ npx kenobi-pages run 42 --params '{"slug":"acme-corp"}'
31
35
 
32
36
  Output:
33
37
  All data is written as JSON to stdout. Status messages go to stderr.
@@ -47,4 +51,4 @@ Environment:
47
51
  Key resolution order:
48
52
  1. KENOBI_PAGES_KEY environment variable
49
53
  2. ~/.kenobi/config.json (written by 'init')
50
- `,[,,...d]=process.argv;(d.length===0||d[0]==="--help"||d[0]==="-h")&&(console.log(V),process.exit(0));var[g,h,...y]=d;g==="init"?await D():g==="schema"&&h==="get"?await M(y):g==="schema"&&h==="push"?await q(y):g==="page"&&h==="get"?await z(y):g==="types"?await H([h,...y]):(console.error(`Unknown command: ${d.join(" ")}`),console.error("Run 'kenobi-pages --help' for usage."),process.exit(1));
54
+ `,[,,...h]=process.argv;(h.length===0||h[0]==="--help"||h[0]==="-h")&&(console.log(ee),process.exit(0));var[u,w,...k]=h;u==="init"?await M():u==="schema"&&w==="get"?await W(k):u==="schema"&&w==="push"?await H(k):u==="page"&&w==="get"?await q(k):u==="types"?await z([w,...k]):u==="workflows"?await Q():u==="run"?await Z([w,...k].filter(Boolean)):(console.error(`Unknown command: ${h.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.1",
3
+ "version": "0.1.3",
4
4
  "description": "Kenobi Pages SDK — fetch personalized page content from Kenobi workflows",
5
5
  "type": "module",
6
6
  "exports": {
package/src/cli.ts CHANGED
@@ -331,6 +331,74 @@ const typesGen = async (args: string[]) => {
331
331
  console.log(output)
332
332
  }
333
333
 
334
+ // ── Run workflow ──
335
+
336
+ const TERMINAL_STATUSES = new Set(["COMPLETED", "FAILED", "CANCELED", "SYSTEM_FAILURE", "CRASHED"])
337
+
338
+ const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms))
339
+
340
+ const runWorkflow = async (args: string[]) => {
341
+ const workflowId = args[0]
342
+ if (!workflowId) {
343
+ console.error("Usage: kenobi-pages run <workflowId> [--params '<json>']")
344
+ process.exit(2)
345
+ }
346
+
347
+ const remaining = args.slice(1)
348
+ const paramsIdx = remaining.indexOf("--params")
349
+ let params: Record<string, string> = {}
350
+ if (paramsIdx !== -1 && remaining[paramsIdx + 1]) {
351
+ try {
352
+ params = JSON.parse(remaining[paramsIdx + 1]) as Record<string, string>
353
+ } catch {
354
+ console.error("Error: Invalid JSON in --params")
355
+ process.exit(2)
356
+ }
357
+ }
358
+
359
+ const handle = (await fetchKenobi(`/api/v1/workflows/${workflowId}/run`, {
360
+ method: "POST",
361
+ body: { params },
362
+ })) as { runId: string; status: string }
363
+
364
+ console.error(`Run triggered: ${handle.runId}`)
365
+ console.error("Polling for completion...")
366
+
367
+ let delay = 2000
368
+ const MAX_DELAY = 15000
369
+
370
+ // eslint-disable-next-line no-constant-condition
371
+ while (true) {
372
+ await sleep(delay)
373
+ const status = (await fetchKenobi(
374
+ `/api/v1/workflows/${workflowId}/runs/${handle.runId}`
375
+ )) as { runId: string; status: string; output?: unknown; finishedAt?: string }
376
+
377
+ console.error(` Status: ${status.status}`)
378
+
379
+ if (TERMINAL_STATUSES.has(status.status)) {
380
+ console.log(JSON.stringify(status, null, 2))
381
+ if (status.status !== "COMPLETED") process.exit(1)
382
+ return
383
+ }
384
+
385
+ delay = Math.min(delay * 1.5, MAX_DELAY)
386
+ }
387
+ }
388
+
389
+ const listWorkflows = async () => {
390
+ const data = (await fetchKenobi("/api/v1/workflows")) as {
391
+ workflows: Array<{
392
+ id: number
393
+ name: string
394
+ slug: string
395
+ params: Array<{ name: string; description?: string }>
396
+ }>
397
+ }
398
+
399
+ console.log(JSON.stringify(data, null, 2))
400
+ }
401
+
334
402
  // ── Router ──
335
403
 
336
404
  const HELP = `kenobi-pages — Kenobi Pages CLI
@@ -342,6 +410,8 @@ Commands:
342
410
  kenobi-pages schema push <name> --file f Push a schema to Kenobi (from file)
343
411
  kenobi-pages page get <workflowId> <slug> Fetch page content for a specific lead
344
412
  kenobi-pages types <workflowId> Generate TypeScript interface from schema
413
+ kenobi-pages workflows List workflows for your org
414
+ kenobi-pages run <workflowId> [--params '{}'] Trigger a workflow run and poll to completion
345
415
 
346
416
  Examples:
347
417
  npx kenobi-pages init
@@ -349,6 +419,8 @@ Examples:
349
419
  npx kenobi-pages schema push "My Page" '{"fields":{"headline":{"type":"string"}}}'
350
420
  npx kenobi-pages types 42 > lib/kenobi-types.ts
351
421
  npx kenobi-pages page get 42 acme-corp
422
+ npx kenobi-pages workflows
423
+ npx kenobi-pages run 42 --params '{"slug":"acme-corp"}'
352
424
 
353
425
  Output:
354
426
  All data is written as JSON to stdout. Status messages go to stderr.
@@ -389,6 +461,10 @@ if (group === "init") {
389
461
  await pageGet(rest)
390
462
  } else if (group === "types") {
391
463
  await typesGen([action, ...rest])
464
+ } else if (group === "workflows") {
465
+ await listWorkflows()
466
+ } else if (group === "run") {
467
+ await runWorkflow([action, ...rest].filter(Boolean))
392
468
  } else {
393
469
  console.error(`Unknown command: ${argv.join(" ")}`)
394
470
  console.error("Run 'kenobi-pages --help' for usage.")
package/src/client.ts CHANGED
@@ -7,6 +7,10 @@ import type {
7
7
  KenobiPageSchema,
8
8
  KenobiSchemaResponse,
9
9
  PostSchemaResponse,
10
+ RunHandle,
11
+ RunStatusResponse,
12
+ WorkflowDetail,
13
+ WorkflowSummary,
10
14
  } from "./types"
11
15
 
12
16
  const DEFAULT_BASE_URL = "https://kenobi.ai"
@@ -38,7 +42,7 @@ const fetchFromKenobi = async (
38
42
  if (opts?.revalidate !== undefined || opts?.tags) {
39
43
  fetchInit.next = {}
40
44
  if (opts.revalidate !== undefined) fetchInit.next.revalidate = opts.revalidate
41
- if (opts.tags) fetchInit.next.tags = opts.tags
45
+ if (opts.tags) fetchInit.next.tags = [...opts.tags]
42
46
  }
43
47
 
44
48
  const response = await fetch(url, fetchInit)
@@ -93,4 +97,31 @@ export const createKenobiPagesClient = (config: KenobiPagesConfig): KenobiPagesC
93
97
  })
94
98
  return (await response.json()) as PostSchemaResponse
95
99
  },
100
+
101
+ listWorkflows: async (): Promise<readonly WorkflowSummary[]> => {
102
+ const response = await fetchFromKenobi(config, "/api/v1/workflows")
103
+ const data = (await response.json()) as { workflows: WorkflowSummary[] }
104
+ return data.workflows
105
+ },
106
+
107
+ getWorkflow: async (workflowId: number): Promise<WorkflowDetail> => {
108
+ const response = await fetchFromKenobi(config, `/api/v1/workflows/${workflowId}`)
109
+ return (await response.json()) as WorkflowDetail
110
+ },
111
+
112
+ triggerRun: async (workflowId: number, params: Record<string, string>): Promise<RunHandle> => {
113
+ const response = await fetchFromKenobi(config, `/api/v1/workflows/${workflowId}/run`, {
114
+ method: "POST",
115
+ body: { params },
116
+ })
117
+ return (await response.json()) as RunHandle
118
+ },
119
+
120
+ getRunStatus: async (workflowId: number, runId: string): Promise<RunStatusResponse> => {
121
+ const response = await fetchFromKenobi(
122
+ config,
123
+ `/api/v1/workflows/${workflowId}/runs/${runId}`
124
+ )
125
+ return (await response.json()) as RunStatusResponse
126
+ },
96
127
  })
package/src/index.ts CHANGED
@@ -9,4 +9,10 @@ export type {
9
9
  KenobiSchemaResponse,
10
10
  OutputFieldSpec,
11
11
  PostSchemaResponse,
12
+ RunHandle,
13
+ RunStatus,
14
+ RunStatusResponse,
15
+ WorkflowDetail,
16
+ WorkflowParam,
17
+ WorkflowSummary,
12
18
  } from "./types"
package/src/types.ts CHANGED
@@ -68,6 +68,50 @@ export interface KenobiPageSchema {
68
68
  readonly fields: Readonly<Record<string, OutputFieldSpec>>
69
69
  }
70
70
 
71
+ // ── Workflow types ──
72
+
73
+ export interface WorkflowParam {
74
+ readonly name: string
75
+ readonly description?: string
76
+ }
77
+
78
+ export interface WorkflowSummary {
79
+ readonly id: number
80
+ readonly name: string
81
+ readonly slug: string
82
+ readonly params: readonly WorkflowParam[]
83
+ readonly outputProvider: string | null
84
+ readonly outputTitle: string | null
85
+ }
86
+
87
+ export interface WorkflowDetail {
88
+ readonly id: number
89
+ readonly name: string
90
+ readonly slug: string
91
+ readonly params: readonly WorkflowParam[]
92
+ readonly output: {
93
+ readonly provider: string | null
94
+ readonly title: string | null
95
+ readonly schema: Record<string, unknown> | null
96
+ }
97
+ readonly destinations: readonly { readonly adapter: string }[]
98
+ }
99
+
100
+ export interface RunHandle {
101
+ readonly runId: string
102
+ readonly status: string
103
+ }
104
+
105
+ export type RunStatus = "QUEUED" | "EXECUTING" | "COMPLETED" | "FAILED" | "CANCELED"
106
+
107
+ export interface RunStatusResponse {
108
+ readonly runId: string
109
+ readonly status: RunStatus
110
+ readonly output: Record<string, unknown> | null
111
+ readonly createdAt: string
112
+ readonly finishedAt: string | null
113
+ }
114
+
71
115
  // ── Client interface ──
72
116
 
73
117
  export interface KenobiPagesClient {
@@ -81,4 +125,8 @@ export interface KenobiPagesClient {
81
125
  opts?: KenobiFetchOptions
82
126
  ) => Promise<KenobiSchemaResponse>
83
127
  readonly postSchema: (name: string, schema: KenobiPageSchema) => Promise<PostSchemaResponse>
128
+ readonly listWorkflows: () => Promise<readonly WorkflowSummary[]>
129
+ readonly getWorkflow: (workflowId: number) => Promise<WorkflowDetail>
130
+ readonly triggerRun: (workflowId: number, params: Record<string, string>) => Promise<RunHandle>
131
+ readonly getRunStatus: (workflowId: number, runId: string) => Promise<RunStatusResponse>
84
132
  }