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 +14 -10
- package/package.json +1 -1
- package/src/cli.ts +76 -0
- package/src/client.ts +32 -1
- package/src/index.ts +6 -0
- package/src/types.ts +48 -0
package/bin/cli.mjs
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
`)},"writeGlobalConfig"),J=t(()=>{let e=process.env[
|
|
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
|
-
`;
|
|
6
|
-
`),console.error(`\u2713 Added ${
|
|
7
|
-
`),console.error(`\u2713 Created .env.local with ${
|
|
8
|
-
${
|
|
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(
|
|
11
|
-
`:""} ${
|
|
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(
|
|
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
|
-
`,[,,...
|
|
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
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
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
|
}
|