@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.
- package/README.md +328 -44
- package/dist/commands/alerts.d.ts +1 -0
- package/dist/commands/alerts.js +11 -30
- package/dist/commands/analytics.d.ts +25 -0
- package/dist/commands/analytics.js +153 -0
- package/dist/commands/contracts.d.ts +12 -0
- package/dist/commands/contracts.js +18 -10
- package/dist/commands/import.d.ts +4 -0
- package/dist/commands/import.js +9 -5
- package/dist/commands/profiles.d.ts +29 -2
- package/dist/commands/profiles.js +257 -25
- package/dist/commands/query.d.ts +2 -0
- package/dist/commands/query.js +25 -0
- package/dist/commands/segments.d.ts +4 -0
- package/dist/commands/segments.js +10 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +117 -132
- package/dist/lib/client.d.ts +26 -0
- package/dist/lib/client.js +30 -7
- package/package.json +11 -2
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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, {}>>;
|
package/dist/commands/import.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
7
|
-
|
|
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, {}>>;
|