diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fffcedb0..1cb828446 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: - main pull_request: +permissions: + contents: read + concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true @@ -24,22 +27,21 @@ jobs: - windows-latest env: - DEVSPACE_ALLOWED_ROOTS: ${{ github.workspace }} DEVSPACE_OAUTH_OWNER_TOKEN: ci-owner-token-that-is-long-enough - DEVSPACE_PUBLIC_BASE_URL: http://127.0.0.1:7676 steps: - name: Checkout uses: actions/checkout@v4 - - name: Setup Node - uses: actions/setup-node@v4 + - name: Setup pnpm and Node + uses: pnpm/setup@84cb39b217b10273981911c288cd62326dc7c6d2 # v2 with: - node-version: 22 - cache: npm + runtime: node@22 + cache: true + install: false - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Install Pi sandbox dependencies if: matrix.os == 'ubuntu-latest' @@ -51,15 +53,18 @@ jobs: fi - name: Typecheck - run: npm run typecheck + run: pnpm typecheck - name: Test env: DEVSPACE_REQUIRE_PI_SANDBOX: ${{ matrix.os == 'ubuntu-latest' && '1' || '0' }} - run: npm test + run: pnpm test + + - name: Package install smoke test + run: pnpm test:package-install - name: Build - run: npm run build + run: pnpm build - name: Doctor run: node dist/cli.js doctor diff --git a/README.md b/README.md index b1e4f0a3f..381930db8 100644 --- a/README.md +++ b/README.md @@ -246,11 +246,14 @@ This year, I began my journey to build a one-person, multi-agent company capable For working on DevSpace itself: +Install pnpm 11.25.0, the version pinned in `package.json`, with +`npm install --global pnpm@11.25.0`, then: + ```bash -npm install --include=dev -npm run dev -npm run typecheck -npm test -npm run build -npm run start +pnpm install --frozen-lockfile +pnpm dev +pnpm typecheck +pnpm test +pnpm build +pnpm start ``` diff --git a/bin/devspace-agentd.js b/bin/devspace-agentd.js new file mode 100755 index 000000000..e6c54ca1b --- /dev/null +++ b/bin/devspace-agentd.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node +import { runEntrypoint } from "./run-entrypoint.js"; + +await runEntrypoint("../src/local-agent-daemon-main.ts", "../dist/local-agent-daemon-main.js"); diff --git a/bin/devspace.js b/bin/devspace.js new file mode 100755 index 000000000..4e4788a1b --- /dev/null +++ b/bin/devspace.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node +import { runEntrypoint } from "./run-entrypoint.js"; + +await runEntrypoint("../src/cli.ts", "../dist/cli.js"); diff --git a/bin/run-entrypoint.js b/bin/run-entrypoint.js new file mode 100644 index 000000000..f32983a1c --- /dev/null +++ b/bin/run-entrypoint.js @@ -0,0 +1,20 @@ +import { existsSync } from "node:fs"; +import { fileURLToPath } from "node:url"; + +export async function runEntrypoint(sourcePath, distPath) { + const sourceUrl = new URL(sourcePath, import.meta.url); + if (existsSync(fileURLToPath(sourceUrl))) { + try { + await import("tsx/esm"); + } catch (error) { + throw new Error( + "DevSpace source checkout detected, but tsx is unavailable. Run `pnpm install` in the checkout; refusing to fall back to potentially stale dist output.", + { cause: error }, + ); + } + await import(sourceUrl.href); + return; + } + + await import(new URL(distPath, import.meta.url).href); +} diff --git a/docs/artifact-exchange.md b/docs/artifact-exchange.md index 6a6f5c75a..f4728eb0d 100644 --- a/docs/artifact-exchange.md +++ b/docs/artifact-exchange.md @@ -1,7 +1,8 @@ # Download a native file DevSpace can save a file attached or generated by an MCP host, such as ChatGPT, -directly into an open workspace. Enable the tool with `DEVSPACE_ARTIFACTS=1`. +directly into an open workspace. Enable the tool with +`artifacts.enabled` in `~/.devspace/config.jsonc`. ## Workflow @@ -36,7 +37,7 @@ file-object shape, trusted OpenAI download hosts, and redirects before streaming Malformed references, unknown fields, absolute paths, traversal, and symlinked parents are rejected. -Downloads are streamed under `DEVSPACE_ARTIFACT_MAX_FILE_BYTES` and published as +Downloads are streamed under `artifacts.maxFileBytes` and published as owner-only files without overwriting an existing destination. The tool is currently available on Linux. It is not registered on macOS, Windows, or BSD because Node.js does not expose the required descriptor-relative filesystem diff --git a/docs/assets/v11-review-ui-after.png b/docs/assets/v11-review-ui-after.png new file mode 100644 index 000000000..adc33c121 Binary files /dev/null and b/docs/assets/v11-review-ui-after.png differ diff --git a/docs/assets/v11-review-ui-before.png b/docs/assets/v11-review-ui-before.png new file mode 100644 index 000000000..0593fa247 Binary files /dev/null and b/docs/assets/v11-review-ui-before.png differ diff --git a/docs/chatgpt-coding-workflow.md b/docs/chatgpt-coding-workflow.md index d7a5d13ce..f6826617c 100644 --- a/docs/chatgpt-coding-workflow.md +++ b/docs/chatgpt-coding-workflow.md @@ -117,8 +117,8 @@ DevSpace discovers standard Agent Skills from: It also keeps compatibility with: - the bundled `subagents` skill when Subagents are enabled, unless `~/.devspace/skills/subagents/SKILL.md` exists -- `DEVSPACE_AGENT_DIR/skills`, defaulting to `~/.codex/skills` -- additional paths from `DEVSPACE_SKILL_PATHS` +- `skills.agentDir/skills`, defaulting to `~/.codex/skills` +- additional paths from `skills.paths` When Subagents are enabled, DevSpace discovers agent profiles from `~/.devspace/agents/*.md` and project `.devspace/agents/*.md`. @@ -130,7 +130,7 @@ Example profiles are packaged under `examples/agents/` for users who want starter templates. Copy or adapt them into one of the active profile directories before use. -Legacy project paths such as `.pi/skills` can be added through `DEVSPACE_SKILL_PATHS` when needed. +Legacy project paths such as `.pi/skills` can be added to `skills.paths` when needed. When `open_workspace` returns matching skills, the model should read the advertised `SKILL.md` before following that skill. @@ -140,8 +140,8 @@ Skill paths may be outside the workspace. DevSpace only permits reading: - advertised `SKILL.md` files - files under a skill directory after that skill's `SKILL.md` has been read -Set `DEVSPACE_SKILLS=0` to hide skills from workspace output. Enable Subagents -and choose providers through `devspace init` or the persisted provider +Set `skills.enabled` to `false` to hide skills from workspace output. Enable +Subagents and choose providers through `devspace init` or the persisted provider configuration. The bundled `subagents` skill teaches the minimal `devspace agents targets`, `devspace agents ls`, `devspace agents run`, `devspace agents continue`, and `devspace agents show` workflow. The catalog @@ -150,48 +150,57 @@ sessions for that workspace. ## Tool Names -DevSpace exposes these tool names: +The Claude surface exposes these tool names: - `open_workspace` - `read` - `write` - `edit` - `bash` +- `show_changes` -By default, DevSpace also runs in `DEVSPACE_TOOL_MODE=minimal`, so dedicated -`grep`, `glob`, and `ls` tools are hidden. Use `bash` with command-line tools -such as `rg`, `find`, and `ls` for search and directory inspection. - -Use `DEVSPACE_TOOL_MODE=full` to restore dedicated search and directory tools. - -The experimental Codex-style surface is enabled with -`DEVSPACE_TOOL_MODE=codex`. It exposes: +DevSpace uses the Codex-style surface by default. It exposes: - `open_workspace` - `read` - `apply_patch` - `exec_command` - `write_stdin` +- `show_changes` -In this mode, `write`, `edit`, `bash`, `grep`, `glob`, and `ls` are not -registered. `exec_command` returns a process session ID when a command is still +In this mode, `write`, `edit`, and `bash` are not registered. `exec_command` +returns a process session ID when a command is still running after its yield window. Use `write_stdin` to poll it, send input, resize a PTY, or send Ctrl-C. Set `tty: true` only for commands that need a terminal. -## Show Changes - -By default, `DEVSPACE_WIDGETS=full`. - -In that mode, DevSpace attaches widget UI to the exposed workspace, file, edit, -and shell tools. The aggregate `show_changes` tool is not exposed by default. +Set `tools.mode` to `claude` in `~/.devspace/config.jsonc` to expose `write`, +`edit`, and `bash` instead of the Codex mutation and command tools. Dedicated +MCP tools for `grep`, `glob`, and `ls` are not registered in either mode; use +the configured shell tool with command-line tools such as `rg`, `find`, and +`ls`. -Use `DEVSPACE_WIDGETS=off` to disable widget UI, or `DEVSPACE_WIDGETS=changes` -to expose the aggregate show-changes flow. +## Show Changes -When `show_changes` is exposed, call it exactly once after the final file -modification in any turn that changes files. It shows the combined changes for -that turn and advances the review point automatically. Reusing a workspace does -not change this workflow. +DevSpace exposes `show_changes` in both tool modes and attaches widget UI only +to `open_workspace` and `show_changes`. Reads, edits, and commands return normal +MCP results without creating an iframe for each call. Set `ui.enabled` to +`false` in `~/.devspace/config.jsonc` to disable UI metadata while keeping the +aggregate review tool available. + +Call `show_changes` exactly once after the final file modification in any turn +that changes files. It shows the combined changes for that turn and advances +the review point automatically. Reusing a workspace does not change this +workflow. + +The model-facing result stays compact: DevSpace returns the workspace ID, a +Git-backed `reviewRef`, and the summary text. MCP Apps hosts receive the full +file list and patch in result metadata for immediate rendering. If a host later +restores only the structured result, the review card can reopen that exact +`reviewRef` from DevSpace's Git review history without advancing the current +review point. + +For local inspection, run `devspace show-changes `. Add `--json` to +include the parsed summary, file list, and patch. ## Shell Use diff --git a/docs/configuration.md b/docs/configuration.md index 93a3d4fa3..6a6f607bd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,155 +1,112 @@ # Configuration Reference -DevSpace can be configured through `devspace init`, persisted config files, or -environment variables. +DevSpace stores durable settings in `~/.devspace/config.jsonc`. The file accepts +comments and trailing commas and is validated before the server starts. Editor +completion is provided by the versioned [JSON Schema](../schema/v1/devspace.schema.json), +also hosted at the URL in the file's `$schema` property. -The default files are: +Authentication stays separate because it contains a secret: ```text -~/.devspace/config.json +~/.devspace/config.jsonc ~/.devspace/auth.json ``` -Use another config directory with: +Run `devspace init` to create both files. `devspace config set publicBaseUrl +` updates the JSONC document without discarding its comments. -```bash -DEVSPACE_CONFIG_DIR=/path/to/config npx @waishnav/devspace serve -``` - -## Commands - -```bash -npx @waishnav/devspace init -npx @waishnav/devspace serve -npx @waishnav/devspace doctor -npx @waishnav/devspace config get -npx @waishnav/devspace config set publicBaseUrl https://devspace.example.com -``` - -## Core Environment Variables - -| Variable | Purpose | -| --- | --- | -| `HOST` | Local bind host. Defaults to `127.0.0.1`. | -| `PORT` | Local port. Defaults to `7676`. | -| `DEVSPACE_ALLOWED_ROOTS` | Comma-separated local roots that workspaces may open. | -| `DEVSPACE_PUBLIC_BASE_URL` | Public origin for the server, without `/mcp`. | -| `DEVSPACE_ALLOWED_HOSTS` | Optional Host header allowlist override. | -| `DEVSPACE_OAUTH_OWNER_TOKEN` | Owner password for OAuth approval. Must be at least 16 characters. | -| `DEVSPACE_WORKTREE_ROOT` | Directory for managed Git worktrees. Defaults to `~/.devspace/worktrees`. | -| `DEVSPACE_STATE_DIR` | Directory for SQLite state. Defaults to `~/.local/share/devspace`. | - -## Native Artifact Download - -Native-file download is disabled by default. Enable it when ChatGPT needs to hand -an attached or generated file into an already-open workspace: - -```bash -DEVSPACE_ARTIFACTS=1 npx @waishnav/devspace serve -``` - -This feature currently supports Linux. It is not registered on macOS, Windows, -or BSD because the secure publication path depends on traversable, -descriptor-anchored directory paths provided by Linux procfs. - -| Variable | Default | Purpose | -| --- | --- | --- | -| `DEVSPACE_ARTIFACTS` | `0` | Expose `download_artifact` for trusted native files. | -| `DEVSPACE_ARTIFACT_MAX_FILE_BYTES` | `104857600` | Maximum streamed size of one file (100 MiB). | - -The same settings may be persisted in `~/.devspace/config.json` as -`artifactsEnabled` and `artifactMaxFileBytes`. +## Complete example -`download_artifact` accepts the native file object supplied by the MCP connector, -a `workspaceId` returned by `open_workspace`, and a relative workspace `path`. -DevSpace safely creates missing parent directories, refuses to overwrite an -existing destination, and returns only the normalized workspace-relative path. -It does not accept conflict modes, expected hashes, arbitrary URL strings, local -paths, embedded credentials, or extra object fields. - -There is no artifact root, total quota, TTL, pinning, persistent database record, -or background artifact cleanup service. See [Native File Download](artifact-exchange.md) -for the supported connector shape and security boundaries. - -## OAuth - -DevSpace uses a single-user OAuth approval flow. - -| Variable | Default | -| --- | --- | -| `DEVSPACE_OAUTH_ACCESS_TOKEN_TTL_SECONDS` | `3600` | -| `DEVSPACE_OAUTH_REFRESH_TOKEN_TTL_SECONDS` | `2592000` | -| `DEVSPACE_OAUTH_SCOPES` | `devspace` | -| `DEVSPACE_OAUTH_ALLOWED_REDIRECT_HOSTS` | `chatgpt.com,localhost,127.0.0.1` | - -MCP clients discover metadata from: - -```text -/.well-known/oauth-protected-resource/mcp -/.well-known/oauth-authorization-server +```jsonc +{ + "$schema": "https://raw.githubusercontent.com/Waishnav/devspace/main/schema/v1/devspace.schema.json", + "configVersion": 1, + + "server": { + "host": "127.0.0.1", + "port": 7676, + // Use the public origin only; do not append /mcp. + "publicBaseUrl": "https://devspace.example.com", + "allowedHosts": [], + "trustProxy": false, + }, + "workspaces": { + "allowedRoots": ["~/personal", "~/work"], + "worktreeRoot": "~/.devspace/worktrees", + }, + "storage": { + "stateDir": "~/.local/share/devspace", + }, + "tools": { + "mode": "codex", + }, + "ui": { + "enabled": true, + }, + "artifacts": { + "enabled": false, + "maxFileBytes": 104857600, + }, + "skills": { + "enabled": true, + "paths": [], + "agentDir": "~/.codex", + }, + "subagents": { + "enabled": false, + "providers": [], + }, + "logging": { + "level": "info", + "format": "json", + "requests": true, + "assets": false, + "toolCalls": true, + "shellCommands": false, + }, + "oauth": { + "accessTokenTtlSeconds": 3600, + "refreshTokenTtlSeconds": 2592000, + "scopes": ["devspace"], + "allowedRedirectHosts": ["chatgpt.com", "localhost", "127.0.0.1"], + }, +} ``` -## Tool Modes - -`DEVSPACE_TOOL_MODE` controls the tool surface. - -| Value | Behavior | -| --- | --- | -| `minimal` | Default. Exposes `open_workspace`, `read`, `write`, `edit`, and `bash`. Clients use `bash` with tools such as `rg`, `find`, and `ls` for inspection. | -| `full` | Exposes the minimal tools plus dedicated `grep`, `glob`, and `ls` tools. | -| `codex` | Experimental. Exposes `open_workspace`, `read`, `apply_patch`, `exec_command`, and `write_stdin`. Existing mutation and shell tools are hidden. | - -`DEVSPACE_MINIMAL_TOOLS` remains a backward-compatible alias when -`DEVSPACE_TOOL_MODE` is unset: `1` selects `minimal` and `0` selects `full`. -The `codex` mode must be selected through `DEVSPACE_TOOL_MODE` and always uses -its fixed short tool names regardless of `DEVSPACE_TOOL_NAMING`. +Omitted sections and keys use the defaults shown above. An empty +`workspaces.allowedRoots` uses the current working directory. Unknown keys are +rejected so spelling mistakes cannot silently alter behavior. -Codex-mode commands run without a PTY by default. Set `tty: true` on -`exec_command` for interactive terminal programs. PTY support uses the optional -`node-pty` dependency; `write_stdin` can send input, poll output, and resize PTY -sessions. +## Tool modes and UI -## Widgets +`tools.mode` accepts two values: -`DEVSPACE_WIDGETS` controls ChatGPT Apps iframe usage. - -| Value | Behavior | -| --- | --- | -| `full` | Default. Widget UI is attached to exposed workspace, file, edit, and shell tools. | -| `changes` | Enables the aggregate `show_changes` tool and attaches widget UI to `open_workspace` and `show_changes`. | -| `off` | Disables widget UI. | - -## Skills - -| Variable | Purpose | +| Value | Tool surface | | --- | --- | -| `DEVSPACE_SKILLS` | Set to `0` to hide skills. Enabled by default. | -| `DEVSPACE_SUBAGENTS` | Optional master override for the persisted Subagents configuration. | -| `DEVSPACE_AGENT_DIR` | Defaults to `~/.codex`; its `skills` child is loaded for compatibility. | -| `DEVSPACE_SKILL_PATHS` | Optional comma-separated additional skill directories. | +| `codex` | Default. `open_workspace`, `read`, `apply_patch`, `exec_command`, `write_stdin`, and `show_changes`. | +| `claude` | `open_workspace`, `read`, `write`, `edit`, `bash`, and `show_changes`. | -DevSpace discovers standard Agent Skills from: +The dedicated MCP tools `grep`, `glob`, and `ls` are not exposed. Each mode uses +its shell tool with programs such as `rg`, `find`, and `ls` when it needs those +operations. -- `~/.agents/skills` -- project `.agents/skills` -- `~/.devspace/skills` +DevSpace attaches Apps UI metadata only to `open_workspace` and `show_changes`. +This avoids rendering an iframe for every read, edit, search, or command call. +Setting `ui.enabled` to `false` removes the metadata but does not remove the +`show_changes` tool. -It also keeps compatibility with: +## Skills and subagents -- the bundled `subagents` skill when Subagents are enabled, unless `~/.devspace/skills/subagents/SKILL.md` exists -- `DEVSPACE_AGENT_DIR/skills`, defaulting to `~/.codex/skills` -- additional paths from `DEVSPACE_SKILL_PATHS` +DevSpace discovers standard Agent Skills from `~/.agents/skills`, project +`.agents/skills`, and `~/.devspace/skills`. It also checks +`skills.agentDir/skills` and each path in `skills.paths`. Relative custom paths +are resolved from the active workspace. -When Subagents are enabled, DevSpace discovers agent profiles -from: +Subagent providers are explicit. Omitted providers are disabled: -- `~/.devspace/agents/*.md` -- project `.devspace/agents/*.md` - -Enable providers and set their defaults in `~/.devspace/config.json`: - -```json +```jsonc { + "configVersion": 1, "subagents": { "enabled": true, "providers": [ @@ -157,103 +114,104 @@ Enable providers and set their defaults in `~/.devspace/config.json`: "id": "codex", "enabled": true, "model": "gpt-5.4", - "effort": "high" + "effort": "high", }, { "id": "claude", "enabled": true, - "model": "sonnet" + "model": "sonnet", }, - { - "id": "grok", - "enabled": true, - "model": "grok-4.5", - "effort": "low" - } - ] - } + ], + }, } ``` -Each entry controls one provider. Providers omitted from the array are disabled. -`model` and `effort` are optional defaults; an invocation override wins over a -profile value, which wins over the provider default. The legacy boolean -`"subagents": true` remains readable and enables every provider, but new -configuration should use the explicit object form. - -`devspace agents targets` shows usable providers and profiles for the current -workspace. Add `--json` for a compact list of exact target names and their -selection metadata. Disabled, unavailable, and unconfigured providers are -omitted. Provider availability is runtime state and never rewrites the -configuration. - -Grok Build is discovered from the `grok` executable. Authenticate it with -`grok login` or `XAI_API_KEY`; DevSpace does not read or store Grok credentials. -Grok supports `grok-build` by default and validates explicit model and effort -values against the ACP session metadata when available. Set `GROK_COMMAND` when -the executable is not on the normal PATH. If your Grok installation selects a -custom agent profile, set `GROK_AGENT_PROFILE` to that profile's path; DevSpace -passes it to `grok agent stdio` without writing to Grok's configuration. - -`open_workspace` returns a compact catalog containing profile names, -descriptions, providers, and optional models/effort levels so the host model can choose an -agent without reading provider-specific launch details. Disabled or unavailable -providers and their profiles are omitted from this model-facing catalog. `devspace agents ls` -lists existing subagent sessions for the current workspace, scoped by the -workspace environment injected into shell commands. The `subagents` -skill teaches the model to use only the minimal `devspace agents ls`, -`devspace agents targets`, `devspace agents run`, `devspace agents continue`, -and `devspace agents show` workflow. - -For Codex, Claude Code, OpenCode, Pi, or another supported Coding Agent, use -the Skills CLI to install the same skill. DevSpace setup prints this command but -does not run it or write into agent skill directories: - -```bash -npx skills add Waishnav/devspace --skill subagents --global -``` +Profiles are loaded from `~/.devspace/agents/*.md` and project +`.devspace/agents/*.md`. `devspace agents targets` prints the configured targets +available in the current workspace. -Starter profile templates are available under `examples/agents/`. Copy or adapt -them into one of the active profile directories before use. +Provider executable discovery remains process-scoped. The supported overrides +are `CODEX_COMMAND`, `CODEX_HOME`, `CLAUDE_COMMAND`, `CURSOR_COMMAND`, +`COPILOT_COMMAND`, `GROK_COMMAND`, and `GROK_AGENT_PROFILE`. DevSpace does not +persist provider credentials. -Legacy project paths such as `.pi/skills` can be added through `DEVSPACE_SKILL_PATHS` when needed. +## Native artifact download -Example: +Set `artifacts.enabled` to `true` when a host needs to save a native attached or +generated file into an open workspace. `artifacts.maxFileBytes` limits one +streamed file. The secure publication path is currently available only on +Linux; the tool is not registered on macOS, Windows, or BSD. -```bash -DEVSPACE_SKILL_PATHS="$HOME/.claude/skills,$HOME/company/skills" \ -npx @waishnav/devspace serve -``` +## Environment boundary -## Logging +Only two user-facing DevSpace environment variables remain: -| Variable | Default | +| Variable | Purpose | | --- | --- | -| `DEVSPACE_LOG_LEVEL` | `info` | -| `DEVSPACE_LOG_FORMAT` | `json` | -| `DEVSPACE_LOG_REQUESTS` | `1` | -| `DEVSPACE_LOG_ASSETS` | `0` | -| `DEVSPACE_LOG_TOOL_CALLS` | `1` | -| `DEVSPACE_LOG_SHELL_COMMANDS` | `0` | -| `DEVSPACE_TRUST_PROXY` | `0` | - -Set `DEVSPACE_LOG_FORMAT=pretty` for local debugging. - -Set `DEVSPACE_LOG_SHELL_COMMANDS=1` only when you intentionally want command -previews in logs. - -## Env-Only Example - -```bash -DEVSPACE_OAUTH_OWNER_TOKEN="$(openssl rand -base64 32)" \ -DEVSPACE_ALLOWED_ROOTS="$HOME/personal,$HOME/work" \ -DEVSPACE_PUBLIC_BASE_URL="https://devspace.example.com" \ -DEVSPACE_WORKTREE_ROOT="$HOME/.devspace/worktrees" \ -DEVSPACE_ARTIFACTS="1" \ -DEVSPACE_TOOL_MODE="minimal" \ -DEVSPACE_WIDGETS="full" \ -npx @waishnav/devspace serve -``` +| `DEVSPACE_CONFIG_DIR` | Bootstrap location for `config.jsonc`, `auth.json`, skills, and profiles. | +| `DEVSPACE_OAUTH_OWNER_TOKEN` | Optional secret override for the owner token stored in `auth.json`. | + +Durable environment settings were removed in v1.1. Move existing deployment +values to these JSONC keys: -The environment assignments must be part of the same command invocation, or -exported first. +| Removed setting | JSONC key | +| --- | --- | +| `HOST`, `PORT` | `server.host`, `server.port` | +| `DEVSPACE_PUBLIC_BASE_URL` | `server.publicBaseUrl` | +| `DEVSPACE_ALLOWED_HOSTS` | `server.allowedHosts` | +| `DEVSPACE_TRUST_PROXY` | `server.trustProxy` | +| `DEVSPACE_ALLOWED_ROOTS` | `workspaces.allowedRoots` | +| `DEVSPACE_WORKTREE_ROOT` | `workspaces.worktreeRoot` | +| `DEVSPACE_STATE_DIR` | `storage.stateDir` | +| `DEVSPACE_TOOL_MODE`, `DEVSPACE_MINIMAL_TOOLS` | `tools.mode` | +| `DEVSPACE_WIDGETS` | `ui.enabled` | +| `DEVSPACE_ARTIFACTS` | `artifacts.enabled` | +| `DEVSPACE_ARTIFACT_MAX_FILE_BYTES` | `artifacts.maxFileBytes` | +| `DEVSPACE_SKILLS` | `skills.enabled` | +| `DEVSPACE_SKILL_PATHS` | `skills.paths` | +| `DEVSPACE_AGENT_DIR` | `skills.agentDir` | +| `DEVSPACE_SUBAGENTS` | `subagents.enabled` | +| `DEVSPACE_LOG_LEVEL` | `logging.level` | +| `DEVSPACE_LOG_FORMAT` | `logging.format` | +| `DEVSPACE_LOG_REQUESTS` | `logging.requests` | +| `DEVSPACE_LOG_ASSETS` | `logging.assets` | +| `DEVSPACE_LOG_TOOL_CALLS` | `logging.toolCalls` | +| `DEVSPACE_LOG_SHELL_COMMANDS` | `logging.shellCommands` | +| `DEVSPACE_OAUTH_ACCESS_TOKEN_TTL_SECONDS` | `oauth.accessTokenTtlSeconds` | +| `DEVSPACE_OAUTH_REFRESH_TOKEN_TTL_SECONDS` | `oauth.refreshTokenTtlSeconds` | +| `DEVSPACE_OAUTH_SCOPES` | `oauth.scopes` | +| `DEVSPACE_OAUTH_ALLOWED_REDIRECT_HOSTS` | `oauth.allowedRedirectHosts` | + +These environment values are not read or auto-imported in v1.1. Environment is +process state, so there is no reliable file DevSpace can migrate on the user's +behalf. + +## v1.0 file migration + +The first v1.1 load performs one migration when `config.jsonc` is missing and +`config.json` exists: + +1. Validate the old JSON document. +2. Translate its known fields into the versioned JSONC structure. +3. Write and validate a temporary `config.jsonc`. +4. Atomically publish it. +5. Rename the old file to `config.json.v1.0.bak`. + +If `config.jsonc` exists, DevSpace never reads `config.json`. Invalid JSONC also +never falls back to the old file. Unsupported legacy keys stop migration with an +actionable error instead of being silently discarded. + +The persisted fields map as follows: + +| v1.0 JSON field | v1.1 JSONC key | +| --- | --- | +| `host`, `port` | `server.host`, `server.port` | +| `publicBaseUrl`, `allowedHosts` | `server.publicBaseUrl`, `server.allowedHosts` | +| `allowedRoots`, `worktreeRoot` | `workspaces.allowedRoots`, `workspaces.worktreeRoot` | +| `stateDir` | `storage.stateDir` | +| `artifactsEnabled`, `artifactMaxFileBytes` | `artifacts.enabled`, `artifacts.maxFileBytes` | +| `agentDir` | `skills.agentDir` | +| `subagents` | `subagents` | +| `tools.mode`, `ui.enabled` | unchanged nested keys | + +`auth.json` is unchanged. diff --git a/docs/gotchas.md b/docs/gotchas.md index 495243bb5..3bf7dd2d8 100644 --- a/docs/gotchas.md +++ b/docs/gotchas.md @@ -65,14 +65,27 @@ If you saved the wrong value: npx @waishnav/devspace config set publicBaseUrl https://your-tunnel-host.example.com ``` +## Tailscale Funnel `/mcp` Returns 404 + +Proxy the whole DevSpace server from the Funnel root: + +```bash +tailscale funnel --bg 7676 +``` + +Do not use `--set-path=/mcp`. Tailscale removes a configured mount path before +proxying to the local service, so a public `/mcp` request can otherwise arrive +at DevSpace as `/`. DevSpace also needs OAuth routes outside `/mcp`, so serving +the whole local origin is the correct setup. + ## Tunnel URL Changed Temporary tunnels often change URLs between runs. -For a one-off run: +Update the configured URL: ```bash -DEVSPACE_PUBLIC_BASE_URL="https://new-tunnel.example.com" npx @waishnav/devspace serve +npx @waishnav/devspace config set publicBaseUrl https://new-tunnel.example.com ``` For a stable URL: @@ -94,11 +107,8 @@ npx @waishnav/devspace doctor Confirm the public URL hostname appears in allowed hosts. If you changed tunnel URLs, update `publicBaseUrl`. -Use this only for intentional local debugging: - -```bash -DEVSPACE_ALLOWED_HOSTS="*" npx @waishnav/devspace serve -``` +For intentional local debugging only, set `server.allowedHosts` to `["*"]` in +`~/.devspace/config.jsonc`. ## OAuth Redirect Host Rejected @@ -110,11 +120,8 @@ localhost 127.0.0.1 ``` -If another MCP client uses a different redirect host, configure: - -```bash -DEVSPACE_OAUTH_ALLOWED_REDIRECT_HOSTS="chatgpt.com,example.com" npx @waishnav/devspace serve -``` +If another MCP client uses a different redirect host, add it to +`oauth.allowedRedirectHosts` in `~/.devspace/config.jsonc`. ## Owner Password Not Accepted @@ -204,11 +211,8 @@ Confirm Bash is detected. ## Skills Do Not Appear -Skills are enabled by default. Check: - -```bash -DEVSPACE_SKILLS=1 npx @waishnav/devspace serve -``` +Skills are enabled by default. Confirm `skills.enabled` is `true` in +`~/.devspace/config.jsonc`. DevSpace looks in standard Agent Skills locations: @@ -219,8 +223,8 @@ DevSpace looks in standard Agent Skills locations: It also checks compatibility and custom paths: - the bundled `subagents` skill when Subagents are enabled, unless `~/.devspace/skills/subagents/SKILL.md` exists -- `DEVSPACE_AGENT_DIR/skills`, defaulting to `~/.codex/skills` -- additional paths from `DEVSPACE_SKILL_PATHS` +- `skills.agentDir/skills`, defaulting to `~/.codex/skills` +- additional paths from `skills.paths` When Subagents are enabled, DevSpace loads agent profiles from `~/.devspace/agents/*.md` and project `.devspace/agents/*.md`, then exposes a @@ -246,19 +250,22 @@ not copy files into agent skill directories. Packaged agent profile examples under `examples/agents/` are starter templates. Copy or adapt them into one of the active profile directories before use. -Legacy project paths such as `.pi/skills` can be added through `DEVSPACE_SKILL_PATHS` when needed. +Legacy project paths such as `.pi/skills` can be added to `skills.paths` when needed. If a skill appears in `open_workspace`, the model must read that skill's `SKILL.md` before reading other files inside the skill directory. ## Review Card Does Not Appear -Per-tool widget cards are enabled by default with: +DevSpace attaches widget UI only to `open_workspace` and `show_changes`. +Ordinary reads, edits, and commands intentionally render as normal tool results +to avoid one iframe per call. Plain MCP clients may ignore ChatGPT Apps widget +metadata and only show text results; `show_changes` remains available there. -```bash -DEVSPACE_WIDGETS=full -``` +If both cards are missing in ChatGPT, confirm that `ui.enabled` is not `false` +in `~/.devspace/config.jsonc` and reconnect the MCP server. -The aggregate `show_changes` tool is only exposed with -`DEVSPACE_WIDGETS=changes`. Plain MCP clients may ignore ChatGPT Apps widget -metadata and only show text results. +Historical `show_changes` cards use the `reviewRef` in their structured result +to recover the exact Git-backed review when a host reloads the app without its +original result metadata. `open_workspace` can rebuild its card directly from +its structured result. diff --git a/docs/security.md b/docs/security.md index d7ec0e1d6..69bbc1303 100644 --- a/docs/security.md +++ b/docs/security.md @@ -51,8 +51,8 @@ DEVSPACE_OAUTH_OWNER_TOKEN="$(openssl rand -base64 32)" ## Public URL And Host Allowlist -DevSpace needs `DEVSPACE_PUBLIC_BASE_URL` so MCP clients can discover OAuth -metadata and connect to the correct resource. +DevSpace needs `server.publicBaseUrl` in `config.jsonc` so MCP clients can +discover OAuth metadata and connect to the correct resource. The value should be the origin only: @@ -60,10 +60,10 @@ The value should be the origin only: https://your-tunnel-host.example.com ``` -Do not include `/mcp` in `DEVSPACE_PUBLIC_BASE_URL`. +Do not include `/mcp` in `server.publicBaseUrl`. By default, DevSpace derives allowed Host headers from the local host and public -URL. Use `DEVSPACE_ALLOWED_HOSTS=*` only for intentional local debugging. +URL. Put `"*"` in `server.allowedHosts` only for intentional local debugging. ## Tunnels @@ -112,7 +112,7 @@ execute transferred content. ## Logs By default, DevSpace logs requests and tool calls. Shell command previews are -disabled unless `DEVSPACE_LOG_SHELL_COMMANDS=1`. +disabled unless `logging.shellCommands` is `true`. Do not enable shell command logging if commands may contain secrets. diff --git a/docs/setup.md b/docs/setup.md index 934b0b8c8..83a66a6eb 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -56,7 +56,7 @@ remain limited to the roots configured for ChatGPT. Setup detects supported Coding Agents and asks which ones DevSpace may use. These choices are stored as provider objects under `subagents` in -`~/.devspace/config.json`. +`~/.devspace/config.jsonc`. If you selected Coding Agents, setup prints: @@ -77,6 +77,16 @@ reverse proxy first and point it at: http://127.0.0.1:7676 ``` +For Tailscale Funnel, proxy the whole DevSpace server from the root path: + +```bash +tailscale funnel --bg 7676 +``` + +Do not mount Funnel only at `/mcp` with `--set-path=/mcp`. DevSpace also serves +OAuth discovery and authorization routes outside `/mcp`, and a path mount can +strip `/mcp` before the request reaches DevSpace. + Enter the public origin without `/mcp`: ```text @@ -99,13 +109,7 @@ Run: npx @waishnav/devspace serve ``` -If your tunnel URL changes for one run, override it without rewriting config: - -```bash -DEVSPACE_PUBLIC_BASE_URL="https://new-tunnel.example.com" npx @waishnav/devspace serve -``` - -For a stable public URL, persist it: +If your tunnel URL changes, update the persisted value before starting: ```bash npx @waishnav/devspace config set publicBaseUrl https://devspace.example.com @@ -120,7 +124,7 @@ password approval page. Enter the Owner password printed during setup. The default config files are: ```text -~/.devspace/config.json +~/.devspace/config.jsonc ~/.devspace/auth.json ``` @@ -141,9 +145,12 @@ Git, Bash, public URL, allowed hosts, and SQLite native dependency status. If you are developing DevSpace itself instead of using the published package: +Local checkout development additionally requires pnpm 11.25.0, the version +pinned in `package.json`. Install it with `npm install --global pnpm@11.25.0`. + ```bash -npm install --include=dev -npm run dev +pnpm install --frozen-lockfile +pnpm dev ``` The same setup rules apply. diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index dd8546fca..000000000 --- a/package-lock.json +++ /dev/null @@ -1,6126 +0,0 @@ -{ - "name": "@waishnav/devspace", - "version": "1.0.8", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@waishnav/devspace", - "version": "1.0.8", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@agentclientprotocol/sdk": "^1.1.0", - "@anthropic-ai/claude-agent-sdk": "^0.3.200", - "@anthropic-ai/sandbox-runtime": "0.0.71", - "@clack/prompts": "^1.5.1", - "@earendil-works/pi-coding-agent": "^0.80.3", - "@modelcontextprotocol/ext-apps": "^1.7.2", - "@modelcontextprotocol/sdk": "^1.29.0", - "@opencode-ai/sdk": "^1.17.13", - "@pierre/diffs": "^1.2.5", - "better-result": "^2.10.0", - "better-sqlite3": "^12.10.0", - "cross-spawn": "^7.0.6", - "diff": "^8.0.3", - "drizzle-orm": "^0.45.2", - "express": "^5.2.1", - "lucide": "^1.24.0", - "react": "^19.2.6", - "react-dom": "^19.2.6", - "semver": "^7.8.4", - "yaml": "^2.9.0", - "zod": "^4.4.3" - }, - "bin": { - "devspace": "dist/cli.js", - "devspace-agentd": "dist/local-agent-daemon-main.js" - }, - "devDependencies": { - "@types/better-sqlite3": "^7.6.13", - "@types/express": "^5.0.6", - "@types/node": "^25.9.1", - "@types/react": "^19.2.15", - "@types/react-dom": "^19.2.3", - "@types/semver": "^7.7.1", - "@vitejs/plugin-react": "^6.0.2", - "tsx": "^4.22.3", - "typescript": "^6.0.3", - "vite": "^8.0.14" - }, - "engines": { - "node": ">=22.19 <27" - }, - "optionalDependencies": { - "node-pty": "^1.1.0" - } - }, - "node_modules/@agentclientprotocol/sdk": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.1.0.tgz", - "integrity": "sha512-NT2KqphUJ3w6EksUL51ZhJgIYgq/ZLGcBPkyMKgRSO5PMVwe9DnKKX+Htnvk6KHh6dUuh34UHK4gKp+4te1Mdg==", - "license": "Apache-2.0", - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - } - }, - "node_modules/@anthropic-ai/claude-agent-sdk": { - "version": "0.3.200", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.200.tgz", - "integrity": "sha512-o13TM3boFIJE4oZdQDFw5TQfiev1sBoxwzKM2QGj/NPtxriGTP0PKNAQsGZvTsiEOIIH5rzPr/H81xVkkAw23g==", - "license": "SEE LICENSE IN README.md", - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.200", - "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.200", - "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.200", - "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.200", - "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.200", - "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.200", - "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.200", - "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.200" - }, - "peerDependencies": { - "@anthropic-ai/sdk": ">=0.93.0", - "@modelcontextprotocol/sdk": "^1.29.0", - "zod": "^4.0.0" - } - }, - "node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": { - "version": "0.3.200", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.200.tgz", - "integrity": "sha512-8UzzInVdRPDNIOvrAxYbHHJD/u13WSBx9fvEeuZnsZ6rZh0qnSI1QwU8Due0V2+m+ZnT3cEonmXDvo2ee/icWg==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": { - "version": "0.3.200", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.200.tgz", - "integrity": "sha512-DCwlQoO8HWGuFElE+Q5pYkiBTalXjjMATRAxXyc94fI6m1ZRqyba66dOea+zTmzHPpOb6zSoHYNLiXy7EjNpcg==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": { - "version": "0.3.200", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.200.tgz", - "integrity": "sha512-NAEonp086ZOsf+3o/9Y5JRclO6C4n4ceiSuCpSDV6SSUOLBmCRi7r/PJOoMsIWwMshC6fnnkDKZamTpHjr75eg==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": { - "version": "0.3.200", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.200.tgz", - "integrity": "sha512-ak0l+zpz3dKPjnBegUhOs1Y5xFveEQ1AVqmq6s8Q7qd3vO4SrDPiUOpxRkjkqWyGD8r8w+ezG+unf3U9IZ6DRg==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": { - "version": "0.3.200", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.200.tgz", - "integrity": "sha512-0R/In8G4fZLFFEIA1SqXRRf9mzDGx7roHpMawNdTT1QlG4XftGTlKMxfukt/YcxwzsNPWg4hJSkEDxsb+3J6FA==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": { - "version": "0.3.200", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.200.tgz", - "integrity": "sha512-Sf5TTCO3bc5ty7FX5F19WT3xbtU+f1biYD9+dDJ7YHyYFWuiPlWcnCJ8El8RSwCTuvz3OexJLwCqGHRWOC3eBg==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": { - "version": "0.3.200", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.200.tgz", - "integrity": "sha512-iJx10bdrk3afa/Oq9QHRh2HaINT/xnsm5OrFNNLbix2CoOEY5lA7f0lk/s0OMiWnfXdv5vvtADpgZ5tvUoQykA==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": { - "version": "0.3.200", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.200.tgz", - "integrity": "sha512-Mka8YDpDIiSJcbrdoBhzX3S0n9DYcoYaEjS7lxwX3GyPi5PvXV4UBuXzj++7ieV/KS4w32Sm3mHQRpeVwnJZ0A==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@anthropic-ai/sandbox-runtime": { - "version": "0.0.71", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sandbox-runtime/-/sandbox-runtime-0.0.71.tgz", - "integrity": "sha512-/ZMCavpMElD0ku2BlA95vezKUsVN0DD/wVd3WIEAfFjkTF2nsmzQA+MhejIWhuSUS9HpxMtTj57eFL+kdbKZ/A==", - "license": "Apache-2.0", - "dependencies": { - "@pondwader/socks5-server": "^1.0.10", - "commander": "^12.1.0", - "node-forge": "^1.4.0", - "zod": "^3.24.1" - }, - "bin": { - "srt": "dist/cli.js" - }, - "engines": { - "node": ">=20.11.0" - } - }, - "node_modules/@anthropic-ai/sandbox-runtime/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@anthropic-ai/sdk": { - "version": "0.110.0", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.110.0.tgz", - "integrity": "sha512-hOP4bNYXDFHDxxiEgzlILXrxZIYCDnhe8sry0RDRKD/QnsEpvZcQpablCdm9X/WuD/YgOiSIkkqsL1mLLlTqJw==", - "license": "MIT", - "peer": true, - "dependencies": { - "json-schema-to-ts": "^3.1.1", - "standardwebhooks": "^1.0.0" - }, - "bin": { - "anthropic-ai-sdk": "bin/cli" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/@babel/runtime": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", - "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@clack/core": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.4.1.tgz", - "integrity": "sha512-FILJa1gGKEFTGZAJE9RpVhrjKz3c3h4ar60dSv6cGuDqufQ84YEIS3GAGvZiN+H6yaLbbvTFNejjCC4tXpZEuw==", - "license": "MIT", - "dependencies": { - "fast-wrap-ansi": "^0.2.0", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 20.12.0" - } - }, - "node_modules/@clack/prompts": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.5.1.tgz", - "integrity": "sha512-zccHj2z2oCCO4yrDiRSlFOxWerGqRiysP7a5jPK6uoI9URKAquwY42Dd/iUP8JWHxEzdRe4TlbvZCo8z1/mhrw==", - "license": "MIT", - "dependencies": { - "@clack/core": "1.4.1", - "fast-string-width": "^3.0.2", - "fast-wrap-ansi": "^0.2.0", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 20.12.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.80.3.tgz", - "integrity": "sha512-TIggw9gCXpA+Ph7OjdTA7ka2NPwTVuPmy39KDSyUzaKq8VvHfMGR7vtRz4JB7Um/RMRblmzhu4p9tUCk6MTgGA==", - "hasShrinkwrap": true, - "license": "MIT", - "dependencies": { - "@earendil-works/pi-agent-core": "^0.80.3", - "@earendil-works/pi-ai": "^0.80.3", - "@earendil-works/pi-tui": "^0.80.3", - "@silvia-odwyer/photon-node": "0.3.4", - "chalk": "5.6.2", - "cross-spawn": "7.0.6", - "diff": "8.0.4", - "glob": "13.0.6", - "highlight.js": "10.7.3", - "hosted-git-info": "9.0.3", - "ignore": "7.0.5", - "jiti": "2.7.0", - "minimatch": "10.2.5", - "proper-lockfile": "4.1.2", - "semver": "7.8.0", - "typebox": "1.1.38", - "undici": "8.5.0", - "yaml": "2.9.0" - }, - "bin": { - "pi": "dist/cli.js" - }, - "engines": { - "node": ">=22.19.0" - }, - "optionalDependencies": { - "@mariozechner/clipboard": "0.3.9" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@anthropic-ai/sdk": { - "version": "0.91.1", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", - "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", - "license": "MIT", - "dependencies": { - "json-schema-to-ts": "^3.1.1" - }, - "bin": { - "anthropic-ai-sdk": "bin/cli" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/crc32": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", - "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/client-bedrock-runtime": { - "version": "3.1048.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1048.0.tgz", - "integrity": "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/credential-provider-node": "^3.972.42", - "@aws-sdk/eventstream-handler-node": "^3.972.16", - "@aws-sdk/middleware-eventstream": "^3.972.12", - "@aws-sdk/middleware-websocket": "^3.972.19", - "@aws-sdk/token-providers": "3.1048.0", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/node-http-handler": "^4.7.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/core": { - "version": "3.974.11", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.11.tgz", - "integrity": "sha512-QpnINq5FZH6EOaDEkmHdT7eUunbvD27pDNQypaWjFyYz7Zl1q3UCMQErBZxpmfGfI7MvI2TlK8KTkgNpv8b1ug==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/xml-builder": "^3.972.24", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/core": "^3.24.2", - "@smithy/signature-v4": "^5.4.2", - "@smithy/types": "^4.14.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.37", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.37.tgz", - "integrity": "sha512-/jpPvEh6f7ntmIzf7dNxoNX6Q8vt8UpesCjbW6mFfk4V1NW6bIy9qxcQ6WbA8As5yQhsZOe+xeNd4xHX8kdY2Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.39", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.39.tgz", - "integrity": "sha512-pIgTpisWyWg7X1bUbzSjuUYosYTD0Ghz2M0hkSTmb3a6i3qV3uU+NYJPI/E2XSC0HcsZh5rsLPzeXrkb2DS0Cg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/node-http-handler": "^4.7.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.41.tgz", - "integrity": "sha512-u2tyjaxJJzW8UtW4SM1ZcPMDwO6y+kV+llvou+Adts0FAKyzes5jG4izQN+KX3yE8ZROpS5y1LJ//xL2iSf76w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/credential-provider-env": "^3.972.37", - "@aws-sdk/credential-provider-http": "^3.972.39", - "@aws-sdk/credential-provider-login": "^3.972.41", - "@aws-sdk/credential-provider-process": "^3.972.37", - "@aws-sdk/credential-provider-sso": "^3.972.41", - "@aws-sdk/credential-provider-web-identity": "^3.972.41", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/credential-provider-imds": "^4.3.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.41.tgz", - "integrity": "sha512-0LBitxXiAiaE5nlFPfpNIww/8FRY/I7WIndWsc9GmNFOM7cE1wNpVNQEGEk9Outg5l8xl+3vybxFyUy4l9q/LQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.42", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.42.tgz", - "integrity": "sha512-D4oon2zbqqsWOJUM99Gm3/ZyJ0IJvTXVN3PyloGb3kQEyI36fjCZheZj422lAgTWWd6TSHgiImLt3RIaLdv3dQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.37", - "@aws-sdk/credential-provider-http": "^3.972.39", - "@aws-sdk/credential-provider-ini": "^3.972.41", - "@aws-sdk/credential-provider-process": "^3.972.37", - "@aws-sdk/credential-provider-sso": "^3.972.41", - "@aws-sdk/credential-provider-web-identity": "^3.972.41", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/credential-provider-imds": "^4.3.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.37", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.37.tgz", - "integrity": "sha512-7nVaHBUaWIddASYfVaA9O4D5ZVjewU3sCol9WqZPGfW0nR+0WqE0xHZnD/U2L33PlOB8KNXGKZ6wOES/QijKzg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.41.tgz", - "integrity": "sha512-IOWAWEHe5LkjSKkkUUX9ciV6Y1scHTsnfEkdt5yyC4Slrc7AGbkLPrpntjqh18ksJAMOaVhoBsO8p2WyTcY2wQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/token-providers": "3.1048.0", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.41.tgz", - "integrity": "sha512-mbACk9Yypa8nm4iGZLs0PofOXEcTDOUw6wDnsPXNDNSd2WNXs1tSo+6nc/fh0jLYdfVZThhBL98PHW4aXFsG5A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/eventstream-handler-node": { - "version": "3.972.16", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.16.tgz", - "integrity": "sha512-yedpPgKftqjU5SlPFHfqWpOw6xSCRieWRG1euWOlXn4WJxt2VX92VprCa2PpSOXjVCAeK6dTjW9eJRXVig9yGA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-eventstream": { - "version": "3.972.12", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.12.tgz", - "integrity": "sha512-tHTHHCHNrq6XklQvlzHBDJG4Iuhh7NVPRdtmvP+nHFA+5sxPlIDzlAHHgfoYHGvT3NXP1yVP/L5c3opUn6T3Qg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-websocket": { - "version": "3.972.19", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.19.tgz", - "integrity": "sha512-mkEhOGYozqKQkbFaVrjwr0faiwwZza1v5/jSY6Tucm3bD+uKTazIUH/4Yo6aMnQD2ua2W9cMP6s8mvwTcjtqHw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/signature-v4": "^5.4.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/nested-clients": { - "version": "3.997.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.9.tgz", - "integrity": "sha512-jPR3rnmRI4hWYyzfmTGBr7NblMp8QYYeflHXba1H6+7CGrWVqWKQzaXFQ4qbExqPRsXN3T3L3JxFhr6aouXUGQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/signature-v4-multi-region": "^3.996.27", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/node-http-handler": "^4.7.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.27", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.27.tgz", - "integrity": "sha512-0Phbz4t6HI3D3skxvG2uI+VWU034/nSIw1T8d+FPzzQG9EQTrw94o9mOKO2Gv3n3Oc8P7JD7RAUxkoneLWv5Eg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/signature-v4": "^5.4.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/token-providers": { - "version": "3.1048.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1048.0.tgz", - "integrity": "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/types": { - "version": "3.973.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.8.tgz", - "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", - "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/xml-builder": { - "version": "3.972.24", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.24.tgz", - "integrity": "sha512-V8z5YcDPfsvzrBlj0xR1vhRtocblhYbqdreCJB/voGd4Sr5zjNAeWxexbnqVtskTJe0vFb5KMqbSL++ePl+zRw==", - "license": "Apache-2.0", - "dependencies": { - "@nodable/entities": "2.1.0", - "@smithy/types": "^4.14.1", - "fast-xml-parser": "5.7.3", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws/lambda-invoke-store": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", - "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-agent-core": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.3.tgz", - "license": "MIT", - "dependencies": { - "@earendil-works/pi-ai": "^0.80.3", - "ignore": "7.0.5", - "typebox": "1.1.38", - "yaml": "2.9.0" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.3.tgz", - "license": "MIT", - "dependencies": { - "@anthropic-ai/sdk": "0.91.1", - "@aws-sdk/client-bedrock-runtime": "3.1048.0", - "@google/genai": "1.52.0", - "@mistralai/mistralai": "2.2.6", - "@opentelemetry/api": "1.9.0", - "@smithy/node-http-handler": "4.7.3", - "http-proxy-agent": "7.0.2", - "https-proxy-agent": "7.0.6", - "openai": "6.26.0", - "partial-json": "0.1.7", - "typebox": "1.1.38" - }, - "bin": { - "pi-ai": "dist/cli.js" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.3.tgz", - "license": "MIT", - "dependencies": { - "get-east-asian-width": "1.6.0", - "marked": "18.0.5" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@google/genai": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", - "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "google-auth-library": "^10.3.0", - "p-retry": "^4.6.2", - "protobufjs": "^7.5.4", - "ws": "^8.18.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.25.2" - }, - "peerDependenciesMeta": { - "@modelcontextprotocol/sdk": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.9.tgz", - "integrity": "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@mariozechner/clipboard-darwin-arm64": "0.3.9", - "@mariozechner/clipboard-darwin-universal": "0.3.9", - "@mariozechner/clipboard-darwin-x64": "0.3.9", - "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", - "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", - "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", - "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", - "@mariozechner/clipboard-linux-x64-musl": "0.3.9", - "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", - "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-arm64": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.9.tgz", - "integrity": "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-universal": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.9.tgz", - "integrity": "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ==", - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-x64": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.9.tgz", - "integrity": "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-gnu": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.9.tgz", - "integrity": "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-musl": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.9.tgz", - "integrity": "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.9.tgz", - "integrity": "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-gnu": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.9.tgz", - "integrity": "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-musl": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.9.tgz", - "integrity": "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-arm64-msvc": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.9.tgz", - "integrity": "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-x64-msvc": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.9.tgz", - "integrity": "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mistralai/mistralai": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.6.tgz", - "integrity": "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.40.0", - "ws": "^8.18.0", - "zod": "^3.25.0 || ^4.0.0", - "zod-to-json-schema": "^3.25.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.9.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@nodable/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/nodable" - } - ], - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.41.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", - "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/codegen": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "license": "BSD-3-Clause" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/eventemitter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", - "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", - "license": "BSD-3-Clause" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/fetch": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", - "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/float": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/utf8": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", - "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", - "license": "BSD-3-Clause" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@silvia-odwyer/photon-node": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", - "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", - "license": "Apache-2.0" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/core": { - "version": "3.24.3", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.3.tgz", - "integrity": "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/credential-provider-imds": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.3.tgz", - "integrity": "sha512-I2Bti0DKFo2IJyN28ijCsx51BAumEYR4/1yZ1FXyBygy9MqbnMqCev4JPth/MbpRfBSRAX35hITSnAdJRo1u5w==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/fetch-http-handler": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.3.tgz", - "integrity": "sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/node-http-handler": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.3.tgz", - "integrity": "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/signature-v4": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.3.tgz", - "integrity": "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/types": { - "version": "4.14.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz", - "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@types/node": { - "version": "22.19.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", - "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/bignumber.js": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/bowser": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", - "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/diff": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", - "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-xml-builder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", - "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "path-expression-matcher": "^1.5.0", - "xml-naming": "^0.1.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-xml-parser": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", - "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "@nodable/entities": "^2.1.0", - "fast-xml-builder": "^1.1.7", - "path-expression-matcher": "^1.5.0", - "strnum": "^2.2.3" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/gaxios": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", - "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", - "license": "Apache-2.0", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/gcp-metadata": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", - "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", - "license": "Apache-2.0", - "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/google-auth-library": { - "version": "10.6.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", - "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", - "license": "Apache-2.0", - "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^7.1.4", - "gcp-metadata": "8.1.2", - "google-logging-utils": "1.1.3", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/google-logging-utils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", - "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/hosted-git-info": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", - "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", - "license": "ISC", - "dependencies": { - "lru-cache": "^11.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "license": "MIT", - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/json-schema-to-ts": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", - "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "ts-algebra": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "license": "MIT", - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/lru-cache": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.4.0.tgz", - "integrity": "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/marked": { - "version": "18.0.5", - "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", - "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/openai": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", - "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", - "license": "Apache-2.0", - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "license": "MIT", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry/node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/partial-json": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", - "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/path-expression-matcher": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", - "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/proper-lockfile/node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/protobufjs": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", - "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "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" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/strnum": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", - "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ts-algebra": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", - "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/typebox": { - "version": "1.1.38", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", - "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/undici": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-8.5.0.tgz", - "integrity": "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==", - "license": "MIT", - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "license": "MIT" - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/xml-naming": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", - "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@hono/node-server": { - "version": "1.19.14", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, - "node_modules/@modelcontextprotocol/ext-apps": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.7.2.tgz", - "integrity": "sha512-OOWKDxdAjYDcgHkmzVzccyyag3FK+jBWPaWu4WvTxFsU4R/cgOX4eep66zPRA5n4v6WfxUNibPyvX4iJ7egYTg==", - "license": "MIT", - "workspaces": [ - "examples/*" - ], - "dependencies": { - "@standard-schema/spec": "^1.1.0" - }, - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.29.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", - "zod": "^3.25.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", - "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", - "license": "MIT", - "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" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", - "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@opencode-ai/sdk": { - "version": "1.17.13", - "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.17.13.tgz", - "integrity": "sha512-VItOGjMzRQx3zypwmeFLNhCiIx32kxS7FqzIJvVZLfyNGCifs3rfGC9qzNKWcxQo4SjNvAw++v4gWWU6Inv+JQ==", - "license": "MIT", - "dependencies": { - "cross-spawn": "7.0.6" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.133.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@pierre/diffs": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@pierre/diffs/-/diffs-1.2.5.tgz", - "integrity": "sha512-uYOz3Kfs5ED0qY0VraUXzylsEKvZPTVdexboM3QKPx/qBZmTT9F3lKAFuPpY5aIrV04sdHtoFCKStyzEu99U2A==", - "license": "apache-2.0", - "dependencies": { - "@pierre/theme": "1.0.3", - "@shikijs/transformers": "^3.0.0", - "diff": "8.0.3", - "hast-util-to-html": "9.0.5", - "lru_map": "0.4.1", - "shiki": "^3.0.0" - }, - "peerDependencies": { - "react": "^18.3.1 || ^19.0.0", - "react-dom": "^18.3.1 || ^19.0.0" - } - }, - "node_modules/@pierre/theme": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@pierre/theme/-/theme-1.0.3.tgz", - "integrity": "sha512-sWHv11TMoqKxKDgTIk5VbhQjdPhs8DCcBxbjh3mRlS3YOM/OcrWoGX6MM8eBGn9cUu3M46Py0JnxsG2nJaFTuA==", - "license": "MIT", - "engines": { - "vscode": "^1.0.0" - } - }, - "node_modules/@pondwader/socks5-server": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@pondwader/socks5-server/-/socks5-server-1.0.10.tgz", - "integrity": "sha512-bQY06wzzR8D2+vVCUoBsr5QS2U6UgPUQRmErNwtsuI6vLcyRKkafjkr3KxbtGFf9aBBIV2mcvlsKD1UYaIV+sg==", - "license": "MIT" - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", - "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@shikijs/core": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.23.0.tgz", - "integrity": "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.23.0", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.5" - } - }, - "node_modules/@shikijs/engine-javascript": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.23.0.tgz", - "integrity": "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.23.0", - "@shikijs/vscode-textmate": "^10.0.2", - "oniguruma-to-es": "^4.3.4" - } - }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", - "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.23.0", - "@shikijs/vscode-textmate": "^10.0.2" - } - }, - "node_modules/@shikijs/langs": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", - "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.23.0" - } - }, - "node_modules/@shikijs/themes": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", - "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.23.0" - } - }, - "node_modules/@shikijs/transformers": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-3.23.0.tgz", - "integrity": "sha512-F9msZVxdF+krQNSdQ4V+Ja5QemeAoTQ2jxt7nJCwhDsdF1JWS3KxIQXA3lQbyKwS3J61oHRUSv4jYWv3CkaKTQ==", - "license": "MIT", - "dependencies": { - "@shikijs/core": "3.23.0", - "@shikijs/types": "3.23.0" - } - }, - "node_modules/@shikijs/types": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", - "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", - "license": "MIT", - "dependencies": { - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, - "node_modules/@shikijs/vscode-textmate": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", - "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", - "license": "MIT" - }, - "node_modules/@stablelib/base64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", - "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", - "license": "MIT", - "peer": true - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/better-sqlite3": { - "version": "7.6.13", - "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", - "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/express": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", - "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^2" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", - "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/node": { - "version": "25.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", - "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" - } - }, - "node_modules/@types/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "19.2.15", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", - "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/@types/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*" - } - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", - "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", - "license": "ISC" - }, - "node_modules/@vitejs/plugin-react": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", - "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rolldown/pluginutils": "^1.0.0" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", - "babel-plugin-react-compiler": "^1.0.0", - "vite": "^8.0.0" - }, - "peerDependenciesMeta": { - "@rolldown/plugin-babel": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - } - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/better-result": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.10.0.tgz", - "integrity": "sha512-oQhh0y1qo2/ZKdAAEvHZAqKKiHOFU5k/bW96fE2ScgQOVkJRiHwB+nOS1SgFsYqRlxMDWvefXi9Q3px7QvgNDw==", - "license": "MIT" - }, - "node_modules/better-sqlite3": { - "version": "12.10.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.10.0.tgz", - "integrity": "sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "bindings": "^1.5.0", - "prebuild-install": "^7.1.1" - }, - "engines": { - "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/diff": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", - "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/drizzle-orm": { - "version": "0.45.2", - "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.2.tgz", - "integrity": "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==", - "license": "Apache-2.0", - "peerDependencies": { - "@aws-sdk/client-rds-data": ">=3", - "@cloudflare/workers-types": ">=4", - "@electric-sql/pglite": ">=0.2.0", - "@libsql/client": ">=0.10.0", - "@libsql/client-wasm": ">=0.10.0", - "@neondatabase/serverless": ">=0.10.0", - "@op-engineering/op-sqlite": ">=2", - "@opentelemetry/api": "^1.4.1", - "@planetscale/database": ">=1.13", - "@prisma/client": "*", - "@tidbcloud/serverless": "*", - "@types/better-sqlite3": "*", - "@types/pg": "*", - "@types/sql.js": "*", - "@upstash/redis": ">=1.34.7", - "@vercel/postgres": ">=0.8.0", - "@xata.io/client": "*", - "better-sqlite3": ">=7", - "bun-types": "*", - "expo-sqlite": ">=14.0.0", - "gel": ">=2", - "knex": "*", - "kysely": "*", - "mysql2": ">=2", - "pg": ">=8", - "postgres": ">=3", - "sql.js": ">=1", - "sqlite3": ">=5" - }, - "peerDependenciesMeta": { - "@aws-sdk/client-rds-data": { - "optional": true - }, - "@cloudflare/workers-types": { - "optional": true - }, - "@electric-sql/pglite": { - "optional": true - }, - "@libsql/client": { - "optional": true - }, - "@libsql/client-wasm": { - "optional": true - }, - "@neondatabase/serverless": { - "optional": true - }, - "@op-engineering/op-sqlite": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@planetscale/database": { - "optional": true - }, - "@prisma/client": { - "optional": true - }, - "@tidbcloud/serverless": { - "optional": true - }, - "@types/better-sqlite3": { - "optional": true - }, - "@types/pg": { - "optional": true - }, - "@types/sql.js": { - "optional": true - }, - "@upstash/redis": { - "optional": true - }, - "@vercel/postgres": { - "optional": true - }, - "@xata.io/client": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "bun-types": { - "optional": true - }, - "expo-sqlite": { - "optional": true - }, - "gel": { - "optional": true - }, - "knex": { - "optional": true - }, - "kysely": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "pg": { - "optional": true - }, - "postgres": { - "optional": true - }, - "prisma": { - "optional": true - }, - "sql.js": { - "optional": true - }, - "sqlite3": { - "optional": true - } - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", - "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "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" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", - "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", - "license": "MIT", - "dependencies": { - "ip-address": "^10.2.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-sha256": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", - "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", - "license": "Unlicense", - "peer": true - }, - "node_modules/fast-string-truncated-width": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", - "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", - "license": "MIT" - }, - "node_modules/fast-string-width": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", - "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", - "license": "MIT", - "dependencies": { - "fast-string-truncated-width": "^3.0.2" - } - }, - "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fast-wrap-ansi": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", - "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", - "license": "MIT", - "dependencies": { - "fast-string-width": "^3.0.2" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hast-util-to-html": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", - "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hono": { - "version": "4.12.25", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.25.tgz", - "integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==", - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/jose": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", - "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/json-schema-to-ts": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", - "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.18.3", - "ts-algebra": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "license": "BSD-2-Clause" - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lru_map": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz", - "integrity": "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==", - "license": "MIT" - }, - "node_modules/lucide": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/lucide/-/lucide-1.24.0.tgz", - "integrity": "sha512-oMAaeuNDc5VCnBb3IjwKYGRT56tqanUm1fyDFT5Tl8hWSZND59gztgjvXje08jKLPVAq0gHJcwZUE8GCQxzBeg==", - "license": "ISC" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", - "license": "MIT", - "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" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-abi": { - "version": "3.92.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", - "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT", - "optional": true - }, - "node_modules/node-forge": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", - "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-pty": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0.tgz", - "integrity": "sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-addon-api": "^7.1.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/oniguruma-parser": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", - "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", - "license": "MIT" - }, - "node_modules/oniguruma-to-es": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", - "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", - "license": "MIT", - "dependencies": { - "oniguruma-parser": "^0.12.2", - "regex": "^6.1.0", - "regex-recursion": "^6.0.2" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkce-challenge": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.12", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/react": { - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", - "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", - "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.6" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", - "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-recursion": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", - "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-utilities": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", - "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", - "license": "MIT" - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rolldown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.133.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.3", - "@rolldown/binding-darwin-arm64": "1.0.3", - "@rolldown/binding-darwin-x64": "1.0.3", - "@rolldown/binding-freebsd-x64": "1.0.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", - "@rolldown/binding-linux-arm64-gnu": "1.0.3", - "@rolldown/binding-linux-arm64-musl": "1.0.3", - "@rolldown/binding-linux-ppc64-gnu": "1.0.3", - "@rolldown/binding-linux-s390x-gnu": "1.0.3", - "@rolldown/binding-linux-x64-gnu": "1.0.3", - "@rolldown/binding-linux-x64-musl": "1.0.3", - "@rolldown/binding-openharmony-arm64": "1.0.3", - "@rolldown/binding-wasm32-wasi": "1.0.3", - "@rolldown/binding-win32-arm64-msvc": "1.0.3", - "@rolldown/binding-win32-x64-msvc": "1.0.3" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/shiki": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.23.0.tgz", - "integrity": "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==", - "license": "MIT", - "dependencies": { - "@shikijs/core": "3.23.0", - "@shikijs/engine-javascript": "3.23.0", - "@shikijs/engine-oniguruma": "3.23.0", - "@shikijs/langs": "3.23.0", - "@shikijs/themes": "3.23.0", - "@shikijs/types": "3.23.0", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "license": "MIT" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/standardwebhooks": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", - "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@stablelib/base64": "^1.0.0", - "fast-sha256": "^1.3.0" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/ts-algebra": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", - "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", - "license": "MIT", - "peer": true - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/tsx": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", - "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.28.0" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", - "license": "MIT", - "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/unist-util-is": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vite": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", - "tinyglobby": "^0.2.17" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", - "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" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrappy": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - } - } -} diff --git a/package.json b/package.json index a633a9130..1835ba80d 100644 --- a/package.json +++ b/package.json @@ -7,14 +7,17 @@ "engines": { "node": ">=22.19 <27" }, + "packageManager": "pnpm@11.25.0", "bin": { - "devspace": "dist/cli.js", - "devspace-agentd": "dist/local-agent-daemon-main.js" + "devspace": "bin/devspace.js", + "devspace-agentd": "bin/devspace-agentd.js" }, "files": [ + "bin", "dist", "docs", "examples", + "schema", "scripts", "skills", "README.md" @@ -24,12 +27,15 @@ }, "scripts": { "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"", - "build": "npm run clean && npm run build:app && tsc -p tsconfig.build.json", + "build": "pnpm clean && pnpm build:app && tsc -p tsconfig.build.json", "build:app": "vite build", - "dev": "node scripts/dev-server.mjs", + "dev": "tsx watch --clear-screen=false src/cli.ts serve", "postinstall": "node scripts/fix-node-pty-permissions.mjs", + "prepack": "pnpm build", + "schema:config": "tsx scripts/generate-config-schema.ts", "start": "node dist/cli.js serve", - "test": "tsx src/config.test.ts && tsx src/onboarding.test.ts && tsx src/cli-workspace.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-config.test.ts && tsx src/local-agent-catalog.test.ts && tsx src/local-agent-presentation.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-daemon-lifecycle.test.ts && tsx src/local-agent-daemon-protocol.test.ts && tsx src/local-agent-daemon.test.ts && tsx src/local-agent-codex.test.ts && tsx src/local-agent-opencode.test.ts && tsx src/local-agent-acp.test.ts && tsx src/local-agent-grok.test.ts && tsx src/local-agent-pi-sandbox.test.ts && tsx src/local-agent-pi.test.ts && tsx src/local-agent-claude.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/local-agent-manager.test.ts && tsx src/roots.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts", + "test": "tsx --test --test-concurrency=1 \"src/**/*.test.ts\"", + "test:package-install": "tsx --test --test-concurrency=1 \"test/package-install-smoke.test.ts\"", "typecheck": "tsc -p tsconfig.json --noEmit" }, "keywords": [], @@ -37,20 +43,21 @@ "license": "MIT", "dependencies": { "@agentclientprotocol/sdk": "^1.1.0", - "@anthropic-ai/claude-agent-sdk": "^0.3.200", + "@anthropic-ai/claude-agent-sdk": "0.3.200", "@anthropic-ai/sandbox-runtime": "0.0.71", "@clack/prompts": "^1.5.1", "@earendil-works/pi-coding-agent": "^0.80.3", "@modelcontextprotocol/ext-apps": "^1.7.2", "@modelcontextprotocol/sdk": "^1.29.0", "@opencode-ai/sdk": "^1.17.13", - "@pierre/diffs": "^1.2.5", + "@pierre/diffs": "^1.3.6", "better-result": "^2.10.0", "better-sqlite3": "^12.10.0", "cross-spawn": "^7.0.6", "diff": "^8.0.3", "drizzle-orm": "^0.45.2", "express": "^5.2.1", + "jsonc-parser": "^3.3.1", "lucide": "^1.24.0", "react": "^19.2.6", "react-dom": "^19.2.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 000000000..ff4ab0a76 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,3992 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@agentclientprotocol/sdk': + specifier: ^1.1.0 + version: 1.1.0(zod@4.4.3) + '@anthropic-ai/claude-agent-sdk': + specifier: 0.3.200 + version: 0.3.200(@anthropic-ai/sdk@0.110.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3) + '@anthropic-ai/sandbox-runtime': + specifier: 0.0.71 + version: 0.0.71 + '@clack/prompts': + specifier: ^1.5.1 + version: 1.5.1 + '@earendil-works/pi-coding-agent': + specifier: ^0.80.3 + version: 0.80.3(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) + '@modelcontextprotocol/ext-apps': + specifier: ^1.7.2 + version: 1.7.2(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(zod@4.4.3) + '@modelcontextprotocol/sdk': + specifier: ^1.29.0 + version: 1.29.0(zod@4.4.3) + '@opencode-ai/sdk': + specifier: ^1.17.13 + version: 1.17.13 + '@pierre/diffs': + specifier: ^1.3.6 + version: 1.3.6(@shikijs/themes@3.23.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + better-result: + specifier: ^2.10.0 + version: 2.10.0 + better-sqlite3: + specifier: ^12.10.0 + version: 12.10.0 + cross-spawn: + specifier: ^7.0.6 + version: 7.0.6 + diff: + specifier: ^8.0.3 + version: 8.0.3 + drizzle-orm: + specifier: ^0.45.2 + version: 0.45.2(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0) + express: + specifier: ^5.2.1 + version: 5.2.1 + jsonc-parser: + specifier: ^3.3.1 + version: 3.3.1 + lucide: + specifier: ^1.24.0 + version: 1.24.0 + react: + specifier: ^19.2.6 + version: 19.2.6 + react-dom: + specifier: ^19.2.6 + version: 19.2.6(react@19.2.6) + semver: + specifier: ^7.8.4 + version: 7.8.4 + yaml: + specifier: ^2.9.0 + version: 2.9.0 + zod: + specifier: ^4.4.3 + version: 4.4.3 + devDependencies: + '@types/better-sqlite3': + specifier: ^7.6.13 + version: 7.6.13 + '@types/express': + specifier: ^5.0.6 + version: 5.0.6 + '@types/node': + specifier: ^25.9.1 + version: 25.9.1 + '@types/react': + specifier: ^19.2.15 + version: 19.2.15 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.15) + '@types/semver': + specifier: ^7.7.1 + version: 7.7.1 + '@vitejs/plugin-react': + specifier: ^6.0.2 + version: 6.0.2(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.3)(yaml@2.9.0)) + tsx: + specifier: ^4.22.3 + version: 4.22.3 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + vite: + specifier: ^8.0.14 + version: 8.0.16(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.3)(yaml@2.9.0) + optionalDependencies: + node-pty: + specifier: ^1.1.0 + version: 1.1.0 + +packages: + + '@agentclientprotocol/sdk@1.1.0': + resolution: {integrity: sha512-NT2KqphUJ3w6EksUL51ZhJgIYgq/ZLGcBPkyMKgRSO5PMVwe9DnKKX+Htnvk6KHh6dUuh34UHK4gKp+4te1Mdg==} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.200': + resolution: {integrity: sha512-8UzzInVdRPDNIOvrAxYbHHJD/u13WSBx9fvEeuZnsZ6rZh0qnSI1QwU8Due0V2+m+ZnT3cEonmXDvo2ee/icWg==} + cpu: [arm64] + os: [darwin] + + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.200': + resolution: {integrity: sha512-DCwlQoO8HWGuFElE+Q5pYkiBTalXjjMATRAxXyc94fI6m1ZRqyba66dOea+zTmzHPpOb6zSoHYNLiXy7EjNpcg==} + cpu: [x64] + os: [darwin] + + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.200': + resolution: {integrity: sha512-ak0l+zpz3dKPjnBegUhOs1Y5xFveEQ1AVqmq6s8Q7qd3vO4SrDPiUOpxRkjkqWyGD8r8w+ezG+unf3U9IZ6DRg==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.200': + resolution: {integrity: sha512-NAEonp086ZOsf+3o/9Y5JRclO6C4n4ceiSuCpSDV6SSUOLBmCRi7r/PJOoMsIWwMshC6fnnkDKZamTpHjr75eg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.200': + resolution: {integrity: sha512-Sf5TTCO3bc5ty7FX5F19WT3xbtU+f1biYD9+dDJ7YHyYFWuiPlWcnCJ8El8RSwCTuvz3OexJLwCqGHRWOC3eBg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.200': + resolution: {integrity: sha512-0R/In8G4fZLFFEIA1SqXRRf9mzDGx7roHpMawNdTT1QlG4XftGTlKMxfukt/YcxwzsNPWg4hJSkEDxsb+3J6FA==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.200': + resolution: {integrity: sha512-iJx10bdrk3afa/Oq9QHRh2HaINT/xnsm5OrFNNLbix2CoOEY5lA7f0lk/s0OMiWnfXdv5vvtADpgZ5tvUoQykA==} + cpu: [arm64] + os: [win32] + + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.200': + resolution: {integrity: sha512-Mka8YDpDIiSJcbrdoBhzX3S0n9DYcoYaEjS7lxwX3GyPi5PvXV4UBuXzj++7ieV/KS4w32Sm3mHQRpeVwnJZ0A==} + cpu: [x64] + os: [win32] + + '@anthropic-ai/claude-agent-sdk@0.3.200': + resolution: {integrity: sha512-o13TM3boFIJE4oZdQDFw5TQfiev1sBoxwzKM2QGj/NPtxriGTP0PKNAQsGZvTsiEOIIH5rzPr/H81xVkkAw23g==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@anthropic-ai/sdk': '>=0.93.0' + '@modelcontextprotocol/sdk': ^1.29.0 + zod: ^4.0.0 + + '@anthropic-ai/sandbox-runtime@0.0.71': + resolution: {integrity: sha512-/ZMCavpMElD0ku2BlA95vezKUsVN0DD/wVd3WIEAfFjkTF2nsmzQA+MhejIWhuSUS9HpxMtTj57eFL+kdbKZ/A==} + engines: {node: '>=20.11.0'} + hasBin: true + + '@anthropic-ai/sdk@0.110.0': + resolution: {integrity: sha512-hOP4bNYXDFHDxxiEgzlILXrxZIYCDnhe8sry0RDRKD/QnsEpvZcQpablCdm9X/WuD/YgOiSIkkqsL1mLLlTqJw==} + hasBin: true + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@anthropic-ai/sdk@0.91.1': + resolution: {integrity: sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==} + hasBin: true + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-bedrock-runtime@3.1048.0': + resolution: {integrity: sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.974.11': + resolution: {integrity: sha512-QpnINq5FZH6EOaDEkmHdT7eUunbvD27pDNQypaWjFyYz7Zl1q3UCMQErBZxpmfGfI7MvI2TlK8KTkgNpv8b1ug==} + engines: {node: '>=20.0.0'} + deprecated: Deprecated due to an error deserialization bug in JSON 1.0 protocol services, see https://github.com/aws/aws-sdk-js-v3/pull/8031. Newer version available. + + '@aws-sdk/credential-provider-env@3.972.37': + resolution: {integrity: sha512-/jpPvEh6f7ntmIzf7dNxoNX6Q8vt8UpesCjbW6mFfk4V1NW6bIy9qxcQ6WbA8As5yQhsZOe+xeNd4xHX8kdY2Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.39': + resolution: {integrity: sha512-pIgTpisWyWg7X1bUbzSjuUYosYTD0Ghz2M0hkSTmb3a6i3qV3uU+NYJPI/E2XSC0HcsZh5rsLPzeXrkb2DS0Cg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.972.41': + resolution: {integrity: sha512-u2tyjaxJJzW8UtW4SM1ZcPMDwO6y+kV+llvou+Adts0FAKyzes5jG4izQN+KX3yE8ZROpS5y1LJ//xL2iSf76w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.41': + resolution: {integrity: sha512-0LBitxXiAiaE5nlFPfpNIww/8FRY/I7WIndWsc9GmNFOM7cE1wNpVNQEGEk9Outg5l8xl+3vybxFyUy4l9q/LQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.42': + resolution: {integrity: sha512-D4oon2zbqqsWOJUM99Gm3/ZyJ0IJvTXVN3PyloGb3kQEyI36fjCZheZj422lAgTWWd6TSHgiImLt3RIaLdv3dQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.37': + resolution: {integrity: sha512-7nVaHBUaWIddASYfVaA9O4D5ZVjewU3sCol9WqZPGfW0nR+0WqE0xHZnD/U2L33PlOB8KNXGKZ6wOES/QijKzg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.972.41': + resolution: {integrity: sha512-IOWAWEHe5LkjSKkkUUX9ciV6Y1scHTsnfEkdt5yyC4Slrc7AGbkLPrpntjqh18ksJAMOaVhoBsO8p2WyTcY2wQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.41': + resolution: {integrity: sha512-mbACk9Yypa8nm4iGZLs0PofOXEcTDOUw6wDnsPXNDNSd2WNXs1tSo+6nc/fh0jLYdfVZThhBL98PHW4aXFsG5A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/eventstream-handler-node@3.972.16': + resolution: {integrity: sha512-yedpPgKftqjU5SlPFHfqWpOw6xSCRieWRG1euWOlXn4WJxt2VX92VprCa2PpSOXjVCAeK6dTjW9eJRXVig9yGA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-eventstream@3.972.12': + resolution: {integrity: sha512-tHTHHCHNrq6XklQvlzHBDJG4Iuhh7NVPRdtmvP+nHFA+5sxPlIDzlAHHgfoYHGvT3NXP1yVP/L5c3opUn6T3Qg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-websocket@3.972.19': + resolution: {integrity: sha512-mkEhOGYozqKQkbFaVrjwr0faiwwZza1v5/jSY6Tucm3bD+uKTazIUH/4Yo6aMnQD2ua2W9cMP6s8mvwTcjtqHw==} + engines: {node: '>= 14.0.0'} + + '@aws-sdk/nested-clients@3.997.9': + resolution: {integrity: sha512-jPR3rnmRI4hWYyzfmTGBr7NblMp8QYYeflHXba1H6+7CGrWVqWKQzaXFQ4qbExqPRsXN3T3L3JxFhr6aouXUGQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.27': + resolution: {integrity: sha512-0Phbz4t6HI3D3skxvG2uI+VWU034/nSIw1T8d+FPzzQG9EQTrw94o9mOKO2Gv3n3Oc8P7JD7RAUxkoneLWv5Eg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1048.0': + resolution: {integrity: sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.973.8': + resolution: {integrity: sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-locate-window@3.965.5': + resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/xml-builder@3.972.24': + resolution: {integrity: sha512-V8z5YcDPfsvzrBlj0xR1vhRtocblhYbqdreCJB/voGd4Sr5zjNAeWxexbnqVtskTJe0vFb5KMqbSL++ePl+zRw==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.2.4': + resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + engines: {node: '>=18.0.0'} + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@clack/core@1.4.1': + resolution: {integrity: sha512-FILJa1gGKEFTGZAJE9RpVhrjKz3c3h4ar60dSv6cGuDqufQ84YEIS3GAGvZiN+H6yaLbbvTFNejjCC4tXpZEuw==} + engines: {node: '>= 20.12.0'} + + '@clack/prompts@1.5.1': + resolution: {integrity: sha512-zccHj2z2oCCO4yrDiRSlFOxWerGqRiysP7a5jPK6uoI9URKAquwY42Dd/iUP8JWHxEzdRe4TlbvZCo8z1/mhrw==} + engines: {node: '>= 20.12.0'} + + '@earendil-works/pi-agent-core@0.80.3': + resolution: {integrity: sha512-3qw0/GeRQBU/nlGjDe5Yb7ePKTmoxefx2YxyKMFAviFUMXpFexBG/hS7mBtwFahFvzrrTPPoRT6sFIDjwoDWPQ==} + engines: {node: '>=22.19.0'} + + '@earendil-works/pi-ai@0.80.3': + resolution: {integrity: sha512-jPZLMeGL5kkMSEAwAklfXTMHqZvfhsJtCCpKGIr5Duk7mc0n4skjB1dugk7y0z3z8ZHIUCmPAWHdyDqgUz5vdA==} + engines: {node: '>=22.19.0'} + hasBin: true + + '@earendil-works/pi-coding-agent@0.80.3': + resolution: {integrity: sha512-TIggw9gCXpA+Ph7OjdTA7ka2NPwTVuPmy39KDSyUzaKq8VvHfMGR7vtRz4JB7Um/RMRblmzhu4p9tUCk6MTgGA==} + engines: {node: '>=22.19.0'} + hasBin: true + + '@earendil-works/pi-tui@0.80.3': + resolution: {integrity: sha512-2BJI6qwRQfnM0Q7seL1+SbacU/jRRjBnN7Hu3n9BjAn7/s5FaBNnvdD1qBQYRsFTHfjqMaDsjYqanPyqwXj99w==} + engines: {node: '>=22.19.0'} + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@google/genai@1.52.0': + resolution: {integrity: sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@modelcontextprotocol/sdk': ^1.25.2 + peerDependenciesMeta: + '@modelcontextprotocol/sdk': + optional: true + + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + + '@mariozechner/clipboard-darwin-arm64@0.3.9': + resolution: {integrity: sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@mariozechner/clipboard-darwin-universal@0.3.9': + resolution: {integrity: sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ==} + engines: {node: '>= 10'} + os: [darwin] + + '@mariozechner/clipboard-darwin-x64@0.3.9': + resolution: {integrity: sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@mariozechner/clipboard-linux-arm64-gnu@0.3.9': + resolution: {integrity: sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@mariozechner/clipboard-linux-arm64-musl@0.3.9': + resolution: {integrity: sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@mariozechner/clipboard-linux-riscv64-gnu@0.3.9': + resolution: {integrity: sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@mariozechner/clipboard-linux-x64-gnu@0.3.9': + resolution: {integrity: sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@mariozechner/clipboard-linux-x64-musl@0.3.9': + resolution: {integrity: sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@mariozechner/clipboard-win32-arm64-msvc@0.3.9': + resolution: {integrity: sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@mariozechner/clipboard-win32-x64-msvc@0.3.9': + resolution: {integrity: sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@mariozechner/clipboard@0.3.9': + resolution: {integrity: sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==} + engines: {node: '>= 10'} + + '@mistralai/mistralai@2.2.6': + resolution: {integrity: sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + + '@modelcontextprotocol/ext-apps@1.7.2': + resolution: {integrity: sha512-OOWKDxdAjYDcgHkmzVzccyyag3FK+jBWPaWu4WvTxFsU4R/cgOX4eep66zPRA5n4v6WfxUNibPyvX4iJ7egYTg==} + engines: {node: '>=20'} + peerDependencies: + '@modelcontextprotocol/sdk': ^1.29.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + + '@napi-rs/wasm-runtime@1.1.5': + resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@nodable/entities@2.1.0': + resolution: {integrity: sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==} + + '@opencode-ai/sdk@1.17.13': + resolution: {integrity: sha512-VItOGjMzRQx3zypwmeFLNhCiIx32kxS7FqzIJvVZLfyNGCifs3rfGC9qzNKWcxQo4SjNvAw++v4gWWU6Inv+JQ==} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/semantic-conventions@1.41.1': + resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} + engines: {node: '>=14'} + + '@oxc-project/types@0.133.0': + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + + '@pierre/diffs@1.3.6': + resolution: {integrity: sha512-a3woaW2QHy78JDxPJK0OJzwZUN4xoQLLIS/pceO8X6+L8gA5D682mP7/w3YxxEVRPXOaoe/p/RJ5Oj/3nrEzew==} + peerDependencies: + react: ^18.3.1 || ^19.0.0 + react-dom: ^18.3.1 || ^19.0.0 + + '@pierre/theme@2.0.0': + resolution: {integrity: sha512-yNDd9GYLQl1mEUJR8AneJ5e4ohLIHQd/wZLWr4fagt78vS2RwwZNW530vVgHqXFAyFVcFlRmGUD5ramXH46OXw==} + engines: {vscode: ^1.0.0} + + '@pierre/theming@1.0.1': + resolution: {integrity: sha512-WCI5Qd7iprDpISL9fBYOLe8RV53+b7mFNA3bPzl60/2CKCSrsKN8zEcep6Y3BAzvARlmca50zGjDodqPGiTUKA==} + peerDependencies: + '@pierre/theme': ^1.1.0 || ^2.0.0 + '@shikijs/themes': ^3.0.0 || ^4.0.0 + react: ^18.3.1 || ^19.0.0 + react-dom: ^18.3.1 || ^19.0.0 + shiki: ^3.0.0 || ^4.0.0 + peerDependenciesMeta: + '@pierre/theme': + optional: true + '@shikijs/themes': + optional: true + react: + optional: true + react-dom: + optional: true + shiki: + optional: true + + '@pondwader/socks5-server@1.0.10': + resolution: {integrity: sha512-bQY06wzzR8D2+vVCUoBsr5QS2U6UgPUQRmErNwtsuI6vLcyRKkafjkr3KxbtGFf9aBBIV2mcvlsKD1UYaIV+sg==} + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} + + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.1': + resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + + '@rolldown/binding-android-arm64@1.0.3': + resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.3': + resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.3': + resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.3': + resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.3': + resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.3': + resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.3': + resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.3': + resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.3': + resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.3': + resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@shikijs/core@3.23.0': + resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==} + + '@shikijs/engine-javascript@3.23.0': + resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==} + + '@shikijs/engine-oniguruma@3.23.0': + resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} + + '@shikijs/langs@3.23.0': + resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} + + '@shikijs/themes@3.23.0': + resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} + + '@shikijs/transformers@3.23.0': + resolution: {integrity: sha512-F9msZVxdF+krQNSdQ4V+Ja5QemeAoTQ2jxt7nJCwhDsdF1JWS3KxIQXA3lQbyKwS3J61oHRUSv4jYWv3CkaKTQ==} + + '@shikijs/types@3.23.0': + resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@silvia-odwyer/photon-node@0.3.4': + resolution: {integrity: sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==} + + '@smithy/core@3.24.3': + resolution: {integrity: sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.3.3': + resolution: {integrity: sha512-I2Bti0DKFo2IJyN28ijCsx51BAumEYR4/1yZ1FXyBygy9MqbnMqCev4JPth/MbpRfBSRAX35hITSnAdJRo1u5w==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.4.3': + resolution: {integrity: sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/node-http-handler@4.7.3': + resolution: {integrity: sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.4.3': + resolution: {integrity: sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.14.2': + resolution: {integrity: sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@stablelib/base64@1.0.1': + resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/better-sqlite3@7.6.13': + resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} + + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/node@25.9.1': + resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} + + '@types/qs@6.15.1': + resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.15': + resolution: {integrity: sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==} + + '@types/retry@0.12.0': + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + + '@types/semver@7.7.1': + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} + + '@vitejs/plugin-react@6.0.2': + resolution: {integrity: sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + better-result@2.10.0: + resolution: {integrity: sha512-oQhh0y1qo2/ZKdAAEvHZAqKKiHOFU5k/bW96fE2ScgQOVkJRiHwB+nOS1SgFsYqRlxMDWvefXi9Q3px7QvgNDw==} + + better-sqlite3@12.10.0: + resolution: {integrity: sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==} + engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x} + + bignumber.js@9.3.1: + resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + diff@8.0.3: + resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} + engines: {node: '>=0.3.1'} + + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + + diff@9.0.0: + resolution: {integrity: sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==} + engines: {node: '>=0.3.1'} + + drizzle-orm@0.45.2: + resolution: {integrity: sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=4' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1.13' + '@prisma/client': '*' + '@tidbcloud/serverless': '*' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/sql.js': '*' + '@upstash/redis': '>=1.34.7' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + better-sqlite3: '>=7' + bun-types: '*' + expo-sqlite: '>=14.0.0' + gel: '>=2' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + prisma: '*' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@libsql/client-wasm': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@prisma/client': + optional: true + '@tidbcloud/serverless': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@upstash/redis': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + gel: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + prisma: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventsource-parser@3.1.0: + resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + express-rate-limit@8.5.2: + resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-sha256@1.3.0: + resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} + + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + + fast-xml-builder@1.2.0: + resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==} + + fast-xml-parser@5.7.3: + resolution: {integrity: sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==} + hasBin: true + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gaxios@7.1.4: + resolution: {integrity: sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==} + engines: {node: '>=18'} + + gcp-metadata@8.1.2: + resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} + engines: {node: '>=18'} + + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + + google-auth-library@10.6.2: + resolution: {integrity: sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==} + engines: {node: '>=18'} + + google-logging-utils@1.1.3: + resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} + engines: {node: '>=14'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + hono@4.12.25: + resolution: {integrity: sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==} + engines: {node: '>=16.9.0'} + + hosted-git-info@9.0.3: + resolution: {integrity: sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==} + engines: {node: ^20.17.0 || >=22.9.0} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + + json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + + json-schema-to-ts@3.1.1: + resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} + engines: {node: '>=16'} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + lru-cache@11.4.0: + resolution: {integrity: sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA==} + engines: {node: 20 || >=22} + + lru_map@0.4.1: + resolution: {integrity: sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==} + + lucide@1.24.0: + resolution: {integrity: sha512-oMAaeuNDc5VCnBb3IjwKYGRT56tqanUm1fyDFT5Tl8hWSZND59gztgjvXje08jKLPVAq0gHJcwZUE8GCQxzBeg==} + + marked@18.0.5: + resolution: {integrity: sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==} + engines: {node: '>= 20'} + hasBin: true + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + node-abi@3.92.0: + resolution: {integrity: sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==} + engines: {node: '>=10'} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + node-forge@1.4.0: + resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} + engines: {node: '>= 6.13.0'} + + node-pty@1.1.0: + resolution: {integrity: sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + oniguruma-parser@0.12.2: + resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} + + oniguruma-to-es@4.3.6: + resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} + + openai@6.26.0: + resolution: {integrity: sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==} + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + + p-retry@4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + partial-json@0.1.7: + resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==} + + path-expression-matcher@1.5.0: + resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} + engines: {node: '>=14.0.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. + hasBin: true + + proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + protobufjs@7.6.4: + resolution: {integrity: sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==} + engines: {node: '>=12.0.0'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + qs@6.15.2: + resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} + engines: {node: '>=0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + react-dom@19.2.6: + resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} + peerDependencies: + react: ^19.2.6 + + react@19.2.6: + resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} + engines: {node: '>=0.10.0'} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + + rolldown@1.0.3: + resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.8.4: + resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shiki@3.23.0: + resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + standardwebhooks@1.0.0: + resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + strnum@2.3.0: + resolution: {integrity: sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==} + + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + ts-algebra@2.0.0: + resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.22.3: + resolution: {integrity: sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==} + engines: {node: '>=18.0.0'} + hasBin: true + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + + typebox@1.1.38: + resolution: {integrity: sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + undici@8.5.0: + resolution: {integrity: sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==} + engines: {node: '>=22.19.0'} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@8.0.16: + resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.18 + 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 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-naming@0.1.0: + resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==} + engines: {node: '>=16.0.0'} + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@agentclientprotocol/sdk@1.1.0(zod@4.4.3)': + dependencies: + zod: 4.4.3 + + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.200': + optional: true + + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.200': + optional: true + + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.200': + optional: true + + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.200': + optional: true + + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.200': + optional: true + + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.200': + optional: true + + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.200': + optional: true + + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.200': + optional: true + + '@anthropic-ai/claude-agent-sdk@0.3.200(@anthropic-ai/sdk@0.110.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)': + dependencies: + '@anthropic-ai/sdk': 0.110.0(zod@4.4.3) + '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) + zod: 4.4.3 + optionalDependencies: + '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.200 + '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.200 + '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.200 + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.200 + '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.200 + '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.200 + '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.200 + '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.200 + + '@anthropic-ai/sandbox-runtime@0.0.71': + dependencies: + '@pondwader/socks5-server': 1.0.10 + commander: 12.1.0 + node-forge: 1.4.0 + zod: 3.25.76 + + '@anthropic-ai/sdk@0.110.0(zod@4.4.3)': + dependencies: + json-schema-to-ts: 3.1.1 + standardwebhooks: 1.0.0 + optionalDependencies: + zod: 4.4.3 + + '@anthropic-ai/sdk@0.91.1(zod@4.4.3)': + dependencies: + json-schema-to-ts: 3.1.1 + optionalDependencies: + zod: 4.4.3 + + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.8 + tslib: 2.8.1 + + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.8 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-bedrock-runtime@3.1048.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.11 + '@aws-sdk/credential-provider-node': 3.972.42 + '@aws-sdk/eventstream-handler-node': 3.972.16 + '@aws-sdk/middleware-eventstream': 3.972.12 + '@aws-sdk/middleware-websocket': 3.972.19 + '@aws-sdk/token-providers': 3.1048.0 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/fetch-http-handler': 5.4.3 + '@smithy/node-http-handler': 4.7.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/core@3.974.11': + dependencies: + '@aws-sdk/types': 3.973.8 + '@aws-sdk/xml-builder': 3.972.24 + '@aws/lambda-invoke-store': 0.2.4 + '@smithy/core': 3.24.3 + '@smithy/signature-v4': 5.4.3 + '@smithy/types': 4.14.2 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.37': + dependencies: + '@aws-sdk/core': 3.974.11 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.39': + dependencies: + '@aws-sdk/core': 3.974.11 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/fetch-http-handler': 5.4.3 + '@smithy/node-http-handler': 4.7.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.972.41': + dependencies: + '@aws-sdk/core': 3.974.11 + '@aws-sdk/credential-provider-env': 3.972.37 + '@aws-sdk/credential-provider-http': 3.972.39 + '@aws-sdk/credential-provider-login': 3.972.41 + '@aws-sdk/credential-provider-process': 3.972.37 + '@aws-sdk/credential-provider-sso': 3.972.41 + '@aws-sdk/credential-provider-web-identity': 3.972.41 + '@aws-sdk/nested-clients': 3.997.9 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/credential-provider-imds': 4.3.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-login@3.972.41': + dependencies: + '@aws-sdk/core': 3.974.11 + '@aws-sdk/nested-clients': 3.997.9 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-node@3.972.42': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.37 + '@aws-sdk/credential-provider-http': 3.972.39 + '@aws-sdk/credential-provider-ini': 3.972.41 + '@aws-sdk/credential-provider-process': 3.972.37 + '@aws-sdk/credential-provider-sso': 3.972.41 + '@aws-sdk/credential-provider-web-identity': 3.972.41 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/credential-provider-imds': 4.3.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-process@3.972.37': + dependencies: + '@aws-sdk/core': 3.974.11 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.972.41': + dependencies: + '@aws-sdk/core': 3.974.11 + '@aws-sdk/nested-clients': 3.997.9 + '@aws-sdk/token-providers': 3.1048.0 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-web-identity@3.972.41': + dependencies: + '@aws-sdk/core': 3.974.11 + '@aws-sdk/nested-clients': 3.997.9 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/eventstream-handler-node@3.972.16': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-eventstream@3.972.12': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-websocket@3.972.19': + dependencies: + '@aws-sdk/core': 3.974.11 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/fetch-http-handler': 5.4.3 + '@smithy/signature-v4': 5.4.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.997.9': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.11 + '@aws-sdk/signature-v4-multi-region': 3.996.27 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/fetch-http-handler': 5.4.3 + '@smithy/node-http-handler': 4.7.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.996.27': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/signature-v4': 5.4.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1048.0': + dependencies: + '@aws-sdk/core': 3.974.11 + '@aws-sdk/nested-clients': 3.997.9 + '@aws-sdk/types': 3.973.8 + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/types@3.973.8': + dependencies: + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.965.5': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.24': + dependencies: + '@nodable/entities': 2.1.0 + '@smithy/types': 4.14.2 + fast-xml-parser: 5.7.3 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.4': {} + + '@babel/runtime@7.29.7': {} + + '@clack/core@1.4.1': + dependencies: + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@clack/prompts@1.5.1': + dependencies: + '@clack/core': 1.4.1 + fast-string-width: 3.0.2 + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@earendil-works/pi-agent-core@0.80.3(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': + dependencies: + '@earendil-works/pi-ai': 0.80.3(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) + ignore: 7.0.5 + typebox: 1.1.38 + yaml: 2.9.0 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-ai@0.80.3(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': + dependencies: + '@anthropic-ai/sdk': 0.91.1(zod@4.4.3) + '@aws-sdk/client-bedrock-runtime': 3.1048.0 + '@google/genai': 1.52.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)) + '@mistralai/mistralai': 2.2.6(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.0 + '@smithy/node-http-handler': 4.7.3 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + openai: 6.26.0(ws@8.21.0)(zod@4.4.3) + partial-json: 0.1.7 + typebox: 1.1.38 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-coding-agent@0.80.3(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': + dependencies: + '@earendil-works/pi-agent-core': 0.80.3(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) + '@earendil-works/pi-ai': 0.80.3(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) + '@earendil-works/pi-tui': 0.80.3 + '@silvia-odwyer/photon-node': 0.3.4 + chalk: 5.6.2 + cross-spawn: 7.0.6 + diff: 8.0.4 + glob: 13.0.6 + highlight.js: 10.7.3 + hosted-git-info: 9.0.3 + ignore: 7.0.5 + jiti: 2.7.0 + minimatch: 10.2.5 + proper-lockfile: 4.1.2 + semver: 7.8.0 + typebox: 1.1.38 + undici: 8.5.0 + yaml: 2.9.0 + optionalDependencies: + '@mariozechner/clipboard': 0.3.9 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-tui@0.80.3': + dependencies: + get-east-asian-width: 1.6.0 + marked: 18.0.5 + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.28.1': + optional: true + + '@esbuild/android-arm64@0.28.1': + optional: true + + '@esbuild/android-arm@0.28.1': + optional: true + + '@esbuild/android-x64@0.28.1': + optional: true + + '@esbuild/darwin-arm64@0.28.1': + optional: true + + '@esbuild/darwin-x64@0.28.1': + optional: true + + '@esbuild/freebsd-arm64@0.28.1': + optional: true + + '@esbuild/freebsd-x64@0.28.1': + optional: true + + '@esbuild/linux-arm64@0.28.1': + optional: true + + '@esbuild/linux-arm@0.28.1': + optional: true + + '@esbuild/linux-ia32@0.28.1': + optional: true + + '@esbuild/linux-loong64@0.28.1': + optional: true + + '@esbuild/linux-mips64el@0.28.1': + optional: true + + '@esbuild/linux-ppc64@0.28.1': + optional: true + + '@esbuild/linux-riscv64@0.28.1': + optional: true + + '@esbuild/linux-s390x@0.28.1': + optional: true + + '@esbuild/linux-x64@0.28.1': + optional: true + + '@esbuild/netbsd-arm64@0.28.1': + optional: true + + '@esbuild/netbsd-x64@0.28.1': + optional: true + + '@esbuild/openbsd-arm64@0.28.1': + optional: true + + '@esbuild/openbsd-x64@0.28.1': + optional: true + + '@esbuild/openharmony-arm64@0.28.1': + optional: true + + '@esbuild/sunos-x64@0.28.1': + optional: true + + '@esbuild/win32-arm64@0.28.1': + optional: true + + '@esbuild/win32-ia32@0.28.1': + optional: true + + '@esbuild/win32-x64@0.28.1': + optional: true + + '@google/genai@1.52.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))': + dependencies: + google-auth-library: 10.6.2 + p-retry: 4.6.2 + protobufjs: 7.6.4 + ws: 8.21.0 + optionalDependencies: + '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@hono/node-server@1.19.14(hono@4.12.25)': + dependencies: + hono: 4.12.25 + + '@mariozechner/clipboard-darwin-arm64@0.3.9': + optional: true + + '@mariozechner/clipboard-darwin-universal@0.3.9': + optional: true + + '@mariozechner/clipboard-darwin-x64@0.3.9': + optional: true + + '@mariozechner/clipboard-linux-arm64-gnu@0.3.9': + optional: true + + '@mariozechner/clipboard-linux-arm64-musl@0.3.9': + optional: true + + '@mariozechner/clipboard-linux-riscv64-gnu@0.3.9': + optional: true + + '@mariozechner/clipboard-linux-x64-gnu@0.3.9': + optional: true + + '@mariozechner/clipboard-linux-x64-musl@0.3.9': + optional: true + + '@mariozechner/clipboard-win32-arm64-msvc@0.3.9': + optional: true + + '@mariozechner/clipboard-win32-x64-msvc@0.3.9': + optional: true + + '@mariozechner/clipboard@0.3.9': + optionalDependencies: + '@mariozechner/clipboard-darwin-arm64': 0.3.9 + '@mariozechner/clipboard-darwin-universal': 0.3.9 + '@mariozechner/clipboard-darwin-x64': 0.3.9 + '@mariozechner/clipboard-linux-arm64-gnu': 0.3.9 + '@mariozechner/clipboard-linux-arm64-musl': 0.3.9 + '@mariozechner/clipboard-linux-riscv64-gnu': 0.3.9 + '@mariozechner/clipboard-linux-x64-gnu': 0.3.9 + '@mariozechner/clipboard-linux-x64-musl': 0.3.9 + '@mariozechner/clipboard-win32-arm64-msvc': 0.3.9 + '@mariozechner/clipboard-win32-x64-msvc': 0.3.9 + optional: true + + '@mistralai/mistralai@2.2.6(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/semantic-conventions': 1.41.1 + ws: 8.21.0 + zod: 4.4.3 + zod-to-json-schema: 3.25.2(zod@4.4.3) + optionalDependencies: + '@opentelemetry/api': 1.9.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@modelcontextprotocol/ext-apps@1.7.2(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(zod@4.4.3)': + dependencies: + '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) + '@standard-schema/spec': 1.1.0 + zod: 4.4.3 + optionalDependencies: + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + + '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.25) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.0 + express: 5.2.1 + express-rate-limit: 8.5.2(express@5.2.1) + hono: 4.12.25 + jose: 6.2.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.4.3 + zod-to-json-schema: 3.25.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@nodable/entities@2.1.0': {} + + '@opencode-ai/sdk@1.17.13': + dependencies: + cross-spawn: 7.0.6 + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/semantic-conventions@1.41.1': {} + + '@oxc-project/types@0.133.0': {} + + '@pierre/diffs@1.3.6(@shikijs/themes@3.23.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@pierre/theme': 2.0.0 + '@pierre/theming': 1.0.1(@pierre/theme@2.0.0)(@shikijs/themes@3.23.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(shiki@3.23.0) + '@shikijs/transformers': 3.23.0 + diff: 9.0.0 + hast-util-to-html: 9.0.5 + lru_map: 0.4.1 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + shiki: 3.23.0 + transitivePeerDependencies: + - '@shikijs/themes' + + '@pierre/theme@2.0.0': {} + + '@pierre/theming@1.0.1(@pierre/theme@2.0.0)(@shikijs/themes@3.23.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(shiki@3.23.0)': + optionalDependencies: + '@pierre/theme': 2.0.0 + '@shikijs/themes': 3.23.0 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + shiki: 3.23.0 + + '@pondwader/socks5-server@1.0.10': {} + + '@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': + dependencies: + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.1': {} + + '@rolldown/binding-android-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-x64@1.0.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.3': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@shikijs/core@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.6 + + '@shikijs/engine-oniguruma@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/themes@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/transformers@3.23.0': + dependencies: + '@shikijs/core': 3.23.0 + '@shikijs/types': 3.23.0 + + '@shikijs/types@3.23.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@silvia-odwyer/photon-node@0.3.4': {} + + '@smithy/core@3.24.3': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.3.3': + dependencies: + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.4.3': + dependencies: + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/node-http-handler@4.7.3': + dependencies: + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@smithy/signature-v4@5.4.3': + dependencies: + '@smithy/core': 3.24.3 + '@smithy/types': 4.14.2 + tslib: 2.8.1 + + '@smithy/types@4.14.2': + dependencies: + tslib: 2.8.1 + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + + '@stablelib/base64@1.0.1': {} + + '@standard-schema/spec@1.1.0': {} + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/better-sqlite3@7.6.13': + dependencies: + '@types/node': 25.9.1 + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 25.9.1 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 25.9.1 + + '@types/express-serve-static-core@5.1.1': + dependencies: + '@types/node': 25.9.1 + '@types/qs': 6.15.1 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.1 + '@types/serve-static': 2.2.0 + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/http-errors@2.0.5': {} + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/node@25.9.1': + dependencies: + undici-types: 7.24.6 + + '@types/qs@6.15.1': {} + + '@types/range-parser@1.2.7': {} + + '@types/react-dom@19.2.3(@types/react@19.2.15)': + dependencies: + '@types/react': 19.2.15 + + '@types/react@19.2.15': + dependencies: + csstype: 3.2.3 + + '@types/retry@0.12.0': {} + + '@types/semver@7.7.1': {} + + '@types/send@1.2.1': + dependencies: + '@types/node': 25.9.1 + + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 25.9.1 + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.3.1': {} + + '@vitejs/plugin-react@6.0.2(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.3)(yaml@2.9.0))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.0.16(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.3)(yaml@2.9.0) + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + agent-base@7.1.4: {} + + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + balanced-match@4.0.4: {} + + base64-js@1.5.1: {} + + better-result@2.10.0: {} + + better-sqlite3@12.10.0: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + + bignumber.js@9.3.1: {} + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + 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 + transitivePeerDependencies: + - supports-color + + bowser@2.14.1: {} + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + + buffer-equal-constant-time@1.0.1: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + ccount@2.0.1: {} + + chalk@5.6.2: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + chownr@1.1.4: {} + + comma-separated-tokens@2.0.3: {} + + commander@12.1.0: {} + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.0.0: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + + data-uri-to-buffer@4.0.1: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-extend@0.6.0: {} + + depd@2.0.0: {} + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + diff@8.0.3: {} + + diff@8.0.4: {} + + diff@9.0.0: {} + + drizzle-orm@0.45.2(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0): + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@types/better-sqlite3': 7.6.13 + better-sqlite3: 12.10.0 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + ee-first@1.1.1: {} + + encodeurl@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + + escape-html@1.0.3: {} + + etag@1.8.1: {} + + eventsource-parser@3.1.0: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.1.0 + + expand-template@2.0.3: {} + + express-rate-limit@8.5.2(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.2.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.2 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + extend@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-sha256@1.3.0: {} + + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-uri@3.1.2: {} + + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + + fast-xml-builder@1.2.0: + dependencies: + path-expression-matcher: 1.5.0 + xml-naming: 0.1.0 + + fast-xml-parser@5.7.3: + dependencies: + '@nodable/entities': 2.1.0 + fast-xml-builder: 1.2.0 + path-expression-matcher: 1.5.0 + strnum: 2.3.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + + file-uri-to-path@1.0.0: {} + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fs-constants@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gaxios@7.1.4: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + node-fetch: 3.3.2 + transitivePeerDependencies: + - supports-color + + gcp-metadata@8.1.2: + dependencies: + gaxios: 7.1.4 + google-logging-utils: 1.1.3 + json-bigint: 1.0.0 + transitivePeerDependencies: + - supports-color + + get-east-asian-width@1.6.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + github-from-package@0.0.0: {} + + glob@13.0.6: + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + + google-auth-library@10.6.2: + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 7.1.4 + gcp-metadata: 8.1.2 + google-logging-utils: 1.1.3 + jws: 4.0.1 + transitivePeerDependencies: + - supports-color + + google-logging-utils@1.1.3: {} + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-symbols@1.1.0: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + highlight.js@10.7.3: {} + + hono@4.12.25: {} + + hosted-git-info@9.0.3: + dependencies: + lru-cache: 11.4.0 + + html-void-elements@3.0.0: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore@7.0.5: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + ip-address@10.2.0: {} + + ipaddr.js@1.9.1: {} + + is-promise@4.0.0: {} + + isexe@2.0.0: {} + + jiti@2.7.0: {} + + jose@6.2.3: {} + + json-bigint@1.0.0: + dependencies: + bignumber.js: 9.3.1 + + json-schema-to-ts@3.1.1: + dependencies: + '@babel/runtime': 7.29.7 + ts-algebra: 2.0.0 + + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + + jsonc-parser@3.3.1: {} + + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + long@5.3.2: {} + + lru-cache@11.4.0: {} + + lru_map@0.4.1: {} + + lucide@1.24.0: {} + + marked@18.0.5: {} + + math-intrinsics@1.1.0: {} + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.1 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-encode@2.0.1: {} + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mimic-response@3.1.0: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + minimist@1.2.8: {} + + minipass@7.1.3: {} + + mkdirp-classic@0.5.3: {} + + ms@2.1.3: {} + + nanoid@3.3.12: {} + + napi-build-utils@2.0.0: {} + + negotiator@1.0.0: {} + + node-abi@3.92.0: + dependencies: + semver: 7.8.4 + + node-addon-api@7.1.1: + optional: true + + node-domexception@1.0.0: {} + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + node-forge@1.4.0: {} + + node-pty@1.1.0: + dependencies: + node-addon-api: 7.1.1 + optional: true + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + oniguruma-parser@0.12.2: {} + + oniguruma-to-es@4.3.6: + dependencies: + oniguruma-parser: 0.12.2 + regex: 6.1.0 + regex-recursion: 6.0.2 + + openai@6.26.0(ws@8.21.0)(zod@4.4.3): + optionalDependencies: + ws: 8.21.0 + zod: 4.4.3 + + p-retry@4.6.2: + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + + parseurl@1.3.3: {} + + partial-json@0.1.7: {} + + path-expression-matcher@1.5.0: {} + + path-key@3.1.1: {} + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.4.0 + minipass: 7.1.3 + + path-to-regexp@8.4.2: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + pkce-challenge@5.0.1: {} + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.92.0 + pump: 3.0.4 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + + property-information@7.1.0: {} + + protobufjs@7.6.4: + 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': 25.9.1 + long: 5.3.2 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + qs@6.15.2: + dependencies: + side-channel: 1.1.0 + + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + react-dom@19.2.6(react@19.2.6): + dependencies: + react: 19.2.6 + scheduler: 0.27.0 + + react@19.2.6: {} + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + require-from-string@2.0.2: {} + + retry@0.12.0: {} + + retry@0.13.1: {} + + rolldown@1.0.3: + dependencies: + '@oxc-project/types': 0.133.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.3 + '@rolldown/binding-darwin-arm64': 1.0.3 + '@rolldown/binding-darwin-x64': 1.0.3 + '@rolldown/binding-freebsd-x64': 1.0.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.3 + '@rolldown/binding-linux-arm64-musl': 1.0.3 + '@rolldown/binding-linux-ppc64-gnu': 1.0.3 + '@rolldown/binding-linux-s390x-gnu': 1.0.3 + '@rolldown/binding-linux-x64-gnu': 1.0.3 + '@rolldown/binding-linux-x64-musl': 1.0.3 + '@rolldown/binding-openharmony-arm64': 1.0.3 + '@rolldown/binding-wasm32-wasi': 1.0.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.3 + '@rolldown/binding-win32-x64-msvc': 1.0.3 + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + scheduler@0.27.0: {} + + semver@7.8.0: {} + + semver@7.8.4: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shiki@3.23.0: + dependencies: + '@shikijs/core': 3.23.0 + '@shikijs/engine-javascript': 3.23.0 + '@shikijs/engine-oniguruma': 3.23.0 + '@shikijs/langs': 3.23.0 + '@shikijs/themes': 3.23.0 + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + sisteransi@1.0.5: {} + + source-map-js@1.2.1: {} + + space-separated-tokens@2.0.2: {} + + standardwebhooks@1.0.0: + dependencies: + '@stablelib/base64': 1.0.1 + fast-sha256: 1.3.0 + + statuses@2.0.2: {} + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-json-comments@2.0.1: {} + + strnum@2.3.0: {} + + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.4 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + toidentifier@1.0.1: {} + + trim-lines@3.0.1: {} + + ts-algebra@2.0.0: {} + + tslib@2.8.1: {} + + tsx@4.22.3: + dependencies: + esbuild: 0.28.1 + optionalDependencies: + fsevents: 2.3.3 + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + type-is@2.1.0: + dependencies: + content-type: 2.0.0 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typebox@1.1.38: {} + + typescript@6.0.3: {} + + undici-types@7.24.6: {} + + undici@8.5.0: {} + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + unpipe@1.0.0: {} + + util-deprecate@1.0.2: {} + + vary@1.1.2: {} + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.3)(yaml@2.9.0): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.3 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 25.9.1 + esbuild: 0.28.1 + fsevents: 2.3.3 + jiti: 2.7.0 + tsx: 4.22.3 + yaml: 2.9.0 + + web-streams-polyfill@3.3.3: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrappy@1.0.2: {} + + ws@8.21.0: {} + + xml-naming@0.1.0: {} + + yaml@2.9.0: {} + + zod-to-json-schema@3.25.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + + zod@3.25.76: {} + + zod@4.4.3: {} + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 000000000..54fb78321 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + '@google/genai': false + better-sqlite3: true + esbuild: true + node-pty: true + protobufjs: false diff --git a/schema/v1/devspace.schema.json b/schema/v1/devspace.schema.json new file mode 100644 index 000000000..e7c18466e --- /dev/null +++ b/schema/v1/devspace.schema.json @@ -0,0 +1,301 @@ +{ + "$id": "https://raw.githubusercontent.com/Waishnav/devspace/main/schema/v1/devspace.schema.json", + "title": "DevSpace configuration", + "description": "Versioned configuration for a local DevSpace MCP server.", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "$schema": { + "default": "https://raw.githubusercontent.com/Waishnav/devspace/main/schema/v1/devspace.schema.json", + "type": "string", + "format": "uri" + }, + "configVersion": { + "type": "number", + "const": 1 + }, + "server": { + "default": {}, + "type": "object", + "properties": { + "host": { + "default": "127.0.0.1", + "type": "string", + "minLength": 1 + }, + "port": { + "default": 7676, + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "publicBaseUrl": { + "default": null, + "anyOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "null" + } + ] + }, + "allowedHosts": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "trustProxy": { + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "workspaces": { + "default": {}, + "type": "object", + "properties": { + "allowedRoots": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "worktreeRoot": { + "default": "~/.devspace/worktrees", + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + }, + "storage": { + "default": {}, + "type": "object", + "properties": { + "stateDir": { + "default": "~/.local/share/devspace", + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + }, + "tools": { + "default": {}, + "type": "object", + "properties": { + "mode": { + "default": "codex", + "type": "string", + "enum": [ + "claude", + "codex" + ] + } + }, + "additionalProperties": false + }, + "ui": { + "default": {}, + "type": "object", + "properties": { + "enabled": { + "default": true, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "artifacts": { + "default": {}, + "type": "object", + "properties": { + "enabled": { + "default": false, + "type": "boolean" + }, + "maxFileBytes": { + "default": 104857600, + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + } + }, + "additionalProperties": false + }, + "skills": { + "default": {}, + "type": "object", + "properties": { + "enabled": { + "default": true, + "type": "boolean" + }, + "paths": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "agentDir": { + "default": "~/.codex", + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + }, + "subagents": { + "default": { + "enabled": false, + "providers": [] + }, + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "providers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "enum": [ + "codex", + "claude", + "opencode", + "pi", + "cursor", + "copilot", + "grok" + ] + }, + "enabled": { + "type": "boolean" + }, + "model": { + "type": "string", + "minLength": 1 + }, + "effort": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "id", + "enabled" + ], + "additionalProperties": false + } + } + }, + "required": [ + "enabled", + "providers" + ], + "additionalProperties": false + }, + "logging": { + "default": {}, + "type": "object", + "properties": { + "level": { + "default": "info", + "type": "string", + "enum": [ + "silent", + "error", + "warn", + "info", + "debug" + ] + }, + "format": { + "default": "json", + "type": "string", + "enum": [ + "json", + "pretty" + ] + }, + "requests": { + "default": true, + "type": "boolean" + }, + "assets": { + "default": false, + "type": "boolean" + }, + "toolCalls": { + "default": true, + "type": "boolean" + }, + "shellCommands": { + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "oauth": { + "default": {}, + "type": "object", + "properties": { + "accessTokenTtlSeconds": { + "default": 3600, + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "refreshTokenTtlSeconds": { + "default": 2592000, + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "scopes": { + "default": [ + "devspace" + ], + "minItems": 1, + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "allowedRedirectHosts": { + "default": [ + "chatgpt.com", + "localhost", + "127.0.0.1" + ], + "minItems": 1, + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + } + }, + "required": [ + "configVersion" + ], + "additionalProperties": false +} diff --git a/scripts/dev-server.mjs b/scripts/dev-server.mjs deleted file mode 100644 index 5585bdae8..000000000 --- a/scripts/dev-server.mjs +++ /dev/null @@ -1,120 +0,0 @@ -import { spawn } from "node:child_process"; -import { readdirSync, statSync, watch } from "node:fs"; -import { join, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -const repoRoot = resolve(fileURLToPath(new URL("..", import.meta.url))); -const watchRoots = ["src"].map((entry) => join(repoRoot, entry)); -const restartDelayMs = 750; -const crashDelayMs = 1500; - -let child; -let restartTimer; -let stoppingForRestart = false; -let shuttingDown = false; - -function log(message) { - console.error(`[devspace:dev] ${message}`); -} - -function start() { - stoppingForRestart = false; - child = spawn("npx", ["tsx", "src/cli.ts", "serve"], { - cwd: repoRoot, - env: process.env, - stdio: "inherit", - }); - - child.on("exit", (code, signal) => { - child = undefined; - if (shuttingDown) return; - if (stoppingForRestart) return; - - log(`server exited (${signal ?? code ?? "unknown"}); restarting in ${crashDelayMs}ms`); - scheduleRestart(crashDelayMs); - }); -} - -function scheduleRestart(delayMs = restartDelayMs) { - clearTimeout(restartTimer); - restartTimer = setTimeout(restart, delayMs); -} - -function restart() { - if (shuttingDown) return; - clearTimeout(restartTimer); - - if (!child) { - start(); - return; - } - - stoppingForRestart = true; - child.once("exit", () => { - if (!shuttingDown) start(); - }); - child.kill("SIGTERM"); - - setTimeout(() => { - if (child && stoppingForRestart) child.kill("SIGKILL"); - }, 3000).unref(); -} - -function watchDirectory(root) { - const watchers = []; - const seen = new Set(); - - function addDirectory(dir) { - if (seen.has(dir)) return; - seen.add(dir); - - const watcher = watch(dir, (event, filename) => { - if (!filename) { - scheduleRestart(); - return; - } - - const path = join(dir, filename.toString()); - if (event === "rename") maybeAddDirectory(path); - scheduleRestart(); - }); - watchers.push(watcher); - - for (const entry of readdirSync(dir)) { - maybeAddDirectory(join(dir, entry)); - } - } - - function maybeAddDirectory(path) { - try { - const stats = statSync(path); - if (stats.isDirectory()) addDirectory(path); - } catch { - // The file may have been deleted between the watch event and stat call. - } - } - - addDirectory(root); - return watchers; -} - -function shutdown() { - shuttingDown = true; - clearTimeout(restartTimer); - if (!child) return process.exit(0); - - child.once("exit", () => process.exit(0)); - child.kill("SIGTERM"); - setTimeout(() => process.exit(1), 3000).unref(); -} - -for (const signal of ["SIGINT", "SIGTERM"]) { - process.on(signal, shutdown); -} - -for (const root of watchRoots) { - watchDirectory(root); -} - -log("watching src; server restarts on changes and after crashes"); -start(); diff --git a/scripts/generate-config-schema.ts b/scripts/generate-config-schema.ts new file mode 100644 index 000000000..324b611b6 --- /dev/null +++ b/scripts/generate-config-schema.ts @@ -0,0 +1,9 @@ +import { mkdirSync, writeFileSync } from "node:fs"; +import { + devspaceConfigJsonSchema, +} from "../src/config-schema.js"; + +const outputPath = new URL("../schema/v1/devspace.schema.json", import.meta.url); + +mkdirSync(new URL(".", outputPath), { recursive: true }); +writeFileSync(outputPath, `${JSON.stringify(devspaceConfigJsonSchema(), null, 2)}\n`); diff --git a/src/bin-launcher.test.ts b/src/bin-launcher.test.ts new file mode 100644 index 000000000..00b43a99f --- /dev/null +++ b/src/bin-launcher.test.ts @@ -0,0 +1,89 @@ +import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { cpSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { writeTestDevspaceConfig } from "./test-support/config.test.js"; + +const projectRoot = fileURLToPath(new URL("..", import.meta.url)); +const tsxRoot = join(projectRoot, "node_modules", "tsx"); + +for (const entrypoint of [ + { + bin: "devspace.js", + source: "src/cli.ts", + dist: "dist/cli.js", + }, + { + bin: "devspace-agentd.js", + source: "src/local-agent-daemon-main.ts", + dist: "dist/local-agent-daemon-main.js", + }, +]) { + testLauncher(entrypoint); +} + +testLinkedCheckoutReadsCurrentConfig(); +testMissingSourceRuntimeFailsClosed(); + +function testLinkedCheckoutReadsCurrentConfig(): void { + const root = mkdtempSync(join(tmpdir(), "devspace-bin-config-test-")); + try { + const env = writeTestDevspaceConfig(root, { tools: { mode: "codex" } }); + const output = execFileSync(process.execPath, [join(projectRoot, "bin", "devspace.js"), "config", "get"], { + encoding: "utf8", + env: { ...process.env, ...env }, + }); + const config = JSON.parse(output) as { tools?: { mode?: string } }; + assert.equal(config.tools?.mode, "codex"); + } finally { + rmSync(root, { recursive: true, force: true }); + } +} + +function testMissingSourceRuntimeFailsClosed(): void { + const root = mkdtempSync(join(tmpdir(), "devspace-bin-missing-tsx-test-")); + try { + cpSync(join(projectRoot, "bin"), join(root, "bin"), { recursive: true }); + mkdirSync(join(root, "src"), { recursive: true }); + mkdirSync(join(root, "dist"), { recursive: true }); + writeFileSync(join(root, "package.json"), JSON.stringify({ type: "module" })); + writeFileSync(join(root, "src", "cli.ts"), 'console.log("source");\n'); + writeFileSync(join(root, "dist", "cli.js"), 'console.log("stale-dist");\n'); + + assert.throws( + () => execFileSync(process.execPath, [join(root, "bin", "devspace.js")], { encoding: "utf8", stdio: "pipe" }), + /source checkout.*tsx.*pnpm install/is, + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } +} + +function testLauncher(entrypoint: { bin: string; source: string; dist: string }): void { + const root = mkdtempSync(join(tmpdir(), "devspace-bin-launcher-test-")); + try { + cpSync(join(projectRoot, "bin"), join(root, "bin"), { recursive: true }); + mkdirSync(dirname(join(root, entrypoint.source)), { recursive: true }); + mkdirSync(dirname(join(root, entrypoint.dist)), { recursive: true }); + mkdirSync(join(root, "node_modules"), { recursive: true }); + symlinkSync(tsxRoot, join(root, "node_modules", "tsx"), process.platform === "win32" ? "junction" : "dir"); + writeFileSync(join(root, "package.json"), JSON.stringify({ type: "module" })); + writeFileSync(join(root, entrypoint.source), 'console.log("source");\n'); + writeFileSync(join(root, entrypoint.dist), 'console.log("dist");\n'); + + const sourceOutput = execFileSync(process.execPath, [join(root, "bin", entrypoint.bin)], { + encoding: "utf8", + }).trim(); + assert.equal(sourceOutput, "source", `${entrypoint.bin} must prefer source in a linked checkout`); + + rmSync(join(root, entrypoint.source)); + const packagedOutput = execFileSync(process.execPath, [join(root, "bin", entrypoint.bin)], { + encoding: "utf8", + }).trim(); + assert.equal(packagedOutput, "dist", `${entrypoint.bin} must use dist in a published package`); + } finally { + rmSync(root, { recursive: true, force: true }); + } +} diff --git a/src/cli-show-changes.test.ts b/src/cli-show-changes.test.ts new file mode 100644 index 000000000..40763b86e --- /dev/null +++ b/src/cli-show-changes.test.ts @@ -0,0 +1,108 @@ +import assert from "node:assert/strict"; +import { execFile } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { mkdtemp, rm, writeFile } from "node:fs/promises"; +import { createRequire } from "node:module"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; +import { createReviewCheckpointManager } from "./review-checkpoints.js"; +import { writeTestDevspaceConfig } from "./test-support/config.test.js"; + +const execFileAsync = promisify(execFile); +const require = createRequire(import.meta.url); +const packageJsonPath = fileURLToPath(new URL("../package.json", import.meta.url)); +const repoRoot = dirname(packageJsonPath); +const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8")) as { + bin: { devspace: string }; +}; +// This verifies the compiled entrypoint declared for the installed `devspace` +// command. npm's package-install shim itself is outside this focused test. +const cliPath = join(repoRoot, packageJson.bin.devspace); +const tscPath = require.resolve("typescript/bin/tsc"); + +test("show-changes prints a Git-backed historical review", async (t) => { + await execFileAsync(process.execPath, [tscPath, "-p", join(repoRoot, "tsconfig.build.json")], { + cwd: repoRoot, + }); + + const root = await mkdtemp(join(tmpdir(), "devspace-cli-show-changes-")); + t.after(() => rm(root, { recursive: true, force: true })); + const project = join(root, "project"); + await execFileAsync("git", ["init", project]); + await git(project, ["config", "user.email", "devspace@example.com"]); + await git(project, ["config", "user.name", "DevSpace Test"]); + await writeFile(join(project, "README.md"), "hello\n"); + await git(project, ["add", "README.md"]); + await git(project, ["commit", "-m", "Initial commit"]); + + const manager = createReviewCheckpointManager(); + await manager.initializeWorkspace({ workspaceId: "ws_cli", root: project }); + await writeFile(join(project, "README.md"), "hello\nreview me\n"); + const review = await manager.reviewChanges({ workspaceId: "ws_cli", root: project }); + + const configDir = join(root, ".devspace"); + const env = writeTestDevspaceConfig(configDir, { + workspaces: { allowedRoots: [project] }, + storage: { stateDir: join(root, ".state") }, + }); + const cliArgs = [cliPath, "show-changes", review.reviewRef]; + const plain = await execFileAsync("node", cliArgs, { + cwd: project, + env: { + ...process.env, + ...env, + DEVSPACE_WORKSPACE_ID: "", + DEVSPACE_WORKSPACE_ROOT: "", + }, + encoding: "utf8", + }); + assert.match(plain.stdout, /\+review me/); + + const json = await execFileAsync("node", [...cliArgs, "--json"], { + cwd: project, + env: { + ...process.env, + ...env, + DEVSPACE_WORKSPACE_ID: "", + DEVSPACE_WORKSPACE_ROOT: "", + }, + encoding: "utf8", + }); + const parsed = JSON.parse(json.stdout) as { + reviewRef: string; + patch: string; + }; + assert.equal(parsed.reviewRef, review.reviewRef); + assert.equal(parsed.patch, review.patch); + + const head = (await execFileAsync("git", ["rev-parse", "HEAD"], { + cwd: project, + encoding: "utf8", + })).stdout.trim(); + await assert.rejects( + execFileAsync("node", [cliPath, "show-changes", head], { + cwd: project, + env: { + ...process.env, + ...env, + DEVSPACE_WORKSPACE_ID: "", + DEVSPACE_WORKSPACE_ROOT: "", + }, + encoding: "utf8", + }), + (error: unknown) => { + assert.match( + (error as { stderr?: string }).stderr ?? "", + /Unknown DevSpace review reference/, + ); + return true; + }, + ); +}); + +async function git(cwd: string, args: string[]): Promise { + await execFileAsync("git", args, { cwd }); +} diff --git a/src/cli.test.ts b/src/cli.test.ts index 9a2022efd..0983417c3 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -11,6 +11,7 @@ import { loadConfig } from "./config.js"; import { localAgentDaemonPaths } from "./local-agent-daemon-lifecycle.js"; import { encodeLocalAgentDaemonResponse } from "./local-agent-daemon-protocol.js"; import { LocalAgentStore } from "./local-agent-store.js"; +import { writeTestDevspaceConfig } from "./test-support/config.test.js"; const execFileAsync = promisify(execFile); const require = createRequire(import.meta.url); @@ -38,6 +39,11 @@ try { mkdirSync(stateDir, { recursive: true }); mkdirSync(join(configDir, "agents"), { recursive: true }); mkdirSync(projectRoot, { recursive: true }); + const cliConfigEnv = writeTestDevspaceConfig(configDir, { + workspaces: { allowedRoots: [projectRoot] }, + storage: { stateDir }, + subagents: { enabled: true, providers: [] }, + }); writeFileSync( join(configDir, "agents", "reviewer.md"), [ @@ -138,13 +144,9 @@ try { encoding: "utf8", env: { ...process.env, - DEVSPACE_CONFIG_DIR: configDir, - DEVSPACE_ALLOWED_ROOTS: projectRoot, - DEVSPACE_STATE_DIR: stateDir, + ...cliConfigEnv, DEVSPACE_WORKSPACE_ID: "ws_current", DEVSPACE_WORKSPACE_ROOT: projectRoot, - DEVSPACE_SUBAGENTS: "1", - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", }, }); @@ -158,13 +160,9 @@ try { encoding: "utf8", env: { ...process.env, - DEVSPACE_CONFIG_DIR: configDir, - DEVSPACE_ALLOWED_ROOTS: projectRoot, - DEVSPACE_STATE_DIR: stateDir, + ...cliConfigEnv, DEVSPACE_WORKSPACE_ID: "ws_current", DEVSPACE_WORKSPACE_ROOT: projectRoot, - DEVSPACE_SUBAGENTS: "1", - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", }, }, ); @@ -181,11 +179,7 @@ try { encoding: "utf8", env: { ...process.env, - DEVSPACE_CONFIG_DIR: configDir, - DEVSPACE_ALLOWED_ROOTS: stateDir, - DEVSPACE_STATE_DIR: stateDir, - DEVSPACE_SUBAGENTS: "1", - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", + ...cliConfigEnv, DEVSPACE_WORKSPACE_ID: "", DEVSPACE_WORKSPACE_ROOT: stateDir, }, @@ -205,13 +199,9 @@ try { encoding: "utf8", env: { ...process.env, - DEVSPACE_CONFIG_DIR: configDir, - DEVSPACE_ALLOWED_ROOTS: projectRoot, - DEVSPACE_STATE_DIR: stateDir, + ...cliConfigEnv, DEVSPACE_WORKSPACE_ID: "ws_current", DEVSPACE_WORKSPACE_ROOT: projectRoot, - DEVSPACE_SUBAGENTS: "1", - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", }, }, ); @@ -224,7 +214,6 @@ try { error: { code: string; message: string; retryable: boolean; target: string }; }; assert.equal(payload.error.code, "UNKNOWN_TARGET"); - assert.equal(payload.error.message, "Unknown subagent profile or provider: missing."); assert.equal(payload.error.retryable, false); assert.equal(payload.error.target, "missing"); @@ -247,13 +236,9 @@ try { encoding: "utf8", env: { ...process.env, - DEVSPACE_CONFIG_DIR: configDir, - DEVSPACE_ALLOWED_ROOTS: projectRoot, - DEVSPACE_STATE_DIR: stateDir, + ...cliConfigEnv, DEVSPACE_WORKSPACE_ID: "ws_current", DEVSPACE_WORKSPACE_ROOT: projectRoot, - DEVSPACE_SUBAGENTS: "1", - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", }, }, ), @@ -268,13 +253,7 @@ try { }); } - assert.equal(loadConfig({ - DEVSPACE_CONFIG_DIR: configDir, - DEVSPACE_ALLOWED_ROOTS: projectRoot, - DEVSPACE_STATE_DIR: stateDir, - DEVSPACE_SUBAGENTS: "1", - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", - }).subagents.enabled, true); + assert.equal(loadConfig(cliConfigEnv).subagents.enabled, true); } finally { rmSync(root, { recursive: true, force: true }); } diff --git a/src/cli.ts b/src/cli.ts index 7cf723f8c..b521556a3 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -8,7 +8,6 @@ import { getShellConfig } from "@earendil-works/pi-coding-agent"; import { satisfies } from "semver"; import { loadConfig } from "./config.js"; import { resolveCliWorkspaceContext } from "./cli-workspace.js"; -import { resolveSubagentsConfig } from "./local-agent-config.js"; import { getLocalAgentProviderAvailabilitySnapshot, } from "./local-agent-availability.js"; @@ -46,14 +45,23 @@ import { import { generateOwnerToken, loadDevspaceFiles, + setDevspaceConfigValue, + setDevspaceConfigValues, writeDevspaceAuth, - writeDevspaceConfig, - type DevspaceUserConfig, } from "./user-config.js"; import { expandHomePath } from "./roots.js"; +import { readReviewRef } from "./review-checkpoints.js"; import { shutdownHttpServer } from "./server-shutdown.js"; -type Command = "serve" | "init" | "doctor" | "config" | "agents" | "help" | "version"; +type Command = + | "serve" + | "init" + | "doctor" + | "config" + | "agents" + | "show-changes" + | "help" + | "version"; const require = createRequire(import.meta.url); const SUPPORTED_NODE_RANGE = ">=20.12 <27"; @@ -80,6 +88,9 @@ async function main(argv: string[]): Promise { case "agents": await runAgentsCommand(args); return; + case "show-changes": + await runShowChanges(args); + return; case "help": printHelp(); return; @@ -91,7 +102,13 @@ async function main(argv: string[]): Promise { function normalizeCommand(command: string | undefined): Command { if (!command || command === "serve" || command === "start") return "serve"; - if (command === "init" || command === "doctor" || command === "config" || command === "agents") return command; + if ( + command === "init" + || command === "doctor" + || command === "config" + || command === "agents" + || command === "show-changes" + ) return command; if (command === "help" || command === "--help" || command === "-h") return "help"; if (command === "version" || command === "--version" || command === "-v") return "version"; throw new Error(`Unknown command: ${command}`); @@ -99,6 +116,9 @@ function normalizeCommand(command: string | undefined): Command { async function ensureConfigured(): Promise { const files = loadDevspaceFiles(); + if (files.migratedLegacyConfig) { + console.log(`Migrated legacy configuration to ${files.configPath}`); + } if (files.configExists && files.authExists) return; if (process.env.DEVSPACE_OAUTH_OWNER_TOKEN) return; @@ -143,7 +163,7 @@ async function runInit({ force }: { force: boolean }): Promise { hint: "Use DevSpace from Codex, Claude Code, OpenCode, Pi, and similar tools.", }, ], - initialValues: files.config.publicBaseUrl ? ["chatgpt"] : ["coding-agents"], + initialValues: files.config.server.publicBaseUrl ? ["chatgpt"] : ["coding-agents"], required: true, }); if (prompts.isCancel(destinationAnswer)) throw new SetupCancelledError(); @@ -153,7 +173,7 @@ async function runInit({ force }: { force: boolean }): Promise { let allowedRoots: string[] | undefined; if (useChatGpt) { - const defaultRoots = files.config.allowedRoots?.join(", ") || process.cwd(); + const defaultRoots = files.config.workspaces.allowedRoots.join(", ") || process.cwd(); const rootsAnswer = await textPrompt({ message: `Which project folders can DevSpace access? Press Enter to use ${defaultRoots}`, placeholder: defaultRoots, @@ -166,7 +186,7 @@ async function runInit({ force }: { force: boolean }): Promise { .filter(Boolean); } - const port = isValidPort(files.config.port) ? files.config.port : 7676; + const port = files.config.server.port; let publicBaseUrl: string | null = null; if (useChatGpt) { @@ -180,16 +200,16 @@ async function runInit({ force }: { force: boolean }): Promise { "Connect ChatGPT", ); publicBaseUrl = normalizePublicBaseUrl(await textPrompt({ - message: files.config.publicBaseUrl - ? `What public URL will ChatGPT connect to? Press Enter to keep ${files.config.publicBaseUrl}` + message: files.config.server.publicBaseUrl + ? `What public URL will ChatGPT connect to? Press Enter to keep ${files.config.server.publicBaseUrl}` : "What public URL will ChatGPT connect to?", - placeholder: files.config.publicBaseUrl ?? "https://your-tunnel-host.example.com", - defaultValue: files.config.publicBaseUrl ?? "", + placeholder: files.config.server.publicBaseUrl ?? "https://your-tunnel-host.example.com", + defaultValue: files.config.server.publicBaseUrl ?? "", validate: validateRequiredPublicBaseUrl, })); } - const currentSubagents = resolveSubagentsConfig(files.config.subagents, {}); + const currentSubagents = files.config.subagents; const availability = getLocalAgentProviderAvailabilitySnapshot(); const configuredProviders = currentSubagents.providers .filter((provider) => provider.enabled) @@ -218,19 +238,20 @@ async function runInit({ force }: { force: boolean }): Promise { selectedProviders, ); - const config: DevspaceUserConfig = { - ...files.config, - host: files.config.host ?? "127.0.0.1", - port, - ...(allowedRoots ? { allowedRoots } : {}), - publicBaseUrl, - subagents, - }; const auth = { ownerToken: files.auth.ownerToken ?? generateOwnerToken(), }; - writeDevspaceConfig(config); + setDevspaceConfigValues([ + { path: ["server", "port"], value: port }, + ...(useChatGpt + ? [{ path: ["server", "publicBaseUrl"], value: publicBaseUrl }] + : []), + ...(allowedRoots + ? [{ path: ["workspaces", "allowedRoots"], value: allowedRoots }] + : []), + { path: ["subagents"], value: subagents }, + ]); writeDevspaceAuth(auth); const lines = [ @@ -295,7 +316,7 @@ async function serve(): Promise { console.log(`allowed roots: ${config.allowedRoots.join(", ")}`); console.log(`allowed hosts: ${config.allowedHosts.join(", ")}`); if (config.allowedHosts.includes("*")) { - console.warn("warning: Host header allowlist is disabled because DEVSPACE_ALLOWED_HOSTS=*"); + console.warn("warning: Host header allowlist is disabled because server.allowedHosts contains '*'"); } console.log("auth: Owner password approval required"); console.log(`logging: ${config.logging.level} ${config.logging.format}`); @@ -369,10 +390,10 @@ function runConfigCommand(args: string[]): void { throw new Error("Missing publicBaseUrl value."); } - writeDevspaceConfig({ - ...files.config, - publicBaseUrl: normalizeOptionalPublicBaseUrl(value), - }); + setDevspaceConfigValue( + ["server", "publicBaseUrl"], + normalizeOptionalPublicBaseUrl(value), + ); console.log(`Updated ${files.configPath}`); } @@ -384,10 +405,11 @@ function printHelp(): void { "Usage:", " devspace Run first-time setup if needed, then start the server", " devspace serve Start the server", - " devspace init Create or update ~/.devspace/config.json and auth.json", + " devspace init Create or update ~/.devspace/config.jsonc and auth.json", " devspace doctor Show config, runtime, and native dependency status", " devspace config get Print persisted config", " devspace config set publicBaseUrl ", + " devspace show-changes [--json]", " devspace agents ls List subagent sessions", " devspace agents run [--model ] [--effort ] ", " devspace agents continue [--model ] [--effort ] ", @@ -396,11 +418,29 @@ function printHelp(): void { " devspace -v, --version Print the installed version", "", "For temporary tunnels:", - " DEVSPACE_PUBLIC_BASE_URL=https://example.trycloudflare.com devspace serve", + " devspace config set publicBaseUrl https://example.trycloudflare.com", + " devspace serve", ].join("\n"), ); } +async function runShowChanges(args: string[]): Promise { + const { args: commandArgs, json } = extractJsonOption(args); + const [reviewRef, ...extra] = commandArgs; + if (!reviewRef || extra.length > 0) { + throw new Error("Usage: devspace show-changes [--json]"); + } + + const config = loadConfig(); + const scope = resolveCliWorkspaceContext(config.allowedRoots); + const review = await readReviewRef(scope.workspaceRoot, reviewRef); + if (json) { + printJson(review); + return; + } + console.log(review.patch || review.result); +} + async function runAgentsCommand(args: string[]): Promise { const [subcommand, ...rest] = args; const { args: commandArgs, json } = extractJsonOption(rest); @@ -667,10 +707,6 @@ async function textPrompt(options: TextPromptOptions): Promise { return value || options.defaultValue; } -function isValidPort(value: unknown): value is number { - return Number.isInteger(value) && Number(value) >= 1 && Number(value) <= 65535; -} - function validateRequiredPublicBaseUrl(value: string | undefined): string | undefined { const trimmed = value?.trim() ?? ""; if (!trimmed) return "Enter the public URL from your tunnel or reverse proxy."; diff --git a/src/config-migration.ts b/src/config-migration.ts new file mode 100644 index 000000000..f24851adb --- /dev/null +++ b/src/config-migration.ts @@ -0,0 +1,96 @@ +import * as z from "zod/v4"; +import { + DEVSPACE_CONFIG_VERSION, + devspaceConfigSchema, + type DevspaceConfig, +} from "./config-schema.js"; +import { storedSubagentsConfigSchema } from "./local-agent-config.js"; +import { LOCAL_AGENT_PROVIDERS } from "./local-agent-profiles.js"; + +const legacyConfigSchema = z.object({ + host: z.string().optional(), + port: z.number().optional(), + allowedRoots: z.array(z.string()).optional(), + publicBaseUrl: z.string().nullable().optional(), + allowedHosts: z.array(z.string()).optional(), + stateDir: z.string().optional(), + worktreeRoot: z.string().optional(), + artifactsEnabled: z.boolean().optional(), + artifactMaxFileBytes: z.number().optional(), + agentDir: z.string().optional(), + subagents: storedSubagentsConfigSchema.optional(), + tools: z.object({ + mode: z.enum(["claude", "codex"]).optional(), + }).strict().optional(), + ui: z.object({ + enabled: z.boolean().optional(), + }).strict().optional(), +}).passthrough(); + +const LEGACY_CONFIG_KEYS = new Set([ + "host", + "port", + "allowedRoots", + "publicBaseUrl", + "allowedHosts", + "stateDir", + "worktreeRoot", + "artifactsEnabled", + "artifactMaxFileBytes", + "agentDir", + "subagents", + "tools", + "ui", +]); + +export function migrateLegacyConfig(value: unknown): DevspaceConfig { + const legacy = legacyConfigSchema.parse(value); + const unsupportedKeys = Object.keys(legacy).filter((key) => !LEGACY_CONFIG_KEYS.has(key)); + if (unsupportedKeys.length > 0) { + throw new Error( + `Unsupported legacy configuration keys: ${unsupportedKeys.sort().join(", ")}`, + ); + } + + return devspaceConfigSchema.parse({ + configVersion: DEVSPACE_CONFIG_VERSION, + server: definedEntries({ + host: legacy.host, + port: legacy.port, + publicBaseUrl: legacy.publicBaseUrl, + allowedHosts: legacy.allowedHosts, + }), + workspaces: definedEntries({ + allowedRoots: legacy.allowedRoots, + worktreeRoot: legacy.worktreeRoot, + }), + storage: definedEntries({ stateDir: legacy.stateDir }), + tools: definedEntries({ mode: legacy.tools?.mode }), + ui: definedEntries({ enabled: legacy.ui?.enabled }), + artifacts: definedEntries({ + enabled: legacy.artifactsEnabled, + maxFileBytes: legacy.artifactMaxFileBytes, + }), + skills: definedEntries({ agentDir: legacy.agentDir }), + subagents: migrateLegacySubagents(legacy.subagents), + }); +} + +function definedEntries>(value: T): Partial { + return Object.fromEntries( + Object.entries(value).filter((entry) => entry[1] !== undefined), + ) as Partial; +} + +function migrateLegacySubagents( + value: z.infer | undefined, +): unknown { + if (value === undefined) return undefined; + if (typeof value !== "boolean") return value; + return { + enabled: value, + providers: value + ? LOCAL_AGENT_PROVIDERS.map((id) => ({ id, enabled: true })) + : [], + }; +} diff --git a/src/config-schema.test.ts b/src/config-schema.test.ts new file mode 100644 index 000000000..1b6ce9883 --- /dev/null +++ b/src/config-schema.test.ts @@ -0,0 +1,20 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { + devspaceConfigJsonSchema, + devspaceConfigSchema, +} from "./config-schema.js"; + +assert.throws( + () => devspaceConfigSchema.parse({ configVersion: 1, typo: true }), + /Unrecognized key/, +); + +const generatedSchema = `${JSON.stringify(devspaceConfigJsonSchema(), null, 2)}\n`; +const committedSchema = readFileSync( + new URL("../schema/v1/devspace.schema.json", import.meta.url), + "utf8", +).replace(/\r\n/g, "\n"); +assert.equal(committedSchema, generatedSchema, "run `npm run schema:config` after changing config-schema.ts"); + +console.log("config schema tests passed"); diff --git a/src/config-schema.ts b/src/config-schema.ts new file mode 100644 index 000000000..c30bb3612 --- /dev/null +++ b/src/config-schema.ts @@ -0,0 +1,97 @@ +import * as z from "zod/v4"; +import { subagentsConfigSchema } from "./local-agent-config.js"; + +export const DEVSPACE_CONFIG_VERSION = 1 as const; +export const DEVSPACE_CONFIG_SCHEMA_URL = + "https://raw.githubusercontent.com/Waishnav/devspace/main/schema/v1/devspace.schema.json"; + +const serverConfigSchema = z.object({ + host: z.string().trim().min(1).default("127.0.0.1"), + port: z.number().int().min(1).max(65_535).default(7676), + publicBaseUrl: z.string().url().nullable().default(null), + allowedHosts: z.array(z.string().trim().min(1)).default([]), + trustProxy: z.boolean().default(false), +}).strict().prefault({}); + +const workspacesConfigSchema = z.object({ + allowedRoots: z.array(z.string().trim().min(1)).default([]), + worktreeRoot: z.string().trim().min(1).default("~/.devspace/worktrees"), +}).strict().prefault({}); + +const storageConfigSchema = z.object({ + stateDir: z.string().trim().min(1).default("~/.local/share/devspace"), +}).strict().prefault({}); + +const toolsConfigSchema = z.object({ + mode: z.enum(["claude", "codex"]).default("codex"), +}).strict().prefault({}); + +const uiConfigSchema = z.object({ + enabled: z.boolean().default(true), +}).strict().prefault({}); + +const artifactsConfigSchema = z.object({ + enabled: z.boolean().default(false), + maxFileBytes: z.number().int().positive().default(100 * 1024 * 1024), +}).strict().prefault({}); + +const skillsConfigSchema = z.object({ + enabled: z.boolean().default(true), + paths: z.array(z.string().trim().min(1)).default([]), + agentDir: z.string().trim().min(1).default("~/.codex"), +}).strict().prefault({}); + +const loggingConfigSchema = z.object({ + level: z.enum(["silent", "error", "warn", "info", "debug"]).default("info"), + format: z.enum(["json", "pretty"]).default("json"), + requests: z.boolean().default(true), + assets: z.boolean().default(false), + toolCalls: z.boolean().default(true), + shellCommands: z.boolean().default(false), +}).strict().prefault({}); + +const oauthConfigSchema = z.object({ + accessTokenTtlSeconds: z.number().int().positive().default(60 * 60), + refreshTokenTtlSeconds: z.number().int().positive().default(30 * 24 * 60 * 60), + scopes: z.array(z.string().trim().min(1)).min(1).default(["devspace"]), + allowedRedirectHosts: z.array(z.string().trim().min(1)).min(1).default([ + "chatgpt.com", + "localhost", + "127.0.0.1", + ]), +}).strict().prefault({}); + +export const devspaceConfigSchema = z.object({ + $schema: z.string().url().default(DEVSPACE_CONFIG_SCHEMA_URL), + configVersion: z.literal(DEVSPACE_CONFIG_VERSION), + server: serverConfigSchema, + workspaces: workspacesConfigSchema, + storage: storageConfigSchema, + tools: toolsConfigSchema, + ui: uiConfigSchema, + artifacts: artifactsConfigSchema, + skills: skillsConfigSchema, + subagents: subagentsConfigSchema.default({ enabled: false, providers: [] }), + logging: loggingConfigSchema, + oauth: oauthConfigSchema, +}).strict(); + +export type DevspaceConfig = z.output; +export type DevspaceConfigInput = z.input; +export type ToolMode = DevspaceConfig["tools"]["mode"]; + +export function defaultDevspaceConfig(): DevspaceConfig { + return devspaceConfigSchema.parse({ configVersion: DEVSPACE_CONFIG_VERSION }); +} + +export function devspaceConfigJsonSchema(): object { + return { + $id: DEVSPACE_CONFIG_SCHEMA_URL, + title: "DevSpace configuration", + description: "Versioned configuration for a local DevSpace MCP server.", + ...z.toJSONSchema(devspaceConfigSchema, { + target: "draft-2020-12", + io: "input", + }), + }; +} diff --git a/src/config.test.ts b/src/config.test.ts index 7b3eeeb6a..5fd24b490 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -1,188 +1,127 @@ import assert from "node:assert/strict"; -import { mkdtempSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; +import { mkdtempSync, rmSync } from "node:fs"; +import { homedir, tmpdir } from "node:os"; +import { join, resolve } from "node:path"; import { loadConfig } from "./config.js"; +import { writeDevspaceAuth, writeDevspaceConfig } from "./user-config.js"; -const emptyConfigDir = mkdtempSync(join(tmpdir(), "devspace-empty-config-test-")); -const baseEnv = { - DEVSPACE_CONFIG_DIR: emptyConfigDir, - DEVSPACE_ALLOWED_ROOTS: process.cwd(), +const configDir = mkdtempSync(join(tmpdir(), "devspace-config-test-")); +const env = { + DEVSPACE_CONFIG_DIR: configDir, DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", }; -assert.equal(loadConfig(baseEnv).widgets, "full"); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_WIDGETS: "changes" }).widgets, "changes"); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_WIDGETS: "full" }).widgets, "full"); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_WIDGETS: "off" }).widgets, "off"); -assert.equal(loadConfig(baseEnv).toolMode, "minimal"); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_TOOL_MODE: "minimal" }).toolMode, "minimal"); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_TOOL_MODE: "full" }).toolMode, "full"); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_TOOL_MODE: "codex" }).toolMode, "codex"); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_MINIMAL_TOOLS: "0" }).toolMode, "full"); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_MINIMAL_TOOLS: "1" }).toolMode, "minimal"); -assert.equal(loadConfig(baseEnv).skillsEnabled, true); -assert.equal(loadConfig(baseEnv).devspaceSkillsDir, join(emptyConfigDir, "skills")); -assert.equal(loadConfig(baseEnv).devspaceAgentsDir, join(emptyConfigDir, "agents")); -assert.deepEqual(loadConfig(baseEnv).subagents, { enabled: false, providers: [] }); -assert.equal(loadConfig(baseEnv).artifactsEnabled, false); -assert.equal(loadConfig(baseEnv).artifactMaxFileBytes, 100 * 1024 * 1024); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_ARTIFACTS: "1" }).artifactsEnabled, true); -assert.equal( - loadConfig({ ...baseEnv, DEVSPACE_ARTIFACT_MAX_FILE_BYTES: "123" }).artifactMaxFileBytes, - 123, -); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_SKILLS: "0" }).skillsEnabled, false); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_SKILLS: "1" }).skillsEnabled, true); -assert.deepEqual(loadConfig({ ...baseEnv, DEVSPACE_SUBAGENTS: "1" }).subagents, { - enabled: true, - providers: [], -}); -assert.throws( - () => loadConfig({ ...baseEnv, DEVSPACE_WIDGETS: "invalid" }), - /Invalid DEVSPACE_WIDGETS: invalid/, -); -assert.throws( - () => loadConfig({ ...baseEnv, DEVSPACE_WIDGETS: "minimal" }), - /Invalid DEVSPACE_WIDGETS: minimal/, -); -assert.throws( - () => loadConfig({ ...baseEnv, DEVSPACE_WIDGETS: "write-only" }), - /Invalid DEVSPACE_WIDGETS: write-only/, -); -assert.throws( - () => loadConfig({ ...baseEnv, DEVSPACE_TOOL_MODE: "invalid" }), - /Invalid DEVSPACE_TOOL_MODE: invalid/, -); - -assert.deepEqual(loadConfig(baseEnv).logging, { - level: "info", - format: "json", - requests: true, - assets: false, - toolCalls: true, - shellCommands: false, - trustProxy: false, -}); - -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_LOG_LEVEL: "silent" }).logging.level, "silent"); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_LOG_LEVEL: "error" }).logging.level, "error"); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_LOG_LEVEL: "warn" }).logging.level, "warn"); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_LOG_LEVEL: "info" }).logging.level, "info"); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_LOG_LEVEL: "debug" }).logging.level, "debug"); - -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_LOG_FORMAT: "json" }).logging.format, "json"); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_LOG_FORMAT: "pretty" }).logging.format, "pretty"); - -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_LOG_REQUESTS: "0" }).logging.requests, false); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_LOG_ASSETS: "1" }).logging.assets, true); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_LOG_TOOL_CALLS: "0" }).logging.toolCalls, false); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_LOG_SHELL_COMMANDS: "1" }).logging.shellCommands, true); -assert.equal(loadConfig({ ...baseEnv, DEVSPACE_TRUST_PROXY: "1" }).logging.trustProxy, true); +try { + const defaults = loadConfig(env); + assert.equal(defaults.host, "127.0.0.1"); + assert.equal(defaults.port, 7676); + assert.equal(defaults.publicBaseUrl, "http://127.0.0.1:7676"); + assert.deepEqual(defaults.allowedRoots, [process.cwd()]); + assert.deepEqual(defaults.allowedHosts, ["localhost", "127.0.0.1", "::1"]); + assert.equal(defaults.toolMode, "codex"); + assert.equal(defaults.uiEnabled, true); + assert.equal(defaults.skillsEnabled, true); + assert.equal(defaults.artifactsEnabled, false); + assert.deepEqual(defaults.subagents, { enabled: false, providers: [] }); + assert.deepEqual(defaults.logging, { + level: "info", + format: "json", + requests: true, + assets: false, + toolCalls: true, + shellCommands: false, + trustProxy: false, + }); -assert.throws( - () => loadConfig({ ...baseEnv, DEVSPACE_LOG_LEVEL: "trace" }), - /Invalid DEVSPACE_LOG_LEVEL: trace/, -); + writeDevspaceConfig({ + configVersion: 1, + server: { + host: "0.0.0.0", + port: 8787, + publicBaseUrl: "https://devspace.example.com/", + allowedHosts: ["example.internal"], + trustProxy: true, + }, + workspaces: { + allowedRoots: ["~/work"], + worktreeRoot: "~/trees", + }, + storage: { stateDir: "~/state" }, + tools: { mode: "claude" }, + ui: { enabled: false }, + artifacts: { enabled: true, maxFileBytes: 321 }, + skills: { enabled: false, paths: ["~/skills"], agentDir: "~/agent" }, + subagents: { + enabled: true, + providers: [{ id: "codex", enabled: true }], + }, + logging: { + level: "debug", + format: "pretty", + requests: false, + assets: true, + toolCalls: false, + shellCommands: true, + }, + oauth: { + accessTokenTtlSeconds: 120, + refreshTokenTtlSeconds: 240, + scopes: ["devspace", "admin"], + allowedRedirectHosts: ["chatgpt.com", "example.com"], + }, + }, env); + writeDevspaceAuth({ ownerToken: "persisted-owner-token-long-enough" }, env); -assert.throws( - () => loadConfig({ ...baseEnv, DEVSPACE_LOG_FORMAT: "color" }), - /Invalid DEVSPACE_LOG_FORMAT: color/, -); + const configured = loadConfig({ DEVSPACE_CONFIG_DIR: configDir }); + assert.equal(configured.configDir, configDir); + assert.equal(configured.host, "0.0.0.0"); + assert.equal(configured.port, 8787); + assert.equal(configured.publicBaseUrl, "https://devspace.example.com"); + assert.deepEqual(configured.allowedRoots, [resolve(homedir(), "work")]); + assert.deepEqual(configured.allowedHosts, [ + "localhost", + "127.0.0.1", + "::1", + "0.0.0.0", + "devspace.example.com", + "example.internal", + ]); + assert.equal(configured.toolMode, "claude"); + assert.equal(configured.uiEnabled, false); + assert.equal(configured.stateDir, resolve(homedir(), "state")); + assert.equal(configured.worktreeRoot, resolve(homedir(), "trees")); + assert.equal(configured.artifactsEnabled, true); + assert.equal(configured.artifactMaxFileBytes, 321); + assert.equal(configured.skillsEnabled, false); + assert.deepEqual(configured.skillPaths, ["~/skills"]); + assert.equal(configured.agentDir, resolve(homedir(), "agent")); + assert.equal(configured.subagents.enabled, true); + assert.equal(configured.oauth.ownerToken, "persisted-owner-token-long-enough"); + assert.equal(configured.oauth.accessTokenTtlSeconds, 120); + assert.deepEqual(configured.oauth.scopes, ["devspace", "admin"]); + assert.deepEqual(configured.logging, { + level: "debug", + format: "pretty", + requests: false, + assets: true, + toolCalls: false, + shellCommands: true, + trustProxy: true, + }); -assert.equal(loadConfig(baseEnv).oauth.ownerToken, "test-owner-token-that-is-long-enough"); -assert.deepEqual(loadConfig(baseEnv).oauth.scopes, ["devspace"]); -assert.deepEqual(loadConfig(baseEnv).oauth.allowedRedirectHosts, [ - "chatgpt.com", - "localhost", - "127.0.0.1", -]); -assert.equal(loadConfig(baseEnv).oauth.accessTokenTtlSeconds, 3600); -assert.equal(loadConfig(baseEnv).oauth.refreshTokenTtlSeconds, 2592000); + assert.equal(loadConfig(env).oauth.ownerToken, env.DEVSPACE_OAUTH_OWNER_TOKEN); +} finally { + rmSync(configDir, { recursive: true, force: true }); +} -assert.deepEqual( - loadConfig({ ...baseEnv, DEVSPACE_OAUTH_SCOPES: "devspace,admin" }).oauth.scopes, - ["devspace", "admin"], -); -assert.deepEqual( - loadConfig({ ...baseEnv, DEVSPACE_OAUTH_ALLOWED_REDIRECT_HOSTS: "chatgpt.com,example.com" }).oauth - .allowedRedirectHosts, - ["chatgpt.com", "example.com"], -); -assert.equal( - loadConfig({ ...baseEnv, DEVSPACE_OAUTH_ACCESS_TOKEN_TTL_SECONDS: "120" }).oauth - .accessTokenTtlSeconds, - 120, -); -assert.equal( - loadConfig({ ...baseEnv, DEVSPACE_OAUTH_REFRESH_TOKEN_TTL_SECONDS: "240" }).oauth - .refreshTokenTtlSeconds, - 240, -); - -assert.throws( - () => loadConfig({ DEVSPACE_CONFIG_DIR: emptyConfigDir, DEVSPACE_ALLOWED_ROOTS: process.cwd() }), - /DEVSPACE_OAUTH_OWNER_TOKEN is required/, -); -assert.throws( - () => loadConfig({ ...baseEnv, DEVSPACE_OAUTH_OWNER_TOKEN: "too-short" }), - /DEVSPACE_OAUTH_OWNER_TOKEN must be at least 16 characters long/, -); -assert.throws( - () => loadConfig({ ...baseEnv, DEVSPACE_OAUTH_ACCESS_TOKEN_TTL_SECONDS: "0" }), - /Invalid DEVSPACE_OAUTH_ACCESS_TOKEN_TTL_SECONDS: 0/, -); -assert.throws( - () => loadConfig({ ...baseEnv, DEVSPACE_ARTIFACT_MAX_FILE_BYTES: "0" }), - /Invalid DEVSPACE_ARTIFACT_MAX_FILE_BYTES: 0/, -); - -assert.equal(loadConfig(baseEnv).publicBaseUrl, "http://127.0.0.1:7676"); -assert.deepEqual(loadConfig(baseEnv).allowedHosts, ["localhost", "127.0.0.1", "::1"]); - -assert.equal( - loadConfig({ ...baseEnv, DEVSPACE_PUBLIC_BASE_URL: "https://abc.trycloudflare.com/" }).publicBaseUrl, - "https://abc.trycloudflare.com", -); -assert.deepEqual( - loadConfig({ ...baseEnv, DEVSPACE_PUBLIC_BASE_URL: "https://abc.trycloudflare.com/" }).allowedHosts, - ["localhost", "127.0.0.1", "::1", "abc.trycloudflare.com"], -); -assert.deepEqual( - loadConfig({ ...baseEnv, DEVSPACE_ALLOWED_HOSTS: "*" }).allowedHosts, - ["*"], -); - -const configDir = mkdtempSync(join(tmpdir(), "devspace-config-test-")); -writeFileSync( - join(configDir, "config.json"), - JSON.stringify({ - port: 8787, - allowedRoots: [process.cwd()], - publicBaseUrl: "https://devspace.example.com", - subagents: true, - artifactsEnabled: true, - artifactMaxFileBytes: 321, - }), -); -writeFileSync( - join(configDir, "auth.json"), - JSON.stringify({ - ownerToken: "persisted-owner-token-long-enough", - }), -); +const missingAuthDir = mkdtempSync(join(tmpdir(), "devspace-config-no-auth-test-")); +try { + assert.throws( + () => loadConfig({ DEVSPACE_CONFIG_DIR: missingAuthDir }), + /OAuth owner token is required/, + ); +} finally { + rmSync(missingAuthDir, { recursive: true, force: true }); +} -const fileConfig = loadConfig({ DEVSPACE_CONFIG_DIR: configDir }); -assert.equal(fileConfig.port, 8787); -assert.equal(fileConfig.oauth.ownerToken, "persisted-owner-token-long-enough"); -assert.equal(fileConfig.publicBaseUrl, "https://devspace.example.com"); -assert.equal(fileConfig.subagents.enabled, true); -assert.equal(fileConfig.subagents.providers.length, 7); -assert.equal(fileConfig.artifactsEnabled, true); -assert.equal(fileConfig.artifactMaxFileBytes, 321); -assert.deepEqual(fileConfig.allowedHosts, [ - "localhost", - "127.0.0.1", - "::1", - "devspace.example.com", -]); +console.log("config tests passed"); diff --git a/src/config.ts b/src/config.ts index 54a131c9a..e53305268 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,18 +1,15 @@ -import { homedir } from "node:os"; -import { join, resolve } from "node:path"; +import { resolve } from "node:path"; +import type { ToolMode } from "./config-schema.js"; import { expandHomePath } from "./roots.js"; -import type { LoggingConfig, LogFormat, LogLevel } from "./logger.js"; +import type { LoggingConfig } from "./logger.js"; import type { OAuthConfig } from "./oauth-provider.js"; import { devspaceAgentsDir, devspaceSkillsDir, loadDevspaceFiles } from "./user-config.js"; -import { resolveSubagentsConfig, type SubagentsConfig } from "./local-agent-config.js"; +import type { SubagentsConfig } from "./local-agent-config.js"; -export type ToolMode = "minimal" | "full" | "codex"; -export type WidgetMode = "off" | "changes" | "full"; -const DEFAULT_OAUTH_ACCESS_TOKEN_TTL_SECONDS = 60 * 60; -const DEFAULT_OAUTH_REFRESH_TOKEN_TTL_SECONDS = 30 * 24 * 60 * 60; -const DEFAULT_ARTIFACT_MAX_FILE_BYTES = 100 * 1024 * 1024; +export type { ToolMode } from "./config-schema.js"; export interface ServerConfig { + configDir: string; host: string; port: number; oauth: OAuthConfig; @@ -20,7 +17,7 @@ export interface ServerConfig { allowedHosts: string[]; publicBaseUrl: string; toolMode: ToolMode; - widgets: WidgetMode; + uiEnabled: boolean; stateDir: string; worktreeRoot: string; artifactsEnabled: boolean; @@ -34,186 +31,13 @@ export interface ServerConfig { logging: LoggingConfig; } -function parsePort(value: string | number | undefined): number { - if (value === undefined || value === "") return 7676; - - const port = Number(value); - if (!Number.isInteger(port) || port < 1 || port > 65535) { - throw new Error(`Invalid PORT: ${value}`); - } - - return port; -} - -function parseAllowedRoots(value: string | string[] | undefined): string[] { - if (Array.isArray(value)) { - const roots = value.map((entry) => entry.trim()).filter(Boolean); - return (roots.length > 0 ? roots : [process.cwd()]).map((root) => resolve(expandHomePath(root))); - } - - const rawRoots = - value - ?.split(",") - .map((entry) => entry.trim()) - .filter(Boolean) ?? []; - - const roots = rawRoots.length > 0 ? rawRoots : [process.cwd()]; - return roots.map((root) => resolve(expandHomePath(root))); -} - -function parseAllowedHosts(value: string | string[] | undefined, derivedHosts: string[]): string[] { - if (Array.isArray(value)) { - return normalizeAllowedHosts(value, derivedHosts); - } - - const rawHosts = - value - ?.split(",") - .map((entry) => entry.trim()) - .filter(Boolean) ?? []; - - return normalizeAllowedHosts(rawHosts, derivedHosts); -} - -function normalizeAllowedHosts(rawHosts: string[], derivedHosts: string[]): string[] { - const hosts = rawHosts.length > 0 ? rawHosts : derivedHosts; - if (hosts.includes("*")) return ["*"]; - return Array.from(new Set(hosts.map((host) => host.trim()).filter(Boolean))); -} - -function parseBoolean(value: string | undefined): boolean { - return ["1", "true", "yes", "on"].includes(value?.toLowerCase() ?? ""); -} - -function parseToolMode(env: NodeJS.ProcessEnv): ToolMode { - const mode = env.DEVSPACE_TOOL_MODE; - if (mode === "minimal" || mode === "full" || mode === "codex") return mode; - if (mode) throw new Error(`Invalid DEVSPACE_TOOL_MODE: ${mode}`); - - if (env.DEVSPACE_MINIMAL_TOOLS !== undefined) { - return parseBoolean(env.DEVSPACE_MINIMAL_TOOLS) ? "minimal" : "full"; - } - return "minimal"; -} - -function parseLogLevel(value: string | undefined): LogLevel { - if (!value || value === "info") return "info"; - if (["silent", "error", "warn", "debug"].includes(value)) return value as LogLevel; - - throw new Error(`Invalid DEVSPACE_LOG_LEVEL: ${value}`); -} - -function parseLogFormat(value: string | undefined): LogFormat { - if (!value || value === "json") return "json"; - if (value === "pretty") return "pretty"; - - throw new Error(`Invalid DEVSPACE_LOG_FORMAT: ${value}`); -} - -function parsePathList(value: string | undefined): string[] { - return ( - value - ?.split(",") - .map((entry) => entry.trim()) - .filter(Boolean) ?? [] - ); -} - -function parseStringList(value: string | undefined, fallback: string[]): string[] { - const entries = value - ?.split(",") - .map((entry) => entry.trim()) - .filter(Boolean); - - return entries && entries.length > 0 ? entries : fallback; -} - -function parsePositiveInteger( - value: string | undefined, - fallback: number, - name: string, - max = Number.MAX_SAFE_INTEGER, -): number { - if (!value) return fallback; - - const parsed = Number(value); - if (!Number.isInteger(parsed) || parsed < 1 || parsed > max) { - throw new Error(`Invalid ${name}: ${value}`); - } - - return parsed; -} - -function parseLoggingConfig(env: NodeJS.ProcessEnv): LoggingConfig { - return { - level: parseLogLevel(env.DEVSPACE_LOG_LEVEL), - format: parseLogFormat(env.DEVSPACE_LOG_FORMAT), - requests: env.DEVSPACE_LOG_REQUESTS === undefined ? true : parseBoolean(env.DEVSPACE_LOG_REQUESTS), - assets: parseBoolean(env.DEVSPACE_LOG_ASSETS), - toolCalls: env.DEVSPACE_LOG_TOOL_CALLS === undefined ? true : parseBoolean(env.DEVSPACE_LOG_TOOL_CALLS), - shellCommands: parseBoolean(env.DEVSPACE_LOG_SHELL_COMMANDS), - trustProxy: parseBoolean(env.DEVSPACE_TRUST_PROXY), - }; -} - -function parseWidgetMode(value: string | undefined): WidgetMode { - if (!value || value === "full") return "full"; - if (value === "off" || value === "changes") return value; - - throw new Error(`Invalid DEVSPACE_WIDGETS: ${value}`); -} - -function parseRequiredSecret(value: string | undefined, name: string): string { - const secret = value?.trim(); - if (!secret) { - throw new Error(`${name} is required for DevSpace OAuth. Run: devspace init`); - } - if (secret.length < 16) { - throw new Error(`${name} must be at least 16 characters long.`); - } - return secret; -} - -function parseOAuthConfig(env: NodeJS.ProcessEnv, ownerToken: string | undefined): OAuthConfig { - return { - ownerToken: parseRequiredSecret(env.DEVSPACE_OAUTH_OWNER_TOKEN ?? ownerToken, "DEVSPACE_OAUTH_OWNER_TOKEN"), - accessTokenTtlSeconds: parsePositiveInteger( - env.DEVSPACE_OAUTH_ACCESS_TOKEN_TTL_SECONDS, - DEFAULT_OAUTH_ACCESS_TOKEN_TTL_SECONDS, - "DEVSPACE_OAUTH_ACCESS_TOKEN_TTL_SECONDS", - ), - refreshTokenTtlSeconds: parsePositiveInteger( - env.DEVSPACE_OAUTH_REFRESH_TOKEN_TTL_SECONDS, - DEFAULT_OAUTH_REFRESH_TOKEN_TTL_SECONDS, - "DEVSPACE_OAUTH_REFRESH_TOKEN_TTL_SECONDS", - ), - scopes: parseStringList(env.DEVSPACE_OAUTH_SCOPES, ["devspace"]), - allowedRedirectHosts: parseStringList(env.DEVSPACE_OAUTH_ALLOWED_REDIRECT_HOSTS, [ - "chatgpt.com", - "localhost", - "127.0.0.1", - ]), - }; -} - -function defaultStateDir(): string { - return join(homedir(), ".local", "share", "devspace"); -} - -function defaultWorktreeRoot(): string { - return join(homedir(), ".devspace", "worktrees"); -} - -function defaultAgentDir(): string { - return join(homedir(), ".codex"); -} - export function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig { const files = loadDevspaceFiles(env); - const host = env.HOST ?? files.config.host ?? "127.0.0.1"; - const port = parsePort(env.PORT ?? files.config.port); + const stored = files.config; + const host = stored.server.host; + const port = stored.server.port; const publicBaseUrl = parsePublicBaseUrl( - env.DEVSPACE_PUBLIC_BASE_URL ?? files.config.publicBaseUrl ?? localPublicBaseUrl(host, port), + stored.server.publicBaseUrl ?? localPublicBaseUrl(host, port), ); const derivedAllowedHosts = [ "localhost", @@ -221,41 +45,66 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig { "::1", host, new URL(publicBaseUrl).hostname, - ...(files.config.allowedHosts ?? []), + ...stored.server.allowedHosts, ]; return { + configDir: files.dir, host, port, - oauth: parseOAuthConfig(env, files.auth.ownerToken), - allowedRoots: parseAllowedRoots(env.DEVSPACE_ALLOWED_ROOTS ?? files.config.allowedRoots), - allowedHosts: parseAllowedHosts(env.DEVSPACE_ALLOWED_HOSTS, derivedAllowedHosts), + oauth: { + ownerToken: parseRequiredSecret( + env.DEVSPACE_OAUTH_OWNER_TOKEN ?? files.auth.ownerToken, + ), + accessTokenTtlSeconds: stored.oauth.accessTokenTtlSeconds, + refreshTokenTtlSeconds: stored.oauth.refreshTokenTtlSeconds, + scopes: stored.oauth.scopes, + allowedRedirectHosts: stored.oauth.allowedRedirectHosts, + }, + allowedRoots: normalizePaths(stored.workspaces.allowedRoots, [process.cwd()]), + allowedHosts: normalizeAllowedHosts(derivedAllowedHosts), publicBaseUrl, - toolMode: parseToolMode(env), - widgets: parseWidgetMode(env.DEVSPACE_WIDGETS), - stateDir: resolve(expandHomePath(env.DEVSPACE_STATE_DIR ?? files.config.stateDir ?? defaultStateDir())), - worktreeRoot: resolve(expandHomePath(env.DEVSPACE_WORKTREE_ROOT ?? files.config.worktreeRoot ?? defaultWorktreeRoot())), - artifactsEnabled: - env.DEVSPACE_ARTIFACTS === undefined - ? files.config.artifactsEnabled === true - : parseBoolean(env.DEVSPACE_ARTIFACTS), - artifactMaxFileBytes: parsePositiveInteger( - env.DEVSPACE_ARTIFACT_MAX_FILE_BYTES ?? numberConfigValue(files.config.artifactMaxFileBytes), - DEFAULT_ARTIFACT_MAX_FILE_BYTES, - "DEVSPACE_ARTIFACT_MAX_FILE_BYTES", - ), - skillsEnabled: env.DEVSPACE_SKILLS === undefined ? true : parseBoolean(env.DEVSPACE_SKILLS), - skillPaths: parsePathList(env.DEVSPACE_SKILL_PATHS), + toolMode: stored.tools.mode, + uiEnabled: stored.ui.enabled, + stateDir: normalizePath(stored.storage.stateDir), + worktreeRoot: normalizePath(stored.workspaces.worktreeRoot), + artifactsEnabled: stored.artifacts.enabled, + artifactMaxFileBytes: stored.artifacts.maxFileBytes, + skillsEnabled: stored.skills.enabled, + skillPaths: stored.skills.paths, devspaceSkillsDir: devspaceSkillsDir(env), devspaceAgentsDir: devspaceAgentsDir(env), - subagents: resolveSubagentsConfig(files.config.subagents, env), - agentDir: resolve(expandHomePath(env.DEVSPACE_AGENT_DIR ?? files.config.agentDir ?? defaultAgentDir())), - logging: parseLoggingConfig(env), + subagents: stored.subagents, + agentDir: normalizePath(stored.skills.agentDir), + logging: { + ...stored.logging, + trustProxy: stored.server.trustProxy, + }, }; } -function numberConfigValue(value: number | undefined): string | undefined { - return value === undefined ? undefined : String(value); +function normalizePaths(paths: string[], fallback: string[] = []): string[] { + return (paths.length > 0 ? paths : fallback).map(normalizePath); +} + +function normalizePath(path: string): string { + return resolve(expandHomePath(path)); +} + +function normalizeAllowedHosts(hosts: string[]): string[] { + if (hosts.includes("*")) return ["*"]; + return Array.from(new Set(hosts.map((host) => host.trim()).filter(Boolean))); +} + +function parseRequiredSecret(value: string | undefined): string { + const secret = value?.trim(); + if (!secret) { + throw new Error("OAuth owner token is required. Run: devspace init"); + } + if (secret.length < 16) { + throw new Error("OAuth owner token must be at least 16 characters long."); + } + return secret; } function parsePublicBaseUrl(value: string): string { diff --git a/src/local-agent-acp.test.ts b/src/local-agent-acp.test.ts index c4f58d482..39227fa66 100644 --- a/src/local-agent-acp.test.ts +++ b/src/local-agent-acp.test.ts @@ -254,11 +254,6 @@ if (completedOverlappingTurn.isErr()) throw completedOverlappingTurn.error; assert.equal(completedOverlappingTurn.value.finalResponse, "overlap response"); await overlapRuntime.close(); -let resolverCalls = 0; -const cachedDriver = new AcpLocalAgentDriver("cursor", {}, () => { - resolverCalls += 1; - return "/usr/local/bin/cursor-agent"; -}); const cachedContext = { agentId: "agt_acp", provider: "cursor" as const, @@ -266,16 +261,6 @@ const cachedContext = { writeMode: "allowed" as const, }; const resolvedProject = resolve("/tmp/project"); -assert.equal(cachedDriver.runtimeKey(cachedContext), `acp:cursor:/usr/local/bin/cursor-agent:allowed:${resolvedProject}`); -assert.equal(cachedDriver.runtimeKey(cachedContext), `acp:cursor:/usr/local/bin/cursor-agent:allowed:${resolvedProject}`); -for (const writeMode of ["read_only", "allowed", "full_access"] as const) { - assert.notEqual( - cachedDriver.runtimeKey({ ...cachedContext, writeMode, workspaceRoot: "/tmp/other-project" }), - cachedDriver.runtimeKey({ ...cachedContext, writeMode }), - `${writeMode} ACP runtimes are scoped to one workspace root`, - ); -} -assert.equal(resolverCalls, 1, "ACP executable identity is resolved once per driver lifecycle"); assert.deepEqual(acpCommandArgs("cursor", cachedContext), [ "acp", "--sandbox", "enabled", "--workspace", resolvedProject, ]); diff --git a/src/local-agent-adapters.test.ts b/src/local-agent-adapters.test.ts index 4938bd0bd..395072a1d 100644 --- a/src/local-agent-adapters.test.ts +++ b/src/local-agent-adapters.test.ts @@ -2,7 +2,6 @@ import assert from "node:assert/strict"; import { delimiter } from "node:path"; import { claudeCommandEnvironment, - createLocalAgentAdapter, extractOpenCodeFinalResponse, extractPiFinalResponse, extractPiProviderError, @@ -10,24 +9,6 @@ import { resolveAcpEffortConfigUpdate, } from "./local-agent-adapters.js"; import { removeDevspaceNodeModulesBinFromPath } from "./local-agent-path.js"; -import type { LocalAgentProvider } from "./local-agent-profiles.js"; - -const providers: LocalAgentProvider[] = [ - "codex", - "claude", - "opencode", - "pi", - "cursor", - "copilot", - "grok", -]; - -for (const provider of providers) { - const adapter = createLocalAgentAdapter(provider); - assert.equal(adapter.provider, provider); - assert.equal(typeof adapter.runtimeKey, "function"); -} - assert.deepEqual( resolveAcpModelConfigUpdate({ sessionId: "session_model_1", diff --git a/src/local-agent-adapters.ts b/src/local-agent-adapters.ts index 2b84c58b4..03a5cc40c 100644 --- a/src/local-agent-adapters.ts +++ b/src/local-agent-adapters.ts @@ -1,4 +1,3 @@ -import type { LocalAgentProvider } from "./local-agent-profiles.js"; import { AcpLocalAgentDriver, resolveAcpCommand, @@ -47,22 +46,6 @@ export function createLocalAgentDrivers( ]; } -export function createLocalAgentAdapter( - provider: LocalAgentProvider, - options: LocalAgentDriverOptions = {}, -): LocalAgentDriver { - switch (provider) { - case "codex": return new CodexLocalAgentDriver(options.env); - case "claude": return new ClaudeLocalAgentDriver(options.claudeQueryFactory, options.env); - case "opencode": return new OpencodeLocalAgentDriver(options.opencodeFactory); - case "pi": return new PiLocalAgentDriver(options.piSessionFactory); - case "cursor": - case "copilot": - case "grok": - return new AcpLocalAgentDriver(provider, options.env); - } -} - export function extractLocalAgentResponseText(value: unknown): string { return extractOpenCodeFinalResponse(value) || extractPiFinalResponse(value); } diff --git a/src/local-agent-availability.test.ts b/src/local-agent-availability.test.ts index 49d88aad5..7e0ebc1ce 100644 --- a/src/local-agent-availability.test.ts +++ b/src/local-agent-availability.test.ts @@ -1,48 +1,12 @@ import assert from "node:assert/strict"; -import { - checkLocalAgentProviderAvailability, - formatLocalAgentProviderAvailabilitySummary, - getLocalAgentProviderAvailabilitySnapshot, -} from "./local-agent-availability.js"; - -{ - const availability = checkLocalAgentProviderAvailability("codex"); - assert.equal(availability.name, "codex"); - assert.equal(typeof availability.available, "boolean"); - if (availability.available) { - assert.equal(availability.note, "available"); - } -} - -{ - const availability = checkLocalAgentProviderAvailability("codex", { - ...process.env, - CODEX_COMMAND: "/definitely/missing/devspace-codex", - }); - assert.equal(availability.available, false); - assert.match(availability.reason ?? "", /executable not found/); -} - -{ - assert.equal(checkLocalAgentProviderAvailability("pi").available, true); -} - -{ - const snapshot = getLocalAgentProviderAvailabilitySnapshot({ - ...process.env, - CODEX_COMMAND: "/definitely/missing/devspace-codex", - }); - assert.deepEqual( - snapshot.map((provider) => provider.name), - ["codex", "claude", "opencode", "pi", "cursor", "copilot", "grok"], - ); - assert.equal(snapshot.find((provider) => provider.name === "pi")?.available, true); -} - -assert.equal( - formatLocalAgentProviderAvailabilitySummary([ - { name: "codex", available: true, note: "available" }, - { name: "pi", available: false, reason: "pi executable not found" }, - ]), - "available: codex (available); unavailable: pi (pi executable not found)", -); +import { getLocalAgentProviderAvailabilitySnapshot } from "./local-agent-availability.js"; + +const snapshot = getLocalAgentProviderAvailabilitySnapshot({ + ...process.env, + CODEX_COMMAND: "/definitely/missing/devspace-codex", +}); +assert.deepEqual(snapshot.find((provider) => provider.name === "codex"), { + name: "codex", + available: false, + reason: "/definitely/missing/devspace-codex executable not found", +}); diff --git a/src/local-agent-availability.ts b/src/local-agent-availability.ts index 822ef6900..3a67b98f7 100644 --- a/src/local-agent-availability.ts +++ b/src/local-agent-availability.ts @@ -18,7 +18,7 @@ export function getLocalAgentProviderAvailabilitySnapshot( return LOCAL_AGENT_PROVIDERS.map((provider) => checkLocalAgentProviderAvailability(provider, env)); } -export function checkLocalAgentProviderAvailability( +function checkLocalAgentProviderAvailability( provider: LocalAgentProvider, env: NodeJS.ProcessEnv = process.env, ): LocalAgentProviderAvailability { @@ -51,21 +51,6 @@ export function assertLocalAgentProviderAvailable( ); } -export function formatLocalAgentProviderAvailabilitySummary( - providers: LocalAgentProviderAvailability[], -): string { - const available = providers - .filter((provider) => provider.available) - .map(formatAvailableProvider); - const unavailable = providers - .filter((provider) => !provider.available) - .map((provider) => `${provider.name} (${provider.reason ?? "unavailable"})`); - return [ - available.length > 0 ? `available: ${available.join(", ")}` : undefined, - unavailable.length > 0 ? `unavailable: ${unavailable.join(", ")}` : undefined, - ].filter(Boolean).join("; "); -} - function packageAvailability( provider: LocalAgentProvider, packageName: string, @@ -124,10 +109,6 @@ function resolveCommand(command: string, env: NodeJS.ProcessEnv): string | undef return undefined; } -function formatAvailableProvider(provider: LocalAgentProviderAvailability): string { - return provider.note ? `${provider.name} (${provider.note})` : provider.name; -} - function executableExists(command: string): boolean { const mode = process.platform === "win32" ? constants.F_OK : constants.X_OK; try { diff --git a/src/local-agent-claude.test.ts b/src/local-agent-claude.test.ts index 183d83ef1..e8b3d506b 100644 --- a/src/local-agent-claude.test.ts +++ b/src/local-agent-claude.test.ts @@ -121,6 +121,8 @@ assert.equal(query?.model, "sonnet"); assert.equal(lastOptions?.resume, undefined); assert.equal(lastOptions?.permissionMode, "dontAsk"); assert.equal(lastOptions?.allowDangerouslySkipPermissions, undefined); +assert.deepEqual(lastOptions?.allowedTools, ["Read(/**)", "Edit(/**)", "Bash"]); +assert.equal(lastOptions?.pathToClaudeCodeExecutable, undefined); const initialSandbox = lastOptions?.sandbox as Record; assert.equal(initialSandbox.enabled, true); assert.equal(initialSandbox.failIfUnavailable, true); @@ -131,17 +133,15 @@ assert.deepEqual((initialSandbox.filesystem as Record).denyWrit const allowedSettings = claudeAuthoritySettings("/tmp/project", "allowed"); const allowedPermissions = allowedSettings.permissions as Record; const allowedSandbox = allowedSettings.sandbox as Record; -assert.ok((allowedPermissions.allow as string[]).includes("Bash(*)")); +assert.deepEqual(allowedPermissions.deny, []); assert.deepEqual(allowedSandbox.filesystem, { allowWrite: ["/tmp/project"], denyWrite: [], - denyRead: (allowedSandbox.filesystem as Record).denyRead, - allowRead: ["/tmp/project"], }); const readOnlySettings = claudeAuthoritySettings("/tmp/project", "read_only"); const readOnlyPermissions = readOnlySettings.permissions as Record; -assert.equal((readOnlyPermissions.allow as string[]).some((rule) => rule.startsWith("Edit(")), false); -assert.ok((readOnlyPermissions.deny as string[]).includes("Bash(*)")); +assert.ok((readOnlyPermissions.deny as string[]).includes("Bash")); +assert.ok((readOnlyPermissions.deny as string[]).includes("Edit")); assert.deepEqual( ((readOnlySettings.sandbox as Record).filesystem as Record).allowWrite, [], @@ -161,8 +161,9 @@ assert.equal( "dontAsk", ); assert.equal(query?.flagSettings[1]?.effortLevel, "low"); -assert.ok( - ((query?.flagSettings[1]?.permissions as Record).allow as string[]).includes("Bash(*)"), +assert.equal( + ((query?.flagSettings[1]?.permissions as Record).deny as string[]).includes("Edit"), + false, ); assert.equal( (query?.flagSettings[2]?.permissions as Record).defaultMode, @@ -177,6 +178,15 @@ const coldRuntime = await driver.createRuntime({ ...context, providerSessionId: assert.equal(coldRuntime.isOk(), true); assert.equal(lastOptions?.resume, "cold_session"); +const customCommandDriver = new ClaudeLocalAgentDriver(({ prompt, options }) => { + lastOptions = options; + return new FakeClaudeQuery(prompt); +}, { CLAUDE_COMMAND: "/opt/claude" }); +const customCommandRuntime = await customCommandDriver.createRuntime(context); +assert.equal(customCommandRuntime.isOk(), true); +assert.equal(lastOptions?.pathToClaudeCodeExecutable, "/opt/claude"); +if (customCommandRuntime.isOk()) await customCommandRuntime.value.close(); + const cancelled = await new ClaudeLocalAgentDriver(async () => { throw new DOMException("cancelled", "AbortError"); }).createRuntime(context); diff --git a/src/local-agent-claude.ts b/src/local-agent-claude.ts index 16feed692..a639f2f46 100644 --- a/src/local-agent-claude.ts +++ b/src/local-agent-claude.ts @@ -1,6 +1,3 @@ -import { spawnSync } from "node:child_process"; -import { homedir } from "node:os"; -import { join } from "node:path"; import { AgentProviderExecutionError, AgentProviderProtocolError, @@ -21,6 +18,14 @@ import type { type ClaudePermissionMode = "default" | "acceptEdits" | "bypassPermissions" | "plan" | "dontAsk" | "auto"; +const CLAUDE_WORKSPACE_ALLOWED_TOOLS = [ + // allowedTools is passed as a session/CLI rule, so `/` is anchored to the query cwd. + "Read(/**)", + "Edit(/**)", + "Bash", +] as const; + + export interface ClaudeQueryLike extends AsyncIterable { close(): void; setPermissionMode(mode: ClaudePermissionMode): Promise; @@ -262,7 +267,7 @@ export function claudeQueryOptions( input: LocalAgentRunInput, env: NodeJS.ProcessEnv = process.env, ): Record { - const executable = env.CLAUDE_COMMAND ?? resolveExecutable("claude", env); + const executable = env.CLAUDE_COMMAND; const permissionMode = claudePermissionMode(input.writeMode); const authority = claudeAuthorityOptions(input.workspaceRoot, input.writeMode); return { @@ -271,6 +276,11 @@ export function claudeQueryOptions( ...(input.effort ? { thinking: { type: "adaptive" }, effort: input.effort } : {}), ...(context.providerSessionId ? { resume: context.providerSessionId } : {}), permissionMode, + // Restricted runtimes stay warm across read_only/allowed turns. Keep the + // workspace capabilities static and narrow individual turns with deny rules. + ...(input.writeMode === "full_access" + ? {} + : { allowedTools: [...CLAUDE_WORKSPACE_ALLOWED_TOOLS] }), sandbox: authority.sandbox, settings: authority.settings, ...(input.writeMode === "full_access" ? { allowDangerouslySkipPermissions: true } : {}), @@ -316,25 +326,10 @@ function claudeAuthorityOptions( }; } - const resolvedWorkspace = workspaceRoot.replaceAll("\\", "/"); - const workspaceRules = [ - `Read(${resolvedWorkspace}/**)`, - `Glob(${resolvedWorkspace}/**)`, - `Grep(${resolvedWorkspace}/**)`, - `LS(${resolvedWorkspace}/**)`, - ]; const allowed = writeMode !== "read_only"; - const protectedPaths = claudeProtectedPaths(); const permissions = { defaultMode: "dontAsk", - allow: [ - ...workspaceRules, - ...(allowed ? [`Edit(${resolvedWorkspace}/**)`, "Bash(*)"] : []), - ], - deny: [ - ...protectedPaths.map((path) => `Read(${path.replaceAll("\\", "/")}/**)`), - ...(allowed ? [] : ["Bash(*)", "Edit(*)", "Write(*)", "NotebookEdit(*)"]), - ], + deny: allowed ? [] : ["Bash", "Edit"], }; const sandbox = { enabled: true, @@ -344,25 +339,11 @@ function claudeAuthorityOptions( filesystem: { allowWrite: allowed ? [workspaceRoot] : [], denyWrite: allowed ? [] : [workspaceRoot], - denyRead: protectedPaths, - allowRead: [workspaceRoot], }, }; return { sandbox, settings: { permissions, sandbox } }; } -function claudeProtectedPaths(): string[] { - const home = homedir(); - return [ - join(home, ".ssh"), - join(home, ".aws"), - join(home, ".gnupg"), - join(home, ".config", "gcloud"), - join(home, ".netrc"), - join(home, ".npmrc"), - ]; -} - export function claudeCommandEnvironment(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv { const next = { ...env }; for (const key of [ @@ -395,18 +376,6 @@ export interface ClaudeUserMessage { parent_tool_use_id: null; } -function resolveExecutable(command: string, env: NodeJS.ProcessEnv): string | undefined { - const commandHasPath = command.includes("/") || command.includes("\\"); - if (commandHasPath) return command; - const result = spawnSync(process.platform === "win32" ? "where.exe" : "which", [command], { - encoding: "utf8", - env, - windowsHide: true, - }); - const executable = result.stdout?.split(/\r?\n/).find((line) => line.trim()); - return executable?.trim() || undefined; -} - function directString(value: unknown): string | undefined { return typeof value === "string" && value.trim() ? value.trim() : undefined; } diff --git a/src/local-agent-client.ts b/src/local-agent-client.ts index eb47ebafa..01f8c1cdc 100644 --- a/src/local-agent-client.ts +++ b/src/local-agent-client.ts @@ -49,6 +49,7 @@ import type { StartLocalAgentInput, } from "./local-agent-manager.js"; import type { LocalAgentRecord, LocalAgentWorkspaceScope } from "./local-agent-store.js"; +import { devspaceConfigDir } from "./user-config.js"; const DEFAULT_STARTUP_TIMEOUT_MS = 8_000; const DEFAULT_REQUEST_TIMEOUT_MS = 30_000; @@ -63,6 +64,7 @@ type RequestError = export interface LocalAgentClientOptions { stateDir: string; + configDir?: string; startupTimeoutMs?: number; requestTimeoutMs?: number; spawnDaemon?: () => void; @@ -84,7 +86,9 @@ export class LocalAgentClient { this.endpoint = options.endpoint ?? this.paths.endpoint; this.startupTimeoutMs = options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS; this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS; - this.spawnDaemon = options.spawnDaemon ?? (() => spawnLocalAgentDaemon(options.stateDir)); + this.spawnDaemon = options.spawnDaemon ?? (() => spawnLocalAgentDaemon( + options.configDir ?? devspaceConfigDir(), + )); } async run( @@ -404,21 +408,33 @@ export class LocalAgentClient { } } -export function createLocalAgentClient(config: Pick): LocalAgentClient { - return new LocalAgentClient({ stateDir: config.stateDir }); +export function createLocalAgentClient( + config: Pick, +): LocalAgentClient { + return new LocalAgentClient({ configDir: config.configDir, stateDir: config.stateDir }); } -export function spawnLocalAgentDaemon(stateDir: string, env: NodeJS.ProcessEnv = process.env): void { +export function spawnLocalAgentDaemon( + configDir: string, + env: NodeJS.ProcessEnv = process.env, +): void { const entrypoint = resolveDaemonEntrypoint(); const child = spawn(process.execPath, [...daemonExecArgv(process.execArgv), entrypoint], { detached: true, stdio: "ignore", windowsHide: true, - env: { ...env, DEVSPACE_STATE_DIR: stateDir }, + env: localAgentDaemonEnvironment(configDir, env), }); child.unref(); } +export function localAgentDaemonEnvironment( + configDir: string, + env: NodeJS.ProcessEnv, +): NodeJS.ProcessEnv { + return { ...env, DEVSPACE_CONFIG_DIR: configDir }; +} + export function daemonExecArgv(execArgv: readonly string[]): string[] { const result: string[] = []; for (let index = 0; index < execArgv.length; index += 1) { diff --git a/src/local-agent-codex.test.ts b/src/local-agent-codex.test.ts index 15a510f53..b5862d2b8 100644 --- a/src/local-agent-codex.test.ts +++ b/src/local-agent-codex.test.ts @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; -import { join, resolve } from "node:path"; +import { join } from "node:path"; import { tmpdir } from "node:os"; import { CodexAppServerRuntime, @@ -12,19 +12,7 @@ import { } from "./local-agent-codex.js"; import { toAgentErrorPayload } from "./local-agent-errors.js"; -let resolverCalls = 0; -const cachedDriver = new CodexLocalAgentDriver( - { CODEX_HOME: "/tmp/codex-home" }, - () => { - resolverCalls += 1; - return { executable: "/usr/local/bin/codex", version: "1.2.3" }; - }, -); const cachedContext = { agentId: "agt_test", provider: "codex" as const, workspaceRoot: "/tmp/project" }; -const resolvedCodexHome = resolve("/tmp/codex-home"); -assert.equal(cachedDriver.runtimeKey(cachedContext), `codex:/usr/local/bin/codex:${resolvedCodexHome}`); -assert.equal(cachedDriver.runtimeKey(cachedContext), `codex:/usr/local/bin/codex:${resolvedCodexHome}`); -assert.equal(resolverCalls, 1, "Codex executable identity is resolved once per driver lifecycle"); assert.equal(parseCodexVersion("codex-cli 0.9.1"), "0.9.1"); assert.equal(sandboxFor("read_only"), "read-only"); diff --git a/src/local-agent-config.test.ts b/src/local-agent-config.test.ts index 713fed653..e37ceafac 100644 --- a/src/local-agent-config.test.ts +++ b/src/local-agent-config.test.ts @@ -1,17 +1,17 @@ import assert from "node:assert/strict"; import { isSubagentProviderEnabled, - resolveSubagentsConfig, subagentProviderConfig, + subagentsConfigSchema, } from "./local-agent-config.js"; -const config = resolveSubagentsConfig({ +const config = subagentsConfigSchema.parse({ enabled: true, providers: [ { id: "codex", enabled: true, model: " gpt-5.4 ", effort: " high " }, { id: "claude", enabled: false, model: "sonnet" }, ], -}, {}); +}); assert.deepEqual(config, { enabled: true, providers: [ @@ -24,31 +24,24 @@ assert.equal(isSubagentProviderEnabled(config, "claude"), false); assert.equal(isSubagentProviderEnabled(config, "pi"), false); assert.equal(subagentProviderConfig(config, "codex")?.model, "gpt-5.4"); -assert.equal(resolveSubagentsConfig(config, { DEVSPACE_SUBAGENTS: "0" }).enabled, false); -assert.equal(resolveSubagentsConfig({ ...config, enabled: false }, { - DEVSPACE_SUBAGENTS: "1", -}).enabled, true); -assert.equal(resolveSubagentsConfig(undefined, {}).providers.length, 0); -assert.equal(resolveSubagentsConfig(true, {}).providers.length, 7); - assert.throws( - () => resolveSubagentsConfig({ + () => subagentsConfigSchema.parse({ enabled: true, providers: [{ id: "codex", enabled: true }, { id: "codex", enabled: false }], - }, {}), + }), /Duplicate subagent provider: codex/, ); assert.throws( - () => resolveSubagentsConfig({ + () => subagentsConfigSchema.parse({ enabled: true, providers: [{ id: "unknown", enabled: true }], - }, {}), + }), /Invalid option/, ); assert.throws( - () => resolveSubagentsConfig({ + () => subagentsConfigSchema.parse({ enabled: true, providers: [{ id: "codex", enabled: true, effort: " " }], - }, {}), + }), /Too small/, ); diff --git a/src/local-agent-config.ts b/src/local-agent-config.ts index 3f1de5aad..62e9c35a0 100644 --- a/src/local-agent-config.ts +++ b/src/local-agent-config.ts @@ -11,7 +11,7 @@ const providerSchema = z.object({ effort: z.string().trim().min(1).optional(), }).strict(); -const subagentsSchema = z.object({ +export const subagentsConfigSchema = z.object({ enabled: z.boolean(), providers: z.array(providerSchema), }).strict().superRefine((value, context) => { @@ -28,26 +28,14 @@ const subagentsSchema = z.object({ } }); -export type SubagentProviderConfig = z.infer; -export type SubagentsConfig = z.infer; -export type StoredSubagentsConfig = boolean | SubagentsConfig; +export const storedSubagentsConfigSchema = z.union([ + z.boolean(), + subagentsConfigSchema, +]); -export function resolveSubagentsConfig( - value: unknown, - env: NodeJS.ProcessEnv = process.env, -): SubagentsConfig { - const stored = value === undefined - ? { enabled: false, providers: [] } - : typeof value === "boolean" - ? legacySubagentsConfig(value) - : subagentsSchema.parse(value); - return { - ...stored, - enabled: env.DEVSPACE_SUBAGENTS === undefined - ? stored.enabled - : parseBoolean(env.DEVSPACE_SUBAGENTS), - }; -} +export type SubagentProviderConfig = z.infer; +export type SubagentsConfig = z.infer; +export type StoredSubagentsConfig = z.infer; export function subagentProviderConfig( config: SubagentsConfig, @@ -62,16 +50,3 @@ export function isSubagentProviderEnabled( ): boolean { return config.enabled && subagentProviderConfig(config, provider)?.enabled === true; } - -function legacySubagentsConfig(enabled: boolean): SubagentsConfig { - return { - enabled, - providers: enabled - ? LOCAL_AGENT_PROVIDERS.map((id) => ({ id, enabled: true })) - : [], - }; -} - -function parseBoolean(value: string): boolean { - return ["1", "true", "yes", "on"].includes(value.toLowerCase()); -} diff --git a/src/local-agent-daemon-lifecycle.test.ts b/src/local-agent-daemon-lifecycle.test.ts index b4438cdcb..41eea2f23 100644 --- a/src/local-agent-daemon-lifecycle.test.ts +++ b/src/local-agent-daemon-lifecycle.test.ts @@ -10,7 +10,6 @@ import { localAgentDaemonPaths, removeLocalAgentDaemonFiles, ensureLocalAgentDaemonSecret, - writeLocalAgentDaemonPid, } from "./local-agent-daemon-lifecycle.js"; const root = await mkdtemp(join(tmpdir(), "devspace-agentd-lifecycle-test-")); @@ -38,7 +37,6 @@ try { const recovered = new LocalAgentDaemonLock(paths); recovered.acquire(); assert.equal(await readFile(paths.lockPath, "utf8"), `${process.pid}\n`); - writeLocalAgentDaemonPid(paths); assert.equal(await readFile(paths.pidPath, "utf8"), `${process.pid}\n`); assert.equal(isProcessAlive(process.pid), true); recovered.release(); diff --git a/src/local-agent-daemon-lifecycle.ts b/src/local-agent-daemon-lifecycle.ts index 3b573b914..df0b81b95 100644 --- a/src/local-agent-daemon-lifecycle.ts +++ b/src/local-agent-daemon-lifecycle.ts @@ -116,10 +116,6 @@ export class LocalAgentDaemonLock { } } -export function writeLocalAgentDaemonPid(paths: LocalAgentDaemonPaths): void { - writeFileSecure(paths.pidPath, `${process.pid}\n`); -} - export function ensureLocalAgentDaemonSecret(paths: LocalAgentDaemonPaths): string { ensureLocalAgentDaemonStateDir(paths.stateDir); try { diff --git a/src/local-agent-daemon-protocol.test.ts b/src/local-agent-daemon-protocol.test.ts index a6dfe4cab..708180987 100644 --- a/src/local-agent-daemon-protocol.test.ts +++ b/src/local-agent-daemon-protocol.test.ts @@ -3,7 +3,6 @@ import { decodeAgentRecord, decodeLocalAgentDaemonRequest, decodeLocalAgentDaemonResponse, - encodeLocalAgentDaemonRequest, encodeLocalAgentDaemonResponse, LocalAgentDaemonProtocolError, } from "./local-agent-daemon-protocol.js"; @@ -24,7 +23,6 @@ const request = decodeLocalAgentDaemonRequest({ assert.equal(request.method, "agent.start"); if (request.method !== "agent.start") throw new Error("expected agent.start request"); assert.equal(request.params.writeMode, "read_only"); -assert.match(encodeLocalAgentDaemonRequest(request), /"method":"agent.start"/); const whitespaceRequest = decodeLocalAgentDaemonRequest({ requestId: "req_whitespace", diff --git a/src/local-agent-daemon.test.ts b/src/local-agent-daemon.test.ts index 3b5abef45..6ea66e652 100644 --- a/src/local-agent-daemon.test.ts +++ b/src/local-agent-daemon.test.ts @@ -5,7 +5,11 @@ import { mkdir, mkdtemp, rm } from "node:fs/promises"; import { createConnection, createServer as createNetServer } from "node:net"; import { join } from "node:path"; import { tmpdir } from "node:os"; -import { daemonExecArgv, LocalAgentClient } from "./local-agent-client.js"; +import { + daemonExecArgv, + localAgentDaemonEnvironment, + LocalAgentClient, +} from "./local-agent-client.js"; import { LocalAgentDaemon, type LocalAgentDaemonManager } from "./local-agent-daemon.js"; import { ensureLocalAgentDaemonSecret, @@ -112,6 +116,12 @@ assert.deepEqual( "detached daemon startup must not inherit inspector flags", ); +assert.deepEqual( + localAgentDaemonEnvironment("/alternate/config", { PATH: "/bin" }), + { PATH: "/bin", DEVSPACE_CONFIG_DIR: "/alternate/config" }, + "the daemon must reload the same persisted configuration as its client", +); + let shutdownSocket: ReturnType | undefined; try { const started = unwrap(await client.run({ diff --git a/src/local-agent-pi.test.ts b/src/local-agent-pi.test.ts index f404105e9..bc0dd1685 100644 --- a/src/local-agent-pi.test.ts +++ b/src/local-agent-pi.test.ts @@ -1,13 +1,10 @@ import assert from "node:assert/strict"; -import { basename } from "node:path"; import type { AgentSessionEvent, AgentSessionEventListener } from "@earendil-works/pi-coding-agent"; import { PiLocalAgentDriver, - piToolsForWriteMode, type PiSessionFactory, type PiSessionLike, } from "./local-agent-pi.js"; -import { createPiSandboxConfig } from "./local-agent-pi-sandbox.js"; import { LocalAgentRuntimePool } from "./local-agent-runtime-pool.js"; import type { LocalAgentRuntimeContext } from "./local-agent-runtime.js"; @@ -105,11 +102,6 @@ assert.equal(second.value.finalResponse, "response:second"); assert.deepEqual(sessions[0]?.model, { id: "model" }); assert.equal(sessions[0]?.effort, "high"); assert.deepEqual(sessionIds, ["pi_session_1"]); -assert.deepEqual(piToolsForWriteMode("allowed"), ["read", "grep", "find", "ls", "edit", "write", "bash"]); -assert.ok( - createPiSandboxConfig().filesystem.denyRead.some((path) => basename(path) === ".ssh"), - "sandbox config includes the protected-home read rule; enforcement is covered by local-agent-pi-sandbox.test.ts", -); assert.deepEqual(sessions[0]?.activeTools, ["read", "grep", "find", "ls"]); assert.deepEqual(sessions[0]?.toolHistory, [ ["read", "grep", "find", "ls"], diff --git a/src/local-agent-presentation.test.ts b/src/local-agent-presentation.test.ts index cd12d54e3..ea1a4d6e6 100644 --- a/src/local-agent-presentation.test.ts +++ b/src/local-agent-presentation.test.ts @@ -1,9 +1,6 @@ import assert from "node:assert/strict"; import type { LocalAgentCatalog } from "./local-agent-catalog.js"; import { - formatAgentObservation, - formatAgentSummary, - formatAgentTargetCatalog, presentAgentObservation, presentAgentReceipt, presentAgentSummary, @@ -35,7 +32,6 @@ assert.deepEqual(presentAgentSummary({ ...record, status: "idle" }), { status: "completed", target: "reviewer", }); -assert.equal(formatAgentSummary(presentAgentSummary(record)), "agt_test running reviewer"); const completed = presentAgentObservation({ ...record, @@ -47,7 +43,6 @@ assert.deepEqual(completed, { status: "completed", response: "Found one issue.", }); -assert.equal(formatAgentObservation(completed), "agt_test completed\n\nFound one issue."); const failed = presentAgentObservation({ ...record, @@ -66,10 +61,6 @@ assert.deepEqual(failed, { retryable: true, }, }); -assert.equal( - formatAgentObservation(failed), - "agt_test failed PROVIDER_EXECUTION_ERROR: Provider disconnected. [retryable]", -); const catalog: LocalAgentCatalog = { enabled: true, @@ -105,10 +96,3 @@ assert.deepEqual(targetCatalog, { }, ], }); -assert.equal( - formatAgentTargetCatalog(targetCatalog), - [ - "codex [provider] model=gpt-5.4 effort=high", - "reviewer [profile, codex] model=gpt-5.4 effort=high - Review changes.", - ].join("\n"), -); diff --git a/src/local-agent-profiles.test.ts b/src/local-agent-profiles.test.ts index 6868e140e..d802c17f3 100644 --- a/src/local-agent-profiles.test.ts +++ b/src/local-agent-profiles.test.ts @@ -3,7 +3,8 @@ import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { loadConfig } from "./config.js"; -import { loadLocalAgentProfiles, summarizeLocalAgentProfile } from "./local-agent-profiles.js"; +import { loadLocalAgentProfiles } from "./local-agent-profiles.js"; +import { writeTestDevspaceConfig } from "./test-support/config.test.js"; const root = await mkdtemp(join(tmpdir(), "devspace-agent-profiles-test-")); @@ -57,12 +58,10 @@ try { ].join("\n"), ); - const enabledConfig = loadConfig({ - DEVSPACE_CONFIG_DIR: configDir, - DEVSPACE_ALLOWED_ROOTS: workspaceRoot, - DEVSPACE_SUBAGENTS: "1", - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", - }); + const enabledConfig = loadConfig(writeTestDevspaceConfig(configDir, { + workspaces: { allowedRoots: [workspaceRoot] }, + subagents: { enabled: true, providers: [] }, + })); const profiles = await loadLocalAgentProfiles(enabledConfig, workspaceRoot); assert.equal(profiles.length, 1); @@ -72,14 +71,6 @@ try { assert.equal(profiles[0]?.model, "sonnet"); assert.equal(profiles[0]?.effort, "high"); assert.equal(profiles[0]?.body, "Project body."); - assert.deepEqual(summarizeLocalAgentProfile(profiles[0]!), { - name: "reviewer", - description: "Project reviewer #1.", - provider: "claude", - model: "sonnet", - effort: "high", - }); - await writeFile( join(workspaceRoot, ".devspace", "agents", "custom.md"), [ @@ -96,12 +87,10 @@ try { const profilesWithInvalid = await loadLocalAgentProfiles(enabledConfig, workspaceRoot); assert.deepEqual(profilesWithInvalid.map((profile) => profile.name), ["reviewer"]); - const disabledConfig = loadConfig({ - DEVSPACE_CONFIG_DIR: configDir, - DEVSPACE_ALLOWED_ROOTS: workspaceRoot, - DEVSPACE_SUBAGENTS: "0", - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", - }); + const disabledConfig = loadConfig(writeTestDevspaceConfig(configDir, { + workspaces: { allowedRoots: [workspaceRoot] }, + subagents: { enabled: false, providers: [] }, + })); assert.deepEqual(await loadLocalAgentProfiles(disabledConfig, workspaceRoot), []); } finally { await rm(root, { recursive: true, force: true }); diff --git a/src/local-agent-profiles.ts b/src/local-agent-profiles.ts index af532e49d..ad99a225c 100644 --- a/src/local-agent-profiles.ts +++ b/src/local-agent-profiles.ts @@ -67,18 +67,6 @@ export async function loadLocalAgentProfiles( .sort((a, b) => a.name.localeCompare(b.name)); } -export function summarizeLocalAgentProfile( - profile: LocalAgentProfile, -): LocalAgentProfileSummary { - return { - name: profile.name, - description: profile.description, - provider: profile.provider, - model: profile.model, - effort: profile.effort, - }; -} - async function loadProfilesFromDirectory(directory: string): Promise { const resolvedDirectory = resolve(directory); if (!existsSync(resolvedDirectory)) return []; diff --git a/src/local-agent-targets.test.ts b/src/local-agent-targets.test.ts index a3a70160f..fde0c000e 100644 --- a/src/local-agent-targets.test.ts +++ b/src/local-agent-targets.test.ts @@ -1,6 +1,5 @@ import assert from "node:assert/strict"; import { - formatAvailableLocalAgentTargets, parseLocalAgentRunArgs, resolveLocalAgentTarget, } from "./local-agent-targets.js"; @@ -147,5 +146,3 @@ assert.deepEqual(parseLocalAgentRunArgs(["codex", "--", "--json", "literal"]), { } assert.equal(resolveLocalAgentTarget("missing", profiles), undefined); -assert.match(formatAvailableLocalAgentTargets(profiles), /profiles: reviewer, claude/); -assert.match(formatAvailableLocalAgentTargets([]), /providers: codex, claude, opencode, pi, cursor, copilot, grok/); diff --git a/src/local-agent-targets.ts b/src/local-agent-targets.ts index 5c852f0e5..367b04761 100644 --- a/src/local-agent-targets.ts +++ b/src/local-agent-targets.ts @@ -156,12 +156,3 @@ export function resolveLocalAgentTarget( return undefined; } - -export function formatAvailableLocalAgentTargets(profiles: LocalAgentProfile[]): string { - const profileNames = profiles.map((profile) => profile.name); - const parts = [ - profileNames.length > 0 ? `profiles: ${profileNames.join(", ")}` : undefined, - `providers: ${LOCAL_AGENT_PROVIDERS.join(", ")}`, - ].filter(Boolean); - return parts.join("; "); -} diff --git a/src/onboarding.test.ts b/src/onboarding.test.ts index ac26f78ca..b4236e9c0 100644 --- a/src/onboarding.test.ts +++ b/src/onboarding.test.ts @@ -2,8 +2,6 @@ import assert from "node:assert/strict"; import { resolveOnboardingUsage, updateOnboardingSubagentsConfig, - usesChatGpt, - usesCodingAgents, } from "./onboarding.js"; for (const [selections, expected] of [ @@ -13,10 +11,6 @@ for (const [selections, expected] of [ ] as const) { assert.equal(resolveOnboardingUsage(selections), expected); } -assert.equal(usesChatGpt("both"), true); -assert.equal(usesCodingAgents("both"), true); -assert.equal(usesChatGpt("coding-agents"), false); -assert.equal(usesCodingAgents("chatgpt"), false); assert.throws(() => resolveOnboardingUsage([]), /Choose ChatGPT, Coding Agents, or both/); assert.deepEqual( diff --git a/src/pi-tools.ts b/src/pi-tools.ts index 238b9c547..06f821976 100644 --- a/src/pi-tools.ts +++ b/src/pi-tools.ts @@ -1,17 +1,11 @@ import { createBashTool, createEditTool, - createFindTool, - createGrepTool, - createLsTool, createReadTool, createWriteTool, type BashToolInput, type EditToolInput, type EditToolDetails, - type FindToolInput, - type GrepToolInput, - type LsToolInput, type ReadToolInput, type WriteToolInput, type AgentToolResult, @@ -97,27 +91,6 @@ export async function editFileTool(input: EditToolInput, context: ToolContext): }, context); } -export async function grepFilesTool(input: GrepToolInput, context: ToolContext): Promise { - if (input.path) resolveAllowedPath(input.path, context.cwd, [context.root]); - const tool = createGrepTool(context.cwd); - - return runTool((params) => tool.execute("grep_files", params), input, context); -} - -export async function findFilesTool(input: FindToolInput, context: ToolContext): Promise { - if (input.path) resolveAllowedPath(input.path, context.cwd, [context.root]); - const tool = createFindTool(context.cwd); - - return runTool((params) => tool.execute("find_files", params), input, context); -} - -export async function listDirectoryTool(input: LsToolInput, context: ToolContext): Promise { - if (input.path) resolveAllowedPath(input.path, context.cwd, [context.root]); - const tool = createLsTool(context.cwd); - - return runTool((params) => tool.execute("list_directory", params), input, context); -} - export async function runShellTool(input: BashToolInput, context: ToolContext): Promise { const tool = createBashTool(context.cwd); const timeout = input.timeout === undefined ? 30 : Math.min(input.timeout, 300); diff --git a/src/request-meta.test.ts b/src/request-meta.test.ts index effd3dd09..5a6c7b02d 100644 --- a/src/request-meta.test.ts +++ b/src/request-meta.test.ts @@ -2,31 +2,17 @@ import assert from "node:assert/strict"; import test from "node:test"; import { openAiConversationScopeId } from "./request-meta.js"; -test("undefined request metadata has no conversation scope", () => { - assert.equal(openAiConversationScopeId(undefined), undefined); -}); - -test("missing session metadata has no conversation scope", () => { - assert.equal(openAiConversationScopeId({}), undefined); -}); - -test("an empty session string has no conversation scope", () => { - assert.equal(openAiConversationScopeId({ "openai/session": "" }), undefined); -}); - -test("a non-string session value has no conversation scope", () => { - assert.equal(openAiConversationScopeId({ "openai/session": 42 }), undefined); - assert.equal(openAiConversationScopeId({ "openai/session": {} }), undefined); -}); - -test("valid OpenAI session metadata returns the raw opaque session value", () => { - assert.equal( - openAiConversationScopeId({ "openai/session": "chat-session-opaque-value" }), - "chat-session-opaque-value", - ); -}); +test("OpenAI conversation scope accepts only a non-empty session string", () => { + for (const meta of [ + undefined, + {}, + { "openai/session": "" }, + { "openai/session": 42 }, + { "openai/session": {} }, + ]) { + assert.equal(openAiConversationScopeId(meta), undefined); + } -test("unrelated metadata fields do not alter the selected conversation scope", () => { assert.equal( openAiConversationScopeId({ "openai/session": "chat-session-opaque-value", diff --git a/src/review-checkpoints.test.ts b/src/review-checkpoints.test.ts index 0c2aeb7bd..37ee2c558 100644 --- a/src/review-checkpoints.test.ts +++ b/src/review-checkpoints.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import test, { type TestContext } from "node:test"; import { promisify } from "node:util"; -import { createReviewCheckpointManager } from "./review-checkpoints.js"; +import { createReviewCheckpointManager, readReviewRef } from "./review-checkpoints.js"; const execFileAsync = promisify(execFile); @@ -18,7 +18,24 @@ test("a clean workspace reports no changes from the last-shown checkpoint", asyn assert.equal(clean.summary.files, 0); assert.equal(clean.patch, ""); - assert.match(clean.result, /No changes since last shown changes/); +}); + +test("initialization reports whether aggregate review is available", async (t) => { + const gitRoot = await committedRepository(t); + const plainRoot = await mkdtemp(join(tmpdir(), "devspace-review-plain-test-")); + t.after(() => rm(plainRoot, { recursive: true, force: true })); + const manager = createReviewCheckpointManager(); + + assert.deepEqual( + await manager.initializeWorkspace({ workspaceId: "ws_git", root: gitRoot }), + { available: true }, + ); + const unavailable = await manager.initializeWorkspace({ + workspaceId: "ws_plain", + root: plainRoot, + }); + assert.equal(unavailable.available, false); + if (!unavailable.available) assert.match(unavailable.reason, /git repository/i); }); test("show_changes reports and advances the last-shown checkpoint", async (t) => { @@ -44,12 +61,62 @@ test("show_changes reports and advances the last-shown checkpoint", async (t) => markReviewed: true, }); assert.equal(markedReviewed.summary.files, 2); + assert.match(markedReviewed.reviewRef, /^[0-9a-f]{40,64}$/); + + const restored = await manager.reviewByRef({ + workspaceId: "ws_incremental", + root, + reviewRef: markedReviewed.reviewRef, + }); + assert.deepEqual(restored.summary, markedReviewed.summary); + assert.deepEqual(restored.files, markedReviewed.files); + assert.equal(restored.patch, markedReviewed.patch); const afterReviewed = await manager.reviewChanges({ workspaceId: "ws_incremental", root }); assert.equal(afterReviewed.summary.files, 0); assert.equal(afterReviewed.patch, ""); }); +test("historical review refs survive later reviews and manager restarts", async (t) => { + const root = await committedRepository(t); + const manager = createReviewCheckpointManager(); + await manager.initializeWorkspace({ workspaceId: "ws_history", root }); + + await writeFile(join(root, "README.md"), "hello\nfirst\n"); + const first = await manager.reviewChanges({ workspaceId: "ws_history", root }); + + await writeFile(join(root, "README.md"), "hello\nfirst\nsecond\n"); + const second = await manager.reviewChanges({ workspaceId: "ws_history", root }); + assert.notEqual(first.reviewRef, second.reviewRef); + + const restarted = createReviewCheckpointManager(); + const restoredFirst = await restarted.reviewByRef({ + workspaceId: "ws_history", + root, + reviewRef: first.reviewRef, + }); + assert.deepEqual(restoredFirst.summary, first.summary); + assert.equal(restoredFirst.patch, first.patch); + assert.match(restoredFirst.patch, /\+first/); + assert.doesNotMatch(restoredFirst.patch, /\+second/); +}); + +test("review refs are scoped to the workspace review history", async (t) => { + const root = await committedRepository(t); + const manager = createReviewCheckpointManager(); + await manager.initializeWorkspace({ workspaceId: "ws_scoped", root }); + + const head = await gitOutput(root, ["rev-parse", "HEAD"]); + await assert.rejects( + () => manager.reviewByRef({ workspaceId: "ws_scoped", root, reviewRef: head }), + /Unknown review reference/, + ); + await assert.rejects( + () => readReviewRef(root, head), + /Unknown DevSpace review reference/, + ); +}); + test("review checkpoints survive a manager restart", async (t) => { const root = await committedRepository(t); const manager = createReviewCheckpointManager(); @@ -108,7 +175,6 @@ test("a missing last-shown checkpoint falls back after restart and can be re-est markReviewed: false, }); assert.equal(fallback.summary.files, 1); - assert.match(fallback.result, /compared from workspace open/); assert.match(fallback.patch, /changed/); const reestablished = await restartedManager.reviewChanges({ @@ -117,7 +183,6 @@ test("a missing last-shown checkpoint falls back after restart and can be re-est markReviewed: true, }); assert.equal(reestablished.summary.files, 1); - assert.match(reestablished.result, /baseline was re-established/); const afterReestablished = await restartedManager.reviewChanges({ workspaceId: "ws_missing_baseline", @@ -228,3 +293,7 @@ async function deleteReviewRef( async function git(cwd: string, args: string[]): Promise { await execFileAsync("git", args, { cwd }); } + +async function gitOutput(cwd: string, args: string[]): Promise { + return (await execFileAsync("git", args, { cwd })).stdout.trim(); +} diff --git a/src/review-checkpoints.ts b/src/review-checkpoints.ts index 0fd8bf361..6f6872752 100644 --- a/src/review-checkpoints.ts +++ b/src/review-checkpoints.ts @@ -20,12 +20,17 @@ export interface ReviewFile { } export interface ReviewChangesResult { + reviewRef: string; result: string; summary: ReviewSummary; files: ReviewFile[]; patch: string; } +export type ReviewAvailability = + | { available: true } + | { available: false; reason: string }; + interface WorkspaceReviewState { root: string; gitRoot?: string; @@ -37,13 +42,18 @@ interface WorkspaceReviewState { } export interface ReviewCheckpointManager { - initializeWorkspace(input: { workspaceId: string; root: string }): Promise; + initializeWorkspace(input: { workspaceId: string; root: string }): Promise; reviewChanges(input: { workspaceId: string; root: string; since?: ReviewSince; markReviewed?: boolean; }): Promise; + reviewByRef(input: { + workspaceId: string; + root: string; + reviewRef: string; + }): Promise; } const REVIEW_REF_PREFIX = "refs/devspace/review"; @@ -57,14 +67,15 @@ export function createReviewCheckpointManager(): ReviewCheckpointManager { const existingState = states.get(workspaceId); assertWorkspaceRoot(existingState, workspaceId, root); if (existingState?.root === root && existingState.gitRoot !== undefined) { - return; + return reviewAvailability(existingState); } const pending = initializations.get(workspaceId); if (pending) { await pending; - assertWorkspaceRoot(states.get(workspaceId), workspaceId, root); - return; + const initializedState = states.get(workspaceId); + assertWorkspaceRoot(initializedState, workspaceId, root); + return reviewAvailability(initializedState); } const initialize = initializeWorkspaceState(states, workspaceId, root); @@ -76,6 +87,7 @@ export function createReviewCheckpointManager(): ReviewCheckpointManager { initializations.delete(workspaceId); } } + return reviewAvailability(states.get(workspaceId)); }, async reviewChanges({ workspaceId, root, since = "last_shown", markReviewed = true }) { @@ -107,15 +119,8 @@ export function createReviewCheckpointManager(): ReviewCheckpointManager { const baselineRef = effectiveSince === "workspace_open" ? state.openRef : state.baselineRef; const baseline = (await git(state.gitRoot, ["rev-parse", "--verify", `${baselineRef}^{commit}`])).stdout.trim(); - const current = await createWorkingTreeSnapshot(state.gitRoot); - const patch = (await git(state.gitRoot, ["diff", "--binary", "--no-color", baseline, current], { - maxBuffer: 50 * 1024 * 1024, - })).stdout; - const numstat = (await git(state.gitRoot, ["diff", "--numstat", "-z", baseline, current], { - maxBuffer: 50 * 1024 * 1024, - })).stdout; - const files = parseNumstat(numstat); - const summary = summarizeFiles(files); + const current = await createWorkingTreeSnapshot(state.gitRoot, baseline); + const review = await readReviewBetween(state.gitRoot, baseline, current); if (markReviewed) { await git(state.gitRoot, ["update-ref", state.baselineRef, current]); @@ -126,19 +131,69 @@ export function createReviewCheckpointManager(): ReviewCheckpointManager { ? ` The last-shown checkpoint was missing, so changes were compared from workspace open${markReviewed ? " and the baseline was re-established" : ""}.` : ""; return { + reviewRef: current, result: `${ - summary.files === 0 + review.summary.files === 0 ? `No changes since ${effectiveSince === "workspace_open" ? "workspace open" : "last shown changes"}.` - : `Changed ${summary.files} ${summary.files === 1 ? "file" : "files"} (+${summary.additions} -${summary.removals}).` + : formatChangedFiles(review.summary) }${fallbackNote}`, - summary, - files, - patch, + ...review, }; }, + + async reviewByRef({ workspaceId, root, reviewRef }) { + let state = states.get(workspaceId); + assertWorkspaceRoot(state, workspaceId, root); + if (!isReadyState(state)) { + await this.initializeWorkspace({ workspaceId, root }); + state = states.get(workspaceId); + } + assertWorkspaceRoot(state, workspaceId, root); + + if (!state?.gitRoot) { + throw new Error(state?.diagnostic ?? "show_changes requires a Git workspace in this version."); + } + + const [openCommit, baselineCommit, reviewCommit] = await Promise.all([ + commitForRef(state.gitRoot, state.openRef), + commitForRef(state.gitRoot, state.baselineRef), + resolveReviewCommitOrUndefined(state.gitRoot, reviewRef), + ]); + if ( + !openCommit + || !baselineCommit + || !reviewCommit + || reviewCommit === openCommit + ) { + throw new Error(`Unknown review reference for workspace ${workspaceId}: ${reviewRef}`); + } + + const [isAfterOpen, isBeforeBaseline] = await Promise.all([ + isAncestor(state.gitRoot, openCommit, reviewCommit), + isAncestor(state.gitRoot, reviewCommit, baselineCommit), + ]); + if (!isAfterOpen || !isBeforeBaseline) { + throw new Error(`Unknown review reference for workspace ${workspaceId}: ${reviewRef}`); + } + + return readReviewCommit(state.gitRoot, reviewCommit); + }, }; } +export async function readReviewRef(root: string, reviewRef: string): Promise { + const eligibility = await getGitEligibility(root); + if (!eligibility.ok || !eligibility.gitRoot) { + throw new Error(eligibility.message ?? "show-changes requires a Git workspace."); + } + + const commit = await resolveReviewCommit(eligibility.gitRoot, reviewRef); + if (!await isKnownReviewCommit(eligibility.gitRoot, commit)) { + throw new Error(`Unknown DevSpace review reference: ${reviewRef}`); + } + return readReviewCommit(eligibility.gitRoot, commit); +} + function assertWorkspaceRoot( state: WorkspaceReviewState | undefined, workspaceId: string, @@ -175,7 +230,8 @@ async function initializeWorkspaceState( ]); if (!openCommit && !baselineCommit) { - const initialCommit = await createWorkingTreeSnapshot(eligibility.gitRoot); + const head = (await git(eligibility.gitRoot, ["rev-parse", "--verify", "HEAD^{commit}"])).stdout.trim(); + const initialCommit = await createWorkingTreeSnapshot(eligibility.gitRoot, head); await git(eligibility.gitRoot, ["update-ref", state.openRef, initialCommit]); await git(eligibility.gitRoot, ["update-ref", state.baselineRef, initialCommit]); state.openRefAvailable = true; @@ -193,6 +249,15 @@ async function initializeWorkspaceState( } } +function reviewAvailability(state: WorkspaceReviewState | undefined): ReviewAvailability { + return state?.gitRoot + ? { available: true } + : { + available: false, + reason: state?.diagnostic ?? "show_changes is unavailable for this workspace.", + }; +} + function isReadyState(state: WorkspaceReviewState | undefined): boolean { return state?.gitRoot !== undefined; } @@ -215,7 +280,7 @@ function reviewRefs( }; } -async function createWorkingTreeSnapshot(gitRoot: string): Promise { +async function createWorkingTreeSnapshot(gitRoot: string, parent: string): Promise { const tempDir = await mkdtemp(join(tmpdir(), "devspace-review-index-")); const indexPath = join(tempDir, "index"); const env = checkpointEnv(indexPath); @@ -224,13 +289,112 @@ async function createWorkingTreeSnapshot(gitRoot: string): Promise { await git(gitRoot, ["read-tree", "HEAD"], { env }); await git(gitRoot, ["add", "-A", "--", "."], { env }); const tree = (await git(gitRoot, ["write-tree"], { env })).stdout.trim(); - const parent = (await git(gitRoot, ["rev-parse", "--verify", "HEAD^{commit}"])).stdout.trim(); return (await git(gitRoot, ["commit-tree", tree, "-p", parent, "-m", "DevSpace review snapshot"], { env })).stdout.trim(); } finally { await rm(tempDir, { recursive: true, force: true }); } } +async function readReviewCommit(gitRoot: string, reviewRef: string): Promise { + const parent = (await git(gitRoot, ["rev-parse", "--verify", `${reviewRef}^1`])).stdout.trim(); + const review = await readReviewBetween(gitRoot, parent, reviewRef); + return { + reviewRef, + result: review.summary.files === 0 ? "No changes in this review." : formatChangedFiles(review.summary), + ...review, + }; +} + +async function readReviewBetween( + gitRoot: string, + before: string, + after: string, +): Promise> { + const patch = (await git(gitRoot, ["diff", "--binary", "--no-color", before, after], { + maxBuffer: 50 * 1024 * 1024, + })).stdout; + const numstat = (await git(gitRoot, ["diff", "--numstat", "-z", before, after], { + maxBuffer: 50 * 1024 * 1024, + })).stdout; + const files = parseNumstat(numstat); + return { + summary: summarizeFiles(files), + files, + patch, + }; +} + +async function resolveReviewCommit(gitRoot: string, reviewRef: string): Promise { + if (!isReviewRef(reviewRef)) { + throw new Error(`Invalid review reference: ${reviewRef}`); + } + return (await git(gitRoot, ["rev-parse", "--verify", `${reviewRef}^{commit}`])).stdout.trim(); +} + +async function resolveReviewCommitOrUndefined( + gitRoot: string, + reviewRef: string, +): Promise { + try { + return await resolveReviewCommit(gitRoot, reviewRef); + } catch { + return undefined; + } +} + +async function isAncestor(gitRoot: string, ancestor: string, descendant: string): Promise { + try { + await git(gitRoot, ["merge-base", "--is-ancestor", ancestor, descendant]); + return true; + } catch { + return false; + } +} + +async function isKnownReviewCommit(gitRoot: string, reviewCommit: string): Promise { + const refs = (await git(gitRoot, [ + "for-each-ref", + "--format=%(refname)\t%(objectname)", + REVIEW_REF_PREFIX, + ])).stdout.trim(); + if (!refs) return false; + + const histories = new Map(); + for (const line of refs.split("\n")) { + const [ref, commit] = line.split("\t"); + if (!ref || !commit) continue; + + const match = ref.match(/^refs\/devspace\/review\/(.+)\/(open|baseline)$/); + if (!match) continue; + const [, workspace, kind] = match; + if (!workspace || !kind) continue; + + const history = histories.get(workspace) ?? {}; + history[kind as "open" | "baseline"] = commit; + histories.set(workspace, history); + } + + const memberships = await Promise.all( + [...histories.values()].map(async ({ open, baseline }) => { + if (!open || !baseline || reviewCommit === open) return false; + const [isAfterOpen, isBeforeBaseline] = await Promise.all([ + isAncestor(gitRoot, open, reviewCommit), + isAncestor(gitRoot, reviewCommit, baseline), + ]); + return isAfterOpen && isBeforeBaseline; + }), + ); + return memberships.some(Boolean); +} + +function isReviewRef(value: string): boolean { + return /^[0-9a-f]{40,64}$/.test(value); +} + +function formatChangedFiles(summary: ReviewSummary): string { + return `Changed ${summary.files} ${summary.files === 1 ? "file" : "files"} (+${summary.additions} -${summary.removals}).`; +} + function checkpointEnv(indexPath: string): NodeJS.ProcessEnv { return { GIT_INDEX_FILE: indexPath, diff --git a/src/server.test.ts b/src/server.test.ts index cb29d11c4..79c21a66c 100644 --- a/src/server.test.ts +++ b/src/server.test.ts @@ -7,7 +7,7 @@ import test, { type TestContext } from "node:test"; import { promisify } from "node:util"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; -import { loadConfig, type ServerConfig } from "./config.js"; +import { loadConfig, type ServerConfig, type ToolMode } from "./config.js"; import type { LocalAgentProviderAvailability } from "./local-agent-availability.js"; import { buildLocalAgentProviderStatuses } from "./local-agent-catalog.js"; import type { SubagentsConfig } from "./local-agent-config.js"; @@ -16,9 +16,155 @@ import { ProcessSessionManager } from "./process-sessions.js"; import { createMcpServer } from "./server.js"; import { SqliteWorkspaceStore } from "./workspace-store.js"; import { WorkspaceRegistry } from "./workspaces.js"; +import { writeTestDevspaceConfig } from "./test-support/config.test.js"; const execFileAsync = promisify(execFile); +test("tool modes expose the expected host-facing tool surface", async (t) => { + const cases: Array<{ + mode: ToolMode; + expected: string[]; + }> = [ + { + mode: "claude", + expected: ["open_workspace", "read", "write", "edit", "bash", "show_changes"], + }, + { + mode: "codex", + expected: ["open_workspace", "read", "apply_patch", "exec_command", "write_stdin", "show_changes"], + }, + ]; + + for (const { mode, expected } of cases) { + await t.test(mode, async (nested) => { + const context = await fixture(nested, { toolMode: mode, uiEnabled: false }); + const tools = await context.client.listTools(); + + assert.deepEqual( + tools.tools.map((tool) => tool.name).sort(), + expected.sort(), + ); + }); + } +}); + +test("UI metadata is limited to workspace and aggregate review", async (t) => { + for (const uiEnabled of [true, false]) { + await t.test(uiEnabled ? "enabled" : "disabled", async (nested) => { + const context = await fixture(nested, { toolMode: "claude", uiEnabled }); + const tools = await context.client.listTools(); + const toolsWithUi = tools.tools + .filter((tool) => Boolean((tool._meta as { ui?: unknown } | undefined)?.ui)) + .map((tool) => tool.name) + .sort(); + + assert.deepEqual(toolsWithUi, uiEnabled ? ["open_workspace", "show_changes"] : []); + }); + } +}); + +test("open_workspace reports aggregate review availability", async (t) => { + const plain = await fixture(t); + const gitWorkspace = await fixture(t, { git: true }); + + const plainReview = structuredContent(await callOpen(plain.client, plain.project, "plain")).review; + const gitReview = structuredContent(await callOpen(gitWorkspace.client, gitWorkspace.project, "git")).review; + + assert.equal((plainReview as { available: boolean }).available, false); + assert.deepEqual(gitReview, { available: true }); +}); + +test("show_changes keeps model output compact and preserves the rich review card", async (t) => { + const context = await fixture(t, { git: true, uiEnabled: false }); + const opened = structuredContent( + await callOpen(context.client, context.project, "review"), + ); + const workspaceId = opened.workspaceId; + assert.equal(typeof workspaceId, "string"); + + await writeFile(join(context.project, "README.md"), "goodbye\n"); + const review = await context.client.callTool({ + name: "show_changes", + arguments: { workspaceId }, + }); + const structured = structuredContent(review); + assert.equal((review._meta as Record | undefined)?.tool, undefined); + + assert.equal(structured.workspaceId, workspaceId); + assert.match(structured.reviewRef as string, /^[0-9a-f]{40,64}$/); + assert.equal("summary" in structured, false); + assert.equal("files" in structured, false); + assert.equal("patch" in structured, false); + + const card = responseCard(review); + assert.deepEqual(card.summary, { + files: 1, + additions: 1, + removals: 1, + }); + assert.deepEqual(card.files, [ + { + path: "README.md", + type: "change", + additions: 1, + removals: 1, + }, + ]); + assert.match( + ((card.payload as { patch?: string } | undefined)?.patch) ?? "", + /-hello\n\+goodbye/, + ); + + const tools = await context.client.listTools(); + const outputProperties = tools.tools.find((tool) => tool.name === "show_changes") + ?.outputSchema?.properties; + assert.ok(outputProperties && "workspaceId" in outputProperties); + assert.ok(outputProperties && "reviewRef" in outputProperties); + assert.equal(outputProperties && "summary" in outputProperties, false); + assert.equal(outputProperties && "files" in outputProperties, false); + assert.equal(outputProperties && "patch" in outputProperties, false); + const inputProperties = tools.tools.find((tool) => tool.name === "show_changes") + ?.inputSchema?.properties; + assert.equal(inputProperties && "reviewRef" in inputProperties, false); +}); + +test("show_changes can reopen a historical review without advancing the checkpoint", async (t) => { + const context = await fixture(t, { git: true }); + const workspaceId = structuredContent( + await callOpen(context.client, context.project, "review-history"), + ).workspaceId; + assert.equal(typeof workspaceId, "string"); + + await writeFile(join(context.project, "README.md"), "first\n"); + const first = structuredContent(await context.client.callTool({ + name: "show_changes", + arguments: { workspaceId }, + })); + const reviewRef = first.reviewRef; + assert.equal(typeof reviewRef, "string"); + + await writeFile(join(context.project, "README.md"), "second\n"); + const reopened = await context.client.callTool({ + name: "show_changes", + arguments: { workspaceId }, + _meta: { "devspace/reviewRef": reviewRef }, + } as Parameters[0]); + assert.equal(structuredContent(reopened).reviewRef, reviewRef); + assert.match( + (((responseCard(reopened).payload as { patch?: string } | undefined)?.patch) ?? ""), + /\+first/, + ); + + const current = await context.client.callTool({ + name: "show_changes", + arguments: { workspaceId }, + }); + assert.match( + (((responseCard(current).payload as { patch?: string } | undefined)?.patch) ?? ""), + /-first\n\+second/, + ); +}); + test("open_workspace keeps lifecycle flags out of model output and preserves complete card metadata", async (t) => { const providerNote = "available"; const context = await fixture(t, { @@ -26,6 +172,8 @@ test("open_workspace keeps lifecycle flags out of model output and preserves com }); const first = await callOpen(context.client, context.project, "chat-1"); const repeated = await callOpen(context.client, context.project, "chat-1"); + assert.equal((first._meta as Record | undefined)?.tool, undefined); + assert.equal((repeated._meta as Record | undefined)?.tool, undefined); const tools = await context.client.listTools(); const openTool = tools.tools.find((tool) => tool.name === "open_workspace"); @@ -124,121 +272,24 @@ test("open_workspace omits providers disabled by configuration", async (t) => { ); }); -test("concurrent checkout opens return one full context and one reuse instruction", async (t) => { - const context = await fixture(t); - const [first, second] = await Promise.all([ - callOpen(context.client, context.project, "chat-1"), - callOpen(context.client, context.project, "chat-1"), - ]); - - assert.equal(structuredContent(first).workspaceId, structuredContent(second).workspaceId); - assert.equal( - [first, second].filter((result) => Array.isArray(structuredContent(result).agentsFiles)).length, - 1, - ); - assert.equal( - [first, second].filter((result) => responseText(result).includes("Workspace already open as")).length, - 1, - ); -}); - -test("new worktrees always receive a fresh workspace and complete worktree context", async (t) => { - const context = await fixture(t, { git: true }); - const checkout = await callOpen(context.client, context.project, "chat-1"); - const firstWorktree = await callOpen(context.client, context.project, "chat-1", "worktree"); - const secondWorktree = await callOpen(context.client, context.project, "chat-1", "worktree"); - const checkoutAgain = await callOpen(context.client, context.project, "chat-1"); - - assert.notEqual(structuredContent(firstWorktree).workspaceId, structuredContent(secondWorktree).workspaceId); - assert.equal(structuredContent(checkoutAgain).workspaceId, structuredContent(checkout).workspaceId); - for (const result of [firstWorktree, secondWorktree]) { - const structured = structuredContent(result); - assert.equal(structured.mode, "worktree"); - assert.ok(Array.isArray(structured.agentsFiles)); - assert.ok(Array.isArray(structured.availableAgentsFiles)); - assert.ok(Array.isArray(structured.skills)); - assert.ok(Array.isArray(structured.agentProviders)); - assert.ok(Array.isArray(structured.agents)); - assert.ok(Array.isArray(structured.skillDiagnostics)); - assert.match(responseText(result), /Opened isolated worktree workspace/); - } - assert.equal(structuredContent(checkoutAgain).agentsFiles, undefined); -}); - -test("checkout opened after a worktree receives its own complete context", async (t) => { - const context = await fixture(t, { git: true }); - const worktree = await callOpen(context.client, context.project, "chat-1", "worktree"); - const checkout = await callOpen(context.client, context.project, "chat-1"); - const checkoutAgain = await callOpen(context.client, context.project, "chat-1"); - - assert.equal(structuredContent(worktree).mode, "worktree"); - assert.ok(Array.isArray(structuredContent(worktree).agentsFiles)); - assert.equal(structuredContent(checkout).mode, "checkout"); - assert.ok(Array.isArray(structuredContent(checkout).agentsFiles)); - assert.equal(structuredContent(checkoutAgain).workspaceId, structuredContent(checkout).workspaceId); - assert.equal(structuredContent(checkoutAgain).agentsFiles, undefined); -}); - -test("a host without conversation metadata receives normal explicit-workspace behavior", async (t) => { - const context = await fixture(t); - const first = await callOpen(context.client, context.project); - const second = await callOpen(context.client, context.project); - - assert.notEqual(structuredContent(first).workspaceId, structuredContent(second).workspaceId); - assert.ok(Array.isArray(structuredContent(first).agentsFiles)); - assert.ok(Array.isArray(structuredContent(second).agentsFiles)); - assert.doesNotMatch(responseText(first), /conversation metadata/i); - assert.doesNotMatch(responseText(second), /conversation metadata/i); -}); - -test("checkout reuse and context suppression survive a registry restart", async (t) => { +test("open_workspace scopes checkout reuse to OpenAI session metadata", async (t) => { const context = await fixture(t); const first = await callOpen(context.client, context.project, "chat-1"); - const firstWorkspaceId = structuredContent(first).workspaceId; - - await context.close(); - - const restoredStore = new SqliteWorkspaceStore(context.stateDir); - const restoredServer = createMcpServer( - context.config, - new WorkspaceRegistry(context.config, restoredStore), - createReviewCheckpointManager(), - new ProcessSessionManager(), - () => [], - [], - ); - const [restoredClientTransport, restoredServerTransport] = InMemoryTransport.createLinkedPair(); - const restoredClient = new Client({ name: "devspace-restored-test-client", version: "1.0.0" }); - let restoredClosed = false; - const closeRestored = async () => { - if (restoredClosed) return; - restoredClosed = true; - await restoredClient.close(); - await restoredServer.close(); - restoredStore.close(); - }; - t.after(closeRestored); - - try { - await Promise.all([ - restoredClient.connect(restoredClientTransport), - restoredServer.connect(restoredServerTransport), - ]); - - const restored = await callOpen(restoredClient, context.project, "chat-1"); - assert.equal(structuredContent(restored).workspaceId, firstWorkspaceId); - assert.equal(structuredContent(restored).agentsFiles, undefined); - } finally { - await closeRestored(); - } + const repeated = await callOpen(context.client, context.project, "chat-1"); + const otherSession = await callOpen(context.client, context.project, "chat-2"); + const unscoped = await callOpen(context.client, context.project); + + assert.equal(structuredContent(repeated).workspaceId, structuredContent(first).workspaceId); + assert.equal(structuredContent(repeated).agentsFiles, undefined); + assert.notEqual(structuredContent(otherSession).workspaceId, structuredContent(first).workspaceId); + assert.notEqual(structuredContent(unscoped).workspaceId, structuredContent(first).workspaceId); + assert.ok(Array.isArray(structuredContent(otherSession).agentsFiles)); + assert.ok(Array.isArray(structuredContent(unscoped).agentsFiles)); }); interface ServerFixture { client: Client; project: string; - config: ServerConfig; - stateDir: string; - close: () => Promise; } async function fixture( @@ -247,6 +298,8 @@ async function fixture( git?: boolean; localAgentProviders?: LocalAgentProviderAvailability[] | (() => LocalAgentProviderAvailability[]); subagents?: SubagentsConfig; + toolMode?: ToolMode; + uiEnabled?: boolean; } = {}, ): Promise { const root = await mkdtemp(join(tmpdir(), "devspace-server-test-")); @@ -279,20 +332,20 @@ async function fixture( const initialProviderAvailability = typeof options.localAgentProviders === "function" ? options.localAgentProviders() : options.localAgentProviders ?? []; - const loadedConfig = loadConfig({ - DEVSPACE_CONFIG_DIR: join(root, ".config"), - DEVSPACE_ALLOWED_ROOTS: root, - DEVSPACE_WORKTREE_ROOT: join(root, ".worktrees"), - DEVSPACE_AGENT_DIR: agentDir, - DEVSPACE_WIDGETS: "full", - DEVSPACE_TOOL_MODE: "full", - DEVSPACE_SUBAGENTS: options.localAgentProviders ? "1" : "0", - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", - PORT: "1", - }); + const loadedConfig = loadConfig(writeTestDevspaceConfig(join(root, ".config"), { + server: { port: 1 }, + workspaces: { allowedRoots: [root], worktreeRoot: join(root, ".worktrees") }, + skills: { agentDir }, + subagents: { enabled: options.localAgentProviders !== undefined, providers: [] }, + })); + const modeConfig: ServerConfig = { + ...loadedConfig, + toolMode: options.toolMode ?? loadedConfig.toolMode, + uiEnabled: options.uiEnabled ?? loadedConfig.uiEnabled, + }; const config: ServerConfig = options.localAgentProviders ? { - ...loadedConfig, + ...modeConfig, subagents: options.subagents ?? { enabled: true, providers: initialProviderAvailability.map((provider) => ({ @@ -301,7 +354,7 @@ async function fixture( })), }, } - : loadedConfig; + : modeConfig; const resolveProviderAvailability: () => LocalAgentProviderAvailability[] = typeof options.localAgentProviders === "function" ? options.localAgentProviders @@ -341,7 +394,7 @@ async function fixture( await rm(root, { recursive: true, force: true }); }); - return { client, project, config, stateDir, close }; + return { client, project }; } async function git(cwd: string, args: string[]): Promise { @@ -352,14 +405,10 @@ async function callOpen( client: Client, path: string, conversationScopeId?: string, - mode?: "checkout" | "worktree", ): Promise>> { const params = { name: "open_workspace", - arguments: { - path, - ...(mode ? { mode } : {}), - }, + arguments: { path }, ...(conversationScopeId ? { _meta: { "openai/session": conversationScopeId } } : {}), @@ -372,15 +421,6 @@ function structuredContent(result: Awaited>): Rec return result.structuredContent as Record; } -function responseText(result: Awaited>): string { - const content = (result as { content?: unknown }).content; - assert.ok(Array.isArray(content)); - const first = content[0] as { type?: unknown; text?: unknown } | undefined; - assert.equal(first?.type, "text"); - assert.equal(typeof first?.text, "string"); - return first?.text as string; -} - function responseCard(result: Awaited>): Record { const metadata = result._meta; assert.ok(metadata && typeof metadata === "object"); diff --git a/src/server.ts b/src/server.ts index 16c2010d6..9e7ded7fd 100644 --- a/src/server.ts +++ b/src/server.ts @@ -17,12 +17,11 @@ import { import express from "express"; import type { Request, Response } from "express"; import * as z from "zod/v4"; -import { applyPatch } from "./apply-patch.js"; import { isArtifactDownloadSupportedPlatform, registerArtifactTools, } from "./artifact-tools.js"; -import { loadConfig, type ServerConfig, type WidgetMode } from "./config.js"; +import { loadConfig, type ServerConfig } from "./config.js"; import { createOpenAIIncomingArtifactAdapter, type IncomingArtifactAdapter, @@ -31,24 +30,15 @@ import { logEvent, requestIp, requestPath, - commandPreview, sessionIdPrefix, } from "./logger.js"; -import { - editFileTool, - findFilesTool, - grepFilesTool, - listDirectoryTool, - readFileTool, - runShellTool, - writeFileTool, -} from "./pi-tools.js"; +import { readFileTool } from "./pi-tools.js"; import { SingleUserOAuthProvider } from "./oauth-provider.js"; import { McpSessionRegistry, type McpSessionCloseResult, } from "./mcp-sessions.js"; -import { ProcessSessionManager, type ProcessSnapshot } from "./process-sessions.js"; +import { ProcessSessionManager } from "./process-sessions.js"; import { createReviewCheckpointManager } from "./review-checkpoints.js"; import { openAiConversationScopeId } from "./request-meta.js"; import { shutdownHttpServer } from "./server-shutdown.js"; @@ -64,32 +54,29 @@ import { formatLocalAgentProviderStatusSummary, type LocalAgentProviderStatus, } from "./local-agent-catalog.js"; +import { getToolSurface } from "./tool-surfaces/index.js"; +import { + contentText, + logFailedToolResponse, + logToolCall, + resultOutputSchema, + textBlock, + workspaceAppDescriptorMeta, +} from "./tool-surfaces/shared.js"; +import { + WORKSPACE_APP_URI, + toolNames, + workspaceIdDescription, + type ToolContent, + type ToolSurface, +} from "./tool-surfaces/types.js"; type Transport = StreamableHTTPServerTransport; // MCP clients can reconnect without closing the previous transport. Bound stale // session retention so abandoned MCP servers do not accumulate for the life of the process. const MCP_SESSION_IDLE_TIMEOUT_MS = 24 * 60 * 60 * 1_000; const MCP_SESSION_CLEANUP_INTERVAL_MS = 5 * 60 * 1_000; -const WORKSPACE_APP_URI = "ui://devspace/workspace-app.html"; const WORKSPACE_APP_MANIFEST_ENTRY = "workspace-app.html"; -const WRITE_TOOL_ANNOTATIONS = { - readOnlyHint: false, - destructiveHint: true, - idempotentHint: false, - openWorldHint: false, -}; -const EDIT_TOOL_ANNOTATIONS = { - readOnlyHint: false, - destructiveHint: true, - idempotentHint: false, - openWorldHint: false, -}; -const SHELL_TOOL_ANNOTATIONS = { - readOnlyHint: false, - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, -}; interface RunningServer { app: ReturnType; @@ -98,10 +85,6 @@ interface RunningServer { close(): Promise; } -type ToolContent = - | { type: "text"; text: string } - | { type: "image"; data: string; mimeType: string }; - interface WorkspaceAppManifestEntry { file: string; css?: string[]; @@ -110,113 +93,23 @@ interface WorkspaceAppManifestEntry { type WorkspaceAppManifest = Record; -interface DiffStats { - additions: number; - removals: number; -} - -type ToolWidgetKind = - | "workspace" - | "read" - | "write" - | "edit" - | "search" - | "directory" - | "shell" - | "show_changes"; - -interface ToolDefinitionMeta extends Record { - ui: { - resourceUri: string; - visibility: ["model"]; - }; -} - -type EmptyToolDefinitionMeta = Record & { - "ui/resourceUri"?: string; -}; - -interface ToolWidgetDescriptorMeta { - _meta: ToolDefinitionMeta | EmptyToolDefinitionMeta; -} - -function shouldAttachWidget(mode: WidgetMode, kind: ToolWidgetKind): boolean { - switch (mode) { - case "off": - return false; - case "changes": - return kind === "workspace" || kind === "show_changes"; - case "full": - return true; - } -} - -function toolWidgetDescriptorMeta( +function serverInstructions( config: ServerConfig, - kind: ToolWidgetKind, -): ToolWidgetDescriptorMeta { - if (!shouldAttachWidget(config.widgets, kind)) return { _meta: {} }; - - return { - _meta: { - ui: { - resourceUri: WORKSPACE_APP_URI, - visibility: ["model"], - }, - }, - }; -} - -const toolNames = { - openWorkspace: "open_workspace", - read: "read", - write: "write", - edit: "edit", - grep: "grep", - glob: "glob", - ls: "ls", - shell: "bash", -} as const; - -const workspaceIdDescription = - "Workspace to use. Reuse the current project's workspaceId."; - -interface ToolLogFields { - tool: string; - workspaceId?: string; - path?: string; - workingDirectory?: string; - command?: string; - commandLength?: number; - success: boolean; - durationMs: number; - error?: string; -} - -function serverInstructions(config: ServerConfig): string { - const artifactInstruction = config.artifactsEnabled && isArtifactDownloadSupportedPlatform() - ? " When the user supplies or generates a file that is not present on the DevSpace host, use download_artifact with its native file value, the existing workspace ID, and a suitable relative destination path chosen from the user's request and project structure. The tool refuses to overwrite an existing destination and returns the normalized workspace-relative path. Use normal workspace tools when explicit inspection, replacement, movement, renaming, or deletion is needed. Do not recreate binary files with write/edit calls or place signed URLs, native file objects, base64 content, or invented host paths in shell commands or logs." - : ""; - const showChangesInstruction = - config.widgets === "changes" - ? " If the turn successfully modifies files by creating, editing, overwriting, deleting, moving, or applying patches, call show_changes exactly once for that workspace after the final related file change and before your final response so the user can inspect the aggregate diff for that turn. Do not call it after every individual file change; do not skip it because individual file-change tools already returned diffs." + toolSurface: ToolSurface, +): string { + const artifactInstruction = + config.artifactsEnabled && isArtifactDownloadSupportedPlatform() + ? " When the user supplies or generates a file that is not present on the DevSpace host, use download_artifact with its native file value, the existing workspace ID, and a suitable relative destination path chosen from the user's request and project structure. The tool refuses to overwrite an existing destination and returns the normalized workspace-relative path. Use normal workspace tools when explicit inspection, replacement, movement, renaming, or deletion is needed. Do not recreate binary files with write/edit calls or place signed URLs, native file objects, base64 content, or invented host paths in shell commands or logs." : ""; - - if (config.toolMode === "codex") { - return `Use DevSpace for coding work. Call ${toolNames.openWorkspace} once for each project folder or isolated worktree, then keep using its workspaceId. During continued work in the same project or worktree, do not call ${toolNames.openWorkspace} again. Open another workspace only when changing projects, switching checkout/worktree mode, creating another isolated worktree, or when the current workspaceId is rejected. Use ${toolNames.read} for direct file reads, apply_patch for all file modifications, exec_command for inspection, tests, builds, and other commands, and write_stdin to poll or interact with running processes. Follow instructions returned by ${toolNames.openWorkspace}; read applicable instruction and skill files before working in their scope.${artifactInstruction}${showChangesInstruction}`; - } - - const inspection = config.toolMode !== "full" - ? `In minimal tool mode, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} are disabled; use ${toolNames.shell} with command-line tools such as grep, rg, find, ls, and tree for search and directory inspection. ` - : `Prefer ${toolNames.read}, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} for file inspection. `; - + const showChangesInstruction = + " If the turn successfully modifies files by creating, editing, overwriting, deleting, moving, or applying patches, call show_changes exactly once for that workspace after the final related file change and before your final response so the user can inspect the aggregate diff for that turn. Do not call it after every individual file change."; const skills = config.skillsEnabled ? `When ${toolNames.openWorkspace} returns available skills and a task matches a skill, use ${toolNames.read} to read that skill's path before proceeding. Skill paths may be outside the workspace, but ${toolNames.read} only permits advertised SKILL.md files and files under already-loaded skill directories. ` : ""; + const agents = `Follow instructions returned by ${toolNames.openWorkspace}. Before working under a path listed in availableAgentsFiles, use ${toolNames.read} to inspect that instruction file and follow it. `; + const common = `Use DevSpace for coding work. Call ${toolNames.openWorkspace} once for each project folder or isolated worktree, then keep using its workspaceId. During continued work in the same project or worktree, do not call ${toolNames.openWorkspace} again. Open another workspace only when changing projects, switching checkout/worktree mode, creating another isolated worktree, or when the current workspaceId is rejected.`; - const agentsMd = `Follow instructions returned by ${toolNames.openWorkspace}. Before working under a path listed in availableAgentsFiles, use ${toolNames.read} to inspect that instruction file and follow it. `; - - return `Use DevSpace for coding work. Call ${toolNames.openWorkspace} once for each project folder or isolated worktree, then keep using its workspaceId. During continued work in the same project or worktree, do not call ${toolNames.openWorkspace} again. Open another workspace only when changing projects, switching checkout/worktree mode, creating another isolated worktree, or when the current workspaceId is rejected. ${agentsMd}${skills}${inspection}Prefer ${toolNames.edit} for targeted modifications, ${toolNames.write} only for new files or complete rewrites, and ${toolNames.shell} for tests, builds, git inspection, package scripts, and commands that are better executed by the shell. Do not create or modify files with ${toolNames.shell}; avoid shell redirection, heredocs, tee, sed -i, perl -i, node/python/ruby scripts, or any command whose purpose is to write project files.${artifactInstruction}${showChangesInstruction}`; + return `${common} ${toolSurface.instructions({ agents, skills })}${artifactInstruction}${showChangesInstruction}`; } function formatVisibleAgent(agent: { @@ -244,17 +137,6 @@ function formatAvailableAgentProvider(provider: { return `${provider.id}${details ? ` (${details})` : ""}`; } -function resultOutputSchema(extra: z.ZodRawShape = {}): z.ZodRawShape { - return { - result: z - .string() - .describe( - "Model-readable result text for follow-up reasoning and plain MCP hosts.", - ), - ...extra, - }; -} - const workspaceSkillOutputSchema = z.object({ name: z.string(), description: z.string(), @@ -285,20 +167,6 @@ const workspaceAvailableAgentsFileOutputSchema = z.object({ path: z.string(), }); -const reviewFileOutputSchema = z.object({ - path: z.string(), - previousPath: z.string().optional(), - type: z.enum(["change", "rename-pure", "rename-changed", "new", "deleted"]), - additions: z.number(), - removals: z.number(), -}); - -const reviewSummaryOutputSchema = z.object({ - files: z.number(), - additions: z.number(), - removals: z.number(), -}); - function sendJsonRpcError( res: Response, status: number, @@ -323,105 +191,6 @@ function requestLogFields(req: Request, config: ServerConfig): Record item.type === "text", - ) - .map((item) => item.text) - .join("\n"); -} - -function toolErrorPreview(content: ToolContent[]): string | undefined { - const text = contentText(content).replace(/\s+/g, " ").trim(); - if (!text) return undefined; - return text.length > 240 ? `${text.slice(0, 237)}...` : text; -} - -function logFailedToolResponse( - config: ServerConfig, - fields: Omit, - content: ToolContent[], - startedAt: number, -): void { - logToolCall(config, { - ...fields, - success: false, - durationMs: Math.round(performance.now() - startedAt), - error: toolErrorPreview(content), - }); -} - -function textBlock(text: string): ToolContent { - return { type: "text", text }; -} - -function textSummary(content: ToolContent[]): { - lines: number; - characters: number; -} { - const text = contentText(content); - return { - lines: text.length === 0 ? 0 : text.split("\n").length, - characters: text.length, - }; -} - -function contentLineCount(content: string): number { - if (content.length === 0) return 0; - return content.endsWith("\n") - ? content.slice(0, -1).split("\n").length - : content.split("\n").length; -} - -function countDiffStats(diff: string | undefined): DiffStats { - if (!diff) return { additions: 0, removals: 0 }; - - let additions = 0; - let removals = 0; - - for (const line of diff.split("\n")) { - if (line.startsWith("+") && !line.startsWith("+++")) additions++; - if (line.startsWith("-") && !line.startsWith("---")) removals++; - } - - return { additions, removals }; -} - -function newFilePatch(path: string, content: string): string { - const lines = - content.length === 0 - ? [] - : content.endsWith("\n") - ? content.slice(0, -1).split("\n") - : content.split("\n"); - const hunkLength = lines.length; - const hunkRange = hunkLength === 0 ? "+0,0" : `+1,${hunkLength}`; - const body = lines.map((line) => `+${line}`).join("\n"); - - return [ - `diff --git a/${path} b/${path}`, - "new file mode 100644", - "index 0000000..0000000", - "--- /dev/null", - `+++ b/${path}`, - `@@ -0,0 ${hunkRange} @@`, - body, - ] - .filter((line) => line.length > 0) - .join("\n"); -} - function assetBaseUrl(config: ServerConfig): string { return `${config.publicBaseUrl.replace(/\/+$/, "")}/mcp-app-assets`; } @@ -509,201 +278,6 @@ async function assertWorkspaceAppAssets(): Promise { } } -function processResult(snapshot: ProcessSnapshot): string { - const status = snapshot.running - ? `Process running with session ID ${snapshot.sessionId}.` - : snapshot.signal - ? `Process exited after signal ${snapshot.signal}.` - : `Process exited with code ${snapshot.exitCode ?? "unknown"}.`; - return snapshot.output ? `${snapshot.output.replace(/\n$/, "")}\n${status}` : status; -} - -function processOutputSchema(): z.ZodRawShape { - return resultOutputSchema({ - sessionId: z.number().optional(), - running: z.boolean(), - exitCode: z.number().int().optional(), - signal: z.string().optional(), - wallTimeMs: z.number().nonnegative(), - outputTruncated: z.boolean(), - }); -} - -function processToolResponse( - tool: "exec_command" | "write_stdin", - workspaceId: string, - snapshot: ProcessSnapshot, - summary: Record, -) { - const result = processResult(snapshot); - const content = [textBlock(result)]; - const outputSummary = textSummary(snapshot.output ? [textBlock(snapshot.output)] : []); - return { - content, - _meta: { - tool, - card: { - workspaceId, - summary: { ...summary, ...outputSummary }, - payload: { content }, - }, - }, - structuredContent: { - result, - sessionId: snapshot.sessionId, - running: snapshot.running, - exitCode: snapshot.exitCode, - signal: snapshot.signal, - wallTimeMs: snapshot.wallTimeMs, - outputTruncated: snapshot.outputTruncated, - }, - }; -} - -function registerCodexProcessTools( - server: McpServer, - config: ServerConfig, - workspaces: WorkspaceRegistry, - processSessions: ProcessSessionManager, -): void { - registerAppTool( - server, - "exec_command", - { - title: "Execute command", - description: - "Run a command in a workspace. Returns its result when it exits during the yield window, otherwise returns a sessionId for write_stdin. Use this for file inspection, tests, builds, package scripts, and long-running processes.", - inputSchema: { - workspaceId: z.string().describe(workspaceIdDescription), - cmd: z.string().min(1).describe("Shell command to execute."), - tty: z - .boolean() - .optional() - .describe("Allocate a pseudo-terminal for interactive commands. Defaults to false."), - columns: z.number().int().min(1).max(1_000).optional().describe("Initial PTY width. Defaults to 80."), - rows: z.number().int().min(1).max(1_000).optional().describe("Initial PTY height. Defaults to 24."), - workingDirectory: z - .string() - .optional() - .describe("Working directory relative to the workspace root. Defaults to the workspace root."), - yieldTimeMs: z - .number() - .int() - .min(0) - .max(30_000) - .optional() - .describe("Milliseconds to wait before returning a running session. Defaults to 10000."), - maxOutputTokens: z - .number() - .int() - .positive() - .max(100_000) - .optional() - .describe("Approximate output token budget. Defaults to 10000."), - }, - outputSchema: processOutputSchema(), - ...toolWidgetDescriptorMeta(config, "shell"), - annotations: SHELL_TOOL_ANNOTATIONS, - }, - async ({ workspaceId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, maxOutputTokens }) => { - const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); - const cwd = workspaces.resolveWorkingDirectory(workspace, workingDirectory); - const snapshot = await processSessions.start({ - workspaceId, - command: cmd, - cwd, - workspaceRoot: workspace.root, - tty, - columns, - rows, - yieldTimeMs, - maxOutputTokens, - }); - - logToolCall(config, { - tool: "exec_command", - workspaceId, - workingDirectory: workingDirectory ?? ".", - command: cmd, - commandLength: cmd.length, - success: true, - durationMs: Math.round(performance.now() - startedAt), - }); - - return processToolResponse("exec_command", workspaceId, snapshot, { - command: cmd, - workingDirectory: workingDirectory ?? ".", - running: snapshot.running, - exitCode: snapshot.exitCode, - wallTimeMs: snapshot.wallTimeMs, - }); - }, - ); - - registerAppTool( - server, - "write_stdin", - { - title: "Write to process", - description: - "Poll or write characters to a process returned by exec_command. Omit chars or pass an empty string to poll. Pass \\u0003 to send Ctrl-C.", - inputSchema: { - workspaceId: z.string().describe("Workspace identifier used to start the process."), - sessionId: z.number().describe("Process session identifier returned by exec_command."), - chars: z.string().optional().describe("Characters to write. Omit or pass an empty string to poll."), - columns: z.number().int().min(1).max(1_000).optional().describe("Resize a PTY to this width."), - rows: z.number().int().min(1).max(1_000).optional().describe("Resize a PTY to this height."), - yieldTimeMs: z - .number() - .int() - .min(0) - .max(30_000) - .optional() - .describe("Milliseconds to wait for process output or completion. Defaults to 10000."), - maxOutputTokens: z - .number() - .int() - .positive() - .max(100_000) - .optional() - .describe("Approximate output token budget. Defaults to 10000."), - }, - outputSchema: processOutputSchema(), - ...toolWidgetDescriptorMeta(config, "shell"), - annotations: SHELL_TOOL_ANNOTATIONS, - }, - async ({ workspaceId, sessionId, chars, columns, rows, yieldTimeMs, maxOutputTokens }) => { - const startedAt = performance.now(); - workspaces.getWorkspace(workspaceId); - const snapshot = await processSessions.write({ - workspaceId, - sessionId, - chars, - columns, - rows, - yieldTimeMs, - maxOutputTokens, - }); - - logToolCall(config, { - tool: "write_stdin", - workspaceId, - success: true, - durationMs: Math.round(performance.now() - startedAt), - }); - - return processToolResponse("write_stdin", workspaceId, snapshot, { - sessionId, - charactersWritten: chars?.length ?? 0, - running: snapshot.running, - exitCode: snapshot.exitCode, - wallTimeMs: snapshot.wallTimeMs, - }); - }, - ); -} - export function createMcpServer( config: ServerConfig, workspaces: WorkspaceRegistry, @@ -712,6 +286,7 @@ export function createMcpServer( resolveLocalAgentProviders: () => LocalAgentProviderStatus[], incomingArtifactAdapters: readonly IncomingArtifactAdapter[], ): McpServer { + const toolSurface = getToolSurface(config.toolMode); const server = new McpServer( { name: "devspace", @@ -721,7 +296,7 @@ export function createMcpServer( "Coding tools for project workspaces. Open each project or worktree once, then reuse its workspaceId.", }, { - instructions: serverInstructions(config), + instructions: serverInstructions(config, toolSurface), }, ); @@ -801,9 +376,16 @@ export function createMcpServer( agentProviders: z.array(workspaceLocalAgentProviderOutputSchema).optional(), agents: z.array(workspaceLocalAgentOutputSchema).optional(), skillDiagnostics: z.array(z.unknown()).optional(), + review: z.discriminatedUnion("available", [ + z.object({ available: z.literal(true) }), + z.object({ + available: z.literal(false), + reason: z.string(), + }), + ]), instruction: z.string(), }, - ...toolWidgetDescriptorMeta(config, "workspace"), + ...workspaceAppDescriptorMeta(config), annotations: { readOnlyHint: true }, }, async ({ path, mode, baseRef }, { _meta }) => { @@ -818,12 +400,10 @@ export function createMcpServer( { path, mode, baseRef }, { conversationScopeId: openAiConversationScopeId(_meta) }, ); - if (config.widgets === "changes") { - await reviewCheckpoints.initializeWorkspace({ - workspaceId: workspace.id, - root: workspace.root, - }); - } + const review = await reviewCheckpoints.initializeWorkspace({ + workspaceId: workspace.id, + root: workspace.root, + }); const cardSkills = workspace.skills .filter((skill) => !skill.disableModelInvocation) .map((skill) => ({ @@ -910,7 +490,6 @@ export function createMcpServer( return { content: resultContent, _meta: { - tool: "open_workspace", card: { workspaceId: workspace.id, root: workspace.root, @@ -925,6 +504,7 @@ export function createMcpServer( skills: cardSkills, agentProviders: cardAgentProviders, agents: cardAgents, + review, instruction: cardInstruction, summary: { mode: workspace.mode, @@ -942,6 +522,7 @@ export function createMcpServer( mode: workspace.mode, sourceRoot: workspace.sourceRoot, worktree: workspace.worktree, + review, ...(includeBootstrapContext ? { agentsFiles: loadedAgentsFiles, @@ -958,8 +539,7 @@ export function createMcpServer( }, ); - registerAppTool( - server, + server.registerTool( toolNames.read, { title: "Read file", @@ -998,7 +578,6 @@ export function createMcpServer( .describe("Maximum number of lines to read."), }, outputSchema: resultOutputSchema(), - ...toolWidgetDescriptorMeta(config, "read"), annotations: { readOnlyHint: true }, }, async ({ workspaceId, ...input }) => { @@ -1024,11 +603,6 @@ export function createMcpServer( } workspaces.markReadPathLoaded(workspace, readPath); - const summary = { - ...textSummary(response.content), - offset: input.offset ?? 1, - limited: input.limit !== undefined, - }; logToolCall(config, { tool: toolNames.read, workspaceId, @@ -1039,15 +613,6 @@ export function createMcpServer( return { ...response, - _meta: { - tool: toolNames.read, - card: { - workspaceId, - path: input.path, - summary, - payload: { content: response.content }, - }, - }, structuredContent: { result: contentText(response.content), }, @@ -1055,608 +620,76 @@ export function createMcpServer( }, ); - if (config.toolMode !== "codex") { - registerAppTool( + toolSurface.register({ server, - toolNames.write, - { - title: "Write file", - description: - `Create or completely overwrite a file in a workspace. Prefer ${toolNames.edit} for targeted changes to existing files.`, - inputSchema: { - workspaceId: z - .string() - .describe(workspaceIdDescription), - path: z - .string() - .describe("File path to write, relative to the workspace root."), - content: z.string().describe("Complete new file content."), - }, - outputSchema: resultOutputSchema(), - ...toolWidgetDescriptorMeta(config, "write"), - annotations: WRITE_TOOL_ANNOTATIONS, - }, - async ({ workspaceId, ...input }) => { - const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); - workspaces.resolvePath(workspace, input.path); - const response = await writeFileTool(input, { - cwd: workspace.root, - root: workspace.root, - }); - - if (response.isError) { - logFailedToolResponse(config, { - tool: toolNames.write, - workspaceId, - path: input.path, - }, response.content, startedAt); - return response; - } - - const patch = newFilePatch(input.path, input.content); - const stats = countDiffStats(patch); - const summary = { - ...stats, - lines: contentLineCount(input.content), - characters: input.content.length, - }; - logToolCall(config, { - tool: toolNames.write, - workspaceId, - path: input.path, - success: true, - durationMs: Math.round(performance.now() - startedAt), - }); - - return { - ...response, - _meta: { - tool: toolNames.write, - card: { - workspaceId, - path: input.path, - summary, - payload: { - content: response.content, - patch, - }, - }, - }, - structuredContent: { - result: contentText(response.content), - }, - }; - }, - ); + config, + workspaces, + processSessions, + }); registerAppTool( server, - toolNames.edit, + "show_changes", { - title: "Edit file", + title: "Show changes", description: - `Edit one file in a workspace by replacing exact text blocks. Prefer this over ${toolNames.write} for targeted changes. Each oldText must match a unique, non-overlapping region of the original file; merge nearby changes into one edit and keep oldText as small as possible while still unique.`, + "Show the changes made in this turn for an open workspace. Call this once after the final related file change and before your final response so the user can review the combined diff. Do not call it after each individual file change.", inputSchema: { - workspaceId: z - .string() - .describe(workspaceIdDescription), - path: z - .string() - .describe("File path to edit, relative to the workspace root."), - edits: z - .array( - z.object({ - oldText: z - .string() - .describe( - "Exact text to replace. Must match uniquely in the original file.", - ), - newText: z.string().describe("Replacement text."), - }), - ) - .min(1), + workspaceId: z.string().describe(workspaceIdDescription), }, outputSchema: resultOutputSchema({ - status: z.literal("applied"), + workspaceId: z.string(), + reviewRef: z.string().regex(/^[0-9a-f]{40,64}$/), }), - ...toolWidgetDescriptorMeta(config, "edit"), - annotations: EDIT_TOOL_ANNOTATIONS, + ...workspaceAppDescriptorMeta(config), + annotations: { readOnlyHint: true }, }, - async ({ workspaceId, ...input }) => { + async ({ workspaceId }, { _meta }) => { const startedAt = performance.now(); const workspace = workspaces.getWorkspace(workspaceId); - workspaces.resolvePath(workspace, input.path); - const response = await editFileTool(input, { - cwd: workspace.root, - root: workspace.root, - }); - - if (response.isError) { - logFailedToolResponse(config, { - tool: toolNames.edit, - workspaceId, - path: input.path, - }, response.content, startedAt); - return response; - } + const reviewRef = typeof _meta?.["devspace/reviewRef"] === "string" + ? _meta["devspace/reviewRef"] + : undefined; + const review = reviewRef + ? await reviewCheckpoints.reviewByRef({ + workspaceId, + root: workspace.root, + reviewRef, + }) + : await reviewCheckpoints.reviewChanges({ + workspaceId, + root: workspace.root, + markReviewed: true, + }); - const stats = countDiffStats( - response.details?.patch ?? response.details?.diff, - ); - const summary = { - ...stats, - editCount: input.edits.length, - }; - const editResultText = `Edited ${input.path} (+${stats.additions} -${stats.removals}).`; - const editContent = [textBlock(editResultText)]; + const content = [textBlock(review.result)]; logToolCall(config, { - tool: toolNames.edit, + tool: "show_changes", workspaceId, - path: input.path, success: true, durationMs: Math.round(performance.now() - startedAt), }); return { - content: editContent, + content, _meta: { - tool: toolNames.edit, card: { workspaceId, - path: input.path, - summary, + summary: review.summary, + files: review.files, payload: { - diff: response.details?.diff, - patch: response.details?.patch, + patch: review.patch, }, }, }, structuredContent: { - status: "applied", - result: contentText(editContent), - }, - }; - }, - ); - } - - if (config.toolMode === "codex") { - registerAppTool( - server, - "apply_patch", - { - title: "Apply patch", - description: - "Apply one Codex-style patch in a workspace. Supports adding, overwriting, updating, deleting, and moving files. Use this for all file modifications. Paths must be relative to the workspace.", - inputSchema: { - workspaceId: z - .string() - .describe(workspaceIdDescription), - patch: z - .string() - .describe("Patch text enclosed by *** Begin Patch and *** End Patch markers."), - }, - outputSchema: resultOutputSchema({ - additions: z.number(), - removals: z.number(), - files: z.array( - z.object({ - path: z.string(), - previousPath: z.string().optional(), - operation: z.enum(["add", "update", "delete", "move"]), - }), - ), - }), - ...toolWidgetDescriptorMeta(config, "edit"), - annotations: EDIT_TOOL_ANNOTATIONS, - }, - async ({ workspaceId, patch }) => { - const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); - const applied = await applyPatch(workspace.root, patch); - const paths = applied.files.map((file) => file.path).join(", "); - const result = `Applied patch to ${applied.files.length} file(s): ${paths}`; - const content = [textBlock(result)]; - const displayPath = applied.files.length === 1 - ? applied.files[0]?.path - : `${applied.files.length} files`; - - logToolCall(config, { - tool: "apply_patch", - workspaceId, - success: true, - durationMs: Math.round(performance.now() - startedAt), - }); - - return { - content, - _meta: { - tool: "apply_patch", - card: { - workspaceId, - path: displayPath, - summary: { - files: applied.files.length, - additions: applied.additions, - removals: applied.removals, - }, - files: applied.files, - payload: { patch: applied.patch }, - }, - }, - structuredContent: { - result, - additions: applied.additions, - removals: applied.removals, - files: applied.files, - }, - }; - }, - ); - } - - if (config.widgets === "changes") { - registerAppTool( - server, - "show_changes", - { - title: "Show changes", - description: - "Show the changes made in this turn for an open workspace. Call this once after the final related file change and before your final response so the user can review the combined diff. Do not call it after each individual file change.", - inputSchema: { - workspaceId: z - .string() - .describe(workspaceIdDescription), - }, - outputSchema: resultOutputSchema(), - ...toolWidgetDescriptorMeta(config, "show_changes"), - annotations: { readOnlyHint: true }, - }, - async ({ workspaceId }) => { - const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); - const review = await reviewCheckpoints.reviewChanges({ - workspaceId, - root: workspace.root, - markReviewed: true, - }); - - const content = [textBlock(review.result)]; - logToolCall(config, { - tool: "show_changes", - workspaceId, - success: true, - durationMs: Math.round(performance.now() - startedAt), - }); - - return { - content, - _meta: { - tool: "show_changes", - card: { - workspaceId, - summary: review.summary, - files: review.files, - payload: { - patch: review.patch, - }, - }, - }, - structuredContent: { - result: contentText(content), - }, - }; - }, - ); - } - - if (config.toolMode === "full") { - registerAppTool( - server, - toolNames.grep, - { - title: "Grep", - description: - "Search file contents in a workspace. Use this before broad reads when looking for symbols, text, or usage sites. Respects project ignore rules.", - inputSchema: { - workspaceId: z - .string() - .describe(workspaceIdDescription), - pattern: z.string().describe("Search pattern."), - path: z - .string() - .optional() - .describe( - "Optional path or glob scope relative to the workspace root.", - ), - include: z.string().optional().describe("Optional include glob."), - }, - outputSchema: resultOutputSchema(), - ...toolWidgetDescriptorMeta(config, "search"), - annotations: { readOnlyHint: true }, - }, - async ({ workspaceId, ...input }) => { - const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); - if (input.path) workspaces.resolvePath(workspace, input.path); - const response = await grepFilesTool(input, { - cwd: workspace.root, - root: workspace.root, - }); - - if (response.isError) { - logFailedToolResponse(config, { - tool: toolNames.grep, - workspaceId, - path: input.path, - }, response.content, startedAt); - return response; - } - - const summary = { - pattern: input.pattern, - scope: input.path ?? ".", - ...textSummary(response.content), - }; - logToolCall(config, { - tool: toolNames.grep, - workspaceId, - path: input.path, - success: true, - durationMs: Math.round(performance.now() - startedAt), - }); - - return { - ...response, - _meta: { - tool: toolNames.grep, - card: { - workspaceId, - path: input.path, - summary, - payload: { content: response.content }, - }, - }, - structuredContent: { - result: contentText(response.content), - }, - }; - }, - ); - - registerAppTool( - server, - toolNames.glob, - { - title: "Glob", - description: - "Find files by glob pattern in a workspace. Use this to discover filenames or narrow file sets before reading. Respects project ignore rules.", - inputSchema: { - workspaceId: z - .string() - .describe(workspaceIdDescription), - pattern: z.string().describe("File glob pattern."), - path: z - .string() - .optional() - .describe("Optional path scope relative to the workspace root."), - }, - outputSchema: resultOutputSchema(), - ...toolWidgetDescriptorMeta(config, "search"), - annotations: { readOnlyHint: true }, - }, - async ({ workspaceId, ...input }) => { - const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); - if (input.path) workspaces.resolvePath(workspace, input.path); - const response = await findFilesTool(input, { - cwd: workspace.root, - root: workspace.root, - }); - - if (response.isError) { - logFailedToolResponse(config, { - tool: toolNames.glob, - workspaceId, - path: input.path, - }, response.content, startedAt); - return response; - } - - const summary = { - pattern: input.pattern, - scope: input.path ?? ".", - ...textSummary(response.content), - }; - logToolCall(config, { - tool: toolNames.glob, - workspaceId, - path: input.path, - success: true, - durationMs: Math.round(performance.now() - startedAt), - }); - - return { - ...response, - _meta: { - tool: toolNames.glob, - card: { - workspaceId, - path: input.path, - summary, - payload: { content: response.content }, - }, - }, - structuredContent: { - result: contentText(response.content), - }, - }; - }, - ); - - registerAppTool( - server, - toolNames.ls, - { - title: "Ls", - description: - "List a directory in a workspace. Use this for directory inspection before reading files.", - inputSchema: { - workspaceId: z - .string() - .describe(workspaceIdDescription), - path: z - .string() - .describe( - "Directory path to list, relative to the workspace root.", - ), - }, - outputSchema: resultOutputSchema(), - ...toolWidgetDescriptorMeta(config, "directory"), - annotations: { readOnlyHint: true }, - }, - async ({ workspaceId, ...input }) => { - const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); - workspaces.resolvePath(workspace, input.path); - const response = await listDirectoryTool(input, { - cwd: workspace.root, - root: workspace.root, - }); - - if (response.isError) { - logFailedToolResponse(config, { - tool: toolNames.ls, - workspaceId, - path: input.path, - }, response.content, startedAt); - return response; - } - - const summary = textSummary(response.content); - logToolCall(config, { - tool: toolNames.ls, - workspaceId, - path: input.path, - success: true, - durationMs: Math.round(performance.now() - startedAt), - }); - - return { - ...response, - _meta: { - tool: toolNames.ls, - card: { - workspaceId, - path: input.path, - summary, - payload: { content: response.content }, - }, - }, - structuredContent: { - result: contentText(response.content), - }, - }; - }, - ); - } - - if (config.toolMode !== "codex") { - registerAppTool( - server, - toolNames.shell, - { - title: "Bash", - description: config.toolMode !== "full" - ? `Run a shell command in a workspace. Use only for tests, builds, git inspection, package scripts, search, file discovery, and directory inspection. In minimal tool mode, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} are disabled; use command-line tools such as grep, rg, find, ls, and tree for those read-only inspection actions. Do not use ${toolNames.shell} to create or modify files. Do not use shell redirection, heredocs, tee, sed -i, perl -i, node/python/ruby scripts, or generated scripts to write project files; use ${toolNames.edit} for targeted changes and ${toolNames.write} for new files or full rewrites. Prefer ${toolNames.read} for direct file reads. This is powerful execution and should only be exposed behind strong authentication.` - : `Run a shell command in a workspace. Use only for tests, builds, git inspection, package scripts, and commands that are better executed by the shell. Do not use ${toolNames.shell} to create or modify files. Do not use shell redirection, heredocs, tee, sed -i, perl -i, node/python/ruby scripts, or generated scripts to write project files; use ${toolNames.edit} for targeted changes and ${toolNames.write} for new files or full rewrites. Prefer ${toolNames.read}, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} for file inspection. This is powerful execution and should only be exposed behind strong authentication.`, - inputSchema: { - workspaceId: z - .string() - .describe(workspaceIdDescription), - command: z - .string() - .describe( - `Shell command to run. Must not create or modify project files; use ${toolNames.edit} or ${toolNames.write} for file changes.`, - ), - workingDirectory: z - .string() - .optional() - .describe( - "Optional working directory relative to the workspace root. Defaults to the workspace root.", - ), - timeout: z - .number() - .positive() - .max(300) - .optional() - .describe("Timeout in seconds. Defaults to 30, max 300."), - }, - outputSchema: resultOutputSchema(), - ...toolWidgetDescriptorMeta(config, "shell"), - annotations: SHELL_TOOL_ANNOTATIONS, - }, - async ({ workspaceId, workingDirectory, ...input }) => { - const startedAt = performance.now(); - const workspace = workspaces.getWorkspace(workspaceId); - const cwd = workspaces.resolveWorkingDirectory( - workspace, - workingDirectory, - ); - const response = await runShellTool(input, { - cwd, - root: workspace.root, - }); - - if (response.isError) { - logFailedToolResponse(config, { - tool: toolNames.shell, workspaceId, - workingDirectory: workingDirectory ?? ".", - command: input.command, - commandLength: input.command.length, - }, response.content, startedAt); - return response; - } - - const summary = { - command: input.command, - workingDirectory: workingDirectory ?? ".", - ...textSummary(response.content), - }; - logToolCall(config, { - tool: toolNames.shell, - workspaceId, - workingDirectory: workingDirectory ?? ".", - command: input.command, - commandLength: input.command.length, - success: true, - durationMs: Math.round(performance.now() - startedAt), - }); - - return { - ...response, - _meta: { - tool: toolNames.shell, - card: { - workspaceId, - path: workingDirectory, - summary, - payload: { content: response.content }, - }, - }, - structuredContent: { - result: contentText(response.content), + reviewRef: review.reviewRef, + result: contentText(content), }, }; }, ); - } - - if (config.toolMode === "codex") { - registerCodexProcessTools(server, config, workspaces, processSessions); - } if (config.artifactsEnabled && isArtifactDownloadSupportedPlatform()) { registerArtifactTools(server, { diff --git a/src/skills.test.ts b/src/skills.test.ts index 9db16a103..41556b3ad 100644 --- a/src/skills.test.ts +++ b/src/skills.test.ts @@ -9,6 +9,7 @@ import { loadWorkspaceSkills, resolveSkillReadPath, } from "./skills.js"; +import { writeTestDevspaceConfig } from "./test-support/config.test.js"; const root = await mkdtemp(join(tmpdir(), "devspace-skills-test-")); const originalHome = process.env.HOME; @@ -160,23 +161,22 @@ try { ].join("\n"), ); - const disabledConfig = loadConfig({ - DEVSPACE_ALLOWED_ROOTS: projectRoot, - DEVSPACE_AGENT_DIR: agentDir, - DEVSPACE_SKILL_PATHS: explicitSkills, - DEVSPACE_SKILLS: "0", - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", - PORT: "1", - }); + const configDir = join(root, ".devspace"); + const disabledConfig = loadConfig(writeTestDevspaceConfig(configDir, { + server: { port: 1 }, + workspaces: { allowedRoots: [projectRoot] }, + skills: { agentDir, paths: [explicitSkills], enabled: false }, + })); assert.deepEqual(loadWorkspaceSkills(disabledConfig, projectRoot).skills, []); - const config = loadConfig({ - DEVSPACE_ALLOWED_ROOTS: projectRoot, - DEVSPACE_AGENT_DIR: agentDir, - DEVSPACE_SKILL_PATHS: [explicitSkills, "~/.claude/skills", "./.claude/skills"].join(","), - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", - PORT: "1", - }); + const config = loadConfig(writeTestDevspaceConfig(configDir, { + server: { port: 1 }, + workspaces: { allowedRoots: [projectRoot] }, + skills: { + agentDir, + paths: [explicitSkills, "~/.claude/skills", "./.claude/skills"], + }, + })); const loaded = loadWorkspaceSkills(config, projectRoot); assert.equal(loaded.skills.some((skill) => skill.name === "agent-global-skill"), true); assert.equal(loaded.skills.some((skill) => skill.name === "agent-project-skill"), true); @@ -195,13 +195,12 @@ try { false, ); - const experimentalConfig = loadConfig({ - DEVSPACE_ALLOWED_ROOTS: projectRoot, - DEVSPACE_AGENT_DIR: agentDir, - DEVSPACE_SUBAGENTS: "1", - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", - PORT: "1", - }); + const experimentalConfig = loadConfig(writeTestDevspaceConfig(configDir, { + server: { port: 1 }, + workspaces: { allowedRoots: [projectRoot] }, + skills: { agentDir }, + subagents: { enabled: true, providers: [] }, + })); assert.equal( loadWorkspaceSkills(experimentalConfig, projectRoot).skills.some( (skill) => skill.name === "subagents", @@ -209,25 +208,21 @@ try { true, ); - const duplicateConfig = loadConfig({ - DEVSPACE_ALLOWED_ROOTS: projectRoot, - DEVSPACE_AGENT_DIR: agentDir, - DEVSPACE_SKILL_PATHS: [explicitSkills, "./.agents/skills"].join(","), - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", - PORT: "1", - }); + const duplicateConfig = loadConfig(writeTestDevspaceConfig(configDir, { + server: { port: 1 }, + workspaces: { allowedRoots: [projectRoot] }, + skills: { agentDir, paths: [explicitSkills, "./.agents/skills"] }, + })); assert.equal( effectiveSkillPaths(duplicateConfig, projectRoot).filter((path) => path === projectAgentsSkills).length, 1, ); - const legacyPiConfig = loadConfig({ - DEVSPACE_ALLOWED_ROOTS: projectRoot, - DEVSPACE_AGENT_DIR: agentDir, - DEVSPACE_SKILL_PATHS: [explicitSkills, join(projectRoot, ".pi", "skills")].join(","), - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", - PORT: "1", - }); + const legacyPiConfig = loadConfig(writeTestDevspaceConfig(configDir, { + server: { port: 1 }, + workspaces: { allowedRoots: [projectRoot] }, + skills: { agentDir, paths: [explicitSkills, join(projectRoot, ".pi", "skills")] }, + })); assert.equal( loadWorkspaceSkills(legacyPiConfig, projectRoot).skills.some((skill) => skill.name === "project-skill"), true, diff --git a/src/test-support/config.test.ts b/src/test-support/config.test.ts new file mode 100644 index 000000000..2c97bc4e8 --- /dev/null +++ b/src/test-support/config.test.ts @@ -0,0 +1,43 @@ +import { + defaultDevspaceConfig, + type DevspaceConfig, +} from "../config-schema.js"; +import { writeDevspaceConfig } from "../user-config.js"; + +type SectionOverrides = { + server?: Partial; + workspaces?: Partial; + storage?: Partial; + tools?: Partial; + ui?: Partial; + artifacts?: Partial; + skills?: Partial; + subagents?: DevspaceConfig["subagents"]; + logging?: Partial; + oauth?: Partial; +}; + +export function writeTestDevspaceConfig( + configDir: string, + overrides: SectionOverrides = {}, +): NodeJS.ProcessEnv { + const defaults = defaultDevspaceConfig(); + const env = { DEVSPACE_CONFIG_DIR: configDir }; + writeDevspaceConfig({ + ...defaults, + server: { ...defaults.server, ...overrides.server }, + workspaces: { ...defaults.workspaces, ...overrides.workspaces }, + storage: { ...defaults.storage, ...overrides.storage }, + tools: { ...defaults.tools, ...overrides.tools }, + ui: { ...defaults.ui, ...overrides.ui }, + artifacts: { ...defaults.artifacts, ...overrides.artifacts }, + skills: { ...defaults.skills, ...overrides.skills }, + subagents: overrides.subagents ?? defaults.subagents, + logging: { ...defaults.logging, ...overrides.logging }, + oauth: { ...defaults.oauth, ...overrides.oauth }, + }, env); + return { + ...env, + DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", + }; +} diff --git a/src/tool-surfaces/claude.ts b/src/tool-surfaces/claude.ts new file mode 100644 index 000000000..4fc21f221 --- /dev/null +++ b/src/tool-surfaces/claude.ts @@ -0,0 +1,249 @@ +import * as z from "zod/v4"; +import { + editFileTool, + runShellTool, + writeFileTool, +} from "../pi-tools.js"; +import { + EDIT_TOOL_ANNOTATIONS, + SHELL_TOOL_ANNOTATIONS, + WRITE_TOOL_ANNOTATIONS, + toolNames, + workspaceIdDescription, + type ToolInstructionContext, + type ToolRegistrationContext, +} from "./types.js"; +import { + contentText, + countDiffStats, + logFailedToolResponse, + logToolCall, + resultOutputSchema, + textBlock, +} from "./shared.js"; + +const CLAUDE_INSTRUCTIONS = `Use ${toolNames.read} for direct file reads, ${toolNames.edit} for targeted modifications, ${toolNames.write} only for new files or complete rewrites, and ${toolNames.shell} for inspection, tests, builds, and other commands. Shell commands run with the local user's authority and are not sandboxed; workspace validation only selects their initial working directory. Follow instructions returned by ${toolNames.openWorkspace}; read applicable instruction and skill files before working in their scope.`; + +export function claudeInstructions({ + agents, + skills, +}: ToolInstructionContext): string { + return `${agents}${skills}${CLAUDE_INSTRUCTIONS}`; +} + +export function registerClaudeTools(context: ToolRegistrationContext): void { + registerClaudeMutationTools(context); + registerShellTool(context); +} + +const CLAUDE_SHELL_DESCRIPTION = `Run a shell command with the local user's authority. Commands are not sandboxed; workspace validation only selects the initial working directory. Use this for file inspection, tests, builds, package scripts, and other commands.`; + +function registerClaudeMutationTools(context: ToolRegistrationContext): void { + const { server, config, workspaces } = context; + + server.registerTool( + toolNames.write, + { + title: "Write file", + description: `Create or completely overwrite a file in a workspace. Prefer ${toolNames.edit} for targeted changes to existing files.`, + inputSchema: { + workspaceId: z.string().describe(workspaceIdDescription), + path: z + .string() + .describe("File path to write, relative to the workspace root."), + content: z.string().describe("Complete new file content."), + }, + outputSchema: resultOutputSchema(), + annotations: WRITE_TOOL_ANNOTATIONS, + }, + async ({ workspaceId, ...input }) => { + const startedAt = performance.now(); + const workspace = workspaces.getWorkspace(workspaceId); + workspaces.resolvePath(workspace, input.path); + const response = await writeFileTool(input, { + cwd: workspace.root, + root: workspace.root, + }); + + if (response.isError) { + logFailedToolResponse( + config, + { + tool: toolNames.write, + workspaceId, + path: input.path, + }, + response.content, + startedAt, + ); + return response; + } + + logToolCall(config, { + tool: toolNames.write, + workspaceId, + path: input.path, + success: true, + durationMs: Math.round(performance.now() - startedAt), + }); + + return { + ...response, + structuredContent: { + result: contentText(response.content), + }, + }; + }, + ); + + server.registerTool( + toolNames.edit, + { + title: "Edit file", + description: `Edit one file in a workspace by replacing exact text blocks. Prefer this over ${toolNames.write} for targeted changes. Each oldText must match a unique, non-overlapping region of the original file; merge nearby changes into one edit and keep oldText as small as possible while still unique.`, + inputSchema: { + workspaceId: z.string().describe(workspaceIdDescription), + path: z + .string() + .describe("File path to edit, relative to the workspace root."), + edits: z + .array( + z.object({ + oldText: z + .string() + .describe( + "Exact text to replace. Must match uniquely in the original file.", + ), + newText: z.string().describe("Replacement text."), + }), + ) + .min(1), + }, + outputSchema: resultOutputSchema({ + status: z.literal("applied"), + }), + annotations: EDIT_TOOL_ANNOTATIONS, + }, + async ({ workspaceId, ...input }) => { + const startedAt = performance.now(); + const workspace = workspaces.getWorkspace(workspaceId); + workspaces.resolvePath(workspace, input.path); + const response = await editFileTool(input, { + cwd: workspace.root, + root: workspace.root, + }); + + if (response.isError) { + logFailedToolResponse( + config, + { + tool: toolNames.edit, + workspaceId, + path: input.path, + }, + response.content, + startedAt, + ); + return response; + } + + const stats = countDiffStats( + response.details?.patch ?? response.details?.diff, + ); + const editResultText = `Edited ${input.path} (+${stats.additions} -${stats.removals}).`; + const editContent = [textBlock(editResultText)]; + logToolCall(config, { + tool: toolNames.edit, + workspaceId, + path: input.path, + success: true, + durationMs: Math.round(performance.now() - startedAt), + }); + + return { + content: editContent, + structuredContent: { + status: "applied", + result: contentText(editContent), + }, + }; + }, + ); +} + +function registerShellTool(context: ToolRegistrationContext): void { + const { server, config, workspaces } = context; + + server.registerTool( + toolNames.shell, + { + title: "Bash", + description: CLAUDE_SHELL_DESCRIPTION, + inputSchema: { + workspaceId: z.string().describe(workspaceIdDescription), + command: z + .string() + .describe("Shell command to execute."), + workingDirectory: z + .string() + .optional() + .describe( + "Optional working directory relative to the workspace root. Defaults to the workspace root.", + ), + timeout: z + .number() + .positive() + .max(300) + .optional() + .describe("Timeout in seconds. Defaults to 30, max 300."), + }, + outputSchema: resultOutputSchema(), + annotations: SHELL_TOOL_ANNOTATIONS, + }, + async ({ workspaceId, workingDirectory, ...input }) => { + const startedAt = performance.now(); + const workspace = workspaces.getWorkspace(workspaceId); + const cwd = workspaces.resolveWorkingDirectory( + workspace, + workingDirectory, + ); + const response = await runShellTool(input, { + cwd, + root: workspace.root, + }); + + if (response.isError) { + logFailedToolResponse( + config, + { + tool: toolNames.shell, + workspaceId, + workingDirectory: workingDirectory ?? ".", + command: input.command, + commandLength: input.command.length, + }, + response.content, + startedAt, + ); + return response; + } + + logToolCall(config, { + tool: toolNames.shell, + workspaceId, + workingDirectory: workingDirectory ?? ".", + command: input.command, + commandLength: input.command.length, + success: true, + durationMs: Math.round(performance.now() - startedAt), + }); + + return { + ...response, + structuredContent: { + result: contentText(response.content), + }, + }; + }, + ); +} diff --git a/src/tool-surfaces/codex.ts b/src/tool-surfaces/codex.ts new file mode 100644 index 000000000..526e175bb --- /dev/null +++ b/src/tool-surfaces/codex.ts @@ -0,0 +1,321 @@ +import * as z from "zod/v4"; +import { applyPatch } from "../apply-patch.js"; +import type { ProcessSnapshot } from "../process-sessions.js"; +import { + EDIT_TOOL_ANNOTATIONS, + SHELL_TOOL_ANNOTATIONS, + toolNames, + workspaceIdDescription, + type ToolRegistrationContext, +} from "./types.js"; +import { + contentText, + resultOutputSchema, + runLoggedToolOperation, + textBlock, +} from "./shared.js"; + +type CodexRegistration = (context: ToolRegistrationContext) => void; + +const CODEX_INSTRUCTIONS = `Use ${toolNames.read} for direct file reads, apply_patch for all file modifications, exec_command for inspection, tests, builds, and other commands, and write_stdin to poll or interact with running processes. Commands run with the local user's authority and are not sandboxed; workspace validation only selects their initial working directory. Follow instructions returned by ${toolNames.openWorkspace}; read applicable instruction and skill files before working in their scope.`; + +export function codexInstructions(): string { + return CODEX_INSTRUCTIONS; +} + +export function registerCodexTools(context: ToolRegistrationContext): void { + for (const register of CODEX_REGISTRATIONS) { + register(context); + } +} + +const CODEX_REGISTRATIONS: readonly CodexRegistration[] = [ + registerApplyPatchTool, + registerCodexProcessTools, +]; + +function processResult(snapshot: ProcessSnapshot): string { + const status = snapshot.running + ? `Process running with session ID ${snapshot.sessionId}.` + : snapshot.signal + ? `Process exited after signal ${snapshot.signal}.` + : `Process exited with code ${snapshot.exitCode ?? "unknown"}.`; + return snapshot.output + ? `${snapshot.output.replace(/\n$/, "")}\n${status}` + : status; +} + +function processOutputSchema(): z.ZodRawShape { + return resultOutputSchema({ + sessionId: z.number().optional(), + running: z.boolean(), + exitCode: z.number().int().optional(), + signal: z.string().optional(), + wallTimeMs: z.number().nonnegative(), + outputTruncated: z.boolean(), + }); +} + +function processToolResponse(snapshot: ProcessSnapshot) { + const result = processResult(snapshot); + const content = [textBlock(result)]; + return { + content, + structuredContent: { + result, + sessionId: snapshot.sessionId, + running: snapshot.running, + exitCode: snapshot.exitCode, + signal: snapshot.signal, + wallTimeMs: snapshot.wallTimeMs, + outputTruncated: snapshot.outputTruncated, + }, + }; +} + +function registerApplyPatchTool(context: ToolRegistrationContext): void { + const { server, config, workspaces } = context; + + server.registerTool( + "apply_patch", + { + title: "Apply patch", + description: + "Apply one Codex-style patch in a workspace. Supports adding, overwriting, updating, deleting, and moving files. Use this for all file modifications. Paths must be relative to the workspace.", + inputSchema: { + workspaceId: z.string().describe(workspaceIdDescription), + patch: z + .string() + .describe( + "Patch text enclosed by *** Begin Patch and *** End Patch markers.", + ), + }, + outputSchema: resultOutputSchema({ + additions: z.number(), + removals: z.number(), + files: z.array( + z.object({ + path: z.string(), + previousPath: z.string().optional(), + operation: z.enum(["add", "update", "delete", "move"]), + }), + ), + }), + annotations: EDIT_TOOL_ANNOTATIONS, + }, + async ({ workspaceId, patch }) => { + const startedAt = performance.now(); + const applied = await runLoggedToolOperation( + config, + { tool: "apply_patch", workspaceId }, + startedAt, + async () => { + const workspace = workspaces.getWorkspace(workspaceId); + return applyPatch(workspace.root, patch); + }, + ); + const paths = applied.files.map((file) => file.path).join(", "); + const result = `Applied patch to ${applied.files.length} file(s): ${paths}`; + const content = [textBlock(result)]; + + return { + content, + structuredContent: { + result, + additions: applied.additions, + removals: applied.removals, + files: applied.files, + }, + }; + }, + ); +} + +function registerCodexProcessTools(context: ToolRegistrationContext): void { + const { server, config, workspaces, processSessions } = context; + + server.registerTool( + "exec_command", + { + title: "Execute command", + description: + "Run a command with the local user's authority. Commands are not sandboxed; workspace validation only selects the initial working directory. Returns the result when it exits during the yield window, otherwise returns a sessionId for write_stdin. Use this for file inspection, tests, builds, package scripts, and long-running processes.", + inputSchema: { + workspaceId: z.string().describe(workspaceIdDescription), + cmd: z.string().min(1).describe("Shell command to execute."), + tty: z + .boolean() + .optional() + .describe( + "Allocate a pseudo-terminal for interactive commands. Defaults to false.", + ), + columns: z + .number() + .int() + .min(1) + .max(1_000) + .optional() + .describe("Initial PTY width. Defaults to 80."), + rows: z + .number() + .int() + .min(1) + .max(1_000) + .optional() + .describe("Initial PTY height. Defaults to 24."), + workingDirectory: z + .string() + .optional() + .describe( + "Working directory relative to the workspace root. Defaults to the workspace root.", + ), + yieldTimeMs: z + .number() + .int() + .min(0) + .max(30_000) + .optional() + .describe( + "Milliseconds to wait before returning a running session. Defaults to 10000.", + ), + maxOutputTokens: z + .number() + .int() + .positive() + .max(100_000) + .optional() + .describe("Approximate output token budget. Defaults to 10000."), + }, + outputSchema: processOutputSchema(), + annotations: SHELL_TOOL_ANNOTATIONS, + }, + async ({ + workspaceId, + cmd, + tty, + columns, + rows, + workingDirectory, + yieldTimeMs, + maxOutputTokens, + }) => { + const startedAt = performance.now(); + const snapshot = await runLoggedToolOperation( + config, + { + tool: "exec_command", + workspaceId, + workingDirectory: workingDirectory ?? ".", + command: cmd, + commandLength: cmd.length, + }, + startedAt, + async () => { + const workspace = workspaces.getWorkspace(workspaceId); + const cwd = workspaces.resolveWorkingDirectory( + workspace, + workingDirectory, + ); + return processSessions.start({ + workspaceId, + command: cmd, + cwd, + workspaceRoot: workspace.root, + tty, + columns, + rows, + yieldTimeMs, + maxOutputTokens, + }); + }, + ); + + return processToolResponse(snapshot); + }, + ); + + server.registerTool( + "write_stdin", + { + title: "Write to process", + description: + "Poll or write characters to a process returned by exec_command. Omit chars or pass an empty string to poll. Pass \\u0003 to send Ctrl-C.", + inputSchema: { + workspaceId: z + .string() + .describe("Workspace identifier used to start the process."), + sessionId: z + .number() + .describe("Process session identifier returned by exec_command."), + chars: z + .string() + .optional() + .describe( + "Characters to write. Omit or pass an empty string to poll.", + ), + columns: z + .number() + .int() + .min(1) + .max(1_000) + .optional() + .describe("Resize a PTY to this width."), + rows: z + .number() + .int() + .min(1) + .max(1_000) + .optional() + .describe("Resize a PTY to this height."), + yieldTimeMs: z + .number() + .int() + .min(0) + .max(30_000) + .optional() + .describe( + "Milliseconds to wait for process output or completion. Defaults to 10000.", + ), + maxOutputTokens: z + .number() + .int() + .positive() + .max(100_000) + .optional() + .describe("Approximate output token budget. Defaults to 10000."), + }, + outputSchema: processOutputSchema(), + annotations: SHELL_TOOL_ANNOTATIONS, + }, + async ({ + workspaceId, + sessionId, + chars, + columns, + rows, + yieldTimeMs, + maxOutputTokens, + }) => { + const startedAt = performance.now(); + const snapshot = await runLoggedToolOperation( + config, + { tool: "write_stdin", workspaceId }, + startedAt, + async () => { + workspaces.getWorkspace(workspaceId); + return processSessions.write({ + workspaceId, + sessionId, + chars, + columns, + rows, + yieldTimeMs, + maxOutputTokens, + }); + }, + ); + + return processToolResponse(snapshot); + }, + ); +} diff --git a/src/tool-surfaces/index.ts b/src/tool-surfaces/index.ts new file mode 100644 index 000000000..f86a6e118 --- /dev/null +++ b/src/tool-surfaces/index.ts @@ -0,0 +1,19 @@ +import type { ToolMode } from "../config.js"; +import { codexInstructions, registerCodexTools } from "./codex.js"; +import { claudeInstructions, registerClaudeTools } from "./claude.js"; +import { type ToolSurface } from "./types.js"; + +const TOOL_SURFACES: Record = { + claude: { + register: registerClaudeTools, + instructions: claudeInstructions, + }, + codex: { + register: registerCodexTools, + instructions: codexInstructions, + }, +}; + +export function getToolSurface(mode: ToolMode): ToolSurface { + return TOOL_SURFACES[mode]; +} diff --git a/src/tool-surfaces/shared.ts b/src/tool-surfaces/shared.ts new file mode 100644 index 000000000..abfc0ba3a --- /dev/null +++ b/src/tool-surfaces/shared.ts @@ -0,0 +1,119 @@ +import * as z from "zod/v4"; +import { logEvent, commandPreview } from "../logger.js"; +import type { ServerConfig } from "../config.js"; +import { + WORKSPACE_APP_URI, + type DiffStats, + type ToolContent, + type ToolLogFields, + type ToolWidgetDescriptorMeta, +} from "./types.js"; + +export function resultOutputSchema(extra: z.ZodRawShape = {}): z.ZodRawShape { + return { + result: z + .string() + .describe( + "Model-readable result text for follow-up reasoning and plain MCP hosts.", + ), + ...extra, + }; +} + +export function workspaceAppDescriptorMeta(config: ServerConfig): ToolWidgetDescriptorMeta { + if (!config.uiEnabled) return { _meta: {} }; + + return { + _meta: { + ui: { + resourceUri: WORKSPACE_APP_URI, + visibility: ["model"], + }, + }, + }; +} + +export function logToolCall(config: ServerConfig, fields: ToolLogFields): void { + if (!config.logging.toolCalls) return; + + const { command, ...safeFields } = fields; + logEvent(config.logging, fields.success ? "info" : "warn", "tool_call", { + ...safeFields, + commandPreview: + config.logging.shellCommands && command + ? commandPreview(command) + : undefined, + }); +} + +export async function runLoggedToolOperation( + config: ServerConfig, + fields: Omit, + startedAt: number, + operation: () => Promise, +): Promise { + try { + const result = await operation(); + logToolCall(config, { + ...fields, + success: true, + durationMs: Math.round(performance.now() - startedAt), + }); + return result; + } catch (error) { + logToolCall(config, { + ...fields, + success: false, + durationMs: Math.round(performance.now() - startedAt), + error: error instanceof Error ? error.message : String(error), + }); + throw error; + } +} + +export function contentText(content: ToolContent[]): string { + return content + .filter( + (item): item is { type: "text"; text: string } => item.type === "text", + ) + .map((item) => item.text) + .join("\n"); +} + +function toolErrorPreview(content: ToolContent[]): string | undefined { + const text = contentText(content).replace(/\s+/g, " ").trim(); + if (!text) return undefined; + return text.length > 240 ? `${text.slice(0, 237)}...` : text; +} + +export function logFailedToolResponse( + config: ServerConfig, + fields: Omit, + content: ToolContent[], + startedAt: number, +): void { + logToolCall(config, { + ...fields, + success: false, + durationMs: Math.round(performance.now() - startedAt), + error: toolErrorPreview(content), + }); +} + +export function textBlock(text: string): ToolContent { + return { type: "text", text }; +} + +export function countDiffStats(diff: string | undefined): DiffStats { + if (!diff) return { additions: 0, removals: 0 }; + + let additions = 0; + let removals = 0; + + for (const line of diff.split("\n")) { + if (line.startsWith("+") && !line.startsWith("+++")) additions++; + if (line.startsWith("-") && !line.startsWith("---")) removals++; + } + + return { additions, removals }; +} diff --git a/src/tool-surfaces/types.ts b/src/tool-surfaces/types.ts new file mode 100644 index 000000000..a9d8131b8 --- /dev/null +++ b/src/tool-surfaces/types.ts @@ -0,0 +1,91 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { ProcessSessionManager } from "../process-sessions.js"; +import type { ServerConfig } from "../config.js"; +import type { WorkspaceRegistry } from "../workspaces.js"; + +export const WORKSPACE_APP_URI = "ui://devspace/workspace-app.html"; + +export const toolNames = { + openWorkspace: "open_workspace", + read: "read", + write: "write", + edit: "edit", + shell: "bash", +} as const; + +export const workspaceIdDescription = + "Workspace to use. Reuse the current project's workspaceId."; + +export const WRITE_TOOL_ANNOTATIONS = { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: false, + openWorldHint: false, +}; + +export const EDIT_TOOL_ANNOTATIONS = { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: false, + openWorldHint: false, +}; + +export const SHELL_TOOL_ANNOTATIONS = { + readOnlyHint: false, + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, +}; + +export type ToolContent = + | { type: "text"; text: string } + | { type: "image"; data: string; mimeType: string }; + +export interface ToolLogFields { + tool: string; + workspaceId?: string; + path?: string; + workingDirectory?: string; + command?: string; + commandLength?: number; + success: boolean; + durationMs: number; + error?: string; +} + +export interface DiffStats { + additions: number; + removals: number; +} + +export interface ToolDefinitionMeta extends Record { + ui: { + resourceUri: string; + visibility: ["model"]; + }; +} + +export type EmptyToolDefinitionMeta = Record & { + "ui/resourceUri"?: string; +}; + +export interface ToolWidgetDescriptorMeta { + _meta: ToolDefinitionMeta | EmptyToolDefinitionMeta; +} + +export interface ToolRegistrationContext { + server: McpServer; + config: ServerConfig; + workspaces: WorkspaceRegistry; + processSessions: ProcessSessionManager; +} + +export interface ToolInstructionContext { + agents: string; + skills: string; +} + +export interface ToolSurface { + register(context: ToolRegistrationContext): void; + instructions(context: ToolInstructionContext): string; +} diff --git a/src/ui/assets/provider-logos/copilot-dark.svg b/src/ui/assets/provider-logos/copilot-dark.svg index d09df8056..275b83930 100644 --- a/src/ui/assets/provider-logos/copilot-dark.svg +++ b/src/ui/assets/provider-logos/copilot-dark.svg @@ -1 +1 @@ - + diff --git a/src/ui/assets/provider-logos/copilot-light.svg b/src/ui/assets/provider-logos/copilot-light.svg new file mode 100644 index 000000000..b52d96cff --- /dev/null +++ b/src/ui/assets/provider-logos/copilot-light.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/provider-logos/cursor-dark.svg b/src/ui/assets/provider-logos/cursor-dark.svg index d50421b51..6849fbc32 100644 --- a/src/ui/assets/provider-logos/cursor-dark.svg +++ b/src/ui/assets/provider-logos/cursor-dark.svg @@ -1 +1,12 @@ - + + + + + + + + diff --git a/src/ui/assets/provider-logos/cursor-light.svg b/src/ui/assets/provider-logos/cursor-light.svg new file mode 100644 index 000000000..b054b189c --- /dev/null +++ b/src/ui/assets/provider-logos/cursor-light.svg @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/ui/assets/provider-logos/openai-dark.svg b/src/ui/assets/provider-logos/openai-dark.svg index 7e19c92d2..cd86afb59 100644 --- a/src/ui/assets/provider-logos/openai-dark.svg +++ b/src/ui/assets/provider-logos/openai-dark.svg @@ -1 +1,11 @@ - + + + + + + + + + + + diff --git a/src/ui/assets/provider-logos/openai-light.svg b/src/ui/assets/provider-logos/openai-light.svg new file mode 100644 index 000000000..a57ca0dab --- /dev/null +++ b/src/ui/assets/provider-logos/openai-light.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/ui/assets/provider-logos/opencode-dark.svg b/src/ui/assets/provider-logos/opencode-dark.svg index 62e10df44..0bdfe0890 100644 --- a/src/ui/assets/provider-logos/opencode-dark.svg +++ b/src/ui/assets/provider-logos/opencode-dark.svg @@ -1 +1 @@ - + diff --git a/src/ui/assets/provider-logos/opencode-light.svg b/src/ui/assets/provider-logos/opencode-light.svg new file mode 100644 index 000000000..8a2743757 --- /dev/null +++ b/src/ui/assets/provider-logos/opencode-light.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/provider-logos/pi-on-light.svg b/src/ui/assets/provider-logos/pi-on-light.svg new file mode 100644 index 000000000..5472a94ee --- /dev/null +++ b/src/ui/assets/provider-logos/pi-on-light.svg @@ -0,0 +1 @@ + diff --git a/src/ui/card-types.test.ts b/src/ui/card-types.test.ts index 3c05d1449..0e1a387c9 100644 --- a/src/ui/card-types.test.ts +++ b/src/ui/card-types.test.ts @@ -1,113 +1,24 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { - isEditTool, - isExpandableCard, - isInitiallyExpandedCard, - isPatchTool, - isShellTool, - isToolName, -} from "./card-types.js"; +import { isExpandableCard } from "./card-types.js"; -test("the supported coding tools are recognized as card tools", () => { - for (const tool of ["apply_patch", "exec_command", "write_stdin"]) { - assert.equal(isToolName(tool), true, `${tool} should be a recognized card tool`); - } +test("aggregate review opens when a patch is available", () => { + const card = { + tool: "show_changes" as const, + files: [{ path: "src/a.ts", type: "change" as const }], + payload: { patch: "diff --git a/src/a.ts b/src/a.ts" }, + }; + assert.equal(isExpandableCard(card), true); }); -test("tool classification distinguishes patch, edit, and shell operations", () => { - assert.equal(isPatchTool("apply_patch"), true); - assert.equal(isEditTool("apply_patch"), false); - assert.equal(isShellTool("apply_patch"), false); - assert.equal(isShellTool("exec_command"), true); - assert.equal(isShellTool("write_stdin"), true); - assert.equal(isEditTool("exec_command"), false); -}); - -test("a patch card expands only when it contains patch content", () => { - assert.equal( - isExpandableCard({ tool: "apply_patch", payload: { patch: "diff --git a/a b/a" } }), - true, - ); - assert.equal(isExpandableCard({ tool: "apply_patch" }), false); -}); - -test("a single-file patch opens immediately", () => { - assert.equal( - isInitiallyExpandedCard({ - tool: "apply_patch", - files: [{ path: "src/a.ts", operation: "update" }], - payload: { patch: "diff --git a/src/a.ts b/src/a.ts" }, - }), - true, - ); -}); - -test("a multi-file patch stays collapsed", () => { - assert.equal( - isInitiallyExpandedCard({ - tool: "apply_patch", - files: [ - { path: "src/a.ts", operation: "update" }, - { path: "src/b.ts", operation: "add" }, - ], - payload: { patch: "diff --git a/src/a.ts b/src/a.ts" }, - }), - false, - ); -}); - -test("show changes still opens immediately", () => { - assert.equal( - isInitiallyExpandedCard({ - tool: "show_changes", - files: [{ path: "src/a.ts", type: "change" }], - payload: { patch: "diff --git a/src/a.ts b/src/a.ts" }, - }), - true, - ); -}); - -test("a workspace card expands when it contains provider metadata", () => { - assert.equal( - isExpandableCard({ - tool: "open_workspace", - agentProviders: [{ id: "codex" }], - }), - true, - ); -}); - -test("a workspace card with details opens immediately", () => { - assert.equal( - isInitiallyExpandedCard({ - tool: "open_workspace", - skills: [{ name: "research" }], - }), - true, - ); -}); - -test("a workspace card expands when it contains agent metadata", () => { - assert.equal( - isExpandableCard({ - tool: "open_workspace", - agents: [{ name: "reviewer", provider: "codex" }], - }), - true, - ); -}); - -test("a workspace card expands when it contains available instruction files", () => { - assert.equal( - isExpandableCard({ - tool: "open_workspace", - availableAgentsFiles: [{ path: "nested/AGENTS.md" }], - }), - true, - ); -}); - -test("an empty workspace card stays collapsed", () => { +test("workspace details open only when there is useful context", () => { assert.equal(isExpandableCard({ tool: "open_workspace" }), false); + assert.equal(isExpandableCard({ + tool: "open_workspace", + skills: [{ name: "research" }], + }), true); + assert.equal(isExpandableCard({ + tool: "open_workspace", + review: { available: false, reason: "Not a Git repository." }, + }), true); }); diff --git a/src/ui/card-types.ts b/src/ui/card-types.ts index cac8b8fd4..6c84d0fa7 100644 --- a/src/ui/card-types.ts +++ b/src/ui/card-types.ts @@ -1,22 +1,8 @@ import type { App } from "@modelcontextprotocol/ext-apps"; -export type ToolName = - | "open_workspace" - | "show_changes" - | "apply_patch" - | "exec_command" - | "write_stdin" - | "read" - | "write" - | "edit" - | "grep" - | "glob" - | "ls" - | "bash"; - +export type ToolName = "open_workspace" | "show_changes"; export type HostContext = NonNullable>; -export type PatchOperation = "add" | "update" | "delete" | "move"; export type ReviewFileType = | "change" | "rename-pure" @@ -41,17 +27,18 @@ export interface ToolResultCard { detached?: boolean; managed?: boolean; }; - status?: string; + review?: + | { available: true } + | { available: false; reason: string }; summary?: Record; files?: Array<{ path?: string; previousPath?: string; - operation?: PatchOperation; type?: ReviewFileType; additions?: number; removals?: number; }>; - payload?: ToolPayload; + payload?: { patch?: string }; agentsFiles?: Array<{ path?: string; content?: string; @@ -80,80 +67,6 @@ export interface ToolResultCard { instruction?: string; } -export interface ToolContent { - type: "text" | "image"; - text?: string; - data?: string; - mimeType?: string; -} - -export interface ToolPayload { - content?: ToolContent[]; - diff?: string; - patch?: string; -} - -export function isToolName(value: unknown): value is ToolName { - return ( - value === "open_workspace" || - value === "show_changes" || - value === "apply_patch" || - value === "exec_command" || - value === "write_stdin" || - value === "read" || - value === "write" || - value === "edit" || - value === "grep" || - value === "glob" || - value === "ls" || - value === "bash" - ); -} - -export function isReadTool(tool: ToolName): boolean { - return tool === "read"; -} - -export function isWriteTool(tool: ToolName): boolean { - return tool === "write"; -} - -export function isEditTool(tool: ToolName): boolean { - return tool === "edit"; -} - -export function isPatchTool(tool: ToolName): boolean { - return tool === "apply_patch"; -} - -export function isSearchTool(tool: ToolName): boolean { - return tool === "grep" || tool === "glob"; -} - -export function isShellTool(tool: ToolName): boolean { - return tool === "bash" || tool === "exec_command" || tool === "write_stdin"; -} - -export function isReviewTool(tool: ToolName): boolean { - return tool === "show_changes"; -} - -export function isToolResultCard(value: unknown): value is Omit { - return Boolean(value && typeof value === "object"); -} - -export function payloadText(payload: ToolPayload | undefined): string { - return ( - payload?.content - ?.map((item) => { - if (item.type === "text") return item.text ?? ""; - return `[${item.mimeType ?? "image"} image payload]`; - }) - .filter(Boolean) - .join("\n\n") ?? "" - ); -} - export function summaryNumber( summary: Record | undefined, key: string, @@ -163,33 +76,26 @@ export function summaryNumber( } export function isExpandableCard(card: ToolResultCard): boolean { - if (card.tool === "open_workspace") { - return ( - Number(card.summary?.agentsFiles ?? 0) > 0 || - Number(card.summary?.skills ?? 0) > 0 || - Number(card.summary?.agentProviders ?? 0) > 0 || - Number(card.summary?.agents ?? 0) > 0 || - Boolean(card.agentsFiles?.length) || - Boolean(card.availableAgentsFiles?.length) || - Boolean(card.skills?.length) || - Boolean(card.agentProviders?.length) || - Boolean(card.agents?.length) || - Boolean(card.worktree) || - Boolean(card.instruction) - ); + if (card.tool === "show_changes") { + return Boolean(card.files?.length || card.payload?.patch); } - if (isReviewTool(card.tool)) return Boolean(card.files?.length || card.payload?.patch); - if (isPatchTool(card.tool)) return Boolean(card.payload?.patch); - - return Boolean(card.payload); + return ( + Number(card.summary?.agentsFiles ?? 0) > 0 || + Number(card.summary?.skills ?? 0) > 0 || + Number(card.summary?.agentProviders ?? 0) > 0 || + Number(card.summary?.agents ?? 0) > 0 || + Boolean(card.agentsFiles?.length) || + Boolean(card.availableAgentsFiles?.length) || + Boolean(card.skills?.length) || + Boolean(card.agentProviders?.length) || + Boolean(card.agents?.length) || + Boolean(card.worktree) || + Boolean(card.instruction) || + card.review?.available === false + ); } export function isInitiallyExpandedCard(card: ToolResultCard): boolean { - if (card.tool === "open_workspace") return isExpandableCard(card); - if (isReviewTool(card.tool)) return isExpandableCard(card); - if (isPatchTool(card.tool)) { - return card.files?.length === 1 && isExpandableCard(card); - } - return false; + return isExpandableCard(card); } diff --git a/src/ui/heavy-payload.tsx b/src/ui/heavy-payload.tsx deleted file mode 100644 index a61e6dacb..000000000 --- a/src/ui/heavy-payload.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import { useEffect, useMemo, useRef } from "react"; -import { createRoot, type Root } from "react-dom/client"; -import { FileStream, getFiletypeFromFileName } from "@pierre/diffs"; -import type { FileStreamOptions } from "@pierre/diffs"; -import { PatchDiff } from "@pierre/diffs/react"; -import { - isEditTool, - isReadTool, - isWriteTool, - payloadText, - summaryNumber, - type HostContext, - type ToolResultCard, -} from "./card-types.js"; -import { pierrePrettyScrollbarCss } from "./scrollbar.js"; - -type ThemeType = "light" | "dark"; - -interface PayloadRendererOptions { - card: ToolResultCard; - hostContext?: HostContext; - errorMessage?: string | null; -} - -interface MountedPayload { - update(options: PayloadRendererOptions): void; - unmount(): void; -} - -export function mountHeavyPayload( - container: HTMLElement, - options: PayloadRendererOptions, -): MountedPayload { - const root = createRoot(container); - root.render(); - - return { - update(nextOptions) { - root.render(); - }, - unmount() { - root.unmount(); - }, - }; -} - -export type { MountedPayload, PayloadRendererOptions }; - -function HeavyPayload({ - card, - hostContext, - errorMessage = null, -}: PayloadRendererOptions) { - const themeType: ThemeType = hostContext?.theme === "light" ? "light" : "dark"; - - if (errorMessage) { - return ; - } - - if (isEditTool(card.tool) || isWriteTool(card.tool)) { - const patch = card.payload?.patch || card.payload?.diff; - if (!patch) return ; - - return ; - } - - const text = payloadText(card.payload); - if (!text) return ; - - if (isReadTool(card.tool)) { - return ( - - ); - } - - return
{text}
; -} - -function FilePayload({ - path, - text, - startLine, - themeType, -}: { - path: string; - text: string; - startLine: number; - themeType: ThemeType; -}) { - const wrapperRef = useRef(null); - const fileOptions: FileStreamOptions = useMemo( - () => ({ - theme: { - light: "pierre-light", - dark: "pierre-dark", - }, - themeType, - overflow: "scroll", - unsafeCSS: pierrePrettyScrollbarCss, - }), - [themeType], - ); - - useEffect(() => { - const wrapper = wrapperRef.current; - if (!wrapper) return; - - const fileStream = new FileStream({ - ...fileOptions, - lang: getFiletypeFromFileName(path), - startingLineIndex: startLine, - }); - const source = new ReadableStream({ - start(controller) { - controller.enqueue(text); - controller.close(); - }, - }); - let disposed = false; - - void fileStream.setup(source, wrapper).then(() => { - if (!disposed) return; - fileStream.cleanUp(); - wrapper.replaceChildren(); - }); - - return () => { - disposed = true; - fileStream.cleanUp(); - wrapper.replaceChildren(); - }; - }, [fileOptions, path, startLine, text]); - - return
; -} - -function DiffPayload({ - patch, - themeType, -}: { - patch: string; - themeType: ThemeType; -}) { - return ( - - ); -} - -function StatusLine({ - message, - tone = "muted", -}: { - message: string; - tone?: "muted" | "error"; -}) { - return
{message}
; -} diff --git a/src/ui/icons.ts b/src/ui/icons.ts index 022105d03..8fcb11c4a 100644 --- a/src/ui/icons.ts +++ b/src/ui/icons.ts @@ -6,20 +6,11 @@ import { Cpu, FileDiff, FileCheck2, - FileMinus, - FilePenLine, - FilePlus, FileText, - Files, FolderGit2, FolderOpen, - FolderTree, GitBranch, GitCommitHorizontal, - LoaderCircle, - Search, - SquareTerminal, - Terminal, createElement, type IconNode, } from "lucide"; @@ -28,42 +19,55 @@ export const toolIcons = { agents: Bot, base: GitCommitHorizontal, chevronDown: ChevronDown, - deleteFile: FileMinus, diff: FileDiff, - editFile: FilePenLine, - files: Files, folderOpen: FolderOpen, - folderTree: FolderTree, gitBranch: GitBranch, instructions: FileText, instructionAvailable: FileText, instructionLoaded: FileCheck2, - loading: LoaderCircle, providers: Cpu, - readFile: FileText, - search: Search, skills: Blocks, sourceCheckout: FolderGit2, - terminal: Terminal, - terminalSquare: SquareTerminal, warning: CircleAlert, - writeFile: FilePlus, } as const satisfies Record; export type ToolIcon = IconNode; const providerLogos = { - claude: new URL("./assets/provider-logos/claude.svg", import.meta.url).href, - codex: new URL("./assets/provider-logos/openai-dark.svg", import.meta.url).href, - copilot: new URL("./assets/provider-logos/copilot-dark.svg", import.meta.url).href, - cursor: new URL("./assets/provider-logos/cursor-dark.svg", import.meta.url).href, - opencode: new URL("./assets/provider-logos/opencode-dark.svg", import.meta.url).href, - pi: new URL("./assets/provider-logos/pi-on-dark.svg", import.meta.url).href, + claude: { + light: new URL("./assets/provider-logos/claude.svg", import.meta.url).href, + dark: new URL("./assets/provider-logos/claude.svg", import.meta.url).href, + }, + codex: { + light: new URL("./assets/provider-logos/openai-light.svg", import.meta.url).href, + dark: new URL("./assets/provider-logos/openai-dark.svg", import.meta.url).href, + }, + copilot: { + light: new URL("./assets/provider-logos/copilot-light.svg", import.meta.url).href, + dark: new URL("./assets/provider-logos/copilot-dark.svg", import.meta.url).href, + }, + cursor: { + light: new URL("./assets/provider-logos/cursor-light.svg", import.meta.url).href, + dark: new URL("./assets/provider-logos/cursor-dark.svg", import.meta.url).href, + }, + opencode: { + light: new URL("./assets/provider-logos/opencode-light.svg", import.meta.url).href, + dark: new URL("./assets/provider-logos/opencode-dark.svg", import.meta.url).href, + }, + pi: { + light: new URL("./assets/provider-logos/pi-on-light.svg", import.meta.url).href, + dark: new URL("./assets/provider-logos/pi-on-dark.svg", import.meta.url).href, + }, } as const; -export function getProviderLogo(name: string): string | undefined { +export type ProviderLogoTheme = "light" | "dark"; + +export function getProviderLogo( + name: string, + theme: ProviderLogoTheme = "dark", +): string | undefined { const normalizedName = name.trim().toLowerCase() as keyof typeof providerLogos; - return providerLogos[normalizedName]; + return providerLogos[normalizedName]?.[theme]; } export function renderIcon(icon: ToolIcon, className = "icon-svg"): SVGElement { diff --git a/src/ui/patch-display.test.ts b/src/ui/patch-display.test.ts index 612809ff6..009b5df35 100644 --- a/src/ui/patch-display.test.ts +++ b/src/ui/patch-display.test.ts @@ -1,205 +1,25 @@ import assert from "node:assert/strict"; +import test from "node:test"; import { getFileChangePathDisplay, - getPatchDisplayParts, getRenderedFileChangeKind, - getRenderedFileChangePathDisplay, } from "./patch-display.js"; -assert.deepEqual(getPatchDisplayParts({}), { - title: "Applied patch", - tone: "edit", +test("rename paths stay compact within one directory", () => { + assert.deepEqual(getFileChangePathDisplay({ + path: "src/new.ts", + previousPath: "src/old.ts", + }), { + current: "new.ts", + previous: "old.ts", + title: "src/old.ts โ†’ src/new.ts", + }); }); -assert.deepEqual( - getPatchDisplayParts({ files: [{ path: "created.ts", operation: "add" }] }), - { - title: "Added 1 file", - iconKind: "added", - tone: "write", - }, -); - -assert.deepEqual( - getPatchDisplayParts({ - files: [ - { path: "a.ts", operation: "add" }, - { path: "b.ts", operation: "add" }, - ], - }), - { - title: "Added 2 files", - iconKind: "added", - tone: "write", - }, -); - -assert.deepEqual( - getFileChangePathDisplay({ - path: "src/new-name.ts", - previousPath: "src/old-name.ts", - }), - { - current: "new-name.ts", - previous: "old-name.ts", - title: "src/old-name.ts โ†’ src/new-name.ts", - }, -); - -assert.deepEqual( - getFileChangePathDisplay({ - path: "packages/new/file.ts", - previousPath: "src/old/file.ts", - }), - { - current: "packages/new/file.ts", - previous: "src/old/file.ts", - title: "src/old/file.ts โ†’ packages/new/file.ts", - }, -); - -assert.deepEqual( - getRenderedFileChangePathDisplay( - [{ path: "src/new-name.ts", previousPath: "src/old-name.ts", operation: "move" }], - { path: "src/new-name.ts" }, - 0, - ), - { - current: "new-name.ts", - previous: "old-name.ts", - title: "src/old-name.ts โ†’ src/new-name.ts", - }, -); - -assert.deepEqual( - getRenderedFileChangePathDisplay( - [ - { path: "shared.ts", previousPath: "first.ts", operation: "move" }, - { path: "shared.ts", previousPath: "second.ts", operation: "move" }, - ], - { path: "shared.ts" }, - 1, - ), - { - current: "shared.ts", - previous: "second.ts", - title: "second.ts โ†’ shared.ts", - }, -); - -assert.equal( - getRenderedFileChangeKind( - [ - { path: "same.tmp", operation: "add" }, - { path: "same.tmp", operation: "delete" }, - ], - { path: "same.tmp", type: "new" }, - 0, - ), - "added", -); - -assert.equal( - getRenderedFileChangeKind( - [ - { path: "same.tmp", operation: "add" }, - { path: "same.tmp", operation: "delete" }, - ], - { path: "same.tmp", type: "deleted" }, - 1, - ), - "deleted", -); - -assert.equal( - getRenderedFileChangeKind( - [{ path: "report.md", operation: "add" }], - { path: "report.md", type: "change" }, +test("card metadata fills gaps in parsed diff metadata", () => { + assert.equal(getRenderedFileChangeKind( + [{ path: "renamed.ts", type: "rename-pure" }], + { path: "renamed.ts" }, 0, - ), - "edited", -); - -assert.equal( - getRenderedFileChangeKind( - [{ path: "renamed.md", previousPath: "old.md", operation: "move" }], - { path: "renamed.md", type: "change" }, - 0, - ), - "renamed", -); - -assert.deepEqual( - getPatchDisplayParts({ files: [{ path: "created.ts", type: "new" }] }), - { - title: "Added 1 file", - iconKind: "added", - tone: "write", - }, -); - -assert.deepEqual( - getPatchDisplayParts({ files: [{ path: "renamed.ts", type: "rename-changed" }] }), - { - title: "Renamed and edited 1 file", - iconKind: "renamed-edited", - tone: "edit", - }, -); - -assert.deepEqual( - getPatchDisplayParts({ files: [{ path: "removed.ts", type: "deleted" }] }), - { - title: "Deleted 1 file", - iconKind: "deleted", - tone: "delete", - }, -); - -assert.deepEqual( - getPatchDisplayParts({ files: [{ path: "unknown.ts" }] }), - { - title: "Changed 1 file", - tone: "edit", - }, -); - -assert.deepEqual( - getPatchDisplayParts({ - files: [ - { path: "created.ts", operation: "add" }, - { path: "edited.ts", operation: "update" }, - ], - }), - { - title: "Changed 2 files", - tone: "edit", - }, -); - -assert.deepEqual( - getPatchDisplayParts({ - files: [ - { path: "same.ts", operation: "add" }, - { path: "same.ts", operation: "update" }, - ], - }), - { - title: "Changed 1 file", - tone: "edit", - }, -); - -assert.deepEqual( - getPatchDisplayParts({ - files: [ - { path: "edited.ts", operation: "update" }, - { path: "moved.ts", previousPath: "old.ts", operation: "move" }, - { path: "removed.ts", operation: "delete" }, - ], - }), - { - title: "Changed 3 files", - tone: "edit", - }, -); + ), "renamed"); +}); diff --git a/src/ui/patch-display.ts b/src/ui/patch-display.ts index ec1f7ad29..83b990e9d 100644 --- a/src/ui/patch-display.ts +++ b/src/ui/patch-display.ts @@ -8,13 +8,7 @@ export type FileChangeKind = | "renamed-edited" | "unknown"; -type ToolResultFile = NonNullable[number]; - -export interface PatchDisplayParts { - title: string; - iconKind?: FileChangeKind; - tone: "edit" | "write" | "delete"; -} +type ReviewFile = NonNullable[number]; export interface FileChangePathDisplay { current: string; @@ -33,37 +27,22 @@ const fileChangeLabels: Record, string> = { export function getPatchDisplayParts( card: Pick, options: { emptyTitle?: string } = {}, -): PatchDisplayParts { +): { title: string } { const files = card.files ?? []; const fileCount = countChangedFiles(files); - - if (fileCount === 0) { - return { title: options.emptyTitle ?? "Applied patch", tone: "edit" }; - } + if (fileCount === 0) return { title: options.emptyTitle ?? "Changes ready" }; const kinds = new Set(files.map(getFileChangeKind)); - const singleKind = kinds.size === 1 ? [...kinds][0] : undefined; - const display: PatchDisplayParts = { - title: changeTitle(singleKind, fileCount), - tone: changeTone(singleKind), + const kind = kinds.size === 1 ? [...kinds][0] : undefined; + const noun = fileCount === 1 ? "file" : "files"; + return { + title: kind && kind !== "unknown" + ? `${fileChangeLabels[kind]} ${fileCount} ${noun}` + : `Changed ${fileCount} ${noun}`, }; - - if (singleKind && singleKind !== "unknown") display.iconKind = singleKind; - return display; } -export function getFileChangeKind(file: ToolResultFile): FileChangeKind { - switch (file.operation) { - case "add": - return "added"; - case "update": - return "edited"; - case "delete": - return "deleted"; - case "move": - return "renamed"; - } - +export function getFileChangeKind(file: ReviewFile): FileChangeKind { switch (file.type) { case "new": return "added"; @@ -82,51 +61,23 @@ export function getFileChangeKind(file: ToolResultFile): FileChangeKind { export function getRenderedFileChangeKind( files: NonNullable, - parsedFile: Pick, + parsedFile: Pick, index: number, ): FileChangeKind { const parsedKind = getFileChangeKind(parsedFile); - - // The diff parser is authoritative for additions, deletions, and native Git - // rename metadata. This also keeps repeated operations on the same path from - // reusing the first matching card entry. - if (parsedKind !== "edited" && parsedKind !== "unknown") return parsedKind; - - // apply_patch emits one card file per generated diff in the same order. Its - // move patch currently lacks Git rename metadata, so preserve the explicit - // move operation when the destination lines up with the parsed diff. - const indexedFile = files[index]; - if ( - indexedFile?.operation === "move" && - (!parsedFile.path || indexedFile.path === parsedFile.path) - ) { - return "renamed"; - } - - const movedFile = files.find((file) => ( - file.operation === "move" && - file.path === parsedFile.path && - (!parsedFile.previousPath || file.previousPath === parsedFile.previousPath) - )); - if (movedFile) return "renamed"; - - // A parsed content change is more accurate than an "add" directive that - // overwrote an existing file. - if (parsedKind === "edited") return "edited"; - - return indexedFile ? getFileChangeKind(indexedFile) : "unknown"; + return parsedKind === "unknown" + ? getFileChangeKind(files[index] ?? {}) + : parsedKind; } export function getFileChangePathDisplay( - file: Pick, + file: Pick, ): FileChangePathDisplay | undefined { const current = file.path ?? file.previousPath; if (!current) return undefined; const previous = file.previousPath; - if (!previous || previous === current) { - return { current, title: current }; - } + if (!previous || previous === current) return { current, title: current }; const sameDirectory = pathDirectory(previous) === pathDirectory(current); return { @@ -138,16 +89,13 @@ export function getFileChangePathDisplay( export function getRenderedFileChangePathDisplay( files: NonNullable, - parsedFile: Pick, + parsedFile: Pick, index: number, ): FileChangePathDisplay | undefined { const indexedFile = files[index]; const matchedFile = indexedFile?.path === parsedFile.path ? indexedFile - : files.find((file) => ( - file.path === parsedFile.path && - (!parsedFile.previousPath || !file.previousPath || file.previousPath === parsedFile.previousPath) - )); + : files.find((file) => file.path === parsedFile.path); const cardFile = matchedFile ?? indexedFile; return getFileChangePathDisplay({ @@ -163,37 +111,14 @@ export function fileChangeKindLabel(kind: FileChangeKind): string { function countChangedFiles(files: NonNullable): number { const paths = new Set(); let unnamedFiles = 0; - for (const file of files) { const path = file.path ?? file.previousPath; - if (path) { - paths.add(path); - } else { - unnamedFiles += 1; - } + if (path) paths.add(path); + else unnamedFiles += 1; } - return paths.size + unnamedFiles; } -function changeTitle(kind: FileChangeKind | undefined, fileCount: number): string { - if (kind && kind !== "unknown") { - return `${fileChangeLabels[kind]} ${fileCount} ${fileNoun(fileCount)}`; - } - - return `Changed ${fileCount} ${fileNoun(fileCount)}`; -} - -function changeTone(kind: FileChangeKind | undefined): PatchDisplayParts["tone"] { - if (kind === "added") return "write"; - if (kind === "deleted") return "delete"; - return "edit"; -} - -function fileNoun(fileCount: number): "file" | "files" { - return fileCount === 1 ? "file" : "files"; -} - function pathDirectory(path: string): string { const separatorIndex = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")); return separatorIndex === -1 ? "" : path.slice(0, separatorIndex); diff --git a/src/ui/tool-display.test.ts b/src/ui/tool-display.test.ts deleted file mode 100644 index 71d3504d7..000000000 --- a/src/ui/tool-display.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -import assert from "node:assert/strict"; -import type { ToolResultCard } from "./card-types.js"; -import { toolIcons } from "./icons.js"; -import { getToolDisplay, getToolHeaderSummary } from "./tool-display.js"; - -const displayCases: Array<[ToolResultCard, { title: string; tone: string }]> = [ - [{ tool: "open_workspace", root: "/tmp/project" }, { title: "Opened workspace", tone: "workspace" }], - [{ tool: "open_workspace", root: "/tmp/project", workspaceReused: true }, { title: "Reused workspace", tone: "workspace" }], - [{ tool: "open_workspace", root: "/tmp/project", mode: "worktree" }, { title: "Opened workspace", tone: "workspace" }], - [{ tool: "open_workspace", root: "/tmp/project", mode: "worktree", workspaceReused: true }, { title: "Reused workspace", tone: "workspace" }], - [{ tool: "read", path: "src/read.ts" }, { title: "Read file", tone: "read" }], - [{ tool: "write", path: "src/write.ts" }, { title: "Wrote file", tone: "write" }], - [{ tool: "edit", path: "src/edit.ts" }, { title: "Edited file", tone: "edit" }], - [{ - tool: "apply_patch", - files: [{ path: "src/new.ts", operation: "add" }], - }, { title: "Added 1 file", tone: "write" }], - [{ - tool: "grep", - summary: { pattern: "needle", scope: "src" }, - }, { title: "Searched files", tone: "search" }], - [{ tool: "ls", path: "src" }, { title: "Listed directory", tone: "directory" }], - [{ tool: "bash", summary: { command: "npm test", exitCode: 0 } }, { title: "Ran command", tone: "shell" }], -]; - -for (const [card, expected] of displayCases) { - assert.deepEqual(pickDisplay(getToolDisplay(card)), expected); -} - -assert.equal(getToolDisplay({ tool: "open_workspace", root: "/tmp/project" }).label, "/tmp/project"); -assert.equal( - getToolDisplay({ tool: "open_workspace", root: "/tmp/project" }).icon, - toolIcons.folderOpen, -); -assert.equal( - getToolDisplay({ tool: "open_workspace", root: "/tmp/project", mode: "worktree" }).icon, - toolIcons.gitBranch, -); -assert.equal( - getToolDisplay({ tool: "grep", summary: { pattern: "needle", scope: "src" } }).label, - "needle in src", -); - -assert.equal( - getToolDisplay({ - tool: "apply_patch", - files: [{ - path: "src/new-name.ts", - previousPath: "src/old-name.ts", - operation: "move", - }], - }).label, - "src/old-name.ts โ†’ src/new-name.ts", -); - -assert.deepEqual( - pickDisplay(getToolDisplay({ - tool: "show_changes", - files: [ - { path: "src/a.ts", type: "change" }, - { path: "src/b.ts", type: "change" }, - ], - })), - { title: "Edited 2 files", tone: "review" }, -); - -assert.deepEqual( - pickDisplay(getToolDisplay({ - tool: "show_changes", - files: [ - { path: "src/a.ts", type: "new" }, - { path: "src/b.ts", type: "change" }, - ], - })), - { title: "Changed 2 files", tone: "review" }, -); - -assert.deepEqual( - pickDisplay(getToolDisplay({ - tool: "show_changes", - files: [{ path: "src/old.ts", type: "deleted" }], - })), - { title: "Deleted 1 file", tone: "review" }, -); - -assert.equal( - getToolDisplay({ tool: "show_changes", payload: { patch: "diff --git a/a b/a" } }).title, - "Changes ready", -); - -assert.equal(getToolDisplay({ tool: "show_changes" }).title, "No changes"); - -assert.equal( - getToolDisplay({ tool: "exec_command", summary: { running: true, command: "npm test" } }).title, - "Command running", -); -assert.equal( - getToolDisplay({ tool: "exec_command", summary: { running: false, exitCode: 1 } }).title, - "Command failed", -); -assert.equal( - getToolDisplay({ tool: "write_stdin", summary: { running: false, exitCode: 0 } }).title, - "Process finished", -); -assert.equal( - getToolDisplay({ tool: "exec_command", summary: { running: true } }).state, - "running", -); -assert.equal( - getToolDisplay({ tool: "exec_command", summary: { running: false, exitCode: 0 } }).state, - "success", -); -assert.equal( - getToolDisplay({ tool: "exec_command", summary: { running: false, exitCode: 1 } }).state, - "error", -); - -assert.deepEqual( - pickDisplay(getToolDisplay({ tool: "glob", summary: { lines: 1, pattern: "**/*.ts" } })), - { title: "Found files", tone: "search" }, -); - -assert.deepEqual( - getToolHeaderSummary({ tool: "glob", summary: { lines: 1 } }), - { kind: "empty" }, -); - -assert.equal( - getToolDisplay({ - tool: "apply_patch", - files: [{ path: "src/removed.ts", operation: "delete" }], - }).icon, - toolIcons.deleteFile, -); - -assert.deepEqual( - getToolHeaderSummary({ tool: "show_changes", summary: { additions: 14, removals: 1 } }), - { kind: "diff", additions: 14, removals: 1 }, -); - -assert.deepEqual( - getToolHeaderSummary({ - tool: "open_workspace", - summary: { mode: "worktree", agentsFiles: 1, skills: 4 }, - }), - { kind: "text", text: "1 instruction ยท 4 skills" }, -); - -assert.deepEqual( - getToolHeaderSummary({ tool: "exec_command", summary: { lines: 3, wallTimeMs: 1_500 } }), - { kind: "text", text: "3 lines ยท 1.5s" }, -); - -assert.deepEqual( - getToolHeaderSummary({ tool: "grep", summary: { lines: 2 } }), - { kind: "text", text: "2 lines" }, -); - -assert.deepEqual( - getToolHeaderSummary({ tool: "read", summary: { lines: 1 } }), - { kind: "text", text: "1 line" }, -); - -assert.deepEqual( - getToolHeaderSummary({ tool: "ls", summary: { lines: 0 } }), - { kind: "text", text: "0 lines" }, -); - -assert.deepEqual( - getToolHeaderSummary({ tool: "open_workspace" }), - { kind: "empty" }, -); - -function pickDisplay(display: ReturnType) { - return { - title: display.title, - tone: display.tone, - }; -} diff --git a/src/ui/tool-display.ts b/src/ui/tool-display.ts deleted file mode 100644 index be64e0b00..000000000 --- a/src/ui/tool-display.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { - isEditTool, - isPatchTool, - isReviewTool, - isShellTool, - isWriteTool, - summaryNumber, - type ToolResultCard, -} from "./card-types.js"; -import { toolIcons, type ToolIcon } from "./icons.js"; -import { - getFileChangePathDisplay, - getPatchDisplayParts, -} from "./patch-display.js"; - -export interface ToolDisplay { - icon: ToolIcon; - title: string; - label?: string; - tone: string; - state?: "running" | "success" | "error"; -} - -export type ToolHeaderSummary = - | { kind: "diff"; additions: number; removals: number } - | { kind: "text"; text: string } - | { kind: "empty" }; - -export function getToolDisplay(card: ToolResultCard): ToolDisplay { - switch (card.tool) { - case "open_workspace": - return { - icon: card.mode === "worktree" ? toolIcons.gitBranch : toolIcons.folderOpen, - title: workspaceTitle(card), - label: card.root ?? card.path, - tone: "workspace", - }; - case "read": - return { - icon: toolIcons.readFile, - title: "Read file", - label: card.path, - tone: "read", - }; - case "write": - return { - icon: toolIcons.writeFile, - title: "Wrote file", - label: card.path, - tone: "write", - }; - case "edit": - return { - icon: toolIcons.editFile, - title: "Edited file", - label: card.path, - tone: "edit", - }; - case "apply_patch": { - const display = getPatchDisplayParts(card); - return { - icon: patchIcon(display.iconKind), - title: display.title, - label: singleFilePath(card), - tone: display.tone, - }; - } - case "grep": - return { - icon: toolIcons.search, - title: "Searched files", - label: searchLabel(card), - tone: "search", - }; - case "glob": { - return { - icon: toolIcons.files, - title: "Found files", - label: searchLabel(card), - tone: "search", - }; - } - case "ls": - return { - icon: toolIcons.folderTree, - title: "Listed directory", - label: card.path, - tone: "directory", - }; - case "bash": - case "exec_command": - return { - icon: toolIcons.terminalSquare, - title: processTitle(card, "command"), - label: processLabel(card), - tone: "shell", - state: processState(card), - }; - case "write_stdin": - return { - icon: toolIcons.terminal, - title: processTitle(card, "process"), - label: processLabel(card), - tone: "shell", - state: processState(card), - }; - case "show_changes": { - const display = getPatchDisplayParts(card, { emptyTitle: "Changes ready" }); - const fileCount = card.files?.length ?? 0; - return { - icon: toolIcons.diff, - title: fileCount > 0 || card.payload?.patch - ? display.title - : "No changes", - label: singleFilePath(card), - tone: "review", - }; - } - } -} - -export function getToolHeaderSummary(card: ToolResultCard): ToolHeaderSummary { - const summary = card.summary ?? {}; - - if (isReviewTool(card.tool) || isPatchTool(card.tool) || isEditTool(card.tool) || isWriteTool(card.tool)) { - return { - kind: "diff", - additions: summaryNumber(summary, "additions") ?? 0, - removals: summaryNumber(summary, "removals") ?? 0, - }; - } - - if (card.tool === "open_workspace") { - const parts = [ - countLabel(summaryNumber(summary, "agentsFiles"), "instruction"), - countLabel(summaryNumber(summary, "skills"), "skill"), - ].filter((part): part is string => Boolean(part)); - return parts.length > 0 ? { kind: "text", text: parts.join(" ยท ") } : { kind: "empty" }; - } - - if (isShellTool(card.tool)) { - const parts = [ - countLabel(summaryNumber(summary, "lines"), "line"), - durationLabel(summaryNumber(summary, "wallTimeMs")), - ].filter((part): part is string => Boolean(part)); - return parts.length > 0 ? { kind: "text", text: parts.join(" ยท ") } : { kind: "empty" }; - } - - if (card.tool === "grep" || card.tool === "read" || card.tool === "ls") { - const lines = countLabel(summaryNumber(summary, "lines"), "line"); - return lines ? { kind: "text", text: lines } : { kind: "empty" }; - } - - return { kind: "empty" }; -} - -function patchIcon(kind: ReturnType["iconKind"]): ToolIcon { - if (kind === "added") return toolIcons.writeFile; - if (kind === "deleted") return toolIcons.deleteFile; - if (kind === "renamed" || kind === "renamed-edited") return toolIcons.files; - return toolIcons.editFile; -} - -function workspaceTitle(card: ToolResultCard): string { - return `${card.workspaceReused ? "Reused" : "Opened"} workspace`; -} - -function singleFilePath(card: ToolResultCard): string | undefined { - if (card.files?.length === 1) { - return getFileChangePathDisplay(card.files[0])?.title ?? card.path; - } - return undefined; -} - -function searchLabel(card: ToolResultCard): string | undefined { - const pattern = card.summary?.pattern; - const scope = card.summary?.scope; - if (typeof pattern !== "string") return card.path; - return typeof scope === "string" && scope !== "." ? `${pattern} in ${scope}` : pattern; -} - -function processTitle(card: ToolResultCard, subject: "command" | "process"): string { - if (card.summary?.running === true) { - return subject === "command" ? "Command running" : "Process running"; - } - - const exitCode = summaryNumber(card.summary, "exitCode"); - if (exitCode !== undefined && exitCode !== 0) { - return subject === "command" ? "Command failed" : "Process failed"; - } - - return subject === "command" ? "Ran command" : "Process finished"; -} - -function processState(card: ToolResultCard): ToolDisplay["state"] { - if (card.summary?.running === true) return "running"; - const exitCode = summaryNumber(card.summary, "exitCode"); - if (exitCode !== undefined && exitCode !== 0) return "error"; - return exitCode === 0 ? "success" : undefined; -} - -function processLabel(card: ToolResultCard): string | undefined { - const command = card.summary?.command; - if (typeof command === "string") return command; - const sessionId = card.summary?.sessionId; - if (typeof sessionId === "number" || typeof sessionId === "string") { - return `Session ${String(sessionId)}`; - } - return card.path; -} - -function countLabel(count: number | undefined, noun: string): string | undefined { - if (count === undefined) return undefined; - return `${count} ${noun}${count === 1 ? "" : "s"}`; -} - -function durationLabel(durationMs: number | undefined): string | undefined { - if (durationMs === undefined) return undefined; - if (durationMs < 1_000) return `${Math.round(durationMs)}ms`; - return `${(durationMs / 1_000).toFixed(durationMs < 10_000 ? 1 : 0)}s`; -} diff --git a/src/ui/tool-result.test.ts b/src/ui/tool-result.test.ts new file mode 100644 index 000000000..b7c5315dd --- /dev/null +++ b/src/ui/tool-result.test.ts @@ -0,0 +1,155 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { + decodeToolResult, + toolResultFromChatGptGlobals, +} from "./tool-result.js"; + +test("workspace cards can be rebuilt from structured content without result metadata", () => { + const decoded = decodeToolResult({ + content: [], + structuredContent: { + workspaceId: "ws_1", + root: "/tmp/project", + mode: "checkout", + skills: [{ name: "tdd", description: "Tests first", path: "/tmp/tdd/SKILL.md" }], + agentsFiles: [{ path: "AGENTS.md", content: "instructions" }], + review: { available: true }, + instruction: "Reuse this workspace.", + }, + }); + + assert.equal(decoded.kind, "card"); + if (decoded.kind !== "card") return; + assert.equal(decoded.card.tool, "open_workspace"); + assert.equal(decoded.card.workspaceId, "ws_1"); + assert.equal(decoded.card.summary?.skills, 1); + assert.equal(decoded.card.summary?.agentsFiles, 1); +}); + +test("review results use rich metadata when the host provides it", () => { + const decoded = decodeToolResult({ + content: [], + structuredContent: { + workspaceId: "ws_1", + reviewRef: "a".repeat(40), + result: "Changed 1 file (+1 -0).", + }, + _meta: { + card: { + workspaceId: "ws_1", + summary: { files: 1, additions: 1, removals: 0 }, + files: [{ path: "new.txt", type: "new", additions: 1, removals: 0 }], + payload: { patch: "diff --git ..." }, + }, + }, + }); + + assert.equal(decoded.kind, "card"); + if (decoded.kind !== "card") return; + assert.equal(decoded.card.tool, "show_changes"); + assert.equal(decoded.card.files?.[0]?.path, "new.txt"); + assert.equal(decoded.card.payload?.patch, "diff --git ..."); +}); + +test("review structured content becomes a reload reference when metadata is missing", () => { + const decoded = decodeToolResult({ + content: [], + structuredContent: { + workspaceId: "ws_1", + reviewRef: "b".repeat(40), + result: "Changed 1 file (+1 -0).", + }, + }); + + assert.deepEqual(decoded, { + kind: "review-reference", + workspaceId: "ws_1", + reviewRef: "b".repeat(40), + }); +}); + +test("incomplete review metadata falls back to the durable review reference", () => { + const decoded = decodeToolResult({ + content: [], + structuredContent: { + workspaceId: "ws_1", + reviewRef: "e".repeat(40), + result: "Changed 1 file (+1 -0).", + }, + _meta: { card: {} }, + }); + + assert.deepEqual(decoded, { + kind: "review-reference", + workspaceId: "ws_1", + reviewRef: "e".repeat(40), + }); +}); + +test("older review results can reload from their structured patch", () => { + const decoded = decodeToolResult({ + content: [], + structuredContent: { + result: "Changed 1 file (+1 -0).", + summary: { files: 1, additions: 1, removals: 0 }, + files: [{ path: "new.txt", type: "new", additions: 1, removals: 0 }], + patch: "diff --git a/new.txt b/new.txt", + }, + }); + + assert.equal(decoded.kind, "card"); + if (decoded.kind !== "card") return; + assert.equal(decoded.card.tool, "show_changes"); + assert.equal(decoded.card.files?.[0]?.path, "new.txt"); + assert.equal(decoded.card.payload?.patch, "diff --git a/new.txt b/new.txt"); +}); + +test("ChatGPT globals restore structured output and hidden MCP result metadata together", () => { + const fullResult: CallToolResult = { + content: [{ type: "text", text: "Changed 1 file." }], + structuredContent: { stale: true }, + _meta: { card: { workspaceId: "ws_1", payload: { patch: "patch" } } }, + }; + const restored = toolResultFromChatGptGlobals({ + toolOutput: { + workspaceId: "ws_1", + reviewRef: "c".repeat(40), + result: "Changed 1 file.", + }, + toolResponseMetadata: { + mcp_tool_result: fullResult, + }, + }); + + assert.deepEqual(restored?.structuredContent, { + workspaceId: "ws_1", + reviewRef: "c".repeat(40), + result: "Changed 1 file.", + }); + assert.deepEqual(restored?._meta, fullResult._meta); +}); + +test("ChatGPT globals also accept result metadata exposed directly", () => { + const restored = toolResultFromChatGptGlobals({ + toolOutput: { + workspaceId: "ws_1", + reviewRef: "d".repeat(40), + result: "Changed 1 file.", + }, + toolResponseMetadata: { + card: { + workspaceId: "ws_1", + summary: { files: 1, additions: 1, removals: 0 }, + }, + }, + }); + + assert.deepEqual(restored?._meta, { + card: { + workspaceId: "ws_1", + summary: { files: 1, additions: 1, removals: 0 }, + }, + }); +}); diff --git a/src/ui/tool-result.ts b/src/ui/tool-result.ts new file mode 100644 index 000000000..efd1bdb4e --- /dev/null +++ b/src/ui/tool-result.ts @@ -0,0 +1,265 @@ +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { ReviewFileType, ToolResultCard } from "./card-types.js"; + +export type DecodedToolResult = + | { kind: "card"; card: ToolResultCard } + | { kind: "review-reference"; workspaceId: string; reviewRef: string } + | { kind: "invalid" }; + +export interface ChatGptToolGlobals { + toolOutput?: unknown; + toolResponseMetadata?: unknown; +} + +export function decodeToolResult(result: CallToolResult): DecodedToolResult { + const structured = asRecord(result.structuredContent); + const metaCard = cardFields(asRecord(asRecord(result._meta)?.card)); + + if (structured) { + const workspaceId = stringField(structured.workspaceId); + const reviewRef = stringField(structured.reviewRef); + if (workspaceId && reviewRef) { + if (isCompleteReviewCard(metaCard)) { + return { + kind: "card", + card: { + ...metaCard, + tool: "show_changes", + workspaceId, + }, + }; + } + return { kind: "review-reference", workspaceId, reviewRef }; + } + + if (typeof structured.patch === "string" && Array.isArray(structured.files)) { + const legacyCard = cardFields({ + ...structured, + payload: { patch: structured.patch }, + }); + if (legacyCard) { + return { kind: "card", card: { ...legacyCard, tool: "show_changes" } }; + } + } + + const root = stringField(structured.root); + const mode = workspaceMode(structured.mode); + if (workspaceId && root && mode) { + const structuredCard = cardFields(structured) ?? {}; + return { + kind: "card", + card: { + ...structuredCard, + ...metaCard, + tool: "open_workspace", + workspaceId, + root, + mode, + summary: metaCard?.summary ?? workspaceSummary(structuredCard), + }, + }; + } + } + + // Existing conversations created before reviewRef was added can still render + // while the host supplies their live MCP Apps result metadata. + if (metaCard?.workspaceId && (metaCard.files?.length || metaCard.payload?.patch)) { + return { kind: "card", card: { ...metaCard, tool: "show_changes" } }; + } + if (metaCard?.workspaceId && metaCard.root && metaCard.mode) { + return { kind: "card", card: { ...metaCard, tool: "open_workspace" } }; + } + + return { kind: "invalid" }; +} + +function isCompleteReviewCard( + card: Partial | undefined, +): card is Partial & { + files: NonNullable; + payload: { patch: string }; + summary: Record; +} { + if (!card || !Array.isArray(card.files) || typeof card.payload?.patch !== "string") { + return false; + } + return numberField(card.summary?.files) !== undefined + && numberField(card.summary?.additions) !== undefined + && numberField(card.summary?.removals) !== undefined; +} + +export function toolResultFromChatGptGlobals( + globals: ChatGptToolGlobals | undefined, +): CallToolResult | undefined { + if (!globals) return undefined; + + const responseMetadata = asRecord(globals.toolResponseMetadata); + const metadataResult = mcpToolResult(globals.toolResponseMetadata); + const structuredContent = asRecord(globals.toolOutput) + ?? asRecord(metadataResult?.structuredContent); + const resultMeta = asRecord(metadataResult?._meta) + ?? directResultMeta(responseMetadata); + if (!metadataResult && !structuredContent && !resultMeta) return undefined; + + return { + ...(metadataResult ?? { content: [] }), + ...(structuredContent ? { structuredContent } : {}), + ...(resultMeta ? { _meta: resultMeta } : {}), + } as CallToolResult; +} + +function directResultMeta( + metadata: Record | undefined, +): Record | undefined { + if (!metadata) return undefined; + return "card" in metadata ? metadata : undefined; +} + +function mcpToolResult(value: unknown): CallToolResult | undefined { + const metadata = asRecord(value); + if (!metadata) return undefined; + + const direct = asRecord(metadata.mcp_tool_result); + if (direct) return direct as CallToolResult; + + const callToolResult = asRecord(metadata.call_tool_result); + const nested = asRecord(callToolResult?.mcp_tool_result); + return nested ? nested as CallToolResult : undefined; +} + +function cardFields(record: Record | undefined): Partial | undefined { + if (!record) return undefined; + + const agentsFiles = arrayRecords(record.agentsFiles)?.map((item) => ({ + path: stringField(item.path), + content: stringField(item.content), + })); + const availableAgentsFiles = arrayRecords(record.availableAgentsFiles)?.map((item) => ({ + path: stringField(item.path), + })); + const skills = arrayRecords(record.skills)?.map((item) => ({ + name: stringField(item.name), + description: stringField(item.description), + path: stringField(item.path), + })); + const agentProviders = arrayRecords(record.agentProviders)?.map((item) => ({ + id: stringField(item.id), + model: stringField(item.model), + effort: stringField(item.effort), + note: stringField(item.note), + })); + const agents = arrayRecords(record.agents)?.map((item) => ({ + name: stringField(item.name), + description: stringField(item.description), + provider: stringField(item.provider), + model: stringField(item.model), + effort: stringField(item.effort), + })); + const files = arrayRecords(record.files)?.map((item) => ({ + path: stringField(item.path), + previousPath: stringField(item.previousPath), + type: reviewFileType(item.type), + additions: numberField(item.additions), + removals: numberField(item.removals), + })); + const worktreeRecord = asRecord(record.worktree); + const reviewRecord = asRecord(record.review); + const summary = asRecord(record.summary); + const payloadRecord = asRecord(record.payload); + + return definedFields({ + workspaceId: stringField(record.workspaceId), + path: stringField(record.path), + root: stringField(record.root), + workspaceReused: booleanField(record.workspaceReused), + includeBootstrapContext: booleanField(record.includeBootstrapContext), + mode: workspaceMode(record.mode), + sourceRoot: stringField(record.sourceRoot), + worktree: worktreeRecord + ? { + path: stringField(worktreeRecord.path), + baseRef: stringField(worktreeRecord.baseRef), + baseSha: stringField(worktreeRecord.baseSha), + dirtySource: booleanField(worktreeRecord.dirtySource), + detached: booleanField(worktreeRecord.detached), + managed: booleanField(worktreeRecord.managed), + } + : undefined, + review: reviewAvailability(reviewRecord), + summary, + files, + payload: payloadRecord ? { patch: stringField(payloadRecord.patch) } : undefined, + agentsFiles, + availableAgentsFiles, + skills, + agentProviders, + agents, + instruction: stringField(record.instruction), + }); +} + +function workspaceSummary(card: Partial): Record { + return { + mode: card.mode, + agentsFiles: card.agentsFiles?.length ?? 0, + availableAgentsFiles: card.availableAgentsFiles?.length ?? 0, + skills: card.skills?.length ?? 0, + agentProviders: card.agentProviders?.length ?? 0, + agents: card.agents?.length ?? 0, + }; +} + +function reviewAvailability( + record: Record | undefined, +): ToolResultCard["review"] { + if (!record || typeof record.available !== "boolean") return undefined; + if (record.available) return { available: true }; + const reason = stringField(record.reason); + return reason ? { available: false, reason } : undefined; +} + +function reviewFileType(value: unknown): ReviewFileType | undefined { + return value === "change" + || value === "rename-pure" + || value === "rename-changed" + || value === "new" + || value === "deleted" + ? value + : undefined; +} + +function workspaceMode(value: unknown): ToolResultCard["mode"] { + return value === "checkout" || value === "worktree" ? value : undefined; +} + +function arrayRecords(value: unknown): Array> | undefined { + if (!Array.isArray(value)) return undefined; + return value.flatMap((item) => { + const record = asRecord(item); + return record ? [record] : []; + }); +} + +function asRecord(value: unknown): Record | undefined { + return value !== null && typeof value === "object" + ? value as Record + : undefined; +} + +function stringField(value: unknown): string | undefined { + return typeof value === "string" ? value : undefined; +} + +function numberField(value: unknown): number | undefined { + return typeof value === "number" && Number.isFinite(value) ? value : undefined; +} + +function booleanField(value: unknown): boolean | undefined { + return typeof value === "boolean" ? value : undefined; +} + +function definedFields>(record: T): T { + return Object.fromEntries( + Object.entries(record).filter(([, value]) => value !== undefined), + ) as T; +} diff --git a/src/ui/vite-env.d.ts b/src/ui/vite-env.d.ts index cbe652dbe..e224b6eba 100644 --- a/src/ui/vite-env.d.ts +++ b/src/ui/vite-env.d.ts @@ -1 +1,8 @@ declare module "*.css"; + +interface Window { + openai?: { + toolOutput?: unknown; + toolResponseMetadata?: unknown; + }; +} diff --git a/src/ui/workspace-app.css b/src/ui/workspace-app.css index bee72228f..08a2ddc92 100644 --- a/src/ui/workspace-app.css +++ b/src/ui/workspace-app.css @@ -42,45 +42,14 @@ body { color: var(--color-text-primary, #f5f5f6); } -.tool-card.workspace, -.tool-card.directory { +.tool-card.workspace { --tool-accent: color-mix(in srgb, var(--color-text-primary, #f5f5f6) 34%, #3b82f6 66%); } -.tool-card.read, -.tool-card.search { - --tool-accent: color-mix(in srgb, var(--color-text-primary, #f5f5f6) 32%, #06b6d4 68%); -} - -.tool-card.write { - --tool-accent: var(--color-success-text, #6fda83); -} - -.tool-card.edit, .tool-card.review { --tool-accent: color-mix(in srgb, var(--color-text-primary, #f5f5f6) 28%, #d99742 72%); } -.tool-card.delete { - --tool-accent: var(--color-danger-text, #ee7676); -} - -.tool-card.shell { - --tool-accent: color-mix(in srgb, var(--color-text-primary, #f5f5f6) 42%, #64748b 58%); -} - -.tool-card.state-success { - --tool-accent: var(--color-success-text, #6fda83); -} - -.tool-card.state-error { - --tool-accent: var(--color-danger-text, #ee7676); -} - -.tool-card.state-running { - --tool-accent: color-mix(in srgb, var(--color-text-primary, #f5f5f6) 30%, #38bdf8 70%); -} - @supports selector(::-webkit-scrollbar) { .pretty-scrollbar::-webkit-scrollbar { width: 12px; @@ -248,29 +217,6 @@ body { transform: rotate(180deg); } -.chevron.loading { - transform: none; -} - -.chevron.loading .icon-svg { - animation: payload-spinner 700ms linear infinite; - fill: none; - stroke-linecap: round; - stroke-dasharray: 38 14; -} - -@keyframes payload-spinner { - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: reduce) { - .chevron.loading .icon-svg { - animation: none; - } -} - .tool-body { border-top: 1px solid var(--tool-card-divider); background: var(--tool-card-body-bg); @@ -708,7 +654,7 @@ body { } .review-diff-file { - overflow: hidden; + overflow: clip; border: 0; border-radius: 0; } @@ -718,6 +664,9 @@ body { } .review-diff-file-header { + position: sticky; + top: 0; + z-index: 2; display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; align-items: center; @@ -726,7 +675,7 @@ body { min-height: 42px; padding: 0 12px; border: 0; - background: transparent; + background: var(--tool-card-body-bg); color: var(--color-text-primary, #f5f5f6); cursor: pointer; font: inherit; @@ -765,7 +714,9 @@ body { } .review-single-file { - overflow: hidden; + max-height: 520px; + overflow-x: hidden; + overflow-y: auto; } .review-diff-file-header:hover { @@ -836,40 +787,24 @@ body { color: var(--color-danger-text, #ee7676); } -.pierre-diff, -.pierre-file { +.pierre-diff { --diffs-bg: var(--tool-payload-bg, var(--color-background-primary, #101114)); --diffs-light-bg: var(--color-background-primary, #ffffff); --diffs-dark-bg: var(--tool-payload-bg, var(--color-background-primary, #101114)); --diffs-font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace); --diffs-header-font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); - --diffs-font-size: var(--font-text-sm-size, 12px); + /* Keep iOS WebKit from inflating dense diff text independently of the card UI. */ + --diffs-font-size: 12px; --diffs-line-height: 20px; + + -webkit-text-size-adjust: 100%; + text-size-adjust: 100%; display: block; - max-height: 420px; - overflow: auto; + overflow: visible; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; } -.text-payload { - max-height: 420px; - margin: 0; - overflow: auto; - padding: 10px 12px; - color: var(--color-text-secondary, #c7c7ce); - font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace); - font-size: var(--font-text-sm-size, 12px); - line-height: 1.55; - white-space: pre-wrap; - overflow-wrap: break-word; -} - -.text-payload.bash { - color: var(--color-text-primary, #f5f5f6); - background: var(--color-background-primary, #101114); -} - @media (max-width: 520px) { .tool-header { grid-template-columns: 36px minmax(0, 1fr) auto 18px; diff --git a/src/ui/workspace-app.tsx b/src/ui/workspace-app.tsx index cd833c2cc..27d4f5b88 100644 --- a/src/ui/workspace-app.tsx +++ b/src/ui/workspace-app.tsx @@ -6,28 +6,37 @@ import { } from "@modelcontextprotocol/ext-apps"; import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { - isEditTool, isExpandableCard, isInitiallyExpandedCard, - isPatchTool, - isReadTool, - isReviewTool, - isToolName, - isToolResultCard, - isWriteTool, - payloadText, + summaryNumber, type HostContext, - type ToolName, type ToolResultCard, } from "./card-types.js"; -import { getProviderLogo, renderIcon, toolIcons, type ToolIcon } from "./icons.js"; import { - getToolDisplay, - getToolHeaderSummary, - type ToolDisplay, -} from "./tool-display.js"; + getProviderLogo, + renderIcon, + toolIcons, + type ProviderLogoTheme, + type ToolIcon, +} from "./icons.js"; +import { + getFileChangePathDisplay, + getPatchDisplayParts, +} from "./patch-display.js"; +import { + decodeToolResult, + toolResultFromChatGptGlobals, + type ChatGptToolGlobals, +} from "./tool-result.js"; import "./workspace-app.css"; +interface CardDisplay { + icon: ToolIcon; + title: string; + label?: string; + tone: "workspace" | "review"; +} + interface MountedPayload { update(options: { card: ToolResultCard; @@ -50,6 +59,8 @@ let currentPayload: MountedPayload | null = null; let currentPayloadContainer: HTMLElement | null = null; let openWorkspaceInstructionKey: string | null = null; let showAvailableWorkspaceInstructions = false; +let pendingToolResult: CallToolResult | null = null; +let pendingReviewKey: string | null = null; const maybeAppRoot = document.querySelector("#app"); @@ -70,35 +81,15 @@ async function boot(): Promise { ); app.ontoolresult = (result) => { - const structuredContent = getStructuredContent>(result); - const metaCard = cardFromMeta(result); - const structured = metaCard - ? { ...structuredContent, ...metaCard } - : structuredContent; - const tool = toolNameFromMeta(result); - - if (!tool || !isToolResultCard(structured)) { - card = null; - expanded = false; - reviewFilesExpanded = false; - openWorkspaceInstructionKey = null; - showAvailableWorkspaceInstructions = false; - errorMessage = "No result card is available for this tool result."; - render(); + if (!connected) { + pendingToolResult = result; return; } - - const nextCard = { ...structured, tool }; - card = nextCard; - expanded = isInitiallyExpandedCard(nextCard); - reviewFilesExpanded = false; - openWorkspaceInstructionKey = null; - showAvailableWorkspaceInstructions = false; - errorMessage = null; - render(); + void applyToolResult(result); }; app.onhostcontextchanged = (ctx) => { + const previousTheme = hostContext?.theme; hostContext = { ...hostContext, ...ctx, @@ -106,10 +97,17 @@ async function boot(): Promise { applyHostContext(); // Workspace details inherit host variables directly. Rebuilding their DOM on // iframe resize would reset an in-progress instruction preview interaction. - if (card?.tool !== "open_workspace") renderPayloadIfNeeded(); + if (card?.tool === "open_workspace") { + if (ctx.theme && ctx.theme !== previousTheme) { + syncWorkspaceProviderLogos(ctx.theme === "light" ? "light" : "dark"); + } + } else { + renderPayloadIfNeeded(); + } }; app.onteardown = async () => { + window.removeEventListener("openai:set_globals", handleChatGptGlobalsChanged); unmountPayload(); return {}; }; @@ -120,13 +118,112 @@ async function boot(): Promise { if (initialContext) hostContext = initialContext; applyHostContext(); connected = true; + window.addEventListener("openai:set_globals", handleChatGptGlobalsChanged); } catch (connectError) { connectionError = connectError instanceof Error ? connectError.message : String(connectError); } + const initialResult = pendingToolResult ?? chatGptRestoredResult(); + pendingToolResult = null; + if (initialResult) { + await applyToolResult(initialResult); + } else { + render(); + } +} + +async function applyToolResult(result: CallToolResult): Promise { + const decoded = decodeToolResult(result); + if (decoded.kind === "card") { + setCard(decoded.card); + return; + } + if (decoded.kind === "invalid") { + clearCard("No result card is available for this tool result."); + return; + } + + const reviewKey = `${decoded.workspaceId}:${decoded.reviewRef}`; + pendingReviewKey = reviewKey; + card = null; + errorMessage = null; + resetCardInteractions(); render(); + + try { + const restored = await reopenReview(decoded.workspaceId, decoded.reviewRef); + if (pendingReviewKey !== reviewKey) return; + + const restoredResult = decodeToolResult(restored); + if (restoredResult.kind !== "card" || restoredResult.card.tool !== "show_changes") { + throw new Error("The host returned an incomplete historical review."); + } + setCard(restoredResult.card); + } catch (reviewError) { + if (pendingReviewKey !== reviewKey) return; + clearCard( + reviewError instanceof Error + ? reviewError.message + : String(reviewError), + ); + } +} + +function setCard(nextCard: ToolResultCard): void { + pendingReviewKey = null; + card = nextCard; + expanded = isInitiallyExpandedCard(nextCard); + reviewFilesExpanded = false; + openWorkspaceInstructionKey = null; + showAvailableWorkspaceInstructions = false; + errorMessage = null; + render(); +} + +function clearCard(message: string): void { + pendingReviewKey = null; + card = null; + errorMessage = message; + resetCardInteractions(); + render(); +} + +function resetCardInteractions(): void { + expanded = false; + reviewFilesExpanded = false; + openWorkspaceInstructionKey = null; + showAvailableWorkspaceInstructions = false; +} + +async function reopenReview( + workspaceId: string, + reviewRef: string, +): Promise { + if (!app) throw new Error("The app bridge is not connected."); + if (!app.getHostCapabilities()?.serverTools) { + throw new Error("This host cannot reload historical review details."); + } + + return app.callServerTool({ + name: "show_changes", + arguments: { workspaceId }, + _meta: { "devspace/reviewRef": reviewRef }, + }); +} + +function chatGptRestoredResult(): CallToolResult | undefined { + return toolResultFromChatGptGlobals(window.openai); +} + +function handleChatGptGlobalsChanged(event: Event): void { + if (!connected || card) return; + + const customEvent = event as CustomEvent<{ globals?: ChatGptToolGlobals }>; + const restored = toolResultFromChatGptGlobals(customEvent.detail?.globals) + ?? chatGptRestoredResult(); + if (restored) void applyToolResult(restored); } function applyHostContext(): void { @@ -162,8 +259,8 @@ function render(): void { return; } - const display = getToolDisplay(card); - if (isReviewTool(card.tool)) { + const display = cardDisplay(card); + if (card.tool === "show_changes") { renderReviewCard(card, display); return; } @@ -241,72 +338,25 @@ async function renderPayloadIfNeeded(): Promise { return; } - if (shouldUseHeavyPayload(card)) { - if (currentPayload) { - currentPayload.update({ card, hostContext, errorMessage }); - return; - } - - setPayloadLoading(target, true); + const visibleFileCount = !reviewFilesExpanded + ? Math.max(3, (card.files ?? []).slice(0, 3).length) + : undefined; - try { - const { mountHeavyPayload } = await import("./heavy-payload.js"); - if (target !== currentPayloadContainer || !expanded || !card) return; - - setPayloadLoading(target, false); - currentPayload = mountHeavyPayload(target, { - card, - hostContext, - errorMessage, - }); - } catch (loadError) { - if (target !== currentPayloadContainer || !expanded) return; - - setPayloadLoading(target, false); - renderStatus( - target, - loadError instanceof Error ? loadError.message : "Unable to load details.", - "error", - ); - } - return; - } - - if (isReviewTool(card.tool) || isPatchTool(card.tool)) { - const visibleFileCount = isReviewTool(card.tool) && !reviewFilesExpanded - ? Math.max(3, (card.files ?? []).slice(0, 3).length) - : undefined; - - if (currentPayload) { - currentPayload.update({ card, hostContext, errorMessage, visibleFileCount }); - return; - } - - renderStatus(target, isReviewTool(card.tool) ? "Loading review..." : "Loading diff..."); - - const { mountReviewPayload } = await import("./review-payload.js"); - if (target !== currentPayloadContainer || !card) return; - - currentPayload = mountReviewPayload(target, { - card, - hostContext, - errorMessage, - visibleFileCount, - }); - return; - } - - const text = payloadText(card.payload); - if (!text) { - renderStatus(target, "No details available."); + if (currentPayload) { + currentPayload.update({ card, hostContext, errorMessage, visibleFileCount }); return; } - renderPrePayload(target, text, card.tool); -} + renderStatus(target, "Loading review..."); + const { mountReviewPayload } = await import("./review-payload.js"); + if (target !== currentPayloadContainer || !card) return; -function shouldUseHeavyPayload(card: ToolResultCard): boolean { - return isReadTool(card.tool) || isEditTool(card.tool) || isWriteTool(card.tool); + currentPayload = mountReviewPayload(target, { + card, + hostContext, + errorMessage, + visibleFileCount, + }); } function unmountPayload(): void { @@ -329,40 +379,36 @@ function renderStatus( container.replaceChildren(element("div", { className: `status ${tone}`, text: message })); } -function renderPrePayload( - container: HTMLElement, - text: string, - tool: string, -): void { - unmountCurrentPayload(); - container.replaceChildren(element("pre", { - className: `text-payload pretty-scrollbar ${tool}`, - text, - })); -} - function renderHeaderSummary(card: ToolResultCard): HTMLElement { - const summary = getToolHeaderSummary(card); - - if (summary.kind === "diff") { + if (card.tool === "show_changes") { const stats = element("span", { className: "stats" }); stats.setAttribute("aria-label", "Diff statistics"); stats.append( - element("span", { className: "add", text: `+${String(summary.additions)}` }), - element("span", { className: "remove", text: `-${String(summary.removals)}` }), + element("span", { + className: "add", + text: `+${String(summaryNumber(card.summary, "additions") ?? 0)}`, + }), + element("span", { + className: "remove", + text: `-${String(summaryNumber(card.summary, "removals") ?? 0)}`, + }), ); return stats; } + const parts = [ + countLabel(summaryNumber(card.summary, "agentsFiles"), "instruction"), + countLabel(summaryNumber(card.summary, "skills"), "skill"), + ].filter((part): part is string => Boolean(part)); const meta = element("span", { - className: `header-meta ${summary.kind === "empty" ? "empty" : ""}`, - text: summary.kind === "text" ? summary.text : "", + className: `header-meta ${parts.length === 0 ? "empty" : ""}`, + text: parts.join(" ยท "), }); - if (summary.kind === "empty") meta.setAttribute("aria-hidden", "true"); + if (parts.length === 0) meta.setAttribute("aria-hidden", "true"); return meta; } -function renderReviewCard(card: ToolResultCard, display: ToolDisplay): void { +function renderReviewCard(card: ToolResultCard, display: CardDisplay): void { unmountPayload(); const files = card.files ?? []; @@ -445,24 +491,42 @@ function renderChevron(isExpanded: boolean, visible: boolean): HTMLElement { return chevron; } -function toolCardClassName(display: ToolDisplay): string { - return ["tool-card", display.tone, display.state ? `state-${display.state}` : undefined] - .filter(Boolean) - .join(" "); +function toolCardClassName(display: CardDisplay): string { + return `tool-card ${display.tone}`; } -function setPayloadLoading(container: HTMLElement, loading: boolean): void { - const header = container.previousElementSibling; - const chevron = header?.querySelector(".chevron"); - if (!chevron) return; +function cardDisplay(card: ToolResultCard): CardDisplay { + if (card.tool === "open_workspace") { + const title = card.workspaceReused === true + ? "Reused workspace" + : card.workspaceReused === false + ? "Opened workspace" + : "Workspace"; + return { + icon: card.mode === "worktree" ? toolIcons.gitBranch : toolIcons.folderOpen, + title, + label: card.root ?? card.path, + tone: "workspace", + }; + } - chevron.classList.toggle("loading", loading); - chevron.replaceChildren( - renderIcon(loading ? toolIcons.loading : toolIcons.chevronDown), - ); + const display = getPatchDisplayParts(card, { emptyTitle: "Changes ready" }); + return { + icon: toolIcons.diff, + title: card.files?.length || card.payload?.patch ? display.title : "No changes", + label: singleFilePath(card), + tone: "review", + }; +} + +function singleFilePath(card: ToolResultCard): string | undefined { + if (card.files?.length !== 1) return undefined; + return getFileChangePathDisplay(card.files[0])?.title ?? card.path; +} - const button = header instanceof HTMLButtonElement ? header : null; - if (button) button.setAttribute("aria-busy", String(loading)); +function countLabel(count: number | undefined, noun: string): string | undefined { + if (count === undefined) return undefined; + return `${count} ${noun}${count === 1 ? "" : "s"}`; } function renderWorkspacePayload(container: HTMLElement, card: ToolResultCard): void { @@ -510,6 +574,15 @@ function renderWorkspacePayload(container: HTMLElement, card: ToolResultCard): v ); } + if (card.review?.available === false) { + appendWorkspaceTextRow( + rows, + "Review", + card.review.reason, + toolIcons.warning, + ); + } + appendWorkspaceInstructions( rows, card.agentsFiles ?? [], @@ -523,6 +596,9 @@ function renderWorkspacePayload(container: HTMLElement, card: ToolResultCard): v const providers = card.agentProviders ?? []; const agents = card.agents ?? []; + const providerLogoTheme: ProviderLogoTheme = hostContext?.theme === "light" + ? "light" + : "dark"; const agentChips: WorkspaceChip[] = agents.map((agent) => { const name = agent.name ?? "Unnamed agent"; const providerName = agent.provider?.trim(); @@ -534,14 +610,17 @@ function renderWorkspacePayload(container: HTMLElement, card: ToolResultCard): v ].filter((value): value is string => Boolean(value)).join("\n"); return { label: name, - logo: providerName ? getProviderLogo(providerName) : undefined, + logo: providerName + ? getProviderLogo(providerName, providerLogoTheme) + : undefined, + logoProvider: providerName, profile: true, title: title || undefined, }; }); const providerChips: WorkspaceChip[] = providers.map((provider) => { const name = provider.id?.trim() || "Unknown provider"; - const logo = getProviderLogo(name); + const logo = getProviderLogo(name, providerLogoTheme); const title = [ provider.model ? `Model: ${provider.model}` : undefined, provider.effort ? `Effort: ${provider.effort}` : undefined, @@ -550,6 +629,7 @@ function renderWorkspacePayload(container: HTMLElement, card: ToolResultCard): v return { label: name, logo, + logoProvider: logo ? name : undefined, bareLogo: Boolean(logo), ariaLabel: name, title: title || name, @@ -576,6 +656,7 @@ function renderWorkspacePayload(container: HTMLElement, card: ToolResultCard): v interface WorkspaceChip { label: string; logo?: string; + logoProvider?: string; profile?: boolean; bareLogo?: boolean; ariaLabel?: string; @@ -853,6 +934,7 @@ function renderWorkspaceChips(chips: WorkspaceChip[]): HTMLElement { ? "workspace-agent-profile-logo" : "workspace-chip-logo"; logo.src = chip.logo; + if (chip.logoProvider) logo.dataset.provider = chip.logoProvider; logo.alt = ""; logo.setAttribute("aria-hidden", "true"); item.append(logo); @@ -865,20 +947,13 @@ function renderWorkspaceChips(chips: WorkspaceChip[]): HTMLElement { return list; } -function toolNameFromMeta(result: CallToolResult): ToolName | undefined { - const meta = result._meta as Record | undefined; - const tool = meta?.tool; - return isToolName(tool) ? tool : undefined; -} - -function cardFromMeta(result: CallToolResult): Partial | undefined { - const meta = result._meta as Record | undefined; - const metaCard = meta?.card; - return metaCard && typeof metaCard === "object" ? metaCard : undefined; -} - -function getStructuredContent(result: CallToolResult): T | undefined { - return result.structuredContent as T | undefined; +function syncWorkspaceProviderLogos(theme: ProviderLogoTheme): void { + for (const logo of document.querySelectorAll("img[data-provider]")) { + const providerName = logo.dataset.provider; + if (!providerName) continue; + const src = getProviderLogo(providerName, theme); + if (src && logo.src !== src) logo.src = src; + } } function element( diff --git a/src/user-config.test.ts b/src/user-config.test.ts new file mode 100644 index 000000000..8e09b9122 --- /dev/null +++ b/src/user-config.test.ts @@ -0,0 +1,195 @@ +import assert from "node:assert/strict"; +import { spawn } from "node:child_process"; +import { + existsSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { + loadDevspaceFiles, + setDevspaceConfigValue, + setDevspaceConfigValues, +} from "./user-config.js"; + +withConfigDir((configDir, env) => { + writeFileSync(join(configDir, "config.json"), JSON.stringify({ + host: "0.0.0.0", + port: 8787, + allowedRoots: ["/work"], + publicBaseUrl: "https://devspace.example.com", + artifactsEnabled: true, + subagents: true, + })); + writeFileSync(join(configDir, "auth.json"), JSON.stringify({ + ownerToken: "test-owner-token", + })); + + const files = loadDevspaceFiles(env); + assert.equal(files.migratedLegacyConfig, true); + assert.equal(files.config.server.host, "0.0.0.0"); + assert.equal(files.config.server.port, 8787); + assert.deepEqual(files.config.workspaces.allowedRoots, ["/work"]); + assert.equal(files.config.artifacts.enabled, true); + assert.equal(files.config.subagents.enabled, true); + assert.equal(files.config.tools.mode, "codex"); + assert.equal(files.config.ui.enabled, true); + assert.equal(files.auth.ownerToken, "test-owner-token"); + assert.equal(existsSync(join(configDir, "config.json")), false); + assert.equal(existsSync(join(configDir, "config.jsonc")), true); + assert.equal(existsSync(join(configDir, "config.json.v1.0.bak")), true); + + const nextLoad = loadDevspaceFiles(env); + assert.equal(nextLoad.migratedLegacyConfig, false); +}); + +await withConfigDirAsync(async (configDir) => { + writeFileSync(join(configDir, "config.json"), JSON.stringify({ + port: 8787, + allowedRoots: ["/work"], + })); + + const results = await Promise.all([ + migrateInChildProcess(configDir), + migrateInChildProcess(configDir), + ]); + assert.equal(results.filter((result) => result.migrated).length, 1); + assert.equal(results.filter((result) => !result.migrated).length, 1); + assert.equal(existsSync(join(configDir, "config.json")), false); + assert.equal(existsSync(join(configDir, "config.jsonc")), true); + assert.equal(existsSync(join(configDir, "config.json.v1.0.bak")), true); + assert.equal(loadDevspaceFiles({ DEVSPACE_CONFIG_DIR: configDir }).config.server.port, 8787); +}); + +withConfigDir((configDir, env) => { + writeFileSync(join(configDir, "config.jsonc"), `{ + // This comment must survive config updates. + "configVersion": 1, + "server": { + "port": 8787, + }, + }\n`); + + const files = loadDevspaceFiles(env); + assert.equal(files.config.server.port, 8787); + assert.equal(files.config.tools.mode, "codex"); + + setDevspaceConfigValue(["server", "publicBaseUrl"], "https://new.example.com", env); + const updated = readFileSync(join(configDir, "config.jsonc"), "utf8"); + assert.match(updated, /This comment must survive config updates/); + assert.equal(loadDevspaceFiles(env).config.server.publicBaseUrl, "https://new.example.com"); + + setDevspaceConfigValues([ + { path: ["server", "port"], value: 7676 }, + { path: ["tools", "mode"], value: "claude" }, + ], env); + const multiUpdated = readFileSync(join(configDir, "config.jsonc"), "utf8"); + assert.match(multiUpdated, /This comment must survive config updates/); + assert.equal(loadDevspaceFiles(env).config.server.port, 7676); + assert.equal(loadDevspaceFiles(env).config.tools.mode, "claude"); +}); + +withConfigDir((configDir, env) => { + writeFileSync(join(configDir, "config.jsonc"), JSON.stringify({ configVersion: 1 })); + writeFileSync(join(configDir, "config.json"), "{"); + assert.equal(loadDevspaceFiles(env).config.server.port, 7676); + assert.equal(existsSync(join(configDir, "config.json")), true); +}); + +withConfigDir((configDir, env) => { + writeFileSync(join(configDir, "config.jsonc"), "{"); + writeFileSync(join(configDir, "config.json"), JSON.stringify({ port: 8787 })); + assert.throws(() => loadDevspaceFiles(env), /Unable to read .*config\.jsonc/); + assert.equal(existsSync(join(configDir, "config.json")), true); +}); + +withConfigDir((configDir, env) => { + writeFileSync(join(configDir, "config.json"), JSON.stringify({ unknownSetting: true })); + assert.throws( + () => loadDevspaceFiles(env), + /Unsupported legacy configuration keys: unknownSetting/, + ); + assert.equal(existsSync(join(configDir, "config.json")), true); + assert.equal(existsSync(join(configDir, "config.jsonc")), false); + assert.equal(existsSync(join(configDir, "config.json.v1.0.bak")), false); +}); + +withConfigDir((configDir, env) => { + const legacyPath = join(configDir, "config.json"); + const backupPath = join(configDir, "config.json.v1.0.bak"); + writeFileSync(legacyPath, JSON.stringify({ port: 8787 })); + writeFileSync(backupPath, JSON.stringify({ port: 7676 })); + + assert.throws( + () => loadDevspaceFiles(env), + (error: unknown) => error instanceof Error + && error.message.includes(`backup already exists at ${backupPath}`) + && error.message.includes(`Move ${backupPath} out of the way, then run DevSpace again.`), + ); +}); + +console.log("user config tests passed"); + +function withConfigDir( + test: (configDir: string, env: NodeJS.ProcessEnv) => void, +): void { + const configDir = mkdtempSync(join(tmpdir(), "devspace-user-config-test-")); + const env = { DEVSPACE_CONFIG_DIR: configDir }; + try { + test(configDir, env); + } finally { + rmSync(configDir, { recursive: true, force: true }); + } +} + +async function withConfigDirAsync( + test: (configDir: string) => Promise, +): Promise { + const configDir = mkdtempSync(join(tmpdir(), "devspace-user-config-test-")); + try { + await test(configDir); + } finally { + rmSync(configDir, { recursive: true, force: true }); + } +} + +async function migrateInChildProcess( + configDir: string, +): Promise<{ migrated: boolean }> { + const moduleUrl = new URL("./user-config.ts", import.meta.url).href; + const source = [ + `import { loadDevspaceFiles } from ${JSON.stringify(moduleUrl)};`, + "const files = loadDevspaceFiles();", + "process.stdout.write(JSON.stringify({ migrated: files.migratedLegacyConfig }));", + ].join("\n"); + + return new Promise((resolve, reject) => { + const child = spawn( + process.execPath, + ["--import", "tsx", "--input-type=module", "--eval", source], + { + env: { ...process.env, DEVSPACE_CONFIG_DIR: configDir }, + stdio: ["ignore", "pipe", "pipe"], + }, + ); + let stdout = ""; + let stderr = ""; + child.stdout.setEncoding("utf8").on("data", (chunk: string) => { + stdout += chunk; + }); + child.stderr.setEncoding("utf8").on("data", (chunk: string) => { + stderr += chunk; + }); + child.once("error", reject); + child.once("close", (code) => { + if (code !== 0) { + reject(new Error(`migration child exited with ${code}: ${stderr}`)); + return; + } + resolve(JSON.parse(stdout) as { migrated: boolean }); + }); + }); +} diff --git a/src/user-config.ts b/src/user-config.ts index 98d05ac68..6775e4b7c 100644 --- a/src/user-config.ts +++ b/src/user-config.ts @@ -1,32 +1,38 @@ import { randomBytes } from "node:crypto"; import { existsSync, + linkSync, mkdirSync, readFileSync, + renameSync, + rmSync, writeFileSync, } from "node:fs"; import { homedir } from "node:os"; -import { join, resolve } from "node:path"; +import { basename, dirname, join, resolve } from "node:path"; +import { + applyEdits, + modify, + parse, + printParseErrorCode, + type ParseError, +} from "jsonc-parser"; +import * as z from "zod/v4"; +import { + defaultDevspaceConfig, + devspaceConfigSchema, + type DevspaceConfig, + type DevspaceConfigInput, +} from "./config-schema.js"; +import { migrateLegacyConfig } from "./config-migration.js"; import { expandHomePath } from "./roots.js"; -import type { StoredSubagentsConfig } from "./local-agent-config.js"; -export interface DevspaceUserConfig { - host?: string; - port?: number; - allowedRoots?: string[]; - publicBaseUrl?: string | null; - allowedHosts?: string[]; - stateDir?: string; - worktreeRoot?: string; - artifactsEnabled?: boolean; - artifactMaxFileBytes?: number; - agentDir?: string; - subagents?: StoredSubagentsConfig; -} +const devspaceAuthConfigSchema = z.object({ + ownerToken: z.string().optional(), +}).passthrough(); -export interface DevspaceAuthConfig { - ownerToken?: string; -} +export type DevspaceUserConfig = DevspaceConfig; +export type DevspaceAuthConfig = z.infer; export interface DevspaceFiles { dir: string; @@ -34,8 +40,14 @@ export interface DevspaceFiles { authPath: string; configExists: boolean; authExists: boolean; - config: DevspaceUserConfig; + config: DevspaceConfig; auth: DevspaceAuthConfig; + migratedLegacyConfig: boolean; +} + +export interface DevspaceConfigEdit { + path: (string | number)[]; + value: unknown; } export function devspaceConfigDir(env: NodeJS.ProcessEnv = process.env): string { @@ -43,9 +55,17 @@ export function devspaceConfigDir(env: NodeJS.ProcessEnv = process.env): string } export function devspaceConfigPath(env: NodeJS.ProcessEnv = process.env): string { + return join(devspaceConfigDir(env), "config.jsonc"); +} + +export function devspaceLegacyConfigPath(env: NodeJS.ProcessEnv = process.env): string { return join(devspaceConfigDir(env), "config.json"); } +export function devspaceLegacyConfigBackupPath(env: NodeJS.ProcessEnv = process.env): string { + return join(devspaceConfigDir(env), "config.json.v1.0.bak"); +} + export function devspaceAuthPath(env: NodeJS.ProcessEnv = process.env): string { return join(devspaceConfigDir(env), "auth.json"); } @@ -60,8 +80,12 @@ export function devspaceAgentsDir(env: NodeJS.ProcessEnv = process.env): string export function loadDevspaceFiles(env: NodeJS.ProcessEnv = process.env): DevspaceFiles { const dir = devspaceConfigDir(env); - const configPath = join(dir, "config.json"); - const authPath = join(dir, "auth.json"); + const configPath = devspaceConfigPath(env); + const legacyConfigPath = devspaceLegacyConfigPath(env); + const authPath = devspaceAuthPath(env); + const migratedLegacyConfig = !existsSync(configPath) && existsSync(legacyConfigPath) + ? migrateLegacyConfigFile(legacyConfigPath, configPath, devspaceLegacyConfigBackupPath(env)) + : false; const configExists = existsSync(configPath); const authExists = existsSync(authPath); @@ -71,28 +95,56 @@ export function loadDevspaceFiles(env: NodeJS.ProcessEnv = process.env): Devspac authPath, configExists, authExists, - config: configExists ? readJsonFile(configPath) : {}, - auth: authExists ? readJsonFile(authPath) : {}, + config: configExists ? readJsoncConfig(configPath) : defaultDevspaceConfig(), + auth: authExists ? readJsonFile(authPath, devspaceAuthConfigSchema) : {}, + migratedLegacyConfig, }; } export function writeDevspaceConfig( - config: DevspaceUserConfig, + config: DevspaceConfigInput, env: NodeJS.ProcessEnv = process.env, ): string { const filePath = devspaceConfigPath(env); - mkdirSync(devspaceConfigDir(env), { recursive: true }); - writeJsonFile(filePath, config, 0o600); + const parsed = devspaceConfigSchema.parse(config); + atomicWrite(filePath, serializeConfig(parsed), 0o600); return filePath; } +export function setDevspaceConfigValue( + path: (string | number)[], + value: unknown, + env: NodeJS.ProcessEnv = process.env, +): string { + return setDevspaceConfigValues([{ path, value }], env); +} + +export function setDevspaceConfigValues( + edits: DevspaceConfigEdit[], + env: NodeJS.ProcessEnv = process.env, +): string { + const files = loadDevspaceFiles(env); + const source = files.configExists + ? readFileSync(files.configPath, "utf8") + : serializeConfig(files.config); + const updated = edits.reduce( + (document, edit) => applyEdits(document, modify(document, edit.path, edit.value, { + formattingOptions: { insertSpaces: true, tabSize: 2, eol: "\n" }, + })), + source, + ); + parseJsoncConfig(updated, files.configPath); + atomicWrite(files.configPath, updated.endsWith("\n") ? updated : `${updated}\n`, 0o600); + return files.configPath; +} + export function writeDevspaceAuth( auth: DevspaceAuthConfig, env: NodeJS.ProcessEnv = process.env, ): string { const filePath = devspaceAuthPath(env); mkdirSync(devspaceConfigDir(env), { recursive: true }); - writeJsonFile(filePath, auth, 0o600); + writeJsonFile(filePath, devspaceAuthConfigSchema.parse(auth), 0o600); return filePath; } @@ -100,15 +152,120 @@ export function generateOwnerToken(): string { return randomBytes(32).toString("base64url"); } -function readJsonFile(filePath: string): T { +function migrateLegacyConfigFile( + legacyPath: string, + configPath: string, + backupPath: string, +): boolean { + if (existsSync(backupPath)) { + throw new Error( + `Unable to migrate ${legacyPath}: backup already exists at ${backupPath}. ` + + `Move ${backupPath} out of the way, then run DevSpace again.`, + ); + } + + let migrated: DevspaceConfig; try { - return JSON.parse(readFileSync(filePath, "utf8")) as T; + migrated = migrateLegacyConfig(JSON.parse(readFileSync(legacyPath, "utf8")) as unknown); } catch (error) { - const reason = error instanceof Error ? error.message : String(error); - throw new Error(`Unable to read ${filePath}: ${reason}`); + throw fileError("migrate", legacyPath, error); + } + + const temporaryPath = temporaryFilePath(configPath); + let published = false; + try { + mkdirSync(dirname(configPath), { recursive: true }); + writeFileSync(temporaryPath, serializeConfig(migrated), { mode: 0o600, flag: "wx" }); + readJsoncConfig(temporaryPath); + try { + // A hard link publishes the complete temporary file atomically without + // replacing config.jsonc if another first-start process won the race. + linkSync(temporaryPath, configPath); + } catch (error) { + if (!isErrnoException(error) || error.code !== "EEXIST") throw error; + readJsoncConfig(configPath); + return false; + } + published = true; + renameSync(legacyPath, backupPath); + } catch (error) { + if (published && existsSync(legacyPath)) { + rmSync(configPath, { force: true }); + } + throw fileError("migrate", legacyPath, error); + } finally { + rmSync(temporaryPath, { force: true }); + } + return true; +} + +function readJsoncConfig(filePath: string): DevspaceConfig { + try { + return parseJsoncConfig(readFileSync(filePath, "utf8"), filePath); + } catch (error) { + if (error instanceof DevspaceConfigFileError) throw error; + throw fileError("read", filePath, error); + } +} + +function parseJsoncConfig(source: string, filePath: string): DevspaceConfig { + const errors: ParseError[] = []; + const value = parse(source, errors, { allowTrailingComma: true }); + if (errors.length > 0) { + const first = errors[0]!; + throw new DevspaceConfigFileError( + `Unable to read ${filePath}: ${printParseErrorCode(first.error)} at offset ${first.offset}`, + ); + } + try { + return devspaceConfigSchema.parse(value); + } catch (error) { + throw fileError("read", filePath, error); + } +} + +function serializeConfig(config: DevspaceConfig): string { + return `${JSON.stringify(config, null, 2)}\n`; +} + +function atomicWrite(filePath: string, source: string, mode: number): void { + mkdirSync(dirname(filePath), { recursive: true }); + const temporaryPath = temporaryFilePath(filePath); + try { + writeFileSync(temporaryPath, source, { mode, flag: "wx" }); + renameSync(temporaryPath, filePath); + } catch (error) { + rmSync(temporaryPath, { force: true }); + throw error; + } +} + +function temporaryFilePath(filePath: string): string { + return join( + dirname(filePath), + `.${basename(filePath)}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`, + ); +} + +function readJsonFile(filePath: string, schema: z.ZodType): T { + try { + return schema.parse(JSON.parse(readFileSync(filePath, "utf8")) as unknown); + } catch (error) { + throw fileError("read", filePath, error); } } function writeJsonFile(filePath: string, value: unknown, mode: number): void { - writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", { mode }); + writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, { mode }); +} + +function fileError(action: "read" | "migrate", filePath: string, error: unknown): Error { + const reason = error instanceof Error ? error.message : String(error); + return new DevspaceConfigFileError(`Unable to ${action} ${filePath}: ${reason}`); } + +function isErrnoException(error: unknown): error is NodeJS.ErrnoException { + return error instanceof Error && "code" in error; +} + +class DevspaceConfigFileError extends Error {} diff --git a/src/workspace-conversation.test.ts b/src/workspace-conversation.test.ts index 5af9f991d..45c45a14d 100644 --- a/src/workspace-conversation.test.ts +++ b/src/workspace-conversation.test.ts @@ -9,6 +9,7 @@ import { loadConfig, type ServerConfig } from "./config.js"; import { openDatabase } from "./db/client.js"; import { SqliteWorkspaceStore } from "./workspace-store.js"; import { WorkspaceRegistry } from "./workspaces.js"; +import { writeTestDevspaceConfig } from "./test-support/config.test.js"; const execFileAsync = promisify(execFile); @@ -242,14 +243,14 @@ test("canonical checkout identity survives macOS var path aliases", { skip: plat return; } - const aliasConfig = loadConfig({ - DEVSPACE_CONFIG_DIR: join(context.root, ".alias-config"), - DEVSPACE_ALLOWED_ROOTS: `${context.root},${macAlias}`, - DEVSPACE_WORKTREE_ROOT: join(context.root, ".worktrees"), - DEVSPACE_AGENT_DIR: join(context.root, "agent"), - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", - PORT: "1", - }); + const aliasConfig = loadConfig(writeTestDevspaceConfig(join(context.root, ".alias-config"), { + server: { port: 1 }, + workspaces: { + allowedRoots: [context.root, macAlias], + worktreeRoot: join(context.root, ".worktrees"), + }, + skills: { agentDir: join(context.root, "agent") }, + })); const aliasRegistry = new WorkspaceRegistry(aliasConfig, context.store); const direct = await context.registry.openWorkspace(context.project, { @@ -416,15 +417,12 @@ async function fixture( if (options.git) await initializeGitRepository(project); - const config = loadConfig({ - DEVSPACE_CONFIG_DIR: join(root, ".config"), - DEVSPACE_ALLOWED_ROOTS: root, - DEVSPACE_WORKTREE_ROOT: join(root, ".worktrees"), - DEVSPACE_AGENT_DIR: agentDir, - DEVSPACE_SUBAGENTS: "1", - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", - PORT: "1", - }); + const config = loadConfig(writeTestDevspaceConfig(join(root, ".config"), { + server: { port: 1 }, + workspaces: { allowedRoots: [root], worktreeRoot: join(root, ".worktrees") }, + skills: { agentDir }, + subagents: { enabled: true, providers: [] }, + })); const openStore = () => { const store = new SqliteWorkspaceStore(stateDir); stores.add(store); diff --git a/src/workspaces.test.ts b/src/workspaces.test.ts index 8584c1b7e..3dab10807 100644 --- a/src/workspaces.test.ts +++ b/src/workspaces.test.ts @@ -9,6 +9,7 @@ import { loadConfig, type ServerConfig } from "./config.js"; import { GitWorktreeError } from "./git-worktrees.js"; import { SqliteWorkspaceStore } from "./workspace-store.js"; import { WorkspaceRegistry } from "./workspaces.js"; +import { writeTestDevspaceConfig } from "./test-support/config.test.js"; const execFileAsync = promisify(execFile); @@ -47,14 +48,17 @@ test("a checkout exposes initial and nested instruction context while filtering await writeFile(join(context.outsideRoot, "secret.txt"), "outside secret\n"); await symlink(join(context.outsideRoot, "secret.txt"), join(unsafeAgentDir, "AGENTS.md")); - const unsafeConfig = loadConfig({ - DEVSPACE_CONFIG_DIR: join(context.root, ".devspace-unsafe-home"), - DEVSPACE_ALLOWED_ROOTS: context.root, - DEVSPACE_WORKTREE_ROOT: join(context.root, ".devspace", "unsafe-worktrees"), - DEVSPACE_AGENT_DIR: unsafeAgentDir, - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", - PORT: "1", - }); + const unsafeConfig = loadConfig(writeTestDevspaceConfig( + join(context.root, ".devspace-unsafe-home"), + { + server: { port: 1 }, + workspaces: { + allowedRoots: [context.root], + worktreeRoot: join(context.root, ".devspace", "unsafe-worktrees"), + }, + skills: { agentDir: unsafeAgentDir }, + }, + )); const unsafeWorkspace = await new WorkspaceRegistry(unsafeConfig).openWorkspace(context.root); assert.deepEqual( @@ -144,13 +148,17 @@ test("a symlinked allowed root preserves checkout and worktree path behavior", { await symlink(context.root, aliasRoot, "dir"); await createGitProject(context.root); - const aliasConfig = loadConfig({ - DEVSPACE_ALLOWED_ROOTS: aliasRoot, - DEVSPACE_WORKTREE_ROOT: join(aliasRoot, ".devspace", "alias-worktrees"), - DEVSPACE_AGENT_DIR: context.agentDir, - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", - PORT: "1", - }); + const aliasConfig = loadConfig(writeTestDevspaceConfig( + join(context.root, ".devspace-alias-home"), + { + server: { port: 1 }, + workspaces: { + allowedRoots: [aliasRoot], + worktreeRoot: join(aliasRoot, ".devspace", "alias-worktrees"), + }, + skills: { agentDir: context.agentDir }, + }, + )); const aliasRegistry = new WorkspaceRegistry(aliasConfig); const worktree = await aliasRegistry.openWorkspace({ @@ -207,15 +215,15 @@ async function fixture(t: TestContext): Promise { await writeFile(join(root, "nested", "AGENTS.md"), "nested instructions\n"); await writeFile(join(root, "nested", "file.txt"), "hello\n"); - const config = loadConfig({ - DEVSPACE_CONFIG_DIR: join(root, ".devspace-home"), - DEVSPACE_ALLOWED_ROOTS: root, - DEVSPACE_WORKTREE_ROOT: join(root, ".devspace", "worktrees"), - DEVSPACE_AGENT_DIR: agentDir, - DEVSPACE_SUBAGENTS: "1", - DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough", - PORT: "1", - }); + const config = loadConfig(writeTestDevspaceConfig(join(root, ".devspace-home"), { + server: { port: 1 }, + workspaces: { + allowedRoots: [root], + worktreeRoot: join(root, ".devspace", "worktrees"), + }, + skills: { agentDir }, + subagents: { enabled: true, providers: [] }, + })); t.after(async () => { await rm(root, { recursive: true, force: true }); diff --git a/test/package-install-smoke.test.ts b/test/package-install-smoke.test.ts new file mode 100644 index 000000000..c53477fd8 --- /dev/null +++ b/test/package-install-smoke.test.ts @@ -0,0 +1,88 @@ +import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { mkdirSync, mkdtempSync, readdirSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { writeTestDevspaceConfig } from "../src/test-support/config.test.js"; + +const projectRoot = fileURLToPath(new URL("..", import.meta.url)); + +testPackedPackageLaunchers(); + +function testPackedPackageLaunchers(): void { + const root = mkdtempSync(join(tmpdir(), "devspace-packed-bin-test-")); + const installRoot = join(root, "install"); + try { + mkdirSync(installRoot, { recursive: true }); + execFileSync(npmExecutable(), ["pack", "--silent", "--pack-destination", root], { + cwd: projectRoot, + encoding: "utf8", + stdio: "pipe", + shell: process.platform === "win32", + }); + const archive = readdirSync(root).find((name) => name.endsWith(".tgz")); + assert.ok(archive, "npm pack must produce a package archive"); + + execFileSync(npmExecutable(), [ + "install", + "--no-audit", + "--no-fund", + "--no-package-lock", + "--no-save", + "--omit=optional", + join(root, archive), + ], { + cwd: installRoot, + encoding: "utf8", + stdio: "pipe", + shell: process.platform === "win32", + }); + + const configRoot = join(root, "config"); + const env = writeTestDevspaceConfig(configRoot, { + storage: { stateDir: join(root, "state") }, + workspaces: { allowedRoots: [root], worktreeRoot: join(root, "worktrees") }, + skills: { agentDir: join(root, "agents") }, + }); + const cliOutput = execInstalledBin(installRoot, "devspace", ["config", "get"], { + ...process.env, + ...env, + }); + const config = JSON.parse(cliOutput) as { tools?: { mode?: string } }; + assert.equal(config.tools?.mode, "codex"); + + execInstalledBin(installRoot, "devspace-agentd", [], { + ...process.env, + ...env, + DEVSPACE_AGENTD_IDLE_TIMEOUT_MS: "0", + DEVSPACE_AGENTD_SHUTDOWN_TIMEOUT_MS: "1000", + }); + } finally { + rmSync(root, { recursive: true, force: true }); + } +} + +function npmExecutable(): string { + return process.platform === "win32" ? "npm.cmd" : "npm"; +} + +function execInstalledBin( + installRoot: string, + name: string, + args: string[], + env: NodeJS.ProcessEnv, +): string { + const executable = join( + installRoot, + "node_modules", + ".bin", + process.platform === "win32" ? `${name}.cmd` : name, + ); + return execFileSync(executable, args, { + encoding: "utf8", + env, + stdio: "pipe", + shell: process.platform === "win32", + }); +}