@formo/cli 0.2.0 → 1.0.2
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 +154 -40
- package/dist/commands/alerts.d.ts +16 -3
- package/dist/commands/alerts.js +94 -24
- package/dist/commands/analytics.d.ts +25 -0
- package/dist/commands/analytics.js +153 -0
- package/dist/commands/boards.d.ts +11 -2
- package/dist/commands/boards.js +49 -17
- package/dist/commands/charts.d.ts +30 -3
- package/dist/commands/charts.js +297 -33
- package/dist/commands/contracts.d.ts +17 -13
- package/dist/commands/contracts.js +165 -53
- package/dist/commands/events.d.ts +9 -0
- package/dist/commands/events.js +64 -0
- package/dist/commands/import.d.ts +8 -4
- package/dist/commands/import.js +33 -11
- package/dist/commands/profiles.d.ts +35 -2
- package/dist/commands/profiles.js +260 -32
- package/dist/commands/query.js +5 -1
- package/dist/commands/segments.d.ts +6 -2
- package/dist/commands/segments.js +20 -6
- package/dist/index.js +15 -3
- package/dist/lib/client.d.ts +10 -1
- package/dist/lib/client.js +27 -4
- package/dist/lib/config.js +10 -0
- package/dist/lib/json.d.ts +5 -0
- package/dist/lib/json.js +54 -0
- package/dist/lib/sql.d.ts +31 -0
- package/dist/lib/sql.js +151 -0
- package/package.json +3 -3
|
@@ -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
|
+
}
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import { Cli } from 'incur';
|
|
2
2
|
export declare const boards: Cli.Cli<{}, undefined, undefined>;
|
|
3
|
-
export
|
|
3
|
+
export interface PaginationOptions {
|
|
4
|
+
page?: number;
|
|
5
|
+
size?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function listBoardsRun(options?: PaginationOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
4
8
|
export declare function getBoardRun(boardId: string): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
5
9
|
export interface CreateBoardOptions {
|
|
6
|
-
|
|
10
|
+
title?: string;
|
|
11
|
+
name?: string;
|
|
7
12
|
description?: string;
|
|
13
|
+
isPublic?: boolean;
|
|
8
14
|
}
|
|
15
|
+
export declare function buildBoardBody(options: CreateBoardOptions | UpdateBoardOptions): Record<string, unknown>;
|
|
9
16
|
export declare function createBoardRun(options: CreateBoardOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
10
17
|
export interface UpdateBoardOptions {
|
|
18
|
+
title?: string;
|
|
11
19
|
name?: string;
|
|
12
20
|
description?: string;
|
|
21
|
+
isPublic?: boolean;
|
|
13
22
|
}
|
|
14
23
|
export declare function updateBoardRun(boardId: string, options: UpdateBoardOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
15
24
|
export declare function deleteBoardRun(boardId: string): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
package/dist/commands/boards.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.boards = void 0;
|
|
4
4
|
exports.listBoardsRun = listBoardsRun;
|
|
5
5
|
exports.getBoardRun = getBoardRun;
|
|
6
|
+
exports.buildBoardBody = buildBoardBody;
|
|
6
7
|
exports.createBoardRun = createBoardRun;
|
|
7
8
|
exports.updateBoardRun = updateBoardRun;
|
|
8
9
|
exports.deleteBoardRun = deleteBoardRun;
|
|
@@ -11,18 +12,30 @@ const client_1 = require("../lib/client");
|
|
|
11
12
|
exports.boards = incur_1.Cli.create('boards', {
|
|
12
13
|
description: 'Dashboard board commands — create, list, update, and delete boards',
|
|
13
14
|
});
|
|
15
|
+
function buildPaginationParams(options = {}) {
|
|
16
|
+
const params = {};
|
|
17
|
+
if (options.page !== undefined)
|
|
18
|
+
params.page = options.page;
|
|
19
|
+
if (options.size !== undefined)
|
|
20
|
+
params.size = options.size;
|
|
21
|
+
return params;
|
|
22
|
+
}
|
|
14
23
|
// ── List boards ──
|
|
15
|
-
function listBoardsRun() {
|
|
24
|
+
function listBoardsRun(options = {}) {
|
|
16
25
|
(0, client_1.requireApiKey)();
|
|
17
26
|
const client = (0, client_1.createClient)();
|
|
18
|
-
return client.get('/v0/boards/');
|
|
27
|
+
return client.get('/v0/boards/', { params: buildPaginationParams(options) });
|
|
19
28
|
}
|
|
20
29
|
exports.boards.command('list', {
|
|
21
30
|
description: 'List all boards for the project',
|
|
31
|
+
options: incur_1.z.object({
|
|
32
|
+
page: incur_1.z.coerce.number().optional().describe('Page number (1-indexed, default 1)'),
|
|
33
|
+
size: incur_1.z.coerce.number().optional().describe('Page size (default 100, max 200)'),
|
|
34
|
+
}),
|
|
22
35
|
examples: [{ description: 'List all dashboard boards' }],
|
|
23
36
|
hint: 'Requires boards:read scope on your API key.',
|
|
24
|
-
run() {
|
|
25
|
-
return listBoardsRun();
|
|
37
|
+
run({ options }) {
|
|
38
|
+
return listBoardsRun(options);
|
|
26
39
|
},
|
|
27
40
|
});
|
|
28
41
|
// ── Get a single board ──
|
|
@@ -44,28 +57,46 @@ exports.boards.command('get', {
|
|
|
44
57
|
return getBoardRun(args.boardId);
|
|
45
58
|
},
|
|
46
59
|
});
|
|
60
|
+
function buildBoardBody(options) {
|
|
61
|
+
const title = options.title ?? options.name;
|
|
62
|
+
const body = {};
|
|
63
|
+
if (title !== undefined) {
|
|
64
|
+
if (!title)
|
|
65
|
+
throw new Error('--title must not be empty');
|
|
66
|
+
body.title = title;
|
|
67
|
+
}
|
|
68
|
+
if (options.description !== undefined) {
|
|
69
|
+
body.description = options.description;
|
|
70
|
+
}
|
|
71
|
+
if (options.isPublic !== undefined) {
|
|
72
|
+
body.isPublic = options.isPublic;
|
|
73
|
+
}
|
|
74
|
+
return body;
|
|
75
|
+
}
|
|
47
76
|
function createBoardRun(options) {
|
|
48
77
|
(0, client_1.requireApiKey)();
|
|
49
78
|
const client = (0, client_1.createClient)();
|
|
50
|
-
const body =
|
|
51
|
-
if (
|
|
52
|
-
|
|
79
|
+
const body = buildBoardBody(options);
|
|
80
|
+
if (!body.title) {
|
|
81
|
+
throw new Error('Provide --title for the board name');
|
|
53
82
|
}
|
|
54
83
|
return client.post('/v0/boards/', body);
|
|
55
84
|
}
|
|
56
85
|
exports.boards.command('create', {
|
|
57
86
|
description: 'Create a new dashboard board',
|
|
58
87
|
options: incur_1.z.object({
|
|
59
|
-
|
|
88
|
+
title: incur_1.z.string().optional().describe('Board title'),
|
|
89
|
+
name: incur_1.z.string().optional().describe('Deprecated alias for --title').meta({ deprecated: true }),
|
|
60
90
|
description: incur_1.z.string().optional().describe('Board description'),
|
|
91
|
+
isPublic: incur_1.z.boolean().optional().describe('Whether the board is publicly viewable'),
|
|
61
92
|
}),
|
|
62
93
|
examples: [
|
|
63
94
|
{
|
|
64
|
-
options: {
|
|
95
|
+
options: { title: 'KPI Dashboard' },
|
|
65
96
|
description: 'Create a board',
|
|
66
97
|
},
|
|
67
98
|
{
|
|
68
|
-
options: {
|
|
99
|
+
options: { title: 'Revenue Metrics', description: 'Weekly revenue tracking' },
|
|
69
100
|
description: 'Create a board with description',
|
|
70
101
|
},
|
|
71
102
|
],
|
|
@@ -77,11 +108,10 @@ exports.boards.command('create', {
|
|
|
77
108
|
function updateBoardRun(boardId, options) {
|
|
78
109
|
(0, client_1.requireApiKey)();
|
|
79
110
|
const client = (0, client_1.createClient)();
|
|
80
|
-
const body =
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
body.description = options.description;
|
|
111
|
+
const body = buildBoardBody(options);
|
|
112
|
+
if (Object.keys(body).length === 0) {
|
|
113
|
+
throw new Error('Provide at least one of --title, --description, or --is-public');
|
|
114
|
+
}
|
|
85
115
|
return client.patch(`/v0/boards/${encodeURIComponent(boardId)}`, body);
|
|
86
116
|
}
|
|
87
117
|
exports.boards.command('update', {
|
|
@@ -90,13 +120,15 @@ exports.boards.command('update', {
|
|
|
90
120
|
boardId: incur_1.z.string().describe('Board ID to update'),
|
|
91
121
|
}),
|
|
92
122
|
options: incur_1.z.object({
|
|
93
|
-
|
|
123
|
+
title: incur_1.z.string().optional().describe('New board title'),
|
|
124
|
+
name: incur_1.z.string().optional().describe('Deprecated alias for --title').meta({ deprecated: true }),
|
|
94
125
|
description: incur_1.z.string().optional().describe('New board description'),
|
|
126
|
+
isPublic: incur_1.z.boolean().optional().describe('Whether the board is publicly viewable'),
|
|
95
127
|
}),
|
|
96
128
|
examples: [
|
|
97
129
|
{
|
|
98
130
|
args: { boardId: 'board_abc123' },
|
|
99
|
-
options: {
|
|
131
|
+
options: { title: 'Renamed Board' },
|
|
100
132
|
description: 'Rename a board',
|
|
101
133
|
},
|
|
102
134
|
],
|
|
@@ -1,7 +1,34 @@
|
|
|
1
1
|
import { Cli } from 'incur';
|
|
2
2
|
export declare const charts: Cli.Cli<{}, undefined, undefined>;
|
|
3
|
-
export
|
|
3
|
+
export interface PaginationOptions {
|
|
4
|
+
page?: number;
|
|
5
|
+
size?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ChartBodyOptions {
|
|
8
|
+
body?: string;
|
|
9
|
+
query?: string;
|
|
10
|
+
chartType?: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
xAxis?: string;
|
|
14
|
+
yAxis?: string;
|
|
15
|
+
groupBy?: string;
|
|
16
|
+
steps?: string;
|
|
17
|
+
settings?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function buildChartBody(options: ChartBodyOptions): Record<string, unknown>;
|
|
20
|
+
export declare function listChartsRun(boardId: string, options?: PaginationOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
21
|
+
export declare function listChartSummariesRun(boardId: string, options?: PaginationOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
4
22
|
export declare function getChartRun(boardId: string, chartId: string): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
5
|
-
export
|
|
6
|
-
|
|
23
|
+
export interface QueryChartOptions {
|
|
24
|
+
dateFrom: string;
|
|
25
|
+
dateTo: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function queryChartRun(boardId: string, chartId: string, options: QueryChartOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
28
|
+
export declare function createChartRun(boardId: string, input: string | ChartBodyOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
29
|
+
export declare function updateChartRun(boardId: string, chartId: string, input: string | ChartBodyOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
30
|
+
export declare function moveChartRun(boardId: string, chartId: string, targetBoardId: string): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
31
|
+
export declare function normalizeDuplicateChartResponse(result: unknown): unknown;
|
|
32
|
+
export declare function duplicateChartRun(boardId: string, chartId: string): Promise<unknown>;
|
|
33
|
+
export declare function reorderChartsRun(boardId: string, chartIds: string): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
7
34
|
export declare function deleteChartRun(boardId: string, chartId: string): Promise<import("axios").AxiosResponse<any, any, {}>>;
|