@formo/cli 0.1.0 → 0.2.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.
@@ -1,8 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.profiles = void 0;
3
+ exports.profilesLabels = exports.profiles = void 0;
4
4
  exports.getProfileRun = getProfileRun;
5
5
  exports.searchProfilesRun = searchProfilesRun;
6
+ exports.buildUpdateProfileBody = buildUpdateProfileBody;
7
+ exports.updateProfileRun = updateProfileRun;
8
+ exports.buildCreateLabelBody = buildCreateLabelBody;
9
+ exports.createProfileLabelRun = createProfileLabelRun;
10
+ exports.buildDeleteLabelBody = buildDeleteLabelBody;
11
+ exports.deleteProfileLabelRun = deleteProfileLabelRun;
6
12
  const incur_1 = require("incur");
7
13
  const client_1 = require("../lib/client");
8
14
  exports.profiles = incur_1.Cli.create('profiles', {
@@ -45,10 +51,10 @@ function searchProfilesRun(options) {
45
51
  const params = {};
46
52
  if (options.address)
47
53
  params.address = options.address;
48
- if (options.limit !== undefined)
49
- params.limit = options.limit;
50
- if (options.offset !== undefined)
51
- params.offset = options.offset;
54
+ if (options.page !== undefined)
55
+ params.page = options.page;
56
+ if (options.size !== undefined)
57
+ params.size = options.size;
52
58
  if (options.orderBy)
53
59
  params.order_by = options.orderBy;
54
60
  if (options.orderDir)
@@ -73,8 +79,8 @@ exports.profiles.command('search', {
73
79
  description: 'Search wallet profiles with optional filters',
74
80
  options: incur_1.z.object({
75
81
  address: incur_1.z.string().optional().describe('Filter by wallet address'),
76
- limit: incur_1.z.coerce.number().optional().describe('Max results to return'),
77
- offset: incur_1.z.coerce.number().optional().describe('Pagination offset'),
82
+ page: incur_1.z.coerce.number().optional().describe('Page number (1-indexed, default 1)'),
83
+ size: incur_1.z.coerce.number().optional().describe('Page size (default 100, max 1000)'),
78
84
  orderBy: incur_1.z
79
85
  .enum([
80
86
  'last_onchain',
@@ -103,15 +109,19 @@ exports.profiles.command('search', {
103
109
  .describe('Logic operator for combining conditions: "and" (default) or "or"'),
104
110
  }),
105
111
  examples: [
106
- { options: { limit: 10 }, description: 'List first 10 profiles' },
112
+ { options: { size: 10 }, description: 'List first 10 profiles' },
107
113
  {
108
- options: { orderBy: 'net_worth_usd', orderDir: 'desc', limit: 5 },
114
+ options: { orderBy: 'net_worth_usd', orderDir: 'desc', size: 5 },
109
115
  description: 'Top 5 profiles by net worth',
110
116
  },
117
+ {
118
+ options: { page: 2, size: 20 },
119
+ description: 'Get the second page of 20 profiles',
120
+ },
111
121
  {
112
122
  options: {
113
123
  conditions: '[{"field":"net_worth_usd","op":"gt","value":10000}]',
114
- limit: 20,
124
+ size: 20,
115
125
  },
116
126
  description: 'Search profiles with net worth > 10000',
117
127
  },
@@ -119,7 +129,7 @@ exports.profiles.command('search', {
119
129
  options: {
120
130
  conditions: '[{"field":"net_worth_usd","op":"gt","value":10000},{"field":"tx_count","op":"gt","value":50}]',
121
131
  logic: 'or',
122
- limit: 20,
132
+ size: 20,
123
133
  },
124
134
  description: 'Search profiles matching either condition',
125
135
  },
@@ -128,3 +138,163 @@ exports.profiles.command('search', {
128
138
  return searchProfilesRun(options);
129
139
  },
130
140
  });
141
+ function buildUpdateProfileBody(options) {
142
+ let body;
143
+ try {
144
+ body = JSON.parse(options.properties);
145
+ if (!body || typeof body !== 'object' || Array.isArray(body))
146
+ throw new Error('not an object');
147
+ }
148
+ catch {
149
+ throw new Error('--properties must be a JSON object of property keys');
150
+ }
151
+ if (Object.keys(body).length === 0) {
152
+ throw new Error('--properties must contain at least one key');
153
+ }
154
+ return body;
155
+ }
156
+ function updateProfileRun(address, options) {
157
+ (0, client_1.requireApiKey)();
158
+ const client = (0, client_1.createClient)();
159
+ return client.put(`/v0/profiles/${encodeURIComponent(address)}/properties`, buildUpdateProfileBody(options));
160
+ }
161
+ exports.profiles.command('update', {
162
+ description: 'Merge-update identity properties on a wallet profile',
163
+ args: incur_1.z.object({
164
+ address: incur_1.z.string().describe('Wallet address (0x... or ENS name)'),
165
+ }),
166
+ options: incur_1.z.object({
167
+ properties: incur_1.z
168
+ .string()
169
+ .describe('JSON object of properties to merge. Allowed keys: user_id, display_name, email, farcaster, discord, twitter, telegram, instagram, website, github, linkedin, facebook, tiktok, youtube, reddit, avatar, description, location, ens, lens, basenames, linea'),
170
+ }),
171
+ examples: [
172
+ {
173
+ args: { address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' },
174
+ options: {
175
+ properties: '{"display_name":"Vitalik","twitter":"VitalikButerin"}',
176
+ },
177
+ description: 'Set display name and Twitter handle',
178
+ },
179
+ {
180
+ args: { address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' },
181
+ options: { properties: '{"email":"alice@example.com"}' },
182
+ description: 'Set just the email',
183
+ },
184
+ ],
185
+ hint: 'Requires profiles:write scope on your API key. Only the listed keys are accepted; unknown keys are rejected.',
186
+ run({ args, options }) {
187
+ return updateProfileRun(args.address, options);
188
+ },
189
+ });
190
+ // ── Labels sub-resource ──
191
+ exports.profilesLabels = incur_1.Cli.create('labels', {
192
+ description: 'Manage labels on a wallet profile',
193
+ });
194
+ function buildCreateLabelBody(options) {
195
+ if (options.labels) {
196
+ try {
197
+ const parsed = JSON.parse(options.labels);
198
+ if (!Array.isArray(parsed) || parsed.length === 0)
199
+ throw new Error('not a non-empty array');
200
+ return parsed;
201
+ }
202
+ catch {
203
+ throw new Error('--labels must be a non-empty JSON array of UserLabelInput objects');
204
+ }
205
+ }
206
+ if (options.tagId) {
207
+ const single = { tag_id: options.tagId };
208
+ if (options.value)
209
+ single.value = options.value;
210
+ if (options.chainId)
211
+ single.chain_id = options.chainId;
212
+ return single;
213
+ }
214
+ throw new Error('Provide --tagId (single label) or --labels (batch JSON array)');
215
+ }
216
+ function createProfileLabelRun(address, options) {
217
+ (0, client_1.requireApiKey)();
218
+ const client = (0, client_1.createClient)();
219
+ return client.post(`/v0/profiles/${encodeURIComponent(address)}/labels`, buildCreateLabelBody(options));
220
+ }
221
+ exports.profilesLabels.command('create', {
222
+ description: 'Upsert one or more labels on a wallet profile',
223
+ args: incur_1.z.object({
224
+ address: incur_1.z.string().describe('Wallet address (0x... or ENS name)'),
225
+ }),
226
+ options: incur_1.z.object({
227
+ tagId: incur_1.z
228
+ .string()
229
+ .optional()
230
+ .describe('Label identifier (e.g. "vip", "airdrop_eligible")'),
231
+ value: incur_1.z.string().optional().describe('Optional label value (e.g. tier name, country code)'),
232
+ chainId: incur_1.z.string().optional().describe('Optional chain identifier the label applies to'),
233
+ labels: incur_1.z
234
+ .string()
235
+ .optional()
236
+ .describe('JSON array of UserLabelInput objects for batch upsert'),
237
+ }),
238
+ examples: [
239
+ {
240
+ args: { address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' },
241
+ options: { tagId: 'vip' },
242
+ description: 'Tag a wallet as VIP',
243
+ },
244
+ {
245
+ args: { address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' },
246
+ options: { tagId: 'tier', value: 'gold', chainId: '1' },
247
+ description: 'Apply a tiered label scoped to a chain',
248
+ },
249
+ {
250
+ args: { address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' },
251
+ options: { labels: '[{"tag_id":"vip"},{"tag_id":"airdrop_eligible","chain_id":"1"}]' },
252
+ description: 'Apply multiple labels in one call',
253
+ },
254
+ ],
255
+ hint: 'Requires profiles:write scope on your API key.',
256
+ run({ args, options }) {
257
+ return createProfileLabelRun(args.address, options);
258
+ },
259
+ });
260
+ function buildDeleteLabelBody(options) {
261
+ if (!options.tagId) {
262
+ throw new Error('--tagId is required');
263
+ }
264
+ const body = { tag_id: options.tagId };
265
+ if (options.chainId)
266
+ body.chain_id = options.chainId;
267
+ return body;
268
+ }
269
+ function deleteProfileLabelRun(address, options) {
270
+ (0, client_1.requireApiKey)();
271
+ const client = (0, client_1.createClient)();
272
+ return client.delete(`/v0/profiles/${encodeURIComponent(address)}/labels`, { data: buildDeleteLabelBody(options) });
273
+ }
274
+ exports.profilesLabels.command('delete', {
275
+ description: 'Delete a label from a wallet profile',
276
+ args: incur_1.z.object({
277
+ address: incur_1.z.string().describe('Wallet address (0x... or ENS name)'),
278
+ }),
279
+ options: incur_1.z.object({
280
+ tagId: incur_1.z.string().describe('Label identifier to delete'),
281
+ chainId: incur_1.z.string().optional().describe('Optional chain identifier to scope the deletion'),
282
+ }),
283
+ examples: [
284
+ {
285
+ args: { address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' },
286
+ options: { tagId: 'vip' },
287
+ description: 'Remove the vip label',
288
+ },
289
+ {
290
+ args: { address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' },
291
+ options: { tagId: 'tier', chainId: '1' },
292
+ description: 'Remove a chain-scoped label',
293
+ },
294
+ ],
295
+ hint: 'Requires profiles:write scope on your API key.',
296
+ run({ args, options }) {
297
+ return deleteProfileLabelRun(args.address, options);
298
+ },
299
+ });
300
+ exports.profiles.command(exports.profilesLabels);
@@ -1 +1,3 @@
1
+ import { Cli } from 'incur';
2
+ export declare const query: Cli.Cli<{}, undefined, undefined>;
1
3
  export declare function queryRunRun(sql: string): Promise<import("axios").AxiosResponse<any, any, {}>>;
@@ -1,9 +1,34 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.query = void 0;
3
4
  exports.queryRunRun = queryRunRun;
5
+ const incur_1 = require("incur");
4
6
  const client_1 = require("../lib/client");
7
+ exports.query = incur_1.Cli.create('query', {
8
+ description: 'SQL analytics query commands',
9
+ });
5
10
  function queryRunRun(sql) {
6
11
  (0, client_1.requireApiKey)();
7
12
  const client = (0, client_1.createClient)();
8
13
  return client.post('/v0/query/', { query: sql });
9
14
  }
15
+ exports.query.command('run', {
16
+ description: 'Run a SQL query against your Formo analytics data',
17
+ args: incur_1.z.object({
18
+ sql: incur_1.z.string().describe('SQL query string to execute'),
19
+ }),
20
+ examples: [
21
+ {
22
+ args: { sql: 'SELECT count(*) FROM events' },
23
+ description: 'Count all events',
24
+ },
25
+ {
26
+ args: { sql: 'SELECT address, net_worth_usd FROM wallet_profiles ORDER BY net_worth_usd DESC LIMIT 10' },
27
+ description: 'Top 10 wallets by net worth',
28
+ },
29
+ ],
30
+ hint: 'Requires query:read scope on your API key.',
31
+ run({ args }) {
32
+ return queryRunRun(args.sql);
33
+ },
34
+ });
@@ -5,5 +5,9 @@ export interface CreateSegmentOptions {
5
5
  title: string;
6
6
  filterSets: string;
7
7
  }
8
+ export declare function buildCreateSegmentBody(options: CreateSegmentOptions): {
9
+ title: string;
10
+ filterSets: unknown;
11
+ };
8
12
  export declare function createSegmentRun(options: CreateSegmentOptions): Promise<import("axios").AxiosResponse<any, any, {}>>;
9
13
  export declare function deleteSegmentRun(segmentId: string): Promise<import("axios").AxiosResponse<any, any, {}>>;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.segments = void 0;
4
4
  exports.listSegmentsRun = listSegmentsRun;
5
+ exports.buildCreateSegmentBody = buildCreateSegmentBody;
5
6
  exports.createSegmentRun = createSegmentRun;
6
7
  exports.deleteSegmentRun = deleteSegmentRun;
7
8
  const incur_1 = require("incur");
@@ -23,9 +24,7 @@ exports.segments.command('list', {
23
24
  return listSegmentsRun();
24
25
  },
25
26
  });
26
- function createSegmentRun(options) {
27
- (0, client_1.requireApiKey)();
28
- const client = (0, client_1.createClient)();
27
+ function buildCreateSegmentBody(options) {
29
28
  let parsedFilterSets;
30
29
  try {
31
30
  parsedFilterSets = JSON.parse(options.filterSets);
@@ -33,10 +32,15 @@ function createSegmentRun(options) {
33
32
  catch {
34
33
  throw new Error('--filterSets must be a valid JSON array');
35
34
  }
36
- return client.post('/v0/segments/', {
35
+ return {
37
36
  title: options.title,
38
37
  filterSets: parsedFilterSets,
39
- });
38
+ };
39
+ }
40
+ function createSegmentRun(options) {
41
+ (0, client_1.requireApiKey)();
42
+ const client = (0, client_1.createClient)();
43
+ return client.post('/v0/segments/', buildCreateSegmentBody(options));
40
44
  }
41
45
  exports.segments.command('create', {
42
46
  description: 'Create a new user segment',
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { Cli } from 'incur';
2
+ import { Cli } from "incur";
3
3
  declare const cli: Cli.Cli<{}, undefined, undefined>;
4
4
  export default cli;