diff --git a/README.md b/README.md index b5db10c..970902c 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Or set the `FORMO_API_KEY` environment variable — it takes precedence over the export FORMO_API_KEY=formo_abc123 ``` -Get your API key from `Settings → API Keys` in the [Formo dashboard](https://app.formo.so). +Get your API key from `Settings → API` in the [Formo dashboard](https://app.formo.so). For local development or proxying, override API hosts with: @@ -208,7 +208,7 @@ Get a single alert by ID. ```bash formo alerts create --name "High value tx" --trigger-type event \ - --trigger-filters '[{"name":"event","operator":"equals","value":"transaction"}]' \ + --trigger-filters '[{"name":"event","operator":"eq","value":"transaction"}]' \ --recipient '[{"type":"email","value":["alerts@myapp.com"]}]' ``` @@ -401,7 +401,7 @@ Pre-built analytics pipes — the same data that powers the Formo dashboard — |---|---| | `--date-from` | Inclusive start date `YYYY-MM-DD` (default: 7 days before `--date-to`) | | `--date-to` | Inclusive end date `YYYY-MM-DD` (default: today) | -| `--filters` | JSON array of `[{field,op,value}]`. Use `in`/`notIn` with a pipe-delimited value (e.g. `"chrome\|firefox"`) | +| `--filters` | JSON array of `[{field,op,value}]`. Use `in`/`nin` with a pipe-delimited value (e.g. `"chrome\|firefox"`) | | `--params` | JSON object of pipe-specific params merged into the query (e.g. `{"limit":10,"group_by":"device"}`) | ```bash @@ -409,7 +409,7 @@ formo analytics kpis formo analytics kpis --date-from 2026-04-01 --date-to 2026-04-30 --params '{"group_by":"device"}' 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}' formo analytics top_wallets --date-from 2026-04-01 --date-to 2026-04-30 --params '{"limit":10}' -formo analytics retention --filters '[{"field":"location","op":"equals","value":"US"}]' +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`. @@ -465,7 +465,7 @@ formo events ingest --event '{"type":"track","channel":"cli","version":"1","anon | Field | Type | Description | |---|---|---| | `field` | `string` | Typed path (see prefixes below) | -| `op` | `string` | `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `nin` | +| `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 deprecated aliases the API still accepts | | `value` | `any` | Value to compare against | | `scope` | `string` | _(token filters only)_ `any` or `protocol` | | `appId` | `string` | _(token filters with `scope: protocol`)_ e.g. `aave-v3` | diff --git a/SKILLS.md b/SKILLS.md index 0657d32..8fe8d5a 100644 --- a/SKILLS.md +++ b/SKILLS.md @@ -111,7 +111,7 @@ formo profiles search --order-by tx_count --order-dir desc --page 2 --size 20 -- | Property | Type | Description | |---|---|---| | `field` | `string` | **Typed path** — a bare name like `net_worth_usd` is silently ignored by the API | -| `op` | `string` | `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `nin` | +| `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 deprecated aliases the API still accepts | | `value` | `any` | Value to compare against | | `scope` | `string` | _(token filters only)_ `any` or `protocol` | | `appId` | `string` | _(token filters with `scope: protocol`)_ e.g. `aave-v3` | @@ -194,7 +194,7 @@ formo analytics [options] |---|---| | `--date-from` | Inclusive start date `YYYY-MM-DD` (default: 7 days before `--date-to`) | | `--date-to` | Inclusive end date `YYYY-MM-DD` (default: today) | -| `--filters` | JSON array of `[{field,op,value}]`. Use `in`/`notIn` with a pipe-delimited value (e.g. `"chrome\|firefox"`) | +| `--filters` | JSON array of `[{field,op,value}]`. Use `in`/`nin` with a pipe-delimited value (e.g. `"chrome\|firefox"`) | | `--params` | JSON object of pipe-specific params merged into the query (e.g. `{"limit":10,"group_by":"device"}`) | **Examples:** @@ -212,7 +212,7 @@ formo analytics funnel --date-from 2026-04-01 --date-to 2026-04-30 --params '{"s formo analytics top_wallets --date-from 2026-04-01 --date-to 2026-04-30 --params '{"limit":10}' # Retention filtered to US visitors -formo analytics retention --filters '[{"field":"location","op":"equals","value":"US"}]' +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`, `breakdown`; `kpis` → `group_by`, `limit`; `top_*` → `limit`, `offset`. @@ -266,7 +266,7 @@ formo alerts create --name "High value tx" --trigger-type event # Create an alert with filters and recipients formo alerts create --name "Whale alert" --trigger-type event \ - --trigger-filters '[{"name":"revenue","operator":"greater_than","value":"100000"}]' \ + --trigger-filters '[{"name":"revenue","operator":"gt","value":"100000"}]' \ --recipient '[{"type":"webhook","value":["https://hooks.example.com/formo"]}]' ``` diff --git a/src/commands/analytics.ts b/src/commands/analytics.ts index de5a970..bc5c87f 100644 --- a/src/commands/analytics.ts +++ b/src/commands/analytics.ts @@ -139,7 +139,7 @@ const sharedOptions = z.object({ .optional() .describe( 'JSON array of filter conditions: [{"field","op","value"}]. ' + - 'Use op "in"/"notIn" with a pipe-delimited value (e.g. "chrome|firefox").', + 'Use op "in"/"nin" with a pipe-delimited value (e.g. "chrome|firefox").', ), params: z .string() diff --git a/src/commands/profiles.ts b/src/commands/profiles.ts index d3985f2..f59b16c 100644 --- a/src/commands/profiles.ts +++ b/src/commands/profiles.ts @@ -256,7 +256,9 @@ profiles.command('search', { 'Chains: chains.balance or chains.{chain_id}.balance. ' + 'Apps: apps.{app_id}.balance. Tokens: tokens.{address}.balance ' + '(optional "scope":"any"|"protocol" + "appId"). Labels: labels.{tag_id}. ' + - 'op: eq, neq, gt, gte, lt, lte, in, nin.', + '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). ' + + 'Long-form spellings (equals, notEquals, greater, greaterOrEqual, less, lessOrEqual, notIn, includes) are deprecated aliases the API still accepts.', ), logic: z .enum(['and', 'or']) diff --git a/src/index.ts b/src/index.ts index e78c625..0bb7a1b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,7 +24,7 @@ function loginGuide(): string { color.boldGreen("How to get your API key:"), "", ` ${color.white("1.")} Go to ${color.cyan(DASHBOARD_URL)}`, - ` ${color.white("2.")} Navigate to ${color.bold("Settings")} → ${color.bold("API Keys")}`, + ` ${color.white("2.")} Navigate to ${color.bold("Settings")} → ${color.bold("API")}`, ` ${color.white("3.")} Click ${color.bold('"Create API Key"')} and copy the key`, ` ${color.white("4.")} Run:`, "", diff --git a/test/commands/analytics.test.ts b/test/commands/analytics.test.ts index 5844f0b..f63f2fa 100644 --- a/test/commands/analytics.test.ts +++ b/test/commands/analytics.test.ts @@ -21,10 +21,10 @@ describe('commands/analytics', function () { it('re-serializes a valid filters JSON array as a string', function () { const params = buildAnalyticsParams({ - filters: '[{"field":"location","op":"equals","value":"US"}]', + filters: '[{"field":"location","op":"eq","value":"US"}]', }); expect(params.filters).to.equal( - '[{"field":"location","op":"equals","value":"US"}]', + '[{"field":"location","op":"eq","value":"US"}]', ); }); diff --git a/test/commands/bodyBuilders.test.ts b/test/commands/bodyBuilders.test.ts index 29c39fb..2102b95 100644 --- a/test/commands/bodyBuilders.test.ts +++ b/test/commands/bodyBuilders.test.ts @@ -38,10 +38,10 @@ describe('commands / body builders', function () { const body = buildAlertBody({ name: 'x', triggerType: 'event', - triggerFilters: '[{"name":"event","operator":"equals","value":"transaction"}]', + triggerFilters: '[{"name":"event","operator":"eq","value":"transaction"}]', }); expect(body.trigger_filters).to.deep.equal([ - { name: 'event', operator: 'equals', value: 'transaction' }, + { name: 'event', operator: 'eq', value: 'transaction' }, ]); }); @@ -386,6 +386,13 @@ describe('commands / body builders', function () { ).to.not.throw(); }); + it('passes deprecated long-form op aliases through verbatim (server folds them to canonical)', function () { + const conds = parseSearchConditions( + '[{"field":"users.net_worth_usd","op":"greater","value":10000}]', + ); + expect((conds[0] as { op: string }).op).to.equal('greater'); + }); + it('rejects a bare (untyped) field — the silent-failure footgun', function () { expect(() => parseSearchConditions('[{"field":"net_worth_usd","op":"gt","value":10000}]'),