@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
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const incur_1 = require("incur");
|
|
5
5
|
const alerts_1 = require("./commands/alerts");
|
|
6
|
+
const analytics_1 = require("./commands/analytics");
|
|
6
7
|
const boards_1 = require("./commands/boards");
|
|
7
8
|
const charts_1 = require("./commands/charts");
|
|
8
9
|
const contracts_1 = require("./commands/contracts");
|
|
@@ -12,213 +13,217 @@ const query_1 = require("./commands/query");
|
|
|
12
13
|
const segments_1 = require("./commands/segments");
|
|
13
14
|
const config_1 = require("./lib/config");
|
|
14
15
|
const ui_1 = require("./lib/ui");
|
|
15
|
-
const DASHBOARD_URL =
|
|
16
|
-
const DOCS_URL =
|
|
17
|
-
const API_BASE_URL =
|
|
16
|
+
const DASHBOARD_URL = "https://app.formo.so";
|
|
17
|
+
const DOCS_URL = "https://docs.formo.so";
|
|
18
|
+
const API_BASE_URL = "https://api.formo.so";
|
|
18
19
|
function loginGuide() {
|
|
19
20
|
return [
|
|
20
|
-
|
|
21
|
-
ui_1.color.boldGreen(
|
|
22
|
-
|
|
23
|
-
` ${ui_1.color.white(
|
|
24
|
-
` ${ui_1.color.white(
|
|
25
|
-
` ${ui_1.color.white(
|
|
26
|
-
` ${ui_1.color.white(
|
|
27
|
-
|
|
28
|
-
` ${ui_1.color.green(
|
|
29
|
-
|
|
21
|
+
"",
|
|
22
|
+
ui_1.color.boldGreen("How to get your API key:"),
|
|
23
|
+
"",
|
|
24
|
+
` ${ui_1.color.white("1.")} Go to ${ui_1.color.cyan(DASHBOARD_URL)}`,
|
|
25
|
+
` ${ui_1.color.white("2.")} Navigate to ${ui_1.color.bold("Settings")} → ${ui_1.color.bold("API Keys")}`,
|
|
26
|
+
` ${ui_1.color.white("3.")} Click ${ui_1.color.bold('"Create API Key"')} and copy the key`,
|
|
27
|
+
` ${ui_1.color.white("4.")} Run:`,
|
|
28
|
+
"",
|
|
29
|
+
` ${ui_1.color.green("formo login <formo_your_api_key_here>")}`,
|
|
30
|
+
"",
|
|
30
31
|
ui_1.color.dim(` Or set the environment variable:`),
|
|
31
|
-
|
|
32
|
-
` ${ui_1.color.green(
|
|
33
|
-
|
|
32
|
+
"",
|
|
33
|
+
` ${ui_1.color.green("export FORMO_API_KEY=formo_your_api_key_here")}`,
|
|
34
|
+
"",
|
|
34
35
|
ui_1.color.dim(` Docs: ${DOCS_URL}`),
|
|
35
|
-
|
|
36
|
-
].join(
|
|
36
|
+
"",
|
|
37
|
+
].join("\n");
|
|
37
38
|
}
|
|
38
39
|
async function validateAndFetchWorkspace(apiKey) {
|
|
39
40
|
try {
|
|
40
41
|
const res = await fetch(`${API_BASE_URL}/api/validate-api-key`, {
|
|
41
|
-
method:
|
|
42
|
-
headers: {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { "Content-Type": "application/json" },
|
|
43
44
|
body: JSON.stringify({ apiKey }),
|
|
44
45
|
});
|
|
45
46
|
if (!res.ok)
|
|
46
47
|
return null;
|
|
47
48
|
const body = (await res.json());
|
|
48
|
-
if (!body.
|
|
49
|
+
if (!body.validated)
|
|
49
50
|
return null;
|
|
50
51
|
return {
|
|
51
|
-
workspace: body.
|
|
52
|
-
projectId: body.
|
|
52
|
+
workspace: body.details,
|
|
53
|
+
projectId: body.scopes?.project_id ?? "",
|
|
53
54
|
};
|
|
54
55
|
}
|
|
55
56
|
catch {
|
|
56
57
|
return null;
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
|
-
const cli = incur_1.Cli.create(
|
|
60
|
-
version:
|
|
61
|
-
description:
|
|
60
|
+
const cli = incur_1.Cli.create("formo", {
|
|
61
|
+
version: "0.2.0",
|
|
62
|
+
description: "Formo API CLI — Web3 analytics from the terminal",
|
|
62
63
|
sync: {
|
|
63
64
|
suggestions: [
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
65
|
+
"get the profile for wallet 0xabc",
|
|
66
|
+
"search profiles with net worth > 10000",
|
|
67
|
+
"run a SQL query on my analytics data",
|
|
68
|
+
"show traffic KPIs for the last 7 days",
|
|
69
|
+
"get the conversion funnel for the last month",
|
|
70
|
+
"list the top wallets by activity",
|
|
71
|
+
"search profiles ordered by last_onchain desc",
|
|
72
|
+
"list all project alerts",
|
|
73
|
+
"create an alert for high-value transactions",
|
|
74
|
+
"list charts in a board",
|
|
75
|
+
"list all tracked contracts",
|
|
76
|
+
"register a new smart contract",
|
|
77
|
+
"list user segments",
|
|
78
|
+
"import wallet addresses",
|
|
76
79
|
],
|
|
77
80
|
},
|
|
78
81
|
});
|
|
79
82
|
// ── login ──
|
|
80
|
-
cli.command(
|
|
81
|
-
description:
|
|
83
|
+
cli.command("login", {
|
|
84
|
+
description: "Authenticate with your Formo API key",
|
|
82
85
|
args: incur_1.z.object({
|
|
83
|
-
apiKey: incur_1.z.string().optional().describe(
|
|
86
|
+
apiKey: incur_1.z.string().optional().describe("Your formo_ API key"),
|
|
84
87
|
}),
|
|
85
88
|
options: incur_1.z.object({}),
|
|
86
|
-
examples: [
|
|
87
|
-
{ args: { apiKey: 'formo_abc123' }, description: 'Save API key' },
|
|
88
|
-
],
|
|
89
|
+
examples: [{ args: { apiKey: "formo_abc123" }, description: "Save API key" }],
|
|
89
90
|
async run({ args }) {
|
|
90
91
|
// No key provided → show guide
|
|
91
92
|
if (!args.apiKey) {
|
|
92
93
|
if (process.stdout.isTTY) {
|
|
93
94
|
process.stderr.write(loginGuide());
|
|
94
95
|
}
|
|
95
|
-
return { ok: false, message:
|
|
96
|
+
return { ok: false, message: "No API key provided. See guide above." };
|
|
96
97
|
}
|
|
97
98
|
const isTTY = process.stdout.isTTY;
|
|
98
99
|
// Validate the key against the API
|
|
99
100
|
if (isTTY) {
|
|
100
|
-
process.stderr.write(
|
|
101
|
+
process.stderr.write("\n" + ui_1.color.dim("Validating API key…") + "\n");
|
|
101
102
|
}
|
|
102
103
|
const workspaceInfo = await validateAndFetchWorkspace(args.apiKey);
|
|
103
104
|
// Save key + workspace info (save even if validation fails — user might be offline)
|
|
104
105
|
// Only include workspace/projectId when present so offline logins don't wipe existing values
|
|
105
106
|
(0, config_1.saveConfig)({
|
|
106
107
|
apiKey: args.apiKey,
|
|
107
|
-
...(workspaceInfo?.workspace !== undefined && {
|
|
108
|
-
|
|
108
|
+
...(workspaceInfo?.workspace !== undefined && {
|
|
109
|
+
workspace: workspaceInfo.workspace,
|
|
110
|
+
}),
|
|
111
|
+
...(workspaceInfo?.projectId !== undefined && {
|
|
112
|
+
projectId: workspaceInfo.projectId,
|
|
113
|
+
}),
|
|
109
114
|
});
|
|
110
115
|
// Show feedback in human mode
|
|
111
116
|
if (isTTY) {
|
|
112
117
|
const masked = args.apiKey.length > 12
|
|
113
|
-
? args.apiKey.slice(0, 8) +
|
|
114
|
-
:
|
|
118
|
+
? args.apiKey.slice(0, 8) + "…" + args.apiKey.slice(-4)
|
|
119
|
+
: "***";
|
|
115
120
|
if (workspaceInfo) {
|
|
116
|
-
process.stderr.write((0, ui_1.success)(
|
|
117
|
-
|
|
121
|
+
process.stderr.write((0, ui_1.success)("API key validated and saved!") +
|
|
122
|
+
"\n" +
|
|
118
123
|
(0, ui_1.info)(`Key: ${ui_1.color.dim(masked)}`) +
|
|
119
|
-
|
|
124
|
+
"\n" +
|
|
120
125
|
(0, ui_1.info)(`Workspace: ${ui_1.color.bold(workspaceInfo.workspace)}`) +
|
|
121
|
-
|
|
126
|
+
"\n" +
|
|
122
127
|
(workspaceInfo.projectId
|
|
123
|
-
? (0, ui_1.info)(`Project: ${ui_1.color.dim(workspaceInfo.projectId)}`) +
|
|
124
|
-
:
|
|
125
|
-
(0, ui_1.info)(`Config: ${ui_1.color.dim(
|
|
126
|
-
|
|
127
|
-
ui_1.color.dim(
|
|
128
|
-
|
|
129
|
-
ui_1.color.dim(
|
|
130
|
-
ui_1.color.green(
|
|
131
|
-
ui_1.color.dim(
|
|
132
|
-
|
|
128
|
+
? (0, ui_1.info)(`Project: ${ui_1.color.dim(workspaceInfo.projectId)}`) + "\n"
|
|
129
|
+
: "") +
|
|
130
|
+
(0, ui_1.info)(`Config: ${ui_1.color.dim("~/.config/formo/config.json")}`) +
|
|
131
|
+
"\n\n" +
|
|
132
|
+
ui_1.color.dim("You can now use all formo commands.") +
|
|
133
|
+
"\n" +
|
|
134
|
+
ui_1.color.dim("Run ") +
|
|
135
|
+
ui_1.color.green("formo --help") +
|
|
136
|
+
ui_1.color.dim(" to see available commands.") +
|
|
137
|
+
"\n\n");
|
|
133
138
|
}
|
|
134
139
|
else {
|
|
135
|
-
process.stderr.write((0, ui_1.warn)(
|
|
136
|
-
|
|
140
|
+
process.stderr.write((0, ui_1.warn)("API key saved but could not be validated.") +
|
|
141
|
+
"\n" +
|
|
137
142
|
(0, ui_1.info)(`Key: ${ui_1.color.dim(masked)}`) +
|
|
138
|
-
|
|
139
|
-
(0, ui_1.info)(`Config: ${ui_1.color.dim(
|
|
140
|
-
|
|
141
|
-
ui_1.color.dim(
|
|
142
|
-
|
|
143
|
+
"\n" +
|
|
144
|
+
(0, ui_1.info)(`Config: ${ui_1.color.dim("~/.config/formo/config.json")}`) +
|
|
145
|
+
"\n" +
|
|
146
|
+
ui_1.color.dim("The API might be unreachable. Your key is saved and will be used for requests.") +
|
|
147
|
+
"\n\n");
|
|
143
148
|
}
|
|
144
149
|
}
|
|
145
150
|
return {
|
|
146
151
|
ok: true,
|
|
147
152
|
message: workspaceInfo
|
|
148
|
-
?
|
|
149
|
-
:
|
|
153
|
+
? "API key validated and saved"
|
|
154
|
+
: "API key saved (not validated)",
|
|
150
155
|
workspace: workspaceInfo?.workspace ?? null,
|
|
151
156
|
projectId: workspaceInfo?.projectId ?? null,
|
|
152
157
|
};
|
|
153
158
|
},
|
|
154
159
|
});
|
|
155
160
|
// ── logout ──
|
|
156
|
-
cli.command(
|
|
157
|
-
description:
|
|
161
|
+
cli.command("logout", {
|
|
162
|
+
description: "Remove saved API key and clear authentication",
|
|
158
163
|
run() {
|
|
159
164
|
const hadKey = !!(0, config_1.readConfig)().apiKey;
|
|
160
165
|
(0, config_1.clearConfig)();
|
|
161
166
|
if (process.stdout.isTTY) {
|
|
162
|
-
process.stderr.write(
|
|
167
|
+
process.stderr.write("\n");
|
|
163
168
|
if (hadKey) {
|
|
164
|
-
process.stderr.write((0, ui_1.success)(
|
|
165
|
-
|
|
166
|
-
(0, ui_1.info)(
|
|
167
|
-
|
|
169
|
+
process.stderr.write((0, ui_1.success)("Logged out successfully.") +
|
|
170
|
+
"\n" +
|
|
171
|
+
(0, ui_1.info)("API key removed from ~/.config/formo/config.json") +
|
|
172
|
+
"\n");
|
|
168
173
|
}
|
|
169
174
|
else {
|
|
170
|
-
process.stderr.write((0, ui_1.info)(
|
|
175
|
+
process.stderr.write((0, ui_1.info)("No saved API key found — already logged out.") + "\n");
|
|
171
176
|
}
|
|
172
177
|
if (process.env.FORMO_API_KEY) {
|
|
173
|
-
process.stderr.write(
|
|
174
|
-
(0, ui_1.warn)(`${ui_1.color.yellow(
|
|
175
|
-
|
|
176
|
-
(0, ui_1.info)(`Run ${ui_1.color.green(
|
|
177
|
-
|
|
178
|
+
process.stderr.write("\n" +
|
|
179
|
+
(0, ui_1.warn)(`${ui_1.color.yellow("FORMO_API_KEY")} env var is still set.`) +
|
|
180
|
+
"\n" +
|
|
181
|
+
(0, ui_1.info)(`Run ${ui_1.color.green("unset FORMO_API_KEY")} to fully log out.`) +
|
|
182
|
+
"\n");
|
|
178
183
|
}
|
|
179
|
-
process.stderr.write(
|
|
184
|
+
process.stderr.write("\n");
|
|
180
185
|
}
|
|
181
186
|
return {
|
|
182
187
|
ok: true,
|
|
183
|
-
message: hadKey ?
|
|
188
|
+
message: hadKey ? "API key removed" : "Already logged out",
|
|
184
189
|
envVarSet: !!process.env.FORMO_API_KEY,
|
|
185
190
|
};
|
|
186
191
|
},
|
|
187
192
|
});
|
|
188
193
|
// ── status ──
|
|
189
|
-
cli.command(
|
|
190
|
-
description:
|
|
194
|
+
cli.command("status", {
|
|
195
|
+
description: "Show current authentication and CLI status",
|
|
191
196
|
run() {
|
|
192
197
|
const apiKey = (0, config_1.getApiKey)();
|
|
193
198
|
const config = (0, config_1.readConfig)();
|
|
194
199
|
const source = process.env.FORMO_API_KEY
|
|
195
|
-
?
|
|
196
|
-
:
|
|
200
|
+
? "FORMO_API_KEY env var"
|
|
201
|
+
: "config file";
|
|
197
202
|
if (process.stdout.isTTY) {
|
|
198
|
-
process.stderr.write(
|
|
203
|
+
process.stderr.write("\n");
|
|
199
204
|
if (apiKey) {
|
|
200
205
|
const masked = apiKey.length > 12
|
|
201
|
-
? apiKey.slice(0, 8) +
|
|
202
|
-
:
|
|
203
|
-
process.stderr.write((0, ui_1.success)(
|
|
204
|
-
|
|
206
|
+
? apiKey.slice(0, 8) + "…" + apiKey.slice(-4)
|
|
207
|
+
: "***";
|
|
208
|
+
process.stderr.write((0, ui_1.success)("Authenticated") +
|
|
209
|
+
"\n" +
|
|
205
210
|
(0, ui_1.info)(`Key: ${ui_1.color.dim(masked)}`) +
|
|
206
|
-
|
|
211
|
+
"\n" +
|
|
207
212
|
(0, ui_1.info)(`Source: ${ui_1.color.dim(source)}`) +
|
|
208
|
-
|
|
213
|
+
"\n" +
|
|
209
214
|
(config.workspace
|
|
210
|
-
? (0, ui_1.info)(`Workspace: ${ui_1.color.bold(config.workspace)}`) +
|
|
211
|
-
:
|
|
215
|
+
? (0, ui_1.info)(`Workspace: ${ui_1.color.bold(config.workspace)}`) + "\n"
|
|
216
|
+
: "") +
|
|
212
217
|
(config.projectId
|
|
213
|
-
? (0, ui_1.info)(`Project ID: ${ui_1.color.dim(config.projectId)}`) +
|
|
214
|
-
:
|
|
215
|
-
|
|
218
|
+
? (0, ui_1.info)(`Project ID: ${ui_1.color.dim(config.projectId)}`) + "\n"
|
|
219
|
+
: "") +
|
|
220
|
+
"\n");
|
|
216
221
|
}
|
|
217
222
|
else {
|
|
218
|
-
process.stderr.write((0, ui_1.error)(
|
|
219
|
-
|
|
220
|
-
(0, ui_1.info)(`Run ${ui_1.color.green(
|
|
221
|
-
|
|
223
|
+
process.stderr.write((0, ui_1.error)("Not authenticated") +
|
|
224
|
+
"\n\n" +
|
|
225
|
+
(0, ui_1.info)(`Run ${ui_1.color.green("formo login")} to get started.`) +
|
|
226
|
+
"\n\n");
|
|
222
227
|
}
|
|
223
228
|
}
|
|
224
229
|
return {
|
|
@@ -226,34 +231,14 @@ cli.command('status', {
|
|
|
226
231
|
source: apiKey ? source : null,
|
|
227
232
|
workspace: config.workspace ?? null,
|
|
228
233
|
projectId: config.projectId ?? null,
|
|
229
|
-
configFile: config.apiKey ?
|
|
234
|
+
configFile: config.apiKey ? "~/.config/formo/config.json" : null,
|
|
230
235
|
};
|
|
231
236
|
},
|
|
232
237
|
});
|
|
233
|
-
// ── query ──
|
|
234
|
-
cli.command('query', {
|
|
235
|
-
description: 'Run a SQL query against your Formo analytics data',
|
|
236
|
-
args: incur_1.z.object({
|
|
237
|
-
sql: incur_1.z.string().describe('SQL query string to execute'),
|
|
238
|
-
}),
|
|
239
|
-
options: incur_1.z.object({}),
|
|
240
|
-
examples: [
|
|
241
|
-
{
|
|
242
|
-
args: { sql: 'SELECT count(*) FROM events' },
|
|
243
|
-
description: 'Count all events',
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
args: { sql: 'SELECT address, net_worth_usd FROM wallet_profiles ORDER BY net_worth_usd DESC LIMIT 10' },
|
|
247
|
-
description: 'Top 10 wallets by net worth',
|
|
248
|
-
},
|
|
249
|
-
],
|
|
250
|
-
hint: 'Requires query:read scope on your API key.',
|
|
251
|
-
run({ args }) {
|
|
252
|
-
return (0, query_1.queryRunRun)(args.sql);
|
|
253
|
-
},
|
|
254
|
-
});
|
|
255
238
|
// ── command groups ──
|
|
256
239
|
cli.command(profiles_1.profiles);
|
|
240
|
+
cli.command(query_1.query);
|
|
241
|
+
cli.command(analytics_1.analytics);
|
|
257
242
|
cli.command(alerts_1.alerts);
|
|
258
243
|
cli.command(boards_1.boards);
|
|
259
244
|
cli.command(charts_1.charts);
|
|
@@ -262,7 +247,7 @@ cli.command(segments_1.segments);
|
|
|
262
247
|
cli.command(import_1.importCmd);
|
|
263
248
|
// Show banner when run with no args (root help)
|
|
264
249
|
const args = process.argv.slice(2);
|
|
265
|
-
const isRootHelp = args.length === 0 || (args.length === 1 && args[0] ===
|
|
250
|
+
const isRootHelp = args.length === 0 || (args.length === 1 && args[0] === "--help");
|
|
266
251
|
if (isRootHelp && process.stdout.isTTY) {
|
|
267
252
|
process.stderr.write((0, ui_1.banner)());
|
|
268
253
|
}
|
package/dist/lib/client.d.ts
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
import { AxiosError } from 'axios';
|
|
2
|
+
export interface ApiErrorBody {
|
|
3
|
+
error?: {
|
|
4
|
+
code?: string;
|
|
5
|
+
message?: string;
|
|
6
|
+
doc_url?: string;
|
|
7
|
+
param?: string;
|
|
8
|
+
details?: Record<string, unknown>;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export interface DecoratedApiError extends Error {
|
|
12
|
+
status?: number;
|
|
13
|
+
code?: string;
|
|
14
|
+
docUrl?: string;
|
|
15
|
+
param?: string;
|
|
16
|
+
details?: Record<string, unknown>;
|
|
17
|
+
transportCode?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Translate an AxiosError into a thrown Error with the API's structured
|
|
21
|
+
* `{ error: { code, message, doc_url, param, details } }` envelope decoded
|
|
22
|
+
* onto the Error instance and into a multi-line, human-readable `.message`.
|
|
23
|
+
*
|
|
24
|
+
* Exported for unit testing — used by the response interceptor below.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseApiError(error: AxiosError): DecoratedApiError;
|
|
1
27
|
declare function createClient(): import("axios").AxiosInstance;
|
|
2
28
|
export declare function requireApiKey(): void;
|
|
3
29
|
export { createClient };
|
package/dist/lib/client.js
CHANGED
|
@@ -3,11 +3,40 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseApiError = parseApiError;
|
|
6
7
|
exports.requireApiKey = requireApiKey;
|
|
7
8
|
exports.createClient = createClient;
|
|
8
9
|
const axios_1 = __importDefault(require("axios"));
|
|
9
10
|
const config_1 = require("./config");
|
|
10
11
|
const BASE_URL = 'https://api.formo.so';
|
|
12
|
+
/**
|
|
13
|
+
* Translate an AxiosError into a thrown Error with the API's structured
|
|
14
|
+
* `{ error: { code, message, doc_url, param, details } }` envelope decoded
|
|
15
|
+
* onto the Error instance and into a multi-line, human-readable `.message`.
|
|
16
|
+
*
|
|
17
|
+
* Exported for unit testing — used by the response interceptor below.
|
|
18
|
+
*/
|
|
19
|
+
function parseApiError(error) {
|
|
20
|
+
const status = error.response?.status;
|
|
21
|
+
const body = error.response?.data;
|
|
22
|
+
const apiError = body?.error;
|
|
23
|
+
const baseMessage = apiError?.message ?? error.message;
|
|
24
|
+
const parts = [];
|
|
25
|
+
parts.push(apiError?.code ? `[${apiError.code}] ${baseMessage}` : baseMessage);
|
|
26
|
+
if (apiError?.param)
|
|
27
|
+
parts.push(`Param: ${apiError.param}`);
|
|
28
|
+
if (apiError?.doc_url)
|
|
29
|
+
parts.push(`Docs: ${apiError.doc_url}`);
|
|
30
|
+
const message = parts.join('\n ');
|
|
31
|
+
return Object.assign(new Error(message), {
|
|
32
|
+
status,
|
|
33
|
+
code: apiError?.code,
|
|
34
|
+
docUrl: apiError?.doc_url,
|
|
35
|
+
param: apiError?.param,
|
|
36
|
+
details: apiError?.details,
|
|
37
|
+
transportCode: error.code,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
11
40
|
function createClient() {
|
|
12
41
|
const apiKey = (0, config_1.getApiKey)();
|
|
13
42
|
const baseURL = BASE_URL;
|
|
@@ -20,13 +49,7 @@ function createClient() {
|
|
|
20
49
|
},
|
|
21
50
|
});
|
|
22
51
|
instance.interceptors.response.use((res) => res.data, (error) => {
|
|
23
|
-
|
|
24
|
-
const data = error.response?.data;
|
|
25
|
-
const errorField = data?.error;
|
|
26
|
-
const message = data?.message ??
|
|
27
|
-
(typeof errorField === 'object' ? errorField?.message : errorField) ??
|
|
28
|
-
error.message;
|
|
29
|
-
throw Object.assign(new Error(message), { status, code: error.code });
|
|
52
|
+
throw parseApiError(error);
|
|
30
53
|
});
|
|
31
54
|
return instance;
|
|
32
55
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formo/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"packageManager": "pnpm@11.1.2",
|
|
4
5
|
"description": "Formo API CLI — query profiles and analytics data",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/getformo/cli.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/getformo/cli#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/getformo/cli/issues"
|
|
13
|
+
},
|
|
5
14
|
"bin": {
|
|
6
15
|
"formo": "dist/index.js"
|
|
7
16
|
},
|
|
@@ -20,7 +29,7 @@
|
|
|
20
29
|
"test:watch": "mocha --watch"
|
|
21
30
|
},
|
|
22
31
|
"dependencies": {
|
|
23
|
-
"axios": "^1.
|
|
32
|
+
"axios": "^1.15.2",
|
|
24
33
|
"incur": "^0.3.4"
|
|
25
34
|
},
|
|
26
35
|
"overrides": {
|