@ekanos/cli 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +13 -13
  2. package/dist/auth/credential-store.d.ts +1 -1
  3. package/dist/auth/credential-store.js +3 -2
  4. package/dist/auth/credential-store.js.map +1 -1
  5. package/dist/auth/device-flow.d.ts +1 -1
  6. package/dist/auth/device-flow.js +11 -10
  7. package/dist/auth/device-flow.js.map +1 -1
  8. package/dist/auth/{fusion-api.d.ts → ekanos-api.d.ts} +9 -9
  9. package/dist/auth/{fusion-api.js → ekanos-api.js} +28 -28
  10. package/dist/auth/ekanos-api.js.map +1 -0
  11. package/dist/auth/session.d.ts +5 -5
  12. package/dist/auth/session.js +21 -11
  13. package/dist/auth/session.js.map +1 -1
  14. package/dist/banner.d.ts +1 -1
  15. package/dist/banner.js +4 -13
  16. package/dist/banner.js.map +1 -1
  17. package/dist/bin.js.map +1 -1
  18. package/dist/commands/dev.d.ts +2 -2
  19. package/dist/commands/dev.js +11 -3
  20. package/dist/commands/dev.js.map +1 -1
  21. package/dist/commands/init.js +72 -30
  22. package/dist/commands/init.js.map +1 -1
  23. package/dist/commands/login.d.ts +1 -1
  24. package/dist/commands/login.js +28 -17
  25. package/dist/commands/login.js.map +1 -1
  26. package/dist/commands/logout.js +31 -5
  27. package/dist/commands/logout.js.map +1 -1
  28. package/dist/commands/publish.d.ts +2 -2
  29. package/dist/commands/publish.js +28 -15
  30. package/dist/commands/publish.js.map +1 -1
  31. package/dist/commands/sources.js +26 -16
  32. package/dist/commands/sources.js.map +1 -1
  33. package/dist/commands/status.d.ts +1 -1
  34. package/dist/commands/status.js +56 -45
  35. package/dist/commands/status.js.map +1 -1
  36. package/dist/commands/test.js +15 -5
  37. package/dist/commands/test.js.map +1 -1
  38. package/dist/commands/upgrade.js +86 -54
  39. package/dist/commands/upgrade.js.map +1 -1
  40. package/dist/commands/use.js +11 -3
  41. package/dist/commands/use.js.map +1 -1
  42. package/dist/commands/validate.js +21 -3
  43. package/dist/commands/validate.js.map +1 -1
  44. package/dist/commands/whoami.d.ts +1 -1
  45. package/dist/commands/whoami.js +17 -7
  46. package/dist/commands/whoami.js.map +1 -1
  47. package/dist/compatibility.js.map +1 -1
  48. package/dist/context.js +10 -3
  49. package/dist/context.js.map +1 -1
  50. package/dist/delegate.js.map +1 -1
  51. package/dist/did-you-mean.js +26 -15
  52. package/dist/did-you-mean.js.map +1 -1
  53. package/dist/exit-codes.d.ts +10 -10
  54. package/dist/findings-renderer.js +6 -4
  55. package/dist/findings-renderer.js.map +1 -1
  56. package/dist/generated/fa-free-names.d.ts +1 -1
  57. package/dist/generated/fa-free-names.js +1 -1
  58. package/dist/generated/fa-free-names.js.map +1 -1
  59. package/dist/harness-scaffold.d.ts +1 -1
  60. package/dist/harness-scaffold.js +4 -2
  61. package/dist/harness-scaffold.js.map +1 -1
  62. package/dist/icon-findings.d.ts +1 -1
  63. package/dist/icon-findings.js +2 -2
  64. package/dist/icon-findings.js.map +1 -1
  65. package/dist/index.js +2 -2
  66. package/dist/index.js.map +1 -1
  67. package/dist/merge-manifest.js.map +1 -1
  68. package/dist/pack.js +1 -1
  69. package/dist/pack.js.map +1 -1
  70. package/dist/project-checks.js.map +1 -1
  71. package/dist/project.d.ts +7 -84
  72. package/dist/project.js +3 -3
  73. package/dist/project.js.map +1 -1
  74. package/dist/publish-api.d.ts +5 -5
  75. package/dist/publish-api.js +27 -33
  76. package/dist/publish-api.js.map +1 -1
  77. package/dist/safe-fs.js.map +1 -1
  78. package/dist/schema-skew.js.map +1 -1
  79. package/dist/source-locator.js.map +1 -1
  80. package/dist/sources-api.d.ts +3 -3
  81. package/dist/sources-api.js +11 -14
  82. package/dist/sources-api.js.map +1 -1
  83. package/dist/templates.js.map +1 -1
  84. package/dist/test-report.js.map +1 -1
  85. package/dist/toolchain-api.d.ts +4 -4
  86. package/dist/toolchain-api.js +1 -1
  87. package/dist/toolchain-api.js.map +1 -1
  88. package/dist/toolchain-resolve.js.map +1 -1
  89. package/dist/toolchain.d.ts +5 -5
  90. package/dist/ui.d.ts +36 -0
  91. package/dist/ui.js +79 -10
  92. package/dist/ui.js.map +1 -1
  93. package/dist/update-notice.d.ts +1 -1
  94. package/dist/update-notice.js.map +1 -1
  95. package/dist/validate-findings.js.map +1 -1
  96. package/dist/wizard.js.map +1 -1
  97. package/package.json +15 -20
  98. package/templates/AGENTS.md.tmpl +13 -8
  99. package/templates/CLAUDE.md.tmpl +1 -1
  100. package/templates/claude-skill.md.tmpl +2 -2
  101. package/templates/harness/next.config.mjs.tmpl +1 -1
  102. package/templates/package.json.tmpl +6 -5
  103. package/templates/pnpm-workspace.yaml.tmpl +8 -0
  104. package/templates/skills/build-integration/SKILL.md +3 -3
  105. package/templates/skills/build-integration/references/api-wiring.md +2 -2
  106. package/templates/skills/build-integration/references/widget-design.md +1 -1
  107. package/dist/auth/fusion-api.js.map +0 -1
@@ -3,7 +3,7 @@
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
5
  "type": "module",
6
- "description": "A Fusion integration built with @ekanos/sdk.",
6
+ "description": "An Ekanos integration built with @ekanos/sdk.",
7
7
  "scripts": {
8
8
  "typecheck": "tsc --noEmit",
9
9
  "test": "vitest run",
@@ -11,18 +11,19 @@
11
11
  "prepare": "node scripts/setup-hooks.mjs"
12
12
  },
13
13
  "dependencies": {
14
- "@ekanos/sdk": "^0.3.0",
15
- "@ekanos/ui": "^0.3.0"
14
+ "@ekanos/sdk": "^0.4.0",
15
+ "@ekanos/ui": "^0.4.0"
16
16
  },
17
17
  "devDependencies": {
18
- "@ekanos/cli": "^0.3.1",
19
- "@ekanos/harness": "^0.3.0",
18
+ "@ekanos/cli": "^0.3.4",
19
+ "@ekanos/harness": "^0.4.0",
20
20
  "@hookform/resolvers": "^5.2.2",
21
21
  "@tailwindcss/postcss": "^4.3.3",
22
22
  "@tanstack/react-query": "^5.101.4",
23
23
  "@types/react": "^19.2.0",
24
24
  "@types/react-dom": "^19.2.0",
25
25
  "next": "^16.0.0",
26
+ "next-intl": "^4.14.7",
26
27
  "react": "^19.2.8",
27
28
  "react-dom": "^19.2.8",
28
29
  "react-hook-form": "^7.68.0",
@@ -0,0 +1,8 @@
1
+ # pnpm 10+ refuses to run dependency build scripts it has not been told to
2
+ # allow, and `vitest` pulls in `esbuild`, whose install script compiles its
3
+ # binary. Without this file the first `pnpm install` writes a placeholder
4
+ # value here instead, and every later `ekanos dev` / `ekanos test` fails
5
+ # with ERR_PNPM_IGNORED_BUILDS until a human edits it. npm and yarn ignore
6
+ # this file.
7
+ allowBuilds:
8
+ esbuild: true
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: build-integration
3
- description: Interview-driven workflow for building and publishing a Fusion integration with the Ekanos SDK. Use when building a new integration from scratch ("build an integration for X", "connect a service like Stripe or Slack", a fresh ekanos scaffold with no substance yet), when adding or changing API wiring ("add an endpoint", "wire this to the API", "add a webhook/tool/schedule"), when researching the target service's API ("how does this API paginate", "what auth does it use", "where are the docs"), or when publishing.
3
+ description: Interview-driven workflow for building and publishing an Ekanos integration with the Ekanos SDK. Use when building a new integration from scratch ("build an integration for X", "connect a service like Stripe or Slack", a fresh ekanos scaffold with no substance yet), when adding or changing API wiring ("add an endpoint", "wire this to the API", "add a webhook/tool/schedule"), when researching the target service's API ("how does this API paginate", "what auth does it use", "where are the docs"), or when publishing.
4
4
  ---
5
5
 
6
- # Build a Fusion Integration
6
+ # Build an Ekanos Integration
7
7
 
8
8
  Guide the user from intent to a published integration. The user supplies
9
9
  judgment (what service, what it should show and do); the CLI supplies
@@ -164,7 +164,7 @@ All of these, in order, all green:
164
164
 
165
165
  ## Phase 7: Publish — gated
166
166
 
167
- Publishing sends the project to the Fusion host and is versioned immutably.
167
+ Publishing sends the project to the Ekanos host and is versioned immutably.
168
168
  NEVER run it on your own judgment. Show the user:
169
169
 
170
170
  - the verify results (1–4 above)
@@ -97,11 +97,11 @@ code. This file is the order of work and the traps.
97
97
 
98
98
  ## Verifying against the real API — live mode
99
99
 
100
- The harness mocks Fusion, never the vendor. Do not tell the developer that
100
+ The harness mocks Ekanos, never the vendor. Do not tell the developer that
101
101
  local work is mock-only: opt the integration into live mode (a `live` block
102
102
  on its `harness.config.ts` entry — see AGENTS.md "Live mode") and the
103
103
  toolbar's fixtures ⇄ live switch runs real widget queries against the real
104
- API, gated only by the declared `egress`. Use fixtures for the Fusion-side
104
+ API, gated only by the declared `egress`. Use fixtures for the Ekanos-side
105
105
  state widgets need first, and for webhook/schedule handlers (the Triggers
106
106
  surface runs those against the mock context by design). `actions[name]`
107
107
  handlers follow the same rule: `useIntegrationAction()` calls run against
@@ -105,7 +105,7 @@ pb-5`) instead of a hand-rolled `px-4`/`px-5` wrapper div.
105
105
  While iterating, expose a temporary way to flip states (a dev-only prop or a
106
106
  mock switch) so the harness can show all four; remove it before publish.
107
107
 
108
- ## Fusion design rules (the harness applies the host theme)
108
+ ## Ekanos design rules (the harness applies the host theme)
109
109
 
110
110
  - **Semantic tokens only**: `bg-card`, `text-foreground`, `text-muted-foreground`,
111
111
  `border-border`, `bg-primary`, `text-destructive`. NEVER raw palette
@@ -1 +0,0 @@
1
- {"version":3,"file":"fusion-api.js","sourceRoot":"","sources":["../../src/auth/fusion-api.ts"],"names":[],"mappings":"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","sourcesContent":["import { authRequiredError, networkError, preconditionError } from '../errors';\n\n/**\n * The CLI's client for the four Fusion auth routes.\n *\n * ---------------------------------------------------------------------------\n * WIRE CONTRACT — the seam between this package and the Fusion web app.\n *\n * The server side is the host's `/api/auth/cli-<verb>` routes, and it ships on\n * a DIFFERENT BRANCH from this file. This block is the single place the shapes\n * are written down on the client side; if you change one side, change the\n * other and update this block. Nothing here imports host code — the two\n * halves are coupled only by HTTP, and the tests stub `fetch`, never the route.\n *\n * GET /api/auth/cli-config [no auth]\n * → 200 { auth0_domain, client_id, scope,\n * device_authorization_endpoint, token_endpoint } all strings\n * → 503 { error, code: \"CLI_NOT_CONFIGURED\" } deployment not provisioned\n *\n * POST /api/auth/cli-session [no auth]\n * ← { auth0_access_token: string }\n * → 200 { access_token, expires_at, user: { id, email } }\n * → 400 { error, code: \"INVALID_REQUEST\" } malformed / wrong shape\n * → 401 { error, code: \"SESSION_EXPIRED\" } Auth0 said no, OR no\n * Fusion account for it\n * → 503 { error, code: \"CLI_NOT_CONFIGURED\" } deployment not provisioned\n * → 500 { error, code: \"INTERNAL_ERROR\" }\n *\n * POST /api/auth/cli-refresh [no auth]\n * ← { auth0_refresh_token: string }\n * → 200 { access_token, expires_at, user: { id, email },\n * auth0_refresh_token } ← ROTATED; the presented one is dead\n * → 400 { error, code: \"INVALID_REQUEST\" }\n * → 401 { error, code: \"SESSION_EXPIRED\" } expired or revoked\n * → 404 { error, code: \"ACCOUNT_NOT_FOUND\",\n * auth0_refresh_token } no Fusion account\n * → 502 { error, code: \"TOKEN_EXCHANGE_FAILED\" } Auth0 returned no\n * rotated token\n * → 503 { error, code: \"CLI_NOT_CONFIGURED\" } deployment not provisioned\n * → 503 { error, code: \"REFRESH_INCOMPLETE\",\n * auth0_refresh_token } rotated, but /userinfo\n * did not answer; RETRYABLE\n * → 500 { error, code: \"INTERNAL_ERROR\",\n * auth0_refresh_token? } present iff rotation\n * completed before the throw\n *\n * !! cli-refresh MAY RETURN `auth0_refresh_token` ON ERROR RESPONSES, and a\n * client MUST persist it before surfacing the error. Once the server's\n * exchange with Auth0 succeeds the presented token is dead and the value\n * in that body is the ONLY copy in existence; discarding it on a failure\n * path destroys the session permanently. Note 503 has TWO codes here —\n * branch on `code`, not on the status alone.\n *\n * All three routes above read the Fusion CLI Auth0 application config, so all\n * three can answer 503 CLI_NOT_CONFIGURED. cli-whoami cannot — it reads no\n * Auth0 config, only the caller's bearer token.\n *\n * GET /api/auth/cli-whoami [Authorization: Bearer <access_token>]\n * → 200 { user: { id, email, name } }\n * → 401 { error, code: \"AUTH_REQUIRED\" } token absent or invalid\n * → 403 { error, code: \"MFA_REQUIRED\" }\n * → 404 { error, code: \"ACCOUNT_NOT_FOUND\" }\n * → 3xx treated as 401, see below\n *\n * `access_token` is a Fusion HS256 JWT. Every authenticated Fusion route\n * accepts it as `Authorization: Bearer` — see `resolveRequestSupabaseJwt` in\n * `@kit/supabase`, which both `requireUser` and the RLS server client consume.\n * ---------------------------------------------------------------------------\n *\n * Two rules hold everywhere in this file:\n *\n * - `redirect: 'manual'`. Fusion answers an unauthenticated page request with\n * a 307 to `/auth/sign-in`; Node's fetch follows redirects by default, so\n * without this a stale token would come back as a 200 full of HTML and be\n * read as success. A 3xx here is treated as \"not authenticated\", full stop.\n *\n * - No token is ever interpolated into an error message. The server's error\n * bodies are `{ error, code }` documents that do not echo credentials, and\n * the request bodies that DO carry credentials are never included in a\n * thrown message.\n */\n\nconst REQUEST_TIMEOUT_MS = 15_000;\n\nexport interface CliAuthConfig {\n auth0Domain: string;\n clientId: string;\n scope: string;\n deviceAuthorizationEndpoint: string;\n tokenEndpoint: string;\n}\n\nexport interface FusionSession {\n accessToken: string;\n expiresAt: string;\n userId: string;\n email: string | null;\n}\n\nexport interface FusionIdentity {\n id: string;\n email: string | null;\n name: string | null;\n}\n\n/** Fetch the device-flow parameters for a host. */\nexport async function fetchCliAuthConfig(host: string): Promise<CliAuthConfig> {\n const response = await request(`${host}/api/auth/cli-config`, {\n method: 'GET',\n headers: { accept: 'application/json' },\n });\n\n throwIfNotConfigured(response, host);\n\n if (!response.ok) {\n throw unexpectedStatus(host, '/api/auth/cli-config', response.status);\n }\n\n const body = await readJson(response, `${host}/api/auth/cli-config`);\n\n const config = {\n auth0Domain: str(body.auth0_domain),\n clientId: str(body.client_id),\n scope: str(body.scope),\n deviceAuthorizationEndpoint: str(body.device_authorization_endpoint),\n tokenEndpoint: str(body.token_endpoint),\n };\n\n if (Object.values(config).some((value) => value === null)) {\n throw preconditionError(\n `${host} returned an incomplete CLI login configuration.`,\n `Confirm the deployment is running a Fusion build that includes the ` +\n `/api/auth/cli-config route.`,\n );\n }\n\n // The endpoints come from the server, so they are attacker-controlled if the\n // host is. Pin them to the Auth0 domain the same response declares, so a\n // compromised or misconfigured Fusion cannot redirect the device flow — and\n // the resulting credentials — to an arbitrary origin.\n assertOnDomain(config.deviceAuthorizationEndpoint!, config.auth0Domain!);\n assertOnDomain(config.tokenEndpoint!, config.auth0Domain!);\n\n return config as CliAuthConfig;\n}\n\n/** Exchange a verified Auth0 access token for a Fusion JWT. */\nexport async function createFusionSession(\n host: string,\n auth0AccessToken: string,\n): Promise<FusionSession> {\n const response = await request(`${host}/api/auth/cli-session`, {\n method: 'POST',\n headers: { 'content-type': 'application/json', accept: 'application/json' },\n body: JSON.stringify({ auth0_access_token: auth0AccessToken }),\n });\n\n throwIfNotConfigured(response, host);\n\n if (response.status === 401) {\n throw authRequiredError(\n `${host} rejected the Auth0 identity.`,\n `Run \"ekanos login\" again. If it keeps failing, confirm your account ` +\n `exists on ${host} — the browser login can succeed for an identity ` +\n `that has no Fusion account.`,\n );\n }\n\n if (!response.ok) {\n throw unexpectedStatus(host, '/api/auth/cli-session', response.status);\n }\n\n return toSession(\n await readJson(response, `${host}/api/auth/cli-session`),\n host,\n );\n}\n\nexport interface RefreshedSession extends FusionSession {\n /** The ROTATED refresh token. The presented one is already invalid. */\n auth0RefreshToken: string;\n}\n\nexport interface RefreshOptions {\n /**\n * Called the INSTANT a rotated refresh token is seen in the response — on\n * success and on failure alike, before anything is thrown.\n *\n * Persist-on-learn, not persist-on-success. Once the server's exchange with\n * Auth0 succeeds the presented token is dead and the replacement in that\n * body is the only copy in existence, so a client that only saves it on the\n * happy path destroys the partner's credential the first time /userinfo\n * blips. The callback runs before any throw so no error path can skip it.\n */\n onRotatedToken?: (rotated: string) => void;\n}\n\n/** Re-mint a Fusion JWT from the stored Auth0 refresh token. */\nexport async function refreshFusionSession(\n host: string,\n auth0RefreshToken: string,\n options: RefreshOptions = {},\n): Promise<RefreshedSession> {\n const response = await request(`${host}/api/auth/cli-refresh`, {\n method: 'POST',\n headers: { 'content-type': 'application/json', accept: 'application/json' },\n body: JSON.stringify({ auth0_refresh_token: auth0RefreshToken }),\n });\n\n // Read the body FIRST, whatever the status: the rotated token can arrive on\n // a 503, a 404 or a 500 just as readily as on a 200, and it must be captured\n // before any branch below throws.\n const body = await readOptionalJson(response);\n const rotated = str(body.auth0_refresh_token);\n\n if (rotated) {\n options.onRotatedToken?.(rotated);\n }\n\n throwIfNotConfigured(response, host, body);\n\n if (response.status === 401) {\n throw authRequiredError(\n `The stored session for ${host} is no longer valid.`,\n `Run \"ekanos login\" to sign in again. This is expected after the ` +\n `refresh token expires, after it is revoked in Auth0, or if two ` +\n `machines used the same credentials file.`,\n );\n }\n\n if (response.status === 503 && body.code === 'REFRESH_INCOMPLETE') {\n // NOT an auth failure. The credential was renewed — and, thanks to the\n // callback above, already saved. Auth0's /userinfo is simply not answering.\n throw networkError(\n `${host} renewed the session but could not verify the identity with Auth0.`,\n `Retry in a moment — your credentials are still valid and have been ` +\n `updated. You do NOT need to run \"ekanos login\" again.`,\n );\n }\n\n if (response.status === 404) {\n throw authRequiredError(\n `${host} has no Fusion account for the signed-in identity.`,\n `Sign in to ${host} in a browser once to have the account created, ` +\n `then retry.`,\n );\n }\n\n if (!response.ok) {\n throw unexpectedStatus(host, '/api/auth/cli-refresh', response.status);\n }\n\n if (!rotated) {\n throw networkError(\n `${host} refreshed the session without returning a rotated refresh token.`,\n `Run \"ekanos login\" to start a fresh session, and report this — the ` +\n `deployment's Auth0 CLI application may not have rotation enabled.`,\n );\n }\n\n return { ...toSession(body, host), auth0RefreshToken: rotated };\n}\n\n/**\n * Prove the Fusion JWT authenticates. Returns null on 401 so the caller can\n * decide whether to refresh and retry — an expired access token is an ordinary\n * event, not an error.\n */\nexport async function fetchIdentity(\n host: string,\n accessToken: string,\n): Promise<FusionIdentity | null> {\n const response = await request(`${host}/api/auth/cli-whoami`, {\n method: 'GET',\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${accessToken}`,\n },\n });\n\n // 401, or the 307 that an auth-gated Fusion route answers with. Both mean\n // \"this token did not authenticate\".\n if (response.status === 401 || isRedirect(response.status)) {\n return null;\n }\n\n if (response.status === 403) {\n throw authRequiredError(\n `${host} requires multi-factor authentication for this account.`,\n `Complete MFA in the browser, then run \"ekanos login\" again.`,\n );\n }\n\n if (response.status === 404) {\n throw authRequiredError(\n `${host} has no account for the signed-in identity.`,\n `Sign in to ${host} in a browser once to have the account created, ` +\n `then run \"ekanos login\" again.`,\n );\n }\n\n if (!response.ok) {\n throw unexpectedStatus(host, '/api/auth/cli-whoami', response.status);\n }\n\n const body = await readJson(response, `${host}/api/auth/cli-whoami`);\n const user = (body.user ?? {}) as Record<string, unknown>;\n const id = str(user.id);\n\n if (!id) {\n throw networkError(\n `${host} returned an identity without a user id.`,\n `Confirm the deployment is running a Fusion build that includes the ` +\n `/api/auth/cli-whoami route.`,\n );\n }\n\n return { id, email: str(user.email), name: str(user.name) };\n}\n\nfunction toSession(body: Record<string, unknown>, host: string): FusionSession {\n const user = (body.user ?? {}) as Record<string, unknown>;\n\n const accessToken = str(body.access_token);\n const expiresAt = str(body.expires_at);\n const userId = str(user.id);\n\n if (!accessToken || !expiresAt || !userId) {\n throw networkError(\n `${host} returned an incomplete session.`,\n `Confirm the deployment is running a Fusion build that includes the ` +\n `CLI auth routes, then retry.`,\n );\n }\n\n return { accessToken, expiresAt, userId, email: str(user.email) };\n}\n\nexport async function request(\n url: string,\n init: RequestInit,\n): Promise<Response> {\n try {\n return await fetch(url, {\n ...init,\n redirect: 'manual',\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n });\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n\n throw networkError(\n `Could not reach ${new URL(url).origin}: ${reason}`,\n `Check the host is correct and reachable from this machine ` +\n `(proxy, VPN, DNS), then retry.`,\n );\n }\n}\n\n/**\n * Best-effort body read for a response whose status may be an error. Returns\n * an empty object rather than throwing, because the caller needs to inspect\n * the body BEFORE deciding what the status means — and a 500 from a proxy may\n * carry no JSON at all.\n */\nasync function readOptionalJson(\n response: Response,\n): Promise<Record<string, unknown>> {\n try {\n const parsed: unknown = await response.json();\n\n return typeof parsed === 'object' && parsed !== null\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n}\n\nasync function readJson(\n response: Response,\n what: string,\n): Promise<Record<string, unknown>> {\n let parsed: unknown;\n\n try {\n parsed = await response.json();\n } catch {\n throw networkError(\n `${what} did not return JSON.`,\n `Confirm the host is a Fusion deployment and not a proxy or captive ` +\n `portal sitting in front of one.`,\n );\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n throw networkError(\n `${what} returned an unexpected payload.`,\n `Confirm the host is a Fusion deployment, then retry.`,\n );\n }\n\n return parsed as Record<string, unknown>;\n}\n\n/**\n * 503 means the deployment is not provisioned for CLI login — an operator\n * action that has not happened, not a fault in the request.\n *\n * Every route that reads the Fusion CLI Auth0 config can answer this, so every\n * caller has to check for it. Falling through to `unexpectedStatus` would tell\n * a partner \"confirm the host is a Fusion deployment\" when the host is a\n * perfectly good Fusion deployment that is simply missing one env var — and\n * would exit 8 (network, retryable) instead of 9 (precondition, not).\n */\nfunction throwIfNotConfigured(\n response: Response,\n host: string,\n body?: Record<string, unknown>,\n): void {\n if (response.status !== 503) return;\n\n // 503 carries two meanings on these routes. Only CLI_NOT_CONFIGURED is a\n // precondition; REFRESH_INCOMPLETE is transient and handled by its caller.\n if (body && body.code !== 'CLI_NOT_CONFIGURED') return;\n\n throw preconditionError(\n `${host} does not have CLI login configured.`,\n `Ask the Fusion operator to provision the \"Fusion CLI\" Auth0 ` +\n `application and set AUTH0_CLI_CLIENT_ID on that deployment.`,\n );\n}\n\nfunction unexpectedStatus(host: string, route: string, status: number) {\n return networkError(\n `${host}${route} responded ${status}.`,\n status >= 500\n ? `The Fusion deployment returned a server error. Retry shortly; if it ` +\n `persists, report it with the status code.`\n : `Confirm the host is a Fusion deployment running a build that ` +\n `includes ${route}.`,\n );\n}\n\nfunction assertOnDomain(endpoint: string, domain: string): void {\n let url: URL;\n\n try {\n url = new URL(endpoint);\n } catch {\n throw preconditionError(\n `The login configuration contains a malformed endpoint.`,\n `Report this to the operator of the Fusion deployment.`,\n );\n }\n\n if (\n url.protocol !== 'https:' ||\n url.host.toLowerCase() !== domain.toLowerCase()\n ) {\n throw preconditionError(\n `The login configuration points at an endpoint outside its own Auth0 ` +\n `domain (${url.host}).`,\n `Do not continue. Report this to the operator of the Fusion ` +\n `deployment — it may be misconfigured or compromised.`,\n );\n }\n}\n\nfunction isRedirect(status: number): boolean {\n return status >= 300 && status < 400;\n}\n\nfunction str(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n"]}