run402 1.54.0 → 1.54.1

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/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
 
@@ -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
 
@@ -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));
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
 
@@ -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)}`);
package/lib/blob.mjs CHANGED
@@ -36,7 +36,7 @@ import { pipeline } from "node:stream/promises";
36
36
 
37
37
  import { resolveProject, resolveProjectId, API } from "./config.mjs";
38
38
  import { getSdk } from "./sdk.mjs";
39
- import { reportSdkError } from "./sdk-errors.mjs";
39
+ import { reportSdkError, fail } from "./sdk-errors.mjs";
40
40
 
41
41
  const HELP = `run402 blob — Direct-to-S3 blob storage
42
42
 
@@ -182,9 +182,8 @@ Examples:
182
182
 
183
183
  const UPLOAD_STATE_DIR = join(homedir(), ".run402", "uploads");
184
184
 
185
- function die(msg, code = 1) {
186
- console.error(JSON.stringify({ status: "error", message: msg }));
187
- process.exit(code);
185
+ function die(msg, exit_code = 1) {
186
+ fail({ code: "BAD_USAGE", message: msg, exit_code });
188
187
  }
189
188
 
190
189
  function parseArgs(args) {
package/lib/cdn.mjs CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  import { resolveProjectId } from "./config.mjs";
17
17
  import { getSdk } from "./sdk.mjs";
18
- import { reportSdkError } from "./sdk-errors.mjs";
18
+ import { reportSdkError, fail } from "./sdk-errors.mjs";
19
19
 
20
20
  const HELP = `run402 cdn — CloudFront CDN diagnostics for public blob URLs
21
21
 
@@ -68,9 +68,8 @@ Examples:
68
68
  `,
69
69
  };
70
70
 
71
- function die(msg, code = 1) {
72
- console.error(JSON.stringify({ status: "error", message: msg }));
73
- process.exit(code);
71
+ function die(msg, exit_code = 1) {
72
+ fail({ code: "BAD_USAGE", message: msg, exit_code });
74
73
  }
75
74
 
76
75
  function parseArgs(args) {
package/lib/config.mjs CHANGED
@@ -7,6 +7,7 @@ import { getApiBase, getConfigDir, getKeystorePath, getAllowancePath } from "../
7
7
  import { readAllowance as coreReadAllowance, saveAllowance as coreSaveAllowance } from "../core-dist/allowance.js";
8
8
  import { loadKeyStore, getProject, saveProject, updateProject, removeProject, saveKeyStore, getActiveProjectId, setActiveProjectId } from "../core-dist/keystore.js";
9
9
  import { getAllowanceAuthHeaders as coreGetAllowanceAuthHeaders } from "../core-dist/allowance-auth.js";
10
+ import { fail } from "./sdk-errors.mjs";
10
11
 
11
12
  export const CONFIG_DIR = getConfigDir();
12
13
  export const ALLOWANCE_FILE = getAllowancePath();
@@ -23,31 +24,54 @@ export function saveAllowance(data) {
23
24
 
24
25
  export function allowanceAuthHeaders(path) {
25
26
  const headers = coreGetAllowanceAuthHeaders(path);
26
- if (!headers) { console.error(JSON.stringify({ status: "error", message: "No agent allowance found. Run: run402 allowance create" })); process.exit(1); }
27
+ if (!headers) {
28
+ fail({
29
+ code: "NO_ALLOWANCE",
30
+ message: "No agent allowance found.",
31
+ hint: "Run: run402 allowance create",
32
+ });
33
+ }
27
34
  return headers;
28
35
  }
29
36
 
30
37
  export function findProject(id) {
31
38
  const p = getProject(id);
32
39
  if (!p) {
33
- const hint = id && !id.startsWith("prj_")
34
- ? ` Hint: project IDs start with "prj_". Check that the argument order is <project_id> <name>.`
35
- : "";
36
- console.error(`Project ${id} not found in local registry.${hint}`);
37
- process.exit(1);
40
+ const idStr = id ?? "";
41
+ const hint = idStr && !String(idStr).startsWith("prj_")
42
+ ? `project IDs start with "prj_". Check that the argument order is <project_id> <name>.`
43
+ : undefined;
44
+ fail({
45
+ code: "PROJECT_NOT_FOUND",
46
+ message: `Project ${idStr} not found in local registry.`,
47
+ hint,
48
+ details: { project_id: idStr, source: "local_registry" },
49
+ });
38
50
  }
39
51
  return p;
40
52
  }
41
53
 
42
54
  export function resolveProject(id) {
43
55
  const projectId = id || getActiveProjectId();
44
- if (!projectId) { console.error("Error: no project specified and no active project set. Run: run402 projects provision"); process.exit(1); }
56
+ if (!projectId) {
57
+ fail({
58
+ code: "NO_ACTIVE_PROJECT",
59
+ message: "no project specified and no active project set.",
60
+ hint: "Run: run402 projects provision",
61
+ });
62
+ }
45
63
  return findProject(projectId);
46
64
  }
47
65
 
48
66
  export function resolveProjectId(id) {
49
67
  const projectId = id || getActiveProjectId();
50
- if (!projectId) { console.error("Error: no project specified and no active project set. Run: run402 projects provision"); process.exit(1); }
68
+ if (!projectId) {
69
+ fail({
70
+ code: "NO_ACTIVE_PROJECT",
71
+ message: "no project specified and no active project set.",
72
+ hint: "Run: run402 projects provision",
73
+ });
74
+ }
51
75
  return projectId;
52
76
  }
53
77
 
package/lib/contracts.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { findProject, API } from "./config.mjs";
2
2
  import { getSdk } from "./sdk.mjs";
3
- import { reportSdkError } from "./sdk-errors.mjs";
3
+ import { reportSdkError, fail, parseFlagJson } from "./sdk-errors.mjs";
4
4
 
5
5
  const HELP = `run402 contracts — KMS-backed Ethereum wallets for smart-contract calls
6
6
 
@@ -100,8 +100,10 @@ async function provisionWallet(projectId, args) {
100
100
  const p = findProject(projectId);
101
101
  const chain = parseFlag(args, "--chain");
102
102
  if (!chain) {
103
- console.error(JSON.stringify({ status: "error", message: "Missing --chain (base-mainnet or base-sepolia)" }));
104
- process.exit(1);
103
+ fail({
104
+ code: "BAD_USAGE",
105
+ message: "Missing --chain (base-mainnet or base-sepolia)",
106
+ });
105
107
  }
106
108
  const recovery = parseFlag(args, "--recovery");
107
109
  // Soft default of one wallet — confirm if project already has one. This
@@ -115,8 +117,11 @@ async function provisionWallet(projectId, args) {
115
117
  const list = await listRes.json();
116
118
  const active = (list.wallets || []).filter((w) => w.status === "active");
117
119
  if (active.length >= 1 && !hasFlag(args, "--yes")) {
118
- console.error(`This project already has ${active.length} active wallet(s). Adding another costs $0.04/day each ($1.20/month). Re-run with --yes to confirm.`);
119
- process.exit(1);
120
+ fail({
121
+ code: "CONFIRMATION_REQUIRED",
122
+ message: `This project already has ${active.length} active wallet(s). Adding another costs $0.04/day each ($1.20/month). Re-run with --yes to confirm.`,
123
+ details: { active_wallets: active.length },
124
+ });
120
125
  }
121
126
  }
122
127
  } catch { /* best-effort */ }
@@ -154,8 +159,10 @@ async function setRecovery(projectId, walletId, args) {
154
159
  const clear = hasFlag(args, "--clear");
155
160
  const address = parseFlag(args, "--address");
156
161
  if (!clear && !address) {
157
- console.error(JSON.stringify({ status: "error", message: "Provide --address 0x... or --clear" }));
158
- process.exit(1);
162
+ fail({
163
+ code: "BAD_USAGE",
164
+ message: "Provide --address 0x... or --clear",
165
+ });
159
166
  }
160
167
  try {
161
168
  await getSdk().contracts.setRecovery(projectId, walletId, clear ? null : address);
@@ -168,8 +175,7 @@ async function setRecovery(projectId, walletId, args) {
168
175
  async function setAlert(projectId, walletId, args) {
169
176
  const threshold = parseFlag(args, "--threshold-wei");
170
177
  if (!threshold) {
171
- console.error(JSON.stringify({ status: "error", message: "Missing --threshold-wei <n>" }));
172
- process.exit(1);
178
+ fail({ code: "BAD_USAGE", message: "Missing --threshold-wei <n>" });
173
179
  }
174
180
  try {
175
181
  await getSdk().contracts.setLowBalanceAlert(projectId, walletId, threshold);
@@ -188,17 +194,22 @@ async function call(projectId, walletId, args) {
188
194
  const chain = parseFlag(args, "--chain") || "base-mainnet";
189
195
  const idempotency = parseFlag(args, "--idempotency-key");
190
196
  if (!to || !abi || !fn || !argsJson) {
191
- console.error(JSON.stringify({ status: "error", message: "Required flags: --to, --abi, --fn, --args. Cost: chain gas + $0.000005 KMS sign fee." }));
192
- process.exit(1);
197
+ fail({
198
+ code: "BAD_USAGE",
199
+ message: "Required flags: --to, --abi, --fn, --args.",
200
+ hint: "Cost: chain gas + $0.000005 KMS sign fee.",
201
+ });
193
202
  }
203
+ const abiFragment = parseFlagJson("--abi", abi);
204
+ const callArgs = parseFlagJson("--args", argsJson);
194
205
  try {
195
206
  const data = await getSdk().contracts.call(projectId, {
196
207
  walletId,
197
208
  chain,
198
209
  contractAddress: to,
199
- abiFragment: JSON.parse(abi),
210
+ abiFragment,
200
211
  functionName: fn,
201
- args: JSON.parse(argsJson),
212
+ args: callArgs,
202
213
  value: value ?? undefined,
203
214
  idempotencyKey: idempotency ?? undefined,
204
215
  });
@@ -215,16 +226,20 @@ async function read(args) {
215
226
  const fn = parseFlag(args, "--fn");
216
227
  const argsJson = parseFlag(args, "--args");
217
228
  if (!chain || !to || !abi || !fn || !argsJson) {
218
- console.error(JSON.stringify({ status: "error", message: "Required flags: --chain, --to, --abi, --fn, --args" }));
219
- process.exit(1);
229
+ fail({
230
+ code: "BAD_USAGE",
231
+ message: "Required flags: --chain, --to, --abi, --fn, --args",
232
+ });
220
233
  }
234
+ const abiFragment = parseFlagJson("--abi", abi);
235
+ const callArgs = parseFlagJson("--args", argsJson);
221
236
  try {
222
237
  const data = await getSdk().contracts.read({
223
238
  chain,
224
239
  contractAddress: to,
225
- abiFragment: JSON.parse(abi),
240
+ abiFragment,
226
241
  functionName: fn,
227
- args: JSON.parse(argsJson),
242
+ args: callArgs,
228
243
  });
229
244
  console.log(JSON.stringify(data, null, 2));
230
245
  } catch (err) {
@@ -244,8 +259,11 @@ async function status(projectId, callId) {
244
259
  async function drain(projectId, walletId, args) {
245
260
  const to = parseFlag(args, "--to");
246
261
  if (!to || !hasFlag(args, "--confirm")) {
247
- console.error(JSON.stringify({ status: "error", message: "Required: --to 0x... and --confirm. Cost: chain gas + $0.000005 KMS sign fee." }));
248
- process.exit(1);
262
+ fail({
263
+ code: "BAD_USAGE",
264
+ message: "Required: --to 0x... and --confirm.",
265
+ hint: "Cost: chain gas + $0.000005 KMS sign fee.",
266
+ });
249
267
  }
250
268
  try {
251
269
  const data = await getSdk().contracts.drain(projectId, walletId, to);
@@ -257,8 +275,7 @@ async function drain(projectId, walletId, args) {
257
275
 
258
276
  async function deleteWallet(projectId, walletId, args) {
259
277
  if (!hasFlag(args, "--confirm")) {
260
- console.error(JSON.stringify({ status: "error", message: "Required: --confirm" }));
261
- process.exit(1);
278
+ fail({ code: "BAD_USAGE", message: "Required: --confirm" });
262
279
  }
263
280
  try {
264
281
  const data = await getSdk().contracts.deleteWallet(projectId, walletId);