run402 1.54.0 → 1.54.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.
Files changed (64) hide show
  1. package/lib/agent.mjs +4 -2
  2. package/lib/ai.mjs +24 -10
  3. package/lib/allowance.mjs +53 -15
  4. package/lib/apps.mjs +13 -11
  5. package/lib/argparse.mjs +147 -0
  6. package/lib/auth.mjs +24 -9
  7. package/lib/billing.mjs +33 -17
  8. package/lib/blob.mjs +58 -26
  9. package/lib/cdn.mjs +3 -4
  10. package/lib/config.mjs +32 -8
  11. package/lib/contracts.mjs +38 -21
  12. package/lib/deploy-v2.mjs +38 -23
  13. package/lib/deploy.mjs +43 -44
  14. package/lib/domains.mjs +24 -8
  15. package/lib/email.mjs +38 -29
  16. package/lib/functions.mjs +77 -34
  17. package/lib/image.mjs +8 -2
  18. package/lib/message.mjs +4 -2
  19. package/lib/projects.mjs +115 -40
  20. package/lib/sdk-errors.mjs +66 -10
  21. package/lib/secrets.mjs +14 -8
  22. package/lib/sender-domain.mjs +11 -5
  23. package/lib/sites.mjs +9 -7
  24. package/lib/status.mjs +5 -2
  25. package/lib/subdomains.mjs +26 -11
  26. package/lib/tier.mjs +8 -2
  27. package/lib/webhooks.mjs +27 -13
  28. package/package.json +1 -1
  29. package/sdk/dist/index.d.ts +1 -0
  30. package/sdk/dist/index.d.ts.map +1 -1
  31. package/sdk/dist/index.js +5 -0
  32. package/sdk/dist/index.js.map +1 -1
  33. package/sdk/dist/namespaces/auth.d.ts +7 -0
  34. package/sdk/dist/namespaces/auth.d.ts.map +1 -1
  35. package/sdk/dist/namespaces/auth.js +24 -0
  36. package/sdk/dist/namespaces/auth.js.map +1 -1
  37. package/sdk/dist/namespaces/billing.d.ts +3 -0
  38. package/sdk/dist/namespaces/billing.d.ts.map +1 -1
  39. package/sdk/dist/namespaces/billing.js +6 -0
  40. package/sdk/dist/namespaces/billing.js.map +1 -1
  41. package/sdk/dist/namespaces/contracts.d.ts +3 -0
  42. package/sdk/dist/namespaces/contracts.d.ts.map +1 -1
  43. package/sdk/dist/namespaces/contracts.js +6 -0
  44. package/sdk/dist/namespaces/contracts.js.map +1 -1
  45. package/sdk/dist/namespaces/email.d.ts +4 -0
  46. package/sdk/dist/namespaces/email.d.ts.map +1 -1
  47. package/sdk/dist/namespaces/email.js +8 -0
  48. package/sdk/dist/namespaces/email.js.map +1 -1
  49. package/sdk/dist/namespaces/projects.d.ts +14 -0
  50. package/sdk/dist/namespaces/projects.d.ts.map +1 -1
  51. package/sdk/dist/namespaces/projects.js +72 -0
  52. package/sdk/dist/namespaces/projects.js.map +1 -1
  53. package/sdk/dist/namespaces/sender-domain.d.ts +2 -0
  54. package/sdk/dist/namespaces/sender-domain.d.ts.map +1 -1
  55. package/sdk/dist/namespaces/sender-domain.js +4 -0
  56. package/sdk/dist/namespaces/sender-domain.js.map +1 -1
  57. package/sdk/dist/scoped.d.ts +8 -1
  58. package/sdk/dist/scoped.d.ts.map +1 -1
  59. package/sdk/dist/scoped.js +21 -0
  60. package/sdk/dist/scoped.js.map +1 -1
  61. package/core-dist/wallet-auth.js +0 -62
  62. package/core-dist/wallet.js +0 -25
  63. package/sdk/core-dist/wallet-auth.js +0 -62
  64. package/sdk/core-dist/wallet.js +0 -25
package/lib/agent.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { allowanceAuthHeaders } from "./config.mjs";
2
2
  import { getSdk } from "./sdk.mjs";
3
- import { reportSdkError } from "./sdk-errors.mjs";
3
+ import { reportSdkError, fail } from "./sdk-errors.mjs";
4
4
 
5
5
  const HELP = `run402 agent — Manage agent identity
6
6
 
@@ -24,7 +24,9 @@ async function contact(args) {
24
24
  if (args[i] === "--email" && args[i + 1]) email = args[++i];
25
25
  if (args[i] === "--webhook" && args[i + 1]) webhook = args[++i];
26
26
  }
27
- if (!name) { console.error(JSON.stringify({ status: "error", message: "Missing --name <name>" })); process.exit(1); }
27
+ if (!name) {
28
+ fail({ code: "BAD_USAGE", message: "Missing --name <name>" });
29
+ }
28
30
  // Preserve the aggressive early exit when no allowance is configured.
29
31
  allowanceAuthHeaders("/agent/v1/contact");
30
32
 
package/lib/ai.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { resolveProjectId } from "./config.mjs";
2
2
  import { getSdk } from "./sdk.mjs";
3
- import { reportSdkError } from "./sdk-errors.mjs";
3
+ import { reportSdkError, fail } from "./sdk-errors.mjs";
4
4
 
5
5
  const HELP = `run402 ai — AI translation and moderation tools
6
6
 
@@ -13,10 +13,10 @@ Subcommands:
13
13
  usage <project_id>
14
14
 
15
15
  Examples:
16
- run402 ai translate proj-001 "Hello world" --to es
17
- run402 ai translate proj-001 "Hello" --to ja --from en --context "formal business email"
18
- run402 ai moderate proj-001 "content to check"
19
- run402 ai usage proj-001
16
+ run402 ai translate prj_abc123 "Hello world" --to es
17
+ run402 ai translate prj_abc123 "Hello" --to ja --from en --context "formal business email"
18
+ run402 ai moderate prj_abc123 "content to check"
19
+ run402 ai usage prj_abc123
20
20
 
21
21
  Notes:
22
22
  - translate requires the AI Translation add-on on the project
@@ -44,8 +44,8 @@ Notes:
44
44
  - Counts against the project's translation word quota
45
45
 
46
46
  Examples:
47
- run402 ai translate proj-001 "Hello world" --to es
48
- run402 ai translate proj-001 "Hello" --to ja --from en \\
47
+ run402 ai translate prj_abc123 "Hello world" --to es
48
+ run402 ai translate prj_abc123 "Hello" --to ja --from en \\
49
49
  --context "formal business email"
50
50
  `,
51
51
  };
@@ -76,8 +76,16 @@ async function translate(args) {
76
76
  const from = parseFlag(args, "--from");
77
77
  const context = parseFlag(args, "--context");
78
78
 
79
- if (!text) { console.error(JSON.stringify({ status: "error", message: "Text required. Usage: run402 ai translate <project_id> <text> --to <lang>" })); process.exit(1); }
80
- if (!to) { console.error(JSON.stringify({ status: "error", message: "--to <lang> is required" })); process.exit(1); }
79
+ if (!text) {
80
+ fail({
81
+ code: "BAD_USAGE",
82
+ message: "Text required.",
83
+ hint: "run402 ai translate <project_id> <text> --to <lang>",
84
+ });
85
+ }
86
+ if (!to) {
87
+ fail({ code: "BAD_USAGE", message: "--to <lang> is required" });
88
+ }
81
89
 
82
90
  try {
83
91
  const data = await getSdk().ai.translate(projectId, { text, to, from: from ?? undefined, context: context ?? undefined });
@@ -101,7 +109,13 @@ async function moderate(args) {
101
109
  const projectId = resolveProjectId(projectOpt || positional[0]);
102
110
  text = positional[1] || null;
103
111
 
104
- if (!text) { console.error(JSON.stringify({ status: "error", message: "Text required. Usage: run402 ai moderate <project_id> <text>" })); process.exit(1); }
112
+ if (!text) {
113
+ fail({
114
+ code: "BAD_USAGE",
115
+ message: "Text required.",
116
+ hint: "run402 ai moderate <project_id> <text>",
117
+ });
118
+ }
105
119
 
106
120
  try {
107
121
  const data = await getSdk().ai.moderate(projectId, text);
package/lib/allowance.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { readAllowance, saveAllowance, ALLOWANCE_FILE, API } from "./config.mjs";
2
2
  import { getSdk } from "./sdk.mjs";
3
- import { reportSdkError } from "./sdk-errors.mjs";
3
+ import { reportSdkError, fail } from "./sdk-errors.mjs";
4
4
 
5
5
  const HELP = `run402 allowance — Manage your agent allowance
6
6
 
@@ -82,7 +82,7 @@ async function status() {
82
82
  const data = await getSdk().allowance.status();
83
83
  if (!data.configured) {
84
84
  console.log(JSON.stringify({ status: "no_wallet", message: "No agent allowance found. Run: run402 allowance create" }));
85
- return;
85
+ process.exit(1);
86
86
  }
87
87
  // Preserve CLI's rail field (SDK doesn't surface it; read from local allowance).
88
88
  const w = readAllowance();
@@ -115,8 +115,11 @@ async function create() {
115
115
  } catch (err) {
116
116
  const msg = (err instanceof Error) ? err.message : String(err);
117
117
  if (/already exists/i.test(msg)) {
118
- console.log(JSON.stringify({ status: "error", message: "Agent allowance already exists. Use 'status' to check it." }));
119
- process.exit(1);
118
+ fail({
119
+ code: "ALLOWANCE_EXISTS",
120
+ message: "Agent allowance already exists.",
121
+ hint: "Use 'status' to check it.",
122
+ });
120
123
  }
121
124
  reportSdkError(err);
122
125
  }
@@ -124,7 +127,13 @@ async function create() {
124
127
 
125
128
  async function fund() {
126
129
  const w = readAllowance();
127
- if (!w) { console.log(JSON.stringify({ status: "error", message: "No agent allowance. Run: run402 allowance create" })); process.exit(1); }
130
+ if (!w) {
131
+ fail({
132
+ code: "NO_ALLOWANCE",
133
+ message: "No agent allowance.",
134
+ hint: "Run: run402 allowance create",
135
+ });
136
+ }
128
137
 
129
138
  if (w.rail === "mpp") {
130
139
  // Tempo Moderato faucet — instant, no polling needed
@@ -139,8 +148,11 @@ async function fund() {
139
148
  });
140
149
  const data = await res.json();
141
150
  if (data.error) {
142
- console.log(JSON.stringify({ status: "error", message: data.error.message || "Tempo faucet failed" }));
143
- process.exit(1);
151
+ fail({
152
+ code: "FAUCET_FAILED",
153
+ message: data.error.message || "Tempo faucet failed",
154
+ details: { rail: "mpp" },
155
+ });
144
156
  }
145
157
 
146
158
  // Re-read balance once (instant confirmation)
@@ -164,8 +176,11 @@ async function fund() {
164
176
  const res = await fetch(`${API}/faucet/v1`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ address: w.address }) });
165
177
  const data = await res.json();
166
178
  if (!res.ok) {
167
- console.log(JSON.stringify({ status: "error", ...data }));
168
- process.exit(1);
179
+ fail({
180
+ code: data?.code || "FAUCET_FAILED",
181
+ message: data?.message || "Faucet request failed",
182
+ details: { http: res.status, ...data },
183
+ });
169
184
  }
170
185
 
171
186
  const MAX_WAIT = 30;
@@ -196,7 +211,13 @@ async function readUsdcBalance(client, usdc, address) {
196
211
 
197
212
  async function balance() {
198
213
  const w = readAllowance();
199
- if (!w) { console.log(JSON.stringify({ status: "error", message: "No agent allowance. Run: run402 allowance create" })); process.exit(1); }
214
+ if (!w) {
215
+ fail({
216
+ code: "NO_ALLOWANCE",
217
+ message: "No agent allowance.",
218
+ hint: "Run: run402 allowance create",
219
+ });
220
+ }
200
221
 
201
222
  const { createPublicClient, http, base, baseSepolia, tempoModerato } = await loadDeps();
202
223
  const mainnetClient = createPublicClient({ chain: base, transport: http() });
@@ -229,19 +250,30 @@ async function exportAddr() {
229
250
  const address = await getSdk().allowance.export();
230
251
  console.log(address);
231
252
  } catch {
232
- console.log(JSON.stringify({ status: "error", message: "No agent allowance." }));
233
- process.exit(1);
253
+ fail({ code: "NO_ALLOWANCE", message: "No agent allowance." });
234
254
  }
235
255
  }
236
256
 
237
257
  async function checkout(args) {
238
258
  const w = readAllowance();
239
- if (!w) { console.log(JSON.stringify({ status: "error", message: "No agent allowance. Run: run402 allowance create" })); process.exit(1); }
259
+ if (!w) {
260
+ fail({
261
+ code: "NO_ALLOWANCE",
262
+ message: "No agent allowance.",
263
+ hint: "Run: run402 allowance create",
264
+ });
265
+ }
240
266
  let amount = null;
241
267
  for (let i = 0; i < args.length; i++) {
242
268
  if (args[i] === "--amount" && args[i + 1]) amount = parseInt(args[++i], 10);
243
269
  }
244
- if (!amount) { console.error(JSON.stringify({ status: "error", message: "Missing --amount <usd_micros> (e.g. --amount 5000000 for $5)" })); process.exit(1); }
270
+ if (!amount) {
271
+ fail({
272
+ code: "BAD_USAGE",
273
+ message: "Missing --amount <usd_micros>",
274
+ hint: "e.g. --amount 5000000 for $5",
275
+ });
276
+ }
245
277
  const res = await fetch(`${API}/billing/v1/checkouts`, {
246
278
  method: "POST",
247
279
  headers: { "Content-Type": "application/json" },
@@ -254,7 +286,13 @@ async function checkout(args) {
254
286
 
255
287
  async function history(args) {
256
288
  const w = readAllowance();
257
- if (!w) { console.log(JSON.stringify({ status: "error", message: "No agent allowance. Run: run402 allowance create" })); process.exit(1); }
289
+ if (!w) {
290
+ fail({
291
+ code: "NO_ALLOWANCE",
292
+ message: "No agent allowance.",
293
+ hint: "Run: run402 allowance create",
294
+ });
295
+ }
258
296
  let limit = 20;
259
297
  for (let i = 0; i < args.length; i++) {
260
298
  if (args[i] === "--limit" && args[i + 1]) limit = parseInt(args[++i], 10);
package/lib/apps.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { allowanceAuthHeaders, saveProject } from "./config.mjs";
2
2
  import { getSdk } from "./sdk.mjs";
3
- import { reportSdkError } from "./sdk-errors.mjs";
3
+ import { reportSdkError, fail } from "./sdk-errors.mjs";
4
4
 
5
5
  const HELP = `run402 apps — Browse and manage the app marketplace
6
6
 
@@ -23,11 +23,11 @@ Examples:
23
23
  run402 apps browse
24
24
  run402 apps browse --tag auth
25
25
  run402 apps fork ver_abc123 my-todo --tier prototype
26
- run402 apps publish proj123 --description "Todo app" --tags todo,auth --visibility public --fork-allowed
27
- run402 apps versions proj123
26
+ run402 apps publish prj_abc123 --description "Todo app" --tags todo,auth --visibility public --fork-allowed
27
+ run402 apps versions prj_abc123
28
28
  run402 apps inspect ver_abc123
29
- run402 apps update proj123 ver_abc123 --description "Updated" --tags todo
30
- run402 apps delete proj123 ver_abc123
29
+ run402 apps update prj_abc123 ver_abc123 --description "Updated" --tags todo
30
+ run402 apps delete prj_abc123 ver_abc123
31
31
  `;
32
32
 
33
33
  const SUB_HELP = {
@@ -76,8 +76,8 @@ Options:
76
76
  --fork-allowed Allow other users to fork this app
77
77
 
78
78
  Examples:
79
- run402 apps publish proj123 --description "Todo app" --tags todo,auth
80
- run402 apps publish proj123 --visibility public --fork-allowed
79
+ run402 apps publish prj_abc123 --description "Todo app" --tags todo,auth
80
+ run402 apps publish prj_abc123 --visibility public --fork-allowed
81
81
  `,
82
82
  update: `run402 apps update — Update a published version's metadata
83
83
 
@@ -96,9 +96,9 @@ Options:
96
96
  --no-fork Disable forking for this version
97
97
 
98
98
  Examples:
99
- run402 apps update proj123 ver_abc123 --description "Updated"
100
- run402 apps update proj123 ver_abc123 --tags todo,auth --fork-allowed
101
- run402 apps update proj123 ver_abc123 --no-fork
99
+ run402 apps update prj_abc123 ver_abc123 --description "Updated"
100
+ run402 apps update prj_abc123 ver_abc123 --tags todo,auth --fork-allowed
101
+ run402 apps update prj_abc123 ver_abc123 --no-fork
102
102
  `,
103
103
  };
104
104
 
@@ -177,7 +177,9 @@ async function versions(projectId) {
177
177
  }
178
178
 
179
179
  async function inspect(versionId) {
180
- if (!versionId) { console.error(JSON.stringify({ status: "error", message: "Missing version ID" })); process.exit(1); }
180
+ if (!versionId) {
181
+ fail({ code: "BAD_USAGE", message: "Missing version ID" });
182
+ }
181
183
  try {
182
184
  const data = await getSdk().apps.getApp(versionId);
183
185
  console.log(JSON.stringify(data, null, 2));
@@ -0,0 +1,147 @@
1
+ import { fail } from "./sdk-errors.mjs";
2
+
3
+ export function normalizeArgv(argv = []) {
4
+ const out = [];
5
+ for (const arg of argv ?? []) {
6
+ if (typeof arg === "string" && arg.startsWith("--") && arg.includes("=")) {
7
+ const eq = arg.indexOf("=");
8
+ out.push(arg.slice(0, eq), arg.slice(eq + 1));
9
+ } else {
10
+ out.push(arg);
11
+ }
12
+ }
13
+ return out;
14
+ }
15
+
16
+ export function hasHelp(args = []) {
17
+ return args.includes("--help") || args.includes("-h");
18
+ }
19
+
20
+ export function assertKnownFlags(args = [], knownFlags = [], flagsWithValues = []) {
21
+ const known = new Set(knownFlags);
22
+ const valueFlags = new Set(flagsWithValues);
23
+ for (let i = 0; i < args.length; i++) {
24
+ const arg = args[i];
25
+ if (valueFlags.has(arg)) {
26
+ i += 1;
27
+ continue;
28
+ }
29
+ if (typeof arg !== "string" || !arg.startsWith("-") || arg === "-") continue;
30
+ if (known.has(arg)) continue;
31
+ failUnknownFlag(arg, known);
32
+ }
33
+ }
34
+
35
+ export function failUnknownFlag(flag, knownFlags = []) {
36
+ const known = [...knownFlags].filter((f) => typeof f === "string" && f.startsWith("-"));
37
+ const closest = closestFlag(flag, known);
38
+ fail({
39
+ code: "UNKNOWN_FLAG",
40
+ message: closest ? `Unknown flag: ${flag}. Did you mean ${closest}?` : `Unknown flag: ${flag}.`,
41
+ details: { flag, closest: closest ? [closest] : [] },
42
+ });
43
+ }
44
+
45
+ export function flagValue(args, flag) {
46
+ const idx = args.indexOf(flag);
47
+ if (idx === -1) return null;
48
+ if (idx + 1 >= args.length) {
49
+ fail({
50
+ code: "BAD_FLAG",
51
+ message: `${flag} requires a value`,
52
+ details: { flag },
53
+ });
54
+ }
55
+ return args[idx + 1];
56
+ }
57
+
58
+ export function parseIntegerFlag(name, value, { min = 1, max = Number.POSITIVE_INFINITY, def } = {}) {
59
+ if (value === undefined || value === null) {
60
+ if (def !== undefined) return def;
61
+ fail({
62
+ code: "BAD_FLAG",
63
+ message: `${name} requires an integer value`,
64
+ details: { flag: name },
65
+ });
66
+ }
67
+ const raw = String(value);
68
+ if (!/^-?\d+$/.test(raw)) {
69
+ fail({
70
+ code: "BAD_FLAG",
71
+ message: `${name} must be an integer, got: ${raw}`,
72
+ details: { flag: name, value: raw },
73
+ });
74
+ }
75
+ const n = Number.parseInt(raw, 10);
76
+ if (n < min) {
77
+ fail({
78
+ code: "BAD_FLAG",
79
+ message: `${name} must be >= ${min}, got: ${n}`,
80
+ details: { flag: name, value: n, min },
81
+ });
82
+ }
83
+ if (n > max) {
84
+ fail({
85
+ code: "BAD_FLAG",
86
+ message: `${name} must be <= ${max}, got: ${n}`,
87
+ details: { flag: name, value: n, max },
88
+ });
89
+ }
90
+ return n;
91
+ }
92
+
93
+ export function failBadProjectId(value) {
94
+ fail({
95
+ code: "BAD_PROJECT_ID",
96
+ message: `Argument '${value}' is not a project id. Project IDs must start with 'prj_'.`,
97
+ hint: "Omit the project id to use the active project, or pass the full prj_... id.",
98
+ details: { value, expected_prefix: "prj_" },
99
+ });
100
+ }
101
+
102
+ export function positionalArgs(args = [], flagsWithValues = []) {
103
+ const valueFlags = new Set(flagsWithValues);
104
+ const out = [];
105
+ for (let i = 0; i < args.length; i++) {
106
+ const arg = args[i];
107
+ if (valueFlags.has(arg)) {
108
+ i += 1;
109
+ continue;
110
+ }
111
+ if (typeof arg === "string" && arg.startsWith("-")) continue;
112
+ out.push(arg);
113
+ }
114
+ return out;
115
+ }
116
+
117
+ function closestFlag(flag, candidates) {
118
+ let best = null;
119
+ let bestDistance = Number.POSITIVE_INFINITY;
120
+ for (const candidate of candidates) {
121
+ const d = levenshtein(flag, candidate);
122
+ if (d < bestDistance) {
123
+ best = candidate;
124
+ bestDistance = d;
125
+ }
126
+ }
127
+ if (!best) return null;
128
+ return bestDistance <= 3 ? best : null;
129
+ }
130
+
131
+ function levenshtein(a, b) {
132
+ const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
133
+ const curr = Array.from({ length: b.length + 1 }, () => 0);
134
+ for (let i = 1; i <= a.length; i++) {
135
+ curr[0] = i;
136
+ for (let j = 1; j <= b.length; j++) {
137
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
138
+ curr[j] = Math.min(
139
+ curr[j - 1] + 1,
140
+ prev[j] + 1,
141
+ prev[j - 1] + cost,
142
+ );
143
+ }
144
+ for (let j = 0; j <= b.length; j++) prev[j] = curr[j];
145
+ }
146
+ return prev[b.length];
147
+ }
package/lib/auth.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { findProject, resolveProjectId, API } from "./config.mjs";
2
2
  import { getSdk } from "./sdk.mjs";
3
- import { reportSdkError } from "./sdk-errors.mjs";
3
+ import { reportSdkError, fail } from "./sdk-errors.mjs";
4
4
 
5
5
  const HELP = `run402 auth — Manage project user authentication
6
6
 
@@ -93,7 +93,7 @@ Notes:
93
93
 
94
94
  Examples:
95
95
  run402 auth settings --allow-password-set true
96
- run402 auth settings --allow-password-set false --project abc123
96
+ run402 auth settings --allow-password-set false --project prj_abc123
97
97
  `,
98
98
  providers: `run402 auth providers — List available auth providers
99
99
 
@@ -105,7 +105,7 @@ Options:
105
105
 
106
106
  Examples:
107
107
  run402 auth providers
108
- run402 auth providers --project abc123
108
+ run402 auth providers --project prj_abc123
109
109
  `,
110
110
  };
111
111
 
@@ -121,8 +121,12 @@ async function magicLink(args) {
121
121
  const redirect = parseFlag(args, "--redirect");
122
122
  const projectId = resolveProjectId(parseFlag(args, "--project"));
123
123
 
124
- if (!email) { console.error(JSON.stringify({ status: "error", message: "Missing --email" })); process.exit(1); }
125
- if (!redirect) { console.error(JSON.stringify({ status: "error", message: "Missing --redirect <url>" })); process.exit(1); }
124
+ if (!email) {
125
+ fail({ code: "BAD_USAGE", message: "Missing --email" });
126
+ }
127
+ if (!redirect) {
128
+ fail({ code: "BAD_USAGE", message: "Missing --redirect <url>" });
129
+ }
126
130
 
127
131
  try {
128
132
  await getSdk().auth.requestMagicLink(projectId, { email, redirectUrl: redirect });
@@ -136,7 +140,9 @@ async function verify(args) {
136
140
  const token = parseFlag(args, "--token");
137
141
  const projectId = resolveProjectId(parseFlag(args, "--project"));
138
142
 
139
- if (!token) { console.error(JSON.stringify({ status: "error", message: "Missing --token" })); process.exit(1); }
143
+ if (!token) {
144
+ fail({ code: "BAD_USAGE", message: "Missing --token" });
145
+ }
140
146
 
141
147
  try {
142
148
  const data = await getSdk().auth.verifyMagicLink(projectId, token);
@@ -152,8 +158,12 @@ async function setPassword(args) {
152
158
  const currentPassword = parseFlag(args, "--current");
153
159
  const projectId = resolveProjectId(parseFlag(args, "--project"));
154
160
 
155
- if (!accessToken) { console.error(JSON.stringify({ status: "error", message: "Missing --token <bearer_token>" })); process.exit(1); }
156
- if (!newPassword) { console.error(JSON.stringify({ status: "error", message: "Missing --new <password>" })); process.exit(1); }
161
+ if (!accessToken) {
162
+ fail({ code: "BAD_USAGE", message: "Missing --token <bearer_token>" });
163
+ }
164
+ if (!newPassword) {
165
+ fail({ code: "BAD_USAGE", message: "Missing --new <password>" });
166
+ }
157
167
 
158
168
  try {
159
169
  await getSdk().auth.setUserPassword(projectId, {
@@ -171,7 +181,12 @@ async function settings(args) {
171
181
  const allowPasswordSet = parseFlag(args, "--allow-password-set");
172
182
  const projectId = resolveProjectId(parseFlag(args, "--project"));
173
183
 
174
- if (allowPasswordSet === null) { console.error(JSON.stringify({ status: "error", message: "Missing --allow-password-set <true|false>" })); process.exit(1); }
184
+ if (allowPasswordSet === null) {
185
+ fail({
186
+ code: "BAD_USAGE",
187
+ message: "Missing --allow-password-set <true|false>",
188
+ });
189
+ }
175
190
 
176
191
  try {
177
192
  await getSdk().auth.settings(projectId, { allow_password_set: allowPasswordSet === "true" });
package/lib/billing.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { API } from "./config.mjs";
2
2
  import { getSdk } from "./sdk.mjs";
3
- import { reportSdkError } from "./sdk-errors.mjs";
3
+ import { reportSdkError, fail } from "./sdk-errors.mjs";
4
4
 
5
5
  const HELP = `run402 billing — Email billing accounts, Stripe tier checkout, email packs
6
6
 
@@ -97,8 +97,11 @@ function parseFlag(args, flag) {
97
97
  async function createEmail(args) {
98
98
  const email = args[0];
99
99
  if (!email) {
100
- console.error(JSON.stringify({ status: "error", message: "Missing email. Usage: run402 billing create-email <email>" }));
101
- process.exit(1);
100
+ fail({
101
+ code: "BAD_USAGE",
102
+ message: "Missing email.",
103
+ hint: "run402 billing create-email <email>",
104
+ });
102
105
  }
103
106
  try {
104
107
  const data = await getSdk().billing.createEmailAccount(email);
@@ -112,8 +115,11 @@ async function linkWallet(args) {
112
115
  const accountId = args[0];
113
116
  const wallet = args[1];
114
117
  if (!accountId || !wallet) {
115
- console.error(JSON.stringify({ status: "error", message: "Usage: run402 billing link-wallet <account_id> <wallet>" }));
116
- process.exit(1);
118
+ fail({
119
+ code: "BAD_USAGE",
120
+ message: "Missing <account_id> and/or <wallet>.",
121
+ hint: "run402 billing link-wallet <account_id> <wallet>",
122
+ });
117
123
  }
118
124
  try {
119
125
  await getSdk().billing.linkWallet(accountId, wallet);
@@ -126,14 +132,16 @@ async function linkWallet(args) {
126
132
  async function tierCheckout(args) {
127
133
  const tier = args[0];
128
134
  if (!tier) {
129
- console.error(JSON.stringify({ status: "error", message: "Usage: run402 billing tier-checkout <tier> [--email <e> | --wallet <w>]" }));
130
- process.exit(1);
135
+ fail({
136
+ code: "BAD_USAGE",
137
+ message: "Missing <tier>.",
138
+ hint: "run402 billing tier-checkout <tier> [--email <e> | --wallet <w>]",
139
+ });
131
140
  }
132
141
  const email = parseFlag(args, "--email");
133
142
  const wallet = parseFlag(args, "--wallet");
134
143
  if (!email && !wallet) {
135
- console.error(JSON.stringify({ status: "error", message: "Must provide --email or --wallet" }));
136
- process.exit(1);
144
+ fail({ code: "BAD_USAGE", message: "Must provide --email or --wallet" });
137
145
  }
138
146
  try {
139
147
  const data = await getSdk().billing.tierCheckout(tier, { email: email ?? undefined, wallet: wallet ?? undefined });
@@ -147,8 +155,7 @@ async function buyPack(args) {
147
155
  const email = parseFlag(args, "--email");
148
156
  const wallet = parseFlag(args, "--wallet");
149
157
  if (!email && !wallet) {
150
- console.error(JSON.stringify({ status: "error", message: "Must provide --email or --wallet" }));
151
- process.exit(1);
158
+ fail({ code: "BAD_USAGE", message: "Must provide --email or --wallet" });
152
159
  }
153
160
  try {
154
161
  const data = await getSdk().billing.buyEmailPack({ email: email ?? undefined, wallet: wallet ?? undefined });
@@ -162,8 +169,11 @@ async function autoRecharge(args) {
162
169
  const accountId = args[0];
163
170
  const state = args[1];
164
171
  if (!accountId || !state || !["on", "off"].includes(state)) {
165
- console.error(JSON.stringify({ status: "error", message: "Usage: run402 billing auto-recharge <account_id> <on|off> [--threshold <n>]" }));
166
- process.exit(1);
172
+ fail({
173
+ code: "BAD_USAGE",
174
+ message: "Missing <account_id> and/or <on|off>.",
175
+ hint: "run402 billing auto-recharge <account_id> <on|off> [--threshold <n>]",
176
+ });
167
177
  }
168
178
  const thresholdStr = parseFlag(args, "--threshold");
169
179
  try {
@@ -182,8 +192,11 @@ async function balance(args) {
182
192
  // Accepts email OR wallet — SDK only models wallet, so keep direct fetch.
183
193
  const id = args[0];
184
194
  if (!id) {
185
- console.error(JSON.stringify({ status: "error", message: "Usage: run402 billing balance <email-or-wallet>" }));
186
- process.exit(1);
195
+ fail({
196
+ code: "BAD_USAGE",
197
+ message: "Missing <email-or-wallet>.",
198
+ hint: "run402 billing balance <email-or-wallet>",
199
+ });
187
200
  }
188
201
  const res = await fetch(`${API}/billing/v1/accounts/${encodeURIComponent(id)}`);
189
202
  const data = await res.json();
@@ -194,8 +207,11 @@ async function balance(args) {
194
207
  async function history(args) {
195
208
  const id = args[0];
196
209
  if (!id) {
197
- console.error(JSON.stringify({ status: "error", message: "Usage: run402 billing history <email-or-wallet> [--limit <n>]" }));
198
- process.exit(1);
210
+ fail({
211
+ code: "BAD_USAGE",
212
+ message: "Missing <email-or-wallet>.",
213
+ hint: "run402 billing history <email-or-wallet> [--limit <n>]",
214
+ });
199
215
  }
200
216
  const limit = parseFlag(args, "--limit") || "50";
201
217
  const res = await fetch(`${API}/billing/v1/accounts/${encodeURIComponent(id)}/history?limit=${encodeURIComponent(limit)}`);