khotan-data 0.0.1 → 0.1.0

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.
Files changed (51) hide show
  1. package/AGENTS.md +54 -0
  2. package/README.md +62 -0
  3. package/dist/cli.js +2585 -0
  4. package/dist/factory.cjs +2319 -0
  5. package/dist/factory.cjs.map +1 -0
  6. package/dist/factory.d.cts +475 -0
  7. package/dist/factory.d.ts +475 -0
  8. package/dist/factory.js +2311 -0
  9. package/dist/factory.js.map +1 -0
  10. package/dist/plug-client.cjs +99 -0
  11. package/dist/plug-client.cjs.map +1 -0
  12. package/dist/plug-client.d.cts +71 -0
  13. package/dist/plug-client.d.ts +71 -0
  14. package/dist/plug-client.js +96 -0
  15. package/dist/plug-client.js.map +1 -0
  16. package/dist/templates/agent-skill.md +73 -0
  17. package/dist/templates/agents.md +41 -0
  18. package/dist/templates/catch.example.ts +36 -0
  19. package/dist/templates/catch.ts +107 -0
  20. package/dist/templates/config-page.tsx +20 -0
  21. package/dist/templates/debug-index-page.tsx +101 -0
  22. package/dist/templates/debug-page.tsx +48 -0
  23. package/dist/templates/graph-page.tsx +11 -0
  24. package/dist/templates/hub.tsx +450 -0
  25. package/dist/templates/inflow.example.ts +61 -0
  26. package/dist/templates/inflow.ts +99 -0
  27. package/dist/templates/khotan-config.ts +40 -0
  28. package/dist/templates/khotan-route.ts +13 -0
  29. package/dist/templates/logs-page.tsx +9 -0
  30. package/dist/templates/logs.tsx +20 -0
  31. package/dist/templates/outflow.example.ts +52 -0
  32. package/dist/templates/outflow.ts +90 -0
  33. package/dist/templates/pass.example.ts +51 -0
  34. package/dist/templates/pass.ts +124 -0
  35. package/dist/templates/plug-debugger.tsx +1185 -0
  36. package/dist/templates/plug.example.ts +93 -0
  37. package/dist/templates/plug.ts +806 -0
  38. package/dist/templates/relay.example.ts +61 -0
  39. package/dist/templates/relay.ts +95 -0
  40. package/dist/templates/runs-table.tsx +592 -0
  41. package/dist/templates/schema.ts +424 -0
  42. package/dist/templates/skill-dashboard.md +144 -0
  43. package/dist/templates/skill-plug.md +193 -0
  44. package/dist/templates/skill-setup.md +119 -0
  45. package/dist/templates/skill-webhook.md +196 -0
  46. package/dist/templates/topology-canvas.tsx +1406 -0
  47. package/dist/templates/var-panel.tsx +276 -0
  48. package/dist/templates/webhook-events-table.tsx +241 -0
  49. package/dist/templates/wire-panel.tsx +216 -0
  50. package/dist/templates/wire.ts +155 -0
  51. package/package.json +46 -5
@@ -0,0 +1,193 @@
1
+ ---
2
+ name: khotan-plug
3
+ description: >
4
+ Create and configure khotan Plugs — HTTP clients for external APIs with
5
+ auth, retry, pagination, and typed endpoints. Use when connecting to a
6
+ new API, defining endpoint contracts, configuring authentication, or
7
+ creating a typed API client.
8
+ ---
9
+
10
+ Create and configure khotan Plugs — HTTP clients for external APIs with auth, retry, pagination, and typed endpoints. Use when connecting to a new API, defining endpoint contracts, configuring authentication, or creating a typed API client.
11
+
12
+ ## Scaffold
13
+
14
+ ```bash
15
+ npx khotan add plug --yes
16
+ ```
17
+
18
+ Creates `{outputDir}/plugs/plug.ts` (the Plug runtime) and `plug.example.ts` (typed contract example).
19
+
20
+ ## Creating a Plug
21
+
22
+ ```typescript
23
+ import { plug, bearer, apiKey, basic, custom } from "./plug";
24
+
25
+ export const stripePlug = plug({
26
+ name: "stripe",
27
+ baseUrl: "https://api.stripe.com/v1",
28
+ auth: bearer(process.env.STRIPE_KEY!),
29
+ retry: { attempts: 3, backoff: 1000 },
30
+ timeout: 30000,
31
+ });
32
+ ```
33
+
34
+ ## Auth Strategies
35
+
36
+ | Function | Usage |
37
+ |----------|-------|
38
+ | `bearer(token)` | `Authorization: Bearer <token>` — static string or async function |
39
+ | `basic(user, pass)` | `Authorization: Basic <base64>` |
40
+ | `apiKey(name, value)` | Custom header (default) or query param with `{ in: "query" }` |
41
+ | `custom(fn)` | Full control: `(headers) => { headers.set(...) }` |
42
+ | `tokenExchange(config)` | OAuth-style: exchanges variables for bearer token, caches, auto-refreshes on 401 |
43
+
44
+ ### Token Exchange Example
45
+
46
+ ```typescript
47
+ const auth = tokenExchange({
48
+ tokenUrl: "/oauth/token",
49
+ buildBody: (vars) => ({ grant_type: "client_credentials", client_id: vars.clientId, client_secret: vars.clientSecret }),
50
+ parseToken: (res) => ({ token: res.access_token, expiresIn: res.expires_in }),
51
+ });
52
+ ```
53
+
54
+ ## Vars (Runtime Variables)
55
+
56
+ For variables managed via the Hub UI instead of env vars:
57
+
58
+ ```typescript
59
+ export const myPlug = plug({
60
+ baseUrl: "https://api.example.com",
61
+ auth: bearer(() => ""), // overridden by vars
62
+ vars: [
63
+ { key: "apiKey", label: "API Key", type: "text", secret: true },
64
+ { key: "orgId", label: "Org ID", type: "text", defaultValue: "org_demo" },
65
+ { key: "_token", label: "", type: "text", hidden: true },
66
+ ] as const,
67
+ });
68
+ ```
69
+
70
+ Vars are encrypted in the database via `KHOTAN_SECRET` and injected per-request. Hidden vars (prefixed `_`) are internal storage (cached tokens, etc). `defaultValue` seeds the database the first time the plug is initialized, and later Hub/CLI edits override that stored value.
71
+
72
+ ## Typed Endpoints
73
+
74
+ Define Zod schemas inline on the plug:
75
+
76
+ ```typescript
77
+ import { z } from "zod";
78
+
79
+ export const myPlug = plug({
80
+ baseUrl: "https://api.example.com",
81
+ auth: bearer(process.env.API_KEY!),
82
+ endpoints: {
83
+ listProducts: {
84
+ method: "GET",
85
+ path: "/products",
86
+ query: z.object({ page: z.number().optional(), limit: z.number().optional() }),
87
+ responses: { 200: z.object({ data: z.array(z.object({ id: z.string(), name: z.string() })), total: z.number() }) },
88
+ },
89
+ createProduct: {
90
+ method: "POST",
91
+ path: "/products",
92
+ body: z.object({ name: z.string(), price: z.number() }),
93
+ responses: { 201: z.object({ id: z.string() }) },
94
+ },
95
+ },
96
+ });
97
+ ```
98
+
99
+ Endpoints power the plug debugger UI, `khotan plug --compare`, and typed clients.
100
+
101
+ ## Typed Client (Contract Pattern)
102
+
103
+ For separate contract definition + type-safe calls:
104
+
105
+ ```typescript
106
+ import { defineContract, createPlugClient } from "khotan-data/plug";
107
+
108
+ const contract = defineContract({
109
+ listProducts: {
110
+ method: "GET",
111
+ path: "/products",
112
+ query: z.object({ page: z.number().optional() }),
113
+ responses: { 200: z.object({ data: z.array(ProductSchema), total: z.number() }) },
114
+ },
115
+ });
116
+
117
+ const client = createPlugClient(contract, myPlug);
118
+ const result = await client.listProducts({ query: { page: 1 } });
119
+ // result.status — 200
120
+ // result.body.data — typed as Product[]
121
+ ```
122
+
123
+ ## Hooks
124
+
125
+ ```typescript
126
+ const myPlug = plug({
127
+ // ...
128
+ hooks: {
129
+ beforeRequest: async (ctx) => {
130
+ // ctx.vars, ctx.setVars, ctx.headers, ctx.url
131
+ },
132
+ afterResponse: async (response, ctx) => {
133
+ // inspect/transform response
134
+ },
135
+ onUnauthorized: async (ctx) => {
136
+ // refresh token, update vars
137
+ },
138
+ },
139
+ });
140
+ ```
141
+
142
+ ## Registering in Factory
143
+
144
+ In `{outputDir}/khotan.ts`:
145
+
146
+ ```typescript
147
+ plugs: [
148
+ {
149
+ name: "stripe",
150
+ plug: stripePlug,
151
+ flows: [
152
+ { name: "charges-inflow", type: "inflow", schedule: "0 * * * *", resource: "orders" },
153
+ ],
154
+ // Optional: wires, catches, passes
155
+ },
156
+ ],
157
+ ```
158
+
159
+ ## Making Requests
160
+
161
+ ```typescript
162
+ // Direct
163
+ const products = await myPlug.get("/products", { params: { limit: "10" } });
164
+ const created = await myPlug.post("/products", { body: { name: "Widget" } });
165
+
166
+ // With vars (factory injects these automatically in wire/debug contexts)
167
+ const data = await myPlug.get("/items", { vars: { apiKey: "..." } });
168
+ ```
169
+
170
+ ## Debugging
171
+
172
+ Use `khotan plug` to test plugs against the running dev server. `khotan probe` remains as a legacy alias:
173
+
174
+ ```bash
175
+ npx khotan plug myPlug --info # See endpoints
176
+ npx khotan plug myPlug GET /products # Fire request
177
+ npx khotan plug myPlug --endpoint listProducts --compare # Check schema
178
+ ```
179
+
180
+ Set `KHOTAN_DEBUG=1` for verbose `[khotan:auth]` and `[khotan:request]` console logs.
181
+
182
+ ## Managing Vars
183
+
184
+ Use the CLI to inspect and update stored plug variables:
185
+
186
+ ```bash
187
+ npx khotan plug vars --list
188
+ npx khotan plug vars myPlug
189
+ npx khotan plug vars myPlug set --json '{"apiKey":"secret","orgId":"org_live"}'
190
+ npx khotan plug vars myPlug clear
191
+ ```
192
+
193
+ Variable reads mask `secret` fields automatically. Hub and CLI both talk to the same `/api/khotan/variables/*` routes.
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: khotan-setup
3
+ description: >
4
+ Set up khotan-data in a Next.js + Drizzle + Postgres project. Use when
5
+ initializing khotan in a new project, adding the database schema,
6
+ configuring the factory, or troubleshooting missing setup steps.
7
+ ---
8
+
9
+ Set up khotan-data in a Next.js + Drizzle + Postgres project. Use when initializing khotan in a new project, adding the database schema, configuring the factory, or troubleshooting missing setup steps.
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ npm install khotan-data
15
+ npx khotan init
16
+ npx khotan add schema --yes
17
+ npx khotan migrate
18
+ npx khotan add plug --yes
19
+ ```
20
+
21
+ ## What Init Creates
22
+
23
+ `npx khotan init` scaffolds three files (never overwrites existing):
24
+
25
+ | File | Purpose |
26
+ |------|---------|
27
+ | `khotan.config.ts` | CLI config — sets `outputDir` (default: `src/khotan` or `khotan`) |
28
+ | `{outputDir}/khotan.ts` | Factory config — register plugs, resources, adapter |
29
+ | `src/app/api/khotan/[...all]/route.ts` | Catch-all API route |
30
+
31
+ Use `npx khotan init --full` for greenfield projects — also installs drizzle-orm, postgres, drizzle-kit, and shadcn.
32
+
33
+ ## Factory Config Pattern
34
+
35
+ Edit `{outputDir}/khotan.ts` after init:
36
+
37
+ ```typescript
38
+ import { khotan, drizzleAdapter } from "khotan-data/factory";
39
+ import { db } from "@/db";
40
+ import { stripeChargesInflow } from "./flows/stripe-charges";
41
+
42
+ const khotanData = khotan({
43
+ adapter: drizzleAdapter(db),
44
+ resources: [
45
+ { name: "products", connectField: "sku" },
46
+ { name: "orders", connectField: "order_number" },
47
+ ],
48
+ plugs: [
49
+ {
50
+ name: "stripe",
51
+ plug: stripePlug,
52
+ flows: [
53
+ stripeChargesInflow,
54
+ ],
55
+ },
56
+ ],
57
+ });
58
+
59
+ export default khotanData;
60
+ ```
61
+
62
+ The factory auto-upserts plugs, flows, and resources to the database on first API request.
63
+
64
+ ## Route Handler
65
+
66
+ The catch-all route delegates all HTTP methods to the factory:
67
+
68
+ ```typescript
69
+ import { toNextJsHandler } from "khotan-data/factory";
70
+ import khotanData from "@/lib/khotan/khotan";
71
+
72
+ export const { GET, POST, PUT, PATCH, DELETE } = toNextJsHandler(khotanData.handler);
73
+ ```
74
+
75
+ ## Database Setup
76
+
77
+ ```bash
78
+ npx khotan add schema --yes # Scaffolds Drizzle table definitions
79
+ npx khotan migrate # Generates + applies migrations (needs DATABASE_URL)
80
+ npx khotan migrate --push # Or push directly without migration files
81
+ ```
82
+
83
+ Tables created: `khotan_plugs`, `khotan_resources`, `khotan_flows`, `khotan_wires`, `khotan_runs`, `khotan_mappings`.
84
+
85
+ The schema command auto-detects your Drizzle schema directory, updates `drizzle.config.ts` glob pattern, and adds the barrel re-export.
86
+
87
+ ## Environment Variables
88
+
89
+ | Variable | Required | Purpose |
90
+ |----------|----------|---------|
91
+ | `DATABASE_URL` | Yes | Postgres connection (used by Drizzle) |
92
+ | `KHOTAN_SECRET` | For variables | AES-256-GCM key for encrypting plug vars |
93
+ | `KHOTAN_DEBUG` | For debugging | Enables `/debug/*` routes and the `plug` CLI (`probe` alias) |
94
+ | `KHOTAN_WEBHOOK_URL` | For webhooks | Public URL for wire callbacks |
95
+
96
+ ## Next.js Config
97
+
98
+ Add to `next.config.ts` if using local/tarball install:
99
+
100
+ ```typescript
101
+ const nextConfig = {
102
+ serverExternalPackages: ["khotan-data"],
103
+ };
104
+ ```
105
+
106
+ ## Verify Setup
107
+
108
+ ```bash
109
+ curl http://localhost:3000/api/khotan/plugs # Should list registered plugs
110
+ curl http://localhost:3000/api/khotan/flows # Should list flows
111
+ curl http://localhost:3000/api/khotan/resources # Should list resources
112
+ ```
113
+
114
+ ## Troubleshooting
115
+
116
+ - **Empty plug list**: Factory upserts on first request — hit any endpoint first, then check `/plugs`
117
+ - **"Cannot find module khotan-data"**: Add to `serverExternalPackages` in next.config.ts
118
+ - **Migration fails**: Ensure `DATABASE_URL` is set and Postgres is reachable
119
+ - **Init won't overwrite**: By design — delete the file manually if you need to re-scaffold
@@ -0,0 +1,196 @@
1
+ ---
2
+ name: khotan-webhook
3
+ description: >
4
+ Set up webhook subscriptions and event processing with khotan Wires,
5
+ Catch, and Pass. Use when receiving webhooks from external services,
6
+ registering callback URLs, processing incoming events durably, or
7
+ forwarding events between services.
8
+ ---
9
+
10
+ Set up webhook subscriptions and event processing with khotan Wires, Catch, and Pass. Use when receiving webhooks from external services, registering callback URLs, processing incoming events durably, or forwarding events between services.
11
+
12
+ ## Wire (Webhook Subscriptions)
13
+
14
+ ```bash
15
+ npx khotan add wire --yes
16
+ ```
17
+
18
+ Scaffolds `{outputDir}/wires/wire.ts` (the builder) and `src/components/khotan/wire.tsx` (UI panel).
19
+
20
+ ### Creating a Wire
21
+
22
+ ```typescript
23
+ import { wire } from "./wire";
24
+
25
+ export const stripeWire = wire({
26
+ events: ["invoice.paid", "charge.succeeded"],
27
+
28
+ async onSubscribe(ctx) {
29
+ const res = await ctx.plug.post<{ id: string; secret: string }>(
30
+ "/webhook_endpoints",
31
+ {
32
+ body: {
33
+ url: ctx.callbackUrl,
34
+ enabled_events: ctx.events,
35
+ },
36
+ },
37
+ );
38
+ await ctx.setWireVars({ webhookSecret: res.secret });
39
+ return { remoteId: res.id };
40
+ },
41
+
42
+ async onUnsubscribe(ctx) {
43
+ await ctx.plug.delete(`/webhook_endpoints/${ctx.remoteId}`);
44
+ },
45
+
46
+ async onVerify(ctx) {
47
+ const signature = ctx.headers["stripe-signature"];
48
+ // Verify HMAC using ctx.wireVars.webhookSecret and ctx.body (raw text)
49
+ return isValidSignature(signature, ctx.body, ctx.wireVars.webhookSecret);
50
+ },
51
+ });
52
+ ```
53
+
54
+ ### Hook Contexts
55
+
56
+ **onSubscribe** receives:
57
+ - `ctx.plug` — Plug with vars/auth auto-injected (BoundPlug)
58
+ - `ctx.callbackUrl` — The URL to register with the external service
59
+ - `ctx.events` — Event types to subscribe to
60
+ - `ctx.wireVars` / `ctx.setWireVars()` — Persist wire-specific data (secrets, tokens)
61
+ - Must return `{ remoteId: string }`
62
+
63
+ **onUnsubscribe** receives:
64
+ - `ctx.plug` — BoundPlug
65
+ - `ctx.remoteId` — The ID returned from onSubscribe
66
+ - `ctx.wireVars` / `ctx.setWireVars()`
67
+
68
+ **onVerify** receives:
69
+ - `ctx.headers` — Incoming request headers
70
+ - `ctx.body` — Raw request body (for signature verification)
71
+ - `ctx.wireVars` — Wire-specific vars
72
+ - Must return `boolean`
73
+
74
+ ### Registering Wires
75
+
76
+ In `{outputDir}/khotan.ts`:
77
+
78
+ ```typescript
79
+ import { stripeWire } from "./wires/stripe-wire";
80
+
81
+ plugs: [
82
+ {
83
+ name: "stripe",
84
+ plug: stripePlug,
85
+ wires: [stripeWire],
86
+ },
87
+ ],
88
+ ```
89
+
90
+ ### Programmatic Wire API
91
+
92
+ ```typescript
93
+ const w = khotanData.wire("stripe");
94
+ await w.create("https://your-domain.com/api/khotan/webhook/stripe");
95
+ await w.get(); // Get wire status
96
+ await w.delete(wireId); // Disconnect
97
+ ```
98
+
99
+ ### Webhook Callback URL
100
+
101
+ Wires register callbacks at: `{webhookUrl}/api/khotan/webhook/{plugName}`
102
+
103
+ For local dev, use ngrok or similar tunnel and set `KHOTAN_WEBHOOK_URL`.
104
+
105
+ ## Catch (Durable Event Processing)
106
+
107
+ ```bash
108
+ npx khotan add catch --yes
109
+ ```
110
+
111
+ Process webhook events durably via Vercel Workflow:
112
+
113
+ ```typescript
114
+ import { catchEvent } from "./webhooks/catch";
115
+
116
+ const processInvoice = catchEvent(async (ctx) => {
117
+ "use workflow";
118
+
119
+ async function persist() {
120
+ "use step";
121
+ // Write to database — retried on failure
122
+ await db.insert(invoices).values(ctx.event);
123
+ }
124
+
125
+ await persist();
126
+ });
127
+ ```
128
+
129
+ Register on the source plug:
130
+
131
+ ```typescript
132
+ { name: "stripe", plug: stripePlug, wires: [stripeWire], catches: [processInvoice] }
133
+ ```
134
+
135
+ ## Pass (Event Forwarding)
136
+
137
+ ```bash
138
+ npx khotan add pass --yes
139
+ ```
140
+
141
+ Forward webhook events to another service:
142
+
143
+ ```typescript
144
+ import { pass } from "./webhooks/pass";
145
+
146
+ const forwardToSlack = pass({
147
+ to: "slack", // Destination plug name (must be registered)
148
+ workflow: async (ctx) => {
149
+ "use workflow";
150
+ // ctx.event — the incoming webhook payload
151
+ // ctx.destVars — destination plug variables
152
+ async function forward() {
153
+ "use step";
154
+ await ctx.destPlug.post("/messages", {
155
+ body: { text: `New event: ${ctx.event.type}` },
156
+ });
157
+ }
158
+ await forward();
159
+ },
160
+ });
161
+ ```
162
+
163
+ Register on the source plug:
164
+
165
+ ```typescript
166
+ { name: "stripe", plug: stripePlug, wires: [stripeWire], passes: [forwardToSlack] }
167
+ ```
168
+
169
+ ## Webhook Flow
170
+
171
+ ```
172
+ External Service → POST /api/khotan/webhook/:plugName
173
+ → onVerify (signature check)
174
+ → Parse event type
175
+ → Start catch workflows (durable processing)
176
+ → Start pass workflows (event forwarding)
177
+ → Return { received: true }
178
+ ```
179
+
180
+ ## Dependencies
181
+
182
+ - **Wire**: Requires `plug` and `schema` components
183
+ - **Catch**: Requires `wire`; needs `workflow` package for Vercel Workflow
184
+ - **Pass**: Requires `wire` and `plug`; needs `workflow` package
185
+
186
+ ## Hub Integration
187
+
188
+ The WirePanel in the Hub UI lets users connect/disconnect webhooks from the browser. It calls `POST /api/khotan/wires/:plugName` with the callback URL.
189
+
190
+ ## Debugging Webhooks
191
+
192
+ 1. Check wire status: `GET /api/khotan/wires/:plugName`
193
+ 2. Verify `onVerify` logic: check wire vars contain the signing secret
194
+ 3. Check factory logs: `KHOTAN_DEBUG=1` enables `[khotan:wire]` log lines
195
+ 4. 401 on webhook receive = `onVerify` returning false
196
+ 5. 500 on webhook = missing `workflow` package or catch/pass misconfigured