@switchboard.spot/cli 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @switchboard.spot/cli
2
2
 
3
3
  Command-line management for [Switchboard](https://switchboard.spot): account
4
- auth, project setup, key rotation, integration setup, usage checks, and smoke
4
+ auth, project setup, integration setup, usage checks, and smoke
5
5
  tests.
6
6
 
7
7
  ## Install
@@ -29,6 +29,18 @@ The build output is written to `dist/` and is safe to delete.
29
29
 
30
30
  ## Publish to npm
31
31
 
32
+ From the repository root, use the release helper for normal interactive
33
+ publishing:
34
+
35
+ ```bash
36
+ bin/publish-cli patch
37
+ ```
38
+
39
+ The helper bumps `packages/cli/package.json` and `package-lock.json`, runs CLI
40
+ tests, runs `npm publish --dry-run`, publishes with public access, verifies the
41
+ npm `latest` dist-tag, and commits the version bump. Pass `minor`, `major`, or an
42
+ explicit version such as `0.3.0` when needed.
43
+
32
44
  Before publishing, npm requires either an interactive account with 2FA enabled
33
45
  or a granular access token with **bypass 2FA** enabled. For token-based
34
46
  publishing, create a granular npm token with read/write access for
@@ -56,9 +68,9 @@ If a first publish fails, `npm view @switchboard.spot/cli` will continue to retu
56
68
  switchboard auth login
57
69
  switchboard projects create --name "My App" --slug my-app
58
70
  switchboard setup project --origin http://localhost:5173 --json
59
- switchboard verify setup
60
- switchboard launch prepare --production-origin https://app.example.com --end-user-terms-url https://app.example.com/terms --end-user-privacy-url https://app.example.com/privacy --support-email support@example.com --contact-email owner@example.com --use-case "Browser chat for signed-in customers"
61
- switchboard verify publish
71
+ switchboard verify setup --app-url <app-url>
72
+ switchboard setup project --origin https://preview.example.com --production-origin https://app.example.com --end-user-terms-url https://app.example.com/terms --end-user-privacy-url https://app.example.com/privacy --support-email support@example.com
73
+ switchboard verify publish --app-url <preview-url>
62
74
  ```
63
75
 
64
76
  Use `--json` for automation, CI, and coding agents.
@@ -67,9 +79,9 @@ Use `--json` for automation, CI, and coding agents.
67
79
 
68
80
  CLI account login does not create Client Gateway end-user sessions. End-user sign-up, sign-in, and refresh require the SDK-managed browser challenge; curl, Node scripts, CI, and CLI account login cannot mint browser sessions without running that real browser/mobile flow.
69
81
 
70
- For local Switchboard development, `switchboard verify setup --client-url http://localhost:4000/m/<slug>/v1` uses the built-in `dev_browser_challenge` token unless a scenario supplies an explicit `browserChallengeToken`. Hosted Switchboard should use managed Turnstile and the SDK-managed real browser challenge. A local sandbox smoke path is: configure allowed origin plus legal/support fields, create an anonymous session through the SDK or browser verification flow, then call Client Gateway chat.
82
+ For local Switchboard development, `switchboard verify setup --app-url <app-url> --client-url http://localhost:4000/g/<slug>/v1` uses the built-in `dev_browser_challenge` token unless a scenario supplies an explicit `browserChallengeToken`. Hosted Switchboard should use managed Turnstile and the SDK-managed real browser challenge. A local sandbox smoke path is: configure allowed origin plus legal/support fields, create an anonymous session through the SDK or browser verification flow, then confirm Client Gateway chat returns the guarded `402 sandbox_completion_requires_prepaid_balance` without calling a provider.
71
83
 
72
- Model discovery is global. Use `GET /v1/models` for OpenAI-compatible discovery or `GET /v1/catalog/models` for catalog/pricing metadata; Client Gateway chat is project-scoped at `/m/<slug>/v1/chat/completions`.
84
+ Model discovery is global. Use `GET /v1/models` for OpenAI-compatible discovery; Client Gateway chat is project-scoped at `/g/<slug>/v1/chat/completions`.
73
85
 
74
86
  Switchboard-managed Turnstile is the default production path. Developers do not paste Cloudflare secrets into the CLI or repo files:
75
87
 
@@ -78,7 +90,7 @@ switchboard setup project --origin <origin> --json
78
90
  switchboard projects turnstile <project-id> --clear
79
91
  ```
80
92
 
81
- `switchboard projects provision-turnstile` remains available as a low-level admin/debug command. If its help is missing, upgrade with `npm install -g @switchboard.spot/cli@latest` before trying dashboard automation or manual Cloudflare keys.
93
+ `switchboard projects provision-turnstile` remains available as a low-level admin/debug command. If its help is missing, upgrade with `npm install -g @switchboard.spot/cli@latest` before using manual Cloudflare keys.
82
94
 
83
95
  ## Configuration
84
96
 
@@ -91,7 +103,6 @@ Environment variables:
91
103
  | --- | --- |
92
104
  | `SWITCHBOARD_BASE_URL` | Switchboard app origin. Defaults to `https://switchboard.spot`; set to `http://localhost:4000` for local development. |
93
105
  | `SWITCHBOARD_PROJECT_ID` | Project context for project-scoped commands. |
94
- | `SWITCHBOARD_API_KEY` | Secret project key for trusted-server gateway smoke tests. |
95
106
  | `SWITCHBOARD_CLIENT_URL` | Public Client Gateway URL for browser/mobile end-user auth and chat. |
96
107
  | `VITE_SWITCHBOARD_CLIENT_URL` | Vite-safe public Client Gateway URL for browser apps. |
97
108
  | `SWITCHBOARD_END_USER_SESSION` | Existing end-user session for Client Gateway checks; the CLI cannot mint one without browser challenge execution. |
@@ -107,7 +118,7 @@ clears both keychain and config-dir account sessions.
107
118
 
108
119
  ## Credential safety
109
120
 
110
- Do not paste `sb_sess_`, `sb_test_`, `sb_live_`, provider keys, private keys, or
121
+ Do not paste `sb_sess_`, provider keys, private keys, or
111
122
  webhook secrets into frontend, mobile, or public code. Browser and mobile code
112
123
  should use `VITE_SWITCHBOARD_CLIENT_URL` in Vite apps or `SWITCHBOARD_CLIENT_URL`
113
124
  in non-Vite tooling, plus end-user sessions.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Switchboard CLI full dashboard parity via the account management API.
3
+ * Switchboard CLI for account, project, gateway, and docs automation.
4
4
  */
5
5
 
6
6
  import { Command } from "commander";
@@ -9,22 +9,14 @@ import path from "path";
9
9
  import { fileURLToPath } from "url";
10
10
  import { registerCommand as registerAuth } from "../lib/commands/auth.js";
11
11
  import { registerAccountCommands } from "../lib/commands/account.js";
12
- import { registerKeysCommands } from "../lib/commands/keys.js";
13
12
  import { registerProjectsCommands } from "../lib/commands/projects.js";
14
- import { registerWorkspacesCommands } from "../lib/commands/workspaces.js";
15
- import { registerOrgCommands } from "../lib/commands/org.js";
16
- import { registerEndUsersCommands } from "../lib/commands/endUsers.js";
17
13
  import { registerBillingCommands } from "../lib/commands/billing.js";
18
- import { registerEnvCommands } from "../lib/commands/env.js";
19
14
  import { registerUsageCommands } from "../lib/commands/usage.js";
20
- import { registerIntegrationCommands } from "../lib/commands/integration.js";
21
15
  import { registerDocsCommands } from "../lib/commands/docs.js";
22
- import { registerInitCommand } from "../lib/commands/init.js";
23
16
  import { registerSetupCommand } from "../lib/commands/setup.js";
24
17
  import { registerHealthCommand } from "../lib/commands/health.js";
25
18
  import { registerDoctorCommand } from "../lib/commands/doctor.js";
26
19
  import { registerVerifyCommands } from "../lib/commands/verify.js";
27
- import { registerLaunchCommands } from "../lib/commands/launch.js";
28
20
 
29
21
  const program = new Command();
30
22
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -39,23 +31,15 @@ program
39
31
  .option("-q, --quiet", "Suppress non-essential output")
40
32
  .version(packageJson.version);
41
33
 
42
- registerInitCommand(program);
43
34
  registerSetupCommand(program);
44
35
  registerHealthCommand(program);
45
36
  registerDoctorCommand(program);
46
37
  registerVerifyCommands(program);
47
- registerLaunchCommands(program);
48
38
  registerAuth(program);
49
39
  registerAccountCommands(program);
50
- registerWorkspacesCommands(program);
51
- registerKeysCommands(program);
52
40
  registerProjectsCommands(program);
53
- registerOrgCommands(program);
54
- registerEndUsersCommands(program);
55
41
  registerBillingCommands(program);
56
- registerEnvCommands(program);
57
42
  registerUsageCommands(program);
58
- registerIntegrationCommands(program);
59
43
  registerDocsCommands(program);
60
44
 
61
45
  await program.parseAsync();
package/lib/client.js CHANGED
@@ -6,11 +6,8 @@ import { accountApiUrl, resolveAccountConfig, resolveConfig } from "./config.js"
6
6
  import { emitHttpError, fail, normalizeError } from "./output.js";
7
7
 
8
8
  const PROJECT_SCOPED_ACCOUNT_PATHS = [
9
- /^\/keys(?:\/|$)/,
10
- /^\/end_users(?:\/|$)/,
11
9
  /^\/billing\/(?:ledger|top_up|prepaid)(?:\/|\?|$)/,
12
10
  /^\/usage(?:\?|$)/,
13
- /^\/integration_kit(?:\?|$)/,
14
11
  ];
15
12
 
16
13
  /**
@@ -69,13 +69,4 @@ export function registerAccountCommands(program) {
69
69
  const { data } = await accountRequest("DELETE", "/me", { json: flags.json });
70
70
  emit(flags.json ? data : `Deleted account ${email}`, flags);
71
71
  });
72
-
73
- account
74
- .command("restore")
75
- .description("Restore the authenticated account")
76
- .action(async (_opts, cmd) => {
77
- const flags = globalFlags(cmd);
78
- const { data } = await accountRequest("POST", "/me/restore", { json: flags.json });
79
- emit(flags.json ? data : `Restored account ${data.user.email}`, flags);
80
- });
81
72
  }
@@ -85,7 +85,6 @@ export function registerCommand(program) {
85
85
  deleteConfigDirAccountToken();
86
86
  saveConfig({
87
87
  projectId: null,
88
- apiKey: null,
89
88
  endUserSession: null,
90
89
  });
91
90
  emit(flags.json ? { ok: true } : "Logged out.", flags);
@@ -215,7 +214,6 @@ export async function exchangeCliLogin({
215
214
 
216
215
  save({
217
216
  projectId: null,
218
- apiKey: null,
219
217
  endUserSession: null,
220
218
  });
221
219
 
@@ -320,8 +318,8 @@ function waitForCallback(callbackServer, timeoutSeconds, json) {
320
318
  export function callbackPage(title, message, tone) {
321
319
  const success = tone === "success";
322
320
  const accent = success ? "#14b8a6" : "#ef4444";
323
- const accentSoft = success ? "#ccfbf1" : "#fee2e2";
324
- const mark = success ? "" : "!";
321
+ const mark = success ? "" : "×";
322
+ const ariaLabel = success ? "Switchboard CLI login complete" : "Switchboard CLI login failed";
325
323
 
326
324
  return `<!doctype html>
327
325
  <html lang="en">
@@ -340,114 +338,41 @@ export function callbackPage(title, message, tone) {
340
338
  body {
341
339
  min-height: 100vh;
342
340
  margin: 0;
341
+ display: grid;
342
+ place-items: center;
343
343
  color: #1f2937;
344
344
  background:
345
345
  repeating-linear-gradient(125deg, transparent, transparent 6px, #e8e8e8 6px, #e8e8e8 7px),
346
346
  #ffffff;
347
347
  }
348
- .page {
349
- min-height: 100vh;
350
- width: min(100%, 80rem);
351
- margin: 0 auto;
352
- display: flex;
353
- flex-direction: column;
354
- border-inline: 1px solid #e5e7eb;
355
- background:
356
- repeating-linear-gradient(125deg, transparent, transparent 6px, #e8e8e8 6px, #e8e8e8 7px),
357
- #ffffff;
358
- }
359
- header {
360
- height: 4.5rem;
361
- display: flex;
362
- align-items: center;
363
- justify-content: space-between;
364
- padding: 0 1.5rem;
365
- border-bottom: 1px solid #e5e7eb;
366
- background: rgba(255, 255, 255, 0.88);
367
- backdrop-filter: blur(12px);
368
- }
369
- .logo {
370
- color: #1f2937;
371
- font-size: 0.95rem;
372
- font-weight: 800;
373
- letter-spacing: 0;
374
- }
375
- .pill {
376
- border: 1px solid #e5e7eb;
377
- border-radius: 999px;
378
- padding: 0.45rem 0.8rem;
379
- background: #ffffff;
380
- color: #4b5563;
381
- font-size: 0.82rem;
382
- font-weight: 600;
383
- }
384
- main {
385
- flex: 1;
348
+ .panel {
349
+ width: min(100%, 12rem);
350
+ aspect-ratio: 1;
386
351
  display: grid;
387
352
  place-items: center;
388
- padding: 5rem 1.5rem;
389
- }
390
- .panel {
391
- width: min(100%, 34rem);
392
353
  border: 1px solid #e5e7eb;
393
354
  border-radius: 0.5rem;
394
- padding: clamp(1.5rem, 5vw, 2.25rem);
355
+ padding: 1.5rem;
395
356
  background: rgba(255, 255, 255, 0.94);
396
357
  box-shadow:
397
358
  0 1.34368px 0.537473px -0.625px rgba(0, 0, 0, 0.09),
398
359
  0 15.5969px 6.23877px -3.125px rgba(0, 0, 0, 0.07),
399
360
  0 43.962px 17.5848px -4.375px rgba(0, 0, 0, 0.04);
400
- text-align: center;
401
- }
402
- .eyebrow {
403
- margin: 0 0 1.25rem;
404
- color: #4b5563;
405
- font-size: 0.78rem;
406
- font-weight: 700;
407
- letter-spacing: 0.12em;
408
- text-transform: uppercase;
409
361
  }
410
362
  .mark {
411
- width: 4rem;
412
- height: 4rem;
413
- margin: 0 auto 1.25rem;
363
+ width: 5rem;
364
+ height: 5rem;
414
365
  display: grid;
415
366
  place-items: center;
416
367
  border: 1px solid ${accent};
417
368
  border-radius: 0.5rem;
418
369
  background: ${accent};
419
370
  color: #111827;
420
- font-size: 1.9rem;
371
+ font-size: 3rem;
372
+ line-height: 1;
421
373
  font-weight: 900;
422
374
  box-shadow: 0 18px 40px -20px ${accent};
423
375
  }
424
- h1 {
425
- margin: 0;
426
- color: #1f2937;
427
- font-size: clamp(2rem, 7vw, 3.5rem);
428
- line-height: 0.98;
429
- letter-spacing: 0;
430
- }
431
- p {
432
- margin: 1rem auto 0;
433
- max-width: 26rem;
434
- color: #4b5563;
435
- font-size: 1rem;
436
- line-height: 1.65;
437
- }
438
- .status {
439
- display: inline-flex;
440
- align-items: center;
441
- gap: 0.5rem;
442
- margin-top: 1.5rem;
443
- border: 1px solid ${accent};
444
- border-radius: 999px;
445
- padding: 0.5rem 0.8rem;
446
- background: ${accentSoft};
447
- color: #1f2937;
448
- font-size: 0.86rem;
449
- font-weight: 700;
450
- }
451
376
  @media (prefers-color-scheme: dark) {
452
377
  :root {
453
378
  background: #020617;
@@ -459,53 +384,20 @@ export function callbackPage(title, message, tone) {
459
384
  repeating-linear-gradient(125deg, transparent, transparent 6px, rgba(55, 65, 81, 0.6) 6px, rgba(55, 65, 81, 0.6) 7px),
460
385
  #020617;
461
386
  }
462
- .page {
463
- border-color: #1f2937;
464
- background:
465
- repeating-linear-gradient(125deg, transparent, transparent 6px, rgba(55, 65, 81, 0.6) 6px, rgba(55, 65, 81, 0.6) 7px),
466
- #020617;
467
- }
468
- header,
469
387
  .panel {
470
388
  border-color: #1f2937;
471
389
  background: rgba(2, 6, 23, 0.94);
472
390
  }
473
- .logo,
474
- h1 {
475
- color: #f3f4f6;
476
- }
477
- .pill,
478
- .eyebrow,
479
- p {
480
- color: #d1d5db;
481
- }
482
- .pill {
483
- border-color: #1f2937;
484
- background: #111827;
485
- }
486
- .mark,
487
- .status {
391
+ .mark {
488
392
  color: #020617;
489
393
  }
490
394
  }
491
395
  </style>
492
396
  </head>
493
397
  <body>
494
- <div class="page">
495
- <header>
496
- <div class="logo">Switchboard</div>
497
- <div class="pill">CLI session</div>
498
- </header>
499
- <main>
500
- <section class="panel" aria-labelledby="callback-title">
501
- <p class="eyebrow">Switchboard CLI</p>
502
- <div class="mark" aria-hidden="true">${mark}</div>
503
- <h1 id="callback-title">${escapeHtml(title)}</h1>
504
- <p>${escapeHtml(message)}</p>
505
- <div class="status">${success ? "Session approved" : "Session not approved"}</div>
506
- </section>
507
- </main>
508
- </div>
398
+ <main class="panel" aria-label="${ariaLabel}">
399
+ <div class="mark" aria-hidden="true">${mark}</div>
400
+ </main>
509
401
  </body>
510
402
  </html>`;
511
403
  }
@@ -20,7 +20,7 @@ export function registerBillingCommands(program) {
20
20
 
21
21
  billing
22
22
  .command("ledger")
23
- .description("List prepaid wallet ledger entries")
23
+ .description("List project spend balance ledger entries")
24
24
  .action(async (_opts, cmd) => {
25
25
  const flags = globalFlags(cmd);
26
26
  const { data } = await accountRequest("GET", "/billing/ledger", {
@@ -37,88 +37,26 @@ export function registerBillingCommands(program) {
37
37
 
38
38
  billing
39
39
  .command("top-up")
40
- .description("Add prepaid wallet funds")
40
+ .description("Add funds to the project spend balance")
41
41
  .requiredOption("--amount-micros <micros>", "Amount in micros", parseInt)
42
42
  .option("--idempotency-key <key>")
43
+ .option("--success-url <url>")
44
+ .option("--cancel-url <url>")
45
+ .option("--open", "Open checkout URL in the default browser")
43
46
  .action(async (opts, cmd) => {
44
47
  const flags = globalFlags(cmd);
45
48
  const body = { amount_micros: opts.amountMicros };
46
49
  if (opts.idempotencyKey) body.idempotency_key = opts.idempotencyKey;
50
+ if (opts.successUrl) body.success_url = opts.successUrl;
51
+ if (opts.cancelUrl) body.cancel_url = opts.cancelUrl;
47
52
 
48
53
  const { data } = await accountRequest("POST", "/billing/top_up", {
49
54
  body,
50
55
  json: flags.json,
51
56
  });
52
- emit(flags.json ? data : `Created top-up ${data.id}`, flags);
53
- });
54
-
55
- billing
56
- .command("checkout")
57
- .description("Create a Stripe-hosted developer billing checkout URL")
58
- .requiredOption("--success-url <url>")
59
- .requiredOption("--cancel-url <url>")
60
- .option("--open", "Open checkout URL in the default browser")
61
- .action(async (opts, cmd) => {
62
- const flags = globalFlags(cmd);
63
- const { data } = await accountRequest("POST", "/billing/checkout", {
64
- body: {
65
- success_url: opts.successUrl,
66
- cancel_url: opts.cancelUrl,
67
- },
68
- json: flags.json,
69
- });
70
57
  if (opts.open) openUrl(data.checkout_url);
71
58
  emit(flags.json ? data : data.checkout_url, flags);
72
59
  });
73
-
74
- billing
75
- .command("portal")
76
- .description("Create a Stripe-hosted developer billing portal URL")
77
- .requiredOption("--return-url <url>")
78
- .option("--open", "Open portal URL in the default browser")
79
- .action(async (opts, cmd) => {
80
- const flags = globalFlags(cmd);
81
- const { data } = await accountRequest("POST", "/billing/portal", {
82
- body: { return_url: opts.returnUrl },
83
- json: flags.json,
84
- });
85
- if (opts.open) openUrl(data.url);
86
- emit(flags.json ? data : data.url, flags);
87
- });
88
-
89
- billing
90
- .command("prepaid")
91
- .description("Update prepaid wallet settings")
92
- .option("--monthly-auto-refill-micros <micros>", "Monthly auto-refill amount", parseInt)
93
- .option("--outage-protection <enabled>", "true or false")
94
- .option("--outage-threshold-micros <micros>", "Outage refill threshold", parseInt)
95
- .option("--outage-refill-micros <micros>", "Outage refill amount", parseInt)
96
- .option("--outage-monthly-cap-micros <micros>", "Outage monthly cap", parseInt)
97
- .action(async (opts, cmd) => {
98
- const flags = globalFlags(cmd);
99
- const body = {};
100
- if (opts.monthlyAutoRefillMicros != null) {
101
- body.prepaid_monthly_auto_refill_micros = opts.monthlyAutoRefillMicros;
102
- }
103
- if (opts.outageProtection != null) {
104
- body.prepaid_outage_protection_enabled = opts.outageProtection === "true";
105
- }
106
- if (opts.outageThresholdMicros != null) {
107
- body.prepaid_outage_threshold_micros = opts.outageThresholdMicros;
108
- }
109
- if (opts.outageRefillMicros != null) {
110
- body.prepaid_outage_refill_micros = opts.outageRefillMicros;
111
- }
112
- if (opts.outageMonthlyCapMicros != null) {
113
- body.prepaid_outage_monthly_cap_micros = opts.outageMonthlyCapMicros;
114
- }
115
-
116
- const { data } = await accountRequest("PATCH", "/billing/prepaid", {
117
- body,
118
- json: flags.json,
119
- });
120
- emit(flags.json ? data : "Updated prepaid settings", flags);
121
- });
122
60
  }
123
61
 
124
62
  function openUrl(url) {
@@ -18,7 +18,7 @@ async function check(name, fn) {
18
18
  export function registerDoctorCommand(program) {
19
19
  program
20
20
  .command("doctor")
21
- .description("Check health, auth, project, catalog, and gateway readiness")
21
+ .description("Check health, auth, project, models, and gateway readiness")
22
22
  .action(async (_opts, cmd) => {
23
23
  const flags = globalFlags(cmd);
24
24
  const report = await runDoctorChecks();
@@ -42,13 +42,17 @@ export async function runDoctorChecks({
42
42
  checks.push(
43
43
  await check("health", async () => {
44
44
  const result = await health(config);
45
+ if (!result.ok) {
46
+ throw new Error(result.data?.status || result.data?.message || `HTTP ${result.status}`);
47
+ }
48
+
45
49
  return { status: result.status, data: result.data };
46
50
  }),
47
51
  );
48
52
 
49
53
  checks.push(
50
- await check("catalog", async () => {
51
- const res = await fetchImpl(`${gatewayApiUrl(config)}/catalog/models`);
54
+ await check("models", async () => {
55
+ const res = await fetchImpl(`${gatewayApiUrl(config)}/models`);
52
56
  const data = await res.json();
53
57
  const status = res.status;
54
58
  if (!res.ok) throw new Error(`HTTP ${status}`);
@@ -80,11 +84,11 @@ export async function runDoctorChecks({
80
84
  checks.push({ name: "project", ok: false, error: "No project selected" });
81
85
  }
82
86
 
83
- if (config.virtualMicroserviceUrl) {
87
+ if (config.clientGatewayUrl) {
84
88
  checks.push(
85
89
  await check("client_gateway_config", async () => {
86
90
  const res = await fetchImpl(
87
- new URL("client/config", ensureTrailingSlash(config.virtualMicroserviceUrl)).href,
91
+ new URL("client/config", ensureTrailingSlash(config.clientGatewayUrl)).href,
88
92
  {
89
93
  headers: { Accept: "application/json" },
90
94
  },
@@ -99,7 +103,7 @@ export async function runDoctorChecks({
99
103
 
100
104
  return {
101
105
  status: res.status,
102
- clientUrl: config.virtualMicroserviceUrl,
106
+ clientUrl: config.clientGatewayUrl,
103
107
  browserChallengeProvider: data?.browser_challenge?.provider ?? null,
104
108
  production_safety: productionSafety,
105
109
  warning: productionBlocked,