diff --git a/README.md b/README.md index efa0969..1386c2c 100644 --- a/README.md +++ b/README.md @@ -438,6 +438,10 @@ formo analytics retention --filters '[{"field":"location","op":"eq","value":"US" > Requires `query:read` scope. Run `formo analytics --help` for the pipe-specific params accepted via `--params`. +On `kpis`, `top_*`, `revenue_*` and `volume_by_metric`, `--params '{"page_scope":"session"}'` widens a `page` filter from page-scoped metrics (the default) to the legacy session scope. + +All user-attribute, profile, social, lifecycle and resource predicates go in the single `--filters` array, using the canonical envelope with named qualifiers (`chain_id`, `app_id`, `token_address`, `scope`, `tag_id`). The retired per-family params — `socials`, `chain_filters`, `app_filters`, `token_filters`, `label_filters`, `profile_filters`, `lifecycle_filter` — are rejected with a `400` if passed through `--params`. + --- ## `formo import` @@ -500,7 +504,7 @@ formo events ingest --events '[{"type":"track","event":"First"},{"type":"track", | Field | Type | Description | |---|---|---| | `field` | `string` | `users.{attribute}` or one of the four resource paths (see below) | -| `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 | +| `op` | `string` | `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `contains`, `startsWith`, `endsWith`, `notEmpty` / `isEmpty` (value-less existence checks). Support is per field — see [operator support](#operator-support). Long-form spellings (`equals`, `greater`, `includes`, …) are retired — the API rejects them with a `400` naming the token | | `value` | `any` | Value to compare against; must be a number on the `.balance` fields | | `chain_id` | `string` | _(optional on any resource filter)_ restrict to one chain; omit to match any | | `app_id` | `string` | _(required by `apps.balance`, and by `tokens.balance` with `scope: protocol`)_ e.g. `aave-v3` | @@ -520,6 +524,21 @@ User attributes for `users.{attribute}`: `net_worth_usd`, `volume`, `revenue`, `points`, `device`, `location`, `lifecycle`, `ens`, `farcaster`, and the other social handles. +### Operator support + +The canonical vocabulary is shared, but each field implements a subset. The API +rejects an unsupported pairing with a `400`: + +| Field class | Supported operators | +|---|---| +| `chains.balance`, `apps.balance`, `tokens.balance` | `eq`, `neq`, `gt`, `gte`, `lt`, `lte` (value must be a JSON number) | +| `labels.value` | comparison operators plus `contains` (case-insensitive) | +| Numeric profile metrics (`users.net_worth_usd`, `users.volume`, `users.revenue`, `users.points`) | comparison operators | +| Routable string attributes (`users.device`, `users.os`, `users.referrer`, `users.utm_*`, `users.click_id`, and the `first_*`/`last_*` attribution variants) | full vocabulary; `contains`/`startsWith`/`endsWith` match case-sensitively | +| Social fields (`users.twitter`, `users.email`, `users.farcaster`, …) | `contains` (case-insensitive) and `notEmpty`; `startsWith`/`endsWith`/`isEmpty` are rejected | +| `users.paid_source` (and `first_`/`last_` variants) | `eq`, `neq`, `in`, `nin`, `notEmpty`, `isEmpty` — it is a fixed ad-network enum | +| `users.lifecycle` | `eq` (one stage) and `in` (a list of stages) | + Combine multiple filters with `--logic and` (default) or `--logic or`. --- diff --git a/SKILLS.md b/SKILLS.md index 8bd4829..26cb13c 100644 --- a/SKILLS.md +++ b/SKILLS.md @@ -120,7 +120,7 @@ formo profiles search --order-by tx_count --order-dir desc --page 2 --size 20 -- | Property | Type | Description | |---|---|---| | `field` | `string` | **Canonical path** — `users.{attribute}` or a resource path below. A bare name like `net_worth_usd` is silently ignored by the API; identifier-in-path fields like `chains.1.balance` are rejected with a `400` | -| `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 | +| `op` | `string` | `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `contains`, `startsWith`, `endsWith`, `notEmpty` / `isEmpty` (value-less existence checks). Support is per field — see the operator table below. Long-form spellings (`equals`, `greater`, `includes`, …) are retired — the API rejects them with a `400` naming the token | | `value` | `any` | Value to compare against; must be a number on the `.balance` fields | | `chain_id` | `string` | _(optional on any resource filter)_ restrict to one chain; omit to match any | | `app_id` | `string` | _(required by `apps.balance`, and by `tokens.balance` with `scope: protocol`)_ e.g. `aave-v3` | @@ -142,6 +142,18 @@ User attributes for `users.{attribute}`: `net_worth_usd`, `volume`, `revenue`, `points`, `device`, `location`, `lifecycle`, `ens`, `farcaster`, and the other social handles. +**Operator support per field** — the vocabulary is shared, but each field implements a subset and the API rejects an unsupported pairing with a `400`: + +| Field class | Supported operators | +|---|---| +| `chains.balance`, `apps.balance`, `tokens.balance` | `eq`, `neq`, `gt`, `gte`, `lt`, `lte` (value must be a JSON number) | +| `labels.value` | comparison operators plus `contains` (case-insensitive) | +| Numeric profile metrics (`users.net_worth_usd`, `users.volume`, `users.revenue`, `users.points`) | comparison operators | +| Routable string attributes (`users.device`, `users.os`, `users.referrer`, `users.utm_*`, `users.click_id`, `first_*`/`last_*` variants) | full vocabulary; `contains`/`startsWith`/`endsWith` match case-sensitively | +| Social fields (`users.twitter`, `users.email`, `users.farcaster`, …) | `contains` (case-insensitive) and `notEmpty`; `startsWith`/`endsWith`/`isEmpty` are rejected | +| `users.paid_source` (and `first_`/`last_` variants) | `eq`, `neq`, `in`, `nin`, `notEmpty`, `isEmpty` — it is a fixed ad-network enum | +| `users.lifecycle` | `eq` (one stage) and `in` (a list of stages) | + Combine multiple filters with `--logic and` (default) or `--logic or`. --- @@ -233,6 +245,10 @@ formo analytics retention --filters '[{"field":"location","op":"eq","value":"US" Each pipe accepts pipe-specific params via `--params` (see each command's `--help`): e.g. `funnel` → `steps`, `window_seconds`, `funnel_type`, `group_by`, `limit`, `attribution`; `kpis` → `group_by`, `limit`; `top_*` → `limit`, `offset`. +On `kpis`, `top_*`, `revenue_*` and `volume_by_metric`, `--params '{"page_scope":"session"}'` widens a `page` filter from page-scoped metrics (the default) to the legacy session scope. + +All user-attribute, profile, social, lifecycle and resource predicates now go in the single `--filters` array, using the same canonical envelope with named qualifiers (`chain_id`, `app_id`, `token_address`, `scope`, `tag_id`). The retired per-family params — `socials`, `chain_filters`, `app_filters`, `token_filters`, `label_filters`, `profile_filters`, `lifecycle_filter` — are rejected with a `400` if passed through `--params`. + --- ## Project Alerts diff --git a/package.json b/package.json index b0e3a4a..cb1c505 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@formo/cli", - "version": "1.1.1", + "version": "1.2.0", "packageManager": "pnpm@11.1.2", "engines": { "node": ">=22.12" diff --git a/src/commands/profiles.ts b/src/commands/profiles.ts index 7d2346a..fbe4076 100644 --- a/src/commands/profiles.ts +++ b/src/commands/profiles.ts @@ -405,8 +405,13 @@ profiles.command('search', { 'apps.balance (+ "app_id", optional "chain_id"); ' + 'tokens.balance (+ "token_address", "scope":"any"|"protocol", "app_id" when scope is "protocol", optional "chain_id"); ' + 'labels.value (+ "tag_id", optional "chain_id"). ' + - 'op: eq, neq, gt, gte, lt, lte, in, nin, contains, notEmpty, isEmpty ' + - '(contains = substring, social fields only; notEmpty/isEmpty = value-less existence checks on string fields). ' + + 'op: eq, neq, gt, gte, lt, lte, in, nin, contains, startsWith, endsWith, notEmpty, isEmpty. ' + + 'Operator support is per field: the .balance fields take comparison operators only; ' + + 'contains works on routable string attributes (users.device, users.os, users.referrer, users.utm_*, users.click_id — case-sensitive), ' + + 'on social fields and on labels.value (case-insensitive); ' + + 'startsWith/endsWith are routable string attributes only; ' + + 'notEmpty/isEmpty are value-less existence checks on string fields; ' + + 'users.lifecycle takes only eq and in. ' + 'Retired and rejected with a 400: identifier-in-path fields (chains.1.balance, apps.uniswap-v3.balance, tokens.0x….balance, labels.vip), ' + 'the "appId" spelling, and long-form operators (equals, notEquals, greater, greaterOrEqual, less, lessOrEqual, notIn, includes).', ),