void 0.20.0 → 0.20.2

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 (93) hide show
  1. package/README.md +5 -1
  2. package/dist/{auth-W9WII-mN.mjs → auth-DPl6kck4.mjs} +46 -26
  3. package/dist/{auth-cmd-CYVhSNFy.mjs → auth-cmd-gniL2fNt.mjs} +5 -4
  4. package/dist/auth-link-NZdjCmSc.mjs +28 -0
  5. package/dist/{build-cmd-CkVD1uOh.mjs → build-cmd-sI18tX_O.mjs} +3 -3
  6. package/dist/{cache-QcUfR-ff.mjs → cache-IHn5MwBC.mjs} +3 -3
  7. package/dist/{cancel-deploy-DsvWKFTe.mjs → cancel-deploy-C5qTOdLi.mjs} +3 -3
  8. package/dist/cf-access-DRsQRe6k.mjs +75 -0
  9. package/dist/cli/cli.mjs +364 -1883
  10. package/dist/cli/env-schema-probe.mjs +11 -2
  11. package/dist/{client-CyCHWSO_.mjs → client-dHfSJvAN.mjs} +336 -58
  12. package/dist/{cloudflare-auth-DRkGe7-s.mjs → cloudflare-auth-6M5llVPC.mjs} +53 -6
  13. package/dist/{cloudflare-cmd-D3ME2GAe.mjs → cloudflare-cmd-4RPGN3KB.mjs} +2 -2
  14. package/dist/{cloudflare-connect-BivMefMA.mjs → cloudflare-connect-t1UU5svD.mjs} +2 -2
  15. package/dist/{cloudflare-operations-CPTpRW6d.mjs → cloudflare-operations-BzWnlC1_.mjs} +1 -1
  16. package/dist/{config-BQFq7QvD.mjs → config-uNGuFsI2.mjs} +1 -1
  17. package/dist/{connect-D9Yr-T5f.mjs → connect-Bfk31O_8.mjs} +6 -6
  18. package/dist/{create-project-DMV-csEm.mjs → create-project-Bk9Z0-Jg.mjs} +6 -5
  19. package/dist/{db-BxoUWL0F.mjs → db-BkRoptAt.mjs} +75 -48
  20. package/dist/{delete-iJOqxBz1.mjs → delete-DouASY9P.mjs} +3 -3
  21. package/dist/{deploy-CcDoeBIf.mjs → deploy-DTaWUS1S.mjs} +137 -128
  22. package/dist/{dev-inbox-DkgRWLkW.mjs → dev-inbox-P0u4tM8Y.mjs} +1 -1
  23. package/dist/{domain-gu_iaHmn.mjs → domain-1RhhOVrC.mjs} +4 -4
  24. package/dist/email-C-lGh51B.mjs +795 -0
  25. package/dist/{env-D4Emu-M_.mjs → env-DBKmK4vc.mjs} +1 -0
  26. package/dist/{env-DmU2To0C.mjs → env-DJHsPE7Z.mjs} +5 -5
  27. package/dist/{env-validation-ENpMy6Ez.mjs → env-validation-CF6KvTRf.mjs} +3 -1
  28. package/dist/{gen-BKw6qHIg.mjs → gen-B_wPnVTK.mjs} +3 -3
  29. package/dist/generate-RTK8_kK1.mjs +47 -0
  30. package/dist/{github-cmd-DJS5Ab-H.mjs → github-cmd-PW7ZnWTp.mjs} +3 -3
  31. package/dist/{headers-D8QfRX9Y.mjs → headers-BAHwgHdW.mjs} +1 -1
  32. package/dist/help-CwOX-zmI.mjs +2216 -0
  33. package/dist/{inbound-BJ70in1n.d.mts → inbound-CH5Mksyy.d.mts} +37 -42
  34. package/dist/{inbound-CNKxb3FY.mjs → inbound-aVHEUhKo.mjs} +143 -101
  35. package/dist/index.mjs +69 -30
  36. package/dist/{init-Dx5cgKqK.mjs → init-BWZ7q5Z4.mjs} +11 -11
  37. package/dist/{link-D_rm5sRH.mjs → link-Rmvu2Wl_.mjs} +4 -4
  38. package/dist/{list-M8XShti7.mjs → list-DEE2S6mY.mjs} +4 -4
  39. package/dist/{local-d1-BE8KBbMy.mjs → local-d1-D2I6Ox5F.mjs} +1 -1
  40. package/dist/{login-C8-UuLnp.mjs → login-Uvferzmm.mjs} +28 -10
  41. package/dist/{logs-DwFU7dMW.mjs → logs-27FenuiC.mjs} +4 -4
  42. package/dist/{mime-BJD7d_qL.mjs → mime-D5Nmdzf7.mjs} +23 -9
  43. package/dist/{node-BkaXcpAc.mjs → node-Ez5KW5rn.mjs} +3 -3
  44. package/dist/operator-auth-B3e08unv.mjs +52 -0
  45. package/dist/operator-client-LUZnlnYk.mjs +82 -0
  46. package/dist/{operator-cmd-DYWRbWUA.mjs → operator-cmd-CjOTmAYE.mjs} +35 -55
  47. package/dist/{output-tFQLLj26.mjs → output-B0cfNSx5.mjs} +316 -2
  48. package/dist/pages/index.mjs +2 -2
  49. package/dist/platform-auth-config-DrbQXXiW.mjs +368 -0
  50. package/dist/platform-auth-protection-Bhtvp0B_.mjs +219 -0
  51. package/dist/platform-auth-recovery-CeOKGVeJ.mjs +310 -0
  52. package/dist/{platform-cmd-BEOVv1PN.mjs → platform-cmd-BFhieCdV.mjs} +16 -6
  53. package/dist/{platform-domain-BszUfiS8.mjs → platform-domain-C74PULqV.mjs} +4 -4
  54. package/dist/{platform-lifecycle-CjSr6xf0.mjs → platform-lifecycle-BwAIgz-t.mjs} +1667 -191
  55. package/dist/{platform-management-W30iCaTL.mjs → platform-management-COogu_Se.mjs} +33 -7
  56. package/dist/{platform-recovery-pHHv4ZSg.mjs → platform-recovery-ewqLefp1.mjs} +6 -5
  57. package/dist/{prepare-CBetXvsN.mjs → prepare-CtDJjoOj.mjs} +2 -2
  58. package/dist/{prepare-BfJvFUtJ.mjs → prepare-blNRQvQl.mjs} +2 -2
  59. package/dist/prerender-render.d.mts +11 -0
  60. package/dist/prerender-render.mjs +111 -0
  61. package/dist/{project-cmd-DCpk1cDt.mjs → project-cmd-DmZK9Hxf.mjs} +32 -14
  62. package/dist/project-team-D8jOJMUJ.mjs +130 -0
  63. package/dist/project-token-DA34bf-C.mjs +75 -0
  64. package/dist/{provision-Blnstcm2.mjs → provision-CSJOjjQk.mjs} +2 -0
  65. package/dist/{requests-Dn8Vheh1.mjs → requests-CUExwGQQ.mjs} +3 -3
  66. package/dist/{rollback-CtlPXEBi.mjs → rollback-CDNGU1gr.mjs} +4 -4
  67. package/dist/{runner-mysql-CgRFl3s6.mjs → runner-mysql-7BPUNGmL.mjs} +1 -1
  68. package/dist/{runner-pg-DCkWPsWS.mjs → runner-pg-BkEza-dX.mjs} +1 -1
  69. package/dist/runtime/ai.mjs +3 -2
  70. package/dist/runtime/email/testing.d.mts +1 -1
  71. package/dist/runtime/email/testing.mjs +3 -3
  72. package/dist/runtime/email-protocol.d.mts +15 -0
  73. package/dist/runtime/email-protocol.mjs +70 -0
  74. package/dist/runtime/email.d.mts +2 -2
  75. package/dist/runtime/email.mjs +189 -96
  76. package/dist/runtime/remote/index.mjs +5 -3
  77. package/dist/{secret-BqTxGqki.mjs → secret-Bzzi2e9E.mjs} +5 -5
  78. package/dist/{skills-Q46GZMO-.mjs → skills-C0RvGjeE.mjs} +1 -1
  79. package/dist/sqlite-validation-BzKMWnO4.mjs +25 -0
  80. package/dist/{subcommand-prompt-WfySCQ7S.mjs → subcommand-prompt-Bmyn5Rlc.mjs} +1 -1
  81. package/dist/{validate-Bihr8WBi.mjs → validate-tBBN_dXH.mjs} +1 -0
  82. package/package.json +12 -7
  83. package/skills/void/SKILL.md +35 -4
  84. package/skills/void/docs/guide/deployment.md +2 -0
  85. package/skills/void/docs/guide/email.md +121 -98
  86. package/skills/void/docs/guide/platform-administration.md +214 -2
  87. package/skills/void/docs/guide/platform-development.md +93 -2
  88. package/skills/void/docs/guide/project-collaboration.md +94 -0
  89. package/skills/void/docs/guide/self-hosted-platform.md +184 -30
  90. package/skills/void/docs/reference/cli.md +294 -15
  91. package/dist/cf-access-AJ1ehiFR.mjs +0 -42
  92. package/dist/cf-access-DsSsZUPr.mjs +0 -67
  93. package/dist/email-r6DHJyAB.mjs +0 -262
package/dist/cli/cli.mjs CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { n as __exportAll } from "../rolldown-runtime-rQ84J-ij.mjs";
3
2
  import { o as join } from "../dist-BrsS7cai.mjs";
4
- import { D as log, G as normalizePlatformApiUrl, O as note, c as import_picocolors, d as formatCloudflareUserMessage, k as outro, l as sectionTitle, p as getPackageDir, s as helpLine, t as categoryTitle } from "../output-tFQLLj26.mjs";
5
- import { t as cfAccessHeaders } from "../cf-access-AJ1ehiFR.mjs";
3
+ import { D as log, d as formatCloudflareUserMessage, p as getPackageDir } from "../output-B0cfNSx5.mjs";
4
+ import { a as printVoidHeader, i as printHelpPage, o as operatorGroups, r as printGlobalHelp, s as parseOperatorArgs, t as getHelpPath } from "../help-CwOX-zmI.mjs";
5
+ import { r as OperatorClientError } from "../operator-client-LUZnlnYk.mjs";
6
6
  import { readFileSync } from "node:fs";
7
+ import { PLATFORM_LOGIN_KINDS, parsePlatformLoginId } from "@void/platform";
7
8
  import { parseArgs } from "node:util";
8
9
  //#region src/cli/args.ts
9
10
  function parseDeployArgs(args) {
@@ -83,7 +84,11 @@ function parseDeployArgs(args) {
83
84
  const authSubcommands = [
84
85
  {
85
86
  name: "login",
86
- description: "Log in with GitHub or Google"
87
+ description: "Log in with an enabled platform login method"
88
+ },
89
+ {
90
+ name: "link",
91
+ description: "Link another login method to your account"
87
92
  },
88
93
  {
89
94
  name: "logout",
@@ -99,13 +104,20 @@ const authSubcommands = [
99
104
  }
100
105
  ];
101
106
  function parseAuthArgs(args) {
102
- const [subcommand, extra] = args;
107
+ const [subcommand, extra, trailing] = args;
108
+ if (subcommand === "link") {
109
+ if (trailing || extra !== void 0 && !/^[a-z][a-z0-9_-]{0,63}$/.test(extra)) throw new Error("auth: Usage: void auth link [connection-id].");
110
+ return {
111
+ subcommand,
112
+ ...extra ? { connectionId: extra } : {}
113
+ };
114
+ }
103
115
  if (!subcommand || ![
104
116
  "login",
105
117
  "logout",
106
118
  "whoami",
107
119
  "token"
108
- ].includes(subcommand)) throw new Error("auth: Usage: void auth <login|logout|whoami|token>.");
120
+ ].includes(subcommand)) throw new Error("auth: Usage: void auth <login|link|logout|whoami|token>.");
109
121
  if (extra) throw new Error(`auth: Unexpected argument '${extra}'.`);
110
122
  return { subcommand };
111
123
  }
@@ -470,7 +482,7 @@ const PLATFORM_FLAG = {
470
482
  const emailSubcommands = [
471
483
  {
472
484
  name: "usage",
473
- description: "Show monthly email send/receive counts and quota"
485
+ description: "Show monthly recipient attempts, inbound receipts, and quota"
474
486
  },
475
487
  {
476
488
  name: "logs",
@@ -490,6 +502,11 @@ const emailSubcommands = [
490
502
  arg: "<address>",
491
503
  description: "Remove a verified recipient"
492
504
  },
505
+ {
506
+ name: "domain",
507
+ arg: "<add|status|list|sync|rotate-secret|remove>",
508
+ description: "Send and receive at your own domain on a Cloudflare zone"
509
+ },
493
510
  {
494
511
  name: "status",
495
512
  description: "Show the email setup state and address map",
@@ -507,9 +524,18 @@ const EMAIL_SUBCOMMANDS = [
507
524
  "destinations",
508
525
  "allow",
509
526
  "disallow",
527
+ "domain",
510
528
  "status",
511
529
  "setup"
512
530
  ];
531
+ const EMAIL_DOMAIN_VERBS = [
532
+ "add",
533
+ "status",
534
+ "list",
535
+ "sync",
536
+ "rotate-secret",
537
+ "remove"
538
+ ];
513
539
  /**
514
540
  * Read a value-taking flag in its `--flag value` or `--flag=value` form. Returns the
515
541
  * raw value, or `undefined` when the flag is the last token or is followed by another
@@ -534,11 +560,12 @@ function readFlagValue(rest, i, flag) {
534
560
  }
535
561
  function parseEmailArgs(args) {
536
562
  const subcommand = args[0];
537
- if (!subcommand || !EMAIL_SUBCOMMANDS.includes(subcommand)) throw new Error("email: Usage: void email <usage|logs|destinations|allow|disallow|status|setup> [args] [--project <name>] [--limit <n>] [--platform cloudflare].");
563
+ if (!subcommand || !EMAIL_SUBCOMMANDS.includes(subcommand)) throw new Error("email: Usage: void email <usage|logs|destinations|allow|disallow|domain|status|setup> [args] [--project <name>] [--limit <n>] [--platform cloudflare].");
538
564
  let limit;
539
565
  const projectValues = [];
540
566
  const platformValues = [];
541
567
  const backendValues = [];
568
+ const subdomainValues = [];
542
569
  const positional = [];
543
570
  const rest = args.slice(1);
544
571
  for (let i = 0; i < rest.length; i++) if (rest[i] === "--project" || rest[i]?.startsWith("--project=")) {
@@ -563,6 +590,11 @@ function parseEmailArgs(args) {
563
590
  if (value === void 0) throw new Error("email: --backend requires a value ('cloudflare').");
564
591
  backendValues.push(value);
565
592
  i = next;
593
+ } else if (rest[i] === "--subdomain" || rest[i]?.startsWith("--subdomain=")) {
594
+ const { value, next } = readFlagValue(rest, i, "--subdomain");
595
+ if (value === void 0 || value.trim() === "") throw new Error("email: --subdomain requires a value (a subdomain label or host).");
596
+ subdomainValues.push(value);
597
+ i = next;
566
598
  } else positional.push(rest[i]);
567
599
  if (projectValues.length > 1) throw new Error("email: --project can only be specified once.");
568
600
  const projectSlug = projectValues[0];
@@ -580,6 +612,18 @@ function parseEmailArgs(args) {
580
612
  if (backendValue !== "cloudflare") throw new Error("email: --backend must be 'cloudflare'.");
581
613
  platform = "cloudflare";
582
614
  }
615
+ if (subdomainValues.length > 1) throw new Error("email: --subdomain can only be specified once.");
616
+ const subdomain = subdomainValues[0];
617
+ if (subcommand === "domain") {
618
+ if (platform === "cloudflare") throw new Error("email: --platform cloudflare applies to `void email status` and `void email setup` only (`domain` is a Void platform command).");
619
+ return parseEmailDomainArgs(positional, {
620
+ projectSlug,
621
+ limit,
622
+ platform,
623
+ subdomain
624
+ });
625
+ }
626
+ if (subdomain !== void 0) throw new Error("email: --subdomain applies to `void email domain add` only.");
583
627
  if (positional.length > 1) throw new Error(`email: \`void email ${subcommand}\` takes exactly one positional argument (got ${positional.length}: ${positional.map((p) => JSON.stringify(p)).join(", ")}).`);
584
628
  const address = positional[0];
585
629
  if ((subcommand === "allow" || subcommand === "disallow") && !address) throw new Error(`email: Usage: void email ${subcommand} <address> [--project <name>].`);
@@ -596,6 +640,32 @@ function parseEmailArgs(args) {
596
640
  platform
597
641
  };
598
642
  }
643
+ /**
644
+ * The nested `void email domain <verb> [<domain>]` group. positional[0] is the
645
+ * verb, positional[1] the domain — `list` takes no domain, every other verb
646
+ * requires exactly one. Shape validation of the domain itself is the command's
647
+ * job (and ultimately the api's — its regex is authoritative); the parser only
648
+ * counts positionals, exactly as it does for `allow <address>`.
649
+ */
650
+ function parseEmailDomainArgs(positional, flags) {
651
+ const verb = positional[0];
652
+ if (!verb || !EMAIL_DOMAIN_VERBS.includes(verb)) throw new Error("email: Usage: void email domain <add|status|list|sync|rotate-secret|remove> [<domain>] [--project <name>].");
653
+ if (flags.subdomain !== void 0 && verb !== "add") throw new Error("email: --subdomain applies to `void email domain add` only.");
654
+ const domain = positional[1];
655
+ if (verb === "list") {
656
+ if (domain !== void 0) throw new Error("email: `void email domain list` takes no domain argument.");
657
+ } else if (domain === void 0) throw new Error(`email: Usage: void email domain ${verb} <domain> [--project <name>].`);
658
+ if (positional.length > 2) throw new Error(`email: \`void email domain ${verb}\` takes exactly one domain argument (got ${positional.length - 1}: ${positional.slice(1).map((p) => JSON.stringify(p)).join(", ")}).`);
659
+ return {
660
+ subcommand: "domain",
661
+ domainVerb: verb,
662
+ domain,
663
+ subdomain: flags.subdomain,
664
+ projectSlug: flags.projectSlug,
665
+ limit: flags.limit,
666
+ platform: flags.platform
667
+ };
668
+ }
599
669
  //#endregion
600
670
  //#region src/cli/env-args.ts
601
671
  const envSubcommands = [{
@@ -1052,1858 +1122,113 @@ function parseGithubArgs(args) {
1052
1122
  throw new Error("github: Usage: void github <install|installations|connect|update|status|disconnect|link|join>.");
1053
1123
  }
1054
1124
  //#endregion
1055
- //#region src/cli/ascii.ts
1056
- const asciiArts = [
1057
- `
1058
- ▄ ▄ ▄▄▄ ▄ ▐▌
1059
- █ █ █ █ ▄ ▐▌
1060
- ▀▄▀ ▀▄▄▄▀ █ ▗▞▀▜▌
1061
- █ ▝▚▄▟▌`,
1062
- `
1063
- oo dP
1064
- 88
1065
- dP .dP .d8888b. dP .d888b88
1066
- 88 d8' 88' \`88 88 88' \`88
1067
- 88 .88' 88. .88 88 88. .88
1068
- 8888P' \`88888P' dP \`88888P8`,
1069
- `
1070
- _ __
1071
- _ ______ (__)___/ /
1072
- | | / / __ \\/ / __ /
1073
- | |/ / /_/ / / /_/ /
1074
- |___/\\____/_/\\__,_/`
1075
- ];
1076
- const OPERATOR_MAX_TIMEOUT_MS = 36e5;
1077
- var OperatorClientError = class extends Error {
1078
- code;
1079
- status;
1080
- details;
1081
- constructor(message, code = "operator_error", status, details) {
1082
- super(message);
1083
- this.code = code;
1084
- this.status = status;
1085
- this.details = details;
1086
- this.name = "OperatorClientError";
1087
- }
1088
- toJSON() {
1089
- return {
1090
- error: this.code,
1091
- message: this.message,
1092
- ...this.status === void 0 ? {} : { status: this.status },
1093
- ...this.details === void 0 ? {} : { details: this.details }
1094
- };
1095
- }
1096
- };
1097
- var OperatorClient = class {
1098
- token;
1099
- fetcher;
1100
- origin;
1101
- constructor(origin, token, fetcher = fetch) {
1102
- this.token = token;
1103
- this.fetcher = fetcher;
1104
- this.origin = normalizePlatformApiUrl(origin);
1105
- }
1106
- async request(path, options = {}) {
1107
- if (!path.startsWith("/") || path.startsWith("//") || path.includes("?") || path.includes("#")) throw new OperatorClientError("Invalid operator API path.");
1108
- const timeoutMs = options.timeoutMs ?? (options.method === "POST" ? 3e5 : 3e4);
1109
- if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1e3 || timeoutMs > 36e5) throw new OperatorClientError(`Operator request timeout must be an integer from 1 to ${OPERATOR_MAX_TIMEOUT_MS / 1e3} seconds.`, "invalid_timeout");
1110
- const url = new URL(`${this.origin}/operator/v1${path}`);
1111
- for (const [name, value] of Object.entries(options.query ?? {})) url.searchParams.set(name, value);
1112
- const timeoutController = new AbortController();
1113
- const timer = setTimeout(() => timeoutController.abort(), timeoutMs);
1114
- timer.unref();
1115
- const signal = options.signal ? AbortSignal.any([options.signal, timeoutController.signal]) : timeoutController.signal;
1116
- try {
1117
- let response;
1118
- try {
1119
- response = await this.fetcher(url, {
1120
- method: options.method ?? "GET",
1121
- headers: {
1122
- ...cfAccessHeaders(process.env, this.origin),
1123
- Authorization: `Bearer ${this.token}`,
1124
- Accept: "application/json",
1125
- ...options.body ? { "Content-Type": "application/json" } : {}
1126
- },
1127
- ...options.body ? { body: JSON.stringify(options.body) } : {},
1128
- redirect: "manual",
1129
- signal
1130
- });
1131
- } catch (error) {
1132
- if (options.signal?.aborted) throw error;
1133
- if (timeoutController.signal.aborted) throw new OperatorClientError(`Operator request exceeded the ${timeoutMs / 1e3}-second timeout. ${options.method === "POST" ? "Its outcome may be unknown; inspect platform state before retrying." : "Check connectivity and try again."}`, "request_timeout");
1134
- throw new OperatorClientError(`Operator request to ${this.origin} failed. ${options.method === "POST" ? "Its outcome may be unknown; inspect platform state before retrying." : "Check connectivity and try again."}`, "connection_failed");
1135
- }
1136
- if (response.status >= 300 && response.status < 400) throw new OperatorClientError("The operator API redirected the request. Credentials were not forwarded; use the exact platform API origin.", "redirect_rejected", response.status);
1137
- let data;
1138
- try {
1139
- data = await response.json();
1140
- } catch {
1141
- throw new OperatorClientError(`Operator API returned invalid JSON (HTTP ${response.status}).${options.method === "POST" ? " Inspect platform state before retrying." : ""}`, "invalid_response", response.status);
1142
- }
1143
- if (!data || typeof data !== "object" || Array.isArray(data)) throw new OperatorClientError("Operator API returned an invalid response object.", "invalid_response", response.status);
1144
- const result = data;
1145
- if (!response.ok) throw new OperatorClientError(typeof result.message === "string" ? result.message : `Operator request failed (HTTP ${response.status}).`, typeof result.error === "string" ? result.error : "request_failed", response.status, result.details);
1146
- if (result.partial === true || result.ok === false) throw new OperatorClientError("The operation did not fully complete. Inspect the partial result before retrying.", "partial_failure", response.status, result);
1147
- return result;
1148
- } finally {
1149
- clearTimeout(timer);
1150
- }
1151
- }
1152
- };
1153
- //#endregion
1154
- //#region src/cli/operator-args.ts
1155
- const operatorCommands = [
1156
- {
1157
- group: "auth",
1158
- action: "login",
1159
- description: "Sign in as a platform administrator",
1160
- path: "/session",
1161
- values: { provider: "OAuth provider" },
1162
- booleans: ["token-stdin"]
1163
- },
1164
- {
1165
- group: "auth",
1166
- action: "logout",
1167
- description: "Revoke the current operator session and remove its local credential",
1168
- path: "/session/logout"
1169
- },
1170
- {
1171
- group: "auth",
1172
- action: "status",
1173
- description: "Inspect operator identity, expiry, and capabilities",
1174
- path: "/session"
1175
- },
1176
- {
1177
- group: "auth",
1178
- action: "token",
1179
- description: "Print an operator token; exchange an API session with --token-stdin",
1180
- path: "/session",
1181
- booleans: ["token-stdin"]
1182
- },
1183
- {
1184
- group: "user",
1185
- action: "list",
1186
- description: "List users",
1187
- path: "/users",
1188
- values: { search: "Search login or email" }
1189
- },
1190
- {
1191
- group: "user",
1192
- action: "show",
1193
- description: "Inspect a user, usage, and projects",
1194
- path: "/users/:id",
1195
- positional: ["id"]
1196
- },
1197
- {
1198
- group: "user",
1199
- action: "plan",
1200
- description: "Change a user plan",
1201
- path: "/users/:id/plan",
1202
- positional: ["id", "plan"],
1203
- method: "POST"
1204
- },
1205
- {
1206
- group: "user",
1207
- action: "suspend",
1208
- description: "Suspend a user and their projects",
1209
- path: "/users/:id/suspend",
1210
- positional: ["id"],
1211
- values: { reason: "Reason for suspension" },
1212
- method: "POST"
1213
- },
1214
- {
1215
- group: "user",
1216
- action: "restore",
1217
- description: "Restore a suspended user",
1218
- path: "/users/:id/restore",
1219
- positional: ["id"],
1220
- method: "POST"
1221
- },
1222
- {
1223
- group: "user",
1224
- action: "delete",
1225
- description: "Delete a user and all owned resources",
1226
- path: "/users/:id/delete",
1227
- positional: ["id"],
1228
- method: "POST"
1229
- },
1230
- {
1231
- group: "project",
1232
- action: "list",
1233
- description: "List tenant projects",
1234
- path: "/projects",
1235
- values: {
1236
- user: "Owner user ID",
1237
- search: "Search projects"
1238
- }
1239
- },
1240
- {
1241
- group: "project",
1242
- action: "show",
1243
- description: "Inspect a project and its resources",
1244
- path: "/projects/:id",
1245
- positional: ["id"]
1246
- },
1247
- {
1248
- group: "project",
1249
- action: "delete",
1250
- description: "Delete a project and its resources",
1251
- path: "/projects/:id/delete",
1252
- positional: ["id"],
1253
- method: "POST"
1254
- },
1255
- {
1256
- group: "deployment",
1257
- action: "list",
1258
- description: "List tenant deployments",
1259
- path: "/deployments",
1260
- values: {
1261
- project: "Project ID",
1262
- search: "Search deployments",
1263
- status: "Deployment status"
1264
- }
1265
- },
1266
- {
1267
- group: "deployment",
1268
- action: "show",
1269
- description: "Inspect a deployment",
1270
- path: "/deployments/:id",
1271
- positional: ["id"]
1272
- },
1273
- {
1274
- group: "deployment",
1275
- action: "cancel",
1276
- description: "Cancel a deployment",
1277
- path: "/deployments/:id/cancel",
1278
- positional: ["id"],
1279
- method: "POST"
1280
- },
1281
- {
1282
- group: "deployment",
1283
- action: "logs",
1284
- description: "Read runtime logs (JSON follow emits one object per line)",
1285
- path: "/deployments/:id/logs",
1286
- positional: ["id"],
1287
- values: {
1288
- since: "Duration or ISO timestamp",
1289
- cursor: "Opaque continuation cursor"
1290
- },
1291
- booleans: ["follow"]
1292
- },
1293
- {
1294
- group: "build",
1295
- action: "show",
1296
- description: "Inspect a managed build",
1297
- path: "/builds/:id",
1298
- positional: ["id"]
1299
- },
1300
- {
1301
- group: "build",
1302
- action: "logs",
1303
- description: "Read build logs (JSON follow emits one object per line)",
1304
- path: "/builds/:id/logs",
1305
- positional: ["id"],
1306
- values: { since: "Last received sequence number" },
1307
- booleans: ["follow"]
1308
- },
1309
- {
1310
- group: "signup",
1311
- action: "show",
1312
- description: "Inspect signup restrictions and allowlist",
1313
- path: "/signup"
1314
- },
1315
- {
1316
- group: "signup",
1317
- action: "restrict",
1318
- description: "Restrict signup to the allowlist",
1319
- path: "/signup/restrict",
1320
- method: "POST"
1321
- },
1322
- {
1323
- group: "signup",
1324
- action: "open",
1325
- description: "Open public signup",
1326
- path: "/signup/open",
1327
- method: "POST"
1328
- },
1329
- {
1330
- group: "signup",
1331
- action: "allow",
1332
- description: "Add a github or email allowlist pattern",
1333
- path: "/signup/allow",
1334
- positional: ["type", "pattern"],
1335
- values: { note: "Allowlist note" },
1336
- method: "POST"
1337
- },
1338
- {
1339
- group: "signup",
1340
- action: "remove",
1341
- description: "Remove a github or email allowlist pattern",
1342
- path: "/signup/remove",
1343
- positional: ["type", "pattern"],
1344
- method: "POST"
1345
- },
1346
- {
1347
- group: "invitation",
1348
- action: "list",
1349
- description: "List invitations",
1350
- path: "/invitations"
1351
- },
1352
- {
1353
- group: "invitation",
1354
- action: "send",
1355
- description: "Send invitations to comma-separated email addresses",
1356
- path: "/invitations/send",
1357
- positional: ["emails"],
1358
- method: "POST"
1359
- },
1360
- {
1361
- group: "invitation",
1362
- action: "revoke",
1363
- description: "Revoke an invitation",
1364
- path: "/invitations/:id/revoke",
1365
- positional: ["id"],
1366
- method: "POST"
1367
- },
1368
- {
1369
- group: "system",
1370
- action: "overview",
1371
- description: "Inspect platform usage and capacity",
1372
- path: "/system/overview"
1373
- },
1374
- {
1375
- group: "system",
1376
- action: "health",
1377
- description: "Check platform health",
1378
- path: "/system/health"
1379
- },
1380
- {
1381
- group: "system",
1382
- action: "cli-versions",
1383
- description: "Inspect CLI version adoption and deployment statistics",
1384
- path: "/system/cli-versions"
1385
- },
1386
- {
1387
- group: "system",
1388
- action: "events",
1389
- description: "List operator audit events",
1390
- path: "/system/events"
1391
- },
1392
- {
1393
- group: "system",
1394
- action: "backfill-queue-tokens",
1395
- description: "Repair missing queue authentication tokens",
1396
- path: "/system/backfill-queue-tokens",
1397
- method: "POST"
1398
- },
1399
- {
1400
- group: "system",
1401
- action: "sandbox-drain",
1402
- description: "Drain and verify managed Sandbox container applications",
1403
- path: "/system/sandbox-drain",
1404
- values: { cursor: "Opaque cursor returned by --plan" },
1405
- method: "POST"
1406
- },
1407
- {
1408
- group: "worker",
1409
- action: "list",
1410
- description: "List hosted platform workers",
1411
- path: "/workers",
1412
- values: { environment: "production or staging" }
1413
- },
1414
- {
1415
- group: "worker",
1416
- action: "show",
1417
- description: "Inspect hosted worker versions",
1418
- path: "/workers/:id",
1419
- positional: ["id"],
1420
- values: { environment: "production or staging" }
1421
- },
1422
- {
1423
- group: "worker",
1424
- action: "rollback",
1425
- description: "Roll back a hosted worker to a version",
1426
- path: "/workers/:id/rollback",
1427
- positional: ["id", "versionId"],
1428
- values: { environment: "production or staging" },
1429
- method: "POST"
1430
- },
1431
- {
1432
- group: "worker",
1433
- action: "rollback-all",
1434
- description: "Roll back all hosted workers to their previous versions",
1435
- path: "/workers/rollback-all",
1436
- values: { environment: "production or staging" },
1437
- method: "POST"
1438
- },
1439
- {
1440
- group: "worker",
1441
- action: "events",
1442
- description: "List hosted worker events or inspect a batch",
1443
- path: "/workers/events",
1444
- positional: ["batchId"],
1445
- optionalPositional: true,
1446
- values: { environment: "production or staging" }
1447
- }
1125
+ //#region src/cli/platform-config-args.ts
1126
+ const PLATFORM_AUTH_ACTIONS = [
1127
+ "list",
1128
+ "show",
1129
+ "initialize",
1130
+ "add",
1131
+ "configure",
1132
+ "test",
1133
+ "enable",
1134
+ "disable",
1135
+ "link",
1136
+ "admission",
1137
+ "protection",
1138
+ "recover"
1448
1139
  ];
1449
- const operatorGroups = [...new Set(operatorCommands.map(({ group }) => group))];
1450
- function parseOperatorArgs(args) {
1140
+ function parsePlatformConfigArgs(args) {
1141
+ if (args.length && args[0] !== "auth") throw new Error("platform: Usage: void platform config auth [command] [options].");
1451
1142
  const positional = [];
1452
- const values = {};
1453
1143
  const flags = /* @__PURE__ */ new Set();
1454
- const booleanFlags = /* @__PURE__ */ new Set([
1455
- "json",
1456
- "plan",
1457
- "yes",
1458
- "follow",
1459
- "token-stdin"
1460
- ]);
1461
- for (let index = 0; index < args.length; index++) {
1144
+ const values = /* @__PURE__ */ new Map();
1145
+ const valueFlags = [
1146
+ "connection",
1147
+ "installation",
1148
+ "id",
1149
+ "file",
1150
+ "client-secret-env",
1151
+ "test-id"
1152
+ ];
1153
+ for (let index = 1; index < args.length; index++) {
1462
1154
  const arg = args[index];
1463
1155
  if (!arg.startsWith("-")) {
1464
1156
  positional.push(arg);
1465
1157
  continue;
1466
1158
  }
1467
- if (!arg.startsWith("--")) throw new Error(`platform: Unknown flag '${arg}'.`);
1468
- const flag = arg.slice(2);
1469
- if (flags.has(flag) || Object.hasOwn(values, flag)) throw new Error(`platform: Duplicate flag '${arg}'.`);
1470
- if (booleanFlags.has(flag)) flags.add(flag);
1471
- else {
1472
- const value = args[++index];
1473
- if (!value || value.startsWith("-")) throw new Error(`platform: ${arg} requires a value.`);
1474
- values[flag] = value;
1159
+ const name = arg.slice(2);
1160
+ if (!arg.startsWith("--") || ![
1161
+ "json",
1162
+ "plan",
1163
+ "yes"
1164
+ ].includes(name) && !valueFlags.includes(name)) throw new Error(`platform: Unknown config auth flag ${arg}.`);
1165
+ if (flags.has(name) || values.has(name)) throw new Error(`platform: Duplicate flag ${arg}.`);
1166
+ if (!valueFlags.includes(name)) {
1167
+ flags.add(name);
1168
+ continue;
1475
1169
  }
1476
- }
1477
- const [group = "", action = "", ...parameters] = positional;
1478
- if (!operatorGroups.includes(group)) throw new Error("platform: Unknown operator command group.");
1479
- const parsed = {
1480
- subcommand: "operator",
1481
- group,
1170
+ const value = args[++index];
1171
+ if (!value || value.startsWith("-")) throw new Error(`platform: ${arg} requires a value.`);
1172
+ values.set(name, value);
1173
+ }
1174
+ const [action = "", target = "", ...extra] = positional;
1175
+ if (extra.length || action && !PLATFORM_AUTH_ACTIONS.includes(action)) throw new Error(`platform: Usage: void platform config auth <${PLATFORM_AUTH_ACTIONS.join("|")}>.`);
1176
+ const needsTarget = action && action !== "list" && action !== "initialize" && action !== "admission";
1177
+ if (needsTarget && !target || !needsTarget && target) throw new Error(`platform: ${action || "config auth"} ${needsTarget ? "requires a method" : "does not accept a method"}.`);
1178
+ if (action === "add") {
1179
+ if (!PLATFORM_LOGIN_KINDS.includes(target)) throw new Error(`platform: Login kind must be ${PLATFORM_LOGIN_KINDS.join(", ")}.`);
1180
+ } else if (action === "protection") {
1181
+ if (![
1182
+ "enable",
1183
+ "disable",
1184
+ "show"
1185
+ ].includes(target)) throw new Error("platform: Usage: void platform config auth protection <enable|disable|show>.");
1186
+ } else if (target) parsePlatformLoginId(target);
1187
+ if (flags.has("plan") && flags.has("yes")) throw new Error("platform: --plan and --yes cannot be combined.");
1188
+ if ((!action || action === "list" || action === "show") && (flags.has("plan") || flags.has("yes"))) throw new Error("platform: Select a configuration change before using --plan or --yes.");
1189
+ if (values.has("id") && action !== "add") throw new Error("platform: --id is only valid when adding a login method.");
1190
+ if (values.has("installation") && action !== "recover" && action !== "protection") throw new Error("platform: --installation is only valid for recovery or platform protection.");
1191
+ if (values.has("file") && ![
1192
+ "add",
1193
+ "configure",
1194
+ "admission",
1195
+ "recover",
1196
+ "protection"
1197
+ ].includes(action) || values.has("client-secret-env") && ![
1198
+ "add",
1199
+ "configure",
1200
+ "recover"
1201
+ ].includes(action)) throw new Error("platform: --file is valid for add, configure, admission, recover, or protection; --client-secret-env is valid for add, configure, or recover.");
1202
+ if (values.has("test-id") && action !== "enable" && action !== "link") throw new Error("platform: --test-id is only valid for enable or link.");
1203
+ const secretEnv = values.get("client-secret-env") ?? "";
1204
+ if (secretEnv && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(secretEnv)) throw new Error("platform: --client-secret-env requires an environment-variable name.");
1205
+ if (values.has("id")) parsePlatformLoginId(values.get("id"));
1206
+ return {
1207
+ subcommand: "config",
1482
1208
  action,
1483
- connection: values.connection ?? "",
1209
+ target,
1210
+ connection: values.get("connection") ?? "",
1211
+ installation: values.get("installation") ?? "",
1212
+ id: values.get("id") ?? "",
1213
+ file: values.get("file") ?? "",
1214
+ clientSecretEnv: secretEnv,
1215
+ testId: values.get("test-id") ?? "",
1484
1216
  json: flags.has("json"),
1485
1217
  plan: flags.has("plan"),
1486
- yes: flags.has("yes"),
1487
- follow: flags.has("follow"),
1488
- tokenStdin: flags.has("token-stdin"),
1489
- provider: values.provider ?? "github",
1490
- method: "GET",
1491
- path: "",
1492
- query: {},
1493
- body: {}
1218
+ yes: flags.has("yes")
1494
1219
  };
1495
- if (!action && args.length === 1) return parsed;
1496
- const command = operatorCommands.find((entry) => entry.group === group && entry.action === action);
1497
- if (!command) throw new Error(`platform: Usage: void platform ${group} <${operatorCommands.filter((entry) => entry.group === group).map(({ action }) => action).join("|")}>.`);
1498
- const count = command.positional?.length ?? 0;
1499
- if (parameters.length > count || !command.optionalPositional && parameters.length !== count) throw new Error(`platform: Usage: void platform ${group} ${action}${command.positional?.map((name) => ` <${name}>`).join("") ?? ""}.`);
1500
- const paged = action === "list" || action === "events" || action === "logs";
1501
- const allowed = /* @__PURE__ */ new Set([
1502
- "connection",
1503
- ...command.method ? ["timeout"] : [],
1504
- ...Object.keys(command.values ?? {}),
1505
- ...paged ? ["limit", ...action !== "logs" ? ["page"] : []] : []
1506
- ]);
1507
- for (const name of Object.keys(values)) if (!allowed.has(name)) throw new Error(`platform: Unknown flag '--${name}' for ${group} ${action}.`);
1508
- for (const name of flags) if (![
1509
- "json",
1510
- ...command.method ? ["yes", "plan"] : [],
1511
- ...command.booleans ?? []
1512
- ].includes(name)) throw new Error(`platform: Unknown flag '--${name}' for ${group} ${action}.`);
1513
- if (parsed.plan && parsed.yes) throw new Error("platform: --plan and --yes cannot be combined.");
1514
- parsed.path = command.path;
1515
- parsed.method = command.method ?? "GET";
1516
- for (const [index, name] of (command.positional ?? []).entries()) {
1517
- const value = parameters[index];
1518
- if (value === void 0) continue;
1519
- if (value === "." || value === "..") throw new Error("platform: An object ID cannot be a dot path segment.");
1520
- if (parsed.path.includes(`:${name}`)) parsed.path = parsed.path.replace(`:${name}`, encodeURIComponent(value));
1521
- else if (group === "worker" && action === "events") parsed.path += `/${encodeURIComponent(value)}`;
1522
- else parsed.body[name] = name === "emails" ? value.split(",").map((email) => email.trim()) : value;
1523
- }
1524
- for (const [name, value] of Object.entries(values)) {
1525
- if (name === "connection" || name === "provider") continue;
1526
- if (name === "timeout") {
1527
- const maximum = OPERATOR_MAX_TIMEOUT_MS / 1e3;
1528
- if (!/^[1-9]\d*$/.test(value) || Number(value) > maximum) throw new Error(`platform: --timeout must be an integer from 1 to ${maximum} seconds.`);
1529
- parsed.timeoutMs = Number(value) * 1e3;
1530
- continue;
1531
- }
1532
- if (name === "page" || name === "limit") {
1533
- const maximum = name === "limit" ? action === "logs" ? 500 : 100 : 1e6;
1534
- if (!/^[1-9]\d*$/.test(value) || Number(value) > maximum) throw new Error(`platform: --${name} must be an integer from 1 to ${maximum}.`);
1535
- }
1536
- if (name === "since" && group === "build" && (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)))) throw new Error("platform: --since must be a nonnegative safe integer for build logs.");
1537
- if (name === "environment" && !["production", "staging"].includes(value)) throw new Error("platform: --environment must be production or staging.");
1538
- if (parsed.method === "POST") parsed.body[name] = value;
1539
- else parsed.query[name] = value;
1540
- }
1541
- if (parsed.body.type && !["github", "email"].includes(String(parsed.body.type))) throw new Error("platform: Signup pattern type must be github or email.");
1542
- return parsed;
1543
- }
1544
- //#endregion
1545
- //#region src/cli/help.ts
1546
- var help_exports = /* @__PURE__ */ __exportAll({
1547
- getHelpPath: () => getHelpPath,
1548
- printGlobalHelp: () => printGlobalHelp,
1549
- printHelpPage: () => printHelpPage,
1550
- printVoidHeader: () => printVoidHeader
1551
- });
1552
- function operatorHelpTree() {
1553
- return Object.fromEntries(operatorGroups.map((group) => {
1554
- const commands = operatorCommands.filter((entry) => entry.group === group);
1555
- return [group, {
1556
- summary: `Administer platform ${group}. Operator credentials are separate from deployment credentials.`,
1557
- usage: [`void platform ${group} <command> [options]`],
1558
- commands: commands.map((entry) => ({
1559
- command: entry.action,
1560
- description: entry.description
1561
- })),
1562
- children: Object.fromEntries(commands.map((entry) => [entry.action, {
1563
- summary: `${entry.description}.`,
1564
- usage: [`void platform ${group} ${entry.action}${entry.positional?.map((name) => entry.optionalPositional ? ` [${name}]` : ` <${name}>`).join("") ?? ""} [options]`],
1565
- options: [
1566
- {
1567
- command: "--connection <id-or-url>",
1568
- description: "Choose a registered connection; otherwise use VOID_API_URL or the active connection"
1569
- },
1570
- {
1571
- command: "--json",
1572
- description: "Write structured JSON to stdout (JSON Lines while following logs)"
1573
- },
1574
- ...entry.method ? [
1575
- {
1576
- command: "--plan",
1577
- description: "Validate and preview without changing anything"
1578
- },
1579
- {
1580
- command: "--yes",
1581
- description: "Apply after preview without prompting; required in unattended scripts"
1582
- },
1583
- {
1584
- command: "--timeout <seconds>",
1585
- description: "Mutation deadline (default 300 seconds; maximum 3600 seconds)"
1586
- }
1587
- ] : [],
1588
- ...[
1589
- "list",
1590
- "events",
1591
- "logs"
1592
- ].includes(entry.action) ? [{
1593
- command: "--limit <number>",
1594
- description: entry.action === "logs" ? "Maximum 500 log records" : "Maximum 100 records per page"
1595
- }, ...entry.action !== "logs" ? [{
1596
- command: "--page <number>",
1597
- description: "One-based page number"
1598
- }] : []] : [],
1599
- ...Object.entries(entry.values ?? {}).map(([flag, description]) => ({
1600
- command: `--${flag} <value>`,
1601
- description
1602
- })),
1603
- ...(entry.booleans ?? []).map((flag) => ({
1604
- command: `--${flag}`,
1605
- description: flag === "follow" ? "Poll new logs until interrupted or the build ends" : "Read a full API session from stdin for explicit operator session exchange"
1606
- }))
1607
- ]
1608
- }]))
1609
- }];
1610
- }));
1611
- }
1612
- const HELP_TREE = {
1613
- connect: {
1614
- summary: "Connect this project to Cloudflare or an existing Void platform.",
1615
- usage: ["void connect [url] [--platform <cloudflare|void>] [--no-login]"],
1616
- options: [{
1617
- command: "--platform <cloudflare|void>",
1618
- description: "Choose the deployment destination"
1619
- }, {
1620
- command: "--no-login",
1621
- description: "Save a Void connection without authenticating"
1622
- }],
1623
- examples: [
1624
- {
1625
- command: "void connect",
1626
- description: "Choose a destination and sign in"
1627
- },
1628
- {
1629
- command: "void connect https://platform.example.com",
1630
- description: "Join a Void platform"
1631
- },
1632
- {
1633
- command: "void connect --platform cloudflare",
1634
- description: "Sign in and select a Cloudflare account"
1635
- }
1636
- ]
1637
- },
1638
- init: {
1639
- summary: "Set up Void in a new or existing project.",
1640
- usage: ["void init [options]"],
1641
- options: [
1642
- {
1643
- command: "--agents",
1644
- description: "Set up agent instructions and skills"
1645
- },
1646
- {
1647
- command: "--tsconfig",
1648
- description: "Only update tsconfig.json"
1649
- },
1650
- {
1651
- command: "--git",
1652
- description: "Initialize a Git repository without prompting"
1653
- },
1654
- {
1655
- command: "--no-git",
1656
- description: "Skip Git repository initialization"
1657
- },
1658
- {
1659
- command: "--github",
1660
- description: "Only create the GitHub Actions deploy workflow (OIDC, no stored token)"
1661
- }
1662
- ],
1663
- examples: [
1664
- {
1665
- command: "void init",
1666
- description: "Scaffold or configure the current project"
1667
- },
1668
- {
1669
- command: "void init --agents",
1670
- description: "Install agent instructions and skills"
1671
- },
1672
- {
1673
- command: "void init --github",
1674
- description: "Scaffold the GitHub Actions deploy workflow"
1675
- }
1676
- ]
1677
- },
1678
- deploy: {
1679
- summary: "Build and deploy. Offers to link an existing Cloudflare Worker when no destination is configured.",
1680
- usage: ["void deploy [options]"],
1681
- options: [
1682
- {
1683
- command: "--project <name>",
1684
- description: "Deploy target project name"
1685
- },
1686
- {
1687
- command: "--dir <path>",
1688
- description: "Deploy a static directory and skip build"
1689
- },
1690
- {
1691
- command: "--spa",
1692
- description: "Use SPA mode for static deploys"
1693
- },
1694
- {
1695
- command: "--skip-build",
1696
- description: "Skip the build step and use existing output (static deploys on Cloudflare)"
1697
- },
1698
- {
1699
- command: "--platform <cloudflare|void>",
1700
- description: "Override the platform saved by void init"
1701
- },
1702
- {
1703
- command: "--require-email",
1704
- description: "Fail instead of deploying without email when the mail domain is not set up (with --platform cloudflare)"
1705
- },
1706
- {
1707
- command: "--debug",
1708
- description: "Mirror the deploy log to stderr (always written to ~/.void/logs)"
1709
- }
1710
- ],
1711
- examples: [
1712
- {
1713
- command: "void deploy",
1714
- description: "Deploy the linked project"
1715
- },
1716
- {
1717
- command: "void deploy --project my-app",
1718
- description: "Deploy to a specific project"
1719
- },
1720
- {
1721
- command: "void deploy --dir dist --spa",
1722
- description: "Deploy a static SPA directory"
1723
- },
1724
- {
1725
- command: "void deploy --platform cloudflare",
1726
- description: "Deploy to your own Cloudflare account"
1727
- }
1728
- ]
1729
- },
1730
- auth: {
1731
- summary: "Log in, log out, and inspect local auth state.",
1732
- usage: ["void auth <command>"],
1733
- commands: [
1734
- {
1735
- command: "login",
1736
- description: "Log in with GitHub or Google"
1737
- },
1738
- {
1739
- command: "logout",
1740
- description: "Log out and remove credentials"
1741
- },
1742
- {
1743
- command: "whoami",
1744
- description: "Show the logged-in username"
1745
- },
1746
- {
1747
- command: "token",
1748
- description: "Copy auth token to clipboard"
1749
- }
1750
- ],
1751
- children: {
1752
- login: basicSubcommand("Log in with GitHub or Google.", "void auth login"),
1753
- logout: basicSubcommand("Log out and remove credentials.", "void auth logout"),
1754
- whoami: basicSubcommand("Show the logged-in username.", "void auth whoami"),
1755
- token: basicSubcommand("Copy auth token to clipboard.", "void auth token")
1756
- }
1757
- },
1758
- cloudflare: {
1759
- summary: "Manage Cloudflare authentication used by Void.",
1760
- usage: ["void cloudflare <command>"],
1761
- commands: [
1762
- {
1763
- command: "login",
1764
- description: "Sign in or switch users with browser OAuth"
1765
- },
1766
- {
1767
- command: "status",
1768
- description: "Show identity, authentication, and the deployment account"
1769
- },
1770
- {
1771
- command: "logout",
1772
- description: "Remove the local browser session"
1773
- }
1774
- ],
1775
- children: {
1776
- login: basicSubcommand("Sign in again or switch Cloudflare users with browser OAuth.", "void cloudflare login"),
1777
- status: basicSubcommand("Show Cloudflare identity, authentication, accessible accounts, and the pinned deployment account.", "void cloudflare status"),
1778
- logout: basicSubcommand("Remove the local browser session.", "void cloudflare logout")
1779
- }
1780
- },
1781
- platform: {
1782
- summary: "Install Void platforms and administer users, projects, access, and operations. Use `void connect` to join one.",
1783
- usage: ["void platform <command>"],
1784
- commands: [
1785
- ...operatorGroups.map((group) => ({
1786
- command: `${group} <command>`,
1787
- description: `Administer platform ${group}`
1788
- })),
1789
- {
1790
- command: "list",
1791
- description: "List installed and connected platforms"
1792
- },
1793
- {
1794
- command: "use [id]",
1795
- description: "Select the default Void platform"
1796
- },
1797
- {
1798
- command: "status [id]",
1799
- description: "Inspect a platform and its health"
1800
- },
1801
- {
1802
- command: "discover",
1803
- description: "Recover installations from a Cloudflare account"
1804
- },
1805
- {
1806
- command: "domain set <domain>",
1807
- description: "Add an application domain to a test platform"
1808
- },
1809
- {
1810
- command: "install",
1811
- description: "Install a Void platform in Cloudflare"
1812
- },
1813
- {
1814
- command: "upgrade [id]",
1815
- description: "Upgrade an installed Void platform"
1816
- },
1817
- {
1818
- command: "rollback [id]",
1819
- description: "Restore a compatible earlier platform runtime"
1820
- },
1821
- {
1822
- command: "repair [id]",
1823
- description: "Reconcile an installed Void platform"
1824
- },
1825
- {
1826
- command: "disable [id]",
1827
- description: "Disable traffic without deleting data"
1828
- },
1829
- {
1830
- command: "enable [id]",
1831
- description: "Restore traffic for a disabled platform"
1832
- },
1833
- {
1834
- command: "uninstall [id]",
1835
- description: "Safely remove an installed Void platform"
1836
- }
1837
- ],
1838
- children: {
1839
- ...operatorHelpTree(),
1840
- domain: {
1841
- summary: "Add an application domain while keeping existing workers.dev URLs.",
1842
- usage: ["void platform domain set <domain> [options]"],
1843
- children: { set: {
1844
- summary: "Set up DNS and verify HTTPS before making an application domain canonical. Rerun to resume.",
1845
- usage: ["void platform domain set <domain> [options]"],
1846
- options: [
1847
- {
1848
- command: "--installation <id>",
1849
- description: "Choose an installed platform; otherwise select interactively"
1850
- },
1851
- {
1852
- command: "--zone <domain>",
1853
- description: "Containing Cloudflare zone; detected when it already exists"
1854
- },
1855
- {
1856
- command: "--dedicated-zone",
1857
- description: "Add catch-all routes for a zone dedicated to this platform"
1858
- },
1859
- {
1860
- command: "--plan",
1861
- description: "Preview without making changes"
1862
- },
1863
- {
1864
- command: "--yes",
1865
- description: "Apply without an interactive confirmation"
1866
- }
1867
- ]
1868
- } }
1869
- },
1870
- list: basicSubcommand("List installed and connected platforms.", "void platform list"),
1871
- use: basicSubcommand("Select the default Void platform.", "void platform use [id]"),
1872
- status: basicSubcommand("Inspect a platform and its health.", "void platform status [id]"),
1873
- discover: {
1874
- summary: "Recover owned platform installations from Cloudflare.",
1875
- usage: ["void platform discover [--account <id>] [--installation <id-or-name>]"],
1876
- options: [{
1877
- command: "--account <id>",
1878
- description: "Cloudflare account id"
1879
- }, {
1880
- command: "--installation <id-or-name>",
1881
- description: "Recover only one installation from a shared account"
1882
- }]
1883
- },
1884
- install: {
1885
- summary: "Install or resume a Void platform with a domain, or workers.dev for testing.",
1886
- usage: ["void platform install [options]"],
1887
- options: [
1888
- {
1889
- command: "--name <slug>",
1890
- description: "Installation name"
1891
- },
1892
- {
1893
- command: "--workers-dev",
1894
- description: "Use workers.dev for testing; add a domain later"
1895
- },
1896
- {
1897
- command: "--display-name <name>",
1898
- description: "Human-readable platform name"
1899
- },
1900
- {
1901
- command: "--account <id>",
1902
- description: "Cloudflare account id"
1903
- },
1904
- {
1905
- command: "--application-domain <domain>",
1906
- description: "Base domain for applications (zone apex is simplest)"
1907
- },
1908
- {
1909
- command: "--zone <domain>",
1910
- description: "Cloudflare zone containing the app domain"
1911
- },
1912
- {
1913
- command: "--dedicated-zone",
1914
- description: "Allow zone-wide application catch-all routes"
1915
- },
1916
- {
1917
- command: "--control-plane-domain <domain>",
1918
- description: "Optional API hostname"
1919
- },
1920
- {
1921
- command: "--plan",
1922
- description: "Print the exact plan without changing Cloudflare"
1923
- },
1924
- {
1925
- command: "--resume",
1926
- description: "Resume the matching local installation"
1927
- },
1928
- {
1929
- command: "--runtime <path>",
1930
- description: "Deploy an integrity-checked runtime directory instead of the package"
1931
- },
1932
- {
1933
- command: "--yes",
1934
- description: "Acknowledge changes without an interactive prompt"
1935
- }
1936
- ]
1937
- },
1938
- upgrade: lifecycleHelp("upgrade", "Upgrade an installed platform without deleting resources."),
1939
- rollback: {
1940
- summary: "Restore a compatible earlier runtime without reversing database migrations.",
1941
- usage: ["void platform rollback [id] --runtime <earlier-path> [--from-runtime <current-path>] [--plan] [--yes]"],
1942
- options: [
1943
- {
1944
- command: "--runtime <path>",
1945
- description: "Earlier compatible runtime to restore"
1946
- },
1947
- {
1948
- command: "--from-runtime <path>",
1949
- description: "Current installed runtime when it differs from the packaged runtime"
1950
- },
1951
- {
1952
- command: "--plan",
1953
- description: "Print the exact plan without making changes"
1954
- },
1955
- {
1956
- command: "--yes",
1957
- description: "Acknowledge non-interactive Cloudflare changes"
1958
- }
1959
- ]
1960
- },
1961
- repair: lifecycleHelp("repair", "Reconcile drift using the installation ownership manifest."),
1962
- disable: lifecycleHelp("disable", "Disable platform traffic without deleting data."),
1963
- enable: lifecycleHelp("enable", "Restore configured traffic for a disabled platform."),
1964
- uninstall: {
1965
- summary: "Safely remove installer-owned platform resources.",
1966
- usage: ["void platform uninstall [id] [options]"],
1967
- options: [
1968
- {
1969
- command: "--plan",
1970
- description: "Print the exact deletion and retention plan"
1971
- },
1972
- {
1973
- command: "--yes",
1974
- description: "Acknowledge changes without an interactive prompt"
1975
- },
1976
- {
1977
- command: "--purge-data",
1978
- description: "Delete installer-owned data even when it contains apps"
1979
- },
1980
- {
1981
- command: "--keep-zone",
1982
- description: "Explicitly retain an installer-created zone"
1983
- }
1984
- ]
1985
- }
1986
- }
1987
- },
1988
- project: {
1989
- summary: "Manage Void projects and deployments.",
1990
- usage: ["void project <command>"],
1991
- commands: [
1992
- {
1993
- command: "status [name]",
1994
- description: "Show deployments for a project"
1995
- },
1996
- {
1997
- command: "link [name]",
1998
- description: "Link this directory to a project"
1999
- },
2000
- {
2001
- command: "list",
2002
- description: "List your projects"
2003
- },
2004
- {
2005
- command: "logs",
2006
- description: "Show runtime logs from the deployed project"
2007
- },
2008
- {
2009
- command: "requests",
2010
- description: "Show request-level traffic (status, method, timing)"
2011
- },
2012
- {
2013
- command: "rollback [deployId]",
2014
- description: "Roll back to a previous deployment"
2015
- },
2016
- {
2017
- command: "cancel [deployId]",
2018
- description: "Cancel an active deployment"
2019
- },
2020
- {
2021
- command: "delete [name]",
2022
- description: "Delete a project and all its resources"
2023
- },
2024
- {
2025
- command: "purge-cache",
2026
- description: "Purge all cached pages"
2027
- }
2028
- ],
2029
- children: {
2030
- status: basicSubcommand("Show deployments for a project.", "void project status [name]", "void project status my-app"),
2031
- link: basicSubcommand("Link this directory to a Void project.", "void project link [name]", "void project link my-app"),
2032
- list: basicSubcommand("List your projects.", "void project list"),
2033
- logs: {
2034
- summary: "Show runtime logs from the deployed project.",
2035
- usage: ["void project logs [options]"],
2036
- options: [
2037
- {
2038
- command: "--filter <text>",
2039
- description: "Substring search over log messages"
2040
- },
2041
- {
2042
- command: "--level <level>",
2043
- description: "Filter by level: error|warn|info|log|debug|all (default: all)"
2044
- },
2045
- {
2046
- command: "--range <range>",
2047
- description: "Time range to query (default: 1h)"
2048
- },
2049
- {
2050
- command: "--deployment <id>",
2051
- description: "Limit logs to one deployment"
2052
- }
2053
- ],
2054
- examples: [
2055
- {
2056
- command: "void project logs --range 30m",
2057
- description: "Show recent project logs"
2058
- },
2059
- {
2060
- command: "void project logs --level error",
2061
- description: "Show only error-level entries and exceptions"
2062
- },
2063
- {
2064
- command: "void project logs --filter websocket",
2065
- description: "Substring search over log messages"
2066
- }
2067
- ]
2068
- },
2069
- requests: {
2070
- summary: "Show request-level traffic (status, method, type, timing). Surfaces 5xx that the edge router generated itself and requests to static/SPA projects that never invoke a worker — neither of which appears in `project logs`.",
2071
- usage: ["void project requests [options]"],
2072
- options: [{
2073
- command: "--status <filter>",
2074
- description: "Filter by status: a class like 5xx or an exact code like 500"
2075
- }, {
2076
- command: "--range <range>",
2077
- description: "Time range to query (default: 1h)"
2078
- }],
2079
- examples: [{
2080
- command: "void project requests --status 5xx",
2081
- description: "Show recent server-error responses, including edge-generated ones"
2082
- }, {
2083
- command: "void project requests --range 24h",
2084
- description: "Show all request traffic in the last day"
2085
- }]
2086
- },
2087
- rollback: basicSubcommand("Roll back to a previous deployment.", "void project rollback [deployId]"),
2088
- cancel: basicSubcommand("Cancel an active deployment.", "void project cancel [deployId]"),
2089
- delete: {
2090
- summary: "Delete a project and all its resources.",
2091
- usage: ["void project delete [name] [options]"],
2092
- options: [{
2093
- command: "--force, -f",
2094
- description: "Delete without confirmation"
2095
- }],
2096
- examples: [{
2097
- command: "void project delete my-app",
2098
- description: "Delete a project"
2099
- }]
2100
- },
2101
- "purge-cache": {
2102
- summary: "Purge all cached pages for a project.",
2103
- usage: ["void project purge-cache [options]"],
2104
- options: [{
2105
- command: "--project <name>",
2106
- description: "Project name override"
2107
- }]
2108
- }
2109
- }
2110
- },
2111
- secret: {
2112
- summary: "Manage production secrets for Void projects.",
2113
- usage: ["void secret <command>"],
2114
- commands: [
2115
- {
2116
- command: "list",
2117
- description: "List project secrets"
2118
- },
2119
- {
2120
- command: "put <name>",
2121
- description: "Set a project secret"
2122
- },
2123
- {
2124
- command: "delete <name>",
2125
- description: "Delete a project secret"
2126
- },
2127
- {
2128
- command: "sync <file>",
2129
- description: "Sync secrets from a dotenv file"
2130
- }
2131
- ],
2132
- children: {
2133
- list: withProjectOption("List project secrets.", "void secret list [options]"),
2134
- put: withProjectOption("Set a project secret.", "void secret put <name> [options]"),
2135
- delete: withProjectOption("Delete a project secret.", "void secret delete <name> [options]"),
2136
- sync: withProjectOption("Sync secrets from a dotenv file.", "void secret sync <file> [options]")
2137
- }
2138
- },
2139
- domain: {
2140
- summary: "Configure custom domains for Void projects.",
2141
- usage: ["void domain <command>"],
2142
- commands: [
2143
- {
2144
- command: "add <hostname>",
2145
- description: "Add a custom domain"
2146
- },
2147
- {
2148
- command: "delete <hostname>",
2149
- description: "Remove a custom domain"
2150
- },
2151
- {
2152
- command: "list",
2153
- description: "List custom domains and status"
2154
- },
2155
- {
2156
- command: "status <hostname>",
2157
- description: "Check domain verification status"
2158
- }
2159
- ],
2160
- children: {
2161
- add: withProjectOption("Add a custom domain.", "void domain add <hostname> [options]"),
2162
- delete: withProjectOption("Remove a custom domain.", "void domain delete <hostname> [options]"),
2163
- list: withProjectOption("List custom domains and status.", "void domain list [options]"),
2164
- status: {
2165
- summary: "Check domain verification status. Prints a rolled-up state with a one-line diagnostic.",
2166
- usage: ["void domain status <hostname> [options]"],
2167
- options: [{
2168
- command: "--project <name>",
2169
- description: "Project name override"
2170
- }, {
2171
- command: "--verbose",
2172
- description: "Also print the raw multi-line status breakdown"
2173
- }]
2174
- }
2175
- }
2176
- },
2177
- email: {
2178
- summary: "Inspect email activity and manage verified recipients.",
2179
- usage: ["void email <command>"],
2180
- commands: [
2181
- {
2182
- command: "usage",
2183
- description: "Show monthly outbound/inbound counts and quota"
2184
- },
2185
- {
2186
- command: "logs",
2187
- description: "Show recent email delivery activity"
2188
- },
2189
- {
2190
- command: "destinations",
2191
- description: "List verified recipient addresses"
2192
- },
2193
- {
2194
- command: "allow <address>",
2195
- description: "Add a recipient and trigger CF verification"
2196
- },
2197
- {
2198
- command: "disallow <address>",
2199
- description: "Remove a recipient from the allowlist"
2200
- },
2201
- {
2202
- command: "status --platform cloudflare",
2203
- description: "Show the email setup state and address map on your own account"
2204
- },
2205
- {
2206
- command: "setup --platform cloudflare",
2207
- description: "Set email up on your own Cloudflare account (one confirmation)"
2208
- }
2209
- ],
2210
- children: {
2211
- usage: withProjectOption("Show monthly outbound/inbound counts and quota.", "void email usage [options]"),
2212
- logs: {
2213
- summary: "Show recent email delivery activity.",
2214
- usage: ["void email logs [options]"],
2215
- options: [{
2216
- command: "--project <name>",
2217
- description: "Project name override"
2218
- }, {
2219
- command: "--limit <n>",
2220
- description: "How many entries to show (positive integer)"
2221
- }]
2222
- },
2223
- destinations: withProjectOption("List verified recipient addresses.", "void email destinations [options]"),
2224
- allow: withProjectOption("Add a recipient and trigger a Cloudflare verification email.", "void email allow <address> [options]"),
2225
- disallow: withProjectOption("Remove a recipient from the verified allowlist.", "void email disallow <address> [options]"),
2226
- status: {
2227
- summary: "Show the email setup state and address map. Exit 1 when not ready.",
2228
- usage: ["void email status --platform cloudflare"],
2229
- options: [{
2230
- command: "--platform cloudflare",
2231
- description: "Your own Cloudflare account (the only platform with a status today)"
2232
- }]
2233
- },
2234
- setup: {
2235
- summary: "Set email up on your own Cloudflare account: enable Email Routing, onboard Email Sending, write send_email + addresses into wrangler.jsonc. Asks once before changing the account.",
2236
- usage: ["void email setup --platform cloudflare"],
2237
- options: [{
2238
- command: "--platform cloudflare",
2239
- description: "Your own Cloudflare account (required; the Void platform needs no setup)"
2240
- }]
2241
- }
2242
- }
2243
- },
2244
- github: {
2245
- summary: "Connect a GitHub repository for automatic deploys.",
2246
- usage: ["void github <command>"],
2247
- commands: [
2248
- {
2249
- command: "install",
2250
- description: "Open the GitHub App install page in your browser"
2251
- },
2252
- {
2253
- command: "installations",
2254
- description: "List your GitHub App installations"
2255
- },
2256
- {
2257
- command: "connect [project]",
2258
- description: "Connect a repository to a project for auto-deploy"
2259
- },
2260
- {
2261
- command: "update [project]",
2262
- description: "Update an existing connection's branch, build executor, or deploy workflow"
2263
- },
2264
- {
2265
- command: "status [project]",
2266
- description: "Show a project's GitHub connection (repo, branch, executor, workflow)"
2267
- },
2268
- {
2269
- command: "disconnect [project]",
2270
- description: "Disconnect a project from its GitHub repository"
2271
- },
2272
- {
2273
- command: "link",
2274
- description: "Link your GitHub account to Void"
2275
- },
2276
- {
2277
- command: "join",
2278
- description: "Join your organization's shared GitHub App installations"
2279
- }
2280
- ],
2281
- children: {
2282
- install: basicSubcommand("Open the GitHub App install page in your browser.", "void github install"),
2283
- installations: basicSubcommand("List your GitHub App installations.", "void github installations"),
2284
- link: basicSubcommand("Link your GitHub account to Void.", "void github link"),
2285
- join: basicSubcommand("Join your organization's shared GitHub App installations.", "void github join"),
2286
- connect: {
2287
- summary: "Connect a repository to a project for automatic deploys.",
2288
- usage: ["void github connect [project] [options]"],
2289
- options: [
2290
- {
2291
- command: "--project <name>",
2292
- description: "Project name (alias for positional)"
2293
- },
2294
- {
2295
- command: "--installation <id>",
2296
- description: "GitHub App installation ID"
2297
- },
2298
- {
2299
- command: "--repo <owner/repo>",
2300
- description: "Repository full name (owner/repo)"
2301
- },
2302
- {
2303
- command: "--branch <name>",
2304
- description: "Branch to deploy from (required in non-interactive mode)"
2305
- },
2306
- {
2307
- command: "--executor <type>",
2308
- description: "Build executor: container (default) | github_actions; prompted interactively when omitted"
2309
- },
2310
- {
2311
- command: "--workflow <path>",
2312
- description: "Authorized deploy workflow file (default .github/workflows/void-deploy.yml); prompted interactively when using github_actions"
2313
- }
2314
- ],
2315
- examples: [
2316
- {
2317
- command: "void github connect [project]",
2318
- description: "Connect a repository to a project"
2319
- },
2320
- {
2321
- command: "void github connect my-app --installation 42 --repo owner/repo --branch main",
2322
- description: "Non-interactive connect (all flags explicit)"
2323
- },
2324
- {
2325
- command: "void github connect my-app --repo owner/repo --branch main --executor github_actions",
2326
- description: "Non-interactive connect using the GitHub Actions executor"
2327
- }
2328
- ]
2329
- },
2330
- update: {
2331
- summary: "Update an existing connection's branch, build executor, or deploy workflow.",
2332
- usage: ["void github update [project] [options]"],
2333
- options: [
2334
- {
2335
- command: "--project <name>",
2336
- description: "Project name (alias for positional)"
2337
- },
2338
- {
2339
- command: "--branch <name>",
2340
- description: "New branch to deploy from"
2341
- },
2342
- {
2343
- command: "--executor <type>",
2344
- description: "New build executor: container | github_actions; prompted interactively when omitted"
2345
- },
2346
- {
2347
- command: "--workflow <path>",
2348
- description: "New authorized deploy workflow file under .github/workflows/; prompted interactively when using github_actions"
2349
- }
2350
- ],
2351
- examples: [{
2352
- command: "void github update my-app --executor github_actions",
2353
- description: "Switch an existing connection to the GitHub Actions executor"
2354
- }, {
2355
- command: "void github update my-app --branch release",
2356
- description: "Change the deploy branch"
2357
- }]
2358
- },
2359
- status: {
2360
- summary: "Show a project's GitHub connection settings.",
2361
- usage: ["void github status [project] [options]"],
2362
- options: [{
2363
- command: "--project <name>",
2364
- description: "Project name (alias for positional)"
2365
- }],
2366
- examples: [{
2367
- command: "void github status",
2368
- description: "Show the linked project's connection"
2369
- }, {
2370
- command: "void github status my-app",
2371
- description: "Show a specific project connection"
2372
- }]
2373
- },
2374
- disconnect: {
2375
- summary: "Disconnect a project from its GitHub repository.",
2376
- usage: ["void github disconnect [project] [options]"],
2377
- options: [{
2378
- command: "--project <name>",
2379
- description: "Project name (alias for positional)"
2380
- }, {
2381
- command: "--yes",
2382
- description: "Skip the confirmation prompt (required in non-interactive shells)"
2383
- }],
2384
- examples: [{
2385
- command: "void github disconnect",
2386
- description: "Disconnect the linked project's repository"
2387
- }, {
2388
- command: "void github disconnect my-app --yes",
2389
- description: "Disconnect a specific project without confirmation"
2390
- }]
2391
- }
2392
- }
2393
- },
2394
- build: {
2395
- summary: "Inspect remote builds.",
2396
- usage: ["void build <command>"],
2397
- commands: [{
2398
- command: "logs [build]",
2399
- description: "Stream, tail, or download logs for a build"
2400
- }],
2401
- children: { logs: {
2402
- summary: "Stream, tail, or download logs for a build.",
2403
- usage: ["void build logs [build] [options]"],
2404
- options: [
2405
- {
2406
- command: "--follow, -f",
2407
- description: "Live-tail until the build finishes"
2408
- },
2409
- {
2410
- command: "--output <file>, -o",
2411
- description: "Write logs to a file instead of stdout"
2412
- },
2413
- {
2414
- command: "--project <slug>, -p",
2415
- description: "Target project (else linked / VOID_PROJECT)"
2416
- }
2417
- ],
2418
- examples: [
2419
- {
2420
- command: "void build logs",
2421
- description: "Print the latest build's logs"
2422
- },
2423
- {
2424
- command: "void build logs -f",
2425
- description: "Follow the latest build until it finishes"
2426
- },
2427
- {
2428
- command: "void build logs bld_123 -o build.log",
2429
- description: "Download a build's logs to a file"
2430
- }
2431
- ]
2432
- } }
2433
- },
2434
- db: {
2435
- summary: "Manage local and remote database state.",
2436
- usage: ["void db <command>"],
2437
- commands: [
2438
- {
2439
- command: "push",
2440
- description: "Push schema to local D1 without migration files"
2441
- },
2442
- {
2443
- command: "generate",
2444
- description: "Generate SQL migrations from schema changes"
2445
- },
2446
- {
2447
- command: "status",
2448
- description: "Show migration status (local and remote)"
2449
- },
2450
- {
2451
- command: "reset",
2452
- description: "Reset local DB and re-run migrations"
2453
- },
2454
- {
2455
- command: "seed",
2456
- description: "Reset local DB, run migrations, and seed"
2457
- },
2458
- {
2459
- command: "execute <sql>",
2460
- description: "Run SQL against the database (--remote for deployed)"
2461
- },
2462
- {
2463
- command: "migrate",
2464
- description: "Apply pending migrations"
2465
- },
2466
- {
2467
- command: "studio",
2468
- description: "Open Drizzle Studio (--remote for deployed PG)"
2469
- },
2470
- {
2471
- command: "export",
2472
- description: "Export local database as SQL"
2473
- },
2474
- {
2475
- command: "connect",
2476
- description: "Connect or provision an external database"
2477
- },
2478
- {
2479
- command: "set-url",
2480
- description: "Update the external database URL"
2481
- },
2482
- {
2483
- command: "rename-migrations",
2484
- description: "Rename migrations to timestamp format"
2485
- }
2486
- ],
2487
- children: {
2488
- push: basicSubcommand("Push schema to local D1 without migration files.", "void db push"),
2489
- generate: basicSubcommand("Generate SQL migrations from schema changes.", "void db generate"),
2490
- status: basicSubcommand("Show migration status for local and remote DBs.", "void db status"),
2491
- reset: basicSubcommand("Reset local DB and re-run migrations.", "void db reset"),
2492
- seed: {
2493
- summary: "Reset local DB, run migrations, and seed data.",
2494
- usage: ["void db seed [options]"],
2495
- options: [{
2496
- command: "--file <path>",
2497
- description: "Seed from a specific file"
2498
- }]
2499
- },
2500
- execute: {
2501
- summary: "Run SQL against the database (local by default; use --remote for the deployed database).",
2502
- usage: ["void db execute <sql>", "void db execute --file <path>"],
2503
- options: [{
2504
- command: "--file <path>",
2505
- description: "Run SQL from a file instead"
2506
- }, {
2507
- command: "--remote",
2508
- description: "Run against the deployed database (D1 via proxy; PostgreSQL via direct connection)"
2509
- }],
2510
- examples: [
2511
- {
2512
- command: "void db execute \"select * from users\"",
2513
- description: "Run inline SQL"
2514
- },
2515
- {
2516
- command: "void db execute --file query.sql",
2517
- description: "Run SQL from a file"
2518
- },
2519
- {
2520
- command: "void db execute --remote \"select count(*) from users\"",
2521
- description: "Query the deployed database"
2522
- }
2523
- ]
2524
- },
2525
- migrate: {
2526
- summary: "Apply pending migrations.",
2527
- usage: ["void db migrate [options]"],
2528
- options: [{
2529
- command: "--remote",
2530
- description: "Apply to the remote database"
2531
- }]
2532
- },
2533
- studio: {
2534
- summary: "Open Drizzle Studio against the database (local by default; use --remote for the deployed PostgreSQL database; D1 remote not yet supported).",
2535
- usage: ["void db studio [options]"],
2536
- options: [{
2537
- command: "--remote",
2538
- description: "Open Studio against the deployed PostgreSQL database (D1 remote not yet supported)"
2539
- }],
2540
- examples: [{
2541
- command: "void db studio",
2542
- description: "Open Studio for the local database"
2543
- }, {
2544
- command: "void db studio --remote",
2545
- description: "Open Studio against the deployed PostgreSQL database"
2546
- }]
2547
- },
2548
- export: {
2549
- summary: "Export local database contents as SQL.",
2550
- usage: ["void db export [options]"],
2551
- options: [
2552
- {
2553
- command: "--output <path>",
2554
- description: "Export to a specific file path"
2555
- },
2556
- {
2557
- command: "--no-data",
2558
- description: "Schema only (no INSERT statements)"
2559
- },
2560
- {
2561
- command: "--no-schema",
2562
- description: "Data only (no CREATE TABLE)"
2563
- },
2564
- {
2565
- command: "--table <name>",
2566
- description: "Export a single table"
2567
- }
2568
- ]
2569
- },
2570
- connect: {
2571
- summary: "Connect or provision an external PostgreSQL/MySQL database.",
2572
- usage: ["void db connect [connection-string]", "void db connect --provider <name-or-module> [options]"],
2573
- options: [
2574
- {
2575
- command: "--provider <name-or-module>",
2576
- description: "Provision with an adapter"
2577
- },
2578
- {
2579
- command: "--name <name>",
2580
- description: "Provider project/database name"
2581
- },
2582
- {
2583
- command: "--region <region>",
2584
- description: "Provider region identifier"
2585
- },
2586
- {
2587
- command: "--local-only",
2588
- description: "Do not sync a linked Void project"
2589
- }
2590
- ],
2591
- examples: [{
2592
- command: "void db connect \"postgresql://user:pass@host/db\"",
2593
- description: "Connect an existing database"
2594
- }, {
2595
- command: "void db connect --provider neon --name my-app",
2596
- description: "Provision a Neon database using NEON_API_KEY"
2597
- }]
2598
- },
2599
- "set-url": basicSubcommand("Update the external database connection string for deployment.", "void db set-url"),
2600
- "rename-migrations": basicSubcommand("Rename migrations to timestamp-based format.", "void db rename-migrations")
2601
- }
2602
- },
2603
- env: {
2604
- summary: "Validate env.ts and generate environment types.",
2605
- usage: ["void env <command>"],
2606
- commands: [{
2607
- command: "check",
2608
- description: "Validate local .env or remote secret names"
2609
- }, {
2610
- command: "types",
2611
- description: "Regenerate .void/env.d.ts from env.ts"
2612
- }],
2613
- children: {
2614
- check: {
2615
- summary: "Validate local .env or required remote secret names.",
2616
- usage: ["void env check [options]"],
2617
- options: [{
2618
- command: "--remote",
2619
- description: "Check required server keys as remote secrets"
2620
- }]
2621
- },
2622
- types: basicSubcommand("Regenerate .void/env.d.ts from env.ts.", "void env types")
2623
- }
2624
- },
2625
- gen: {
2626
- summary: "Scaffold routes, models, background jobs, and framework entries.",
2627
- usage: ["void gen <command>"],
2628
- commands: [
2629
- {
2630
- command: "model <name> [cols...]",
2631
- description: "Generate migration, CRUD routes, and DB types"
2632
- },
2633
- {
2634
- command: "migration <name>",
2635
- description: "Generate an empty migration file"
2636
- },
2637
- {
2638
- command: "route <path>",
2639
- description: "Generate a route with handler exports"
2640
- },
2641
- {
2642
- command: "middleware <name>",
2643
- description: "Generate a numbered middleware file"
2644
- },
2645
- {
2646
- command: "ssr",
2647
- description: "Generate SSR entry, client, and App component"
2648
- },
2649
- {
2650
- command: "cron <name>",
2651
- description: "Generate a cron job file"
2652
- },
2653
- {
2654
- command: "queue <name>",
2655
- description: "Generate a queue consumer file"
2656
- }
2657
- ],
2658
- children: {
2659
- model: {
2660
- summary: "Generate a migration, CRUD routes, and database types.",
2661
- usage: ["void gen model <name> [columns...]"],
2662
- examples: [{
2663
- command: "void gen model post title:string body:text",
2664
- description: "Create a model"
2665
- }]
2666
- },
2667
- migration: basicSubcommand("Generate an empty migration file.", "void gen migration <name>"),
2668
- route: {
2669
- summary: "Generate a route with handler exports.",
2670
- usage: ["void gen route <path> [options]"],
2671
- options: [{
2672
- command: "--methods <list>",
2673
- description: "Comma-separated methods (default: get)"
2674
- }],
2675
- examples: [{
2676
- command: "void gen route api/posts --methods get,post",
2677
- description: "Create a route"
2678
- }]
2679
- },
2680
- middleware: basicSubcommand("Generate a numbered middleware file.", "void gen middleware <name>"),
2681
- ssr: {
2682
- summary: "Generate SSR entry, client, and App component.",
2683
- usage: ["void gen ssr [options]"],
2684
- options: [
2685
- {
2686
- command: "--react",
2687
- description: "Use React"
2688
- },
2689
- {
2690
- command: "--vue",
2691
- description: "Use Vue"
2692
- },
2693
- {
2694
- command: "--svelte",
2695
- description: "Use Svelte"
2696
- },
2697
- {
2698
- command: "--solid",
2699
- description: "Use Solid"
2700
- }
2701
- ]
2702
- },
2703
- cron: basicSubcommand("Generate a cron job file.", "void gen cron <name>"),
2704
- queue: basicSubcommand("Generate a queue consumer file.", "void gen queue <name>")
2705
- }
2706
- },
2707
- prepare: {
2708
- summary: "Generate .void artifacts without starting Vite.",
2709
- usage: ["void prepare"],
2710
- examples: [{
2711
- command: "void prepare",
2712
- description: "Refresh generated project types"
2713
- }]
2714
- }
2715
- };
2716
- function basicSubcommand(summary, usage, example) {
2717
- return {
2718
- summary,
2719
- usage: [usage],
2720
- examples: example ? [{
2721
- command: example,
2722
- description: summary
2723
- }] : void 0
2724
- };
2725
- }
2726
- function withProjectOption(summary, usage) {
2727
- return {
2728
- summary,
2729
- usage: [usage],
2730
- options: [{
2731
- command: "--project <name>",
2732
- description: "Project name override"
2733
- }]
2734
- };
2735
- }
2736
- function lifecycleHelp(command, summary) {
2737
- return {
2738
- summary,
2739
- usage: [`void platform ${command} [id] [--plan]${command === "disable" ? "" : " [--runtime <path>]"} [--yes]`],
2740
- options: [
2741
- {
2742
- command: "--plan",
2743
- description: "Print the exact plan without making changes"
2744
- },
2745
- ...command === "disable" ? [] : [{
2746
- command: "--runtime <path>",
2747
- description: "Deploy an integrity-checked runtime directory instead of the package"
2748
- }],
2749
- {
2750
- command: "--yes",
2751
- description: "Acknowledge non-interactive Cloudflare changes"
2752
- }
2753
- ]
2754
- };
2755
- }
2756
- function printGlobalHelp() {
2757
- printVoidHeader();
2758
- note([
2759
- globalCommandLine(["init"], "void init"),
2760
- ...globalOptionLines(["init"]),
2761
- "",
2762
- globalCommandLine(["connect"], "void connect [url]"),
2763
- ...globalOptionLines(["connect"]),
2764
- "",
2765
- globalCommandLine(["deploy"], "void deploy"),
2766
- ...globalOptionLines(["deploy"]),
2767
- "",
2768
- ...globalCommandGroup("auth"),
2769
- "",
2770
- ...globalCommandGroup("platform"),
2771
- "",
2772
- ...globalCommandGroup("project"),
2773
- "",
2774
- categoryTitle("secrets"),
2775
- ...globalCommandLines("secret"),
2776
- "",
2777
- ...globalCommandGroup("domain"),
2778
- "",
2779
- ...globalCommandGroup("email"),
2780
- "",
2781
- ...globalCommandGroup("github"),
2782
- "",
2783
- ...globalCommandGroup("build"),
2784
- "",
2785
- categoryTitle("database"),
2786
- ...globalCommandLines("db", {
2787
- execute: "execute <sql>",
2788
- export: "export [--output <path>]",
2789
- seed: "seed [--file <path>]"
2790
- }),
2791
- ...globalOptionLines(["db", "execute"]),
2792
- ...globalOptionLines(["db", "export"]),
2793
- "",
2794
- ...globalCommandGroup("env"),
2795
- ...globalOptionLines(["env", "check"]),
2796
- "",
2797
- categoryTitle("codegen"),
2798
- globalCommandLine(["prepare"], "void prepare"),
2799
- ...globalCommandLines("gen", { route: "route <path> [--methods]" }),
2800
- "",
2801
- categoryTitle("agent"),
2802
- helpLine("void init --agents", requiredOption(["init"], "--agents").description),
2803
- "",
2804
- categoryTitle("help"),
2805
- helpLine("void help <command>", "Show command-specific help"),
2806
- helpLine("void <command> --help", "Show command-specific help")
2807
- ].join("\n"), sectionTitle("Commands"));
2808
- note([helpLine("VOID_TOKEN", "Auth token (or use `void auth login`)"), helpLine("VOID_PROJECT", "Deploy target project name override")].join("\n"), sectionTitle("Environment"));
2809
- outro("https://void.cloud");
2810
- }
2811
- function printVoidHeader() {
2812
- const version = JSON.parse(readFileSync(join(getPackageDir(), "package.json"), "utf-8")).version;
2813
- const art = asciiArts[Math.floor(Math.random() * asciiArts.length)];
2814
- console.log(import_picocolors.default.blue(`${art} v${version}\n`));
2815
- }
2816
- function getHelpPath(args) {
2817
- if (args.length === 0) return null;
2818
- const command = args[0];
2819
- if (command === "help") return args.slice(1);
2820
- if (command === "--help" || command === "-h") return [];
2821
- if (args[1] === "help") return [command, ...args.slice(2)];
2822
- const helpIndex = args.findIndex((arg) => arg === "--help" || arg === "-h");
2823
- if (helpIndex === -1) return null;
2824
- return getKnownHelpPrefix(args.slice(0, helpIndex));
2825
- }
2826
- function printHelpPage(path) {
2827
- const page = findHelpPage(path);
2828
- if (!page) throw new Error(`Unknown help topic: ${formatHelpPath(path)}`);
2829
- const lines = [
2830
- page.summary,
2831
- "",
2832
- categoryTitle("Usage"),
2833
- ...page.usage.map((usage) => ` ${usage}`)
2834
- ];
2835
- appendHelpSection(lines, "Commands", page.commands);
2836
- appendHelpSection(lines, "Options", page.options);
2837
- appendHelpSection(lines, "Examples", page.examples);
2838
- if (page.commands) lines.push("", `Run \`void help ${path.join(" ")} <command>\` for command-specific help.`);
2839
- note(lines.join("\n"), sectionTitle(`void ${path.join(" ")}`));
2840
- }
2841
- function appendHelpSection(lines, title, entries) {
2842
- if (!entries || entries.length === 0) return;
2843
- lines.push("", categoryTitle(title));
2844
- for (const entry of entries) lines.push(helpLine(entry.command, entry.description));
2845
- }
2846
- function globalCommandGroup(group) {
2847
- return [categoryTitle(group), ...globalCommandLines(group)];
2848
- }
2849
- function globalCommandLines(group, commandOverrides) {
2850
- return (requiredPage([group]).commands ?? []).map((command) => helpLine(`void ${group} ${commandOverrides?.[command.command.split(" ")[0]] ?? command.command}`, command.description));
2851
- }
2852
- function globalCommandLine(path, command) {
2853
- return helpLine(command, formatGlobalDescription(requiredPage(path).summary));
2854
- }
2855
- function globalOptionLines(path) {
2856
- return (requiredPage(path).options ?? []).map((option) => helpLine(` ${option.command}`, option.description));
2857
- }
2858
- function requiredOption(path, command) {
2859
- const option = requiredPage(path).options?.find((entry) => entry.command === command);
2860
- if (!option) throw new Error(`Missing help option: ${formatHelpPath(path)} ${command}`);
2861
- return option;
2862
- }
2863
- function requiredPage(path) {
2864
- const page = findHelpPage(path);
2865
- if (!page) throw new Error(`Missing help page: ${formatHelpPath(path)}`);
2866
- return page;
2867
- }
2868
- function formatGlobalDescription(summary) {
2869
- return summary.replace(/\.$/, "");
2870
- }
2871
- function findHelpPage(path) {
2872
- let tree = HELP_TREE;
2873
- let page = null;
2874
- for (const part of path) {
2875
- const node = tree?.[part];
2876
- if (!node) return null;
2877
- page = node;
2878
- tree = node.children;
2879
- }
2880
- return page;
2881
- }
2882
- function getKnownHelpPrefix(args) {
2883
- let tree = HELP_TREE;
2884
- const path = [];
2885
- for (const arg of args) {
2886
- if (arg.startsWith("-")) break;
2887
- const node = tree?.[arg];
2888
- if (!node) {
2889
- if (path.length === 0) return [arg];
2890
- if (tree) return [...path, arg];
2891
- break;
2892
- }
2893
- path.push(arg);
2894
- tree = node.children;
2895
- }
2896
- return path;
2897
- }
2898
- function formatHelpPath(path) {
2899
- return path.length === 0 ? "help" : path.join(" ");
2900
1220
  }
2901
1221
  //#endregion
2902
1222
  //#region src/cli/platform-args.ts
2903
1223
  const platformSubcommands = [
1224
+ {
1225
+ name: "config",
1226
+ hint: "Configure authentication",
1227
+ description: "Configure login methods, company protection, or provider recovery"
1228
+ },
2904
1229
  ...operatorGroups.map((name) => ({
2905
1230
  name,
2906
- hint: "Operator commands",
1231
+ hint: name === "email" ? "Domains, policy, limits, outcomes, and recovery" : "Operator commands",
2907
1232
  description: `Administer platform ${name}`
2908
1233
  })),
2909
1234
  {
@@ -3019,6 +1344,7 @@ function parsePlatformArgs(args) {
3019
1344
  "--timeout"
3020
1345
  ].includes(args[0] ?? "")) return parseOperatorArgs(args);
3021
1346
  const [subcommand, ...rest] = args;
1347
+ if (subcommand === "config") return parsePlatformConfigArgs(rest);
3022
1348
  if (subcommand === "domain") {
3023
1349
  if (rest.length === 0) return {
3024
1350
  subcommand,
@@ -3139,6 +1465,7 @@ function parsePlatformArgs(args) {
3139
1465
  let yes = false;
3140
1466
  const valueFlags = /* @__PURE__ */ new Set([
3141
1467
  "--account",
1468
+ "--auth-config",
3142
1469
  "--application-domain",
3143
1470
  "--control-plane-domain",
3144
1471
  "--display-name",
@@ -3164,6 +1491,7 @@ function parsePlatformArgs(args) {
3164
1491
  return {
3165
1492
  subcommand,
3166
1493
  ...workersDev ? { workersDev: true } : {},
1494
+ ...values.has("--auth-config") ? { authConfig: values.get("--auth-config") } : {},
3167
1495
  account: values.get("--account") ?? "",
3168
1496
  applicationDomain: values.get("--application-domain") ?? "",
3169
1497
  controlPlaneDomain: values.get("--control-plane-domain") ?? "",
@@ -3177,7 +1505,7 @@ function parsePlatformArgs(args) {
3177
1505
  zone: values.get("--zone") ?? ""
3178
1506
  };
3179
1507
  }
3180
- throw new Error("platform: Usage: void platform <auth|user|project|deployment|build|signup|invitation|system|worker|list|use|status|discover|install|domain|upgrade|rollback|repair|disable|enable|uninstall>.");
1508
+ throw new Error("platform: Usage: void platform <config|auth|user|project|deployment|build|signup|invitation|email|system|worker|list|use|status|discover|install|domain|upgrade|rollback|repair|disable|enable|uninstall>.");
3181
1509
  }
3182
1510
  //#endregion
3183
1511
  //#region src/cli/prepare-args.ts
@@ -3202,6 +1530,11 @@ const LOG_LEVELS = /* @__PURE__ */ new Set([
3202
1530
  "all"
3203
1531
  ]);
3204
1532
  const STATUS_FILTER = /^([1-5]xx|[1-5]\d{2}|all)$/;
1533
+ const TEAM_ROLES = /* @__PURE__ */ new Set([
1534
+ "reader",
1535
+ "collaborator",
1536
+ "admin"
1537
+ ]);
3205
1538
  const projectSubcommands = [
3206
1539
  {
3207
1540
  name: "status",
@@ -3217,6 +1550,16 @@ const projectSubcommands = [
3217
1550
  name: "list",
3218
1551
  description: "List your projects"
3219
1552
  },
1553
+ {
1554
+ name: "token",
1555
+ arg: "<create|list|renew|revoke>",
1556
+ description: "Manage project-scoped CI deploy credentials"
1557
+ },
1558
+ {
1559
+ name: "team",
1560
+ hint: "list | invite | invitations | role | remove | revoke | leave | pending | accept | decline",
1561
+ description: "Manage project members and invitations"
1562
+ },
3220
1563
  {
3221
1564
  name: "logs",
3222
1565
  description: "Show runtime logs from the deployed project"
@@ -3269,6 +1612,46 @@ function parseProjectArgs(args) {
3269
1612
  if (rest.length > 0) throw new Error(`project: Unexpected argument '${rest[0]}'.`);
3270
1613
  return { subcommand };
3271
1614
  }
1615
+ if (subcommand === "token") {
1616
+ const action = rest.shift();
1617
+ if (!action || ![
1618
+ "create",
1619
+ "list",
1620
+ "renew",
1621
+ "revoke"
1622
+ ].includes(action)) throw new Error("project: Usage: void project token <create|list|renew|revoke>.");
1623
+ let credentialId = "";
1624
+ let projectSlug = "";
1625
+ let name = "";
1626
+ let expiresInDays;
1627
+ let json = false;
1628
+ for (let index = 0; index < rest.length; index++) {
1629
+ const value = rest[index];
1630
+ if (value === "--project" && rest[index + 1]) projectSlug = rest[++index];
1631
+ else if (value === "--name" && rest[index + 1]) name = rest[++index];
1632
+ else if (value === "--expires-in" && rest[index + 1]) {
1633
+ const parsed = Number(rest[++index]);
1634
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 90) throw new Error("project: --expires-in must be an integer from 1 to 90 days.");
1635
+ expiresInDays = parsed;
1636
+ } else if (value === "--json") json = true;
1637
+ else if (value.startsWith("-")) throw new Error(`project: Unknown flag '${value}'.`);
1638
+ else if ((action === "renew" || action === "revoke") && !credentialId) credentialId = value;
1639
+ else throw new Error(`project: Unexpected argument '${value}'.`);
1640
+ }
1641
+ if ((action === "renew" || action === "revoke") && !credentialId) throw new Error(`project: ${action} requires a credential ID.`);
1642
+ if (action !== "create" && name) throw new Error("project: --name is only valid with token create.");
1643
+ if (action !== "create" && action !== "renew" && expiresInDays !== void 0) throw new Error("project: --expires-in is only valid with token create or token renew.");
1644
+ return {
1645
+ subcommand,
1646
+ action,
1647
+ credentialId,
1648
+ projectSlug,
1649
+ name,
1650
+ expiresInDays,
1651
+ json
1652
+ };
1653
+ }
1654
+ if (subcommand === "team") return parseTeamArgs(rest);
3272
1655
  if (subcommand === "logs") {
3273
1656
  let filter = "";
3274
1657
  let range = "1h";
@@ -3345,7 +1728,105 @@ function parseProjectArgs(args) {
3345
1728
  projectSlug
3346
1729
  };
3347
1730
  }
3348
- throw new Error("project: Usage: void project <status|link|list|logs|requests|rollback|cancel|delete|purge-cache>.");
1731
+ throw new Error("project: Usage: void project <status|link|list|team|token|logs|requests|rollback|cancel|delete|purge-cache>.");
1732
+ }
1733
+ function parseTeamArgs(rest) {
1734
+ const [action, ...args] = rest;
1735
+ const usage = "project: Usage: void project team <list|invite|invitations|role|remove|revoke|leave|pending|accept|decline>.";
1736
+ if (!action) return {
1737
+ subcommand: "team",
1738
+ action: ""
1739
+ };
1740
+ const positionals = [];
1741
+ let projectSlug = "";
1742
+ let role = "";
1743
+ for (let index = 0; index < args.length; index++) {
1744
+ const arg = args[index];
1745
+ if (arg === "--project" || arg === "--role") {
1746
+ const value = args[++index];
1747
+ if (!value || value.startsWith("-")) throw new Error(`project: ${arg} requires a value.`);
1748
+ if (arg === "--project") {
1749
+ if (projectSlug) throw new Error("project: Duplicate flag '--project'.");
1750
+ projectSlug = value;
1751
+ } else {
1752
+ if (role) throw new Error("project: Duplicate flag '--role'.");
1753
+ role = value;
1754
+ }
1755
+ } else if (arg.startsWith("-")) throw new Error(`project: Unknown flag '${arg}'.`);
1756
+ else positionals.push(arg);
1757
+ }
1758
+ if (role && !TEAM_ROLES.has(role)) throw new Error(`project: Invalid role '${role}'. Use one of reader|collaborator|admin.`);
1759
+ if ([
1760
+ "pending",
1761
+ "accept",
1762
+ "decline"
1763
+ ].includes(action) && projectSlug) throw new Error(`project: --project is not used with team ${action}.`);
1764
+ if (action === "list" || action === "invitations" || action === "leave") {
1765
+ if (positionals.length || role) throw new Error(`project: Usage: void project team ${action} [--project <slug>].`);
1766
+ return {
1767
+ subcommand: "team",
1768
+ action,
1769
+ projectSlug
1770
+ };
1771
+ }
1772
+ if (action === "invite") {
1773
+ if (positionals.length !== 1 || !role) throw new Error("project: Usage: void project team invite <registered-email> --role <reader|collaborator|admin> [--project <slug>].");
1774
+ const email = positionals[0];
1775
+ if (email.length > 320 || !/^[^\s@]+@[^\s@]+$/.test(email)) throw new Error("project: Invitee must be a registered email address.");
1776
+ return {
1777
+ subcommand: "team",
1778
+ action,
1779
+ email,
1780
+ role,
1781
+ projectSlug
1782
+ };
1783
+ }
1784
+ if (action === "role") {
1785
+ if (positionals.length !== 2 || role) throw new Error("project: Usage: void project team role <user-id> <reader|collaborator|admin> [--project <slug>].");
1786
+ const nextRole = positionals[1];
1787
+ if (!TEAM_ROLES.has(nextRole)) throw new Error(`project: Invalid role '${nextRole}'. Use one of reader|collaborator|admin.`);
1788
+ return {
1789
+ subcommand: "team",
1790
+ action,
1791
+ userId: positionals[0],
1792
+ role: nextRole,
1793
+ projectSlug
1794
+ };
1795
+ }
1796
+ if (action === "remove") {
1797
+ if (positionals.length !== 1 || role) throw new Error("project: Usage: void project team remove <user-id> [--project <slug>].");
1798
+ return {
1799
+ subcommand: "team",
1800
+ action,
1801
+ userId: positionals[0],
1802
+ projectSlug
1803
+ };
1804
+ }
1805
+ if (action === "revoke") {
1806
+ if (positionals.length !== 1 || role) throw new Error("project: Usage: void project team revoke <invitation-id> [--project <slug>].");
1807
+ return {
1808
+ subcommand: "team",
1809
+ action,
1810
+ invitationId: positionals[0],
1811
+ projectSlug
1812
+ };
1813
+ }
1814
+ if (action === "pending") {
1815
+ if (positionals.length || role) throw new Error("project: Usage: void project team pending.");
1816
+ return {
1817
+ subcommand: "team",
1818
+ action
1819
+ };
1820
+ }
1821
+ if (action === "accept" || action === "decline") {
1822
+ if (positionals.length !== 1 || role) throw new Error(`project: Usage: void project team ${action} <invitation-id>.`);
1823
+ return {
1824
+ subcommand: "team",
1825
+ action,
1826
+ invitationId: positionals[0]
1827
+ };
1828
+ }
1829
+ throw new Error(usage);
3349
1830
  }
3350
1831
  //#endregion
3351
1832
  //#region src/cli/secret-args.ts
@@ -3449,7 +1930,7 @@ async function runSubcommandGroup(command, subcommands, parse, run) {
3449
1930
  const hasNamedSubcommand = args.slice(1).some((arg) => subcommands.some(({ name }) => name === arg));
3450
1931
  try {
3451
1932
  if ((!sub || sub.startsWith("-") && !hasNamedSubcommand) && process.stdin.isTTY) {
3452
- const { promptSubcommand } = await import("../subcommand-prompt-WfySCQ7S.mjs");
1933
+ const { promptSubcommand } = await import("../subcommand-prompt-Bmyn5Rlc.mjs");
3453
1934
  const result = await promptSubcommand(command, subcommands);
3454
1935
  if (!result) process.exit(0);
3455
1936
  await run(root, parse(buildArgs(result, args.slice(1))));
@@ -3488,7 +1969,7 @@ if (helpPath) {
3488
1969
  log.error(formatCloudflareUserMessage(err instanceof Error ? err.message : String(err)));
3489
1970
  process.exit(1);
3490
1971
  }
3491
- import("../deploy-CcDoeBIf.mjs").then((n) => n.n).then(({ runDeploy }) => {
1972
+ import("../deploy-DTaWUS1S.mjs").then((n) => n.n).then(({ runDeploy }) => {
3492
1973
  runDeploy(root, {
3493
1974
  projectSlug: deployArgs.projectSlug,
3494
1975
  dir: deployArgs.dir,
@@ -3506,24 +1987,24 @@ if (helpPath) {
3506
1987
  });
3507
1988
  } else if (command === "connect") (async () => {
3508
1989
  const parsed = parseConnectArgs(args.slice(1));
3509
- const { runConnectCommand } = await import("../connect-D9Yr-T5f.mjs");
1990
+ const { runConnectCommand } = await import("../connect-Bfk31O_8.mjs");
3510
1991
  await runConnectCommand(process.cwd(), parsed);
3511
1992
  })().catch((err) => {
3512
1993
  log.error(formatCloudflareUserMessage(err instanceof Error ? err.message : String(err)));
3513
1994
  process.exit(1);
3514
1995
  });
3515
- else if (command === "auth") runSubcommandGroup("auth", authSubcommands, parseAuthArgs, (root, parsed) => import("../auth-cmd-CYVhSNFy.mjs").then(({ runAuthCommand }) => runAuthCommand(root, parsed)));
3516
- else if (command === "platform") runSubcommandGroup("platform", platformSubcommands, parsePlatformArgs, (root, parsed) => import("../platform-cmd-BEOVv1PN.mjs").then(({ runPlatformCommand }) => runPlatformCommand(root, parsed)));
3517
- else if (command === "cloudflare") runSubcommandGroup("cloudflare", cloudflareSubcommands, parseCloudflareArgs, (root, parsed) => import("../cloudflare-cmd-D3ME2GAe.mjs").then(({ runCloudflareCommand }) => runCloudflareCommand(root, parsed)));
3518
- else if (command === "project") runSubcommandGroup("project", projectSubcommands, parseProjectArgs, (root, parsed) => import("../project-cmd-DCpk1cDt.mjs").then(({ runProjectCommand }) => runProjectCommand(root, parsed)));
3519
- else if (command === "secret") runSubcommandGroup("secret", secretSubcommands, parseSecretArgs, (root, parsed) => import("../secret-BqTxGqki.mjs").then(({ runSecretCommand }) => runSecretCommand(root, parsed)));
3520
- else if (command === "domain") runSubcommandGroup("domain", domainSubcommands, parseDomainArgs, (root, parsed) => import("../domain-gu_iaHmn.mjs").then(({ runDomainCommand }) => runDomainCommand(root, parsed)));
3521
- else if (command === "email") runSubcommandGroup("email", emailSubcommands, parseEmailArgs, (root, parsed) => import("../email-r6DHJyAB.mjs").then(({ runEmailCommand }) => runEmailCommand(root, parsed)));
3522
- else if (command === "db") runSubcommandGroup("db", dbSubcommands, parseDbArgs, (root, parsed) => import("../db-BxoUWL0F.mjs").then(({ runDbCommand }) => runDbCommand(root, parsed)));
3523
- else if (command === "env") runSubcommandGroup("env", envSubcommands, parseEnvArgs, (root, parsed) => import("../env-DmU2To0C.mjs").then(({ runEnvCommand }) => runEnvCommand(root, parsed)));
3524
- else if (command === "gen") runSubcommandGroup("gen", genSubcommands, parseGenArgs, (root, parsed) => import("../gen-BKw6qHIg.mjs").then(({ runGenCommand }) => runGenCommand(root, parsed)));
3525
- else if (command === "github") runSubcommandGroup("github", githubSubcommands, parseGithubArgs, (root, parsed) => import("../github-cmd-DJS5Ab-H.mjs").then(({ runGithubCommand }) => runGithubCommand(root, parsed)));
3526
- else if (command === "build") runSubcommandGroup("build", buildSubcommands, parseBuildArgs, (root, parsed) => import("../build-cmd-CkVD1uOh.mjs").then(({ runBuildCommand }) => runBuildCommand(root, parsed)));
1996
+ else if (command === "auth") runSubcommandGroup("auth", authSubcommands, parseAuthArgs, (root, parsed) => import("../auth-cmd-gniL2fNt.mjs").then(({ runAuthCommand }) => runAuthCommand(root, parsed)));
1997
+ else if (command === "platform") runSubcommandGroup("platform", platformSubcommands, parsePlatformArgs, (root, parsed) => import("../platform-cmd-BFhieCdV.mjs").then(({ runPlatformCommand }) => runPlatformCommand(root, parsed)));
1998
+ else if (command === "cloudflare") runSubcommandGroup("cloudflare", cloudflareSubcommands, parseCloudflareArgs, (root, parsed) => import("../cloudflare-cmd-4RPGN3KB.mjs").then(({ runCloudflareCommand }) => runCloudflareCommand(root, parsed)));
1999
+ else if (command === "project") runSubcommandGroup("project", projectSubcommands, parseProjectArgs, (root, parsed) => import("../project-cmd-DmZK9Hxf.mjs").then(({ runProjectCommand }) => runProjectCommand(root, parsed)));
2000
+ else if (command === "secret") runSubcommandGroup("secret", secretSubcommands, parseSecretArgs, (root, parsed) => import("../secret-Bzzi2e9E.mjs").then(({ runSecretCommand }) => runSecretCommand(root, parsed)));
2001
+ else if (command === "domain") runSubcommandGroup("domain", domainSubcommands, parseDomainArgs, (root, parsed) => import("../domain-1RhhOVrC.mjs").then(({ runDomainCommand }) => runDomainCommand(root, parsed)));
2002
+ else if (command === "email") runSubcommandGroup("email", emailSubcommands, parseEmailArgs, (root, parsed) => import("../email-C-lGh51B.mjs").then(({ runEmailCommand }) => runEmailCommand(root, parsed)));
2003
+ else if (command === "db") runSubcommandGroup("db", dbSubcommands, parseDbArgs, (root, parsed) => import("../db-BkRoptAt.mjs").then(({ runDbCommand }) => runDbCommand(root, parsed)));
2004
+ else if (command === "env") runSubcommandGroup("env", envSubcommands, parseEnvArgs, (root, parsed) => import("../env-DJHsPE7Z.mjs").then(({ runEnvCommand }) => runEnvCommand(root, parsed)));
2005
+ else if (command === "gen") runSubcommandGroup("gen", genSubcommands, parseGenArgs, (root, parsed) => import("../gen-B_wPnVTK.mjs").then(({ runGenCommand }) => runGenCommand(root, parsed)));
2006
+ else if (command === "github") runSubcommandGroup("github", githubSubcommands, parseGithubArgs, (root, parsed) => import("../github-cmd-PW7ZnWTp.mjs").then(({ runGithubCommand }) => runGithubCommand(root, parsed)));
2007
+ else if (command === "build") runSubcommandGroup("build", buildSubcommands, parseBuildArgs, (root, parsed) => import("../build-cmd-sI18tX_O.mjs").then(({ runBuildCommand }) => runBuildCommand(root, parsed)));
3527
2008
  else if (command === "prepare") {
3528
2009
  const root = process.cwd();
3529
2010
  let prepareArgs;
@@ -3533,7 +2014,7 @@ else if (command === "prepare") {
3533
2014
  log.error(formatCloudflareUserMessage(err instanceof Error ? err.message : String(err)));
3534
2015
  process.exit(1);
3535
2016
  }
3536
- import("../prepare-BfJvFUtJ.mjs").then(({ runPrepareCommand }) => {
2017
+ import("../prepare-blNRQvQl.mjs").then(({ runPrepareCommand }) => {
3537
2018
  runPrepareCommand(root, prepareArgs).catch((err) => {
3538
2019
  log.error(formatCloudflareUserMessage(err.message ?? String(err)));
3539
2020
  process.exit(1);
@@ -3541,7 +2022,7 @@ else if (command === "prepare") {
3541
2022
  });
3542
2023
  } else if (command === "init") {
3543
2024
  const root = process.cwd();
3544
- import("../init-Dx5cgKqK.mjs").then(({ runInitCommand, parseInitArgs }) => {
2025
+ import("../init-BWZ7q5Z4.mjs").then(({ runInitCommand, parseInitArgs }) => {
3545
2026
  let initArgs;
3546
2027
  try {
3547
2028
  initArgs = parseInitArgs(args.slice(1));
@@ -3578,11 +2059,11 @@ else if (command === "prepare") {
3578
2059
  {
3579
2060
  name: "auth",
3580
2061
  hint: "login | logout | whoami | token",
3581
- description: "Login, logout, and token management"
2062
+ description: "Login, account linking, logout, and token management"
3582
2063
  },
3583
2064
  {
3584
2065
  name: "platform",
3585
- hint: "auth | user | project | deployment | signup | system | install",
2066
+ hint: "auth | user | project | deployment | signup | email | system | install",
3586
2067
  description: "Install platforms and administer users, projects, access, and operations"
3587
2068
  },
3588
2069
  {
@@ -3592,8 +2073,8 @@ else if (command === "prepare") {
3592
2073
  },
3593
2074
  {
3594
2075
  name: "project",
3595
- hint: "status | link | list | logs | requests | rollback | cancel | delete | purge-cache",
3596
- description: "Manage projects and deployments"
2076
+ hint: "status | link | list | team | token | logs | requests | rollback | cancel | delete | purge-cache",
2077
+ description: "Manage projects, teams, deployments, and scoped CI credentials"
3597
2078
  },
3598
2079
  {
3599
2080
  name: "secret",
@@ -3607,7 +2088,7 @@ else if (command === "prepare") {
3607
2088
  },
3608
2089
  {
3609
2090
  name: "email",
3610
- hint: "usage | logs | destinations | allow | disallow | status | setup",
2091
+ hint: "usage | logs | destinations | allow | disallow | domain | status | setup",
3611
2092
  description: "Inspect email activity and manage verified recipients"
3612
2093
  },
3613
2094
  {
@@ -3646,41 +2127,41 @@ else if (command === "prepare") {
3646
2127
  ];
3647
2128
  (async () => {
3648
2129
  printVoidHeader();
3649
- const { promptSubcommand } = await import("../subcommand-prompt-WfySCQ7S.mjs");
2130
+ const { promptSubcommand } = await import("../subcommand-prompt-Bmyn5Rlc.mjs");
3650
2131
  const result = await promptSubcommand("", topLevelCommands);
3651
2132
  if (!result) process.exit(0);
3652
2133
  const cmd = result.subcommand;
3653
2134
  const root = process.cwd();
3654
2135
  switch (cmd) {
3655
2136
  case "connect": {
3656
- const { runConnectCommand } = await import("../connect-D9Yr-T5f.mjs");
2137
+ const { runConnectCommand } = await import("../connect-Bfk31O_8.mjs");
3657
2138
  await runConnectCommand(root, parseConnectArgs([]));
3658
2139
  return;
3659
2140
  }
3660
2141
  case "init": {
3661
- const { runInitCommand, parseInitArgs } = await import("../init-Dx5cgKqK.mjs");
2142
+ const { runInitCommand, parseInitArgs } = await import("../init-BWZ7q5Z4.mjs");
3662
2143
  await runInitCommand(root, parseInitArgs([]));
3663
2144
  return;
3664
2145
  }
3665
2146
  case "deploy": {
3666
- const { runDeploy } = await import("../deploy-CcDoeBIf.mjs").then((n) => n.n);
2147
+ const { runDeploy } = await import("../deploy-DTaWUS1S.mjs").then((n) => n.n);
3667
2148
  await runDeploy(root);
3668
2149
  return;
3669
2150
  }
3670
- case "auth": return runSubcommandGroup("auth", authSubcommands, parseAuthArgs, (r, p) => import("../auth-cmd-CYVhSNFy.mjs").then(({ runAuthCommand }) => runAuthCommand(r, p)));
3671
- case "platform": return runSubcommandGroup("platform", platformSubcommands, parsePlatformArgs, (r, p) => import("../platform-cmd-BEOVv1PN.mjs").then(({ runPlatformCommand }) => runPlatformCommand(r, p)));
3672
- case "cloudflare": return runSubcommandGroup("cloudflare", cloudflareSubcommands, parseCloudflareArgs, (r, p) => import("../cloudflare-cmd-D3ME2GAe.mjs").then(({ runCloudflareCommand }) => runCloudflareCommand(r, p)));
3673
- case "project": return runSubcommandGroup("project", projectSubcommands, parseProjectArgs, (r, p) => import("../project-cmd-DCpk1cDt.mjs").then(({ runProjectCommand }) => runProjectCommand(r, p)));
3674
- case "secret": return runSubcommandGroup("secret", secretSubcommands, parseSecretArgs, (r, p) => import("../secret-BqTxGqki.mjs").then(({ runSecretCommand }) => runSecretCommand(r, p)));
3675
- case "domain": return runSubcommandGroup("domain", domainSubcommands, parseDomainArgs, (r, p) => import("../domain-gu_iaHmn.mjs").then(({ runDomainCommand }) => runDomainCommand(r, p)));
3676
- case "github": return runSubcommandGroup("github", githubSubcommands, parseGithubArgs, (r, p) => import("../github-cmd-DJS5Ab-H.mjs").then(({ runGithubCommand }) => runGithubCommand(r, p)));
3677
- case "build": return runSubcommandGroup("build", buildSubcommands, parseBuildArgs, (r, p) => import("../build-cmd-CkVD1uOh.mjs").then(({ runBuildCommand }) => runBuildCommand(r, p)));
3678
- case "email": return runSubcommandGroup("email", emailSubcommands, parseEmailArgs, (r, p) => import("../email-r6DHJyAB.mjs").then(({ runEmailCommand }) => runEmailCommand(r, p)));
3679
- case "db": return runSubcommandGroup("db", dbSubcommands, parseDbArgs, (r, p) => import("../db-BxoUWL0F.mjs").then(({ runDbCommand }) => runDbCommand(r, p)));
3680
- case "env": return runSubcommandGroup("env", envSubcommands, parseEnvArgs, (r, p) => import("../env-DmU2To0C.mjs").then(({ runEnvCommand }) => runEnvCommand(r, p)));
3681
- case "gen": return runSubcommandGroup("gen", genSubcommands, parseGenArgs, (r, p) => import("../gen-BKw6qHIg.mjs").then(({ runGenCommand }) => runGenCommand(r, p)));
2151
+ case "auth": return runSubcommandGroup("auth", authSubcommands, parseAuthArgs, (r, p) => import("../auth-cmd-gniL2fNt.mjs").then(({ runAuthCommand }) => runAuthCommand(r, p)));
2152
+ case "platform": return runSubcommandGroup("platform", platformSubcommands, parsePlatformArgs, (r, p) => import("../platform-cmd-BFhieCdV.mjs").then(({ runPlatformCommand }) => runPlatformCommand(r, p)));
2153
+ case "cloudflare": return runSubcommandGroup("cloudflare", cloudflareSubcommands, parseCloudflareArgs, (r, p) => import("../cloudflare-cmd-4RPGN3KB.mjs").then(({ runCloudflareCommand }) => runCloudflareCommand(r, p)));
2154
+ case "project": return runSubcommandGroup("project", projectSubcommands, parseProjectArgs, (r, p) => import("../project-cmd-DmZK9Hxf.mjs").then(({ runProjectCommand }) => runProjectCommand(r, p)));
2155
+ case "secret": return runSubcommandGroup("secret", secretSubcommands, parseSecretArgs, (r, p) => import("../secret-Bzzi2e9E.mjs").then(({ runSecretCommand }) => runSecretCommand(r, p)));
2156
+ case "domain": return runSubcommandGroup("domain", domainSubcommands, parseDomainArgs, (r, p) => import("../domain-1RhhOVrC.mjs").then(({ runDomainCommand }) => runDomainCommand(r, p)));
2157
+ case "github": return runSubcommandGroup("github", githubSubcommands, parseGithubArgs, (r, p) => import("../github-cmd-PW7ZnWTp.mjs").then(({ runGithubCommand }) => runGithubCommand(r, p)));
2158
+ case "build": return runSubcommandGroup("build", buildSubcommands, parseBuildArgs, (r, p) => import("../build-cmd-sI18tX_O.mjs").then(({ runBuildCommand }) => runBuildCommand(r, p)));
2159
+ case "email": return runSubcommandGroup("email", emailSubcommands, parseEmailArgs, (r, p) => import("../email-C-lGh51B.mjs").then(({ runEmailCommand }) => runEmailCommand(r, p)));
2160
+ case "db": return runSubcommandGroup("db", dbSubcommands, parseDbArgs, (r, p) => import("../db-BkRoptAt.mjs").then(({ runDbCommand }) => runDbCommand(r, p)));
2161
+ case "env": return runSubcommandGroup("env", envSubcommands, parseEnvArgs, (r, p) => import("../env-DJHsPE7Z.mjs").then(({ runEnvCommand }) => runEnvCommand(r, p)));
2162
+ case "gen": return runSubcommandGroup("gen", genSubcommands, parseGenArgs, (r, p) => import("../gen-B_wPnVTK.mjs").then(({ runGenCommand }) => runGenCommand(r, p)));
3682
2163
  case "prepare": {
3683
- const { runPrepareCommand } = await import("../prepare-BfJvFUtJ.mjs");
2164
+ const { runPrepareCommand } = await import("../prepare-blNRQvQl.mjs");
3684
2165
  await runPrepareCommand(root, parsePrepareArgs([]));
3685
2166
  return;
3686
2167
  }
@@ -3694,4 +2175,4 @@ else if (command === "prepare") {
3694
2175
  });
3695
2176
  } else printGlobalHelp();
3696
2177
  //#endregion
3697
- export { OperatorClient as n, OperatorClientError as r, help_exports as t };
2178
+ export {};