@formo/cli 0.2.0 → 1.0.2

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 CHANGED
@@ -26,6 +26,13 @@ export FORMO_API_KEY=formo_abc123
26
26
 
27
27
  Get your API key from `Settings → API Keys` in the [Formo dashboard](https://app.formo.so).
28
28
 
29
+ For local development or proxying, override API hosts with:
30
+
31
+ ```bash
32
+ export FORMO_API_BASE_URL=http://localhost:3001
33
+ export FORMO_EVENTS_BASE_URL=http://localhost:3002
34
+ ```
35
+
29
36
  ---
30
37
 
31
38
  ## Auth commands
@@ -82,18 +89,19 @@ Search wallet profiles with filters, sorting, and pagination. Returns a `Paginat
82
89
  | `--address` | Filter by wallet address |
83
90
  | `--page` | Page number (1-indexed, default `1`) |
84
91
  | `--size` | Page size (default `100`, max `1000`) |
85
- | `--orderBy` | `last_onchain`, `first_onchain`, `net_worth_usd`, `updated_at`, `tx_count`, `first_seen`, `last_seen`, `num_sessions`, `revenue`, `volume`, `points` |
86
- | `--orderDir` | `asc` or `desc` |
92
+ | `--order-by` | `last_onchain`, `first_onchain`, `net_worth_usd`, `updated_at`, `tx_count`, `first_seen`, `last_seen`, `num_sessions`, `revenue`, `volume`, `points` |
93
+ | `--order-dir` | `asc` or `desc` |
87
94
  | `--expand` | Comma-separated fields to expand |
88
95
  | `--conditions` | JSON array of `FilterCondition` objects (see below) |
89
96
  | `--logic` | Combine conditions with `and` (default) or `or` |
90
97
 
91
98
  ```bash
92
99
  formo profiles search --size 10
93
- formo profiles search --orderBy net_worth_usd --orderDir desc --size 5
100
+ formo profiles search --order-by net_worth_usd --order-dir desc --size 5
94
101
  formo profiles search --page 2 --size 20
95
- formo profiles search --conditions '[{"field":"net_worth_usd","op":"gt","value":10000}]' --size 20
96
- formo profiles search --conditions '[{"field":"net_worth_usd","op":"gt","value":10000},{"field":"tx_count","op":"gt","value":50}]' --logic or --size 20
102
+ formo profiles search --conditions '[{"field":"users.net_worth_usd","op":"gt","value":10000}]' --size 20
103
+ formo profiles search --conditions '[{"field":"users.net_worth_usd","op":"gt","value":10000},{"field":"users.volume","op":"gt","value":1000}]' --logic or --size 20
104
+ formo profiles search --conditions '[{"field":"chains.1.balance","op":"gt","value":1000}]' --size 20
97
105
  ```
98
106
 
99
107
  ### `profiles update <address>`
@@ -113,21 +121,34 @@ formo profiles update vitalik.eth --properties '{"email":"alice@example.com"}'
113
121
 
114
122
  > Requires `profiles:write` scope.
115
123
 
124
+ ### `profiles properties batch`
125
+
126
+ Batch update first-party properties for up to 100 wallets.
127
+
128
+ ```bash
129
+ formo profiles properties batch \
130
+ --rows '[{"address":"0xd8dA...","display_name":"alice.eth","email":"alice@example.com"}]'
131
+ ```
132
+
116
133
  ### `profiles labels create <address>`
117
134
 
118
- Upsert one or more labels on a wallet profile. Provide either a single label via `--tagId` or a batch via `--labels`.
135
+ Upsert one or more labels on a wallet profile. Provide either a single label via `--tag-id` or a batch via `--labels`.
119
136
 
120
137
  | Option | Description |
121
138
  |---|---|
122
- | `--tagId` | Label identifier (e.g. `vip`, `airdrop_eligible`) |
139
+ | `--tag-id` | Label identifier (e.g. `vip`, `airdrop_eligible`) |
123
140
  | `--value` | Optional label value (e.g. tier name, country code) |
124
- | `--chainId` | Optional chain identifier the label applies to |
141
+ | `--chain-id` | Optional chain identifier the label applies to |
142
+ | `--timestamp` | Optional historical ISO-8601 timestamp |
143
+ | `--is-deleted` | Backfill a historical label removal tombstone |
125
144
  | `--labels` | JSON array of `UserLabelInput` objects for batch upsert |
126
145
 
127
146
  ```bash
128
- formo profiles labels create 0xd8dA... --tagId vip
129
- formo profiles labels create 0xd8dA... --tagId tier --value gold --chainId 1
147
+ formo profiles labels create 0xd8dA... --tag-id vip
148
+ formo profiles labels create 0xd8dA... --tag-id tier --value gold --chain-id 1
130
149
  formo profiles labels create 0xd8dA... --labels '[{"tag_id":"vip"},{"tag_id":"airdrop_eligible","chain_id":"1"}]'
150
+ formo profiles labels create 0xd8dA... --tag-id tier --timestamp 2024-03-15T00:00:00.000Z --is-deleted
151
+ formo profiles labels batch --labels '[{"address":"0xd8dA...","tag_id":"vip","value":"tier-1"}]'
131
152
  ```
132
153
 
133
154
  ### `profiles labels delete <address>`
@@ -136,12 +157,12 @@ Delete a label from a wallet profile.
136
157
 
137
158
  | Option | Description |
138
159
  |---|---|
139
- | `--tagId` | Label identifier to delete (required) |
140
- | `--chainId` | Optional chain identifier to scope the deletion |
160
+ | `--tag-id` | Label identifier to delete (required) |
161
+ | `--chain-id` | Optional chain identifier to scope the deletion |
141
162
 
142
163
  ```bash
143
- formo profiles labels delete 0xd8dA... --tagId vip
144
- formo profiles labels delete 0xd8dA... --tagId tier --chainId 1
164
+ formo profiles labels delete 0xd8dA... --tag-id vip
165
+ formo profiles labels delete 0xd8dA... --tag-id tier --chain-id 1
145
166
  ```
146
167
 
147
168
  > Requires `profiles:write` scope.
@@ -163,14 +184,14 @@ Get a single alert by ID.
163
184
  | Option | Description |
164
185
  |---|---|
165
186
  | `--name` | Alert name |
166
- | `--triggerType` | Trigger type (e.g. `event`, `threshold`) |
167
- | `--triggerFilters` | JSON array of trigger filter objects |
187
+ | `--trigger-type` | Trigger type: `event` or `user` |
188
+ | `--trigger-filters` | JSON array of trigger filter objects |
168
189
  | `--recipient` | JSON array of recipient objects |
169
190
  | `--secret` | Webhook secret |
170
191
 
171
192
  ```bash
172
- formo alerts create --name "High value tx" --triggerType event \
173
- --triggerFilters '[{"name":"event","operator":"equals","value":"transaction"}]' \
193
+ formo alerts create --name "High value tx" --trigger-type event \
194
+ --trigger-filters '[{"name":"event","operator":"equals","value":"transaction"}]' \
174
195
  --recipient '[{"type":"email","value":["alerts@myapp.com"]}]'
175
196
  ```
176
197
 
@@ -180,11 +201,18 @@ Same options as `create`. Replaces the alert configuration.
180
201
  ### `alerts delete <alertId>`
181
202
  Delete an alert.
182
203
 
183
- ### `alerts toggle <alertId> --status <active|paused>`
184
- Toggle an alert between `active` and `paused`.
204
+ ### `alerts toggle <alertId> --status <active|inactive>`
205
+ Toggle an alert between `active` and `inactive`.
185
206
 
186
207
  ```bash
187
- formo alerts toggle alert_abc123 --status paused
208
+ formo alerts toggle alert_abc123 --status inactive
209
+ ```
210
+
211
+ ### `alerts test <alertId>`
212
+ Send a test alert delivery with optional sample payloads.
213
+
214
+ ```bash
215
+ formo alerts test alert_abc123 --sample-event '{"event":"transaction","revenue":250}'
188
216
  ```
189
217
 
190
218
  ---
@@ -203,19 +231,21 @@ Get a single board by ID.
203
231
 
204
232
  | Option | Description |
205
233
  |---|---|
206
- | `--name` | Board name |
234
+ | `--title` | Board title |
207
235
  | `--description` | Optional board description |
236
+ | `--is-public` | Make the board publicly viewable |
208
237
 
209
238
  ```bash
210
- formo boards create --name "Revenue Metrics" --description "Weekly revenue tracking"
239
+ formo boards create --title "Revenue Metrics" --description "Weekly revenue tracking"
211
240
  ```
212
241
 
213
242
  ### `boards update <boardId>`
214
243
 
215
244
  | Option | Description |
216
245
  |---|---|
217
- | `--name` | New board name |
246
+ | `--title` | New board title |
218
247
  | `--description` | New board description |
248
+ | `--is-public` | Update public visibility |
219
249
 
220
250
  ### `boards delete <boardId>`
221
251
  Delete a board.
@@ -226,19 +256,37 @@ Delete a board.
226
256
 
227
257
  Chart commands. Charts live inside a board. Requires `charts:read` / `charts:write`.
228
258
 
229
- ### `charts list --boardId <boardId>`
259
+ ### `charts list --board-id <boardId>`
230
260
  List all charts in a board.
231
261
 
232
- ### `charts get <chartId> --boardId <boardId>`
262
+ ### `charts get <chartId> --board-id <boardId>`
233
263
  Get a single chart by ID.
234
264
 
235
- ### `charts create --boardId <boardId> --body '<json>'`
236
- Create a chart from a JSON config string.
265
+ ### `charts meta --board-id <boardId>`
266
+ List lightweight chart metadata without executing chart queries.
267
+
268
+ ### `charts create --board-id <boardId> [options]`
269
+ Create a chart from typed flags or a raw JSON body.
270
+
271
+ ```bash
272
+ formo charts create --board-id brd_123 --title "Daily Active Users" \
273
+ --chart-type line \
274
+ --query "SELECT toDate(timestamp) AS date, countDistinct(address) AS users FROM events GROUP BY date ORDER BY date" \
275
+ --x-axis date --y-axis users
276
+
277
+ formo charts create --board-id brd_123 --body '{"title":"Recent Events","chart_type":"table","query":"SELECT * FROM events LIMIT 10"}'
278
+ ```
237
279
 
238
- ### `charts update <chartId> --boardId <boardId> --body '<json>'`
280
+ ### `charts update <chartId> --board-id <boardId> --body '<json>'`
239
281
  Update a chart.
240
282
 
241
- ### `charts delete <chartId> --boardId <boardId>`
283
+ ### `charts query <chartId> --board-id <boardId> --date-from <YYYY-MM-DD> --date-to <YYYY-MM-DD>`
284
+ Execute a saved chart that uses `{{date_from}}` / `{{date_to}}` variables.
285
+
286
+ ### `charts move|duplicate|reorder`
287
+ Move a chart to another board, duplicate a chart, or reorder charts in a board.
288
+
289
+ ### `charts delete <chartId> --board-id <boardId>`
242
290
  Delete a chart.
243
291
 
244
292
  ---
@@ -250,6 +298,12 @@ Smart contract commands. Requires `contracts:read` / `contracts:write`.
250
298
  ### `contracts list`
251
299
  List all tracked contracts. Returns `{ data: Contract[], deploy: { last_deployed_at, diff }, total, page, size, has_more }`.
252
300
 
301
+ ### `contracts get <chain> <address>`
302
+ Get a single tracked contract.
303
+
304
+ ### `contracts recommendations`
305
+ List contracts the project already interacts with but has not added yet.
306
+
253
307
  ### `contracts create`
254
308
 
255
309
  | Option | Description |
@@ -257,13 +311,15 @@ List all tracked contracts. Returns `{ data: Contract[], deploy: { last_deployed
257
311
  | `--address` | Contract address (`0x…`) |
258
312
  | `--chain` | Chain ID (e.g. `1`, `137`) |
259
313
  | `--name` | Human-readable contract name |
260
- | `--abi` | Contract ABI as a JSON string |
261
- | `--events` | Events configuration as a JSON string |
314
+ | `--abi` | Contract ABI as a JSON string; sent stringified to the API |
315
+ | `--events` | JSON array of ABI event objects to monitor |
316
+ | `--start-block` | Optional start block |
317
+ | `--include-in-pipeline` | Include this contract in the Goldsky events pipeline (`true` by default in the API) |
262
318
 
263
319
  ```bash
264
320
  formo contracts create --address 0x1f9840a85d5af5bf1d1762f925bdaddc4201f984 --chain 1 \
265
321
  --name "UNI Token" --abi '[{"type":"event","name":"Transfer","inputs":[]}]' \
266
- --events '{"Transfer":true}'
322
+ --events '[{"type":"event","name":"Transfer","inputs":[]}]'
267
323
  ```
268
324
 
269
325
  ### `contracts update <chain> <address>`
@@ -272,7 +328,12 @@ formo contracts create --address 0x1f9840a85d5af5bf1d1762f925bdaddc4201f984 --ch
272
328
  |---|---|
273
329
  | `--name` | Updated contract name |
274
330
  | `--abi` | Updated ABI |
275
- | `--events` | Updated events config |
331
+ | `--events` | Updated JSON array of ABI event objects |
332
+ | `--start-block` | Optional start block |
333
+ | `--include-in-pipeline` | Include or exclude this contract from the Goldsky events pipeline |
334
+
335
+ ### `contracts pipeline <chain> <address> --include-in-pipeline <true|false>`
336
+ Toggle pipeline inclusion without re-sending the full ABI/events payload.
276
337
 
277
338
  ### `contracts delete <chain> <address>`
278
339
  Remove a tracked contract.
@@ -291,7 +352,7 @@ List all user segments.
291
352
  | Option | Description |
292
353
  |---|---|
293
354
  | `--title` | Segment title |
294
- | `--filterSets` | JSON array of filter set strings defining the segment |
355
+ | `--filter-sets` | JSON array of filter set strings defining the segment |
295
356
 
296
357
  ### `segments delete <segmentId>`
297
358
  Delete a user segment.
@@ -313,6 +374,31 @@ formo query run "SELECT address, net_worth_usd FROM wallet_profiles ORDER BY net
313
374
 
314
375
  ---
315
376
 
377
+ ## `formo analytics`
378
+
379
+ Pre-built analytics pipes — the same data that powers the Formo dashboard — without writing SQL. Each pipe is a subcommand: `formo analytics <pipe>`.
380
+
381
+ **Pipes:** `kpis`, `event_timeseries`, `funnel`, `flow`, `frequency`, `lifecycle`, `retention`, `revenue_overview`, `revenue_by_metric`, `revenue_timeseries`, `volume_by_metric`, `top_chains`, `top_events`, `top_locations`, `top_pages`, `top_sources`, `top_wallets`
382
+
383
+ | Option | Description |
384
+ |---|---|
385
+ | `--date-from` | Inclusive start date `YYYY-MM-DD` (default: 7 days before `--date-to`) |
386
+ | `--date-to` | Inclusive end date `YYYY-MM-DD` (default: today) |
387
+ | `--filters` | JSON array of `[{field,op,value}]`. Use `in`/`notIn` with a pipe-delimited value (e.g. `"chrome\|firefox"`) |
388
+ | `--params` | JSON object of pipe-specific params merged into the query (e.g. `{"limit":10,"group_by":"device"}`) |
389
+
390
+ ```bash
391
+ formo analytics kpis
392
+ formo analytics kpis --date-from 2026-04-01 --date-to 2026-04-30 --params '{"group_by":"device"}'
393
+ formo analytics funnel --date-from 2026-04-01 --date-to 2026-04-30 --params '{"steps":[{"type":"event","event":"page","name":"page::0","filters":[]},{"type":"track","event":"connect","name":"connect::1","filters":[]}],"window_seconds":86400}'
394
+ formo analytics top_wallets --date-from 2026-04-01 --date-to 2026-04-30 --params '{"limit":10}'
395
+ formo analytics retention --filters '[{"field":"location","op":"equals","value":"US"}]'
396
+ ```
397
+
398
+ > Requires `query:read` scope. Run `formo analytics <pipe> --help` for the pipe-specific params accepted via `--params`.
399
+
400
+ ---
401
+
316
402
  ## `formo import`
317
403
 
318
404
  ### `import wallets`
@@ -322,10 +408,24 @@ Bulk-import wallet addresses into the project via the events API.
322
408
  | Option | Description |
323
409
  |---|---|
324
410
  | `--addresses` | JSON array of wallet address strings |
325
- | `--writeKey` | Project write SDK key |
411
+ | `--rows` | JSON array of `{address,properties?}` objects |
326
412
 
327
413
  ```bash
328
- formo import wallets --addresses '["0xabc...","0xdef..."]' --writeKey write_key_xyz
414
+ formo import wallets --addresses '["0xabc...","0xdef..."]'
415
+ formo import wallets --rows '[{"address":"0xabc...","properties":{"display_name":"Alice"}}]'
416
+ ```
417
+
418
+ ---
419
+
420
+ ## `formo events`
421
+
422
+ ### `events ingest`
423
+
424
+ Send raw analytics events to `events.formo.so`. This command uses a project SDK write key, not the workspace API key.
425
+
426
+ ```bash
427
+ export FORMO_WRITE_KEY=formo_write_key_xxx
428
+ formo events ingest --event '{"type":"track","channel":"cli","version":"1","anonymous_id":"anon_123","event":"CLI Test","context":{},"properties":{},"original_timestamp":"2026-04-27T23:05:38.000Z","sent_at":"2026-04-27T23:05:42.000Z","message_id":"cli-test-1"}'
329
429
  ```
330
430
 
331
431
  ---
@@ -336,16 +436,30 @@ formo import wallets --addresses '["0xabc...","0xdef..."]' --writeKey write_key_
336
436
 
337
437
  ```json
338
438
  [
339
- { "field": "net_worth_usd", "op": "gt", "value": 10000 },
340
- { "field": "tx_count", "op": "gte", "value": 5 }
439
+ { "field": "users.net_worth_usd", "op": "gt", "value": 10000 },
440
+ { "field": "chains.1.balance", "op": "gte", "value": 1000 }
341
441
  ]
342
442
  ```
343
443
 
444
+ > **The `field` must be a typed path.** A bare name like `net_worth_usd` is
445
+ > silently ignored by the API (no error, no filtering — the search returns
446
+ > everything). Always prefix the field with its type.
447
+
344
448
  | Field | Type | Description |
345
449
  |---|---|---|
346
- | `field` | `string` | Profile field to filter on |
450
+ | `field` | `string` | Typed path (see prefixes below) |
347
451
  | `op` | `string` | `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `nin` |
348
452
  | `value` | `any` | Value to compare against |
453
+ | `scope` | `string` | _(token filters only)_ `any` or `protocol` |
454
+ | `appId` | `string` | _(token filters with `scope: protocol`)_ e.g. `aave-v3` |
455
+
456
+ | Prefix | Examples |
457
+ |---|---|
458
+ | `users.` | `users.net_worth_usd`, `users.volume`, `users.revenue`, `users.points`, `users.device`, `users.location`, `users.lifecycle`, `users.ens`, `users.farcaster` |
459
+ | `chains.` | `chains.balance` (any chain), `chains.1.balance` (Ethereum) |
460
+ | `apps.` | `apps.uniswap-v3.balance` |
461
+ | `tokens.` | `tokens.0xA0b8…48.balance` |
462
+ | `labels.` | `labels.coinbase.verified_account` |
349
463
 
350
464
  Combine multiple conditions with `--logic and` (default) or `--logic or`.
351
465
 
@@ -1,23 +1,36 @@
1
1
  import { Cli } from 'incur';
2
2
  export declare const alerts: Cli.Cli<{}, undefined, undefined>;
3
- export declare function listAlertsRun(): Promise<import("axios").AxiosResponse<any, any, {}>>;
3
+ export interface PaginationOptions {
4
+ page?: number;
5
+ size?: number;
6
+ }
7
+ export declare function listAlertsRun(options?: PaginationOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
4
8
  export declare function getAlertRun(alertId: string): Promise<import("axios").AxiosResponse<any, any, {}>>;
5
9
  export interface CreateAlertOptions {
6
10
  name: string;
7
- triggerType: string;
11
+ triggerType: 'event' | 'user' | string;
8
12
  triggerFilters?: string;
9
13
  recipient?: string;
10
14
  secret?: string;
15
+ slackPropertyKeys?: string;
11
16
  }
12
17
  export declare function buildAlertBody(options: CreateAlertOptions | UpdateAlertOptions): Record<string, unknown>;
13
18
  export declare function createAlertRun(options: CreateAlertOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
14
19
  export interface UpdateAlertOptions {
15
20
  name: string;
16
- triggerType: string;
21
+ triggerType: 'event' | 'user' | string;
17
22
  triggerFilters?: string;
18
23
  recipient?: string;
19
24
  secret?: string;
25
+ slackPropertyKeys?: string;
20
26
  }
21
27
  export declare function updateAlertRun(alertId: string, options: UpdateAlertOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
22
28
  export declare function deleteAlertRun(alertId: string): Promise<import("axios").AxiosResponse<any, any, {}>>;
23
29
  export declare function toggleAlertRun(alertId: string, status: string): Promise<import("axios").AxiosResponse<any, any, {}>>;
30
+ export interface TestAlertOptions {
31
+ sampleEvent?: string;
32
+ sampleUser?: string;
33
+ recipientOverrides?: string;
34
+ }
35
+ export declare function buildTestAlertBody(options: TestAlertOptions): Record<string, unknown> | undefined;
36
+ export declare function testAlertRun(alertId: string, options?: TestAlertOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
@@ -8,23 +8,37 @@ exports.createAlertRun = createAlertRun;
8
8
  exports.updateAlertRun = updateAlertRun;
9
9
  exports.deleteAlertRun = deleteAlertRun;
10
10
  exports.toggleAlertRun = toggleAlertRun;
11
+ exports.buildTestAlertBody = buildTestAlertBody;
12
+ exports.testAlertRun = testAlertRun;
11
13
  const incur_1 = require("incur");
12
14
  const client_1 = require("../lib/client");
15
+ const json_1 = require("../lib/json");
13
16
  exports.alerts = incur_1.Cli.create('alerts', {
14
17
  description: 'Project alert commands — create, list, update, and delete alerts',
15
18
  });
16
- // ── List alerts ──
17
- function listAlertsRun() {
19
+ function buildPaginationParams(options = {}) {
20
+ const params = {};
21
+ if (options.page !== undefined)
22
+ params.page = options.page;
23
+ if (options.size !== undefined)
24
+ params.size = options.size;
25
+ return params;
26
+ }
27
+ function listAlertsRun(options = {}) {
18
28
  (0, client_1.requireApiKey)();
19
29
  const client = (0, client_1.createClient)();
20
- return client.get('/v0/alerts/');
30
+ return client.get('/v0/alerts/', { params: buildPaginationParams(options) });
21
31
  }
22
32
  exports.alerts.command('list', {
23
33
  description: 'List all alerts for the project',
34
+ options: incur_1.z.object({
35
+ page: incur_1.z.coerce.number().optional().describe('Page number (1-indexed, default 1)'),
36
+ size: incur_1.z.coerce.number().optional().describe('Page size (default 100, max 200)'),
37
+ }),
24
38
  examples: [{ description: 'List all project alerts' }],
25
39
  hint: 'Requires alerts:read scope on your API key.',
26
- run() {
27
- return listAlertsRun();
40
+ run({ options }) {
41
+ return listAlertsRun(options);
28
42
  },
29
43
  });
30
44
  // ── Get a single alert ──
@@ -50,26 +64,20 @@ function buildAlertBody(options) {
50
64
  const body = {
51
65
  name: options.name,
52
66
  trigger_type: options.triggerType,
67
+ trigger_filters: [],
53
68
  };
54
69
  if (options.triggerFilters) {
55
- try {
56
- body.trigger_filters = JSON.parse(options.triggerFilters);
57
- }
58
- catch {
59
- throw new Error('--triggerFilters must be a valid JSON array');
60
- }
70
+ body.trigger_filters = (0, json_1.parseJsonArray)(options.triggerFilters, '--trigger-filters');
61
71
  }
62
72
  if (options.recipient) {
63
- try {
64
- body.recipient = JSON.parse(options.recipient);
65
- }
66
- catch {
67
- throw new Error('--recipient must be a valid JSON array');
68
- }
73
+ body.recipient = (0, json_1.parseJsonArray)(options.recipient, '--recipient');
69
74
  }
70
75
  if (options.secret !== undefined) {
71
76
  body.secret = options.secret;
72
77
  }
78
+ if (options.slackPropertyKeys !== undefined) {
79
+ body.slack_property_keys = (0, json_1.parseJsonArray)(options.slackPropertyKeys, '--slack-property-keys');
80
+ }
73
81
  return body;
74
82
  }
75
83
  function createAlertRun(options) {
@@ -81,7 +89,7 @@ exports.alerts.command('create', {
81
89
  description: 'Create a new project alert',
82
90
  options: incur_1.z.object({
83
91
  name: incur_1.z.string().describe('Alert name'),
84
- triggerType: incur_1.z.string().describe('Trigger type (e.g. "event", "threshold")'),
92
+ triggerType: incur_1.z.enum(['event', 'user']).describe('Trigger type'),
85
93
  triggerFilters: incur_1.z
86
94
  .string()
87
95
  .optional()
@@ -91,6 +99,10 @@ exports.alerts.command('create', {
91
99
  .optional()
92
100
  .describe('JSON array of recipient objects'),
93
101
  secret: incur_1.z.string().optional().describe('Webhook secret for the alert'),
102
+ slackPropertyKeys: incur_1.z
103
+ .string()
104
+ .optional()
105
+ .describe('JSON array of event/user property keys to include in Slack alerts'),
94
106
  }),
95
107
  examples: [
96
108
  {
@@ -115,7 +127,7 @@ exports.alerts.command('update', {
115
127
  }),
116
128
  options: incur_1.z.object({
117
129
  name: incur_1.z.string().describe('Alert name'),
118
- triggerType: incur_1.z.string().describe('Trigger type'),
130
+ triggerType: incur_1.z.enum(['event', 'user']).describe('Trigger type'),
119
131
  triggerFilters: incur_1.z
120
132
  .string()
121
133
  .optional()
@@ -125,6 +137,10 @@ exports.alerts.command('update', {
125
137
  .optional()
126
138
  .describe('JSON array of recipient objects'),
127
139
  secret: incur_1.z.string().optional().describe('Webhook secret for the alert'),
140
+ slackPropertyKeys: incur_1.z
141
+ .string()
142
+ .optional()
143
+ .describe('JSON array of event/user property keys to include in Slack alerts'),
128
144
  }),
129
145
  examples: [
130
146
  {
@@ -161,21 +177,24 @@ exports.alerts.command('delete', {
161
177
  function toggleAlertRun(alertId, status) {
162
178
  (0, client_1.requireApiKey)();
163
179
  const client = (0, client_1.createClient)();
164
- return client.patch(`/v0/alerts/${encodeURIComponent(alertId)}`, { status });
180
+ const normalizedStatus = status === 'paused' ? 'inactive' : status;
181
+ return client.patch(`/v0/alerts/${encodeURIComponent(alertId)}`, {
182
+ status: normalizedStatus,
183
+ });
165
184
  }
166
185
  exports.alerts.command('toggle', {
167
- description: 'Toggle an alert status (active/paused)',
186
+ description: 'Toggle an alert status (active/inactive)',
168
187
  args: incur_1.z.object({
169
188
  alertId: incur_1.z.string().describe('Alert ID to toggle'),
170
189
  }),
171
190
  options: incur_1.z.object({
172
- status: incur_1.z.enum(['active', 'paused']).describe('New status'),
191
+ status: incur_1.z.enum(['active', 'inactive', 'paused']).describe('New status. "paused" is accepted as a deprecated alias for "inactive".'),
173
192
  }),
174
193
  examples: [
175
194
  {
176
195
  args: { alertId: 'alert_abc123' },
177
- options: { status: 'paused' },
178
- description: 'Pause an alert',
196
+ options: { status: 'inactive' },
197
+ description: 'Deactivate an alert',
179
198
  },
180
199
  ],
181
200
  hint: 'Requires alerts:write scope on your API key.',
@@ -183,3 +202,54 @@ exports.alerts.command('toggle', {
183
202
  return toggleAlertRun(args.alertId, options.status);
184
203
  },
185
204
  });
205
+ function buildTestAlertBody(options) {
206
+ const body = {};
207
+ if (options.sampleEvent !== undefined) {
208
+ body.sampleEvent = (0, json_1.parseJsonObject)(options.sampleEvent, '--sample-event');
209
+ }
210
+ if (options.sampleUser !== undefined) {
211
+ body.sampleUser = (0, json_1.parseJsonObject)(options.sampleUser, '--sample-user');
212
+ }
213
+ if (options.recipientOverrides !== undefined) {
214
+ body.recipientOverrides = (0, json_1.parseJsonArray)(options.recipientOverrides, '--recipient-overrides');
215
+ }
216
+ return Object.keys(body).length > 0 ? body : undefined;
217
+ }
218
+ function testAlertRun(alertId, options = {}) {
219
+ (0, client_1.requireApiKey)();
220
+ const client = (0, client_1.createClient)();
221
+ return client.post(`/v0/alerts/${encodeURIComponent(alertId)}/test`, buildTestAlertBody(options));
222
+ }
223
+ exports.alerts.command('test', {
224
+ description: 'Send a test delivery for an alert',
225
+ args: incur_1.z.object({
226
+ alertId: incur_1.z.string().describe('Alert ID to test'),
227
+ }),
228
+ options: incur_1.z.object({
229
+ sampleEvent: incur_1.z
230
+ .string()
231
+ .optional()
232
+ .describe('Optional JSON object to use as the sample event'),
233
+ sampleUser: incur_1.z
234
+ .string()
235
+ .optional()
236
+ .describe('Optional JSON object to use as the sample user/profile'),
237
+ recipientOverrides: incur_1.z
238
+ .string()
239
+ .optional()
240
+ .describe('Optional JSON array of recipient objects to test instead of saved recipients'),
241
+ }),
242
+ examples: [
243
+ {
244
+ args: { alertId: 'alert_abc123' },
245
+ options: {
246
+ sampleEvent: '{"event":"transaction","revenue":250}',
247
+ },
248
+ description: 'Send a test alert with a sample event',
249
+ },
250
+ ],
251
+ hint: 'Requires alerts:write scope on your API key.',
252
+ run({ args, options }) {
253
+ return testAlertRun(args.alertId, options);
254
+ },
255
+ });
@@ -0,0 +1,25 @@
1
+ import { Cli } from 'incur';
2
+ export declare const analytics: Cli.Cli<{}, undefined, undefined>;
3
+ export interface AnalyticsOptions {
4
+ dateFrom?: string;
5
+ dateTo?: string;
6
+ filters?: string;
7
+ params?: string;
8
+ }
9
+ /**
10
+ * Build the query-string params for an analytics pipe request.
11
+ *
12
+ * - `dateFrom`/`dateTo` map to the API's snake_case `date_from`/`date_to`.
13
+ * All pipes, including `funnel` and `flow`, use snake_case.
14
+ * - `filters` is a JSON array of `{ field, op, value }` objects, re-serialized
15
+ * as a JSON string (the pipe expects a JSON-encoded array in the query).
16
+ * - `params` is a JSON object of any pipe-specific params (e.g. funnel
17
+ * `steps`, kpis `group_by`, `limit`). Object/array values are JSON-encoded
18
+ * (pipes like funnel expect `steps` as a JSON-encoded string); primitives
19
+ * pass through unchanged. Reserved keys (the date/filters flags) are
20
+ * rejected, and the validated flags below always take precedence.
21
+ *
22
+ * Exported for unit testing.
23
+ */
24
+ export declare function buildAnalyticsParams(options: AnalyticsOptions): Record<string, string | number | boolean>;
25
+ export declare function runAnalytics(pipe: string, options: AnalyticsOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;