@bankr/cli 0.3.38 → 0.3.43
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 +50 -158
- package/dist/cli.js +159 -145
- package/dist/commands/fees.js +17 -7
- package/dist/commands/llm.js +64 -0
- package/dist/commands/login.d.ts +38 -0
- package/dist/commands/login.js +129 -38
- package/dist/commands/{profile.d.ts → project.d.ts} +6 -6
- package/dist/commands/{profile.js → project.js} +65 -65
- package/dist/commands/prompt.d.ts +11 -0
- package/dist/commands/prompt.js +20 -11
- package/dist/commands/status.d.ts +5 -1
- package/dist/commands/status.js +54 -5
- package/dist/commands/x402.js +2 -2
- package/dist/lib/api.d.ts +29 -8
- package/dist/lib/api.js +26 -12
- package/dist/lib/statusUpdates.d.ts +14 -0
- package/dist/lib/statusUpdates.js +38 -0
- package/package.json +1 -1
package/dist/commands/fees.js
CHANGED
|
@@ -3,6 +3,7 @@ import { confirm, password, checkbox } from "@inquirer/prompts";
|
|
|
3
3
|
import { isAddress } from "viem";
|
|
4
4
|
import { getCreatorFees, getBeneficiaryFeesStreaming, getTokenCreatorFees, getUserInfo, claimTokenLaunchFees, } from "../lib/api.js";
|
|
5
5
|
import { emitCESP } from "../lib/cesp/engine.js";
|
|
6
|
+
import { CHAIN_LABELS } from "../lib/chains.js";
|
|
6
7
|
import { requireApiKey } from "../lib/config.js";
|
|
7
8
|
import { txExplorer } from "../lib/explorer.js";
|
|
8
9
|
import * as output from "../lib/output.js";
|
|
@@ -129,7 +130,7 @@ function drawChart(dailyData) {
|
|
|
129
130
|
const xAxisPadding = " ".repeat(14);
|
|
130
131
|
if (dailyData.length > 0) {
|
|
131
132
|
const first = dailyData[0].date.slice(5);
|
|
132
|
-
const last = dailyData
|
|
133
|
+
const last = dailyData.at(-1).date.slice(5);
|
|
133
134
|
const mid = dailyData[Math.floor(dailyData.length / 2)].date.slice(5);
|
|
134
135
|
const gap1 = Math.floor(CHART_WIDTH / 2) - first.length;
|
|
135
136
|
const gap2 = CHART_WIDTH - Math.floor(CHART_WIDTH / 2) - mid.length;
|
|
@@ -653,12 +654,21 @@ export async function feesClaimWalletCommand(address, opts) {
|
|
|
653
654
|
],
|
|
654
655
|
},
|
|
655
656
|
];
|
|
656
|
-
//
|
|
657
|
-
// broadcasts on Base only, and a Base tx to
|
|
658
|
-
// gas-burning no-op. Skip them loudly.
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
|
|
657
|
+
// Launches on other chains can't be claimed here: this flow signs and
|
|
658
|
+
// broadcasts on Base only, and a Base tx to another chain's initializer is a
|
|
659
|
+
// gas-burning no-op. Skip them loudly, linking each token page's claim flow.
|
|
660
|
+
const chainOf = (t) => t.chain ?? "base";
|
|
661
|
+
const baseChainTokens = selectedTokens.filter((t) => chainOf(t) === "base");
|
|
662
|
+
const skippedTokens = selectedTokens.filter((t) => chainOf(t) !== "base");
|
|
663
|
+
if (skippedTokens.length > 0) {
|
|
664
|
+
const names = [...new Set(skippedTokens.map(chainOf))].map((chain) => CHAIN_LABELS[chain] ?? chain);
|
|
665
|
+
const skippedChains = names.length > 1
|
|
666
|
+
? `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`
|
|
667
|
+
: names[0];
|
|
668
|
+
output.warn(`${skippedTokens.length} ${skippedChains} token(s) skipped — CLI claiming supports Base only for now. Claim them on each token's page instead:`);
|
|
669
|
+
for (const t of skippedTokens) {
|
|
670
|
+
output.dim(` ${t.symbol}: https://bankr.bot/terminal/trade?out=${t.tokenAddress}&chain=${chainOf(t)}`);
|
|
671
|
+
}
|
|
662
672
|
}
|
|
663
673
|
// Filter to tokens with initializer (required for local tx building)
|
|
664
674
|
const buildableTokens = baseChainTokens.filter((t) => t.initializer);
|
package/dist/commands/llm.js
CHANGED
|
@@ -38,6 +38,15 @@ const GATEWAY_MODELS = [
|
|
|
38
38
|
input: IMAGE_INPUT,
|
|
39
39
|
cost: { input: 10.0, output: 50.0, cacheRead: 1.0, cacheWrite: 12.5 },
|
|
40
40
|
},
|
|
41
|
+
{
|
|
42
|
+
id: "claude-opus-5.5",
|
|
43
|
+
name: "Claude Opus 5.5",
|
|
44
|
+
owned_by: "anthropic",
|
|
45
|
+
contextWindow: 1000000,
|
|
46
|
+
maxTokens: 128000,
|
|
47
|
+
input: IMAGE_INPUT,
|
|
48
|
+
cost: { input: 4.0, output: 20.0, cacheRead: 0.2, cacheWrite: 5.0 },
|
|
49
|
+
},
|
|
41
50
|
{
|
|
42
51
|
id: "claude-opus-5",
|
|
43
52
|
name: "Claude Opus 5",
|
|
@@ -83,6 +92,15 @@ const GATEWAY_MODELS = [
|
|
|
83
92
|
input: IMAGE_INPUT,
|
|
84
93
|
cost: { input: 5.0, output: 25.0, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
85
94
|
},
|
|
95
|
+
{
|
|
96
|
+
id: "claude-sonnet-5.5",
|
|
97
|
+
name: "Claude Sonnet 5.5",
|
|
98
|
+
owned_by: "anthropic",
|
|
99
|
+
contextWindow: 1000000,
|
|
100
|
+
maxTokens: 128000,
|
|
101
|
+
input: IMAGE_INPUT,
|
|
102
|
+
cost: { input: 2.0, output: 10.0, cacheRead: 0.2, cacheWrite: 2.5 },
|
|
103
|
+
},
|
|
86
104
|
{
|
|
87
105
|
id: "claude-sonnet-5",
|
|
88
106
|
name: "Claude Sonnet 5",
|
|
@@ -182,6 +200,12 @@ const GATEWAY_MODELS = [
|
|
|
182
200
|
maxTokens: 65536,
|
|
183
201
|
input: IMAGE_INPUT,
|
|
184
202
|
cost: { input: 1.25, output: 10.0, cacheRead: 0.125, cacheWrite: 0.375 },
|
|
203
|
+
// Google retires the 2.5 line on 2026-10-20 (published on every Vertex
|
|
204
|
+
// endpoint). Without this the offline catalog advertises it as active.
|
|
205
|
+
deprecation: {
|
|
206
|
+
replaced_by: "gemini-3.1-pro",
|
|
207
|
+
removed_at: "2026-10-20T00:00:00.000Z",
|
|
208
|
+
},
|
|
185
209
|
},
|
|
186
210
|
{
|
|
187
211
|
id: "gemini-2.5-flash",
|
|
@@ -191,6 +215,10 @@ const GATEWAY_MODELS = [
|
|
|
191
215
|
maxTokens: 65535,
|
|
192
216
|
input: IMAGE_INPUT,
|
|
193
217
|
cost: { input: 0.3, output: 2.5, cacheRead: 0.03, cacheWrite: 0.0833 },
|
|
218
|
+
deprecation: {
|
|
219
|
+
replaced_by: "gemini-3.8-flash",
|
|
220
|
+
removed_at: "2026-10-20T00:00:00.000Z",
|
|
221
|
+
},
|
|
194
222
|
},
|
|
195
223
|
// Gemma
|
|
196
224
|
{
|
|
@@ -243,6 +271,24 @@ const GATEWAY_MODELS = [
|
|
|
243
271
|
input: IMAGE_INPUT,
|
|
244
272
|
cost: { input: 10.0, output: 50.0, cacheRead: 1.0, cacheWrite: 12.5 },
|
|
245
273
|
},
|
|
274
|
+
{
|
|
275
|
+
id: "gpt-6-sol",
|
|
276
|
+
name: "GPT 6 Sol",
|
|
277
|
+
owned_by: "openai",
|
|
278
|
+
contextWindow: 1050000,
|
|
279
|
+
maxTokens: 128000,
|
|
280
|
+
input: IMAGE_INPUT,
|
|
281
|
+
cost: { input: 2.0, output: 10.0, cacheRead: 0.2, cacheWrite: 2.5 },
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
id: "gpt-6-luna",
|
|
285
|
+
name: "GPT 6 Luna",
|
|
286
|
+
owned_by: "openai",
|
|
287
|
+
contextWindow: 1050000,
|
|
288
|
+
maxTokens: 128000,
|
|
289
|
+
input: IMAGE_INPUT,
|
|
290
|
+
cost: { input: 0.1, output: 0.5, cacheRead: 0.01, cacheWrite: 0.125 },
|
|
291
|
+
},
|
|
246
292
|
{
|
|
247
293
|
id: "gpt-5.6-sol",
|
|
248
294
|
name: "GPT 5.6 Sol",
|
|
@@ -355,6 +401,15 @@ const GATEWAY_MODELS = [
|
|
|
355
401
|
cost: { input: 0.05, output: 0.4, cacheRead: 0.005, cacheWrite: 0 },
|
|
356
402
|
},
|
|
357
403
|
// Other providers
|
|
404
|
+
{
|
|
405
|
+
id: "grok-4.7",
|
|
406
|
+
name: "Grok 4.7",
|
|
407
|
+
owned_by: "x-ai",
|
|
408
|
+
contextWindow: 500000,
|
|
409
|
+
maxTokens: 500000,
|
|
410
|
+
input: IMAGE_INPUT,
|
|
411
|
+
cost: { input: 2.0, output: 6.0, cacheRead: 0.5, cacheWrite: 0 },
|
|
412
|
+
},
|
|
358
413
|
{
|
|
359
414
|
id: "grok-4.6",
|
|
360
415
|
name: "Grok 4.6",
|
|
@@ -604,6 +659,15 @@ const GATEWAY_MODELS = [
|
|
|
604
659
|
input: TEXT_INPUT,
|
|
605
660
|
cost: { input: 0.6, output: 2.4, cacheRead: 0.06, cacheWrite: 0 },
|
|
606
661
|
},
|
|
662
|
+
{
|
|
663
|
+
id: "glm-5.3-flashx",
|
|
664
|
+
name: "GLM-5.3 FlashX",
|
|
665
|
+
owned_by: "z-ai",
|
|
666
|
+
contextWindow: 1048576,
|
|
667
|
+
maxTokens: 131072,
|
|
668
|
+
input: IMAGE_INPUT,
|
|
669
|
+
cost: { input: 0.37, output: 1.25, cacheRead: 0.075, cacheWrite: 0 },
|
|
670
|
+
},
|
|
607
671
|
{
|
|
608
672
|
id: "glm-5.3-flash",
|
|
609
673
|
name: "GLM-5.3 Flash",
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -11,6 +11,37 @@ export declare class MfaStepUpError extends Error {
|
|
|
11
11
|
/** Keyed on `code`, never on message text, so a server wording change can't
|
|
12
12
|
* silently turn this back into the opaque failure. */
|
|
13
13
|
export declare function mfaStepUpErrorFrom(body: unknown): MfaStepUpError | null;
|
|
14
|
+
/** Any 5xx: a gateway page (ALB/CloudFront 502/503/504) the API never saw, or
|
|
15
|
+
* an API catch-all such as a DB blip. Idempotent callers retry it; the API's
|
|
16
|
+
* own message, when it sent one, is kept for the final report. */
|
|
17
|
+
export declare class TransientHttpError extends Error {
|
|
18
|
+
readonly status: number;
|
|
19
|
+
constructor(status: number, apiMessage?: string);
|
|
20
|
+
}
|
|
21
|
+
/** True for a request the API never answered: undici's `fetch failed`, a
|
|
22
|
+
* socket-level code, or a gateway error page. An API refusal is not one. */
|
|
23
|
+
export declare function isTransientNetworkError(err: unknown, depth?: number): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Retry a login step on network blips only. The OTP is already spent by the
|
|
26
|
+
* time the wallet step runs, so a dropped connection here used to cost the
|
|
27
|
+
* user a fresh code; three quick attempts ride out the transient case.
|
|
28
|
+
*/
|
|
29
|
+
export declare function withNetworkRetry<T>(run: () => Promise<T>, opts?: {
|
|
30
|
+
attempts?: number;
|
|
31
|
+
baseDelayMs?: number;
|
|
32
|
+
sleep?: (ms: number) => Promise<void>;
|
|
33
|
+
}): Promise<T>;
|
|
34
|
+
/** Key names are unique per user, so the default carries the time to the
|
|
35
|
+
* second: a scripted re-login seconds after a dropped mint response (which
|
|
36
|
+
* already created the key) must not collide with the earlier default. */
|
|
37
|
+
export declare function defaultCliKeyName(now?: Date): string;
|
|
38
|
+
/** Headless login (`--code` / `--ni`) accepts the Terms of Service as part of
|
|
39
|
+
* logging in, so the one-time code isn't burned on a missing flag. Interactive
|
|
40
|
+
* login returns undefined to defer to the prompt. */
|
|
41
|
+
export declare function resolveTermsAcceptance(opts: {
|
|
42
|
+
headless: boolean;
|
|
43
|
+
acceptTerms?: boolean;
|
|
44
|
+
}): boolean | undefined;
|
|
14
45
|
/** The web app that fronts an API URL: `BANKR_WEB_URL` when set (local dev,
|
|
15
46
|
* staging), bankr.bot for the official API, else the API host with its
|
|
16
47
|
* `api.`/`api-` prefix dropped. */
|
|
@@ -41,6 +72,13 @@ export declare function resolveKeyPermissionOverrides(opts: {
|
|
|
41
72
|
readOnly?: boolean;
|
|
42
73
|
agentApiEnabled?: boolean;
|
|
43
74
|
};
|
|
75
|
+
/**
|
|
76
|
+
* Parse a login-gated response. The body is read as text first so a gateway
|
|
77
|
+
* HTML error page becomes a `TransientHttpError` instead of a `SyntaxError`
|
|
78
|
+
* from `res.json()`. Every 5xx is transient (callers that opted into the
|
|
79
|
+
* retry replay it; others just report it); a 4xx keeps the API's message.
|
|
80
|
+
*/
|
|
81
|
+
export declare function readGatedResponse<T>(res: Response, label: string): Promise<T>;
|
|
44
82
|
declare const CHALLENGE_STATUSES: readonly ["pending", "verified", "expired", "abandoned", "failed"];
|
|
45
83
|
export type ChallengeStatus = (typeof CHALLENGE_STATUSES)[number];
|
|
46
84
|
/**
|
package/dist/commands/login.js
CHANGED
|
@@ -35,6 +35,77 @@ export function mfaStepUpErrorFrom(body) {
|
|
|
35
35
|
return null;
|
|
36
36
|
return new MfaStepUpError(typeof context === "string" ? context : "unknown");
|
|
37
37
|
}
|
|
38
|
+
const NETWORK_ERROR_CODES = new Set([
|
|
39
|
+
"ECONNRESET",
|
|
40
|
+
"ECONNREFUSED",
|
|
41
|
+
"ETIMEDOUT",
|
|
42
|
+
"ENOTFOUND",
|
|
43
|
+
"EAI_AGAIN",
|
|
44
|
+
"EPIPE",
|
|
45
|
+
"UND_ERR_SOCKET",
|
|
46
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
47
|
+
]);
|
|
48
|
+
/** Any 5xx: a gateway page (ALB/CloudFront 502/503/504) the API never saw, or
|
|
49
|
+
* an API catch-all such as a DB blip. Idempotent callers retry it; the API's
|
|
50
|
+
* own message, when it sent one, is kept for the final report. */
|
|
51
|
+
export class TransientHttpError extends Error {
|
|
52
|
+
constructor(status, apiMessage) {
|
|
53
|
+
super(apiMessage ?? `Bankr API unavailable (${status})`);
|
|
54
|
+
this.status = status;
|
|
55
|
+
this.name = "TransientHttpError";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** True for a request the API never answered: undici's `fetch failed`, a
|
|
59
|
+
* socket-level code, or a gateway error page. An API refusal is not one. */
|
|
60
|
+
export function isTransientNetworkError(err, depth = 0) {
|
|
61
|
+
if (depth > 5)
|
|
62
|
+
return false;
|
|
63
|
+
if (!(err instanceof Error) || err instanceof MfaStepUpError)
|
|
64
|
+
return false;
|
|
65
|
+
if (err instanceof TransientHttpError)
|
|
66
|
+
return true;
|
|
67
|
+
// undici: "fetch failed" when no connection, "terminated" on a mid-body drop.
|
|
68
|
+
if (err instanceof TypeError &&
|
|
69
|
+
(err.message === "fetch failed" || err.message === "terminated")) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
const code = err.code;
|
|
73
|
+
if (typeof code === "string" && NETWORK_ERROR_CODES.has(code))
|
|
74
|
+
return true;
|
|
75
|
+
return isTransientNetworkError(err.cause, depth + 1);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Retry a login step on network blips only. The OTP is already spent by the
|
|
79
|
+
* time the wallet step runs, so a dropped connection here used to cost the
|
|
80
|
+
* user a fresh code; three quick attempts ride out the transient case.
|
|
81
|
+
*/
|
|
82
|
+
export async function withNetworkRetry(run, opts = {}) {
|
|
83
|
+
const { attempts = 3, baseDelayMs = 1000, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), } = opts;
|
|
84
|
+
for (let attempt = 1;; attempt++) {
|
|
85
|
+
try {
|
|
86
|
+
return await run();
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
if (attempt >= attempts || !isTransientNetworkError(err))
|
|
90
|
+
throw err;
|
|
91
|
+
await sleep(baseDelayMs * 2 ** (attempt - 1));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Key names are unique per user, so the default carries the time to the
|
|
96
|
+
* second: a scripted re-login seconds after a dropped mint response (which
|
|
97
|
+
* already created the key) must not collide with the earlier default. */
|
|
98
|
+
export function defaultCliKeyName(now = new Date()) {
|
|
99
|
+
const iso = now.toISOString();
|
|
100
|
+
return `CLI-${iso.slice(0, 10)}-${iso.slice(11, 19).replace(/:/g, "")}`;
|
|
101
|
+
}
|
|
102
|
+
/** Headless login (`--code` / `--ni`) accepts the Terms of Service as part of
|
|
103
|
+
* logging in, so the one-time code isn't burned on a missing flag. Interactive
|
|
104
|
+
* login returns undefined to defer to the prompt. */
|
|
105
|
+
export function resolveTermsAcceptance(opts) {
|
|
106
|
+
// `--accept-terms` is set-only (no `--no-accept-terms`), so `||` is safe.
|
|
107
|
+
return opts.acceptTerms || opts.headless || undefined;
|
|
108
|
+
}
|
|
38
109
|
/** The web app that fronts an API URL: `BANKR_WEB_URL` when set (local dev,
|
|
39
110
|
* staging), bankr.bot for the official API, else the API host with its
|
|
40
111
|
* `api.`/`api-` prefix dropped. */
|
|
@@ -111,7 +182,9 @@ export function resolveKeyPermissionOverrides(opts) {
|
|
|
111
182
|
return overrides;
|
|
112
183
|
}
|
|
113
184
|
async function verifyPrivyOtp(privyConfig, email, code) {
|
|
114
|
-
|
|
185
|
+
// Retry only a request that never got an answer. If Privy did answer and
|
|
186
|
+
// the socket dropped, the replay reads as INVALID_CODE: no worse than today.
|
|
187
|
+
const res = await withNetworkRetry(() => fetch("https://auth.privy.io/api/v1/passwordless/authenticate", {
|
|
115
188
|
method: "POST",
|
|
116
189
|
headers: {
|
|
117
190
|
"Content-Type": "application/json",
|
|
@@ -119,7 +192,7 @@ async function verifyPrivyOtp(privyConfig, email, code) {
|
|
|
119
192
|
"privy-client-id": privyConfig.privyClientId,
|
|
120
193
|
},
|
|
121
194
|
body: JSON.stringify({ email, code, mode: "login-or-sign-up" }),
|
|
122
|
-
});
|
|
195
|
+
}));
|
|
123
196
|
if (!res.ok) {
|
|
124
197
|
if (INVALID_CODE_STATUSES.includes(res.status)) {
|
|
125
198
|
throw new Error("INVALID_CODE");
|
|
@@ -131,30 +204,49 @@ async function verifyPrivyOtp(privyConfig, email, code) {
|
|
|
131
204
|
}
|
|
132
205
|
return (await res.json());
|
|
133
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Parse a login-gated response. The body is read as text first so a gateway
|
|
209
|
+
* HTML error page becomes a `TransientHttpError` instead of a `SyntaxError`
|
|
210
|
+
* from `res.json()`. Every 5xx is transient (callers that opted into the
|
|
211
|
+
* retry replay it; others just report it); a 4xx keeps the API's message.
|
|
212
|
+
*/
|
|
213
|
+
export async function readGatedResponse(res, label) {
|
|
214
|
+
const text = await res.text();
|
|
215
|
+
let body;
|
|
216
|
+
try {
|
|
217
|
+
body = JSON.parse(text);
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
body = undefined;
|
|
221
|
+
}
|
|
222
|
+
const isJsonObject = !!body && typeof body === "object";
|
|
223
|
+
const { message, error } = (isJsonObject ? body : {});
|
|
224
|
+
const apiMessage = (typeof message === "string" && message) ||
|
|
225
|
+
(typeof error === "string" && error) ||
|
|
226
|
+
undefined;
|
|
227
|
+
if (!res.ok) {
|
|
228
|
+
if (res.status >= 500)
|
|
229
|
+
throw new TransientHttpError(res.status, apiMessage);
|
|
230
|
+
throw (mfaStepUpErrorFrom(body) ??
|
|
231
|
+
new Error(apiMessage ?? `${label} (${res.status})`));
|
|
232
|
+
}
|
|
233
|
+
if (!isJsonObject)
|
|
234
|
+
throw new Error(`${label}: unexpected response`);
|
|
235
|
+
return body;
|
|
236
|
+
}
|
|
134
237
|
async function callGenerateWallet(apiUrl, tokens) {
|
|
135
238
|
const res = await fetch(`${apiUrl}/cli/generate-wallet`, {
|
|
136
239
|
method: "POST",
|
|
137
240
|
headers: authHeaders(tokens),
|
|
138
241
|
});
|
|
139
|
-
|
|
140
|
-
if (!res.ok) {
|
|
141
|
-
throw (mfaStepUpErrorFrom(body) ??
|
|
142
|
-
new Error(body.message ||
|
|
143
|
-
body.error ||
|
|
144
|
-
`Wallet generation failed (${res.status})`));
|
|
145
|
-
}
|
|
146
|
-
return body;
|
|
242
|
+
return readGatedResponse(res, "Wallet generation failed");
|
|
147
243
|
}
|
|
148
244
|
async function callAcceptTerms(apiUrl, tokens) {
|
|
149
245
|
const res = await fetch(`${apiUrl}/user/accept-terms`, {
|
|
150
246
|
method: "POST",
|
|
151
247
|
headers: authHeaders(tokens),
|
|
152
248
|
});
|
|
153
|
-
|
|
154
|
-
const body = (await res.json().catch(() => ({})));
|
|
155
|
-
throw (mfaStepUpErrorFrom(body) ??
|
|
156
|
-
new Error(body.message || body.error || `Accept terms failed (${res.status})`));
|
|
157
|
-
}
|
|
249
|
+
await readGatedResponse(res, "Accept terms failed");
|
|
158
250
|
}
|
|
159
251
|
async function callGenerateApiKey(apiUrl, tokens, opts) {
|
|
160
252
|
const res = await fetch(`${apiUrl}/api-keys`, {
|
|
@@ -162,14 +254,9 @@ async function callGenerateApiKey(apiUrl, tokens, opts) {
|
|
|
162
254
|
headers: authHeaders(tokens),
|
|
163
255
|
body: JSON.stringify(opts),
|
|
164
256
|
});
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
new Error(body.message ||
|
|
169
|
-
body.error ||
|
|
170
|
-
`API key generation failed (${res.status})`));
|
|
171
|
-
}
|
|
172
|
-
return body;
|
|
257
|
+
// Parsed through the same seam so a gateway page reports cleanly; the mint
|
|
258
|
+
// itself is still never retried (see runGatedStep).
|
|
259
|
+
return readGatedResponse(res, "API key generation failed");
|
|
173
260
|
}
|
|
174
261
|
// ── Out-of-band MFA approval ─────────────────────────────────────────
|
|
175
262
|
// Mirrors `MFA_CHALLENGE_STATUSES` in @bankr/shared (the CLI can't import it).
|
|
@@ -370,13 +457,18 @@ async function authenticateWithEmail(apiUrl, providedEmail, providedCode) {
|
|
|
370
457
|
/**
|
|
371
458
|
* Run a login-gated call; on the MFA step-up envelope, take the browser
|
|
372
459
|
* approval hop once and retry. A second refusal, or an approval that expires
|
|
373
|
-
* or fails, exits with the browser-key guidance.
|
|
460
|
+
* or fails, exits with the browser-key guidance. Network blips are retried
|
|
461
|
+
* only where the caller opts in, so a step is never replayed by default: a key
|
|
462
|
+
* mint whose response was dropped has already consumed its unique name.
|
|
374
463
|
*/
|
|
375
|
-
async function runGatedStep(apiUrl, tokens, label, run) {
|
|
464
|
+
async function runGatedStep(apiUrl, tokens, label, run, opts = {}) {
|
|
465
|
+
const { retryOnNetworkBlip = false } = opts;
|
|
376
466
|
for (const retry of [false, true]) {
|
|
377
467
|
const spin = output.spinner(label);
|
|
378
468
|
try {
|
|
379
|
-
const result =
|
|
469
|
+
const result = retryOnNetworkBlip
|
|
470
|
+
? await withNetworkRetry(run)
|
|
471
|
+
: await run();
|
|
380
472
|
spin.stop();
|
|
381
473
|
return result;
|
|
382
474
|
}
|
|
@@ -416,8 +508,9 @@ function exitWithApiKeyGuidance(apiUrl) {
|
|
|
416
508
|
async function emailLoginFlow(apiUrl, opts) {
|
|
417
509
|
// Step 1: Authenticate via email OTP
|
|
418
510
|
const { tokens } = await authenticateWithEmail(apiUrl, opts.email, opts.code);
|
|
419
|
-
// Step 2:
|
|
420
|
-
|
|
511
|
+
// Step 2: Resolve (or create) the wallet. The label stays neutral because
|
|
512
|
+
// whether the user is new only comes back in the response.
|
|
513
|
+
const wallet = await runGatedStep(apiUrl, tokens, "Loading wallet...", () => callGenerateWallet(apiUrl, tokens), { retryOnNetworkBlip: true });
|
|
421
514
|
// Show wallet info
|
|
422
515
|
output.blank();
|
|
423
516
|
if (wallet.isNewUser) {
|
|
@@ -437,23 +530,21 @@ async function emailLoginFlow(apiUrl, opts) {
|
|
|
437
530
|
output.blank();
|
|
438
531
|
output.dim(" Please review our Terms of Service: https://bankr.bot/terms");
|
|
439
532
|
output.blank();
|
|
440
|
-
const accepted = opts.acceptTerms ??
|
|
441
|
-
(
|
|
442
|
-
?
|
|
443
|
-
:
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
theme: output.bankrTheme,
|
|
447
|
-
}));
|
|
533
|
+
const accepted = resolveTermsAcceptance({ headless, acceptTerms: opts.acceptTerms }) ??
|
|
534
|
+
(await confirm({
|
|
535
|
+
message: "Do you accept the Terms of Service?",
|
|
536
|
+
default: false,
|
|
537
|
+
theme: output.bankrTheme,
|
|
538
|
+
}));
|
|
448
539
|
if (!accepted) {
|
|
449
540
|
output.fatal("Terms must be accepted to continue.");
|
|
450
541
|
}
|
|
451
|
-
await runGatedStep(apiUrl, tokens, "Accepting terms...", () => callAcceptTerms(apiUrl, tokens));
|
|
542
|
+
await runGatedStep(apiUrl, tokens, "Accepting terms...", () => callAcceptTerms(apiUrl, tokens), { retryOnNetworkBlip: true });
|
|
452
543
|
output.success("Terms accepted");
|
|
453
544
|
}
|
|
454
545
|
// Step 4: Generate API key
|
|
455
546
|
output.blank();
|
|
456
|
-
const defaultKeyName =
|
|
547
|
+
const defaultKeyName = defaultCliKeyName();
|
|
457
548
|
const keyName = opts.keyName ??
|
|
458
549
|
(headless
|
|
459
550
|
? defaultKeyName
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export declare function
|
|
1
|
+
export declare function projectViewCommand(opts: {
|
|
2
2
|
json?: boolean;
|
|
3
3
|
}): Promise<void>;
|
|
4
|
-
export declare function
|
|
4
|
+
export declare function projectCreateCommand(opts: {
|
|
5
5
|
name?: string;
|
|
6
6
|
description?: string;
|
|
7
7
|
token?: string;
|
|
@@ -9,7 +9,7 @@ export declare function profileCreateCommand(opts: {
|
|
|
9
9
|
website?: string;
|
|
10
10
|
json?: boolean;
|
|
11
11
|
}): Promise<void>;
|
|
12
|
-
export declare function
|
|
12
|
+
export declare function projectUpdateCommand(opts: {
|
|
13
13
|
slug?: string;
|
|
14
14
|
name?: string;
|
|
15
15
|
description?: string;
|
|
@@ -18,13 +18,13 @@ export declare function profileUpdateCommand(opts: {
|
|
|
18
18
|
website?: string;
|
|
19
19
|
json?: boolean;
|
|
20
20
|
}): Promise<void>;
|
|
21
|
-
export declare function
|
|
21
|
+
export declare function projectDeleteCommand(opts: {
|
|
22
22
|
slug?: string;
|
|
23
23
|
}): Promise<void>;
|
|
24
|
-
export declare function
|
|
24
|
+
export declare function projectAddUpdateCommand(opts: {
|
|
25
25
|
slug?: string;
|
|
26
26
|
title?: string;
|
|
27
27
|
content?: string;
|
|
28
28
|
json?: boolean;
|
|
29
29
|
}): Promise<void>;
|
|
30
|
-
//# sourceMappingURL=
|
|
30
|
+
//# sourceMappingURL=project.d.ts.map
|