@craftedxp/sdk-node 0.2.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.
package/CONSUMING.md ADDED
@@ -0,0 +1,119 @@
1
+ # Consuming @craftedxp/sdk-node
2
+
3
+ Three install paths, fastest → production. Pick one.
4
+
5
+ ## TL;DR
6
+
7
+ ```bash
8
+ # In YOUR Node.js / TypeScript project
9
+ npm install /path/to/craftedxp-sdk-node-0.2.0.tgz # or git+ssh, or (eventually) @craftedxp/sdk-node
10
+ ```
11
+
12
+ ```ts
13
+ import { PlatformClient } from '@craftedxp/sdk-node'
14
+
15
+ const client = new PlatformClient({ apiKey: process.env.VOICE_AGENT_SK! })
16
+ const me = await client.me.get()
17
+ ```
18
+
19
+ Node 18+. No peer deps, no native modules, no `pod install`. Pure JS.
20
+
21
+ ---
22
+
23
+ ## Path 1 — Local tarball (fastest)
24
+
25
+ In the SDK repo:
26
+
27
+ ```bash
28
+ cd voice-assistant/sdk/node
29
+ npm install
30
+ npm pack # emits craftedxp-sdk-node-0.2.0.tgz
31
+ ```
32
+
33
+ In your app:
34
+
35
+ ```bash
36
+ npm install /abs/path/to/voice-assistant/sdk/node/craftedxp-sdk-node-0.2.0.tgz
37
+ ```
38
+
39
+ `npm pack` runs the `prepare` hook, which rebuilds `dist/`, so the tarball ships `dist/index.js` (CJS) + `dist/index.mjs` (ESM) + `dist/index.d.ts` + sourcemaps — and nothing else.
40
+
41
+ When the SDK changes, re-run `npm pack` and re-install.
42
+
43
+ ---
44
+
45
+ ## Path 2 — Git dependency
46
+
47
+ Works once you've subtree-split the SDK into its own repo:
48
+
49
+ ```bash
50
+ # one-time, in the main repo
51
+ git subtree split --prefix=sdk/node -b sdk-node
52
+ git push git@github.com:your-org/sdk-node.git sdk-node:main
53
+ ```
54
+
55
+ Then in your app:
56
+
57
+ ```jsonc
58
+ {
59
+ "dependencies": {
60
+ "@craftedxp/sdk-node": "git+ssh://git@github.com:your-org/sdk-node.git#v0.1.0",
61
+ },
62
+ }
63
+ ```
64
+
65
+ npm clones + runs `prepare`, which rebuilds `dist/`. Tag releases (`git tag v0.1.0`) so your pins are stable.
66
+
67
+ ---
68
+
69
+ ## Path 3 — npm registry (production)
70
+
71
+ ```bash
72
+ cd voice-assistant/sdk/node
73
+ npm login
74
+ npm publish --access public
75
+ ```
76
+
77
+ Consumer side:
78
+
79
+ ```bash
80
+ npm install @craftedxp/sdk-node
81
+ ```
82
+
83
+ For private distribution, see GitHub Packages / Verdaccio / npm Enterprise — same pattern as the sibling React Native SDK.
84
+
85
+ ---
86
+
87
+ ## Runtime requirements
88
+
89
+ - **Node ≥ 18** — needed for native `fetch`, `FormData`, `Blob`, `AbortController`. These were stabilised in 18.x; older Nodes would need polyfills we don't want to carry.
90
+ - **Bundler-friendly** — emits proper `"exports"` with conditional CJS + ESM + types. Works in Next.js (server), Fastify, Express, plain scripts, tests (Vitest / Jest), Cloud Functions, Cloud Run.
91
+
92
+ ---
93
+
94
+ ## Environment
95
+
96
+ Set your API key via environment variable — never hard-code an `sk_` value:
97
+
98
+ ```bash
99
+ # .env
100
+ VOICE_AGENT_SK=sk_your_key_here
101
+ VOICE_AGENT_BASE_URL=http://localhost:8080 # for local dev
102
+ ```
103
+
104
+ ```ts
105
+ const client = new PlatformClient({
106
+ apiKey: process.env.VOICE_AGENT_SK!,
107
+ baseUrl: process.env.VOICE_AGENT_BASE_URL,
108
+ })
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Troubleshooting
114
+
115
+ - **`TypeError: fetch is not a function`** — Node version is below 18. Upgrade, or pass a `fetch` implementation explicitly via `new PlatformClient({ fetch: ... })`.
116
+ - **`PlatformError: unauthorized` on every call** — API key missing or wrong. `client.me.get()` is the cleanest validation.
117
+ - **`PlatformError: rate_limited`** — you hit the per-org rate limit (10/min for `POST /v1/calls`, 60/min for `POST /v1/call-tokens`, 100/min everywhere else). The SDK auto-retries with backoff up to `maxRetries` (default 2); raise it or space out calls if you're doing bulk operations.
118
+ - **`PlatformError: payment_required`** — org ran out of credit. `client.credits.getBalance()` + top up via the dashboard.
119
+ - **File uploads timing out** — `uploadFile` already sets a 5-minute timeout; if you're uploading huge PDFs, override via... well, uploads above a few MB are better split into smaller documents anyway. Let me know if that's a real pinch point and we'll plumb a `timeoutMs` override on the upload method.
package/DEVELOPING.md ADDED
@@ -0,0 +1,82 @@
1
+ # Developing `@craftedxp/sdk-node`
2
+
3
+ Pure JS, zero-dep, runs in Node 18+. Fast iteration — no native compilation, no platform quirks.
4
+
5
+ ## TL;DR
6
+
7
+ ```bash
8
+ cd voice-assistant/sdk/node
9
+ npm install
10
+ npm run dev # tsup --watch → rebuilds dist/ on save
11
+ ```
12
+
13
+ Point a consumer at the local build:
14
+
15
+ ```bash
16
+ # In the consumer project:
17
+ npm install /abs/path/to/voice-assistant/sdk/node # file: dep
18
+ # or
19
+ yalc add @craftedxp/sdk-node
20
+ ```
21
+
22
+ After each rebuild the consumer picks up changes on next `require` (server restart or nodemon). For Next.js API routes, the dev server auto-reloads.
23
+
24
+ ## Local testing against the platform
25
+
26
+ ```bash
27
+ # Terminal 1 — run the platform server
28
+ cd voice-assistant/server && npm run dev
29
+
30
+ # Terminal 2 — run the SDK in watch mode
31
+ cd voice-assistant/sdk/node && npm run dev
32
+
33
+ # Terminal 3 — a scratch consumer
34
+ cd /tmp && mkdir sdk-smoke && cd sdk-smoke
35
+ npm init -y
36
+ npm install /path/to/voice-assistant/sdk/node
37
+ cat > smoke.mjs <<'EOF'
38
+ import { PlatformClient } from '@craftedxp/sdk-node'
39
+ const client = new PlatformClient({
40
+ apiKey: process.env.VOICE_AGENT_SK,
41
+ baseUrl: 'http://localhost:8080',
42
+ })
43
+ console.log(await client.me.get())
44
+ console.log((await client.agents.list()).map(a => ({ id: a.agentId, name: a.name })))
45
+ EOF
46
+ VOICE_AGENT_SK=sk_... node smoke.mjs
47
+ ```
48
+
49
+ ## Adding a new resource
50
+
51
+ Say we add `/v1/phone-numbers` server-side. The SDK pattern:
52
+
53
+ 1. Add types to `src/types.ts` (`PhoneNumber`, `PhoneNumberCreateInput`, etc.)
54
+ 2. Create `src/resources/phoneNumbers.ts` following the shape of existing resources (factory taking `HttpClient`, returning `{ create, list, get, delete, ... }`)
55
+ 3. Wire it onto `PlatformClient` in `src/PlatformClient.ts`
56
+ 4. Re-export the resource type from `src/index.ts`
57
+ 5. Add a section to `README.md` under "API surface"
58
+ 6. `npm run build` + `npm run typecheck`
59
+
60
+ Each resource is ~30–80 lines. The HTTP + error handling is centralised in `src/http.ts` — resources never call `fetch` directly.
61
+
62
+ ## Style
63
+
64
+ - **No top-level state.** Resources are factories keyed on the HTTP client, never module-scoped singletons. Consumers that instantiate multiple clients (e.g. in a test harness spanning multiple orgs) need isolation.
65
+ - **Dates as `number` (ms).** Matches the server's Firestore Timestamp serialisation. Consumers who want Date objects can wrap.
66
+ - **No mutation of inputs.** Every resource method JSON-serialises inputs via `JSON.stringify` — pass whatever you want, we don't touch it.
67
+ - **Errors always `PlatformError`.** Network errors get wrapped with `code: 'unknown'` + `status: 0`, but the class is consistent so `catch (err)` / `instanceof` works across all failures.
68
+
69
+ ## Before releasing
70
+
71
+ ```bash
72
+ npm run build # emits dist/
73
+ npm run typecheck
74
+ npm pack --dry-run # verify tarball contents — should be dist/ + docs + package.json
75
+ ```
76
+
77
+ Then bump `version` in `package.json`, commit, tag, publish.
78
+
79
+ ## Related
80
+
81
+ - [CONSUMING.md](./CONSUMING.md) — external install paths
82
+ - `../web/DEVELOPING.md` + `../react-native/DEVELOPING.md` — sibling SDKs
package/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # @craftedxp/sdk-node
2
+
3
+ Node.js / TypeScript SDK for the voice agent platform. Use it from your **backend** to mint short-lived call tokens, manage agents, query calls, and upload knowledge-base docs.
4
+
5
+ > Pairs with `@craftedxp/voice-rn` (the React Native client SDK). Your app calls `fetchToken` → your backend uses this SDK → the platform mints a `ct_` token → returned to the app. The `sk_` API key only ever lives on your backend.
6
+
7
+ Zero runtime dependencies. Node 18+.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @craftedxp/sdk-node
13
+ ```
14
+
15
+ ## Quick start — minting a call token
16
+
17
+ The most common use:
18
+
19
+ ```ts
20
+ import { PlatformClient } from '@craftedxp/sdk-node'
21
+
22
+ const client = new PlatformClient({
23
+ apiKey: process.env.VOICE_AGENT_SK!, // never ship this to a client
24
+ baseUrl: 'https://api.your-server.com', // or http://localhost:8080 for dev
25
+ })
26
+
27
+ // In your "/voice-token" route handler — called by the RN SDK's fetchToken:
28
+ async function mintForCall({ agentId, userId, context, metadata }) {
29
+ const { token } = await client.callTokens.mint({
30
+ agentId,
31
+ ttlSeconds: 600, // 10 min default; up to 3600 (60 min)
32
+ contactId: userId, // optional → cross-call memory
33
+ context, // optional arbitrary JSON, lowered into agent's system prompt
34
+ metadata, // optional opaque keys round-tripped on call.ended webhook (≤1 KB)
35
+ })
36
+ return { token } // hand to the RN SDK
37
+ }
38
+ ```
39
+
40
+ The full call-token surface:
41
+
42
+ ```ts
43
+ client.callTokens.mint({ agentId, ttlSeconds, context, metadata, contactId, vars, allowedOrigins })
44
+ client.callTokens.list({ includeRevoked, includeExpired, limit })
45
+ client.callTokens.revoke(tokenId)
46
+ ```
47
+
48
+ ## Other resources
49
+
50
+ ```ts
51
+ client.me.get() // the org behind the sk_
52
+
53
+ client.agents.create({ name, systemPrompt, ... }) // CRUD on agents
54
+ client.agents.list({ limit, cursor })
55
+ client.agents.get(agentId)
56
+ client.agents.update(agentId, patch)
57
+ client.agents.delete(agentId)
58
+ client.agents.webhooks(agentId).create({ url, secret, events })
59
+ client.agents.webhooks(agentId).list()
60
+ // ...
61
+
62
+ client.calls.list({ agentId, status, limit, cursor }) // call records
63
+ client.calls.listAll({ agentId }) // async iterator across pages
64
+ client.calls.get(callId)
65
+
66
+ client.knowledgeBases.create({ name }) // KB CRUD + uploads
67
+ client.knowledgeBases.list()
68
+ client.knowledgeBases.uploadFile(kbId, filePath)
69
+ client.knowledgeBases.delete(kbId)
70
+
71
+ client.credits.getBalance() // billing
72
+ client.credits.getLedger({ limit, cursor })
73
+
74
+ client.webhooks.deliveries({ agentId, callId, webhookId }) // org-wide delivery log
75
+ ```
76
+
77
+ ## Webhook signature verification
78
+
79
+ ```ts
80
+ import express from 'express'
81
+ import { verifyWebhookSignature } from '@craftedxp/sdk-node'
82
+
83
+ const app = express()
84
+
85
+ app.post(
86
+ '/webhooks/voice-agent',
87
+ // Raw body — we need the exact bytes to recompute the HMAC.
88
+ express.raw({ type: 'application/json' }),
89
+ (req, res) => {
90
+ const sig = req.header('X-Platform-Signature-256') ?? ''
91
+ if (!verifyWebhookSignature(req.body, sig, process.env.VOICE_AGENT_WEBHOOK_SECRET!)) {
92
+ return res.status(401).send('invalid signature')
93
+ }
94
+ const event = JSON.parse(req.body.toString('utf8'))
95
+ // ...handle event
96
+ res.status(200).end()
97
+ },
98
+ )
99
+ ```
100
+
101
+ The function is timing-safe; copy it into your middleware verbatim. No PlatformClient instance required — `verifyWebhookSignature` is a standalone helper so any framework (Koa, Next.js route handlers, Hono, etc.) can use it.
102
+
103
+ ## Errors
104
+
105
+ All HTTP errors throw a typed `PlatformError`:
106
+
107
+ ```ts
108
+ import { PlatformError } from '@craftedxp/sdk-node'
109
+
110
+ try {
111
+ await client.callTokens.mint({ agentId })
112
+ } catch (err) {
113
+ if (err instanceof PlatformError) {
114
+ console.error(err.code, err.status, err.message, err.field, err.docsUrl)
115
+ }
116
+ throw err
117
+ }
118
+ ```
119
+
120
+ The HTTP layer auto-retries 429 (honouring `Retry-After`) and 5xx responses with exponential backoff. Default `maxRetries = 2` (3 total attempts); pass `0` to `new PlatformClient({ maxRetries: 0 })` to disable.
121
+
122
+ ## Pagination
123
+
124
+ Cursor-based. Use the `listAll` async iterator if you want every page without managing the cursor manually:
125
+
126
+ ```ts
127
+ for await (const call of client.calls.listAll({ agentId })) {
128
+ // process each call across all pages
129
+ }
130
+ ```
131
+
132
+ ## What's NOT in this SDK
133
+
134
+ - Voice / WebSocket / audio streaming. Use `@craftedxp/voice-rn` (React Native) for the call path itself.
135
+ - Browser-side use. This SDK assumes a server-side environment with native `fetch`. The `sk_` API key must never reach a browser.
136
+ - Phone numbers, outbound dialling. Telephony is on the platform roadmap; the SDK will surface it when the server endpoints land.
137
+
138
+ ## Migration from `@voxline/node@0.1.0`
139
+
140
+ `@voxline/node` was a pre-launch internal name. This package replaces it under the `@craftedxp` org. API surface is unchanged except for the Phase 12 additions (`context` / `metadata` / `contactId` on `callTokens.mint`).
141
+
142
+ ```diff
143
+ - import { PlatformClient } from '@voxline/node'
144
+ + import { PlatformClient } from '@craftedxp/sdk-node'
145
+
146
+ const client = new PlatformClient({ apiKey })
147
+ - await client.callTokens.mint({ agentId, vars: { name: 'Arun' } })
148
+ + await client.callTokens.mint({ agentId, context: { name: 'Arun', orders: [/* nested ok */] } })
149
+ ```
150
+
151
+ `vars` is still accepted server-side for backward compatibility.