run402 1.54.2 → 1.54.4

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/email.mjs CHANGED
@@ -115,6 +115,40 @@ compatibility; new code should use 'info'.
115
115
 
116
116
  Usage:
117
117
  run402 email get-raw <message_id> [--output <file>] [--project <id>]
118
+ `,
119
+ create: `run402 email create — Create a project mailbox
120
+
121
+ Usage:
122
+ run402 email create <slug> [--project <id>]
123
+
124
+ Arguments:
125
+ <slug> Mailbox slug (3-63 chars, lowercase alphanumeric +
126
+ hyphens, no consecutive hyphens). Becomes
127
+ <slug>@mail.run402.com.
128
+
129
+ Options:
130
+ --project <id> Project ID (defaults to the active project)
131
+
132
+ Notes:
133
+ - One mailbox per project
134
+
135
+ Examples:
136
+ run402 email create my-app
137
+ run402 email create my-app --project prj_abc123
138
+ `,
139
+ get: `run402 email get — Get a message with replies
140
+
141
+ Usage:
142
+ run402 email get <message_id> [--project <id>]
143
+
144
+ Arguments:
145
+ <message_id> Message ID to fetch
146
+
147
+ Options:
148
+ --project <id> Project ID (defaults to the active project)
149
+
150
+ Examples:
151
+ run402 email get msg_abc123
118
152
  `,
119
153
  };
120
154
 
package/lib/functions.mjs CHANGED
@@ -2,7 +2,7 @@ import { readFileSync } from "fs";
2
2
  import { findProject, API } from "./config.mjs";
3
3
  import { getSdk } from "./sdk.mjs";
4
4
  import { reportSdkError, fail } from "./sdk-errors.mjs";
5
- import { assertKnownFlags, hasHelp, normalizeArgv, parseIntegerFlag } from "./argparse.mjs";
5
+ import { assertKnownFlags, hasHelp, normalizeArgv, parseIntegerFlag, validateRegularFile } from "./argparse.mjs";
6
6
 
7
7
  const HELP = `run402 functions — Manage serverless functions
8
8
 
@@ -141,6 +141,29 @@ Examples:
141
141
  run402 functions update prj_abc123 send-reminders --schedule '0 */4 * * *'
142
142
  run402 functions update prj_abc123 send-reminders --schedule-remove
143
143
  run402 functions update prj_abc123 my-func --timeout 15 --memory 256
144
+ `,
145
+ list: `run402 functions list — List all functions for a project
146
+
147
+ Usage:
148
+ run402 functions list <project_id>
149
+
150
+ Arguments:
151
+ <project_id> Target project ID
152
+
153
+ Examples:
154
+ run402 functions list prj_abc123
155
+ `,
156
+ delete: `run402 functions delete — Delete a function from a project
157
+
158
+ Usage:
159
+ run402 functions delete <project_id> <name>
160
+
161
+ Arguments:
162
+ <project_id> Target project ID
163
+ <name> Function name to delete
164
+
165
+ Examples:
166
+ run402 functions delete prj_abc123 stripe-webhook
144
167
  `,
145
168
  };
146
169
 
@@ -158,6 +181,7 @@ async function deploy(projectId, name, args) {
158
181
  if (!opts.file) {
159
182
  fail({ code: "BAD_USAGE", message: "Missing --file <file>" });
160
183
  }
184
+ validateRegularFile(opts.file, "--file");
161
185
  const code = readFileSync(opts.file, "utf-8");
162
186
 
163
187
  const deployOpts = { name, code };
package/lib/image.mjs CHANGED
@@ -27,12 +27,44 @@ Notes:
27
27
  - Use --output to save directly to a file instead of printing base64
28
28
  `;
29
29
 
30
+ const SUB_HELP = {
31
+ generate: `run402 image generate — Generate an AI image from a text prompt
32
+
33
+ Usage:
34
+ run402 image generate "<prompt>" [options]
35
+
36
+ Arguments:
37
+ <prompt> Text prompt describing the image (quote it)
38
+
39
+ Options:
40
+ --aspect <ratio> Image aspect ratio: square | landscape | portrait
41
+ (default: square)
42
+ --output <file> Save image to file (e.g. output.png). If omitted,
43
+ returns base64 JSON to stdout.
44
+
45
+ Notes:
46
+ - Requires a funded allowance (run402 allowance create && run402 allowance fund)
47
+ - Payments are processed automatically via x402 micropayments
48
+ - Use --output to save directly to a file instead of printing base64
49
+
50
+ Examples:
51
+ run402 image generate "a startup mascot, pixel art"
52
+ run402 image generate "futuristic city at night" --aspect landscape
53
+ run402 image generate "portrait of a cat CEO" --aspect portrait --output cat.png
54
+ `,
55
+ };
56
+
30
57
  export async function run(sub, args) {
31
58
  if (!sub || sub === '--help' || sub === '-h') {
32
59
  console.log(HELP);
33
60
  process.exit(0);
34
61
  }
35
62
 
63
+ if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
64
+ console.log(SUB_HELP[sub] || HELP);
65
+ process.exit(0);
66
+ }
67
+
36
68
  if (sub !== "generate") {
37
69
  console.error(`Unknown subcommand: ${sub}\n`);
38
70
  console.log(HELP);
@@ -43,7 +75,7 @@ export async function run(sub, args) {
43
75
  let i = 0;
44
76
  if (i < args.length && !args[i].startsWith("--")) opts.prompt = args[i++];
45
77
  while (i < args.length) {
46
- if (args[i] === "--help" || args[i] === "-h") { console.log(HELP); process.exit(0); }
78
+ if (args[i] === "--help" || args[i] === "-h") { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
47
79
  else if (args[i] === "--aspect" && args[i + 1]) { opts.aspect = args[++i]; }
48
80
  else if (args[i] === "--output" && args[i + 1]) { opts.output = args[++i]; }
49
81
  i++;
package/lib/message.mjs CHANGED
@@ -10,21 +10,68 @@ Usage:
10
10
  Notes:
11
11
  - Requires an active tier (run402 tier set <tier>)
12
12
  - Requires an allowance (run402 allowance create)
13
+ - Messages are capped at 8 KB (8192 bytes UTF-8) to keep the developer
14
+ inbox useful and prevent payload-dump misuse. Trim or summarize long
15
+ content (e.g. stack traces) before sending.
13
16
 
14
17
  Examples:
15
18
  run402 message send "Hello from my agent!"
16
19
  `;
17
20
 
21
+ // Cap message body at a Twitter-ish but engineer-generous size: enough for
22
+ // a few paragraphs and a stack-trace excerpt, small enough that a misbehaving
23
+ // agent script can't dump arbitrary content into the developer inbox in one
24
+ // call. UTF-8 bytes (not characters) — emoji and accented chars count as
25
+ // multiple bytes.
26
+ const MESSAGE_MAX_BYTES = 8192;
27
+
28
+ const SUB_HELP = {
29
+ send: `run402 message send — Send a message to Run402 developers
30
+
31
+ Usage:
32
+ run402 message send <text>
33
+
34
+ Arguments:
35
+ <text> Message body (quote it; remaining args are joined with
36
+ spaces if multiple positional words are provided)
37
+
38
+ Notes:
39
+ - Requires an active tier (run402 tier set <tier>)
40
+ - Requires an allowance (run402 allowance create)
41
+ - Messages are capped at 8 KB (8192 bytes UTF-8) to keep the developer
42
+ inbox useful and prevent payload-dump misuse.
43
+
44
+ Examples:
45
+ run402 message send "Hello from my agent!"
46
+ `,
47
+ };
48
+
18
49
  async function send(text) {
19
- if (!text) {
50
+ if (!text || typeof text !== "string") {
20
51
  fail({ code: "BAD_USAGE", message: "Missing message text." });
21
52
  }
53
+ // Cap check runs BEFORE the allowance check so oversized payloads surface
54
+ // a structured size error instead of being masked by a missing-allowance
55
+ // exit.
56
+ const bytes = Buffer.byteLength(text, "utf-8");
57
+ if (bytes > MESSAGE_MAX_BYTES) {
58
+ fail({
59
+ code: "MESSAGE_TOO_LONG",
60
+ message: `Message is ${bytes} bytes; maximum is ${MESSAGE_MAX_BYTES} bytes (~8 KB).`,
61
+ hint: "Trim or summarize the message.",
62
+ details: { bytes, max_bytes: MESSAGE_MAX_BYTES },
63
+ });
64
+ }
22
65
  // Preserve the aggressive early exit when no allowance is configured.
23
66
  allowanceAuthHeaders("/message/v1");
24
67
 
25
68
  try {
26
69
  await getSdk().admin.sendMessage(text);
27
- console.log(JSON.stringify({ status: "ok", message: "Message sent to Run402 developers." }));
70
+ console.log(JSON.stringify({
71
+ status: "ok",
72
+ message: "Message sent to Run402 developers.",
73
+ bytes_sent: bytes,
74
+ }));
28
75
  } catch (err) {
29
76
  reportSdkError(err);
30
77
  }
@@ -33,7 +80,7 @@ async function send(text) {
33
80
  export async function run(sub, args) {
34
81
  if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
35
82
  if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
36
- console.log(HELP);
83
+ console.log(SUB_HELP[sub] || HELP);
37
84
  process.exit(0);
38
85
  }
39
86
  if (sub !== "send") {
package/lib/projects.mjs CHANGED
@@ -2,7 +2,7 @@ import { readFileSync } from "fs";
2
2
  import { findProject, loadKeyStore, API, allowanceAuthHeaders, resolveProjectId, getActiveProjectId } from "./config.mjs";
3
3
  import { getSdk } from "./sdk.mjs";
4
4
  import { reportSdkError, fail, parseFlagJson } from "./sdk-errors.mjs";
5
- import { assertKnownFlags, failBadProjectId, hasHelp, normalizeArgv, positionalArgs } from "./argparse.mjs";
5
+ import { assertKnownFlags, failBadProjectId, hasHelp, normalizeArgv, positionalArgs, resolvePositionalProject, validateRegularFile } from "./argparse.mjs";
6
6
 
7
7
  const HELP = `run402 projects — Manage your deployed Run402 projects
8
8
 
@@ -123,7 +123,37 @@ async function provision(args) {
123
123
  const opts = { tier: "prototype", name: undefined };
124
124
  for (let i = 0; i < args.length; i++) {
125
125
  if (args[i] === "--tier" && args[i + 1]) opts.tier = args[++i];
126
- if (args[i] === "--name" && args[i + 1]) opts.name = args[++i];
126
+ // Use !== undefined so an empty-string value is captured (and rejected
127
+ // below) rather than silently dropped by the falsy-check pattern (GH-176).
128
+ if (args[i] === "--name" && args[i + 1] !== undefined) opts.name = args[++i];
129
+ }
130
+ // Validate --name when provided. Omitted --name lets the server pick a
131
+ // default. The same envelope should also be enforced server-side (GH-176).
132
+ if (opts.name !== undefined) {
133
+ if (opts.name === "") {
134
+ fail({
135
+ code: "BAD_PROJECT_NAME",
136
+ message: "--name must not be empty.",
137
+ details: { field: "--name" },
138
+ hint: "Provide a 1-128 character name, or omit --name to use the server-assigned default.",
139
+ });
140
+ }
141
+ if (opts.name.length > 128) {
142
+ fail({
143
+ code: "BAD_PROJECT_NAME",
144
+ message: `--name must be 1-128 characters, got ${opts.name.length}.`,
145
+ details: { field: "--name", length: opts.name.length, max: 128 },
146
+ });
147
+ }
148
+ // eslint-disable-next-line no-control-regex
149
+ if (/[\x00-\x1f\x7f]/.test(opts.name)) {
150
+ fail({
151
+ code: "BAD_PROJECT_NAME",
152
+ message: "--name contains control characters (newline, tab, etc).",
153
+ details: { field: "--name" },
154
+ hint: "Project names should be a single-line label.",
155
+ });
156
+ }
127
157
  }
128
158
  // Preserve the aggressive early exit when no allowance is configured —
129
159
  // gives the user a more specific prompt than the SDK's 401/402 path.
@@ -145,13 +175,14 @@ async function provision(args) {
145
175
  }
146
176
 
147
177
  async function applyExpose(projectId, args = []) {
148
- const p = findProject(projectId);
149
178
  let file = null;
150
179
  let inline = null;
151
180
  for (let i = 0; i < args.length; i++) {
152
181
  if (args[i] === "--file" && args[i + 1]) { file = args[++i]; }
153
182
  else if (!inline && !args[i].startsWith("--")) { inline = args[i]; }
154
183
  }
184
+ if (file) validateRegularFile(file, "--file");
185
+ const p = findProject(projectId);
155
186
  const raw = file ? readFileSync(file, "utf-8") : inline;
156
187
  if (!raw) {
157
188
  fail({
@@ -222,7 +253,6 @@ async function keys(projectId) {
222
253
  }
223
254
 
224
255
  async function sqlCmd(projectId, args = []) {
225
- const p = findProject(projectId);
226
256
  let file = null;
227
257
  let query = null;
228
258
  let paramsRaw = null;
@@ -231,6 +261,8 @@ async function sqlCmd(projectId, args = []) {
231
261
  else if (args[i] === "--params" && args[i + 1]) { paramsRaw = args[++i]; }
232
262
  else if (!query && !args[i].startsWith("--")) { query = args[i]; }
233
263
  }
264
+ if (file) validateRegularFile(file, "--file");
265
+ const p = findProject(projectId);
234
266
  const sql = file ? readFileSync(file, "utf-8") : query;
235
267
  if (!sql) {
236
268
  fail({
@@ -259,6 +291,13 @@ async function sqlCmd(projectId, args = []) {
259
291
  }
260
292
 
261
293
  async function rest(projectId, table, queryParams) {
294
+ if (!table) {
295
+ fail({
296
+ code: "BAD_USAGE",
297
+ message: "Missing <table> argument. Usage: run402 projects rest [id] <table> [\"<query>\"]",
298
+ hint: "Run 'run402 projects schema <id>' to list tables.",
299
+ });
300
+ }
262
301
  const p = findProject(projectId);
263
302
  const res = await fetch(`${API}/rest/v1/${table}${queryParams ? '?' + queryParams : ''}`, { headers: { "apikey": p.anon_key } });
264
303
  const data = await res.json();
@@ -373,35 +412,6 @@ async function deleteProject(projectId, args = []) {
373
412
  }
374
413
  }
375
414
 
376
- // Resolve a positional project_id argument with active-project fallback (GH-102).
377
- // Callers can tighten the legacy shorthand when a bare non-prj positional is
378
- // more likely a mistyped project id than an argument for the active project.
379
- function resolvePositionalProject(args, opts = {}) {
380
- const first = Array.isArray(args) ? args[0] : undefined;
381
- if (typeof first === "string" && first.startsWith("prj_")) {
382
- return { projectId: first, rest: args.slice(1) };
383
- }
384
- if (
385
- typeof first === "string" &&
386
- first.length > 0 &&
387
- !first.startsWith("-") &&
388
- Array.isArray(opts.rejectBareFirstWhenFlagPresent) &&
389
- opts.rejectBareFirstWhenFlagPresent.some((flag) => args.includes(flag))
390
- ) {
391
- failBadProjectId(first);
392
- }
393
- if (typeof first === "string" && first.length > 0 && !first.startsWith("-") && opts.rejectBareFirst) {
394
- failBadProjectId(first);
395
- }
396
- if (typeof first === "string" && first.length > 0 && !first.startsWith("-") && opts.maxBarePositionals !== undefined) {
397
- const bare = positionalArgs(args, opts.valueFlags ?? []);
398
- if (bare.length > opts.maxBarePositionals) {
399
- failBadProjectId(first);
400
- }
401
- }
402
- return { projectId: resolveProjectId(null), rest: Array.isArray(args) ? args : [] };
403
- }
404
-
405
415
  const FLAGS_BY_SUB = {
406
416
  provision: { known: ["--tier", "--name"], values: ["--tier", "--name"] },
407
417
  sql: { known: ["--file", "--params"], values: ["--file", "--params"] },
@@ -29,7 +29,7 @@
29
29
  * validation: the call wasn't sent, so retrying is safe and won't help unless
30
30
  * the user fixes input.
31
31
  */
32
- export function fail({ message, code, hint, details, next_actions, retryable = false, safe_to_retry = true, exit_code = 1 } = {}) {
32
+ export function fail({ message, code, hint, details, next_actions, field, retryable = false, safe_to_retry = true, exit_code = 1 } = {}) {
33
33
  const envelope = {
34
34
  status: "error",
35
35
  code: code ?? "BAD_USAGE",
@@ -39,6 +39,7 @@ export function fail({ message, code, hint, details, next_actions, retryable = f
39
39
  };
40
40
  if (hint !== undefined) envelope.hint = hint;
41
41
  if (details !== undefined) envelope.details = details;
42
+ if (field !== undefined) envelope.field = field;
42
43
  envelope.next_actions = Array.isArray(next_actions) ? next_actions : [];
43
44
  envelope.trace_id = null;
44
45
  console.error(JSON.stringify(envelope));
package/lib/secrets.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from "fs";
2
2
  import { getSdk } from "./sdk.mjs";
3
3
  import { reportSdkError, fail } from "./sdk-errors.mjs";
4
+ import { validateRegularFile } from "./argparse.mjs";
4
5
 
5
6
  const HELP = `run402 secrets — Manage project secrets
6
7
 
@@ -45,6 +46,33 @@ Notes:
45
46
  Examples:
46
47
  run402 secrets set prj_abc123 STRIPE_KEY sk-1234
47
48
  run402 secrets set prj_abc123 TLS_CERT --file cert.pem
49
+ `,
50
+ list: `run402 secrets list — List all secrets for a project
51
+
52
+ Usage:
53
+ run402 secrets list <id>
54
+
55
+ Arguments:
56
+ <id> Project ID (from 'run402 projects list')
57
+
58
+ Notes:
59
+ - Returns secret keys with a value_hash (first 8 hex chars of SHA-256)
60
+ for verifying the correct value was set; raw values are write-only
61
+
62
+ Examples:
63
+ run402 secrets list prj_abc123
64
+ `,
65
+ delete: `run402 secrets delete — Delete a secret from a project
66
+
67
+ Usage:
68
+ run402 secrets delete <id> <key>
69
+
70
+ Arguments:
71
+ <id> Project ID (from 'run402 projects list')
72
+ <key> Secret key name to remove
73
+
74
+ Examples:
75
+ run402 secrets delete prj_abc123 STRIPE_KEY
48
76
  `,
49
77
  };
50
78
 
@@ -55,6 +83,7 @@ async function set(projectId, key, args = []) {
55
83
  if (args[i] === "--file" && args[i + 1]) { file = args[++i]; }
56
84
  else if (!value && !args[i].startsWith("--")) { value = args[i]; }
57
85
  }
86
+ if (file) validateRegularFile(file, "--file");
58
87
  const val = file ? readFileSync(file, "utf-8") : value;
59
88
  if (!val) {
60
89
  fail({
@@ -22,6 +22,83 @@ Examples:
22
22
  run402 sender-domain inbound-disable kysigned.com
23
23
  `;
24
24
 
25
+ const SUB_HELP = {
26
+ register: `run402 sender-domain register — Register a custom sender domain
27
+
28
+ Usage:
29
+ run402 sender-domain register <domain> [--project <id>]
30
+
31
+ Arguments:
32
+ <domain> Custom sender domain (e.g. kysigned.com)
33
+
34
+ Options:
35
+ --project <id> Project ID (defaults to the active project)
36
+
37
+ Notes:
38
+ - Returns DNS records (DKIM, SPF, DMARC) to add at your DNS provider
39
+ - Use 'run402 sender-domain status' to poll until verified
40
+
41
+ Examples:
42
+ run402 sender-domain register kysigned.com
43
+ run402 sender-domain register kysigned.com --project prj_abc123
44
+ `,
45
+ status: `run402 sender-domain status — Check verification status of the project's sender domain
46
+
47
+ Usage:
48
+ run402 sender-domain status [--project <id>]
49
+
50
+ Options:
51
+ --project <id> Project ID (defaults to the active project)
52
+
53
+ Examples:
54
+ run402 sender-domain status
55
+ run402 sender-domain status --project prj_abc123
56
+ `,
57
+ remove: `run402 sender-domain remove — Remove the project's custom sender domain
58
+
59
+ Usage:
60
+ run402 sender-domain remove [--project <id>]
61
+
62
+ Options:
63
+ --project <id> Project ID (defaults to the active project)
64
+
65
+ Examples:
66
+ run402 sender-domain remove
67
+ run402 sender-domain remove --project prj_abc123
68
+ `,
69
+ "inbound-enable": `run402 sender-domain inbound-enable — Enable inbound email for a sender domain
70
+
71
+ Usage:
72
+ run402 sender-domain inbound-enable <domain> [--project <id>]
73
+
74
+ Arguments:
75
+ <domain> Custom sender domain to enable inbound on
76
+
77
+ Options:
78
+ --project <id> Project ID (defaults to the active project)
79
+
80
+ Notes:
81
+ - Requires the domain to be DKIM-verified first
82
+
83
+ Examples:
84
+ run402 sender-domain inbound-enable kysigned.com
85
+ `,
86
+ "inbound-disable": `run402 sender-domain inbound-disable — Disable inbound email for a sender domain
87
+
88
+ Usage:
89
+ run402 sender-domain inbound-disable <domain> [--project <id>]
90
+
91
+ Arguments:
92
+ <domain> Custom sender domain to disable inbound on
93
+
94
+ Options:
95
+ --project <id> Project ID (defaults to the active project)
96
+
97
+ Examples:
98
+ run402 sender-domain inbound-disable kysigned.com
99
+ `,
100
+ };
101
+
25
102
  function parseFlag(args, flag) {
26
103
  for (let i = 0; i < args.length; i++) {
27
104
  if (args[i] === flag && args[i + 1]) return args[i + 1];
@@ -106,7 +183,7 @@ async function inboundToggle(action, args) {
106
183
 
107
184
  export async function run(sub, args) {
108
185
  if (!sub || sub === "--help" || sub === "-h") { console.log(HELP); process.exit(0); }
109
- if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(HELP); process.exit(0); }
186
+ if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
110
187
  switch (sub) {
111
188
  case "register": await register(args); break;
112
189
  case "status": await status(args); break;
package/lib/service.mjs CHANGED
@@ -13,6 +13,35 @@ Notes:
13
13
  balance, tier, projects), use 'run402 status'.
14
14
  `;
15
15
 
16
+ const SUB_HELP = {
17
+ status: `run402 service status — Public service availability report
18
+
19
+ Usage:
20
+ run402 service status
21
+
22
+ Notes:
23
+ - Unauthenticated and free; no allowance required
24
+ - Returns uptime, supported capabilities, operator, and deployment info
25
+ - For account state (allowance, balance, tier, projects), use
26
+ 'run402 status' instead
27
+
28
+ Examples:
29
+ run402 service status
30
+ `,
31
+ health: `run402 service health — Service liveness check
32
+
33
+ Usage:
34
+ run402 service health
35
+
36
+ Notes:
37
+ - Unauthenticated and free; no allowance required
38
+ - Returns per-dependency status and the deployed version
39
+
40
+ Examples:
41
+ run402 service health
42
+ `,
43
+ };
44
+
16
45
  async function status() {
17
46
  try {
18
47
  const data = await getSdk().service.status();
@@ -34,7 +63,7 @@ async function health() {
34
63
  export async function run(sub, args) {
35
64
  if (!sub || sub === "--help" || sub === "-h") { console.log(HELP); process.exit(0); }
36
65
  if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
37
- console.log(HELP);
66
+ console.log(SUB_HELP[sub] || HELP);
38
67
  process.exit(0);
39
68
  }
40
69
  switch (sub) {
@@ -10,7 +10,7 @@ Usage:
10
10
  Subcommands:
11
11
  claim <name> [--project <id>] [--deployment <id>] Claim a subdomain
12
12
  delete <name> --confirm [--project <id>] Release a subdomain. Requires --confirm.
13
- list [<id>] List subdomains for a project
13
+ list [<id>] | list --project <id> List subdomains for a project
14
14
 
15
15
  Options default to the active project and its last deployment when omitted.
16
16
  Legacy syntax 'claim <deployment_id> <name>' is still supported.
@@ -48,6 +48,35 @@ Notes:
48
48
  Examples:
49
49
  run402 subdomains claim myapp
50
50
  run402 subdomains claim myapp --deployment dpl_abc123 --project prj_abc123
51
+ `,
52
+ list: `run402 subdomains list — List subdomains claimed by a project
53
+
54
+ Usage:
55
+ run402 subdomains list [<id>]
56
+
57
+ Arguments:
58
+ <id> Project ID (defaults to the active project)
59
+
60
+ Examples:
61
+ run402 subdomains list
62
+ run402 subdomains list prj_abc123
63
+ `,
64
+ delete: `run402 subdomains delete — Release a claimed subdomain
65
+
66
+ Usage:
67
+ run402 subdomains delete <name> --confirm [--project <id>]
68
+
69
+ Arguments:
70
+ <name> Subdomain name to release
71
+
72
+ Options:
73
+ --confirm Required: releasing a subdomain is irreversible and
74
+ makes it available for any other project to claim
75
+ --project <id> Project ID (defaults to the active project)
76
+
77
+ Examples:
78
+ run402 subdomains delete myapp --confirm
79
+ run402 subdomains delete myapp --confirm --project prj_abc123
51
80
  `,
52
81
  };
53
82
 
@@ -122,8 +151,24 @@ async function deleteSubdomain(allArgs) {
122
151
  }
123
152
  }
124
153
 
125
- async function list(projectIdArg) {
126
- const projectId = resolveProjectId(projectIdArg);
154
+ function parseProjectFlag(args) {
155
+ let project = null;
156
+ const rest = [];
157
+ for (let i = 0; i < args.length; i++) {
158
+ if (args[i] === "--project" && args[i + 1]) { project = args[++i]; }
159
+ else { rest.push(args[i]); }
160
+ }
161
+ return { project, rest };
162
+ }
163
+
164
+ async function list(args) {
165
+ const argList = Array.isArray(args) ? args : [];
166
+ const { project, rest } = parseProjectFlag(argList);
167
+ // Either --project <id> or a positional id is accepted; --project wins
168
+ // when both are supplied. Falls back to the active project when neither
169
+ // is given. Keeps backward-compat with the legacy `subdomains list <id>`
170
+ // form (GH-231; mirrors the GH-209 fix for `domains list`).
171
+ const projectId = resolveProjectId(project || rest[0]);
127
172
  try {
128
173
  const data = await getSdk().subdomains.list(projectId);
129
174
  console.log(JSON.stringify(data, null, 2));
@@ -148,7 +193,7 @@ export async function run(sub, args) {
148
193
  break;
149
194
  }
150
195
  case "delete": await deleteSubdomain(args); break;
151
- case "list": await list(args[0]); break;
196
+ case "list": await list(args); break;
152
197
  default:
153
198
  console.error(`Unknown subcommand: ${sub}\n`);
154
199
  console.log(HELP);
package/lib/tier.mjs CHANGED
@@ -24,6 +24,46 @@ Examples:
24
24
  run402 tier set hobby
25
25
  `;
26
26
 
27
+ const SUB_HELP = {
28
+ status: `run402 tier status — Show current tier subscription state
29
+
30
+ Usage:
31
+ run402 tier status
32
+
33
+ Notes:
34
+ - Returns the current tier name, status, and expiry
35
+ - Use 'run402 tier set <tier>' to subscribe, renew, or upgrade
36
+
37
+ Examples:
38
+ run402 tier status
39
+ `,
40
+ set: `run402 tier set — Subscribe, renew, or upgrade your tier
41
+
42
+ Usage:
43
+ run402 tier set <tier>
44
+
45
+ Arguments:
46
+ <tier> One of: prototype, hobby, team
47
+
48
+ Tiers:
49
+ prototype $0.10/7d (free with testnet faucet)
50
+ hobby $5/30d
51
+ team $20/30d
52
+
53
+ Notes:
54
+ Server auto-detects action based on current allowance state:
55
+ - No tier or expired -> subscribe
56
+ - Same tier, active -> renew (extends from expiry)
57
+ - Higher tier -> upgrade (prorated refund to allowance)
58
+ - Lower tier, active -> rejected (wait for expiry)
59
+ Pays via x402 micropayments.
60
+
61
+ Examples:
62
+ run402 tier set prototype
63
+ run402 tier set hobby
64
+ `,
65
+ };
66
+
27
67
  async function status() {
28
68
  try {
29
69
  const data = await getSdk().tier.status();
@@ -55,7 +95,7 @@ export async function run(sub, args) {
55
95
  process.exit(0);
56
96
  }
57
97
  if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
58
- console.log(HELP);
98
+ console.log(SUB_HELP[sub] || HELP);
59
99
  process.exit(0);
60
100
  }
61
101
  switch (sub) {