run402 2.23.0 → 2.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/cache.mjs CHANGED
@@ -56,15 +56,18 @@ Subcommands:
56
56
  invalidate --all --host <h> Invalidate all rows for a host
57
57
 
58
58
  Common flags:
59
- --json Machine-readable output
60
59
  --locale <code> (inspect only) Inspect a specific locale's row
61
60
  (default: project's default locale)
62
61
  --release-id <id> (inspect only) Inspect a specific release id
63
62
  (default: project's active release)
64
63
 
64
+ Output:
65
+ Stdout is JSON. inspect emits the cache row object; invalidate emits
66
+ { deleted, host?, path?, generation }.
67
+
65
68
  Examples:
66
69
  run402 cache inspect https://eagles.kychon.com/the-guys
67
- run402 cache inspect https://eagles.kychon.com/the-guys --locale es --json
70
+ run402 cache inspect https://eagles.kychon.com/the-guys --locale es
68
71
  run402 cache invalidate https://eagles.kychon.com/the-guys
69
72
  run402 cache invalidate --prefix /blog/ --host eagles.kychon.com
70
73
  run402 cache invalidate --all --host eagles.kychon.com
@@ -105,7 +108,7 @@ export async function run(sub, args) {
105
108
 
106
109
  async function inspect(args) {
107
110
  const parsed = normalizeArgv(args);
108
- assertKnownFlags(parsed, ["--json", "--locale", "--release-id", "--help", "-h"]);
111
+ assertKnownFlags(parsed, ["--locale", "--release-id", "--help", "-h"]);
109
112
  if (hasFlag(parsed, ["--help", "-h"])) {
110
113
  console.log(HELP);
111
114
  return;
@@ -130,19 +133,11 @@ async function inspect(args) {
130
133
 
131
134
  const locale = flagValue(parsed, "--locale");
132
135
  const releaseId = flagValue(parsed, "--release-id");
133
- const json = hasFlag(parsed, ["--json"]);
134
136
 
135
137
  try {
136
138
  const sdk = getSdk();
137
- // SDK shape — the gateway's cache inspect endpoint isn't yet wired
138
- // (separate task). For now the CLI POSTs to the same /cache/v1/
139
- // namespace with kind=inspect.
140
139
  const result = await sdk.cache.inspect(url, { locale, releaseId });
141
- if (json) {
142
- console.log(JSON.stringify(result, null, 2));
143
- } else {
144
- console.log(formatInspectResult(result));
145
- }
140
+ console.log(JSON.stringify(result, null, 2));
146
141
  } catch (err) {
147
142
  reportSdkError(err);
148
143
  }
@@ -150,14 +145,13 @@ async function inspect(args) {
150
145
 
151
146
  async function invalidate(args) {
152
147
  const parsed = normalizeArgv(args);
153
- assertKnownFlags(parsed, ["--json", "--prefix", "--host", "--all", "--help", "-h"]);
148
+ assertKnownFlags(parsed, ["--prefix", "--host", "--all", "--help", "-h"]);
154
149
  if (hasFlag(parsed, ["--help", "-h"])) {
155
150
  console.log(HELP);
156
151
  return;
157
152
  }
158
153
 
159
154
  const positionals = positionalArgs(parsed);
160
- const json = hasFlag(parsed, ["--json"]);
161
155
  const prefix = flagValue(parsed, "--prefix");
162
156
  const host = flagValue(parsed, "--host");
163
157
  const all = hasFlag(parsed, ["--all"]);
@@ -175,7 +169,7 @@ async function invalidate(args) {
175
169
  }
176
170
  try {
177
171
  const result = await sdk.cache.invalidateAll({ host });
178
- emit(result, json);
172
+ console.log(JSON.stringify(result, null, 2));
179
173
  } catch (err) {
180
174
  reportSdkError(err);
181
175
  }
@@ -198,7 +192,7 @@ async function invalidate(args) {
198
192
  }
199
193
  try {
200
194
  const result = await sdk.cache.invalidatePrefix({ host, prefix });
201
- emit(result, json);
195
+ console.log(JSON.stringify(result, null, 2));
202
196
  } catch (err) {
203
197
  reportSdkError(err);
204
198
  }
@@ -227,38 +221,8 @@ async function invalidate(args) {
227
221
  }
228
222
  try {
229
223
  const result = await sdk.cache.invalidate(url);
230
- emit(result, json);
224
+ console.log(JSON.stringify(result, null, 2));
231
225
  } catch (err) {
232
226
  reportSdkError(err);
233
227
  }
234
228
  }
235
-
236
- function emit(result, json) {
237
- if (json) {
238
- console.log(JSON.stringify(result, null, 2));
239
- } else {
240
- const parts = [`Invalidated ${result.deleted} cache row(s)`];
241
- if (result.host) parts.push(`on ${result.host}`);
242
- if (result.path) parts.push(`for ${result.path}`);
243
- parts.push(`(generation: ${result.generation})`);
244
- console.log(parts.join(" "));
245
- }
246
- }
247
-
248
- function formatInspectResult(result) {
249
- if (result.status === "MISS") {
250
- return `MISS — no cache row for ${result.url || "this URL"}.`;
251
- }
252
- const lines = [
253
- `${result.status} — ${result.host}${result.path}`,
254
- ` locale: ${result.locale}`,
255
- ` releaseId: ${result.releaseId}`,
256
- ` cachedAt: ${result.cachedAt}`,
257
- ` expiresAt: ${result.expiresAt}`,
258
- ` contentSha256: ${result.contentSha256}`,
259
- ];
260
- if (result.writtenUnderGeneration) {
261
- lines.push(` writtenUnderGen: ${result.writtenUnderGeneration}`);
262
- }
263
- return lines.join("\n");
264
- }
package/lib/doctor.mjs CHANGED
@@ -23,10 +23,12 @@ import {
23
23
  const HELP = `run402 doctor — Health and config diagnostics
24
24
 
25
25
  Usage:
26
- run402 doctor [--json] [--verbose]
26
+ run402 doctor [--verbose] [--no-scan] [--scan-dir <D>]
27
+
28
+ Output:
29
+ Stdout is a JSON report { ok, checks: [{ name, status, value?, hint?, message? }] }.
27
30
 
28
31
  Options:
29
- --json Emit a structured JSON report on stdout
30
32
  --verbose Include extra detail (timing, error messages)
31
33
  --no-scan Skip the source-tree scan (config / health checks only)
32
34
  --scan-dir D Scan a custom directory instead of \`<cwd>/src\`
@@ -54,7 +56,6 @@ export async function run(sub, args = []) {
54
56
  console.log(HELP);
55
57
  return;
56
58
  }
57
- const json = all.includes("--json");
58
59
  const verbose = all.includes("--verbose");
59
60
  const skipScan = all.includes("--no-scan");
60
61
  const scanDirArgIdx = all.indexOf("--scan-dir");
@@ -280,38 +281,6 @@ export async function run(sub, args = []) {
280
281
  // 'empty' fail.
281
282
  const allOk = checks.every((c) => c.status === "ok" || c.status === "warning" || c.status === "skipped");
282
283
 
283
- if (json) {
284
- console.log(JSON.stringify({ ok: allOk, checks }, null, 2));
285
- } else {
286
- console.log(`Run402 doctor — ${allOk ? "all checks passed" : "issues found"}`);
287
- console.log("");
288
- for (const c of checks) {
289
- const icon =
290
- c.status === "ok" ? "✓"
291
- : c.status === "warning" ? "⚠"
292
- : c.status === "skipped" ? "·"
293
- : c.status === "missing" || c.status === "empty" ? "⚠"
294
- : "✗";
295
- const status = c.status === "ok" ? "ok" : c.status;
296
- console.log(` ${icon} ${c.name.padEnd(16)} ${status}`);
297
- if (c.hint) console.log(` → ${c.hint}`);
298
- if (c.message) console.log(` ${c.message}`);
299
- if (c.value && c.value.gaps && Array.isArray(c.value.gaps)) {
300
- for (const gap of c.value.gaps) console.log(` • ${gap}`);
301
- }
302
- // Source-scan details — print every finding with file:line + canonical fix-it.
303
- if (c.name === "source_scan" && c.value && Array.isArray(c.value.details)) {
304
- for (const f of c.value.details) {
305
- const sev = f.severity === "error" ? "✗" : "⚠";
306
- const location = f.line ? `${f.file}:${f.line}` : f.file;
307
- console.log(` ${sev} ${f.code} ${location}`);
308
- console.log(` ${f.message}`);
309
- if (f.canonical_name) console.log(` fix: ${f.canonical_name}`);
310
- if (f.docs) console.log(` docs: ${f.docs}`);
311
- }
312
- }
313
- }
314
- }
315
-
284
+ console.log(JSON.stringify({ ok: allOk, checks }, null, 2));
316
285
  process.exit(allOk ? 0 : 1);
317
286
  }
@@ -21,14 +21,19 @@ import { fail } from "./sdk-errors.mjs";
21
21
  const HELP = `run402 init astro — Scaffold a deployable Astro project
22
22
 
23
23
  Usage:
24
- run402 init astro [<dir>] [--force] [--json]
24
+ run402 init astro [<dir>] [--force]
25
25
 
26
26
  Arguments:
27
27
  <dir> Target directory (default: current directory)
28
28
 
29
29
  Options:
30
30
  --force Overwrite a non-empty directory
31
- --json Emit a structured JSON summary on stdout
31
+
32
+ Output:
33
+ Stdout is a JSON summary { dir, files_created, created, next_steps }.
34
+ Progress lines ("Scaffolded ...", "Files created:", "Next steps:") go to
35
+ stderr so a human re-running interactively sees what's happening while
36
+ a script piping stdout to jq stays clean.
32
37
 
33
38
  The scaffolded project includes:
34
39
  - package.json (with 'dev' / 'deploy' scripts)
@@ -53,7 +58,6 @@ export async function runInitAstro(args = []) {
53
58
  return;
54
59
  }
55
60
  const force = args.includes("--force");
56
- const json = args.includes("--json");
57
61
  const positionals = args.filter((a) => !a.startsWith("--"));
58
62
  const targetDir = resolve(positionals[0] ?? ".");
59
63
 
@@ -155,7 +159,7 @@ import Layout from "../layouts/Layout.astro";
155
159
  // SSR time. The first request renders + caches; subsequent requests
156
160
  // HIT the cache. When an admin edits the row, call cache.invalidate()
157
161
  // from your save handler for sub-second freshness.
158
- import { db, getUser, cache } from "@run402/functions";
162
+ import { db } from "@run402/functions";
159
163
  import Layout from "../layouts/Layout.astro";
160
164
 
161
165
  const { slug } = Astro.params;
@@ -335,32 +339,31 @@ For error-code reference: https://run402.com/errors/#R402_AUTH_REQUIRED
335
339
  writeFileSync(fullPath, file.content, "utf-8");
336
340
  }
337
341
 
338
- if (json) {
339
- console.log(
340
- JSON.stringify(
341
- {
342
- dir: targetDir,
343
- files_created: files.map((f) => f.path),
344
- created: true,
345
- next_steps: [
346
- `cd ${positionals[0] ?? "."}`,
347
- "npm install",
348
- "run402 deploy",
349
- ],
350
- },
351
- null,
352
- 2,
353
- ),
354
- );
355
- } else {
356
- console.log(`Scaffolded Astro project at ${targetDir}`);
357
- console.log("");
358
- console.log("Files created:");
359
- for (const f of files) console.log(` - ${f.path}`);
360
- console.log("");
361
- console.log("Next steps:");
362
- if (positionals[0]) console.log(` cd ${positionals[0]}`);
363
- console.log(" npm install");
364
- console.log(" run402 deploy");
365
- }
342
+ // Human-readable progress goes to stderr; stdout stays JSON-clean.
343
+ console.error(`Scaffolded Astro project at ${targetDir}`);
344
+ console.error("");
345
+ console.error("Files created:");
346
+ for (const f of files) console.error(` - ${f.path}`);
347
+ console.error("");
348
+ console.error("Next steps:");
349
+ if (positionals[0]) console.error(` cd ${positionals[0]}`);
350
+ console.error(" npm install");
351
+ console.error(" run402 deploy");
352
+
353
+ console.log(
354
+ JSON.stringify(
355
+ {
356
+ dir: targetDir,
357
+ files_created: files.map((f) => f.path),
358
+ created: true,
359
+ next_steps: [
360
+ `cd ${positionals[0] ?? "."}`,
361
+ "npm install",
362
+ "run402 deploy",
363
+ ],
364
+ },
365
+ null,
366
+ 2,
367
+ ),
368
+ );
366
369
  }
package/lib/init.mjs CHANGED
@@ -18,14 +18,17 @@ Usage:
18
18
  Required when an allowance already exists on
19
19
  the other rail; protects scripted re-runs from
20
20
  silently flipping billing networks.
21
- run402 init --json Same as init, but emit a JSON summary on stdout
22
- (human lines go to stderr — for agent automation)
23
21
 
24
22
  Options:
25
23
  --switch-rail Confirm switching the persisted payment rail. Re-running
26
24
  init with the SAME rail as the existing allowance is always
27
25
  idempotent and does not need this flag.
28
- --json Emit a structured JSON summary on stdout.
26
+
27
+ Output:
28
+ Stdout is a JSON summary { config_dir, allowance, rail, network, balance,
29
+ tier, projects_saved, next_step }. Progress lines (Config / Allowance /
30
+ Balance / Tier / Next) go to stderr so a human re-running interactively
31
+ sees what's happening while a script piping stdout to jq stays clean.
29
32
 
30
33
  Steps (idempotent when re-run with the same rail; pass --switch-rail to change rails):
31
34
  1. Creates config directory (~/.config/run402)
@@ -61,7 +64,6 @@ export async function run(args = []) {
61
64
 
62
65
  if (args.includes("--help") || args.includes("-h")) { console.log(HELP); process.exit(0); }
63
66
 
64
- const jsonMode = args.includes("--json");
65
67
  const isMpp = args[0] === "mpp";
66
68
  const requestedRail = isMpp ? "mpp" : "x402";
67
69
  const switchRailConfirmed = args.includes("--switch-rail");
@@ -75,9 +77,9 @@ export async function run(args = []) {
75
77
  });
76
78
  }
77
79
 
78
- // In --json mode, human-readable lines go to stderr so stdout stays clean for
79
- // agents. We also collect structured data for the final JSON emit.
80
- const write = jsonMode ? (s) => console.error(s) : (s) => console.log(s);
80
+ // Human-readable progress lines go to stderr so stdout stays JSON-clean for
81
+ // agents. Final structured summary emits to stdout at the end.
82
+ const write = (s) => console.error(s);
81
83
  const line = (label, value) => write(` ${label.padEnd(10)} ${value}`);
82
84
  const summary = {
83
85
  config_dir: CONFIG_DIR,
@@ -265,7 +267,5 @@ export async function run(args = []) {
265
267
  write("");
266
268
  summary.next_step = nextStep;
267
269
 
268
- if (jsonMode) {
269
- console.log(JSON.stringify(summary, null, 2));
270
- }
270
+ console.log(JSON.stringify(summary, null, 2));
271
271
  }
package/lib/logs.mjs CHANGED
@@ -21,7 +21,7 @@ import { reportSdkError, fail } from "./sdk-errors.mjs";
21
21
  const HELP = `run402 logs — Fetch function logs by request id
22
22
 
23
23
  Usage:
24
- run402 logs --request-id <req_id> [--function <name>] [--project <id>] [--json] [--tail <n>]
24
+ run402 logs --request-id <req_id> [--function <name>] [--project <id>] [--tail <n>]
25
25
 
26
26
  Required:
27
27
  --request-id <req_id> The req_... id (from x-run402-request-id header)
@@ -30,12 +30,14 @@ Optional:
30
30
  --function <name> Limit to one function (default: scan all functions in the project)
31
31
  --project <id> Project id (default: \$RUN402_PROJECT_ID)
32
32
  --tail <n> Max entries per function (default 100)
33
- --json Machine-readable output
33
+
34
+ Output:
35
+ Stdout is JSON { ok, request_id, project_id, scanned, entries, errors? }.
34
36
 
35
37
  Examples:
36
38
  run402 logs --request-id req_abc123
37
39
  run402 logs --request-id req_abc123 --function ssr
38
- run402 logs --request-id req_abc123 --project prj_xyz --json
40
+ run402 logs --request-id req_abc123 --project prj_xyz
39
41
 
40
42
  Tip: the request id appears in:
41
43
  - The 'x-run402-request-id' response header on every SSR response
@@ -50,7 +52,6 @@ export async function run(sub, args = []) {
50
52
  return;
51
53
  }
52
54
 
53
- const json = all.includes("--json");
54
55
  const requestId = pickFlagValue(all, "--request-id");
55
56
  const fnName = pickFlagValue(all, "--function");
56
57
  const projectIdArg = pickFlagValue(all, "--project");
@@ -98,18 +99,19 @@ export async function run(sub, args = []) {
98
99
  const list = await sdk.functions.list(projectId);
99
100
  fnNames = (list?.functions ?? []).map((f) => f.name);
100
101
  if (fnNames.length === 0) {
101
- if (json) console.log(JSON.stringify({ ok: true, entries: [], scanned: [] }));
102
- else console.log("No functions in project " + projectId);
102
+ console.log(JSON.stringify({ ok: true, request_id: requestId, project_id: projectId, entries: [], scanned: [] }, null, 2));
103
103
  return;
104
104
  }
105
105
  }
106
106
 
107
- // Query each function in parallel; aggregate entries.
107
+ // Query each function in parallel; aggregate entries. SDK returns
108
+ // FunctionLogsResult = { logs: FunctionLogEntry[] }; unwrap to the array
109
+ // so the aggregated JSON has a flat entries[] field.
108
110
  const results = await Promise.allSettled(
109
111
  fnNames.map((name) =>
110
112
  sdk.functions
111
113
  .logs(projectId, name, { requestId, tail })
112
- .then((entries) => ({ name, entries: entries ?? [] })),
114
+ .then((result) => ({ name, entries: result?.logs ?? [] })),
113
115
  ),
114
116
  );
115
117
 
@@ -127,35 +129,28 @@ export async function run(sub, args = []) {
127
129
  }
128
130
  }
129
131
 
130
- // Sort by timestamp ascending.
131
- allEntries.sort((a, b) => (a.ts ?? 0) - (b.ts ?? 0));
132
-
133
- if (json) {
134
- console.log(
135
- JSON.stringify(
136
- {
137
- ok: errors.length === 0,
138
- request_id: requestId,
139
- project_id: projectId,
140
- scanned,
141
- entries: allEntries,
142
- ...(errors.length > 0 && { errors }),
143
- },
144
- null,
145
- 2,
146
- ),
147
- );
148
- } else {
149
- if (allEntries.length === 0) {
150
- console.log(`No log entries found for ${requestId} across ${scanned.length} function(s).`);
151
- } else {
152
- for (const e of allEntries) {
153
- const t = e.ts ? new Date(e.ts).toISOString() : "";
154
- console.log(`[${t}] [${e.function}] ${e.message ?? ""}`);
155
- }
156
- console.log(`\n${allEntries.length} entries across ${scanned.length} function(s) for ${requestId}.`);
157
- }
158
- }
132
+ // Sort by timestamp ascending. FunctionLogEntry.timestamp is an ISO 8601
133
+ // string; convert to epoch ms for comparison.
134
+ allEntries.sort((a, b) => {
135
+ const ta = a.timestamp ? Date.parse(a.timestamp) : 0;
136
+ const tb = b.timestamp ? Date.parse(b.timestamp) : 0;
137
+ return ta - tb;
138
+ });
139
+
140
+ console.log(
141
+ JSON.stringify(
142
+ {
143
+ ok: errors.length === 0,
144
+ request_id: requestId,
145
+ project_id: projectId,
146
+ scanned,
147
+ entries: allEntries,
148
+ ...(errors.length > 0 && { errors }),
149
+ },
150
+ null,
151
+ 2,
152
+ ),
153
+ );
159
154
  } catch (err) {
160
155
  reportSdkError(err);
161
156
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "run402",
3
- "version": "2.23.0",
3
+ "version": "2.24.0",
4
4
  "description": "CLI for Run402 — provision Postgres databases, deploy static sites, generate images, and manage wallets via x402 and MPP micropayments.",
5
5
  "type": "module",
6
6
  "bin": {