@riseworks/riseworks-sdk 1.0.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.
Files changed (41) hide show
  1. package/README.md +240 -0
  2. package/ai-skills/README.md +28 -0
  3. package/ai-skills/STANDARDS.md +66 -0
  4. package/ai-skills/references/API_WORKFLOWS.md +200 -0
  5. package/ai-skills/references/TYPES.md +190 -0
  6. package/ai-skills/rise-auth-and-setup/README.md +14 -0
  7. package/ai-skills/rise-auth-and-setup/SKILL.md +57 -0
  8. package/ai-skills/rise-auth-and-setup/metadata.json +12 -0
  9. package/ai-skills/rise-auth-and-setup/references/SETUP_PATTERNS.md +26 -0
  10. package/ai-skills/rise-debugging-and-errors/SKILL.md +51 -0
  11. package/ai-skills/rise-payments-workflows/README.md +19 -0
  12. package/ai-skills/rise-payments-workflows/SKILL.md +109 -0
  13. package/ai-skills/rise-payments-workflows/metadata.json +12 -0
  14. package/ai-skills/rise-payments-workflows/references/PAYMENT_PATTERNS.md +36 -0
  15. package/ai-skills/rise-sdk-integration/README.md +25 -0
  16. package/ai-skills/rise-sdk-integration/SKILL.md +130 -0
  17. package/ai-skills/rise-sdk-integration/metadata.json +12 -0
  18. package/ai-skills/rise-sdk-integration/references/PATTERNS.md +28 -0
  19. package/ai-skills/rise-security-and-approvals/SKILL.md +33 -0
  20. package/ai-skills/rise-teams-and-invites/README.md +13 -0
  21. package/ai-skills/rise-teams-and-invites/SKILL.md +66 -0
  22. package/ai-skills/rise-teams-and-invites/metadata.json +12 -0
  23. package/ai-skills/rise-teams-and-invites/references/TEAM_PATTERNS.md +25 -0
  24. package/ai-skills/rise-v1-migration/README.md +22 -0
  25. package/ai-skills/rise-v1-migration/SKILL.md +184 -0
  26. package/ai-skills/rise-v1-migration/metadata.json +13 -0
  27. package/ai-skills/rise-v1-migration/references/ENDPOINT_MAPPINGS.md +72 -0
  28. package/ai-skills/rise-v1-migration/references/MIGRATION_PATTERNS.md +66 -0
  29. package/ai-skills/rise-webhooks/README.md +41 -0
  30. package/ai-skills/rise-webhooks/SKILL.md +143 -0
  31. package/ai-skills/rise-webhooks/metadata.json +13 -0
  32. package/ai-skills/rise-webhooks/references/HANDLER.md +60 -0
  33. package/ai-skills/rise-webhooks/references/REGISTRATION.md +113 -0
  34. package/dist/index.cjs +1406 -0
  35. package/dist/index.d.cts +6865 -0
  36. package/dist/index.d.cts.map +1 -0
  37. package/dist/index.d.mts +6865 -0
  38. package/dist/index.d.mts.map +1 -0
  39. package/dist/index.mjs +1383 -0
  40. package/package.json +87 -0
  41. package/scripts/add-skills.mjs +158 -0
package/README.md ADDED
@@ -0,0 +1,240 @@
1
+ # Rise SDK
2
+
3
+ Official TypeScript/JavaScript SDK for Rise B2B integrations.
4
+
5
+ It includes:
6
+ - A typed API client for B2B and `v2` endpoints
7
+ - Claude-style AI coding skills you can copy into `.claude/skills/`
8
+ - Built-in webhook validation
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install riseworks-sdk
14
+ ```
15
+
16
+ ## Quick Start
17
+
18
+ ```ts
19
+ import { RiseApiClient } from 'riseworks-sdk'
20
+
21
+ const client = new RiseApiClient({
22
+ environment: 'stg',
23
+ jwtToken: process.env.RISE_JWT_TOKEN!,
24
+ })
25
+
26
+ const me = await client.me.get()
27
+ const organizations = await client.user.getOrganizations()
28
+ ```
29
+
30
+ ## AI Coding Skills
31
+
32
+ The npm package ships reusable Claude-style `SKILL.md` files for coding assistants.
33
+
34
+ **Add skills from the CLI (no copy-paste):**
35
+
36
+ ```bash
37
+ # Add all skills to all supported agents (default)
38
+ npx riseworks-sdk add-skills
39
+
40
+ # Add to one agent only
41
+ npx riseworks-sdk add-skills --agent cursor rise-sdk-integration rise-webhooks
42
+
43
+ # List available skills and supported agents
44
+ npx riseworks-sdk add-skills --list
45
+ ```
46
+
47
+ Or copy manually from `node_modules/riseworks-sdk/ai-skills/` into `.claude/skills/`, `.cursor/skills/`, etc.
48
+
49
+ Available skills:
50
+
51
+ - `rise-sdk-integration`
52
+ - `rise-v1-migration`
53
+ - `rise-payments-workflows`
54
+ - `rise-webhooks`
55
+ - `rise-teams-and-invites`
56
+ - `rise-auth-and-setup`
57
+ - `rise-security-and-approvals`
58
+ - `rise-debugging-and-errors`
59
+
60
+ Use these when you want Claude Code or a similar coding assistant to write better Rise integration code.
61
+
62
+ ## Low-Level API Groups
63
+
64
+ The lower-level client remains available for direct endpoint access.
65
+
66
+ - `client.auth`
67
+ - `client.webhooks`
68
+ - `client.company`
69
+ - `client.organizations`
70
+ - `client.entityBalance`
71
+ - `client.invites`
72
+ - `client.me`
73
+ - `client.payments`
74
+ - `client.billPay`
75
+ - `client.payroll`
76
+ - `client.team`
77
+ - `client.teams`
78
+ - `client.user`
79
+
80
+ Withdrawals are not part of the SDK, and `client.company` / `client.organizations` are read-only: those endpoints require reCAPTCHA verification headers that only a browser session can produce, so they cannot be called server-to-server. Use the Rise dashboard for withdrawals and company profile changes.
81
+
82
+ Every `POST`/`PUT` request automatically carries a fresh `x-idempotency-key` header. Pass your own key to `payments.prepare/create/execute`, `getPaymentTypedData`, `executePaymentWithSignedData`, or `sendPayment` (as `idempotencyKey`) when you want retries of the same logical operation deduplicated on your key instead.
83
+
84
+ ## Branded ID types
85
+
86
+ The SDK exports branded nanoid types for API params and responses. Use them when you have a plain `string` from a response and need to pass it to another method:
87
+
88
+ | Type | Use case |
89
+ |------|----------|
90
+ | `TeamNanoid` | `payments.get()`, `teams.get()`, `teams.getUsers()`, `billPay.*`, payroll, invites |
91
+ | `UserNanoid` | `teams.getMemberSettings()`, company members |
92
+ | `CompanyNanoid` | `teams.create()`, company APIs, webhooks |
93
+ | `WithdrawAccountNanoid` | Typing withdraw-account ids in webhook event payloads |
94
+ | `WebhookEndpointNanoid` | `webhooks.get()`, `webhooks.update()`, `webhooks.test()` |
95
+ | `WebhookDeliveryNanoid` | `webhooks.retryDelivery()`, delivery history |
96
+ | `InviteNanoid` | Invite execute/list flows |
97
+ | `TransactionNanoid` | Typing payment response `transaction` fields |
98
+
99
+ ```ts
100
+ import {
101
+ RiseApiClient,
102
+ type TeamNanoid,
103
+ type UserNanoid,
104
+ type CompanyNanoid,
105
+ type WithdrawAccountNanoid,
106
+ type WebhookEndpointNanoid,
107
+ type WebhookDeliveryNanoid,
108
+ type InviteNanoid,
109
+ type TransactionNanoid,
110
+ } from 'riseworks-sdk'
111
+
112
+ const client = new RiseApiClient({ environment: 'stg', jwtToken: '…' })
113
+ const { data } = await client.user.getTeams()
114
+ const teamNanoid = data?.teams?.[0]?.nanoid // string
115
+
116
+ await client.payments.get({
117
+ team_nanoid: teamNanoid as TeamNanoid,
118
+ state: 'all',
119
+ query_type: 'payable',
120
+ start_date: new Date(),
121
+ end_date: new Date(),
122
+ })
123
+ ```
124
+
125
+ ## Examples
126
+
127
+ ### Teams
128
+
129
+ ```ts
130
+ const team = await client.teams.get({ team_nanoid: 'te_123' })
131
+
132
+ await client.teams.update(
133
+ { team_nanoid: 'te_123' },
134
+ { name: 'Finance Ops' },
135
+ )
136
+ const members = await client.teams.getUsers({ team_nanoid: 'te_123' })
137
+ ```
138
+
139
+ ### Bill Pay
140
+
141
+ ```ts
142
+ await client.billPay.createRecipient(
143
+ { team_nanoid: 'te_123' },
144
+ { email: 'vendor@example.com' },
145
+ )
146
+
147
+ const payment = await client.billPay.sendInstantPayment({
148
+ from: 'te_123',
149
+ amount_cents: 125000,
150
+ currency_symbol: 'USD',
151
+ external_recipient_email: 'vendor@example.com',
152
+ payment_data: {
153
+ role_description: 'Design work',
154
+ invoice_description: 'Invoice INV-2026-001',
155
+ services_description: 'Landing page design',
156
+ payment_details: 'Net 15',
157
+ rise_sow: false,
158
+ },
159
+ })
160
+ ```
161
+
162
+ ### Treasury
163
+
164
+ ```ts
165
+ const balance = await client.entityBalance.get({
166
+ nanoid: 'te_123',
167
+ })
168
+ ```
169
+
170
+ ### Batch payments
171
+
172
+ ```ts
173
+ const result = await client.payments.sendPayment({
174
+ from: 'te_123' as TeamNanoid,
175
+ to: [{ to: 'us_123' as UserNanoid, amount_cents: 50000, currency_symbol: 'USD' }],
176
+ pay_now: true,
177
+ })
178
+
179
+ console.log(result.data.transaction)
180
+ // Recipients the server skipped or flagged as recently paid are surfaced —
181
+ // check them instead of assuming every recipient in `to` was paid.
182
+ console.log(result.failed_payments)
183
+ console.log(result.duplicates)
184
+ ```
185
+
186
+ Before signing, `sendPayment` verifies the server-provided typed data against your request (expected chain id for the environment, payment count, max per-payment amount) and throws instead of signing anything broader than what you asked for.
187
+
188
+ ### Webhooks
189
+
190
+ ```ts
191
+ import express from 'express'
192
+ import { WebhookValidator } from 'riseworks-sdk'
193
+
194
+ const app = express()
195
+ const validator = new WebhookValidator(process.env.RISE_WEBHOOK_SECRET!)
196
+
197
+ app.post('/rise-webhooks', express.raw({ type: 'application/json' }), (req, res) => {
198
+ try {
199
+ const event = validator.validateEvent(
200
+ req.body,
201
+ req.headers['x-rise-signature'] as string,
202
+ )
203
+
204
+ console.log(event.event_type)
205
+ res.status(200).json({ received: true })
206
+ } catch (error) {
207
+ res.status(400).json({
208
+ error: error instanceof Error ? error.message : 'Webhook validation failed',
209
+ })
210
+ }
211
+ })
212
+ ```
213
+
214
+ ## Exports
215
+
216
+ The package exports:
217
+ - `RiseApiClient`
218
+ - `WebhookValidator`
219
+ - Webhook event types
220
+ - Generated API request/response types
221
+
222
+ ## Authentication
223
+
224
+ You can authenticate with either:
225
+ - `jwtToken`
226
+ - `riseIdAuth` for automatic SIWE-based JWT generation and refresh
227
+
228
+ ```ts
229
+ const client = new RiseApiClient({
230
+ environment: 'prod',
231
+ riseIdAuth: {
232
+ riseId: process.env.RISE_ID!,
233
+ privateKey: process.env.RISE_PRIVATE_KEY!,
234
+ },
235
+ })
236
+ ```
237
+
238
+ ## License
239
+
240
+ MIT
@@ -0,0 +1,28 @@
1
+ # Rise AI Coding Skills
2
+
3
+ This package ships reusable `SKILL.md` files (Agent Skills format) for AI-assisted Rise SDK integrations. The same skills work across **Claude Code, Cursor, Windsurf, and Codex**. Skills describe the SDK at a point in time; the **installed SDK source is the source of truth** for current methods, types, and behavior—agents are instructed to read the SDK code when implementing.
4
+
5
+ Skills live directly under `ai-skills/` (e.g. `ai-skills/rise-sdk-integration/SKILL.md`). The format is agent-agnostic, so the CLI copies that content into each agent’s folder (`.claude/skills`, `.cursor/skills`, etc.).
6
+
7
+ **Add skills via CLI:**
8
+
9
+ ```bash
10
+ npx riseworks-sdk add-skills # add all skills to all agents (default)
11
+ npx riseworks-sdk add-skills --agent cursor rise-sdk-integration rise-webhooks
12
+ npx riseworks-sdk add-skills --list # list skills and supported agents
13
+ ```
14
+
15
+ Or copy from `node_modules/riseworks-sdk/ai-skills/` into your project's agent skills directory (e.g. `.claude/skills/`, `.cursor/skills/`).
16
+
17
+ **Reference:** `ai-skills/references/TYPES.md` — TypeScript types (webhook events, request/response shapes, nanoids). Copied with skills when using the CLI so skills can link to `../references/TYPES.md`.
18
+
19
+ Available skills:
20
+
21
+ - `rise-sdk-integration`
22
+ - `rise-v1-migration`
23
+ - `rise-payments-workflows`
24
+ - `rise-webhooks`
25
+ - `rise-teams-and-invites`
26
+ - `rise-auth-and-setup`
27
+ - `rise-security-and-approvals`
28
+ - `rise-debugging-and-errors`
@@ -0,0 +1,66 @@
1
+ # Skill authoring standards (reference)
2
+
3
+ This package follows the [Agent Skills](https://agentskills.io/specification) format and best practices so skills trigger reliably and produce good output.
4
+
5
+ ## Frontmatter (required)
6
+
7
+ - **name**: Max 64 chars, lowercase letters, numbers, hyphens only. Must match the skill directory name.
8
+ - **description**: Max 1024 chars. Primary trigger for agents—include **what** the skill does and **when** to use it. Use third person or imperative (“Use this skill when…”). Add keywords users might say (e.g. “payments”, “migration”, “webhooks”).
9
+
10
+ Optional fields we can use:
11
+
12
+ - **compatibility**: Short note on environment (e.g. “Node.js, TypeScript” or “Claude Code, Cursor, Windsurf, Codex”).
13
+ - **metadata**: e.g. `author`, `version` for traceability.
14
+
15
+ ## Body structure (recommended by spec)
16
+
17
+ - **Step-by-step instructions** where the task has a clear sequence.
18
+ - **Common edge cases** (what can go wrong, what to validate).
19
+ - **Examples** of inputs and outputs (or minimal code snippets) so the agent can mirror real usage.
20
+ - **When not to use** (boundaries) so the skill does not trigger on unrelated tasks.
21
+
22
+ ## Conciseness (Claude best practices)
23
+
24
+ - Only include what the agent does not already know. Omit generic explanations.
25
+ - Keep the main `SKILL.md` under ~500 lines. Move long reference material to `references/` and link from the body.
26
+ - Prefer one level of reference (e.g. `references/REFERENCE.md`) rather than deep chains.
27
+
28
+ ## Progressive disclosure
29
+
30
+ - Agents load only **name** and **description** at discovery (~100 tokens).
31
+ - When the task matches, they load the full **SKILL.md** (aim for <5000 tokens).
32
+ - Put extra docs in `references/` or `scripts/` and reference them so they are loaded only when needed.
33
+
34
+ ## Optional authoring files
35
+
36
+ We can also keep extra authoring and human-facing files in each skill directory when they add value:
37
+
38
+ - **`README.md`**: short human summary of the skill, its scope, and file layout.
39
+ - **`metadata.json`**: version, domain, references, and authoring metadata for tooling and maintainers.
40
+ - **`references/*.md`**: detailed implementation guidance for agents to load on demand.
41
+
42
+ Important:
43
+
44
+ - `SKILL.md` remains the **agent-facing compiled document** and should stay concise.
45
+ - `README.md` and `metadata.json` help maintainers and future tooling, but should not replace `SKILL.md`.
46
+ - Prefer adding support files only where they improve output quality materially; avoid adding boilerplate to every skill directory without a clear use.
47
+
48
+ ## Triggering (optimizing descriptions)
49
+
50
+ - Describe **user intent** and **context**, not internal implementation.
51
+ - Include both explicit triggers (“when the user says X”) and implicit ones (“even if they don’t say ‘Rise SDK’”).
52
+ - Test with ~20 eval queries (10 should-trigger, 10 should-not-trigger) and refine the description so it does not over- or under-trigger.
53
+
54
+ ## SDK as source of truth
55
+
56
+ - Skills document the SDK at a point in time and may not reflect the latest API. **Include a note in SDK-related skills** that agents should always read the installed SDK code (e.g. `node_modules/riseworks-sdk`) for current methods, types, and behavior—the SDK may have new functions or types that are not yet described in the skills.
57
+
58
+ ## Documentation
59
+
60
+ - Point to the official **Rise B2B API docs** ([https://v2-docs.riseworks.io/](https://v2-docs.riseworks.io/)) in a "Documentation" section when it helps: Quickstart, Authentication, SDK, Webhooks, API Reference. That way the agent can suggest the right guide or reference instead of duplicating content in the skill.
61
+
62
+ ## Validation
63
+
64
+ - Ensure `name` matches the directory and fits the spec (length, characters).
65
+ - Keep `description` under 1024 characters.
66
+ - You can validate with: `skills-ref validate ./skill-name` (from [agentskills/agentskills](https://github.com/agentskills/agentskills)).
@@ -0,0 +1,200 @@
1
+ # Rise SDK API workflows
2
+
3
+ Use this reference when the user is building application code with
4
+ `riseworks-sdk` and needs the actual SDK method flow, not just type names.
5
+
6
+ ## Default approach
7
+
8
+ - Start by identifying the entity the user already has: company, team, user, or
9
+ withdraw account.
10
+ - If they do not have the required nanoid yet, generate discovery calls first.
11
+ - Prefer the SDK method that directly matches the requested workflow.
12
+ - Use exported request/response types instead of anonymous object shapes.
13
+
14
+ ## Common discovery flows
15
+
16
+ ### Auth bootstrap
17
+
18
+ ```ts
19
+ const client = new RiseApiClient({
20
+ environment: 'stg',
21
+ jwtToken,
22
+ })
23
+
24
+ const me = await client.me.get()
25
+ const teams = await client.user.getTeams()
26
+ const orgs = await client.user.getOrganizations()
27
+ ```
28
+
29
+ Use this when the user needs current-user, company, or initial team/company
30
+ context.
31
+
32
+ ### Team and company discovery
33
+
34
+ - `client.user.getTeams()` — first stop when a payment or member operation needs
35
+ a `team_nanoid`
36
+ - `client.user.getOrganizations()` — first stop when company-scoped operations
37
+ need a `company_nanoid`
38
+ - `client.company.getUsers({ nanoid })` — get company users once company context
39
+ is known
40
+ - `client.teams.getUsers({ team_nanoid })` — get payees or members for a team
41
+
42
+ ## Teams workflows
43
+
44
+ ### Team CRUD
45
+
46
+ ```ts
47
+ const created = await client.teams.create(body)
48
+ const team = await client.teams.get({ team_nanoid })
49
+ await client.teams.update({ team_nanoid }, body)
50
+ await client.teams.delete({ team_nanoid })
51
+ ```
52
+
53
+ ### Team settings and members
54
+
55
+ ```ts
56
+ const settings = await client.teams.getSettings({ team_nanoid })
57
+ await client.teams.updateSettings({ team_nanoid }, body)
58
+
59
+ const users = await client.teams.getUsers({ team_nanoid })
60
+ const member = await client.teams.getMemberSummary({ team_nanoid, user_nanoid })
61
+ await client.teams.removeMember({ team_nanoid, user_nanoid })
62
+ ```
63
+
64
+ ## Invite workflows
65
+
66
+ ### Regular employee/contractor invites
67
+
68
+ No signing required:
69
+
70
+ ```ts
71
+ await client.invites.send({
72
+ nanoid: companyOrTeamNanoid,
73
+ invites: [{ email, role }],
74
+ })
75
+ ```
76
+
77
+ ### Manager invites
78
+
79
+ Automatic signing requires `riseIdAuth` or `privateKey`:
80
+
81
+ ```ts
82
+ await client.invites.sendManagerInvite({
83
+ nanoid,
84
+ emails,
85
+ role,
86
+ })
87
+ ```
88
+
89
+ Manual flow:
90
+
91
+ ```ts
92
+ const typed = await client.invites.getManagerInviteTypedData(data)
93
+ // sign typed.data.typed_data
94
+ await client.invites.executeManagerInviteWithSignedData(dataWithSignature)
95
+ ```
96
+
97
+ ## Payment workflows
98
+
99
+ ### Query payments
100
+
101
+ ```ts
102
+ const payments = await client.payments.get({
103
+ team_nanoid,
104
+ state: 'all',
105
+ query_type: 'payable',
106
+ start_date: new Date(start),
107
+ end_date: new Date(end),
108
+ })
109
+ ```
110
+
111
+ Use `team_nanoid` from `client.user.getTeams()` when the user does not already
112
+ have one.
113
+
114
+ ### Internal Rise payments
115
+
116
+ Automatic flow:
117
+
118
+ ```ts
119
+ await client.payments.sendPayment({
120
+ from: teamNanoid,
121
+ to: [{ to: userNanoid, amount_cents: 10000, currency_symbol: 'USD' }],
122
+ pay_now: true,
123
+ network: 'arbitrum',
124
+ })
125
+ ```
126
+
127
+ Manual flow — the execute step takes the payment group id from the signed data,
128
+ so the two calls must use **different** idempotency keys (a shared key collides
129
+ with the 3s request-dedup window). Omitting the key lets each call generate its
130
+ own:
131
+
132
+ ```ts
133
+ const typed = await client.payments.getPaymentTypedData(body)
134
+ // sign typed.data.typed_data
135
+ await client.payments.executePaymentWithSignedData(bodyWithSignature)
136
+ ```
137
+
138
+ `sendPayment()` does the same end to end.
139
+
140
+ Safe retries: keys are per call — never share one key between the prepare
141
+ (POST) and the execute (PUT). When retrying the *same* call, do the opposite:
142
+ pass an explicit `idempotencyKey`, reuse that exact key on the retry, and wait
143
+ about 3 seconds between attempts. The server dedups on the key and returns the
144
+ same transaction; a 409 "Duplicate request" within the window means the first
145
+ attempt is still processing. This applies to prepare and execute alike. Set
146
+ `external_id` on each payment as the durable guard — it is unique per payer
147
+ team, so a duplicate submission is rejected rather than paid twice.
148
+
149
+ ## Bill pay workflows
150
+
151
+ ### External recipients
152
+
153
+ ```ts
154
+ await client.billPay.createRecipient(
155
+ { team_nanoid },
156
+ { email: 'vendor@example.com' },
157
+ )
158
+ ```
159
+
160
+ ### Instant external payment
161
+
162
+ ```ts
163
+ await client.billPay.sendInstantPayment({
164
+ from: teamNanoid,
165
+ amount_cents: 10000,
166
+ currency_symbol: 'USD',
167
+ network: 'arbitrum',
168
+ external_recipient_email: 'vendor@example.com',
169
+ payment_data: {
170
+ role_description: 'Vendor',
171
+ invoice_description: 'March invoice',
172
+ rise_sow: false,
173
+ },
174
+ })
175
+ ```
176
+
177
+ ## Balance and withdrawal workflows
178
+
179
+ ### Balance lookup
180
+
181
+ ```ts
182
+ const balance = await client.entityBalance.get({ nanoid })
183
+ ```
184
+
185
+ ### Withdrawal
186
+
187
+ Withdrawals are not available via the SDK: the withdraw endpoints require reCAPTCHA verification headers only a browser session can produce. Point users to the Rise dashboard for withdrawals.
188
+
189
+ ## Webhooks
190
+
191
+ - **Default registration path**: the Rise UI / dashboard.
192
+ - **Programmatic management**: only use SDK webhook registration when the user
193
+ explicitly wants admin automation or backend-managed webhook configuration.
194
+ - **Receiving webhooks**: use `WebhookValidator` and `RiseWebhookEvent`.
195
+
196
+ ## Output quality bar
197
+
198
+ - Generate the discovery call when the required nanoid is missing.
199
+ - Use the exact SDK method names that exist in `riseworks-sdk`.
200
+ - Prefer complete SDK flows over pseudo-code.