@formo/cli 1.0.0 → 1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Formo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -10,6 +10,23 @@ npm install -g @formo/cli
10
10
  npx @formo/cli
11
11
  ```
12
12
 
13
+ ## Install the Formo Analytics agent skill
14
+
15
+ This repository also ships the [`formo-analytics`](skills/formo-analytics/SKILL.md) skill for Claude Code, Codex, and other [Agent Skills](https://agentskills.io)-compatible tools. It teaches agents to use Formo's MCP server, CLI, and REST API for project-scoped product and onchain analytics.
16
+
17
+ Install it in the current project:
18
+
19
+ ```bash
20
+ npx skills add https://github.com/getformo/cli/tree/main/skills/formo-analytics
21
+ ```
22
+
23
+ Or install it globally for Claude Code and Codex:
24
+
25
+ ```bash
26
+ npx skills add getformo/cli --skill formo-analytics --global \
27
+ --agent claude-code --agent codex
28
+ ```
29
+
13
30
  ## Authentication
14
31
 
15
32
  Save your API key locally:
@@ -24,7 +41,14 @@ Or set the `FORMO_API_KEY` environment variable — it takes precedence over the
24
41
  export FORMO_API_KEY=formo_abc123
25
42
  ```
26
43
 
27
- Get your API key from `Settings → API Keys` in the [Formo dashboard](https://app.formo.so).
44
+ Get your API key from `Settings → API` in the [Formo dashboard](https://app.formo.so).
45
+
46
+ For local development or proxying, override API hosts with:
47
+
48
+ ```bash
49
+ export FORMO_API_BASE_URL=http://localhost:3001
50
+ export FORMO_EVENTS_BASE_URL=http://localhost:3002
51
+ ```
28
52
 
29
53
  ---
30
54
 
@@ -114,6 +138,15 @@ formo profiles update vitalik.eth --properties '{"email":"alice@example.com"}'
114
138
 
115
139
  > Requires `profiles:write` scope.
116
140
 
141
+ ### `profiles properties batch`
142
+
143
+ Batch update first-party properties for up to 100 wallets.
144
+
145
+ ```bash
146
+ formo profiles properties batch \
147
+ --rows '[{"address":"0xd8dA...","display_name":"alice.eth","email":"alice@example.com"}]'
148
+ ```
149
+
117
150
  ### `profiles labels create <address>`
118
151
 
119
152
  Upsert one or more labels on a wallet profile. Provide either a single label via `--tag-id` or a batch via `--labels`.
@@ -123,12 +156,16 @@ Upsert one or more labels on a wallet profile. Provide either a single label via
123
156
  | `--tag-id` | Label identifier (e.g. `vip`, `airdrop_eligible`) |
124
157
  | `--value` | Optional label value (e.g. tier name, country code) |
125
158
  | `--chain-id` | Optional chain identifier the label applies to |
159
+ | `--timestamp` | Optional historical ISO-8601 timestamp |
160
+ | `--is-deleted` | Backfill a historical label removal tombstone |
126
161
  | `--labels` | JSON array of `UserLabelInput` objects for batch upsert |
127
162
 
128
163
  ```bash
129
164
  formo profiles labels create 0xd8dA... --tag-id vip
130
165
  formo profiles labels create 0xd8dA... --tag-id tier --value gold --chain-id 1
131
166
  formo profiles labels create 0xd8dA... --labels '[{"tag_id":"vip"},{"tag_id":"airdrop_eligible","chain_id":"1"}]'
167
+ formo profiles labels create 0xd8dA... --tag-id tier --timestamp 2024-03-15T00:00:00.000Z --is-deleted
168
+ formo profiles labels batch --labels '[{"address":"0xd8dA...","tag_id":"vip","value":"tier-1"}]'
132
169
  ```
133
170
 
134
171
  ### `profiles labels delete <address>`
@@ -164,14 +201,14 @@ Get a single alert by ID.
164
201
  | Option | Description |
165
202
  |---|---|
166
203
  | `--name` | Alert name |
167
- | `--trigger-type` | Trigger type (e.g. `event`, `threshold`) |
204
+ | `--trigger-type` | Trigger type: `event` or `user` |
168
205
  | `--trigger-filters` | JSON array of trigger filter objects |
169
206
  | `--recipient` | JSON array of recipient objects |
170
207
  | `--secret` | Webhook secret |
171
208
 
172
209
  ```bash
173
210
  formo alerts create --name "High value tx" --trigger-type event \
174
- --trigger-filters '[{"name":"event","operator":"equals","value":"transaction"}]' \
211
+ --trigger-filters '[{"name":"event","operator":"eq","value":"transaction"}]' \
175
212
  --recipient '[{"type":"email","value":["alerts@myapp.com"]}]'
176
213
  ```
177
214
 
@@ -181,11 +218,18 @@ Same options as `create`. Replaces the alert configuration.
181
218
  ### `alerts delete <alertId>`
182
219
  Delete an alert.
183
220
 
184
- ### `alerts toggle <alertId> --status <active|paused>`
185
- Toggle an alert between `active` and `paused`.
221
+ ### `alerts toggle <alertId> --status <active|inactive>`
222
+ Toggle an alert between `active` and `inactive`.
186
223
 
187
224
  ```bash
188
- formo alerts toggle alert_abc123 --status paused
225
+ formo alerts toggle alert_abc123 --status inactive
226
+ ```
227
+
228
+ ### `alerts test <alertId>`
229
+ Send a test alert delivery with optional sample payloads.
230
+
231
+ ```bash
232
+ formo alerts test alert_abc123 --sample-event '{"event":"transaction","revenue":250}'
189
233
  ```
190
234
 
191
235
  ---
@@ -204,19 +248,21 @@ Get a single board by ID.
204
248
 
205
249
  | Option | Description |
206
250
  |---|---|
207
- | `--name` | Board name |
251
+ | `--title` | Board title |
208
252
  | `--description` | Optional board description |
253
+ | `--is-public` | Make the board publicly viewable |
209
254
 
210
255
  ```bash
211
- formo boards create --name "Revenue Metrics" --description "Weekly revenue tracking"
256
+ formo boards create --title "Revenue Metrics" --description "Weekly revenue tracking"
212
257
  ```
213
258
 
214
259
  ### `boards update <boardId>`
215
260
 
216
261
  | Option | Description |
217
262
  |---|---|
218
- | `--name` | New board name |
263
+ | `--title` | New board title |
219
264
  | `--description` | New board description |
265
+ | `--is-public` | Update public visibility |
220
266
 
221
267
  ### `boards delete <boardId>`
222
268
  Delete a board.
@@ -233,12 +279,30 @@ List all charts in a board.
233
279
  ### `charts get <chartId> --board-id <boardId>`
234
280
  Get a single chart by ID.
235
281
 
236
- ### `charts create --board-id <boardId> --body '<json>'`
237
- Create a chart from a JSON config string.
282
+ ### `charts meta --board-id <boardId>`
283
+ List lightweight chart metadata without executing chart queries.
284
+
285
+ ### `charts create --board-id <boardId> [options]`
286
+ Create a chart from typed flags or a raw JSON body.
287
+
288
+ ```bash
289
+ formo charts create --board-id brd_123 --title "Daily Active Users" \
290
+ --chart-type line \
291
+ --query "SELECT toDate(timestamp) AS date, countDistinct(address) AS users FROM events GROUP BY date ORDER BY date" \
292
+ --x-axis date --y-axis users
293
+
294
+ formo charts create --board-id brd_123 --body '{"title":"Recent Events","chart_type":"table","query":"SELECT * FROM events LIMIT 10"}'
295
+ ```
238
296
 
239
297
  ### `charts update <chartId> --board-id <boardId> --body '<json>'`
240
298
  Update a chart.
241
299
 
300
+ ### `charts query <chartId> --board-id <boardId> --date-from <YYYY-MM-DD> --date-to <YYYY-MM-DD>`
301
+ Execute a saved chart that uses `{{date_from}}` / `{{date_to}}` variables.
302
+
303
+ ### `charts move|duplicate|reorder`
304
+ Move a chart to another board, duplicate a chart, or reorder charts in a board.
305
+
242
306
  ### `charts delete <chartId> --board-id <boardId>`
243
307
  Delete a chart.
244
308
 
@@ -251,6 +315,12 @@ Smart contract commands. Requires `contracts:read` / `contracts:write`.
251
315
  ### `contracts list`
252
316
  List all tracked contracts. Returns `{ data: Contract[], deploy: { last_deployed_at, diff }, total, page, size, has_more }`.
253
317
 
318
+ ### `contracts get <chain> <address>`
319
+ Get a single tracked contract.
320
+
321
+ ### `contracts recommendations`
322
+ List contracts the project already interacts with but has not added yet.
323
+
254
324
  ### `contracts create`
255
325
 
256
326
  | Option | Description |
@@ -258,13 +328,15 @@ List all tracked contracts. Returns `{ data: Contract[], deploy: { last_deployed
258
328
  | `--address` | Contract address (`0x…`) |
259
329
  | `--chain` | Chain ID (e.g. `1`, `137`) |
260
330
  | `--name` | Human-readable contract name |
261
- | `--abi` | Contract ABI as a JSON string |
262
- | `--events` | Events configuration as a JSON string |
331
+ | `--abi` | Contract ABI as a JSON string; sent stringified to the API |
332
+ | `--events` | JSON array of ABI event objects to monitor |
333
+ | `--start-block` | Optional start block |
334
+ | `--include-in-pipeline` | Include this contract in the Goldsky events pipeline (`true` by default in the API) |
263
335
 
264
336
  ```bash
265
337
  formo contracts create --address 0x1f9840a85d5af5bf1d1762f925bdaddc4201f984 --chain 1 \
266
338
  --name "UNI Token" --abi '[{"type":"event","name":"Transfer","inputs":[]}]' \
267
- --events '{"Transfer":true}'
339
+ --events '[{"type":"event","name":"Transfer","inputs":[]}]'
268
340
  ```
269
341
 
270
342
  ### `contracts update <chain> <address>`
@@ -273,7 +345,12 @@ formo contracts create --address 0x1f9840a85d5af5bf1d1762f925bdaddc4201f984 --ch
273
345
  |---|---|
274
346
  | `--name` | Updated contract name |
275
347
  | `--abi` | Updated ABI |
276
- | `--events` | Updated events config |
348
+ | `--events` | Updated JSON array of ABI event objects |
349
+ | `--start-block` | Optional start block |
350
+ | `--include-in-pipeline` | Include or exclude this contract from the Goldsky events pipeline |
351
+
352
+ ### `contracts pipeline <chain> <address> --include-in-pipeline <true|false>`
353
+ Toggle pipeline inclusion without re-sending the full ABI/events payload.
277
354
 
278
355
  ### `contracts delete <chain> <address>`
279
356
  Remove a tracked contract.
@@ -324,7 +401,7 @@ Pre-built analytics pipes — the same data that powers the Formo dashboard —
324
401
  |---|---|
325
402
  | `--date-from` | Inclusive start date `YYYY-MM-DD` (default: 7 days before `--date-to`) |
326
403
  | `--date-to` | Inclusive end date `YYYY-MM-DD` (default: today) |
327
- | `--filters` | JSON array of `[{field,op,value}]`. Use `in`/`notIn` with a pipe-delimited value (e.g. `"chrome\|firefox"`) |
404
+ | `--filters` | JSON array of `[{field,op,value}]`. Use `in`/`nin` with a pipe-delimited value (e.g. `"chrome\|firefox"`) |
328
405
  | `--params` | JSON object of pipe-specific params merged into the query (e.g. `{"limit":10,"group_by":"device"}`) |
329
406
 
330
407
  ```bash
@@ -332,7 +409,7 @@ formo analytics kpis
332
409
  formo analytics kpis --date-from 2026-04-01 --date-to 2026-04-30 --params '{"group_by":"device"}'
333
410
  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}'
334
411
  formo analytics top_wallets --date-from 2026-04-01 --date-to 2026-04-30 --params '{"limit":10}'
335
- formo analytics retention --filters '[{"field":"location","op":"equals","value":"US"}]'
412
+ formo analytics retention --filters '[{"field":"location","op":"eq","value":"US"}]'
336
413
  ```
337
414
 
338
415
  > Requires `query:read` scope. Run `formo analytics <pipe> --help` for the pipe-specific params accepted via `--params`.
@@ -348,10 +425,24 @@ Bulk-import wallet addresses into the project via the events API.
348
425
  | Option | Description |
349
426
  |---|---|
350
427
  | `--addresses` | JSON array of wallet address strings |
351
- | `--write-key` | Project write SDK key |
428
+ | `--rows` | JSON array of `{address,properties?}` objects |
429
+
430
+ ```bash
431
+ formo import wallets --addresses '["0xabc...","0xdef..."]'
432
+ formo import wallets --rows '[{"address":"0xabc...","properties":{"display_name":"Alice"}}]'
433
+ ```
434
+
435
+ ---
436
+
437
+ ## `formo events`
438
+
439
+ ### `events ingest`
440
+
441
+ Send raw analytics events to `events.formo.so`. This command uses a project SDK write key, not the workspace API key.
352
442
 
353
443
  ```bash
354
- formo import wallets --addresses '["0xabc...","0xdef..."]' --write-key write_key_xyz
444
+ export FORMO_WRITE_KEY=formo_write_key_xxx
445
+ 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"}'
355
446
  ```
356
447
 
357
448
  ---
@@ -374,7 +465,7 @@ formo import wallets --addresses '["0xabc...","0xdef..."]' --write-key write_key
374
465
  | Field | Type | Description |
375
466
  |---|---|---|
376
467
  | `field` | `string` | Typed path (see prefixes below) |
377
- | `op` | `string` | `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `nin` |
468
+ | `op` | `string` | `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `contains` (social fields only), `notEmpty` / `isEmpty` (value-less existence checks). Long-form spellings (`equals`, `greater`, `includes`, …) are retired — the API rejects them with a `400` naming the token |
378
469
  | `value` | `any` | Value to compare against |
379
470
  | `scope` | `string` | _(token filters only)_ `any` or `protocol` |
380
471
  | `appId` | `string` | _(token filters with `scope: protocol`)_ e.g. `aave-v3` |
@@ -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('--trigger-filters 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
+ });
@@ -15,7 +15,7 @@ exports.analytics = incur_1.Cli.create('analytics', {
15
15
  const PIPES = [
16
16
  { name: 'kpis', description: 'Traffic KPIs: visitors, pageviews, bounce rate, session duration' },
17
17
  { name: 'event_timeseries', description: 'Event counts over time' },
18
- { name: 'funnel', description: 'Conversion funnel across ordered steps. --params: steps (JSON array of {type,event,name,filters?}), window_seconds, funnel_type, breakdown' },
18
+ { name: 'funnel', description: 'Conversion funnel across ordered steps. --params: steps (JSON array of {type,event,name,filters?}), window_seconds, funnel_type, group_by, limit, attribution' },
19
19
  { name: 'flow', description: 'User path/flow analysis. --params: start_step / end_step (JSON {type,event,...}), global_filters, window_seconds, max_steps' },
20
20
  { name: 'frequency', description: 'Engagement frequency distribution' },
21
21
  { name: 'lifecycle', description: 'User lifecycle stages (new, returning, power, resurrected, churned)' },
@@ -123,7 +123,7 @@ const sharedOptions = incur_1.z.object({
123
123
  .string()
124
124
  .optional()
125
125
  .describe('JSON array of filter conditions: [{"field","op","value"}]. ' +
126
- 'Use op "in"/"notIn" with a pipe-delimited value (e.g. "chrome|firefox").'),
126
+ 'Use op "in"/"nin" with a pipe-delimited value (e.g. "chrome|firefox").'),
127
127
  params: incur_1.z
128
128
  .string()
129
129
  .optional()
@@ -1,15 +1,24 @@
1
1
  import { Cli } from 'incur';
2
2
  export declare const boards: Cli.Cli<{}, undefined, undefined>;
3
- export declare function listBoardsRun(): Promise<import("axios").AxiosResponse<any, any, {}>>;
3
+ export interface PaginationOptions {
4
+ page?: number;
5
+ size?: number;
6
+ }
7
+ export declare function listBoardsRun(options?: PaginationOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
4
8
  export declare function getBoardRun(boardId: string): Promise<import("axios").AxiosResponse<any, any, {}>>;
5
9
  export interface CreateBoardOptions {
6
- name: string;
10
+ title?: string;
11
+ name?: string;
7
12
  description?: string;
13
+ isPublic?: boolean;
8
14
  }
15
+ export declare function buildBoardBody(options: CreateBoardOptions | UpdateBoardOptions): Record<string, unknown>;
9
16
  export declare function createBoardRun(options: CreateBoardOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
10
17
  export interface UpdateBoardOptions {
18
+ title?: string;
11
19
  name?: string;
12
20
  description?: string;
21
+ isPublic?: boolean;
13
22
  }
14
23
  export declare function updateBoardRun(boardId: string, options: UpdateBoardOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
15
24
  export declare function deleteBoardRun(boardId: string): Promise<import("axios").AxiosResponse<any, any, {}>>;