From fc7ce38318e82dfd60699796caba961717bdee5a Mon Sep 17 00:00:00 2001 From: Kiro Agent <244629292+kiro-agent@users.noreply.github.com> Date: Mon, 12 Oct 2026 02:04:54 +0000 Subject: [PATCH 1/5] Add the Redrob Code Desktop shell in apps/desktop An Electron main process and preload that start the engine as a child process and give the interface one typed bridge. The engine is the release's own CLI binary, bundled under resources/engine and started as `redrob serve` on 127.0.0.1 behind a random Basic credential. The CLI stays vanilla. The app never writes to PATH or to a CLI install directory, because the CLI recognises its own install by location and writing there would break `redrob upgrade`. The only way the app installs the CLI is by running the official installer. Desktop artifacts are all named redrob-code-desktop-*, which `install` can never download, and a test pins that. The interface is served from oc://renderer rather than file://: a file:// page sends Origin: null, which the engine's CORS rules refuse, while oc://renderer is already allowed. The Console sign-in key goes from the device-flow response straight into the engine's credential store and never reaches the renderer. The app lives under apps/ because upstream still develops packages/app and packages/desktop, which every upstream sync would otherwise collide with. --- apps/desktop/.gitignore | 3 + apps/desktop/AGENTS.md | 26 + apps/desktop/electron-builder.config.mjs | 75 +++ apps/desktop/package.json | 38 ++ apps/desktop/script/build.ts | 45 ++ apps/desktop/script/dev.ts | 37 ++ apps/desktop/script/package.ts | 36 ++ apps/desktop/script/prepare-engine.ts | 44 ++ apps/desktop/script/smoke.ts | 37 ++ apps/desktop/src/main/cli.ts | 61 +++ apps/desktop/src/main/engine-binary.ts | 97 ++++ apps/desktop/src/main/engine.ts | 223 +++++++++ apps/desktop/src/main/git.ts | 73 +++ apps/desktop/src/main/index.ts | 251 ++++++++++ apps/desktop/src/main/listening.ts | 40 ++ apps/desktop/src/main/renderer-protocol.ts | 34 ++ apps/desktop/src/main/settings.ts | 69 +++ apps/desktop/src/main/shell-env.ts | 36 ++ apps/desktop/src/main/sign-in.ts | 121 +++++ apps/desktop/src/main/updater.ts | 70 +++ apps/desktop/src/preload/index.ts | 55 +++ apps/desktop/src/shared/bridge.ts | 147 ++++++ apps/desktop/test/engine-binary.test.ts | 107 ++++ apps/desktop/test/engine.test.ts | 62 +++ apps/desktop/test/fixture/fake-engine.ts | 24 + apps/desktop/test/listening.test.ts | 25 + apps/desktop/test/misc.test.ts | 76 +++ apps/desktop/test/sign-in.test.ts | 82 ++++ apps/desktop/tsconfig.json | 10 + bun.lock | 542 ++++++++++++++++++++- package.json | 3 +- 31 files changed, 2535 insertions(+), 14 deletions(-) create mode 100644 apps/desktop/.gitignore create mode 100644 apps/desktop/AGENTS.md create mode 100644 apps/desktop/electron-builder.config.mjs create mode 100644 apps/desktop/package.json create mode 100644 apps/desktop/script/build.ts create mode 100644 apps/desktop/script/dev.ts create mode 100644 apps/desktop/script/package.ts create mode 100644 apps/desktop/script/prepare-engine.ts create mode 100644 apps/desktop/script/smoke.ts create mode 100644 apps/desktop/src/main/cli.ts create mode 100644 apps/desktop/src/main/engine-binary.ts create mode 100644 apps/desktop/src/main/engine.ts create mode 100644 apps/desktop/src/main/git.ts create mode 100644 apps/desktop/src/main/index.ts create mode 100644 apps/desktop/src/main/listening.ts create mode 100644 apps/desktop/src/main/renderer-protocol.ts create mode 100644 apps/desktop/src/main/settings.ts create mode 100644 apps/desktop/src/main/shell-env.ts create mode 100644 apps/desktop/src/main/sign-in.ts create mode 100644 apps/desktop/src/main/updater.ts create mode 100644 apps/desktop/src/preload/index.ts create mode 100644 apps/desktop/src/shared/bridge.ts create mode 100644 apps/desktop/test/engine-binary.test.ts create mode 100644 apps/desktop/test/engine.test.ts create mode 100755 apps/desktop/test/fixture/fake-engine.ts create mode 100644 apps/desktop/test/listening.test.ts create mode 100644 apps/desktop/test/misc.test.ts create mode 100644 apps/desktop/test/sign-in.test.ts create mode 100644 apps/desktop/tsconfig.json diff --git a/apps/desktop/.gitignore b/apps/desktop/.gitignore new file mode 100644 index 0000000000..9fb5b9399f --- /dev/null +++ b/apps/desktop/.gitignore @@ -0,0 +1,3 @@ +out/ +dist/ +resources/engine/ diff --git a/apps/desktop/AGENTS.md b/apps/desktop/AGENTS.md new file mode 100644 index 0000000000..6343519f5f --- /dev/null +++ b/apps/desktop/AGENTS.md @@ -0,0 +1,26 @@ +# apps/desktop + +The Electron shell for Redrob Code Desktop: main process and preload only. The interface is +`apps/studio`. Design of record: `docs/DESKTOP.md`. + +- **The CLI stays vanilla.** Never copy the bundled engine onto PATH or into a CLI install directory + (`~/.redrob/bin`, `~/bin`, `%LOCALAPPDATA%\Redrob\bin`). The only way this app installs the CLI is by + running the official installer (`src/main/cli.ts`). `isCurlInstall` / `isWindowsInstallerInstall` + recognise a CLI by its location, so writing there breaks `redrob upgrade`. +- **Artifact names start `redrob-code-desktop-`.** `install` downloads `redrob-.` from the + same release; `test/misc.test.ts` fails if the two could ever collide. +- **One bridge.** Every IPC channel is named in `src/shared/bridge.ts` and implemented in + `src/preload/index.ts` and `src/main/index.ts`. The interface talks to the engine over HTTP itself; add + a channel only for what a web page cannot do. +- **Secrets stay out of the renderer.** The Console API key goes from the device-flow response straight + to the engine (`src/main/sign-in.ts`). The renderer only ever holds the engine's random local credential. +- **The renderer is served from `oc://renderer`**, which the engine's CORS allowlist already accepts. + Do not load it from `file://`: that origin is `null` and the engine refuses it. +- **No native modules.** The main process and preload are bundled into `out/` by `script/build.ts` and + nothing else ships in `app.asar` (`!node_modules/**/*` in the builder config). The terminal uses the + engine's pty service. +- **The handshake prefix is duplicated** from `packages/redrob/src/cli/cmd/serve.ts`; + `test/listening.test.ts` pins it. + +Verify with `bun run typecheck`, `bun test`, and on a real build +`bun run prepare-engine && bun run package --dir && bun run smoke`. diff --git a/apps/desktop/electron-builder.config.mjs b/apps/desktop/electron-builder.config.mjs new file mode 100644 index 0000000000..66d9148ded --- /dev/null +++ b/apps/desktop/electron-builder.config.mjs @@ -0,0 +1,75 @@ +// electron-builder configuration for Redrob Code Desktop. +// +// Artifact names all start `redrob-code-desktop-`. The CLI's assets in the same release are +// `redrob--.{zip,tar.gz}` and `redrob-code-x64-setup.exe`, and `install` looks for exactly +// `redrob-$target.$ext`, so no desktop file can ever be picked up by the CLI installer or by +// `redrob upgrade`. test/artifacts.test.ts holds that line. +// +// Signing on Windows follows release.yml's rule: Azure Trusted Signing when all six AZURE_* values are +// present, else the organisation PFX through CSC_LINK / CSC_KEY_PASSWORD, else unsigned (and the +// workflow says so). macOS and Linux targets are declared now so their milestones change the +// workflow, not this file. + +const azure = + process.env.AZURE_ARTIFACT_SIGNING_ENDPOINT && + process.env.AZURE_ARTIFACT_SIGNING_ACCOUNT && + process.env.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE + ? { + endpoint: process.env.AZURE_ARTIFACT_SIGNING_ENDPOINT, + codeSigningAccountName: process.env.AZURE_ARTIFACT_SIGNING_ACCOUNT, + certificateProfileName: process.env.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE, + publisherName: "Redrob", + } + : undefined + +/** @type {import("electron-builder").Configuration} */ +export default { + appId: "ai.redrob.code", + productName: "Redrob Code", + executableName: "redrob-code-desktop", + artifactName: "redrob-code-desktop-${version}-${os}-${arch}.${ext}", + copyright: "Copyright © Redrob", + directories: { output: "dist", buildResources: "build" }, + // Everything the app runs is bundled into out/ (script/build.ts), so there is nothing to collect. + // In a bun workspace electron-builder finds no node_modules beside the app and walks up to the + // workspace root, which would pack the root's dependencies (the AWS SDK, effect, …) into app.asar + // and grow it from 1.7 MB to 69 MB. The exclusion stops that; the packaged smoke test proves the + // bundle needs nothing more. + files: ["out/**/*", "package.json", "!node_modules/**/*"], + npmRebuild: false, + nodeGypRebuild: false, + asar: true, + extraResources: [ + { from: "../studio/dist", to: "studio" }, + { from: "resources/engine", to: "engine" }, + ], + protocols: [{ name: "Redrob Code", schemes: ["redrob-code"] }], + publish: [{ provider: "github", owner: "redrob-labs", repo: "redrob-code", releaseType: "release" }], + win: { + target: [{ target: "nsis", arch: ["x64"] }], + signtoolOptions: { signingHashAlgorithms: ["sha256"] }, + ...(azure ? { azureSignOptions: azure } : {}), + }, + nsis: { + // Per-user, no UAC prompt, and the person sees where it goes before anything is written. + oneClick: false, + perMachine: false, + allowElevation: false, + allowToChangeInstallationDirectory: true, + // Uninstalling the app removes the app. The CLI, the engine's data and the person's sessions stay. + deleteAppDataOnUninstall: false, + shortcutName: "Redrob Code", + uninstallDisplayName: "Redrob Code Desktop", + }, + linux: { + target: ["AppImage", "deb", "tar.gz"], + category: "Development", + executableName: "redrob-code-desktop", + }, + mac: { + target: ["dmg", "zip"], + category: "public.app-category.developer-tools", + hardenedRuntime: true, + notarize: false, + }, +} diff --git a/apps/desktop/package.json b/apps/desktop/package.json new file mode 100644 index 0000000000..6f89985fb3 --- /dev/null +++ b/apps/desktop/package.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "name": "@redrob-code/desktop", + "productName": "Redrob Code", + "description": "Redrob Code desktop app", + "version": "0.0.0", + "private": true, + "type": "module", + "license": "MIT", + "author": { + "name": "Redrob", + "email": "support@redrob.ai" + }, + "homepage": "https://console.redrob.ai/code", + "main": "out/main.js", + "exports": { + "./bridge": "./src/shared/bridge.ts" + }, + "scripts": { + "build": "bun run script/build.ts", + "dev": "bun run script/dev.ts", + "electron:install": "node node_modules/electron/install.js", + "prepare-engine": "bun run script/prepare-engine.ts", + "package": "bun run script/package.ts", + "smoke": "bun run script/smoke.ts", + "test": "bun test", + "typecheck": "tsgo --noEmit" + }, + "devDependencies": { + "@tsconfig/bun": "catalog:", + "@types/bun": "catalog:", + "@types/node": "catalog:", + "@typescript/native-preview": "catalog:", + "electron": "43.7.9", + "electron-builder": "26.17.0", + "electron-updater": "6.8.10" + } +} diff --git a/apps/desktop/script/build.ts b/apps/desktop/script/build.ts new file mode 100644 index 0000000000..6193063020 --- /dev/null +++ b/apps/desktop/script/build.ts @@ -0,0 +1,45 @@ +#!/usr/bin/env bun +/** + * Builds the main process and the preload into `out/`. + * + * Everything is bundled into two files with only `electron` left external, so the packaged app has no + * `node_modules` to collect. That is deliberate: electron-builder's dependency walker and a bun + * workspace's hoisted layout disagree, and a bundle sidesteps the question entirely. + * + * The preload is CommonJS because a sandboxed preload cannot load an ES module. + */ +import { rm } from "node:fs/promises" +import path from "node:path" + +const root = path.resolve(import.meta.dir, "..") +const out = path.join(root, "out") +await rm(out, { recursive: true, force: true }) + +const version = process.env["REDROB_VERSION"] ?? "0.0.0" +const common = { + target: "node" as const, + external: ["electron"], + minify: false, + sourcemap: "linked" as const, + define: { "process.env.REDROB_CODE_DESKTOP_VERSION": JSON.stringify(version) }, +} + +const builds = [ + { entry: "src/main/index.ts", format: "esm" as const, naming: "main.js" }, + { entry: "src/preload/index.ts", format: "cjs" as const, naming: "preload.cjs" }, +] + +for (const build of builds) { + const result = await Bun.build({ + ...common, + entrypoints: [path.join(root, build.entry)], + outdir: out, + format: build.format, + naming: build.naming, + }) + if (!result.success) { + for (const log of result.logs) console.error(log) + process.exit(1) + } +} +console.log(`built ${builds.map((b) => b.naming).join(", ")} into ${path.relative(process.cwd(), out) || "out"}`) diff --git a/apps/desktop/script/dev.ts b/apps/desktop/script/dev.ts new file mode 100644 index 0000000000..dda4863b1c --- /dev/null +++ b/apps/desktop/script/dev.ts @@ -0,0 +1,37 @@ +#!/usr/bin/env bun +/** + * Development: the interface from Vite with hot reload, the main process rebuilt once, Electron on top. + * + * The engine comes from REDROB_CODE_BIN when set, else this checkout's release build + * (packages/redrob/dist), else an installed CLI. `http://localhost:` is on the engine's CORS + * allowlist, so the dev page talks to it the same way the packaged `oc://renderer` page does. + */ +import { spawn } from "node:child_process" +import path from "node:path" + +const root = path.resolve(import.meta.dir, "..") +const studio = path.resolve(root, "..", "studio") +const port = Number(process.env["REDROB_CODE_STUDIO_PORT"] ?? 5173) +const url = `http://localhost:${port}` + +const vite = spawn("bun", ["run", "dev", "--port", String(port), "--strictPort"], { cwd: studio, stdio: "inherit" }) +const build = Bun.spawnSync(["bun", "run", path.join(root, "script", "build.ts")], { + stdio: ["inherit", "inherit", "inherit"], +}) +if (build.exitCode !== 0) process.exit(build.exitCode ?? 1) + +for (let i = 0; i < 100; i++) { + const ready = await fetch(url).then( + (response) => response.ok, + () => false, + ) + if (ready) break + await Bun.sleep(200) +} + +const electron = (await import("electron")).default as unknown as string +const app = spawn(electron, [root], { stdio: "inherit", env: { ...process.env, REDROB_CODE_STUDIO_URL: url } }) +app.once("exit", (code) => { + vite.kill() + process.exit(code ?? 0) +}) diff --git a/apps/desktop/script/package.ts b/apps/desktop/script/package.ts new file mode 100644 index 0000000000..12ecdcf14c --- /dev/null +++ b/apps/desktop/script/package.ts @@ -0,0 +1,36 @@ +#!/usr/bin/env bun +/** + * Builds the interface and the main process, then packages them with electron-builder. + * + * bun run package installer for this platform + * bun run package --dir unpacked app only (what CI smoke-tests) + * + * The engine must already be in resources/engine (`bun run prepare-engine`). Publishing is never done + * here: the release workflow uploads the artifacts itself, next to the CLI's. + */ +import { $ } from "bun" +import { existsSync } from "node:fs" +import path from "node:path" +import { bundledName } from "../src/main/engine-binary" + +const root = path.resolve(import.meta.dir, "..") +const args = process.argv.slice(2) +const version = process.env["REDROB_VERSION"] ?? "0.0.0" + +if (!existsSync(path.join(root, "resources", "engine", bundledName(process.platform)))) { + console.error("resources/engine is empty; run `bun run prepare-engine` first") + process.exit(1) +} + +await $`bun run --cwd ${path.join(root, "..", "studio")} build` +await $`bun run ${path.join(root, "script", "build.ts")}`.env({ ...process.env, REDROB_VERSION: version }) + +const builderArgs = [ + "--config", + "electron-builder.config.mjs", + "--publish", + "never", + `-c.extraMetadata.version=${version}`, + ...(args.includes("--dir") ? ["--dir"] : []), +] +await $`bunx electron-builder ${builderArgs}`.cwd(root) diff --git a/apps/desktop/script/prepare-engine.ts b/apps/desktop/script/prepare-engine.ts new file mode 100644 index 0000000000..6666c2ae3d --- /dev/null +++ b/apps/desktop/script/prepare-engine.ts @@ -0,0 +1,44 @@ +#!/usr/bin/env bun +/** + * Puts the engine binary where the packaged app looks for it: `resources/engine/redrob` (or + * `redrob.bin` on Windows, so electron-builder does not sign a second time an exe the release already + * signed). + * + * The source is, in order: `--from `, `REDROB_CODE_ENGINE`, or this checkout's own release build + * at packages/redrob/dist/redrob--/bin. The release workflow passes the SIGNED binary with + * `--from`, so the engine inside the app is byte-for-byte the one published for the CLI. + */ +import { chmod, copyFile, mkdir, rm } from "node:fs/promises" +import { existsSync } from "node:fs" +import path from "node:path" +import { bundledName, releaseTarget } from "../src/main/engine-binary" + +const root = path.resolve(import.meta.dir, "..") +const repo = path.resolve(root, "..", "..") +const argv = process.argv.slice(2) +const flag = (name: string) => { + const index = argv.indexOf(name) + return index === -1 ? undefined : argv[index + 1] +} + +const platform = (flag("--platform") ?? process.platform) as NodeJS.Platform +const arch = flag("--arch") ?? process.arch +const exe = platform === "win32" ? "redrob.exe" : "redrob" +const source = + flag("--from") ?? + process.env["REDROB_CODE_ENGINE"] ?? + path.join(repo, "packages", "redrob", "dist", releaseTarget(platform, arch), "bin", exe) + +if (!existsSync(source)) { + console.error(`no engine binary at ${source}`) + console.error("build one with `bun run --cwd packages/redrob build --single`, or pass --from ") + process.exit(1) +} + +const target = path.join(root, "resources", "engine") +await rm(target, { recursive: true, force: true }) +await mkdir(target, { recursive: true }) +const destination = path.join(target, bundledName(platform)) +await copyFile(source, destination) +if (platform !== "win32") await chmod(destination, 0o755) +console.log(`engine: ${source} -> ${path.relative(root, destination)}`) diff --git a/apps/desktop/script/smoke.ts b/apps/desktop/script/smoke.ts new file mode 100644 index 0000000000..f08dcafa9c --- /dev/null +++ b/apps/desktop/script/smoke.ts @@ -0,0 +1,37 @@ +#!/usr/bin/env bun +/** + * Launches the unpacked, packaged app with `--smoke-test` and fails unless it exits 0. + * + * The app then starts the bundled engine exactly as an installed copy would, proves it answers an + * authenticated health check and refuses an unauthenticated one, and quits. That is the minimum a + * release must prove before an installer is published. + */ +import { spawnSync } from "node:child_process" +import { existsSync, readdirSync } from "node:fs" +import path from "node:path" + +const dist = path.resolve(import.meta.dir, "..", "dist") +const candidates: string[] = [] +if (process.platform === "win32") candidates.push(path.join(dist, "win-unpacked", "redrob-code-desktop.exe")) +if (process.platform === "linux") { + for (const dir of readdirSync(dist).filter((name) => name.startsWith("linux") && name.endsWith("unpacked"))) + candidates.push(path.join(dist, dir, "redrob-code-desktop")) +} +if (process.platform === "darwin") { + for (const dir of readdirSync(dist).filter((name) => name.startsWith("mac"))) + candidates.push(path.join(dist, dir, "Redrob Code.app", "Contents", "MacOS", "Redrob Code")) +} +const app = candidates.find((file) => existsSync(file)) +if (!app) { + console.error(`no packaged app under ${dist}; run \`bun run package --dir\` first`) + process.exit(1) +} + +// CI runners have no display; the smoke test opens no window, so Electron runs headless there. +const extra = process.platform === "linux" ? ["--no-sandbox", "--disable-gpu", "--ozone-platform=headless"] : [] +const result = spawnSync(app, ["--smoke-test", ...extra], { stdio: "inherit", timeout: 120_000 }) +if (result.error) { + console.error(result.error.message) + process.exit(1) +} +process.exit(result.status ?? 1) diff --git a/apps/desktop/src/main/cli.ts b/apps/desktop/src/main/cli.ts new file mode 100644 index 0000000000..622b33bbd2 --- /dev/null +++ b/apps/desktop/src/main/cli.ts @@ -0,0 +1,61 @@ +import { execFile, spawn } from "node:child_process" +import type { CliStatus } from "../shared/bridge" + +/** + * The vanilla CLI, seen from the desktop app. + * + * The app only ever LOOKS for the CLI and, when asked, runs the OFFICIAL installer -- the same + * command the README and the Console's /code page give. It never copies its own engine onto PATH, so + * what that button produces is an ordinary CLI install that updates itself with `redrob upgrade`, + * indistinguishable from one done in a terminal. + */ + +export const INSTALL_SH = "https://console.redrob.ai/code/install.sh" +export const INSTALL_PS1 = "https://console.redrob.ai/code/install.ps1" + +export function installCommand(platform: NodeJS.Platform): { display: string; file: string; args: string[] } { + if (platform === "win32") { + const script = `irm ${INSTALL_PS1} | iex` + return { + display: script, + file: "powershell.exe", + args: ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], + } + } + const script = `curl -fsSL ${INSTALL_SH} | sh` + return { display: script, file: "/bin/sh", args: ["-c", script] } +} + +export function cliVersion(binary: string): Promise { + return new Promise((resolve) => { + execFile(binary, ["--version"], { timeout: 10_000, windowsHide: true, encoding: "utf8" }, (error, stdout) => { + if (error) return resolve(null) + const match = /\d+\.\d+\.\d+\S*/.exec(stdout) + resolve(match?.[0] ?? (stdout.trim() || null)) + }) + }) +} + +export async function cliStatus(found: string | null, platform: NodeJS.Platform): Promise { + return { + installed: found !== null, + path: found, + version: found ? await cliVersion(found) : null, + installCommand: installCommand(platform).display, + } +} + +export function runInstaller( + platform: NodeJS.Platform, + env: NodeJS.ProcessEnv, +): Promise<{ ok: boolean; output: string }> { + const command = installCommand(platform) + return new Promise((resolve) => { + let output = "" + const child = spawn(command.file, command.args, { env, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] }) + child.stdout?.on("data", (chunk) => (output += String(chunk))) + child.stderr?.on("data", (chunk) => (output += String(chunk))) + child.once("error", (error) => resolve({ ok: false, output: `${output}\n${error.message}` })) + child.once("exit", (code) => resolve({ ok: code === 0, output })) + }) +} diff --git a/apps/desktop/src/main/engine-binary.ts b/apps/desktop/src/main/engine-binary.ts new file mode 100644 index 0000000000..81a3af4f53 --- /dev/null +++ b/apps/desktop/src/main/engine-binary.ts @@ -0,0 +1,97 @@ +import path from "node:path" +import type { EngineSource } from "../shared/bridge" + +/** + * Where the engine binary comes from. + * + * The desktop app never needs the CLI to be installed: it ships its own copy of the engine inside its + * resources and runs that by default. It never puts that copy on PATH or into the CLI's install + * directory either, so a vanilla `install.sh` / `install.ps1` / `redrob-code-x64-setup.exe` install keeps + * recognising itself and updating itself (`isCurlInstall` and `isWindowsInstallerInstall` in + * packages/redrob/src/installation/index.ts both check the location). + * + * Pure: every input arrives as an argument, so the order can be tested on any platform. + */ + +export type ResolveInput = { + platform: NodeJS.Platform + arch: string + env: Record + home: string + /** `process.resourcesPath` when packaged. */ + resourcesPath: string | null + /** The repository root, when running unpackaged from a checkout. */ + repoRoot: string | null + preferInstalledCli: boolean + exists: (file: string) => boolean +} + +export type Resolved = { binary: string; source: EngineSource } + +/** The bundled engine's file name. On Windows it is `.bin` so electron-builder does not re-sign an exe the release already signed. */ +export function bundledName(platform: NodeJS.Platform) { + return platform === "win32" ? "redrob.bin" : "redrob" +} + +/** The release build's directory name for this platform, as `packages/redrob/script/build.ts` writes it. */ +export function releaseTarget(platform: NodeJS.Platform, arch: string) { + const os = platform === "win32" ? "windows" : platform + return `redrob-${os}-${arch}` +} + +/** + * Where the official installers put the CLI, in the order `install` and `install.ps1` choose them. + * `install`: REDROB_INSTALL_DIR, XDG_BIN_DIR, ~/bin, ~/.redrob/bin. The Windows installer: + * %LOCALAPPDATA%\Redrob\bin, or REDROB_CODE_INSTALL_DIR. + */ +export function cliCandidates(input: Pick): string[] { + const { platform, env, home } = input + const join = platform === "win32" ? path.win32.join : path.posix.join + const exe = platform === "win32" ? "redrob.exe" : "redrob" + const dirs: string[] = [] + if (platform === "win32") { + if (env["REDROB_CODE_INSTALL_DIR"]) dirs.push(env["REDROB_CODE_INSTALL_DIR"]) + const local = env["LOCALAPPDATA"] ?? join(home, "AppData", "Local") + dirs.push(join(local, "Redrob", "bin")) + } else { + if (env["REDROB_INSTALL_DIR"]) dirs.push(env["REDROB_INSTALL_DIR"]) + if (env["XDG_BIN_DIR"]) dirs.push(env["XDG_BIN_DIR"]) + dirs.push(join(home, "bin"), join(home, ".redrob", "bin"), join(home, ".local", "bin")) + } + const fromPath = (env["PATH"] ?? env["Path"] ?? "").split(platform === "win32" ? ";" : ":").filter(Boolean) + return [...new Set([...dirs, ...fromPath].map((dir) => join(dir, exe)))] +} + +export function findCli(input: Pick): string | null { + return cliCandidates(input).find((file) => input.exists(file)) ?? null +} + +export function resolveEngineBinary(input: ResolveInput): Resolved | null { + const override = input.env["REDROB_CODE_BIN"]?.trim() + if (override && input.exists(override)) return { binary: override, source: "override" } + + const cli = findCli(input) + if (input.preferInstalledCli && cli) return { binary: cli, source: "cli" } + + const join = input.platform === "win32" ? path.win32.join : path.posix.join + if (input.resourcesPath) { + const bundled = join(input.resourcesPath, "engine", bundledName(input.platform)) + if (input.exists(bundled)) return { binary: bundled, source: "bundled" } + } + if (input.repoRoot) { + const exe = input.platform === "win32" ? "redrob.exe" : "redrob" + const local = join( + input.repoRoot, + "packages", + "redrob", + "dist", + releaseTarget(input.platform, input.arch), + "bin", + exe, + ) + if (input.exists(local)) return { binary: local, source: "bundled" } + } + + if (cli) return { binary: cli, source: "cli" } + return null +} diff --git a/apps/desktop/src/main/engine.ts b/apps/desktop/src/main/engine.ts new file mode 100644 index 0000000000..56821bb318 --- /dev/null +++ b/apps/desktop/src/main/engine.ts @@ -0,0 +1,223 @@ +import { spawn, type ChildProcess } from "node:child_process" +import { randomBytes } from "node:crypto" +import type { EngineInfo, EngineSource, EngineState } from "../shared/bridge" +import { createLineScanner } from "./listening" + +/** + * Runs the engine as a child process and keeps it running. + * + * One engine per app session, on 127.0.0.1 and a port the OS picks, behind a random Basic credential + * that only this process and the interface know. The engine serves every project directory itself + * (each request names its location), so opening another project never restarts it. + */ + +export type EngineOptions = { + binary: string + source: EngineSource + env: Record + /** How long to wait for the listening line before treating the start as failed. */ + startTimeoutMs?: number + /** Restarts after an unexpected exit before giving up and showing the error. */ + maxRestarts?: number + onState: (state: EngineState) => void + fetch?: typeof globalThis.fetch + spawnProcess?: typeof spawn + platform?: NodeJS.Platform +} + +const LOG_LIMIT = 200_000 + +export function createCredentials() { + const username = "redrob" + const password = randomBytes(24).toString("base64url") + const token = Buffer.from(`${username}:${password}`).toString("base64") + return { username, password, authToken: token, authorization: `Basic ${token}` } +} + +export class EngineManager { + private child: ChildProcess | null = null + private info: EngineInfo | null = null + private logs = "" + private restarts = 0 + private stopping = false + private readonly credentials = createCredentials() + private readonly options: Required> & + Pick + + constructor(options: EngineOptions) { + this.options = { startTimeoutMs: 30_000, maxRestarts: 3, ...options } + } + + current(): EngineInfo | null { + return this.info + } + + output(): string { + return this.logs + } + + async start(): Promise { + this.stopping = false + this.options.onState({ status: "starting" }) + try { + const info = await this.launch() + this.info = info + this.restarts = 0 + this.options.onState({ status: "ready", url: info.url, version: await this.version(info), source: info.source }) + return info + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + this.options.onState({ status: "failed", message, attempts: this.restarts + 1 }) + throw error + } + } + + async restart(): Promise { + await this.stop() + this.restarts = 0 + return this.start() + } + + async stop(): Promise { + this.stopping = true + const child = this.child + this.child = null + this.info = null + if (child && child.exitCode === null && child.pid) { + await killTree(child, this.options.platform ?? process.platform) + } + this.options.onState({ status: "stopped" }) + } + + private append(text: string) { + this.logs = (this.logs + text).slice(-LOG_LIMIT) + } + + private launch(): Promise { + const platform = this.options.platform ?? process.platform + const spawnProcess = this.options.spawnProcess ?? spawn + const env: Record = { + ...this.options.env, + REDROB_SERVER_USERNAME: this.credentials.username, + REDROB_SERVER_PASSWORD: this.credentials.password, + REDROB_CLIENT: "desktop", + } + // The bundled copy is updated by the app's own updater. Letting it update itself would rewrite a + // file inside a signed application bundle. + if (this.options.source === "bundled") env["REDROB_DISABLE_AUTOUPDATE"] = "1" + + return new Promise((resolve, reject) => { + const child = spawnProcess(this.options.binary, ["serve", "--hostname", "127.0.0.1", "--port", "0"], { + env: env as NodeJS.ProcessEnv, + stdio: ["ignore", "pipe", "pipe"], + windowsHide: true, + // Its own process group on macOS and Linux, so stopping it also stops what it started. + detached: platform !== "win32", + }) + this.child = child + let settled = false + const timer = setTimeout(() => { + fail(new Error(`The engine did not start within ${Math.round(this.options.startTimeoutMs / 1000)}s.`)) + }, this.options.startTimeoutMs) + + const fail = (error: Error) => { + if (settled) return + settled = true + clearTimeout(timer) + void killTree(child, platform) + reject(error) + } + + const scan = createLineScanner((url) => { + if (settled) return + settled = true + clearTimeout(timer) + resolve({ + url, + authorization: this.credentials.authorization, + authToken: this.credentials.authToken, + source: this.options.source, + binary: this.options.binary, + }) + }) + + child.stdout?.setEncoding("utf8") + child.stderr?.setEncoding("utf8") + child.stdout?.on("data", (chunk: string) => { + this.append(chunk) + scan(chunk) + }) + child.stderr?.on("data", (chunk: string) => this.append(chunk)) + child.once("error", (error) => fail(error)) + child.once("exit", (code, signal) => { + this.append(`\n[engine exited with ${signal ?? `code ${code}`}]\n`) + if (!settled) { + fail(new Error(`The engine exited before it was ready (${signal ?? `code ${code}`}).`)) + return + } + if (this.child === child) this.child = null + if (!this.stopping) void this.recover() + }) + }) + } + + /** An exit nobody asked for: try again a few times with a growing delay, then report it. */ + private async recover() { + this.info = null + if (this.restarts >= this.options.maxRestarts) { + this.options.onState({ + status: "failed", + message: "The engine stopped unexpectedly and could not be restarted.", + attempts: this.restarts, + }) + return + } + this.restarts += 1 + await new Promise((done) => setTimeout(done, 500 * 2 ** this.restarts)) + if (this.stopping) return + await this.start().catch(() => undefined) + } + + private async version(info: EngineInfo): Promise { + const fetcher = this.options.fetch ?? globalThis.fetch + try { + // The v1 health route is the one that names the version (handlers/global.ts). + const response = await fetcher(`${info.url}/global/health`, { headers: { Authorization: info.authorization } }) + if (!response.ok) return null + const body = (await response.json()) as { version?: unknown } + return typeof body.version === "string" ? body.version : null + } catch { + return null + } + } +} + +/** Stops a process and everything it started. */ +export function killTree(child: ChildProcess, platform: NodeJS.Platform): Promise { + return new Promise((resolve) => { + if (!child.pid || child.exitCode !== null) return resolve() + const done = () => resolve() + child.once("exit", done) + const fallback = setTimeout(() => { + try { + child.kill("SIGKILL") + } catch {} + resolve() + }, 5_000) + child.once("exit", () => clearTimeout(fallback)) + if (platform === "win32") { + // `/T` takes the tree. Without it the engine's language servers and shells survive, and holding + // files open in a worktree is what makes deleting that worktree fail with EBUSY later. + spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" }).once( + "error", + () => child.kill(), + ) + return + } + try { + process.kill(-child.pid, "SIGTERM") + } catch { + child.kill("SIGTERM") + } + }) +} diff --git a/apps/desktop/src/main/git.ts b/apps/desktop/src/main/git.ts new file mode 100644 index 0000000000..85b4654ce0 --- /dev/null +++ b/apps/desktop/src/main/git.ts @@ -0,0 +1,73 @@ +import { execFile } from "node:child_process" +import type { GitChanges } from "../shared/bridge" + +/** + * What changed in a project folder, read from git directly. + * + * The working tree is the truth the person will commit, whatever made the change -- the agent, the + * terminal, or their editor -- so the changes view reads it rather than reconstructing it from tool + * calls. Read-only: nothing here writes to the repository. + */ + +const MAX_DIFF = 2_000_000 + +function git(cwd: string, args: string[]): Promise<{ code: number; stdout: string }> { + return new Promise((resolve) => { + execFile( + "git", + ["-c", "core.quotepath=false", ...args], + { cwd, maxBuffer: MAX_DIFF * 2, windowsHide: true, encoding: "utf8" }, + (error, stdout) => { + const code = + error && typeof (error as { code?: unknown }).code === "number" ? (error as { code: number }).code : 0 + resolve({ code: error ? code || 1 : 0, stdout: stdout ?? "" }) + }, + ) + }) +} + +/** Parses `git status --porcelain=v1 -z`. Exported for the tests. */ +export function parseStatus(output: string): GitChanges["files"] { + const entries = output.split("\0") + const files: GitChanges["files"] = [] + for (let i = 0; i < entries.length; i++) { + const entry = entries[i] + if (!entry || entry.length < 4) continue + const x = entry[0] + const y = entry[1] + const file = entry.slice(3) + if (x === "R" || x === "C") { + files.push({ path: file, status: "renamed" }) + i++ // the original path follows as its own entry + continue + } + if (x === "?" && y === "?") files.push({ path: file, status: "untracked" }) + else if (x === "A" || y === "A") files.push({ path: file, status: "added" }) + else if (x === "D" || y === "D") files.push({ path: file, status: "deleted" }) + else files.push({ path: file, status: "modified" }) + } + return files +} + +export async function gitChanges(directory: string): Promise { + const inside = await git(directory, ["rev-parse", "--is-inside-work-tree"]) + if (inside.code !== 0 || inside.stdout.trim() !== "true") { + return { repository: false, branch: null, files: [], diff: "", truncated: false } + } + const [branch, status, diff] = await Promise.all([ + git(directory, ["rev-parse", "--abbrev-ref", "HEAD"]), + git(directory, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]), + // HEAD may not exist in a fresh repository; fall back to the index then. + git(directory, ["diff", "HEAD", "--no-color", "--no-ext-diff"]).then((result) => + result.code === 0 ? result : git(directory, ["diff", "--cached", "--no-color", "--no-ext-diff"]), + ), + ]) + const truncated = diff.stdout.length > MAX_DIFF + return { + repository: true, + branch: branch.code === 0 ? branch.stdout.trim() : null, + files: parseStatus(status.stdout), + diff: truncated ? diff.stdout.slice(0, MAX_DIFF) : diff.stdout, + truncated, + } +} diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts new file mode 100644 index 0000000000..f53b8ad21c --- /dev/null +++ b/apps/desktop/src/main/index.ts @@ -0,0 +1,251 @@ +import { existsSync } from "node:fs" +import os from "node:os" +import path from "node:path" +import { BrowserWindow, app, dialog, ipcMain, net, protocol, shell } from "electron" +import { Channel, type AppInfo, type EngineState, type Settings, type UpdaterState } from "../shared/bridge" +import { cliStatus, runInstaller } from "./cli" +import { EngineManager } from "./engine" +import { findCli, resolveEngineBinary } from "./engine-binary" +import { gitChanges } from "./git" +import { RENDERER_HOST, RENDERER_SCHEME, RENDERER_URL, resolveRendererFile } from "./renderer-protocol" +import { createSettingsStore } from "./settings" +import { loadShellEnv } from "./shell-env" +import { consoleUrl, createSignIn } from "./sign-in" +import { createUpdater } from "./updater" + +const DEEP_LINK_SCHEME = app.isPackaged ? "redrob-code" : "redrob-code-dev" +const SMOKE_TEST = process.argv.includes("--smoke-test") + +protocol.registerSchemesAsPrivileged([ + { + scheme: RENDERER_SCHEME, + privileges: { standard: true, secure: true, supportFetchAPI: true, corsEnabled: true, stream: true }, + }, +]) + +if (!SMOKE_TEST && !app.requestSingleInstanceLock()) { + app.quit() +} else { + void main() +} + +async function main() { + await app.whenReady() + + const platform = process.platform + const env = { ...process.env, ...loadShellEnv(platform, process.env) } + const settings = createSettingsStore(app.getPath("userData"), app.getLocale()) + const repoRoot = app.isPackaged ? null : path.resolve(app.getAppPath(), "..", "..") + let window: BrowserWindow | null = null + + const send = (channel: string, payload: unknown) => { + if (window && !window.isDestroyed()) window.webContents.send(channel, payload) + } + + let engineState: EngineState = { status: "starting" } + const resolved = resolveEngineBinary({ + platform, + arch: process.arch, + env, + home: os.homedir(), + resourcesPath: app.isPackaged ? process.resourcesPath : null, + repoRoot, + preferInstalledCli: settings.get().preferInstalledCli, + exists: existsSync, + }) + + const engine = resolved + ? new EngineManager({ + binary: resolved.binary, + source: resolved.source, + env, + onState: (state) => { + engineState = state + send(Channel.engineState, state) + }, + }) + : null + + if (SMOKE_TEST) return smokeTest(engine) + + app.setAsDefaultProtocolClient(DEEP_LINK_SCHEME) + const rendererRoot = app.isPackaged + ? path.join(process.resourcesPath, "studio") + : path.resolve(app.getAppPath(), "..", "studio", "dist") + protocol.handle(RENDERER_SCHEME, (request) => { + const file = resolveRendererFile(rendererRoot, request.url, existsSync) + if (!file) return new Response("Not found", { status: 404 }) + return net.fetch(file) + }) + + let updaterState: UpdaterState = { status: "idle" } + const updater = createUpdater({ + packaged: app.isPackaged, + platform, + env, + onState: (state) => { + updaterState = state + send(Channel.updaterState, state) + }, + }) + updater.setChannel(settings.get().updateChannel) + + const signIn = createSignIn({ + consoleUrl: consoleUrl(env), + fetch: globalThis.fetch, + openExternal: (url) => shell.openExternal(url), + onProgress: (progress) => send(Channel.signInProgress, progress), + storeKey: async (key, label) => { + const info = engine?.current() + if (!info) throw new Error("The engine is not running, so the key has nowhere to go.") + const response = await fetch(`${info.url}/api/integration/redrob/connect/key`, { + method: "POST", + headers: { Authorization: info.authorization, "Content-Type": "application/json" }, + body: JSON.stringify({ key, label }), + }) + if (!response.ok) throw new Error(`The engine did not accept the key (${response.status}).`) + }, + }) + + ipcMain.handle(Channel.engineInfo, () => engine?.current() ?? null) + ipcMain.handle(Channel.engineState, () => engineState) + ipcMain.handle(Channel.engineLogs, () => engine?.output() ?? "") + ipcMain.handle(Channel.engineRestart, async () => { + await engine?.restart() + }) + ipcMain.handle(Channel.signInStart, () => { + void signIn.start() + }) + ipcMain.handle(Channel.signInCancel, () => signIn.cancel()) + ipcMain.handle(Channel.pickDirectory, async () => { + const result = window + ? await dialog.showOpenDialog(window, { properties: ["openDirectory", "createDirectory"] }) + : await dialog.showOpenDialog({ properties: ["openDirectory", "createDirectory"] }) + return result.canceled ? null : (result.filePaths[0] ?? null) + }) + ipcMain.handle(Channel.openExternal, async (_event, url: unknown) => { + if (typeof url === "string" && /^https?:\/\//i.test(url)) await shell.openExternal(url) + }) + ipcMain.handle(Channel.gitChanges, (_event, directory: unknown) => { + if (typeof directory !== "string" || !path.isAbsolute(directory)) + throw new Error("An absolute folder path is required.") + return gitChanges(directory) + }) + ipcMain.handle(Channel.cliStatus, () => + cliStatus(findCli({ platform, env, home: os.homedir(), exists: existsSync }), platform), + ) + ipcMain.handle(Channel.cliInstall, () => runInstaller(platform, env)) + ipcMain.handle(Channel.settingsGet, () => settings.get()) + ipcMain.handle(Channel.settingsSet, (_event, patch: Partial) => { + const next = settings.set(patch ?? {}) + if (patch?.updateChannel) updater.setChannel(next.updateChannel) + return next + }) + ipcMain.handle(Channel.updaterCheck, () => updater.check()) + ipcMain.handle(Channel.updaterInstall, () => updater.install()) + ipcMain.handle(Channel.updaterState, () => updaterState) + ipcMain.handle( + Channel.appInfo, + (): AppInfo => ({ + version: app.getVersion(), + platform: platform === "win32" ? "windows" : platform === "darwin" ? "macos" : "linux", + arch: process.arch, + packaged: app.isPackaged, + }), + ) + + const createWindow = () => { + window = new BrowserWindow({ + width: 1280, + height: 840, + minWidth: 900, + minHeight: 600, + show: false, + title: "Redrob Code", + backgroundColor: "#111214", + titleBarStyle: platform === "darwin" ? "hiddenInset" : platform === "win32" ? "hidden" : "default", + titleBarOverlay: platform === "win32" ? { color: "#00000000", symbolColor: "#8a8f98", height: 40 } : false, + webPreferences: { + preload: path.join(app.getAppPath(), "out", "preload.cjs"), + contextIsolation: true, + nodeIntegration: false, + sandbox: true, + spellcheck: false, + }, + }) + window.once("ready-to-show", () => window?.show()) + // Links open in the browser, never inside the app window. + window.webContents.setWindowOpenHandler(({ url }) => { + if (/^https?:\/\//i.test(url)) void shell.openExternal(url) + return { action: "deny" } + }) + window.webContents.on("will-navigate", (event, url) => { + const devUrl = env["REDROB_CODE_STUDIO_URL"] + const internal = url.startsWith(`${RENDERER_SCHEME}://${RENDERER_HOST}/`) || (devUrl && url.startsWith(devUrl)) + if (internal) return + event.preventDefault() + if (/^https?:\/\//i.test(url)) void shell.openExternal(url) + }) + const devUrl = !app.isPackaged ? env["REDROB_CODE_STUDIO_URL"] : undefined + void window.loadURL(devUrl ?? RENDERER_URL) + window.on("closed", () => (window = null)) + } + + createWindow() + app.on("second-instance", () => { + if (!window) return createWindow() + if (window.isMinimized()) window.restore() + window.focus() + }) + app.on("activate", () => { + if (!window) createWindow() + }) + app.on("window-all-closed", () => { + if (platform !== "darwin") app.quit() + }) + let quitting = false + app.on("before-quit", (event) => { + if (quitting || !engine) return + event.preventDefault() + quitting = true + void engine.stop().finally(() => app.quit()) + }) + + if (!engine) { + engineState = { + status: "failed", + message: "No Redrob Code engine was found in this app. Reinstall it, or set REDROB_CODE_BIN.", + attempts: 0, + } + send(Channel.engineState, engineState) + } else { + await engine.start().catch(() => undefined) + } + if (app.isPackaged) void updater.check() +} + +/** + * `--smoke-test`: start the engine the packaged app would start, prove it answers an authenticated + * health check, stop it, and exit 0 -- or 1 with the reason. CI runs this against the packaged build, + * so a release cannot ship an app whose engine does not start. + */ +async function smokeTest(engine: EngineManager | null) { + const fail = (message: string) => { + console.error(`smoke test failed: ${message}`) + app.exit(1) + } + if (!engine) return fail("no engine binary was found") + try { + const info = await engine.start() + const response = await fetch(`${info.url}/api/health`, { headers: { Authorization: info.authorization } }) + const body = (await response.json()) as { healthy?: boolean } + if (!response.ok || body.healthy !== true) return fail(`health answered ${response.status}`) + const refused = await fetch(`${info.url}/api/health`) + if (refused.status !== 401) return fail(`an unauthenticated request was answered ${refused.status}, not 401`) + console.log(`smoke test passed: ${info.source} engine at ${info.url}`) + await engine.stop() + app.exit(0) + } catch (error) { + fail(error instanceof Error ? error.message : String(error)) + } +} diff --git a/apps/desktop/src/main/listening.ts b/apps/desktop/src/main/listening.ts new file mode 100644 index 0000000000..28b8e2e658 --- /dev/null +++ b/apps/desktop/src/main/listening.ts @@ -0,0 +1,40 @@ +/** + * The engine's startup handshake, read from the other side. + * + * `redrob serve` prints `redrob server listening on http://host:port` once it is bound + * (`SERVER_LISTENING_PREFIX` in packages/redrob/src/cli/cmd/serve.ts). The prefix is duplicated here + * rather than imported because importing serve.ts would pull the whole engine into the Electron main + * bundle; test/listening.test.ts reads serve.ts and fails if the two ever disagree. + */ +export const SERVER_LISTENING_PREFIX = "redrob server listening" + +/** The base URL from one line of output, or null when the line is something else. */ +export function parseListeningUrl(line: string): string | null { + const trimmed = line.trim() + if (!trimmed.startsWith(SERVER_LISTENING_PREFIX)) return null + const match = /\son\s+(https?:\/\/\S+)/.exec(trimmed) + return match?.[1]?.replace(/\/+$/, "") ?? null +} + +/** + * Splits a stream into lines and reports the first listening URL. Fed chunk by chunk, because a + * chunk boundary can fall in the middle of the line. + */ +export function createLineScanner(onUrl: (url: string) => void) { + let buffer = "" + let found = false + return (chunk: string) => { + if (found) return + buffer += chunk + const lines = buffer.split(/\r?\n/) + buffer = lines.pop() ?? "" + for (const line of lines) { + const url = parseListeningUrl(line) + if (url) { + found = true + onUrl(url) + return + } + } + } +} diff --git a/apps/desktop/src/main/renderer-protocol.ts b/apps/desktop/src/main/renderer-protocol.ts new file mode 100644 index 0000000000..8a53600e17 --- /dev/null +++ b/apps/desktop/src/main/renderer-protocol.ts @@ -0,0 +1,34 @@ +import path from "node:path" +import { pathToFileURL } from "node:url" + +/** + * The interface is served from `oc://renderer/`, not `file://`. + * + * A page loaded from `file://` sends `Origin: null`, which the engine's CORS rules refuse + * (packages/server/src/cors.ts). `oc://renderer` is already on that allowlist for exactly this reason, + * so serving the bundle under it needs no `--cors` flag and no change to the engine. + */ + +export const RENDERER_SCHEME = "oc" +export const RENDERER_HOST = "renderer" +export const RENDERER_URL = `${RENDERER_SCHEME}://${RENDERER_HOST}/index.html` + +/** + * The file a request maps to, or null when it would leave the bundle directory. Unknown paths fall + * back to index.html, so a reload on any in-app route still loads the app. + */ +export function resolveRendererFile(root: string, requestUrl: string, exists: (file: string) => boolean) { + let url: URL + try { + url = new URL(requestUrl) + } catch { + return null + } + if (url.protocol !== `${RENDERER_SCHEME}:` || url.host !== RENDERER_HOST) return null + const relative = decodeURIComponent(url.pathname).replace(/^\/+/, "") || "index.html" + const resolvedRoot = path.resolve(root) + const file = path.resolve(resolvedRoot, relative) + if (file !== resolvedRoot && !file.startsWith(resolvedRoot + path.sep)) return null + if (exists(file)) return pathToFileURL(file).toString() + return pathToFileURL(path.join(resolvedRoot, "index.html")).toString() +} diff --git a/apps/desktop/src/main/settings.ts b/apps/desktop/src/main/settings.ts new file mode 100644 index 0000000000..0f90e4c9d2 --- /dev/null +++ b/apps/desktop/src/main/settings.ts @@ -0,0 +1,69 @@ +import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs" +import path from "node:path" +import type { Settings } from "../shared/bridge" + +/** + * The app's own preferences, as one JSON file in Electron's userData directory. + * + * Only preferences live here. Credentials and sessions belong to the engine, and the engine's data + * directory is shared with the CLI so both see the same sign-in and history. + */ + +export const DEFAULT_SETTINGS: Settings = { + language: "en", + theme: "system", + updateChannel: "stable", + preferInstalledCli: false, + consent: { crashReports: false, usageEvents: false, decided: false }, + recentProjects: [], +} + +const MAX_RECENT = 12 + +/** Fills anything missing or malformed from the defaults, so an old or hand-edited file still loads. */ +export function normalize(input: unknown, locale = "en"): Settings { + const raw = (input && typeof input === "object" ? input : {}) as Partial + const pick = (value: unknown, allowed: readonly T[], fallback: T): T => + allowed.includes(value as T) ? (value as T) : fallback + const consent = (raw.consent && typeof raw.consent === "object" ? raw.consent : {}) as Partial + return { + language: pick(raw.language, ["en", "ko"], locale.toLowerCase().startsWith("ko") ? "ko" : "en"), + theme: pick(raw.theme, ["system", "light", "dark"], "system"), + updateChannel: pick(raw.updateChannel, ["stable", "alpha"], "stable"), + preferInstalledCli: raw.preferInstalledCli === true, + consent: { + crashReports: consent.crashReports === true, + usageEvents: consent.usageEvents === true, + decided: consent.decided === true, + }, + recentProjects: Array.isArray(raw.recentProjects) + ? [...new Set(raw.recentProjects.filter((item): item is string => typeof item === "string"))].slice(0, MAX_RECENT) + : [], + } +} + +export function createSettingsStore(directory: string, locale: string) { + const file = path.join(directory, "redrob-code-settings.json") + let current = load() + + function load(): Settings { + try { + return normalize(JSON.parse(readFileSync(file, "utf8")), locale) + } catch { + return normalize({}, locale) + } + } + + return { + get: () => current, + set(patch: Partial): Settings { + current = normalize({ ...current, ...patch, consent: { ...current.consent, ...patch.consent } }, locale) + mkdirSync(directory, { recursive: true }) + // Written beside and renamed over, so a crash mid-write never leaves half a file. + const temporary = `${file}.tmp` + writeFileSync(temporary, `${JSON.stringify(current, null, 2)}\n`, "utf8") + renameSync(temporary, file) + return current + }, + } +} diff --git a/apps/desktop/src/main/shell-env.ts b/apps/desktop/src/main/shell-env.ts new file mode 100644 index 0000000000..d0fdcb501d --- /dev/null +++ b/apps/desktop/src/main/shell-env.ts @@ -0,0 +1,36 @@ +import { execFileSync } from "node:child_process" + +/** + * The person's login-shell environment, for an app launched from the Dock or a desktop entry. + * + * A GUI app on macOS and Linux starts with a minimal PATH, so the engine would not find `git`, `node`, + * `bun` or anything installed by a version manager, and every shell command the agent runs would fail + * in ways that look like the project's fault. Windows GUI apps inherit the user's PATH already. + */ +export function loadShellEnv(platform: NodeJS.Platform, env: NodeJS.ProcessEnv): Record { + if (platform === "win32") return {} + const shell = env["SHELL"] || (platform === "darwin" ? "/bin/zsh" : "/bin/bash") + const marker = "__REDROB_CODE_ENV__" + try { + const output = execFileSync(shell, ["-ilc", `printf '${marker}'; env -0`], { + encoding: "utf8", + timeout: 5_000, + stdio: ["ignore", "pipe", "ignore"], + env: { ...env, REDROB_CODE_RESOLVING_ENV: "1" }, + }) + return parseEnv(output.slice(output.indexOf(marker) + marker.length)) + } catch { + return {} + } +} + +export function parseEnv(output: string): Record { + const result: Record = {} + for (const entry of output.split("\0")) { + const index = entry.indexOf("=") + if (index <= 0) continue + result[entry.slice(0, index)] = entry.slice(index + 1) + } + delete result["REDROB_CODE_RESOLVING_ENV"] + return result +} diff --git a/apps/desktop/src/main/sign-in.ts b/apps/desktop/src/main/sign-in.ts new file mode 100644 index 0000000000..940ebf925f --- /dev/null +++ b/apps/desktop/src/main/sign-in.ts @@ -0,0 +1,121 @@ +import type { SignInProgress } from "../shared/bridge" + +/** + * Console sign-in, by the RFC 8628 device flow the Console already serves for every Redrob product. + * + * POST {console}/device/authorize {product:"code"} -> deviceCode, userCode, verificationUriComplete + * the person approves in the browser, choosing the workspace + * POST {console}/device/token {deviceCode} -> apiKey (exactly once) + * + * The key goes from the Console response straight into the engine's credential store + * (`POST /api/integration/redrob/connect/key`) and is never handed to the interface or written by the + * app. That is the single-store rule from docs/PROVIDER-AUTH.md: the engine is the source of truth, and + * the CLI on the same machine sees the same sign-in. + */ + +export const DEFAULT_CONSOLE_URL = "https://console.redrob.ai/api/backend/v1" + +export function consoleUrl(env: Record) { + return (env["REDROB_CONSOLE_URL"]?.trim() || DEFAULT_CONSOLE_URL).replace(/\/+$/, "") +} + +type Authorization = { + deviceCode: string + userCode: string + verificationUri: string + verificationUriComplete: string + expiresIn: number + interval: number +} + +type Token = { apiKey: string; apiKeyName: string; accountName: string } + +export type SignInDeps = { + consoleUrl: string + fetch: typeof globalThis.fetch + openExternal: (url: string) => Promise + /** Stores the key in the engine. Only this function ever sees it. */ + storeKey: (key: string, label: string) => Promise + onProgress: (progress: SignInProgress) => void + sleep?: (ms: number) => Promise + now?: () => number +} + +/** The RFC 8628 code from a failed poll, wherever this deployment put it. */ +export function deviceError(body: unknown): string | null { + if (!body || typeof body !== "object") return null + const record = body as Record + if (typeof record["error"] === "string") return record["error"] + const nested = record["error"] + if (nested && typeof nested === "object") { + const code = (nested as Record)["code"] + if (typeof code === "string") return code + } + if (typeof record["code"] === "string") return record["code"] + return null +} + +export function createSignIn(deps: SignInDeps) { + const sleep = deps.sleep ?? ((ms: number) => new Promise((done) => setTimeout(done, ms))) + const now = deps.now ?? Date.now + let run = 0 + + async function post(path: string, body: unknown) { + const response = await deps.fetch(`${deps.consoleUrl}${path}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }) + const json = (await response.json().catch(() => null)) as unknown + return { response, json } + } + + async function start() { + const mine = ++run + try { + const { response, json } = await post("/device/authorize", { product: "code" }) + if (!response.ok) throw new Error(`The Console refused to start sign-in (${response.status}).`) + const auth = json as Authorization + const expiresAt = now() + auth.expiresIn * 1000 + deps.onProgress({ + status: "waiting", + userCode: auth.userCode, + verificationUri: auth.verificationUriComplete || auth.verificationUri, + expiresAt, + }) + await deps.openExternal(auth.verificationUriComplete || auth.verificationUri) + + let interval = Math.max(1, auth.interval) * 1000 + while (mine === run) { + await sleep(interval) + if (mine !== run) return + if (now() > expiresAt) return deps.onProgress({ status: "expired" }) + const poll = await post("/device/token", { deviceCode: auth.deviceCode }) + if (poll.response.ok) { + const token = poll.json as Token + await deps.storeKey(token.apiKey, token.apiKeyName) + return deps.onProgress({ status: "connected", accountName: token.accountName, keyName: token.apiKeyName }) + } + const code = deviceError(poll.json) + if (code === "authorization_pending") continue + if (code === "slow_down") { + interval += 5_000 + continue + } + if (code === "access_denied") return deps.onProgress({ status: "denied" }) + if (code === "expired_token" || code === "invalid_grant") return deps.onProgress({ status: "expired" }) + throw new Error(`Sign-in failed (${poll.response.status}${code ? `, ${code}` : ""}).`) + } + } catch (error) { + if (mine !== run) return + deps.onProgress({ status: "failed", message: error instanceof Error ? error.message : String(error) }) + } + } + + function cancel() { + run++ + deps.onProgress({ status: "cancelled" }) + } + + return { start, cancel } +} diff --git a/apps/desktop/src/main/updater.ts b/apps/desktop/src/main/updater.ts new file mode 100644 index 0000000000..b7305a2925 --- /dev/null +++ b/apps/desktop/src/main/updater.ts @@ -0,0 +1,70 @@ +import electronUpdater from "electron-updater" +import type { UpdateChannel, UpdaterState } from "../shared/bridge" + +/** + * App updates, from this repository's GitHub Releases. + * + * The desktop installers and their `latest.yml` feed are uploaded to the same release as the CLI + * archives, under names the CLI installer never matches (`redrob-code-desktop-*`). Stable reads the + * newest full release; alpha also accepts prereleases, which `install.sh` and `redrob upgrade` ignore, + * so an alpha desktop build never reaches a CLI user. + * + * The engine inside the app is updated with the app. It never updates itself (the engine is started + * with REDROB_DISABLE_AUTOUPDATE), and a separately installed CLI keeps its own `redrob upgrade`. + */ + +const { autoUpdater } = electronUpdater + +export function createUpdater(options: { + packaged: boolean + platform: NodeJS.Platform + env: Record + onState: (state: UpdaterState) => void +}) { + let state: UpdaterState = { status: "idle" } + const emit = (next: UpdaterState) => { + state = next + options.onState(next) + } + + const reason = !options.packaged + ? "Updates apply to installed builds only." + : options.platform === "linux" && !options.env["APPIMAGE"] + ? "This Linux package is updated by its package manager." + : null + + if (!reason) { + autoUpdater.autoDownload = true + autoUpdater.autoInstallOnAppQuit = true + autoUpdater.on("checking-for-update", () => emit({ status: "checking" })) + autoUpdater.on("update-available", (info) => emit({ status: "available", version: info.version })) + autoUpdater.on("update-not-available", () => emit({ status: "none" })) + autoUpdater.on("download-progress", (progress) => + emit({ status: "downloading", percent: Math.round(progress.percent) }), + ) + autoUpdater.on("update-downloaded", (info) => emit({ status: "ready", version: info.version })) + autoUpdater.on("error", (error) => emit({ status: "error", message: error?.message ?? String(error) })) + } + + return { + state: () => state, + setChannel(channel: UpdateChannel) { + if (reason) return + autoUpdater.allowPrerelease = channel === "alpha" + // Leaving alpha for stable means going to a LOWER version; without this the app would stay on + // the alpha it has until stable overtakes it. + autoUpdater.allowDowngrade = channel === "stable" + }, + async check() { + if (reason) return emit({ status: "unsupported", reason }) + try { + await autoUpdater.checkForUpdates() + } catch (error) { + emit({ status: "error", message: error instanceof Error ? error.message : String(error) }) + } + }, + install() { + if (state.status === "ready") autoUpdater.quitAndInstall() + }, + } +} diff --git a/apps/desktop/src/preload/index.ts b/apps/desktop/src/preload/index.ts new file mode 100644 index 0000000000..fbef12e548 --- /dev/null +++ b/apps/desktop/src/preload/index.ts @@ -0,0 +1,55 @@ +import { contextBridge, ipcRenderer } from "electron" +import { Channel, type RedrobCodeBridge } from "../shared/bridge" + +/** + * Exposes `window.redrobCode` and nothing else. Every method is a named channel; the interface never + * gets `ipcRenderer` itself, so it cannot reach a handler this file does not name. + */ + +function subscribe(channel: string, callback: (value: T) => void) { + const listener = (_event: unknown, value: T) => callback(value) + ipcRenderer.on(channel, listener) + return () => { + ipcRenderer.removeListener(channel, listener) + } +} + +const bridge: RedrobCodeBridge = { + engine: { + info: () => ipcRenderer.invoke(Channel.engineInfo), + restart: () => ipcRenderer.invoke(Channel.engineRestart), + logs: () => ipcRenderer.invoke(Channel.engineLogs), + onState: (callback) => { + // Late subscribers get the current state immediately, then every change. + void ipcRenderer.invoke(Channel.engineState).then(callback) + return subscribe(Channel.engineState, callback) + }, + }, + signIn: { + start: () => ipcRenderer.invoke(Channel.signInStart), + cancel: () => ipcRenderer.invoke(Channel.signInCancel), + onProgress: (callback) => subscribe(Channel.signInProgress, callback), + }, + pickDirectory: () => ipcRenderer.invoke(Channel.pickDirectory), + openExternal: (url) => ipcRenderer.invoke(Channel.openExternal, url), + gitChanges: (directory) => ipcRenderer.invoke(Channel.gitChanges, directory), + cli: { + status: () => ipcRenderer.invoke(Channel.cliStatus), + install: () => ipcRenderer.invoke(Channel.cliInstall), + }, + settings: { + get: () => ipcRenderer.invoke(Channel.settingsGet), + set: (patch) => ipcRenderer.invoke(Channel.settingsSet, patch), + }, + updater: { + check: () => ipcRenderer.invoke(Channel.updaterCheck), + install: () => ipcRenderer.invoke(Channel.updaterInstall), + onState: (callback) => { + void ipcRenderer.invoke(Channel.updaterState).then(callback) + return subscribe(Channel.updaterState, callback) + }, + }, + app: { info: () => ipcRenderer.invoke(Channel.appInfo) }, +} + +contextBridge.exposeInMainWorld("redrobCode", bridge) diff --git a/apps/desktop/src/shared/bridge.ts b/apps/desktop/src/shared/bridge.ts new file mode 100644 index 0000000000..9266a938a4 --- /dev/null +++ b/apps/desktop/src/shared/bridge.ts @@ -0,0 +1,147 @@ +/** + * The one contract between the Electron main process and the interface. + * + * The preload implements it with `ipcRenderer.invoke`, the interface reads it as `window.redrobCode`. + * Nothing else crosses the boundary: the interface talks to the engine directly over HTTP with the + * address and credentials it gets from `engine.info()`, and asks main only for what a web page + * cannot do itself. + * + * Kept free of Electron and Node imports so the interface can import the types without pulling either + * into a browser bundle. + */ + +export const Channel = { + engineInfo: "redrob-code:engine:info", + engineRestart: "redrob-code:engine:restart", + engineLogs: "redrob-code:engine:logs", + engineState: "redrob-code:engine:state", + signInStart: "redrob-code:sign-in:start", + signInCancel: "redrob-code:sign-in:cancel", + signInProgress: "redrob-code:sign-in:progress", + pickDirectory: "redrob-code:dialog:pick-directory", + openExternal: "redrob-code:shell:open-external", + gitChanges: "redrob-code:git:changes", + cliStatus: "redrob-code:cli:status", + cliInstall: "redrob-code:cli:install", + settingsGet: "redrob-code:settings:get", + settingsSet: "redrob-code:settings:set", + updaterCheck: "redrob-code:updater:check", + updaterInstall: "redrob-code:updater:install", + updaterState: "redrob-code:updater:state", + appInfo: "redrob-code:app:info", +} as const + +export type EngineState = + | { status: "starting" } + | { status: "ready"; url: string; version: string | null; source: EngineSource } + | { status: "failed"; message: string; attempts: number } + | { status: "stopped" } + +/** Where the running engine binary came from. */ +export type EngineSource = "override" | "cli" | "bundled" + +export type EngineInfo = { + url: string + /** `Basic …`, for the Authorization header. */ + authorization: string + /** The same credential without the scheme, for `?auth_token=` where a header cannot be set. */ + authToken: string + source: EngineSource + binary: string +} + +export type SignInProgress = + | { status: "waiting"; userCode: string; verificationUri: string; expiresAt: number } + | { status: "connected"; accountName: string; keyName: string } + | { status: "denied" } + | { status: "expired" } + | { status: "cancelled" } + | { status: "failed"; message: string } + +export type GitChanges = { + /** False when the folder is not a git repository; the other fields are then empty. */ + repository: boolean + branch: string | null + files: { path: string; status: "added" | "modified" | "deleted" | "renamed" | "untracked" }[] + /** Unified diff of tracked changes against HEAD. Untracked files are listed, not diffed. */ + diff: string + truncated: boolean +} + +export type CliStatus = { + /** A `redrob` binary was found where the official installer puts it, or on PATH. */ + installed: boolean + path: string | null + version: string | null + /** The command the official installer runs on this platform, shown before running it. */ + installCommand: string +} + +export type ThemeMode = "system" | "light" | "dark" +export type Language = "en" | "ko" +export type UpdateChannel = "stable" | "alpha" + +export type Settings = { + language: Language + theme: ThemeMode + updateChannel: UpdateChannel + /** Prefer an installed `redrob` CLI over the bundled engine when the CLI is present. */ + preferInstalledCli: boolean + /** Both default to false: nothing is sent until the person says yes. */ + consent: { crashReports: boolean; usageEvents: boolean; decided: boolean } + recentProjects: string[] +} + +export type UpdaterState = + | { status: "idle" } + | { status: "checking" } + | { status: "available"; version: string } + | { status: "downloading"; percent: number } + | { status: "ready"; version: string } + | { status: "none" } + | { status: "unsupported"; reason: string } + | { status: "error"; message: string } + +export type AppInfo = { + version: string + platform: "windows" | "macos" | "linux" + arch: string + packaged: boolean +} + +export interface RedrobCodeBridge { + engine: { + info(): Promise + restart(): Promise + logs(): Promise + onState(callback: (state: EngineState) => void): () => void + } + signIn: { + start(): Promise + cancel(): Promise + onProgress(callback: (progress: SignInProgress) => void): () => void + } + pickDirectory(): Promise + openExternal(url: string): Promise + gitChanges(directory: string): Promise + cli: { + status(): Promise + install(): Promise<{ ok: boolean; output: string }> + } + settings: { + get(): Promise + set(patch: Partial): Promise + } + updater: { + check(): Promise + install(): Promise + onState(callback: (state: UpdaterState) => void): () => void + } + app: { info(): Promise } +} + +declare global { + interface Window { + redrobCode?: RedrobCodeBridge + } +} diff --git a/apps/desktop/test/engine-binary.test.ts b/apps/desktop/test/engine-binary.test.ts new file mode 100644 index 0000000000..31ede8ae7a --- /dev/null +++ b/apps/desktop/test/engine-binary.test.ts @@ -0,0 +1,107 @@ +import { describe, expect, test } from "bun:test" +import { cliCandidates, findCli, releaseTarget, resolveEngineBinary } from "../src/main/engine-binary" + +const base = { + arch: "x64", + home: "/home/a", + resourcesPath: "/opt/app/resources", + repoRoot: null, + preferInstalledCli: false, +} + +const only = + (...files: string[]) => + (file: string) => + files.includes(file) + +describe("resolveEngineBinary", () => { + test("an explicit override wins", () => { + const resolved = resolveEngineBinary({ + ...base, + platform: "linux", + env: { REDROB_CODE_BIN: "/x/redrob", PATH: "/usr/bin" }, + exists: only("/x/redrob", "/opt/app/resources/engine/redrob"), + }) + expect(resolved).toEqual({ binary: "/x/redrob", source: "override" }) + }) + + test("the bundled engine is the default, even when a CLI is installed", () => { + const resolved = resolveEngineBinary({ + ...base, + platform: "linux", + env: { PATH: "" }, + exists: only("/home/a/.redrob/bin/redrob", "/opt/app/resources/engine/redrob"), + }) + expect(resolved).toEqual({ binary: "/opt/app/resources/engine/redrob", source: "bundled" }) + }) + + test("an installed CLI is used first only when the person asked for it", () => { + const resolved = resolveEngineBinary({ + ...base, + platform: "linux", + preferInstalledCli: true, + env: { PATH: "" }, + exists: only("/home/a/.redrob/bin/redrob", "/opt/app/resources/engine/redrob"), + }) + expect(resolved).toEqual({ binary: "/home/a/.redrob/bin/redrob", source: "cli" }) + }) + + test("Windows bundles redrob.bin so the signed exe is not signed twice", () => { + const resolved = resolveEngineBinary({ + ...base, + platform: "win32", + resourcesPath: "C:\\App\\resources", + env: { LOCALAPPDATA: "C:\\Users\\a\\AppData\\Local", Path: "" }, + exists: only("C:\\App\\resources\\engine\\redrob.bin"), + }) + expect(resolved).toEqual({ binary: "C:\\App\\resources\\engine\\redrob.bin", source: "bundled" }) + }) + + test("an unpackaged checkout uses its own release build", () => { + const resolved = resolveEngineBinary({ + ...base, + platform: "linux", + resourcesPath: null, + repoRoot: "/src/redrob-code", + env: { PATH: "" }, + exists: only("/src/redrob-code/packages/redrob/dist/redrob-linux-x64/bin/redrob"), + }) + expect(resolved?.source).toBe("bundled") + }) + + test("with nothing bundled it falls back to an installed CLI, then to nothing", () => { + const env = { PATH: "/usr/local/bin" } + expect(resolveEngineBinary({ ...base, platform: "linux", env, exists: only("/usr/local/bin/redrob") })).toEqual({ + binary: "/usr/local/bin/redrob", + source: "cli", + }) + expect(resolveEngineBinary({ ...base, platform: "linux", env, exists: () => false })).toBeNull() + }) +}) + +describe("cli locations", () => { + test("follow install's order on macOS and Linux", () => { + const list = cliCandidates({ platform: "darwin", home: "/Users/a", env: { XDG_BIN_DIR: "/xdg", PATH: "" } }) + expect(list.slice(0, 4)).toEqual([ + "/xdg/redrob", + "/Users/a/bin/redrob", + "/Users/a/.redrob/bin/redrob", + "/Users/a/.local/bin/redrob", + ]) + }) + + test("include the Windows installer's fixed directory", () => { + const found = findCli({ + platform: "win32", + home: "C:\\Users\\a", + env: { LOCALAPPDATA: "C:\\Users\\a\\AppData\\Local", Path: "" }, + exists: only("C:\\Users\\a\\AppData\\Local\\Redrob\\bin\\redrob.exe"), + }) + expect(found).toBe("C:\\Users\\a\\AppData\\Local\\Redrob\\bin\\redrob.exe") + }) + + test("release targets match packages/redrob/script/build.ts", () => { + expect(releaseTarget("win32", "x64")).toBe("redrob-windows-x64") + expect(releaseTarget("darwin", "arm64")).toBe("redrob-darwin-arm64") + }) +}) diff --git a/apps/desktop/test/engine.test.ts b/apps/desktop/test/engine.test.ts new file mode 100644 index 0000000000..cd3c59f92f --- /dev/null +++ b/apps/desktop/test/engine.test.ts @@ -0,0 +1,62 @@ +import { describe, expect, test } from "bun:test" +import { chmodSync } from "node:fs" +import path from "node:path" +import type { EngineState } from "../src/shared/bridge" +import { EngineManager } from "../src/main/engine" + +const fake = path.join(import.meta.dir, "fixture", "fake-engine.ts") +chmodSync(fake, 0o755) + +// A shebang script stands in for the engine binary, which Windows cannot execute directly; the Windows +// path is covered by the packaged smoke test in CI instead. +describe.skipIf(process.platform === "win32")("EngineManager", () => { + test("starts, authenticates, reports the version, and stops", async () => { + const states: EngineState[] = [] + const engine = new EngineManager({ + binary: fake, + source: "bundled", + env: { ...process.env }, + onState: (state) => states.push(state), + }) + const info = await engine.start() + expect(info.url).toMatch(/^http:\/\/127\.0\.0\.1:\d+$/) + expect((await fetch(`${info.url}/api/health`, { headers: { Authorization: info.authorization } })).status).toBe(200) + expect((await fetch(`${info.url}/api/health`)).status).toBe(401) + expect(states.at(-1)).toEqual({ status: "ready", url: info.url, version: "9.9.9", source: "bundled" }) + // The bundled copy is updated by the app, so it is told not to update itself. + expect(engine.output()).toContain("client=desktop autoupdate=1") + await engine.stop() + expect(states.at(-1)).toEqual({ status: "stopped" }) + expect(engine.current()).toBeNull() + }) + + test("restarts after an unexpected exit", async () => { + const states: EngineState[] = [] + const engine = new EngineManager({ + binary: fake, + source: "cli", + env: { ...process.env, FAKE_ENGINE_CRASH_AFTER_MS: "300" }, + maxRestarts: 1, + onState: (state) => states.push(state), + }) + await engine.start() + expect(engine.output()).toContain("autoupdate=\n") + await Bun.sleep(2_500) + const statuses = states.map((state) => state.status) + expect(statuses.filter((status) => status === "ready").length).toBeGreaterThanOrEqual(2) + await engine.stop() + }, 10_000) + + test("reports a binary that never prints the handshake", async () => { + const states: EngineState[] = [] + const engine = new EngineManager({ + binary: "/bin/sleep", + source: "override", + env: { ...process.env }, + startTimeoutMs: 500, + onState: (state) => states.push(state), + }) + await expect(engine.start()).rejects.toThrow() + expect(states.at(-1)?.status).toBe("failed") + }) +}) diff --git a/apps/desktop/test/fixture/fake-engine.ts b/apps/desktop/test/fixture/fake-engine.ts new file mode 100755 index 0000000000..d2610c5af7 --- /dev/null +++ b/apps/desktop/test/fixture/fake-engine.ts @@ -0,0 +1,24 @@ +#!/usr/bin/env bun +// Stands in for `redrob serve`: checks the credential the manager passes, answers the two health +// routes, and prints the same handshake line. `FAKE_ENGINE_CRASH_AFTER_MS` makes it exit on its own. +const user = process.env["REDROB_SERVER_USERNAME"] +const pass = process.env["REDROB_SERVER_PASSWORD"] +const expected = `Basic ${Buffer.from(`${user}:${pass}`).toString("base64")}` + +const server = Bun.serve({ + hostname: "127.0.0.1", + port: 0, + fetch(request) { + if (request.headers.get("authorization") !== expected) return new Response("no", { status: 401 }) + const url = new URL(request.url) + if (url.pathname === "/global/health") return Response.json({ healthy: true, version: "9.9.9" }) + if (url.pathname === "/api/health") return Response.json({ healthy: true }) + return new Response("missing", { status: 404 }) + }, +}) + +console.log(`client=${process.env["REDROB_CLIENT"]} autoupdate=${process.env["REDROB_DISABLE_AUTOUPDATE"] ?? ""}`) +console.log(`redrob server listening on http://127.0.0.1:${server.port}`) + +const crash = Number(process.env["FAKE_ENGINE_CRASH_AFTER_MS"] ?? 0) +if (crash > 0) setTimeout(() => process.exit(3), crash) diff --git a/apps/desktop/test/listening.test.ts b/apps/desktop/test/listening.test.ts new file mode 100644 index 0000000000..24255b8d9a --- /dev/null +++ b/apps/desktop/test/listening.test.ts @@ -0,0 +1,25 @@ +import { expect, test } from "bun:test" +import { readFileSync } from "node:fs" +import path from "node:path" +import { SERVER_LISTENING_PREFIX, createLineScanner, parseListeningUrl } from "../src/main/listening" + +test("the prefix is the one serve.ts prints", () => { + // Duplicated rather than imported (see listening.ts). This is what keeps the copy honest: a reworded + // line would hang the app at startup with nothing to read. + const serve = readFileSync(path.resolve(import.meta.dir, "../../../packages/redrob/src/cli/cmd/serve.ts"), "utf8") + expect(serve).toContain(`export const SERVER_LISTENING_PREFIX = "${SERVER_LISTENING_PREFIX}"`) +}) + +test("parses the url and ignores everything else", () => { + expect(parseListeningUrl("redrob server listening on http://127.0.0.1:51234")).toBe("http://127.0.0.1:51234") + expect(parseListeningUrl("Warning: REDROB_SERVER_PASSWORD is not set; server is unsecured.")).toBeNull() +}) + +test("finds the line across chunk boundaries, once", () => { + const urls: string[] = [] + const scan = createLineScanner((url) => urls.push(url)) + scan("booting\nredrob server list") + scan("ening on http://127.0.0.1:4") + scan("096\nredrob server listening on http://127.0.0.1:9\n") + expect(urls).toEqual(["http://127.0.0.1:4096"]) +}) diff --git a/apps/desktop/test/misc.test.ts b/apps/desktop/test/misc.test.ts new file mode 100644 index 0000000000..7411dac6a5 --- /dev/null +++ b/apps/desktop/test/misc.test.ts @@ -0,0 +1,76 @@ +import { expect, test } from "bun:test" +import { readFileSync } from "node:fs" +import path from "node:path" +import { installCommand } from "../src/main/cli" +import { parseStatus } from "../src/main/git" +import { resolveRendererFile } from "../src/main/renderer-protocol" +import { normalize } from "../src/main/settings" +import { parseEnv } from "../src/main/shell-env" + +test("the renderer protocol never serves a file outside the bundle", () => { + const root = "/app/studio" + const exists = (file: string) => file === "/app/studio/assets/a.js" || file === "/app/studio/index.html" + expect(resolveRendererFile(root, "oc://renderer/assets/a.js", exists)).toBe("file:///app/studio/assets/a.js") + expect(resolveRendererFile(root, "oc://renderer/sessions/123", exists)).toBe("file:///app/studio/index.html") + // URL parsing already collapses dot segments at the root, so this one stays inside the bundle... + expect(resolveRendererFile(root, "oc://renderer/%2e%2e/%2e%2e/etc/passwd", exists)).toStartWith("file:///app/studio/") + // ...and an encoded slash, which survives URL parsing and only becomes `../` once decoded, is refused. + expect(resolveRendererFile(root, "oc://renderer/..%2f..%2fetc%2fpasswd", exists)).toBeNull() + expect(resolveRendererFile(root, "oc://elsewhere/index.html", exists)).toBeNull() +}) + +test("settings default to sending nothing, and repair a bad file", () => { + const settings = normalize( + { consent: { crashReports: "yes" }, theme: "neon", recentProjects: ["/a", "/a", 3] }, + "ko-KR", + ) + expect(settings.consent).toEqual({ crashReports: false, usageEvents: false, decided: false }) + expect(settings.theme).toBe("system") + expect(settings.language).toBe("ko") + expect(settings.recentProjects).toEqual(["/a"]) +}) + +test("the install button runs the official installer, nothing else", () => { + expect(installCommand("win32").display).toBe("irm https://console.redrob.ai/code/install.ps1 | iex") + expect(installCommand("linux").display).toBe("curl -fsSL https://console.redrob.ai/code/install.sh | sh") +}) + +test("parses porcelain status, including renames and untracked files", () => { + const output = [" M src/a.ts", "?? new.ts", "R new-name.ts", "old-name.ts", "D gone.ts", "A added.ts", ""].join( + "\0", + ) + expect(parseStatus(output)).toEqual([ + { path: "src/a.ts", status: "modified" }, + { path: "new.ts", status: "untracked" }, + { path: "new-name.ts", status: "renamed" }, + { path: "gone.ts", status: "deleted" }, + { path: "added.ts", status: "added" }, + ]) +}) + +test("parses a NUL-separated environment", () => { + expect(parseEnv("PATH=/usr/bin:/bin\0EMPTY=\0A=b=c\0REDROB_CODE_RESOLVING_ENV=1\0")).toEqual({ + PATH: "/usr/bin:/bin", + EMPTY: "", + A: "b=c", + }) +}) + +test("desktop artifact names can never match what the CLI installer downloads", () => { + // install looks for `redrob-$target$archive_ext` with APP=redrob; release.yml also publishes + // redrob-code-x64-setup.exe. Every desktop artifact starts `redrob-code-desktop-`. + const config = readFileSync(path.resolve(import.meta.dir, "../electron-builder.config.mjs"), "utf8") + expect(config).toContain('artifactName: "redrob-code-desktop-${version}-${os}-${arch}.${ext}"') + const install = readFileSync(path.resolve(import.meta.dir, "../../../install"), "utf8") + expect(install).toContain("APP=redrob") + expect(install).toContain('filename="$APP-$target$archive_ext"') + const cliTargets = /^redrob-(linux|darwin|windows)-(x64|arm64)(-baseline)?(-musl)?\.(zip|tar\.gz)$/ + for (const name of [ + "redrob-code-desktop-0.4.0-win-x64.exe", + "redrob-code-desktop-0.4.0-linux-x86_64.AppImage", + "redrob-code-desktop-0.4.0-mac-arm64.dmg", + ]) { + expect(cliTargets.test(name)).toBe(false) + expect(name).not.toBe("redrob-code-x64-setup.exe") + } +}) diff --git a/apps/desktop/test/sign-in.test.ts b/apps/desktop/test/sign-in.test.ts new file mode 100644 index 0000000000..b1839e495f --- /dev/null +++ b/apps/desktop/test/sign-in.test.ts @@ -0,0 +1,82 @@ +import { expect, test } from "bun:test" +import type { SignInProgress } from "../src/shared/bridge" +import { consoleUrl, createSignIn, deviceError } from "../src/main/sign-in" + +function console_(replies: { status: number; body: unknown }[]) { + const calls: { url: string; body: unknown }[] = [] + const fetch = (async (url: string, init?: RequestInit) => { + calls.push({ url, body: JSON.parse(String(init?.body)) }) + const reply = replies.shift()! + return new Response(JSON.stringify(reply.body), { status: reply.status }) + }) as unknown as typeof globalThis.fetch + return { fetch, calls } +} + +const authorization = { + status: 200, + body: { + deviceCode: "dev-1", + userCode: "K7QM-2XR9", + verificationUri: "https://console.redrob.ai/connect", + verificationUriComplete: "https://console.redrob.ai/connect?code=K7QM-2XR9", + expiresIn: 600, + interval: 5, + }, +} + +test("the key goes to the engine and never into a progress event", async () => { + const { fetch, calls } = console_([ + authorization, + { status: 400, body: { error: "authorization_pending" } }, + { status: 400, body: { error: "slow_down" } }, + { status: 200, body: { apiKey: "rrk_abc_secret", apiKeyName: "Redrob Code (2026-10-11)", accountName: "Acme" } }, + ]) + const progress: SignInProgress[] = [] + const stored: string[] = [] + const opened: string[] = [] + const waits: number[] = [] + const flow = createSignIn({ + consoleUrl: "https://c/api/backend/v1", + fetch, + openExternal: async (url) => void opened.push(url), + storeKey: async (key) => void stored.push(key), + onProgress: (p) => progress.push(p), + sleep: async (ms) => void waits.push(ms), + now: () => 0, + }) + await flow.start() + + expect(calls[0]).toEqual({ url: "https://c/api/backend/v1/device/authorize", body: { product: "code" } }) + expect(opened).toEqual(["https://console.redrob.ai/connect?code=K7QM-2XR9"]) + expect(stored).toEqual(["rrk_abc_secret"]) + // slow_down adds five seconds to every later poll, as RFC 8628 asks. + expect(waits).toEqual([5000, 5000, 10000]) + expect(progress.at(-1)).toEqual({ status: "connected", accountName: "Acme", keyName: "Redrob Code (2026-10-11)" }) + expect(JSON.stringify(progress)).not.toContain("rrk_abc_secret") +}) + +test("a denial stops polling and says so", async () => { + const { fetch } = console_([authorization, { status: 403, body: { error: { code: "access_denied" } } }]) + const progress: SignInProgress[] = [] + const flow = createSignIn({ + consoleUrl: "https://c", + fetch, + openExternal: async () => {}, + storeKey: async () => { + throw new Error("must not be called") + }, + onProgress: (p) => progress.push(p), + sleep: async () => {}, + now: () => 0, + }) + await flow.start() + expect(progress.at(-1)).toEqual({ status: "denied" }) +}) + +test("console url honours the engine's override flag", () => { + expect(consoleUrl({})).toBe("https://console.redrob.ai/api/backend/v1") + expect(consoleUrl({ REDROB_CONSOLE_URL: "http://localhost:3001/api/backend/v1/" })).toBe( + "http://localhost:3001/api/backend/v1", + ) + expect(deviceError({ code: "expired_token" })).toBe("expired_token") +}) diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json new file mode 100644 index 0000000000..2e0079e8a8 --- /dev/null +++ b/apps/desktop/tsconfig.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@tsconfig/bun/tsconfig.json", + "compilerOptions": { + "lib": ["ESNext", "DOM"], + "types": ["node", "bun"], + "noUncheckedIndexedAccess": false + }, + "include": ["src", "script", "test"] +} diff --git a/bun.lock b/bun.lock index 94ec851482..60ec870acc 100644 --- a/bun.lock +++ b/bun.lock @@ -27,6 +27,42 @@ "turbo": "2.10.2", }, }, + "apps/desktop": { + "name": "@redrob-code/desktop", + "version": "0.0.0", + "devDependencies": { + "@tsconfig/bun": "catalog:", + "@types/bun": "catalog:", + "@types/node": "catalog:", + "@typescript/native-preview": "catalog:", + "electron": "43.7.9", + "electron-builder": "26.17.0", + "electron-updater": "6.8.10", + }, + }, + "apps/studio": { + "name": "@redrob-code/studio", + "version": "0.0.0", + "dependencies": { + "@redrob-code/client": "workspace:*", + "@redrob-labs/ui": "1.3.1", + "@tanstack/react-query": "5.104.1", + "@xterm/addon-fit": "0.11.0", + "@xterm/xterm": "6.0.0", + "react": "19.3.0", + "react-dom": "19.3.0", + }, + "devDependencies": { + "@redrob-code/desktop": "workspace:*", + "@tsconfig/bun": "catalog:", + "@types/bun": "catalog:", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "@typescript/native-preview": "catalog:", + "@vitejs/plugin-react": "6.1.2", + "vite": "8.3.3", + }, + }, "packages/cli": { "name": "@redrob-code/cli", "version": "0.0.0", @@ -866,6 +902,24 @@ "@effect/sql-sqlite-bun": ["@effect/sql-sqlite-bun@4.0.0-beta.83", "", { "peerDependencies": { "effect": "^4.0.0-beta.83" } }, "sha512-6OaxLsWffxkh9pXYUSyj/AxjVb9URY2rG9U6atjxClWy30Jx77R9Pm3Rrc7cQ63kQurePavEw1bQbzQ/SILiQQ=="], + "@electron-internal/extract-zip": ["@electron-internal/extract-zip@1.0.5", "", {}, "sha512-+bqFCP98pLI0Tt0XQo1TmlXtwjWchISndDOxCkEcIuUgXWpBnLyRI+2DU+mesvnMMX6L1XDqYNA0lXNDHd/yiA=="], + + "@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="], + + "@electron/fuses": ["@electron/fuses@1.8.0", "", { "dependencies": { "chalk": "^4.1.1", "fs-extra": "^9.0.1", "minimist": "^1.2.5" }, "bin": { "electron-fuses": "dist/bin.js" } }, "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw=="], + + "@electron/get": ["@electron/get@5.1.0", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^3.0.0", "graceful-fs": "^4.2.11", "progress": "^2.0.3", "semver": "^7.6.3", "sumchecker": "^3.0.1" }, "optionalDependencies": { "undici": "^7.24.4" } }, "sha512-3kSBtG8ObcTVfXanm5vVJ6UnBLEVmVsRk1M+vGqCuMBV+XLCbJYuWQful+yIy0GQDsSlK0kHEriEHn7SPk4EnA=="], + + "@electron/notarize": ["@electron/notarize@2.5.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.1", "promise-retry": "^2.0.1" } }, "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A=="], + + "@electron/osx-sign": ["@electron/osx-sign@1.3.3", "", { "dependencies": { "compare-version": "^0.1.2", "debug": "^4.3.4", "fs-extra": "^10.0.0", "isbinaryfile": "^4.0.8", "minimist": "^1.2.6", "plist": "^3.0.5" }, "bin": { "electron-osx-flat": "bin/electron-osx-flat.js", "electron-osx-sign": "bin/electron-osx-sign.js" } }, "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg=="], + + "@electron/rebuild": ["@electron/rebuild@4.2.1", "", { "dependencies": { "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.1.1", "node-abi": "^4.2.0", "node-api-version": "^0.2.1", "node-gyp": "^12.2.0", "read-binary-file-arch": "^1.0.6" }, "bin": { "electron-rebuild": "lib/cli.js" } }, "sha512-IS4pG3BysRrCLJ8xTAfvg+IQDbiUoAzdJy2SfjcYKnLDy5DpCdjnPdhS5Zf5UMfrkB24xu0KC+TqcOxg+eG+6w=="], + + "@electron/universal": ["@electron/universal@2.0.3", "", { "dependencies": { "@electron/asar": "^3.3.1", "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.3.1", "dir-compare": "^4.2.0", "fs-extra": "^11.1.1", "minimatch": "^9.0.3", "plist": "^3.1.0" } }, "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g=="], + + "@electron/windows-sign": ["@electron/windows-sign@1.2.2", "", { "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", "fs-extra": "^11.1.1", "minimist": "^1.2.8", "postject": "^1.0.0-alpha.6" }, "bin": { "electron-windows-sign": "bin/electron-windows-sign.js" } }, "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], @@ -1016,6 +1070,10 @@ "@lydell/node-pty-win32-x64": ["@lydell/node-pty-win32-x64@1.2.0-beta.12", "", { "os": "win32", "cpu": "x64" }, "sha512-IDFa00g7qUDGUYgByrUBJtC+mOjYVt/8KYyWivCg5JjGOHbBUACUQZLl0jTWmnr+tld/UyTpX90a2PY6oTVtRw=="], + "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@2.0.0", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg=="], + + "@malept/flatpak-bundler": ["@malept/flatpak-bundler@0.4.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.0", "lodash": "^4.17.15", "tmp-promise": "^3.0.2" } }, "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q=="], + "@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="], "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="], @@ -1032,6 +1090,8 @@ "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="], + "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "@nodable/entities": ["@nodable/entities@3.0.0", "", {}, "sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw=="], "@npm/types": ["@npm/types@1.0.2", "", {}, "sha512-KXZccTDEnWqNrrx6JjpJKU/wJvNeg9BDgjS0XhmlZab7br921HtyVbsYzJr4L+xIvjdJ20Wh9dgxgCI2a5CEQw=="], @@ -1160,6 +1220,8 @@ "@oslojs/jwt": ["@oslojs/jwt@0.2.0", "", { "dependencies": { "@oslojs/encoding": "0.4.1" } }, "sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg=="], + "@oxc-project/types": ["@oxc-project/types@0.153.0", "", {}, "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw=="], + "@oxlint-tsgolint/darwin-arm64": ["@oxlint-tsgolint/darwin-arm64@0.21.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-P20j3MLqfwIT+94qGU3htC7dWp4pXGZW1p1p7FRUzu1aopq7c9nPCgf0W/WjktqQ57+iuTq9mbSlwWinl6+H1A=="], "@oxlint-tsgolint/darwin-x64": ["@oxlint-tsgolint/darwin-x64@0.21.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-81TmmuBcPedEA0MwRmObuQuXnCprS1UiHQWGe7pseqNAJzUWXeAPrayqKTACX92VpruJI+yvY0XJrFp11PpcTA=="], @@ -1242,6 +1304,14 @@ "@pdf-lib/upng": ["@pdf-lib/upng@1.0.1", "", { "dependencies": { "pako": "^1.0.10" } }, "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ=="], + "@peculiar/asn1-schema": ["@peculiar/asn1-schema@2.10.0", "", { "dependencies": { "@peculiar/utils": "^2.0.2", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-GhokD41lV4gQrrLYm3wCkHfBOnJrnhDMgt4XeMW8gzfE1UdJqIuSwsE+ggf82XBjUXRJylcm+KIGQFa4utIVLw=="], + + "@peculiar/json-schema": ["@peculiar/json-schema@1.1.12", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w=="], + + "@peculiar/utils": ["@peculiar/utils@2.0.3", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ=="], + + "@peculiar/webcrypto": ["@peculiar/webcrypto@1.7.1", "", { "dependencies": { "@peculiar/asn1-schema": "^2.7.0", "@peculiar/json-schema": "^1.1.12", "@peculiar/utils": "^2.0.2", "tslib": "^2.8.1", "webcrypto-core": "^1.9.2" } }, "sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ=="], + "@pierre/diffs": ["@pierre/diffs@1.2.10", "", { "dependencies": { "@pierre/theme": "1.0.3", "@pierre/theming": "0.0.1", "@shikijs/transformers": "^3.0.0 || ^4.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0 || ^4.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-rPeAmDWarxFVTQpaf4y6wTxjZxU44xKJKoJti2zU21P06DVd9nRHZX+xSIObLB307Qjpaesyb1x/j0z94t7vLw=="], "@pierre/theme": ["@pierre/theme@1.0.3", "", {}, "sha512-sWHv11TMoqKxKDgTIk5VbhQjdPhs8DCcBxbjh3mRlS3YOM/OcrWoGX6MM8eBGn9cUu3M46Py0JnxsG2nJaFTuA=="], @@ -1286,6 +1356,8 @@ "@redrob-code/core": ["@redrob-code/core@workspace:packages/core"], + "@redrob-code/desktop": ["@redrob-code/desktop@workspace:apps/desktop"], + "@redrob-code/effect-drizzle-sqlite": ["@redrob-code/effect-drizzle-sqlite@workspace:packages/effect-drizzle-sqlite"], "@redrob-code/effect-sqlite-node": ["@redrob-code/effect-sqlite-node@workspace:packages/effect-sqlite-node"], @@ -1310,10 +1382,46 @@ "@redrob-code/slack": ["@redrob-code/slack@workspace:packages/slack"], + "@redrob-code/studio": ["@redrob-code/studio@workspace:apps/studio"], + "@redrob-code/tui": ["@redrob-code/tui@workspace:packages/tui"], "@redrob-labs/sdk": ["@redrob-labs/sdk@workspace:packages/sdk/js"], + "@redrob-labs/ui": ["@redrob-labs/ui@1.3.1", "", { "peerDependencies": { "react": "^18.2.0 || ^19.0.0", "react-dom": "^18.2.0 || ^19.0.0" } }, "sha512-fEaubVbps9rT7b9RNTikWo8GU5yaGbbzdCY4Y2wjFaZZ2V6TuzqN+/XwGjDUmpNZN54xKmegYZ8ZXFG+sTgFzA=="], + + "@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.13", "", { "os": "android", "cpu": "arm" }, "sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.13", "", { "os": "android", "cpu": "arm64" }, "sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.13", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.13", "", { "os": "linux", "cpu": "arm" }, "sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.13", "", { "os": "linux", "cpu": "ppc64" }, "sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.13", "", { "os": "linux", "cpu": "s390x" }, "sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.13", "", { "os": "linux", "cpu": "x64" }, "sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.13", "", { "os": "linux", "cpu": "x64" }, "sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.13", "", { "os": "none", "cpu": "arm64" }, "sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.13", "", { "os": "win32", "cpu": "arm64" }, "sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.13", "", { "os": "win32", "cpu": "x64" }, "sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], + "@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="], "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-fjETeq1k5ffyXqRgS6+3hpvqseLalp1kjNfRbXpUgWR8FpZ1CmQfiNHovc5lncYjt/Vg5JK/WJEmLahjwMa0og=="], @@ -1346,6 +1454,8 @@ "@silvia-odwyer/photon-node": ["@silvia-odwyer/photon-node@0.3.4", "", {}, "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA=="], + "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], + "@slack/bolt": ["@slack/bolt@3.22.0", "", { "dependencies": { "@slack/logger": "^4.0.0", "@slack/oauth": "^2.6.3", "@slack/socket-mode": "^1.3.6", "@slack/types": "^2.13.0", "@slack/web-api": "^6.13.0", "@types/express": "^4.16.1", "@types/promise.allsettled": "^1.0.3", "@types/tsscmp": "^1.0.0", "axios": "^1.7.4", "express": "^4.21.0", "path-to-regexp": "^8.1.0", "promise.allsettled": "^1.0.2", "raw-body": "^2.3.3", "tsscmp": "^1.0.6" } }, "sha512-iKDqGPEJDnrVwxSVlFW6OKTkijd7s4qLBeSufoBsTM0reTyfdp/5izIQVkxNfzjHi3o6qjdYbRXkYad5HBsBog=="], "@slack/logger": ["@slack/logger@4.0.1", "", { "dependencies": { "@types/node": ">=18" } }, "sha512-6cmdPrV/RYfd2U0mDGiMK8S7OJqpCTm7enMLRR3edccsPX8j7zXTLnaEF4fhxxJJTAIOil6+qZrnUPTuaLvwrQ=="], @@ -1452,6 +1562,12 @@ "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], + "@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="], + + "@tanstack/query-core": ["@tanstack/query-core@5.104.1", "", {}, "sha512-TiRghcrGkUTE+M4JBxvpPSpBhxvIzQairw+cksrHYGr+2uwC77yW1SY9nYxnYRYOppvucf8rEg6b5byoz4rTpw=="], + + "@tanstack/react-query": ["@tanstack/react-query@5.104.1", "", { "dependencies": { "@tanstack/query-core": "5.104.1" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-Zz70EgjahNI7aCz8BZxM3vUGU44ycxEAzMuCUjLeA7SVhhOkg3ZgPjPJPvHjs6Rmky/dtmnu1WzjGHNHmhCoZQ=="], + "@tsconfig/bun": ["@tsconfig/bun@1.0.9", "", {}, "sha512-4M0/Ivfwcpz325z6CwSifOBZYji3DFOEpY6zEUt0+Xi2qRhzwvmqQN9XAHJh3OVvRJuAqVTLU2abdCplvp6mwQ=="], "@tsconfig/node22": ["@tsconfig/node22@22.0.2", "", {}, "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA=="], @@ -1486,16 +1602,24 @@ "@types/cacache": ["@types/cacache@20.0.1", "", { "dependencies": { "@types/node": "*", "minipass": "*" } }, "sha512-QlKW3AFoFr/hvPHwFHMIVUH/ZCYeetBNou3PCmxu5LaNDvrtBlPJtIA6uhmU9JRt9oxj7IYoqoLcpxtzpPiTcw=="], + "@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="], + "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], "@types/cross-spawn": ["@types/cross-spawn@6.0.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA=="], + "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], + "@types/express": ["@types/express@4.17.25", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "^1" } }, "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw=="], "@types/express-serve-static-core": ["@types/express-serve-static-core@4.19.9", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg=="], + "@types/fs-extra": ["@types/fs-extra@9.0.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA=="], + "@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="], + "@types/http-cache-semantics": ["@types/http-cache-semantics@4.2.0", "", {}, "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q=="], + "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="], "@types/is-stream": ["@types/is-stream@1.1.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg=="], @@ -1504,12 +1628,16 @@ "@types/jsonwebtoken": ["@types/jsonwebtoken@8.5.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg=="], + "@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="], + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], "@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="], "@types/mime-types": ["@types/mime-types@3.0.1", "", {}, "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ=="], + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + "@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="], @@ -1532,6 +1660,12 @@ "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], + "@types/react": ["@types/react@19.3.0", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg=="], + + "@types/react-dom": ["@types/react-dom@19.3.0", "", { "peerDependencies": { "@types/react": "^19.3.0" } }, "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q=="], + + "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="], + "@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="], "@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="], @@ -1580,6 +1714,14 @@ "@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@6.1.2", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "oxc-transform-react": "^0.152.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler", "oxc-transform-react"] }, "sha512-fhJNGUdscQa4HkI0xFxwonWyhnVReCPDbXI7FH7jmzpnVCrFQI7PRrhWecO263610vOrs9tr2Y4xcf8uAdFxbw=="], + + "@xmldom/xmldom": ["@xmldom/xmldom@0.8.15", "", {}, "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA=="], + + "@xterm/addon-fit": ["@xterm/addon-fit@0.11.0", "", {}, "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g=="], + + "@xterm/xterm": ["@xterm/xterm@6.0.0", "", {}, "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg=="], + "@zip.js/zip.js": ["@zip.js/zip.js@2.7.62", "", {}, "sha512-OaLvZ8j4gCkLn048ypkZu29KX30r8/OfFF2w4Jo5WXFr+J04J+lzJ5TKZBVgFXhlvSkqNFQdfnY1Q8TMTCyBVA=="], "abbrev": ["abbrev@4.0.0", "", {}, "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA=="], @@ -1606,10 +1748,12 @@ "ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="], - "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "anynum": ["anynum@1.0.1", "", {}, "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A=="], + "app-builder-lib": ["app-builder-lib@26.17.0", "", { "dependencies": { "@electron/asar": "3.4.1", "@electron/fuses": "^1.8.0", "@electron/get": "^3.0.0", "@electron/notarize": "2.5.0", "@electron/osx-sign": "1.3.3", "@electron/rebuild": "^4.0.4", "@electron/universal": "2.0.3", "@malept/flatpak-bundler": "^0.4.0", "@noble/hashes": "^1.8.0", "@peculiar/webcrypto": "^1.7.1", "@types/fs-extra": "9.0.13", "ajv": "^8.18.0", "asn1js": "^3.0.10", "async-exit-hook": "^2.0.1", "builder-util": "26.16.0", "builder-util-runtime": "9.7.0", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", "debug": "^4.3.4", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", "electron-publish": "26.16.0", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", "jiti": "^2.4.2", "js-yaml": "^4.1.0", "json5": "^2.2.3", "lazy-val": "^1.0.5", "minimatch": "^10.2.5", "pkijs": "^3.4.0", "plist": "3.1.0", "proper-lockfile": "^4.1.2", "resedit": "^1.7.0", "semver": "~7.7.3", "tar": "^7.5.7", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0", "unzipper": "^0.12.3", "which": "^5.0.0" }, "peerDependencies": { "dmg-builder": "26.17.0", "electron-builder-squirrel-windows": "26.17.0" } }, "sha512-wfL7EhujuODDW3uMSD8RPcr/MLzmT0dmpVfWV0wFUWXI0rIdaukzAJ7EFUSyHXOpxuBZqaIGlQt6VeQhtHhetw=="], + "archiver": ["archiver@7.0.1", "", { "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", "zip-stream": "^6.0.1" } }, "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ=="], "archiver-utils": ["archiver-utils@5.0.2", "", { "dependencies": { "glob": "^10.0.0", "graceful-fs": "^4.2.0", "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA=="], @@ -1626,18 +1770,26 @@ "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], + "asn1js": ["asn1js@3.0.10", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.5", "tslib": "^2.8.1" } }, "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg=="], + "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + "async-exit-hook": ["async-exit-hook@2.0.1", "", {}, "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw=="], + "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + "at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="], + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], "avvio": ["avvio@9.3.0", "", { "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" } }, "sha512-g2tQ7LE7oOSqDfwEm3M+ZCMTJc7KiZCdJ4UwyZJb5ckTKyYu50OYmvv0mCFXPuYXoM4zkSt8zM9XQ9KCvxA74A=="], + "aws4": ["aws4@1.13.2", "", {}, "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw=="], + "aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="], "axios": ["axios@1.20.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg=="], @@ -1684,6 +1836,8 @@ "bonjour-service": ["bonjour-service@1.3.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA=="], + "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="], + "bottleneck": ["bottleneck@2.19.5", "", {}, "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="], "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], @@ -1700,10 +1854,16 @@ "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "buffer-indexof-polyfill": ["buffer-indexof-polyfill@1.0.2", "", {}, "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A=="], "buffers": ["buffers@0.1.1", "", {}, "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="], + "builder-util": ["builder-util@26.16.0", "", { "dependencies": { "@types/debug": "^4.1.6", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "cross-spawn": "^7.0.6", "debug": "^4.3.4", "fs-extra": "^10.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "js-yaml": "^4.1.0", "sanitize-filename": "^1.6.3", "source-map-support": "^0.5.19", "stat-mode": "^1.0.0", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0" } }, "sha512-RLyJhB7Si3YkzKR9ubQslWuXW3Vhs3CGe1i+SeixBZ0qTd1mk3XBmssvY22TlB6CS5blyko8Gu1JzpYk8UkYAg=="], + + "builder-util-runtime": ["builder-util-runtime@9.7.0", "", { "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" } }, "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw=="], + "bun-ffi-structs": ["bun-ffi-structs@0.2.4", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-AJzsqoVFs1KBbJbWHIYrVZLDC3NhTqqh25awRXqzoLzmBAKr5oqk6+CwuYHAekKx+VBCYVohBoKuRq40dV+TYg=="], "bun-pty": ["bun-pty@0.4.8", "", {}, "sha512-rO70Mrbr13+jxHHHu2YBkk2pNqrJE5cJn29WE++PUr+GFA0hq/VgtQPZANJ8dJo6d7XImvBk37Innt8GM7O28w=="], @@ -1714,10 +1874,16 @@ "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + "bytestreamjs": ["bytestreamjs@2.0.1", "", {}, "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ=="], + "c12": ["c12@3.3.3", "", { "dependencies": { "chokidar": "^5.0.0", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q=="], "cacache": ["cacache@20.0.4", "", { "dependencies": { "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", "glob": "^13.0.0", "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^13.0.0" } }, "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA=="], + "cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="], + + "cacheable-request": ["cacheable-request@7.0.4", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^6.0.1", "responselike": "^2.0.0" } }, "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg=="], + "call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], @@ -1730,6 +1896,8 @@ "chainsaw": ["chainsaw@0.1.0", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="], + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], @@ -1738,6 +1906,8 @@ "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + "chromium-pickle-js": ["chromium-pickle-js@0.2.0", "", {}, "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw=="], + "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], @@ -1748,6 +1918,8 @@ "cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="], + "clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="], + "cluster-key-slot": ["cluster-key-slot@1.1.1", "", {}, "sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw=="], "cmd-shim": ["cmd-shim@8.0.0", "", {}, "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA=="], @@ -1766,6 +1938,8 @@ "common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="], + "compare-version": ["compare-version@0.1.2", "", {}, "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A=="], + "compress-commons": ["compress-commons@6.0.2", "", { "dependencies": { "crc-32": "^1.2.0", "crc32-stream": "^6.0.0", "is-stream": "^2.0.1", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg=="], "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], @@ -1792,6 +1966,8 @@ "crc32-stream": ["crc32-stream@6.0.0", "", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^4.0.0" } }, "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g=="], + "cross-dirname": ["cross-dirname@0.1.0", "", {}, "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q=="], + "cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], @@ -1814,10 +1990,14 @@ "decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="], + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + "default-browser": ["default-browser@5.5.1", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw=="], "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], + "defer-to-connect": ["defer-to-connect@2.0.1", "", {}, "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="], + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], @@ -1842,10 +2022,16 @@ "detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], + "detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="], + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], "diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="], + "dir-compare": ["dir-compare@4.2.0", "", { "dependencies": { "minimatch": "^3.0.5", "p-limit": "^3.1.0 " } }, "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ=="], + + "dmg-builder": ["dmg-builder@26.17.0", "", { "dependencies": { "app-builder-lib": "26.17.0", "builder-util": "26.16.0", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0" } }, "sha512-EAw1f2iGkICmUvAVBOjm7v5mxJdB7/+OZSycHFhKBimiTkCXtvkCC6wBKzHo7ZQYi58C2MmwIleRbgRnROlK2Q=="], + "dns-packet": ["dns-packet@5.6.1", "", { "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" } }, "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw=="], "docx": ["docx@9.8.1", "", { "dependencies": { "@types/node": "^26.6.2", "hash.js": "^1.1.7", "jszip": "^3.10.1", "nanoid": "^6.0.1", "xml": "^1.0.1", "xml-js": "^1.6.8" } }, "sha512-bv4yDxgiV9aRyh327HG+V36wG0f23wk32SDhXGQpUhnykVtIPaR1K4ISi8E+DYJ7OcRQRkR8Y70QYlSS08V1cA=="], @@ -1858,7 +2044,9 @@ "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], - "dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], + "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + + "dotenv-expand": ["dotenv-expand@11.0.7", "", { "dependencies": { "dotenv": "^16.4.5" } }, "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA=="], "drizzle-kit": ["drizzle-kit@1.0.0-rc.2", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "get-tsconfig": "^4.13.6", "jiti": "^2.6.1" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-TRxUmj1wDA2QCt3GvuhfamvIa66wJ7+MzSxBMKkpRtYScjHTumT9BE+x6daSzuEacSrPEuUH5/cW1uo5RkoPIg=="], @@ -1876,8 +2064,22 @@ "effect": ["effect@4.0.0-beta.83", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-0wsak8RtgGAr9UWSbVDgJHZcUqMSvicHcvaZv1MbMM7MCGgW4Rn/137J1MHQbwYPcwYGxT/IqehFd+UbYuj78w=="], + "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], + + "electron": ["electron@43.7.9", "", { "dependencies": { "@electron-internal/extract-zip": "^1.0.1", "@electron/get": "^5.0.0", "@types/node": "^24.9.0" }, "bin": { "electron": "cli.js", "install-electron": "install.js" } }, "sha512-wZkQ/R9F5Bv8dM5vX6vWboNo5ZCBC/IYQV195dtG9JvFAlNbebzlkWWSE5esgpCVy3rVYQ/g09GeknJwvQnS9A=="], + + "electron-builder": ["electron-builder@26.17.0", "", { "dependencies": { "app-builder-lib": "26.17.0", "builder-util": "26.16.0", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.17.0", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "./cli.js", "install-app-deps": "./install-app-deps.js" } }, "sha512-iYHBRiagS9sDIbZx1ZD113f5rEGQvtpvTvHf70ovHKJ8mRvxwIkWX7JCwfmVQmVS4eX735p7TZmoxHnBPwF3vA=="], + + "electron-builder-squirrel-windows": ["electron-builder-squirrel-windows@26.17.0", "", { "dependencies": { "app-builder-lib": "26.17.0", "builder-util": "26.16.0", "electron-winstaller": "5.4.0" } }, "sha512-uyh+D+B0m12qCRp9c/7dwBirHTh7JcvAzj5eTEbb4dXPPGAhYPx1nozzbu1fC6AdULfat6ZNqgWhipTFpb82lQ=="], + + "electron-publish": ["electron-publish@26.16.0", "", { "dependencies": { "@types/fs-extra": "^9.0.11", "aws4": "^1.13.2", "builder-util": "26.16.0", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "form-data": "^4.0.5", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "mime": "^2.5.2" } }, "sha512-Vt3KzQIiw9BImvNOYtndg9Mjki+tl4+1sQiC/+G5j8khWaENOJFWodiB+sUl6yyHwtd37avehskdtPw7f8y/+Q=="], + "electron-to-chromium": ["electron-to-chromium@1.5.415", "", {}, "sha512-958V+Kbhtgz+SxXeEVKBjrlKRBIDAYvUJfwhjxMZ5S6ut9jAl7l9ZKBkBrvjyjZE36PabLUo2L8kEeV5O4vgJg=="], + "electron-updater": ["electron-updater@6.8.10", "", { "dependencies": { "builder-util-runtime": "9.7.0", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.5.0", "semver": "~7.7.3", "tiny-typed-emitter": "^2.1.0" } }, "sha512-6ZSwTUoBNtI1f9Pp8y6zCigD8LKV18Rb1+1A7sZzvKvA17o2sqSqKmm0t6uvKeF+HZqA4vsmw4qq0JSS241USQ=="], + + "electron-winstaller": ["electron-winstaller@5.4.0", "", { "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", "fs-extra": "^7.0.1", "lodash": "^4.17.21", "temp": "^0.9.0" }, "optionalDependencies": { "@electron/windows-sign": "^1.1.2" } }, "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg=="], + "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], @@ -1890,7 +2092,9 @@ "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], - "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + "env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="], + + "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="], "es-abstract": ["es-abstract@1.24.2", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg=="], @@ -1910,12 +2114,16 @@ "es-to-primitive": ["es-to-primitive@1.3.4", "", { "dependencies": { "es-abstract-get": "^1.0.0", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "is-callable": "^1.2.7", "is-date-object": "^1.1.0", "is-symbol": "^1.1.1" } }, "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw=="], + "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="], + "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], @@ -1978,6 +2186,8 @@ "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + "filelist": ["filelist@1.0.6", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA=="], + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="], @@ -2008,10 +2218,14 @@ "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], + "fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "fstream": ["fstream@1.0.12", "", { "dependencies": { "graceful-fs": "^4.1.2", "inherits": "~2.0.0", "mkdirp": ">=0.5 0", "rimraf": "2" } }, "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -2050,6 +2264,8 @@ "glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="], + "global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="], + "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], "google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="], @@ -2058,6 +2274,8 @@ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + "got": ["got@11.8.6", "", { "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" } }, "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g=="], + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "graphql": ["graphql@16.14.2", "", {}, "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA=="], @@ -2070,6 +2288,8 @@ "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], "has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="], @@ -2104,6 +2324,8 @@ "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + "http2-wrapper": ["http2-wrapper@1.0.3", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg=="], + "https": ["https@1.0.0", "", {}, "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg=="], "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], @@ -2218,6 +2440,8 @@ "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + "isbinaryfile": ["isbinaryfile@5.0.7", "", {}, "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ=="], + "isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], "isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="], @@ -2228,6 +2452,8 @@ "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + "jake": ["jake@10.9.4", "", { "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" } }, "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA=="], + "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], "jose": ["jose@5.2.3", "", {}, "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA=="], @@ -2242,6 +2468,8 @@ "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + "json-parse-even-better-errors": ["json-parse-even-better-errors@5.0.0", "", {}, "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ=="], "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], @@ -2256,12 +2484,16 @@ "json-stringify-nice": ["json-stringify-nice@1.1.4", "", {}, "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw=="], + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + "json-with-bigint": ["json-with-bigint@3.5.12", "", {}, "sha512-uwbF/wSSuOgC7qqlq27Xp5B6a2MHVug3t0idZdTqu0JnlFvgJuH7ju+KAk/J06C7GfhoYy2gnb9wz2INqcne7w=="], "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + "jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="], @@ -2278,16 +2510,44 @@ "jwt-decode": ["jwt-decode@3.1.2", "", {}, "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="], + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], + "lazy-val": ["lazy-val@1.0.5", "", {}, "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q=="], + "lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="], "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="], "light-my-request": ["light-my-request@6.6.0", "", { "dependencies": { "cookie": "^1.0.1", "process-warning": "^4.0.0", "set-cookie-parser": "^2.6.0" } }, "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A=="], + "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], + "listenercount": ["listenercount@1.0.1", "", {}, "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ=="], "locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="], @@ -2332,7 +2592,7 @@ "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], - "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="], "lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], @@ -2342,6 +2602,8 @@ "marked": ["marked@17.0.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="], + "matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], @@ -2374,6 +2636,8 @@ "mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="], + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + "minimalistic-assert": ["minimalistic-assert@1.0.1", "", {}, "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="], "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], @@ -2412,8 +2676,12 @@ "negotiator": ["negotiator@1.1.0", "", { "dependencies": { "content-type": "^2.1.0" } }, "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg=="], + "node-abi": ["node-abi@4.37.0", "", { "dependencies": { "semver": "^7.6.3" } }, "sha512-O9XXMXLeRHQtvh43NCah3y9N1K69tecOmu8Y5p2ay4iXvtpbQr0VaroJF9EmRVp3vkdoj3qn4IFfX+ZYtC94fw=="], + "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], + "node-api-version": ["node-api-version@0.2.1", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q=="], + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], @@ -2426,12 +2694,16 @@ "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], + "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], + "node-releases": ["node-releases@2.0.53", "", {}, "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ=="], "nopt": ["nopt@9.0.0", "", { "dependencies": { "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw=="], "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + "normalize-url": ["normalize-url@6.1.0", "", {}, "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="], + "npm-bundled": ["npm-bundled@5.0.0", "", { "dependencies": { "npm-normalize-package-bin": "^5.0.0" } }, "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw=="], "npm-install-checks": ["npm-install-checks@8.0.0", "", { "dependencies": { "semver": "^7.1.1" } }, "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA=="], @@ -2494,9 +2766,11 @@ "oxlint-tsgolint": ["oxlint-tsgolint@0.21.0", "", { "optionalDependencies": { "@oxlint-tsgolint/darwin-arm64": "0.21.0", "@oxlint-tsgolint/darwin-x64": "0.21.0", "@oxlint-tsgolint/linux-arm64": "0.21.0", "@oxlint-tsgolint/linux-x64": "0.21.0", "@oxlint-tsgolint/win32-arm64": "0.21.0", "@oxlint-tsgolint/win32-x64": "0.21.0" }, "bin": { "tsgolint": "bin/tsgolint.js" } }, "sha512-HiWPhANwRnN1pZJQ2SgNB3WRR+1etLJHmRzQ/MJhyINsEIaOUCjxhlXJKbEaVUwdnyXwRWqo/P9Fx21lz0/mSg=="], + "p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="], + "p-finally": ["p-finally@1.0.0", "", {}, "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow=="], - "p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="], @@ -2542,11 +2816,13 @@ "pdf-lib": ["pdf-lib@1.17.1", "", { "dependencies": { "@pdf-lib/standard-fonts": "^1.0.0", "@pdf-lib/upng": "^1.0.1", "pako": "^1.0.11", "tslib": "^1.11.1" } }, "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw=="], + "pe-library": ["pe-library@0.4.1", "", {}, "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw=="], + "perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], "pino": ["pino@10.3.1", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg=="], @@ -2560,12 +2836,20 @@ "pkg-up": ["pkg-up@3.1.0", "", { "dependencies": { "find-up": "^3.0.0" } }, "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA=="], + "pkijs": ["pkijs@3.4.1", "", { "dependencies": { "@noble/hashes": "1.8.0", "asn1js": "^3.0.6", "bytestreamjs": "^2.0.1", "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-Oo/NZcSWccq8KyoG7gLE9fnltgHns+pNCjCAp/WmjsUySi+sX7y4z4Xqu4fVb42CDHzRPl33fjzT15V1wvcyhA=="], + + "plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="], + "poe-oauth": ["poe-oauth@0.0.14", "", {}, "sha512-EHteyy5PsT8cl9zVjYcCQsm6FmLgqfwBkGj6IzUA83d9aWc57bH+jj2ItwJlf6RBhK+DN5N8YVlwEc0gOLb5fw=="], "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], + "postcss": ["postcss@8.5.29", "", { "dependencies": { "nanoid": "^3.3.19", "picocolors": "^1.1.1", "source-map-js": "^1.2.2" } }, "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ=="], + "postcss-selector-parser": ["postcss-selector-parser@7.1.5", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw=="], + "postject": ["postject@1.0.0-alpha.6", "", { "dependencies": { "commander": "^9.4.0" }, "bin": { "postject": "dist/cli.js" } }, "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A=="], + "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], "pptxgenjs": ["pptxgenjs@4.0.1", "", { "dependencies": { "@types/node": "^22.8.1", "https": "^1.0.0", "image-size": "^1.2.1", "jszip": "^3.10.1" } }, "sha512-TeJISr8wouAuXw4C1F/mC33xbZs/FuEG6nH9FG1Zj+nuPcGMP5YRHl6X+j3HSUnS1f3at6k75ZZXPMZlA5Lj9A=="], @@ -2582,12 +2866,18 @@ "proggy": ["proggy@4.0.0", "", {}, "sha512-MbA4R+WQT76ZBm/5JUpV9yqcJt92175+Y0Bodg3HgiXzrmKu7Ggq+bpn6y6wHH+gN9NcyKn3yg1+d47VaKwNAQ=="], + "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], + "promise-all-reject-late": ["promise-all-reject-late@1.0.1", "", {}, "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw=="], "promise-call-limit": ["promise-call-limit@3.0.2", "", {}, "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw=="], + "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], + "promise.allsettled": ["promise.allsettled@1.0.7", "", { "dependencies": { "array.prototype.map": "^1.0.5", "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", "get-intrinsic": "^1.2.1", "iterate-value": "^1.0.2" } }, "sha512-hezvKvQQmsFkOdrZfYxUxkyxl8mgFQeT259Ajj9PXdbg9VzBCWrItOev72JyWxkCD5VSSqAeHmlN3tWx4DlmsA=="], + "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], + "property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="], "protobufjs": ["protobufjs@7.6.5", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw=="], @@ -2596,23 +2886,33 @@ "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + "pure-rand": ["pure-rand@8.4.2", "", {}, "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng=="], + "pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="], + + "pvutils": ["pvutils@1.2.0", "", {}, "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg=="], + "qs": ["qs@6.15.3", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A=="], "queue": ["queue@6.0.2", "", { "dependencies": { "inherits": "~2.0.3" } }, "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA=="], "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], + "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], "raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="], "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], - "react": ["react@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "react": ["react@19.3.0", "", {}, "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog=="], + + "react-dom": ["react-dom@19.3.0", "", { "dependencies": { "scheduler": "^0.28.0" }, "peerDependencies": { "react": "^19.3.0" } }, "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q=="], - "react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="], + "read-binary-file-arch": ["read-binary-file-arch@1.0.6", "", { "dependencies": { "debug": "^4.3.4" }, "bin": { "read-binary-file-arch": "cli.js" } }, "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg=="], "read-cmd-shim": ["read-cmd-shim@6.0.0", "", {}, "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A=="], @@ -2642,17 +2942,25 @@ "remeda": ["remeda@2.26.0", "", { "dependencies": { "type-fest": "^4.41.0" } }, "sha512-lmNNwtaC6Co4m0WTTNoZ/JlpjEqAjPZO0+czC9YVRQUpkbS4x8Hmh+Mn9HPfJfiXqUQ5IXXgSXSOB2pBKAytdA=="], + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "resedit": ["resedit@1.7.2", "", { "dependencies": { "pe-library": "^0.4.1" } }, "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA=="], + "reselect": ["reselect@4.1.8", "", {}, "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="], "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], + "resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="], + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + "responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="], + "ret": ["ret@0.5.0", "", {}, "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="], - "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], @@ -2660,6 +2968,10 @@ "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], + "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], + + "rolldown": ["rolldown@1.2.13", "", { "dependencies": { "@oxc-project/types": "=0.153.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.13", "@rolldown/binding-android-arm64": "1.2.13", "@rolldown/binding-darwin-arm64": "1.2.13", "@rolldown/binding-darwin-x64": "1.2.13", "@rolldown/binding-freebsd-x64": "1.2.13", "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", "@rolldown/binding-linux-arm64-gnu": "1.2.13", "@rolldown/binding-linux-arm64-musl": "1.2.13", "@rolldown/binding-linux-ppc64-gnu": "1.2.13", "@rolldown/binding-linux-s390x-gnu": "1.2.13", "@rolldown/binding-linux-x64-gnu": "1.2.13", "@rolldown/binding-linux-x64-musl": "1.2.13", "@rolldown/binding-openharmony-arm64": "1.2.13", "@rolldown/binding-win32-arm64-msvc": "1.2.13", "@rolldown/binding-win32-x64-msvc": "1.2.13" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ=="], + "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], @@ -2680,11 +2992,13 @@ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "sanitize-filename": ["sanitize-filename@1.6.4", "", { "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg=="], + "sax": ["sax@1.6.1", "", {}, "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q=="], "saxes": ["saxes@5.0.1", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw=="], - "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + "scheduler": ["scheduler@0.28.0", "", {}, "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw=="], "section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="], @@ -2692,8 +3006,12 @@ "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + "semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="], + "send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="], + "serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="], + "seroval": ["seroval@1.3.2", "", {}, "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ=="], "seroval-plugins": ["seroval-plugins@1.3.3", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w=="], @@ -2730,6 +3048,8 @@ "sigstore": ["sigstore@4.1.1", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.1", "@sigstore/protobuf-specs": "^0.5.0", "@sigstore/sign": "^4.1.1", "@sigstore/tuf": "^4.0.2", "@sigstore/verify": "^3.1.1" } }, "sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w=="], + "simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="], + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], @@ -2746,6 +3066,12 @@ "sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="], + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@1.2.2", "", {}, "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], @@ -2780,6 +3106,8 @@ "standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="], + "stat-mode": ["stat-mode@1.0.0", "", {}, "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg=="], + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], @@ -2810,6 +3138,10 @@ "strnum": ["strnum@2.4.2", "", { "dependencies": { "anynum": "^1.0.1" } }, "sha512-rDG3Ah4TV0k1hWvLSzkZtMmLN9+eS+h3knq4MP6A42Y3Yh5qGNnOUs1jJkoSr8FG5dsL28c7KgkIBzSEykqtuw=="], + "sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], "system-architecture": ["system-architecture@0.1.0", "", {}, "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA=="], @@ -2820,18 +3152,28 @@ "teex": ["teex@1.0.1", "", { "dependencies": { "streamx": "^2.12.5" } }, "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg=="], + "temp": ["temp@0.9.4", "", { "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" } }, "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA=="], + + "temp-file": ["temp-file@3.4.0", "", { "dependencies": { "async-exit-hook": "^2.0.1", "fs-extra": "^10.0.0" } }, "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg=="], + "text-decoder": ["text-decoder@1.2.7", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ=="], "thread-stream": ["thread-stream@4.2.0", "", { "dependencies": { "real-require": "^1.0.0" } }, "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ=="], "thunky": ["thunky@1.1.0", "", {}, "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="], + "tiny-async-pool": ["tiny-async-pool@1.3.0", "", { "dependencies": { "semver": "^5.5.0" } }, "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA=="], + + "tiny-typed-emitter": ["tiny-typed-emitter@2.1.0", "", {}, "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA=="], + "tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="], "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], "tmp": ["tmp@0.2.7", "", {}, "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw=="], + "tmp-promise": ["tmp-promise@3.0.3", "", { "dependencies": { "tmp": "^0.2.0" } }, "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ=="], + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], "toad-cache": ["toad-cache@3.7.4", "", {}, "sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg=="], @@ -2852,6 +3194,8 @@ "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + "truncate-utf8-bytes": ["truncate-utf8-bytes@1.0.2", "", { "dependencies": { "utf8-byte-length": "^1.0.1" } }, "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ=="], + "ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -2900,6 +3244,8 @@ "universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="], + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], "unzip-stream": ["unzip-stream@0.3.4", "", { "dependencies": { "binary": "^0.3.0", "mkdirp": "^0.5.1" } }, "sha512-PyofABPVv+d7fL7GOpusx7eRT9YETY2X04PhwbSipdj6bMxVCFJrr+nm0Mxqbf9hUiTin/UsnuFWBXlDZFy0Cw=="], @@ -2908,6 +3254,8 @@ "update-browserslist-db": ["update-browserslist-db@1.3.1", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ=="], + "utf8-byte-length": ["utf8-byte-length@1.0.5", "", {}, "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="], + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], @@ -2924,6 +3272,8 @@ "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + "vite": ["vite@8.3.3", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.28", "rolldown": "~1.2.11", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cTAldKPImjg6c+gk48U19POPn3GCBzZwpdsN8ZMEEcbpes+6/wvfqUd0C2y3qYY4wsj8PwgFwrZ/1jBPVttMSg=="], + "vscode-jsonrpc": ["vscode-jsonrpc@8.2.1", "", {}, "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ=="], "vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="], @@ -2934,6 +3284,8 @@ "web-tree-sitter": ["web-tree-sitter@0.25.10", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="], + "webcrypto-core": ["webcrypto-core@1.9.2", "", { "dependencies": { "@peculiar/asn1-schema": "^2.7.0", "@peculiar/json-schema": "^1.1.12", "@peculiar/utils": "^2.0.2", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q=="], + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], @@ -2970,6 +3322,8 @@ "xml-naming": ["xml-naming@0.3.0", "", {}, "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ=="], + "xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], + "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="], "xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="], @@ -2984,6 +3338,8 @@ "yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "zip-stream": ["zip-stream@6.0.1", "", { "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" } }, "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA=="], "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], @@ -3188,6 +3544,26 @@ "@bufbuild/protoplugin/typescript": ["typescript@5.4.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ=="], + "@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="], + + "@electron/asar/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "@electron/asar/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "@electron/fuses/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], + + "@electron/get/undici": ["undici@7.30.0", "", {}, "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ=="], + + "@electron/notarize/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], + + "@electron/osx-sign/isbinaryfile": ["isbinaryfile@4.0.10", "", {}, "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw=="], + + "@electron/universal/fs-extra": ["fs-extra@11.3.1", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g=="], + + "@electron/universal/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "@electron/windows-sign/fs-extra": ["fs-extra@11.3.1", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g=="], + "@fastify/ajv-compiler/fast-uri": ["fast-uri@4.1.3", "", {}, "sha512-7+72G6vLt7jjNas8SmSATx2qeyRIjxeqO3i4IkmDTxlqYZRKANhOe1bnovcp4WZmvsYrp60WyqPyHqgRiX0yXw=="], "@fastify/proxy-addr/ipaddr.js": ["ipaddr.js@2.5.0", "", {}, "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w=="], @@ -3204,6 +3580,8 @@ "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + "@malept/flatpak-bundler/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], + "@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], "@modelcontextprotocol/sdk/jose": ["jose@6.2.10", "", {}, "sha512-iiW7J9qRFlGxvCOIBDBDxFePQSn7ZMAnrYGhrrOo6siO/MIqwfyilLR27pkfDgUk+raLuzADS8A3S/KLBisc0g=="], @@ -3304,6 +3682,20 @@ "ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.10.0", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-FMsAEjLUt5pWuRE2LDC/LCvVrFjLlrEzUITH5+5SZtfq7KZ2wrOHjQVxzz92sju8S9ltpzW87CLW8/b0oBXVCw=="], + "app-builder-lib/@electron/get": ["@electron/get@3.1.0", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ=="], + + "app-builder-lib/ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="], + + "app-builder-lib/hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], + + "app-builder-lib/js-yaml": ["js-yaml@4.3.2", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA=="], + + "app-builder-lib/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "app-builder-lib/unzipper": ["unzipper@0.12.5", "", { "dependencies": { "bluebird": "~3.7.2", "duplexer2": "~0.1.4", "fs-extra": "11.3.1", "graceful-fs": "^4.2.2", "node-int64": "^0.4.0" } }, "sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A=="], + + "app-builder-lib/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], + "archiver/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], "archiver-utils/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], @@ -3324,8 +3716,16 @@ "body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + "builder-util/js-yaml": ["js-yaml@4.3.2", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA=="], + "c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + "c12/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], + + "cacheable-request/get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], + + "clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="], + "compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], "compress-commons/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], @@ -3336,12 +3736,28 @@ "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "dir-compare/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "dmg-builder/js-yaml": ["js-yaml@4.3.2", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA=="], + "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], "duplexer2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], "effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "electron-builder/yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="], + + "electron-publish/form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="], + + "electron-publish/mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="], + + "electron-updater/js-yaml": ["js-yaml@4.3.2", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA=="], + + "electron-updater/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "electron-winstaller/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="], + "engine.io-client/ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], "es-get-iterator/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], @@ -3358,7 +3774,7 @@ "fast-json-stringify/fast-uri": ["fast-uri@4.1.3", "", {}, "sha512-7+72G6vLt7jjNas8SmSATx2qeyRIjxeqO3i4IkmDTxlqYZRKANhOe1bnovcp4WZmvsYrp60WyqPyHqgRiX0yXw=="], - "fdir/picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], + "filelist/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -3382,6 +3798,10 @@ "light-my-request/process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="], + "lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + "minipass-fetch/iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], @@ -3390,6 +3810,8 @@ "negotiator/content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], + "node-gyp/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + "node-gyp/undici": ["undici@6.28.0", "", {}, "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA=="], "node-gyp-build-optional-packages/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], @@ -3406,12 +3828,22 @@ "openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + "p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], + "p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], "pdf-lib/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], + "postcss/nanoid": ["nanoid@3.3.20", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ=="], + + "postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], + + "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "readdir-glob/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], "redrob/@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.60", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.54", "@ai-sdk/provider": "3.0.12", "@ai-sdk/provider-utils": "4.0.33" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Rnok3cThg6awBwaDSyiZpgRpbV7pqxGYrA89LODCo5cuEHeP2h0AM0lLHP7zIkclAdXfOm4wldKi/S2T/DGCOw=="], @@ -3420,6 +3852,8 @@ "rimraf/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + "roarr/sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], + "safe-array-concat/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], "safe-push-apply/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], @@ -3428,6 +3862,8 @@ "send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], + "serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], + "shiki/@shikijs/core": ["@shikijs/core@4.2.0", "", { "dependencies": { "@shikijs/primitive": "4.2.0", "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ=="], "shiki/@shikijs/types": ["@shikijs/types@4.2.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw=="], @@ -3442,9 +3878,11 @@ "tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + "temp/rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="], + "thread-stream/real-require": ["real-require@1.0.0", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="], - "tinyglobby/picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], + "tiny-async-pool/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], "tree-sitter-bash/node-addon-api": ["node-addon-api@8.9.2", "", {}, "sha512-VijLXbi3UACN69I0JVXJsX4tjACjNoQDgv2gTF6sx2wWEi8tkSg2eX8p5gSIFi8z2+DL3oHmY6OyKce38SDolg=="], @@ -3460,7 +3898,7 @@ "which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -3582,12 +4020,18 @@ "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "@electron/asar/minimatch/brace-expansion": ["brace-expansion@1.1.21", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw=="], + + "@electron/universal/minimatch/brace-expansion": ["brace-expansion@2.1.4", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg=="], + "@gitlab/opencode-gitlab-auth/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], "@hey-api/json-schema-ref-parser/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], "@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.3.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^2.0.0", "debug": "^4.4.3", "http-errors": "^2.0.1", "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", "qs": "^6.15.2", "raw-body": "^3.0.2", "type-is": "^2.1.0" } }, "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw=="], @@ -3680,6 +4124,22 @@ "ai-gateway-provider/@ai-sdk/perplexity/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.49", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^6.28.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8e7pd+82bobqrFOaD5dG/PiEuvLYr5olaE3I56ch0jipR0H7sGD6ohwTUynv6k8O8QidWiyIbEsZCtr/2dyXIA=="], + "app-builder-lib/@electron/get/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "app-builder-lib/@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], + + "app-builder-lib/@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "app-builder-lib/hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + + "app-builder-lib/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "app-builder-lib/unzipper/bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="], + + "app-builder-lib/unzipper/fs-extra": ["fs-extra@11.3.1", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g=="], + + "app-builder-lib/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], + "archiver-utils/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], "archiver-utils/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], @@ -3696,14 +4156,34 @@ "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "builder-util/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "c12/chokidar/readdirp": ["readdirp@5.1.1", "", {}, "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA=="], "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "dir-compare/minimatch/brace-expansion": ["brace-expansion@1.1.21", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw=="], + + "dmg-builder/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "duplexer2/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "duplexer2/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + "electron-builder/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "electron-builder/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "electron-builder/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "electron-publish/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "electron-updater/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "electron-winstaller/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], + + "electron-winstaller/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], + "exceljs/archiver/archiver-utils": ["archiver-utils@2.1.0", "", { "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^2.0.0" } }, "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw=="], "exceljs/archiver/buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], @@ -3714,6 +4194,8 @@ "express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "filelist/minimatch/brace-expansion": ["brace-expansion@2.1.4", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg=="], + "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], @@ -3748,6 +4230,8 @@ "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "temp/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], "unzipper/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], @@ -3774,6 +4258,10 @@ "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-o1GX0+IPlFi/D8ei9y/jj3yucJWNfPnbB5appVBWevAyUdZA5KzQ2nK/hDxiu9olTZlFEFpf1m1Rn3FaGxHqsw=="], + "@electron/asar/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "@electron/universal/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "@modelcontextprotocol/sdk/express/body-parser/content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], "@modelcontextprotocol/sdk/express/body-parser/iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], @@ -3818,6 +4306,10 @@ "ai-gateway-provider/@ai-sdk/perplexity/@ai-sdk/provider-utils/undici": ["undici@6.28.0", "", {}, "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA=="], + "app-builder-lib/@electron/get/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], + + "app-builder-lib/@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], + "archiver-utils/glob/minimatch/brace-expansion": ["brace-expansion@2.1.4", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg=="], "archiver-utils/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], @@ -3830,6 +4322,18 @@ "babel-plugin-module-resolver/glob/path-scurry/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + "dir-compare/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "electron-builder/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "electron-builder/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "electron-builder/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "electron-builder/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "electron-publish/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "exceljs/archiver/archiver-utils/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "exceljs/archiver/archiver-utils/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], @@ -3838,6 +4342,8 @@ "exceljs/archiver/zip-stream/compress-commons": ["compress-commons@4.1.2", "", { "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg=="], + "filelist/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "fstream/rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "readdir-glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], @@ -3848,6 +4354,8 @@ "rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "temp/rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "venice-ai-sdk-provider/@ai-sdk/openai-compatible/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "venice-ai-sdk-provider/@ai-sdk/openai-compatible/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], @@ -3856,6 +4364,10 @@ "babel-plugin-module-resolver/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "electron-builder/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "electron-builder/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "exceljs/archiver/archiver-utils/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "exceljs/archiver/archiver-utils/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], @@ -3870,12 +4382,16 @@ "rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "temp/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.21", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw=="], + "exceljs/archiver/archiver-utils/glob/minimatch/brace-expansion": ["brace-expansion@1.1.21", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw=="], "exceljs/archiver/zip-stream/archiver-utils/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "fstream/rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "temp/rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "exceljs/archiver/archiver-utils/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "exceljs/archiver/zip-stream/archiver-utils/glob/minimatch/brace-expansion": ["brace-expansion@1.1.21", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw=="], diff --git a/package.json b/package.json index 3a48ec46b4..059291aa62 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "packages": [ "packages/*", "packages/sdk/js", - "packages/slack" + "packages/slack", + "apps/*" ], "catalog": { "@effect/opentelemetry": "4.0.0-beta.83", From 74802152a6177a62df77dbc63df47e87b19e8a3a Mon Sep 17 00:00:00 2001 From: Kiro Agent <244629292+kiro-agent@users.noreply.github.com> Date: Mon, 12 Oct 2026 02:04:54 +0000 Subject: [PATCH 2/5] Add the desktop interface in apps/studio React 19 on @redrob-labs/ui, talking to the engine through @redrob-code/client with one shared event stream. M0 scope: first-run privacy choice (both switches off), optional Console sign-in, recent projects, sessions with chat and permission prompts, the working tree's git changes, and a terminal on the engine's pty service, so the app ships no native modules. Every string is in English and Korean, and a test fails if either table is missing a key or a placeholder. --- apps/studio/.gitignore | 1 + apps/studio/AGENTS.md | 17 ++ apps/studio/index.html | 20 +++ apps/studio/package.json | 34 ++++ apps/studio/src/App.tsx | 78 +++++++++ apps/studio/src/engine.tsx | 86 ++++++++++ apps/studio/src/i18n.ts | 188 +++++++++++++++++++++ apps/studio/src/lib/diff.ts | 31 ++++ apps/studio/src/lib/live.ts | 56 +++++++ apps/studio/src/main.tsx | 21 +++ apps/studio/src/styles.css | 246 ++++++++++++++++++++++++++++ apps/studio/src/views/Changes.tsx | 67 ++++++++ apps/studio/src/views/Chat.tsx | 192 ++++++++++++++++++++++ apps/studio/src/views/Consent.tsx | 36 ++++ apps/studio/src/views/Home.tsx | 128 +++++++++++++++ apps/studio/src/views/Settings.tsx | 174 ++++++++++++++++++++ apps/studio/src/views/SignIn.tsx | 66 ++++++++ apps/studio/src/views/Startup.tsx | 46 ++++++ apps/studio/src/views/Terminal.tsx | 107 ++++++++++++ apps/studio/src/views/Workspace.tsx | 103 ++++++++++++ apps/studio/test/lib.test.ts | 66 ++++++++ apps/studio/tsconfig.json | 12 ++ apps/studio/vite.config.ts | 12 ++ 23 files changed, 1787 insertions(+) create mode 100644 apps/studio/.gitignore create mode 100644 apps/studio/AGENTS.md create mode 100644 apps/studio/index.html create mode 100644 apps/studio/package.json create mode 100644 apps/studio/src/App.tsx create mode 100644 apps/studio/src/engine.tsx create mode 100644 apps/studio/src/i18n.ts create mode 100644 apps/studio/src/lib/diff.ts create mode 100644 apps/studio/src/lib/live.ts create mode 100644 apps/studio/src/main.tsx create mode 100644 apps/studio/src/styles.css create mode 100644 apps/studio/src/views/Changes.tsx create mode 100644 apps/studio/src/views/Chat.tsx create mode 100644 apps/studio/src/views/Consent.tsx create mode 100644 apps/studio/src/views/Home.tsx create mode 100644 apps/studio/src/views/Settings.tsx create mode 100644 apps/studio/src/views/SignIn.tsx create mode 100644 apps/studio/src/views/Startup.tsx create mode 100644 apps/studio/src/views/Terminal.tsx create mode 100644 apps/studio/src/views/Workspace.tsx create mode 100644 apps/studio/test/lib.test.ts create mode 100644 apps/studio/tsconfig.json create mode 100644 apps/studio/vite.config.ts diff --git a/apps/studio/.gitignore b/apps/studio/.gitignore new file mode 100644 index 0000000000..849ddff3b7 --- /dev/null +++ b/apps/studio/.gitignore @@ -0,0 +1 @@ +dist/ diff --git a/apps/studio/AGENTS.md b/apps/studio/AGENTS.md new file mode 100644 index 0000000000..c3cab0e0e6 --- /dev/null +++ b/apps/studio/AGENTS.md @@ -0,0 +1,17 @@ +# apps/studio + +The Redrob Code desktop interface: React 19, Vite, `@redrob-labs/ui`, TanStack Query. It runs only +inside `apps/desktop`, which supplies `window.redrobCode` (types in `@redrob-code/desktop/bridge`). + +- **Talk to the engine with `@redrob-code/client`** through `useEngine()` (`src/engine.tsx`). There is + one event stream for the whole window; subscribe with `useEngineEvents`, do not open another. +- **Every visible string is a key in `src/i18n.ts`**, in English and Korean. `test/lib.test.ts` fails if a + key or a `{placeholder}` is missing from either table. +- **Use the design system** (`@redrob-labs/ui`) for components and tokens. `src/styles.css` is layout + only; it defines no colours of its own beyond diff highlights. +- **Effects return a function or nothing.** An expression-bodied `useEffect(() => el.scrollIntoView())` + returns a promise in current Chromium and crashes React on unmount. +- **The bundle is served from `oc://renderer/`**, so asset URLs are relative (`base: "./"`) and the CSP + in `index.html` allows only the local engine. + +Verify with `bun run typecheck`, `bun test` and `bun run build`. diff --git a/apps/studio/index.html b/apps/studio/index.html new file mode 100644 index 0000000000..8c8469a7e6 --- /dev/null +++ b/apps/studio/index.html @@ -0,0 +1,20 @@ + + + + + + + + Redrob Code + + +
+ + + diff --git a/apps/studio/package.json b/apps/studio/package.json new file mode 100644 index 0000000000..e5ff2e69d7 --- /dev/null +++ b/apps/studio/package.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "name": "@redrob-code/studio", + "description": "The Redrob Code desktop interface", + "version": "0.0.0", + "private": true, + "type": "module", + "license": "MIT", + "scripts": { + "dev": "vite", + "build": "vite build", + "test": "bun test", + "typecheck": "tsgo --noEmit" + }, + "dependencies": { + "@redrob-code/client": "workspace:*", + "@redrob-labs/ui": "1.3.1", + "@tanstack/react-query": "5.104.1", + "@xterm/addon-fit": "0.11.0", + "@xterm/xterm": "6.0.0", + "react": "19.3.0", + "react-dom": "19.3.0" + }, + "devDependencies": { + "@redrob-code/desktop": "workspace:*", + "@tsconfig/bun": "catalog:", + "@types/bun": "catalog:", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "@typescript/native-preview": "catalog:", + "@vitejs/plugin-react": "6.1.2", + "vite": "8.3.3" + } +} diff --git a/apps/studio/src/App.tsx b/apps/studio/src/App.tsx new file mode 100644 index 0000000000..f175649887 --- /dev/null +++ b/apps/studio/src/App.tsx @@ -0,0 +1,78 @@ +import { useCallback, useEffect, useMemo, useState } from "react" +import type { AppInfo, EngineInfo, EngineState, Settings } from "@redrob-code/desktop/bridge" +import { EngineProvider } from "./engine" +import { I18nContext, translate, type Translate } from "./i18n" +import { Consent } from "./views/Consent" +import { NotDesktop, Startup } from "./views/Startup" +import { Home } from "./views/Home" + +/** + * The top of the interface: wait for the engine, ask for consent once, then the app. + * + * Sign-in is not a gate. The engine also works with a person's own provider keys, so the sign-in + * card is offered inside the app rather than standing in front of it. + */ +export function App() { + const bridge = window.redrobCode + const [state, setState] = useState({ status: "starting" }) + const [info, setInfo] = useState(null) + const [settings, setSettings] = useState(null) + const [appInfo, setAppInfo] = useState(null) + + useEffect(() => { + if (!bridge) return + void bridge.settings.get().then(setSettings) + void bridge.app.info().then(setAppInfo) + return bridge.engine.onState((next) => { + setState(next) + if (next.status === "ready") void bridge.engine.info().then(setInfo) + else setInfo(null) + }) + }, [bridge]) + + const language = settings?.language ?? "en" + const t = useCallback((key, vars) => translate(language, key, vars), [language]) + useTheme(settings?.theme ?? "system") + useEffect(() => { + document.documentElement.lang = language + }, [language]) + + const update = useMemo( + () => async (patch: Partial) => { + if (!bridge) return + setSettings(await bridge.settings.set(patch)) + }, + [bridge], + ) + + if (!bridge) return + + return ( + + {!settings || !info ? ( + + ) : !settings.consent.decided ? ( + + ) : ( + + + + )} + + ) +} + +/** Applies the theme the way redrob-ui reads it: `data-theme` on the root element. */ +function useTheme(mode: Settings["theme"]) { + useEffect(() => { + const media = window.matchMedia("(prefers-color-scheme: dark)") + const apply = () => { + const dark = mode === "dark" || (mode === "system" && media.matches) + document.documentElement.dataset["theme"] = dark ? "dark" : "light" + document.documentElement.dataset["themeMode"] = mode + } + apply() + media.addEventListener("change", apply) + return () => media.removeEventListener("change", apply) + }, [mode]) +} diff --git a/apps/studio/src/engine.tsx b/apps/studio/src/engine.tsx new file mode 100644 index 0000000000..8b8b49d5f9 --- /dev/null +++ b/apps/studio/src/engine.tsx @@ -0,0 +1,86 @@ +import { createContext, useContext, useEffect, useMemo, useRef, type ReactNode } from "react" +import { RedrobCode } from "@redrob-code/client" +import type { EngineInfo } from "@redrob-code/desktop/bridge" + +/** + * The engine connection: one typed client and one shared event stream for the whole window. + * + * The event stream is global across project folders (packages/server/src/handlers/event.ts), so it + * is opened once here and every view filters what it needs, rather than each view opening its own. + */ + +export type Client = ReturnType + +/** The fields the interface reads from an event. The full union lives in @redrob-code/protocol. */ +export type EngineEvent = { + type: string + data?: { sessionID?: string; textID?: string; delta?: string; [key: string]: unknown } + location?: { directory: string } +} + +type Listener = (event: EngineEvent) => void + +type EngineContextValue = { + info: EngineInfo + client: Client + subscribe: (listener: Listener) => () => void +} + +const EngineContext = createContext(null) + +export function useEngine() { + const value = useContext(EngineContext) + if (!value) throw new Error("useEngine needs an EngineProvider") + return value +} + +/** Calls the handler for every engine event, for as long as the component is mounted. */ +export function useEngineEvents(handler: Listener) { + const { subscribe } = useEngine() + const ref = useRef(handler) + ref.current = handler + useEffect(() => subscribe((event) => ref.current(event)), [subscribe]) +} + +export function EngineProvider(props: { info: EngineInfo; children: ReactNode }) { + const { info } = props + const { value, listeners } = useMemo(() => { + const client = RedrobCode.make({ baseUrl: info.url, headers: { Authorization: info.authorization } }) + const listeners = new Set() + const value: EngineContextValue = { + info, + client, + subscribe(listener) { + listeners.add(listener) + return () => { + listeners.delete(listener) + } + }, + } + return { value, listeners } + }, [info]) + + useEffect(() => { + const controller = new AbortController() + let attempt = 0 + const run = async () => { + while (!controller.signal.aborted) { + try { + for await (const event of value.client.events.subscribe({ signal: controller.signal })) { + attempt = 0 + for (const listener of listeners) listener(event as unknown as EngineEvent) + } + } catch { + if (controller.signal.aborted) return + } + // The stream ends when the engine restarts; reconnect with a short, growing delay. + attempt = Math.min(attempt + 1, 6) + await new Promise((done) => setTimeout(done, 250 * 2 ** attempt)) + } + } + void run() + return () => controller.abort() + }, [value, listeners]) + + return {props.children} +} diff --git a/apps/studio/src/i18n.ts b/apps/studio/src/i18n.ts new file mode 100644 index 0000000000..41b48dc868 --- /dev/null +++ b/apps/studio/src/i18n.ts @@ -0,0 +1,188 @@ +import { createContext, useContext } from "react" +import type { Language } from "@redrob-code/desktop/bridge" + +/** + * Every visible string, in English and Korean. Korean is a first-class locale here as it is in the + * CLI, so a key missing from either table is a type error rather than a blank label. + */ + +const en = { + "app.name": "Redrob Code", + "app.notDesktop": "Open this inside the Redrob Code desktop app. It needs the app to start the engine.", + "engine.starting": "Starting the engine…", + "engine.failed": "The engine did not start", + "engine.restart": "Try again", + "engine.logs": "Engine output", + "engine.source.bundled": "Built-in engine", + "engine.source.cli": "Installed CLI", + "engine.source.override": "REDROB_CODE_BIN", + "signIn.title": "Sign in to Redrob", + "signIn.body": "One sign-in covers every model. Your key is stored in the engine, never in this app.", + "signIn.start": "Sign in with the Console", + "signIn.waiting": "Approve this code in your browser", + "signIn.reopen": "Open the page again", + "signIn.cancel": "Cancel", + "signIn.connected": "Signed in to {account}", + "signIn.denied": "Sign-in was declined in the browser.", + "signIn.expired": "The code expired. Start again.", + "signIn.failed": "Sign-in failed: {message}", + "signIn.skip": "Continue without signing in", + "account.signedIn": "Signed in", + "account.signedOut": "Not signed in", + "consent.title": "Help improve Redrob Code?", + "consent.body": + "Both are off unless you turn them on. Neither ever includes your code, prompts, file paths or repository names.", + "consent.crash": "Send crash reports", + "consent.usage": "Share anonymous usage events", + "consent.continue": "Continue", + "projects.title": "Projects", + "projects.open": "Open a folder", + "projects.empty": "Open a project folder to start.", + "projects.recent": "Recent", + "sessions.title": "Sessions", + "sessions.new": "New session", + "sessions.empty": "No sessions in this project yet.", + "sessions.untitled": "Untitled session", + "tabs.chat": "Chat", + "tabs.changes": "Changes", + "tabs.terminal": "Terminal", + "chat.placeholder": "Describe the task…", + "chat.send": "Send", + "chat.stop": "Stop", + "chat.empty": "Ask for a change in this project.", + "chat.working": "Working", + "chat.tool.pending": "Preparing", + "chat.tool.running": "Running", + "chat.tool.completed": "Done", + "chat.tool.error": "Failed", + "chat.error": "Something went wrong: {message}", + "permission.title": "Permission needed", + "permission.once": "Allow once", + "permission.always": "Always allow", + "permission.reject": "Deny", + "changes.notRepo": "This folder is not a git repository, so there are no changes to show.", + "changes.none": "No changes against {branch}.", + "changes.refresh": "Refresh", + "changes.truncated": "The diff is too large to show in full.", + "terminal.starting": "Opening a terminal…", + "terminal.failed": "The terminal could not connect: {message}", + "settings.title": "Settings", + "settings.language": "Language", + "settings.theme": "Theme", + "settings.updates": "Updates", + "settings.channel.stable": "Stable", + "settings.channel.alpha": "Alpha", + "settings.checkUpdates": "Check for updates", + "settings.installUpdate": "Restart to update", + "settings.cli": "Command-line tool", + "settings.cli.installed": "The redrob command is installed at {path} ({version}).", + "settings.cli.missing": "The redrob command is not installed. The app does not need it.", + "settings.cli.install": "Install the redrob command", + "settings.cli.confirm": "This runs the official installer:", + "settings.cli.prefer": "Use the installed CLI as the engine when it is present", + "settings.privacy": "Privacy", + "settings.engine": "Engine", + "settings.close": "Close", + "update.available": "Version {version} is downloading.", + "update.ready": "Version {version} is ready.", + "update.none": "You have the latest version.", + "update.checking": "Checking…", + "update.error": "Update check failed: {message}", +} as const + +export type MessageKey = keyof typeof en + +const ko: Record = { + "app.name": "Redrob Code", + "app.notDesktop": "Redrob Code 데스크톱 앱에서 열어 주세요. 엔진을 시작하려면 앱이 필요합니다.", + "engine.starting": "엔진을 시작하는 중…", + "engine.failed": "엔진을 시작하지 못했습니다", + "engine.restart": "다시 시도", + "engine.logs": "엔진 출력", + "engine.source.bundled": "내장 엔진", + "engine.source.cli": "설치된 CLI", + "engine.source.override": "REDROB_CODE_BIN", + "signIn.title": "Redrob에 로그인", + "signIn.body": "한 번의 로그인으로 모든 모델을 사용합니다. 키는 이 앱이 아닌 엔진에 저장됩니다.", + "signIn.start": "콘솔로 로그인", + "signIn.waiting": "브라우저에서 이 코드를 승인하세요", + "signIn.reopen": "페이지 다시 열기", + "signIn.cancel": "취소", + "signIn.connected": "{account}에 로그인했습니다", + "signIn.denied": "브라우저에서 로그인이 거부되었습니다.", + "signIn.expired": "코드가 만료되었습니다. 다시 시작하세요.", + "signIn.failed": "로그인 실패: {message}", + "signIn.skip": "로그인하지 않고 계속", + "account.signedIn": "로그인됨", + "account.signedOut": "로그인 안 됨", + "consent.title": "Redrob Code 개선에 참여하시겠어요?", + "consent.body": "켜기 전까지는 둘 다 꺼져 있습니다. 코드, 프롬프트, 파일 경로, 저장소 이름은 절대 포함되지 않습니다.", + "consent.crash": "오류 보고서 보내기", + "consent.usage": "익명 사용 이벤트 공유", + "consent.continue": "계속", + "projects.title": "프로젝트", + "projects.open": "폴더 열기", + "projects.empty": "시작하려면 프로젝트 폴더를 여세요.", + "projects.recent": "최근 항목", + "sessions.title": "세션", + "sessions.new": "새 세션", + "sessions.empty": "이 프로젝트에는 아직 세션이 없습니다.", + "sessions.untitled": "제목 없는 세션", + "tabs.chat": "대화", + "tabs.changes": "변경 사항", + "tabs.terminal": "터미널", + "chat.placeholder": "작업을 설명하세요…", + "chat.send": "보내기", + "chat.stop": "중지", + "chat.empty": "이 프로젝트에서 원하는 변경을 요청하세요.", + "chat.working": "작업 중", + "chat.tool.pending": "준비 중", + "chat.tool.running": "실행 중", + "chat.tool.completed": "완료", + "chat.tool.error": "실패", + "chat.error": "문제가 발생했습니다: {message}", + "permission.title": "권한이 필요합니다", + "permission.once": "이번만 허용", + "permission.always": "항상 허용", + "permission.reject": "거부", + "changes.notRepo": "이 폴더는 git 저장소가 아니어서 표시할 변경 사항이 없습니다.", + "changes.none": "{branch} 대비 변경 사항이 없습니다.", + "changes.refresh": "새로 고침", + "changes.truncated": "diff가 너무 커서 전부 표시할 수 없습니다.", + "terminal.starting": "터미널을 여는 중…", + "terminal.failed": "터미널에 연결하지 못했습니다: {message}", + "settings.title": "설정", + "settings.language": "언어", + "settings.theme": "테마", + "settings.updates": "업데이트", + "settings.channel.stable": "안정", + "settings.channel.alpha": "알파", + "settings.checkUpdates": "업데이트 확인", + "settings.installUpdate": "다시 시작하여 업데이트", + "settings.cli": "명령줄 도구", + "settings.cli.installed": "redrob 명령이 {path}에 설치되어 있습니다 ({version}).", + "settings.cli.missing": "redrob 명령이 설치되어 있지 않습니다. 앱 사용에는 필요하지 않습니다.", + "settings.cli.install": "redrob 명령 설치", + "settings.cli.confirm": "공식 설치 프로그램을 실행합니다:", + "settings.cli.prefer": "설치된 CLI가 있으면 엔진으로 사용", + "settings.privacy": "개인정보", + "settings.engine": "엔진", + "settings.close": "닫기", + "update.available": "버전 {version}을 다운로드하는 중입니다.", + "update.ready": "버전 {version}이 준비되었습니다.", + "update.none": "최신 버전을 사용 중입니다.", + "update.checking": "확인 중…", + "update.error": "업데이트 확인 실패: {message}", +} + +export const dictionaries: Record> = { en, ko } + +export function translate(language: Language, key: MessageKey, vars?: Record) { + const template = dictionaries[language][key] ?? dictionaries.en[key] + return template.replace(/\{(\w+)\}/g, (match, name: string) => (vars && name in vars ? String(vars[name]) : match)) +} + +export type Translate = (key: MessageKey, vars?: Record) => string + +export const I18nContext = createContext((key, vars) => translate("en", key, vars)) +export const useT = () => useContext(I18nContext) diff --git a/apps/studio/src/lib/diff.ts b/apps/studio/src/lib/diff.ts new file mode 100644 index 0000000000..7c51664767 --- /dev/null +++ b/apps/studio/src/lib/diff.ts @@ -0,0 +1,31 @@ +/** A unified diff split into files and classified lines, for a read-only changes view. */ + +export type DiffLine = { kind: "add" | "remove" | "context" | "hunk" | "meta"; text: string } +export type DiffFile = { path: string; additions: number; deletions: number; lines: DiffLine[] } + +export function parseUnifiedDiff(diff: string): DiffFile[] { + const files: DiffFile[] = [] + let current: DiffFile | null = null + for (const line of diff.split("\n")) { + if (line.startsWith("diff --git ")) { + const match = / b\/(.+)$/.exec(line) + current = { path: match?.[1] ?? line.slice(11), additions: 0, deletions: 0, lines: [] } + files.push(current) + continue + } + if (!current) continue + if (line.startsWith("+++ ") || line.startsWith("--- ")) { + continue + } + if (line.startsWith("@@")) current.lines.push({ kind: "hunk", text: line }) + else if (line.startsWith("+")) { + current.additions++ + current.lines.push({ kind: "add", text: line }) + } else if (line.startsWith("-")) { + current.deletions++ + current.lines.push({ kind: "remove", text: line }) + } else if (line.startsWith(" ")) current.lines.push({ kind: "context", text: line }) + else if (line.length > 0) current.lines.push({ kind: "meta", text: line }) + } + return files +} diff --git a/apps/studio/src/lib/live.ts b/apps/studio/src/lib/live.ts new file mode 100644 index 0000000000..b5c9f51533 --- /dev/null +++ b/apps/studio/src/lib/live.ts @@ -0,0 +1,56 @@ +import type { EngineEvent } from "../engine" + +/** + * Text that is still streaming, keyed by text id, for one session. + * + * Committed messages come from `messages.list`; this only covers the gap between a `text.delta` + * arriving and the step ending, so the person sees the answer form instead of waiting for it whole. + */ +export type LiveText = Record + +export function applyLiveEvent(state: LiveText, event: EngineEvent, sessionID: string): LiveText { + if (event.data?.sessionID !== sessionID) return state + const textID = typeof event.data.textID === "string" ? event.data.textID : null + switch (event.type) { + case "session.next.text.started": + return textID ? { ...state, [textID]: "" } : state + case "session.next.text.delta": + return textID && typeof event.data.delta === "string" + ? { ...state, [textID]: (state[textID] ?? "") + event.data.delta } + : state + case "session.next.step.ended": + case "session.next.step.failed": + return {} + default: + return state + } +} + +/** Events after which the committed message list has changed and should be read again. */ +export function changesMessages(event: EngineEvent, sessionID: string) { + if (event.data?.sessionID !== sessionID) return false + return [ + "session.next.prompted", + "session.next.prompt.admitted", + "session.next.text.ended", + "session.next.tool.called", + "session.next.tool.success", + "session.next.tool.failed", + "session.next.step.ended", + "session.next.step.failed", + "session.next.shell.ended", + "session.next.compaction.ended", + ].includes(event.type) +} + +/** A short, single-line description of what a tool was asked to do. */ +export function describeToolInput(input: unknown): string { + if (typeof input === "string") return input.slice(0, 160) + if (!input || typeof input !== "object") return "" + const record = input as Record + for (const key of ["filePath", "path", "command", "pattern", "url", "description"]) { + const value = record[key] + if (typeof value === "string" && value) return value.slice(0, 160) + } + return "" +} diff --git a/apps/studio/src/main.tsx b/apps/studio/src/main.tsx new file mode 100644 index 0000000000..1fe3893cb3 --- /dev/null +++ b/apps/studio/src/main.tsx @@ -0,0 +1,21 @@ +import "@redrob-labs/ui/tokens.css" +import "@redrob-labs/ui/preflight.css" +import "@redrob-labs/ui/styles.css" +import "@xterm/xterm/css/xterm.css" +import "./styles.css" +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" +import { StrictMode } from "react" +import { createRoot } from "react-dom/client" +import { App } from "./App" + +const queries = new QueryClient({ + defaultOptions: { queries: { retry: 1, refetchOnWindowFocus: false, staleTime: 2_000 } }, +}) + +createRoot(document.getElementById("root")!).render( + + + + + , +) diff --git a/apps/studio/src/styles.css b/apps/studio/src/styles.css new file mode 100644 index 0000000000..fbd2bd2db2 --- /dev/null +++ b/apps/studio/src/styles.css @@ -0,0 +1,246 @@ +/* + * Layout only. Colour, type and spacing come from @redrob-labs/ui tokens, so the app follows the + * design system's light and dark themes without a palette of its own. + */ + +html, +body, +#root { + height: 100%; +} + +.rc-center { + display: grid; + place-items: center; + min-height: 100%; + padding: var(--space-6, 24px); +} + +.rc-stack { + display: flex; + flex-direction: column; + gap: var(--space-3, 12px); +} + +.rc-row { + display: flex; + align-items: center; + gap: var(--space-2, 8px); + flex-wrap: wrap; +} + +.rc-between { + justify-content: space-between; +} + +.rc-narrow { + max-width: 520px; + width: 100%; +} + +.rc-muted { + color: var(--color-text-muted, var(--text-muted, inherit)); + opacity: 0.85; +} + +.rc-danger, +.rc-remove { + color: var(--color-danger, #d64545); +} + +.rc-add { + color: var(--color-success, #2f9e5a); +} + +.rc-label { + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.04em; + opacity: 0.7; + margin: var(--space-3, 12px) 0 var(--space-1, 4px); +} + +.rc-ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; +} + +.rc-pre { + white-space: pre-wrap; + overflow-wrap: anywhere; + margin: 0; +} + +.rc-code { + font-family: var(--font-mono, monospace); + font-size: 28px; + letter-spacing: 0.12em; +} + +.rc-log { + max-height: 320px; + overflow: auto; + font-family: var(--font-mono, monospace); + font-size: 12px; + white-space: pre-wrap; + margin: 0; +} + +.rc-aside { + display: flex; + flex-direction: column; + gap: var(--space-2, 8px); + padding: var(--space-3, 12px); +} + +.rc-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 2px; +} + +.rc-list__item { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-2, 8px); + padding: 6px 8px; + border: 0; + border-radius: 6px; + background: transparent; + color: inherit; + text-align: left; + cursor: pointer; +} + +.rc-list__item:hover, +.rc-list__item[aria-current="page"] { + background: var(--color-surface-hover, rgba(127, 127, 127, 0.12)); +} + +.rc-workspace { + display: grid; + grid-template-columns: 260px minmax(0, 1fr); + height: 100%; + min-height: 0; +} + +.rc-sessions { + padding: var(--space-3, 12px); + border-right: 1px solid var(--color-border, rgba(127, 127, 127, 0.2)); + overflow: auto; +} + +.rc-main { + display: flex; + flex-direction: column; + min-height: 0; + padding: var(--space-3, 12px) var(--space-4, 16px); +} + +.rc-panel { + flex: 1; + min-height: 0; + overflow: auto; + padding-top: var(--space-3, 12px); +} + +.rc-chat { + display: flex; + flex-direction: column; + gap: var(--space-3, 12px); + height: 100%; +} + +.rc-chat__log { + flex: 1; + min-height: 0; + overflow: auto; + display: flex; + flex-direction: column; + gap: var(--space-3, 12px); +} + +.rc-tool { + display: flex; + align-items: center; + gap: var(--space-2, 8px); + font-size: 13px; + margin: 4px 0; +} + +.rc-tool__name { + font-family: var(--font-mono, monospace); +} + +.rc-files { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 4px; +} + +.rc-diff { + border: 1px solid var(--color-border, rgba(127, 127, 127, 0.25)); + border-radius: 8px; + overflow: hidden; +} + +.rc-diff__head { + display: flex; + gap: var(--space-2, 8px); + padding: 6px 10px; + font-size: 13px; + border-bottom: 1px solid var(--color-border, rgba(127, 127, 127, 0.25)); +} + +.rc-diff__body { + margin: 0; + overflow-x: auto; + font-family: var(--font-mono, monospace); + font-size: 12px; + line-height: 1.5; +} + +.rc-diff__line { + padding: 0 10px; + white-space: pre; +} + +.rc-diff__line--add { + background: rgba(47, 158, 90, 0.14); +} + +.rc-diff__line--remove { + background: rgba(214, 69, 69, 0.14); +} + +.rc-diff__line--hunk, +.rc-diff__line--meta { + opacity: 0.65; +} + +.rc-terminal { + display: flex; + flex-direction: column; + gap: var(--space-2, 8px); + height: 100%; +} + +.rc-terminal__host { + flex: 1; + min-height: 240px; +} + +.rc-settings { + max-width: 760px; + padding: var(--space-4, 16px); +} diff --git a/apps/studio/src/views/Changes.tsx b/apps/studio/src/views/Changes.tsx new file mode 100644 index 0000000000..322899b01f --- /dev/null +++ b/apps/studio/src/views/Changes.tsx @@ -0,0 +1,67 @@ +import { useQuery } from "@tanstack/react-query" +import { Alert, Badge, Button, EmptyState, Loader } from "@redrob-labs/ui" +import { useEngineEvents } from "../engine" +import { useT } from "../i18n" +import { parseUnifiedDiff } from "../lib/diff" + +const TONE = { added: "success", untracked: "info", modified: "brand", deleted: "danger", renamed: "neutral" } as const + +/** The project's working-tree changes against HEAD, refreshed whenever a tool finishes. */ +export function Changes(props: { directory: string }) { + const t = useT() + const changes = useQuery({ + queryKey: ["git", props.directory], + queryFn: () => window.redrobCode!.gitChanges(props.directory), + }) + useEngineEvents((event) => { + if (event.type === "session.next.tool.success" || event.type === "session.next.step.ended") void changes.refetch() + }) + + if (changes.isLoading) return + const data = changes.data + if (!data) return {String(changes.error ?? "")} + if (!data.repository) return + + const files = parseUnifiedDiff(data.diff) + return ( +
+
+ {data.branch ?? "HEAD"} + +
+ {data.files.length === 0 ? ( + + ) : ( +
    + {data.files.map((file) => ( +
  • + + {file.status} + + {file.path} +
  • + ))} +
+ )} + {data.truncated && {t("changes.truncated")}} + {files.map((file) => ( +
+
+ {file.path} + +{file.additions} + −{file.deletions} +
+
+            {file.lines.map((line, index) => (
+              
+ {line.text || " "} +
+ ))} +
+
+ ))} +
+ ) +} diff --git a/apps/studio/src/views/Chat.tsx b/apps/studio/src/views/Chat.tsx new file mode 100644 index 0000000000..21fddb1543 --- /dev/null +++ b/apps/studio/src/views/Chat.tsx @@ -0,0 +1,192 @@ +import { useEffect, useRef, useState } from "react" +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" +import { Alert, Button, Composer, EmptyState, Message, TaskStatus } from "@redrob-labs/ui" +import type { MessagesListOutput } from "@redrob-code/client" +import { useEngine, useEngineEvents } from "../engine" +import { useT, type MessageKey } from "../i18n" +import { applyLiveEvent, changesMessages, describeToolInput, type LiveText } from "../lib/live" + +type Item = MessagesListOutput["data"][number] +type Assistant = Extract +type Part = Assistant["content"][number] + +/** One session's conversation: committed messages, text still streaming, pending permissions. */ +export function Chat(props: { sessionID: string; busy: boolean }) { + const t = useT() + const { client } = useEngine() + const queries = useQueryClient() + const [live, setLive] = useState({}) + const [draft, setDraft] = useState("") + const bottom = useRef(null) + + const messages = useQuery({ + queryKey: ["messages", props.sessionID], + queryFn: () => client.messages.list({ sessionID: props.sessionID, order: "asc", limit: 500 }), + }) + const permissions = useQuery({ + queryKey: ["permissions", props.sessionID], + queryFn: () => client.permissions.list({ sessionID: props.sessionID }), + }) + + useEngineEvents((event) => { + setLive((state) => applyLiveEvent(state, event, props.sessionID)) + if (changesMessages(event, props.sessionID)) { + void queries.invalidateQueries({ queryKey: ["messages", props.sessionID] }) + } + if (event.type.startsWith("permission.v2.") && event.data?.sessionID === props.sessionID) { + void queries.invalidateQueries({ queryKey: ["permissions", props.sessionID] }) + } + }) + + const send = useMutation({ + mutationFn: (text: string) => client.sessions.prompt({ sessionID: props.sessionID, prompt: { text } }), + onSuccess: () => setDraft(""), + }) + const stop = useMutation({ mutationFn: () => client.sessions.interrupt({ sessionID: props.sessionID }) }) + const reply = useMutation({ + mutationFn: (input: { requestID: string; reply: "once" | "always" | "reject" }) => + client.permissions.reply({ sessionID: props.sessionID, ...input }), + onSettled: () => void queries.invalidateQueries({ queryKey: ["permissions", props.sessionID] }), + }) + + const items = messages.data?.data ?? [] + const streaming = Object.entries(live).filter(([, text]) => text.length > 0) + // A block body on purpose: recent Chromium returns a promise from scrollIntoView, and an effect that + // returns anything but a function crashes React when the component unmounts. + useEffect(() => { + bottom.current?.scrollIntoView({ block: "end" }) + }, [items.length, streaming.length]) + + const error = send.error ?? stop.error ?? messages.error + return ( +
+
+ {items.length === 0 && !messages.isLoading && } + {items.map((item) => ( + + ))} + {streaming.map(([id, text]) => ( + +

{text}

+
+ ))} + {(permissions.data ?? []).map((request) => ( + + + + +
+ } + > + + {request.action} {request.resources.join(", ")} + + + ))} +
+
+ {error && {t("chat.error", { message: errorText(error) })}} + : undefined} + onSubmit={(value) => { + if (value.trim()) send.mutate(value.trim()) + }} + onStop={() => stop.mutate()} + /> +
+ ) +} + +function ChatItem(props: { item: Item }) { + const { item } = props + switch (item.type) { + case "user": + return ( + +

{item.text}

+
+ ) + case "assistant": + return ( + + {item.content.map((part) => ( + + ))} + + ) + case "shell": + return ( + + $ {item.command} +
{item.output}
+
+ ) + case "system": + case "synthetic": + return ( + +

{item.text}

+
+ ) + default: + return null + } +} + +const TOOL_STATE: Record = { + pending: { state: "queued", key: "chat.tool.pending" }, + running: { state: "running", key: "chat.tool.running" }, + completed: { state: "done", key: "chat.tool.completed" }, + error: { state: "failed", key: "chat.tool.error" }, +} + +function AssistantPart(props: { part: Part }) { + const t = useT() + const { part } = props + if (part.type === "text") return

{part.text}

+ if (part.type === "reasoning") return null + const status = TOOL_STATE[part.state.status] ?? TOOL_STATE["pending"]! + return ( +
+ + {part.name} + {describeToolInput(part.state.input)} + {part.state.status === "error" && {part.state.error.message}} +
+ ) +} + +function errorText(error: unknown) { + if (error instanceof Error) return error.message + if (error && typeof error === "object" && "message" in error) return String((error as { message: unknown }).message) + return String(error) +} diff --git a/apps/studio/src/views/Consent.tsx b/apps/studio/src/views/Consent.tsx new file mode 100644 index 0000000000..5bf86f63cd --- /dev/null +++ b/apps/studio/src/views/Consent.tsx @@ -0,0 +1,36 @@ +import { useState } from "react" +import { Button, Card, Switch } from "@redrob-labs/ui" +import type { Settings } from "@redrob-code/desktop/bridge" +import { useT } from "../i18n" + +/** + * The first-run privacy choice: two separate switches, both off, nothing pre-ticked. Continuing with + * both off is a complete answer, and the choice can be changed in Settings at any time. + */ +export function Consent(props: { settings: Settings; onDone: (patch: Partial) => void }) { + const t = useT() + const [crash, setCrash] = useState(props.settings.consent.crashReports) + const [usage, setUsage] = useState(props.settings.consent.usageEvents) + return ( +
+ props.onDone({ consent: { crashReports: crash, usageEvents: usage, decided: true } })} + > + {t("consent.continue")} + + } + className="rc-narrow" + > +
+ setCrash(event.target.checked)} /> + setUsage(event.target.checked)} /> +
+
+
+ ) +} diff --git a/apps/studio/src/views/Home.tsx b/apps/studio/src/views/Home.tsx new file mode 100644 index 0000000000..89d35f053e --- /dev/null +++ b/apps/studio/src/views/Home.tsx @@ -0,0 +1,128 @@ +import { useCallback, useState } from "react" +import { useQuery, useQueryClient } from "@tanstack/react-query" +import { AppShell, Badge, Button, EmptyState } from "@redrob-labs/ui" +import type { AppInfo, EngineState, Settings as SettingsValue } from "@redrob-code/desktop/bridge" +import { useEngine, useEngineEvents } from "../engine" +import { useT } from "../i18n" +import { SettingsPanel } from "./Settings" +import { SignIn } from "./SignIn" +import { Workspace } from "./Workspace" + +function folderName(directory: string) { + return directory.split(/[\\/]/).filter(Boolean).pop() ?? directory +} + +export function Home(props: { + settings: SettingsValue + appInfo: AppInfo | null + engineState: EngineState + onSettings: (patch: Partial) => Promise +}) { + const t = useT() + const bridge = window.redrobCode! + const { client } = useEngine() + const queries = useQueryClient() + const [project, setProject] = useState(props.settings.recentProjects[0] ?? null) + const [settingsOpen, setSettingsOpen] = useState(false) + const [skipSignIn, setSkipSignIn] = useState(false) + + const redrob = useQuery({ + queryKey: ["integration", "redrob"], + queryFn: () => client.integrations.get({ integrationID: "redrob" }), + }) + const signedIn = (redrob.data?.data?.connections.length ?? 0) > 0 + useEngineEvents((event) => { + if (event.type === "integration.connection.updated" || event.type === "integration.updated") + void queries.invalidateQueries({ queryKey: ["integration"] }) + }) + const refreshSignIn = useCallback(() => void queries.invalidateQueries({ queryKey: ["integration"] }), [queries]) + + const open = async (directory: string) => { + setProject(directory) + const recent = [directory, ...props.settings.recentProjects.filter((item) => item !== directory)] + await props.onSettings({ recentProjects: recent }) + } + + const pick = async () => { + const directory = await bridge.pickDirectory() + if (directory) await open(directory) + } + + const windows = props.appInfo?.platform === "windows" + + return ( + + {redrob.isSuccess && ( + + {signedIn ? t("account.signedIn") : t("account.signedOut")} + + )} + + + } + aside={ + + } + > + {settingsOpen ? ( + setSettingsOpen(false)} + /> + ) : redrob.isSuccess && !signedIn && !skipSignIn ? ( +
+ setSkipSignIn(true)} /> +
+ ) : project ? ( + + ) : ( +
+ void pick()}> + {t("projects.open")} + + } + /> +
+ )} +
+ ) +} diff --git a/apps/studio/src/views/Settings.tsx b/apps/studio/src/views/Settings.tsx new file mode 100644 index 0000000000..a0ea57034e --- /dev/null +++ b/apps/studio/src/views/Settings.tsx @@ -0,0 +1,174 @@ +import { useEffect, useState } from "react" +import { useMutation, useQuery } from "@tanstack/react-query" +import { Alert, Button, Card, Switch, ThemeSwitch } from "@redrob-labs/ui" +import type { AppInfo, EngineState, Settings, UpdaterState } from "@redrob-code/desktop/bridge" +import { useEngine } from "../engine" +import { useT, type Translate } from "../i18n" + +export function SettingsPanel(props: { + settings: Settings + engineState: EngineState + appInfo: AppInfo | null + onChange: (patch: Partial) => Promise + onClose: () => void +}) { + const t = useT() + const bridge = window.redrobCode! + const { info } = useEngine() + const [updater, setUpdater] = useState({ status: "idle" }) + const [confirmInstall, setConfirmInstall] = useState(false) + useEffect(() => bridge.updater.onState(setUpdater), [bridge]) + + const cli = useQuery({ queryKey: ["cli"], queryFn: () => bridge.cli.status() }) + const install = useMutation({ mutationFn: () => bridge.cli.install(), onSettled: () => void cli.refetch() }) + const { settings } = props + + return ( +
+
+

{t("settings.title")}

+ +
+ + +
+ {(["en", "ko"] as const).map((language) => ( + + ))} +
+
+ + + void props.onChange({ theme })} /> + + + +
+
+ {(["stable", "alpha"] as const).map((channel) => ( + + ))} + + {updater.status === "ready" && ( + + )} +
+ +
+
+ + +
+

+ {cli.data?.installed + ? t("settings.cli.installed", { path: cli.data.path ?? "", version: cli.data.version ?? "?" }) + : t("settings.cli.missing")} +

+ {cli.data && !cli.data.installed && !confirmInstall && ( + + )} + {cli.data && confirmInstall && !cli.data.installed && ( + install.mutate()}> + {t("settings.cli.install")} + + } + > + {cli.data.installCommand} + + )} + {install.data &&
{install.data.output}
} + void props.onChange({ preferInstalledCli: event.target.checked })} + /> +
+
+ + +
+ + void props.onChange({ consent: { ...settings.consent, crashReports: event.target.checked } }) + } + /> + + void props.onChange({ consent: { ...settings.consent, usageEvents: event.target.checked } }) + } + /> +
+
+ + +
+

+ {t(`engine.source.${info.source}` as const)} · {info.binary} +

+ +
+
+
+ ) +} + +function UpdaterLine(props: { state: UpdaterState; t: Translate }) { + const { state, t } = props + switch (state.status) { + case "checking": + return

{t("update.checking")}

+ case "available": + return

{t("update.available", { version: state.version })}

+ case "downloading": + return

{state.percent}%

+ case "ready": + return

{t("update.ready", { version: state.version })}

+ case "none": + return

{t("update.none")}

+ case "unsupported": + return

{state.reason}

+ case "error": + return

{t("update.error", { message: state.message })}

+ default: + return null + } +} diff --git a/apps/studio/src/views/SignIn.tsx b/apps/studio/src/views/SignIn.tsx new file mode 100644 index 0000000000..c385177c4f --- /dev/null +++ b/apps/studio/src/views/SignIn.tsx @@ -0,0 +1,66 @@ +import { useEffect, useState } from "react" +import { Alert, Button, Card } from "@redrob-labs/ui" +import type { SignInProgress } from "@redrob-code/desktop/bridge" +import { useT } from "../i18n" + +/** + * Console sign-in. The interface only ever sees the user code and the outcome: the key travels from + * the Console to the engine inside the main process. + */ +export function SignIn(props: { onConnected: () => void; onSkip?: () => void }) { + const t = useT() + const bridge = window.redrobCode! + const [progress, setProgress] = useState(null) + + useEffect( + () => + bridge.signIn.onProgress((next) => { + setProgress(next) + if (next.status === "connected") props.onConnected() + }), + [bridge, props.onConnected], + ) + + const waiting = progress?.status === "waiting" ? progress : null + const problem = + progress?.status === "denied" + ? t("signIn.denied") + : progress?.status === "expired" + ? t("signIn.expired") + : progress?.status === "failed" + ? t("signIn.failed", { message: progress.message }) + : null + + return ( + +
+ {problem && {problem}} + {waiting ? ( + <> +

{t("signIn.waiting")}

+ {waiting.userCode} +
+ + +
+ + ) : ( +
+ + {props.onSkip && ( + + )} +
+ )} +
+
+ ) +} diff --git a/apps/studio/src/views/Startup.tsx b/apps/studio/src/views/Startup.tsx new file mode 100644 index 0000000000..e6157d73cb --- /dev/null +++ b/apps/studio/src/views/Startup.tsx @@ -0,0 +1,46 @@ +import { useState } from "react" +import { Alert, Button, EmptyState, Loader } from "@redrob-labs/ui" +import type { EngineState } from "@redrob-code/desktop/bridge" +import { translate, useT } from "../i18n" + +export function NotDesktop() { + return ( +
+ +
+ ) +} + +export function Startup(props: { state: EngineState }) { + const t = useT() + const [logs, setLogs] = useState(null) + const bridge = window.redrobCode! + + if (props.state.status !== "failed") { + return ( +
+ +
+ ) + } + + return ( +
+ void bridge.engine.restart()}> + {t("engine.restart")} + + } + > + {props.state.message} + + + {logs !== null &&
{logs || "—"}
} +
+ ) +} diff --git a/apps/studio/src/views/Terminal.tsx b/apps/studio/src/views/Terminal.tsx new file mode 100644 index 0000000000..f829322210 --- /dev/null +++ b/apps/studio/src/views/Terminal.tsx @@ -0,0 +1,107 @@ +import { useEffect, useRef, useState } from "react" +import { Alert, Loader } from "@redrob-labs/ui" +import { Terminal as XTerm } from "@xterm/xterm" +import { FitAddon } from "@xterm/addon-fit" +import { useEngine } from "../engine" +import { useT } from "../i18n" + +/** + * A real shell in the project folder, run by the engine's pty service. + * + * The browser WebSocket cannot send an Authorization header, so the socket is opened with a + * single-use ticket minted over authenticated HTTP (packages/server/src/handlers/pty.ts). Output is + * raw bytes; one binary frame starting with 0x00 carries the resume cursor and is not shown. Resizing + * goes through `ptys.update`, not the socket. Running the shell in the engine rather than in Electron + * keeps native modules out of the app. + */ +export function Terminal(props: { directory: string }) { + const t = useT() + const { client, info } = useEngine() + const host = useRef(null) + const [status, setStatus] = useState<"starting" | "open" | { error: string }>("starting") + + useEffect(() => { + if (!host.current) return + const location = { directory: props.directory } + // A literal font stack: xterm measures glyphs on a canvas, which cannot resolve CSS variables. + const term = new XTerm({ + fontFamily: '"Cascadia Mono", "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace', + fontSize: 13, + cursorBlink: true, + scrollback: 5000, + }) + const fit = new FitAddon() + term.loadAddon(fit) + term.open(host.current) + fit.fit() + + let socket: WebSocket | null = null + let ptyID: string | null = null + let disposed = false + const decoder = new TextDecoder() + const encoder = new TextEncoder() + + const connect = async () => { + const created = await client.ptys.create({ location, cwd: props.directory, title: "Redrob Code" }) + ptyID = created.data.id + if (disposed) return + await client.ptys.update({ ptyID, location, size: { rows: term.rows, cols: term.cols } }) + + const tokenUrl = new URL(`/api/pty/${encodeURIComponent(ptyID)}/connect-token`, info.url) + tokenUrl.searchParams.set("location[directory]", props.directory) + const response = await fetch(tokenUrl, { + method: "POST", + headers: { Authorization: info.authorization, "x-redrob-ticket": "1" }, + }) + if (!response.ok) throw new Error(`connect-token answered ${response.status}`) + const { data } = (await response.json()) as { data: { ticket: string } } + if (disposed) return + + const socketUrl = new URL(`/api/pty/${encodeURIComponent(ptyID)}/connect`, info.url) + socketUrl.protocol = socketUrl.protocol === "https:" ? "wss:" : "ws:" + socketUrl.searchParams.set("ticket", data.ticket) + socketUrl.searchParams.set("location[directory]", props.directory) + socket = new WebSocket(socketUrl) + socket.binaryType = "arraybuffer" + socket.onopen = () => setStatus("open") + socket.onmessage = (message) => { + if (typeof message.data === "string") return term.write(message.data) + const bytes = new Uint8Array(message.data as ArrayBuffer) + if (bytes[0] === 0) return // the cursor control frame + term.write(decoder.decode(bytes, { stream: true })) + } + socket.onclose = (event) => { + if (!disposed && event.code !== 1000) setStatus({ error: `closed (${event.code})` }) + } + } + + const input = term.onData((value) => { + if (socket?.readyState === WebSocket.OPEN) socket.send(encoder.encode(value)) + }) + const resize = new ResizeObserver(() => { + fit.fit() + if (ptyID) + void client.ptys.update({ ptyID, location, size: { rows: term.rows, cols: term.cols } }).catch(() => {}) + }) + resize.observe(host.current) + + connect().catch((error: unknown) => setStatus({ error: error instanceof Error ? error.message : String(error) })) + + return () => { + disposed = true + resize.disconnect() + input.dispose() + socket?.close(1000) + if (ptyID) void client.ptys.remove({ ptyID, location }).catch(() => {}) + term.dispose() + } + }, [client, info, props.directory]) + + return ( +
+ {status === "starting" && } + {typeof status === "object" && {t("terminal.failed", { message: status.error })}} +
+
+ ) +} diff --git a/apps/studio/src/views/Workspace.tsx b/apps/studio/src/views/Workspace.tsx new file mode 100644 index 0000000000..26d1d0c485 --- /dev/null +++ b/apps/studio/src/views/Workspace.tsx @@ -0,0 +1,103 @@ +import { useState } from "react" +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" +import { Button, EmptyState, Tabs, TaskStatus } from "@redrob-labs/ui" +import { useEngine, useEngineEvents } from "../engine" +import { useT } from "../i18n" +import { Changes } from "./Changes" +import { Chat } from "./Chat" +import { Terminal } from "./Terminal" + +type Tab = "chat" | "changes" | "terminal" + +/** One project folder: its sessions on the left, the selected session's chat, changes and terminal. */ +export function Workspace(props: { directory: string }) { + const t = useT() + const { client } = useEngine() + const queries = useQueryClient() + const [sessionID, setSessionID] = useState(null) + const [tab, setTab] = useState("chat") + + const sessions = useQuery({ + queryKey: ["sessions", props.directory], + queryFn: () => client.sessions.list({ directory: props.directory, order: "desc", limit: 50 }), + }) + const active = useQuery({ queryKey: ["sessions", "active"], queryFn: () => client.sessions.active() }) + useEngineEvents((event) => { + if (event.type.startsWith("session.next.step.") || event.type === "session.next.prompted") { + void queries.invalidateQueries({ queryKey: ["sessions"] }) + } + }) + + const create = useMutation({ + mutationFn: () => client.sessions.create({ location: { directory: props.directory } }), + onSuccess: (session) => { + setSessionID(session.id) + setTab("chat") + void queries.invalidateQueries({ queryKey: ["sessions", props.directory] }) + }, + }) + + const list = sessions.data?.data ?? [] + const selected = sessionID ?? list[0]?.id ?? null + + return ( +
+
+
+

{t("sessions.title")}

+ +
+ {list.length === 0 && !sessions.isLoading ? ( +

{t("sessions.empty")}

+ ) : ( +
    + {list.map((session) => ( +
  • + +
  • + ))} +
+ )} +
+
+ setTab(id as Tab)} + items={[ + { id: "chat", label: t("tabs.chat") }, + { id: "changes", label: t("tabs.changes") }, + { id: "terminal", label: t("tabs.terminal") }, + ]} + /> +
+ {tab === "chat" && + (selected ? ( + + ) : ( + create.mutate()}> + {t("sessions.new")} + + } + /> + ))} + {tab === "changes" && } + {tab === "terminal" && } +
+
+
+ ) +} diff --git a/apps/studio/test/lib.test.ts b/apps/studio/test/lib.test.ts new file mode 100644 index 0000000000..1327c8b2cf --- /dev/null +++ b/apps/studio/test/lib.test.ts @@ -0,0 +1,66 @@ +import { expect, test } from "bun:test" +import { parseUnifiedDiff } from "../src/lib/diff" +import { applyLiveEvent, changesMessages, describeToolInput } from "../src/lib/live" +import { dictionaries, translate } from "../src/i18n" + +test("splits a unified diff into files with counts", () => { + const diff = [ + "diff --git a/src/a.ts b/src/a.ts", + "index 1..2 100644", + "--- a/src/a.ts", + "+++ b/src/a.ts", + "@@ -1,2 +1,2 @@", + " keep", + "-old", + "+new", + "+more", + "diff --git a/b.md b/b.md", + "@@ -0,0 +1 @@", + "+hello", + ].join("\n") + const files = parseUnifiedDiff(diff) + expect(files.map((file) => [file.path, file.additions, file.deletions])).toEqual([ + ["src/a.ts", 2, 1], + ["b.md", 1, 0], + ]) + expect(files[0].lines.map((line) => line.kind)).toEqual(["meta", "hunk", "context", "remove", "add", "add"]) +}) + +test("streams text for one session and clears when the step ends", () => { + let state = {} + state = applyLiveEvent(state, { type: "session.next.text.started", data: { sessionID: "s", textID: "t" } }, "s") + state = applyLiveEvent( + state, + { type: "session.next.text.delta", data: { sessionID: "s", textID: "t", delta: "Hel" } }, + "s", + ) + state = applyLiveEvent( + state, + { type: "session.next.text.delta", data: { sessionID: "x", textID: "t", delta: "!" } }, + "s", + ) + state = applyLiveEvent( + state, + { type: "session.next.text.delta", data: { sessionID: "s", textID: "t", delta: "lo" } }, + "s", + ) + expect(state).toEqual({ t: "Hello" }) + expect(applyLiveEvent(state, { type: "session.next.step.ended", data: { sessionID: "s" } }, "s")).toEqual({}) + expect(changesMessages({ type: "session.next.tool.success", data: { sessionID: "s" } }, "s")).toBe(true) + expect(changesMessages({ type: "session.next.text.delta", data: { sessionID: "s" } }, "s")).toBe(false) +}) + +test("describes tool input briefly", () => { + expect(describeToolInput({ filePath: "src/a.ts", content: "…" })).toBe("src/a.ts") + expect(describeToolInput({ command: "bun test" })).toBe("bun test") + expect(describeToolInput(null)).toBe("") +}) + +test("Korean covers every English key, and placeholders survive", () => { + expect(Object.keys(dictionaries.ko).sort()).toEqual(Object.keys(dictionaries.en).sort()) + for (const [key, english] of Object.entries(dictionaries.en)) { + const names = (text: string) => [...text.matchAll(/\{(\w+)\}/g)].map((m) => m[1]).sort() + expect(names(dictionaries.ko[key as keyof typeof dictionaries.en])).toEqual(names(english)) + } + expect(translate("en", "signIn.connected", { account: "Acme" })).toBe("Signed in to Acme") +}) diff --git a/apps/studio/tsconfig.json b/apps/studio/tsconfig.json new file mode 100644 index 0000000000..5128df2f8d --- /dev/null +++ b/apps/studio/tsconfig.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@tsconfig/bun/tsconfig.json", + "compilerOptions": { + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "jsx": "react-jsx", + "jsxImportSource": "react", + "types": ["bun", "vite/client"], + "noUncheckedIndexedAccess": false + }, + "include": ["src", "test", "vite.config.ts"] +} diff --git a/apps/studio/vite.config.ts b/apps/studio/vite.config.ts new file mode 100644 index 0000000000..cf645d1478 --- /dev/null +++ b/apps/studio/vite.config.ts @@ -0,0 +1,12 @@ +import react from "@vitejs/plugin-react" +import { defineConfig } from "vite" + +// Relative asset URLs, because the packaged app serves this bundle from oc://renderer/ rather than a +// web server root. +export default defineConfig({ + base: "./", + plugins: [react()], + // Loaded from disk inside the app, never over a network, so one large chunk costs nothing. + build: { outDir: "dist", emptyOutDir: true, target: "chrome130", sourcemap: true, chunkSizeWarningLimit: 4096 }, + server: { strictPort: true }, +}) From 6a1d0ae9a6d5dc586d2854981d9788ca05df7337 Mon Sep 17 00:00:00 2001 From: Kiro Agent <244629292+kiro-agent@users.noreply.github.com> Date: Mon, 12 Oct 2026 02:04:54 +0000 Subject: [PATCH 3/5] Build, smoke-test and release the desktop app without touching the CLI path The desktop workflow typechecks and tests both apps, packages the app for Windows and Linux, and launches the packaged build with --smoke-test, which proves the bundled engine starts, accepts its credential and refuses a request without one. It also runs the vanilla `install` script against each pull request's own build, so adding the app can never take the CLI install away unnoticed. In release.yml the desktop-windows job runs after `publish` and only adds files, so a desktop failure cannot hold back or roll back the CLI. It packages the signed redrob.exe the CLI users download, so the app and the CLI run identical engines. --- .github/workflows/desktop.yml | 114 ++++++++++++++++++++++++++++++ .github/workflows/release.yml | 127 ++++++++++++++++++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 .github/workflows/desktop.yml diff --git a/.github/workflows/desktop.yml b/.github/workflows/desktop.yml new file mode 100644 index 0000000000..6837e28cb8 --- /dev/null +++ b/.github/workflows/desktop.yml @@ -0,0 +1,114 @@ +name: desktop + +# Redrob Code Desktop (apps/desktop + apps/studio), and the vanilla CLI install beside it. +# +# Three things are proven on every pull request: +# +# 1. The app's own code typechecks and its tests pass. +# 2. A PACKAGED app starts its bundled engine: `--smoke-test` launches the unpacked build, waits for +# the engine handshake, proves an authenticated health check succeeds and an unauthenticated one +# gets 401, then exits. Windows first, because Windows ships first. +# 3. The CLI still installs the vanilla way. Adding a desktop app must never take the curl / script +# install away, so the official `install` script is run against this commit's own build and the +# installed `redrob` must answer `--version`. +# +# Not yet required checks: add `desktop (windows)`, `desktop (linux)` and `cli install (*)` to branch +# protection once they have been green on develop for a while, as was done for the Windows unit jobs. + +on: + pull_request: + push: + branches: [develop] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: desktop-${{ github.ref }} + cancel-in-progress: true + +jobs: + desktop: + name: desktop (${{ matrix.name }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - name: windows + os: windows-latest + - name: linux + os: ubuntu-latest + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - uses: ./.github/actions/setup-bun + + - name: Typecheck + run: | + set -euo pipefail + (cd apps/desktop && bun run typecheck) + (cd apps/studio && bun run typecheck) + + - name: Unit tests + run: | + set -euo pipefail + (cd apps/desktop && bun test) + (cd apps/studio && bun test) + + # The same build the release makes, for this runner's platform only. + - name: Build the engine + run: bun run --cwd packages/redrob build --single --skip-install + env: + REDROB_VERSION: 0.0.0-ci + + - name: Package the app (unpacked) + run: | + set -euo pipefail + cd apps/desktop + bun run prepare-engine + bun run package --dir + + - name: Smoke test the packaged app + run: bun run --cwd apps/desktop smoke + + cli-install: + name: cli install (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - uses: ./.github/actions/setup-bun + + - name: Build the engine + run: bun run --cwd packages/redrob build --single --skip-install + env: + REDROB_VERSION: 0.0.0-ci + + # The official installer, unchanged, pointed at this commit's binary. A temporary directory keeps + # it off the runner's PATH and proves REDROB_INSTALL_DIR is still honoured. + - name: Install with the vanilla script + run: | + set -euo pipefail + os="$(uname -s | tr '[:upper:]' '[:lower:]')" + arch="$(uname -m)" + case "$arch" in x86_64) arch=x64 ;; aarch64|arm64) arch=arm64 ;; esac + binary="packages/redrob/dist/redrob-${os}-${arch}/bin/redrob" + dir="$RUNNER_TEMP/redrob-bin" + REDROB_INSTALL_DIR="$dir" ./install --binary "$binary" --no-modify-path + "$dir/redrob" --version + # The desktop app must never have put anything here. + if ls "$dir" | grep -q desktop; then + echo "::error::a desktop artifact appeared in the CLI install directory" + exit 1 + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8b3cf71006..53780ffc66 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -860,3 +860,130 @@ jobs: echo "[Open the promotion pull request]($url)" } >> "$GITHUB_STEP_SUMMARY" echo "::notice::release published; promote develop to main at $url" + + # Redrob Code Desktop for Windows, added to the release the CLI was just published in. + # + # It runs AFTER `publish` and nothing depends on it, so a desktop failure can never hold back or + # roll back the CLI: the archives, the CLI installer, SHA256SUMS and VERSION are already out by the + # time this starts. Every file it uploads starts `redrob-code-desktop-` (plus electron-updater's + # `latest.yml` feed), and `install` only ever downloads `redrob-.`, so the two cannot + # be confused. See docs/DESKTOP.md. + # + # The engine inside the app is the SIGNED `redrob.exe` from `sign-windows`, unpacked from the very + # archive the CLI users download, so app and CLI run byte-identical engines. + desktop-windows: + needs: [version, sign-windows, publish] + runs-on: windows-latest + timeout-minutes: 45 + env: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + AZURE_ARTIFACT_SIGNING_ENDPOINT: ${{ secrets.AZURE_ARTIFACT_SIGNING_ENDPOINT }} + AZURE_ARTIFACT_SIGNING_ACCOUNT: ${{ secrets.AZURE_ARTIFACT_SIGNING_ACCOUNT }} + AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE: ${{ secrets.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE }} + WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }} + WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }} + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + ref: develop + fetch-depth: 1 + + - uses: ./.github/actions/setup-bun + + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: cli-windows-signed + path: cli + + - name: Put the signed engine in the app + run: | + set -euo pipefail + mkdir -p engine + 7z x -y -oengine cli/redrob-windows-x64.zip >/dev/null + bun run --cwd apps/desktop prepare-engine --platform win32 --arch x64 --from "$GITHUB_WORKSPACE/engine/redrob.exe" + + # The organisation PFX first, because it is what this repository actually signs with (see the + # `method` note on `sign-windows`) and electron-builder signs with it natively, including the + # app exe INSIDE the installer before latest.yml records the installer's hash. Azure Trusted + # Signing through electron-builder's azureSignOptions is the fallback and is not yet proven in + # this workflow. Otherwise unsigned, and said so. + - name: Choose the signing method + id: config + run: | + set -euo pipefail + if [ -n "${WIN_CSC_LINK:-}" ] && [ -n "${WIN_CSC_KEY_PASSWORD:-}" ]; then + echo "method=pfx" >> "$GITHUB_OUTPUT" + elif [ -n "${AZURE_ARTIFACT_SIGNING_ENDPOINT:-}" ] && [ -n "${AZURE_ARTIFACT_SIGNING_ACCOUNT:-}" ] \ + && [ -n "${AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE:-}" ] && [ -n "${AZURE_CLIENT_ID:-}" ]; then + echo "method=azure" >> "$GITHUB_OUTPUT" + echo "::warning::signing the desktop installer through Azure Trusted Signing, which is not yet verified here" + else + echo "method=none" >> "$GITHUB_OUTPUT" + echo "::warning::no Windows signing method is configured; the desktop installer will be unsigned" + fi + + - name: Azure login + if: steps.config.outputs.method == 'azure' + uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0 + with: + client-id: ${{ env.AZURE_CLIENT_ID }} + tenant-id: ${{ env.AZURE_TENANT_ID }} + subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} + + - name: Package and smoke-test + env: + REDROB_VERSION: ${{ needs.version.outputs.version }} + METHOD: ${{ steps.config.outputs.method }} + run: | + set -euo pipefail + cd apps/desktop + if [ "$METHOD" = "pfx" ]; then + export CSC_LINK="$WIN_CSC_LINK" CSC_KEY_PASSWORD="$WIN_CSC_KEY_PASSWORD" + # The config only enables azureSignOptions when these are set; PFX must not race it. + unset AZURE_ARTIFACT_SIGNING_ENDPOINT AZURE_ARTIFACT_SIGNING_ACCOUNT AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE + else + # Without this electron-builder goes looking for a certificate on its own. + export CSC_IDENTITY_AUTO_DISCOVERY=false + if [ "$METHOD" = "none" ]; then + unset AZURE_ARTIFACT_SIGNING_ENDPOINT AZURE_ARTIFACT_SIGNING_ACCOUNT AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE + fi + fi + bun run package + bun run smoke + ls dist + + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: desktop-windows + path: | + apps/desktop/dist/redrob-code-desktop-*.exe + apps/desktop/dist/redrob-code-desktop-*.exe.blockmap + apps/desktop/dist/latest.yml + + - name: Add the desktop installer to the release + if: ${{ !inputs.dry_run }} + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ needs.version.outputs.tag }} + run: | + set -euo pipefail + shopt -s nullglob + files=(apps/desktop/dist/redrob-code-desktop-*.exe apps/desktop/dist/redrob-code-desktop-*.exe.blockmap apps/desktop/dist/latest.yml) + if [ "${#files[@]}" -lt 3 ]; then + echo "::error::expected the installer, its blockmap and latest.yml; found ${#files[@]} file(s)" + exit 1 + fi + for file in "${files[@]}"; do + for attempt in 1 2 3; do + gh release upload "$TAG" "$file" --clobber && break + echo "::warning::upload of $(basename "$file") failed on attempt $attempt" + sleep $((attempt * 10)) + [ "$attempt" = 3 ] && exit 1 + done + done + echo "::notice::desktop installer added to $TAG (signing: ${{ steps.config.outputs.method }})" From 876a313ec399c44ad097a45c8611e1924e3e71ec Mon Sep 17 00:00:00 2001 From: Kiro Agent <244629292+kiro-agent@users.noreply.github.com> Date: Mon, 12 Oct 2026 02:04:54 +0000 Subject: [PATCH 4/5] Document Redrob Code Desktop beside the unchanged CLI install The README no longer says this repository ships no GUI. It states that the desktop app is optional, ships in the same release, and leaves the CLI install and PATH alone. docs/DESKTOP.md is the design of record. --- AGENTS.md | 7 ++++- README.ko.md | 15 ++++++---- README.md | 16 ++++++---- docs/DESKTOP.md | 78 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 docs/DESKTOP.md diff --git a/AGENTS.md b/AGENTS.md index deb58794c3..c0f908dd6b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,8 +4,13 @@ Redrob Code: the engine behind Redrob Cowork, a fork of OpenCode. It is consumed version rather than a branch — `constants.json` in `redrob-cowork` names the release the desktop app ships, so **a change here reaches users only through a release and a pin bump**, never by merging. +This repository also holds Redrob Code Desktop (`apps/desktop`, `apps/studio`), which ships in the +same release as the CLI with no pin. `apps/` is ours alone: upstream has no such directory, so upstream +syncs never touch it. Do not move the app under `packages/app` or `packages/desktop`, which upstream +still develops. Design of record: `docs/DESKTOP.md`. + Per-package instructions live beside the code they describe and are more specific than this file. -There are ten of them; list them with `git ls-files | grep 'AGENTS.md$'`. **Read the nearest one +There are twelve of them; list them with `git ls-files | grep 'AGENTS.md$'`. **Read the nearest one before editing a package** — `packages/redrob/`, `packages/redrob/test/`, `packages/schema/`, `packages/llm/` and `packages/codemode/` each have their own, and three sit deeper under `packages/redrob/src/**`. diff --git a/README.ko.md b/README.ko.md index cbe9ea34a4..1317b55dcb 100644 --- a/README.ko.md +++ b/README.ko.md @@ -131,12 +131,17 @@ id를 쓰는 설정·에이전트·명령은 `redrob/auto`로 바꾸세요. ### 범위 레드롭 코드는 엔진, CLI, 터미널 UI 그리고 그 위에 올라가는 SDK와 플러그인 -표면입니다. 자체 그래픽 인터페이스는 제공하지 않습니다. 데스크톱 셸도, 웹 UI도, -브라우저 프런트엔드도 없습니다. +표면이며, **레드롭 코드 데스크톱**(`apps/desktop`, `apps/studio`)을 포함합니다. -**레드롭 워크가 유일한 GUI입니다.** `redrob serve`가 노출하는 HTTP API로 이 -엔진을 구동하며, 요청마다 `x-redrob-directory` 헤더로 프로젝트를 선택합니다. -API, ACP, MCP 표면이 두 제품 사이의 계약이므로 안정적으로 유지해야 합니다. +데스크톱 앱은 선택 사항이며 CLI를 대신하지 않고 함께 배포됩니다. 위의 설치 명령은 +그대로이며 여전히 CLI만 설치합니다. 데스크톱 설치 파일 +`redrob-code-desktop--win-x64.exe`는 같은 릴리스에 게시되고 자체 엔진을 +내장합니다. CLI 설치나 PATH는 건드리지 않습니다. +[docs/DESKTOP.md](./docs/DESKTOP.md)를 참고하세요. + +레드롭 코워크도 `redrob serve`가 노출하는 HTTP API로 이 엔진을 구동하며, 요청마다 +프로젝트를 선택합니다. API, ACP, MCP 표면은 모든 클라이언트와의 계약이므로 +안정적으로 유지해야 합니다. ### 개발 diff --git a/README.md b/README.md index 1083c2f4bc..f727da3252 100644 --- a/README.md +++ b/README.md @@ -136,13 +136,17 @@ new files to `.redrob/`. ### Scope Redrob Code is the engine, the CLI and the terminal UI, plus the SDKs and plugin -surface built on them. It ships no graphical interface of its own: no desktop -shell, no web UI, no browser front end. +surface built on them, and **Redrob Code Desktop** (`apps/desktop`, `apps/studio`). -**Redrob Work is the only GUI.** It drives this engine over the HTTP API that -`redrob serve` exposes, selecting a project per request with the -`x-redrob-directory` header. The API, ACP and MCP surfaces are the contract -between the two; keep them stable. +The desktop app is optional and ships beside the CLI, not instead of it. The install +commands above are unchanged and still give you the CLI on its own. The desktop +installer, `redrob-code-desktop--win-x64.exe`, is published in the same release +and bundles its own engine. It never touches your CLI install or your PATH. See +[docs/DESKTOP.md](./docs/DESKTOP.md). + +Redrob Cowork also drives this engine over the HTTP API that `redrob serve` exposes, +selecting a project per request. The API, ACP and MCP surfaces are the contract with +every client; keep them stable. ### Development diff --git a/docs/DESKTOP.md b/docs/DESKTOP.md new file mode 100644 index 0000000000..a19013d55a --- /dev/null +++ b/docs/DESKTOP.md @@ -0,0 +1,78 @@ +# Redrob Code Desktop + +Design of record for the desktop app in `apps/desktop` (Electron main + preload) and `apps/studio` +(React interface). The CLI is unchanged and remains a complete product on its own. + +## Two products, one release + +| | CLI (unchanged) | Desktop | +| -------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| Install | `curl -fsSL https://console.redrob.ai/code/install.sh \| sh`, `install.ps1`, `redrob-code-x64-setup.exe` | `redrob-code-desktop--win-x64.exe` | +| Release assets | `redrob--.{zip,tar.gz}`, `redrob-code-x64-setup.exe`, `SHA256SUMS`, `VERSION` | `redrob-code-desktop-*`, `latest.yml` | +| Updates | `redrob upgrade` | electron-updater, stable and alpha channels | + +- The CLI assets are published first. `release.yml`'s `desktop-windows` job runs after `publish` and + only adds files, so a desktop failure never holds back the CLI. +- No desktop file name can match what `install` downloads (`APP=redrob`, `redrob-$target$ext`). + `apps/desktop/test/misc.test.ts` pins this. +- The `desktop` workflow runs the vanilla `install` script against every pull request's build. + +## How the app runs the engine + +``` +Electron main ── spawn ──> redrob serve --hostname 127.0.0.1 --port 0 (random Basic credential) + │ waits for "redrob server listening on " (serve.ts, pinned by test/listening.test.ts) + ▼ +oc://renderer/ (apps/studio) ── HTTP + SSE + WebSocket ──> engine (@redrob-code/client) +``` + +- **The engine is bundled** as `resources/engine/redrob` (`redrob.bin` on Windows), copied from the + release's own signed CLI binary, so the app and the CLI run byte-identical engines. +- **Resolution order:** `REDROB_CODE_BIN`, then an installed CLI if the person chose "use the installed + CLI", then the bundled engine, then an installed CLI. +- **The bundled engine never updates itself** (`REDROB_DISABLE_AUTOUPDATE=1`); the app's updater + replaces it. A separately installed CLI keeps `redrob upgrade`. +- **The app never writes to PATH or to a CLI install directory.** Settings → "Install the redrob command" + runs the official installer, which produces an ordinary, self-updating CLI install. +- **The interface is served from `oc://renderer`**, which the engine's CORS allowlist already accepts + (`packages/server/src/cors.ts`). `file://` would send `Origin: null` and be refused. +- **The engine's data directory is shared with the CLI**, so sign-in, sessions and history appear in + both. + +## Sign-in + +The Console device flow (`/v1/device/authorize`, `/v1/device/token`, product `code`) runs in the main +process. The returned key goes straight to the engine (`POST /api/integration/redrob/connect/key`). It +never reaches the interface and is never stored by the app. Signing in is offered, not required: the +engine also works with a person's own provider keys. + +## Privacy + +On first run there are two switches, both off: crash reports and usage events. Nothing is collected in +this milestone either way. The choice is stored for the telemetry work that follows, which will never +send code, prompts, file paths or repository names. + +## Verifying a change + +```sh +cd apps/desktop && bun run typecheck && bun test +cd apps/studio && bun run typecheck && bun test && bun run build +bun run --cwd packages/redrob build --single --skip-install +cd apps/desktop && bun run prepare-engine && bun run package --dir && bun run smoke +``` + +`smoke` launches the packaged app with `--smoke-test`. The app starts the bundled engine, checks that an +authenticated health request succeeds and an unauthenticated one gets 401, then exits 0. + +## Roadmap + +| Milestone | Scope | +| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| M0 (this) | Shell, bundled engine, sign-in, projects, sessions (chat, changes, terminal), en/ko, Windows installer, updater, CI smoke and CLI install checks | +| M1 | Contest engine: blind build-off with rival-vendor builders, verification, pairwise judge, gates, `redrob buildoff` | +| M2 | Quality Lab: `redrob bench --replay`, head-to-head against the human patch, quality percentile, judge reliability checks with no human labels | +| M3 | Build-off interface | +| M4 | Refinement rounds, standard mined from the repo automatically, Linux packages | +| M5 | Fleet View, macOS on Apple Silicon | +| M6 | Conflict radar, shared engine daemon with the CLI, macOS on Intel | +| M7 | Remote runtimes, enterprise policy | From 399192e1743cf92e7606aba5d90bc8933f9ec250 Mon Sep 17 00:00:00 2001 From: Kiro Agent <244629292+kiro-agent@users.noreply.github.com> Date: Mon, 12 Oct 2026 02:10:21 +0000 Subject: [PATCH 5/5] Resolve renderer protocol test paths for the platform it runs on On Windows /app/studio resolves to a drive path, so the expected file URLs are built with path.resolve and pathToFileURL instead of written as POSIX literals. The code under test was already right; the first Windows run showed the test was not. --- apps/desktop/test/misc.test.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/desktop/test/misc.test.ts b/apps/desktop/test/misc.test.ts index 7411dac6a5..b538fb739c 100644 --- a/apps/desktop/test/misc.test.ts +++ b/apps/desktop/test/misc.test.ts @@ -1,6 +1,7 @@ import { expect, test } from "bun:test" import { readFileSync } from "node:fs" import path from "node:path" +import { pathToFileURL } from "node:url" import { installCommand } from "../src/main/cli" import { parseStatus } from "../src/main/git" import { resolveRendererFile } from "../src/main/renderer-protocol" @@ -8,12 +9,16 @@ import { normalize } from "../src/main/settings" import { parseEnv } from "../src/main/shell-env" test("the renderer protocol never serves a file outside the bundle", () => { - const root = "/app/studio" - const exists = (file: string) => file === "/app/studio/assets/a.js" || file === "/app/studio/index.html" - expect(resolveRendererFile(root, "oc://renderer/assets/a.js", exists)).toBe("file:///app/studio/assets/a.js") - expect(resolveRendererFile(root, "oc://renderer/sessions/123", exists)).toBe("file:///app/studio/index.html") + // Resolved for this platform, so on Windows "/app/studio" is a drive path like the app would see. + const root = path.resolve("/app/studio") + const asset = path.join(root, "assets", "a.js") + const index = path.join(root, "index.html") + const url = (file: string) => pathToFileURL(file).toString() + const exists = (file: string) => file === asset || file === index + expect(resolveRendererFile(root, "oc://renderer/assets/a.js", exists)).toBe(url(asset)) + expect(resolveRendererFile(root, "oc://renderer/sessions/123", exists)).toBe(url(index)) // URL parsing already collapses dot segments at the root, so this one stays inside the bundle... - expect(resolveRendererFile(root, "oc://renderer/%2e%2e/%2e%2e/etc/passwd", exists)).toStartWith("file:///app/studio/") + expect(resolveRendererFile(root, "oc://renderer/%2e%2e/%2e%2e/etc/passwd", exists)).toStartWith(url(root)) // ...and an encoded slash, which survives URL parsing and only becomes `../` once decoded, is refused. expect(resolveRendererFile(root, "oc://renderer/..%2f..%2fetc%2fpasswd", exists)).toBeNull() expect(resolveRendererFile(root, "oc://elsewhere/index.html", exists)).toBeNull()