kenobi-pages 0.1.2 → 0.1.4

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.
@@ -0,0 +1,2 @@
1
+ import type { KenobiPagesClient, KenobiPagesConfig } from "./types";
2
+ export declare const createKenobiPagesClient: (config: KenobiPagesConfig) => KenobiPagesClient;
package/dist/client.js ADDED
@@ -0,0 +1 @@
1
+ var l=Object.defineProperty;var o=(n,s)=>l(n,"name",{value:s,configurable:!0});import{KenobiPagesError as m,KenobiNotFoundError as p,KenobiUnauthorizedError as b}from"./errors";const d="https://kenobi.ai",t=o(async(n,s,e)=>{const i=`${(n.baseUrl??d).replace(/\/+$/,"")}${s}`,u={"x-kenobi-key":n.apiKey,"Content-Type":"application/json"},c={method:e?.method??"GET",headers:u};e?.body!==void 0&&(c.body=JSON.stringify(e.body)),(e?.revalidate!==void 0||e?.tags)&&(c.next={},e.revalidate!==void 0&&(c.next.revalidate=e.revalidate),e.tags&&(c.next.tags=[...e.tags]));const a=await fetch(i,c);if(a.ok)return a;if(a.status===401)throw new b;if(a.status===404)throw new p;const w=await a.text().catch(()=>"Unknown error");throw new m(`Kenobi API error (${a.status}): ${w}`,a.status,"API_ERROR")},"fetchFromKenobi"),f=o(n=>({getPage:o(async(s,e,r)=>{try{return await(await t(n,`/api/v1/pages/${s}/${e}`,r)).json()}catch(i){if(i instanceof p)return null;throw i}},"getPage"),getSchema:o(async(s,e)=>await(await t(n,`/api/v1/pages/${s}/schema`,e)).json(),"getSchema"),postSchema:o(async(s,e)=>await(await t(n,"/api/v1/pages/schema",{method:"POST",body:{name:s,schema:e}})).json(),"postSchema"),listWorkflows:o(async()=>(await(await t(n,"/api/v1/workflows")).json()).workflows,"listWorkflows"),getWorkflow:o(async s=>await(await t(n,`/api/v1/workflows/${s}`)).json(),"getWorkflow"),triggerRun:o(async(s,e)=>await(await t(n,`/api/v1/workflows/${s}/run`,{method:"POST",body:{params:e}})).json(),"triggerRun"),getRunStatus:o(async(s,e)=>await(await t(n,`/api/v1/workflows/${s}/runs/${e}`)).json(),"getRunStatus")}),"createKenobiPagesClient");export{f as createKenobiPagesClient};
@@ -0,0 +1,11 @@
1
+ export declare class KenobiPagesError extends Error {
2
+ readonly status: number;
3
+ readonly code: string;
4
+ constructor(message: string, status: number, code: string);
5
+ }
6
+ export declare class KenobiNotFoundError extends KenobiPagesError {
7
+ constructor(message?: string);
8
+ }
9
+ export declare class KenobiUnauthorizedError extends KenobiPagesError {
10
+ constructor(message?: string);
11
+ }
package/dist/errors.js ADDED
@@ -0,0 +1 @@
1
+ var u=Object.defineProperty;var e=(o,r)=>u(o,"name",{value:r,configurable:!0});class s extends Error{static{e(this,"KenobiPagesError")}status;code;constructor(r,t,n){super(r),this.name="KenobiPagesError",this.status=t,this.code=n}}class a extends s{static{e(this,"KenobiNotFoundError")}constructor(r="Resource not found"){super(r,404,"NOT_FOUND"),this.name="KenobiNotFoundError"}}class c extends s{static{e(this,"KenobiUnauthorizedError")}constructor(r="Unauthorized \u2014 check your API key"){super(r,401,"UNAUTHORIZED"),this.name="KenobiUnauthorizedError"}}export{a as KenobiNotFoundError,s as KenobiPagesError,c as KenobiUnauthorizedError};
@@ -0,0 +1,3 @@
1
+ export { createKenobiPagesClient } from "./client";
2
+ export { KenobiPagesError, KenobiNotFoundError, KenobiUnauthorizedError } from "./errors";
3
+ export type { KenobiPagesClient, KenobiPagesConfig, KenobiFetchOptions, KenobiPageResponse, KenobiPageSchema, KenobiSchemaResponse, OutputFieldSpec, PostSchemaResponse, RunHandle, RunStatus, RunStatusResponse, WorkflowDetail, WorkflowParam, WorkflowSummary, } from "./types";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{createKenobiPagesClient as n}from"./client";import{KenobiPagesError as t,KenobiNotFoundError as a,KenobiUnauthorizedError as i}from"./errors";export{a as KenobiNotFoundError,t as KenobiPagesError,i as KenobiUnauthorizedError,n as createKenobiPagesClient};
@@ -0,0 +1,106 @@
1
+ export interface KenobiPagesConfig {
2
+ /** Org-scoped public key from the Kenobi workflow builder (pk_live_... or pk_test_...). */
3
+ readonly apiKey: string;
4
+ /** Base URL of the Kenobi API. Defaults to https://kenobi.ai */
5
+ readonly baseUrl?: string;
6
+ }
7
+ export interface KenobiFetchOptions {
8
+ /** Next.js ISR revalidation interval in seconds, or `false` to disable caching. */
9
+ readonly revalidate?: number | false;
10
+ /** Next.js cache tags for on-demand revalidation via `revalidateTag()`. */
11
+ readonly tags?: readonly string[];
12
+ }
13
+ export interface KenobiPageResponse {
14
+ readonly content: Record<string, unknown>;
15
+ readonly metadata: Record<string, unknown> | null;
16
+ readonly updatedAt: string;
17
+ }
18
+ export interface KenobiSchemaResponse {
19
+ readonly schema: Record<string, unknown>;
20
+ readonly provider: string;
21
+ readonly title: string;
22
+ }
23
+ export interface PostSchemaResponse {
24
+ readonly sourceKey: string;
25
+ readonly name: string;
26
+ }
27
+ interface FieldSpecBase {
28
+ readonly description?: string;
29
+ readonly optional?: boolean;
30
+ }
31
+ export type OutputFieldSpec = ({
32
+ readonly type: "string";
33
+ readonly min?: number;
34
+ readonly max?: number;
35
+ } & FieldSpecBase) | ({
36
+ readonly type: "url";
37
+ } & FieldSpecBase) | ({
38
+ readonly type: "number";
39
+ readonly min?: number;
40
+ readonly max?: number;
41
+ readonly integer?: boolean;
42
+ } & FieldSpecBase) | ({
43
+ readonly type: "boolean";
44
+ } & FieldSpecBase) | ({
45
+ readonly type: "enum";
46
+ readonly values?: readonly string[];
47
+ } & FieldSpecBase) | ({
48
+ readonly type: "object";
49
+ readonly fields: Readonly<Record<string, OutputFieldSpec>>;
50
+ } & FieldSpecBase) | ({
51
+ readonly type: "array";
52
+ readonly items: OutputFieldSpec;
53
+ readonly min?: number;
54
+ readonly max?: number;
55
+ } & FieldSpecBase);
56
+ export interface KenobiPageSchema {
57
+ readonly fields: Readonly<Record<string, OutputFieldSpec>>;
58
+ }
59
+ export interface WorkflowParam {
60
+ readonly name: string;
61
+ readonly description?: string;
62
+ }
63
+ export interface WorkflowSummary {
64
+ readonly id: number;
65
+ readonly name: string;
66
+ readonly slug: string;
67
+ readonly params: readonly WorkflowParam[];
68
+ readonly outputProvider: string | null;
69
+ readonly outputTitle: string | null;
70
+ }
71
+ export interface WorkflowDetail {
72
+ readonly id: number;
73
+ readonly name: string;
74
+ readonly slug: string;
75
+ readonly params: readonly WorkflowParam[];
76
+ readonly output: {
77
+ readonly provider: string | null;
78
+ readonly title: string | null;
79
+ readonly schema: Record<string, unknown> | null;
80
+ };
81
+ readonly destinations: readonly {
82
+ readonly adapter: string;
83
+ }[];
84
+ }
85
+ export interface RunHandle {
86
+ readonly runId: string;
87
+ readonly status: string;
88
+ }
89
+ export type RunStatus = "QUEUED" | "EXECUTING" | "COMPLETED" | "FAILED" | "CANCELED";
90
+ export interface RunStatusResponse {
91
+ readonly runId: string;
92
+ readonly status: RunStatus;
93
+ readonly output: Record<string, unknown> | null;
94
+ readonly createdAt: string;
95
+ readonly finishedAt: string | null;
96
+ }
97
+ export interface KenobiPagesClient {
98
+ readonly getPage: (workflowId: number, slug: string, opts?: KenobiFetchOptions) => Promise<KenobiPageResponse | null>;
99
+ readonly getSchema: (workflowId: number, opts?: KenobiFetchOptions) => Promise<KenobiSchemaResponse>;
100
+ readonly postSchema: (name: string, schema: KenobiPageSchema) => Promise<PostSchemaResponse>;
101
+ readonly listWorkflows: () => Promise<readonly WorkflowSummary[]>;
102
+ readonly getWorkflow: (workflowId: number) => Promise<WorkflowDetail>;
103
+ readonly triggerRun: (workflowId: number, params: Record<string, string>) => Promise<RunHandle>;
104
+ readonly getRunStatus: (workflowId: number, runId: string) => Promise<RunStatusResponse>;
105
+ }
106
+ export {};
package/dist/types.js ADDED
File without changes
package/package.json CHANGED
@@ -1,17 +1,22 @@
1
1
  {
2
2
  "name": "kenobi-pages",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Kenobi Pages SDK — fetch personalized page content from Kenobi workflows",
5
5
  "type": "module",
6
6
  "exports": {
7
- ".": "./src/index.ts"
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ }
8
11
  },
12
+ "types": "./dist/index.d.ts",
13
+ "main": "./dist/index.js",
9
14
  "bin": {
10
15
  "kenobi-pages": "./bin/cli.mjs"
11
16
  },
12
17
  "files": [
13
18
  "bin",
14
- "src"
19
+ "dist"
15
20
  ],
16
21
  "publishConfig": {
17
22
  "access": "public"
package/src/build.ts DELETED
@@ -1,50 +0,0 @@
1
- #!/usr/bin/env tsx
2
-
3
- import * as fs from "fs"
4
- import * as path from "path"
5
-
6
- import * as esbuild from "esbuild"
7
-
8
- const isProd =
9
- process.argv.includes("--prod") || process.env.NODE_ENV === "production"
10
-
11
- const packageJsonPath = path.join(process.cwd(), "package.json")
12
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"))
13
- const version = packageJson.version as string
14
-
15
- const entryFile = path.join(process.cwd(), "src", "cli.ts")
16
- const outFile = path.join(process.cwd(), "bin", "cli.mjs")
17
-
18
- const build = async () => {
19
- console.log(
20
- `📦 Building CLI ${isProd ? "(production)" : "(development)"} v${version}...`
21
- )
22
-
23
- const result = await esbuild.build({
24
- entryPoints: [entryFile],
25
- bundle: true,
26
- platform: "node",
27
- target: ["node18"],
28
- format: "esm",
29
- treeShaking: true,
30
- minify: isProd,
31
- keepNames: true,
32
- metafile: true,
33
- logLevel: "info",
34
- outfile: outFile,
35
- banner: {
36
- js: "#!/usr/bin/env node",
37
- },
38
- })
39
-
40
- if (result.metafile) {
41
- const outputs = Object.values(result.metafile.outputs)
42
- const totalSize = outputs.reduce((acc, output) => acc + output.bytes, 0)
43
- console.log(`\n✅ Build complete: ${(totalSize / 1024).toFixed(2)} KB → ${outFile}`)
44
- }
45
- }
46
-
47
- build().catch((error) => {
48
- console.error("Build failed:", error)
49
- process.exit(1)
50
- })
package/src/cli.ts DELETED
@@ -1,472 +0,0 @@
1
- import {
2
- existsSync,
3
- mkdirSync,
4
- readFileSync,
5
- writeFileSync,
6
- appendFileSync,
7
- } from "node:fs"
8
- import { readFile } from "node:fs/promises"
9
- import { homedir } from "node:os"
10
- import { join, resolve } from "node:path"
11
- import { createInterface } from "node:readline"
12
-
13
- const DEFAULT_BASE_URL = "https://kenobi.ai"
14
- const GLOBAL_CONFIG_DIR = join(homedir(), ".kenobi")
15
- const GLOBAL_CONFIG_PATH = join(GLOBAL_CONFIG_DIR, "config.json")
16
- const ENV_VAR_NAME = "KENOBI_PAGES_KEY"
17
-
18
- // ── Types ──
19
-
20
- interface GlobalConfig {
21
- apiKey?: string
22
- baseUrl?: string
23
- }
24
-
25
- interface FetchOptions {
26
- method?: string
27
- body?: Record<string, unknown>
28
- }
29
-
30
- interface OutputFieldSpec {
31
- type: string
32
- optional?: boolean
33
- description?: string
34
- values?: string[]
35
- fields?: Record<string, OutputFieldSpec>
36
- items?: OutputFieldSpec
37
- }
38
-
39
- // ── Global config ──
40
-
41
- const readGlobalConfig = (): GlobalConfig => {
42
- try {
43
- return JSON.parse(readFileSync(GLOBAL_CONFIG_PATH, "utf-8")) as GlobalConfig
44
- } catch {
45
- return {}
46
- }
47
- }
48
-
49
- const writeGlobalConfig = (config: GlobalConfig) => {
50
- mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true })
51
- writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n")
52
- }
53
-
54
- // ── Helpers ──
55
-
56
- const getApiKey = (): string => {
57
- const key = process.env[ENV_VAR_NAME] || readGlobalConfig().apiKey
58
- if (!key) {
59
- console.error("Error: No API key found.")
60
- console.error("")
61
- console.error("Run 'npx kenobi-pages init' to set up your API key.")
62
- console.error("")
63
- console.error("Or set the KENOBI_PAGES_KEY environment variable directly.")
64
- process.exit(1)
65
- }
66
- return key
67
- }
68
-
69
- const getBaseUrl = (): string =>
70
- (
71
- process.env.KENOBI_BASE_URL ??
72
- readGlobalConfig().baseUrl ??
73
- DEFAULT_BASE_URL
74
- ).replace(/\/+$/, "")
75
-
76
- const fetchKenobi = async (path: string, opts: FetchOptions = {}) => {
77
- const url = `${getBaseUrl()}${path}`
78
- const res = await fetch(url, {
79
- method: opts.method ?? "GET",
80
- headers: {
81
- "x-kenobi-key": getApiKey(),
82
- "Content-Type": "application/json",
83
- },
84
- body: opts.body ? JSON.stringify(opts.body) : undefined,
85
- })
86
-
87
- if (!res.ok) {
88
- const text = await res.text().catch(() => "Unknown error")
89
- const exitCode =
90
- res.status === 401 || res.status === 403
91
- ? 4
92
- : res.status === 404
93
- ? 3
94
- : 1
95
- console.error(`Error ${res.status}: ${text}`)
96
- process.exit(exitCode)
97
- }
98
-
99
- return res.json()
100
- }
101
-
102
- const prompt = (question: string): Promise<string> =>
103
- new Promise((resolve) => {
104
- const rl = createInterface({ input: process.stdin, output: process.stderr })
105
- rl.question(question, (answer) => {
106
- rl.close()
107
- resolve(answer.trim())
108
- })
109
- })
110
-
111
- const readStdin = (): Promise<string> =>
112
- new Promise((resolve, reject) => {
113
- let data = ""
114
- process.stdin.setEncoding("utf-8")
115
- process.stdin.on("data", (chunk: string) => {
116
- data += chunk
117
- })
118
- process.stdin.on("end", () => resolve(data))
119
- process.stdin.on("error", reject)
120
- if (process.stdin.isTTY) resolve("")
121
- })
122
-
123
- const parseFileArg = async (args: string[]): Promise<string | null> => {
124
- const fileIdx = args.indexOf("--file")
125
- if (fileIdx !== -1 && args[fileIdx + 1]) {
126
- return readFile(args[fileIdx + 1], "utf-8")
127
- }
128
- return null
129
- }
130
-
131
- // ── Init ──
132
-
133
- const initCommand = async () => {
134
- console.error("Kenobi Pages — Setup")
135
- console.error("")
136
- console.error("Find your Pages API key in the workflow builder:")
137
- console.error(" https://kenobi.ai/testing/cortex")
138
- console.error("")
139
-
140
- const key = await prompt("Paste your API key (pk_live_... or pk_test_...): ")
141
-
142
- if (!key) {
143
- console.error("No key provided. Aborting.")
144
- process.exit(1)
145
- }
146
-
147
- if (!key.startsWith("pk_live_") && !key.startsWith("pk_test_")) {
148
- console.error(
149
- `Warning: "${key}" doesn't look like a Kenobi public key (expected pk_live_... or pk_test_...).`
150
- )
151
- const proceed = await prompt("Continue anyway? (y/N): ")
152
- if (proceed.toLowerCase() !== "y") {
153
- console.error("Aborting.")
154
- process.exit(1)
155
- }
156
- }
157
-
158
- const config = readGlobalConfig()
159
- config.apiKey = key
160
- writeGlobalConfig(config)
161
- console.error(`✓ Saved to ${GLOBAL_CONFIG_PATH}`)
162
-
163
- const envLocalPath = resolve(process.cwd(), ".env.local")
164
- const envPath = resolve(process.cwd(), ".env")
165
- const targetEnvFile = existsSync(envLocalPath)
166
- ? envLocalPath
167
- : existsSync(envPath)
168
- ? envPath
169
- : null
170
-
171
- if (targetEnvFile) {
172
- const contents = readFileSync(targetEnvFile, "utf-8")
173
- if (contents.includes(ENV_VAR_NAME)) {
174
- console.error(
175
- `✓ ${ENV_VAR_NAME} already present in ${targetEnvFile.split("/").pop()}`
176
- )
177
- } else {
178
- const suffix = contents.endsWith("\n") ? "" : "\n"
179
- appendFileSync(targetEnvFile, `${suffix}${ENV_VAR_NAME}="${key}"\n`)
180
- console.error(
181
- `✓ Added ${ENV_VAR_NAME} to ${targetEnvFile.split("/").pop()}`
182
- )
183
- }
184
- } else {
185
- const createEnvLocal = await prompt(
186
- "No .env.local found. Create one with your API key? (Y/n): "
187
- )
188
- if (createEnvLocal.toLowerCase() !== "n") {
189
- writeFileSync(envLocalPath, `${ENV_VAR_NAME}="${key}"\n`)
190
- console.error(`✓ Created .env.local with ${ENV_VAR_NAME}`)
191
- }
192
- }
193
-
194
- console.error("")
195
- console.error("Done! You can now use kenobi-pages commands.")
196
- console.error(
197
- ' npx kenobi-pages schema push "My Page" \'{"fields":{...}}\''
198
- )
199
- console.error(" npx kenobi-pages types <workflowId>")
200
- }
201
-
202
- // ── Subcommands ──
203
-
204
- const schemaGet = async (args: string[]) => {
205
- const workflowId = args[0]
206
- if (!workflowId) {
207
- console.error("Usage: kenobi-pages schema get <workflowId>")
208
- process.exit(2)
209
- }
210
-
211
- const data = await fetchKenobi(`/api/v1/pages/${workflowId}/schema`)
212
- console.log(JSON.stringify(data, null, 2))
213
- }
214
-
215
- const schemaPush = async (args: string[]) => {
216
- const name = args[0]
217
- if (!name) {
218
- console.error("Usage: kenobi-pages schema push <name> '<json>'")
219
- console.error(" kenobi-pages schema push <name> --file schema.json")
220
- console.error(" echo '<json>' | kenobi-pages schema push <name>")
221
- process.exit(2)
222
- }
223
-
224
- const remaining = args.slice(1)
225
- const fileContent = await parseFileArg(remaining)
226
-
227
- const inlineArg = !fileContent
228
- ? remaining.find((a) => a.startsWith("{"))
229
- : null
230
- const stdinContent = !fileContent && !inlineArg ? await readStdin() : null
231
- const rawJson = fileContent ?? inlineArg ?? stdinContent
232
-
233
- if (!rawJson || rawJson.trim().length === 0) {
234
- console.error("Error: No schema provided.")
235
- console.error("Pass inline JSON, --file <path>, or pipe to stdin.")
236
- process.exit(2)
237
- }
238
-
239
- let schema: Record<string, unknown>
240
- try {
241
- schema = JSON.parse(rawJson) as Record<string, unknown>
242
- } catch {
243
- console.error("Error: Invalid JSON.")
244
- process.exit(2)
245
- }
246
-
247
- const data = (await fetchKenobi("/api/v1/pages/schema", {
248
- method: "POST",
249
- body: { name, schema },
250
- })) as { name: string; sourceKey: string }
251
-
252
- console.log(JSON.stringify(data, null, 2))
253
- console.error(`Schema "${data.name}" pushed successfully.`)
254
- console.error(`Source key: ${data.sourceKey}`)
255
- console.error(
256
- "You can now select this schema as an output target in the Kenobi workflow builder."
257
- )
258
- }
259
-
260
- const pageGet = async (args: string[]) => {
261
- const [workflowId, slug] = args
262
- if (!workflowId || !slug) {
263
- console.error("Usage: kenobi-pages page get <workflowId> <slug>")
264
- process.exit(2)
265
- }
266
-
267
- const data = await fetchKenobi(`/api/v1/pages/${workflowId}/${slug}`)
268
- console.log(JSON.stringify(data, null, 2))
269
- }
270
-
271
- const typesGen = async (args: string[]) => {
272
- const workflowId = args[0]
273
- if (!workflowId) {
274
- console.error("Usage: kenobi-pages types <workflowId>")
275
- process.exit(2)
276
- }
277
-
278
- const data = (await fetchKenobi(
279
- `/api/v1/pages/${workflowId}/schema`
280
- )) as { schema: { fields?: Record<string, OutputFieldSpec> }; title?: string }
281
- const schema = data.schema
282
-
283
- if (!schema?.fields) {
284
- console.error("Error: Workflow has no output schema with fields.")
285
- process.exit(1)
286
- }
287
-
288
- const indent = (level: number) => " ".repeat(level)
289
-
290
- const fieldToTs = (spec: OutputFieldSpec, level = 1): string => {
291
- switch (spec.type) {
292
- case "string":
293
- case "url":
294
- return "string"
295
- case "number":
296
- return "number"
297
- case "boolean":
298
- return "boolean"
299
- case "enum":
300
- return spec.values?.length
301
- ? spec.values.map((v) => `"${v}"`).join(" | ")
302
- : "string"
303
- case "object": {
304
- const entries = Object.entries(spec.fields ?? {})
305
- if (entries.length === 0) return "Record<string, unknown>"
306
- const lines = entries.map(([k, v]) => {
307
- const opt = v.optional ? "?" : ""
308
- return `${indent(level)}${k}${opt}: ${fieldToTs(v, level + 1)}`
309
- })
310
- return `{\n${lines.join("\n")}\n${indent(level - 1)}}`
311
- }
312
- case "array":
313
- return `Array<${fieldToTs(spec.items!, level)}>`
314
- default:
315
- return "unknown"
316
- }
317
- }
318
-
319
- const entries = Object.entries(schema.fields)
320
- const lines = entries.map(([k, v]) => {
321
- const opt = v.optional ? "?" : ""
322
- const comment = v.description ? ` /** ${v.description} */\n` : ""
323
- return `${comment} ${k}${opt}: ${fieldToTs(v, 2)}`
324
- })
325
-
326
- const typeName = data.title
327
- ? data.title.replace(/[^a-zA-Z0-9]+/g, "")
328
- : "PageContent"
329
-
330
- const output = `export interface ${typeName} {\n${lines.join("\n")}\n}\n`
331
- console.log(output)
332
- }
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
-
402
- // ── Router ──
403
-
404
- const HELP = `kenobi-pages — Kenobi Pages CLI
405
-
406
- Commands:
407
- kenobi-pages init Set up your API key (interactive)
408
- kenobi-pages schema get <workflowId> Fetch a workflow's output schema
409
- kenobi-pages schema push <name> '<json>' Push a schema to Kenobi (inline JSON)
410
- kenobi-pages schema push <name> --file f Push a schema to Kenobi (from file)
411
- kenobi-pages page get <workflowId> <slug> Fetch page content for a specific lead
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
415
-
416
- Examples:
417
- npx kenobi-pages init
418
- npx kenobi-pages schema get 42
419
- npx kenobi-pages schema push "My Page" '{"fields":{"headline":{"type":"string"}}}'
420
- npx kenobi-pages types 42 > lib/kenobi-types.ts
421
- npx kenobi-pages page get 42 acme-corp
422
- npx kenobi-pages workflows
423
- npx kenobi-pages run 42 --params '{"slug":"acme-corp"}'
424
-
425
- Output:
426
- All data is written as JSON to stdout. Status messages go to stderr.
427
- Pipe stdout to jq, a file, or another command safely.
428
-
429
- Exit codes:
430
- 0 Success
431
- 1 API or network error
432
- 2 Invalid usage (bad arguments)
433
- 3 Resource not found (404)
434
- 4 Unauthorized (invalid API key)
435
-
436
- Environment:
437
- KENOBI_PAGES_KEY Org public key. Set via 'init' or manually.
438
- KENOBI_BASE_URL Optional. Override the Kenobi API base URL.
439
-
440
- Key resolution order:
441
- 1. KENOBI_PAGES_KEY environment variable
442
- 2. ~/.kenobi/config.json (written by 'init')
443
- `
444
-
445
- const [, , ...argv] = process.argv
446
-
447
- if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
448
- console.log(HELP)
449
- process.exit(0)
450
- }
451
-
452
- const [group, action, ...rest] = argv
453
-
454
- if (group === "init") {
455
- await initCommand()
456
- } else if (group === "schema" && action === "get") {
457
- await schemaGet(rest)
458
- } else if (group === "schema" && action === "push") {
459
- await schemaPush(rest)
460
- } else if (group === "page" && action === "get") {
461
- await pageGet(rest)
462
- } else if (group === "types") {
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))
468
- } else {
469
- console.error(`Unknown command: ${argv.join(" ")}`)
470
- console.error("Run 'kenobi-pages --help' for usage.")
471
- process.exit(1)
472
- }
package/src/client.ts DELETED
@@ -1,127 +0,0 @@
1
- import { KenobiPagesError, KenobiNotFoundError, KenobiUnauthorizedError } from "./errors"
2
- import type {
3
- KenobiPagesClient,
4
- KenobiPagesConfig,
5
- KenobiFetchOptions,
6
- KenobiPageResponse,
7
- KenobiPageSchema,
8
- KenobiSchemaResponse,
9
- PostSchemaResponse,
10
- RunHandle,
11
- RunStatusResponse,
12
- WorkflowDetail,
13
- WorkflowSummary,
14
- } from "./types"
15
-
16
- const DEFAULT_BASE_URL = "https://kenobi.ai"
17
-
18
- // ── Internal fetch helper ──
19
-
20
- const fetchFromKenobi = async (
21
- config: KenobiPagesConfig,
22
- path: string,
23
- opts?: KenobiFetchOptions & { method?: string; body?: unknown }
24
- ): Promise<Response> => {
25
- const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "")
26
- const url = `${baseUrl}${path}`
27
-
28
- const headers: Record<string, string> = {
29
- "x-kenobi-key": config.apiKey,
30
- "Content-Type": "application/json",
31
- }
32
-
33
- const fetchInit: RequestInit & { next?: { revalidate?: number | false; tags?: readonly string[] } } = {
34
- method: opts?.method ?? "GET",
35
- headers,
36
- }
37
-
38
- if (opts?.body !== undefined) {
39
- fetchInit.body = JSON.stringify(opts.body)
40
- }
41
-
42
- if (opts?.revalidate !== undefined || opts?.tags) {
43
- fetchInit.next = {}
44
- if (opts.revalidate !== undefined) fetchInit.next.revalidate = opts.revalidate
45
- if (opts.tags) fetchInit.next.tags = opts.tags
46
- }
47
-
48
- const response = await fetch(url, fetchInit)
49
-
50
- if (response.ok) return response
51
-
52
- if (response.status === 401) {
53
- throw new KenobiUnauthorizedError()
54
- }
55
-
56
- if (response.status === 404) {
57
- throw new KenobiNotFoundError()
58
- }
59
-
60
- const errorBody = await response.text().catch(() => "Unknown error")
61
- throw new KenobiPagesError(
62
- `Kenobi API error (${response.status}): ${errorBody}`,
63
- response.status,
64
- "API_ERROR"
65
- )
66
- }
67
-
68
- // ── Client factory ──
69
-
70
- export const createKenobiPagesClient = (config: KenobiPagesConfig): KenobiPagesClient => ({
71
- getPage: async (
72
- workflowId: number,
73
- slug: string,
74
- opts?: KenobiFetchOptions
75
- ): Promise<KenobiPageResponse | null> => {
76
- try {
77
- const response = await fetchFromKenobi(config, `/api/v1/pages/${workflowId}/${slug}`, opts)
78
- return (await response.json()) as KenobiPageResponse
79
- } catch (err) {
80
- if (err instanceof KenobiNotFoundError) return null
81
- throw err
82
- }
83
- },
84
-
85
- getSchema: async (
86
- workflowId: number,
87
- opts?: KenobiFetchOptions
88
- ): Promise<KenobiSchemaResponse> => {
89
- const response = await fetchFromKenobi(config, `/api/v1/pages/${workflowId}/schema`, opts)
90
- return (await response.json()) as KenobiSchemaResponse
91
- },
92
-
93
- postSchema: async (name: string, schema: KenobiPageSchema): Promise<PostSchemaResponse> => {
94
- const response = await fetchFromKenobi(config, "/api/v1/pages/schema", {
95
- method: "POST",
96
- body: { name, schema },
97
- })
98
- return (await response.json()) as PostSchemaResponse
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
- },
127
- })
package/src/errors.ts DELETED
@@ -1,25 +0,0 @@
1
- export class KenobiPagesError extends Error {
2
- readonly status: number
3
- readonly code: string
4
-
5
- constructor(message: string, status: number, code: string) {
6
- super(message)
7
- this.name = "KenobiPagesError"
8
- this.status = status
9
- this.code = code
10
- }
11
- }
12
-
13
- export class KenobiNotFoundError extends KenobiPagesError {
14
- constructor(message = "Resource not found") {
15
- super(message, 404, "NOT_FOUND")
16
- this.name = "KenobiNotFoundError"
17
- }
18
- }
19
-
20
- export class KenobiUnauthorizedError extends KenobiPagesError {
21
- constructor(message = "Unauthorized — check your API key") {
22
- super(message, 401, "UNAUTHORIZED")
23
- this.name = "KenobiUnauthorizedError"
24
- }
25
- }
package/src/index.ts DELETED
@@ -1,18 +0,0 @@
1
- export { createKenobiPagesClient } from "./client"
2
- export { KenobiPagesError, KenobiNotFoundError, KenobiUnauthorizedError } from "./errors"
3
- export type {
4
- KenobiPagesClient,
5
- KenobiPagesConfig,
6
- KenobiFetchOptions,
7
- KenobiPageResponse,
8
- KenobiPageSchema,
9
- KenobiSchemaResponse,
10
- OutputFieldSpec,
11
- PostSchemaResponse,
12
- RunHandle,
13
- RunStatus,
14
- RunStatusResponse,
15
- WorkflowDetail,
16
- WorkflowParam,
17
- WorkflowSummary,
18
- } from "./types"
package/src/types.ts DELETED
@@ -1,132 +0,0 @@
1
- // ── Client configuration ──
2
-
3
- export interface KenobiPagesConfig {
4
- /** Org-scoped public key from the Kenobi workflow builder (pk_live_... or pk_test_...). */
5
- readonly apiKey: string
6
- /** Base URL of the Kenobi API. Defaults to https://kenobi.ai */
7
- readonly baseUrl?: string
8
- }
9
-
10
- // ── Next.js fetch options ──
11
-
12
- export interface KenobiFetchOptions {
13
- /** Next.js ISR revalidation interval in seconds, or `false` to disable caching. */
14
- readonly revalidate?: number | false
15
- /** Next.js cache tags for on-demand revalidation via `revalidateTag()`. */
16
- readonly tags?: readonly string[]
17
- }
18
-
19
- // ── API response types ──
20
-
21
- export interface KenobiPageResponse {
22
- readonly content: Record<string, unknown>
23
- readonly metadata: Record<string, unknown> | null
24
- readonly updatedAt: string
25
- }
26
-
27
- export interface KenobiSchemaResponse {
28
- readonly schema: Record<string, unknown>
29
- readonly provider: string
30
- readonly title: string
31
- }
32
-
33
- export interface PostSchemaResponse {
34
- readonly sourceKey: string
35
- readonly name: string
36
- }
37
-
38
- // ── Schema DSL types (standalone — no @kenobi/cortex dependency) ──
39
-
40
- interface FieldSpecBase {
41
- readonly description?: string
42
- readonly optional?: boolean
43
- }
44
-
45
- export type OutputFieldSpec =
46
- | ({ readonly type: "string"; readonly min?: number; readonly max?: number } & FieldSpecBase)
47
- | ({ readonly type: "url" } & FieldSpecBase)
48
- | ({
49
- readonly type: "number"
50
- readonly min?: number
51
- readonly max?: number
52
- readonly integer?: boolean
53
- } & FieldSpecBase)
54
- | ({ readonly type: "boolean" } & FieldSpecBase)
55
- | ({ readonly type: "enum"; readonly values?: readonly string[] } & FieldSpecBase)
56
- | ({
57
- readonly type: "object"
58
- readonly fields: Readonly<Record<string, OutputFieldSpec>>
59
- } & FieldSpecBase)
60
- | ({
61
- readonly type: "array"
62
- readonly items: OutputFieldSpec
63
- readonly min?: number
64
- readonly max?: number
65
- } & FieldSpecBase)
66
-
67
- export interface KenobiPageSchema {
68
- readonly fields: Readonly<Record<string, OutputFieldSpec>>
69
- }
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
-
115
- // ── Client interface ──
116
-
117
- export interface KenobiPagesClient {
118
- readonly getPage: (
119
- workflowId: number,
120
- slug: string,
121
- opts?: KenobiFetchOptions
122
- ) => Promise<KenobiPageResponse | null>
123
- readonly getSchema: (
124
- workflowId: number,
125
- opts?: KenobiFetchOptions
126
- ) => Promise<KenobiSchemaResponse>
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>
132
- }