run402 3.7.14 → 3.8.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.
Files changed (104) hide show
  1. package/README.md +4 -2
  2. package/cli.mjs +6 -0
  3. package/core-dist/config.js +9 -0
  4. package/core-dist/keystore.js +103 -68
  5. package/core-dist/profile-state.js +137 -0
  6. package/lib/config.mjs +31 -20
  7. package/lib/credentials.mjs +273 -0
  8. package/lib/deploy-v2.mjs +11 -0
  9. package/lib/doctor.mjs +18 -2
  10. package/lib/domains.mjs +40 -20
  11. package/lib/next-actions.mjs +7 -0
  12. package/lib/projects.mjs +65 -27
  13. package/lib/sdk-errors.mjs +24 -3
  14. package/lib/sdk.mjs +3 -2
  15. package/lib/up.mjs +19 -0
  16. package/lib/up.test.mjs +29 -0
  17. package/lib/update-check.mjs +722 -0
  18. package/lib/update-check.test.mjs +438 -0
  19. package/package.json +1 -1
  20. package/sdk/core-dist/config.js +9 -0
  21. package/sdk/core-dist/keystore.js +103 -68
  22. package/sdk/core-dist/profile-state.js +137 -0
  23. package/sdk/dist/credentials.d.ts +24 -7
  24. package/sdk/dist/credentials.d.ts.map +1 -1
  25. package/sdk/dist/credentials.js +4 -4
  26. package/sdk/dist/errors.d.ts +27 -1
  27. package/sdk/dist/errors.d.ts.map +1 -1
  28. package/sdk/dist/errors.js +61 -3
  29. package/sdk/dist/errors.js.map +1 -1
  30. package/sdk/dist/index.d.ts +16 -2
  31. package/sdk/dist/index.d.ts.map +1 -1
  32. package/sdk/dist/index.js +9 -3
  33. package/sdk/dist/index.js.map +1 -1
  34. package/sdk/dist/kernel.d.ts +9 -0
  35. package/sdk/dist/kernel.d.ts.map +1 -1
  36. package/sdk/dist/kernel.js +40 -1
  37. package/sdk/dist/kernel.js.map +1 -1
  38. package/sdk/dist/namespaces/ai.d.ts.map +1 -1
  39. package/sdk/dist/namespaces/ai.js +5 -10
  40. package/sdk/dist/namespaces/ai.js.map +1 -1
  41. package/sdk/dist/namespaces/apps.d.ts.map +1 -1
  42. package/sdk/dist/namespaces/apps.js +5 -13
  43. package/sdk/dist/namespaces/apps.js.map +1 -1
  44. package/sdk/dist/namespaces/assets.d.ts.map +1 -1
  45. package/sdk/dist/namespaces/assets.js +9 -22
  46. package/sdk/dist/namespaces/assets.js.map +1 -1
  47. package/sdk/dist/namespaces/auth.d.ts.map +1 -1
  48. package/sdk/dist/namespaces/auth.js +16 -43
  49. package/sdk/dist/namespaces/auth.js.map +1 -1
  50. package/sdk/dist/namespaces/contracts.d.ts.map +1 -1
  51. package/sdk/dist/namespaces/contracts.js +12 -31
  52. package/sdk/dist/namespaces/contracts.js.map +1 -1
  53. package/sdk/dist/namespaces/credentials.d.ts +48 -0
  54. package/sdk/dist/namespaces/credentials.d.ts.map +1 -0
  55. package/sdk/dist/namespaces/credentials.js +115 -0
  56. package/sdk/dist/namespaces/credentials.js.map +1 -0
  57. package/sdk/dist/namespaces/domains.d.ts +8 -2
  58. package/sdk/dist/namespaces/domains.d.ts.map +1 -1
  59. package/sdk/dist/namespaces/domains.js +41 -23
  60. package/sdk/dist/namespaces/domains.js.map +1 -1
  61. package/sdk/dist/namespaces/email.d.ts.map +1 -1
  62. package/sdk/dist/namespaces/email.js +8 -19
  63. package/sdk/dist/namespaces/email.js.map +1 -1
  64. package/sdk/dist/namespaces/functions.d.ts.map +1 -1
  65. package/sdk/dist/namespaces/functions.js +14 -37
  66. package/sdk/dist/namespaces/functions.js.map +1 -1
  67. package/sdk/dist/namespaces/jobs.d.ts.map +1 -1
  68. package/sdk/dist/namespaces/jobs.js +8 -19
  69. package/sdk/dist/namespaces/jobs.js.map +1 -1
  70. package/sdk/dist/namespaces/projects.d.ts +4 -4
  71. package/sdk/dist/namespaces/projects.d.ts.map +1 -1
  72. package/sdk/dist/namespaces/projects.js +18 -42
  73. package/sdk/dist/namespaces/projects.js.map +1 -1
  74. package/sdk/dist/namespaces/secrets.d.ts.map +1 -1
  75. package/sdk/dist/namespaces/secrets.js +5 -10
  76. package/sdk/dist/namespaces/secrets.js.map +1 -1
  77. package/sdk/dist/namespaces/sender-domain.d.ts.map +1 -1
  78. package/sdk/dist/namespaces/sender-domain.js +6 -16
  79. package/sdk/dist/namespaces/sender-domain.js.map +1 -1
  80. package/sdk/dist/namespaces/subdomains.d.ts.map +1 -1
  81. package/sdk/dist/namespaces/subdomains.js +5 -10
  82. package/sdk/dist/namespaces/subdomains.js.map +1 -1
  83. package/sdk/dist/node/actions-node.d.ts.map +1 -1
  84. package/sdk/dist/node/actions-node.js +46 -15
  85. package/sdk/dist/node/actions-node.js.map +1 -1
  86. package/sdk/dist/node/credentials.d.ts +18 -4
  87. package/sdk/dist/node/credentials.d.ts.map +1 -1
  88. package/sdk/dist/node/credentials.js +37 -9
  89. package/sdk/dist/node/credentials.js.map +1 -1
  90. package/sdk/dist/node/index.d.ts +11 -2
  91. package/sdk/dist/node/index.d.ts.map +1 -1
  92. package/sdk/dist/node/index.js +26 -1
  93. package/sdk/dist/node/index.js.map +1 -1
  94. package/sdk/dist/node/target-profile.d.ts.map +1 -1
  95. package/sdk/dist/node/target-profile.js +6 -4
  96. package/sdk/dist/node/target-profile.js.map +1 -1
  97. package/sdk/dist/project-auth-classification.d.ts +125 -0
  98. package/sdk/dist/project-auth-classification.d.ts.map +1 -0
  99. package/sdk/dist/project-auth-classification.js +135 -0
  100. package/sdk/dist/project-auth-classification.js.map +1 -0
  101. package/sdk/dist/project-credentials.d.ts +4 -0
  102. package/sdk/dist/project-credentials.d.ts.map +1 -0
  103. package/sdk/dist/project-credentials.js +9 -0
  104. package/sdk/dist/project-credentials.js.map +1 -0
@@ -0,0 +1,273 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+ import {
4
+ activeProfile,
5
+ getProject,
6
+ loadKeyStore,
7
+ projectCredentialsFile,
8
+ removeProject,
9
+ saveProject,
10
+ } from "./config.mjs";
11
+ import { assertKnownFlags, flagValue, normalizeArgv, positionalArgs } from "./argparse.mjs";
12
+ import { fail } from "./sdk-errors.mjs";
13
+
14
+ const HELP = `run402 credentials — Manage local credential material
15
+
16
+ Usage:
17
+ run402 credentials <subcommand> [args...]
18
+
19
+ Subcommands:
20
+ project-keys Manage the local project-key cache
21
+
22
+ Examples:
23
+ run402 credentials project-keys list
24
+ run402 credentials project-keys status --project prj_abc123
25
+ run402 credentials project-keys import --project prj_abc123 --service-key-stdin
26
+ run402 credentials project-keys export --project prj_abc123 --reveal
27
+ `;
28
+
29
+ const PROJECT_KEYS_HELP = `run402 credentials project-keys — Manage local project-key cache entries
30
+
31
+ Usage:
32
+ run402 credentials project-keys <subcommand> [options]
33
+
34
+ Subcommands:
35
+ list List cached project-key entries, redacted
36
+ status --project <id> Show one cached entry, redacted
37
+ import --project <id> --service-key-stdin
38
+ import --project <id> --service-key-env <env>
39
+ export --project <id> --reveal Print cached keys, including secrets
40
+ remove --project <id> Remove one cached key entry
41
+
42
+ Notes:
43
+ - This is a LOCAL CACHE surface. It is not project inventory.
44
+ - list/status never reveal full keys.
45
+ - export requires --reveal.
46
+ - import accepts service keys through stdin or an environment variable, not argv.
47
+ `;
48
+
49
+ function parseProjectKeyFlags(args, extraKnown = [], valueFlagsExtra = []) {
50
+ const parsed = normalizeArgv(args);
51
+ const valueFlags = ["--project", ...valueFlagsExtra];
52
+ assertKnownFlags(parsed, ["--project", "--help", "-h", ...extraKnown], valueFlags);
53
+ return {
54
+ projectId: flagValue(parsed, "--project"),
55
+ parsed,
56
+ rest: positionalArgs(parsed, valueFlags),
57
+ };
58
+ }
59
+
60
+ function fingerprint(value) {
61
+ if (!value) return null;
62
+ return createHash("sha256").update(value).digest("hex").slice(0, 16);
63
+ }
64
+
65
+ function prefix(value) {
66
+ if (!value) return null;
67
+ return `${value.slice(0, 8)}...`;
68
+ }
69
+
70
+ function provenance() {
71
+ const profile = activeProfile();
72
+ return {
73
+ source: "local_cache",
74
+ cache_path: projectCredentialsFile(),
75
+ wallet: profile,
76
+ profile,
77
+ };
78
+ }
79
+
80
+ function redactedEntry(projectId, entry) {
81
+ return {
82
+ project_id: projectId,
83
+ configured: Boolean(entry),
84
+ has_anon_key: Boolean(entry?.anon_key),
85
+ has_service_key: Boolean(entry?.service_key),
86
+ anon_key_prefix: prefix(entry?.anon_key),
87
+ service_key_prefix: prefix(entry?.service_key),
88
+ anon_key_fingerprint: fingerprint(entry?.anon_key),
89
+ service_key_fingerprint: fingerprint(entry?.service_key),
90
+ site_url: entry?.site_url ?? null,
91
+ cached_at: entry?.cached_at ?? null,
92
+ ...provenance(),
93
+ };
94
+ }
95
+
96
+ function requireProjectFlag(projectId, usage) {
97
+ if (!projectId) {
98
+ fail({
99
+ code: "BAD_USAGE",
100
+ message: "Missing --project <id>.",
101
+ hint: usage,
102
+ });
103
+ }
104
+ return projectId;
105
+ }
106
+
107
+ function requireCachedProject(projectId) {
108
+ const entry = getProject(projectId);
109
+ if (!entry) {
110
+ fail({
111
+ code: "PROJECT_CREDENTIAL_NOT_FOUND",
112
+ message: `No local project credentials cached for ${projectId}.`,
113
+ hint: "Import keys with `run402 credentials project-keys import --project <id> --service-key-stdin` if this operation truly requires local project credentials.",
114
+ details: { project_id: projectId, ...provenance() },
115
+ next_actions: [{
116
+ type: "run_command",
117
+ command: `run402 credentials project-keys import --project ${projectId} --service-key-stdin`,
118
+ why: "Import a service key for credential-required operations.",
119
+ }],
120
+ });
121
+ }
122
+ return entry;
123
+ }
124
+
125
+ async function list(args) {
126
+ const { rest } = parseProjectKeyFlags(args);
127
+ if (rest.length > 0) {
128
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for project-keys list: ${rest[0]}` });
129
+ }
130
+ const store = loadKeyStore(projectCredentialsFile());
131
+ const projects = Object.entries(store.projects ?? {})
132
+ .sort(([a], [b]) => a.localeCompare(b))
133
+ .map(([projectId, entry]) => redactedEntry(projectId, entry));
134
+ console.log(JSON.stringify({ projects, ...provenance() }, null, 2));
135
+ }
136
+
137
+ async function status(args) {
138
+ const { projectId, rest } = parseProjectKeyFlags(args);
139
+ if (rest.length > 0) {
140
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for project-keys status: ${rest[0]}` });
141
+ }
142
+ const id = requireProjectFlag(projectId, "run402 credentials project-keys status --project <id>");
143
+ console.log(JSON.stringify(redactedEntry(id, getProject(id)), null, 2));
144
+ }
145
+
146
+ function readSecretInput(parsed) {
147
+ const fromEnv = flagValue(parsed, "--service-key-env");
148
+ const fromStdin = parsed.includes("--service-key-stdin");
149
+ if (fromEnv && fromStdin) {
150
+ fail({ code: "BAD_USAGE", message: "Use either --service-key-env or --service-key-stdin, not both." });
151
+ }
152
+ if (fromEnv) {
153
+ const value = process.env[fromEnv];
154
+ if (!value) {
155
+ fail({
156
+ code: "BAD_ENV",
157
+ message: `Environment variable ${fromEnv} is empty or unset.`,
158
+ details: { env: fromEnv },
159
+ });
160
+ }
161
+ return value.trim();
162
+ }
163
+ if (fromStdin) return readFileSync(0, "utf-8").trim();
164
+ fail({
165
+ code: "BAD_USAGE",
166
+ message: "Import requires --service-key-stdin or --service-key-env <env>.",
167
+ hint: "Do not pass service keys as command-line values; argv can leak through shell history and process listings.",
168
+ });
169
+ }
170
+
171
+ async function importKey(args) {
172
+ const { projectId, parsed, rest } = parseProjectKeyFlags(
173
+ args,
174
+ ["--service-key-stdin", "--service-key-env", "--anon-key-env"],
175
+ ["--service-key-env", "--anon-key-env"],
176
+ );
177
+ if (rest.length > 0) {
178
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for project-keys import: ${rest[0]}` });
179
+ }
180
+ const id = requireProjectFlag(projectId, "run402 credentials project-keys import --project <id> --service-key-stdin");
181
+ const serviceKey = readSecretInput(parsed);
182
+ if (!serviceKey) {
183
+ fail({ code: "BAD_USAGE", message: "Service key input was empty." });
184
+ }
185
+ const anonEnv = flagValue(parsed, "--anon-key-env");
186
+ const anonKey = anonEnv ? process.env[anonEnv] : undefined;
187
+ if (anonEnv && !anonKey) {
188
+ fail({ code: "BAD_ENV", message: `Environment variable ${anonEnv} is empty or unset.`, details: { env: anonEnv } });
189
+ }
190
+ const existing = getProject(id);
191
+ saveProject(id, {
192
+ anon_key: anonKey ?? existing?.anon_key ?? "",
193
+ service_key: serviceKey,
194
+ site_url: existing?.site_url,
195
+ deployed_at: existing?.deployed_at,
196
+ last_deployment_id: existing?.last_deployment_id,
197
+ source: "manual_import",
198
+ cached_at: new Date().toISOString(),
199
+ });
200
+ console.log(JSON.stringify({ imported: true, ...redactedEntry(id, getProject(id)) }, null, 2));
201
+ }
202
+
203
+ async function exportKey(args) {
204
+ const { projectId, parsed, rest } = parseProjectKeyFlags(args, ["--reveal"]);
205
+ if (rest.length > 0) {
206
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for project-keys export: ${rest[0]}` });
207
+ }
208
+ const id = requireProjectFlag(projectId, "run402 credentials project-keys export --project <id> --reveal");
209
+ if (!parsed.includes("--reveal")) {
210
+ fail({
211
+ code: "REVEAL_REQUIRED",
212
+ message: "Exporting full project keys requires --reveal.",
213
+ hint: "Use `run402 credentials project-keys status --project <id>` for redacted output.",
214
+ details: { project_id: id, ...provenance() },
215
+ });
216
+ }
217
+ const entry = requireCachedProject(id);
218
+ console.log(JSON.stringify({ project_id: id, ...entry, ...provenance(), revealed: true }, null, 2));
219
+ }
220
+
221
+ async function remove(args) {
222
+ const { projectId, rest } = parseProjectKeyFlags(args);
223
+ if (rest.length > 0) {
224
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for project-keys remove: ${rest[0]}` });
225
+ }
226
+ const id = requireProjectFlag(projectId, "run402 credentials project-keys remove --project <id>");
227
+ const existed = Boolean(getProject(id));
228
+ removeProject(id, projectCredentialsFile());
229
+ console.log(JSON.stringify({ project_id: id, removed: existed, ...provenance() }, null, 2));
230
+ }
231
+
232
+ async function runProjectKeys(sub, args) {
233
+ if (!sub || sub === "--help" || sub === "-h") {
234
+ console.log(PROJECT_KEYS_HELP);
235
+ process.exit(0);
236
+ }
237
+ if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) {
238
+ console.log(PROJECT_KEYS_HELP);
239
+ process.exit(0);
240
+ }
241
+ switch (sub) {
242
+ case "list": await list(args); break;
243
+ case "status": await status(args); break;
244
+ case "import": await importKey(args); break;
245
+ case "export": await exportKey(args); break;
246
+ case "remove": await remove(args); break;
247
+ default:
248
+ fail({
249
+ code: "UNKNOWN_SUBCOMMAND",
250
+ message: `Unknown credentials project-keys subcommand: ${sub}`,
251
+ hint: "Run `run402 credentials project-keys --help` for usage.",
252
+ details: { command: "credentials project-keys", subcommand: sub },
253
+ });
254
+ }
255
+ }
256
+
257
+ export async function run(sub, args = []) {
258
+ if (!sub || sub === "--help" || sub === "-h") {
259
+ console.log(HELP);
260
+ process.exit(0);
261
+ }
262
+ if (sub === "project-keys") {
263
+ const [projectKeySub, ...rest] = Array.isArray(args) ? args : [];
264
+ await runProjectKeys(projectKeySub, rest);
265
+ return;
266
+ }
267
+ fail({
268
+ code: "UNKNOWN_SUBCOMMAND",
269
+ message: `Unknown credentials subcommand: ${sub}`,
270
+ hint: "Run `run402 credentials --help` for usage.",
271
+ details: { command: "credentials", subcommand: sub },
272
+ });
273
+ }
package/lib/deploy-v2.mjs CHANGED
@@ -38,6 +38,7 @@ import { normalizeArgv } from "./argparse.mjs";
38
38
  import { loadLiveControlPlaneSession } from "../core-dist/control-plane-session.js";
39
39
  import { withAutoApprove } from "./operator.mjs";
40
40
  import { editRequestAction, nextAction, retryAction } from "./next-actions.mjs";
41
+ import { createUpdateCheckScheduler, emitUpdateNotice } from "./update-check.mjs";
41
42
 
42
43
  const APPLY_HELP = `run402 deploy apply — Unified deploy primitive (v1.34+)
43
44
 
@@ -444,6 +445,7 @@ function parsePromoteArgs(args) {
444
445
 
445
446
  async function promoteCmd(args) {
446
447
  const opts = parsePromoteArgs(args);
448
+ emitDeployUpdateNotice("promote", args, { quiet: opts.quiet });
447
449
  const projectId = opts.project ?? resolveProjectId(null);
448
450
 
449
451
  // Preserve the aggressive early-exit when no allowance is configured
@@ -500,6 +502,13 @@ function makeStderrEventWriter(quiet) {
500
502
  };
501
503
  }
502
504
 
505
+ function emitDeployUpdateNotice(subcommand, args, { quiet = false } = {}) {
506
+ const scheduler = createUpdateCheckScheduler({
507
+ command: ["run402", "deploy", subcommand, ...args],
508
+ });
509
+ emitUpdateNotice(scheduler.cachedNotice, { quiet });
510
+ }
511
+
503
512
  function parseApplyArgs(args) {
504
513
  const opts = {
505
514
  manifest: null,
@@ -787,6 +796,7 @@ async function applyCmd(args) {
787
796
  const opts = parseApplyArgs(args);
788
797
  const { source, error: sourceError } = resolveApplySource(opts, hasStdinSource());
789
798
  if (sourceError) fail(sourceError);
799
+ emitDeployUpdateNotice("apply", args, { quiet: opts.quiet });
790
800
 
791
801
  let raw;
792
802
  let manifestPath = null;
@@ -1377,6 +1387,7 @@ async function resumeCmd(args) {
1377
1387
  project: parsed.flags["--project"] ?? null,
1378
1388
  quiet: Boolean(parsed.flags["--quiet"]),
1379
1389
  };
1390
+ emitDeployUpdateNotice("resume", args, { quiet: opts.quiet });
1380
1391
  const project = resolveProjectId(opts.project);
1381
1392
 
1382
1393
  allowanceAuthHeaders("/apply/v1/operations");
package/lib/doctor.mjs CHANGED
@@ -19,22 +19,25 @@ import {
19
19
  scanSourceTree,
20
20
  SCAN_SEVERITY,
21
21
  } from "./doctor-source-scan.mjs";
22
+ import { doctorUpdateCheck } from "./update-check.mjs";
22
23
 
23
24
  const HELP = `run402 doctor — Health and config diagnostics
24
25
 
25
26
  Usage:
26
- run402 doctor [--verbose] [--no-scan] [--scan-dir <D>]
27
+ run402 doctor [--verbose] [--refresh] [--no-scan] [--scan-dir <D>]
27
28
 
28
29
  Output:
29
30
  Stdout is a JSON report { ok, checks: [{ name, status, value?, hint?, message? }] }.
30
31
 
31
32
  Options:
32
33
  --verbose Include extra detail (timing, error messages)
34
+ --refresh Wait for a bounded live npm version check for the run402 CLI
33
35
  --no-scan Skip the source-tree scan (config / health checks only)
34
36
  --scan-dir D Scan a custom directory instead of \`<cwd>/src\`
35
37
 
36
38
  Checks performed:
37
39
  - Config directory exists and is writable
40
+ - Installed run402 CLI version and update guidance
38
41
  - Allowance is configured and on a valid rail (x402 / mpp)
39
42
  - Keystore has at least one wallet
40
43
  - API_BASE is reachable (network check via /health)
@@ -60,6 +63,7 @@ export async function run(sub, args = []) {
60
63
  return;
61
64
  }
62
65
  const verbose = all.includes("--verbose");
66
+ const refresh = all.includes("--refresh");
63
67
  const skipScan = all.includes("--no-scan");
64
68
  const scanDirArgIdx = all.indexOf("--scan-dir");
65
69
  const scanDirOverride = scanDirArgIdx >= 0 ? all[scanDirArgIdx + 1] : null;
@@ -87,6 +91,18 @@ export async function run(sub, args = []) {
87
91
  });
88
92
  }
89
93
 
94
+ // 1b. CLI version/update state. This is advisory: stale or unknown version
95
+ // state should help the user, not hide the rest of doctor.
96
+ try {
97
+ checks.push(await doctorUpdateCheck({ refresh }));
98
+ } catch (err) {
99
+ checks.push({
100
+ name: "cli_update",
101
+ status: "unknown",
102
+ message: err instanceof Error ? err.message : String(err),
103
+ });
104
+ }
105
+
90
106
  // 2. Allowance.
91
107
  try {
92
108
  const allowance = readAllowance();
@@ -324,7 +340,7 @@ export async function run(sub, args = []) {
324
340
  // 'warning' counts as ok for exit-code purposes — gaps are surfaced in
325
341
  // output but don't fail the doctor. Only hard 'error' / 'missing' /
326
342
  // 'empty' fail.
327
- const allOk = checks.every((c) => c.status === "ok" || c.status === "warning" || c.status === "skipped");
343
+ const allOk = checks.every((c) => c.status === "ok" || c.status === "warning" || c.status === "skipped" || c.status === "unknown");
328
344
 
329
345
  console.log(JSON.stringify({ ok: allOk, checks }, null, 2));
330
346
  process.exit(allOk ? 0 : 1);
package/lib/domains.mjs CHANGED
@@ -9,14 +9,15 @@ Usage:
9
9
  run402 domains <subcommand> [args...]
10
10
 
11
11
  Subcommands:
12
- add <domain> <subdomain_name> [--project <id>] Register a custom domain
13
- list [--project <id>] List custom domains for a project
14
- status <domain> [--project <id>] Check domain DNS/SSL status
15
- delete <domain> --confirm [--project <id>] Release a custom domain. Requires --confirm.
12
+ add <domain> <subdomain_name> [--project <id>] [--auth principal|service-key]
13
+ list [--project <id>] [--auth principal|service-key]
14
+ status <domain> [--project <id>] [--auth principal|service-key]
15
+ delete <domain> --confirm [--project <id>] [--auth principal|service-key]
16
16
 
17
17
  Examples:
18
18
  run402 domains add example.com myapp
19
- run402 domains add example.com myapp --project prj_123
19
+ run402 domains add example.com myapp --project prj_123
20
+ run402 domains list --project prj_123 --auth service-key
20
21
  run402 domains list
21
22
  run402 domains status example.com
22
23
  run402 domains delete example.com --confirm
@@ -31,7 +32,7 @@ const SUB_HELP = {
31
32
  add: `run402 domains add — Register a custom domain for a project
32
33
 
33
34
  Usage:
34
- run402 domains add <domain> <subdomain_name> [--project <id>]
35
+ run402 domains add <domain> <subdomain_name> [--project <id>] [--auth principal|service-key]
35
36
 
36
37
  Arguments:
37
38
  <domain> Custom domain (e.g. example.com)
@@ -39,6 +40,8 @@ Arguments:
39
40
 
40
41
  Options:
41
42
  --project <id> Project ID (defaults to the active project)
43
+ --auth <mode> principal (default, server-authoritative) or service-key
44
+ (uses local project-key cache)
42
45
 
43
46
  Notes:
44
47
  - After adding, configure DNS as shown in the response
@@ -52,10 +55,14 @@ Examples:
52
55
  list: `run402 domains list — List custom domains for a project
53
56
 
54
57
  Usage:
55
- run402 domains list [--project <id>]
58
+ run402 domains list [--project <id>] [--auth principal|service-key]
56
59
 
57
- Arguments:
58
- <id> Project ID (defaults to the active project)
60
+ Arguments:
61
+ <id> Project ID (defaults to the active project)
62
+
63
+ Options:
64
+ --auth <mode> principal (default, server-authoritative) or service-key
65
+ (uses local project-key cache)
59
66
 
60
67
  Examples:
61
68
  run402 domains list
@@ -64,13 +71,15 @@ Examples:
64
71
  status: `run402 domains status — Check DNS/SSL status of a custom domain
65
72
 
66
73
  Usage:
67
- run402 domains status <domain> [--project <id>]
74
+ run402 domains status <domain> [--project <id>] [--auth principal|service-key]
68
75
 
69
76
  Arguments:
70
77
  <domain> Custom domain to check
71
78
 
72
79
  Options:
73
80
  --project <id> Project ID (defaults to the active project)
81
+ --auth <mode> principal (default, server-authoritative) or service-key
82
+ (uses local project-key cache)
74
83
 
75
84
  Examples:
76
85
  run402 domains status example.com
@@ -79,7 +88,7 @@ Examples:
79
88
  delete: `run402 domains delete — Release a custom domain
80
89
 
81
90
  Usage:
82
- run402 domains delete <domain> --confirm [--project <id>]
91
+ run402 domains delete <domain> --confirm [--project <id>] [--auth principal|service-key]
83
92
 
84
93
  Arguments:
85
94
  <domain> Custom domain to release
@@ -89,6 +98,8 @@ Options:
89
98
  project and clears its DNS/SSL configuration
90
99
  (irreversible)
91
100
  --project <id> Project ID (defaults to the active project)
101
+ --auth <mode> principal (default, server-authoritative) or service-key
102
+ (uses local project-key cache)
92
103
 
93
104
  Examples:
94
105
  run402 domains delete example.com --confirm
@@ -97,17 +108,26 @@ Examples:
97
108
 
98
109
  function parseProjectFlag(args, extraKnown = []) {
99
110
  const parsedArgs = normalizeArgv(args);
100
- const valueFlags = ["--project"];
111
+ const valueFlags = ["--project", "--auth"];
101
112
  assertKnownFlags(parsedArgs, [...valueFlags, ...extraKnown, "--help", "-h"], valueFlags);
113
+ const auth = flagValue(parsedArgs, "--auth") ?? "principal";
114
+ if (auth !== "principal" && auth !== "service-key") {
115
+ fail({
116
+ code: "BAD_FLAG",
117
+ message: "--auth must be one of: principal, service-key.",
118
+ details: { flag: "--auth", value: auth, allowed: ["principal", "service-key"] },
119
+ });
120
+ }
102
121
  return {
103
122
  project: flagValue(parsedArgs, "--project"),
123
+ authMode: auth === "service-key" ? "service_key" : "principal",
104
124
  rest: positionalArgs(parsedArgs, valueFlags),
105
125
  args: parsedArgs,
106
126
  };
107
127
  }
108
128
 
109
129
  async function add(args) {
110
- const { project, rest } = parseProjectFlag(args);
130
+ const { project, authMode, rest } = parseProjectFlag(args);
111
131
  const domain = rest[0];
112
132
  const subdomainName = rest[1];
113
133
  if (!domain || !subdomainName) {
@@ -122,7 +142,7 @@ async function add(args) {
122
142
  }
123
143
  const projectId = resolveProjectId(project);
124
144
  try {
125
- const data = await getSdk().domains.add(projectId, { domain, subdomainName });
145
+ const data = await getSdk().domains.add(projectId, { domain, subdomainName, authMode });
126
146
  console.log(JSON.stringify(data, null, 2));
127
147
  } catch (err) {
128
148
  reportSdkError(err);
@@ -131,7 +151,7 @@ async function add(args) {
131
151
 
132
152
  async function list(args) {
133
153
  const argList = Array.isArray(args) ? args : [];
134
- const { project, rest } = parseProjectFlag(argList);
154
+ const { project, authMode, rest } = parseProjectFlag(argList);
135
155
  if (rest.length > 0) {
136
156
  fail({
137
157
  code: "BAD_USAGE",
@@ -141,7 +161,7 @@ async function list(args) {
141
161
  }
142
162
  const projectId = resolveProjectId(project);
143
163
  try {
144
- const data = await getSdk().domains.list(projectId);
164
+ const data = await getSdk().domains.list(projectId, { authMode });
145
165
  console.log(JSON.stringify(data, null, 2));
146
166
  } catch (err) {
147
167
  reportSdkError(err);
@@ -149,7 +169,7 @@ async function list(args) {
149
169
  }
150
170
 
151
171
  async function status(args) {
152
- const { project, rest } = parseProjectFlag(args);
172
+ const { project, authMode, rest } = parseProjectFlag(args);
153
173
  const domain = rest[0];
154
174
  if (!domain) {
155
175
  fail({
@@ -163,7 +183,7 @@ async function status(args) {
163
183
  }
164
184
  const projectId = resolveProjectId(project);
165
185
  try {
166
- const data = await getSdk().domains.status(projectId, domain);
186
+ const data = await getSdk().domains.status(projectId, domain, { authMode });
167
187
  console.log(JSON.stringify(data, null, 2));
168
188
  } catch (err) {
169
189
  reportSdkError(err);
@@ -171,7 +191,7 @@ async function status(args) {
171
191
  }
172
192
 
173
193
  async function deleteDomain(args) {
174
- const { project, rest, args: parsedArgs } = parseProjectFlag(args, ["--confirm"]);
194
+ const { project, authMode, rest, args: parsedArgs } = parseProjectFlag(args, ["--confirm"]);
175
195
  const domain = rest[0];
176
196
  if (!domain) {
177
197
  fail({
@@ -192,7 +212,7 @@ async function deleteDomain(args) {
192
212
  }
193
213
  const projectId = resolveProjectId(project);
194
214
  try {
195
- await getSdk().domains.remove(domain, { projectId });
215
+ await getSdk().domains.remove(domain, { projectId, authMode });
196
216
  console.log(JSON.stringify({ domain, project_id: projectId, released: true }));
197
217
  } catch (err) {
198
218
  reportSdkError(err);
@@ -46,6 +46,13 @@ export function createProjectAction() {
46
46
  });
47
47
  }
48
48
 
49
+ export function selectProjectAction() {
50
+ return nextAction("edit_request", {
51
+ command: "run402 projects use <project_id>",
52
+ why: "Select a server-visible project, or pass --project <project_id> on the command.",
53
+ });
54
+ }
55
+
49
56
  export function setTierAction(tier = "prototype") {
50
57
  return nextAction("renew_tier", {
51
58
  command: `run402 tier set ${tier}`,