diff --git a/.gitignore b/.gitignore index a547bf3..623fde5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,10 +7,11 @@ yarn-error.log* pnpm-debug.log* lerna-debug.log* -node_modules -dist -dist-ssr -*.local +frontend/node_modules +frontend/dist +frontend/dist-ssr +frontend/*.local +server/target # Editor directories and files .vscode/* diff --git a/.living_spec/README.md b/.living_spec/README.md index a06509d..ec59ec4 100644 --- a/.living_spec/README.md +++ b/.living_spec/README.md @@ -172,6 +172,10 @@ If a user hands you this document and says "Apply this process to my project": **MANDATORY:** Before completing Step 4 (Verification) of any story, you MUST run all applicable linters and fix ALL errors and warnings. Zero tolerance for warnings or errors. +**AUTO-RUN CHECKS:** Always run the required lint/test/build checks as soon as relevant changes are made. Do not ask for permission to run them—run them automatically and fix any failures. + +**ALWAYS FIX DIAGNOSTICS:** At every stage, you must proactively fix all errors and warnings without waiting for user confirmation. Do not pause to ask whether to fix diagnostics—fix them immediately as part of the workflow. + ### TypeScript/JavaScript: Biome * **Tool:** [Biome](https://biomejs.dev/) - Fast formatter and linter diff --git a/.living_spec/specs/00_CONTEXT.md b/.living_spec/specs/00_CONTEXT.md index 6ce22c9..3fb68d9 100644 --- a/.living_spec/specs/00_CONTEXT.md +++ b/.living_spec/specs/00_CONTEXT.md @@ -1,7 +1,7 @@ # Project Context ## High-Level Goal -To build a standalone **Agentic AI Code Assistant** application using Tauri. The assistant will facilitate a "Story-Driven Spec Workflow" (SDSW) for software development. Unlike a passive chat interface, this assistant acts as an **Agent**, capable of using tools to read the filesystem, execute shell commands, manage git repositories, and modify code directly to implement features. +To build a standalone **Agentic AI Code Assistant** application as a single Rust binary that serves a Vite/React web UI and exposes a WebSocket API. The assistant will facilitate a "Story-Driven Spec Workflow" (SDSW) for software development. Unlike a passive chat interface, this assistant acts as an **Agent**, capable of using tools to read the filesystem, execute shell commands, manage git repositories, and modify code directly to implement features. ## Core Features 1. **Chat Interface:** A conversational UI for the user to interact with the AI assistant. @@ -28,6 +28,6 @@ To build a standalone **Agentic AI Code Assistant** application using Tauri. The ## Glossary * **SDSW:** Story-Driven Spec Workflow. -* **Tauri:** The framework used to build this assistant (Rust backend + Web frontend). +* **Web Server Binary:** The Rust binary that serves the Vite/React frontend and exposes the WebSocket API. * **Living Spec:** The collection of Markdown files in `.living_spec/` that define the project. * **Tool Call:** A structured request from the LLM to execute a specific native function. \ No newline at end of file diff --git a/.living_spec/specs/tech/STACK.md b/.living_spec/specs/tech/STACK.md index 0e7bb6c..455b4c9 100644 --- a/.living_spec/specs/tech/STACK.md +++ b/.living_spec/specs/tech/STACK.md @@ -1,12 +1,12 @@ # Tech Stack & Constraints ## Overview -This project is a desktop application built with **Tauri**. It functions as an **Agentic Code Assistant** capable of safely executing tools on the host system. +This project is a standalone Rust **web server binary** that serves a Vite/React frontend and exposes a **WebSocket API**. The built frontend assets are packaged with the binary (in a `frontend` directory) and served as static files. It functions as an **Agentic Code Assistant** capable of safely executing tools on the host system. ## Core Stack -* **Backend:** Rust (Tauri Core) +* **Backend:** Rust (Web Server) * **MSRV:** Stable (latest) - * **Framework:** Tauri v2 + * **Framework:** Poem HTTP server with WebSocket support for streaming; HTTP APIs should use Poem OpenAPI (Swagger) for non-streaming endpoints. * **Frontend:** TypeScript + React * **Build Tool:** Vite * **Styling:** CSS Modules or Tailwind (TBD - Defaulting to CSS Modules) @@ -17,12 +17,12 @@ This project is a desktop application built with **Tauri**. It functions as an * The application follows a **Tool-Use (Function Calling)** architecture: 1. **Frontend:** Collects user input and sends it to the LLM. 2. **LLM:** Decides to generate text OR request a **Tool Call** (e.g., `execute_shell`, `read_file`). -3. **Tauri Backend (The "Hand"):** +3. **Web Server Backend (The "Hand"):** * Intercepts Tool Calls. * Validates the request against the **Safety Policy**. * Executes the native code (File I/O, Shell Process, Search). * Returns the output (stdout/stderr/file content) to the LLM. - * **Event Loop:** The backend emits real-time events (`chat:update`) to the frontend to ensure UI responsiveness during long-running Agent tasks. + * **Streaming:** The backend sends real-time updates over WebSocket to keep the UI responsive during long-running Agent tasks. ## LLM Provider Abstraction To support both Remote and Local models, the system implements a `ModelProvider` abstraction layer. @@ -39,8 +39,7 @@ To support both Remote and Local models, the system implements a `ModelProvider` * Otherwise → Ollama * Single unified model dropdown with section headers ("Anthropic", "Ollama") * **API Key Management:** - * Anthropic API key stored in OS keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service) - * Uses `keyring` crate for cross-platform secure storage + * Anthropic API key stored server-side and persisted securely * On first use of Claude model, user prompted to enter API key * Key persists across sessions (no re-entry needed) @@ -98,15 +97,11 @@ To support both Remote and Local models, the system implements a `ModelProvider` * `tokio`: Async runtime. * `reqwest`: For LLM API calls (Anthropic, Ollama). * `eventsource-stream`: For Server-Sent Events (Anthropic streaming). - * `keyring`: Secure API key storage in OS keychain. * `uuid`: For unique message IDs. * `chrono`: For timestamps. - * `tauri-plugin-dialog`: Native system dialogs. - * `tauri-plugin-store`: Persistent key-value storage. + * `poem`: HTTP server framework. + * `poem-openapi`: OpenAPI (Swagger) for non-streaming HTTP APIs. * **JavaScript:** - * `@tauri-apps/api`: Tauri Bridge. - * `@tauri-apps/plugin-dialog`: Dialog API. - * `@tauri-apps/plugin-store`: Store API. * `react-markdown`: For rendering chat responses. ## Safety & Sandbox diff --git a/.living_spec/stories/archive/24_tauri_to_browser_ui.md b/.living_spec/stories/archive/24_tauri_to_browser_ui.md new file mode 100644 index 0000000..96fd3fd --- /dev/null +++ b/.living_spec/stories/archive/24_tauri_to_browser_ui.md @@ -0,0 +1,23 @@ +# Story 01: Replace Tauri with Browser UI Served by Rust Binary + +## User Story +As a user, I want to run a single Rust binary that serves the web UI and exposes a WebSocket API, so I can use the app in my browser without installing a desktop shell. + +## Acceptance Criteria +- The app runs as a single Rust binary that: + - Serves the built frontend assets from a `frontend` directory. + - Exposes a WebSocket endpoint for chat streaming and tool execution. +- The browser UI uses the WebSocket API for: + - Sending chat messages. + - Receiving streaming token updates and final chat history updates. + - Requesting file operations, search, and shell execution. +- The project selection UI uses a browser file picker (not native OS dialogs). +- Model preference and last project selection are persisted server-side (no Tauri store). +- The Tauri backend and configuration are removed from the build pipeline. +- The frontend remains a Vite/React build and is served as static assets by the Rust binary. + +## Out of Scope +- Reworking the LLM provider implementations beyond wiring changes. +- Changing the UI layout/visual design. +- Adding authentication or multi-user support. +- Switching away from Vite for frontend builds. diff --git a/README.md b/README.md index e9f75bf..d524c49 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,19 @@ -# Tauri + React + Typescript +# Living Spec Standalone (Web Server Binary) -This template should help get you started developing with Tauri, React and Typescript in Vite. +This app runs as a single Rust web server binary that serves the Vite/React frontend and exposes APIs. +The frontend lives in the `frontend/` directory. ## Running it ```bash -pnpm tauri dev +# Build the frontend +cd frontend +pnpm install +pnpm build +cd .. + +# Run the server (serves embedded frontend/dist/) +cargo run --manifest-path server/Cargo.toml ``` diff --git a/biome.json b/biome.json index feac2b5..0cdceba 100644 --- a/biome.json +++ b/biome.json @@ -1,12 +1,12 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.10/schema.json", + "$schema": "https://biomejs.dev/schemas/2.3.15/schema.json", "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }, "files": { - "includes": ["**", "!!**/dist"] + "includes": ["frontend/**"] }, "formatter": { "enabled": true, diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..b82c32b --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,13 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local diff --git a/.vscode/extensions.json b/frontend/.vscode/extensions.json similarity index 100% rename from .vscode/extensions.json rename to frontend/.vscode/extensions.json diff --git a/index.html b/frontend/index.html similarity index 100% rename from index.html rename to frontend/index.html diff --git a/package.json b/frontend/package.json similarity index 79% rename from package.json rename to frontend/package.json index 53d26a9..bcfaca6 100644 --- a/package.json +++ b/frontend/package.json @@ -7,14 +7,10 @@ "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", - "tauri": "tauri", + "server": "cargo run --manifest-path server/Cargo.toml", "test": "jest" }, "dependencies": { - "@tauri-apps/api": "^2", - "@tauri-apps/plugin-dialog": "^2.4.2", - "@tauri-apps/plugin-opener": "^2", - "@tauri-apps/plugin-store": "^2.4.1", "@types/react-syntax-highlighter": "^15.5.13", "react": "^19.1.0", "react-dom": "^19.1.0", @@ -22,7 +18,6 @@ "react-syntax-highlighter": "^16.1.0" }, "devDependencies": { - "@tauri-apps/cli": "^2", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.4.3", diff --git a/pnpm-lock.yaml b/frontend/pnpm-lock.yaml similarity index 96% rename from pnpm-lock.yaml rename to frontend/pnpm-lock.yaml index f796e91..4762441 100644 --- a/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -8,18 +8,6 @@ importers: .: dependencies: - '@tauri-apps/api': - specifier: ^2 - version: 2.9.1 - '@tauri-apps/plugin-dialog': - specifier: ^2.4.2 - version: 2.4.2 - '@tauri-apps/plugin-opener': - specifier: ^2 - version: 2.5.2 - '@tauri-apps/plugin-store': - specifier: ^2.4.1 - version: 2.4.1 '@types/react-syntax-highlighter': specifier: ^15.5.13 version: 15.5.13 @@ -36,9 +24,6 @@ importers: specifier: ^16.1.0 version: 16.1.0(react@19.2.3) devDependencies: - '@tauri-apps/cli': - specifier: ^2 - version: 2.9.6 '@testing-library/jest-dom': specifier: ^6.0.0 version: 6.9.1 @@ -624,89 +609,6 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} - '@tauri-apps/api@2.9.1': - resolution: {integrity: sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw==} - - '@tauri-apps/cli-darwin-arm64@2.9.6': - resolution: {integrity: sha512-gf5no6N9FCk1qMrti4lfwP77JHP5haASZgVbBgpZG7BUepB3fhiLCXGUK8LvuOjP36HivXewjg72LTnPDScnQQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@tauri-apps/cli-darwin-x64@2.9.6': - resolution: {integrity: sha512-oWh74WmqbERwwrwcueJyY6HYhgCksUc6NT7WKeXyrlY/FPmNgdyQAgcLuTSkhRFuQ6zh4Np1HZpOqCTpeZBDcw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@tauri-apps/cli-linux-arm-gnueabihf@2.9.6': - resolution: {integrity: sha512-/zde3bFroFsNXOHN204DC2qUxAcAanUjVXXSdEGmhwMUZeAQalNj5cz2Qli2elsRjKN/hVbZOJj0gQ5zaYUjSg==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - - '@tauri-apps/cli-linux-arm64-gnu@2.9.6': - resolution: {integrity: sha512-pvbljdhp9VOo4RnID5ywSxgBs7qiylTPlK56cTk7InR3kYSTJKYMqv/4Q/4rGo/mG8cVppesKIeBMH42fw6wjg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@tauri-apps/cli-linux-arm64-musl@2.9.6': - resolution: {integrity: sha512-02TKUndpodXBCR0oP//6dZWGYcc22Upf2eP27NvC6z0DIqvkBBFziQUcvi2n6SrwTRL0yGgQjkm9K5NIn8s6jw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@tauri-apps/cli-linux-riscv64-gnu@2.9.6': - resolution: {integrity: sha512-fmp1hnulbqzl1GkXl4aTX9fV+ubHw2LqlLH1PE3BxZ11EQk+l/TmiEongjnxF0ie4kV8DQfDNJ1KGiIdWe1GvQ==} - engines: {node: '>= 10'} - cpu: [riscv64] - os: [linux] - - '@tauri-apps/cli-linux-x64-gnu@2.9.6': - resolution: {integrity: sha512-vY0le8ad2KaV1PJr+jCd8fUF9VOjwwQP/uBuTJvhvKTloEwxYA/kAjKK9OpIslGA9m/zcnSo74czI6bBrm2sYA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@tauri-apps/cli-linux-x64-musl@2.9.6': - resolution: {integrity: sha512-TOEuB8YCFZTWVDzsO2yW0+zGcoMiPPwcUgdnW1ODnmgfwccpnihDRoks+ABT1e3fHb1ol8QQWsHSCovb3o2ENQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@tauri-apps/cli-win32-arm64-msvc@2.9.6': - resolution: {integrity: sha512-ujmDGMRc4qRLAnj8nNG26Rlz9klJ0I0jmZs2BPpmNNf0gM/rcVHhqbEkAaHPTBVIrtUdf7bGvQAD2pyIiUrBHQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@tauri-apps/cli-win32-ia32-msvc@2.9.6': - resolution: {integrity: sha512-S4pT0yAJgFX8QRCyKA1iKjZ9Q/oPjCZf66A/VlG5Yw54Nnr88J1uBpmenINbXxzyhduWrIXBaUbEY1K80ZbpMg==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@tauri-apps/cli-win32-x64-msvc@2.9.6': - resolution: {integrity: sha512-ldWuWSSkWbKOPjQMJoYVj9wLHcOniv7diyI5UAJ4XsBdtaFB0pKHQsqw/ItUma0VXGC7vB4E9fZjivmxur60aw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@tauri-apps/cli@2.9.6': - resolution: {integrity: sha512-3xDdXL5omQ3sPfBfdC8fCtDKcnyV7OqyzQgfyT5P3+zY6lcPqIYKQBvUasNvppi21RSdfhy44ttvJmftb0PCDw==} - engines: {node: '>= 10'} - hasBin: true - - '@tauri-apps/plugin-dialog@2.4.2': - resolution: {integrity: sha512-lNIn5CZuw8WZOn8zHzmFmDSzg5zfohWoa3mdULP0YFh/VogVdMVWZPcWSHlydsiJhRQYaTNSYKN7RmZKE2lCYQ==} - - '@tauri-apps/plugin-opener@2.5.2': - resolution: {integrity: sha512-ei/yRRoCklWHImwpCcDK3VhNXx+QXM9793aQ64YxpqVF0BDuuIlXhZgiAkc15wnPVav+IbkYhmDJIv5R326Mew==} - - '@tauri-apps/plugin-store@2.4.1': - resolution: {integrity: sha512-ckGSEzZ5Ii4Hf2D5x25Oqnm2Zf9MfDWAzR+volY0z/OOBz6aucPKEY0F649JvQ0Vupku6UJo7ugpGRDOFOunkA==} - '@testing-library/dom@9.3.4': resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==} engines: {node: '>=14'} @@ -2834,67 +2736,6 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@tauri-apps/api@2.9.1': {} - - '@tauri-apps/cli-darwin-arm64@2.9.6': - optional: true - - '@tauri-apps/cli-darwin-x64@2.9.6': - optional: true - - '@tauri-apps/cli-linux-arm-gnueabihf@2.9.6': - optional: true - - '@tauri-apps/cli-linux-arm64-gnu@2.9.6': - optional: true - - '@tauri-apps/cli-linux-arm64-musl@2.9.6': - optional: true - - '@tauri-apps/cli-linux-riscv64-gnu@2.9.6': - optional: true - - '@tauri-apps/cli-linux-x64-gnu@2.9.6': - optional: true - - '@tauri-apps/cli-linux-x64-musl@2.9.6': - optional: true - - '@tauri-apps/cli-win32-arm64-msvc@2.9.6': - optional: true - - '@tauri-apps/cli-win32-ia32-msvc@2.9.6': - optional: true - - '@tauri-apps/cli-win32-x64-msvc@2.9.6': - optional: true - - '@tauri-apps/cli@2.9.6': - optionalDependencies: - '@tauri-apps/cli-darwin-arm64': 2.9.6 - '@tauri-apps/cli-darwin-x64': 2.9.6 - '@tauri-apps/cli-linux-arm-gnueabihf': 2.9.6 - '@tauri-apps/cli-linux-arm64-gnu': 2.9.6 - '@tauri-apps/cli-linux-arm64-musl': 2.9.6 - '@tauri-apps/cli-linux-riscv64-gnu': 2.9.6 - '@tauri-apps/cli-linux-x64-gnu': 2.9.6 - '@tauri-apps/cli-linux-x64-musl': 2.9.6 - '@tauri-apps/cli-win32-arm64-msvc': 2.9.6 - '@tauri-apps/cli-win32-ia32-msvc': 2.9.6 - '@tauri-apps/cli-win32-x64-msvc': 2.9.6 - - '@tauri-apps/plugin-dialog@2.4.2': - dependencies: - '@tauri-apps/api': 2.9.1 - - '@tauri-apps/plugin-opener@2.5.2': - dependencies: - '@tauri-apps/api': 2.9.1 - - '@tauri-apps/plugin-store@2.4.1': - dependencies: - '@tauri-apps/api': 2.9.1 - '@testing-library/dom@9.3.4': dependencies: '@babel/code-frame': 7.27.1 diff --git a/public/tauri.svg b/frontend/public/tauri.svg similarity index 100% rename from public/tauri.svg rename to frontend/public/tauri.svg diff --git a/public/vite.svg b/frontend/public/vite.svg similarity index 100% rename from public/vite.svg rename to frontend/public/vite.svg diff --git a/src/App.css b/frontend/src/App.css similarity index 95% rename from src/App.css rename to frontend/src/App.css index 67b0230..a071cc6 100644 --- a/src/App.css +++ b/frontend/src/App.css @@ -23,7 +23,10 @@ .container { margin: 0; - padding-top: 10vh; + padding-top: 0; + height: 100vh; + overflow: hidden; + box-sizing: border-box; display: flex; flex-direction: column; justify-content: center; @@ -186,7 +189,10 @@ details summary span:first-child { } /* Ensure scroll functionality is maintained */ +html, body, -html { - overflow-x: hidden; +#root { + height: 100%; + margin: 0; + overflow: hidden; } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..fd6c5b3 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,93 @@ +import * as React from "react"; +import { api } from "./api/client"; +import { Chat } from "./components/Chat"; +import "./App.css"; + +function App() { + const [projectPath, setProjectPath] = React.useState(null); + const [errorMsg, setErrorMsg] = React.useState(null); + const [pathInput, setPathInput] = React.useState(""); + const [isOpening, setIsOpening] = React.useState(false); + + async function openProject(path: string) { + if (!path.trim()) { + setErrorMsg("Please enter a project path."); + return; + } + + try { + setErrorMsg(null); + setIsOpening(true); + const confirmedPath = await api.openProject(path.trim()); + setProjectPath(confirmedPath); + } catch (e) { + console.error(e); + const message = + e instanceof Error + ? e.message + : typeof e === "string" + ? e + : "An error occurred opening the project."; + setErrorMsg(message); + } finally { + setIsOpening(false); + } + } + + function handleOpen() { + void openProject(pathInput); + } + + async function closeProject() { + try { + await api.closeProject(); + setProjectPath(null); + } catch (e) { + console.error(e); + } + } + + return ( +
+ {!projectPath ? ( +
+

AI Code Assistant

+

Paste a project path to start the Story-Driven Spec Workflow.

+ setPathInput(event.target.value)} + onKeyDown={(event) => { + if (event.key === "Enter") { + handleOpen(); + } + }} + style={{ width: "100%", padding: "10px", marginTop: "12px" }} + /> + +
+ ) : ( +
+ +
+ )} + + {errorMsg && ( +
+

Error: {errorMsg}

+
+ )} +
+ ); +} + +export default App; diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts new file mode 100644 index 0000000..a49a287 --- /dev/null +++ b/frontend/src/api/client.ts @@ -0,0 +1,226 @@ +export type WsRequest = + | { + type: "chat"; + messages: Message[]; + config: ProviderConfig; + } + | { + type: "cancel"; + }; + +export type WsResponse = + | { type: "token"; content: string } + | { type: "update"; messages: Message[] } + | { type: "error"; message: string }; + +export interface ProviderConfig { + provider: string; + model: string; + base_url?: string; + enable_tools?: boolean; +} + +export type Role = "system" | "user" | "assistant" | "tool"; + +export interface ToolCall { + id?: string; + type: string; + function: { + name: string; + arguments: string; + }; +} + +export interface Message { + role: Role; + content: string; + tool_calls?: ToolCall[]; + tool_call_id?: string; +} + +export interface FileEntry { + name: string; + kind: "file" | "dir"; +} + +export interface SearchResult { + path: string; + matches: number; +} + +export interface CommandOutput { + stdout: string; + stderr: string; + exit_code: number; +} + +const DEFAULT_API_BASE = "/api"; +const DEFAULT_WS_PATH = "/ws"; + +function buildApiUrl(path: string, baseUrl = DEFAULT_API_BASE): string { + return `${baseUrl}${path}`; +} + +async function requestJson( + path: string, + options: RequestInit = {}, + baseUrl = DEFAULT_API_BASE, +): Promise { + const res = await fetch(buildApiUrl(path, baseUrl), { + headers: { + "Content-Type": "application/json", + ...(options.headers ?? {}), + }, + ...options, + }); + + if (!res.ok) { + const text = await res.text(); + throw new Error(text || `Request failed (${res.status})`); + } + + return res.json() as Promise; +} + +export const api = { + getCurrentProject(baseUrl?: string) { + return requestJson("/project", {}, baseUrl); + }, + openProject(path: string, baseUrl?: string) { + return requestJson( + "/project", + { method: "POST", body: JSON.stringify({ path }) }, + baseUrl, + ); + }, + closeProject(baseUrl?: string) { + return requestJson("/project", { method: "DELETE" }, baseUrl); + }, + getModelPreference(baseUrl?: string) { + return requestJson("/model", {}, baseUrl); + }, + setModelPreference(model: string, baseUrl?: string) { + return requestJson( + "/model", + { method: "POST", body: JSON.stringify({ model }) }, + baseUrl, + ); + }, + getOllamaModels(baseUrlParam?: string, baseUrl?: string) { + const url = new URL( + buildApiUrl("/ollama/models", baseUrl), + window.location.origin, + ); + if (baseUrlParam) { + url.searchParams.set("base_url", baseUrlParam); + } + return requestJson(url.pathname + url.search, {}, ""); + }, + getAnthropicApiKeyExists(baseUrl?: string) { + return requestJson("/anthropic/key/exists", {}, baseUrl); + }, + setAnthropicApiKey(api_key: string, baseUrl?: string) { + return requestJson( + "/anthropic/key", + { method: "POST", body: JSON.stringify({ api_key }) }, + baseUrl, + ); + }, + readFile(path: string, baseUrl?: string) { + return requestJson( + "/fs/read", + { method: "POST", body: JSON.stringify({ path }) }, + baseUrl, + ); + }, + writeFile(path: string, content: string, baseUrl?: string) { + return requestJson( + "/fs/write", + { method: "POST", body: JSON.stringify({ path, content }) }, + baseUrl, + ); + }, + listDirectory(path: string, baseUrl?: string) { + return requestJson( + "/fs/list", + { method: "POST", body: JSON.stringify({ path }) }, + baseUrl, + ); + }, + searchFiles(query: string, baseUrl?: string) { + return requestJson( + "/fs/search", + { method: "POST", body: JSON.stringify({ query }) }, + baseUrl, + ); + }, + execShell(command: string, args: string[], baseUrl?: string) { + return requestJson( + "/shell/exec", + { method: "POST", body: JSON.stringify({ command, args }) }, + baseUrl, + ); + }, + cancelChat(baseUrl?: string) { + return requestJson("/chat/cancel", { method: "POST" }, baseUrl); + }, +}; + +export class ChatWebSocket { + private socket?: WebSocket; + private onToken?: (content: string) => void; + private onUpdate?: (messages: Message[]) => void; + private onError?: (message: string) => void; + + connect( + handlers: { + onToken?: (content: string) => void; + onUpdate?: (messages: Message[]) => void; + onError?: (message: string) => void; + }, + wsPath = DEFAULT_WS_PATH, + ) { + this.onToken = handlers.onToken; + this.onUpdate = handlers.onUpdate; + this.onError = handlers.onError; + + const protocol = window.location.protocol === "https:" ? "wss" : "ws"; + const wsUrl = `${protocol}://${window.location.host}${wsPath}`; + this.socket = new WebSocket(wsUrl); + + this.socket.onmessage = (event) => { + try { + const data = JSON.parse(event.data) as WsResponse; + if (data.type === "token") this.onToken?.(data.content); + if (data.type === "update") this.onUpdate?.(data.messages); + if (data.type === "error") this.onError?.(data.message); + } catch (err) { + this.onError?.(String(err)); + } + }; + + this.socket.onerror = () => { + this.onError?.("WebSocket error"); + }; + } + + sendChat(messages: Message[], config: ProviderConfig) { + this.send({ type: "chat", messages, config }); + } + + cancel() { + this.send({ type: "cancel" }); + } + + close() { + this.socket?.close(); + } + + private send(payload: WsRequest) { + if (!this.socket || this.socket.readyState !== WebSocket.OPEN) { + this.onError?.("WebSocket is not connected"); + return; + } + this.socket.send(JSON.stringify(payload)); + } +} diff --git a/src/assets/react.svg b/frontend/src/assets/react.svg similarity index 100% rename from src/assets/react.svg rename to frontend/src/assets/react.svg diff --git a/frontend/src/components/Chat.tsx b/frontend/src/components/Chat.tsx new file mode 100644 index 0000000..a436304 --- /dev/null +++ b/frontend/src/components/Chat.tsx @@ -0,0 +1,906 @@ +import * as React from "react"; +import Markdown from "react-markdown"; +import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; +import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism"; +import { api, ChatWebSocket } from "../api/client"; +import type { Message, ProviderConfig, ToolCall } from "../types"; + +const { useCallback, useEffect, useRef, useState } = React; + +interface ChatProps { + projectPath: string; + onCloseProject: () => void; +} + +export function Chat({ projectPath, onCloseProject }: ChatProps) { + const [messages, setMessages] = useState([]); + const [input, setInput] = useState(""); + const [loading, setLoading] = useState(false); + const [model, setModel] = useState("llama3.1"); + const [enableTools, setEnableTools] = useState(true); + const [availableModels, setAvailableModels] = useState([]); + const [claudeModels] = useState([ + "claude-3-5-sonnet-20241022", + "claude-3-5-haiku-20241022", + ]); + const [streamingContent, setStreamingContent] = useState(""); + const [showApiKeyDialog, setShowApiKeyDialog] = useState(false); + const [apiKeyInput, setApiKeyInput] = useState(""); + + const wsRef = useRef(null); + const messagesEndRef = useRef(null); + const inputRef = useRef(null); + const scrollContainerRef = useRef(null); + const shouldAutoScrollRef = useRef(true); + const lastScrollTopRef = useRef(0); + const userScrolledUpRef = useRef(false); + const pendingMessageRef = useRef(""); + + const estimateTokens = (text: string): number => Math.ceil(text.length / 4); + + const getContextWindowSize = (modelName: string): number => { + if (modelName.startsWith("claude-")) return 200000; + if (modelName.includes("llama3")) return 8192; + if (modelName.includes("qwen2.5")) return 32768; + if (modelName.includes("deepseek")) return 16384; + return 8192; + }; + + const calculateContextUsage = (): { + used: number; + total: number; + percentage: number; + } => { + let totalTokens = 0; + + totalTokens += 200; + + for (const msg of messages) { + totalTokens += estimateTokens(msg.content); + if (msg.tool_calls) { + totalTokens += estimateTokens(JSON.stringify(msg.tool_calls)); + } + } + + if (streamingContent) { + totalTokens += estimateTokens(streamingContent); + } + + const contextWindow = getContextWindowSize(model); + const percentage = Math.round((totalTokens / contextWindow) * 100); + + return { + used: totalTokens, + total: contextWindow, + percentage, + }; + }; + + const contextUsage = calculateContextUsage(); + + const getContextEmoji = (percentage: number): string => { + if (percentage >= 90) return "🔴"; + if (percentage >= 75) return "🟡"; + return "🟢"; + }; + + useEffect(() => { + api + .getOllamaModels() + .then(async (models) => { + if (models.length > 0) { + const sortedModels = models.sort((a, b) => + a.toLowerCase().localeCompare(b.toLowerCase()), + ); + setAvailableModels(sortedModels); + + try { + const savedModel = await api.getModelPreference(); + if (savedModel) { + setModel(savedModel); + } else if (sortedModels.length > 0) { + setModel(sortedModels[0]); + } + } catch (e) { + console.error(e); + } + } + }) + .catch((err) => console.error(err)); + }, []); + + useEffect(() => { + const ws = new ChatWebSocket(); + wsRef.current = ws; + + ws.connect({ + onToken: (content) => { + setStreamingContent((prev: string) => prev + content); + }, + onUpdate: (history) => { + setMessages(history); + setStreamingContent(""); + const last = history[history.length - 1]; + if (last?.role === "assistant" && !last.tool_calls) { + setLoading(false); + } + }, + onError: (message) => { + console.error("WebSocket error:", message); + setLoading(false); + }, + }); + + return () => { + ws.close(); + wsRef.current = null; + }; + }, []); + + const scrollToBottom = useCallback(() => { + const element = scrollContainerRef.current; + if (element) { + element.scrollTop = element.scrollHeight; + lastScrollTopRef.current = element.scrollHeight; + } + }, []); + + const handleScroll = () => { + const element = scrollContainerRef.current; + if (!element) return; + + const currentScrollTop = element.scrollTop; + const isAtBottom = + element.scrollHeight - element.scrollTop - element.clientHeight < 5; + + if (currentScrollTop < lastScrollTopRef.current) { + userScrolledUpRef.current = true; + shouldAutoScrollRef.current = false; + } + + if (isAtBottom) { + userScrolledUpRef.current = false; + shouldAutoScrollRef.current = true; + } + + lastScrollTopRef.current = currentScrollTop; + }; + + const autoScrollKey = messages.length + streamingContent.length; + + useEffect(() => { + if ( + autoScrollKey >= 0 && + shouldAutoScrollRef.current && + !userScrolledUpRef.current + ) { + scrollToBottom(); + } + }, [autoScrollKey, scrollToBottom]); + + useEffect(() => { + inputRef.current?.focus(); + }, []); + + const cancelGeneration = async () => { + try { + wsRef.current?.cancel(); + await api.cancelChat(); + + if (streamingContent) { + setMessages((prev: Message[]) => [ + ...prev, + { role: "assistant", content: streamingContent }, + ]); + setStreamingContent(""); + } + + setLoading(false); + } catch (e) { + console.error("Failed to cancel chat:", e); + } + }; + + const sendMessage = async (messageOverride?: string) => { + const messageToSend = messageOverride ?? input; + if (!messageToSend.trim() || loading) return; + + if (model.startsWith("claude-")) { + const hasKey = await api.getAnthropicApiKeyExists(); + if (!hasKey) { + pendingMessageRef.current = messageToSend; + setShowApiKeyDialog(true); + return; + } + } + + const userMsg: Message = { role: "user", content: messageToSend }; + const newHistory = [...messages, userMsg]; + + setMessages(newHistory); + if (!messageOverride || messageOverride === input) { + setInput(""); + } + setLoading(true); + setStreamingContent(""); + + try { + const config: ProviderConfig = { + provider: model.startsWith("claude-") ? "anthropic" : "ollama", + model, + base_url: "http://localhost:11434", + enable_tools: enableTools, + }; + + wsRef.current?.sendChat(newHistory, config); + } catch (e) { + console.error("Chat error:", e); + const errorMessage = String(e); + if (!errorMessage.includes("Chat cancelled by user")) { + setMessages((prev: Message[]) => [ + ...prev, + { role: "assistant", content: `**Error:** ${e}` }, + ]); + } + setLoading(false); + } + }; + + const handleSaveApiKey = async () => { + if (!apiKeyInput.trim()) return; + + try { + await api.setAnthropicApiKey(apiKeyInput); + setShowApiKeyDialog(false); + setApiKeyInput(""); + + const pendingMessage = pendingMessageRef.current; + pendingMessageRef.current = ""; + + if (pendingMessage.trim()) { + sendMessage(pendingMessage); + } + } catch (e) { + console.error("Failed to save API key:", e); + alert(`Failed to save API key: ${e}`); + } + }; + + const clearSession = async () => { + const confirmed = window.confirm( + "Are you sure? This will clear all messages and reset the conversation context.", + ); + + if (confirmed) { + try { + await api.cancelChat(); + wsRef.current?.cancel(); + } catch (e) { + console.error("Failed to cancel chat:", e); + } + + setMessages([]); + setStreamingContent(""); + setLoading(false); + } + }; + + return ( +
+
+
+
+ {projectPath} +
+ +
+ +
+
+ {getContextEmoji(contextUsage.percentage)} {contextUsage.percentage} + % +
+ + + {availableModels.length > 0 || claudeModels.length > 0 ? ( + + ) : ( + { + const newModel = e.target.value; + setModel(newModel); + api.setModelPreference(newModel).catch(console.error); + }} + placeholder="Model" + style={{ + padding: "6px 12px", + borderRadius: "99px", + border: "none", + fontSize: "0.9em", + background: "#2f2f2f", + color: "#ececec", + outline: "none", + }} + /> + )} + +
+
+ +
+
+ {messages.map((msg: Message, idx: number) => ( +
+
+ {msg.role === "user" ? ( + msg.content + ) : msg.role === "tool" ? ( +
+ + + + Tool Output + {msg.tool_call_id && ` (${msg.tool_call_id})`} + + +
+											{msg.content}
+										
+
+ ) : ( +
+ { + const match = /language-(\w+)/.exec(className || ""); + const isInline = !className; + return !isInline && match ? ( + + {String(children).replace(/\n$/, "")} + + ) : ( + + {children} + + ); + }, + }} + > + {msg.content} + +
+ )} + + {msg.tool_calls && ( +
+ {msg.tool_calls.map((tc: ToolCall, i: number) => { + let argsSummary = ""; + try { + const args = JSON.parse(tc.function.arguments); + const firstKey = Object.keys(args)[0]; + if (firstKey && args[firstKey]) { + argsSummary = String(args[firstKey]); + if (argsSummary.length > 50) { + argsSummary = `${argsSummary.substring(0, 47)}...`; + } + } + } catch (_e) { + // ignore + } + + return ( +
+ + + {tc.function.name} + {argsSummary && `(${argsSummary})`} + +
+ ); + })} +
+ )} +
+
+ ))} + {loading && streamingContent && ( +
+
+ { + const match = /language-(\w+)/.exec(className || ""); + const isInline = !className; + return !isInline && match ? ( + + {String(children).replace(/\n$/, "")} + + ) : ( + + {children} + + ); + }, + }} + > + {streamingContent} + +
+
+ )} + {loading && !streamingContent && ( +
+ Thinking... +
+ )} +
+
+
+ +
+
+ setInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + sendMessage(); + } + }} + placeholder="Send a message..." + style={{ + flex: 1, + padding: "14px 20px", + borderRadius: "24px", + border: "1px solid #333", + outline: "none", + fontSize: "1rem", + fontWeight: "500", + background: "#2f2f2f", + color: "#ececec", + boxShadow: "0 2px 6px rgba(0,0,0,0.02)", + }} + /> + +
+
+ + {showApiKeyDialog && ( +
+
+

+ Enter Anthropic API Key +

+

+ To use Claude models, please enter your Anthropic API key. Your + key will be stored server-side and reused across sessions. +

+ setApiKeyInput(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && handleSaveApiKey()} + placeholder="sk-ant-..." + style={{ + width: "100%", + padding: "12px", + borderRadius: "8px", + border: "1px solid #555", + backgroundColor: "#1a1a1a", + color: "#ececec", + fontSize: "1em", + marginBottom: "20px", + outline: "none", + }} + /> +
+ + +
+
+
+ )} +
+ ); +} diff --git a/src/main.tsx b/frontend/src/main.tsx similarity index 86% rename from src/main.tsx rename to frontend/src/main.tsx index c08eb09..a90798a 100644 --- a/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import * as React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; diff --git a/src/types.ts b/frontend/src/types.ts similarity index 52% rename from src/types.ts rename to frontend/src/types.ts index bc25a06..2c02f93 100644 --- a/src/types.ts +++ b/frontend/src/types.ts @@ -1,19 +1,3 @@ -export interface FileEntry { - name: string; - kind: "file" | "dir"; -} - -export interface SearchResult { - path: string; - matches: number; -} - -export interface CommandOutput { - stdout: string; - stderr: string; - exit_code: number; -} - export type Role = "system" | "user" | "assistant" | "tool"; export interface ToolCall { @@ -38,3 +22,45 @@ export interface ProviderConfig { base_url?: string; enable_tools?: boolean; } + +export interface FileEntry { + name: string; + kind: "file" | "dir"; +} + +export interface SearchResult { + path: string; + matches: number; +} + +export interface CommandOutput { + stdout: string; + stderr: string; + exit_code: number; +} + +export type WsRequest = + | { + type: "chat"; + messages: Message[]; + config: ProviderConfig; + } + | { + type: "cancel"; + }; + +export type WsResponse = + | { type: "token"; content: string } + | { type: "update"; messages: Message[] } + | { type: "error"; message: string }; + +// Re-export API client types for convenience +export type { + Message as ApiMessage, + ProviderConfig as ApiProviderConfig, + FileEntry as ApiFileEntry, + SearchResult as ApiSearchResult, + CommandOutput as ApiCommandOutput, + WsRequest as ApiWsRequest, + WsResponse as ApiWsResponse, +}; diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..41c9893 --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1,10 @@ +/// + +declare module "react" { + interface InputHTMLAttributes { + webkitdirectory?: string; + directory?: string; + } +} + +export {}; diff --git a/tsconfig.json b/frontend/tsconfig.json similarity index 100% rename from tsconfig.json rename to frontend/tsconfig.json diff --git a/tsconfig.node.json b/frontend/tsconfig.node.json similarity index 100% rename from tsconfig.node.json rename to frontend/tsconfig.node.json diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..863aea4 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,20 @@ +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +// https://vite.dev/config/ +export default defineConfig(() => ({ + plugins: [react()], + server: { + proxy: { + "/api": "http://localhost:3001", + "/ws": { + target: "ws://localhost:3001", + ws: true, + }, + }, + }, + build: { + outDir: "dist", + emptyOutDir: true, + }, +})); diff --git a/server/Cargo.lock b/server/Cargo.lock new file mode 100644 index 0000000..0e9ecb4 --- /dev/null +++ b/server/Cargo.lock @@ -0,0 +1,3022 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "headers" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" +dependencies = [ + "base64", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.181" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "living-spec-standalone-server" +version = "0.1.0" +dependencies = [ + "async-trait", + "chrono", + "eventsource-stream", + "futures", + "ignore", + "mime_guess", + "poem", + "poem-openapi", + "reqwest", + "rust-embed", + "serde", + "serde_json", + "tempfile", + "tokio", + "uuid", + "walkdir", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "tokio", + "version_check", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "poem" +version = "3.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f977080932c87287147dca052951c3e2696f8759863f6b4e4c0c9ffe7a4cc8b" +dependencies = [ + "base64", + "bytes", + "futures-util", + "headers", + "http", + "http-body-util", + "hyper", + "hyper-util", + "mime", + "multer", + "nix", + "parking_lot", + "percent-encoding", + "pin-project-lite", + "poem-derive", + "quick-xml", + "regex", + "rfc7239", + "serde", + "serde_json", + "serde_urlencoded", + "serde_yaml", + "smallvec", + "sync_wrapper", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tokio-util", + "tracing", + "wildmatch", +] + +[[package]] +name = "poem-derive" +version = "3.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "056e2fea6de1cb240ffe23cfc4fc370b629f8be83b5f27e16b7acd5231a72de4" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "poem-openapi" +version = "5.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ccbcc395bf4dd03df1da32da351b6b6732e4074ce27ddec315650e52a2be44c" +dependencies = [ + "base64", + "bytes", + "derive_more", + "futures-util", + "indexmap", + "itertools", + "mime", + "num-traits", + "poem", + "poem-openapi-derive", + "quick-xml", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "serde_yaml", + "thiserror 2.0.18", + "tokio", +] + +[[package]] +name = "poem-openapi-derive" +version = "5.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41273b691a3d467a8c44d05506afba9f7b6bd56c9cdf80123de13fe52d7ec587" +dependencies = [ + "darling", + "http", + "indexmap", + "mime", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "syn", + "thiserror 2.0.18", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "rfc7239" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a82f1d1e38e9a85bb58ffcfadf22ed6f2c94e8cd8581ec2b0f80a2a6858350f" +dependencies = [ + "uncased", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rust-embed" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" +dependencies = [ + "sha2", + "walkdir", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +dependencies = [ + "fastrand", + "getrandom 0.4.1", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489a59b6730eda1b0171fcfda8b121f4bee2b35cba8645ca35c5f7ba3eb736c1" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.7+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "247eaa3197818b831697600aadf81514e577e0cba5eab10f7e064e78ae154df1" +dependencies = [ + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadc29d668c91fcc564941132e17b28a7ceb2f3ebf0b9dae3e03fd7a6748eb0d" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror 2.0.18", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "wildmatch" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29333c3ea1ba8b17211763463ff24ee84e41c78224c16b001cd907e663a38c68" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/server/Cargo.toml b/server/Cargo.toml new file mode 100644 index 0000000..810af6b --- /dev/null +++ b/server/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "living-spec-standalone-server" +version = "0.1.0" +edition = "2024" +build = "build.rs" + +[dependencies] +poem = { version = "3", features = ["websocket"] } +poem-openapi = { version = "5", features = ["swagger-ui"] } +tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +reqwest = { version = "0.13.2", features = ["json", "stream"] } +futures = "0.3" +uuid = { version = "1.20.0", features = ["v4", "serde"] } +chrono = { version = "0.4.43", features = ["serde"] } +async-trait = "0.1.89" +ignore = "0.4.25" +walkdir = "2.5.0" +eventsource-stream = "0.2.3" +rust-embed = "8" +mime_guess = "2" + +[dev-dependencies] +tempfile = "3" diff --git a/server/build.rs b/server/build.rs new file mode 100644 index 0000000..4e396fc --- /dev/null +++ b/server/build.rs @@ -0,0 +1,23 @@ +use std::fs; +use std::path::Path; + +fn main() { + let dist_dir = Path::new("../frontend/dist"); + + println!("cargo:rerun-if-changed=build.rs"); + + if let Ok(entries) = fs::read_dir(dist_dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + if let Ok(sub_entries) = fs::read_dir(&path) { + for sub_entry in sub_entries.flatten() { + println!("cargo:rerun-if-changed={}", sub_entry.path().display()); + } + } + } else { + println!("cargo:rerun-if-changed={}", path.display()); + } + } + } +} diff --git a/server/src/commands/chat.rs b/server/src/commands/chat.rs new file mode 100644 index 0000000..228377c --- /dev/null +++ b/server/src/commands/chat.rs @@ -0,0 +1,378 @@ +use crate::llm::prompts::SYSTEM_PROMPT; +use crate::llm::types::{Message, Role, ToolCall, ToolDefinition, ToolFunctionDefinition}; +use crate::state::SessionState; +use crate::store::StoreOps; +use serde::Deserialize; +use serde_json::json; + +const MAX_TURNS: usize = 30; +const KEY_ANTHROPIC_API_KEY: &str = "anthropic_api_key"; + +#[derive(Deserialize, Clone)] +pub struct ProviderConfig { + pub provider: String, + pub model: String, + pub base_url: Option, + pub enable_tools: Option, +} + +fn get_anthropic_api_key_exists_impl(store: &dyn StoreOps) -> bool { + match store.get(KEY_ANTHROPIC_API_KEY) { + Some(value) => value.as_str().map(|k| !k.is_empty()).unwrap_or(false), + None => false, + } +} + +fn set_anthropic_api_key_impl(store: &dyn StoreOps, api_key: &str) -> Result<(), String> { + store.set(KEY_ANTHROPIC_API_KEY, json!(api_key)); + store.save()?; + + match store.get(KEY_ANTHROPIC_API_KEY) { + Some(value) => { + if let Some(retrieved) = value.as_str() { + if retrieved != api_key { + return Err("Retrieved key does not match saved key".to_string()); + } + } else { + return Err("Stored value is not a string".to_string()); + } + } + None => { + return Err("API key was saved but cannot be retrieved".to_string()); + } + } + + Ok(()) +} + +fn get_anthropic_api_key_impl(store: &dyn StoreOps) -> Result { + match store.get(KEY_ANTHROPIC_API_KEY) { + Some(value) => { + if let Some(key) = value.as_str() { + if key.is_empty() { + Err("Anthropic API key is empty. Please set your API key.".to_string()) + } else { + Ok(key.to_string()) + } + } else { + Err("Stored API key is not a string".to_string()) + } + } + None => Err("Anthropic API key not found. Please set your API key.".to_string()), + } +} + +fn parse_tool_arguments(args_str: &str) -> Result { + serde_json::from_str(args_str).map_err(|e| format!("Error parsing arguments: {e}")) +} + +pub fn get_tool_definitions() -> Vec { + vec![ + ToolDefinition { + kind: "function".to_string(), + function: ToolFunctionDefinition { + name: "read_file".to_string(), + description: "Reads the complete content of a file from the project. Use this to understand existing code before making changes.".to_string(), + parameters: json!({ + "type": "object", + "properties": { + "path": { "type": "string", "description": "Relative path to the file from project root" } + }, + "required": ["path"] + }), + }, + }, + ToolDefinition { + kind: "function".to_string(), + function: ToolFunctionDefinition { + name: "write_file".to_string(), + description: "Creates or completely overwrites a file with new content. YOU MUST USE THIS to implement code changes - do not suggest code to the user. The content parameter must contain the COMPLETE file including all imports, functions, and unchanged code.".to_string(), + parameters: json!({ + "type": "object", + "properties": { + "path": { "type": "string", "description": "Relative path to the file from project root" }, + "content": { "type": "string", "description": "The complete file content to write (not a diff or partial code)" } + }, + "required": ["path", "content"] + }), + }, + }, + ToolDefinition { + kind: "function".to_string(), + function: ToolFunctionDefinition { + name: "list_directory".to_string(), + description: "Lists all files and directories at a given path. Use this to explore the project structure.".to_string(), + parameters: json!({ + "type": "object", + "properties": { + "path": { "type": "string", "description": "Relative path to list (use '.' for project root)" } + }, + "required": ["path"] + }), + }, + }, + ToolDefinition { + kind: "function".to_string(), + function: ToolFunctionDefinition { + name: "search_files".to_string(), + description: "Searches for text patterns across all files in the project. Use this to find functions, variables, or code patterns when you don't know which file they're in." + .to_string(), + parameters: json!({ + "type": "object", + "properties": { + "query": { "type": "string", "description": "The text pattern to search for across all files" } + }, + "required": ["query"] + }), + }, + }, + ToolDefinition { + kind: "function".to_string(), + function: ToolFunctionDefinition { + name: "exec_shell".to_string(), + description: "Executes a shell command in the project root directory. Use this to run tests, build commands, git operations, or any command-line tool. Examples: cargo check, npm test, git status.".to_string(), + parameters: json!({ + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The command binary to execute (e.g., 'git', 'cargo', 'npm', 'ls')" + }, + "args": { + "type": "array", + "items": { "type": "string" }, + "description": "Array of arguments to pass to the command (e.g., ['status'] for git status)" + } + }, + "required": ["command", "args"] + }), + }, + }, + ] +} + +pub async fn get_ollama_models(base_url: Option) -> Result, String> { + use crate::llm::providers::ollama::OllamaProvider; + let url = base_url.unwrap_or_else(|| "http://localhost:11434".to_string()); + OllamaProvider::get_models(&url).await +} + +pub fn get_anthropic_api_key_exists(store: &dyn StoreOps) -> Result { + Ok(get_anthropic_api_key_exists_impl(store)) +} + +pub fn set_anthropic_api_key(store: &dyn StoreOps, api_key: String) -> Result<(), String> { + set_anthropic_api_key_impl(store, &api_key) +} + +pub async fn chat( + messages: Vec, + config: ProviderConfig, + state: &SessionState, + store: &dyn StoreOps, + mut on_update: F, + mut on_token: U, +) -> Result, String> +where + F: FnMut(&[Message]) + Send, + U: FnMut(&str) + Send, +{ + use crate::llm::providers::anthropic::AnthropicProvider; + use crate::llm::providers::ollama::OllamaProvider; + + let _ = state.cancel_tx.send(false); + let mut cancel_rx = state.cancel_rx.clone(); + cancel_rx.borrow_and_update(); + + let base_url = config + .base_url + .clone() + .unwrap_or_else(|| "http://localhost:11434".to_string()); + + let is_claude = config.model.starts_with("claude-"); + + if !is_claude && config.provider.as_str() != "ollama" { + return Err(format!("Unsupported provider: {}", config.provider)); + } + + let tool_defs = get_tool_definitions(); + let tools = if config.enable_tools.unwrap_or(true) { + tool_defs.as_slice() + } else { + &[] + }; + + let mut current_history = messages.clone(); + + current_history.insert( + 0, + Message { + role: Role::System, + content: SYSTEM_PROMPT.to_string(), + tool_calls: None, + tool_call_id: None, + }, + ); + + current_history.insert( + 1, + Message { + role: Role::System, + content: "REMINDER: Distinguish between showing examples (use code blocks in chat) vs implementing changes (use write_file tool). Keywords like 'show me', 'example', 'how does' = chat response. Keywords like 'create', 'add', 'implement', 'fix' = use tools." + .to_string(), + tool_calls: None, + tool_call_id: None, + }, + ); + + let mut new_messages: Vec = Vec::new(); + let mut turn_count = 0; + + loop { + if *cancel_rx.borrow() { + return Err("Chat cancelled by user".to_string()); + } + + if turn_count >= MAX_TURNS { + return Err("Max conversation turns reached.".to_string()); + } + turn_count += 1; + + let response = if is_claude { + let api_key = get_anthropic_api_key_impl(store)?; + let anthropic_provider = AnthropicProvider::new(api_key); + anthropic_provider + .chat_stream( + &config.model, + ¤t_history, + tools, + &mut cancel_rx, + |token| on_token(token), + ) + .await + .map_err(|e| format!("Anthropic Error: {e}"))? + } else { + let ollama_provider = OllamaProvider::new(base_url.clone()); + ollama_provider + .chat_stream( + &config.model, + ¤t_history, + tools, + &mut cancel_rx, + |token| on_token(token), + ) + .await + .map_err(|e| format!("Ollama Error: {e}"))? + }; + + if let Some(tool_calls) = response.tool_calls { + let assistant_msg = Message { + role: Role::Assistant, + content: response.content.unwrap_or_default(), + tool_calls: Some(tool_calls.clone()), + tool_call_id: None, + }; + + current_history.push(assistant_msg.clone()); + new_messages.push(assistant_msg); + on_update(¤t_history[2..]); + + for call in tool_calls { + if *cancel_rx.borrow() { + return Err("Chat cancelled before tool execution".to_string()); + } + + let output = execute_tool(&call, state).await; + + let tool_msg = Message { + role: Role::Tool, + content: output, + tool_calls: None, + tool_call_id: call.id, + }; + + current_history.push(tool_msg.clone()); + new_messages.push(tool_msg); + on_update(¤t_history[2..]); + } + } else { + let assistant_msg = Message { + role: Role::Assistant, + content: response.content.unwrap_or_default(), + tool_calls: None, + tool_call_id: None, + }; + + new_messages.push(assistant_msg.clone()); + current_history.push(assistant_msg); + on_update(¤t_history[2..]); + break; + } + } + + Ok(new_messages) +} + +async fn execute_tool(call: &ToolCall, state: &SessionState) -> String { + use crate::commands::{fs, search, shell}; + + let name = call.function.name.as_str(); + let args: serde_json::Value = match parse_tool_arguments(&call.function.arguments) { + Ok(v) => v, + Err(e) => return e, + }; + + match name { + "read_file" => { + let path = args["path"].as_str().unwrap_or("").to_string(); + match fs::read_file(path, state).await { + Ok(content) => content, + Err(e) => format!("Error: {e}"), + } + } + "write_file" => { + let path = args["path"].as_str().unwrap_or("").to_string(); + let content = args["content"].as_str().unwrap_or("").to_string(); + match fs::write_file(path, content, state).await { + Ok(()) => "File written successfully.".to_string(), + Err(e) => format!("Error: {e}"), + } + } + "list_directory" => { + let path = args["path"].as_str().unwrap_or("").to_string(); + match fs::list_directory(path, state).await { + Ok(entries) => serde_json::to_string(&entries).unwrap_or_default(), + Err(e) => format!("Error: {e}"), + } + } + "search_files" => { + let query = args["query"].as_str().unwrap_or("").to_string(); + match search::search_files(query, state).await { + Ok(results) => serde_json::to_string(&results).unwrap_or_default(), + Err(e) => format!("Error: {e}"), + } + } + "exec_shell" => { + let command = args["command"].as_str().unwrap_or("").to_string(); + let args_vec: Vec = args["args"] + .as_array() + .map(|arr| { + arr.iter() + .map(|v| v.as_str().unwrap_or("").to_string()) + .collect() + }) + .unwrap_or_default(); + + match shell::exec_shell(command, args_vec, state).await { + Ok(output) => serde_json::to_string(&output).unwrap_or_default(), + Err(e) => format!("Error: {e}"), + } + } + _ => format!("Unknown tool: {name}"), + } +} + +pub fn cancel_chat(state: &SessionState) -> Result<(), String> { + state.cancel_tx.send(true).map_err(|e| e.to_string())?; + Ok(()) +} diff --git a/server/src/commands/fs.rs b/server/src/commands/fs.rs new file mode 100644 index 0000000..5a060cc --- /dev/null +++ b/server/src/commands/fs.rs @@ -0,0 +1,191 @@ +use crate::state::SessionState; +use crate::store::StoreOps; +use serde::Serialize; +use serde_json::json; +use std::fs; +use std::path::PathBuf; + +const KEY_LAST_PROJECT: &str = "last_project_path"; +const KEY_SELECTED_MODEL: &str = "selected_model"; + +/// Resolves a relative path against the active project root (pure function for testing). +/// Returns error if path attempts traversal (..). +fn resolve_path_impl(root: PathBuf, relative_path: &str) -> Result { + if relative_path.contains("..") { + return Err("Security Violation: Directory traversal ('..') is not allowed.".to_string()); + } + + Ok(root.join(relative_path)) +} + +/// Resolves a relative path against the active project root. +/// Returns error if no project is open or if path attempts traversal (..). +fn resolve_path(state: &SessionState, relative_path: &str) -> Result { + let root = state.get_project_root()?; + resolve_path_impl(root, relative_path) +} + +/// Validate that a path exists and is a directory (pure function for testing) +async fn validate_project_path(path: PathBuf) -> Result<(), String> { + tokio::task::spawn_blocking(move || { + if !path.exists() { + return Err(format!("Path does not exist: {}", path.display())); + } + if !path.is_dir() { + return Err(format!("Path is not a directory: {}", path.display())); + } + Ok(()) + }) + .await + .map_err(|e| format!("Task failed: {}", e))? +} + +pub async fn open_project( + path: String, + state: &SessionState, + store: &dyn StoreOps, +) -> Result { + let p = PathBuf::from(&path); + + validate_project_path(p.clone()).await?; + + { + let mut root = state.project_root.lock().map_err(|e| e.to_string())?; + *root = Some(p); + } + + store.set(KEY_LAST_PROJECT, json!(path)); + store.save()?; + + Ok(path) +} + +pub fn close_project(state: &SessionState, store: &dyn StoreOps) -> Result<(), String> { + { + let mut root = state.project_root.lock().map_err(|e| e.to_string())?; + *root = None; + } + + store.delete(KEY_LAST_PROJECT); + store.save()?; + + Ok(()) +} + +pub fn get_current_project( + state: &SessionState, + store: &dyn StoreOps, +) -> Result, String> { + { + let root = state.project_root.lock().map_err(|e| e.to_string())?; + if let Some(path) = &*root { + return Ok(Some(path.to_string_lossy().to_string())); + } + } + + if let Some(path_str) = store + .get(KEY_LAST_PROJECT) + .as_ref() + .and_then(|val| val.as_str()) + { + let p = PathBuf::from(path_str); + if p.exists() && p.is_dir() { + let mut root = state.project_root.lock().map_err(|e| e.to_string())?; + *root = Some(p); + return Ok(Some(path_str.to_string())); + } + } + + Ok(None) +} + +pub fn get_model_preference(store: &dyn StoreOps) -> Result, String> { + if let Some(model) = store + .get(KEY_SELECTED_MODEL) + .as_ref() + .and_then(|val| val.as_str()) + { + return Ok(Some(model.to_string())); + } + Ok(None) +} + +pub fn set_model_preference(model: String, store: &dyn StoreOps) -> Result<(), String> { + store.set(KEY_SELECTED_MODEL, json!(model)); + store.save()?; + Ok(()) +} + +async fn read_file_impl(full_path: PathBuf) -> Result { + tokio::task::spawn_blocking(move || { + fs::read_to_string(&full_path).map_err(|e| format!("Failed to read file: {}", e)) + }) + .await + .map_err(|e| format!("Task failed: {}", e))? +} + +pub async fn read_file(path: String, state: &SessionState) -> Result { + let full_path = resolve_path(state, &path)?; + read_file_impl(full_path).await +} + +async fn write_file_impl(full_path: PathBuf, content: String) -> Result<(), String> { + tokio::task::spawn_blocking(move || { + if let Some(parent) = full_path.parent() { + fs::create_dir_all(parent) + .map_err(|e| format!("Failed to create directories: {}", e))?; + } + + fs::write(&full_path, content).map_err(|e| format!("Failed to write file: {}", e)) + }) + .await + .map_err(|e| format!("Task failed: {}", e))? +} + +pub async fn write_file(path: String, content: String, state: &SessionState) -> Result<(), String> { + let full_path = resolve_path(state, &path)?; + write_file_impl(full_path, content).await +} + +#[derive(Serialize, Debug, poem_openapi::Object)] +pub struct FileEntry { + pub name: String, + pub kind: String, +} + +async fn list_directory_impl(full_path: PathBuf) -> Result, String> { + tokio::task::spawn_blocking(move || { + let entries = fs::read_dir(&full_path).map_err(|e| format!("Failed to read dir: {}", e))?; + + let mut result = Vec::new(); + for entry in entries { + let entry = entry.map_err(|e| e.to_string())?; + let ft = entry.file_type().map_err(|e| e.to_string())?; + let name = entry.file_name().to_string_lossy().to_string(); + + result.push(FileEntry { + name, + kind: if ft.is_dir() { + "dir".to_string() + } else { + "file".to_string() + }, + }); + } + + result.sort_by(|a, b| match (a.kind.as_str(), b.kind.as_str()) { + ("dir", "file") => std::cmp::Ordering::Less, + ("file", "dir") => std::cmp::Ordering::Greater, + _ => a.name.cmp(&b.name), + }); + + Ok(result) + }) + .await + .map_err(|e| format!("Task failed: {}", e))? +} + +pub async fn list_directory(path: String, state: &SessionState) -> Result, String> { + let full_path = resolve_path(state, &path)?; + list_directory_impl(full_path).await +} diff --git a/src-tauri/src/commands/mod.rs b/server/src/commands/mod.rs similarity index 100% rename from src-tauri/src/commands/mod.rs rename to server/src/commands/mod.rs diff --git a/server/src/commands/search.rs b/server/src/commands/search.rs new file mode 100644 index 0000000..5c70b67 --- /dev/null +++ b/server/src/commands/search.rs @@ -0,0 +1,65 @@ +use crate::state::SessionState; +use ignore::WalkBuilder; +use serde::Serialize; +use std::fs; +use std::path::PathBuf; + +#[derive(Serialize, Debug, poem_openapi::Object)] +pub struct SearchResult { + pub path: String, + pub matches: usize, +} + +fn get_project_root(state: &SessionState) -> Result { + state.get_project_root() +} + +pub async fn search_files( + query: String, + state: &SessionState, +) -> Result, String> { + let root = get_project_root(state)?; + search_files_impl(query, root).await +} + +pub async fn search_files_impl(query: String, root: PathBuf) -> Result, String> { + let root_clone = root.clone(); + + let results = tokio::task::spawn_blocking(move || { + let mut matches = Vec::new(); + let walker = WalkBuilder::new(&root_clone).git_ignore(true).build(); + + for result in walker { + match result { + Ok(entry) => { + if !entry.file_type().map(|ft| ft.is_file()).unwrap_or(false) { + continue; + } + + let path = entry.path(); + if let Ok(content) = fs::read_to_string(path) + && content.contains(&query) + { + let relative = path + .strip_prefix(&root_clone) + .unwrap_or(path) + .to_string_lossy() + .to_string(); + + matches.push(SearchResult { + path: relative, + matches: 1, + }); + } + } + Err(err) => eprintln!("Error walking dir: {}", err), + } + } + + matches + }) + .await + .map_err(|e| format!("Search task failed: {e}"))?; + + Ok(results) +} diff --git a/server/src/commands/shell.rs b/server/src/commands/shell.rs new file mode 100644 index 0000000..1ec4d68 --- /dev/null +++ b/server/src/commands/shell.rs @@ -0,0 +1,58 @@ +use crate::state::SessionState; +use serde::Serialize; +use std::path::PathBuf; +use std::process::Command; + +/// Helper to get the root path (cloned) without joining +fn get_project_root(state: &SessionState) -> Result { + state.get_project_root() +} + +#[derive(Serialize, Debug, poem_openapi::Object)] +pub struct CommandOutput { + pub stdout: String, + pub stderr: String, + pub exit_code: i32, +} + +/// Execute shell command logic (pure function for testing) +async fn exec_shell_impl( + command: String, + args: Vec, + root: PathBuf, +) -> Result { + // Security Allowlist + let allowed_commands = [ + "git", "cargo", "npm", "yarn", "pnpm", "node", "bun", "ls", "find", "grep", "mkdir", "rm", + "mv", "cp", "touch", "rustc", "rustfmt", + ]; + + if !allowed_commands.contains(&command.as_str()) { + return Err(format!("Command '{}' is not in the allowlist.", command)); + } + + let output = tokio::task::spawn_blocking(move || { + Command::new(&command) + .args(&args) + .current_dir(root) + .output() + }) + .await + .map_err(|e| format!("Task join error: {}", e))? + .map_err(|e| format!("Failed to execute command: {}", e))?; + + Ok(CommandOutput { + stdout: String::from_utf8_lossy(&output.stdout).to_string(), + stderr: String::from_utf8_lossy(&output.stderr).to_string(), + exit_code: output.status.code().unwrap_or(-1), + }) +} + +pub async fn exec_shell( + command: String, + args: Vec, + state: &SessionState, +) -> Result { + let root = get_project_root(state)?; + exec_shell_impl(command, args, root).await +} diff --git a/src-tauri/src/llm/mod.rs b/server/src/llm/mod.rs similarity index 100% rename from src-tauri/src/llm/mod.rs rename to server/src/llm/mod.rs diff --git a/src-tauri/src/llm/prompts.rs b/server/src/llm/prompts.rs similarity index 52% rename from src-tauri/src/llm/prompts.rs rename to server/src/llm/prompts.rs index 14d4951..65870d4 100644 --- a/src-tauri/src/llm/prompts.rs +++ b/server/src/llm/prompts.rs @@ -89,148 +89,3 @@ REMEMBER: Remember: You are an autonomous agent that can both explain concepts and take action. Choose appropriately based on the user's request. "#; - -// ----------------------------------------------------------------------------- -// Tests -// ----------------------------------------------------------------------------- - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_system_prompt_not_empty() { - assert!(SYSTEM_PROMPT.len() > 100); - } - - #[test] - fn test_system_prompt_contains_key_instructions() { - // Check for critical instruction sections - assert!(SYSTEM_PROMPT.contains("CRITICAL INSTRUCTIONS")); - assert!(SYSTEM_PROMPT.contains("YOUR CAPABILITIES")); - assert!(SYSTEM_PROMPT.contains("YOUR WORKFLOW")); - assert!(SYSTEM_PROMPT.contains("CRITICAL RULES")); - } - - #[test] - fn test_system_prompt_mentions_all_tools() { - // Verify all tools are mentioned - assert!(SYSTEM_PROMPT.contains("read_file")); - assert!(SYSTEM_PROMPT.contains("write_file")); - assert!(SYSTEM_PROMPT.contains("list_directory")); - assert!(SYSTEM_PROMPT.contains("search_files")); - assert!(SYSTEM_PROMPT.contains("exec_shell")); - } - - #[test] - fn test_system_prompt_has_example_section() { - assert!(SYSTEM_PROMPT.contains("EXAMPLES OF CORRECT BEHAVIOR")); - assert!(SYSTEM_PROMPT.contains("EXAMPLES OF INCORRECT BEHAVIOR")); - } - - #[test] - fn test_system_prompt_emphasizes_distinction() { - // Check that the prompt emphasizes the difference between examples and implementation - assert!(SYSTEM_PROMPT.contains("Distinguish Between Examples and Implementation")); - assert!(SYSTEM_PROMPT.contains("Teaching vs Implementing")); - } - - #[test] - fn test_system_prompt_has_security_warning() { - // Check for security-related instructions - assert!(SYSTEM_PROMPT.contains("Read Before Write")); - assert!(SYSTEM_PROMPT.contains("Complete Files Only")); - } - - #[test] - fn test_system_prompt_mentions_write_file_overwrites() { - // Important warning about write_file behavior - assert!(SYSTEM_PROMPT.contains("OVERWRITES")); - assert!(SYSTEM_PROMPT.contains("COMPLETE file content")); - } - - #[test] - fn test_system_prompt_has_correct_examples() { - // Check for example scenarios - assert!(SYSTEM_PROMPT.contains("Example 1")); - assert!(SYSTEM_PROMPT.contains("Example 2")); - assert!(SYSTEM_PROMPT.contains("Example 3")); - assert!(SYSTEM_PROMPT.contains("Example 4")); - } - - #[test] - fn test_system_prompt_discourages_placeholders() { - // Check that it warns against using placeholders - assert!(SYSTEM_PROMPT.contains("placeholders")); - assert!(SYSTEM_PROMPT.contains("rest of code")); - } - - #[test] - fn test_system_prompt_encourages_autonomy() { - // Check that it encourages the agent to take initiative - assert!(SYSTEM_PROMPT.contains("Take Initiative")); - assert!(SYSTEM_PROMPT.contains("autonomous")); - } - - #[test] - fn test_system_prompt_mentions_living_spec() { - // Check for reference to .living_spec - assert!(SYSTEM_PROMPT.contains(".living_spec/README.md")); - } - - #[test] - fn test_system_prompt_has_workflow_steps() { - // Check for workflow guidance - assert!(SYSTEM_PROMPT.contains("Understand")); - assert!(SYSTEM_PROMPT.contains("Explore")); - assert!(SYSTEM_PROMPT.contains("Implement")); - assert!(SYSTEM_PROMPT.contains("Verify")); - assert!(SYSTEM_PROMPT.contains("Report")); - } - - #[test] - fn test_system_prompt_uses_past_tense_for_reporting() { - // Check that it instructs to report in past tense - assert!(SYSTEM_PROMPT.contains("past tense")); - } - - #[test] - fn test_system_prompt_format_is_valid() { - // Basic format checks - assert!(SYSTEM_PROMPT.starts_with("You are an AI Agent")); - assert!(SYSTEM_PROMPT.contains("Remember:")); - } - - #[test] - fn test_system_prompt_has_keyword_guidance() { - // Check for keyword-based decision guidance - assert!(SYSTEM_PROMPT.contains("show")); - assert!(SYSTEM_PROMPT.contains("create")); - assert!(SYSTEM_PROMPT.contains("add")); - assert!(SYSTEM_PROMPT.contains("implement")); - assert!(SYSTEM_PROMPT.contains("fix")); - } - - #[test] - fn test_system_prompt_length_reasonable() { - // Ensure prompt is substantial but not excessively long - let line_count = SYSTEM_PROMPT.lines().count(); - assert!(line_count > 50); - assert!(line_count < 300); - } - - #[test] - fn test_system_prompt_has_shell_command_examples() { - // Check for shell command mentions - assert!(SYSTEM_PROMPT.contains("cargo")); - assert!(SYSTEM_PROMPT.contains("npm")); - assert!(SYSTEM_PROMPT.contains("git")); - } - - #[test] - fn test_system_prompt_warns_against_announcements() { - // Check that it discourages announcing actions - assert!(SYSTEM_PROMPT.contains("Don't announce")); - assert!(SYSTEM_PROMPT.contains("Be Direct")); - } -} diff --git a/src-tauri/src/llm/providers/anthropic.rs b/server/src/llm/providers/anthropic.rs similarity index 85% rename from src-tauri/src/llm/providers/anthropic.rs rename to server/src/llm/providers/anthropic.rs index 54916a2..e34b533 100644 --- a/src-tauri/src/llm/providers/anthropic.rs +++ b/server/src/llm/providers/anthropic.rs @@ -5,7 +5,6 @@ use futures::StreamExt; use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue}; use serde::{Deserialize, Serialize}; use serde_json::json; -use tauri::{AppHandle, Emitter}; use tokio::sync::watch::Receiver; const ANTHROPIC_API_URL: &str = "https://api.anthropic.com/v1/messages"; @@ -70,7 +69,6 @@ impl AnthropicProvider { } } - /// Convert our internal tool definitions to Anthropic format fn convert_tools(tools: &[ToolDefinition]) -> Vec { tools .iter() @@ -82,16 +80,12 @@ impl AnthropicProvider { .collect() } - /// Convert our internal messages to Anthropic format fn convert_messages(messages: &[Message]) -> Vec { let mut anthropic_messages: Vec = Vec::new(); for msg in messages { match msg.role { Role::System => { - // Anthropic doesn't support system messages in the messages array - // They should be passed separately in the 'system' parameter - // For now, we'll skip them or convert to user messages continue; } Role::User => { @@ -102,17 +96,14 @@ impl AnthropicProvider { } Role::Assistant => { if let Some(tool_calls) = &msg.tool_calls { - // Assistant message with tool calls let mut blocks = Vec::new(); - // Add text content if present if !msg.content.is_empty() { blocks.push(AnthropicContentBlock::Text { text: msg.content.clone(), }); } - // Add tool use blocks for call in tool_calls { let input: serde_json::Value = serde_json::from_str(&call.function.arguments).unwrap_or(json!({})); @@ -132,7 +123,6 @@ impl AnthropicProvider { content: AnthropicContent::Blocks(blocks), }); } else { - // Regular assistant message anthropic_messages.push(AnthropicMessage { role: "assistant".to_string(), content: AnthropicContent::Text(msg.content.clone()), @@ -140,7 +130,6 @@ impl AnthropicProvider { } } Role::Tool => { - // Tool result - needs to be sent as a user message with tool_result content let tool_use_id = msg.tool_call_id.clone().unwrap_or_default(); anthropic_messages.push(AnthropicMessage { role: "user".to_string(), @@ -158,7 +147,6 @@ impl AnthropicProvider { anthropic_messages } - /// Extract system prompt from messages fn extract_system_prompt(messages: &[Message]) -> String { messages .iter() @@ -168,20 +156,21 @@ impl AnthropicProvider { .join("\n\n") } - pub async fn chat_stream( + pub async fn chat_stream( &self, - app: &AppHandle, model: &str, messages: &[Message], tools: &[ToolDefinition], cancel_rx: &mut Receiver, - ) -> Result { - // Convert messages and tools + mut on_token: F, + ) -> Result + where + F: FnMut(&str), + { let anthropic_messages = Self::convert_messages(messages); let anthropic_tools = Self::convert_tools(tools); let system_prompt = Self::extract_system_prompt(messages); - // Build request let mut request_body = json!({ "model": model, "max_tokens": 4096, @@ -197,7 +186,6 @@ impl AnthropicProvider { request_body["tools"] = json!(anthropic_tools); } - // Build headers let mut headers = HeaderMap::new(); headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); headers.insert( @@ -209,7 +197,6 @@ impl AnthropicProvider { HeaderValue::from_static(ANTHROPIC_VERSION), ); - // Make streaming request let response = self .client .post(ANTHROPIC_API_URL) @@ -217,7 +204,7 @@ impl AnthropicProvider { .json(&request_body) .send() .await - .map_err(|e| format!("Failed to send request to Anthropic: {}", e))?; + .map_err(|e| format!("Failed to send request to Anthropic: {e}"))?; if !response.status().is_success() { let status = response.status(); @@ -225,14 +212,13 @@ impl AnthropicProvider { .text() .await .unwrap_or_else(|_| "Unknown error".to_string()); - return Err(format!("Anthropic API error {}: {}", status, error_text)); + return Err(format!("Anthropic API error {status}: {error_text}")); } - // Process streaming response let mut stream = response.bytes_stream(); let mut accumulated_text = String::new(); let mut tool_calls: Vec = Vec::new(); - let mut current_tool_use: Option<(String, String, String)> = None; // (id, name, input_json) + let mut current_tool_use: Option<(String, String, String)> = None; loop { let chunk = tokio::select! { @@ -250,14 +236,11 @@ impl AnthropicProvider { } }; - let bytes = chunk.map_err(|e| format!("Stream error: {}", e))?; + let bytes = chunk.map_err(|e| format!("Stream error: {e}"))?; let text = String::from_utf8_lossy(&bytes); - // Parse SSE events for line in text.lines() { if let Some(json_str) = line.strip_prefix("data: ") { - // Remove "data: " prefix - if json_str == "[DONE]" { break; } @@ -269,7 +252,6 @@ impl AnthropicProvider { match event.event_type.as_str() { "content_block_start" => { - // Check if this is a tool use block if let Some(content_block) = event.data.get("content_block") && content_block.get("type") == Some(&json!("tool_use")) { @@ -280,16 +262,12 @@ impl AnthropicProvider { } "content_block_delta" => { if let Some(delta) = event.data.get("delta") { - // Text delta if delta.get("type") == Some(&json!("text_delta")) { if let Some(text) = delta.get("text").and_then(|t| t.as_str()) { accumulated_text.push_str(text); - // Emit token to frontend - let _ = app.emit("chat:token", text); + on_token(text); } - } - // Tool input delta - else if delta.get("type") == Some(&json!("input_json_delta")) + } else if delta.get("type") == Some(&json!("input_json_delta")) && let Some((_, _, input_json)) = &mut current_tool_use && let Some(partial) = delta.get("partial_json").and_then(|p| p.as_str()) @@ -299,7 +277,6 @@ impl AnthropicProvider { } } "content_block_stop" => { - // Finalize tool use if we have one if let Some((id, name, input_json)) = current_tool_use.take() { tool_calls.push(ToolCall { id: Some(id), diff --git a/src-tauri/src/llm/providers/mod.rs b/server/src/llm/providers/mod.rs similarity index 100% rename from src-tauri/src/llm/providers/mod.rs rename to server/src/llm/providers/mod.rs diff --git a/src-tauri/src/llm/providers/ollama.rs b/server/src/llm/providers/ollama.rs similarity index 60% rename from src-tauri/src/llm/providers/ollama.rs rename to server/src/llm/providers/ollama.rs index 5249145..e3fdce4 100644 --- a/src-tauri/src/llm/providers/ollama.rs +++ b/server/src/llm/providers/ollama.rs @@ -5,7 +5,6 @@ use async_trait::async_trait; use futures::StreamExt; use serde::{Deserialize, Serialize}; use serde_json::Value; -use tauri::{AppHandle, Emitter}; pub struct OllamaProvider { base_url: String, @@ -40,19 +39,21 @@ impl OllamaProvider { Ok(body.models.into_iter().map(|m| m.name).collect()) } - /// Streaming chat that emits tokens via Tauri events - pub async fn chat_stream( + /// Streaming chat that calls `on_token` for each token chunk. + pub async fn chat_stream( &self, - app: &AppHandle, model: &str, messages: &[Message], tools: &[ToolDefinition], cancel_rx: &mut tokio::sync::watch::Receiver, - ) -> Result { + mut on_token: F, + ) -> Result + where + F: FnMut(&str) + Send, + { let client = reqwest::Client::new(); let url = format!("{}/api/chat", self.base_url.trim_end_matches('/')); - // Convert domain Messages to Ollama Messages let ollama_messages: Vec = messages .iter() .map(|m| { @@ -86,7 +87,7 @@ impl OllamaProvider { let request_body = OllamaRequest { model, messages: ollama_messages, - stream: true, // Enable streaming + stream: true, tools, }; @@ -103,14 +104,12 @@ impl OllamaProvider { return Err(format!("Ollama API error {}: {}", status, text)); } - // Process streaming response let mut stream = res.bytes_stream(); let mut buffer = String::new(); let mut accumulated_content = String::new(); let mut final_tool_calls: Option> = None; loop { - // Check for cancellation if *cancel_rx.borrow() { return Err("Chat cancelled by user".to_string()); } @@ -123,7 +122,6 @@ impl OllamaProvider { } } _ = cancel_rx.changed() => { - // changed() fires on any change, check if it's actually true if *cancel_rx.borrow() { return Err("Chat cancelled by user".to_string()); } else { @@ -135,7 +133,6 @@ impl OllamaProvider { let chunk = chunk_result.map_err(|e| format!("Stream error: {}", e))?; buffer.push_str(&String::from_utf8_lossy(&chunk)); - // Process complete lines (newline-delimited JSON) while let Some(newline_pos) = buffer.find('\n') { let line = buffer[..newline_pos].trim().to_string(); buffer = buffer[newline_pos + 1..].to_string(); @@ -144,20 +141,14 @@ impl OllamaProvider { continue; } - // Parse the streaming response let stream_msg: OllamaStreamResponse = serde_json::from_str(&line).map_err(|e| format!("JSON parse error: {}", e))?; - // Emit token if there's content if !stream_msg.message.content.is_empty() { accumulated_content.push_str(&stream_msg.message.content); - - // Emit chat:token event - app.emit("chat:token", &stream_msg.message.content) - .map_err(|e| e.to_string())?; + on_token(&stream_msg.message.content); } - // Check for tool calls if let Some(tool_calls) = stream_msg.message.tool_calls { final_tool_calls = Some( tool_calls @@ -174,7 +165,6 @@ impl OllamaProvider { ); } - // If done, break if stream_msg.done { break; } @@ -202,8 +192,6 @@ struct OllamaModelTag { name: String, } -// --- Request Types --- - #[derive(Serialize)] struct OllamaRequest<'a> { model: &'a str, @@ -240,34 +228,6 @@ struct OllamaRequestFunctionCall { arguments: Value, } -// --- Response Types --- - -#[derive(Deserialize)] -#[allow(dead_code)] -struct OllamaResponse { - message: OllamaResponseMessage, -} - -#[derive(Deserialize)] -#[allow(dead_code)] -struct OllamaResponseMessage { - content: String, - tool_calls: Option>, -} - -#[derive(Deserialize)] -struct OllamaResponseToolCall { - function: OllamaResponseFunctionCall, -} - -#[derive(Deserialize)] -struct OllamaResponseFunctionCall { - name: String, - arguments: Value, // Ollama returns Object, we convert to String for internal storage -} - -// --- Streaming Response Types --- - #[derive(Deserialize)] struct OllamaStreamResponse { message: OllamaStreamMessage, @@ -282,107 +242,25 @@ struct OllamaStreamMessage { tool_calls: Option>, } +#[derive(Deserialize)] +struct OllamaResponseToolCall { + function: OllamaResponseFunctionCall, +} + +#[derive(Deserialize)] +struct OllamaResponseFunctionCall { + name: String, + arguments: Value, +} + #[async_trait] impl ModelProvider for OllamaProvider { async fn chat( &self, - model: &str, - messages: &[Message], - tools: &[ToolDefinition], + _model: &str, + _messages: &[Message], + _tools: &[ToolDefinition], ) -> Result { - let client = reqwest::Client::new(); - let url = format!("{}/api/chat", self.base_url.trim_end_matches('/')); - - // Convert domain Messages to Ollama Messages (handling String -> Object args mismatch) - let ollama_messages: Vec = messages - .iter() - .map(|m| { - let tool_calls = m.tool_calls.as_ref().map(|calls| { - calls - .iter() - .map(|tc| { - // Try to parse string args as JSON, fallback to string value if fails - let args_val: Value = serde_json::from_str(&tc.function.arguments) - .unwrap_or(Value::String(tc.function.arguments.clone())); - - OllamaRequestToolCall { - kind: tc.kind.clone(), - function: OllamaRequestFunctionCall { - name: tc.function.name.clone(), - arguments: args_val, - }, - } - }) - .collect() - }); - - OllamaRequestMessage { - role: m.role.clone(), - content: m.content.clone(), - tool_calls, - tool_call_id: m.tool_call_id.clone(), - } - }) - .collect(); - - let request_body = OllamaRequest { - model, - messages: ollama_messages, - stream: false, - tools, - }; - - // Debug: Log the request body - if let Ok(json_str) = serde_json::to_string_pretty(&request_body) { - eprintln!("=== Ollama Request ===\n{}\n===================", json_str); - } - - let res = client - .post(&url) - .json(&request_body) - .send() - .await - .map_err(|e| format!("Request failed: {}", e))?; - - if !res.status().is_success() { - let status = res.status(); - let text = res.text().await.unwrap_or_default(); - eprintln!( - "=== Ollama Error Response ===\n{}\n========================", - text - ); - return Err(format!("Ollama API error {}: {}", status, text)); - } - - let response_body: OllamaResponse = res - .json() - .await - .map_err(|e| format!("Failed to parse response: {}", e))?; - - // Convert Response back to Domain types - let content = if response_body.message.content.is_empty() { - None - } else { - Some(response_body.message.content) - }; - - let tool_calls = response_body.message.tool_calls.map(|calls| { - calls - .into_iter() - .map(|tc| ToolCall { - id: None, // Ollama doesn't typically send IDs - kind: "function".to_string(), - function: FunctionCall { - name: tc.function.name, - arguments: tc.function.arguments.to_string(), // Convert Object -> String - }, - }) - .collect() - }); - - Ok(CompletionResponse { - content, - tool_calls, - }) + Err("Non-streaming Ollama chat not implemented for server".to_string()) } } diff --git a/server/src/llm/types.rs b/server/src/llm/types.rs new file mode 100644 index 0000000..ee391e9 --- /dev/null +++ b/server/src/llm/types.rs @@ -0,0 +1,69 @@ +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum Role { + System, + User, + Assistant, + Tool, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Message { + pub role: Role, + pub content: String, + + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_calls: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_call_id: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ToolCall { + pub id: Option, + pub function: FunctionCall, + #[serde(rename = "type")] + pub kind: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct FunctionCall { + pub name: String, + pub arguments: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ToolDefinition { + #[serde(rename = "type")] + pub kind: String, + pub function: ToolFunctionDefinition, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ToolFunctionDefinition { + pub name: String, + pub description: String, + pub parameters: serde_json::Value, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CompletionResponse { + pub content: Option, + pub tool_calls: Option>, +} + +#[async_trait] +#[allow(dead_code)] +pub trait ModelProvider: Send + Sync { + async fn chat( + &self, + model: &str, + messages: &[Message], + tools: &[ToolDefinition], + ) -> Result; +} diff --git a/server/src/main.rs b/server/src/main.rs new file mode 100644 index 0000000..580aca8 --- /dev/null +++ b/server/src/main.rs @@ -0,0 +1,396 @@ +mod commands; +mod llm; +mod state; +mod store; + +use crate::commands::{chat, fs}; +use crate::llm::types::Message; +use crate::state::SessionState; +use crate::store::JsonFileStore; +use futures::{SinkExt, StreamExt}; +use poem::web::websocket::{Message as WsMessage, WebSocket}; +use poem::{ + EndpointExt, Response, Route, Server, get, handler, + http::{StatusCode, header}, + listener::TcpListener, + web::{Data, Path}, +}; +use poem_openapi::{Object, OpenApi, OpenApiService, param::Query, payload::Json}; +use rust_embed::RustEmbed; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use std::sync::Arc; +use tokio::sync::mpsc; + +#[derive(Clone)] +struct AppContext { + state: Arc, + store: Arc, +} + +#[derive(RustEmbed)] +#[folder = "../frontend/dist"] +struct EmbeddedAssets; + +type OpenApiResult = poem::Result; + +fn bad_request(message: String) -> poem::Error { + poem::Error::from_string(message, StatusCode::BAD_REQUEST) +} + +#[handler] +fn health() -> &'static str { + "ok" +} + +fn serve_embedded(path: &str) -> Response { + let normalized = if path.is_empty() { + "index.html" + } else { + path.trim_start_matches('/') + }; + let is_asset_request = normalized.starts_with("assets/"); + let asset = if is_asset_request { + EmbeddedAssets::get(normalized) + } else { + EmbeddedAssets::get(normalized).or_else(|| { + if normalized == "index.html" { + None + } else { + EmbeddedAssets::get("index.html") + } + }) + }; + + match asset { + Some(content) => { + let body = content.data.into_owned(); + let mime = mime_guess::from_path(normalized) + .first_or_octet_stream() + .to_string(); + + Response::builder() + .status(StatusCode::OK) + .header(header::CONTENT_TYPE, mime) + .body(body) + } + None => Response::builder() + .status(StatusCode::NOT_FOUND) + .body("Not Found"), + } +} + +#[handler] +fn embedded_asset(Path(path): Path) -> Response { + let asset_path = format!("assets/{path}"); + serve_embedded(&asset_path) +} + +#[handler] +fn embedded_file(Path(path): Path) -> Response { + serve_embedded(&path) +} + +#[handler] +fn embedded_index() -> Response { + serve_embedded("index.html") +} + +#[derive(Deserialize, Object)] +struct PathPayload { + path: String, +} + +#[derive(Deserialize, Object)] +struct ModelPayload { + model: String, +} + +#[derive(Deserialize, Object)] +struct ApiKeyPayload { + api_key: String, +} + +#[derive(Deserialize, Object)] +struct FilePathPayload { + path: String, +} + +#[derive(Deserialize, Object)] +struct WriteFilePayload { + path: String, + content: String, +} + +#[derive(Deserialize, Object)] +struct SearchPayload { + query: String, +} + +#[derive(Deserialize, Object)] +struct ExecShellPayload { + command: String, + args: Vec, +} +struct Api { + ctx: Arc, +} + +#[OpenApi] +impl Api { + #[oai(path = "/project", method = "get")] + async fn get_current_project(&self) -> OpenApiResult>> { + let ctx = self.ctx.clone(); + let result = + fs::get_current_project(&ctx.state, ctx.store.as_ref()).map_err(bad_request)?; + Ok(Json(result)) + } + + #[oai(path = "/project", method = "post")] + async fn open_project(&self, payload: Json) -> OpenApiResult> { + let ctx = self.ctx.clone(); + let confirmed = fs::open_project(payload.0.path, &ctx.state, ctx.store.as_ref()) + .await + .map_err(bad_request)?; + Ok(Json(confirmed)) + } + + #[oai(path = "/project", method = "delete")] + async fn close_project(&self) -> OpenApiResult> { + let ctx = self.ctx.clone(); + fs::close_project(&ctx.state, ctx.store.as_ref()).map_err(bad_request)?; + Ok(Json(true)) + } + + #[oai(path = "/model", method = "get")] + async fn get_model_preference(&self) -> OpenApiResult>> { + let ctx = self.ctx.clone(); + let result = fs::get_model_preference(ctx.store.as_ref()).map_err(bad_request)?; + Ok(Json(result)) + } + + #[oai(path = "/model", method = "post")] + async fn set_model_preference(&self, payload: Json) -> OpenApiResult> { + let ctx = self.ctx.clone(); + fs::set_model_preference(payload.0.model, ctx.store.as_ref()).map_err(bad_request)?; + Ok(Json(true)) + } + + #[oai(path = "/ollama/models", method = "get")] + async fn get_ollama_models( + &self, + base_url: Query>, + ) -> OpenApiResult>> { + let models = chat::get_ollama_models(base_url.0) + .await + .map_err(bad_request)?; + Ok(Json(models)) + } + + #[oai(path = "/anthropic/key/exists", method = "get")] + async fn get_anthropic_api_key_exists(&self) -> OpenApiResult> { + let ctx = self.ctx.clone(); + let exists = chat::get_anthropic_api_key_exists(ctx.store.as_ref()).map_err(bad_request)?; + Ok(Json(exists)) + } + + #[oai(path = "/anthropic/key", method = "post")] + async fn set_anthropic_api_key( + &self, + payload: Json, + ) -> OpenApiResult> { + let ctx = self.ctx.clone(); + chat::set_anthropic_api_key(ctx.store.as_ref(), payload.0.api_key).map_err(bad_request)?; + Ok(Json(true)) + } + + #[oai(path = "/fs/read", method = "post")] + async fn read_file(&self, payload: Json) -> OpenApiResult> { + let ctx = self.ctx.clone(); + let content = fs::read_file(payload.0.path, &ctx.state) + .await + .map_err(bad_request)?; + Ok(Json(content)) + } + + #[oai(path = "/fs/write", method = "post")] + async fn write_file(&self, payload: Json) -> OpenApiResult> { + let ctx = self.ctx.clone(); + fs::write_file(payload.0.path, payload.0.content, &ctx.state) + .await + .map_err(bad_request)?; + Ok(Json(true)) + } + + #[oai(path = "/fs/list", method = "post")] + async fn list_directory( + &self, + payload: Json, + ) -> OpenApiResult>> { + let ctx = self.ctx.clone(); + let entries = fs::list_directory(payload.0.path, &ctx.state) + .await + .map_err(bad_request)?; + Ok(Json(entries)) + } + + #[oai(path = "/fs/search", method = "post")] + async fn search_files( + &self, + payload: Json, + ) -> OpenApiResult>> { + let ctx = self.ctx.clone(); + let results = crate::commands::search::search_files(payload.0.query, &ctx.state) + .await + .map_err(bad_request)?; + Ok(Json(results)) + } + + #[oai(path = "/shell/exec", method = "post")] + async fn exec_shell( + &self, + payload: Json, + ) -> OpenApiResult> { + let ctx = self.ctx.clone(); + let output = + crate::commands::shell::exec_shell(payload.0.command, payload.0.args, &ctx.state) + .await + .map_err(bad_request)?; + Ok(Json(output)) + } + + #[oai(path = "/chat/cancel", method = "post")] + async fn cancel_chat(&self) -> OpenApiResult> { + let ctx = self.ctx.clone(); + chat::cancel_chat(&ctx.state).map_err(bad_request)?; + Ok(Json(true)) + } +} + +#[derive(Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +enum WsRequest { + Chat { + messages: Vec, + config: chat::ProviderConfig, + }, + Cancel, +} + +#[derive(Serialize)] +#[serde(tag = "type", rename_all = "snake_case")] +enum WsResponse { + Token { content: String }, + Update { messages: Vec }, + Error { message: String }, +} + +#[handler] +async fn ws_handler(ws: WebSocket, ctx: Data<&AppContext>) -> impl poem::IntoResponse { + let ctx = ctx.0.clone(); + ws.on_upgrade(move |socket| async move { + let (mut sink, mut stream) = socket.split(); + let (tx, mut rx) = mpsc::unbounded_channel::(); + + let forward = tokio::spawn(async move { + while let Some(msg) = rx.recv().await { + if let Ok(text) = serde_json::to_string(&msg) + && sink.send(WsMessage::Text(text)).await.is_err() + { + break; + } + } + }); + + while let Some(Ok(msg)) = stream.next().await { + if let WsMessage::Text(text) = msg { + let parsed: Result = serde_json::from_str(&text); + match parsed { + Ok(WsRequest::Chat { messages, config }) => { + let tx_updates = tx.clone(); + let tx_tokens = tx.clone(); + let ctx_clone = ctx.clone(); + + let result = chat::chat( + messages, + config, + &ctx_clone.state, + ctx_clone.store.as_ref(), + |history| { + let _ = tx_updates.send(WsResponse::Update { + messages: history.to_vec(), + }); + }, + |token| { + let _ = tx_tokens.send(WsResponse::Token { + content: token.to_string(), + }); + }, + ) + .await; + + if let Err(err) = result { + let _ = tx.send(WsResponse::Error { message: err }); + } + } + Ok(WsRequest::Cancel) => { + let _ = chat::cancel_chat(&ctx.state); + } + Err(err) => { + let _ = tx.send(WsResponse::Error { + message: format!("Invalid request: {err}"), + }); + } + } + } + } + + drop(tx); + let _ = forward.await; + }) +} + +#[tokio::main] +async fn main() -> Result<(), std::io::Error> { + let app_state = Arc::new(SessionState::default()); + let store = Arc::new( + JsonFileStore::from_path(PathBuf::from("store.json")).map_err(std::io::Error::other)?, + ); + + let ctx = AppContext { + state: app_state, + store, + }; + let ctx_arc = Arc::new(ctx.clone()); + + let api_service = OpenApiService::new( + Api { + ctx: ctx_arc.clone(), + }, + "Living Spec API", + "1.0", + ) + .server("http://127.0.0.1:3001/api"); + let docs_service = OpenApiService::new( + Api { + ctx: ctx_arc.clone(), + }, + "Living Spec API", + "1.0", + ) + .server("http://127.0.0.1:3001/api"); + + let app = Route::new() + .nest("/api", api_service) + .nest("/docs", docs_service.swagger_ui()) + .at("/ws", get(ws_handler)) + .at("/health", get(health)) + .at("/assets/*path", get(embedded_asset)) + .at("/", get(embedded_index)) + .at("/*path", get(embedded_file)) + .data(ctx); + + Server::new(TcpListener::bind("127.0.0.1:3001")) + .run(app) + .await +} diff --git a/server/src/state.rs b/server/src/state.rs new file mode 100644 index 0000000..db9baa7 --- /dev/null +++ b/server/src/state.rs @@ -0,0 +1,30 @@ +use std::path::PathBuf; +use std::sync::Mutex; +use tokio::sync::watch; + +pub struct SessionState { + pub project_root: Mutex>, + pub cancel_tx: watch::Sender, + pub cancel_rx: watch::Receiver, +} + +impl Default for SessionState { + fn default() -> Self { + let (cancel_tx, cancel_rx) = watch::channel(false); + Self { + project_root: Mutex::new(None), + cancel_tx, + cancel_rx, + } + } +} + +impl SessionState { + pub fn get_project_root(&self) -> Result { + let root_guard = self.project_root.lock().map_err(|e| e.to_string())?; + let root = root_guard + .as_ref() + .ok_or_else(|| "No project is currently open.".to_string())?; + Ok(root.clone()) + } +} diff --git a/server/src/store.rs b/server/src/store.rs new file mode 100644 index 0000000..1bdd8d3 --- /dev/null +++ b/server/src/store.rs @@ -0,0 +1,82 @@ +use serde_json::Value; +use std::collections::HashMap; +use std::fs; +use std::path::{Path, PathBuf}; +use std::sync::Mutex; + +pub trait StoreOps: Send + Sync { + fn get(&self, key: &str) -> Option; + fn set(&self, key: &str, value: Value); + fn delete(&self, key: &str); + fn save(&self) -> Result<(), String>; +} + +pub struct JsonFileStore { + path: PathBuf, + data: Mutex>, +} + +impl JsonFileStore { + pub fn new(path: PathBuf) -> Result { + let data = if path.exists() { + let content = + fs::read_to_string(&path).map_err(|e| format!("Failed to read store: {e}"))?; + if content.trim().is_empty() { + HashMap::new() + } else { + serde_json::from_str::>(&content) + .map_err(|e| format!("Failed to parse store: {e}"))? + } + } else { + HashMap::new() + }; + + Ok(Self { + path, + data: Mutex::new(data), + }) + } + + pub fn from_path>(path: P) -> Result { + Self::new(path.as_ref().to_path_buf()) + } + + #[allow(dead_code)] + pub fn path(&self) -> &Path { + &self.path + } + + fn ensure_parent_dir(&self) -> Result<(), String> { + if let Some(parent) = self.path.parent() { + fs::create_dir_all(parent) + .map_err(|e| format!("Failed to create store directory: {e}"))?; + } + Ok(()) + } +} + +impl StoreOps for JsonFileStore { + fn get(&self, key: &str) -> Option { + self.data.lock().ok().and_then(|map| map.get(key).cloned()) + } + + fn set(&self, key: &str, value: Value) { + if let Ok(mut map) = self.data.lock() { + map.insert(key.to_string(), value); + } + } + + fn delete(&self, key: &str) { + if let Ok(mut map) = self.data.lock() { + map.remove(key); + } + } + + fn save(&self) -> Result<(), String> { + self.ensure_parent_dir()?; + let map = self.data.lock().map_err(|e| e.to_string())?; + let content = + serde_json::to_string_pretty(&*map).map_err(|e| format!("Serialize failed: {e}"))?; + fs::write(&self.path, content).map_err(|e| format!("Failed to write store: {e}")) + } +} diff --git a/src-tauri/.config/nextest.toml b/src-tauri/.config/nextest.toml deleted file mode 100644 index 4d0581d..0000000 --- a/src-tauri/.config/nextest.toml +++ /dev/null @@ -1,31 +0,0 @@ -# Nextest configuration for living-spec-standalone -# See https://nexte.st/book/configuration.html for more details - -[profile.default] -# Show output for failing tests -failure-output = "immediate" -# Show output for passing tests as well -success-output = "never" -# Cancel test run on the first failure -fail-fast = false -# Number of retries for failing tests -retries = 0 - -[profile.ci] -# CI-specific profile -failure-output = "immediate-final" -success-output = "never" -fail-fast = false -# Retry flaky tests once in CI -retries = 1 - -[profile.coverage] -# Profile specifically for code coverage runs -failure-output = "immediate-final" -success-output = "never" -fail-fast = false -retries = 0 - -# Test groups configuration -[test-groups.integration] -max-threads = 1 diff --git a/src-tauri/.gitignore b/src-tauri/.gitignore deleted file mode 100644 index b21bd68..0000000 --- a/src-tauri/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ - -# Generated by Tauri -# will have schema files for capabilities auto-completion -/gen/schemas diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock deleted file mode 100644 index 7bc720c..0000000 --- a/src-tauri/Cargo.lock +++ /dev/null @@ -1,5859 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" - -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-lock" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-process" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" -dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "async-signal" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix", - "signal-hook-registry", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "atk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" -dependencies = [ - "atk-sys", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "aws-lc-rs" -version = "1.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", -] - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" -dependencies = [ - "serde_core", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2", -] - -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "brotli" -version = "8.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bstr" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "bumpalo" -version = "3.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" - -[[package]] -name = "bytemuck" -version = "1.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" -dependencies = [ - "serde", -] - -[[package]] -name = "cairo-rs" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" -dependencies = [ - "bitflags 2.10.0", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", - "thiserror 1.0.69", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "camino" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" -dependencies = [ - "serde_core", -] - -[[package]] -name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 2.0.17", -] - -[[package]] -name = "cargo_toml" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" -dependencies = [ - "serde", - "toml 0.9.10+spec-1.1.0", -] - -[[package]] -name = "cc" -version = "1.2.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", -] - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chrono" -version = "0.4.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-link 0.2.1", -] - -[[package]] -name = "cmake" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" -dependencies = [ - "cc", -] - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "cookie" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" -dependencies = [ - "time", - "version_check", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" -dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.10.1", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.10.1", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "cssparser" -version = "0.29.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "matches", - "phf 0.10.1", - "proc-macro2", - "quote", - "smallvec", - "syn 1.0.109", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.111", -] - -[[package]] -name = "ctor" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" -dependencies = [ - "quote", - "syn 2.0.111", -] - -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.111", -] - -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "deranged" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" -dependencies = [ - "powerfmt", - "serde_core", -] - -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.111", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.2", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "dispatch2" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" -dependencies = [ - "bitflags 2.10.0", - "block2", - "libc", - "objc2", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "dlopen2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "dlopen2_derive" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" -dependencies = [ - "serde", -] - -[[package]] -name = "dtoa" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" - -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "embed-resource" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" -dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 0.9.10+spec-1.1.0", - "vswhom", - "winreg", -] - -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "endi" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" - -[[package]] -name = "enumflags2" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "erased-serde" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" -dependencies = [ - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "eventsource-stream" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" -dependencies = [ - "futures-core", - "nom", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" - -[[package]] -name = "flate2" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "gdk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", - "once_cell", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" -dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkx11" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" -dependencies = [ - "gdk", - "gdkx11-sys", - "gio", - "glib", - "libc", - "x11", -] - -[[package]] -name = "gdkx11-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" -dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps", - "x11", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "gio" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "gio-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", -] - -[[package]] -name = "glib" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.10.0", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "glib-macros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" -dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.2", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - -[[package]] -name = "globset" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gtk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" -dependencies = [ - "atk", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "gtk3-macros" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "h2" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap 2.12.1", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "html5ever" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" -dependencies = [ - "log", - "mac", - "markup5ever", - "match_token", -] - -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "system-configuration", - "tokio", - "tower-service", - "tracing", - "windows-registry", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core 0.62.2", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ico" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" -dependencies = [ - "byteorder", - "png", -] - -[[package]] -name = "icu_collections" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" - -[[package]] -name = "icu_properties" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" - -[[package]] -name = "icu_provider" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "ignore" -version = "0.4.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" -dependencies = [ - "crossbeam-deque", - "globset", - "log", - "memchr", - "regex-automata", - "same-file", - "walkdir", - "winapi-util", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", -] - -[[package]] -name = "infer" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" -dependencies = [ - "cfb", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "is-docker" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" -dependencies = [ - "once_cell", -] - -[[package]] -name = "is-wsl" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", -] - -[[package]] -name = "itoa" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" - -[[package]] -name = "javascriptcore-rs" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" -dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "json-patch" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" -dependencies = [ - "jsonptr", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "jsonptr" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "keyboard-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" -dependencies = [ - "bitflags 2.10.0", - "serde", - "unicode-segmentation", -] - -[[package]] -name = "kuchikiki" -version = "0.8.8-speedreader" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" -dependencies = [ - "cssparser", - "html5ever", - "indexmap 2.12.1", - "selectors", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libappindicator" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" -dependencies = [ - "glib", - "gtk", - "gtk-sys", - "libappindicator-sys", - "log", -] - -[[package]] -name = "libappindicator-sys" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" -dependencies = [ - "gtk-sys", - "libloading", - "once_cell", -] - -[[package]] -name = "libc" -version = "0.2.178" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "libredox" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" -dependencies = [ - "bitflags 2.10.0", - "libc", -] - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "litemap" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" - -[[package]] -name = "living-spec-standalone" -version = "0.1.0" -dependencies = [ - "async-trait", - "chrono", - "eventsource-stream", - "futures", - "ignore", - "reqwest 0.13.1", - "serde", - "serde_json", - "tauri", - "tauri-build", - "tauri-plugin-dialog", - "tauri-plugin-opener", - "tauri-plugin-store", - "tempfile", - "tokio", - "uuid", - "walkdir", -] - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "markup5ever" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" -dependencies = [ - "log", - "phf 0.11.3", - "phf_codegen 0.11.3", - "string_cache", - "string_cache_codegen", - "tendril", -] - -[[package]] -name = "match_token" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.61.2", -] - -[[package]] -name = "muda" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" -dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "once_cell", - "png", - "serde", - "thiserror 2.0.17", - "windows-sys 0.60.2", -] - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.10.0", - "jni-sys", - "log", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset", -] - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_enum" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "objc2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" -dependencies = [ - "objc2-encode", - "objc2-exception-helper", -] - -[[package]] -name = "objc2-app-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.10.0", - "block2", - "libc", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-text", - "objc2-core-video", - "objc2-foundation", - "objc2-quartz-core", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" -dependencies = [ - "bitflags 2.10.0", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-data" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" -dependencies = [ - "bitflags 2.10.0", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.10.0", - "dispatch2", - "objc2", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags 2.10.0", - "dispatch2", - "objc2", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-text" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags 2.10.0", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", -] - -[[package]] -name = "objc2-core-video" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" -dependencies = [ - "bitflags 2.10.0", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-io-surface", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-exception-helper" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" -dependencies = [ - "cc", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.10.0", - "block2", - "libc", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags 2.10.0", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-javascript-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586" -dependencies = [ - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.10.0", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-security" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" -dependencies = [ - "bitflags 2.10.0", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" -dependencies = [ - "bitflags 2.10.0", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-web-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" -dependencies = [ - "bitflags 2.10.0", - "block2", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "objc2-javascript-core", - "objc2-security", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "open" -version = "5.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" -dependencies = [ - "dunce", - "is-wsl", - "libc", - "pathdiff", -] - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "pango" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" -dependencies = [ - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link 0.2.1", -] - -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_shared 0.8.0", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_macros 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.5", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.5", -] - -[[package]] -name = "phf_macros" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher 1.0.1", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "plist" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" -dependencies = [ - "base64 0.22.1", - "indexmap 2.12.1", - "quick-xml", - "serde", - "time", -] - -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "potential_utf" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" -dependencies = [ - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "proc-macro-crate" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" -dependencies = [ - "toml_edit 0.23.10+spec-1.0.0", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - -[[package]] -name = "proc-macro2" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quick-xml" -version = "0.38.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" -dependencies = [ - "memchr", -] - -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror 2.0.17", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" -dependencies = [ - "aws-lc-rs", - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.17", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.60.2", -] - -[[package]] -name = "quote" -version = "1.0.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.10.0", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror 2.0.17", -] - -[[package]] -name = "ref-cast" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "regex" -version = "1.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" - -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", -] - -[[package]] -name = "reqwest" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" -dependencies = [ - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "mime", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", - "serde", - "serde_json", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", -] - -[[package]] -name = "rfd" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" -dependencies = [ - "block2", - "dispatch2", - "glib-sys", - "gobject-sys", - "gtk-sys", - "js-sys", - "log", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "raw-window-handle", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-sys 0.60.2", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.16", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" -dependencies = [ - "bitflags 2.10.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" -dependencies = [ - "aws-lc-rs", - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pki-types" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-platform-verifier" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" -dependencies = [ - "core-foundation 0.10.1", - "core-foundation-sys", - "jni", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - -[[package]] -name = "rustls-webpki" -version = "0.103.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" -dependencies = [ - "aws-lc-rs", - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "schemars" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" -dependencies = [ - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive", - "serde", - "serde_json", - "url", - "uuid", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.111", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "security-framework" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" -dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.10.1", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "selectors" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" -dependencies = [ - "bitflags 1.3.2", - "cssparser", - "derive_more", - "fxhash", - "log", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc", - "smallvec", -] - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" -dependencies = [ - "serde", - "serde_core", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde-untagged" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" -dependencies = [ - "erased-serde", - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "serde_json" -version = "1.0.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_spanned" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "3.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.12.1", - "schemars 0.9.0", - "schemars 1.1.0", - "serde_core", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "serialize-to-javascript" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "servo_arc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" -dependencies = [ - "nodrop", - "stable_deref_trait", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" -dependencies = [ - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "softbuffer" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" -dependencies = [ - "bytemuck", - "js-sys", - "ndk", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "objc2-quartz-core", - "raw-window-handle", - "redox_syscall", - "tracing", - "wasm-bindgen", - "web-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "soup3" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" -dependencies = [ - "futures-channel", - "gio", - "glib", - "libc", - "soup3-sys", -] - -[[package]] -name = "soup3-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "swift-rs" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" -dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml 0.8.2", - "version-compare", -] - -[[package]] -name = "tao" -version = "0.34.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" -dependencies = [ - "bitflags 2.10.0", - "block2", - "core-foundation 0.10.1", - "core-graphics", - "crossbeam-channel", - "dispatch", - "dlopen2", - "dpi", - "gdkwayland-sys", - "gdkx11-sys", - "gtk", - "jni", - "lazy_static", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "once_cell", - "parking_lot", - "raw-window-handle", - "scopeguard", - "tao-macros", - "unicode-segmentation", - "url", - "windows", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "tao-macros" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "tauri" -version = "2.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a3868da5508446a7cd08956d523ac3edf0a8bc20bf7e4038f9a95c2800d2033" -dependencies = [ - "anyhow", - "bytes", - "cookie", - "dirs", - "dunce", - "embed_plist", - "getrandom 0.3.4", - "glob", - "gtk", - "heck 0.5.0", - "http", - "jni", - "libc", - "log", - "mime", - "muda", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "percent-encoding", - "plist", - "raw-window-handle", - "reqwest 0.12.28", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "swift-rs", - "tauri-build", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "thiserror 2.0.17", - "tokio", - "tray-icon", - "url", - "webkit2gtk", - "webview2-com", - "window-vibrancy", - "windows", -] - -[[package]] -name = "tauri-build" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17fcb8819fd16463512a12f531d44826ce566f486d7ccd211c9c8cebdaec4e08" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs", - "glob", - "heck 0.5.0", - "json-patch", - "schemars 0.8.22", - "semver", - "serde", - "serde_json", - "tauri-utils", - "tauri-winres", - "toml 0.9.10+spec-1.1.0", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa9844cefcf99554a16e0a278156ae73b0d8680bbc0e2ad1e4287aadd8489cf" -dependencies = [ - "base64 0.22.1", - "brotli", - "ico", - "json-patch", - "plist", - "png", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "sha2", - "syn 2.0.111", - "tauri-utils", - "thiserror 2.0.17", - "time", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3764a12f886d8245e66b7ee9b43ccc47883399be2019a61d80cf0f4117446fde" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.111", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-plugin" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1d0a4860b7ff570c891e1d2a586bf1ede205ff858fbc305e0b5ae5d14c1377" -dependencies = [ - "anyhow", - "glob", - "plist", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri-utils", - "toml 0.9.10+spec-1.1.0", - "walkdir", -] - -[[package]] -name = "tauri-plugin-dialog" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9204b425d9be8d12aa60c2a83a289cf7d1caae40f57f336ed1155b3a5c0e359b" -dependencies = [ - "log", - "raw-window-handle", - "rfd", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "tauri-plugin-fs", - "thiserror 2.0.17", - "url", -] - -[[package]] -name = "tauri-plugin-fs" -version = "2.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804" -dependencies = [ - "anyhow", - "dunce", - "glob", - "percent-encoding", - "schemars 0.8.22", - "serde", - "serde_json", - "serde_repr", - "tauri", - "tauri-plugin", - "tauri-utils", - "thiserror 2.0.17", - "toml 0.9.10+spec-1.1.0", - "url", -] - -[[package]] -name = "tauri-plugin-opener" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c26b72571d25dee25667940027114e60f569fc3974f8cefbe50c2cbc5fd65e3b" -dependencies = [ - "dunce", - "glob", - "objc2-app-kit", - "objc2-foundation", - "open", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "thiserror 2.0.17", - "url", - "windows", - "zbus", -] - -[[package]] -name = "tauri-plugin-store" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca1a8ff83c269b115e98726ffc13f9e548a10161544a92ad121d6d0a96e16ea" -dependencies = [ - "dunce", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "thiserror 2.0.17", - "tokio", - "tracing", -] - -[[package]] -name = "tauri-runtime" -version = "2.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f766fe9f3d1efc4b59b17e7a891ad5ed195fa8d23582abb02e6c9a01137892" -dependencies = [ - "cookie", - "dpi", - "gtk", - "http", - "jni", - "objc2", - "objc2-ui-kit", - "objc2-web-kit", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror 2.0.17", - "url", - "webkit2gtk", - "webview2-com", - "windows", -] - -[[package]] -name = "tauri-runtime-wry" -version = "2.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187a3f26f681bdf028f796ccf57cf478c1ee422c50128e5a0a6ebeb3f5910065" -dependencies = [ - "gtk", - "http", - "jni", - "log", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "once_cell", - "percent-encoding", - "raw-window-handle", - "softbuffer", - "tao", - "tauri-runtime", - "tauri-utils", - "url", - "webkit2gtk", - "webview2-com", - "windows", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a423c51176eb3616ee9b516a9fa67fed5f0e78baaba680e44eb5dd2cc37490" -dependencies = [ - "anyhow", - "brotli", - "cargo_metadata", - "ctor", - "dunce", - "glob", - "html5ever", - "http", - "infer", - "json-patch", - "kuchikiki", - "log", - "memchr", - "phf 0.11.3", - "proc-macro2", - "quote", - "regex", - "schemars 0.8.22", - "semver", - "serde", - "serde-untagged", - "serde_json", - "serde_with", - "swift-rs", - "thiserror 2.0.17", - "toml 0.9.10+spec-1.1.0", - "url", - "urlpattern", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-winres" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" -dependencies = [ - "dunce", - "embed-resource", - "toml 0.9.10+spec-1.1.0", -] - -[[package]] -name = "tempfile" -version = "3.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" -dependencies = [ - "fastrand", - "getrandom 0.3.4", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" -dependencies = [ - "thiserror-impl 2.0.17", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "time" -version = "0.3.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" - -[[package]] -name = "time-macros" -version = "0.2.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" -dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "toml" -version = "0.9.10+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" -dependencies = [ - "indexmap 2.12.1", - "serde_core", - "serde_spanned 1.0.4", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 0.7.14", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.12.1", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap 2.12.1", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.23.10+spec-1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" -dependencies = [ - "indexmap 2.12.1", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "winnow 0.7.14", -] - -[[package]] -name = "toml_parser" -version = "1.0.6+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" -dependencies = [ - "winnow 0.7.14", -] - -[[package]] -name = "toml_writer" -version = "1.0.6+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" -dependencies = [ - "bitflags 2.10.0", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tray-icon" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d5572781bee8e3f994d7467084e1b1fd7a93ce66bd480f8156ba89dee55a2b" -dependencies = [ - "crossbeam-channel", - "dirs", - "libappindicator", - "muda", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "once_cell", - "png", - "serde", - "thiserror 2.0.17", - "windows-sys 0.60.2", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "uds_windows" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" -dependencies = [ - "memoffset", - "tempfile", - "winapi", -] - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - -[[package]] -name = "unicode-ident" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "urlpattern" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" -dependencies = [ - "regex", - "serde", - "unic-ucd-ident", - "url", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" -dependencies = [ - "getrandom 0.3.4", - "js-sys", - "serde_core", - "wasm-bindgen", -] - -[[package]] -name = "version-compare" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 2.0.111", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webkit2gtk" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "soup3", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pkg-config", - "soup3-sys", - "system-deps", -] - -[[package]] -name = "webpki-root-certs" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webview2-com" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows", - "windows-core 0.61.2", - "windows-implement", - "windows-interface", -] - -[[package]] -name = "webview2-com-macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "webview2-com-sys" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" -dependencies = [ - "thiserror 2.0.17", - "windows", - "windows-core 0.61.2", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "window-vibrancy" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" -dependencies = [ - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "raw-window-handle", - "windows-sys 0.59.0", - "windows-version", -] - -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core 0.61.2", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link 0.2.1", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-version" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" -dependencies = [ - "cfg-if", - "windows-sys 0.59.0", -] - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "writeable" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" - -[[package]] -name = "wry" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728b7d4c8ec8d81cab295e0b5b8a4c263c0d41a785fb8f8c4df284e5411140a2" -dependencies = [ - "base64 0.22.1", - "block2", - "cookie", - "crossbeam-channel", - "dirs", - "dpi", - "dunce", - "gdkx11", - "gtk", - "html5ever", - "http", - "javascriptcore-rs", - "jni", - "kuchikiki", - "libc", - "ndk", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "sha2", - "soup3", - "tao-macros", - "thiserror 2.0.17", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "yoke" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", - "synstructure", -] - -[[package]] -name = "zbus" -version = "5.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91" -dependencies = [ - "async-broadcast", - "async-executor", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener", - "futures-core", - "futures-lite", - "hex", - "nix", - "ordered-stream", - "serde", - "serde_repr", - "tracing", - "uds_windows", - "uuid", - "windows-sys 0.61.2", - "winnow 0.7.14", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "5.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.111", - "zbus_names", - "zvariant", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" -dependencies = [ - "serde", - "static_assertions", - "winnow 0.7.14", - "zvariant", -] - -[[package]] -name = "zerocopy" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" - -[[package]] -name = "zerotrie" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "zmij" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e404bcd8afdaf006e529269d3e85a743f9480c3cef60034d77860d02964f3ba" - -[[package]] -name = "zvariant" -version = "5.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c" -dependencies = [ - "endi", - "enumflags2", - "serde", - "winnow 0.7.14", - "zvariant_derive", - "zvariant_utils", -] - -[[package]] -name = "zvariant_derive" -version = "5.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.111", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 2.0.111", - "winnow 0.7.14", -] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml deleted file mode 100644 index 351934f..0000000 --- a/src-tauri/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "living-spec-standalone" -version = "0.1.0" -description = "A Tauri App" -authors = ["you"] -edition = "2024" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -# The `_lib` suffix may seem redundant but it is necessary -# to make the lib name unique and wouldn't conflict with the bin name. -# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 -name = "living_spec_standalone_lib" -crate-type = ["staticlib", "cdylib", "rlib"] - -[build-dependencies] -tauri-build = { version = "2", features = [] } - -[dependencies] -tauri = { version = "2", features = [] } -tauri-plugin-opener = "2" -serde = { version = "1", features = ["derive"] } -serde_json = "1" -tauri-plugin-dialog = "2.6.0" -ignore = "0.4.25" -walkdir = "2.5.0" -reqwest = { version = "0.13.1", features = ["json", "blocking", "stream"] } -futures = "0.3" -uuid = { version = "1.20.0", features = ["v4", "serde"] } -chrono = { version = "0.4.43", features = ["serde"] } -async-trait = "0.1.89" -tauri-plugin-store = "2.4.2" -tokio = { version = "1", features = ["sync"] } -eventsource-stream = "0.2.3" - -[dev-dependencies] -tempfile = "3" diff --git a/src-tauri/build.rs b/src-tauri/build.rs deleted file mode 100644 index d860e1e..0000000 --- a/src-tauri/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - tauri_build::build() -} diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json deleted file mode 100644 index e7f9d8b..0000000 --- a/src-tauri/capabilities/default.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "default", - "description": "Capability for the main window", - "windows": ["main"], - "permissions": [ - "core:default", - "opener:default", - "dialog:default", - "store:default" - ] -} diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png deleted file mode 100644 index 6be5e50..0000000 Binary files a/src-tauri/icons/128x128.png and /dev/null differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png deleted file mode 100644 index e81bece..0000000 Binary files a/src-tauri/icons/128x128@2x.png and /dev/null differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png deleted file mode 100644 index a437dd5..0000000 Binary files a/src-tauri/icons/32x32.png and /dev/null differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png deleted file mode 100644 index 0ca4f27..0000000 Binary files a/src-tauri/icons/Square107x107Logo.png and /dev/null differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png deleted file mode 100644 index b81f820..0000000 Binary files a/src-tauri/icons/Square142x142Logo.png and /dev/null differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png deleted file mode 100644 index 624c7bf..0000000 Binary files a/src-tauri/icons/Square150x150Logo.png and /dev/null differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png deleted file mode 100644 index c021d2b..0000000 Binary files a/src-tauri/icons/Square284x284Logo.png and /dev/null differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png deleted file mode 100644 index 6219700..0000000 Binary files a/src-tauri/icons/Square30x30Logo.png and /dev/null differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png deleted file mode 100644 index f9bc048..0000000 Binary files a/src-tauri/icons/Square310x310Logo.png and /dev/null differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png deleted file mode 100644 index d5fbfb2..0000000 Binary files a/src-tauri/icons/Square44x44Logo.png and /dev/null differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png deleted file mode 100644 index 63440d7..0000000 Binary files a/src-tauri/icons/Square71x71Logo.png and /dev/null differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png deleted file mode 100644 index f3f705a..0000000 Binary files a/src-tauri/icons/Square89x89Logo.png and /dev/null differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png deleted file mode 100644 index 4556388..0000000 Binary files a/src-tauri/icons/StoreLogo.png and /dev/null differ diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns deleted file mode 100644 index 12a5bce..0000000 Binary files a/src-tauri/icons/icon.icns and /dev/null differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico deleted file mode 100644 index b3636e4..0000000 Binary files a/src-tauri/icons/icon.ico and /dev/null differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png deleted file mode 100644 index e1cd261..0000000 Binary files a/src-tauri/icons/icon.png and /dev/null differ diff --git a/src-tauri/src/commands/chat.rs b/src-tauri/src/commands/chat.rs deleted file mode 100644 index cbb5575..0000000 --- a/src-tauri/src/commands/chat.rs +++ /dev/null @@ -1,857 +0,0 @@ -use crate::commands::fs::{StoreOps, TauriStoreWrapper}; -use crate::llm::prompts::SYSTEM_PROMPT; -use crate::llm::types::{Message, Role, ToolCall, ToolDefinition, ToolFunctionDefinition}; -use crate::state::SessionState; -use serde::Deserialize; -use serde_json::json; -use tauri::{AppHandle, State}; - -// ----------------------------------------------------------------------------- -// Constants -// ----------------------------------------------------------------------------- - -const MAX_TURNS: usize = 30; -const STORE_PATH: &str = "store.json"; -const KEY_ANTHROPIC_API_KEY: &str = "anthropic_api_key"; - -// ----------------------------------------------------------------------------- -// Types -// ----------------------------------------------------------------------------- - -#[derive(Deserialize, Clone)] -pub struct ProviderConfig { - pub provider: String, - pub model: String, - pub base_url: Option, - pub enable_tools: Option, -} - -// ----------------------------------------------------------------------------- -// Pure Implementation Functions -// ----------------------------------------------------------------------------- - -fn get_anthropic_api_key_exists_impl(store: &dyn StoreOps) -> bool { - match store.get(KEY_ANTHROPIC_API_KEY) { - Some(value) => { - if let Some(key) = value.as_str() { - !key.is_empty() - } else { - false - } - } - None => false, - } -} - -fn set_anthropic_api_key_impl(store: &dyn StoreOps, api_key: &str) -> Result<(), String> { - store.set(KEY_ANTHROPIC_API_KEY, json!(api_key)); - store.save()?; - - // Verify it was saved - match store.get(KEY_ANTHROPIC_API_KEY) { - Some(value) => { - if let Some(retrieved) = value.as_str() { - if retrieved != api_key { - return Err("Retrieved key does not match saved key".to_string()); - } - } else { - return Err("Stored value is not a string".to_string()); - } - } - None => { - return Err("API key was saved but cannot be retrieved".to_string()); - } - } - - Ok(()) -} - -fn get_anthropic_api_key_impl(store: &dyn StoreOps) -> Result { - match store.get(KEY_ANTHROPIC_API_KEY) { - Some(value) => { - if let Some(key) = value.as_str() { - if key.is_empty() { - Err("Anthropic API key is empty. Please set your API key.".to_string()) - } else { - Ok(key.to_string()) - } - } else { - Err("Stored API key is not a string".to_string()) - } - } - None => Err("Anthropic API key not found. Please set your API key.".to_string()), - } -} - -fn parse_tool_arguments(args_str: &str) -> Result { - serde_json::from_str(args_str).map_err(|e| format!("Error parsing arguments: {e}")) -} - -pub fn get_tool_definitions() -> Vec { - vec![ - ToolDefinition { - kind: "function".to_string(), - function: ToolFunctionDefinition { - name: "read_file".to_string(), - description: "Reads the complete content of a file from the project. Use this to understand existing code before making changes.".to_string(), - parameters: json!({ - "type": "object", - "properties": { - "path": { "type": "string", "description": "Relative path to the file from project root" } - }, - "required": ["path"] - }), - }, - }, - ToolDefinition { - kind: "function".to_string(), - function: ToolFunctionDefinition { - name: "write_file".to_string(), - description: "Creates or completely overwrites a file with new content. YOU MUST USE THIS to implement code changes - do not suggest code to the user. The content parameter must contain the COMPLETE file including all imports, functions, and unchanged code.".to_string(), - parameters: json!({ - "type": "object", - "properties": { - "path": { "type": "string", "description": "Relative path to the file from project root" }, - "content": { "type": "string", "description": "The complete file content to write (not a diff or partial code)" } - }, - "required": ["path", "content"] - }), - }, - }, - ToolDefinition { - kind: "function".to_string(), - function: ToolFunctionDefinition { - name: "list_directory".to_string(), - description: "Lists all files and directories at a given path. Use this to explore the project structure.".to_string(), - parameters: json!({ - "type": "object", - "properties": { - "path": { "type": "string", "description": "Relative path to list (use '.' for project root)" } - }, - "required": ["path"] - }), - }, - }, - ToolDefinition { - kind: "function".to_string(), - function: ToolFunctionDefinition { - name: "search_files".to_string(), - description: "Searches for text patterns across all files in the project. Use this to find functions, variables, or code patterns when you don't know which file they're in." - .to_string(), - parameters: json!({ - "type": "object", - "properties": { - "query": { "type": "string", "description": "The text pattern to search for across all files" } - }, - "required": ["query"] - }), - }, - }, - ToolDefinition { - kind: "function".to_string(), - function: ToolFunctionDefinition { - name: "exec_shell".to_string(), - description: "Executes a shell command in the project root directory. Use this to run tests, build commands, git operations, or any command-line tool. Examples: cargo check, npm test, git status.".to_string(), - parameters: json!({ - "type": "object", - "properties": { - "command": { - "type": "string", - "description": "The command binary to execute (e.g., 'git', 'cargo', 'npm', 'ls')" - }, - "args": { - "type": "array", - "items": { "type": "string" }, - "description": "Array of arguments to pass to the command (e.g., ['status'] for git status)" - } - }, - "required": ["command", "args"] - }), - }, - }, - ] -} - -// ----------------------------------------------------------------------------- -// Tauri Commands (Thin Wrappers) -// ----------------------------------------------------------------------------- - -#[tauri::command] -pub async fn get_ollama_models(base_url: Option) -> Result, String> { - use crate::llm::providers::ollama::OllamaProvider; - let url = base_url.unwrap_or_else(|| "http://localhost:11434".to_string()); - OllamaProvider::get_models(&url).await -} - -#[tauri::command] -pub async fn get_anthropic_api_key_exists(app: AppHandle) -> Result { - use tauri_plugin_store::StoreExt; - - let store = app - .store(STORE_PATH) - .map_err(|e| format!("Failed to access store: {e}"))?; - - let wrapper = TauriStoreWrapper { store: &store }; - Ok(get_anthropic_api_key_exists_impl(&wrapper)) -} - -#[tauri::command] -pub async fn set_anthropic_api_key(app: AppHandle, api_key: String) -> Result<(), String> { - use tauri_plugin_store::StoreExt; - - let store = app - .store(STORE_PATH) - .map_err(|e| format!("Failed to access store: {e}"))?; - - let wrapper = TauriStoreWrapper { store: &store }; - set_anthropic_api_key_impl(&wrapper, &api_key) -} - -#[tauri::command] -pub async fn chat( - app: AppHandle, - messages: Vec, - config: ProviderConfig, - state: State<'_, SessionState>, -) -> Result, String> { - use crate::llm::providers::anthropic::AnthropicProvider; - use crate::llm::providers::ollama::OllamaProvider; - use tauri::Emitter; - use tauri_plugin_store::StoreExt; - - // Reset cancel flag at start of new request - let _ = state.cancel_tx.send(false); - - // Get a clone of the cancellation receiver - let mut cancel_rx = state.cancel_rx.clone(); - - // Mark the receiver as having seen the current (false) value - cancel_rx.borrow_and_update(); - - // Setup Provider - let base_url = config - .base_url - .clone() - .unwrap_or_else(|| "http://localhost:11434".to_string()); - - // Determine provider from model name - let is_claude = config.model.starts_with("claude-"); - - if !is_claude && config.provider.as_str() != "ollama" { - return Err(format!("Unsupported provider: {}", config.provider)); - } - - // Define Tools - let tool_defs = get_tool_definitions(); - let tools = if config.enable_tools.unwrap_or(true) { - tool_defs.as_slice() - } else { - &[] - }; - - // Agent Loop - let mut current_history = messages.clone(); - - // Inject System Prompt - current_history.insert( - 0, - Message { - role: Role::System, - content: SYSTEM_PROMPT.to_string(), - tool_calls: None, - tool_call_id: None, - }, - ); - - // Inject reminder as a second system message - current_history.insert( - 1, - Message { - role: Role::System, - content: "REMINDER: Distinguish between showing examples (use code blocks in chat) vs implementing changes (use write_file tool). Keywords like 'show me', 'example', 'how does' = chat response. Keywords like 'create', 'add', 'implement', 'fix' = use tools.".to_string(), - tool_calls: None, - tool_call_id: None, - }, - ); - - let mut new_messages: Vec = Vec::new(); - let mut turn_count = 0; - - loop { - // Check for cancellation at start of loop - if *cancel_rx.borrow() { - return Err("Chat cancelled by user".to_string()); - } - - if turn_count >= MAX_TURNS { - return Err("Max conversation turns reached.".to_string()); - } - turn_count += 1; - - // Call LLM with streaming - let response = if is_claude { - // Use Anthropic provider - let store = app - .store(STORE_PATH) - .map_err(|e| format!("Failed to access store: {e}"))?; - - let wrapper = TauriStoreWrapper { store: &store }; - let api_key = get_anthropic_api_key_impl(&wrapper)?; - let anthropic_provider = AnthropicProvider::new(api_key); - anthropic_provider - .chat_stream(&app, &config.model, ¤t_history, tools, &mut cancel_rx) - .await - .map_err(|e| format!("Anthropic Error: {e}"))? - } else { - // Use Ollama provider - let ollama_provider = OllamaProvider::new(base_url.clone()); - ollama_provider - .chat_stream(&app, &config.model, ¤t_history, tools, &mut cancel_rx) - .await - .map_err(|e| format!("Ollama Error: {e}"))? - }; - - // Process Response - if let Some(tool_calls) = response.tool_calls { - // The Assistant wants to run tools - let assistant_msg = Message { - role: Role::Assistant, - content: response.content.unwrap_or_default(), - tool_calls: Some(tool_calls.clone()), - tool_call_id: None, - }; - - current_history.push(assistant_msg.clone()); - new_messages.push(assistant_msg); - // Emit history excluding system prompts (indices 0 and 1) - app.emit("chat:update", ¤t_history[2..]) - .map_err(|e| e.to_string())?; - - // Execute Tools - for call in tool_calls { - // Check for cancellation before executing each tool - if *cancel_rx.borrow() { - return Err("Chat cancelled before tool execution".to_string()); - } - - let output = execute_tool(&call, &state).await; - - let tool_msg = Message { - role: Role::Tool, - content: output, - tool_calls: None, - tool_call_id: call.id, - }; - - current_history.push(tool_msg.clone()); - new_messages.push(tool_msg); - // Emit history excluding system prompts (indices 0 and 1) - app.emit("chat:update", ¤t_history[2..]) - .map_err(|e| e.to_string())?; - } - } else { - // Final text response - let assistant_msg = Message { - role: Role::Assistant, - content: response.content.unwrap_or_default(), - tool_calls: None, - tool_call_id: None, - }; - - new_messages.push(assistant_msg.clone()); - current_history.push(assistant_msg); - // Emit history excluding system prompts (indices 0 and 1) - app.emit("chat:update", ¤t_history[2..]) - .map_err(|e| e.to_string())?; - break; - } - } - - Ok(new_messages) -} - -async fn execute_tool(call: &ToolCall, state: &State<'_, SessionState>) -> String { - use crate::commands::{fs, search, shell}; - - let name = call.function.name.as_str(); - // Parse arguments. They come as a JSON string from the LLM abstraction. - let args: serde_json::Value = match parse_tool_arguments(&call.function.arguments) { - Ok(v) => v, - Err(e) => return e, - }; - - match name { - "read_file" => { - let path = args["path"].as_str().unwrap_or("").to_string(); - match fs::read_file(path, state.clone()).await { - Ok(content) => content, - Err(e) => format!("Error: {e}"), - } - } - "write_file" => { - let path = args["path"].as_str().unwrap_or("").to_string(); - let content = args["content"].as_str().unwrap_or("").to_string(); - match fs::write_file(path, content, state.clone()).await { - Ok(()) => "File written successfully.".to_string(), - Err(e) => format!("Error: {e}"), - } - } - "list_directory" => { - let path = args["path"].as_str().unwrap_or("").to_string(); - match fs::list_directory(path, state.clone()).await { - Ok(entries) => serde_json::to_string(&entries).unwrap_or_default(), - Err(e) => format!("Error: {e}"), - } - } - "search_files" => { - let query = args["query"].as_str().unwrap_or("").to_string(); - match search::search_files(query, state.clone()).await { - Ok(results) => serde_json::to_string(&results).unwrap_or_default(), - Err(e) => format!("Error: {e}"), - } - } - "exec_shell" => { - let command = args["command"].as_str().unwrap_or("").to_string(); - let args_vec: Vec = args["args"] - .as_array() - .map(|arr| { - arr.iter() - .map(|v| v.as_str().unwrap_or("").to_string()) - .collect() - }) - .unwrap_or_default(); - - match shell::exec_shell(command, args_vec, state.clone()).await { - Ok(output) => serde_json::to_string(&output).unwrap_or_default(), - Err(e) => format!("Error: {e}"), - } - } - _ => format!("Unknown tool: {name}"), - } -} - -#[tauri::command] -pub async fn cancel_chat(state: State<'_, SessionState>) -> Result<(), String> { - state.cancel_tx.send(true).map_err(|e| e.to_string())?; - Ok(()) -} - -// ----------------------------------------------------------------------------- -// Tests -// ----------------------------------------------------------------------------- - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::MockStore; - use std::collections::HashMap; - - // Tests for get_anthropic_api_key_exists_impl - mod get_anthropic_api_key_exists_tests { - use super::*; - - #[test] - fn test_key_exists_and_not_empty() { - let mut data = HashMap::new(); - data.insert(KEY_ANTHROPIC_API_KEY.to_string(), json!("sk-ant-test123")); - let store = MockStore::with_data(data); - - let result = get_anthropic_api_key_exists_impl(&store); - - assert!(result); - } - - #[test] - fn test_key_exists_but_empty() { - let mut data = HashMap::new(); - data.insert(KEY_ANTHROPIC_API_KEY.to_string(), json!("")); - let store = MockStore::with_data(data); - - let result = get_anthropic_api_key_exists_impl(&store); - - assert!(!result); - } - - #[test] - fn test_key_not_exists() { - let store = MockStore::new(); - - let result = get_anthropic_api_key_exists_impl(&store); - - assert!(!result); - } - - #[test] - fn test_key_exists_but_not_string() { - let mut data = HashMap::new(); - data.insert(KEY_ANTHROPIC_API_KEY.to_string(), json!(123)); - let store = MockStore::with_data(data); - - let result = get_anthropic_api_key_exists_impl(&store); - - assert!(!result); - } - } - - // Tests for set_anthropic_api_key_impl - mod set_anthropic_api_key_tests { - use super::*; - - #[test] - fn test_set_new_key() { - let store = MockStore::new(); - let api_key = "sk-ant-new-key".to_string(); - - let result = set_anthropic_api_key_impl(&store, &api_key); - - assert!(result.is_ok()); - assert_eq!( - store.get(KEY_ANTHROPIC_API_KEY), - Some(json!("sk-ant-new-key")) - ); - } - - #[test] - fn test_set_overwrites_existing_key() { - let mut data = HashMap::new(); - data.insert(KEY_ANTHROPIC_API_KEY.to_string(), json!("old-key")); - let store = MockStore::with_data(data); - let api_key = "sk-ant-new-key".to_string(); - - let result = set_anthropic_api_key_impl(&store, &api_key); - - assert!(result.is_ok()); - assert_eq!( - store.get(KEY_ANTHROPIC_API_KEY), - Some(json!("sk-ant-new-key")) - ); - } - - #[test] - fn test_set_empty_string() { - let store = MockStore::new(); - let api_key = "".to_string(); - - let result = set_anthropic_api_key_impl(&store, &api_key); - - assert!(result.is_ok()); - assert_eq!(store.get(KEY_ANTHROPIC_API_KEY), Some(json!(""))); - } - } - - // Tests for get_anthropic_api_key_impl - mod get_anthropic_api_key_tests { - use super::*; - - #[test] - fn test_get_existing_key() { - let mut data = HashMap::new(); - data.insert(KEY_ANTHROPIC_API_KEY.to_string(), json!("sk-ant-test-key")); - let store = MockStore::with_data(data); - - let result = get_anthropic_api_key_impl(&store); - - assert_eq!(result, Ok("sk-ant-test-key".to_string())); - } - - #[test] - fn test_get_key_not_found() { - let store = MockStore::new(); - - let result = get_anthropic_api_key_impl(&store); - - assert_eq!( - result, - Err("Anthropic API key not found. Please set your API key.".to_string()) - ); - } - - #[test] - fn test_get_empty_key() { - let mut data = HashMap::new(); - data.insert(KEY_ANTHROPIC_API_KEY.to_string(), json!("")); - let store = MockStore::with_data(data); - - let result = get_anthropic_api_key_impl(&store); - - assert_eq!( - result, - Err("Anthropic API key is empty. Please set your API key.".to_string()) - ); - } - - #[test] - fn test_get_key_not_string() { - let mut data = HashMap::new(); - data.insert(KEY_ANTHROPIC_API_KEY.to_string(), json!(12345)); - let store = MockStore::with_data(data); - - let result = get_anthropic_api_key_impl(&store); - - assert_eq!(result, Err("Stored API key is not a string".to_string())); - } - } - - // Tests for parse_tool_arguments - mod parse_tool_arguments_tests { - use super::*; - - #[test] - fn test_parse_valid_json() { - let args_str = r#"{"path": "test.txt"}"#; - - let result = parse_tool_arguments(args_str); - - assert!(result.is_ok()); - let value = result.unwrap(); - assert_eq!(value["path"], "test.txt"); - } - - #[test] - fn test_parse_complex_json() { - let args_str = r#"{"command": "git", "args": ["status", "--short"]}"#; - - let result = parse_tool_arguments(args_str); - - assert!(result.is_ok()); - let value = result.unwrap(); - assert_eq!(value["command"], "git"); - assert_eq!(value["args"][0], "status"); - assert_eq!(value["args"][1], "--short"); - } - - #[test] - fn test_parse_invalid_json() { - let args_str = r#"{"path": "test.txt"#; // Missing closing brace - - let result = parse_tool_arguments(args_str); - - assert!(result.is_err()); - assert!(result.unwrap_err().contains("Error parsing arguments")); - } - - #[test] - fn test_parse_empty_json() { - let args_str = "{}"; - - let result = parse_tool_arguments(args_str); - - assert!(result.is_ok()); - let value = result.unwrap(); - assert_eq!(value, json!({})); - } - - #[test] - fn test_parse_empty_string() { - let args_str = ""; - - let result = parse_tool_arguments(args_str); - - assert!(result.is_err()); - } - } - - // Tests for get_tool_definitions - mod get_tool_definitions_tests { - use super::*; - - #[test] - fn test_returns_all_tools() { - let tools = get_tool_definitions(); - - assert_eq!(tools.len(), 5); - } - - #[test] - fn test_read_file_tool_exists() { - let tools = get_tool_definitions(); - - let read_file = tools - .iter() - .find(|t| t.function.name == "read_file") - .expect("read_file tool should exist"); - - assert_eq!(read_file.kind, "function"); - assert!( - read_file - .function - .description - .contains("Reads the complete content") - ); - } - - #[test] - fn test_write_file_tool_exists() { - let tools = get_tool_definitions(); - - let write_file = tools - .iter() - .find(|t| t.function.name == "write_file") - .expect("write_file tool should exist"); - - assert_eq!(write_file.kind, "function"); - assert!( - write_file - .function - .description - .contains("Creates or completely overwrites") - ); - } - - #[test] - fn test_list_directory_tool_exists() { - let tools = get_tool_definitions(); - - let list_directory = tools - .iter() - .find(|t| t.function.name == "list_directory") - .expect("list_directory tool should exist"); - - assert_eq!(list_directory.kind, "function"); - assert!( - list_directory - .function - .description - .contains("Lists all files and directories") - ); - } - - #[test] - fn test_search_files_tool_exists() { - let tools = get_tool_definitions(); - - let search_files = tools - .iter() - .find(|t| t.function.name == "search_files") - .expect("search_files tool should exist"); - - assert_eq!(search_files.kind, "function"); - assert!( - search_files - .function - .description - .contains("Searches for text patterns") - ); - } - - #[test] - fn test_exec_shell_tool_exists() { - let tools = get_tool_definitions(); - - let exec_shell = tools - .iter() - .find(|t| t.function.name == "exec_shell") - .expect("exec_shell tool should exist"); - - assert_eq!(exec_shell.kind, "function"); - assert!( - exec_shell - .function - .description - .contains("Executes a shell command") - ); - } - - #[test] - fn test_all_tools_have_function_kind() { - let tools = get_tool_definitions(); - - for tool in tools { - assert_eq!(tool.kind, "function"); - } - } - - #[test] - fn test_all_tools_have_non_empty_descriptions() { - let tools = get_tool_definitions(); - - for tool in tools { - assert!(!tool.function.description.is_empty()); - assert!(!tool.function.name.is_empty()); - } - } - - #[test] - fn test_read_file_parameters() { - let tools = get_tool_definitions(); - let read_file = tools - .iter() - .find(|t| t.function.name == "read_file") - .unwrap(); - - let params = &read_file.function.parameters; - assert_eq!(params["type"], "object"); - assert!(params["properties"]["path"].is_object()); - assert_eq!(params["required"][0], "path"); - } - - #[test] - fn test_write_file_parameters() { - let tools = get_tool_definitions(); - let write_file = tools - .iter() - .find(|t| t.function.name == "write_file") - .unwrap(); - - let params = &write_file.function.parameters; - assert_eq!(params["type"], "object"); - assert!(params["properties"]["path"].is_object()); - assert!(params["properties"]["content"].is_object()); - assert_eq!(params["required"][0], "path"); - assert_eq!(params["required"][1], "content"); - } - - #[test] - fn test_exec_shell_parameters() { - let tools = get_tool_definitions(); - let exec_shell = tools - .iter() - .find(|t| t.function.name == "exec_shell") - .unwrap(); - - let params = &exec_shell.function.parameters; - assert_eq!(params["type"], "object"); - assert!(params["properties"]["command"].is_object()); - assert!(params["properties"]["args"].is_object()); - assert_eq!(params["required"][0], "command"); - assert_eq!(params["required"][1], "args"); - } - } - - // Tests for get_project_root helper - mod get_project_root_tests { - use super::*; - use std::sync::Mutex; - use tempfile::TempDir; - - #[test] - fn test_get_project_root_no_project() { - let state = SessionState { - project_root: Mutex::new(None), - cancel_tx: tokio::sync::watch::channel(false).0, - cancel_rx: tokio::sync::watch::channel(false).1, - }; - - let result = state.get_project_root(); - - assert!(result.is_err()); - assert_eq!(result.unwrap_err(), "No project is currently open."); - } - - #[test] - fn test_get_project_root_success() { - let temp_dir = TempDir::new().unwrap(); - let path = temp_dir.path().to_path_buf(); - let state = SessionState { - project_root: Mutex::new(Some(path.clone())), - cancel_tx: tokio::sync::watch::channel(false).0, - cancel_rx: tokio::sync::watch::channel(false).1, - }; - - let result = state.get_project_root(); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), path); - } - } -} diff --git a/src-tauri/src/commands/fs.rs b/src-tauri/src/commands/fs.rs deleted file mode 100644 index a3b3879..0000000 --- a/src-tauri/src/commands/fs.rs +++ /dev/null @@ -1,852 +0,0 @@ -use crate::state::SessionState; -#[cfg(test)] -use crate::test_utils::MockStore; -use serde::Serialize; -use serde_json::json; -use std::fs; -use std::path::PathBuf; -use tauri::{AppHandle, State}; -use tauri_plugin_store::StoreExt; - -const STORE_PATH: &str = "store.json"; -const KEY_LAST_PROJECT: &str = "last_project_path"; -const KEY_SELECTED_MODEL: &str = "selected_model"; - -// ----------------------------------------------------------------------------- -// Store Abstraction -// ----------------------------------------------------------------------------- - -/// Trait to abstract store operations for testing -pub trait StoreOps: Send + Sync { - fn get(&self, key: &str) -> Option; - fn set(&self, key: &str, value: serde_json::Value); - fn delete(&self, key: &str); - fn save(&self) -> Result<(), String>; -} - -// ----------------------------------------------------------------------------- -// Store Wrapper for Production -// ----------------------------------------------------------------------------- - -/// Wrapper for Tauri Store that implements StoreOps -pub struct TauriStoreWrapper<'a> { - pub store: &'a tauri_plugin_store::Store, -} - -impl<'a> StoreOps for TauriStoreWrapper<'a> { - fn get(&self, key: &str) -> Option { - self.store.get(key) - } - - fn set(&self, key: &str, value: serde_json::Value) { - self.store.set(key, value); - } - - fn delete(&self, key: &str) { - self.store.delete(key); - } - - fn save(&self) -> Result<(), String> { - self.store - .save() - .map_err(|e| format!("Failed to save store: {}", e)) - } -} - -// ----------------------------------------------------------------------------- -// Helper Functions -// ----------------------------------------------------------------------------- - -/// Resolves a relative path against the active project root (pure function for testing). -/// Returns error if path attempts traversal (..). -fn resolve_path_impl(root: PathBuf, relative_path: &str) -> Result { - // specific check for traversal - if relative_path.contains("..") { - return Err("Security Violation: Directory traversal ('..') is not allowed.".to_string()); - } - - // Join path - let full_path = root.join(relative_path); - - Ok(full_path) -} - -/// Resolves a relative path against the active project root. -/// Returns error if no project is open or if path attempts traversal (..). -fn resolve_path(state: &State<'_, SessionState>, relative_path: &str) -> Result { - let root = state.inner().get_project_root()?; - resolve_path_impl(root, relative_path) -} - -// ----------------------------------------------------------------------------- -// Commands -// ----------------------------------------------------------------------------- - -/// Validate that a path exists and is a directory (pure function for testing) -async fn validate_project_path(path: PathBuf) -> Result<(), String> { - tauri::async_runtime::spawn_blocking(move || { - if !path.exists() { - return Err(format!("Path does not exist: {}", path.display())); - } - if !path.is_dir() { - return Err(format!("Path is not a directory: {}", path.display())); - } - Ok(()) - }) - .await - .map_err(|e| format!("Task failed: {}", e))? -} - -/// Open project implementation (testable with store abstraction) -async fn open_project_impl( - path: String, - state: &SessionState, - store: &dyn StoreOps, -) -> Result { - let p = PathBuf::from(&path); - - // Validate path - validate_project_path(p.clone()).await?; - - // Update session state - { - let mut root = state.project_root.lock().map_err(|e| e.to_string())?; - *root = Some(p.clone()); - } - - // Persist to store - store.set(KEY_LAST_PROJECT, json!(path)); - store.save()?; - - Ok(path) -} - -#[tauri::command] -pub async fn open_project( - app: AppHandle, - path: String, - state: State<'_, SessionState>, -) -> Result { - let store = app - .store(STORE_PATH) - .map_err(|e| format!("Failed to access store: {}", e))?; - let wrapper = TauriStoreWrapper { store: &store }; - open_project_impl(path, state.inner(), &wrapper).await -} - -/// Close project implementation (testable with store abstraction) -fn close_project_impl(state: &SessionState, store: &dyn StoreOps) -> Result<(), String> { - // Clear session state - { - let mut root = state.project_root.lock().map_err(|e| e.to_string())?; - *root = None; - } - - // Clear from store - store.delete(KEY_LAST_PROJECT); - store.save()?; - - Ok(()) -} - -#[tauri::command] -pub async fn close_project(app: AppHandle, state: State<'_, SessionState>) -> Result<(), String> { - let store = app - .store(STORE_PATH) - .map_err(|e| format!("Failed to access store: {}", e))?; - let wrapper = TauriStoreWrapper { store: &store }; - close_project_impl(state.inner(), &wrapper) -} - -/// Get current project implementation (testable with store abstraction) -fn get_current_project_impl( - state: &SessionState, - store: &dyn StoreOps, -) -> Result, String> { - // 1. Check in-memory state - { - let root = state.project_root.lock().map_err(|e| e.to_string())?; - if let Some(path) = &*root { - return Ok(Some(path.to_string_lossy().to_string())); - } - } - - // 2. Check store - if let Some(path_str) = store - .get(KEY_LAST_PROJECT) - .as_ref() - .and_then(|val| val.as_str()) - { - let p = PathBuf::from(path_str); - if p.exists() && p.is_dir() { - // Update session state - let mut root = state.project_root.lock().map_err(|e| e.to_string())?; - *root = Some(p.clone()); - return Ok(Some(path_str.to_string())); - } - } - - Ok(None) -} - -#[tauri::command] -pub async fn get_current_project( - app: AppHandle, - state: State<'_, SessionState>, -) -> Result, String> { - let store = app - .store(STORE_PATH) - .map_err(|e| format!("Failed to access store: {}", e))?; - let wrapper = TauriStoreWrapper { store: &store }; - get_current_project_impl(state.inner(), &wrapper) -} - -/// Get model preference implementation (testable with store abstraction) -fn get_model_preference_impl(store: &dyn StoreOps) -> Result, String> { - if let Some(model) = store - .get(KEY_SELECTED_MODEL) - .as_ref() - .and_then(|val| val.as_str()) - { - return Ok(Some(model.to_string())); - } - Ok(None) -} - -#[tauri::command] -pub async fn get_model_preference(app: AppHandle) -> Result, String> { - let store = app - .store(STORE_PATH) - .map_err(|e| format!("Failed to access store: {}", e))?; - let wrapper = TauriStoreWrapper { store: &store }; - get_model_preference_impl(&wrapper) -} - -/// Set model preference implementation (testable with store abstraction) -fn set_model_preference_impl(model: String, store: &dyn StoreOps) -> Result<(), String> { - store.set(KEY_SELECTED_MODEL, json!(model)); - store.save()?; - Ok(()) -} - -#[tauri::command] -pub async fn set_model_preference(app: AppHandle, model: String) -> Result<(), String> { - let store = app - .store(STORE_PATH) - .map_err(|e| format!("Failed to access store: {}", e))?; - let wrapper = TauriStoreWrapper { store: &store }; - set_model_preference_impl(model, &wrapper) -} - -/// Read file implementation (pure function for testing) -async fn read_file_impl(full_path: PathBuf) -> Result { - tauri::async_runtime::spawn_blocking(move || { - fs::read_to_string(&full_path).map_err(|e| format!("Failed to read file: {}", e)) - }) - .await - .map_err(|e| format!("Task failed: {}", e))? -} - -#[tauri::command] -pub async fn read_file(path: String, state: State<'_, SessionState>) -> Result { - let full_path = resolve_path(&state, &path)?; - read_file_impl(full_path).await -} - -/// Write file implementation (pure function for testing) -async fn write_file_impl(full_path: PathBuf, content: String) -> Result<(), String> { - tauri::async_runtime::spawn_blocking(move || { - // Ensure parent directory exists - if let Some(parent) = full_path.parent() { - fs::create_dir_all(parent) - .map_err(|e| format!("Failed to create directories: {}", e))?; - } - - fs::write(&full_path, content).map_err(|e| format!("Failed to write file: {}", e)) - }) - .await - .map_err(|e| format!("Task failed: {}", e))? -} - -#[tauri::command] -pub async fn write_file( - path: String, - content: String, - state: State<'_, SessionState>, -) -> Result<(), String> { - let full_path = resolve_path(&state, &path)?; - write_file_impl(full_path, content).await -} - -#[derive(Serialize, Debug)] -pub struct FileEntry { - name: String, - kind: String, // "file" | "dir" -} - -/// List directory implementation (pure function for testing) -async fn list_directory_impl(full_path: PathBuf) -> Result, String> { - tauri::async_runtime::spawn_blocking(move || { - let entries = fs::read_dir(&full_path).map_err(|e| format!("Failed to read dir: {}", e))?; - - let mut result = Vec::new(); - for entry in entries { - let entry = entry.map_err(|e| e.to_string())?; - let ft = entry.file_type().map_err(|e| e.to_string())?; - let name = entry.file_name().to_string_lossy().to_string(); - - result.push(FileEntry { - name, - kind: if ft.is_dir() { - "dir".to_string() - } else { - "file".to_string() - }, - }); - } - - // Sort: directories first, then files - result.sort_by(|a, b| match (a.kind.as_str(), b.kind.as_str()) { - ("dir", "file") => std::cmp::Ordering::Less, - ("file", "dir") => std::cmp::Ordering::Greater, - _ => a.name.cmp(&b.name), - }); - - Ok(result) - }) - .await - .map_err(|e| format!("Task failed: {}", e))? -} - -#[tauri::command] -pub async fn list_directory( - path: String, - state: State<'_, SessionState>, -) -> Result, String> { - let full_path = resolve_path(&state, &path)?; - list_directory_impl(full_path).await -} - -// ----------------------------------------------------------------------------- -// Tests -// ----------------------------------------------------------------------------- - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - use std::sync::Mutex; - use tempfile::TempDir; - - /// Helper to create a test SessionState with a given root path - fn create_test_state(root: Option) -> SessionState { - let (cancel_tx, cancel_rx) = tokio::sync::watch::channel(false); - SessionState { - project_root: Mutex::new(root), - cancel_tx, - cancel_rx, - } - } - - // Tests for validate_project_path function - mod validate_project_path_tests { - use super::*; - - #[tokio::test] - async fn test_validate_project_path_valid_directory() { - let temp_dir = TempDir::new().unwrap(); - let path = temp_dir.path().to_path_buf(); - - let result = validate_project_path(path).await; - - assert!(result.is_ok()); - } - - #[tokio::test] - async fn test_validate_project_path_not_exists() { - let path = PathBuf::from("/nonexistent/path/xyz"); - - let result = validate_project_path(path).await; - - assert!(result.is_err()); - assert!(result.unwrap_err().contains("Path does not exist")); - } - - #[tokio::test] - async fn test_validate_project_path_is_file() { - let temp_dir = TempDir::new().unwrap(); - let file_path = temp_dir.path().join("test.txt"); - fs::write(&file_path, "content").unwrap(); - - let result = validate_project_path(file_path).await; - - assert!(result.is_err()); - assert!(result.unwrap_err().contains("not a directory")); - } - - #[tokio::test] - async fn test_validate_project_path_nested_directory() { - let temp_dir = TempDir::new().unwrap(); - let nested = temp_dir.path().join("nested/dir"); - fs::create_dir_all(&nested).unwrap(); - - let result = validate_project_path(nested).await; - - assert!(result.is_ok()); - } - - #[tokio::test] - async fn test_validate_project_path_empty_directory() { - let temp_dir = TempDir::new().unwrap(); - let empty_dir = temp_dir.path().join("empty"); - fs::create_dir(&empty_dir).unwrap(); - - let result = validate_project_path(empty_dir).await; - - assert!(result.is_ok()); - } - } - - // Tests for open_project_impl - mod open_project_tests { - use super::*; - - #[tokio::test] - async fn test_open_project_impl_success() { - let temp_dir = TempDir::new().unwrap(); - let path = temp_dir.path().to_string_lossy().to_string(); - let state = create_test_state(None); - let store = MockStore::new(); - - let result = open_project_impl(path.clone(), &state, &store).await; - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), path); - - // Verify state was updated - let root = state.project_root.lock().unwrap(); - assert!(root.is_some()); - - // Verify store was updated - assert!(store.get(KEY_LAST_PROJECT).is_some()); - } - - #[tokio::test] - async fn test_open_project_impl_invalid_path() { - let state = create_test_state(None); - let store = MockStore::new(); - - let result = open_project_impl("/nonexistent/path".to_string(), &state, &store).await; - - assert!(result.is_err()); - assert!(result.unwrap_err().contains("does not exist")); - } - - #[tokio::test] - async fn test_open_project_impl_file_not_directory() { - let temp_dir = TempDir::new().unwrap(); - let file_path = temp_dir.path().join("file.txt"); - fs::write(&file_path, "content").unwrap(); - let path = file_path.to_string_lossy().to_string(); - - let state = create_test_state(None); - let store = MockStore::new(); - - let result = open_project_impl(path, &state, &store).await; - - assert!(result.is_err()); - assert!(result.unwrap_err().contains("not a directory")); - } - } - - // Tests for close_project_impl - mod close_project_tests { - use super::*; - - #[test] - fn test_close_project_impl() { - let temp_dir = TempDir::new().unwrap(); - let state = create_test_state(Some(temp_dir.path().to_path_buf())); - let store = MockStore::new(); - store.set(KEY_LAST_PROJECT, json!("/some/path")); - - let result = close_project_impl(&state, &store); - - assert!(result.is_ok()); - - // Verify state was cleared - let root = state.project_root.lock().unwrap(); - assert!(root.is_none()); - - // Verify store was cleared - assert!(store.get(KEY_LAST_PROJECT).is_none()); - } - } - - // Tests for get_current_project_impl - mod get_current_project_tests { - use super::*; - - #[test] - fn test_get_current_project_impl_from_memory() { - let temp_dir = TempDir::new().unwrap(); - let path = temp_dir.path().to_path_buf(); - let state = create_test_state(Some(path.clone())); - let store = MockStore::new(); - - let result = get_current_project_impl(&state, &store); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), Some(path.to_string_lossy().to_string())); - } - - #[test] - fn test_get_current_project_impl_from_store() { - let temp_dir = TempDir::new().unwrap(); - let path = temp_dir.path().to_string_lossy().to_string(); - let state = create_test_state(None); - let store = MockStore::new(); - store.set(KEY_LAST_PROJECT, json!(path.clone())); - - let result = get_current_project_impl(&state, &store); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), Some(path)); - - // Verify state was updated - let root = state.project_root.lock().unwrap(); - assert!(root.is_some()); - } - - #[test] - fn test_get_current_project_impl_no_project() { - let state = create_test_state(None); - let store = MockStore::new(); - - let result = get_current_project_impl(&state, &store); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), None); - } - - #[test] - fn test_get_current_project_impl_store_path_invalid() { - let state = create_test_state(None); - let store = MockStore::new(); - store.set(KEY_LAST_PROJECT, json!("/nonexistent/path")); - - let result = get_current_project_impl(&state, &store); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), None); - } - } - - // Tests for model preference functions - mod model_preference_tests { - use super::*; - - #[test] - fn test_get_model_preference_impl_exists() { - let store = MockStore::new(); - store.set(KEY_SELECTED_MODEL, json!("gpt-4")); - - let result = get_model_preference_impl(&store); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), Some("gpt-4".to_string())); - } - - #[test] - fn test_get_model_preference_impl_not_exists() { - let store = MockStore::new(); - - let result = get_model_preference_impl(&store); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), None); - } - - #[test] - fn test_set_model_preference_impl() { - let store = MockStore::new(); - - let result = set_model_preference_impl("claude-3".to_string(), &store); - - assert!(result.is_ok()); - assert_eq!(store.get(KEY_SELECTED_MODEL), Some(json!("claude-3"))); - } - } - - // Tests for resolve_path helper function - mod resolve_path_tests { - use super::*; - - #[test] - fn test_resolve_path_no_project_open() { - let state = create_test_state(None); - - let result = state.get_project_root(); - - assert!(result.is_err()); - assert_eq!(result.unwrap_err(), "No project is currently open."); - } - - #[test] - fn test_resolve_path_valid() { - let temp_dir = TempDir::new().unwrap(); - let root = temp_dir.path().to_path_buf(); - - let result = resolve_path_impl(root.clone(), "test.txt"); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), root.join("test.txt")); - } - - #[test] - fn test_resolve_path_blocks_traversal() { - let temp_dir = TempDir::new().unwrap(); - let root = temp_dir.path().to_path_buf(); - - let result = resolve_path_impl(root, "../etc/passwd"); - - assert!(result.is_err()); - assert!(result.unwrap_err().contains("Directory traversal")); - } - - #[test] - fn test_resolve_path_nested() { - let temp_dir = TempDir::new().unwrap(); - let root = temp_dir.path().to_path_buf(); - - let result = resolve_path_impl(root.clone(), "src/main.rs"); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), root.join("src/main.rs")); - } - } - - // Tests for read_file command - mod read_file_tests { - use super::*; - - #[tokio::test] - async fn test_read_file_success() { - let temp_dir = TempDir::new().unwrap(); - let file_path = temp_dir.path().join("test.txt"); - fs::write(&file_path, "Hello, World!").unwrap(); - - let result = read_file_impl(file_path).await; - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), "Hello, World!"); - } - - #[tokio::test] - async fn test_read_file_not_found() { - let temp_dir = TempDir::new().unwrap(); - let file_path = temp_dir.path().join("nonexistent.txt"); - - let result = read_file_impl(file_path).await; - - assert!(result.is_err()); - assert!(result.unwrap_err().contains("Failed to read file")); - } - - #[tokio::test] - async fn test_read_file_no_project() { - let state = create_test_state(None); - - let result = state.get_project_root(); - - assert!(result.is_err()); - assert_eq!(result.unwrap_err(), "No project is currently open."); - } - - #[tokio::test] - async fn test_read_file_blocks_traversal() { - let temp_dir = TempDir::new().unwrap(); - let root = temp_dir.path().to_path_buf(); - - let result = resolve_path_impl(root, "../etc/passwd"); - - assert!(result.is_err()); - assert!(result.unwrap_err().contains("Directory traversal")); - } - - #[tokio::test] - async fn test_read_file_nested_path() { - let temp_dir = TempDir::new().unwrap(); - let nested_dir = temp_dir.path().join("src"); - fs::create_dir(&nested_dir).unwrap(); - let file_path = nested_dir.join("lib.rs"); - fs::write(&file_path, "pub fn main() {}").unwrap(); - - let result = read_file_impl(file_path).await; - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), "pub fn main() {}"); - } - } - - // Tests for write_file command - mod write_file_tests { - use super::*; - - #[tokio::test] - async fn test_write_file_success() { - let temp_dir = TempDir::new().unwrap(); - let file_path = temp_dir.path().join("test.txt"); - - let result = write_file_impl(file_path.clone(), "Hello, World!".to_string()).await; - - assert!(result.is_ok()); - let content = fs::read_to_string(file_path).unwrap(); - assert_eq!(content, "Hello, World!"); - } - - #[tokio::test] - async fn test_write_file_creates_parent_dirs() { - let temp_dir = TempDir::new().unwrap(); - let file_path = temp_dir.path().join("src/nested/test.txt"); - - let result = write_file_impl(file_path.clone(), "content".to_string()).await; - - assert!(result.is_ok()); - assert!(file_path.exists()); - let content = fs::read_to_string(file_path).unwrap(); - assert_eq!(content, "content"); - } - - #[tokio::test] - async fn test_write_file_overwrites_existing() { - let temp_dir = TempDir::new().unwrap(); - let file_path = temp_dir.path().join("test.txt"); - fs::write(&file_path, "old content").unwrap(); - - let result = write_file_impl(file_path.clone(), "new content".to_string()).await; - - assert!(result.is_ok()); - let content = fs::read_to_string(file_path).unwrap(); - assert_eq!(content, "new content"); - } - - #[tokio::test] - async fn test_write_file_no_project() { - let state = create_test_state(None); - - let result = state.get_project_root(); - - assert!(result.is_err()); - assert_eq!(result.unwrap_err(), "No project is currently open."); - } - - #[tokio::test] - async fn test_write_file_blocks_traversal() { - let temp_dir = TempDir::new().unwrap(); - let root = temp_dir.path().to_path_buf(); - - let result = resolve_path_impl(root, "../etc/passwd"); - - assert!(result.is_err()); - assert!(result.unwrap_err().contains("Directory traversal")); - } - } - - // Tests for list_directory command - mod list_directory_tests { - use super::*; - - #[tokio::test] - async fn test_list_directory_success() { - let temp_dir = TempDir::new().unwrap(); - fs::write(temp_dir.path().join("file1.txt"), "").unwrap(); - fs::write(temp_dir.path().join("file2.txt"), "").unwrap(); - fs::create_dir(temp_dir.path().join("dir1")).unwrap(); - - let result = list_directory_impl(temp_dir.path().to_path_buf()).await; - - assert!(result.is_ok()); - let entries = result.unwrap(); - assert_eq!(entries.len(), 3); - - // Check that directories come first - assert_eq!(entries[0].kind, "dir"); - assert_eq!(entries[0].name, "dir1"); - - // Files should be sorted alphabetically after directories - assert_eq!(entries[1].kind, "file"); - assert_eq!(entries[2].kind, "file"); - } - - #[tokio::test] - async fn test_list_directory_empty() { - let temp_dir = TempDir::new().unwrap(); - let empty_dir = temp_dir.path().join("empty"); - fs::create_dir(&empty_dir).unwrap(); - - let result = list_directory_impl(empty_dir).await; - - assert!(result.is_ok()); - assert_eq!(result.unwrap().len(), 0); - } - - #[tokio::test] - async fn test_list_directory_not_found() { - let temp_dir = TempDir::new().unwrap(); - let nonexistent = temp_dir.path().join("nonexistent"); - - let result = list_directory_impl(nonexistent).await; - - assert!(result.is_err()); - assert!(result.unwrap_err().contains("Failed to read dir")); - } - - #[tokio::test] - async fn test_list_directory_no_project() { - let state = create_test_state(None); - - let result = state.get_project_root(); - - assert!(result.is_err()); - assert_eq!(result.unwrap_err(), "No project is currently open."); - } - - #[tokio::test] - async fn test_list_directory_blocks_traversal() { - let temp_dir = TempDir::new().unwrap(); - let root = temp_dir.path().to_path_buf(); - - let result = resolve_path_impl(root, "../etc"); - - assert!(result.is_err()); - assert!(result.unwrap_err().contains("Directory traversal")); - } - - #[tokio::test] - async fn test_list_directory_sorting() { - let temp_dir = TempDir::new().unwrap(); - fs::write(temp_dir.path().join("zebra.txt"), "").unwrap(); - fs::write(temp_dir.path().join("apple.txt"), "").unwrap(); - fs::create_dir(temp_dir.path().join("zoo")).unwrap(); - fs::create_dir(temp_dir.path().join("animal")).unwrap(); - - let result = list_directory_impl(temp_dir.path().to_path_buf()).await; - - assert!(result.is_ok()); - let entries = result.unwrap(); - - // Directories first (alphabetically) - assert_eq!(entries[0].name, "animal"); - assert_eq!(entries[0].kind, "dir"); - assert_eq!(entries[1].name, "zoo"); - assert_eq!(entries[1].kind, "dir"); - - // Files next (alphabetically) - assert_eq!(entries[2].name, "apple.txt"); - assert_eq!(entries[2].kind, "file"); - assert_eq!(entries[3].name, "zebra.txt"); - assert_eq!(entries[3].kind, "file"); - } - } -} diff --git a/src-tauri/src/commands/search.rs b/src-tauri/src/commands/search.rs deleted file mode 100644 index 8631b73..0000000 --- a/src-tauri/src/commands/search.rs +++ /dev/null @@ -1,373 +0,0 @@ -use crate::state::SessionState; -use ignore::WalkBuilder; -use serde::Serialize; -use std::fs; -use std::path::PathBuf; -use tauri::State; - -// ----------------------------------------------------------------------------- -// Helper Functions -// ----------------------------------------------------------------------------- - -/// Helper to get the root path (cloned) without joining -fn get_project_root(state: &State<'_, SessionState>) -> Result { - state.inner().get_project_root() -} - -// ----------------------------------------------------------------------------- -// Commands -// ----------------------------------------------------------------------------- - -#[derive(Serialize, Debug)] -pub struct SearchResult { - path: String, // Relative path - matches: usize, -} - -/// Searches for files containing the specified query string within the current project. -/// -/// This command performs a case-sensitive substring search across all files in the project, -/// respecting `.gitignore` rules by default. The search is executed on a blocking thread -/// to avoid blocking the async runtime. -/// -/// # Arguments -/// -/// * `query` - The search string to look for in file contents -/// * `state` - The session state containing the project root path -/// -/// # Returns -/// -/// Returns a `Vec` containing: -/// - `path`: The relative path of each matching file -/// - `matches`: The number of matches (currently simplified to 1 per file) -/// -/// # Errors -/// -/// Returns an error if: -/// - No project is currently open -/// - The project root lock cannot be acquired -/// - The search task fails to execute -/// -/// # Note -/// -/// This is a naive implementation that reads entire files into memory. -/// For production use, consider using streaming/buffered reads or the `grep-searcher` crate. -/// Search files implementation (pure function for testing) -pub async fn search_files_impl(query: String, root: PathBuf) -> Result, String> { - let root_clone = root.clone(); - - // Run computationally expensive search on a blocking thread - let results = tauri::async_runtime::spawn_blocking(move || { - let mut matches = Vec::new(); - // default to respecting .gitignore - let walker = WalkBuilder::new(&root_clone).git_ignore(true).build(); - - for result in walker { - match result { - Ok(entry) => { - if !entry.file_type().map(|ft| ft.is_file()).unwrap_or(false) { - continue; - } - - let path = entry.path(); - // Try to read file - // Note: This is a naive implementation reading whole files into memory. - // For production, we should stream/buffer reads or use grep-searcher. - if let Ok(content) = fs::read_to_string(path) { - // Simple substring search (case-sensitive) - if content.contains(&query) { - // Compute relative path for display - let relative = path - .strip_prefix(&root_clone) - .unwrap_or(path) - .to_string_lossy() - .to_string(); - - matches.push(SearchResult { - path: relative, - matches: 1, // Simplified count for now - }); - } - } - } - Err(err) => eprintln!("Error walking dir: {}", err), - } - } - matches - }) - .await - .map_err(|e| format!("Search task failed: {}", e))?; - - Ok(results) -} - -#[tauri::command] -pub async fn search_files( - query: String, - state: State<'_, SessionState>, -) -> Result, String> { - let root = get_project_root(&state)?; - search_files_impl(query, root).await -} - -// ----------------------------------------------------------------------------- -// Tests -// ----------------------------------------------------------------------------- - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - use std::sync::Mutex; - use tempfile::TempDir; - - /// Helper to create a test SessionState with a given root path - fn create_test_state(root: Option) -> SessionState { - let (cancel_tx, cancel_rx) = tokio::sync::watch::channel(false); - SessionState { - project_root: Mutex::new(root), - cancel_tx, - cancel_rx, - } - } - - #[tokio::test] - async fn test_search_files_no_project_open() { - let state = create_test_state(None); - - let result = state.get_project_root(); - - assert!(result.is_err()); - assert_eq!(result.unwrap_err(), "No project is currently open."); - } - - #[tokio::test] - async fn test_search_files_finds_matching_file() { - let temp_dir = TempDir::new().unwrap(); - let test_file = temp_dir.path().join("test.txt"); - fs::write(&test_file, "This is a test file with some content").unwrap(); - - let state = create_test_state(Some(temp_dir.path().to_path_buf())); - - let root = state.get_project_root().unwrap(); - let results = search_files_impl("test".to_string(), root).await.unwrap(); - - assert_eq!(results.len(), 1); - assert_eq!(results[0].path, "test.txt"); - assert_eq!(results[0].matches, 1); - } - - #[tokio::test] - async fn test_search_files_multiple_matches() { - let temp_dir = TempDir::new().unwrap(); - - // Create multiple files with matching content - fs::write(temp_dir.path().join("file1.txt"), "hello world").unwrap(); - fs::write(temp_dir.path().join("file2.txt"), "hello again").unwrap(); - fs::write(temp_dir.path().join("file3.txt"), "goodbye").unwrap(); - - let state = create_test_state(Some(temp_dir.path().to_path_buf())); - - let root = state.get_project_root().unwrap(); - let results = search_files_impl("hello".to_string(), root).await.unwrap(); - - assert_eq!(results.len(), 2); - let paths: Vec<&str> = results.iter().map(|r| r.path.as_str()).collect(); - assert!(paths.contains(&"file1.txt")); - assert!(paths.contains(&"file2.txt")); - } - - #[tokio::test] - async fn test_search_files_no_matches() { - let temp_dir = TempDir::new().unwrap(); - fs::write(temp_dir.path().join("test.txt"), "This is some content").unwrap(); - - let state = create_test_state(Some(temp_dir.path().to_path_buf())); - - let root = state.get_project_root().unwrap(); - let results = search_files_impl("nonexistent".to_string(), root) - .await - .unwrap(); - - assert_eq!(results.len(), 0); - } - - #[tokio::test] - async fn test_search_files_case_sensitive() { - let temp_dir = TempDir::new().unwrap(); - fs::write(temp_dir.path().join("test.txt"), "Hello World").unwrap(); - - let state = create_test_state(Some(temp_dir.path().to_path_buf())); - - // Search for lowercase - should not match - let root = state.get_project_root().unwrap(); - let results = search_files_impl("hello".to_string(), root.clone()) - .await - .unwrap(); - assert_eq!(results.len(), 0); - - // Search for correct case - should match - let results = search_files_impl("Hello".to_string(), root).await.unwrap(); - assert_eq!(results.len(), 1); - } - - #[tokio::test] - async fn test_search_files_nested_directories() { - let temp_dir = TempDir::new().unwrap(); - - // Create nested directory structure - let nested_dir = temp_dir.path().join("subdir"); - fs::create_dir(&nested_dir).unwrap(); - - fs::write(temp_dir.path().join("root.txt"), "match").unwrap(); - fs::write(nested_dir.join("nested.txt"), "match").unwrap(); - - let state = create_test_state(Some(temp_dir.path().to_path_buf())); - - let root = state.get_project_root().unwrap(); - let results = search_files_impl("match".to_string(), root).await.unwrap(); - - assert_eq!(results.len(), 2); - let paths: Vec<&str> = results.iter().map(|r| r.path.as_str()).collect(); - assert!(paths.contains(&"root.txt")); - assert!(paths.contains(&"subdir/nested.txt") || paths.contains(&"subdir\\nested.txt")); - } - - #[tokio::test] - async fn test_search_files_respects_gitignore() { - let temp_dir = TempDir::new().unwrap(); - - // Initialize git repo (required for ignore crate to respect .gitignore) - std::process::Command::new("git") - .args(["init"]) - .current_dir(temp_dir.path()) - .output() - .unwrap(); - - // Create .gitignore - fs::write(temp_dir.path().join(".gitignore"), "ignored.txt\n").unwrap(); - - // Create files - fs::write(temp_dir.path().join("included.txt"), "searchterm").unwrap(); - fs::write(temp_dir.path().join("ignored.txt"), "searchterm").unwrap(); - - let state = create_test_state(Some(temp_dir.path().to_path_buf())); - - let root = state.get_project_root().unwrap(); - let results = search_files_impl("searchterm".to_string(), root) - .await - .unwrap(); - - // Should find the non-ignored file, but not the ignored one - // The gitignore file itself might be included - let has_included = results.iter().any(|r| r.path == "included.txt"); - let has_ignored = results.iter().any(|r| r.path == "ignored.txt"); - - assert!(has_included, "included.txt should be found"); - assert!( - !has_ignored, - "ignored.txt should NOT be found (it's in .gitignore)" - ); - } - - #[tokio::test] - async fn test_search_files_skips_binary_files() { - let temp_dir = TempDir::new().unwrap(); - - // Create a text file - fs::write(temp_dir.path().join("text.txt"), "searchable").unwrap(); - - // Create a binary file (will fail to read as UTF-8) - fs::write(temp_dir.path().join("binary.bin"), [0xFF, 0xFE, 0xFD]).unwrap(); - - let state = create_test_state(Some(temp_dir.path().to_path_buf())); - - let root = state.get_project_root().unwrap(); - let results = search_files_impl("searchable".to_string(), root) - .await - .unwrap(); - - // Should only find the text file - assert_eq!(results.len(), 1); - assert_eq!(results[0].path, "text.txt"); - } - - #[tokio::test] - async fn test_search_files_empty_query() { - let temp_dir = TempDir::new().unwrap(); - fs::write(temp_dir.path().join("test.txt"), "content").unwrap(); - - let state = create_test_state(Some(temp_dir.path().to_path_buf())); - - let root = state.get_project_root().unwrap(); - let results = search_files_impl("".to_string(), root).await.unwrap(); - - // Empty string is contained in all strings, so should match - assert_eq!(results.len(), 1); - } - - #[cfg(unix)] - #[tokio::test] - async fn test_search_files_handles_permission_errors() { - use std::os::unix::fs::PermissionsExt; - - let temp_dir = TempDir::new().unwrap(); - - // Create a subdirectory with a file - let restricted_dir = temp_dir.path().join("restricted"); - fs::create_dir(&restricted_dir).unwrap(); - fs::write(restricted_dir.join("secret.txt"), "searchterm").unwrap(); - - // Remove read permissions from the directory - let mut perms = fs::metadata(&restricted_dir).unwrap().permissions(); - perms.set_mode(0o000); - fs::set_permissions(&restricted_dir, perms).unwrap(); - - // Create an accessible file - fs::write(temp_dir.path().join("accessible.txt"), "searchterm").unwrap(); - - let root = temp_dir.path().to_path_buf(); - let results = search_files_impl("searchterm".to_string(), root) - .await - .unwrap(); - - // Should find the accessible file, walker error for restricted dir is logged but not fatal - assert_eq!(results.len(), 1); - assert_eq!(results[0].path, "accessible.txt"); - - // Restore permissions for cleanup - let mut perms = fs::metadata(&restricted_dir).unwrap().permissions(); - perms.set_mode(0o755); - fs::set_permissions(&restricted_dir, perms).unwrap(); - } - - #[tokio::test] - async fn test_search_files_handles_broken_symlink() { - let temp_dir = TempDir::new().unwrap(); - - // Create a broken symlink (points to non-existent file) - #[cfg(unix)] - { - use std::os::unix::fs as unix_fs; - unix_fs::symlink("/nonexistent/path", temp_dir.path().join("broken_link")).unwrap(); - } - #[cfg(windows)] - { - use std::os::windows::fs as windows_fs; - windows_fs::symlink_file("C:\\nonexistent\\path", temp_dir.path().join("broken_link")) - .unwrap(); - } - - // Create a normal file - fs::write(temp_dir.path().join("normal.txt"), "searchterm").unwrap(); - - let root = temp_dir.path().to_path_buf(); - let results = search_files_impl("searchterm".to_string(), root) - .await - .unwrap(); - - // Should find the normal file, broken symlink error is logged but not fatal - assert_eq!(results.len(), 1); - assert_eq!(results[0].path, "normal.txt"); - } -} diff --git a/src-tauri/src/commands/shell.rs b/src-tauri/src/commands/shell.rs deleted file mode 100644 index 814456d..0000000 --- a/src-tauri/src/commands/shell.rs +++ /dev/null @@ -1,280 +0,0 @@ -use crate::state::SessionState; -use serde::Serialize; -use std::path::PathBuf; -use std::process::Command; -use tauri::State; - -// ----------------------------------------------------------------------------- -// Helper Functions -// ----------------------------------------------------------------------------- - -/// Helper to get the root path (cloned) without joining -fn get_project_root(state: &State<'_, SessionState>) -> Result { - state.inner().get_project_root() -} - -// ----------------------------------------------------------------------------- -// Commands -// ----------------------------------------------------------------------------- - -#[derive(Serialize, Debug)] -pub struct CommandOutput { - stdout: String, - stderr: String, - exit_code: i32, -} - -/// Execute shell command logic (pure function for testing) -async fn exec_shell_impl( - command: String, - args: Vec, - root: PathBuf, -) -> Result { - // Security Allowlist - let allowed_commands = [ - "git", "cargo", "npm", "yarn", "pnpm", "node", "bun", "ls", "find", "grep", "mkdir", "rm", - "mv", "cp", "touch", "rustc", "rustfmt", - ]; - - if !allowed_commands.contains(&command.as_str()) { - return Err(format!("Command '{}' is not in the allowlist.", command)); - } - - let output = tauri::async_runtime::spawn_blocking(move || { - Command::new(&command) - .args(&args) - .current_dir(root) - .output() - }) - .await - .map_err(|e| format!("Task join error: {}", e))? - .map_err(|e| format!("Failed to execute command: {}", e))?; - - Ok(CommandOutput { - stdout: String::from_utf8_lossy(&output.stdout).to_string(), - stderr: String::from_utf8_lossy(&output.stderr).to_string(), - exit_code: output.status.code().unwrap_or(-1), - }) -} - -#[tauri::command] -pub async fn exec_shell( - command: String, - args: Vec, - state: State<'_, SessionState>, -) -> Result { - let root = get_project_root(&state)?; - exec_shell_impl(command, args, root).await -} - -// ----------------------------------------------------------------------------- -// Tests -// ----------------------------------------------------------------------------- - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - use std::sync::Mutex; - use tempfile::TempDir; - - /// Helper to create a test SessionState with a given root path - fn create_test_state(root: Option) -> SessionState { - let (cancel_tx, cancel_rx) = tokio::sync::watch::channel(false); - SessionState { - project_root: Mutex::new(root), - cancel_tx, - cancel_rx, - } - } - - // Tests for get_project_root helper function - mod get_project_root_tests { - use super::*; - - #[test] - fn test_get_project_root_no_project() { - let state = create_test_state(None); - let result = state.get_project_root(); - - assert!(result.is_err()); - assert_eq!(result.unwrap_err(), "No project is currently open."); - } - - #[test] - fn test_get_project_root_success() { - let temp_dir = TempDir::new().unwrap(); - let path = temp_dir.path().to_path_buf(); - let state = create_test_state(Some(path.clone())); - let result = state.get_project_root(); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), path); - } - } - - // Tests for exec_shell command - mod exec_shell_tests { - use super::*; - - #[tokio::test] - async fn test_exec_shell_success() { - let temp_dir = TempDir::new().unwrap(); - fs::write(temp_dir.path().join("test.txt"), "hello").unwrap(); - - let state = create_test_state(Some(temp_dir.path().to_path_buf())); - - let root = state.get_project_root().unwrap(); - let result = exec_shell_impl("ls".to_string(), vec![], root).await; - - assert!(result.is_ok()); - let output = result.unwrap(); - assert!(output.stdout.contains("test.txt")); - assert_eq!(output.exit_code, 0); - } - - #[tokio::test] - async fn test_exec_shell_with_args() { - let temp_dir = TempDir::new().unwrap(); - fs::write(temp_dir.path().join("test.txt"), "hello world").unwrap(); - - let state = create_test_state(Some(temp_dir.path().to_path_buf())); - - // Use grep to search in file - let root = state.get_project_root().unwrap(); - let result = exec_shell_impl( - "grep".to_string(), - vec!["hello".to_string(), "test.txt".to_string()], - root, - ) - .await; - - assert!(result.is_ok()); - let output = result.unwrap(); - assert!(output.stdout.contains("hello world")); - assert_eq!(output.exit_code, 0); - } - - #[tokio::test] - async fn test_exec_shell_command_not_in_allowlist() { - let temp_dir = TempDir::new().unwrap(); - let state = create_test_state(Some(temp_dir.path().to_path_buf())); - - let root = state.get_project_root().unwrap(); - let result = exec_shell_impl("curl".to_string(), vec![], root).await; - - assert!(result.is_err()); - assert!(result.unwrap_err().contains("not in the allowlist")); - } - - #[tokio::test] - async fn test_exec_shell_no_project_open() { - let state = create_test_state(None); - - let result = state.get_project_root(); - - assert!(result.is_err()); - assert_eq!(result.unwrap_err(), "No project is currently open."); - } - - #[tokio::test] - async fn test_exec_shell_command_failure() { - let temp_dir = TempDir::new().unwrap(); - let state = create_test_state(Some(temp_dir.path().to_path_buf())); - - // Try to list a nonexistent file - let root = state.get_project_root().unwrap(); - let result = exec_shell_impl( - "ls".to_string(), - vec!["nonexistent_file_xyz.txt".to_string()], - root, - ) - .await; - - assert!(result.is_ok()); - let output = result.unwrap(); - assert!(!output.stderr.is_empty()); - assert_ne!(output.exit_code, 0); - } - - #[tokio::test] - async fn test_exec_shell_git_command() { - let temp_dir = TempDir::new().unwrap(); - - // Initialize git repo - std::process::Command::new("git") - .args(["init"]) - .current_dir(temp_dir.path()) - .output() - .unwrap(); - - let state = create_test_state(Some(temp_dir.path().to_path_buf())); - - let root = state.get_project_root().unwrap(); - let result = exec_shell_impl("git".to_string(), vec!["status".to_string()], root).await; - - assert!(result.is_ok()); - let output = result.unwrap(); - assert_eq!(output.exit_code, 0); - assert!(!output.stdout.is_empty()); - } - - #[tokio::test] - async fn test_exec_shell_mkdir_command() { - let temp_dir = TempDir::new().unwrap(); - let state = create_test_state(Some(temp_dir.path().to_path_buf())); - - let root = state.get_project_root().unwrap(); - let result = - exec_shell_impl("mkdir".to_string(), vec!["test_dir".to_string()], root).await; - - assert!(result.is_ok()); - let output = result.unwrap(); - assert_eq!(output.exit_code, 0); - assert!(temp_dir.path().join("test_dir").exists()); - } - - #[tokio::test] - async fn test_exec_shell_all_allowed_commands() { - let temp_dir = TempDir::new().unwrap(); - let state = create_test_state(Some(temp_dir.path().to_path_buf())); - - let allowed_commands = [ - "git", "cargo", "npm", "yarn", "pnpm", "node", "bun", "ls", "find", "grep", - "mkdir", "rm", "mv", "cp", "touch", "rustc", "rustfmt", - ]; - - let root = state.get_project_root().unwrap(); - for cmd in allowed_commands { - // Just verify the command is allowed, not necessarily successful - let result = exec_shell_impl(cmd.to_string(), vec![], root.clone()).await; - - // Should not fail with "not in allowlist" error - if result.is_err() { - assert!(!result.unwrap_err().contains("not in the allowlist")); - } - } - } - - #[tokio::test] - async fn test_exec_shell_output_encoding() { - let temp_dir = TempDir::new().unwrap(); - fs::write(temp_dir.path().join("test.txt"), "Hello 世界").unwrap(); - - let state = create_test_state(Some(temp_dir.path().to_path_buf())); - - let root = state.get_project_root().unwrap(); - let result = exec_shell_impl( - "grep".to_string(), - vec!["Hello".to_string(), "test.txt".to_string()], - root, - ) - .await; - - assert!(result.is_ok()); - let output = result.unwrap(); - // Should handle UTF-8 content - assert!(output.stdout.contains("Hello")); - } - } -} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs deleted file mode 100644 index deeb908..0000000 --- a/src-tauri/src/lib.rs +++ /dev/null @@ -1,36 +0,0 @@ -mod commands; -mod llm; -mod state; - -#[cfg(test)] -pub mod test_utils; - -use state::SessionState; - -#[cfg_attr(mobile, tauri::mobile_entry_point)] -pub fn run() { - tauri::Builder::default() - .plugin(tauri_plugin_opener::init()) - .plugin(tauri_plugin_dialog::init()) - .plugin(tauri_plugin_store::Builder::default().build()) - .manage(SessionState::default()) - .invoke_handler(tauri::generate_handler![ - commands::fs::open_project, - commands::fs::close_project, - commands::fs::get_current_project, - commands::fs::get_model_preference, - commands::fs::set_model_preference, - commands::fs::read_file, - commands::fs::write_file, - commands::fs::list_directory, - commands::search::search_files, - commands::shell::exec_shell, - commands::chat::chat, - commands::chat::get_ollama_models, - commands::chat::cancel_chat, - commands::chat::get_anthropic_api_key_exists, - commands::chat::set_anthropic_api_key - ]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} diff --git a/src-tauri/src/llm/types.rs b/src-tauri/src/llm/types.rs deleted file mode 100644 index 101d5a0..0000000 --- a/src-tauri/src/llm/types.rs +++ /dev/null @@ -1,353 +0,0 @@ -use async_trait::async_trait; -use serde::{Deserialize, Serialize}; -use std::fmt::Debug; - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum Role { - System, - User, - Assistant, - Tool, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Message { - pub role: Role, - pub content: String, - - // For assistant messages that request tool execution - #[serde(skip_serializing_if = "Option::is_none")] - pub tool_calls: Option>, - - // For tool output messages, we need to link back to the call ID - // Note: OpenAI uses 'tool_call_id', Ollama sometimes just relies on sequence. - // We will include it for compatibility. - #[serde(skip_serializing_if = "Option::is_none")] - pub tool_call_id: Option, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct ToolCall { - // ID is required by OpenAI, optional/generated for Ollama depending on version - pub id: Option, - pub function: FunctionCall, - #[serde(rename = "type")] - pub kind: String, // usually "function" -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct FunctionCall { - pub name: String, - pub arguments: String, // JSON string of arguments -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct ToolDefinition { - #[serde(rename = "type")] - pub kind: String, // "function" - pub function: ToolFunctionDefinition, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct ToolFunctionDefinition { - pub name: String, - pub description: String, - pub parameters: serde_json::Value, // JSON Schema object -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct CompletionResponse { - pub content: Option, - pub tool_calls: Option>, -} - -/// The abstraction for different LLM providers (Ollama, Anthropic, etc.) -#[async_trait] -#[allow(dead_code)] -pub trait ModelProvider: Send + Sync { - async fn chat( - &self, - model: &str, - messages: &[Message], - tools: &[ToolDefinition], - ) -> Result; -} - -// ----------------------------------------------------------------------------- -// Tests -// ----------------------------------------------------------------------------- - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_role_serialization() { - let system = Role::System; - let user = Role::User; - let assistant = Role::Assistant; - let tool = Role::Tool; - - assert_eq!(serde_json::to_string(&system).unwrap(), r#""system""#); - assert_eq!(serde_json::to_string(&user).unwrap(), r#""user""#); - assert_eq!(serde_json::to_string(&assistant).unwrap(), r#""assistant""#); - assert_eq!(serde_json::to_string(&tool).unwrap(), r#""tool""#); - } - - #[test] - fn test_role_deserialization() { - let system: Role = serde_json::from_str(r#""system""#).unwrap(); - let user: Role = serde_json::from_str(r#""user""#).unwrap(); - let assistant: Role = serde_json::from_str(r#""assistant""#).unwrap(); - let tool: Role = serde_json::from_str(r#""tool""#).unwrap(); - - assert_eq!(system, Role::System); - assert_eq!(user, Role::User); - assert_eq!(assistant, Role::Assistant); - assert_eq!(tool, Role::Tool); - } - - #[test] - fn test_message_serialization_simple() { - let msg = Message { - role: Role::User, - content: "Hello".to_string(), - tool_calls: None, - tool_call_id: None, - }; - - let json = serde_json::to_string(&msg).unwrap(); - assert!(json.contains(r#""role":"user""#)); - assert!(json.contains(r#""content":"Hello""#)); - assert!(!json.contains("tool_calls")); - assert!(!json.contains("tool_call_id")); - } - - #[test] - fn test_message_serialization_with_tool_calls() { - let msg = Message { - role: Role::Assistant, - content: "I'll help you with that".to_string(), - tool_calls: Some(vec![ToolCall { - id: Some("call_123".to_string()), - function: FunctionCall { - name: "read_file".to_string(), - arguments: r#"{"path":"test.txt"}"#.to_string(), - }, - kind: "function".to_string(), - }]), - tool_call_id: None, - }; - - let json = serde_json::to_string(&msg).unwrap(); - assert!(json.contains(r#""role":"assistant""#)); - assert!(json.contains("tool_calls")); - assert!(json.contains("read_file")); - } - - #[test] - fn test_message_deserialization() { - let json = r#"{ - "role": "user", - "content": "Hello world" - }"#; - - let msg: Message = serde_json::from_str(json).unwrap(); - assert_eq!(msg.role, Role::User); - assert_eq!(msg.content, "Hello world"); - assert!(msg.tool_calls.is_none()); - assert!(msg.tool_call_id.is_none()); - } - - #[test] - fn test_tool_call_serialization() { - let tool_call = ToolCall { - id: Some("call_abc".to_string()), - function: FunctionCall { - name: "write_file".to_string(), - arguments: r#"{"path":"out.txt","content":"data"}"#.to_string(), - }, - kind: "function".to_string(), - }; - - let json = serde_json::to_string(&tool_call).unwrap(); - assert!(json.contains(r#""id":"call_abc""#)); - assert!(json.contains(r#""name":"write_file""#)); - assert!(json.contains(r#""type":"function""#)); - } - - #[test] - fn test_tool_call_deserialization() { - let json = r#"{ - "id": "call_xyz", - "function": { - "name": "list_directory", - "arguments": "{\"path\":\".\"}" - }, - "type": "function" - }"#; - - let tool_call: ToolCall = serde_json::from_str(json).unwrap(); - assert_eq!(tool_call.id, Some("call_xyz".to_string())); - assert_eq!(tool_call.function.name, "list_directory"); - assert_eq!(tool_call.kind, "function"); - } - - #[test] - fn test_function_call_with_complex_arguments() { - let func_call = FunctionCall { - name: "exec_shell".to_string(), - arguments: r#"{"command":"git","args":["status","--short"]}"#.to_string(), - }; - - let json = serde_json::to_string(&func_call).unwrap(); - assert!(json.contains("exec_shell")); - assert!(json.contains("git")); - assert!(json.contains("status")); - } - - #[test] - fn test_tool_definition_serialization() { - let tool_def = ToolDefinition { - kind: "function".to_string(), - function: ToolFunctionDefinition { - name: "read_file".to_string(), - description: "Reads a file from disk".to_string(), - parameters: serde_json::json!({ - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "File path" - } - }, - "required": ["path"] - }), - }, - }; - - let json = serde_json::to_string(&tool_def).unwrap(); - assert!(json.contains(r#""type":"function""#)); - assert!(json.contains("read_file")); - assert!(json.contains("Reads a file from disk")); - } - - #[test] - fn test_tool_definition_deserialization() { - let json = r#"{ - "type": "function", - "function": { - "name": "search_files", - "description": "Search for files", - "parameters": { - "type": "object", - "properties": { - "query": { - "type": "string" - } - }, - "required": ["query"] - } - } - }"#; - - let tool_def: ToolDefinition = serde_json::from_str(json).unwrap(); - assert_eq!(tool_def.kind, "function"); - assert_eq!(tool_def.function.name, "search_files"); - assert_eq!(tool_def.function.description, "Search for files"); - } - - #[test] - fn test_completion_response_with_content() { - let response = CompletionResponse { - content: Some("Here is the answer".to_string()), - tool_calls: None, - }; - - assert!(response.content.is_some()); - assert!(response.tool_calls.is_none()); - } - - #[test] - fn test_completion_response_with_tool_calls() { - let response = CompletionResponse { - content: None, - tool_calls: Some(vec![ToolCall { - id: Some("call_1".to_string()), - function: FunctionCall { - name: "test_func".to_string(), - arguments: "{}".to_string(), - }, - kind: "function".to_string(), - }]), - }; - - assert!(response.content.is_none()); - assert!(response.tool_calls.is_some()); - assert_eq!(response.tool_calls.unwrap().len(), 1); - } - - #[test] - fn test_message_with_tool_call_id() { - let msg = Message { - role: Role::Tool, - content: "File content here".to_string(), - tool_calls: None, - tool_call_id: Some("call_123".to_string()), - }; - - let json = serde_json::to_string(&msg).unwrap(); - assert!(json.contains(r#""role":"tool""#)); - assert!(json.contains(r#""tool_call_id":"call_123""#)); - } - - #[test] - fn test_tool_call_without_id() { - let tool_call = ToolCall { - id: None, - function: FunctionCall { - name: "test".to_string(), - arguments: "{}".to_string(), - }, - kind: "function".to_string(), - }; - - let json = serde_json::to_string(&tool_call).unwrap(); - // When id is None, it serializes as null, not omitted - assert!(json.contains(r#""id":null"#)); - } - - #[test] - fn test_message_clone() { - let msg = Message { - role: Role::User, - content: "test".to_string(), - tool_calls: None, - tool_call_id: None, - }; - - let cloned = msg.clone(); - assert_eq!(msg.role, cloned.role); - assert_eq!(msg.content, cloned.content); - } - - #[test] - fn test_role_equality() { - assert_eq!(Role::User, Role::User); - assert_ne!(Role::User, Role::Assistant); - assert_ne!(Role::System, Role::Tool); - } - - #[test] - fn test_tool_function_definition_with_no_parameters() { - let func_def = ToolFunctionDefinition { - name: "simple_tool".to_string(), - description: "A simple tool".to_string(), - parameters: serde_json::json!({}), - }; - - assert_eq!(func_def.name, "simple_tool"); - assert_eq!(func_def.parameters, serde_json::json!({})); - } -} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs deleted file mode 100644 index 8b26516..0000000 --- a/src-tauri/src/main.rs +++ /dev/null @@ -1,6 +0,0 @@ -// Prevents additional console window on Windows in release, DO NOT REMOVE!! -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - -fn main() { - living_spec_standalone_lib::run() -} diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs deleted file mode 100644 index 13414b8..0000000 --- a/src-tauri/src/state.rs +++ /dev/null @@ -1,215 +0,0 @@ -use std::path::PathBuf; -use std::sync::Mutex; -use tokio::sync::watch; - -pub struct SessionState { - pub project_root: Mutex>, - pub cancel_tx: watch::Sender, - pub cancel_rx: watch::Receiver, -} - -impl Default for SessionState { - fn default() -> Self { - let (cancel_tx, cancel_rx) = watch::channel(false); - Self { - project_root: Mutex::new(None), - cancel_tx, - cancel_rx, - } - } -} - -impl SessionState { - /// Get the project root path from the session state - /// Returns an error if no project is currently open - pub fn get_project_root(&self) -> Result { - let root_guard = self.project_root.lock().map_err(|e| e.to_string())?; - let root = root_guard - .as_ref() - .ok_or_else(|| "No project is currently open.".to_string())?; - Ok(root.clone()) - } -} - -// ----------------------------------------------------------------------------- -// Tests -// ----------------------------------------------------------------------------- - -#[cfg(test)] -mod tests { - use super::*; - use std::path::PathBuf; - - #[test] - fn test_session_state_default() { - let state = SessionState::default(); - - // Check that project_root is None - let root = state.project_root.lock().unwrap(); - assert!(root.is_none()); - } - - #[test] - fn test_session_state_cancel_channel() { - let state = SessionState::default(); - - // Initial value should be false - assert!(!(*state.cancel_rx.borrow())); - - // Send a cancel signal - state.cancel_tx.send(true).unwrap(); - - // Receiver should now see true - assert!(*state.cancel_rx.borrow()); - } - - #[test] - fn test_session_state_set_project_root() { - let state = SessionState::default(); - - // Set a project root - let test_path = PathBuf::from("/test/path"); - { - let mut root = state.project_root.lock().unwrap(); - *root = Some(test_path.clone()); - } - - // Verify it was set - let root = state.project_root.lock().unwrap(); - assert_eq!(root.as_ref().unwrap(), &test_path); - } - - #[test] - fn test_session_state_clear_project_root() { - let state = SessionState::default(); - - // Set a project root - { - let mut root = state.project_root.lock().unwrap(); - *root = Some(PathBuf::from("/test/path")); - } - - // Clear it - { - let mut root = state.project_root.lock().unwrap(); - *root = None; - } - - // Verify it's cleared - let root = state.project_root.lock().unwrap(); - assert!(root.is_none()); - } - - #[test] - fn test_session_state_multiple_cancel_signals() { - let state = SessionState::default(); - - // Send multiple signals - state.cancel_tx.send(true).unwrap(); - assert!(*state.cancel_rx.borrow()); - - state.cancel_tx.send(false).unwrap(); - assert!(!(*state.cancel_rx.borrow())); - - state.cancel_tx.send(true).unwrap(); - assert!(*state.cancel_rx.borrow()); - } - - #[test] - fn test_session_state_cancel_rx_clone() { - let state = SessionState::default(); - - // Clone the receiver - let rx_clone = state.cancel_rx.clone(); - - // Send a signal - state.cancel_tx.send(true).unwrap(); - - // Both receivers should see the new value - assert!(*state.cancel_rx.borrow()); - assert!(*rx_clone.borrow()); - } - - #[test] - fn test_session_state_mutex_not_poisoned() { - let state = SessionState::default(); - - // Lock and unlock multiple times - for i in 0..5 { - let mut root = state.project_root.lock().unwrap(); - *root = Some(PathBuf::from(format!("/path/{}", i))); - } - - // Should still be able to lock - let root = state.project_root.lock().unwrap(); - assert!(root.is_some()); - } - - #[test] - fn test_session_state_project_root_with_different_paths() { - let state = SessionState::default(); - - let paths = vec![ - PathBuf::from("/absolute/path"), - PathBuf::from("relative/path"), - PathBuf::from("./current/dir"), - PathBuf::from("../parent/dir"), - ]; - - for path in paths { - let mut root = state.project_root.lock().unwrap(); - *root = Some(path.clone()); - drop(root); - - let root = state.project_root.lock().unwrap(); - assert_eq!(root.as_ref().unwrap(), &path); - } - } - - #[test] - fn test_session_state_cancel_channel_independent_of_root() { - let state = SessionState::default(); - - // Set project root - { - let mut root = state.project_root.lock().unwrap(); - *root = Some(PathBuf::from("/test")); - } - - // Cancel channel should still work independently - state.cancel_tx.send(true).unwrap(); - assert!(*state.cancel_rx.borrow()); - - // Clear project root - { - let mut root = state.project_root.lock().unwrap(); - *root = None; - } - - // Cancel channel should still work - state.cancel_tx.send(false).unwrap(); - assert!(!(*state.cancel_rx.borrow())); - } - - #[test] - fn test_session_state_multiple_instances() { - let state1 = SessionState::default(); - let state2 = SessionState::default(); - - // Set different values - { - let mut root1 = state1.project_root.lock().unwrap(); - *root1 = Some(PathBuf::from("/path1")); - } - { - let mut root2 = state2.project_root.lock().unwrap(); - *root2 = Some(PathBuf::from("/path2")); - } - - // Verify they're independent - let root1 = state1.project_root.lock().unwrap(); - let root2 = state2.project_root.lock().unwrap(); - assert_eq!(root1.as_ref().unwrap(), &PathBuf::from("/path1")); - assert_eq!(root2.as_ref().unwrap(), &PathBuf::from("/path2")); - } -} diff --git a/src-tauri/src/test_utils.rs b/src-tauri/src/test_utils.rs deleted file mode 100644 index 41f092b..0000000 --- a/src-tauri/src/test_utils.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::commands::fs::StoreOps; -use serde_json::json; -use std::collections::HashMap; -use std::sync::Mutex; - -/// Mock store for testing - stores data in memory -pub struct MockStore { - data: Mutex>, -} - -impl MockStore { - pub fn new() -> Self { - Self { - data: Mutex::new(HashMap::new()), - } - } - - /// Create a MockStore with initial data - pub fn with_data(initial: HashMap) -> Self { - Self { - data: Mutex::new(initial), - } - } -} - -impl Default for MockStore { - fn default() -> Self { - Self::new() - } -} - -impl StoreOps for MockStore { - fn get(&self, key: &str) -> Option { - self.data.lock().unwrap().get(key).cloned() - } - - fn set(&self, key: &str, value: serde_json::Value) { - self.data.lock().unwrap().insert(key.to_string(), value); - } - - fn delete(&self, key: &str) { - self.data.lock().unwrap().remove(key); - } - - fn save(&self) -> Result<(), String> { - // Mock implementation - always succeeds - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_mock_store_new() { - let store = MockStore::new(); - assert!(store.get("key").is_none()); - } - - #[test] - fn test_mock_store_set_and_get() { - let store = MockStore::new(); - store.set("key", json!("value")); - assert_eq!(store.get("key"), Some(json!("value"))); - } - - #[test] - fn test_mock_store_delete() { - let store = MockStore::new(); - store.set("key", json!("value")); - store.delete("key"); - assert!(store.get("key").is_none()); - } - - #[test] - fn test_mock_store_save() { - let store = MockStore::new(); - assert!(store.save().is_ok()); - } - - #[test] - fn test_mock_store_overwrite() { - let store = MockStore::new(); - store.set("key", json!("old")); - store.set("key", json!("new")); - assert_eq!(store.get("key"), Some(json!("new"))); - } - - #[test] - fn test_mock_store_multiple_keys() { - let store = MockStore::new(); - store.set("key1", json!("value1")); - store.set("key2", json!("value2")); - assert_eq!(store.get("key1"), Some(json!("value1"))); - assert_eq!(store.get("key2"), Some(json!("value2"))); - } - - #[test] - fn test_mock_store_with_data() { - let mut initial = HashMap::new(); - initial.insert("existing".to_string(), json!("data")); - - let store = MockStore::with_data(initial); - assert_eq!(store.get("existing"), Some(json!("data"))); - } -} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json deleted file mode 100644 index 32cb64c..0000000 --- a/src-tauri/tauri.conf.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "$schema": "https://schema.tauri.app/config/2", - "productName": "living-spec-standalone", - "version": "0.1.0", - "identifier": "io.crashlabs.living-spec-standalone", - "build": { - "beforeDevCommand": "pnpm dev", - "devUrl": "http://localhost:1420", - "beforeBuildCommand": "pnpm build", - "frontendDist": "../dist" - }, - "app": { - "windows": [ - { - "title": "living-spec-standalone", - "width": 800, - "height": 600 - } - ], - "security": { - "csp": null - } - }, - "bundle": { - "active": true, - "targets": "all", - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ] - } -} diff --git a/src/App.tsx b/src/App.tsx deleted file mode 100644 index bd6fc2f..0000000 --- a/src/App.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { invoke } from "@tauri-apps/api/core"; -import { open } from "@tauri-apps/plugin-dialog"; -import { useEffect, useState } from "react"; -import { Chat } from "./components/Chat"; -import "./App.css"; - -function App() { - const [projectPath, setProjectPath] = useState(null); - const [errorMsg, setErrorMsg] = useState(null); - - useEffect(() => { - invoke("get_current_project") - .then((path) => { - if (path) setProjectPath(path); - }) - .catch((e) => console.error(e)); - }, []); - - async function closeProject() { - try { - await invoke("close_project"); - setProjectPath(null); - } catch (e) { - console.error(e); - } - } - - async function selectProject() { - try { - setErrorMsg(null); - // Open native folder picker - const selected = await open({ - directory: true, - multiple: false, - }); - - if (selected === null) { - // User cancelled selection - return; - } - - // Invoke backend command to verify and set state - // Note: invoke argument names must match Rust function args - const confirmedPath = await invoke("open_project", { - path: selected, - }); - setProjectPath(confirmedPath); - } catch (e) { - console.error(e); - setErrorMsg( - typeof e === "string" ? e : "An error occurred opening the project.", - ); - } - } - - return ( -
- {!projectPath ? ( -
-

AI Code Assistant

-

- Please select a project folder to start the Story-Driven Spec - Workflow. -

- -
- ) : ( -
- -
- )} - - {errorMsg && ( -
-

Error: {errorMsg}

-
- )} -
- ); -} - -export default App; diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx deleted file mode 100644 index 9c272a1..0000000 --- a/src/components/Chat.tsx +++ /dev/null @@ -1,933 +0,0 @@ -import { invoke } from "@tauri-apps/api/core"; -import { listen } from "@tauri-apps/api/event"; -import { ask } from "@tauri-apps/plugin-dialog"; -import { useEffect, useRef, useState } from "react"; -import Markdown from "react-markdown"; -import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; -import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism"; -import type { Message, ProviderConfig } from "../types"; - -interface ChatProps { - projectPath: string; - onCloseProject: () => void; -} - -export function Chat({ projectPath, onCloseProject }: ChatProps) { - const [messages, setMessages] = useState([]); - const [input, setInput] = useState(""); - const [loading, setLoading] = useState(false); - const [model, setModel] = useState("llama3.1"); // Default local model - const [enableTools, setEnableTools] = useState(true); - const [availableModels, setAvailableModels] = useState([]); - const [claudeModels] = useState([ - "claude-3-5-sonnet-20241022", - "claude-3-5-haiku-20241022", - ]); - const [streamingContent, setStreamingContent] = useState(""); - const [showApiKeyDialog, setShowApiKeyDialog] = useState(false); - const [apiKeyInput, setApiKeyInput] = useState(""); - const messagesEndRef = useRef(null); - const inputRef = useRef(null); - const scrollContainerRef = useRef(null); - const shouldAutoScrollRef = useRef(true); - const lastScrollTopRef = useRef(0); - const userScrolledUpRef = useRef(false); - const pendingMessageRef = useRef(""); - - // Token estimation and context window tracking - const estimateTokens = (text: string): number => { - return Math.ceil(text.length / 4); - }; - - const getContextWindowSize = (modelName: string): number => { - if (modelName.startsWith("claude-")) return 200000; - if (modelName.includes("llama3")) return 8192; - if (modelName.includes("qwen2.5")) return 32768; - if (modelName.includes("deepseek")) return 16384; - return 8192; // Default - }; - - const calculateContextUsage = (): { - used: number; - total: number; - percentage: number; - } => { - let totalTokens = 0; - - // System prompts (approximate) - totalTokens += 200; - - // All messages - for (const msg of messages) { - totalTokens += estimateTokens(msg.content); - if (msg.tool_calls) { - totalTokens += estimateTokens(JSON.stringify(msg.tool_calls)); - } - } - - // Streaming content - if (streamingContent) { - totalTokens += estimateTokens(streamingContent); - } - - const contextWindow = getContextWindowSize(model); - const percentage = Math.round((totalTokens / contextWindow) * 100); - - return { - used: totalTokens, - total: contextWindow, - percentage, - }; - }; - - const contextUsage = calculateContextUsage(); - - const getContextEmoji = (percentage: number): string => { - if (percentage >= 90) return "🔴"; - if (percentage >= 75) return "🟡"; - return "🟢"; - }; - - useEffect(() => { - invoke("get_ollama_models") - .then(async (models) => { - if (models.length > 0) { - // Sort models alphabetically (case-insensitive) - const sortedModels = models.sort((a, b) => - a.toLowerCase().localeCompare(b.toLowerCase()), - ); - setAvailableModels(sortedModels); - - // Check backend store for saved model - try { - const savedModel = await invoke( - "get_model_preference", - ); - if (savedModel) { - setModel(savedModel); - } else if (models.length > 0) { - setModel(models[0]); - } - } catch (e) { - console.error(e); - } - } - }) - .catch((err) => console.error(err)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - const unlistenUpdatePromise = listen("chat:update", (event) => { - setMessages(event.payload); - setStreamingContent(""); // Clear streaming content when final update arrives - }); - - const unlistenTokenPromise = listen("chat:token", (event) => { - setStreamingContent((prev) => prev + event.payload); - }); - - return () => { - unlistenUpdatePromise.then((unlisten) => unlisten()); - unlistenTokenPromise.then((unlisten) => unlisten()); - }; - }, []); - - const scrollToBottom = () => { - const element = scrollContainerRef.current; - if (element) { - element.scrollTop = element.scrollHeight; - lastScrollTopRef.current = element.scrollHeight; - } - }; - - const handleScroll = () => { - const element = scrollContainerRef.current; - if (!element) return; - - const currentScrollTop = element.scrollTop; - const isAtBottom = - element.scrollHeight - element.scrollTop - element.clientHeight < 5; - - // Detect if user scrolled UP - if (currentScrollTop < lastScrollTopRef.current) { - userScrolledUpRef.current = true; - shouldAutoScrollRef.current = false; - } - - // If user scrolled back to bottom, re-enable auto-scroll - if (isAtBottom) { - userScrolledUpRef.current = false; - shouldAutoScrollRef.current = true; - } - - lastScrollTopRef.current = currentScrollTop; - }; - - // Smart auto-scroll: only scroll if user hasn't scrolled up - // biome-ignore lint/correctness/useExhaustiveDependencies: We intentionally trigger on messages/streamingContent changes - useEffect(() => { - if (shouldAutoScrollRef.current && !userScrolledUpRef.current) { - scrollToBottom(); - } - }, [messages, streamingContent]); - - useEffect(() => { - inputRef.current?.focus(); - }, []); - - const cancelGeneration = async () => { - try { - await invoke("cancel_chat"); - - // Preserve any partial streaming content as a message - if (streamingContent) { - setMessages((prev) => [ - ...prev, - { role: "assistant", content: streamingContent }, - ]); - setStreamingContent(""); - } - - setLoading(false); - } catch (e) { - console.error("Failed to cancel chat:", e); - } - }; - - const sendMessage = async (messageOverride?: string) => { - const messageToSend = messageOverride ?? input; - if (!messageToSend.trim() || loading) return; - - // Check if using Claude and API key is required - if (model.startsWith("claude-")) { - const hasKey = await invoke("get_anthropic_api_key_exists"); - if (!hasKey) { - // Store the pending message before showing the dialog - pendingMessageRef.current = messageToSend; - setShowApiKeyDialog(true); - return; - } - } - - const userMsg: Message = { role: "user", content: messageToSend }; - const newHistory = [...messages, userMsg]; - - setMessages(newHistory); - // Clear input field (works for both direct input and override scenarios) - if (!messageOverride || messageOverride === input) { - setInput(""); - } - setLoading(true); - setStreamingContent(""); // Clear any previous streaming content - - try { - const config: ProviderConfig = { - provider: model.startsWith("claude-") ? "anthropic" : "ollama", - model: model, - base_url: "http://localhost:11434", - enable_tools: enableTools, - }; - - // Invoke backend chat command - // We rely on 'chat:update' events to update the state in real-time - await invoke("chat", { - messages: newHistory, - config: config, - }); - } catch (e) { - console.error("Chat error:", e); - // Don't show error message if user cancelled - const errorMessage = String(e); - if (!errorMessage.includes("Chat cancelled by user")) { - setMessages((prev) => [ - ...prev, - { role: "assistant", content: `**Error:** ${e}` }, - ]); - } - } finally { - setLoading(false); - } - }; - - const handleSaveApiKey = async () => { - if (!apiKeyInput.trim()) return; - - try { - await invoke("set_anthropic_api_key", { apiKey: apiKeyInput }); - setShowApiKeyDialog(false); - setApiKeyInput(""); - - // Restore the pending message and retry - const pendingMessage = pendingMessageRef.current; - pendingMessageRef.current = ""; - - if (pendingMessage.trim()) { - // Pass the message directly to avoid state timing issues - sendMessage(pendingMessage); - } - } catch (e) { - console.error("Failed to save API key:", e); - alert(`Failed to save API key: ${e}`); - } - }; - - const clearSession = async () => { - const confirmed = await ask( - "Are you sure? This will clear all messages and reset the conversation context.", - { - title: "New Session", - kind: "warning", - }, - ); - - if (confirmed) { - // Cancel any in-flight backend requests first - try { - await invoke("cancel_chat"); - } catch (e) { - console.error("Failed to cancel chat:", e); - } - - // Then clear frontend state - setMessages([]); - setStreamingContent(""); - setLoading(false); - } - }; - - return ( -
- {/* Sticky Header */} -
- {/* Project Info */} -
-
- {projectPath} -
- -
- - {/* Model Controls */} -
- {/* Context Usage Indicator */} -
- {getContextEmoji(contextUsage.percentage)} {contextUsage.percentage} - % -
- - - {availableModels.length > 0 || claudeModels.length > 0 ? ( - - ) : ( - { - const newModel = e.target.value; - setModel(newModel); - invoke("set_model_preference", { model: newModel }).catch( - console.error, - ); - }} - placeholder="Model" - style={{ - padding: "6px 12px", - borderRadius: "99px", - border: "none", - fontSize: "0.9em", - background: "#2f2f2f", - color: "#ececec", - outline: "none", - }} - /> - )} - -
-
- - {/* Messages Area */} -
-
- {messages.map((msg, idx) => ( -
-
- {msg.role === "user" ? ( - msg.content - ) : msg.role === "tool" ? ( -
- - - - Tool Output - {msg.tool_call_id && ` (${msg.tool_call_id})`} - - -
-                      {msg.content}
-                    
-
- ) : ( -
- { - const match = /language-(\w+)/.exec(className || ""); - const isInline = !className; - return !isInline && match ? ( - - {String(children).replace(/\n$/, "")} - - ) : ( - - {children} - - ); - }, - }} - > - {msg.content} - -
- )} - - {/* Show Tool Calls if present */} - {msg.tool_calls && ( -
- {msg.tool_calls.map((tc, i) => { - // Parse arguments to extract key info - let argsSummary = ""; - try { - const args = JSON.parse(tc.function.arguments); - const firstKey = Object.keys(args)[0]; - if (firstKey && args[firstKey]) { - argsSummary = String(args[firstKey]); - // Truncate if too long - if (argsSummary.length > 50) { - argsSummary = `${argsSummary.substring(0, 47)}...`; - } - } - } catch (_e) { - // If parsing fails, just show empty - } - - return ( -
- - - {tc.function.name} - {argsSummary && `(${argsSummary})`} - -
- ); - })} -
- )} -
-
- ))} - {loading && streamingContent && ( -
-
- { - const match = /language-(\w+)/.exec(className || ""); - const isInline = !className; - return !isInline && match ? ( - - {String(children).replace(/\n$/, "")} - - ) : ( - - {children} - - ); - }, - }} - > - {streamingContent} - -
-
- )} - {loading && !streamingContent && ( -
- Thinking... -
- )} -
-
-
- - {/* Input Area */} -
-
- setInput(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") { - sendMessage(); - } - }} - placeholder="Send a message..." - style={{ - flex: 1, - padding: "14px 20px", - borderRadius: "24px", - border: "1px solid #333", - outline: "none", - fontSize: "1rem", - fontWeight: "500", - background: "#2f2f2f", - color: "#ececec", - boxShadow: "0 2px 6px rgba(0,0,0,0.02)", - }} - /> - -
-
- - {/* API Key Dialog */} - {showApiKeyDialog && ( -
-
-

- Enter Anthropic API Key -

-

- To use Claude models, please enter your Anthropic API key. Your - key will be stored securely in your system keychain. -

- setApiKeyInput(e.target.value)} - onKeyDown={(e) => e.key === "Enter" && handleSaveApiKey()} - placeholder="sk-ant-..." - style={{ - width: "100%", - padding: "12px", - borderRadius: "8px", - border: "1px solid #555", - backgroundColor: "#1a1a1a", - color: "#ececec", - fontSize: "1em", - marginBottom: "20px", - outline: "none", - }} - /> -
- - -
-
-
- )} -
- ); -} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts deleted file mode 100644 index 11f02fe..0000000 --- a/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/store.json b/store.json new file mode 100644 index 0000000..0f44fa4 --- /dev/null +++ b/store.json @@ -0,0 +1,4 @@ +{ + "last_project_path": "/Users/dave/workspace/projects/crashlabs/labs/materialist.tech/so-101", + "selected_model": "claude-3-5-sonnet-20241022" +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts deleted file mode 100644 index ddad22a..0000000 --- a/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; - -// @ts-expect-error process is a nodejs global -const host = process.env.TAURI_DEV_HOST; - -// https://vite.dev/config/ -export default defineConfig(async () => ({ - plugins: [react()], - - // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` - // - // 1. prevent Vite from obscuring rust errors - clearScreen: false, - // 2. tauri expects a fixed port, fail if that port is not available - server: { - port: 1420, - strictPort: true, - host: host || false, - hmr: host - ? { - protocol: "ws", - host, - port: 1421, - } - : undefined, - watch: { - // 3. tell Vite to ignore watching `src-tauri` - ignored: ["**/src-tauri/**"], - }, - }, -}));