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/deploy-v2.mjs CHANGED
@@ -26,7 +26,7 @@
26
26
  import { readFileSync } from "node:fs";
27
27
  import { resolve, dirname, isAbsolute, join } from "node:path";
28
28
  import { getSdk } from "./sdk.mjs";
29
- import { reportSdkError } from "./sdk-errors.mjs";
29
+ import { reportSdkError, fail } from "./sdk-errors.mjs";
30
30
  import { allowanceAuthHeaders, resolveProjectId } from "./config.mjs";
31
31
 
32
32
  const APPLY_HELP = `run402 deploy apply — Unified deploy primitive (v1.34+)
@@ -107,8 +107,11 @@ export async function runDeployV2(sub, args) {
107
107
  if (sub === "resume") return await resumeCmd(args);
108
108
  if (sub === "list") return await listCmd(args);
109
109
  if (sub === "events") return await eventsCmd(args);
110
- console.error(JSON.stringify({ status: "error", message: `Unknown deploy subcommand: ${sub}` }));
111
- process.exit(1);
110
+ fail({
111
+ code: "BAD_USAGE",
112
+ message: `Unknown deploy subcommand: ${sub}`,
113
+ details: { subcommand: sub },
114
+ });
112
115
  }
113
116
 
114
117
  async function readStdin() {
@@ -142,8 +145,11 @@ async function applyCmd(args) {
142
145
  const manifestPath = isAbsolute(opts.manifest) ? opts.manifest : resolve(process.cwd(), opts.manifest);
143
146
  raw = readFileSync(manifestPath, "utf-8");
144
147
  } catch (err) {
145
- console.error(JSON.stringify({ status: "error", message: `Failed to read manifest: ${err.message}` }));
146
- process.exit(1);
148
+ fail({
149
+ code: "BAD_USAGE",
150
+ message: `Failed to read manifest: ${err.message}`,
151
+ details: { flag: "--manifest", path: opts.manifest },
152
+ });
147
153
  }
148
154
  } else {
149
155
  raw = await readStdin();
@@ -153,18 +159,21 @@ async function applyCmd(args) {
153
159
  try {
154
160
  spec = JSON.parse(raw);
155
161
  } catch (err) {
156
- console.error(JSON.stringify({ status: "error", message: `Manifest is not valid JSON: ${err.message}` }));
157
- process.exit(1);
162
+ fail({
163
+ code: "BAD_USAGE",
164
+ message: `Manifest is not valid JSON: ${err.message}`,
165
+ details: { source: opts.manifest ? "manifest" : opts.spec ? "spec" : "stdin", parse_error: err.message },
166
+ });
158
167
  }
159
168
 
160
169
  if (opts.manifest) resolveFileDataPaths(spec, dirname(resolve(opts.manifest)));
161
170
 
162
171
  if (opts.project && spec.project_id && spec.project_id !== opts.project) {
163
- console.error(JSON.stringify({
164
- status: "error",
172
+ fail({
173
+ code: "BAD_USAGE",
165
174
  message: `project_id conflict: spec.project_id=${spec.project_id} but --project=${opts.project}`,
166
- }));
167
- process.exit(1);
175
+ details: { spec_project_id: spec.project_id, flag_project_id: opts.project },
176
+ });
168
177
  }
169
178
  if (opts.project) spec.project_id = opts.project;
170
179
  if (!spec.project_id) spec.project_id = resolveProjectId(null);
@@ -198,8 +207,11 @@ async function resumeCmd(args) {
198
207
  if (!args[i].startsWith("-") && !opts.operationId) opts.operationId = args[i];
199
208
  }
200
209
  if (!opts.operationId) {
201
- console.error(JSON.stringify({ status: "error", message: "Usage: run402 deploy resume <operation_id>" }));
202
- process.exit(1);
210
+ fail({
211
+ code: "BAD_USAGE",
212
+ message: "Missing <operation_id>.",
213
+ hint: "run402 deploy resume <operation_id>",
214
+ });
203
215
  }
204
216
 
205
217
  allowanceAuthHeaders("/deploy/v2/operations");
@@ -243,8 +255,11 @@ async function eventsCmd(args) {
243
255
  if (!args[i].startsWith("-") && !opts.operationId) opts.operationId = args[i];
244
256
  }
245
257
  if (!opts.operationId) {
246
- console.error(JSON.stringify({ status: "error", message: "Usage: run402 deploy events <operation_id>" }));
247
- process.exit(1);
258
+ fail({
259
+ code: "BAD_USAGE",
260
+ message: "Missing <operation_id>.",
261
+ hint: "run402 deploy events <operation_id>",
262
+ });
248
263
  }
249
264
 
250
265
  const project = resolveProjectId(opts.project);
@@ -383,11 +398,11 @@ function resolveFileDataPaths(spec, baseDir) {
383
398
  m.sql = readFileSync(p, "utf-8");
384
399
  delete m.sql_path;
385
400
  } catch (err) {
386
- console.error(JSON.stringify({
387
- status: "error",
401
+ fail({
402
+ code: "BAD_USAGE",
388
403
  message: `Failed to read migration sql_path '${m.sql_path}': ${err.message}`,
389
- }));
390
- process.exit(1);
404
+ details: { migration_id: m.id, sql_path: m.sql_path },
405
+ });
391
406
  }
392
407
  }
393
408
  }
@@ -421,10 +436,10 @@ function readFileEntry(entry, baseDir) {
421
436
  if (entry.contentType) out.contentType = entry.contentType;
422
437
  return out;
423
438
  } catch (err) {
424
- console.error(JSON.stringify({
425
- status: "error",
439
+ fail({
440
+ code: "BAD_USAGE",
426
441
  message: `Failed to read file '${entry.path}': ${err.message}`,
427
- }));
428
- process.exit(1);
442
+ details: { path: entry.path },
443
+ });
429
444
  }
430
445
  }
package/lib/deploy.mjs CHANGED
@@ -3,7 +3,7 @@ import { dirname, resolve } from "path";
3
3
  import { resolveProjectId } from "./config.mjs";
4
4
  import { resolveFilePathsInManifest, resolveMigrationsFile } from "./manifest.mjs";
5
5
  import { getSdk } from "./sdk.mjs";
6
- import { reportSdkError } from "./sdk-errors.mjs";
6
+ import { reportSdkError, fail } from "./sdk-errors.mjs";
7
7
 
8
8
  const HELP = `run402 deploy — Deploy to an existing project on Run402
9
9
 
@@ -111,8 +111,8 @@ async function readStdin() {
111
111
  * Load + parse the manifest from --manifest file or stdin, and resolve any
112
112
  * referenced files[].path / migrations_file against the manifest's directory.
113
113
  *
114
- * Returns { manifest } on success, or { error } with a structured error object
115
- * on any fs / parse failure. Never throws.
114
+ * Returns the parsed manifest on success. On any fs / parse failure, calls
115
+ * `fail()` (which writes the canonical error envelope to stderr and exits 1).
116
116
  */
117
117
  async function loadManifest(opts) {
118
118
  let raw;
@@ -125,21 +125,18 @@ async function loadManifest(opts) {
125
125
  raw = readFileSync(opts.manifest, "utf-8");
126
126
  } catch (err) {
127
127
  if (err && err.code === "ENOENT") {
128
- return { error: {
129
- status: "error",
128
+ fail({
129
+ code: "BAD_USAGE",
130
130
  message: `File not found: ${manifestAbs}`,
131
- field: "manifest",
132
- path: manifestAbs,
133
131
  hint: "Check that --manifest points to an existing JSON file.",
134
- } };
132
+ details: { field: "manifest", path: manifestAbs },
133
+ });
135
134
  }
136
- return { error: {
137
- status: "error",
135
+ fail({
136
+ code: "BAD_USAGE",
138
137
  message: err && err.message ? err.message : String(err),
139
- field: "manifest",
140
- path: manifestAbs,
141
- ...(err && err.code ? { code: err.code } : {}),
142
- } };
138
+ details: { field: "manifest", path: manifestAbs, ...(err && err.code ? { syscall_code: err.code } : {}) },
139
+ });
143
140
  }
144
141
  } else {
145
142
  raw = await readStdin();
@@ -149,12 +146,15 @@ async function loadManifest(opts) {
149
146
  try {
150
147
  manifest = JSON.parse(raw);
151
148
  } catch (err) {
152
- return { error: {
153
- status: "error",
149
+ fail({
150
+ code: "BAD_USAGE",
154
151
  message: `Manifest is not valid JSON: ${err.message}`,
155
- field: opts.manifest ? "manifest" : "stdin",
156
- ...(opts.manifest ? { path: resolve(opts.manifest) } : {}),
157
- } };
152
+ details: {
153
+ field: opts.manifest ? "manifest" : "stdin",
154
+ ...(opts.manifest ? { path: resolve(opts.manifest) } : {}),
155
+ parse_error: err.message,
156
+ },
157
+ });
158
158
  }
159
159
 
160
160
  if (opts.manifest) {
@@ -163,25 +163,29 @@ async function loadManifest(opts) {
163
163
  resolveFilePathsInManifest(manifest, baseDir);
164
164
  } catch (err) {
165
165
  if (err && err.code === "ENOENT") {
166
- return { error: {
167
- status: "error",
166
+ fail({
167
+ code: "BAD_USAGE",
168
168
  message: `File not found: ${err.absPath || err.path || "<unknown>"}`,
169
- field: err.field || "manifest",
170
- ...(err.absPath || err.path ? { path: err.absPath || err.path } : {}),
171
169
  hint: `Paths in manifest.${err.field || "files[].path"} are resolved relative to the manifest file's directory (${baseDir}).`,
172
- } };
170
+ details: {
171
+ field: err.field || "manifest",
172
+ ...(err.absPath || err.path ? { path: err.absPath || err.path } : {}),
173
+ },
174
+ });
173
175
  }
174
- return { error: {
175
- status: "error",
176
+ fail({
177
+ code: "BAD_USAGE",
176
178
  message: err && err.message ? err.message : String(err),
177
- ...(err && err.field ? { field: err.field } : {}),
178
- ...(err && (err.absPath || err.path) ? { path: err.absPath || err.path } : {}),
179
- ...(err && err.code ? { code: err.code } : {}),
180
- } };
179
+ details: {
180
+ ...(err && err.field ? { field: err.field } : {}),
181
+ ...(err && (err.absPath || err.path) ? { path: err.absPath || err.path } : {}),
182
+ ...(err && err.code ? { syscall_code: err.code } : {}),
183
+ },
184
+ });
181
185
  }
182
186
  }
183
187
 
184
- return { manifest };
188
+ return manifest;
185
189
  }
186
190
 
187
191
  export async function run(args) {
@@ -210,25 +214,20 @@ export async function run(args) {
210
214
  if (args[i] === "--project" && args[i + 1]) opts.project = args[++i];
211
215
  }
212
216
 
213
- const manifestResult = await loadManifest(opts);
214
- if (manifestResult.error) {
215
- console.error(JSON.stringify(manifestResult.error));
216
- process.exit(1);
217
- }
218
- const manifest = manifestResult.manifest;
217
+ const manifest = await loadManifest(opts);
219
218
 
220
219
  // If both sources set project_id and they disagree, refuse to deploy rather
221
220
  // than silently shipping to the wrong target.
222
221
  if (opts.project && manifest.project_id && opts.project !== manifest.project_id) {
223
- const err = {
224
- status: "error",
222
+ fail({
223
+ code: "BAD_USAGE",
225
224
  message: `project_id conflict: manifest.project_id=${manifest.project_id} but --project=${opts.project}`,
226
- manifest_project_id: manifest.project_id,
227
- flag_project_id: opts.project,
228
225
  hint: "Remove one of them or make them match. The --project flag and manifest.project_id must agree (or only one of them must be set).",
229
- };
230
- console.error(JSON.stringify(err));
231
- process.exit(1);
226
+ details: {
227
+ manifest_project_id: manifest.project_id,
228
+ flag_project_id: opts.project,
229
+ },
230
+ });
232
231
  }
233
232
 
234
233
  if (opts.project) manifest.project_id = opts.project;
package/lib/domains.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 domains — Manage custom domains
6
6
 
@@ -40,7 +40,13 @@ async function add(args) {
40
40
  const { project, rest } = parseProjectFlag(args);
41
41
  const domain = rest[0];
42
42
  const subdomainName = rest[1];
43
- if (!domain || !subdomainName) { console.error("Usage: run402 domains add <domain> <subdomain_name> [--project <id>]"); process.exit(1); }
43
+ if (!domain || !subdomainName) {
44
+ fail({
45
+ code: "BAD_USAGE",
46
+ message: "Missing <domain> and/or <subdomain_name>.",
47
+ hint: "run402 domains add <domain> <subdomain_name> [--project <id>]",
48
+ });
49
+ }
44
50
  const projectId = resolveProjectId(project);
45
51
  try {
46
52
  const data = await getSdk().domains.add(projectId, domain, subdomainName);
@@ -63,7 +69,13 @@ async function list(projectIdArg) {
63
69
  async function status(args) {
64
70
  const { project, rest } = parseProjectFlag(args);
65
71
  const domain = rest[0];
66
- if (!domain) { console.error("Usage: run402 domains status <domain> [--project <id>]"); process.exit(1); }
72
+ if (!domain) {
73
+ fail({
74
+ code: "BAD_USAGE",
75
+ message: "Missing <domain>.",
76
+ hint: "run402 domains status <domain> [--project <id>]",
77
+ });
78
+ }
67
79
  const projectId = resolveProjectId(project);
68
80
  try {
69
81
  const data = await getSdk().domains.status(projectId, domain);
@@ -76,15 +88,19 @@ async function status(args) {
76
88
  async function deleteDomain(args) {
77
89
  const { project, rest } = parseProjectFlag(args);
78
90
  const domain = rest.find((a) => !a.startsWith("--"));
79
- if (!domain) { console.error("Usage: run402 domains delete <domain> --confirm [--project <id>]"); process.exit(1); }
91
+ if (!domain) {
92
+ fail({
93
+ code: "BAD_USAGE",
94
+ message: "Missing <domain>.",
95
+ hint: "run402 domains delete <domain> --confirm [--project <id>]",
96
+ });
97
+ }
80
98
  if (!Array.isArray(args) || !args.includes("--confirm")) {
81
- console.error(JSON.stringify({
82
- status: "error",
99
+ fail({
83
100
  code: "CONFIRMATION_REQUIRED",
84
101
  message: `Destructive: releasing custom domain '${domain}' detaches it from this project and clears its DNS/SSL configuration. This is irreversible. Re-run with --confirm to proceed.`,
85
102
  details: { domain },
86
- }));
87
- process.exit(1);
103
+ });
88
104
  }
89
105
  const projectId = resolveProjectId(project);
90
106
  try {
package/lib/email.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, parseFlagJson } from "./sdk-errors.mjs";
4
4
 
5
5
  const HELP = `run402 email — Send emails from your project
6
6
 
@@ -130,14 +130,12 @@ function parseVars(args) {
130
130
  for (let i = 0; i < args.length; i++) {
131
131
  if (args[i] === "--vars" && args[i + 1]) {
132
132
  const raw = args[++i];
133
- let parsed;
134
- try { parsed = JSON.parse(raw); } catch {
135
- console.error(JSON.stringify({ status: "error", message: "Invalid JSON for --vars. Expected a JSON object, e.g. '{\"key\":\"value\"}'" }));
136
- process.exit(1);
137
- }
133
+ const parsed = parseFlagJson("--vars", raw);
138
134
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
139
- console.error(JSON.stringify({ status: "error", message: "--vars must be a JSON object, e.g. '{\"key\":\"value\"}'" }));
140
- process.exit(1);
135
+ fail({
136
+ code: "BAD_USAGE",
137
+ message: "--vars must be a JSON object, e.g. '{\"key\":\"value\"}'",
138
+ });
141
139
  }
142
140
  for (const [k, v] of Object.entries(parsed)) vars[k] = typeof v === "string" ? v : String(v);
143
141
  }
@@ -161,8 +159,11 @@ async function create(args) {
161
159
  }
162
160
  const projectId = resolveProjectId(projectOpt);
163
161
  if (!slug) {
164
- console.error(JSON.stringify({ status: "error", message: "Missing slug. Usage: run402 email create <slug>" }));
165
- process.exit(1);
162
+ fail({
163
+ code: "BAD_USAGE",
164
+ message: "Missing slug.",
165
+ hint: "run402 email create <slug>",
166
+ });
166
167
  }
167
168
 
168
169
  try {
@@ -184,8 +185,7 @@ async function send(args) {
184
185
  const variables = parseVars(args);
185
186
 
186
187
  if (!to) {
187
- console.error(JSON.stringify({ status: "error", message: "Missing --to <email>" }));
188
- process.exit(1);
188
+ fail({ code: "BAD_USAGE", message: "Missing --to <email>" });
189
189
  }
190
190
 
191
191
  try {
@@ -228,8 +228,11 @@ async function get(args) {
228
228
  }
229
229
  const projectId = resolveProjectId(projectOpt);
230
230
  if (!messageId) {
231
- console.error(JSON.stringify({ status: "error", message: "Missing message_id. Usage: run402 email get <message_id>" }));
232
- process.exit(1);
231
+ fail({
232
+ code: "BAD_USAGE",
233
+ message: "Missing message_id.",
234
+ hint: "run402 email get <message_id>",
235
+ });
233
236
  }
234
237
  try {
235
238
  const data = await getSdk().email.get(projectId, messageId);
@@ -250,8 +253,11 @@ async function getRaw(args) {
250
253
  }
251
254
  const projectId = resolveProjectId(projectOpt);
252
255
  if (!messageId) {
253
- console.error(JSON.stringify({ status: "error", message: "Missing message_id. Usage: run402 email get-raw <message_id> [--output <file>]" }));
254
- process.exit(1);
256
+ fail({
257
+ code: "BAD_USAGE",
258
+ message: "Missing message_id.",
259
+ hint: "run402 email get-raw <message_id> [--output <file>]",
260
+ });
255
261
  }
256
262
 
257
263
  try {
@@ -286,12 +292,17 @@ async function reply(args) {
286
292
  const projectId = resolveProjectId(projectOpt);
287
293
 
288
294
  if (!messageId) {
289
- console.error(JSON.stringify({ status: "error", message: "Missing message_id. Usage: run402 email reply <message_id> --html \"...\"" }));
290
- process.exit(1);
295
+ fail({
296
+ code: "BAD_USAGE",
297
+ message: "Missing message_id.",
298
+ hint: 'run402 email reply <message_id> --html "..."',
299
+ });
291
300
  }
292
301
  if (!html && !text) {
293
- console.error(JSON.stringify({ status: "error", message: "Provide --html and/or --text for the reply body" }));
294
- process.exit(1);
302
+ fail({
303
+ code: "BAD_USAGE",
304
+ message: "Provide --html and/or --text for the reply body",
305
+ });
295
306
  }
296
307
 
297
308
  try {
@@ -299,12 +310,11 @@ async function reply(args) {
299
310
  const original = await getSdk().email.get(projectId, messageId);
300
311
  const replyTo = original.from || original.from_address || original.sender || null;
301
312
  if (!replyTo) {
302
- console.error(JSON.stringify({
303
- status: "error",
313
+ fail({
314
+ code: "BAD_USAGE",
304
315
  message: "Original message has no from address to reply to",
305
- original_keys: Object.keys(original),
306
- }));
307
- process.exit(1);
316
+ details: { original_keys: Object.keys(original) },
317
+ });
308
318
  }
309
319
  const origSubject = typeof original.subject === "string" ? original.subject : "";
310
320
  const defaultSubject = origSubject && origSubject.toLowerCase().startsWith("re:")
@@ -339,11 +349,10 @@ async function deleteMailbox(args) {
339
349
  const confirmed = args.includes("--confirm");
340
350
 
341
351
  if (!confirmed) {
342
- console.error(JSON.stringify({
343
- status: "error",
352
+ fail({
353
+ code: "CONFIRMATION_REQUIRED",
344
354
  message: "Destructive: deleting a mailbox is irreversible (drops all messages and webhook subscriptions). Re-run with --confirm to proceed.",
345
- }));
346
- process.exit(1);
355
+ });
347
356
  }
348
357
 
349
358
  try {
package/lib/functions.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFileSync } from "fs";
2
2
  import { findProject, API } from "./config.mjs";
3
3
  import { getSdk } from "./sdk.mjs";
4
- import { reportSdkError } from "./sdk-errors.mjs";
4
+ import { reportSdkError, fail } from "./sdk-errors.mjs";
5
5
 
6
6
  const HELP = `run402 functions — Manage serverless functions
7
7
 
@@ -152,7 +152,9 @@ async function deploy(projectId, name, args) {
152
152
  if (args[i] === "--deps" && args[i + 1]) opts.deps = args[++i].split(",");
153
153
  if (args[i] === "--schedule" && i + 1 < args.length) opts.schedule = args[++i];
154
154
  }
155
- if (!opts.file) { console.error(JSON.stringify({ status: "error", message: "Missing --file <file>" })); process.exit(1); }
155
+ if (!opts.file) {
156
+ fail({ code: "BAD_USAGE", message: "Missing --file <file>" });
157
+ }
156
158
  const code = readFileSync(opts.file, "utf-8");
157
159
 
158
160
  const deployOpts = { name, code };
@@ -212,7 +214,13 @@ async function logs(projectId, name, args) {
212
214
  if (since !== undefined) {
213
215
  const parsed = Number(since);
214
216
  const ms = Number.isNaN(parsed) ? new Date(since).getTime() : parsed;
215
- if (Number.isNaN(ms)) { console.error(JSON.stringify({ status: "error", message: `Invalid --since value: ${since}` })); process.exit(1); }
217
+ if (Number.isNaN(ms)) {
218
+ fail({
219
+ code: "BAD_USAGE",
220
+ message: `Invalid --since value: ${since}`,
221
+ details: { flag: "--since", value: since },
222
+ });
223
+ }
216
224
  sinceIso = new Date(ms).toISOString();
217
225
  }
218
226
 
@@ -282,8 +290,10 @@ async function update(projectId, name, args) {
282
290
  if (memory !== undefined) updateOpts.memory = memory;
283
291
 
284
292
  if (Object.keys(updateOpts).length === 0) {
285
- console.error(JSON.stringify({ status: "error", message: "Provide at least one of: --schedule, --schedule-remove, --timeout, --memory" }));
286
- process.exit(1);
293
+ fail({
294
+ code: "BAD_USAGE",
295
+ message: "Provide at least one of: --schedule, --schedule-remove, --timeout, --memory",
296
+ });
287
297
  }
288
298
 
289
299
  try {
package/lib/image.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { writeFileSync } from "fs";
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 image — Generate AI images via x402 micropayments
6
6
 
@@ -49,7 +49,13 @@ export async function run(sub, args) {
49
49
  i++;
50
50
  }
51
51
 
52
- if (!opts.prompt) { console.error(JSON.stringify({ status: "error", message: "Prompt required. Usage: run402 image generate \"your prompt\"" })); process.exit(1); }
52
+ if (!opts.prompt) {
53
+ fail({
54
+ code: "BAD_USAGE",
55
+ message: "Prompt required.",
56
+ hint: 'run402 image generate "your prompt"',
57
+ });
58
+ }
53
59
 
54
60
  try {
55
61
  const data = await getSdk().ai.generateImage({ prompt: opts.prompt, aspect: opts.aspect });
package/lib/message.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 message — Send messages to Run402 developers
6
6
 
@@ -16,7 +16,9 @@ Examples:
16
16
  `;
17
17
 
18
18
  async function send(text) {
19
- if (!text) { console.error(JSON.stringify({ status: "error", message: "Missing message text" })); process.exit(1); }
19
+ if (!text) {
20
+ fail({ code: "BAD_USAGE", message: "Missing message text." });
21
+ }
20
22
  // Preserve the aggressive early exit when no allowance is configured.
21
23
  allowanceAuthHeaders("/message/v1");
22
24
 
package/lib/projects.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFileSync } from "fs";
2
2
  import { findProject, loadKeyStore, API, allowanceAuthHeaders, resolveProjectId, getActiveProjectId } from "./config.mjs";
3
3
  import { getSdk } from "./sdk.mjs";
4
- import { reportSdkError } from "./sdk-errors.mjs";
4
+ import { reportSdkError, fail, parseFlagJson } from "./sdk-errors.mjs";
5
5
 
6
6
  const HELP = `run402 projects — Manage your deployed Run402 projects
7
7
 
@@ -153,12 +153,21 @@ async function applyExpose(projectId, args = []) {
153
153
  }
154
154
  const raw = file ? readFileSync(file, "utf-8") : inline;
155
155
  if (!raw) {
156
- console.error(JSON.stringify({ status: "error", message: "Missing manifest. Provide inline JSON or use --file <path>" }));
157
- process.exit(1);
156
+ fail({
157
+ code: "BAD_USAGE",
158
+ message: "Missing manifest.",
159
+ hint: "Provide inline JSON or use --file <path>",
160
+ });
158
161
  }
159
162
  let manifest;
160
163
  try { manifest = JSON.parse(raw); }
161
- catch { console.error(JSON.stringify({ status: "error", message: "Invalid JSON for manifest" })); process.exit(1); }
164
+ catch (err) {
165
+ fail({
166
+ code: "BAD_USAGE",
167
+ message: "Invalid JSON for manifest",
168
+ details: { parse_error: err.message },
169
+ });
170
+ }
162
171
  const res = await fetch(`${API}/projects/v1/admin/${projectId}/expose`, {
163
172
  method: "POST",
164
173
  headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
@@ -222,11 +231,22 @@ async function sqlCmd(projectId, args = []) {
222
231
  else if (!query && !args[i].startsWith("--")) { query = args[i]; }
223
232
  }
224
233
  const sql = file ? readFileSync(file, "utf-8") : query;
225
- if (!sql) { console.error(JSON.stringify({ status: "error", message: "Missing SQL query. Provide inline or use --file <path>" })); process.exit(1); }
234
+ if (!sql) {
235
+ fail({
236
+ code: "BAD_USAGE",
237
+ message: "Missing SQL query.",
238
+ hint: "Provide inline or use --file <path>",
239
+ });
240
+ }
226
241
  let params;
227
242
  if (paramsRaw) {
228
- try { params = JSON.parse(paramsRaw); } catch { console.error(JSON.stringify({ status: "error", message: "Invalid JSON for --params. Expected a JSON array, e.g. '[42, \"hello\"]'" })); process.exit(1); }
229
- if (!Array.isArray(params)) { console.error(JSON.stringify({ status: "error", message: "--params must be a JSON array, e.g. '[42, \"hello\"]'" })); process.exit(1); }
243
+ params = parseFlagJson("--params", paramsRaw);
244
+ if (!Array.isArray(params)) {
245
+ fail({
246
+ code: "BAD_USAGE",
247
+ message: "--params must be a JSON array, e.g. '[42, \"hello\"]'",
248
+ });
249
+ }
230
250
  }
231
251
  const useParams = params && params.length > 0;
232
252
  const headers = { "Authorization": `Bearer ${p.service_key}`, "Content-Type": useParams ? "application/json" : "text/plain" };
@@ -264,7 +284,13 @@ async function schema(projectId) {
264
284
  }
265
285
 
266
286
  async function use(projectId) {
267
- if (!projectId) { console.error("Usage: run402 projects use <project_id>"); process.exit(1); }
287
+ if (!projectId) {
288
+ fail({
289
+ code: "BAD_USAGE",
290
+ message: "Missing <project_id>.",
291
+ hint: "run402 projects use <project_id>",
292
+ });
293
+ }
268
294
  try {
269
295
  await getSdk().projects.use(projectId);
270
296
  console.log(JSON.stringify({ status: "ok", active_project_id: projectId }));
@@ -274,7 +300,13 @@ async function use(projectId) {
274
300
  }
275
301
 
276
302
  async function pin(projectId) {
277
- if (!projectId) { console.error(JSON.stringify({ status: "error", message: "Usage: run402 projects pin <project_id>" })); process.exit(1); }
303
+ if (!projectId) {
304
+ fail({
305
+ code: "BAD_USAGE",
306
+ message: "Missing <project_id>.",
307
+ hint: "run402 projects pin <project_id>",
308
+ });
309
+ }
278
310
  try {
279
311
  const data = await getSdk().projects.pin(projectId);
280
312
  console.log(JSON.stringify(data, null, 2));
@@ -284,7 +316,13 @@ async function pin(projectId) {
284
316
  }
285
317
 
286
318
  async function promoteUser(projectId, email) {
287
- if (!email) { console.error(JSON.stringify({ status: "error", message: "Usage: run402 projects promote-user <project_id> <email>" })); process.exit(1); }
319
+ if (!email) {
320
+ fail({
321
+ code: "BAD_USAGE",
322
+ message: "Missing <email>.",
323
+ hint: "run402 projects promote-user <project_id> <email>",
324
+ });
325
+ }
288
326
  const p = findProject(projectId);
289
327
  const res = await fetch(`${API}/projects/v1/admin/${projectId}/promote-user`, {
290
328
  method: "POST",
@@ -297,7 +335,13 @@ async function promoteUser(projectId, email) {
297
335
  }
298
336
 
299
337
  async function demoteUser(projectId, email) {
300
- if (!email) { console.error(JSON.stringify({ status: "error", message: "Usage: run402 projects demote-user <project_id> <email>" })); process.exit(1); }
338
+ if (!email) {
339
+ fail({
340
+ code: "BAD_USAGE",
341
+ message: "Missing <email>.",
342
+ hint: "run402 projects demote-user <project_id> <email>",
343
+ });
344
+ }
301
345
  const p = findProject(projectId);
302
346
  const res = await fetch(`${API}/projects/v1/admin/${projectId}/demote-user`, {
303
347
  method: "POST",