diff --git a/packages/dashboard/app/components/QuickChatFAB.css b/packages/dashboard/app/components/QuickChatFAB.css index b559e2feb..fe9de509d 100644 --- a/packages/dashboard/app/components/QuickChatFAB.css +++ b/packages/dashboard/app/components/QuickChatFAB.css @@ -566,10 +566,12 @@ top: 0 !important; width: 100vw !important; height: 100vh !important; - height: var(--vv-height, calc(100dvh - var(--keyboard-overlap, 0px))) !important; + height: 100dvh !important; + height: var(--vv-height, 100dvh) !important; max-width: 100vw; max-height: 100vh; - max-height: var(--vv-height, calc(100dvh - var(--keyboard-overlap, 0px))); + max-height: 100dvh; + max-height: var(--vv-height, 100dvh); border-radius: 0; border: none; z-index: 1100; @@ -617,16 +619,34 @@ } .quick-chat-panel-header { - flex-wrap: wrap; - row-gap: var(--space-sm); + flex-wrap: nowrap; + row-gap: 0; /* iOS PWA: status bar (black-translucent) overlays the panel from top: 0, clipping the close button. Reserve top inset on mobile. */ padding-top: calc(10px + env(safe-area-inset-top, 0px)); } + .quick-chat-panel-title-wrap { + flex: 1 1 auto; + min-width: 0; + } + + .quick-chat-panel-header h3 { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .quick-chat-model-tag { + max-width: 12ch; + flex-shrink: 1; + } + .quick-chat-panel-header-actions { - width: 100%; - justify-content: space-between; + width: auto; + flex-shrink: 0; + justify-content: flex-end; + min-width: 0; } .chat-tool-calls-group-summary { diff --git a/packages/dashboard/app/components/SettingsModal.css b/packages/dashboard/app/components/SettingsModal.css index eb72b16bf..645d70915 100644 --- a/packages/dashboard/app/components/SettingsModal.css +++ b/packages/dashboard/app/components/SettingsModal.css @@ -556,6 +556,27 @@ --status-color: var(--text-muted); } +.remote-share-block { + display: flex; + flex-direction: column; + gap: var(--space-sm); + margin: 0 var(--space-xl) var(--space-md); + padding: var(--space-sm) var(--space-md); + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: color-mix(in srgb, var(--text-muted) 5%, transparent); +} + +.remote-share-row { + display: flex; + flex-direction: column; + gap: var(--space-xs); +} + +.remote-share-row .settings-qr-preview-image { + margin-top: var(--space-xs); +} + .remote-status-dot { width: var(--space-sm); height: var(--space-sm); diff --git a/packages/dashboard/app/components/SettingsModal.tsx b/packages/dashboard/app/components/SettingsModal.tsx index d55747f27..2594eee54 100644 --- a/packages/dashboard/app/components/SettingsModal.tsx +++ b/packages/dashboard/app/components/SettingsModal.tsx @@ -428,6 +428,7 @@ export function SettingsModal({ const [remoteUrlPreview, setRemoteUrlPreview] = useState<{ url: string; expiresAt: string | null; tokenType: "persistent" | "short-lived" } | null>(null); const [remoteQrSvg, setRemoteQrSvg] = useState(null); const [remoteShortLivedToken, setRemoteShortLivedToken] = useState<{ token: string; expiresAt: string; ttlMs: number } | null>(null); + const [tunnelShareLink, setTunnelShareLink] = useState<{ url: string; qrSvg: string | null } | null>(null); // Project memory state const [memoryContent, setMemoryContent] = useState(""); @@ -676,6 +677,36 @@ export function SettingsModal({ }); }, [activeSection, loadRemoteData]); + // When the tunnel is running, fetch a persistent-token authenticated URL + + // QR so the user can share/scan it without digging into Advanced Settings. + useEffect(() => { + if (activeSection !== "remote") return; + if (remoteStatus?.state !== "running") { + setTunnelShareLink(null); + return; + } + let cancelled = false; + (async () => { + try { + const qr = await fetchRemoteQr("image/svg", { projectId, tokenType: "persistent" }); + if (cancelled) return; + setTunnelShareLink({ url: qr.url, qrSvg: qr.data ?? null }); + } catch { + if (cancelled) return; + try { + const link = await fetchRemoteUrl({ projectId, tokenType: "persistent" }); + if (cancelled) return; + setTunnelShareLink({ url: link.url, qrSvg: null }); + } catch { + if (!cancelled) setTunnelShareLink(null); + } + } + })(); + return () => { + cancelled = true; + }; + }, [activeSection, projectId, remoteStatus?.state, remoteStatus?.url]); + // Lazy-load git remotes for the rebase-remote dropdown when the Worktrees // section becomes visible. Failure is non-fatal: the dropdown falls back // to just "Use git default". @@ -3952,6 +3983,29 @@ export function SettingsModal({ {remoteStatus?.url && {remoteStatus.url}} {remoteStatus?.lastError && {remoteStatus.lastError}} + {tunnelState === "running" && (remoteStatus?.url || tunnelShareLink) && ( +
+ {remoteStatus?.url && ( +
+ Tunnel URL: + {remoteStatus.url} +
+ )} + {tunnelShareLink?.url && ( +
+ Scan to access: + {tunnelShareLink.url} + {tunnelShareLink.qrSvg && ( + Remote access QR code + )} +
+ )} +
+ )}
diff --git a/packages/dashboard/app/components/__tests__/QuickChatFAB.test.tsx b/packages/dashboard/app/components/__tests__/QuickChatFAB.test.tsx index afca8c238..f98300678 100644 --- a/packages/dashboard/app/components/__tests__/QuickChatFAB.test.tsx +++ b/packages/dashboard/app/components/__tests__/QuickChatFAB.test.tsx @@ -273,14 +273,16 @@ describe("QuickChatFAB", () => { expect(screen.getByTestId("quick-chat-input")).toBeDisabled(); - resolveSessionCreation?.({ - session: { - ...mockSession, - id: "session-002", - agentId: "__fn_agent__", - modelProvider: "openai", - modelId: "gpt-4o", - }, + await act(async () => { + resolveSessionCreation?.({ + session: { + ...mockSession, + id: "session-002", + agentId: "__fn_agent__", + modelProvider: "openai", + modelId: "gpt-4o", + }, + }); }); await waitFor(() => { diff --git a/packages/dashboard/app/components/__tests__/SettingsModal.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModal.test.tsx index d650110dd..7442ef205 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModal.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModal.test.tsx @@ -1695,7 +1695,7 @@ describe("SettingsModal", () => { await userEvent.click(screen.getByRole("button", { name: "Start Tunnel" })); await waitFor(() => { - expect(screen.getByText("https://tail.example")).toBeInTheDocument(); + expect(screen.getByText("https://tail.example", { selector: ".remote-status-url" })).toBeInTheDocument(); }); }); diff --git a/packages/dashboard/package.json b/packages/dashboard/package.json index eab756450..e0359b058 100644 --- a/packages/dashboard/package.json +++ b/packages/dashboard/package.json @@ -67,6 +67,7 @@ "lucide-react": "^1.7.0", "multer": "^2.1.1", "node-pty": "npm:@homebridge/node-pty-prebuilt-multiarch@^0.13.1", + "qrcode": "^1.5.4", "react": "^19.0.0", "react-dom": "^19.0.0", "react-markdown": "^10.1.0", @@ -81,6 +82,7 @@ "@types/archiver": "^7.0.0", "@types/express": "^5.0.0", "@types/node": "^22.0.0", + "@types/qrcode": "^1.5.6", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@types/ws": "^8.5.0", diff --git a/packages/dashboard/src/routes/register-settings-memory-routes.ts b/packages/dashboard/src/routes/register-settings-memory-routes.ts index 1aabe9de4..4fd548210 100644 --- a/packages/dashboard/src/routes/register-settings-memory-routes.ts +++ b/packages/dashboard/src/routes/register-settings-memory-routes.ts @@ -41,6 +41,7 @@ import { updatePiExtensionDisabledIds, } from "@fusion/core"; import { createFnAgent as engineCreateFnAgent } from "@fusion/engine"; +import QRCode from "qrcode"; import { ApiError, badRequest } from "../api-error.js"; import { generateRemoteToken, issueRemoteAuthToken, maskRemoteToken } from "../remote-auth.js"; import { invalidateAllGlobalSettingsCaches } from "../project-store-resolver.js"; @@ -607,7 +608,12 @@ export function registerSettingsMemoryRoutes(ctx: ApiRoutesContext, deps: Settin const format = req.query.format === "image/svg" ? "image/svg" : "text"; const payload = await buildRemoteLoginUrlForTokenType(scopedStore, tokenType, getCurrentTunnelUrl(engine ?? options?.engine)); if (format === "image/svg") { - const svg = `${payload.loginUrl.replace(/&/g, "&").replace(/`; + const svg = await QRCode.toString(payload.loginUrl, { + type: "svg", + errorCorrectionLevel: "M", + margin: 1, + width: 256, + }); res.json({ url: payload.loginUrl, tokenType: payload.tokenType, expiresAt: payload.expiresAt, format, data: svg }); return; } diff --git a/packages/engine/src/remote-access/provider-adapters.ts b/packages/engine/src/remote-access/provider-adapters.ts index 0909619aa..67c6d4f52 100644 --- a/packages/engine/src/remote-access/provider-adapters.ts +++ b/packages/engine/src/remote-access/provider-adapters.ts @@ -149,7 +149,19 @@ const tailscaleAdapter: TunnelProviderAdapter = { return buildCommand(config); }, parseReadiness(line: string, _stream: TunnelOutputStream) { - return parseCommonReadiness(line); + // tailscale funnel prints "Available on the internet:" first (no URL), + // then a follow-up line with https://.ts.net/. Don't signal ready + // until we have the URL itself, otherwise the manager freezes status + // before the URL line arrives. + const normalized = line.trim(); + if (!normalized) return null; + const urlMatch = normalized.match(URL_PATTERN); + const url = urlMatch?.[1]; + if (!url) return null; + if (/\bts\.net\b/i.test(url) || /\b(tailscale|funnel|serve)\b/i.test(normalized)) { + return { ready: true, url }; + } + return null; }, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7d45c5d03..ee2242c94 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -226,6 +226,9 @@ importers: node-pty: specifier: npm:@homebridge/node-pty-prebuilt-multiarch@^0.13.1 version: '@homebridge/node-pty-prebuilt-multiarch@0.13.1' + qrcode: + specifier: ^1.5.4 + version: 1.5.4 react: specifier: ^19.0.0 version: 19.2.4 @@ -263,6 +266,9 @@ importers: '@types/node': specifier: ^22.0.0 version: 22.19.15 + '@types/qrcode': + specifier: ^1.5.6 + version: 1.5.6 '@types/react': specifier: ^19.0.0 version: 19.2.14 @@ -2634,6 +2640,9 @@ packages: '@types/plist@3.0.5': resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} + '@types/qrcode@1.5.6': + resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} + '@types/qs@6.15.0': resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} @@ -3158,6 +3167,10 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + caniuse-lite@1.0.30001781: resolution: {integrity: sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==} @@ -3250,6 +3263,9 @@ packages: resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} engines: {node: '>=20'} + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} @@ -3426,6 +3442,10 @@ packages: supports-color: optional: true + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} @@ -3504,6 +3524,9 @@ packages: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} + dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + dir-compare@4.2.0: resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} @@ -5122,6 +5145,10 @@ packages: resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} engines: {node: '>=10.4.0'} + pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + postcss-load-config@6.0.1: resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} engines: {node: '>= 18'} @@ -5219,6 +5246,11 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qrcode@1.5.4: + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} + engines: {node: '>=10.13.0'} + hasBin: true + qs@6.15.0: resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} engines: {node: '>=0.6'} @@ -5334,6 +5366,9 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + resedit@1.7.2: resolution: {integrity: sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==} engines: {node: '>=12', npm: '>=6'} @@ -5455,6 +5490,9 @@ packages: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -6100,6 +6138,9 @@ packages: resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -6123,6 +6164,10 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -6181,6 +6226,9 @@ packages: xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -6200,6 +6248,10 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} @@ -6208,6 +6260,10 @@ packages: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + yargs@16.2.0: resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} engines: {node: '>=10'} @@ -9329,6 +9385,10 @@ snapshots: xmlbuilder: 15.1.1 optional: true + '@types/qrcode@1.5.6': + dependencies: + '@types/node': 25.5.2 + '@types/qs@6.15.0': {} '@types/range-parser@1.2.7': {} @@ -9987,6 +10047,8 @@ snapshots: callsites@3.1.0: {} + camelcase@5.3.1: {} + caniuse-lite@1.0.30001781: {} ccount@2.0.1: {} @@ -10066,6 +10128,12 @@ snapshots: slice-ansi: 8.0.0 string-width: 8.2.0 + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + cliui@7.0.4: dependencies: string-width: 4.2.3 @@ -10221,6 +10289,8 @@ snapshots: dependencies: ms: 2.1.3 + decamelize@1.2.0: {} + decimal.js@10.6.0: {} decode-named-character-reference@1.3.0: @@ -10286,6 +10356,8 @@ snapshots: diff@8.0.4: {} + dijkstrajs@1.0.3: {} + dir-compare@4.2.0: dependencies: minimatch: 3.1.5 @@ -12309,6 +12381,8 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 + pngjs@5.0.0: {} + postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(yaml@2.8.3): dependencies: lilconfig: 3.1.3 @@ -12422,6 +12496,12 @@ snapshots: punycode@2.3.1: {} + qrcode@1.5.4: + dependencies: + dijkstrajs: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 + qs@6.15.0: dependencies: side-channel: 1.1.0 @@ -12582,6 +12662,8 @@ snapshots: require-from-string@2.0.2: {} + require-main-filename@2.0.0: {} + resedit@1.7.2: dependencies: pe-library: 0.4.1 @@ -12737,6 +12819,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-blocking@2.0.0: {} + setprototypeof@1.2.0: {} sharp@0.33.5: @@ -13603,6 +13687,8 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + which-module@2.0.1: {} + which@2.0.2: dependencies: isexe: 2.0.0 @@ -13622,6 +13708,12 @@ snapshots: word-wrap@1.2.5: {} + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -13659,6 +13751,8 @@ snapshots: xmlchars@2.2.0: {} + y18n@4.0.3: {} + y18n@5.0.8: {} yallist@3.1.1: {} @@ -13669,10 +13763,29 @@ snapshots: yaml@2.8.3: {} + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + yargs-parser@20.2.9: {} yargs-parser@21.1.1: {} + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + yargs@16.2.0: dependencies: cliui: 7.0.4