void 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/AGENTS_TEMPLATE.md +1 -1
  2. package/dist/{account-cmd-C84Ee8cO.mjs → account-cmd-vJhh5lrb.mjs} +5 -5
  3. package/dist/{auth-CSkdO2Bb.mjs → auth-CQkSDS50.mjs} +22 -49
  4. package/dist/auth-DX3JURcX.mjs +2 -0
  5. package/dist/{auth-link-CioEg6uY.mjs → auth-link-D75t9wO2.mjs} +5 -5
  6. package/dist/{auth-router-BsR981d4.mjs → auth-router-ClfhL1J7.mjs} +7 -7
  7. package/dist/{build-cmd-LzvNJORH.mjs → build-cmd-zMi1iFnS.mjs} +6 -6
  8. package/dist/{cache-C4MvnrMH.mjs → cache-ZtQmyxVV.mjs} +5 -5
  9. package/dist/{cancel-deploy-abUxpP2n.mjs → cancel-deploy-Box0kkEV.mjs} +5 -5
  10. package/dist/{cf-access-P0iZnu0q.mjs → cf-access-BGe5sWXF.mjs} +4 -48
  11. package/dist/{cf-access-DRsQRe6k.mjs → cf-access-TWNzXltp.mjs} +3 -11
  12. package/dist/cli/cli.mjs +47 -44
  13. package/dist/cli/cloudflare-operation-process.mjs +14 -5
  14. package/dist/cli/env-schema-probe.mjs +3 -3
  15. package/dist/client-B9UBI03V.mjs +2 -0
  16. package/dist/{client-RV8NVeB8.mjs → client-DKAsFA4X.mjs} +33 -15
  17. package/dist/{cloudflare-cmd-BcrVyTmJ.mjs → cloudflare-cmd-CmY8dGkd.mjs} +1 -1
  18. package/dist/{cloudflare-connect-B8uPZ4nx.mjs → cloudflare-connect-IiR3ZieC.mjs} +1 -1
  19. package/dist/{cloudflare-operations-AiWashgg.mjs → cloudflare-operations-BCdXSsmo.mjs} +1 -1
  20. package/dist/{cloudflare-operations-fxHb-byx.mjs → cloudflare-operations-DzH-A966.mjs} +1 -1
  21. package/dist/{connect-Bd4kJd9U.mjs → connect-B-usTnlF.mjs} +9 -9
  22. package/dist/{create-project-bMf6ffLZ.mjs → create-project-CYOOAYcn.mjs} +12 -12
  23. package/dist/{create-project-Boczwj5r.mjs → create-project-FWbf5Olo.mjs} +1 -1
  24. package/dist/{db-8uG64XLl.mjs → db-Ck2eZ2iB.mjs} +15 -15
  25. package/dist/{delete-NvbiyeJf.mjs → delete-sK56qtzZ.mjs} +5 -5
  26. package/dist/{deploy-DzAIwqNU.mjs → deploy-gCVaiJbS.mjs} +86 -44
  27. package/dist/{deploy-bjXdFCtn.mjs → deploy-irP5q0-x.mjs} +1 -1
  28. package/dist/{domain-y5Tvydvo.mjs → domain-DFfQE8jg.mjs} +6 -6
  29. package/dist/{email-B3umsW75.mjs → email-CdnhUHvJ.mjs} +7 -7
  30. package/dist/{env-BS6qYHDb.mjs → env-DmGdHi5i.mjs} +8 -8
  31. package/dist/{env-types-C2_bIk_b.mjs → env-types-D5kds_OZ.mjs} +1 -1
  32. package/dist/{env-validation-BPn7vk-V.mjs → env-validation-C_8tPdhs.mjs} +2 -2
  33. package/dist/{env-validation-DbTg7-ar.mjs → env-validation-zZOCcQVZ.mjs} +1 -1
  34. package/dist/{gen-B580--vC.mjs → gen-CshAkukR.mjs} +2 -2
  35. package/dist/gen-DvQTWzDU.mjs +2 -0
  36. package/dist/{github-cmd-v45BdfKX.mjs → github-cmd-DftuEvIt.mjs} +13 -13
  37. package/dist/{headers-DWi2IXWx.mjs → headers-2rKda9Y6.mjs} +1 -1
  38. package/dist/{help-daGKjXGk.mjs → help-Dotbf6iz.mjs} +8 -4
  39. package/dist/help-mabuIV98.mjs +2 -0
  40. package/dist/{inbound-aVHEUhKo.mjs → inbound-fRkqcXaC.mjs} +1 -1
  41. package/dist/index.mjs +16 -20
  42. package/dist/{init-Bvy7zrBo.mjs → init-BTQEZZGJ.mjs} +25 -22
  43. package/dist/{link-CDqqCjFl.mjs → link-B46sYH0K.mjs} +6 -6
  44. package/dist/{list-CZj0dzKY.mjs → list-BnPrIsU_.mjs} +6 -6
  45. package/dist/log-vqY3pqwI.mjs +29 -0
  46. package/dist/{login-UZKFM_u7.mjs → login-BuiMyUrx.mjs} +8 -8
  47. package/dist/login-DIy9Sb-U.mjs +2 -0
  48. package/dist/{logs-CUZ6t9t3.mjs → logs-CZG9d-ic.mjs} +10 -11
  49. package/dist/migrate-BCiJ8w0f.mjs +2 -0
  50. package/dist/{migrate-DHul7PRV.mjs → migrate-q4_njtaW.mjs} +2 -2
  51. package/dist/{operator-args-CLgKGlwU.mjs → operator-args-B3BTmSoi.mjs} +12 -5
  52. package/dist/{operator-auth-8KnOVXVd.mjs → operator-auth-7erYc6Lt.mjs} +2 -2
  53. package/dist/{operator-client-DUwumHpQ.mjs → operator-client-CpHjTd0t.mjs} +3 -3
  54. package/dist/{operator-cmd-DBA6dl0m.mjs → operator-cmd-DGqPJUD3.mjs} +6 -6
  55. package/dist/{output-Dm_A4Tbv.mjs → output-CbM85T4k.mjs} +18 -10
  56. package/dist/pages/index.mjs +1 -1
  57. package/dist/{platform-args-BJdRtlLq.mjs → platform-args-3k8X9-H_.mjs} +11 -3
  58. package/dist/platform-args-DdnM6WIf.mjs +2 -0
  59. package/dist/{platform-auth-config-B56E9YP1.mjs → platform-auth-config-DDrxUy4m.mjs} +6 -6
  60. package/dist/{platform-auth-protection-BDWO_aER.mjs → platform-auth-protection-BLadnnct.mjs} +8 -8
  61. package/dist/{platform-auth-recovery-1gg4CSRe.mjs → platform-auth-recovery-CmCzX2Fl.mjs} +6 -6
  62. package/dist/{platform-cmd-DMcQStSc.mjs → platform-cmd-DMDEc9uS.mjs} +10 -10
  63. package/dist/{platform-cmd-Bt-1w6pY.mjs → platform-cmd-pMTRhvYs.mjs} +1 -1
  64. package/dist/{platform-domain-BNkcz0OB.mjs → platform-domain-Di6EYrQl.mjs} +5 -5
  65. package/dist/{platform-lifecycle-9OyBALhH.mjs → platform-lifecycle-BBoNzojY.mjs} +1264 -407
  66. package/dist/{platform-lifecycle-BE6C_jxh.mjs → platform-lifecycle-BFdSi9Qt.mjs} +1 -1
  67. package/dist/{platform-management-CjwLVQwN.mjs → platform-management-CWOQOWXm.mjs} +5 -5
  68. package/dist/{platform-management-CnyTdcWX.mjs → platform-management-DG47jys8.mjs} +1 -1
  69. package/dist/{platform-plans-config-BNGKGr4P.mjs → platform-plans-config-Vl9BP3H4.mjs} +3 -3
  70. package/dist/{platform-recovery-D6KSpuFm.mjs → platform-recovery-BJnQJEtQ.mjs} +4 -4
  71. package/dist/{platform-registry-QBa4DK32.mjs → platform-registry-DkPPXfp_.mjs} +2 -2
  72. package/dist/{prepare-B5Mkic5u.mjs → prepare-CBLT3CHH.mjs} +3 -3
  73. package/dist/prepare-CnjcTV2s.mjs +2 -0
  74. package/dist/{prepare-DOsL0CC9.mjs → prepare-DnaKBi--.mjs} +1 -1
  75. package/dist/{project-C0Fqu05O.mjs → project-DKG5Ra8p.mjs} +15 -82
  76. package/dist/project-DsblYifa.mjs +2 -0
  77. package/dist/{project-cmd-CNzrqvBv.mjs → project-cmd-DxnXstAq.mjs} +19 -19
  78. package/dist/{project-team-DQOWPfQT.mjs → project-team-BmHcaALJ.mjs} +5 -5
  79. package/dist/{project-token-CTDYU0Cc.mjs → project-token-GionZlBI.mjs} +5 -5
  80. package/dist/{project-zero-trust-BNAkW-_0.mjs → project-zero-trust-JLCjVQA7.mjs} +5 -5
  81. package/dist/prompts-2TGE6oXx.mjs +2 -0
  82. package/dist/prompts-BNUoTiaf.mjs +2044 -0
  83. package/dist/{provision-C4ORE7G7.mjs → provision-CVku1JFU.mjs} +1 -1
  84. package/dist/{provision-CJFgTZY8.mjs → provision-CaoxO_U7.mjs} +1 -1
  85. package/dist/{requests-MhxYnau8.mjs → requests-BOjIVAeF.mjs} +5 -5
  86. package/dist/{rollback-CJ6iSDoU.mjs → rollback-CwR3pLj3.mjs} +6 -6
  87. package/dist/runtime/ai.mjs +3 -5
  88. package/dist/runtime/email/testing.mjs +1 -1
  89. package/dist/runtime/email.mjs +1 -1
  90. package/dist/runtime/remote/index.mjs +2 -2
  91. package/dist/{secret-Dli5fP0B.mjs → secret-ByWJWBUG.mjs} +7 -7
  92. package/dist/{skills-D1II1Juz.mjs → skills-C1PlclPt.mjs} +1 -1
  93. package/dist/{subcommand-prompt-CY1C4fvl.mjs → subcommand-prompt-klFCfcor.mjs} +40 -32
  94. package/package.json +8 -9
  95. package/skills/void/SKILL.md +6 -1
  96. package/skills/void/docs/guide/deployment.md +22 -0
  97. package/skills/void/docs/guide/edge/headers.md +1 -1
  98. package/skills/void/docs/guide/email/domains.md +5 -1
  99. package/skills/void/docs/guide/email/sending.md +1 -1
  100. package/skills/void/docs/guide/platform/administration/zero-trust.md +27 -9
  101. package/skills/void/docs/guide/platform/installation/ci.md +1 -1
  102. package/skills/void/docs/guide/platform/installation/credentials.md +10 -3
  103. package/skills/void/docs/guide/platform/installation/domains.md +8 -3
  104. package/skills/void/docs/guide/platform/installation/first-deployment.md +2 -2
  105. package/skills/void/docs/guide/platform/installation/maintenance.md +12 -0
  106. package/skills/void/docs/guide/platform/installation/prerequisites.md +8 -7
  107. package/skills/void/docs/guide/platform/installation/setup.md +80 -10
  108. package/skills/void/docs/guide/remote-dev.md +1 -1
  109. package/skills/void/docs/reference/cli/auth.md +4 -3
  110. package/skills/void/docs/reference/cli/database.md +1 -1
  111. package/skills/void/docs/reference/cli/deploy.md +20 -0
  112. package/skills/void/docs/reference/cli/platform-installation.md +42 -27
  113. package/skills/void/docs/reference/cli/platform-zero-trust.md +14 -0
  114. package/skills/void/docs/reference/config.md +1 -1
  115. package/dist/auth-CCjt0hcq.mjs +0 -2
  116. package/dist/client-Cu7jWiF1.mjs +0 -2
  117. package/dist/dist-BuDuKZJv.mjs +0 -2
  118. package/dist/dist-CTBk70IR.mjs +0 -1310
  119. package/dist/gen-BVaUUumi.mjs +0 -2
  120. package/dist/help-DofyZuY7.mjs +0 -2
  121. package/dist/log-ChfPKsVd.mjs +0 -26
  122. package/dist/login-DYt_An22.mjs +0 -2
  123. package/dist/migrate-8_2u55MD.mjs +0 -2
  124. package/dist/platform-args-D3RXyR6h.mjs +0 -2
  125. package/dist/prepare-cgvDMtSb.mjs +0 -2
  126. package/dist/project-BuUwfj6f.mjs +0 -2
@@ -1,3 +1,3 @@
1
1
  import { r as scanWranglerDotenvKeys } from "./dotenv-VQxupEUv.mjs";
2
- import { _ as resolvePinnedApiBaseUrl, a as checkCloudflareProvisionDrift, c as enableD1ReadReplicationViaApi, d as isQueueConfirmedAbsent, f as isRealD1Id, g as provisionEnv, h as provisionCloudflareResources, i as assertValidResourceName, l as isAlreadyGoneKillError, m as normalizeRunWranglerResult, n as acquireProvisionLock, o as createDefaultRunWrangler, p as isRealKvId, r as assertNoTransportRedirectInProjectDotenv, s as deleteEnvVarCaseAware, t as WranglerQuarantineError, u as isQuarantineError, v as resolveProperLockfileKey } from "./provision-CJFgTZY8.mjs";
2
+ import { _ as resolvePinnedApiBaseUrl, a as checkCloudflareProvisionDrift, c as enableD1ReadReplicationViaApi, d as isQueueConfirmedAbsent, f as isRealD1Id, g as provisionEnv, h as provisionCloudflareResources, i as assertValidResourceName, l as isAlreadyGoneKillError, m as normalizeRunWranglerResult, n as acquireProvisionLock, o as createDefaultRunWrangler, p as isRealKvId, r as assertNoTransportRedirectInProjectDotenv, s as deleteEnvVarCaseAware, t as WranglerQuarantineError, u as isQuarantineError, v as resolveProperLockfileKey } from "./provision-CaoxO_U7.mjs";
3
3
  export { WranglerQuarantineError, acquireProvisionLock, assertNoTransportRedirectInProjectDotenv, assertValidResourceName, checkCloudflareProvisionDrift, createDefaultRunWrangler, deleteEnvVarCaseAware, enableD1ReadReplicationViaApi, isAlreadyGoneKillError, isQuarantineError, isQueueConfirmedAbsent, isRealD1Id, isRealKvId, normalizeRunWranglerResult, provisionCloudflareResources, provisionEnv, resolvePinnedApiBaseUrl, resolveProperLockfileKey, scanWranglerDotenvKeys };
@@ -1,7 +1,7 @@
1
1
  import { a as join, n as dirname, o as normalize, t as basename, u as resolve$2 } from "./dist-BVqXeR_h.mjs";
2
2
  import { i as persistCloudflareConfig } from "./cloudflare-config-Bktvwtpf.mjs";
3
3
  import { A as writeProvisionedWranglerConfig, s as assertNoDuplicateMembers, v as readProvisionedBindingEntry } from "./wrangler-7K-bW_DL.mjs";
4
- import { i as voidWarn, r as voidLog } from "./log-ChfPKsVd.mjs";
4
+ import { i as voidWarn, r as voidLog } from "./log-vqY3pqwI.mjs";
5
5
  import { r as scanWranglerDotenvKeys } from "./dotenv-VQxupEUv.mjs";
6
6
  import { c as resolveCloudflareOperationArgv, n as decodeCloudflareOperationResult, r as encodeCloudflareOperation } from "./cloudflare-process-B-wekeR6.mjs";
7
7
  import { createRequire } from "node:module";
@@ -1,8 +1,8 @@
1
- import { c as getToken } from "./auth-CSkdO2Bb.mjs";
2
- import { o as readProjectConfig } from "./project-C0Fqu05O.mjs";
3
- import { O as intro, P as outro, j as log } from "./dist-CTBk70IR.mjs";
4
- import { n as PlatformClient } from "./client-RV8NVeB8.mjs";
5
- import { a as dim, c as import_picocolors, r as cliTitle } from "./output-Dm_A4Tbv.mjs";
1
+ import { r as readProjectConfig } from "./project-DKG5Ra8p.mjs";
2
+ import { c as getToken } from "./auth-CQkSDS50.mjs";
3
+ import { H as outro, L as log, N as intro } from "./prompts-BNUoTiaf.mjs";
4
+ import { n as PlatformClient } from "./client-DKAsFA4X.mjs";
5
+ import { a as dim, c as import_picocolors, r as cliTitle } from "./output-CbM85T4k.mjs";
6
6
  //#region src/cli/requests.ts
7
7
  async function runRequestsCommand(root, range, status) {
8
8
  console.log();
@@ -1,8 +1,8 @@
1
- import { c as getToken } from "./auth-CSkdO2Bb.mjs";
2
- import { i as readDeploymentPlatform, o as readProjectConfig } from "./project-C0Fqu05O.mjs";
3
- import { E as confirm, I as select, O as intro, P as outro, T as cancel, W as isCancel, j as log } from "./dist-CTBk70IR.mjs";
4
- import { n as PlatformClient } from "./client-RV8NVeB8.mjs";
5
- import { a as dim, c as import_picocolors, i as createSpinner, o as ensureUtc, r as cliTitle } from "./output-Dm_A4Tbv.mjs";
1
+ import { n as readDeploymentPlatform, r as readProjectConfig } from "./project-DKG5Ra8p.mjs";
2
+ import { c as getToken } from "./auth-CQkSDS50.mjs";
3
+ import { A as confirm, H as outro, K as select, L as log, N as intro, O as cancel, tt as isCancel } from "./prompts-BNUoTiaf.mjs";
4
+ import { n as PlatformClient } from "./client-DKAsFA4X.mjs";
5
+ import { a as dim, c as import_picocolors, i as createSpinner, o as ensureUtc, r as cliTitle } from "./output-CbM85T4k.mjs";
6
6
  //#region src/cli/rollback.ts
7
7
  async function runRollbackCommand(root, deployId) {
8
8
  console.log();
@@ -83,7 +83,7 @@ async function runRollbackCommand(root, deployId) {
83
83
  outro(dim("Traffic is now serving the rolled-back deployment."));
84
84
  }
85
85
  async function runCloudflareRollback(root, deployId) {
86
- const { activateCloudflareExistingVersion, readCloudflareWorkerStatus, resolveCloudflareOperationContext } = await import("./cloudflare-operations-AiWashgg.mjs");
86
+ const { activateCloudflareExistingVersion, readCloudflareWorkerStatus, resolveCloudflareOperationContext } = await import("./cloudflare-operations-BCdXSsmo.mjs");
87
87
  const context = resolveCloudflareOperationContext(root);
88
88
  const status = await readCloudflareWorkerStatus(context);
89
89
  const deployedVersionIds = new Set(status.deployedVersionIds);
@@ -1,4 +1,4 @@
1
- import { t as cfAccessHeaders } from "../cf-access-DRsQRe6k.mjs";
1
+ import { t as cfAccessHeaders } from "../cf-access-TWNzXltp.mjs";
2
2
  import { a as getRawRuntimeEnv } from "../env-raw-Cx8ElDdj.mjs";
3
3
  import { a as readUsageLimit, i as limitOperation, r as limitFromResponse, t as UsageLimit } from "../limits-Cjuk2VPm.mjs";
4
4
  //#region src/runtime/ai-types.ts
@@ -27,8 +27,6 @@ const PROVIDER_KEY_MAP = {
27
27
  };
28
28
  //#endregion
29
29
  //#region src/runtime/ai.ts
30
- const PROXY_URL = "https://proxy.void.cloud/ai";
31
- const STAGING_PROXY_URL = "https://proxy.staging.void.cloud/ai";
32
30
  const VOID_FILE_MARKER = "__voidFile";
33
31
  const PROVIDER_AUTH_DEFAULTS = {
34
32
  "google-ai-studio": {
@@ -239,8 +237,8 @@ function resolveProviderKey(provider, runtimeEnv, options) {
239
237
  }
240
238
  function getProxyUrl(runtimeEnv) {
241
239
  const custom = runtimeEnv.__VOID_PROXY_URL;
242
- if (typeof custom === "string" && custom.length > 0) return custom + "/ai";
243
- return runtimeEnv.__VOID_STAGING ? STAGING_PROXY_URL : PROXY_URL;
240
+ if (typeof custom === "string" && custom.length > 0) return custom.replace(/\/+$/, "") + "/ai";
241
+ throw new Error("ai: No connected platform proxy is available. Run `void connect <url>` first.");
244
242
  }
245
243
  function providerAuthDefaults(provider) {
246
244
  return PROVIDER_AUTH_DEFAULTS[provider] ?? {
@@ -1,7 +1,7 @@
1
1
  import { i as normalizeEmail, r as buildMime } from "../../mime-D5Nmdzf7.mjs";
2
2
  import { n as compileEmailPattern, t as compareEmailRoutes } from "../../email--tEtMoj_.mjs";
3
3
  import { y as registerEmailSink } from "../../dev-inbox-P0u4tM8Y.mjs";
4
- import { i as createMessageProxy, n as MAX_INBOUND_HEADERS, t as MAX_INBOUND_BYTES } from "../../inbound-aVHEUhKo.mjs";
4
+ import { i as createMessageProxy, n as MAX_INBOUND_HEADERS, t as MAX_INBOUND_BYTES } from "../../inbound-fRkqcXaC.mjs";
5
5
  //#region src/runtime/email/testing.ts
6
6
  /**
7
7
  * Test harnesses for void/email.
@@ -1,7 +1,7 @@
1
1
  import { i as normalizeEmail, n as MimeError, r as buildMime } from "../mime-D5Nmdzf7.mjs";
2
2
  import { a as DEV_INBOX_ENV_VAR, b as serializeCapturedMessage, c as DEV_INBOX_TOKEN_ENV_VAR, d as captureMessage, h as hasActiveSink, i as DEV_INBOX_CAPTURE_URL, p as dispatchToSinks } from "../dev-inbox-P0u4tM8Y.mjs";
3
3
  import { a as getRawRuntimeEnv } from "../env-raw-Cx8ElDdj.mjs";
4
- import { a as defineEmail, c as parseEmail, i as createMessageProxy, l as replyEmail, o as enactEmailActions, r as ParseEmailError, s as isForwardableEmailMessage } from "../inbound-aVHEUhKo.mjs";
4
+ import { a as defineEmail, c as parseEmail, i as createMessageProxy, l as replyEmail, o as enactEmailActions, r as ParseEmailError, s as isForwardableEmailMessage } from "../inbound-fRkqcXaC.mjs";
5
5
  //#region src/runtime/email.ts
6
6
  const VALID_SLUG = /^[a-z0-9](?:[a-z0-9-]{0,54}[a-z0-9])?$/;
7
7
  function makeError(code, message, cause) {
@@ -1,4 +1,4 @@
1
- import { t as cfAccessHeaders } from "../../cf-access-DRsQRe6k.mjs";
1
+ import { t as cfAccessHeaders } from "../../cf-access-TWNzXltp.mjs";
2
2
  import { n as serializeR2Conditional } from "../../r2-conditions-D1Wk8i7b.mjs";
3
3
  //#region src/runtime/remote/client.ts
4
4
  function readableMessage(value) {
@@ -49,7 +49,7 @@ async function proxyError(response) {
49
49
  return /* @__PURE__ */ new Error(`remote: Proxy request failed with ${response.status}: ${message}`);
50
50
  }
51
51
  /**
52
- * HTTP client for the remote bindings proxy (proxy.void.cloud).
52
+ * HTTP client for the connected platform's remote bindings proxy.
53
53
  *
54
54
  * Used by ProxyD1Database, ProxyKVNamespace, and ProxyR2Bucket to forward
55
55
  * binding operations to the platform proxy during local development.
@@ -1,9 +1,9 @@
1
- import { c as getToken } from "./auth-CSkdO2Bb.mjs";
2
- import { i as readDeploymentPlatform, o as readProjectConfig } from "./project-C0Fqu05O.mjs";
3
- import { _ as validateSecretValues } from "./env-validation-BPn7vk-V.mjs";
4
- import { E as confirm, F as password, N as note, O as intro, P as outro, T as cancel, W as isCancel, j as log } from "./dist-CTBk70IR.mjs";
5
- import { n as PlatformClient } from "./client-RV8NVeB8.mjs";
6
- import { r as cliTitle } from "./output-Dm_A4Tbv.mjs";
1
+ import { n as readDeploymentPlatform, r as readProjectConfig } from "./project-DKG5Ra8p.mjs";
2
+ import { c as getToken } from "./auth-CQkSDS50.mjs";
3
+ import { _ as validateSecretValues } from "./env-validation-C_8tPdhs.mjs";
4
+ import { A as confirm, B as note, H as outro, L as log, N as intro, O as cancel, U as password, tt as isCancel } from "./prompts-BNUoTiaf.mjs";
5
+ import { n as PlatformClient } from "./client-DKAsFA4X.mjs";
6
+ import { r as cliTitle } from "./output-CbM85T4k.mjs";
7
7
  import { r as resolveProjectBySlug, t as getRequestedProjectSlug } from "./resolve-project-BG5ORGID.mjs";
8
8
  import { readFileSync } from "node:fs";
9
9
  //#region src/cli/secret.ts
@@ -78,7 +78,7 @@ async function runSecretCommand(root, args) {
78
78
  }
79
79
  async function runCloudflareSecretCommand(root, args) {
80
80
  if (args.projectName) throw new Error("secret: --project selects a Void platform project and is not used for direct Cloudflare apps.");
81
- const { deleteCloudflareSecret, listCloudflareSecrets, putCloudflareSecret, resolveCloudflareOperationContext, syncCloudflareSecrets } = await import("./cloudflare-operations-AiWashgg.mjs");
81
+ const { deleteCloudflareSecret, listCloudflareSecrets, putCloudflareSecret, resolveCloudflareOperationContext, syncCloudflareSecrets } = await import("./cloudflare-operations-BCdXSsmo.mjs");
82
82
  const context = resolveCloudflareOperationContext(root);
83
83
  if (args.action === "list") {
84
84
  const secrets = await listCloudflareSecrets(context);
@@ -1,5 +1,5 @@
1
1
  import { a as join, l as relative } from "./dist-BVqXeR_h.mjs";
2
- import { j as log } from "./dist-CTBk70IR.mjs";
2
+ import { L as log } from "./prompts-BNUoTiaf.mjs";
3
3
  import { n as getPackageDir } from "./agents-B_pX_HmQ.mjs";
4
4
  import { existsSync, mkdirSync, readFileSync, readdirSync, readlinkSync, symlinkSync } from "node:fs";
5
5
  //#region src/cli/skills.ts
@@ -1,5 +1,5 @@
1
- import { S as S_STEP_SUBMIT, _ as S_RADIO_ACTIVE, c as S_CHECKBOX_INACTIVE, i as S_BAR_END, q as wrapAnsi, r as S_BAR, s as S_CHECKBOX_ACTIVE, v as S_RADIO_INACTIVE, y as S_STEP_ACTIVE } from "./dist-CTBk70IR.mjs";
2
- import { c as import_picocolors } from "./output-Dm_A4Tbv.mjs";
1
+ import { $ as wrapTextWithPrefix, G as promptTitle, M as getGuide, V as optionText, b as S_RADIO_ACTIVE, d as S_CHECKBOX_SELECTED, i as S_BAR_END, it as wrapAnsi, j as formatInstructionFooter, n as SELECT_INSTRUCTIONS, r as S_BAR, t as MULTISELECT_INSTRUCTIONS, u as S_CHECKBOX_INACTIVE, w as S_STEP_SUBMIT } from "./prompts-BNUoTiaf.mjs";
2
+ import { c as import_picocolors } from "./output-CbM85T4k.mjs";
3
3
  //#region src/cli/subcommand-prompt.ts
4
4
  function promptSubcommand(command, subcommands) {
5
5
  return new Promise((resolve) => {
@@ -45,16 +45,25 @@ function promptSubcommand(command, subcommands) {
45
45
  }
46
46
  return `${lines[0]}…`;
47
47
  }
48
+ function guidePrefix() {
49
+ return getGuide() ? import_picocolors.default.gray(S_BAR) + " " : " ";
50
+ }
51
+ function renderOption(marker, label, focused, suffix = "") {
52
+ return guidePrefix() + optionText(stdout, marker + " ", label, focused ? (value) => import_picocolors.default.blue(import_picocolors.default.bold(value)) : import_picocolors.default.dim, suffix);
53
+ }
54
+ function instructions(items) {
55
+ return formatInstructionFooter(items, getGuide(), stdout);
56
+ }
48
57
  function writeFrame(header, options, footer) {
49
- const count = Math.max(1, Math.min(7, (stdout.rows || 24) - footer.length - 5));
58
+ const heading = header.trimEnd().split("\n");
59
+ const end = getGuide() ? [import_picocolors.default.gray(S_BAR_END)] : [];
60
+ const count = Math.max(1, Math.min(7, (stdout.rows || 24) - heading.length - footer.length - end.length - 1));
50
61
  const start = Math.max(0, Math.min(cursor - Math.floor(count / 2), options.length - count));
51
62
  previousFrame = [
52
- truncate(header),
53
- import_picocolors.default.gray(S_BAR),
63
+ ...heading.map((line) => truncate(line)),
54
64
  ...options.slice(start, start + count).map((option) => truncate(option)),
55
- import_picocolors.default.gray(S_BAR),
56
65
  ...footer.map((line) => truncate(line)),
57
- import_picocolors.default.gray(S_BAR_END)
66
+ ...end
58
67
  ].join("\n");
59
68
  stdout.write(previousFrame + "\n");
60
69
  }
@@ -69,7 +78,7 @@ function promptSubcommand(command, subcommands) {
69
78
  const title = command ? `void ${command}` : "void";
70
79
  function headerLine() {
71
80
  const argPart = selectedArg ? ` ${selectedArg}` : "";
72
- return `${import_picocolors.default.cyan(S_STEP_ACTIVE)} ${import_picocolors.default.cyan(title)} ${import_picocolors.default.dim("·")} ${import_picocolors.default.cyan(`${selectedSub.name}${argPart}`)}`;
81
+ return promptTitle(`${import_picocolors.default.blue(title)} ${import_picocolors.default.dim("·")} ${selectedSub.name}${argPart}`, "active", { output: stdout });
73
82
  }
74
83
  function renderSelect() {
75
84
  const lines = [];
@@ -78,22 +87,21 @@ function promptSubcommand(command, subcommands) {
78
87
  for (let i = 0; i < subcommands.length; i++) {
79
88
  const sub = subcommands[i];
80
89
  const isFocused = i === cursor;
81
- const radio = isFocused ? import_picocolors.default.green(S_RADIO_ACTIVE) : import_picocolors.default.dim(S_RADIO_INACTIVE);
90
+ const radio = isFocused ? import_picocolors.default.blue(S_RADIO_ACTIVE) : import_picocolors.default.dim(" ");
82
91
  const name = sub.name.padEnd(maxName);
83
92
  let suffix = "";
84
- if (sub.arg) {
85
- if (isFocused && input.length > 0) suffix = ` ${import_picocolors.default.cyan(truncate(input, (stdout.columns || 80) - maxName - 9, true))}`;
93
+ if (isFocused && sub.arg) {
94
+ if (isFocused && input.length > 0) suffix = ` ${import_picocolors.default.blue(truncate(input, (stdout.columns || 80) - maxName - 9, true))}`;
86
95
  else suffix = ` ${import_picocolors.default.dim(sub.arg)}`;
87
- } else if (sub.hint) suffix = ` ${import_picocolors.default.dim(sub.hint)}`;
88
- if (isFocused) lines.push(`${import_picocolors.default.gray(S_BAR)} ${radio} ${import_picocolors.default.reset(name)}${suffix}`);
89
- else lines.push(`${import_picocolors.default.gray(S_BAR)} ${radio} ${import_picocolors.default.dim(name)}${suffix}`);
96
+ } else if (isFocused && sub.hint) suffix = ` ${import_picocolors.default.dim(sub.hint)}`;
97
+ lines.push(renderOption(radio, name, isFocused, suffix));
90
98
  }
91
- const footer = [`${import_picocolors.default.gray(S_BAR)} ${import_picocolors.default.dim(focused.description)}`];
99
+ const footer = [guidePrefix() + import_picocolors.default.dim(focused.description), ...instructions(SELECT_INSTRUCTIONS)];
92
100
  if (focused.arg) {
93
- if (focused.arg.startsWith("<")) footer.push(`${import_picocolors.default.gray(S_BAR)} ${import_picocolors.default.dim(`Type ${focused.arg}, then press Enter`)}`);
94
- else footer.push(`${import_picocolors.default.gray(S_BAR)} ${import_picocolors.default.dim(`Type ${focused.arg}, then press Enter, or press Enter to skip`)}`);
101
+ if (focused.arg.startsWith("<")) footer.push(`${guidePrefix()}${import_picocolors.default.dim(`Type ${focused.arg}, then press Enter`)}`);
102
+ else footer.push(`${guidePrefix()}${import_picocolors.default.dim(`Type ${focused.arg}, then press Enter, or press Enter to skip`)}`);
95
103
  }
96
- writeFrame(`${import_picocolors.default.cyan(S_STEP_ACTIVE)} ${import_picocolors.default.cyan(title)}`, lines, footer);
104
+ writeFrame(promptTitle(import_picocolors.default.blue(title), "active", { output: stdout }), lines, footer);
97
105
  }
98
106
  function renderFlags() {
99
107
  const flags = selectedSub.flags;
@@ -104,19 +112,19 @@ function promptSubcommand(command, subcommands) {
104
112
  const flag = flags[i];
105
113
  const state = flagStates[i];
106
114
  const isFocused = i === cursor;
107
- const checkbox = state.enabled ? import_picocolors.default.green(S_CHECKBOX_ACTIVE) : isFocused ? S_CHECKBOX_INACTIVE : import_picocolors.default.dim(S_CHECKBOX_INACTIVE);
115
+ const checkbox = state.enabled ? import_picocolors.default.blue(S_CHECKBOX_SELECTED) : isFocused ? import_picocolors.default.blue(S_CHECKBOX_INACTIVE) : import_picocolors.default.dim(S_CHECKBOX_INACTIVE);
108
116
  const name = flag.name.padEnd(maxFlag);
109
117
  let valueText = "";
110
118
  if (flag.arg) {
111
119
  if (state.enabled && state.value.length > 0) {
112
120
  const value = truncate(state.value, (stdout.columns || 80) - maxFlag - 9, true);
113
- valueText = ` ${isFocused ? import_picocolors.default.cyan(value) : import_picocolors.default.dim(value)}`;
121
+ valueText = ` ${isFocused ? import_picocolors.default.blue(value) : import_picocolors.default.dim(value)}`;
114
122
  } else valueText = ` ${import_picocolors.default.dim(flag.arg)}`;
115
123
  }
116
- if (isFocused) lines.push(`${import_picocolors.default.gray(S_BAR)} ${checkbox} ${import_picocolors.default.reset(name)}${valueText}`);
117
- else lines.push(`${import_picocolors.default.gray(S_BAR)} ${checkbox} ${import_picocolors.default.dim(name)}${valueText}`);
124
+ const pointer = isFocused ? import_picocolors.default.blue(S_RADIO_ACTIVE) : " ";
125
+ lines.push(renderOption(pointer + " " + checkbox, name, isFocused, valueText));
118
126
  }
119
- writeFrame(headerLine(), lines, [`${import_picocolors.default.gray(S_BAR)} ${import_picocolors.default.dim(focused.description)}`]);
127
+ writeFrame(headerLine(), lines, [guidePrefix() + import_picocolors.default.dim(focused.description), ...instructions(MULTISELECT_INSTRUCTIONS)]);
120
128
  }
121
129
  function renderMultiSelect() {
122
130
  const options = selectedSub.followUp.options;
@@ -126,21 +134,21 @@ function promptSubcommand(command, subcommands) {
126
134
  const opt = options[i];
127
135
  const selected = multiSelectStates[i];
128
136
  const isFocused = i === cursor;
129
- const checkbox = selected ? import_picocolors.default.green(S_CHECKBOX_ACTIVE) : isFocused ? S_CHECKBOX_INACTIVE : import_picocolors.default.dim(S_CHECKBOX_INACTIVE);
137
+ const checkbox = selected ? import_picocolors.default.blue(S_CHECKBOX_SELECTED) : isFocused ? import_picocolors.default.blue(S_CHECKBOX_INACTIVE) : import_picocolors.default.dim(S_CHECKBOX_INACTIVE);
130
138
  const label = opt.label.padEnd(maxLabel);
131
- if (isFocused) lines.push(`${import_picocolors.default.gray(S_BAR)} ${checkbox} ${import_picocolors.default.reset(label)}`);
132
- else lines.push(`${import_picocolors.default.gray(S_BAR)} ${checkbox} ${import_picocolors.default.dim(label)}`);
139
+ const pointer = isFocused ? import_picocolors.default.blue(S_RADIO_ACTIVE) : " ";
140
+ lines.push(renderOption(pointer + " " + checkbox, label, isFocused));
133
141
  }
134
- writeFrame(headerLine(), lines, [`${import_picocolors.default.gray(S_BAR)} ${import_picocolors.default.dim("space to toggle, enter to confirm")}`]);
142
+ writeFrame(headerLine(), lines, instructions(MULTISELECT_INSTRUCTIONS));
135
143
  }
136
144
  function renderRepeatInput() {
137
145
  const followUp = selectedSub.followUp;
138
146
  const lines = [];
139
- for (const entry of repeatEntries) lines.push(`${import_picocolors.default.gray(S_BAR)} ${import_picocolors.default.dim(entry)}`);
140
- if (repeatInput.length > 0) lines.push(`${import_picocolors.default.gray(S_BAR)} ${import_picocolors.default.cyan(truncate(repeatInput, (stdout.columns || 80) - 4, true))}`);
141
- else lines.push(`${import_picocolors.default.gray(S_BAR)} ${import_picocolors.default.dim(followUp.placeholder)}`);
147
+ for (const entry of repeatEntries) lines.push(`${guidePrefix()}${import_picocolors.default.dim(entry)}`);
148
+ if (repeatInput.length > 0) lines.push(`${guidePrefix()}${import_picocolors.default.blue(truncate(repeatInput, (stdout.columns || 80) - 4, true))}`);
149
+ else lines.push(`${guidePrefix()}${import_picocolors.default.dim(followUp.placeholder)}`);
142
150
  cursor = lines.length - 1;
143
- writeFrame(headerLine(), lines, [`${import_picocolors.default.gray(S_BAR)} ${import_picocolors.default.dim("enter to add, empty to finish")}`]);
151
+ writeFrame(headerLine(), lines, instructions(["Enter add", "Empty input finish"]));
144
152
  }
145
153
  function cleanup() {
146
154
  if (resolved) return;
@@ -163,7 +171,7 @@ function promptSubcommand(command, subcommands) {
163
171
  if (typeof value === "string") summary += ` ${value}`;
164
172
  }
165
173
  if (result.extraArgs && result.extraArgs.length > 0) summary += ` ${result.extraArgs.join(" ")}`;
166
- stdout.write(`${import_picocolors.default.gray(S_STEP_SUBMIT)} ${import_picocolors.default.dim(title)} ${import_picocolors.default.dim("·")} ${import_picocolors.default.dim(summary)}\n`);
174
+ stdout.write(wrapTextWithPrefix(stdout, `${import_picocolors.default.dim(title)} ${import_picocolors.default.dim("·")} ${import_picocolors.default.dim(summary)}`, " ", `${import_picocolors.default.gray(S_STEP_SUBMIT)} `) + "\n");
167
175
  }
168
176
  resolve(result);
169
177
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "void",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/voidzero-dev/void.git",
@@ -339,11 +339,11 @@
339
339
  "@cloudflare/workers-auth": "0.9.5",
340
340
  "@cloudflare/workers-types": "^4.20260702.1",
341
341
  "@napi-rs/keyring": "2.1.0",
342
- "@void/deploy-cloudflare": "0.23.0",
343
- "@void/deploy-core": "0.23.0",
344
- "@void/edge": "0.23.0",
345
- "@void/isr": "0.23.0",
346
- "@void/platform": "0.23.0",
342
+ "@void/deploy-cloudflare": "0.24.0",
343
+ "@void/deploy-core": "0.24.0",
344
+ "@void/edge": "0.24.0",
345
+ "@void/isr": "0.24.0",
346
+ "@void/platform": "0.24.0",
347
347
  "acorn": "^8.18.0",
348
348
  "better-auth": "^1.7.6",
349
349
  "better-sqlite3": "^13.0.3",
@@ -370,13 +370,12 @@
370
370
  "undici": "8.11.2"
371
371
  },
372
372
  "devDependencies": {
373
- "@clack/core": "^1.5.1",
374
- "@clack/prompts": "^1.8.1",
375
373
  "@electric-sql/pglite": "0.5.8",
376
374
  "@types/better-sqlite3": "^9.6.0",
377
375
  "@types/node": "^26.6.3",
378
376
  "@types/pg": "^8.23.1",
379
377
  "@types/proper-lockfile": "^4.1.4",
378
+ "@voidzero-dev/vite-plus-prompts": "0.0.0",
380
379
  "arktype": "^2.2.5",
381
380
  "ci-info": "^4.4.0",
382
381
  "estree-walker": "^3.0.3",
@@ -394,7 +393,7 @@
394
393
  "zod": "^4.6.5"
395
394
  },
396
395
  "peerDependencies": {
397
- "@void/md": "0.23.0",
396
+ "@void/md": "0.24.0",
398
397
  "arktype": ">=2.0.0",
399
398
  "valibot": ">=1.0.0-beta.7",
400
399
  "vite": "^8.0.0",
@@ -16,7 +16,12 @@ workflows. Mention Wrangler only when the user needs to recognize a literal
16
16
  configuration file or environment variable.
17
17
 
18
18
  Use `void` and `@void/*` in imports, examples, and package manifests. Follow the
19
- project's existing framework and deployment destination.
19
+ project's existing framework and deployment destination. Deploying to a Void
20
+ platform requires an explicit `void connect <url>` first; never infer a platform
21
+ from old examples, tokens, or project IDs.
22
+ For a one-time deployment elsewhere, connect to that platform, then set
23
+ `VOID_API_URL` for the deploy command and pass `--project <slug>`. This preserves
24
+ the app's existing saved destination, including when the target project is new.
20
25
 
21
26
  ## Task Routing
22
27
 
@@ -55,6 +55,23 @@ To set up deployment later, use `void connect --platform cloudflare` or `void co
55
55
 
56
56
  Owners can [share a platform project](./project-collaboration.md) with readers, collaborators, and project administrators. This does not apply to direct Cloudflare deployments.
57
57
 
58
+ ### Deploy to another Void platform
59
+
60
+ Connect to the additional platform once, then select it for the deployment:
61
+
62
+ ```sh
63
+ void connect https://second.example.com
64
+ VOID_API_URL=https://second.example.com void deploy --platform void --project my-app
65
+ ```
66
+
67
+ Void uses `my-app` on that platform and offers to create it if needed. Your saved
68
+ destination stays unchanged, including when a new project is created or deployment
69
+ fails. The next plain `void deploy` still uses your original destination.
70
+
71
+ Pass `--project` or `VOID_PROJECT` when selecting a different platform; project links
72
+ belong to their original platform. Each platform has its own projects, secrets, and
73
+ data. For an app without a saved destination, its first deployment still saves a link.
74
+
58
75
  ### Migrations
59
76
 
60
77
  Deploy applies pending SQL migrations from `db/migrations/`. If your schema has changes without a migration, deploy stops. Run `void db generate`, review and commit the SQL, then deploy again. See the [Database guide](./database.md).
@@ -84,6 +101,11 @@ If none is set, Void asks you to choose a project. Direct Cloudflare deploys use
84
101
 
85
102
  ### CI preparation
86
103
 
104
+ For a Void platform, run `void connect "$VOID_API_URL" --no-login` in each fresh
105
+ CI environment before deployment. Set `VOID_API_URL` to the platform you selected;
106
+ there is no default hosted platform. The generated GitHub Actions workflow
107
+ includes this connection step.
108
+
87
109
  If your CI pipeline runs typechecking or other static analysis before deploy, run `void prepare` after install to generate the `.void/` artifacts without booting Vite.
88
110
 
89
111
  ### Environment variables
@@ -35,7 +35,7 @@ Header rules do not apply to ISR cache responses.
35
35
 
36
36
  ### Blocked headers
37
37
 
38
- `Set-Cookie` and `Clear-Site-Data` are ignored in header rules. On a shared domain such as `*.void.app`, a rule could otherwise affect cookies or data belonging to another project's subdomain.
38
+ `Set-Cookie` and `Clear-Site-Data` are ignored in header rules. On a shared domain such as `*.apps.example.com`, a rule could otherwise affect cookies or data belonging to another project's subdomain.
39
39
 
40
40
  ## Framework `_headers` files
41
41
 
@@ -50,7 +50,9 @@ Use a mail subdomain to keep existing mail on `acme.com` with its current provid
50
50
 
51
51
  Sign in with `void cloudflare login`, or use a `CLOUDFLARE_API_TOKEN` with **Email Routing Edit** and **Email Sending Edit** in addition to deploy permissions. If your browser session lacks email permissions, log out and sign in again. Global API Keys aren't supported.
52
52
 
53
- Run `void deploy --platform cloudflare`. Void shows the domain, routing, sending, DNS, and handler addresses it would configure. Accept to set up email and deploy. Later deploys reuse the setup. Without `email.from`, or if you decline the initial setup, the app deploys without email.
53
+ Run `void deploy --platform cloudflare`. Void shows the domain, routing, sending, DNS, and handler addresses it would configure. Accept to set up email and deploy. Later deploys reuse the setup. If you decline the initial setup, the app deploys without email.
54
+
55
+ Without `email.from`, Void skips automatic setup and preserves authored `cloudflare.send_email` bindings and `cloudflare.addresses`; check their readiness yourself. `--require-email` still refuses deployment when automatic setup cannot be verified.
54
56
 
55
57
  If subdomain setup needs a dashboard step, follow the checklist under **Email → Settings → Subdomains**, then retry. After DNS changes, check readiness with:
56
58
 
@@ -78,6 +80,8 @@ Setup enables the zone's plus addressing so `support+T-42@mail.acme.com` reaches
78
80
 
79
81
  On Workers Free, `sendEmail()` can send to verified destinations listed under **Email Routing → Destination addresses** in Cloudflare.
80
82
 
83
+ Configure your sender domain's SPF, DKIM, and DMARC records using [Cloudflare's email authentication guidance](https://developers.cloudflare.com/email-service/concepts/email-authentication/), then check the authentication results in a received message's original headers. A successful `sendEmail()` result confirms provider acceptance; it does not confirm delivery or authentication readiness.
84
+
81
85
  Sending to arbitrary recipients requires [Workers Paid](https://dash.cloudflare.com/?to=/:account/workers/plans) and Email Sending onboarding. After upgrading, run `void email setup --platform cloudflare`. Void doesn't retry onboarding during ordinary deploys.
82
86
 
83
87
  Existing DMARC or bounce-domain records can block onboarding; the checklist identifies conflicts to resolve. Inbound mail remains available.
@@ -34,7 +34,7 @@ Check both failure shapes: `result.error` reports a request failure, while `resu
34
34
 
35
35
  ## Setup {#setup}
36
36
 
37
- On a platform with email enabled, deploy without additional email configuration. Ask your administrator for its shared mail domain. Administrators [enable email during installation or upgrade](/guide/platform/installation/credentials#runtime-token-permissions); Void Cloud uses `mail.void.cloud`.
37
+ On a platform with email enabled, deploy without additional email configuration. Ask your administrator for its shared mail domain. Administrators [enable email during installation or upgrade](/guide/platform/installation/credentials#runtime-token-permissions).
38
38
 
39
39
  Each project on an email-enabled platform has:
40
40
 
@@ -8,6 +8,17 @@ Project Zero Trust puts Cloudflare Access in front of the hostnames of projects
8
8
 
9
9
  This is separate from the Access login and protection for the platform API. Projects deployed directly to Cloudflare are not affected.
10
10
 
11
+ New installations choose app access during `void platform install`. The installer
12
+ reuses the platform policy or helps you select separate company rules, and finishes
13
+ setup before opening traffic. Existing installations keep their settings when
14
+ upgraded; use this page to configure app access if it has not been set up yet.
15
+
16
+ If you choose public apps in an account with Default-Deny, Void manages scoped
17
+ public rules instead. New projects and custom domains remain public automatically.
18
+ Keep these rules enabled while Default-Deny is on; removing them can block traffic.
19
+ Public-only installations need no identity providers or company policies until you
20
+ choose to protect apps. To renew their token in Admin, leave the policy fields blank.
21
+
11
22
  ## Requirements
12
23
 
13
24
  - A Cloudflare Zero Trust organization in the account that hosts the platform, with the identity providers and reusable Access policies you want to use. Select at least one Allow policy that matches people by identity. Void rejects Bypass and Service Auth policies, and rules that allow Everyone or any service token.
@@ -116,22 +127,29 @@ If a project must accept such requests:
116
127
 
117
128
  Void creates and owns these self-hosted applications in your Zero Trust organization:
118
129
 
119
- | Application | Covers | Policy | Exists |
120
- | -------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------ |
121
- | Shared project application | `*.<your domain>` and this installation's `workers.dev` testing URLs | Your selected policies and identity providers | Once, while Zero Trust is enabled |
122
- | Public exception | One public project's `<slug>.<your domain>` and `workers.dev` testing URL | A Bypass policy for Everyone, named `Void project is public` | One for each public project |
123
- | Custom domain application | All custom domains of one protected project | Your selected policies and identity providers | One for each protected project that has custom domains |
124
- | Health check exception | `void-platform-health.<your domain>/health` | A Bypass policy for Everyone, named `Void platform health check` | Once, while Zero Trust is enabled on an installation with a domain |
130
+ | Application | Covers | Policy | Exists |
131
+ | --------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------ |
132
+ | Shared project application | `*.<your domain>` and this installation's `workers.dev` testing URLs | Your selected policies and identity providers | Once for protected-app configurations |
133
+ | Public app management check | A reserved, unused dispatch check path | Deny Everyone; verifies management permissions without opening hostnames | Once, instead of the shared wildcard in public-only mode |
134
+ | Public exception | One public project's `<slug>.<your domain>` and `workers.dev` testing URL | A Bypass policy for Everyone, named `Void project is public` | One for each public project |
135
+ | Custom domain application | All managed custom domains of one project | Company policies for protected projects; Bypass for public ones | One for each project with managed custom domains |
136
+ | Health check exception | `void-platform-health.<your domain>/health` | A Bypass policy for Everyone, named `Void platform health check` | Once, while Zero Trust is enabled on an installation with a domain |
137
+
138
+ Installations configured through the installer also manage public custom-domain
139
+ rules. Their custom-domain application changes policy when a project switches
140
+ between public and protected access.
125
141
 
126
- Custom domains of a public project need no Access application.
142
+ If the installer already configured the same health-check exception, Zero Trust
143
+ reuses it. Disabling project Zero Trust leaves that existing exception in place
144
+ so platform health checks continue to work.
127
145
 
128
146
  The number of applications Void needs is:
129
147
 
130
148
  ```text
131
- 2 + public projects + protected projects that have custom domains
149
+ 1 + health-check applications + public projects + projects with managed custom domains
132
150
  ```
133
151
 
134
- Use 1 instead of 2 on an installation without a domain. For example, 40 projects with 10 public and 5 protected projects that have custom domains need 2 + 10 + 5 = 17 applications. Void stops with an error when the account has more than 500 Access applications.
152
+ The first application is the shared company policy or the public-app management check. Count a health exception once, including when it is shared with the installer. For example, 40 projects with one health exception, 10 public projects, and 5 projects with managed custom domains need 1 + 1 + 10 + 5 = 17 applications. Void stops with an error when the account has more than 500 Access applications.
135
153
 
136
154
  #### Recognizing Void's Applications
137
155
 
@@ -49,7 +49,7 @@ void platform install \
49
49
  --yes
50
50
  ```
51
51
 
52
- Mutations require `--yes` in CI; a read-only `--plan` does not. Custom runtimes also require `--runtime <directory>`. For a new installation, run `--plan` with the same account, name, and domain options as the unattended install. If you use `--auth-config`, pass the same file to both commands. Register the printed callback when creating a login provider's OAuth or OIDC client manually; automatic Access setup manages its own application. A custom API hostname is optional.
52
+ Mutations require `--yes` in CI; a read-only `--plan` does not. Custom runtimes also require `--runtime <directory>`. For a new installation, run `--plan` with the same account, name, and domain options as the unattended install. If you use `--auth-config`, pass the same file to both commands. Register `<platform-api-url>/auth/callback` when creating a login provider's OAuth or OIDC client manually; automatic Access setup manages its own application. A custom API hostname is optional.
53
53
 
54
54
  :::
55
55
 
@@ -8,15 +8,20 @@ Keep one password-manager entry for this platform. Paste the saved values into t
8
8
 
9
9
  ## Cloudflare API Tokens {#runtime-token-permissions}
10
10
 
11
- For a domain installation, create two custom tokens using Cloudflare's [API token setup](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/). Name them **Void Platform Management** and **Void Platform Runtime**. Scope them to your selected account and application zone.
11
+ For a domain installation or Cloudflare Access setup, the installer guides you through creating separate management and runtime tokens using Cloudflare's [API token setup](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/). Its links preselect the permissions and suggest names based on your platform's display name. Scope the tokens to your selected account and, when using a domain, its zone, save them in your password manager, and paste them into the masked CLI prompts.
12
12
 
13
- For workers.dev testing with the default API hostname, browser login can authorize installation: you only need to create the runtime token and R2 credentials below. Skip the zone permissions until you add a domain.
13
+ For workers.dev testing with the default API hostname, browser login can authorize
14
+ infrastructure setup. Void also checks account-wide Access requirements; if the
15
+ current credential cannot read them, the installer asks for a scoped Access token.
16
+ Protected apps and public apps under Default-Deny need an ongoing app-management
17
+ token, stored encrypted. Access setup requires a management token even on
18
+ workers.dev. Skip zone permissions until you add a domain.
14
19
 
15
20
  Browser login does not grant AI Gateway access. Include **Account → AI Gateway → Edit** on the runtime token so Void can verify and create that resource.
16
21
 
17
22
  The management token lets your CLI install and maintain the platform. The runtime token is stored as a Worker secret so the platform can deploy apps after you close your terminal. They are separate credentials.
18
23
 
19
- The installer’s runtime-token link preselects required account permissions. Compare the form with the checklist shown beside the link, and select the indicated zone for a domain installation.
24
+ Compare each token's form with the permission checklist below, and select the indicated account and zone for a domain installation. The installer saves a management token entered at the prompt in this installation’s encrypted local checkpoint and reuses it on resume. Upgrades, repairs, and other lifecycle maintenance also use that saved management token. During installation, a rejected token can be replaced at the prompt. An explicit `CLOUDFLARE_API_TOKEN` or `CF_API_TOKEN` takes precedence. Keep your password-manager copy for maintenance or another machine; scripts supply it through `CLOUDFLARE_API_TOKEN`.
20
25
 
21
26
  ::: details Permissions to select for each token
22
27
 
@@ -39,6 +44,8 @@ Use the following permissions for the core platform. Cloudflare may label write
39
44
  | Zone | Workers Routes | Edit | — |
40
45
  | Zone | Cache Purge | — | Purge |
41
46
 
47
+ When Void configures Access in the installation account, the initial management-token link also selects **Account → Access: Apps and Policies → Edit** and **Account → Access: Organizations, Identity Providers, and Groups → Read**. Protection adds **Account → Access: Service Tokens → Edit**. When the authentication configuration or saved setup selects existing applications, the link requests Read access instead of Edit. These permissions belong to the management token; the runtime token does not need them.
48
+
42
49
  The management token also needs **Zone Edit** with authority to create zones if you ask Void to create the zone. If it already exists, use the selected zone with Zone Read and DNS Edit. Nested application domains additionally need **SSL and Certificates: Read** on the management token. A custom runtime that enables custom project domains needs **SSL and Certificates: Edit** on the runtime token; the core runtime does not enable that feature.
43
50
 
44
51
  Void checks access before provisioning. If it reports a missing permission, update the token's permissions for the selected account or zone and retry.
@@ -66,9 +66,14 @@ void platform repair <installation-id> --dashboard-url https://dash.example.com
66
66
 
67
67
  The origin must contain no credentials, path, query, or fragment. Void saves it
68
68
  for login callbacks and keeps it across upgrades and repairs. The command does
69
- not create a dashboard Worker or DNS records; deploy that app separately. Omit
70
- the option to keep the saved origin. The dashboard provides sign-in, linked
71
- login methods, and sign-out; use the CLI for user project and team management.
69
+ not create a dashboard Worker or DNS records. Follow the source app’s
70
+ [standalone dashboard preparation](https://github.com/voidzero-dev/void/tree/main/platform/packages/dashboard#standalone-deployment)
71
+ to deploy your own copy with an unused Worker name, your dashboard hostname,
72
+ and the installed API Worker’s service binding. Set its `API_URL` to the public
73
+ API origin, `DASHBOARD_URL` to the dashboard origin, and `SITE_DOMAIN` to the
74
+ installation’s application domain. Omit the option to keep the saved origin.
75
+ The dashboard provides sign-in, linked login methods, and sign-out; use the CLI
76
+ for user project and team management.
72
77
 
73
78
  If Access protects the platform, its application must cover this dashboard
74
79
  origin too. Configure the origin when installing protection. To change it on an
@@ -33,8 +33,8 @@ void project token create --name ci --expires-in 30
33
33
  ```
34
34
 
35
35
  Store the printed `VOID_TOKEN` and `VOID_API_URL` in the CI secret manager, then
36
- use `void connect <url> --no-login` in a fresh checkout if connection metadata
37
- is not committed. Rotate with `void project token renew <id>` and revoke with
36
+ run `void connect "$VOID_API_URL" --no-login` before deploying in each fresh CI
37
+ environment. Project files do not replace this connection step. Rotate with `void project token renew <id>` and revoke with
38
38
  `void project token revoke <id>`. A human login token is not a CI credential.
39
39
 
40
40
  If Cloudflare Access protects the platform, Access proof and the project