@planescrm/sdk 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/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # @planes/sdk
2
+
3
+ TypeScript SDK for the [Planes CRM](https://planescrm.com) platform — a typed API
4
+ client, an ergonomic webhook router, OAuth helpers, and an action-handler server
5
+ for building agents.
6
+
7
+ > **Not yet published.** `@planes/sdk` is currently a private workspace package —
8
+ > it is **not** on npm. Consume it from within this monorepo as a workspace
9
+ > dependency (`"@planes/sdk": "workspace:*"`), or request access from the Planes
10
+ > team. The `npm install @planes/sdk` examples below describe the intended
11
+ > published surface once publishing is enabled.
12
+
13
+ ## Client
14
+
15
+ `PlanesClient` is a typed client over the `/api/v1` REST surface. It accepts
16
+ exactly **one** authentication mode.
17
+
18
+ ```ts
19
+ import { PlanesClient } from '@planes/sdk';
20
+
21
+ // 1. A pre-obtained OAuth access token (not auto-refreshed).
22
+ const client = new PlanesClient({ accessToken: 'tok_...' });
23
+
24
+ // 2. A pk_live_ API key.
25
+ const client = new PlanesClient({ apiKey: 'pk_live_...' });
26
+
27
+ // 3. OAuth-managed — the client obtains and refreshes access tokens itself,
28
+ // persisting them through a TokenStorage (in-memory by default).
29
+ const client = new PlanesClient({
30
+ clientId: process.env.PLANES_CLIENT_ID!,
31
+ clientSecret: process.env.PLANES_CLIENT_SECRET!,
32
+ });
33
+
34
+ const { data, total } = await client.opportunities.list({ page: 1, limit: 20 });
35
+ const opp = await client.opportunities.get('opp_123');
36
+ ```
37
+
38
+ Resources: `opportunities`, `customers`, `properties`, `calendarEvents`,
39
+ `contracts`, `jobs`, `estimates`, `invoices`, `inventory`.
40
+
41
+ ### Errors
42
+
43
+ Every failure throws a subclass of `PlanesError`, each carrying `status` and
44
+ `code`:
45
+
46
+ ```ts
47
+ import { PlanesError, RateLimitError, ValidationError } from '@planes/sdk';
48
+
49
+ try {
50
+ await client.opportunities.get('missing');
51
+ } catch (err) {
52
+ if (err instanceof RateLimitError) {
53
+ await sleep(err.retryAfter * 1000); // seconds
54
+ } else if (err instanceof ValidationError) {
55
+ console.error(err.fieldErrors); // [{ field, message }]
56
+ } else if (err instanceof PlanesError) {
57
+ console.error(err.status, err.code, err.message);
58
+ }
59
+ }
60
+ ```
61
+
62
+ ## Webhooks
63
+
64
+ `client.webhooks` is an ergonomic router over the signed webhook envelope. Handlers
65
+ subscribe to a `"{entity_type}.{event_type}"` key, an entity wildcard
66
+ `"{entity_type}.*"`, or the global wildcard `"*"`. It holds no auth — the signing
67
+ secret is supplied per `process()` call — so wire it into your own HTTP handler.
68
+
69
+ ```ts
70
+ client.webhooks
71
+ .on('opportunity.created', (d) => console.log(d.event.snapshot))
72
+ .on('opportunity.*', (d) => audit(d))
73
+ .on('*', (d) => metrics.count(d.event.entity_type));
74
+
75
+ // inside your POST route (Express-style):
76
+ app.post('/webhooks/planes', async (req, res) => {
77
+ try {
78
+ await client.webhooks.process({
79
+ body: req.rawBody, // the verbatim request body string
80
+ signature: req.header('X-Planes-Signature')!,
81
+ secret: process.env.PLANES_WEBHOOK_SECRET!,
82
+ });
83
+ res.sendStatus(204);
84
+ } catch {
85
+ res.sendStatus(400); // signature verification failed
86
+ }
87
+ });
88
+ ```
89
+
90
+ ## OAuth (authorization-code flow)
91
+
92
+ Use the OAuth helpers to drive the login redirect and exchange the returned code
93
+ for tokens.
94
+
95
+ ```ts
96
+ import { buildAuthorizeUrl, exchangeCode } from '@planes/sdk';
97
+
98
+ const baseUrl = 'https://api.planescrm.com/api/v1';
99
+
100
+ // 1. Redirect the user to the authorization URL.
101
+ const authorizeUrl = buildAuthorizeUrl({
102
+ baseUrl,
103
+ clientId: process.env.PLANES_CLIENT_ID!,
104
+ redirectUri: 'https://your.app/oauth/callback',
105
+ scopes: ['opportunities:read', 'opportunities:write'],
106
+ });
107
+
108
+ // 2. In your callback handler, exchange the code for tokens.
109
+ const tokens = await exchangeCode({
110
+ baseUrl,
111
+ clientId: process.env.PLANES_CLIENT_ID!,
112
+ clientSecret: process.env.PLANES_CLIENT_SECRET!,
113
+ code: req.query.code as string,
114
+ redirectUri: 'https://your.app/oauth/callback',
115
+ });
116
+
117
+ // 3. Seed an OAuth-managed client so refreshes happen automatically.
118
+ import { PlanesClient, MemoryTokenStorage } from '@planes/sdk';
119
+
120
+ const client = new PlanesClient({
121
+ clientId: process.env.PLANES_CLIENT_ID!,
122
+ clientSecret: process.env.PLANES_CLIENT_SECRET!,
123
+ tokenStorage: new MemoryTokenStorage({
124
+ accessToken: tokens.access_token,
125
+ refreshToken: tokens.refresh_token,
126
+ expiresAt: Date.now() + tokens.expires_in * 1000,
127
+ }),
128
+ });
129
+ ```
130
+
131
+ ## Agents (action handlers)
132
+
133
+ Build an agent HTTP server that receives platform action requests. `createAgentApp()`
134
+ gives a chainable builder; call `registerAction(type, handler)` per action and
135
+ `listen()` to start.
136
+
137
+ ```ts
138
+ import { createAgentApp } from '@planes/sdk';
139
+
140
+ createAgentApp()
141
+ .registerAction('enrich-opportunity', async (ctx) => {
142
+ // ctx is the flattened ActionContext: action_type, entity_type,
143
+ // entity_id, tenant_id, data, stage, metadata, callback_token, ...
144
+ const enriched = await enrich(ctx.entity_id);
145
+ return { status: 'completed', output: { enriched } };
146
+ })
147
+ .registerAction('send-welcome-email', async (ctx) => {
148
+ await sendEmail(ctx.data);
149
+ return { status: 'completed', output: { sent: true } };
150
+ })
151
+ .listen({ port: 3000, path: '/actions' });
152
+ ```
153
+
154
+ The handler's returned `ActionResult` is serialized straight back as the HTTP
155
+ response — which is exactly how the platform's external-agent handler consumes it.