@curviate/cli 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.
@@ -0,0 +1,445 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildPreviewOutput
4
+ } from "./chunk-R3VLWLVV.js";
5
+ import {
6
+ streamAll
7
+ } from "./chunk-SND3NHCT.js";
8
+ import {
9
+ createClient,
10
+ renderError,
11
+ renderSuccess,
12
+ renderUnexpectedError,
13
+ resolveEffectiveConfig
14
+ } from "./chunk-2NCPJJPC.js";
15
+ import {
16
+ GLOBAL_FLAGS
17
+ } from "./chunk-6JNCLLNY.js";
18
+
19
+ // src/commands/webhook.ts
20
+ import { defineCommand } from "citty";
21
+ import { readFileSync } from "fs";
22
+ function rejectPreviewOnRead(preview, out) {
23
+ if (preview) {
24
+ out.stderr.write("error: --preview is only valid on write commands (mutations). Reads just run.\n");
25
+ process.exit(2);
26
+ }
27
+ }
28
+ function rejectAllOnNonPaginated(all, out) {
29
+ if (all) {
30
+ out.stderr.write("error: --all is not supported on non-paginated commands.\n");
31
+ process.exit(2);
32
+ }
33
+ }
34
+ function buildOutputStreams() {
35
+ return {
36
+ stdout: { write: (s) => process.stdout.write(s) },
37
+ stderr: { write: (s) => process.stderr.write(s) }
38
+ };
39
+ }
40
+ function resolveOutputOpts(flags) {
41
+ return {
42
+ json: (flags.json ?? false) || !process.stdout.isTTY,
43
+ isTTY: process.stdout.isTTY ?? false,
44
+ fields: flags.fields
45
+ };
46
+ }
47
+ async function handleError(err, outOpts, out) {
48
+ const { CurviateError } = await import("@curviate/sdk");
49
+ if (err instanceof CurviateError) {
50
+ const { getExitCode } = await import("./exit-codes-NFIR57ZA.js");
51
+ renderError(err, outOpts, out);
52
+ process.exit(getExitCode(err.code));
53
+ }
54
+ renderUnexpectedError(err, out);
55
+ process.exit(1);
56
+ }
57
+ async function runWebhookCreate(client, flags, out) {
58
+ if (!flags.source) {
59
+ out.stderr.write("error: --source is required (messaging | user | account_status).\n");
60
+ process.exit(2);
61
+ }
62
+ if (!flags["request-url"]) {
63
+ out.stderr.write("error: --request-url is required (HTTPS URL for webhook deliveries).\n");
64
+ process.exit(2);
65
+ }
66
+ if (!flags["account-ids"]) {
67
+ out.stderr.write("error: --account-ids is required (comma-separated list of acc_\u2026 ids).\n");
68
+ process.exit(2);
69
+ }
70
+ const accountIds = flags["account-ids"].split(",").map((s) => s.trim()).filter(Boolean);
71
+ const body = {
72
+ source: flags.source,
73
+ request_url: flags["request-url"],
74
+ account_ids: accountIds
75
+ };
76
+ if (flags.name) body["name"] = flags.name;
77
+ if (flags.format) body["format"] = flags.format;
78
+ if (flags.enabled !== void 0) body["enabled"] = flags.enabled;
79
+ if (flags.events) {
80
+ body["events"] = flags.events.split(",").map((s) => s.trim()).filter(Boolean);
81
+ }
82
+ if (flags.data) {
83
+ body["data"] = flags.data.split(",").map((s) => s.trim()).filter(Boolean);
84
+ }
85
+ const outOpts = resolveOutputOpts(flags);
86
+ if (flags.preview) {
87
+ const preview = buildPreviewOutput({ method: "webhooks.create", args: {}, body });
88
+ out.stdout.write(JSON.stringify(preview) + "\n");
89
+ return;
90
+ }
91
+ try {
92
+ const result = await client.webhooks.create(body);
93
+ renderSuccess(result, outOpts, out);
94
+ } catch (err) {
95
+ await handleError(err, outOpts, out);
96
+ }
97
+ }
98
+ async function runWebhookList(client, flags, out) {
99
+ rejectPreviewOnRead(flags.preview, out);
100
+ const outOpts = resolveOutputOpts(flags);
101
+ const all = flags.all ?? false;
102
+ const maxPages = flags["max-pages"] ? parseInt(flags["max-pages"], 10) : 100;
103
+ const limit = flags.limit ? parseInt(flags.limit, 10) : void 0;
104
+ const cursor = flags.cursor;
105
+ const params = {};
106
+ if (limit !== void 0) params["limit"] = limit;
107
+ if (cursor) params["cursor"] = cursor;
108
+ try {
109
+ if (all) {
110
+ const fn = (p) => client.webhooks.list(p);
111
+ for await (const item of streamAll(fn, params, {
112
+ maxPages,
113
+ onTruncated: (msg) => out.stderr.write(msg + "\n")
114
+ })) {
115
+ out.stdout.write(JSON.stringify(item) + "\n");
116
+ }
117
+ } else {
118
+ const result = await client.webhooks.list(params);
119
+ renderSuccess(result, outOpts, out);
120
+ }
121
+ } catch (err) {
122
+ await handleError(err, outOpts, out);
123
+ }
124
+ }
125
+ async function runWebhookEvents(client, flags, out) {
126
+ rejectPreviewOnRead(flags.preview, out);
127
+ rejectAllOnNonPaginated(flags.all, out);
128
+ const outOpts = resolveOutputOpts(flags);
129
+ try {
130
+ const result = await client.webhooks.listEvents();
131
+ renderSuccess(result, outOpts, out);
132
+ } catch (err) {
133
+ await handleError(err, outOpts, out);
134
+ }
135
+ }
136
+ async function runWebhookUpdate(client, flags, out) {
137
+ if (flags.source !== void 0) {
138
+ out.stderr.write("error: --source cannot be changed after creation (source is immutable).\n");
139
+ process.exit(2);
140
+ }
141
+ const id = flags.id ?? "";
142
+ const body = {};
143
+ if (flags.name !== void 0) body["name"] = flags.name;
144
+ if (flags["request-url"]) body["request_url"] = flags["request-url"];
145
+ if (flags.enabled !== void 0) body["enabled"] = flags.enabled;
146
+ if (flags.format) body["format"] = flags.format;
147
+ if (flags.events) {
148
+ body["events"] = flags.events.split(",").map((s) => s.trim()).filter(Boolean);
149
+ }
150
+ if (flags.data) {
151
+ body["data"] = flags.data.split(",").map((s) => s.trim()).filter(Boolean);
152
+ }
153
+ if (flags["account-ids"]) {
154
+ body["account_ids"] = flags["account-ids"].split(",").map((s) => s.trim()).filter(Boolean);
155
+ }
156
+ const outOpts = resolveOutputOpts(flags);
157
+ if (flags.preview) {
158
+ const preview = buildPreviewOutput({
159
+ method: "webhooks.update",
160
+ args: { id },
161
+ body
162
+ });
163
+ out.stdout.write(JSON.stringify(preview) + "\n");
164
+ return;
165
+ }
166
+ try {
167
+ const result = await client.webhooks.update(id, body);
168
+ renderSuccess(result, outOpts, out);
169
+ } catch (err) {
170
+ await handleError(err, outOpts, out);
171
+ }
172
+ }
173
+ async function runWebhookDelete(client, flags, out) {
174
+ const id = flags.id ?? "";
175
+ const outOpts = resolveOutputOpts(flags);
176
+ if (flags.preview) {
177
+ const preview = buildPreviewOutput({
178
+ method: "webhooks.delete",
179
+ args: { id },
180
+ body: {}
181
+ });
182
+ out.stdout.write(JSON.stringify(preview) + "\n");
183
+ return;
184
+ }
185
+ try {
186
+ const result = await client.webhooks.delete(id);
187
+ renderSuccess(result, outOpts, out);
188
+ } catch (err) {
189
+ await handleError(err, outOpts, out);
190
+ }
191
+ }
192
+ async function runWebhookStateDiff(client, flags, out) {
193
+ rejectPreviewOnRead(flags.preview, out);
194
+ const accountId = flags["account-id"] ?? "";
195
+ const outOpts = resolveOutputOpts(flags);
196
+ const query = {};
197
+ if (flags.cursor) query["cursor"] = flags.cursor;
198
+ try {
199
+ const result = await client.webhooks.getStateDiff(accountId, query);
200
+ renderSuccess(result, outOpts, out);
201
+ } catch (err) {
202
+ await handleError(err, outOpts, out);
203
+ }
204
+ }
205
+ async function runWebhookVerify(input, out) {
206
+ const { constructEvent, WebhookSignatureError } = await import("@curviate/sdk");
207
+ try {
208
+ const event = await constructEvent(
209
+ input.rawBody,
210
+ input.signatureHeader,
211
+ input.secret,
212
+ ...input.replayWindowSecs !== void 0 ? [{ replayWindowSecs: input.replayWindowSecs }] : []
213
+ );
214
+ out.stdout.write(JSON.stringify(event) + "\n");
215
+ } catch (err) {
216
+ if (err instanceof WebhookSignatureError) {
217
+ const envelope = {
218
+ error: {
219
+ name: "WebhookSignatureError",
220
+ reason: err.reason,
221
+ message: err.message
222
+ }
223
+ };
224
+ out.stdout.write(JSON.stringify(envelope) + "\n");
225
+ out.stderr.write(`error: webhook verification failed \u2014 ${err.reason}: ${err.message}
226
+ `);
227
+ process.exit(2);
228
+ }
229
+ out.stderr.write(`error: unexpected error during webhook verification: ${String(err)}
230
+ `);
231
+ process.exit(1);
232
+ }
233
+ }
234
+ var webhookCreateCommand = defineCommand({
235
+ meta: { name: "create", description: "Register a new webhook endpoint." },
236
+ args: {
237
+ ...GLOBAL_FLAGS,
238
+ source: { type: "string", description: "Event source: messaging | user | account_status.", required: true },
239
+ "request-url": { type: "string", description: "HTTPS URL to receive webhook deliveries.", required: true },
240
+ "account-ids": { type: "string", description: "Comma-separated account ids to target (required).", required: true },
241
+ name: { type: "string", description: "Human-readable name (1\u2013100 chars)." },
242
+ format: { type: "string", description: "Delivery encoding: json | form (default: json)." },
243
+ enabled: { type: "boolean", description: "Create as enabled (default: true).", default: true },
244
+ events: { type: "string", description: "Comma-separated event names to subscribe to." },
245
+ data: { type: "string", description: "Comma-separated field-remapping keys." }
246
+ },
247
+ async run({ args }) {
248
+ const flags = args;
249
+ const cfg = await resolveEffectiveConfig({
250
+ apiKey: flags["api-key"],
251
+ baseUrl: flags["base-url"],
252
+ timeout: flags.timeout,
253
+ profile: flags.profile
254
+ });
255
+ if (!cfg.apiKey) {
256
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
257
+ process.exit(3);
258
+ }
259
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
260
+ const out = buildOutputStreams();
261
+ await runWebhookCreate(client, flags, out);
262
+ }
263
+ });
264
+ var webhookListCommand = defineCommand({
265
+ meta: { name: "list", description: "List registered webhooks." },
266
+ args: { ...GLOBAL_FLAGS },
267
+ async run({ args }) {
268
+ const flags = args;
269
+ const cfg = await resolveEffectiveConfig({
270
+ apiKey: flags["api-key"],
271
+ baseUrl: flags["base-url"],
272
+ timeout: flags.timeout,
273
+ profile: flags.profile
274
+ });
275
+ if (!cfg.apiKey) {
276
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
277
+ process.exit(3);
278
+ }
279
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
280
+ const out = buildOutputStreams();
281
+ await runWebhookList(client, flags, out);
282
+ }
283
+ });
284
+ var webhookEventsCommand = defineCommand({
285
+ meta: { name: "events", description: "List the canonical webhook event catalogue." },
286
+ args: { ...GLOBAL_FLAGS },
287
+ async run({ args }) {
288
+ const flags = args;
289
+ const cfg = await resolveEffectiveConfig({
290
+ apiKey: flags["api-key"],
291
+ baseUrl: flags["base-url"],
292
+ timeout: flags.timeout,
293
+ profile: flags.profile
294
+ });
295
+ if (!cfg.apiKey) {
296
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
297
+ process.exit(3);
298
+ }
299
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
300
+ const out = buildOutputStreams();
301
+ await runWebhookEvents(client, flags, out);
302
+ }
303
+ });
304
+ var webhookUpdateCommand = defineCommand({
305
+ meta: { name: "update", description: "Update a webhook in place (source is immutable)." },
306
+ args: {
307
+ ...GLOBAL_FLAGS,
308
+ id: { type: "positional", description: "Webhook id (wh_\u2026)." },
309
+ "request-url": { type: "string", description: "Replace the delivery URL." },
310
+ name: { type: "string", description: "Replace the name (or clear with empty string)." },
311
+ enabled: { type: "boolean", description: "Enable or disable the webhook." },
312
+ format: { type: "string", description: "Replace the delivery encoding: json | form." },
313
+ events: { type: "string", description: "Replace subscribed events (comma-separated)." },
314
+ data: { type: "string", description: "Replace field-remapping keys (comma-separated)." },
315
+ "account-ids": { type: "string", description: "Replace targeted accounts (comma-separated)." }
316
+ },
317
+ async run({ args }) {
318
+ const flags = args;
319
+ const cfg = await resolveEffectiveConfig({
320
+ apiKey: flags["api-key"],
321
+ baseUrl: flags["base-url"],
322
+ timeout: flags.timeout,
323
+ profile: flags.profile
324
+ });
325
+ if (!cfg.apiKey) {
326
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
327
+ process.exit(3);
328
+ }
329
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
330
+ const out = buildOutputStreams();
331
+ await runWebhookUpdate(client, flags, out);
332
+ }
333
+ });
334
+ var webhookDeleteCommand = defineCommand({
335
+ meta: { name: "delete", description: "Permanently remove a webhook subscription." },
336
+ args: {
337
+ ...GLOBAL_FLAGS,
338
+ id: { type: "positional", description: "Webhook id (wh_\u2026)." }
339
+ },
340
+ async run({ args }) {
341
+ const flags = args;
342
+ const cfg = await resolveEffectiveConfig({
343
+ apiKey: flags["api-key"],
344
+ baseUrl: flags["base-url"],
345
+ timeout: flags.timeout,
346
+ profile: flags.profile
347
+ });
348
+ if (!cfg.apiKey) {
349
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
350
+ process.exit(3);
351
+ }
352
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
353
+ const out = buildOutputStreams();
354
+ await runWebhookDelete(client, flags, out);
355
+ }
356
+ });
357
+ var webhookStateDiffCommand = defineCommand({
358
+ meta: { name: "state-diff", description: "Get the set of changes for an account since the last known version." },
359
+ args: {
360
+ ...GLOBAL_FLAGS,
361
+ "account-id": { type: "positional", description: "Account id (acc_\u2026)." }
362
+ },
363
+ async run({ args }) {
364
+ const flags = args;
365
+ const cfg = await resolveEffectiveConfig({
366
+ apiKey: flags["api-key"],
367
+ baseUrl: flags["base-url"],
368
+ timeout: flags.timeout,
369
+ profile: flags.profile
370
+ });
371
+ if (!cfg.apiKey) {
372
+ process.stderr.write("error: no API key \u2014 run `curviate login` or pass --api-key.\n");
373
+ process.exit(3);
374
+ }
375
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, timeout: cfg.timeout });
376
+ const out = buildOutputStreams();
377
+ await runWebhookStateDiff(client, flags, out);
378
+ }
379
+ });
380
+ var webhookVerifyCommand = defineCommand({
381
+ meta: { name: "verify", description: "Verify a webhook signature offline (no network call)." },
382
+ args: {
383
+ ...GLOBAL_FLAGS,
384
+ secret: {
385
+ type: "string",
386
+ description: "The webhook signing secret from your webhook registration.",
387
+ required: true
388
+ },
389
+ header: {
390
+ type: "string",
391
+ description: "The full X-Curviate-Signature header value (t=\u2026,v1=\u2026). Reads from stdin if omitted."
392
+ },
393
+ body: {
394
+ type: "string",
395
+ description: "Path to a file containing the raw webhook body, or - for stdin."
396
+ },
397
+ "max-age-secs": {
398
+ type: "string",
399
+ description: "Maximum event age in seconds before rejecting as replay (default: 300)."
400
+ }
401
+ },
402
+ async run({ args }) {
403
+ const flags = args;
404
+ const out = buildOutputStreams();
405
+ let rawBody = "";
406
+ if (flags.body) {
407
+ if (flags.body === "-") {
408
+ rawBody = readFileSync("/dev/stdin", "utf8");
409
+ } else {
410
+ rawBody = readFileSync(flags.body, "utf8");
411
+ }
412
+ }
413
+ const signatureHeader = flags.header ?? "";
414
+ const secret = flags.secret ?? "";
415
+ const replayWindowSecs = flags["max-age-secs"] ? parseInt(flags["max-age-secs"], 10) : void 0;
416
+ await runWebhookVerify({ secret, signatureHeader, rawBody, replayWindowSecs }, out);
417
+ }
418
+ });
419
+ var webhookCommand = defineCommand({
420
+ meta: { name: "webhook", description: "Webhook management and signature verification." },
421
+ subCommands: {
422
+ create: webhookCreateCommand,
423
+ list: webhookListCommand,
424
+ events: webhookEventsCommand,
425
+ update: webhookUpdateCommand,
426
+ delete: webhookDeleteCommand,
427
+ "state-diff": webhookStateDiffCommand,
428
+ verify: webhookVerifyCommand
429
+ },
430
+ async run() {
431
+ process.stderr.write(
432
+ "Usage: curviate webhook <subcommand>\n create | list | events | update | delete | state-diff | verify\n"
433
+ );
434
+ }
435
+ });
436
+ export {
437
+ runWebhookCreate,
438
+ runWebhookDelete,
439
+ runWebhookEvents,
440
+ runWebhookList,
441
+ runWebhookStateDiff,
442
+ runWebhookUpdate,
443
+ runWebhookVerify,
444
+ webhookCommand
445
+ };
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@curviate/cli",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Official command-line interface for the Curviate API.",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "homepage": "https://docs.curviate.com",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/curviate/cli.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/curviate/cli/issues"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "bin": {
20
+ "curviate": "./dist/cli.js"
21
+ },
22
+ "files": [
23
+ "dist/",
24
+ "LICENSE",
25
+ "README.md",
26
+ "CHANGELOG.md"
27
+ ],
28
+ "engines": {
29
+ "node": ">=18"
30
+ },
31
+ "scripts": {
32
+ "build": "tsup",
33
+ "prepack": "node scripts/check-clean.mjs && tsup",
34
+ "test": "vitest run",
35
+ "test:watch": "vitest",
36
+ "typecheck": "tsc --noEmit",
37
+ "lint": "eslint src test",
38
+ "check:clean": "node scripts/check-clean.mjs",
39
+ "verify:dist": "pnpm build && node scripts/verify-dist.mjs",
40
+ "clean": "rm -rf dist *.tsbuildinfo"
41
+ },
42
+ "dependencies": {
43
+ "@curviate/sdk": "^0.1.1",
44
+ "citty": "^0.1.6"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^22.15.21",
48
+ "eslint": "^9.0.0",
49
+ "typescript-eslint": "^8.0.0",
50
+ "tsup": "^8.3.5",
51
+ "typescript": "^5.7.2",
52
+ "vitest": "^3.2.2"
53
+ }
54
+ }