@formo/cli 0.1.0 → 1.0.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.
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analytics = void 0;
4
+ exports.buildAnalyticsParams = buildAnalyticsParams;
5
+ exports.runAnalytics = runAnalytics;
6
+ const incur_1 = require("incur");
7
+ const client_1 = require("../lib/client");
8
+ exports.analytics = incur_1.Cli.create('analytics', {
9
+ description: 'Pre-built analytics query commands — KPIs, funnels, retention, revenue, and top-N breakdowns',
10
+ });
11
+ // The pre-built analytics pipes exposed at GET /v0/<pipe>. Each requires the
12
+ // query:read scope. Common params (date_from, date_to, filters) are shared;
13
+ // pipe-specific params (e.g. funnel `steps`, kpis `group_by`, `limit`) are
14
+ // passed through the generic --params JSON object.
15
+ const PIPES = [
16
+ { name: 'kpis', description: 'Traffic KPIs: visitors, pageviews, bounce rate, session duration' },
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' },
19
+ { name: 'flow', description: 'User path/flow analysis. --params: start_step / end_step (JSON {type,event,...}), global_filters, window_seconds, max_steps' },
20
+ { name: 'frequency', description: 'Engagement frequency distribution' },
21
+ { name: 'lifecycle', description: 'User lifecycle stages (new, returning, power, resurrected, churned)' },
22
+ { name: 'retention', description: 'Retention cohort analysis (params: id_type, event_type, event_name, min_users)' },
23
+ { name: 'revenue_overview', description: 'Revenue overview with optional breakdown (params: group_by, rank_by)' },
24
+ { name: 'revenue_by_metric', description: 'Revenue ranked by a metric column (params: metric_column, limit, offset)' },
25
+ { name: 'revenue_timeseries', description: 'Revenue over time (params: address)' },
26
+ { name: 'volume_by_metric', description: 'Trading volume ranked by a metric column (params: metric_column, limit, offset)' },
27
+ { name: 'top_chains', description: 'Top chains by activity (params: limit, offset)' },
28
+ { name: 'top_events', description: 'Top events by count (params: limit, offset, type)' },
29
+ { name: 'top_locations', description: 'Top locations (params: limit, offset)' },
30
+ { name: 'top_pages', description: 'Top pages by traffic (params: limit, offset, mode)' },
31
+ { name: 'top_sources', description: 'Top acquisition sources (params: metric_column, limit, offset)' },
32
+ { name: 'top_wallets', description: 'Top wallets by activity (params: limit, offset)' },
33
+ ];
34
+ // Keys --params is not allowed to set: they have dedicated, validated flags
35
+ // (--date-from/--date-to/--filters). Rejecting them prevents --params from
36
+ // silently overriding validated input or pushing an invalid `filters` value
37
+ // (e.g. a non-JSON string) over the wire. Both casings of the date keys are
38
+ // rejected so a stray camelCase key can't slip through unvalidated.
39
+ const RESERVED_PARAM_KEYS = new Set([
40
+ 'date_from',
41
+ 'date_to',
42
+ 'dateFrom',
43
+ 'dateTo',
44
+ 'filters',
45
+ ]);
46
+ /**
47
+ * Build the query-string params for an analytics pipe request.
48
+ *
49
+ * - `dateFrom`/`dateTo` map to the API's snake_case `date_from`/`date_to`.
50
+ * All pipes, including `funnel` and `flow`, use snake_case.
51
+ * - `filters` is a JSON array of `{ field, op, value }` objects, re-serialized
52
+ * as a JSON string (the pipe expects a JSON-encoded array in the query).
53
+ * - `params` is a JSON object of any pipe-specific params (e.g. funnel
54
+ * `steps`, kpis `group_by`, `limit`). Object/array values are JSON-encoded
55
+ * (pipes like funnel expect `steps` as a JSON-encoded string); primitives
56
+ * pass through unchanged. Reserved keys (the date/filters flags) are
57
+ * rejected, and the validated flags below always take precedence.
58
+ *
59
+ * Exported for unit testing.
60
+ */
61
+ function buildAnalyticsParams(options) {
62
+ const out = {};
63
+ // --params first, so the validated flags below override it.
64
+ if (options.params) {
65
+ let parsed;
66
+ try {
67
+ parsed = JSON.parse(options.params);
68
+ }
69
+ catch {
70
+ throw new Error('--params must be a valid JSON object');
71
+ }
72
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
73
+ throw new Error('--params must be a valid JSON object');
74
+ }
75
+ for (const [key, value] of Object.entries(parsed)) {
76
+ if (RESERVED_PARAM_KEYS.has(key)) {
77
+ throw new Error(`--params may not set "${key}" — use the --date-from/--date-to/--filters flags instead`);
78
+ }
79
+ if (value === null || value === undefined)
80
+ continue;
81
+ if (typeof value === 'object') {
82
+ out[key] = JSON.stringify(value);
83
+ }
84
+ else {
85
+ out[key] = value;
86
+ }
87
+ }
88
+ }
89
+ if (options.dateFrom)
90
+ out.date_from = options.dateFrom;
91
+ if (options.dateTo)
92
+ out.date_to = options.dateTo;
93
+ if (options.filters) {
94
+ let parsed;
95
+ try {
96
+ parsed = JSON.parse(options.filters);
97
+ }
98
+ catch {
99
+ throw new Error('--filters must be a valid JSON array of {field,op,value} objects');
100
+ }
101
+ if (!Array.isArray(parsed)) {
102
+ throw new Error('--filters must be a valid JSON array of {field,op,value} objects');
103
+ }
104
+ out.filters = JSON.stringify(parsed);
105
+ }
106
+ return out;
107
+ }
108
+ function runAnalytics(pipe, options) {
109
+ (0, client_1.requireApiKey)();
110
+ const client = (0, client_1.createClient)();
111
+ return client.get(`/v0/${pipe}`, { params: buildAnalyticsParams(options) });
112
+ }
113
+ const sharedOptions = incur_1.z.object({
114
+ dateFrom: incur_1.z
115
+ .string()
116
+ .optional()
117
+ .describe('Inclusive start date YYYY-MM-DD (default: 7 days before --date-to)'),
118
+ dateTo: incur_1.z
119
+ .string()
120
+ .optional()
121
+ .describe('Inclusive end date YYYY-MM-DD (default: today)'),
122
+ filters: incur_1.z
123
+ .string()
124
+ .optional()
125
+ .describe('JSON array of filter conditions: [{"field","op","value"}]. ' +
126
+ 'Use op "in"/"notIn" with a pipe-delimited value (e.g. "chrome|firefox").'),
127
+ params: incur_1.z
128
+ .string()
129
+ .optional()
130
+ .describe('JSON object of pipe-specific params merged into the query, e.g. ' +
131
+ '{"limit":10,"group_by":"device"} or funnel ' +
132
+ '{"steps":[{"type":"event","event":"page","name":"page::0","filters":[]}]}. ' +
133
+ 'May not set date_from/date_to/filters; use the dedicated --date-from/--date-to/--filters flags.'),
134
+ });
135
+ for (const pipe of PIPES) {
136
+ exports.analytics.command(pipe.name, {
137
+ description: pipe.description,
138
+ options: sharedOptions,
139
+ examples: [
140
+ {
141
+ description: `Get ${pipe.name} for the last 7 days (default range)`,
142
+ },
143
+ {
144
+ options: { dateFrom: '2026-04-01', dateTo: '2026-04-30' },
145
+ description: `Get ${pipe.name} for April 2026`,
146
+ },
147
+ ],
148
+ hint: 'Requires query:read scope on your API key. Pass pipe-specific params via --params.',
149
+ run({ options }) {
150
+ return runAnalytics(pipe.name, options);
151
+ },
152
+ });
153
+ }
@@ -8,11 +8,23 @@ export interface CreateContractOptions {
8
8
  abi: string;
9
9
  events: string;
10
10
  }
11
+ export declare function buildCreateContractBody(options: CreateContractOptions): {
12
+ address: string;
13
+ chain: number;
14
+ name: string;
15
+ abi: unknown;
16
+ events: unknown;
17
+ };
11
18
  export declare function createContractRun(options: CreateContractOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
12
19
  export interface UpdateContractOptions {
13
20
  name: string;
14
21
  abi: string;
15
22
  events: string;
16
23
  }
24
+ export declare function buildUpdateContractBody(options: UpdateContractOptions): {
25
+ name: string;
26
+ abi: unknown;
27
+ events: unknown;
28
+ };
17
29
  export declare function updateContractRun(chain: string, address: string, options: UpdateContractOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
18
30
  export declare function deleteContractRun(chain: string, address: string): Promise<import("axios").AxiosResponse<any, any, {}>>;
@@ -2,7 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.contracts = void 0;
4
4
  exports.listContractsRun = listContractsRun;
5
+ exports.buildCreateContractBody = buildCreateContractBody;
5
6
  exports.createContractRun = createContractRun;
7
+ exports.buildUpdateContractBody = buildUpdateContractBody;
6
8
  exports.updateContractRun = updateContractRun;
7
9
  exports.deleteContractRun = deleteContractRun;
8
10
  const incur_1 = require("incur");
@@ -24,9 +26,7 @@ exports.contracts.command('list', {
24
26
  return listContractsRun();
25
27
  },
26
28
  });
27
- function createContractRun(options) {
28
- (0, client_1.requireApiKey)();
29
- const client = (0, client_1.createClient)();
29
+ function buildCreateContractBody(options) {
30
30
  let parsedAbi;
31
31
  try {
32
32
  parsedAbi = JSON.parse(options.abi);
@@ -41,13 +41,18 @@ function createContractRun(options) {
41
41
  catch {
42
42
  throw new Error('--events must be valid JSON');
43
43
  }
44
- return client.post('/v0/contracts/', {
44
+ return {
45
45
  address: options.address,
46
46
  chain: options.chain,
47
47
  name: options.name,
48
48
  abi: parsedAbi,
49
49
  events: parsedEvents,
50
- });
50
+ };
51
+ }
52
+ function createContractRun(options) {
53
+ (0, client_1.requireApiKey)();
54
+ const client = (0, client_1.createClient)();
55
+ return client.post('/v0/contracts/', buildCreateContractBody(options));
51
56
  }
52
57
  exports.contracts.command('create', {
53
58
  description: 'Register a new smart contract to track',
@@ -75,9 +80,7 @@ exports.contracts.command('create', {
75
80
  return createContractRun(options);
76
81
  },
77
82
  });
78
- function updateContractRun(chain, address, options) {
79
- (0, client_1.requireApiKey)();
80
- const client = (0, client_1.createClient)();
83
+ function buildUpdateContractBody(options) {
81
84
  let parsedAbi;
82
85
  try {
83
86
  parsedAbi = JSON.parse(options.abi);
@@ -92,11 +95,16 @@ function updateContractRun(chain, address, options) {
92
95
  catch {
93
96
  throw new Error('--events must be valid JSON');
94
97
  }
95
- return client.put(`/v0/contracts/${encodeURIComponent(chain)}/${encodeURIComponent(address)}`, {
98
+ return {
96
99
  name: options.name,
97
100
  abi: parsedAbi,
98
101
  events: parsedEvents,
99
- });
102
+ };
103
+ }
104
+ function updateContractRun(chain, address, options) {
105
+ (0, client_1.requireApiKey)();
106
+ const client = (0, client_1.createClient)();
107
+ return client.put(`/v0/contracts/${encodeURIComponent(chain)}/${encodeURIComponent(address)}`, buildUpdateContractBody(options));
100
108
  }
101
109
  exports.contracts.command('update', {
102
110
  description: 'Update a tracked contract',
@@ -4,4 +4,8 @@ export interface ImportWalletsOptions {
4
4
  addresses: string;
5
5
  writeKey: string;
6
6
  }
7
+ export declare function buildImportBody(options: ImportWalletsOptions): {
8
+ addresses: any[];
9
+ writeKey: string;
10
+ };
7
11
  export declare function importWalletsRun(options: ImportWalletsOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
@@ -1,15 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.importCmd = void 0;
4
+ exports.buildImportBody = buildImportBody;
4
5
  exports.importWalletsRun = importWalletsRun;
5
6
  const incur_1 = require("incur");
6
7
  const client_1 = require("../lib/client");
7
8
  exports.importCmd = incur_1.Cli.create('import', {
8
9
  description: 'Import commands — bulk import wallet addresses into your project',
9
10
  });
10
- function importWalletsRun(options) {
11
- (0, client_1.requireApiKey)();
12
- const client = (0, client_1.createClient)();
11
+ function buildImportBody(options) {
13
12
  let parsedAddresses;
14
13
  try {
15
14
  parsedAddresses = JSON.parse(options.addresses);
@@ -20,10 +19,15 @@ function importWalletsRun(options) {
20
19
  catch {
21
20
  throw new Error('--addresses must be a valid JSON array of wallet address strings');
22
21
  }
23
- return client.post('/v0/import/', {
22
+ return {
24
23
  addresses: parsedAddresses,
25
24
  writeKey: options.writeKey,
26
- });
25
+ };
26
+ }
27
+ function importWalletsRun(options) {
28
+ (0, client_1.requireApiKey)();
29
+ const client = (0, client_1.createClient)();
30
+ return client.post('/v0/import/', buildImportBody(options));
27
31
  }
28
32
  exports.importCmd.command('wallets', {
29
33
  description: 'Bulk import wallet addresses into the project',
@@ -3,12 +3,39 @@ export declare const profiles: Cli.Cli<{}, undefined, undefined>;
3
3
  export declare function getProfileRun(address: string, expand?: string): Promise<import("axios").AxiosResponse<any, any, {}>>;
4
4
  export interface SearchProfilesOptions {
5
5
  address?: string;
6
- limit?: number;
7
- offset?: number;
6
+ page?: number;
7
+ size?: number;
8
8
  orderBy?: string;
9
9
  orderDir?: string;
10
10
  expand?: string;
11
11
  conditions?: string;
12
12
  logic?: 'and' | 'or';
13
13
  }
14
+ /**
15
+ * Parse and validate the --conditions JSON. Ensures it is an array of
16
+ * `{ field, op, value }` objects whose `field` is a typed path (e.g.
17
+ * `users.net_worth_usd`) — a bare name like `net_worth_usd` is silently
18
+ * dropped by the API, so it is rejected here. Exported for unit testing.
19
+ */
20
+ export declare function parseSearchConditions(raw: string): unknown[];
14
21
  export declare function searchProfilesRun(options: SearchProfilesOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
22
+ export interface UpdateProfileOptions {
23
+ properties: string;
24
+ }
25
+ export declare function buildUpdateProfileBody(options: UpdateProfileOptions): Record<string, unknown>;
26
+ export declare function updateProfileRun(address: string, options: UpdateProfileOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
27
+ export declare const profilesLabels: Cli.Cli<{}, undefined, undefined>;
28
+ export interface CreateProfileLabelOptions {
29
+ tagId?: string;
30
+ value?: string;
31
+ chainId?: string;
32
+ labels?: string;
33
+ }
34
+ export declare function buildCreateLabelBody(options: CreateProfileLabelOptions): unknown;
35
+ export declare function createProfileLabelRun(address: string, options: CreateProfileLabelOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
36
+ export interface DeleteProfileLabelOptions {
37
+ tagId: string;
38
+ chainId?: string;
39
+ }
40
+ export declare function buildDeleteLabelBody(options: DeleteProfileLabelOptions): Record<string, string>;
41
+ export declare function deleteProfileLabelRun(address: string, options: DeleteProfileLabelOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;