@dbx-tools/cli 0.9.132 → 0.9.136

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/package.json CHANGED
@@ -34,30 +34,34 @@
34
34
  "dependencies": {
35
35
  "@clack/prompts": "^1.7.0",
36
36
  "@databricks/appkit": "0.81.0",
37
- "@dbx-tools/appkit": "^0.9.132",
38
- "@dbx-tools/appkit-memory": "^0.9.132",
39
- "@dbx-tools/appkit-model-gateway": "^0.9.132",
40
- "@dbx-tools/auth": "^0.9.132",
41
- "@dbx-tools/auth-gate": "^0.9.132",
42
- "@dbx-tools/cli-args": "^0.9.132",
43
- "@dbx-tools/cli-service": "^0.9.132",
44
- "@dbx-tools/core": "^0.9.132",
45
- "@dbx-tools/databricks": "^0.9.132",
46
- "@dbx-tools/email": "^0.9.132",
47
- "@dbx-tools/lakebase": "^0.9.132",
48
- "@dbx-tools/postgres": "^0.9.132",
49
- "@dbx-tools/shared-auth": "^0.9.132",
50
- "@dbx-tools/shared-core": "^0.9.132",
51
- "@dbx-tools/shared-email": "^0.9.132",
52
- "@dbx-tools/shared-genie-code": "^0.9.132",
53
- "@dbx-tools/shared-model-gateway": "^0.9.132",
54
- "@dbx-tools/tunnel": "^0.9.132",
37
+ "@dbx-tools/appkit": "^0.9.136",
38
+ "@dbx-tools/appkit-generator": "^0.9.136",
39
+ "@dbx-tools/appkit-memory": "^0.9.136",
40
+ "@dbx-tools/appkit-model-gateway": "^0.9.136",
41
+ "@dbx-tools/appkit-web-search": "^0.9.136",
42
+ "@dbx-tools/auth": "^0.9.136",
43
+ "@dbx-tools/auth-gate": "^0.9.136",
44
+ "@dbx-tools/cli-args": "^0.9.136",
45
+ "@dbx-tools/cli-service": "^0.9.136",
46
+ "@dbx-tools/core": "^0.9.136",
47
+ "@dbx-tools/databricks": "^0.9.136",
48
+ "@dbx-tools/email": "^0.9.136",
49
+ "@dbx-tools/lakebase": "^0.9.136",
50
+ "@dbx-tools/postgres": "^0.9.136",
51
+ "@dbx-tools/shared-auth": "^0.9.136",
52
+ "@dbx-tools/shared-core": "^0.9.136",
53
+ "@dbx-tools/shared-email": "^0.9.136",
54
+ "@dbx-tools/shared-generator": "^0.9.136",
55
+ "@dbx-tools/shared-genie-code": "^0.9.136",
56
+ "@dbx-tools/shared-model-gateway": "^0.9.136",
57
+ "@dbx-tools/tunnel": "^0.9.136",
55
58
  "commander": "^15.0.0",
56
59
  "concurrently": "^10.0.3",
57
60
  "get-port": "^7.1.0",
58
61
  "http-proxy-3": "^1.23.1",
59
62
  "pg": "^8.22.0",
60
63
  "smol-toml": "1.8.0",
64
+ "yaml": "^2.9.0",
61
65
  "zod": "4.3.6"
62
66
  },
63
67
  "main": "./lib/index.js",
@@ -115,6 +119,22 @@
115
119
  "types": "./lib/src/auth/options.d.ts",
116
120
  "default": "./lib/src/auth/options.js"
117
121
  },
122
+ "./brand": {
123
+ "types": "./lib/src/brand/cli.d.ts",
124
+ "default": "./lib/src/brand/cli.js"
125
+ },
126
+ "./brand/export": {
127
+ "types": "./lib/src/brand/export.d.ts",
128
+ "default": "./lib/src/brand/export.js"
129
+ },
130
+ "./brand/options": {
131
+ "types": "./lib/src/brand/options.d.ts",
132
+ "default": "./lib/src/brand/options.js"
133
+ },
134
+ "./brand/run": {
135
+ "types": "./lib/src/brand/run.d.ts",
136
+ "default": "./lib/src/brand/run.js"
137
+ },
118
138
  "./graphiti": {
119
139
  "types": "./lib/src/graphiti/cli.d.ts",
120
140
  "default": "./lib/src/graphiti/cli.js"
@@ -158,7 +178,7 @@
158
178
  "./package.json": "./package.json"
159
179
  }
160
180
  },
161
- "version": "0.9.132",
181
+ "version": "0.9.136",
162
182
  "types": "./lib/index.d.ts",
163
183
  "dbxToolsConfig": {
164
184
  "tags": [
@@ -207,6 +227,22 @@
207
227
  "types": "./lib/src/auth/options.d.ts",
208
228
  "default": "./lib/src/auth/options.js"
209
229
  },
230
+ "./brand": {
231
+ "types": "./lib/src/brand/cli.d.ts",
232
+ "default": "./lib/src/brand/cli.js"
233
+ },
234
+ "./brand/export": {
235
+ "types": "./lib/src/brand/export.d.ts",
236
+ "default": "./lib/src/brand/export.js"
237
+ },
238
+ "./brand/options": {
239
+ "types": "./lib/src/brand/options.d.ts",
240
+ "default": "./lib/src/brand/options.js"
241
+ },
242
+ "./brand/run": {
243
+ "types": "./lib/src/brand/run.d.ts",
244
+ "default": "./lib/src/brand/run.js"
245
+ },
210
246
  "./graphiti": {
211
247
  "types": "./lib/src/graphiti/cli.d.ts",
212
248
  "default": "./lib/src/graphiti/cli.js"
@@ -0,0 +1,52 @@
1
+ /**
2
+ * `dbx brand` researched brand-package export.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import { addArgs, parseArgs } from "@dbx-tools/cli-args";
8
+ import { Command } from "commander";
9
+
10
+ import { BrandExportOptionsSchema } from "./options.ts";
11
+ import { exportBrandFromPrompt } from "./run.ts";
12
+
13
+ /** Replaceable command boundaries used by tests and embedders. */
14
+ export interface BrandCliDependencies {
15
+ readonly exportBrandFromPrompt?: typeof exportBrandFromPrompt;
16
+ readonly writeProgress?: (value: string) => void;
17
+ readonly writeResult?: (value: unknown) => void;
18
+ }
19
+
20
+ /** Build the lazily loaded `dbx brand` command. */
21
+ export function buildProgram(name = "dbx brand", dependencies: BrandCliDependencies = {}): Command {
22
+ const runExport = dependencies.exportBrandFromPrompt ?? exportBrandFromPrompt;
23
+ const writeProgress =
24
+ dependencies.writeProgress ?? ((value: string) => process.stderr.write(`${value}\n`));
25
+ const writeResult =
26
+ dependencies.writeResult ??
27
+ ((value: unknown) => process.stdout.write(`${JSON.stringify(value, null, 2)}\n`));
28
+ const program = addArgs(
29
+ new Command()
30
+ .name(name)
31
+ .description(
32
+ "Research a brand brief and export brand.yaml with web and mobile visual references",
33
+ ),
34
+ BrandExportOptionsSchema,
35
+ );
36
+ program
37
+ .argument("<prompt>", "brand, company, product, or design brief to research")
38
+ .action(async (prompt: string) => {
39
+ const options = parseArgs(program, BrandExportOptionsSchema);
40
+ const result = await runExport(prompt, options, {
41
+ onProgress(event) {
42
+ writeProgress(`[${event.stage}] ${event.message}`);
43
+ },
44
+ });
45
+ writeResult({
46
+ brand: result.brandYamlPath,
47
+ manifest: result.manifestPath,
48
+ references: result.referenceCount,
49
+ });
50
+ });
51
+ return program;
52
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Brand package filesystem export.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import { access, mkdir, rename, rm, writeFile } from "node:fs/promises";
8
+ import path from "node:path";
9
+ import type { BrandResearch } from "@dbx-tools/appkit-generator";
10
+ import type { BrandPackage } from "@dbx-tools/shared-generator";
11
+ import { BrandPackageSchema } from "@dbx-tools/shared-generator";
12
+ import { stringify } from "yaml";
13
+
14
+ /** Inputs required to write a generated brand package. */
15
+ export interface WriteBrandPackageOptions {
16
+ readonly outputDirectory: string;
17
+ readonly prompt: string;
18
+ readonly brandPackage: BrandPackage;
19
+ readonly research: BrandResearch;
20
+ readonly force?: boolean;
21
+ }
22
+
23
+ /** Files written for a generated brand package. */
24
+ export interface WrittenBrandPackage {
25
+ readonly outputDirectory: string;
26
+ readonly brandYamlPath: string;
27
+ readonly manifestPath: string;
28
+ readonly referenceCount: number;
29
+ }
30
+
31
+ function imageExtension(contentType?: string): string {
32
+ if (!contentType) return ".img";
33
+ const normalized = contentType.split(";", 1)[0].trim().toLowerCase();
34
+ switch (normalized) {
35
+ case "image/avif":
36
+ return ".avif";
37
+ case "image/gif":
38
+ return ".gif";
39
+ case "image/jpeg":
40
+ return ".jpg";
41
+ case "image/png":
42
+ return ".png";
43
+ case "image/svg+xml":
44
+ return ".svg";
45
+ case "image/webp":
46
+ return ".webp";
47
+ default:
48
+ return ".img";
49
+ }
50
+ }
51
+
52
+ function fileSlug(value: string): string {
53
+ return (
54
+ value
55
+ .normalize("NFKD")
56
+ .replace(/[\u0300-\u036f]/g, "")
57
+ .toLowerCase()
58
+ .replace(/[^a-z0-9]+/g, "-")
59
+ .replace(/^-+|-+$/g, "")
60
+ .slice(0, 60) || "reference"
61
+ );
62
+ }
63
+
64
+ async function writeAtomic(filePath: string, content: string | Uint8Array): Promise<void> {
65
+ await mkdir(path.dirname(filePath), { recursive: true });
66
+ const temporaryPath = `${filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
67
+ await writeFile(temporaryPath, content);
68
+ await rename(temporaryPath, filePath);
69
+ }
70
+
71
+ async function fileExists(filePath: string): Promise<boolean> {
72
+ try {
73
+ await access(filePath);
74
+ return true;
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Write a normal `brand.yaml` plus research provenance and downloaded visual
82
+ * references.
83
+ */
84
+ export async function writeBrandPackage(
85
+ options: WriteBrandPackageOptions,
86
+ ): Promise<WrittenBrandPackage> {
87
+ const outputDirectory = path.resolve(options.outputDirectory);
88
+ const brandYamlPath = path.join(outputDirectory, "brand.yaml");
89
+ const packageExists = await fileExists(brandYamlPath);
90
+ if (packageExists && !options.force) {
91
+ throw new Error(`Brand package already exists at ${brandYamlPath}; pass --force to replace it`);
92
+ }
93
+ if (options.force) {
94
+ await rm(path.join(outputDirectory, "references"), { recursive: true, force: true });
95
+ }
96
+
97
+ const visualReferences = structuredClone(options.brandPackage.visualReferences);
98
+ let referenceCount = 0;
99
+ for (
100
+ let referenceIndex = 0;
101
+ referenceIndex < options.research.visualReferences.length;
102
+ referenceIndex += 1
103
+ ) {
104
+ const researchReference = options.research.visualReferences[referenceIndex];
105
+ const exportedReference = visualReferences[referenceIndex];
106
+ if (!exportedReference) continue;
107
+ for (let imageIndex = 0; imageIndex < researchReference.images.length; imageIndex += 1) {
108
+ const researchImage = researchReference.images[imageIndex];
109
+ const exportedImage = exportedReference.images[imageIndex];
110
+ if (!exportedImage || !researchImage.data) continue;
111
+ const fileName = `${fileSlug(researchReference.appName)}-${imageIndex + 1}${imageExtension(researchImage.contentType)}`;
112
+ const relativePath = path.posix.join("references", researchReference.platform, fileName);
113
+ await writeAtomic(path.join(outputDirectory, relativePath), researchImage.data);
114
+ exportedImage.localPath = `./${relativePath}`;
115
+ referenceCount += 1;
116
+ }
117
+ }
118
+
119
+ const brandPackage = BrandPackageSchema.parse({
120
+ ...options.brandPackage,
121
+ visualReferences,
122
+ });
123
+ const manifestPath = path.join(outputDirectory, "brand-package.json");
124
+ await writeAtomic(brandYamlPath, stringify(brandPackage.brand, { indent: 2 }));
125
+ await writeAtomic(
126
+ manifestPath,
127
+ `${JSON.stringify(
128
+ {
129
+ prompt: options.prompt,
130
+ generatedAt: new Date().toISOString(),
131
+ ...brandPackage,
132
+ },
133
+ null,
134
+ 2,
135
+ )}\n`,
136
+ );
137
+
138
+ return {
139
+ outputDirectory,
140
+ brandYamlPath,
141
+ manifestPath,
142
+ referenceCount,
143
+ };
144
+ }
@@ -0,0 +1,13 @@
1
+ // GENERATED by projen watch - DO NOT EDIT.
2
+ // Regenerated from the exporting modules in ./src.
3
+ // Hand edits are overwritten on the next watch; this file is read-only.
4
+
5
+ export * as cli from "./cli.ts";
6
+ export * as exportModule from "./export.ts";
7
+ export * as options from "./options.ts";
8
+ export * as run from "./run.ts";
9
+ export type { BrandCliDependencies } from "./cli.ts";
10
+ export type { WriteBrandPackageOptions, WrittenBrandPackage } from "./export.ts";
11
+ export { BrandExportOptionsSchema } from "./options.ts";
12
+ export type { BrandExportOptions } from "./options.ts";
13
+ export type { BrandExportCallbacks } from "./run.ts";
@@ -0,0 +1,48 @@
1
+ /**
2
+ * `dbx brand` option contracts.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import { DatabricksAuthOptionsSchema } from "@dbx-tools/auth/config";
8
+ import { z } from "zod";
9
+
10
+ /** Options for exporting one researched brand package. */
11
+ export const BrandExportOptionsSchema = z
12
+ .object({
13
+ profile: DatabricksAuthOptionsSchema.shape.profile,
14
+ name: z.string().trim().min(1).optional().describe("Known canonical brand name."),
15
+ website: z.string().url().optional().describe("Known official HTTPS website."),
16
+ output: z
17
+ .string()
18
+ .trim()
19
+ .min(1)
20
+ .default("branding")
21
+ .describe("Output directory for brand.yaml, provenance, and visual references."),
22
+ model: z
23
+ .string()
24
+ .trim()
25
+ .min(1)
26
+ .optional()
27
+ .describe("Optional Databricks model preference for brand synthesis."),
28
+ maxSources: z.coerce
29
+ .number()
30
+ .int()
31
+ .min(1)
32
+ .max(8)
33
+ .default(4)
34
+ .describe("Maximum general web sources to read."),
35
+ sourceMaxLength: z.coerce
36
+ .number()
37
+ .int()
38
+ .min(1_000)
39
+ .max(20_000)
40
+ .default(8_000)
41
+ .describe("Maximum characters retained from each source page."),
42
+ force: z.boolean().default(false).describe("Replace files in an existing output directory."),
43
+ })
44
+ .strict()
45
+ .describe("Options for exporting a researched brand package.");
46
+
47
+ /** Resolved brand export options. */
48
+ export type BrandExportOptions = z.output<typeof BrandExportOptionsSchema>;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Standalone researched brand generation for the consolidated CLI.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import type { BrandResearch } from "@dbx-tools/appkit-generator";
8
+ import { generateBrandPackage } from "@dbx-tools/appkit-generator/generate";
9
+ import {
10
+ runWebAssetPageFetch,
11
+ runWebFetch,
12
+ runWebImageFetch,
13
+ } from "@dbx-tools/appkit-web-search/fetch";
14
+ import { createWebSearchRuntime } from "@dbx-tools/appkit-web-search/runtime";
15
+ import { runWebSearch } from "@dbx-tools/appkit-web-search/search";
16
+ import { createWorkspaceClient } from "@dbx-tools/databricks/workspace-client";
17
+ import type { BrandGenerationProgressEvent } from "@dbx-tools/shared-generator";
18
+
19
+ import { writeBrandPackage, type WrittenBrandPackage } from "./export.ts";
20
+ import type { BrandExportOptions } from "./options.ts";
21
+
22
+ /** Optional observers for one CLI brand generation run. */
23
+ export interface BrandExportCallbacks {
24
+ readonly onProgress?: (event: BrandGenerationProgressEvent) => void | Promise<void>;
25
+ }
26
+
27
+ /**
28
+ * Research a freeform brand brief, synthesize a portable context, and export
29
+ * the package to disk.
30
+ */
31
+ export async function exportBrandFromPrompt(
32
+ prompt: string,
33
+ options: BrandExportOptions,
34
+ callbacks: BrandExportCallbacks = {},
35
+ ): Promise<WrittenBrandPackage> {
36
+ const workspaceClient = await createWorkspaceClient({
37
+ ...(options.profile ? { profile: options.profile } : {}),
38
+ });
39
+ const host = (await workspaceClient.config.getHost()).toString();
40
+ const runtime = createWebSearchRuntime({
41
+ ...(options.model ? { model: options.model } : {}),
42
+ });
43
+ let research: BrandResearch | undefined;
44
+ const brandPackage = await generateBrandPackage(
45
+ {
46
+ prompt,
47
+ ...(options.name ? { name: options.name } : {}),
48
+ ...(options.website ? { website: options.website } : {}),
49
+ },
50
+ {
51
+ client: {
52
+ search: (request, signal) =>
53
+ runWebSearch(
54
+ request,
55
+ runtime,
56
+ {
57
+ client: workspaceClient,
58
+ host,
59
+ ...(options.profile ? { cacheIdentity: `profile:${options.profile}` } : {}),
60
+ },
61
+ signal,
62
+ ),
63
+ fetch: (request, signal) => runWebFetch(request, runtime, signal),
64
+ fetchAssetPage: (request, signal) => runWebAssetPageFetch(request, runtime, signal),
65
+ fetchImage: (request, signal) => runWebImageFetch(request, runtime, signal),
66
+ },
67
+ workspaceClient,
68
+ ...(options.model ? { model: options.model } : {}),
69
+ maxSources: options.maxSources,
70
+ sourceMaxLength: options.sourceMaxLength,
71
+ ...(callbacks.onProgress ? { onProgress: callbacks.onProgress } : {}),
72
+ onResearch(value) {
73
+ research = value;
74
+ },
75
+ },
76
+ );
77
+ if (!research) throw new Error("Brand generation completed without research provenance");
78
+ return writeBrandPackage({
79
+ outputDirectory: options.output,
80
+ prompt,
81
+ brandPackage,
82
+ research,
83
+ force: options.force,
84
+ });
85
+ }
package/src/cli.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * `dev` bootstraps or repairs a workspace and forwards to projen.
5
5
  * `appkit` provides AppKit environment helpers.
6
6
  * `auth` manages Databricks OAuth and access tokens.
7
+ * `brand` researches and exports portable brand packages.
7
8
  * `tunnel` runs a public portr tunnel with passwordless access gating.
8
9
  *
9
10
  * Feature commands load their sibling packages only when selected and forward
@@ -88,7 +89,7 @@ export function buildProgram(name: string = PROGRAM_NAMES[0]): Command {
88
89
  const program = new Command()
89
90
  .name(name)
90
91
  .description(
91
- "Databricks developer tools: workspace lifecycle, AppKit env, auth, tunnels, and local proxies",
92
+ "Databricks developer tools: workspace lifecycle, AppKit env, auth, brand generation, tunnels, and local proxies",
92
93
  )
93
94
  .enablePositionalOptions()
94
95
  .showHelpAfterError()
@@ -119,6 +120,13 @@ export function buildProgram(name: string = PROGRAM_NAMES[0]): Command {
119
120
  async () => (await import("./auth/cli.ts")).buildProgram,
120
121
  );
121
122
 
123
+ addForwardedCommand(
124
+ program,
125
+ "brand",
126
+ "Research and export a portable brand package",
127
+ async () => (await import("./brand/cli.ts")).buildProgram,
128
+ );
129
+
122
130
  addForwardedCommand(
123
131
  program,
124
132
  "tunnel",
@@ -163,6 +171,7 @@ export async function buildDocumentationProgram(name: string = PROGRAM_NAMES[0])
163
171
  const children = [
164
172
  (await import("./appkit/cli.ts")).buildProgram("appkit"),
165
173
  (await import("./auth/cli.ts")).buildProgram("auth"),
174
+ (await import("./brand/cli.ts")).buildProgram("brand"),
166
175
  (await import("./tunnel/cli.ts")).buildProgram("tunnel"),
167
176
  (await import("./lakebase-proxy/cli.ts")).buildProgram("lakebase-proxy"),
168
177
  (await import("./model-gateway/cli.ts")).buildProgram("model-gateway"),
package/src/tunnel/cli.ts CHANGED
@@ -132,6 +132,7 @@ async function run(raw: TunnelOptions, command: readonly string[]): Promise<void
132
132
  forwardHeaders: resolved.gate.forwardHeaders,
133
133
  gatePaths: resolved.gate.gatePaths,
134
134
  brandName: resolved.gate.brandName,
135
+ passkeyNamePrefix: resolved.gate.passkeyNamePrefix,
135
136
  bindHosts: resolved.bindHosts,
136
137
  });
137
138
 
@@ -45,6 +45,9 @@ export const TunnelOptionsSchema = z
45
45
  allow: list("Email allow-list patterns.").meta({ env: "TUNNEL_AUTH_ALLOW" }),
46
46
  subject: text("Verification email subject.").meta({ env: "TUNNEL_AUTH_SUBJECT" }),
47
47
  brandName: text("Verification email brand name.").meta({ env: "TUNNEL_AUTH_BRAND_NAME" }),
48
+ passkeyNamePrefix: text("Prefix for humanized tunnel passkey names.").meta({
49
+ env: "TUNNEL_AUTH_PASSKEY_NAME_PREFIX",
50
+ }),
48
51
  message: text("Verification email message.").meta({ env: "TUNNEL_AUTH_MESSAGE" }),
49
52
  sessionTtlSeconds: z.coerce
50
53
  .number<number>()
@@ -127,6 +130,7 @@ export function resolveTunnelOptions(options: TunnelOptions): ResolvedTunnelOpti
127
130
  allow: options.allow,
128
131
  subject: options.subject,
129
132
  brandName: options.brandName,
133
+ passkeyNamePrefix: options.passkeyNamePrefix,
130
134
  message: options.message,
131
135
  // Coerced, not resolved: the plugin owns the env name and the default for
132
136
  // these, so the flag is passed through as the `config` value it expects and
@@ -46,6 +46,8 @@ export interface ProxyOptions {
46
46
  gatePaths?: readonly string[];
47
47
  /** Brand name for the hosted login page shown on a denied browser navigation. */
48
48
  brandName?: string;
49
+ /** Prefix added before the humanized tunnel subdomain in new passkey names. */
50
+ passkeyNamePrefix?: string;
49
51
  /**
50
52
  * Addresses to listen on. Defaults to a single `0.0.0.0` (every interface),
51
53
  * the portr/platform case. Pass specific interface IPs to expose the gate on
@@ -77,6 +79,7 @@ async function handleAuthRoute(
77
79
  path: string,
78
80
  gate: AuthGateApi,
79
81
  brandName: string,
82
+ passkeyNamePrefix?: string,
80
83
  ): Promise<boolean> {
81
84
  if (!tunnelGate.isAuthPath(path)) return false;
82
85
  if (tunnelGate.wantsHostedLogin(request)) {
@@ -84,6 +87,7 @@ async function handleAuthRoute(
84
87
  response.end(
85
88
  tunnelLoginPage.loginPageHtml({
86
89
  brandName,
90
+ passkeyNamePrefix,
87
91
  returnTo: tunnelGate.loginReturnTo(request),
88
92
  }),
89
93
  );
@@ -126,13 +130,20 @@ export async function startProxy(options: ProxyOptions): Promise<void> {
126
130
  void (async () => {
127
131
  const path = (request.url ?? "/").split("?")[0] ?? "/";
128
132
  const brandName = options.brandName ?? "this app";
129
- if (gate && (await handleAuthRoute(request, response, path, gate, brandName))) return;
133
+ const passkeyNamePrefix = options.passkeyNamePrefix;
134
+ if (
135
+ gate &&
136
+ (await handleAuthRoute(request, response, path, gate, brandName, passkeyNamePrefix))
137
+ ) {
138
+ return;
139
+ }
130
140
  if ((await decide(request)) === "deny") {
131
141
  // A denied browser navigation gets the hosted login page; anything else
132
142
  // (an XHR, a /ws upgrade probe, a non-HTML fetch) gets the 401 JSON.
133
143
  if (gate && tunnelGate.wantsLoginPage(request)) {
134
144
  const html = tunnelLoginPage.loginPageHtml({
135
145
  brandName,
146
+ passkeyNamePrefix,
136
147
  returnTo: tunnelGate.requestReturnTo(request),
137
148
  });
138
149
  response.writeHead(401, { "content-type": "text/html; charset=utf-8" });