Skip to content

Commit 9e36959

Browse files
yosriadyclaude
andauthored
Update CLI to latest API (#7)
* Update CLI to latest API * Unify analytics funnel/flow date params to snake_case The API is being updated so /v0/funnel and /v0/flow accept snake_case date_from/date_to like every other pipe. Drop the CAMEL_DATE_PIPES special-case so the CLI always sends snake_case, and remove the now-unused `pipe` arg from buildAnalyticsParams. Live funnel/flow integration tests are skipped until the API-side fix deploys (production still only accepts camelCase for these two pipes, so a snake_case call returns HTTP 400); the deterministic snake_case unit test keeps the CLI behavior locked meanwhile. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Use kebab-case flags in docs, help text, and error messages incur renders flag definitions in kebab-case (--date-from, --order-by, --tag-id, …) and that matches the Node/TS CLI ecosystem standard (oclif/commander/yargs), but the docs and hand-written CLI strings used camelCase, so a user copying from `formo <cmd> --help`'s flag list got a form the docs didn't show. Normalize every user-facing flag reference to kebab-case across README.md, SKILLS.md, command describe()/hint/error strings, and update the test assertions that matched the old camelCase error text. Both spellings still work at runtime (incur accepts camelCase too); this is consistency only. Known limitation: incur renders in-code `examples:` option keys verbatim (camelCase) because they are type-bound to the zod schema, so the example lines in `--help` remain camelCase. Not fixable without an incur change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Patch incur for kebab examples; enforce typed --conditions; fix SKILLS pagination - Patch incur (pnpm patchedDependencies, patches/incur.patch) so formatExamples() kebab-cases option keys like the flag-definition renderer already does — `--help` example lines now match the flag list (--order-by, --date-from, …) and so do generated skills/MCP. - profiles search: validate --conditions field paths client-side. A bare/untyped field (e.g. "net_worth_usd") is silently ignored by the API and returns the entire unfiltered dataset; now it fails fast with an actionable error. Added parseSearchConditions() + tests. - SKILLS.md: profiles search documented --limit/--offset but the CLI exposes --page/--size; corrected table and examples (matches README). Addresses Codex review P1 (unenforced typed-path requirement) and P2 (SKILLS.md pagination drift). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Extend incur patch: kebab-case flags in --llms manifest, usage, CTAs The earlier patch only fixed formatExamples(). Three other incur renderers still emitted raw camelCase keys: - Skill.js renderCommandBody(): the `--llms`/`--llms-full` Options tables (`| --triggerType |`) — agent-facing manifest. - Help.js formatCommand(): explicit usage-pattern option lines. - Cli.js formatCta(): suggested-command / error-CTA strings. All now apply the same kebab transform, so every user- and agent-facing flag surface (help, examples, manifest, skills, MCP, CTAs) is consistent. patches/incur.patch now carries 4 hunks. Verified: `--llms-full` option tables and `--help` examples both kebab; build/lint pass; 87 passing / 0 failing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent fea317b commit 9e36959

15 files changed

Lines changed: 670 additions & 89 deletions

README.md

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -82,18 +82,19 @@ Search wallet profiles with filters, sorting, and pagination. Returns a `Paginat
8282
| `--address` | Filter by wallet address |
8383
| `--page` | Page number (1-indexed, default `1`) |
8484
| `--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` |
85+
| `--order-by` | `last_onchain`, `first_onchain`, `net_worth_usd`, `updated_at`, `tx_count`, `first_seen`, `last_seen`, `num_sessions`, `revenue`, `volume`, `points` |
86+
| `--order-dir` | `asc` or `desc` |
8787
| `--expand` | Comma-separated fields to expand |
8888
| `--conditions` | JSON array of `FilterCondition` objects (see below) |
8989
| `--logic` | Combine conditions with `and` (default) or `or` |
9090

9191
```bash
9292
formo profiles search --size 10
93-
formo profiles search --orderBy net_worth_usd --orderDir desc --size 5
93+
formo profiles search --order-by net_worth_usd --order-dir desc --size 5
9494
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
95+
formo profiles search --conditions '[{"field":"users.net_worth_usd","op":"gt","value":10000}]' --size 20
96+
formo profiles search --conditions '[{"field":"users.net_worth_usd","op":"gt","value":10000},{"field":"users.volume","op":"gt","value":1000}]' --logic or --size 20
97+
formo profiles search --conditions '[{"field":"chains.1.balance","op":"gt","value":1000}]' --size 20
9798
```
9899

99100
### `profiles update <address>`
@@ -115,18 +116,18 @@ formo profiles update vitalik.eth --properties '{"email":"alice@example.com"}'
115116
116117
### `profiles labels create <address>`
117118

118-
Upsert one or more labels on a wallet profile. Provide either a single label via `--tagId` or a batch via `--labels`.
119+
Upsert one or more labels on a wallet profile. Provide either a single label via `--tag-id` or a batch via `--labels`.
119120

120121
| Option | Description |
121122
|---|---|
122-
| `--tagId` | Label identifier (e.g. `vip`, `airdrop_eligible`) |
123+
| `--tag-id` | Label identifier (e.g. `vip`, `airdrop_eligible`) |
123124
| `--value` | Optional label value (e.g. tier name, country code) |
124-
| `--chainId` | Optional chain identifier the label applies to |
125+
| `--chain-id` | Optional chain identifier the label applies to |
125126
| `--labels` | JSON array of `UserLabelInput` objects for batch upsert |
126127

127128
```bash
128-
formo profiles labels create 0xd8dA... --tagId vip
129-
formo profiles labels create 0xd8dA... --tagId tier --value gold --chainId 1
129+
formo profiles labels create 0xd8dA... --tag-id vip
130+
formo profiles labels create 0xd8dA... --tag-id tier --value gold --chain-id 1
130131
formo profiles labels create 0xd8dA... --labels '[{"tag_id":"vip"},{"tag_id":"airdrop_eligible","chain_id":"1"}]'
131132
```
132133

@@ -136,12 +137,12 @@ Delete a label from a wallet profile.
136137

137138
| Option | Description |
138139
|---|---|
139-
| `--tagId` | Label identifier to delete (required) |
140-
| `--chainId` | Optional chain identifier to scope the deletion |
140+
| `--tag-id` | Label identifier to delete (required) |
141+
| `--chain-id` | Optional chain identifier to scope the deletion |
141142

142143
```bash
143-
formo profiles labels delete 0xd8dA... --tagId vip
144-
formo profiles labels delete 0xd8dA... --tagId tier --chainId 1
144+
formo profiles labels delete 0xd8dA... --tag-id vip
145+
formo profiles labels delete 0xd8dA... --tag-id tier --chain-id 1
145146
```
146147

147148
> Requires `profiles:write` scope.
@@ -163,14 +164,14 @@ Get a single alert by ID.
163164
| Option | Description |
164165
|---|---|
165166
| `--name` | Alert name |
166-
| `--triggerType` | Trigger type (e.g. `event`, `threshold`) |
167-
| `--triggerFilters` | JSON array of trigger filter objects |
167+
| `--trigger-type` | Trigger type (e.g. `event`, `threshold`) |
168+
| `--trigger-filters` | JSON array of trigger filter objects |
168169
| `--recipient` | JSON array of recipient objects |
169170
| `--secret` | Webhook secret |
170171

171172
```bash
172-
formo alerts create --name "High value tx" --triggerType event \
173-
--triggerFilters '[{"name":"event","operator":"equals","value":"transaction"}]' \
173+
formo alerts create --name "High value tx" --trigger-type event \
174+
--trigger-filters '[{"name":"event","operator":"equals","value":"transaction"}]' \
174175
--recipient '[{"type":"email","value":["alerts@myapp.com"]}]'
175176
```
176177

@@ -226,19 +227,19 @@ Delete a board.
226227

227228
Chart commands. Charts live inside a board. Requires `charts:read` / `charts:write`.
228229

229-
### `charts list --boardId <boardId>`
230+
### `charts list --board-id <boardId>`
230231
List all charts in a board.
231232

232-
### `charts get <chartId> --boardId <boardId>`
233+
### `charts get <chartId> --board-id <boardId>`
233234
Get a single chart by ID.
234235

235-
### `charts create --boardId <boardId> --body '<json>'`
236+
### `charts create --board-id <boardId> --body '<json>'`
236237
Create a chart from a JSON config string.
237238

238-
### `charts update <chartId> --boardId <boardId> --body '<json>'`
239+
### `charts update <chartId> --board-id <boardId> --body '<json>'`
239240
Update a chart.
240241

241-
### `charts delete <chartId> --boardId <boardId>`
242+
### `charts delete <chartId> --board-id <boardId>`
242243
Delete a chart.
243244

244245
---
@@ -291,7 +292,7 @@ List all user segments.
291292
| Option | Description |
292293
|---|---|
293294
| `--title` | Segment title |
294-
| `--filterSets` | JSON array of filter set strings defining the segment |
295+
| `--filter-sets` | JSON array of filter set strings defining the segment |
295296

296297
### `segments delete <segmentId>`
297298
Delete a user segment.
@@ -313,6 +314,31 @@ formo query run "SELECT address, net_worth_usd FROM wallet_profiles ORDER BY net
313314
314315
---
315316

317+
## `formo analytics`
318+
319+
Pre-built analytics pipes — the same data that powers the Formo dashboard — without writing SQL. Each pipe is a subcommand: `formo analytics <pipe>`.
320+
321+
**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`
322+
323+
| Option | Description |
324+
|---|---|
325+
| `--date-from` | Inclusive start date `YYYY-MM-DD` (default: 7 days before `--date-to`) |
326+
| `--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"`) |
328+
| `--params` | JSON object of pipe-specific params merged into the query (e.g. `{"limit":10,"group_by":"device"}`) |
329+
330+
```bash
331+
formo analytics kpis
332+
formo analytics kpis --date-from 2026-04-01 --date-to 2026-04-30 --params '{"group_by":"device"}'
333+
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+
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"}]'
336+
```
337+
338+
> Requires `query:read` scope. Run `formo analytics <pipe> --help` for the pipe-specific params accepted via `--params`.
339+
340+
---
341+
316342
## `formo import`
317343

318344
### `import wallets`
@@ -322,10 +348,10 @@ Bulk-import wallet addresses into the project via the events API.
322348
| Option | Description |
323349
|---|---|
324350
| `--addresses` | JSON array of wallet address strings |
325-
| `--writeKey` | Project write SDK key |
351+
| `--write-key` | Project write SDK key |
326352

327353
```bash
328-
formo import wallets --addresses '["0xabc...","0xdef..."]' --writeKey write_key_xyz
354+
formo import wallets --addresses '["0xabc...","0xdef..."]' --write-key write_key_xyz
329355
```
330356

331357
---
@@ -336,16 +362,30 @@ formo import wallets --addresses '["0xabc...","0xdef..."]' --writeKey write_key_
336362

337363
```json
338364
[
339-
{ "field": "net_worth_usd", "op": "gt", "value": 10000 },
340-
{ "field": "tx_count", "op": "gte", "value": 5 }
365+
{ "field": "users.net_worth_usd", "op": "gt", "value": 10000 },
366+
{ "field": "chains.1.balance", "op": "gte", "value": 1000 }
341367
]
342368
```
343369

370+
> **The `field` must be a typed path.** A bare name like `net_worth_usd` is
371+
> silently ignored by the API (no error, no filtering — the search returns
372+
> everything). Always prefix the field with its type.
373+
344374
| Field | Type | Description |
345375
|---|---|---|
346-
| `field` | `string` | Profile field to filter on |
376+
| `field` | `string` | Typed path (see prefixes below) |
347377
| `op` | `string` | `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `nin` |
348378
| `value` | `any` | Value to compare against |
379+
| `scope` | `string` | _(token filters only)_ `any` or `protocol` |
380+
| `appId` | `string` | _(token filters with `scope: protocol`)_ e.g. `aave-v3` |
381+
382+
| Prefix | Examples |
383+
|---|---|
384+
| `users.` | `users.net_worth_usd`, `users.volume`, `users.revenue`, `users.points`, `users.device`, `users.location`, `users.lifecycle`, `users.ens`, `users.farcaster` |
385+
| `chains.` | `chains.balance` (any chain), `chains.1.balance` (Ethereum) |
386+
| `apps.` | `apps.uniswap-v3.balance` |
387+
| `tokens.` | `tokens.0xA0b8…48.balance` |
388+
| `labels.` | `labels.coinbase.verified_account` |
349389

350390
Combine multiple conditions with `--logic and` (default) or `--logic or`.
351391

0 commit comments

Comments
 (0)