diff --git a/.gitignore b/.gitignore index fd8e844..058ee93 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ src-tauri/gen/ *.log Thumbs.db .DS_Store + +src-tauri/binaries/* diff --git a/HANDOFF.md b/HANDOFF.md index 2443ce6..161d899 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,20 +1,35 @@ # Handoff ## Current Task -- Capture the current task, branch, and the user-facing outcome here before clearing context. +- Branch: `refactor/cursor-sdk`. +- Refactor PRD/spec generation from Codex/Claude ACP-backed Rust commands to Cursor SDK-backed generation. +- Current slice focuses on PRD/spec generation and settings, not chat migration. ## Key Decisions -- List the decisions that would be expensive to rediscover. +- `@cursor/sdk` cannot be bundled into the Vite webview because it imports Node/Bun runtime modules and native dependencies. +- React composes Cursor PRD/spec prompts and drives the existing generation flow. +- `src/cursorAgentRunner.ts` is the Bun-only SDK entry point; do not import it from webview code. +- Rust delegates Cursor generation to the Bun runner, stores the Cursor API key via OS credential storage, and saves generated Markdown. +- Cursor API keys are never written to `.specforge/settings.json` or localStorage. +- Chat execution still has the legacy Codex/Claude CLI path and is outside this slice. ## Open Questions -- List the questions that still need user input or follow-up implementation. +- Packaged app strategy for the Bun runner and `@cursor/sdk` dependency still needs a deliberate follow-up. +- Chat/execution migration to Cursor SDK remains pending. ## Files Modified -- Record the files changed in the current slice of work. +- Added Cursor SDK runtime files: `src/cursorAgentRunner.ts`, `src/lib/cursorAgentRuntime.ts`, `src-tauri/src/cursor_agent.rs`, `src-tauri/src/secrets.rs`. +- Updated settings/project state, config screens, PRD/spec handlers, runtime bridge, Rust models/project normalization, docs, and dependency manifests. ## Verification -- `bun run build`: -- `cargo check --manifest-path .\src-tauri\Cargo.toml`: +- `bun test`: passed, 77 tests. +- `bunx tsc --noEmit`: passed. +- `bun run lint`: passed with existing warnings. +- `bun run build`: passed. +- `cargo fmt --manifest-path .\src-tauri\Cargo.toml`: ran. +- `cargo check --manifest-path .\src-tauri\Cargo.toml`: passed. ## Next Steps -- List the next 1-3 concrete actions. +- Decide how the Bun Cursor runner should be bundled for packaged desktop builds. +- Migrate chat/execution off legacy Codex/Claude CLI runtime when ready. +- Address existing Biome warnings separately if a clean lint output is desired. diff --git a/README.md b/README.md index 4f8affc..6a9a032 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # SpecForge -SpecForge is a Tauri desktop app for spec-driven development. It helps you load a PRD or technical spec from each document pane, review the source and generated documents side by side, inspect the workspace, and hand an approved spec off to an AI coding agent with configurable autonomy. +SpecForge is a Tauri desktop app for spec-driven development. It helps you configure a project, generate and review PRD/spec documents with Cursor SDK agents, inspect the workspace, and keep approved implementation work visible through the desktop shell. -The current codebase is an MVP shell built with React, Zustand, Tailwind, HeroUI, Tauri, and Rust. It includes a browser-safe demo path for the web UI and a desktop runtime path for real filesystem, git, and CLI access. +The current codebase is an MVP shell built with React, Zustand, Tailwind, HeroUI, Tauri, and Rust. It includes a browser-safe demo path for the web UI and a desktop runtime path for real filesystem, git, secure Cursor API key storage, and document persistence. ## What It Does @@ -10,7 +10,9 @@ The current codebase is an MVP shell built with React, Zustand, Tailwind, HeroUI - Bundles `docs/PRD.md` and `docs/SPEC.md` into the app as the default startup documents. - Scans a workspace folder while respecting `.gitignore`. - Lets you review and edit PRD/spec documents in a split workspace. -- Shows environment health for Claude CLI, Codex CLI, and Git. +- Saves the Cursor API key in the OS credential store, not in `.specforge/settings.json`. +- Shows environment health for Cursor SDK key access and Git. +- Generates PRD/spec Markdown with editable Cursor agent descriptions. - Streams agent output and supports stepped, milestone, and full-autonomy execution modes. - Surfaces git diff review data before approvals. - Falls back to simulated workspace and diff data when running outside the Tauri desktop shell. @@ -39,7 +41,7 @@ The current codebase is an MVP shell built with React, Zustand, Tailwind, HeroUI - Rust toolchain - Tauri desktop prerequisites for your OS - Git -- Optional: local `codex` and `claude` CLIs if you want environment detection and agent handoff to use real binaries +- Cursor API key for PRD/spec generation ## Getting Started @@ -64,7 +66,7 @@ bun run tauri dev Important: - `bun run dev` is useful for UI work, but it uses fallback workspace/diff behavior when Tauri is not present. -- `bun run tauri dev` is required for real file access, workspace scanning, PDF parsing, git diffing, and CLI execution. +- `bun run tauri dev` is required for real file access, workspace scanning, PDF parsing, git diffing, secure Cursor key storage, and document saving. ## Common Commands @@ -87,7 +89,8 @@ bun install --force - The React app never talks to the shell or filesystem directly. - All desktop/runtime operations flow through `src/lib/runtime.ts`. -- Rust commands in `src-tauri/src/lib.rs` own filesystem access, workspace walking, PDF parsing, git diffing, and CLI process control. +- Rust commands in `src-tauri/src/lib.rs` own filesystem access, workspace walking, PDF parsing, git diffing, OS credential storage, and document saving. +- PRD/spec generation is run through a Bun TypeScript runner using `@cursor/sdk`; Rust delegates to that runner and saves the generated Markdown after the frontend receives it. - Payloads crossing the Tauri boundary use camelCase. - The desktop app preserves a demo path in browser mode so the UI can still be explored without native services. @@ -102,4 +105,4 @@ If you change the review flow, model options, import flow, or autonomy behavior, ## Current Status -This repository is an active MVP. The review workspace, import flow, environment scan, diff preview, and simulated execution loop are implemented. Some product ideas documented in `docs/SPEC.md` are still aspirational and should be treated as roadmap material unless they are reflected in the current code. +This repository is an active MVP. The review workspace, import flow, Cursor SDK PRD/spec generation path, environment scan, diff preview, and simulated execution loop are implemented. Chat execution still has legacy CLI runtime code and is intentionally outside the current Cursor SDK refactor scope. diff --git a/bun.lock b/bun.lock index f2dd43a..13c069d 100644 --- a/bun.lock +++ b/bun.lock @@ -5,6 +5,7 @@ "": { "name": "specforge", "dependencies": { + "@cursor/sdk": "^1.0.10", "@heroui/react": "^3.0.2", "@heroui/styles": "^3.0.2", "@tauri-apps/api": "^2.8.0", @@ -96,6 +97,12 @@ "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.11", "", { "os": "win32", "cpu": "x64" }, "sha512-A8D3JM/00C2KQgUV3oj8Ba15EHEYwebAGCy5Sf9GAjr5Y3+kJIYOiESoqRDeuRZueuMdCsbLZIUqmPhpYXJE9A=="], + "@bufbuild/protobuf": ["@bufbuild/protobuf@1.10.0", "", {}, "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag=="], + + "@connectrpc/connect": ["@connectrpc/connect@1.7.0", "", { "peerDependencies": { "@bufbuild/protobuf": "^1.10.0" } }, "sha512-iNKdJRi69YP3mq6AePRT8F/HrxWCewrhxnLMNm0vpqXAR8biwzRtO6Hjx80C6UvtKJ5sFmffQT7I4Baecz389w=="], + + "@connectrpc/connect-node": ["@connectrpc/connect-node@1.7.0", "", { "dependencies": { "undici": "^5.28.4" }, "peerDependencies": { "@bufbuild/protobuf": "^1.10.0", "@connectrpc/connect": "1.7.0" } }, "sha512-6vaPIkG/NyhxlYgytLoR9KYbPhczEboFB2OYWkA9qvUz1K7efXfeGrlRxoLtpa+r8VxyIOw73w5ktNe743nD+A=="], + "@csstools/color-helpers": ["@csstools/color-helpers@5.1.0", "", {}, "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA=="], "@csstools/css-calc": ["@csstools/css-calc@2.1.4", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ=="], @@ -106,6 +113,18 @@ "@csstools/css-tokenizer": ["@csstools/css-tokenizer@3.0.4", "", {}, "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw=="], + "@cursor/sdk": ["@cursor/sdk@1.0.10", "", { "dependencies": { "@bufbuild/protobuf": "1.10.0", "@connectrpc/connect": "^1.6.1", "@connectrpc/connect-node": "^1.6.1", "@statsig/js-client": "3.31.0", "sqlite3": "^5.1.7", "zod": "^3.25.0" }, "optionalDependencies": { "@cursor/sdk-darwin-arm64": "1.0.10", "@cursor/sdk-darwin-x64": "1.0.10", "@cursor/sdk-linux-arm64": "1.0.10", "@cursor/sdk-linux-x64": "1.0.10", "@cursor/sdk-win32-x64": "1.0.10" } }, "sha512-j2y2sbDBgxMPZqXWUyCRfzatpD4h0Vg4SLvVLBV+j65A8m+e9gTdrSUK3eaUdIs9IAAZe1gngP2aOKMw6/tq+Q=="], + + "@cursor/sdk-darwin-arm64": ["@cursor/sdk-darwin-arm64@1.0.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-uwNhyH2fyJhiSHhgWlozeuelBMyjotVN7jmqrPxaBR2Qii4JYmuhlNvo4fiNhojvLjC5EMF1pnM5tr+Uyt/G1g=="], + + "@cursor/sdk-darwin-x64": ["@cursor/sdk-darwin-x64@1.0.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z0IVJB5cfyQ3lHz9MEjyH8bnmpaLRx/eh1E6MKC95lLr5K+1jPITsKgK3P9NwhIl1kc0NEA/z90mXxDOWoc2fg=="], + + "@cursor/sdk-linux-arm64": ["@cursor/sdk-linux-arm64@1.0.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-443sB9wDmlsdMDSgcGbmaNf5H+3IoIFhnmxSACFXbdFYNYj4U6e1TWxJqpl/FI/MjTodQFNQBvGbUo6SUcwj8w=="], + + "@cursor/sdk-linux-x64": ["@cursor/sdk-linux-x64@1.0.10", "", { "os": "linux", "cpu": "x64" }, "sha512-elRt/lsH6xw1LyD4HcPAJINk5q7Apj4F68lmemb0UZOC01w5PfHsjUkURg7CkPWL7PmNgUjxXTaQe3EdEq8now=="], + + "@cursor/sdk-win32-x64": ["@cursor/sdk-win32-x64@1.0.10", "", { "os": "win32", "cpu": "x64" }, "sha512-5Fyb7aZYnSPRQPg/reHpwEw8SDhJHg1W+ARyDCByysI2II59RFqqBdlDay7iwUCKaziemuebFK5KNSVt8WlYTA=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], @@ -158,6 +177,8 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], + "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], + "@formatjs/ecma402-abstract": ["@formatjs/ecma402-abstract@2.3.6", "", { "dependencies": { "@formatjs/fast-memoize": "2.2.7", "@formatjs/intl-localematcher": "0.6.2", "decimal.js": "^10.4.3", "tslib": "^2.8.0" } }, "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw=="], "@formatjs/fast-memoize": ["@formatjs/fast-memoize@2.2.7", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ=="], @@ -168,6 +189,8 @@ "@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.6.2", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA=="], + "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="], + "@heroui/react": ["@heroui/react@3.0.2", "", { "dependencies": { "@heroui/styles": "3.0.2", "@radix-ui/react-avatar": "1.1.11", "@react-aria/i18n": "3.12.16", "@react-aria/ssr": "3.9.10", "@react-aria/utils": "3.33.1", "@react-stately/utils": "3.11.0", "@react-types/color": "3.1.4", "@react-types/shared": "3.33.1", "input-otp": "1.4.2", "react-aria-components": "1.16.0", "tailwind-merge": "3.4.0", "tailwind-variants": "3.2.2" }, "peerDependencies": { "react": ">=19.0.0", "react-dom": ">=19.0.0", "tailwindcss": ">=4.0.0" } }, "sha512-HWcYFurH+OnLITgIvQKyCd6BhYLApyzg0qqL3T5xemK5hgo1Nr+wQGQ5JSNVfBAmF4tWSS9TOzr24UHEO+21Ww=="], "@heroui/styles": ["@heroui/styles@3.0.2", "", { "dependencies": { "tailwind-variants": "3.2.2", "tw-animate-css": "1.4.0" }, "peerDependencies": { "tailwindcss": ">=4.0.0" } }, "sha512-UGohTT5WVgVUqosujtUegGevtNkmKLi/V29+zhT4a1lwCQxY5sD2PMtInk1ImBSqtxzfrb5uPoxySb5v1LptYQ=="], @@ -190,6 +213,10 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@npmcli/fs": ["@npmcli/fs@1.1.1", "", { "dependencies": { "@gar/promisify": "^1.0.1", "semver": "^7.3.5" } }, "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ=="], + + "@npmcli/move-file": ["@npmcli/move-file@1.1.2", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg=="], + "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.11", "", { "dependencies": { "@radix-ui/react-context": "1.1.3", "@radix-ui/react-primitive": "2.1.4", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q=="], "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], @@ -474,6 +501,10 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="], + "@statsig/client-core": ["@statsig/client-core@3.31.0", "", {}, "sha512-SuxQD6TmVszPG7FoMKwTk/uyBuVFk7XnxI3T/E0uyb7PL7GNjONtfsoh+NqBBVUJVse0CUeSFfgJPoZy1ZOslQ=="], + + "@statsig/js-client": ["@statsig/js-client@3.31.0", "", { "dependencies": { "@statsig/client-core": "3.31.0" } }, "sha512-LFa5E0LjT6sTfZv3sNGoyRLSZ1078+agdgOA+Vm1ecjG+KbSOfBLTW7hMwimrJ29slRwbYDzbtKaPJo/R37N2g=="], + "@swc/helpers": ["@swc/helpers@0.5.21", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg=="], "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="], @@ -538,6 +569,8 @@ "@testing-library/react": ["@testing-library/react@16.3.2", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "@testing-library/dom": "^10.0.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g=="], + "@tootallnate/once": ["@tootallnate/once@1.1.2", "", {}, "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="], + "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], @@ -576,32 +609,66 @@ "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + "abbrev": ["abbrev@1.1.1", "", {}, "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], + + "aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="], + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + "aproba": ["aproba@2.1.0", "", {}, "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew=="], + + "are-we-there-yet": ["are-we-there-yet@3.0.1", "", { "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" } }, "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg=="], + "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.17", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-HdrkN8eVG2CXxeifv/VdJ4A4RSra1DTW8dc/hdxzhGHN8QePs6gKaWM9pHPcpCoxYZJuOZ8drHmbdpLHjCYjLA=="], + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + "cacache": ["cacache@15.3.0", "", { "dependencies": { "@npmcli/fs": "^1.0.0", "@npmcli/move-file": "^1.0.1", "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "glob": "^7.1.4", "infer-owner": "^1.0.4", "lru-cache": "^6.0.0", "minipass": "^3.1.1", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.2", "mkdirp": "^1.0.3", "p-map": "^4.0.0", "promise-inflight": "^1.0.1", "rimraf": "^3.0.2", "ssri": "^8.0.1", "tar": "^6.0.2", "unique-filename": "^1.1.1" } }, "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ=="], + "caniuse-lite": ["caniuse-lite@1.0.30001787", "", {}, "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg=="], "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], + "chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], + + "clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="], + "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + "color-support": ["color-support@1.1.3", "", { "bin": { "color-support": "bin.js" } }, "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "console-control-strings": ["console-control-strings@1.1.0", "", {}, "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], @@ -618,8 +685,14 @@ "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + + "delegates": ["delegates@1.0.0", "", {}, "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="], + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], @@ -628,10 +701,20 @@ "electron-to-chromium": ["electron-to-chromium@1.5.334", "", {}, "sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog=="], + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="], + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], @@ -640,36 +723,78 @@ "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + + "fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="], + + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "gauge": ["gauge@4.0.4", "", { "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", "console-control-strings": "^1.1.0", "has-unicode": "^2.0.1", "signal-exit": "^3.0.7", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.5" } }, "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg=="], + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "has-unicode": ["has-unicode@2.0.1", "", {}, "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="], + "html-encoding-sniffer": ["html-encoding-sniffer@4.0.0", "", { "dependencies": { "whatwg-encoding": "^3.1.1" } }, "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ=="], + "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], + "iconoir-react": ["iconoir-react@7.11.0", "", { "peerDependencies": { "react": "18 || 19" } }, "sha512-uvTKtnHYwbbTsmQ6HCcliYd50WK0GbjP497RwdISxKzfS01x4cK1Mn/F2mT/t2roSaJQ0I+KnHxMcyvmNMXWsQ=="], "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], + "infer-owner": ["infer-owner@1.0.4", "", {}, "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A=="], + + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + "input-otp": ["input-otp@1.4.2", "", { "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA=="], "intl-messageformat": ["intl-messageformat@10.7.18", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.6", "@formatjs/fast-memoize": "2.2.7", "@formatjs/icu-messageformat-parser": "2.11.4", "tslib": "^2.8.0" } }, "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g=="], + "ip-address": ["ip-address@10.1.1", "", {}, "sha512-1FMu8/N15Ck1BL551Jf42NYIoin2unWjLQ2Fze/DXryJRl5twqtwNHlO39qERGbIOcKYWHdgRryhOC+NG4eaLw=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="], + "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], @@ -712,18 +837,64 @@ "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "make-fetch-happen": ["make-fetch-happen@9.1.0", "", { "dependencies": { "agentkeepalive": "^4.1.3", "cacache": "^15.2.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^4.0.1", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^6.0.0", "minipass": "^3.1.3", "minipass-collect": "^1.0.2", "minipass-fetch": "^1.3.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.2", "promise-retry": "^2.0.1", "socks-proxy-agent": "^6.0.0", "ssri": "^8.0.0" } }, "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg=="], + + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], + + "minipass-collect": ["minipass-collect@1.0.2", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA=="], + + "minipass-fetch": ["minipass-fetch@1.4.1", "", { "dependencies": { "minipass": "^3.1.0", "minipass-sized": "^1.0.3", "minizlib": "^2.0.0" }, "optionalDependencies": { "encoding": "^0.1.12" } }, "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw=="], + + "minipass-flush": ["minipass-flush@1.0.7", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA=="], + + "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], + + "minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="], + + "minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], + + "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + + "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], + + "node-abi": ["node-abi@3.89.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA=="], + + "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], + + "node-gyp": ["node-gyp@8.4.1", "", { "dependencies": { "env-paths": "^2.2.0", "glob": "^7.1.4", "graceful-fs": "^4.2.6", "make-fetch-happen": "^9.1.0", "nopt": "^5.0.0", "npmlog": "^6.0.0", "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.2", "which": "^2.0.2" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w=="], + "node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="], + "nopt": ["nopt@5.0.0", "", { "dependencies": { "abbrev": "1" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ=="], + + "npmlog": ["npmlog@6.0.2", "", { "dependencies": { "are-we-there-yet": "^3.0.0", "console-control-strings": "^1.1.0", "gauge": "^4.0.3", "set-blocking": "^2.0.0" } }, "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg=="], + "nwsapi": ["nwsapi@2.2.23", "", {}, "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ=="], + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="], + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], @@ -734,10 +905,20 @@ "postcss": ["postcss@8.5.9", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw=="], + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], + "promise-inflight": ["promise-inflight@1.0.1", "", {}, "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="], + + "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], + + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + "react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="], "react-aria": ["react-aria@3.47.0", "", { "dependencies": { "@internationalized/string": "^3.2.7", "@react-aria/breadcrumbs": "^3.5.32", "@react-aria/button": "^3.14.5", "@react-aria/calendar": "^3.9.5", "@react-aria/checkbox": "^3.16.5", "@react-aria/color": "^3.1.5", "@react-aria/combobox": "^3.15.0", "@react-aria/datepicker": "^3.16.1", "@react-aria/dialog": "^3.5.34", "@react-aria/disclosure": "^3.1.3", "@react-aria/dnd": "^3.11.6", "@react-aria/focus": "^3.21.5", "@react-aria/gridlist": "^3.14.4", "@react-aria/i18n": "^3.12.16", "@react-aria/interactions": "^3.27.1", "@react-aria/label": "^3.7.25", "@react-aria/landmark": "^3.0.10", "@react-aria/link": "^3.8.9", "@react-aria/listbox": "^3.15.3", "@react-aria/menu": "^3.21.0", "@react-aria/meter": "^3.4.30", "@react-aria/numberfield": "^3.12.5", "@react-aria/overlays": "^3.31.2", "@react-aria/progress": "^3.4.30", "@react-aria/radio": "^3.12.5", "@react-aria/searchfield": "^3.8.12", "@react-aria/select": "^3.17.3", "@react-aria/selection": "^3.27.2", "@react-aria/separator": "^3.4.16", "@react-aria/slider": "^3.8.5", "@react-aria/ssr": "^3.9.10", "@react-aria/switch": "^3.7.11", "@react-aria/table": "^3.17.11", "@react-aria/tabs": "^3.11.1", "@react-aria/tag": "^3.8.1", "@react-aria/textfield": "^3.18.5", "@react-aria/toast": "^3.0.11", "@react-aria/tooltip": "^3.9.2", "@react-aria/tree": "^3.1.7", "@react-aria/utils": "^3.33.1", "@react-aria/visually-hidden": "^3.8.31", "@react-types/shared": "^3.33.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-nvahimIqdByl/PXk/xPkG30LPRzcin+/Uk0uFfwbbKRRFC9aa22a6BRULZLqVHwa9GaNyKe6CDUxO1Dde4v0kA=="], @@ -756,12 +937,20 @@ "react-stately": ["react-stately@3.45.0", "", { "dependencies": { "@react-stately/calendar": "^3.9.3", "@react-stately/checkbox": "^3.7.5", "@react-stately/collections": "^3.12.10", "@react-stately/color": "^3.9.5", "@react-stately/combobox": "^3.13.0", "@react-stately/data": "^3.15.2", "@react-stately/datepicker": "^3.16.1", "@react-stately/disclosure": "^3.0.11", "@react-stately/dnd": "^3.7.4", "@react-stately/form": "^3.2.4", "@react-stately/list": "^3.13.4", "@react-stately/menu": "^3.9.11", "@react-stately/numberfield": "^3.11.0", "@react-stately/overlays": "^3.6.23", "@react-stately/radio": "^3.11.5", "@react-stately/searchfield": "^3.5.19", "@react-stately/select": "^3.9.2", "@react-stately/selection": "^3.20.9", "@react-stately/slider": "^3.7.5", "@react-stately/table": "^3.15.4", "@react-stately/tabs": "^3.8.9", "@react-stately/toast": "^3.1.3", "@react-stately/toggle": "^3.9.5", "@react-stately/tooltip": "^3.5.11", "@react-stately/tree": "^3.9.6", "@react-types/shared": "^3.33.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-G3bYr0BIiookpt4H05VeZUuVS/FslQAj2TeT8vDfCiL314Y+LtPXIPe/a3eamCA0wljy7z1EDYKV50Qbz7pcJg=="], + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], + + "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + "rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="], "rrweb-cssom": ["rrweb-cssom@0.8.0", "", {}, "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="], @@ -770,18 +959,44 @@ "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], + "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "socks": ["socks@2.8.8", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog=="], + + "socks-proxy-agent": ["socks-proxy-agent@6.2.1", "", { "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", "socks": "^2.6.2" } }, "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "sqlite3": ["sqlite3@5.1.7", "", { "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^7.0.0", "prebuild-install": "^7.1.1", "tar": "^6.1.11" }, "optionalDependencies": { "node-gyp": "8.x" } }, "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog=="], + + "ssri": ["ssri@8.0.1", "", { "dependencies": { "minipass": "^3.1.1" } }, "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ=="], + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="], @@ -794,6 +1009,12 @@ "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="], + "tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], + + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], @@ -816,16 +1037,26 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "unique-filename": ["unique-filename@1.1.1", "", { "dependencies": { "unique-slug": "^2.0.0" } }, "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ=="], + + "unique-slug": ["unique-slug@2.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w=="], + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + "vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="], "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="], @@ -842,20 +1073,30 @@ "whatwg-url": ["whatwg-url@14.2.0", "", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + "wide-align": ["wide-align@1.1.5", "", { "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="], "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="], - "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "zustand": ["zustand@5.0.12", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g=="], "@asamuzakjp/css-color/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "@npmcli/fs/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], @@ -872,6 +1113,48 @@ "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], + "cacache/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + + "cacache/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "make-fetch-happen/http-proxy-agent": ["http-proxy-agent@4.0.1", "", { "dependencies": { "@tootallnate/once": "1", "agent-base": "6", "debug": "4" } }, "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg=="], + + "make-fetch-happen/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + + "make-fetch-happen/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + + "make-fetch-happen/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-collect/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-fetch/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "node-abi/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "node-gyp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "socks-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "ssri/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], + + "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + + "make-fetch-happen/http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "make-fetch-happen/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], } } diff --git a/docs/PRD.md b/docs/PRD.md index 55b727c..47b7e21 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -2,44 +2,60 @@ ## 1. Product Overview -**SpecForge** is a setup-first desktop workspace for project-scoped agent chat. After a project is configured, the primary surface is a multi-topic chat workspace where each topic keeps its own transcript, context attachments, runtime state, approvals, and diff history. +**SpecForge** is a setup-first desktop workspace for spec-driven development. After a project is configured, the primary product flow helps users generate, review, and refine PRD and technical spec documents with Cursor SDK agents while keeping local project data under the desktop app's control. -The product combines four responsibilities in one desktop shell: +The product combines five responsibilities in one desktop shell: * project setup and saved workspace defaults -* multi-session agent chat with real CLI-backed turns +* Cursor SDK-backed PRD/spec generation * PRD/spec review and editing +* secure local Cursor API key storage * approval-aware diff and terminal visibility ## 2. Target Audience -* **Solo engineers:** Wanting a desktop-native agent workspace with multiple project topics instead of a single disposable prompt thread. -* **Technical leads and PMs:** Wanting to keep PRD/spec work visible while letting implementation happen in isolated chat topics. -* **AI-assisted developers:** Wanting Codex CLI or Claude Code orchestration with per-topic context and explicit approval controls. +* **Solo engineers:** Wanting a desktop-native workspace for turning rough product intent into usable PRD/spec artifacts. +* **Technical leads and PMs:** Wanting editable agent descriptions and repeatable PRD/spec generation without leaking secrets into project settings. +* **AI-assisted developers:** Wanting Cursor SDK agents for product/spec planning while preserving local review, diff, and approval visibility. ## 3. Primary User Flow -1. **Open setup:** The app starts on configuration until a workspace is chosen and `.specforge/settings.json` exists. -2. **Choose the project folder:** SpecForge scans the workspace, restores saved project settings, and restores the most recent chat topic when available. -3. **Save configuration:** The user sets model/reasoning defaults, PRD/spec paths, and optional supporting documents, then continues into `/chat`. -4. **Land in chat:** `/chat` is the primary post-setup workspace. If the project has no saved topics yet, SpecForge creates the first one automatically. -5. **Work inside topics:** Each topic keeps isolated transcript, isolated selected context, isolated runtime state, and isolated approval/diff history. -6. **Use seeded context:** New topics start with PRD, SPEC, configured supporting docs, and a workspace summary already attached. -7. **Attach more files explicitly:** Additional workspace files can be attached per topic and never bleed into other topics. -8. **Review output:** The `/review` screen remains available for PRD/spec/file editing, while its execute view mirrors the active chat topic instead of launching a separate run. +1. **Open Projects:** The app starts on the Projects / Workspace Initialization screen until a workspace is chosen. The Projects screen is limited to workspace selection, recent projects, and the disabled clone placeholder. +2. **Choose the project folder:** SpecForge scans the workspace, creates `.specforge/settings.json` from defaults when it is missing, restores saved project settings, restores the most recent chat topic when available, and opens the review workspace. +3. **Review Settings when needed:** If default project settings were created, SpecForge prompts the user to open Settings. Cursor API key, model/reasoning defaults, editable PRD/spec/execution agent descriptions, PRD/spec paths, and optional supporting documents are configured from `/settings`. +4. **Connect Cursor:** The user saves a Cursor API key through the desktop runtime. The key is stored in the OS credential store and never in `.specforge/settings.json`. +5. **Refine or generate a PRD:** The user can run the built-in Grill PRD action to ask one focused follow-up question with a recommended answer, or generate the PRD directly. PRD generation sends the PRD agent description plus the user prompt to Cursor SDK, then asks Rust to save the generated Markdown. +6. **Refine or generate a spec:** The user can run the built-in Grill Spec action against the current PRD and spec brief to ask one focused follow-up question with a recommended answer, or generate the spec directly. Spec generation sends the spec agent description, user prompt, and chosen PRD content to Cursor SDK, then asks Rust to save the generated Markdown. +7. **Review output:** The `/review` screen remains available for PRD/spec/file editing and diff visibility. +8. **Continue in chat when needed:** `/chat` remains available below review in navigation, but chat execution is outside the current Cursor SDK refactor scope. ## 4. Functional Requirements ### 4.1. Project Setup And Persistence -* **Project-scoped settings:** Saving setup must create or update `.specforge/settings.json` inside the selected workspace. +* **Project-scoped settings:** Opening a workspace without `.specforge/settings.json` must create that file from default settings, and saving setup must update it inside the selected workspace. +* **Secret separation:** Cursor API keys must be stored through the OS credential store and must not be written to `.specforge/settings.json`. +* **Editable agent descriptions:** Settings must persist user-editable descriptions for PRD, spec, and execution agents. +* **Cursor defaults:** Model and reasoning defaults must use Cursor SDK-compatible options. * **Project-scoped sessions:** Chat metadata must be stored in `.specforge/sessions/index.json`. * **Per-topic snapshots:** Each topic must be persisted in `.specforge/sessions/.json`. * **Last-active restore:** Reopening the app should restore the last active project and the last active topic when available. +* **Recent projects:** The Projects screen must show recently opened project folders from browser `localStorage` and allow reopening them through the desktop runtime. Reopening a project without `.specforge/settings.json` must create default settings and prompt the user to review Settings. +* **Git clone placeholder:** Setup may show a repository URL clone option as a disabled/presentational control. It must not invoke Git or write files until the desktop clone flow is implemented. -### 4.2. Chat Workspace +### 4.2. PRD And Spec Generation -* **Primary route:** `/chat` must be the default destination after setup or project restore. +* **Cursor SDK runtime:** PRD/spec generation must run through `@cursor/sdk` from the TypeScript side. +* **Rust desktop boundary:** Rust must not call Codex or Claude ACP for PRD/spec generation; it reads local inputs, stores secrets, delegates Cursor SDK execution to the Bun TypeScript runner, and saves generated documents. +* **Existing UX preservation:** The user flow for choosing a PRD and generating a spec must remain unchanged except for the underlying Cursor SDK runtime. +* **PRD agent:** PRD generation must send the editable PRD agent description and the user's PRD prompt. +* **Spec agent:** Spec generation must send the editable spec agent description, the user's spec prompt, and the selected PRD content. +* **Built-in Grill Me:** PRD and spec empty states must expose secondary Grill actions that run the same Cursor SDK model with SpecForge's built-in grill-me instruction, ask exactly one next question, include a recommended answer, and append the response back into the generation prompt for user editing. +* **Execution agent description:** Settings must expose the execution agent description now, even though execution migration is not part of this slice. + +### 4.3. Chat Workspace + +* **Secondary route:** `/chat` must remain available after setup, but review is the primary destination while document review is the active focus. * **Three-zone desktop layout:** The chat screen must provide a topic list, transcript/composer workspace, and context/artifacts panel. * **Topic management:** Users must be able to create, search, select, rename, and delete topics. * **Per-topic isolation:** Messages, context items, runtime state, pending approvals, pending diff, and terminal output must remain scoped to a single topic. @@ -48,27 +64,31 @@ The product combines four responsibilities in one desktop shell: * **Explicit file attachment:** Workspace files can be attached manually from the chat UI and only affect the active topic. * **Inline controls:** Send, approve, and stop actions must live directly in the chat composer area rather than in modal flows. -### 4.3. Runtime And Approval Semantics +### 4.4. Runtime And Approval Semantics -* **Real CLI-backed turns:** Chat turns must run through the desktop backend using headless Codex CLI or Claude Code invocations. +* **Chat runtime scope:** Chat turns still run through the legacy desktop backend path in this release and are not part of the current Cursor SDK PRD/spec migration. * **Stepped mode:** The first pass must be proposal-first or read-only, then require explicit approval before a write-capable rerun. * **Milestone mode:** One assistant turn may make changes, but it must pause on the resulting real git diff before the next turn. * **God mode:** The assistant may complete the turn without an approval pause while still surfacing output and diff history afterward. * **Session-scoped stop behavior:** Stop requests must only affect the active topic run and preserve existing emergency-stop semantics. * **Visible artifacts:** Terminal output and diff history must remain visible for each topic after the run. -### 4.4. Caveman Requirement +### 4.5. Caveman Requirement * **Always-on mode:** Chat must always apply Caveman-style response guidance automatically for every topic. * **No chat-entry verification:** Entering `/chat` must not trigger a blocking install or verification step. * **Built-in prompt behavior:** The Caveman behavior must be injected by SpecForge's own system prompt so users do not need to spend turn tokens enabling it. * **Never gate navigation or settings:** Caveman activation must not stop topic switching, route changes, or model/autonomy edits. -### 4.5. Review And Settings +### 4.6. Review And Settings * **Review remains available:** `/review` must still support PRD/spec/file editing and document generation. +* **Primary post-setup route:** `/review` is the default destination after setup and is ordered above chat in the main sidebar. +* **Projects remains available:** `/` must remain accessible after a project is configured and must not auto-redirect away during last-project restore. +* **Top-bar model controls:** Review must expose model, reasoning, and approval mode controls in the top app bar beside the File/Edit/Selection/Terminal/Help menu, not in a separate left control column. * **Read-only execution mirror:** The execute panel in review must reflect the active chat topic runtime and diff, but must not start, approve, or stop a separate execution engine. -* **Settings remain project-scoped:** Model/reasoning defaults, prompt templates, document paths, and supporting docs remain editable from setup and settings. +* **Projects is selection-only:** Workspace configuration controls must live in Settings, not on the Projects screen. +* **Settings remain project-scoped:** Model/reasoning defaults, agent descriptions, document paths, and supporting docs remain editable from Settings. ## 5. Non-Goals diff --git a/docs/SPEC.md b/docs/SPEC.md index 131ea57..02a4436 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -4,28 +4,29 @@ SpecForge is a split desktop application: -* **React webview:** Owns routing, topic/session management UI, PRD/spec editing, workspace browsing, settings, and passive rendering of runtime output. -* **Tauri/Rust backend:** Owns filesystem access, workspace scanning, session persistence, git diffing, native dialogs, PDF parsing, CLI process execution, Caveman verification, and chat event streaming. +* **React webview:** Owns routing, topic/session management UI, PRD/spec editing, workspace browsing, settings, passive rendering of runtime output, and PRD/spec prompt orchestration. +* **Bun TypeScript runner:** Owns `@cursor/sdk` execution for PRD/spec generation because the SDK local runtime requires a Node-compatible process and cannot be bundled into the browser webview. +* **Tauri/Rust backend:** Owns filesystem access, workspace scanning, session persistence, git diffing, native dialogs, PDF parsing, OS credential storage for the Cursor API key, generated document saving, Bun runner delegation, and chat event streaming. -The webview never executes shell commands or writes workspace files directly. All desktop work continues to flow through `src/lib/runtime.ts` into Tauri commands exposed from `src-tauri/src/lib.rs`. +The webview never writes workspace files directly. All desktop data access continues to flow through `src/lib/runtime.ts` into Tauri commands exposed from `src-tauri/src/lib.rs`. PRD/spec model calls run through `src/cursorAgentRunner.ts` using Bun and `@cursor/sdk`; Rust does not implement provider-specific prompt logic. ## 2. Routes -* `/` is the project configuration flow. -* `/chat` is the primary post-setup workspace. -* `/review` is the document and file editing workspace. +* `/` is the Projects / Workspace Initialization screen for local workspace selection, recent project reopening, and the disabled clone placeholder. +* `/review` is the primary post-setup document and file editing workspace. +* `/chat` is the secondary agent conversation workspace. * `/settings` holds project-scoped and local runtime configuration. -When a saved project is restored, the app routes to `/chat` by default. +After setup completion or a manual project open from Projects, the app routes to `/review` by default. During automatic last-project restore, the app does not redirect away from `/`, so Projects remains available with the active project loaded. Chat remains available from the sidebar below Review. ## 3. State Model ### 3.1. Frontend stores -* **`useProjectStore`:** PRD/spec content, document paths, prompt templates, selected project defaults, annotations, and open workspace file tabs. +* **`useProjectStore`:** PRD/spec content, document paths, editable agent descriptions, selected project defaults, annotations, and open workspace file tabs. * **`useChatStore`:** Chat session summaries, `activeSessionId`, loaded per-topic snapshots, per-topic drafts, and Caveman readiness state. * **`useAgentStore`:** A lightweight runtime mirror used by review and shared execution UI. In chat-first flows it mirrors the active chat topic runtime rather than owning an independent executor. -* **`useSettingsStore`:** Theme, CLI override paths, last opened project path, environment scan results, and workspace entries. +* **`useSettingsStore`:** Theme, in-memory Cursor API key input, last opened project path, environment scan results, and workspace entries. ### 3.2. Persistence @@ -33,6 +34,8 @@ Project settings live in: * `.specforge/settings.json` +The Cursor API key is not part of project settings. It is stored by Rust through the OS credential store and exposed to the frontend only when Cursor SDK generation needs it. + Chat data lives in: * `.specforge/sessions/index.json` @@ -68,7 +71,7 @@ Additional workspace files are attached explicitly per topic from the chat UI. S ### 4.2. Runtime orchestration -Chat turns are executed in Rust as headless CLI invocations: +Chat turns are still executed in Rust as the legacy headless CLI path: * **Codex provider:** mapped to suggest, auto-edit, or full-auto style permissions depending on the selected autonomy mode * **Claude provider:** mapped to default, accept-edits, or bypass-permissions style permissions @@ -89,11 +92,31 @@ Rust keeps a session-keyed runtime map so the following remain isolated by `sess Review mode does not expose these controls directly; it only mirrors the active topic state. -## 5. Tauri Command Surface +## 5. PRD/Spec Generation + +PRD/spec generation now runs in the TypeScript layer with `@cursor/sdk`, isolated in a Bun runner so Vite does not bundle Node-only SDK dependencies into the webview. + +* `src/lib/cursorAgentRuntime.ts` composes Cursor prompts and invokes the desktop Cursor runner command. +* `src/cursorAgentRunner.ts` creates the local Cursor SDK agent, streams run events, waits for completion, and extracts final Markdown. +* `src/hooks/useDocumentHandlers.ts` keeps the existing PRD/spec button and prompt flow, but fetches the Cursor API key from Rust before invoking the Cursor generation path. +* Rust does not call Codex or Claude ACP for PRD/spec generation. +* Rust launches the Bun runner, validates the workspace root, and parses the runner's structured JSON-line output. +* Rust validates the workspace root and Markdown output path through `save_workspace_document`, strips wrapping Markdown code fences, creates parent directories, and writes the generated document. +* The PRD agent receives the editable PRD agent description plus the user's PRD prompt. +* The spec agent receives the editable spec agent description, the user's spec prompt, and the selected PRD content. +* `buildCursorPrdGrillPrompt` and `buildCursorSpecGrillPrompt` prepend SpecForge's built-in grill-me workflow to the editable PRD/spec agent descriptions. These prompts ask exactly one next question, include a recommended answer, infer answers already present in supplied context, and explicitly avoid drafting the final document. +* `useDocumentHandlers` exposes Grill PRD and Grill Spec handlers. The handlers reuse the Cursor SDK runner, stream events into the review terminal, and append the returned grill question block into the relevant generation textarea for the user to answer or edit before generating the document. +* The execution agent description is persisted in project settings for the upcoming execution migration. + +## 6. Tauri Command Surface The desktop runtime currently exposes: * `run_environment_scan` +* `run_cursor_agent_prompt` +* `get_cursor_api_key` +* `save_cursor_api_key` +* `delete_cursor_api_key` * `pick_document` * `pick_project_folder` * `load_project_context` @@ -101,8 +124,7 @@ The desktop runtime currently exposes: * `read_workspace_file` * `get_workspace_snapshot` * `git_get_diff` -* `generate_prd_document` -* `generate_spec_document` +* `save_workspace_document` * `create_chat_session` * `load_chat_session` * `save_chat_session` @@ -114,7 +136,7 @@ The desktop runtime currently exposes: Chat runtime updates are streamed through a typed `chat-session-event` payload carrying the session id plus the current session snapshot or summary update. -## 6. Caveman Integration +## 7. Caveman Integration SpecForge now treats Caveman as a built-in chat response mode instead of a runtime-installed dependency. @@ -122,13 +144,14 @@ Each outgoing chat turn prepends a compact Caveman-style instruction before the There is no chat-entry verification or installation path tied to navigation, and Caveman state must never block topic changes, route changes, or session configuration edits. -## 7. Review Workspace +## 8. Review Workspace -The review screen still provides: +The main sidebar follows the full-height review-screen pattern from the Stitch design: fixed-width desktop navigation, Projects, Review, Chat, and Settings ordering, Dracula Enterprise colors, and Review above Chat. The review screen also has a top app bar with File/Edit/Selection/Terminal/Help menu labels plus compact model, reasoning, and approval-mode controls. The review screen still provides: * PRD/spec editing * workspace file browsing * PRD/spec generation +* PRD/spec grill-me refinement before generation Its execute panel is now a read-only mirror of the active chat topic: @@ -138,9 +161,19 @@ Its execute panel is now a read-only mirror of the active chat topic: This prevents review from launching a second execution engine that could diverge from chat state. -## 8. Known Limits +## 9. Setup Clone Placeholder + +The setup screen includes a presentational Git clone card beside the local folder picker. The repository URL input and Clone button are disabled and must not call Git, Tauri commands, filesystem writes, or network operations until a dedicated clone implementation is added. + +The Projects screen also persists up to eight recently opened project folders in the `recentProjects` field of the `specforge.settings` `localStorage` record. Opening a recent project calls the existing `load_project_context` Tauri command for that saved path; React still does not perform filesystem access directly. + +When a picked or recent project has no `.specforge/settings.json`, the React handler immediately calls `save_project_settings` with the default settings returned by `load_project_context`, reloads the project context, and shows a HeroUI modal asking the user to review project defaults in Settings. + +The Projects screen does not render workspace configuration controls. Cursor API key management, model/reasoning defaults, agent descriptions, document paths, and supporting document configuration are owned by `/settings`. + +## 10. Known Limits * Opened workspace file tabs remain in-memory only; there is still no save-to-disk flow. * The desktop runtime is required for real project persistence, chat sessions, and CLI-backed turns. -* The provider set remains limited to Codex CLI and Claude Code for this version. +* Chat execution still uses the legacy Codex CLI and Claude Code path; the current Cursor SDK refactor is limited to PRD/spec generation. * **Planned dependencies not yet installed:** `react-markdown`, `react-syntax-highlighter`, and `tauri-plugin-store` are referenced in design documents but are not currently in `package.json` or `Cargo.toml`. Features that depend on them (rich markdown rendering, syntax-highlighted code blocks, native key-value persistence) are aspirational and should not be assumed functional until the dependencies are added. diff --git a/docs/agent-context/api-conventions.md b/docs/agent-context/api-conventions.md index 27f72e8..e50c611 100644 --- a/docs/agent-context/api-conventions.md +++ b/docs/agent-context/api-conventions.md @@ -9,12 +9,14 @@ - `src/types.ts` ## Command Inventory -- `run_environment_scan` reports Claude, Codex, and Git CLI health. +- `run_environment_scan` reports Cursor key availability and Git health. +- `get_cursor_api_key`, `save_cursor_api_key`, and `delete_cursor_api_key` use the OS credential store. Cursor keys are never written to `.specforge/settings.json`. - `parse_document` reads Markdown directly and extracts text from PDFs. - `open_workspace_folder` and `read_workspace_file` back the workspace import flow. +- `save_workspace_document` validates and writes generated PRD/spec Markdown inside the selected workspace. - `get_workspace_snapshot` returns a shallow repo snapshot for diagnostics. - `git_get_diff` returns the real working diff or a sample diff when empty. -- `spawn_cli_agent`, `approve_action`, and `kill_agent_process` drive the simulated execution loop. +- `spawn_cli_agent`, `approve_action`, and `kill_agent_process` drive the legacy simulated execution loop. ## Workspace Rules - Imported workspace paths are normalized to forward slashes before they reach the webview. @@ -24,4 +26,4 @@ ## UX Contracts - `stepped` and `milestone` modes both require a visible diff and an interrupt path. - `god_mode` is allowed in the UI, but the review/execution shell still needs status, diff, and halt surfaces. -- Browser mode is a fallback/demo path. It is allowed to show sample data, but production desktop behavior must use the Rust backend. +- Browser mode is a fallback/demo path. It is allowed to show sample data, but production desktop data access must use the Rust backend. diff --git a/docs/agent-context/architecture.md b/docs/agent-context/architecture.md index 4388661..4486d9f 100644 --- a/docs/agent-context/architecture.md +++ b/docs/agent-context/architecture.md @@ -6,21 +6,23 @@ - Zustand holds the long-lived client state across three stores: - `useProjectStore` for PRD/spec content, active tabs, approval state, and refinement flow. - `useAgentStore` for terminal output, milestone state, pending diffs, and execution summaries. - - `useSettingsStore` for theme choice, binary overrides, environment diagnostics, and workspace entries. + - `useSettingsStore` for theme choice, in-memory Cursor key input, environment diagnostics, and workspace entries. ## Frontend Responsibilities - `src/App.tsx` is the orchestration shell. It owns routing, keyboard shortcuts, environment refresh, document import flow, workspace scanning state, and the fallback execution loop. - `src/components/` contains the split-pane review UI, the execution view, the control deck, the workspace tree, and settings surface. - `src/lib/runtime.ts` is the only frontend file that should know about Tauri `invoke()` or event listeners. +- `src/lib/cursorAgentRuntime.ts` owns Cursor PRD/spec prompt composition and calls the desktop Cursor runner command. +- `src/cursorAgentRunner.ts` is the Bun-only `@cursor/sdk` execution entry point and must not be imported by webview code. - `src/lib/workspaceImport.ts` owns browser-side workspace snapshots, `.gitignore` filtering, document detection, and text-file parsing. ## Backend Responsibilities -- `src-tauri/src/lib.rs` owns environment scans, document parsing, workspace walking, git diff retrieval, process-control state, and the simulated agent loop. +- `src-tauri/src/lib.rs` owns environment scans, document parsing, workspace walking, git diff retrieval, Cursor API key storage, generated document saving, Bun runner delegation, process-control state, and the simulated agent loop. - The backend uses `ignore::WalkBuilder` to respect `.gitignore` while scanning an imported workspace. - Tauri commands are the only allowed path for shell, file, and diff operations. ## Real Gaps To Keep In Mind - `src/App.tsx`, `src/styles.css`, and `src-tauri/src/lib.rs` are the largest code hotspots and the first places that should be decomposed. - `docs/SPEC.md` currently describes some tooling that is not present in `package.json`, so docs drift is already a live risk. -- There is no automated test suite or committed frontend formatter/linter yet. +- Vitest, TypeScript, Biome, and CI exist; keep tests focused around changed behavior. - The frontend/browser fallback uses fake workspace and diff data for demo mode. Production logic must not depend on that data. diff --git a/docs/agent-context/testing-guide.md b/docs/agent-context/testing-guide.md index 5e60bfe..5efd6db 100644 --- a/docs/agent-context/testing-guide.md +++ b/docs/agent-context/testing-guide.md @@ -2,20 +2,22 @@ ## Automated Verification Today - Frontend build: `bun run build` +- Frontend typecheck: `bunx tsc --noEmit` +- Frontend tests: `bun test` - Rust compile check: `cargo check --manifest-path .\src-tauri\Cargo.toml` - Rust formatting: `cargo fmt --manifest-path .\src-tauri\Cargo.toml` ## Current Reality -- There is no automated frontend test suite. -- There is no CI workflow in the repo today. -- In this environment, `cargo check --manifest-path .\src-tauri\Cargo.toml` succeeds. -- In this environment, `bun run build` currently fails before TypeScript/Vite can run because Bun reports corrupted `node_modules` bin shims. The repair command is `bun install --force`. +- Vitest covers the current config normalization and Cursor prompt helpers. +- CI validates typecheck, lint, and tests on push/PR. +- Use Bun for frontend commands. ## Manual Smoke Paths - Launch the review shell and confirm the default PRD/spec panes load `docs/PRD.md` and `docs/SPEC.md`. - Open a workspace folder and confirm `.gitignore` exclusions are respected. - Import both Markdown and PDF documents and verify the selected target pane updates correctly. -- Refresh diagnostics in Settings and verify Claude/Codex/Git status cards update without breaking the page. +- Refresh diagnostics in Settings and verify Cursor SDK key and Git status cards update without breaking the page. +- Save and clear a Cursor API key and verify it does not appear in `.specforge/settings.json`. - Start a build in browser mode and confirm the fallback execution stream, pending diff, approval gate, and emergency stop still work. ## High-Value Next Tests diff --git a/docs/superpowers/plans/2026-04-29-cursor-sdk-refactor.md b/docs/superpowers/plans/2026-04-29-cursor-sdk-refactor.md new file mode 100644 index 0000000..b5393b6 --- /dev/null +++ b/docs/superpowers/plans/2026-04-29-cursor-sdk-refactor.md @@ -0,0 +1,71 @@ +# Cursor SDK Refactor Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace Codex/Claude CLI-backed PRD/spec generation with Cursor SDK-backed TypeScript generation and secure Cursor key storage. + +**Architecture:** Rust reads and writes local machine data, stores the Cursor key in the OS credential store, and delegates Cursor execution to a Bun TypeScript runner. React owns Cursor agent prompt construction; `src/cursorAgentRunner.ts` owns SDK calls for PRD/spec generation. + +**Tech Stack:** React 19, TypeScript, Tauri 2, Rust, Cursor TypeScript SDK, Rust `keyring`. + +--- + +### Task 1: Settings Shape + +**Files:** +- Modify: `src/types.ts` +- Modify: `src/lib/agentConfig.ts` +- Modify: `src/lib/projectConfig.ts` +- Modify: `src-tauri/src/models.rs` +- Modify: `src-tauri/src/project.rs` + +- [ ] Add Cursor model/provider types. +- [ ] Add `prdAgentDescription`, `specAgentDescription`, and `executionAgentDescription`. +- [ ] Preserve legacy `prdPrompt` and `specPrompt` while reading old settings. + +### Task 2: Secure Cursor Key + +**Files:** +- Modify: `src/lib/runtime.ts` +- Modify: `src/store/useSettingsStore.ts` +- Modify: `src-tauri/src/lib.rs` +- Create: `src-tauri/src/secrets.rs` + +- [ ] Store the Cursor API key in the OS credential store. +- [ ] Expose only status plus save/delete commands to React. +- [ ] Avoid persisting the key in project settings or localStorage. + +### Task 3: Cursor Generation + +**Files:** +- Create: `src/lib/cursorAgentRuntime.ts` +- Create: `src/cursorAgentRunner.ts` +- Create: `src-tauri/src/cursor_agent.rs` +- Modify: `src/hooks/useDocumentHandlers.ts` +- Modify: `src-tauri/src/documents.rs` +- Modify: `src-tauri/src/lib.rs` + +- [ ] Build PRD and spec prompts in TypeScript. +- [ ] Run Cursor SDK from the Bun TypeScript runner with the configured Cursor key and model. +- [ ] Save generated markdown through Rust file-writing command. + +### Task 4: UI and Docs + +**Files:** +- Modify: `src/components/ProjectAiSettingsCard.tsx` +- Modify: `src/screens/ConfigurationScreen.tsx` +- Modify: `src/components/SettingsView.tsx` +- Modify: `docs/PRD.md` +- Modify: `docs/SPEC.md` + +- [ ] Replace CLI availability UX for this workflow with Cursor API key configuration. +- [ ] Rename prompt labels to agent descriptions. +- [ ] Document Cursor SDK ownership and key storage. + +### Task 5: Verification + +**Commands:** +- `bun test` +- `bun run lint` +- `bun run build` +- `cargo check --manifest-path .\src-tauri\Cargo.toml` diff --git a/docs/superpowers/plans/2026-04-30-cursor-node-sidecar.md b/docs/superpowers/plans/2026-04-30-cursor-node-sidecar.md new file mode 100644 index 0000000..4ba2808 --- /dev/null +++ b/docs/superpowers/plans/2026-04-30-cursor-node-sidecar.md @@ -0,0 +1,1070 @@ +# Cursor Node Sidecar Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the current Bun-on-PATH Cursor runner bridge with a packaged Cursor SDK sidecar that Rust can launch reliably in development and bundled desktop builds. + +**Architecture:** Keep Tauri/Rust as the trusted desktop backend for filesystem, keyring, workspace validation, document writes, git diffing, and process supervision. Move all `@cursor/sdk` calls into one compiled Bun sidecar executable with a small JSON protocol over stdin/stdout JSON lines. Keep the React webview contract unchanged through `src/lib/runtime.ts`. + +**Tech Stack:** Tauri 2, Rust `std::process::Command`, Bun `build --compile`, TypeScript, `@cursor/sdk`, Vitest, existing OS keyring integration. + +--- + +## Context And Constraints + +Current state: + +- `src-tauri/src/cursor_agent.rs` shells out to `bun` from `PATH`. +- Rust runs source files directly: `src/cursorAgentRunner.ts` and `src/cursorModelsRunner.ts`. +- This works in a dev checkout but is fragile for packaged desktop builds because users may not have Bun installed and source `.ts` files are not a stable runtime asset. + +Current docs checked: + +- Tauri 2 sidecar docs support bundled external binaries through `bundle.externalBin` and require target-triple sidecar names such as `name-x86_64-pc-windows-msvc.exe`. +- Tauri docs show sidecars can communicate through stdin/stdout and stream output. +- Bun docs support single-file executables with `bun build --compile`, including Windows targets and automatic `.exe` output. + +Approval gates: + +- Changing `src-tauri/tauri.conf.json` needs explicit user approval under this repo's `AGENTS.md`. +- Adding `tauri-plugin-shell` would be a new runtime dependency and also needs approval. This plan avoids that dependency by launching the bundled binary with `std::process::Command`. + +## File Structure + +Create: + +- `src/lib/cursorSidecarProtocol.ts`: shared TypeScript request/response/event types and pure helpers. +- `src/cursorSidecar.ts`: single sidecar executable entrypoint that dispatches `listModels` and `runAgentPrompt`. +- `scripts/build-cursor-sidecar.ts`: builds the sidecar executable and writes it to `src-tauri/binaries/specforge-cursor-sidecar-[.exe]`. +- `src/lib/cursorSidecarProtocol.test.ts`: unit tests for protocol normalization and output parsing helpers. + +Modify: + +- `package.json`: add `sidecar:build`; update Tauri scripts to build the sidecar before launching/packaging. +- `src-tauri/tauri.conf.json`: add `bundle.externalBin` after explicit approval. +- `src-tauri/src/cursor_agent.rs`: launch the packaged sidecar binary instead of resolving `bun` and `.ts` runner paths. +- `src-tauri/Cargo.toml`: no new dependency expected. +- `docs/SPEC.md`: update architecture text from “Bun TypeScript runner” to “packaged Cursor SDK sidecar”. + +Remove after migration: + +- `src/cursorAgentRunner.ts` +- `src/cursorModelsRunner.ts` + +--- + +### Task 1: Add The TypeScript Sidecar Protocol + +**Files:** + +- Create: `src/lib/cursorSidecarProtocol.ts` +- Create: `src/lib/cursorSidecarProtocol.test.ts` + +- [ ] **Step 1: Write failing protocol tests** + +Create `src/lib/cursorSidecarProtocol.test.ts`: + +```ts +import { describe, expect, it } from "vitest"; + +import { + formatCursorStreamEvent, + normalizeCursorModels, + parseSidecarOutputLine, + stripWrappingCodeFence +} from "./cursorSidecarProtocol"; + +describe("cursor sidecar protocol", () => { + it("parses event output lines", () => { + expect(parseSidecarOutputLine('{"type":"event","text":"[status] queued"}')).toEqual({ + type: "event", + text: "[status] queued" + }); + }); + + it("parses result output lines", () => { + expect(parseSidecarOutputLine('{"type":"result","content":"Done"}')).toEqual({ + type: "result", + content: "Done" + }); + }); + + it("rejects malformed output lines", () => { + expect(() => parseSidecarOutputLine("{broken")).toThrow("Cursor sidecar returned malformed JSON"); + }); + + it("normalizes Cursor models", () => { + expect( + normalizeCursorModels([ + { + id: "composer-2", + displayName: "Composer 2", + parameters: [ + { + id: "thinking", + values: [{ value: "medium", displayName: "Medium" }] + } + ] + } + ]) + ).toEqual([ + { + id: "composer-2", + label: "Composer 2", + description: undefined, + parameters: [ + { + id: "thinking", + label: "Thinking", + values: [{ value: "medium", label: "Medium" }] + } + ] + } + ]); + }); + + it("formats known stream events", () => { + expect(formatCursorStreamEvent({ type: "thinking", text: "Checking files" })).toBe( + "[thinking] Checking files" + ); + expect(formatCursorStreamEvent({ type: "tool_call", name: "grep", status: "running" })).toBe( + "[tool] grep: running" + ); + expect(formatCursorStreamEvent({ type: "status", status: "complete" })).toBe( + "[status] complete" + ); + }); + + it("strips a wrapping markdown code fence", () => { + expect(stripWrappingCodeFence("```md\n# Title\n```")).toBe("# Title"); + }); +}); +``` + +- [ ] **Step 2: Run the tests and verify they fail** + +Run: + +```powershell +bun run test -- src/lib/cursorSidecarProtocol.test.ts +``` + +Expected: FAIL because `src/lib/cursorSidecarProtocol.ts` does not exist. + +- [ ] **Step 3: Implement protocol helpers** + +Create `src/lib/cursorSidecarProtocol.ts`: + +```ts +import type { CursorModel, ModelId, ReasoningProfileId } from "../types"; + +export type CursorSidecarRequest = + | { + command: "listModels"; + apiKey?: string; + } + | { + command: "runAgentPrompt"; + apiKey: string; + workspaceRoot: string; + model: ModelId; + reasoning: ReasoningProfileId; + prompt: string; + }; + +export type CursorSidecarOutputLine = + | { type: "event"; text: string } + | { type: "result"; content: string } + | { type: "models"; models: CursorModel[] }; + +export interface SdkModelParameterValue { + value: string; + displayName?: string; +} + +export interface SdkModelParameter { + id: string; + displayName?: string; + values?: SdkModelParameterValue[]; +} + +export interface SdkModel { + id: string; + displayName?: string; + description?: string; + parameters?: SdkModelParameter[]; +} + +export type CursorStreamEvent = { + type?: string; + message?: { + content?: Array<{ + type?: string; + text?: string; + }>; + }; + text?: string; + name?: string; + status?: string; +}; + +export function parseSidecarOutputLine(line: string): CursorSidecarOutputLine { + try { + const parsed = JSON.parse(line) as CursorSidecarOutputLine; + + if (parsed.type === "event" && typeof parsed.text === "string") { + return parsed; + } + + if (parsed.type === "result" && typeof parsed.content === "string") { + return parsed; + } + + if (parsed.type === "models" && Array.isArray(parsed.models)) { + return parsed; + } + } catch (error) { + throw new Error( + `Cursor sidecar returned malformed JSON: ${error instanceof Error ? error.message : String(error)}` + ); + } + + throw new Error(`Cursor sidecar returned an unsupported output line: ${line}`); +} + +export function normalizeCursorModels(models: SdkModel[]): CursorModel[] { + return models + .filter((model) => model.id.trim().length > 0) + .map((model) => ({ + id: model.id, + label: model.displayName?.trim() || formatLabel(model.id), + description: model.description?.trim() || undefined, + parameters: model.parameters + ?.map((parameter) => ({ + id: parameter.id, + label: parameter.displayName?.trim() || formatLabel(parameter.id), + values: (parameter.values ?? []) + .filter((entry) => entry.value.trim().length > 0) + .map((entry) => ({ + value: entry.value, + label: entry.displayName?.trim() || formatLabel(entry.value) + })) + })) + .filter((parameter) => parameter.values.length > 0) + })); +} + +export function extractCursorRunText( + runResult?: { result?: string }, + events: CursorStreamEvent[] = [] +) { + if (runResult?.result?.trim()) { + return runResult.result.trim(); + } + + return events + .flatMap((event) => + event.type === "assistant" + ? event.message?.content + ?.filter((block) => block.type === "text" && block.text) + .map((block) => block.text ?? "") ?? [] + : [] + ) + .join("") + .trim(); +} + +export function formatCursorStreamEvent(event: CursorStreamEvent) { + switch (event.type) { + case "thinking": + return event.text?.trim() ? `[thinking] ${event.text.trim()}` : ""; + case "tool_call": + return event.name ? `[tool] ${event.name}${event.status ? `: ${event.status}` : ""}` : ""; + case "status": + return event.status ? `[status] ${event.status}` : ""; + default: + return ""; + } +} + +export function stripWrappingCodeFence(content: string) { + const trimmed = content.trim(); + + if (!trimmed.startsWith("```")) { + return trimmed; + } + + const lines = trimmed.split(/\r?\n/); + const firstLine = lines[0]?.trimStart() ?? ""; + const lastLine = lines[lines.length - 1]?.trimStart() ?? ""; + + if (!firstLine.startsWith("```") || !lastLine.startsWith("```")) { + return trimmed; + } + + return lines.slice(1, -1).join("\n").trim(); +} + +function formatLabel(value: string) { + return value + .split(/[-_]/) + .filter(Boolean) + .map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`) + .join(" "); +} +``` + +- [ ] **Step 4: Run the protocol tests** + +Run: + +```powershell +bun run test -- src/lib/cursorSidecarProtocol.test.ts +``` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +```powershell +git add src/lib/cursorSidecarProtocol.ts src/lib/cursorSidecarProtocol.test.ts +git commit -m "test: add cursor sidecar protocol" +``` + +--- + +### Task 2: Build One Cursor SDK Sidecar Entrypoint + +**Files:** + +- Create: `src/cursorSidecar.ts` +- Modify: `src/cursorAgentRunner.ts` +- Modify: `src/cursorModelsRunner.ts` + +- [ ] **Step 1: Create the sidecar entrypoint** + +Create `src/cursorSidecar.ts`: + +```ts +import { Agent, Cursor } from "@cursor/sdk"; +import { + extractCursorRunText, + formatCursorStreamEvent, + normalizeCursorModels, + stripWrappingCodeFence, + type CursorSidecarOutputLine, + type CursorSidecarRequest, + type CursorStreamEvent, + type SdkModel +} from "./lib/cursorSidecarProtocol"; + +async function main() { + const request = JSON.parse(await readStdin()) as CursorSidecarRequest; + + switch (request.command) { + case "listModels": { + const models = await Cursor.models.list( + request.apiKey?.trim() ? { apiKey: request.apiKey.trim() } : undefined + ); + writeOutputLine({ type: "models", models: normalizeCursorModels(models as SdkModel[]) }); + return; + } + + case "runAgentPrompt": { + await runAgentPrompt(request); + return; + } + } +} + +async function runAgentPrompt(request: Extract) { + const agent = await Agent.create({ + apiKey: request.apiKey, + model: { + id: request.model, + params: [{ id: "thinking", value: request.reasoning === "max" ? "high" : request.reasoning }] + }, + local: { cwd: request.workspaceRoot } + }); + const events: CursorStreamEvent[] = []; + + try { + const run = await agent.send(request.prompt); + + for await (const event of run.stream() as AsyncGenerator) { + events.push(event); + const text = formatCursorStreamEvent(event); + + if (text) { + writeOutputLine({ type: "event", text }); + } + } + + const result = await run.wait(); + const content = extractCursorRunText(result, events); + + if (!content.trim()) { + throw new Error("Cursor SDK returned an empty response."); + } + + writeOutputLine({ type: "result", content: stripWrappingCodeFence(content) }); + } finally { + agent.close(); + } +} + +async function readStdin() { + const chunks: Buffer[] = []; + + for await (const chunk of process.stdin) { + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + } + + return Buffer.concat(chunks).toString("utf8") || "{}"; +} + +function writeOutputLine(line: CursorSidecarOutputLine) { + process.stdout.write(`${JSON.stringify(line)}\n`); +} + +main().catch((error) => { + const message = error instanceof Error ? error.message : String(error); + process.stderr.write(`${message}\n`); + process.exit(1); +}); +``` + +- [ ] **Step 2: Keep legacy runner paths as thin compatibility wrappers** + +Replace `src/cursorModelsRunner.ts` with: + +```ts +import { Cursor } from "@cursor/sdk"; +import { normalizeCursorModels, type SdkModel } from "./lib/cursorSidecarProtocol"; + +interface CursorModelsRunnerRequest { + apiKey?: string; +} + +async function main() { + const request = JSON.parse(await readStdin()) as CursorModelsRunnerRequest; + const models = await Cursor.models.list( + request.apiKey?.trim() ? { apiKey: request.apiKey.trim() } : undefined + ); + + process.stdout.write(JSON.stringify(normalizeCursorModels(models as SdkModel[]))); +} + +async function readStdin() { + const chunks: Buffer[] = []; + + for await (const chunk of process.stdin) { + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + } + + return Buffer.concat(chunks).toString("utf8") || "{}"; +} + +main().catch((error) => { + const message = error instanceof Error ? error.message : String(error); + process.stderr.write(`${message}\n`); + process.exit(1); +}); +``` + +Replace duplicated helper code in `src/cursorAgentRunner.ts` by importing `extractCursorRunText`, `formatCursorStreamEvent`, and `stripWrappingCodeFence` from `./lib/cursorSidecarProtocol`. + +- [ ] **Step 3: Typecheck** + +Run: + +```powershell +bun run typecheck +``` + +Expected: PASS. + +- [ ] **Step 4: Commit** + +```powershell +git add src/cursorSidecar.ts src/cursorAgentRunner.ts src/cursorModelsRunner.ts +git commit -m "refactor: unify cursor sdk runner entrypoint" +``` + +--- + +### Task 3: Add A Sidecar Build Script + +**Files:** + +- Create: `scripts/build-cursor-sidecar.ts` +- Modify: `package.json` + +- [ ] **Step 1: Create the sidecar build script** + +Create `scripts/build-cursor-sidecar.ts`: + +```ts +import { execFileSync } from "node:child_process"; +import { mkdirSync, rmSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; + +const repoRoot = resolve(import.meta.dir, ".."); +const sidecarName = "specforge-cursor-sidecar"; +const targetTriple = execFileSync("rustc", ["--print", "host-tuple"], { + cwd: repoRoot, + encoding: "utf8" +}).trim(); +const extension = process.platform === "win32" ? ".exe" : ""; +const outputPath = join( + repoRoot, + "src-tauri", + "binaries", + `${sidecarName}-${targetTriple}${extension}` +); + +mkdirSync(dirname(outputPath), { recursive: true }); +rmSync(outputPath, { force: true }); + +const result = await Bun.build({ + entrypoints: [join(repoRoot, "src", "cursorSidecar.ts")], + compile: { + outfile: outputPath + }, + minify: true, + define: { + "process.env.NODE_ENV": JSON.stringify("production") + } +}); + +if (!result.success) { + for (const log of result.logs) { + console.error(log); + } + process.exit(1); +} + +console.log(`Built Cursor sidecar: ${outputPath}`); +``` + +- [ ] **Step 2: Add package scripts** + +Modify `package.json` scripts: + +```json +{ + "scripts": { + "dev": "vite", + "sidecar:build": "bun scripts/build-cursor-sidecar.ts", + "tauri:dev": "bun run sidecar:build && tauri dev", + "build": "tsc && vite build", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest", + "lint": "bun ./node_modules/@biomejs/biome/bin/biome check src", + "lint:fix": "bun ./node_modules/@biomejs/biome/bin/biome check --fix src", + "preview": "vite preview", + "tauri": "tauri" + } +} +``` + +- [ ] **Step 3: Build the sidecar** + +Run: + +```powershell +bun run sidecar:build +``` + +Expected: creates `src-tauri/binaries/specforge-cursor-sidecar-x86_64-pc-windows-msvc.exe` on this Windows workspace, or the matching host triple on another machine. + +- [ ] **Step 4: Typecheck** + +Run: + +```powershell +bun run typecheck +``` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +```powershell +git add package.json scripts/build-cursor-sidecar.ts src-tauri/binaries/.gitkeep +git commit -m "build: compile cursor sdk sidecar" +``` + +If `src-tauri/binaries/.gitkeep` does not exist, create it so the directory is tracked while generated sidecar binaries remain ignored. + +--- + +### Task 4: Configure Tauri To Bundle The Sidecar + +**Files:** + +- Modify: `src-tauri/tauri.conf.json` +- Modify: `.gitignore` + +- [ ] **Step 1: Get explicit approval** + +Ask: + +```text +This task modifies src-tauri/tauri.conf.json to bundle the Cursor sidecar. Approve that config change? +``` + +Expected: continue only after the user approves. + +- [ ] **Step 2: Add the external binary config** + +Modify `src-tauri/tauri.conf.json`: + +```json +{ + "bundle": { + "active": false, + "externalBin": ["binaries/specforge-cursor-sidecar"], + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + } +} +``` + +- [ ] **Step 3: Ignore generated sidecar binaries** + +Add to `.gitignore`: + +```gitignore +src-tauri/binaries/specforge-cursor-sidecar-* +!src-tauri/binaries/.gitkeep +``` + +- [ ] **Step 4: Validate config** + +Run: + +```powershell +cargo check --manifest-path .\src-tauri\Cargo.toml +``` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +```powershell +git add src-tauri/tauri.conf.json .gitignore src-tauri/binaries/.gitkeep +git commit -m "build: bundle cursor sidecar with tauri" +``` + +--- + +### Task 5: Teach Rust To Launch The Packaged Sidecar + +**Files:** + +- Modify: `src-tauri/src/cursor_agent.rs` + +- [ ] **Step 1: Add a Rust unit for output parsing before changing process launch** + +Append tests to `src-tauri/src/cursor_agent.rs`: + +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_runner_output_reads_result_and_events() { + let output = "{\"type\":\"event\",\"text\":\"[status] running\"}\n{\"type\":\"result\",\"content\":\"Done\"}\n"; + let parsed = parse_runner_output(output).expect("output should parse"); + + assert_eq!(parsed.content, "Done"); + assert_eq!(parsed.events, vec![String::from("[status] running")]); + } + + #[test] + fn parse_model_output_reads_models_line() { + let output = "{\"type\":\"models\",\"models\":[{\"id\":\"composer-2\",\"label\":\"Composer 2\"}]}\n"; + let parsed = parse_model_output(output).expect("models should parse"); + + assert_eq!(parsed.len(), 1); + assert_eq!(parsed[0].id, "composer-2"); + } +} +``` + +- [ ] **Step 2: Run Rust tests and verify failure** + +Run: + +```powershell +cargo test --manifest-path .\src-tauri\Cargo.toml cursor_agent +``` + +Expected: FAIL because `parse_model_output` does not exist yet and `parse_runner_output` still expects only event/result lines. + +- [ ] **Step 3: Replace Bun resolution with sidecar path resolution** + +In `src-tauri/src/cursor_agent.rs`, remove direct `which::which("bun")` and runner path checks. Add: + +```rust +fn resolve_cursor_sidecar_path() -> Result { + if let Ok(path) = std::env::var("SPECFORGE_CURSOR_SIDECAR") { + let path = PathBuf::from(path); + + if path.exists() { + return Ok(path); + } + + return Err(format!( + "SPECFORGE_CURSOR_SIDECAR points to a missing file: {}.", + path.display() + )); + } + + let app_root = resolve_app_root()?; + let target_triple = option_env!("TAURI_ENV_TARGET_TRIPLE") + .map(str::to_string) + .or_else(|| host_target_triple().ok()) + .ok_or_else(|| String::from("Unable to determine the Cursor sidecar target triple."))?; + let extension = if cfg!(windows) { ".exe" } else { "" }; + let sidecar_path = app_root.join("src-tauri").join("binaries").join(format!( + "specforge-cursor-sidecar-{target_triple}{extension}" + )); + + if sidecar_path.exists() { + Ok(sidecar_path) + } else { + Err(format!( + "Cursor SDK sidecar was not found at {}. Run `bun run sidecar:build` first.", + sidecar_path.display() + )) + } +} + +fn host_target_triple() -> Result { + let output = Command::new("rustc") + .arg("--print") + .arg("host-tuple") + .output() + .map_err(|error| format!("Unable to query rustc host tuple: {error}"))?; + + if !output.status.success() { + return Err(String::from_utf8_lossy(&output.stderr).trim().to_string()); + } + + Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) +} +``` + +- [ ] **Step 4: Send command-tagged requests to the sidecar** + +Change the Rust request structs: + +```rust +#[derive(Serialize)] +#[serde(rename_all = "camelCase", tag = "command")] +enum CursorSidecarRequest { + #[serde(rename = "runAgentPrompt")] + RunAgentPrompt { + api_key: String, + workspace_root: String, + model: String, + reasoning: String, + prompt: String, + }, + #[serde(rename = "listModels")] + ListModels { api_key: Option }, +} +``` + +Update `run_cursor_agent_prompt_sync` to serialize `CursorSidecarRequest::RunAgentPrompt`. + +Update `list_cursor_models_sync` to serialize `CursorSidecarRequest::ListModels`. + +- [ ] **Step 5: Extract process execution into one helper** + +Add: + +```rust +fn run_cursor_sidecar(request: &CursorSidecarRequest) -> Result { + let sidecar_path = resolve_cursor_sidecar_path()?; + let request_json = serde_json::to_vec(request) + .map_err(|error| format!("Unable to prepare Cursor sidecar request: {error}"))?; + let mut child = Command::new(&sidecar_path) + .env("NO_COLOR", "1") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|error| { + format!( + "Unable to start the Cursor SDK sidecar at {}: {error}", + sidecar_path.display() + ) + })?; + + let mut stdin = child + .stdin + .take() + .ok_or_else(|| String::from("Unable to open the Cursor SDK sidecar input."))?; + stdin + .write_all(&request_json) + .map_err(|error| format!("Unable to send the Cursor SDK sidecar request: {error}"))?; + drop(stdin); + + let output = child + .wait_with_output() + .map_err(|error| format!("Unable to read Cursor SDK sidecar output: {error}"))?; + let stdout = String::from_utf8_lossy(&output.stdout).to_string(); + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); + + if !output.status.success() { + return Err(format_process_failure(&stderr, &stdout)); + } + + Ok(stdout) +} +``` + +- [ ] **Step 6: Parse model sidecar output** + +Add: + +```rust +#[derive(Deserialize)] +#[serde(tag = "type", rename_all = "lowercase")] +enum CursorSidecarOutputLine { + Event { text: String }, + Result { content: String }, + Models { models: Vec }, +} + +fn parse_model_output(stdout: &str) -> Result, String> { + for line in stdout.lines().filter(|line| !line.trim().is_empty()) { + let parsed: CursorSidecarOutputLine = serde_json::from_str(line).map_err(|error| { + format!("Cursor SDK sidecar returned malformed output: {error}. Output line: {line}") + })?; + + if let CursorSidecarOutputLine::Models { models } = parsed { + return Ok(models); + } + } + + Err(String::from("Cursor SDK sidecar returned no model list.")) +} +``` + +Update `parse_runner_output` to use `CursorSidecarOutputLine` and ignore `Models`. + +- [ ] **Step 7: Run Rust checks** + +Run: + +```powershell +cargo test --manifest-path .\src-tauri\Cargo.toml cursor_agent +cargo check --manifest-path .\src-tauri\Cargo.toml +``` + +Expected: PASS. + +- [ ] **Step 8: Commit** + +```powershell +git add src-tauri/src/cursor_agent.rs +git commit -m "fix: launch packaged cursor sidecar" +``` + +--- + +### Task 6: Remove The Legacy Runner Files + +**Files:** + +- Delete: `src/cursorAgentRunner.ts` +- Delete: `src/cursorModelsRunner.ts` +- Modify: `docs/SPEC.md` + +- [ ] **Step 1: Search for legacy runner references** + +Run: + +```powershell +Get-ChildItem -Path src,src-tauri,docs -Recurse -File | + Where-Object { $_.FullName -notmatch '\\target\\' } | + Select-String -Pattern 'cursorAgentRunner','cursorModelsRunner','Bun TypeScript runner' -CaseSensitive:$false | + Select-Object Path,LineNumber,Line +``` + +Expected: references exist in docs and possibly the deleted files only. + +- [ ] **Step 2: Delete legacy runner files** + +Delete: + +```powershell +Remove-Item -LiteralPath .\src\cursorAgentRunner.ts +Remove-Item -LiteralPath .\src\cursorModelsRunner.ts +``` + +- [ ] **Step 3: Update docs** + +In `docs/SPEC.md`, replace the architecture bullets with: + +```md +* **React webview:** Owns routing, topic/session management UI, PRD/spec editing, workspace browsing, settings, passive rendering of runtime output, and PRD/spec prompt orchestration. +* **Cursor SDK sidecar:** Owns `@cursor/sdk` execution for PRD/spec generation because the SDK local runtime requires a Node-compatible process and cannot be bundled into the browser webview. The sidecar is compiled during desktop builds and communicates with Rust through a typed JSON stdin/stdout protocol. +* **Tauri/Rust backend:** Owns filesystem access, workspace scanning, session persistence, git diffing, native dialogs, PDF parsing, OS credential storage for the Cursor API key, generated document saving, sidecar supervision, and chat event streaming. +``` + +In section `5. PRD/Spec Generation`, replace runner-specific bullets with: + +```md +* `src/lib/cursorAgentRuntime.ts` composes Cursor prompts and invokes the desktop Cursor sidecar command through Rust. +* `src/cursorSidecar.ts` creates the local Cursor SDK agent, streams run events, waits for completion, lists available models, and emits structured JSON lines. +* Rust validates the workspace root, reads the Cursor API key from OS credential storage, launches the packaged sidecar, and parses its structured JSON-line output. +``` + +- [ ] **Step 4: Verify no legacy references remain** + +Run: + +```powershell +Get-ChildItem -Path src,src-tauri,docs -Recurse -File | + Where-Object { $_.FullName -notmatch '\\target\\' } | + Select-String -Pattern 'cursorAgentRunner','cursorModelsRunner','Bun TypeScript runner' -CaseSensitive:$false | + Select-Object Path,LineNumber,Line +``` + +Expected: no output. + +- [ ] **Step 5: Run frontend checks** + +Run: + +```powershell +bun run test +bun run build +``` + +Expected: PASS. + +- [ ] **Step 6: Commit** + +```powershell +git add src docs/SPEC.md +git commit -m "docs: document cursor sidecar architecture" +``` + +--- + +### Task 7: End-To-End Runtime Verification + +**Files:** + +- No source changes expected. + +- [ ] **Step 1: Build sidecar and frontend** + +Run: + +```powershell +bun run sidecar:build +bun run build +``` + +Expected: both PASS. + +- [ ] **Step 2: Check Rust** + +Run: + +```powershell +cargo check --manifest-path .\src-tauri\Cargo.toml +``` + +Expected: PASS. + +- [ ] **Step 3: Start Tauri dev** + +Run: + +```powershell +bun run tauri:dev +``` + +Expected: app launches and the sidecar binary is found without requiring `bun` at runtime. + +- [ ] **Step 4: Manual Cursor smoke test** + +In the app: + +1. Open Settings. +2. Save a valid Cursor API key. +3. Confirm model list loads. +4. Open a project folder. +5. Run PRD generation with a short prompt. +6. Confirm the terminal/event log shows Cursor status/tool events. +7. Confirm generated Markdown is non-empty and can be saved through the existing document flow. + +Expected: no error mentioning missing Bun, missing `.ts` runner, or malformed sidecar output. + +- [ ] **Step 5: Commit verification-only doc note if needed** + +If verification uncovers a necessary docs clarification, update `docs/SPEC.md` and commit: + +```powershell +git add docs/SPEC.md +git commit -m "docs: clarify cursor sidecar verification" +``` + +If no docs change is needed, do not create an empty commit. + +--- + +## Final Verification + +Run these before calling the work complete: + +```powershell +bun run test +bun run build +cargo test --manifest-path .\src-tauri\Cargo.toml cursor_agent +cargo check --manifest-path .\src-tauri\Cargo.toml +bun run sidecar:build +``` + +Expected: + +- All commands exit successfully. +- `src-tauri/binaries/specforge-cursor-sidecar-[.exe]` exists locally. +- No runtime path depends on `which::which("bun")`. +- No app path references `src/cursorAgentRunner.ts` or `src/cursorModelsRunner.ts`. +- `docs/SPEC.md` describes the packaged sidecar rather than source TypeScript runners. + +## Self-Review + +Spec coverage: + +- The plan keeps Rust as the trusted desktop backend. +- The plan keeps `@cursor/sdk` inside a Node-compatible sidecar. +- The plan removes runtime dependence on Bun from the packaged app. +- The plan updates shipped docs. +- The plan includes the required approval gate before changing `src-tauri/tauri.conf.json`. + +Placeholder scan: + +- No placeholder markers or unspecified implementation steps remain. + +Type consistency: + +- TypeScript sidecar requests use command tags: `listModels` and `runAgentPrompt`. +- Rust serializes the same command tags through `CursorSidecarRequest`. +- Sidecar output lines use `event`, `result`, and `models`. diff --git a/docs/superpowers/plans/2026-04-30-review-state-zustand-refactor.md b/docs/superpowers/plans/2026-04-30-review-state-zustand-refactor.md new file mode 100644 index 0000000..368022f --- /dev/null +++ b/docs/superpowers/plans/2026-04-30-review-state-zustand-refactor.md @@ -0,0 +1,172 @@ +# Review State Zustand Refactor Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Move review workspace state from `App.tsx` into Zustand and simplify `PrdScreen.tsx` and its component wiring. + +**Architecture:** Add `useWorkspaceUiStore` for non-persisted review shell state, then update hooks to read/write that store instead of receiving setters from `App.tsx`. Keep Cursor SDK runners because Tauri uses them as the Bun/TypeScript bridge to `@cursor/sdk`. + +**Tech Stack:** React 19, Zustand 5, TypeScript, Vitest, Tauri command bridge, Cursor SDK. + +--- + +### Task 1: Workspace UI Store + +**Files:** +- Create: `src/store/useWorkspaceUiStore.ts` +- Create: `src/store/useWorkspaceUiStore.test.ts` +- Modify: `src/hooks/useAppStoreSlices.ts` + +- [ ] **Step 1: Write failing tests** + +```ts +import { beforeEach, describe, expect, it } from "vitest"; +import { DEFAULT_PENDING_DIFF } from "../lib/runtime"; +import { useWorkspaceUiStore } from "./useWorkspaceUiStore"; + +describe("useWorkspaceUiStore", () => { + beforeEach(() => { + useWorkspaceUiStore.getState().resetWorkspaceUi(); + }); + + it("resets transient generation and workspace state", () => { + useWorkspaceUiStore.setState({ + commandSearch: "abc", + prdGenerationPrompt: "make PRD", + prdGenerationError: "bad", + specGenerationPrompt: "make spec", + specGenerationError: "bad spec", + workspaceNotice: "notice", + projectRootName: "Repo", + projectRootPath: "C:/Repo", + hasSelectedProject: true + }); + + useWorkspaceUiStore.getState().resetWorkspaceUi(); + + expect(useWorkspaceUiStore.getState()).toMatchObject({ + commandSearch: "", + prdGenerationPrompt: "", + prdGenerationError: "", + specGenerationPrompt: "", + specGenerationError: "", + workspaceNotice: "Finish the setup flow to load a project workspace.", + projectRootName: "No project selected", + projectRootPath: "", + hasSelectedProject: false + }); + }); + + it("stores loaded project shell metadata together", () => { + useWorkspaceUiStore.getState().setProjectShell({ + rootName: "SpecForge", + rootPath: "C:/repo", + configPath: ".specforge/project.json", + hasSelectedProject: true, + hasSavedProjectSettings: true + }); + + expect(useWorkspaceUiStore.getState()).toMatchObject({ + projectRootName: "SpecForge", + projectRootPath: "C:/repo", + projectConfigPath: ".specforge/project.json", + hasSelectedProject: true, + hasSavedProjectSettings: true + }); + }); + + it("restores latest diff to the runtime fallback", () => { + useWorkspaceUiStore.getState().setLatestDiff("diff --git a/file b/file"); + useWorkspaceUiStore.getState().resetLatestDiff(); + + expect(useWorkspaceUiStore.getState().latestDiff).toBe(DEFAULT_PENDING_DIFF); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `bun run test src/store/useWorkspaceUiStore.test.ts` + +Expected: FAIL because `useWorkspaceUiStore` does not exist. + +- [ ] **Step 3: Implement store and slice export** + +Create the store with explicit setter actions for each state group and export `useWorkspaceUiStoreSlice()` from `useAppStoreSlices.ts`. + +- [ ] **Step 4: Run test to verify it passes** + +Run: `bun run test src/store/useWorkspaceUiStore.test.ts` + +Expected: PASS. + +### Task 2: Migrate App Hook Inputs + +**Files:** +- Modify: `src/App.tsx` +- Modify: `src/hooks/useAppView.ts` +- Modify: `src/hooks/useAppUiHandlers.ts` +- Modify: `src/hooks/useDocumentHandlers.ts` +- Modify: `src/hooks/useProjectHandlers.ts` +- Modify: `src/hooks/useAppScreenProps.ts` + +- [ ] **Step 1: Update hook option types** + +Replace individual prompt/error/project-root/search/cursor-model/external-editor props with `workspaceUiState` where those values are needed. + +- [ ] **Step 2: Update call sites** + +Use `workspaceUiState` actions in `App.tsx` for diagnostics, project restore, document generation, and workspace file opening. + +- [ ] **Step 3: Remove migrated `useState` calls** + +Delete app-local state for command search, import/project loading, latest diff, project root/config/status/error, workspace notice, external editors, cursor models, saved/selected/restore flags, workspace file lookup, and generation prompt/error. + +- [ ] **Step 4: Run tests** + +Run: `bun run test` + +Expected: PASS. + +### Task 3: Simplify Review Screen Wiring + +**Files:** +- Modify: `src/screens/PrdScreen.tsx` +- Modify: `src/hooks/useAppScreenProps.ts` + +- [ ] **Step 1: Reduce `PrdScreenProps`** + +Keep only callbacks/refs that are naturally owned by `App.tsx`: `onOpenChat`, `onRefresh`, `searchInputRef`, and the three child prop bags as an interim boundary. + +- [ ] **Step 2: Move topbar state selection into `PrdScreen`** + +Read agent status, workspace root name, model settings, Cursor models, and configured providers from stores/derived props so `ReviewTopBar` no longer depends on `controlColumnProps`. + +- [ ] **Step 3: Run build** + +Run: `bun run build` + +Expected: PASS. + +### Task 4: Verification + +**Files:** +- No new files. + +- [ ] **Step 1: Run frontend test suite** + +Run: `bun run test` + +Expected: PASS. + +- [ ] **Step 2: Run frontend build** + +Run: `bun run build` + +Expected: PASS. + +- [ ] **Step 3: Review Cursor runner usage** + +Run: `Select-String -Path 'src-tauri/src/cursor_agent.rs','src/cursorAgentRunner.ts','src/cursorModelsRunner.ts' -Pattern 'Agent.create|Cursor.models.list|cursorAgentRunner|cursorModelsRunner'` + +Expected: Output shows Rust commands invoking the TypeScript runners and runners using documented Cursor SDK APIs. diff --git a/docs/superpowers/specs/2026-04-29-cursor-sdk-refactor-design.md b/docs/superpowers/specs/2026-04-29-cursor-sdk-refactor-design.md new file mode 100644 index 0000000..593c394 --- /dev/null +++ b/docs/superpowers/specs/2026-04-29-cursor-sdk-refactor-design.md @@ -0,0 +1,24 @@ +# Cursor SDK Refactor Design + +## Goal + +Move SpecForge's PRD and spec generation away from Codex and Claude CLI orchestration and onto Cursor SDK agents, while keeping provider-specific model logic in TypeScript/Bun and Rust focused on the desktop boundary. + +## Decisions + +- Cursor API key is stored in the OS credential store through Rust `keyring` commands. +- The key is never written to `.specforge/settings.json` or browser `localStorage`. +- Project settings continue to live in `.specforge/settings.json`, but they store Cursor model defaults and editable agent descriptions. +- PRD generation uses a PRD agent description plus the user's product context. +- Spec generation uses a spec agent description plus the current PRD and the user's technical guidance. +- Execution agent description is added to settings now, but chat and execution screens are not refactored in this phase. + +## Architecture + +Rust remains the data bridge: workspace selection, settings read/write, document read/write, git diff, Cursor key storage, and delegation to the Bun runner. React owns Cursor prompt construction, and `src/cursorAgentRunner.ts` owns `@cursor/sdk` calls because the SDK cannot be bundled into the webview. Generated markdown returns to the React flow, then Rust writes it to the configured workspace path. + +## Verification + +- Unit tests cover Cursor model defaults, settings migration from legacy prompt fields, and prompt payload construction. +- Rust check verifies keyring commands and payload shape. +- Frontend build verifies the React settings and generation flow compile together. diff --git a/docs/superpowers/specs/2026-04-30-review-state-zustand-refactor-design.md b/docs/superpowers/specs/2026-04-30-review-state-zustand-refactor-design.md new file mode 100644 index 0000000..008531a --- /dev/null +++ b/docs/superpowers/specs/2026-04-30-review-state-zustand-refactor-design.md @@ -0,0 +1,34 @@ +# Review State Zustand Refactor Design + +## Goal + +Move review-screen UI state out of `App.tsx` into Zustand and reduce `PrdScreen.tsx` from a prop-bag pass-through into a store-backed review shell. + +## Architecture + +Add a focused workspace UI store for project shell metadata, generation form state, search state, Cursor model metadata, external editor metadata, notices, and browser/desktop workspace file lookup. Keep persisted project document data in `useProjectStore`, execution runtime in `useAgentStore`, and saved settings in `useSettingsStore`. + +`App.tsx` remains responsible for route selection, refs needed by DOM/file pickers, subscriptions, and top-level async orchestration. Review screen components subscribe to the store slices they render with `useShallow`, matching Zustand guidance and reducing prop drilling. + +## Cursor SDK Runners + +Keep `src/cursorAgentRunner.ts` and `src/cursorModelsRunner.ts`. They are the documented TypeScript SDK bridge used by Tauri Rust commands, since Rust cannot import `@cursor/sdk` directly. The runner code should stay thin and aligned with Cursor SDK docs: `Agent.create`, `agent.send`, `run.stream`, `run.wait`, and `Cursor.models.list`. + +## State Ownership + +- `useProjectStore`: PRD/spec content, configured paths, model/reasoning/autonomy settings, active review tab, pane modes, editor tabs. +- `useAgentStore`: execution status, terminal output, pending diff, execution summary. +- `useSettingsStore`: theme, Cursor key input, persisted recent projects, environment scan, workspace entries. +- New `useWorkspaceUiStore`: command search, search open flag, import/project loading flags, latest diff, root metadata, project status/error messages, workspace notice, external editors, Cursor models, saved/selected/restore flags, workspace file lookup, PRD/spec generation prompt/error state. + +## UI Flow + +`PrdScreen` reads its own search/topbar state from stores and renders: + +- `ReviewTopBar`: status, workspace root name, model/reasoning/mode controls, refresh and chat actions. +- `MainWorkspace`: kept initially as a presentational component but fed by a small review container instead of `App.tsx`. +- `InspectorColumn`: fed by a workspace inspector container using store selectors. + +## Testing + +Add unit tests for the new store reset/project metadata/generation state actions first. Then run the existing focused tests and full build. Because this is a refactor, UI behavior should remain unchanged. diff --git a/package.json b/package.json index a1a690a..a4195fb 100644 --- a/package.json +++ b/package.json @@ -6,22 +6,24 @@ "packageManager": "bun@1.3.6", "scripts": { "dev": "vite", - "tauri:dev": "tauri dev", + "sidecar:build": "bun scripts/build-cursor-sidecar.ts", + "tauri:dev": "bun run sidecar:build && tauri dev", "build": "tsc && vite build", "typecheck": "tsc --noEmit", "test": "vitest run", "test:watch": "vitest", - "lint": "biome check src", - "lint:fix": "biome check --fix src", + "lint": "bun ./node_modules/@biomejs/biome/bin/biome check src", + "lint:fix": "bun ./node_modules/@biomejs/biome/bin/biome check --fix src", "preview": "vite preview", "tauri": "tauri" }, "dependencies": { + "@cursor/sdk": "^1.0.10", "@heroui/react": "^3.0.2", "@heroui/styles": "^3.0.2", "@tauri-apps/api": "^2.8.0", - "ignore": "^7.0.5", "iconoir-react": "^7.11.0", + "ignore": "^7.0.5", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.9.4", @@ -43,4 +45,4 @@ "@biomejs/biome": "^2.0.0", "vitest": "^3.2.1" } -} \ No newline at end of file +} diff --git a/scripts/build-cursor-sidecar.ts b/scripts/build-cursor-sidecar.ts new file mode 100644 index 0000000..1d7b828 --- /dev/null +++ b/scripts/build-cursor-sidecar.ts @@ -0,0 +1,40 @@ +import { execFileSync } from "node:child_process"; +import { mkdirSync, rmSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; + +const repoRoot = resolve(import.meta.dir, ".."); +const sidecarName = "specforge-cursor-sidecar"; +const targetTriple = execFileSync("rustc", ["--print", "host-tuple"], { + cwd: repoRoot, + encoding: "utf8" +}).trim(); +const extension = process.platform === "win32" ? ".exe" : ""; +const outputPath = join( + repoRoot, + "src-tauri", + "binaries", + `${sidecarName}-${targetTriple}${extension}` +); + +mkdirSync(dirname(outputPath), { recursive: true }); +rmSync(outputPath, { force: true }); + +const result = await Bun.build({ + entrypoints: [join(repoRoot, "src", "cursorSidecar.ts")], + compile: { + outfile: outputPath + }, + minify: true, + define: { + "process.env.NODE_ENV": JSON.stringify("production") + } +}); + +if (!result.success) { + for (const log of result.logs) { + console.error(log); + } + process.exit(1); +} + +console.log(`Built Cursor sidecar: ${outputPath}`); diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 0d32899..fe7c576 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -8,6 +8,26 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aegis" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78412fa53e6da95324e8902c3641b3ff32ab45258582ea997eb9169c68ffa219" +dependencies = [ + "cc", + "softaes", +] + [[package]] name = "aes" version = "0.8.4" @@ -19,6 +39,20 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -43,6 +77,30 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-native-keyring-store" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c6349ddff23194f8fdce2ea8849380f5a4868c1648965b70e801e104cba9b3" +dependencies = [ + "base64 0.22.1", + "jni", + "keyring-core", + "log", + "ndk-context", + "regex", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -52,12 +110,98 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "antithesis_sdk" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18dbd97a5b6c21cc9176891cf715f7f0c273caf3959897f43b9bd1231939e675" +dependencies = [ + "libc", + "libloading 0.8.9", + "linkme", + "once_cell", + "rand 0.8.5", + "rustc_version_runtime", + "serde", + "serde_json", +] + [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "apple-native-keyring-store" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7be2f067ccd8d4b4d4a66ddafe0f32a5dff31732f32dbff85fefc40929b1f72" +dependencies = [ + "keyring-core", + "log", + "security-framework", +] + +[[package]] +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] + [[package]] name = "ashpd" version = "0.11.1" @@ -80,6 +224,12 @@ dependencies = [ "zbus", ] +[[package]] +name = "assoc" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdc70193dadb9d7287fa4b633f15f90c876915b31f6af17da307fc59c9859a8" + [[package]] name = "async-broadcast" version = "0.7.2" @@ -142,7 +292,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix", + "rustix 1.1.4", "slab", "windows-sys 0.61.2", ] @@ -184,7 +334,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix", + "rustix 1.1.4", ] [[package]] @@ -210,7 +360,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix", + "rustix 1.1.4", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -280,6 +430,42 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bigdecimal" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" +dependencies = [ + "autocfg", + "libm", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.11.0", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.117", + "which 4.4.2", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -310,6 +496,27 @@ dependencies = [ "serde_core", ] +[[package]] +name = "bitpacking" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a7139abd3d9cebf8cd6f920a389cf3dc9576172e32f4563f188cae3c3eb019" +dependencies = [ + "crunchy", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -350,6 +557,40 @@ dependencies = [ "piper", ] +[[package]] +name = "bon" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47dbe92550676ee653353c310dfb9cf6ba17ee70396e1f7cf0a2020ad49b2fe" +dependencies = [ + "bon-macros", + "rustversion", +] + +[[package]] +name = "bon-macros" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" +dependencies = [ + "darling", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.117", +] + +[[package]] +name = "branches" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e426eb5cc1900033930ec955317b302e68f19f326cc7bb0c8a86865a826cdf0c" +dependencies = [ + "rustc_version", +] + [[package]] name = "brotli" version = "8.0.2" @@ -398,6 +639,20 @@ name = "bytemuck" version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] name = "byteorder" @@ -502,12 +757,27 @@ dependencies = [ "shlex", ] +[[package]] +name = "census" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4c707c6a209cbe82d10abd08e1ea8995e9ea937d2550646e02798948992be0" + [[package]] name = "cesu8" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + [[package]] name = "cfb" version = "0.7.3" @@ -535,6 +805,18 @@ 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 = "cfg_block" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18758054972164c3264f7c8386f5fc6da6114cb46b619fd365d4e3b2dc3ae487" + [[package]] name = "chrono" version = "0.4.44" @@ -542,8 +824,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-link 0.2.1", ] @@ -557,6 +841,63 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading 0.8.9", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "combine" version = "4.6.7" @@ -617,7 +958,7 @@ dependencies = [ "bitflags 2.11.0", "core-foundation", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -641,6 +982,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -678,12 +1028,28 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-skiplist" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.7" @@ -691,6 +1057,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -744,6 +1111,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "darling" version = "0.23.0" @@ -779,22 +1155,102 @@ dependencies = [ ] [[package]] -name = "deranged" -version = "0.5.8" +name = "dashmap" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ - "powerfmt", - "serde_core", + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", + "serde", ] [[package]] -name = "derive_more" -version = "0.99.20" +name = "datasketches" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case", +checksum = "c286de4e81ea2590afc24d754e0f83810c566f50a1388fa75ebd57928c0d9745" + +[[package]] +name = "db-keystore" +version = "0.4.2-pre.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d682fd95a70cd0de8dd61a95cbd5fe16c9411cdd335864bcce58552f3df6c4" +dependencies = [ + "anyhow", + "clap", + "futures", + "keyring-core", + "log", + "regex", + "serde", + "serde_json", + "turso", + "uuid", + "zeroize", +] + +[[package]] +name = "dbus" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b942602992bb7acfd1f51c49811c58a610ef9181b6e66f3e519d79b540a3bf73" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "dbus-secret-service" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6" +dependencies = [ + "aes", + "block-padding", + "cbc", + "dbus", + "fastrand", + "hkdf", + "num", + "once_cell", + "openssl", + "sha2", + "zeroize", +] + +[[package]] +name = "dbus-secret-service-keyring-store" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21d8f54da401bb5eb2a4d873ac4b359f4a599df2ca8634bb5b8c045e5ee78757" +dependencies = [ + "dbus-secret-service", + "keyring-core", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +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", @@ -830,6 +1286,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -882,7 +1339,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" dependencies = [ - "libloading", + "libloading 0.7.4", ] [[package]] @@ -929,6 +1386,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + [[package]] name = "dpi" version = "0.1.2" @@ -1036,6 +1499,25 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "env_filter" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +dependencies = [ + "log", +] + +[[package]] +name = "env_logger" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +dependencies = [ + "env_filter", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1084,6 +1566,29 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fastbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" +dependencies = [ + "getrandom 0.3.4", + "libm", + "siphasher 1.0.2", +] + +[[package]] +name = "fastdivide" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afc2bd4d5a73106dd53d10d73d3401c2f32730ba2c0b93ddb888a8983680471" + [[package]] name = "fastrand" version = "2.4.1" @@ -1143,6 +1648,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1150,7 +1664,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -1164,6 +1678,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -1179,6 +1699,22 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs4" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" +dependencies = [ + "rustix 1.1.4", + "windows-sys 0.59.0", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futf" version = "0.1.5" @@ -1189,6 +1725,21 @@ dependencies = [ "new_debug_unreachable", ] +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -1196,6 +1747,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1263,6 +1815,7 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1381,6 +1934,36 @@ dependencies = [ "x11", ] +[[package]] +name = "genawaiter" +version = "0.99.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86bd0361bcbde39b13475e6e36cb24c329964aa2611be285289d1e4b751c1a0" +dependencies = [ + "genawaiter-macro", +] + +[[package]] +name = "genawaiter-macro" +version = "0.99.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b32dfe1fdfc0bbde1f22a5da25355514b5e450c33a6af6770884c8750aedfbc" + +[[package]] +name = "generator" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link 0.2.1", + "windows-result 0.4.1", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1438,6 +2021,16 @@ dependencies = [ "wasip3", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gio" version = "0.18.4" @@ -1618,6 +2211,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -1627,6 +2226,17 @@ dependencies = [ "foldhash 0.1.5", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + [[package]] name = "hashbrown" version = "0.17.0" @@ -1657,6 +2267,33 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "html5ever" version = "0.29.1" @@ -1679,6 +2316,12 @@ dependencies = [ "markup5ever 0.38.0", ] +[[package]] +name = "htmlescape" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" + [[package]] name = "http" version = "1.4.0" @@ -1968,6 +2611,35 @@ dependencies = [ "generic-array", ] +[[package]] +name = "intrusive-collections" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" +dependencies = [ + "memoffset", +] + +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "io-uring" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d09b98f7eace8982db770e4408e7470b028ce513ac28fecdc6bf4c30fe92b62" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "libc", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -1984,6 +2656,30 @@ dependencies = [ "serde", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[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.18" @@ -2153,6 +2849,41 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "keyring" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc17b941cdab9063edc0f241dbe10ba9895a8b3f92aa908fd4b3533fea2ae841" +dependencies = [ + "android-native-keyring-store", + "apple-native-keyring-store", + "base64 0.22.1", + "clap", + "db-keystore", + "dbus-secret-service-keyring-store", + "keyring-core", + "linux-keyutils-keyring-store", + "rpassword", + "rprompt", + "windows-native-keyring-store", + "zbus-secret-service-keyring-store", +] + +[[package]] +name = "keyring-core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1e621458ca9c51aa110bd0339d4751a056b9576bf1253aee1aa560dda0fc9d" +dependencies = [ + "chrono", + "dashmap", + "log", + "regex", + "ron", + "serde", + "uuid", +] + [[package]] name = "kuchikiki" version = "0.8.8-speedreader" @@ -2165,12 +2896,30 @@ dependencies = [ "selectors 0.24.0", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "leb128fmt" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "levenshtein_automata" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" + [[package]] name = "libappindicator" version = "0.9.0" @@ -2191,7 +2940,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" dependencies = [ "gtk-sys", - "libloading", + "libloading 0.7.4", "once_cell", ] @@ -2202,19 +2951,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] -name = "libgit2-sys" -version = "0.18.3+1.9.2" +name = "libdbus-sys" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" dependencies = [ "cc", - "libc", - "libz-sys", "pkg-config", ] [[package]] -name = "libloading" +name = "libgit2-sys" +version = "0.18.3+1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + +[[package]] +name = "libloading" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" @@ -2223,6 +2982,31 @@ dependencies = [ "winapi", ] +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libmimalloc-sys" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d1eacfa31c33ec25e873c136ba5669f00f9866d0688bea7be4d3f7e43067df6" +dependencies = [ + "cc", +] + [[package]] name = "libredox" version = "0.1.16" @@ -2244,6 +3028,52 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linkme" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83272d46373fb8decca684579ac3e7c8f3d71d4cc3aa693df8759e260ae41cf" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d59e20403c7d08fe62b4376edfe5c7fb2ef1e6b1465379686d0f21c8df444b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "linux-keyutils" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590" +dependencies = [ + "bitflags 2.11.0", + "libc", +] + +[[package]] +name = "linux-keyutils-keyring-store" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39fbed79f71dc21eb21d3d07c0e908a3c58ff9a1fdbf5cf44230fb3deb6d994b" +dependencies = [ + "keyring-core", + "linux-keyutils", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2271,6 +3101,19 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + [[package]] name = "lopdf" version = "0.36.0" @@ -2289,7 +3132,7 @@ dependencies = [ "jiff", "log", "md-5", - "nom", + "nom 8.0.0", "nom_locate", "rand 0.9.2", "rangemap", @@ -2301,6 +3144,21 @@ dependencies = [ "weezl", ] +[[package]] +name = "lru" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "lz4_flex" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db9a0d582c2874f68138a16ce1867e0ffde6c0bb0a0df85e1f36d04146db488a" + [[package]] name = "mac" version = "0.1.1" @@ -2343,6 +3201,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "matches" version = "0.1.10" @@ -2359,12 +3226,30 @@ dependencies = [ "digest", ] +[[package]] +name = "measure_time" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51c55d61e72fc3ab704396c5fa16f4c184db37978ae4e94ca8959693a235fc0e" +dependencies = [ + "log", +] + [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.9.1" @@ -2374,12 +3259,49 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "cfg-if", + "miette-derive", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "mimalloc" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3627c4272df786b9260cabaa46aec1d59c93ede723d4c3ef646c503816b0640" +dependencies = [ + "libmimalloc-sys", +] + [[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" @@ -2422,6 +3344,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "murmurhash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" + [[package]] name = "ndk" version = "0.9.0" @@ -2464,6 +3392,16 @@ 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 = "nom" version = "8.0.0" @@ -2481,7 +3419,49 @@ checksum = "0b577e2d69827c4740cba2b52efaad1c4cc7c73042860b199710b3575c68438d" dependencies = [ "bytecount", "memchr", - "nom", + "nom 8.0.0", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", ] [[package]] @@ -2490,6 +3470,37 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2650,12 +3661,87 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "oneshot" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269bca4c2591a28585d6bf10d9ed0332b7d76900a1b02bec41bdc3a2cdcda107" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "openssl-src" +version = "300.6.0+3.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8e8cbfd3a4a8c8f089147fd7aaa33cf8c7450c4d09f8f80698a0cf093abeff4" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-float" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -2666,6 +3752,30 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "ownedbytes" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fbd56f7631767e61784dc43f8580f403f4475bd4aaa4da003e6295e1bab4a7e" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "pack1" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6e7cd9bd638dc2c831519a0caa1c006cab771a92b1303403a8322773c5b72d6" +dependencies = [ + "bytemuck", +] + [[package]] name = "pango" version = "0.18.3" @@ -2720,6 +3830,12 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "pastey" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5a797f0e07bdf071d15742978fc3128ec6c22891c31a3a931513263904c982a" + [[package]] name = "percent-encoding" version = "2.3.2" @@ -2972,7 +4088,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -2982,6 +4098,18 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.13.1" @@ -3105,6 +4233,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "quick-xml" version = "0.38.4" @@ -3144,6 +4295,12 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -3155,7 +4312,7 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc", - "rand_pcg", + "rand_pcg 0.2.1", ] [[package]] @@ -3254,12 +4411,30 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_pcg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rangemap" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" +[[package]] +name = "rapidhash" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +dependencies = [ + "rustversion", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -3413,6 +4588,77 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "roaring" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dedc5658c6ecb3bdb5ef5f3295bb9253f42dcf3fd1402c03f6b1f7659c3c4a9" +dependencies = [ + "bytemuck", + "byteorder", +] + +[[package]] +name = "ron" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4147b952f3f819eca0e99527022f7d6a8d05f111aeb0a62960c74eb283bec8fc" +dependencies = [ + "bitflags 2.11.0", + "once_cell", + "serde", + "serde_derive", + "typeid", + "unicode-ident", +] + +[[package]] +name = "rpassword" +version = "7.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2501c67132bd19c3005b0111fba298907ef002c8c1cf68e25634707e38bf66fe" +dependencies = [ + "libc", + "rtoolbox", + "windows-sys 0.61.2", +] + +[[package]] +name = "rprompt" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69abf524bb9ccb7c071f7231441288d74b48d176cb309eb00e6f77d186c6e035" +dependencies = [ + "rtoolbox", + "windows-sys 0.59.0", +] + +[[package]] +name = "rtoolbox" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "rust-stemmers" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.1.2" @@ -3428,6 +4674,29 @@ dependencies = [ "semver", ] +[[package]] +name = "rustc_version_runtime" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd18cd2bae1820af0b6ad5e54f4a51d0f3fcc53b05f845675074efcc7af071d" +dependencies = [ + "rustc_version", + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.1.4" @@ -3437,7 +4706,7 @@ dependencies = [ "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] @@ -3447,6 +4716,12 @@ 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" @@ -3519,6 +4794,48 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "secret-service" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a62d7f86047af0077255a29494136b9aaaf697c76ff70b8e49cded4e2623c14" +dependencies = [ + "aes", + "cbc", + "futures-util", + "generic-array", + "getrandom 0.2.17", + "hkdf", + "num", + "once_cell", + "serde", + "sha2", + "zbus", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.24.0" @@ -3551,7 +4868,7 @@ dependencies = [ "phf 0.13.1", "phf_codegen 0.13.1", "precomputed-hash", - "rustc-hash", + "rustc-hash 2.1.2", "servo_arc 0.4.3", "smallvec", ] @@ -3733,6 +5050,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sha2" version = "0.10.9" @@ -3744,12 +5067,41 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shuttle" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab17edba38d63047f46780cf7360acf7467fec2c048928689a5c1dd1c2b4e31" +dependencies = [ + "assoc", + "bitvec", + "cfg-if", + "generator", + "hex", + "owo-colors", + "rand 0.8.5", + "rand_core 0.6.4", + "rand_pcg 0.3.1", + "scoped-tls", + "smallvec", + "tracing", +] + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -3766,6 +5118,15 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "simsimd" +version = "6.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fb3bc3cdce07a7d7d4caa4c54f8aa967f6be41690482b54b24100a2253fa70" +dependencies = [ + "cc", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -3778,6 +5139,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +[[package]] +name = "sketches-ddsketch" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e40b6cf54d988dc1a2223531b969c9a9e30906ad90ef64890c27b4bfbb46ea" +dependencies = [ + "serde", +] + [[package]] name = "slab" version = "0.4.12" @@ -3800,6 +5170,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "softaes" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef461faaeb36c340b6c887167a9054a034f6acfc50a014ead26a02b4356b3de" + [[package]] name = "softbuffer" version = "0.4.8" @@ -3854,13 +5230,15 @@ version = "0.1.0" dependencies = [ "git2", "ignore", + "keyring", + "keyring-core", "lopdf", "rfd", "serde", "serde_json", "tauri", "tauri-build", - "which", + "which 8.0.2", ] [[package]] @@ -3935,6 +5313,34 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.117", +] + +[[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" @@ -3946,6 +5352,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "1.0.109" @@ -3983,22 +5395,170 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[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 = "tantivy" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edde6a10743fff00a4e1a8c9ef020bf5f3cbad301b7d2d39f2b07f123c4eac07" +dependencies = [ + "aho-corasick", + "arc-swap", + "base64 0.22.1", + "bitpacking", + "bon", + "byteorder", + "census", + "crc32fast", + "crossbeam-channel", + "datasketches", + "downcast-rs 2.0.2", + "fastdivide", + "fnv", + "fs4", + "htmlescape", + "itertools 0.14.0", + "levenshtein_automata", + "log", + "lru", + "lz4_flex", + "measure_time", + "memmap2", + "once_cell", + "oneshot", + "rayon", + "regex", + "rust-stemmers", + "rustc-hash 2.1.2", + "serde", + "serde_json", + "sketches-ddsketch", + "smallvec", + "tantivy-bitpacker", + "tantivy-columnar", + "tantivy-common", + "tantivy-fst", + "tantivy-query-grammar", + "tantivy-stacker", + "tantivy-tokenizer-api", + "tempfile", + "thiserror 2.0.18", + "time", + "typetag", + "uuid", + "winapi", +] + +[[package]] +name = "tantivy-bitpacker" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fed3d674429bcd2de5d0a6d1aa5495fed8afd9c5ecce993019caf7615f53fa4" +dependencies = [ + "bitpacking", +] + +[[package]] +name = "tantivy-columnar" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c57166f5bcfd478f370ab8445afb4678dce44801fa5ce5c451aaf8595583c5dc" +dependencies = [ + "downcast-rs 2.0.2", + "fastdivide", + "itertools 0.14.0", + "serde", + "tantivy-bitpacker", + "tantivy-common", + "tantivy-sstable", + "tantivy-stacker", +] + +[[package]] +name = "tantivy-common" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf10915aa75da3c3b0d58b58853d2e889efbaf32d4982a4c3715dde6bba23e5" +dependencies = [ + "async-trait", + "byteorder", + "ownedbytes", + "serde", + "time", +] + +[[package]] +name = "tantivy-fst" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d60769b80ad7953d8a7b2c70cdfe722bbcdcac6bccc8ac934c40c034d866fc18" +dependencies = [ + "byteorder", + "regex-syntax", + "utf8-ranges", +] + +[[package]] +name = "tantivy-query-grammar" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfadb8526b6da90704feb293b0701a6aae62ea14983143344be2dc5ce30f1d82" +dependencies = [ + "fnv", + "nom 7.1.3", + "ordered-float", + "serde", + "serde_json", +] + +[[package]] +name = "tantivy-sstable" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2cfc3ac5164cbadc28965ffb145a8f47582a60ae5897859ad8d4316596c606" +dependencies = [ + "futures-util", + "itertools 0.14.0", + "tantivy-bitpacker", + "tantivy-common", + "tantivy-fst", + "zstd", +] + +[[package]] +name = "tantivy-stacker" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbb051742da9d53ca9e8fff43a9b10e319338b24e2c0e15d0372df19ffeb951" +dependencies = [ + "murmurhash32", + "tantivy-common", ] [[package]] -name = "system-deps" -version = "6.2.2" +name = "tantivy-tokenizer-api" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +checksum = "eac258c2c6390673f2685813afeeafcb8c4e0ee7de8dd3fc46838dcc37263f98" dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml 0.8.2", - "version-compare", + "serde", ] [[package]] @@ -4050,6 +5610,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.16" @@ -4279,7 +5845,7 @@ dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", - "rustix", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -4344,6 +5910,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.47" @@ -4588,6 +6163,19 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" +dependencies = [ + "crossbeam-channel", + "symlink", + "thiserror 2.0.18", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.31" @@ -4606,6 +6194,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -4636,6 +6254,201 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "turso" +version = "0.6.0-pre.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c331e6a1f17f7864b71b2f3c009e9c2c2624bc549ff2025831fcb3be6fa2062f" +dependencies = [ + "mimalloc", + "thiserror 2.0.18", + "tracing", + "tracing-subscriber", + "turso_core", + "turso_sdk_kit", + "turso_sync_sdk_kit", +] + +[[package]] +name = "turso_core" +version = "0.6.0-pre.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4803a83babd2a8869f1c62468d32d4bc3300b9e4287e08a7d91cc5deea2bbf13" +dependencies = [ + "aegis", + "aes", + "aes-gcm", + "antithesis_sdk", + "arc-swap", + "bigdecimal", + "bitflags 2.11.0", + "branches", + "bumpalo", + "bytemuck", + "cfg_aliases", + "cfg_block", + "chrono", + "crc32c", + "crossbeam-skiplist", + "either", + "fallible-iterator", + "fastbloom", + "hex", + "intrusive-collections", + "io-uring", + "libc", + "libloading 0.8.9", + "libm", + "loom", + "miette", + "num-bigint", + "num-traits", + "pack1", + "parking_lot", + "pastey", + "polling", + "rand 0.9.2", + "rapidhash", + "regex", + "regex-syntax", + "roaring", + "rustc-hash 2.1.2", + "rustix 1.1.4", + "ryu", + "serde_json", + "shuttle", + "simsimd", + "smallvec", + "strum", + "strum_macros", + "tantivy", + "tempfile", + "thiserror 2.0.18", + "tracing", + "tracing-subscriber", + "turso_ext", + "turso_macros", + "turso_parser", + "twox-hash", + "uncased", + "uuid", + "windows-sys 0.61.2", +] + +[[package]] +name = "turso_ext" +version = "0.6.0-pre.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823ee9910bc2f59e6ce9a045b9a11c812fa67e6b8242690ff7ae9e0ac42d1782" +dependencies = [ + "chrono", + "getrandom 0.3.4", + "turso_macros", +] + +[[package]] +name = "turso_macros" +version = "0.6.0-pre.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be58b0098c329a7a80d9684c896bab5c8e1bbf4e94ec0a45d5ce89b12e41a751" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "turso_parser" +version = "0.6.0-pre.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab02213f1e7115b7a657c886bf369bf84d4cffb7ca2ba219941b1d3bd60bd92" +dependencies = [ + "bitflags 2.11.0", + "memchr", + "miette", + "strum", + "strum_macros", + "thiserror 2.0.18", + "turso_macros", +] + +[[package]] +name = "turso_sdk_kit" +version = "0.6.0-pre.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b1563ab83a79ed936935e1f3d1ebb8775c16036fd58c3d99acdd4e777a6f61" +dependencies = [ + "bindgen", + "env_logger", + "parking_lot", + "tracing", + "tracing-appender", + "tracing-subscriber", + "turso_core", + "turso_sdk_kit_macros", +] + +[[package]] +name = "turso_sdk_kit_macros" +version = "0.6.0-pre.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "482301b24cf8d81e87bb83c6f4efc1efdc935a0489052c0d00e2f5b4e432c10a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "turso_sync_engine" +version = "0.6.0-pre.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f187919622f8ee861637bd5ce4d779b86becf18e7446f901c28d2a49399d2b7f" +dependencies = [ + "base64 0.22.1", + "bytes", + "genawaiter", + "http", + "libc", + "prost", + "roaring", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", + "turso_core", + "turso_parser", + "uuid", +] + +[[package]] +name = "turso_sync_sdk_kit" +version = "0.6.0-pre.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5f0bf6f8ca34c9148e1d799b3750c9154627a40c357761250e1930c7212128b" +dependencies = [ + "bindgen", + "env_logger", + "genawaiter", + "parking_lot", + "tracing", + "tracing-appender", + "tracing-subscriber", + "turso_core", + "turso_sdk_kit", + "turso_sdk_kit_macros", + "turso_sync_engine", +] + +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" +dependencies = [ + "rand 0.9.2", +] + [[package]] name = "typeid" version = "1.0.3" @@ -4648,6 +6461,30 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "typetag" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be2212c8a9b9bcfca32024de14998494cf9a5dfa59ea1b829de98bac374b86bf" +dependencies = [ + "erased-serde", + "inventory", + "once_cell", + "serde", + "typetag-impl", +] + +[[package]] +name = "typetag-impl" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27a7a9b72ba121f6f1f6c3632b85604cac41aedb5ddc70accbebb6cac83de846" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "uds_windows" version = "1.2.1" @@ -4659,6 +6496,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + [[package]] name = "unic-char-property" version = "0.9.0" @@ -4733,12 +6579,28 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "url" version = "2.5.8" @@ -4776,12 +6638,24 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8-ranges" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" + [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.23.0" @@ -4791,9 +6665,16 @@ dependencies = [ "getrandom 0.4.2", "js-sys", "serde_core", + "sha1_smol", "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" @@ -4990,8 +6871,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" dependencies = [ "cc", - "downcast-rs", - "rustix", + "downcast-rs 1.2.1", + "rustix 1.1.4", "scoped-tls", "smallvec", "wayland-sys", @@ -5004,7 +6885,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" dependencies = [ "bitflags 2.11.0", - "rustix", + "rustix 1.1.4", "wayland-backend", "wayland-scanner", ] @@ -5151,6 +7032,18 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + [[package]] name = "which" version = "8.0.2" @@ -5299,6 +7192,19 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-native-keyring-store" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5fd986f648459dd29aa252ed3a5ad11a60c0b1251bf81625fb03a86c69d274e" +dependencies = [ + "byteorder", + "keyring-core", + "regex", + "windows-sys 0.61.2", + "zeroize", +] + [[package]] name = "windows-numerics" version = "0.2.0" @@ -5760,6 +7666,15 @@ dependencies = [ "x11-dl", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x11" version = "2.21.0" @@ -5826,7 +7741,7 @@ dependencies = [ "hex", "libc", "ordered-stream", - "rustix", + "rustix 1.1.4", "serde", "serde_repr", "tracing", @@ -5839,6 +7754,17 @@ dependencies = [ "zvariant", ] +[[package]] +name = "zbus-secret-service-keyring-store" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ccede190ba363386a24e8021c7f3848393976609ec9f5d1f8c6c09ef37075b4" +dependencies = [ + "keyring-core", + "secret-service", + "zbus", +] + [[package]] name = "zbus_macros" version = "5.14.0" @@ -5906,6 +7832,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "zerotrie" version = "0.2.4" @@ -5945,6 +7891,34 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "zvariant" version = "5.10.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index b01d415..2ece5aa 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -20,6 +20,8 @@ tauri-build = { version = "2", features = [] } [dependencies] git2 = { version = "0.20", default-features = false, features = ["vendored-libgit2"] } ignore = "0.4" +keyring = "4.0.0" +keyring-core = "1.0.0" lopdf = "0.36" rfd = "0.15" serde = { version = "1", features = ["derive"] } diff --git a/src-tauri/binaries/.gitkeep b/src-tauri/binaries/.gitkeep new file mode 100644 index 0000000..b9af0ea --- /dev/null +++ b/src-tauri/binaries/.gitkeep @@ -0,0 +1 @@ +tracked directory for generated Cursor sidecar binaries diff --git a/src-tauri/src/chat/commands.rs b/src-tauri/src/chat/commands.rs index c06b656..8ae8184 100644 --- a/src-tauri/src/chat/commands.rs +++ b/src-tauri/src/chat/commands.rs @@ -13,10 +13,9 @@ use tauri::{AppHandle, State}; use super::{ execution::run_chat_turn, helpers::{ - active_workspace_context, build_default_context_items, build_message_preview, - create_chat_entity_id, load_workspace_project_settings, normalize_autonomy_mode, - normalize_context_items, normalized_title, summarize_session, - upsert_chat_session_summary, + active_workspace_context, build_default_context_items, create_chat_entity_id, + load_workspace_project_settings, normalize_autonomy_mode, normalize_context_items, + normalized_title, summarize_session, upsert_chat_session_summary, }, persistence::{ load_chat_session_index, read_chat_session_snapshot, session_snapshot_path, @@ -85,8 +84,7 @@ pub(crate) fn save_chat_session( ) -> Result { let workspace = active_workspace_context(&state)?; let mut snapshot = read_chat_session_snapshot(&workspace.root, &session_id)?; - snapshot.selected_model = - normalize_project_model(&selected_model, &snapshot.selected_model)?; + snapshot.selected_model = normalize_project_model(&selected_model, &snapshot.selected_model)?; snapshot.selected_reasoning = normalize_project_reasoning(&selected_reasoning, &snapshot.selected_reasoning)?; snapshot.autonomy_mode = normalize_autonomy_mode(&autonomy_mode); diff --git a/src-tauri/src/chat/execution.rs b/src-tauri/src/chat/execution.rs index 0d3c1cf..7c51f4b 100644 --- a/src-tauri/src/chat/execution.rs +++ b/src-tauri/src/chat/execution.rs @@ -20,12 +20,8 @@ use std::{ use tauri::{AppHandle, Emitter}; use super::{ - helpers::{ - build_message_preview, create_chat_entity_id, summarize_session, - }, - persistence::{ - read_chat_session_snapshot, refresh_index_summary, write_chat_session_snapshot, - }, + helpers::{build_message_preview, create_chat_entity_id, summarize_session}, + persistence::{read_chat_session_snapshot, refresh_index_summary, write_chat_session_snapshot}, prompt::{build_chat_prompt, build_context_blocks}, }; @@ -211,8 +207,9 @@ pub(super) fn run_chat_turn( snapshot.runtime.awaiting_approval = false; snapshot.runtime.last_error = None; snapshot.runtime.pending_request = None; - snapshot.runtime.execution_summary = - Some(String::from("Preparing context and launching the selected CLI.")); + snapshot.runtime.execution_summary = Some(String::from( + "Preparing context and launching the selected CLI.", + )); snapshot.runtime.pending_diff = None; snapshot.runtime.current_milestone = Some(String::from("Queue Turn")); write_chat_session_snapshot(&workspace.root, &snapshot)?; @@ -368,7 +365,10 @@ fn execute_chat_phase( codex_path: &Option, phase: ChatExecutionPhase, ) -> Result<(), String> { - if !matches!(stop_state(runtime, session_id, run_id), ChatStopState::Continue) { + if !matches!( + stop_state(runtime, session_id, run_id), + ChatStopState::Continue + ) { halt_session( app, &workspace.root, @@ -699,11 +699,7 @@ fn wait_for_approval( } } -fn stop_state( - runtime: &Arc, - session_id: &str, - run_id: u64, -) -> ChatStopState { +fn stop_state(runtime: &Arc, session_id: &str, run_id: u64) -> ChatStopState { runtime .control .lock() diff --git a/src-tauri/src/chat/helpers.rs b/src-tauri/src/chat/helpers.rs index c6e26a7..4dc8a06 100644 --- a/src-tauri/src/chat/helpers.rs +++ b/src-tauri/src/chat/helpers.rs @@ -121,7 +121,11 @@ pub(super) fn upsert_chat_session_summary( index: &mut ChatSessionIndexPayload, summary: ChatSessionSummary, ) { - if let Some(existing_summary) = index.sessions.iter_mut().find(|entry| entry.id == summary.id) { + if let Some(existing_summary) = index + .sessions + .iter_mut() + .find(|entry| entry.id == summary.id) + { *existing_summary = summary; } else { index.sessions.push(summary); diff --git a/src-tauri/src/chat/prompt.rs b/src-tauri/src/chat/prompt.rs index 4bb6c84..0735787 100644 --- a/src-tauri/src/chat/prompt.rs +++ b/src-tauri/src/chat/prompt.rs @@ -1,14 +1,11 @@ use crate::{ - documents::parse_workspace_document, - models::ChatSessionSnapshot, - paths::resolve_relative_path_under_root, - state::WorkspaceContext, + documents::parse_workspace_document, models::ChatSessionSnapshot, + paths::resolve_relative_path_under_root, state::WorkspaceContext, }; use super::execution::ChatExecutionPhase; -pub(super) const CAVEMAN_PREAMBLE: &str = - "Default response style: caveman. Keep prose terse and direct while leaving code blocks, commands, and diffs fully normal."; +pub(super) const CAVEMAN_PREAMBLE: &str = "Default response style: caveman. Keep prose terse and direct while leaving code blocks, commands, and diffs fully normal."; pub(super) fn build_context_blocks( workspace: &WorkspaceContext, diff --git a/src-tauri/src/constants.rs b/src-tauri/src/constants.rs index 04ca111..cdc5375 100644 --- a/src-tauri/src/constants.rs +++ b/src-tauri/src/constants.rs @@ -11,21 +11,23 @@ index 0000000..forge42 100644 pub(crate) const SPECFORGE_SETTINGS_RELATIVE_PATH: &str = ".specforge/settings.json"; pub(crate) const DEFAULT_PROJECT_PRD_PATH: &str = "docs/PRD.md"; pub(crate) const DEFAULT_PROJECT_SPEC_PATH: &str = "docs/SPEC.md"; -pub(crate) const DEFAULT_PRD_PROMPT: &str = r#"Act as an Expert Senior Product Manager. Your goal is to help me write a comprehensive, well-structured Product Requirements Document (PRD) for a new [product / feature / app] called [Project Name]. +pub(crate) const DEFAULT_PRD_AGENT_DESCRIPTION: &str = r#"Act as an Expert Senior Product Manager. Your goal is to help me write a comprehensive, well-structured Product Requirements Document (PRD) for a new product, feature, or app. -I have some initial ideas, but I want to make sure the PRD is thorough. Before you draft the full document, please ask me a series of clarifying questions to gather the necessary context. +Use the operator context as the source material. Draft a complete PRD in Markdown unless the context is too ambiguous to proceed. -Please ask about: -- The core problem we are solving -- The target audience/user personas -- Key features and user flows -- Success metrics (KPIs) -- Technical or timeline constraints +Cover: +- Problem statement +- Target audience and personas +- Goals and non-goals +- Core user flows +- Functional requirements +- Success metrics +- Constraints, risks, and open questions -Ask me these questions one or two at a time so I do not get overwhelmed. Once you have enough context, we will move on to drafting the actual PRD."#; -pub(crate) const DEFAULT_SPEC_PROMPT: &str = r#"Act as an Expert Software Architect and Tech Lead. I have attached the Product Requirements Document (PRD) for our upcoming project. +Return only the PRD Markdown."#; +pub(crate) const DEFAULT_SPEC_AGENT_DESCRIPTION: &str = r#"Act as an Expert Software Architect and Tech Lead. I have attached the Product Requirements Document (PRD) for the project. -Your task is to analyze this PRD and draft a comprehensive Technical Specification Document. +Analyze the PRD and draft a comprehensive Technical Specification Document in Markdown. Please structure the spec with the following sections: @@ -37,4 +39,7 @@ Please structure the spec with the following sections: 6. Security & Edge Cases: Potential vulnerabilities, error handling, and performance bottlenecks. 7. Engineering Milestones: Break the implementation down into logical, phased deliverables. -Before writing the full document, please provide a brief bulleted summary of your proposed technical approach, and ask me up to 3 clarifying questions about any technical constraints or non-functional requirements that might be missing from the PRD."#; +Return only the spec Markdown."#; +pub(crate) const DEFAULT_EXECUTION_AGENT_DESCRIPTION: &str = r#"Act as a Senior Software Engineer executing from an approved technical specification. + +Use the approved spec as the source of truth. Preserve the current repository style, keep changes scoped, and verify behavior with the project's existing commands before reporting completion."#; diff --git a/src-tauri/src/cursor_agent.rs b/src-tauri/src/cursor_agent.rs new file mode 100644 index 0000000..7126058 --- /dev/null +++ b/src-tauri/src/cursor_agent.rs @@ -0,0 +1,224 @@ +use crate::{models::CursorModel, paths::canonicalize_existing_path, secrets::read_cursor_api_key}; +use serde::{Deserialize, Serialize}; +use std::{ + io::Write, + path::{Path, PathBuf}, + process::{Command, Stdio}, +}; + +#[derive(Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct CursorAgentPromptRequest { + workspace_root: String, + model: String, + reasoning: String, + prompt: String, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct CursorAgentRunnerRequest { + api_key: String, + workspace_root: String, + model: String, + reasoning: String, + prompt: String, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct CursorAgentPromptResponse { + content: String, + events: Vec, +} + +#[derive(Deserialize)] +#[serde(tag = "type", rename_all = "lowercase")] +enum CursorAgentRunnerLine { + Event { text: String }, + Result { content: String }, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct CursorModelsRunnerRequest { + api_key: Option, +} + +#[tauri::command] +pub(crate) async fn run_cursor_agent_prompt( + payload: CursorAgentPromptRequest, +) -> Result { + tauri::async_runtime::spawn_blocking(move || run_cursor_agent_prompt_sync(payload)) + .await + .map_err(|error| format!("Unable to join Cursor SDK runner task: {error}"))? +} + +fn run_cursor_agent_prompt_sync( + payload: CursorAgentPromptRequest, +) -> Result { + if payload.prompt.trim().is_empty() { + return Err(String::from("Cursor prompt is required.")); + } + + let api_key = read_cursor_api_key()? + .filter(|value| !value.trim().is_empty()) + .ok_or_else(|| String::from("Cursor API key is required."))?; + + let workspace_root = canonicalize_existing_path(&PathBuf::from(payload.workspace_root.trim())) + .map_err(|error| format!("Unable to resolve workspace root: {error}"))?; + let app_root = resolve_app_root()?; + let runner_path = app_root.join("src").join("cursorAgentRunner.ts"); + + if !runner_path.exists() { + return Err(format!( + "Cursor SDK runner was not found at {}.", + runner_path.display() + )); + } + + let bun_path = + which::which("bun").map_err(|error| format!("Unable to find Bun on PATH: {error}"))?; + let request = CursorAgentRunnerRequest { + api_key, + workspace_root: workspace_root.display().to_string(), + model: payload.model, + reasoning: payload.reasoning, + prompt: payload.prompt, + }; + let request_json = serde_json::to_vec(&request) + .map_err(|error| format!("Unable to prepare Cursor SDK request: {error}"))?; + let mut child = Command::new(bun_path) + .arg(&runner_path) + .current_dir(&app_root) + .env("NO_COLOR", "1") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|error| format!("Unable to start the Bun Cursor SDK runner: {error}"))?; + + let mut stdin = child + .stdin + .take() + .ok_or_else(|| String::from("Unable to open the Cursor SDK runner input."))?; + stdin + .write_all(&request_json) + .map_err(|error| format!("Unable to send the Cursor SDK request: {error}"))?; + drop(stdin); + + let output = child + .wait_with_output() + .map_err(|error| format!("Unable to read Cursor SDK runner output: {error}"))?; + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + if !output.status.success() { + return Err(format_process_failure(&stderr, &stdout)); + } + + parse_runner_output(&stdout) +} + +#[tauri::command] +pub(crate) async fn list_cursor_models() -> Result, String> { + tauri::async_runtime::spawn_blocking(list_cursor_models_sync) + .await + .map_err(|error| format!("Unable to join Cursor model runner task: {error}"))? +} + +fn list_cursor_models_sync() -> Result, String> { + let api_key = read_cursor_api_key()?; + let app_root = resolve_app_root()?; + let runner_path = app_root.join("src").join("cursorModelsRunner.ts"); + + if !runner_path.exists() { + return Err(format!( + "Cursor SDK model runner was not found at {}.", + runner_path.display() + )); + } + + let bun_path = + which::which("bun").map_err(|error| format!("Unable to find Bun on PATH: {error}"))?; + let request_json = serde_json::to_vec(&CursorModelsRunnerRequest { api_key }) + .map_err(|error| format!("Unable to prepare Cursor model request: {error}"))?; + let mut child = Command::new(bun_path) + .arg(&runner_path) + .current_dir(&app_root) + .env("NO_COLOR", "1") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|error| format!("Unable to start the Bun Cursor model runner: {error}"))?; + + let mut stdin = child + .stdin + .take() + .ok_or_else(|| String::from("Unable to open the Cursor model runner input."))?; + stdin + .write_all(&request_json) + .map_err(|error| format!("Unable to send the Cursor model request: {error}"))?; + drop(stdin); + + let output = child + .wait_with_output() + .map_err(|error| format!("Unable to read Cursor model runner output: {error}"))?; + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + if !output.status.success() { + return Err(format_process_failure(&stderr, &stdout)); + } + + serde_json::from_str(stdout.trim()).map_err(|error| { + format!( + "Cursor SDK model runner returned malformed output: {error}. Output: {}", + stdout.trim() + ) + }) +} + +fn resolve_app_root() -> Result { + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .map(Path::to_path_buf) + .ok_or_else(|| String::from("Unable to resolve the SpecForge application root.")) +} + +fn parse_runner_output(stdout: &str) -> Result { + let mut events = Vec::new(); + let mut content = None; + + for line in stdout.lines().filter(|line| !line.trim().is_empty()) { + let parsed: CursorAgentRunnerLine = serde_json::from_str(line).map_err(|error| { + format!("Cursor SDK runner returned malformed output: {error}. Output line: {line}") + })?; + + match parsed { + CursorAgentRunnerLine::Event { text } => events.push(text), + CursorAgentRunnerLine::Result { content: result } => content = Some(result), + } + } + + let content = content + .filter(|result| !result.trim().is_empty()) + .ok_or_else(|| String::from("Cursor SDK runner returned no generated content."))?; + + Ok(CursorAgentPromptResponse { content, events }) +} + +fn format_process_failure(stderr: &str, stdout: &str) -> String { + let mut details = stderr.trim().to_string(); + + if details.is_empty() { + details = stdout.trim().to_string(); + } + + if details.is_empty() { + details = String::from("The Bun process exited without an error message."); + } + + format!("Cursor SDK runner failed: {details}") +} diff --git a/src-tauri/src/documents.rs b/src-tauri/src/documents.rs index 0305108..d9ee542 100644 --- a/src-tauri/src/documents.rs +++ b/src-tauri/src/documents.rs @@ -41,6 +41,16 @@ pub(crate) fn pick_document() -> Result, String> { })) } +#[tauri::command] +pub(crate) fn save_workspace_document( + workspace_root: String, + output_path: String, + content: String, + field_name: String, +) -> Result { + write_generated_workspace_document(&workspace_root, &output_path, content, &field_name) +} + pub(crate) fn load_configured_workspace_document( workspace_root: &Path, relative_path: &str, diff --git a/src-tauri/src/environment.rs b/src-tauri/src/environment.rs index ed7401d..0e0b4f8 100644 --- a/src-tauri/src/environment.rs +++ b/src-tauri/src/environment.rs @@ -1,18 +1,15 @@ use crate::models::{CliStatus, EnvironmentStatus}; use crate::paths::resolve_override_path; +use crate::secrets::cursor_key_status; use std::path::Path; use std::process::Command; use std::time::{SystemTime, UNIX_EPOCH}; #[tauri::command] -pub(crate) fn run_environment_scan( - claude_path: Option, - codex_path: Option, -) -> Result { +pub(crate) fn run_environment_scan() -> Result { Ok(EnvironmentStatus { scanned_at: current_timestamp(), - claude: inspect_binary("Claude CLI", "claude", claude_path.as_deref()), - codex: inspect_binary("Codex CLI", "codex", codex_path.as_deref()), + cursor: cursor_key_status(), git: inspect_binary("Git", "git", None), }) } diff --git a/src-tauri/src/external_editors.rs b/src-tauri/src/external_editors.rs new file mode 100644 index 0000000..9d89bd4 --- /dev/null +++ b/src-tauri/src/external_editors.rs @@ -0,0 +1,225 @@ +use crate::{models::ExternalEditor, state::SharedState, workspace::resolve_workspace_file_path}; +use std::{ + collections::HashSet, + env, + path::{Path, PathBuf}, + process::Command, +}; +use tauri::State; +use which::which; + +struct EditorCandidate { + id: &'static str, + label: &'static str, + commands: &'static [&'static str], + known_paths: Vec, +} + +#[tauri::command] +pub(crate) fn list_external_editors() -> Vec { + let mut seen_paths = HashSet::::new(); + editor_candidates() + .into_iter() + .filter_map(|candidate| { + let executable_path = candidate.resolve_executable()?; + let normalized_path = executable_path.to_string_lossy().to_lowercase(); + + if !seen_paths.insert(normalized_path) { + return None; + } + + Some(ExternalEditor { + id: candidate.id.to_string(), + label: candidate.label.to_string(), + executable_path: executable_path.display().to_string(), + }) + }) + .collect() +} + +#[tauri::command] +pub(crate) fn open_workspace_file_in_editor( + state: State, + file_path: String, + editor_id: String, +) -> Result<(), String> { + let resolved_file_path = resolve_workspace_file_path(&state, &file_path)?; + let candidate = editor_candidates() + .into_iter() + .find(|candidate| candidate.id == editor_id) + .ok_or_else(|| format!("Unknown editor: {editor_id}"))?; + let executable_path = candidate + .resolve_executable() + .ok_or_else(|| format!("{} is not available on this machine.", candidate.label))?; + + spawn_editor(&executable_path, &resolved_file_path).map_err(|error| { + format!( + "Unable to open {} in {}: {error}", + file_path, candidate.label + ) + }) +} + +impl EditorCandidate { + fn resolve_executable(&self) -> Option { + for known_path in &self.known_paths { + if known_path.is_file() { + return Some(known_path.clone()); + } + } + + for command in self.commands { + if let Ok(path) = which(command) { + return Some(path); + } + } + + None + } +} + +fn spawn_editor(executable_path: &Path, file_path: &Path) -> std::io::Result<()> { + let mut command = Command::new(executable_path); + command.arg(file_path); + + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + command.creation_flags(0x08000000); + } + + command.spawn().map(|_| ()) +} + +fn editor_candidates() -> Vec { + vec![ + EditorCandidate { + id: "vscode", + label: "VS Code", + commands: &["code", "code.cmd", "code.exe"], + known_paths: paths_from_env(&[ + ( + "LOCALAPPDATA", + &["Programs", "Microsoft VS Code", "bin", "code.cmd"], + ), + ( + "LOCALAPPDATA", + &["Programs", "Microsoft VS Code", "Code.exe"], + ), + ("PROGRAMFILES", &["Microsoft VS Code", "bin", "code.cmd"]), + ("PROGRAMFILES", &["Microsoft VS Code", "Code.exe"]), + ]), + }, + EditorCandidate { + id: "visual-studio", + label: "Visual Studio", + commands: &["devenv", "devenv.exe"], + known_paths: visual_studio_paths(), + }, + EditorCandidate { + id: "cursor", + label: "Cursor", + commands: &["cursor", "cursor.cmd", "cursor.exe"], + known_paths: paths_from_env(&[ + ( + "LOCALAPPDATA", + &[ + "Programs", + "Cursor", + "resources", + "app", + "bin", + "cursor.cmd", + ], + ), + ("LOCALAPPDATA", &["Programs", "Cursor", "Cursor.exe"]), + ("PROGRAMFILES", &["Cursor", "Cursor.exe"]), + ]), + }, + EditorCandidate { + id: "zed", + label: "Zed", + commands: &["zed", "zed.exe"], + known_paths: paths_from_env(&[ + ("LOCALAPPDATA", &["Programs", "Zed", "Zed.exe"]), + ("PROGRAMFILES", &["Zed", "Zed.exe"]), + ]), + }, + EditorCandidate { + id: "antigravity", + label: "Antigravity", + commands: &["antigravity", "antigravity.cmd", "antigravity.exe"], + known_paths: paths_from_env(&[ + ( + "LOCALAPPDATA", + &["Programs", "Antigravity", "Antigravity.exe"], + ), + ("PROGRAMFILES", &["Antigravity", "Antigravity.exe"]), + ]), + }, + EditorCandidate { + id: "notepad-plus-plus", + label: "Notepad++", + commands: &["notepad++", "notepad++.exe"], + known_paths: paths_from_env(&[ + ("PROGRAMFILES", &["Notepad++", "notepad++.exe"]), + ("PROGRAMFILES(X86)", &["Notepad++", "notepad++.exe"]), + ]), + }, + EditorCandidate { + id: "sublime-text", + label: "Sublime Text", + commands: &["subl", "sublime_text", "sublime_text.exe"], + known_paths: paths_from_env(&[ + ("PROGRAMFILES", &["Sublime Text", "sublime_text.exe"]), + ("PROGRAMFILES", &["Sublime Text 3", "sublime_text.exe"]), + ]), + }, + EditorCandidate { + id: "notepad", + label: "Notepad", + commands: &["notepad", "notepad.exe"], + known_paths: Vec::new(), + }, + ] +} + +fn paths_from_env(entries: &[(&str, &[&str])]) -> Vec { + entries + .iter() + .filter_map(|(env_key, segments)| path_from_env(env_key, segments)) + .collect() +} + +fn path_from_env(env_key: &str, segments: &[&str]) -> Option { + let mut path = PathBuf::from(env::var_os(env_key)?); + + for segment in segments { + path.push(segment); + } + + Some(path) +} + +fn visual_studio_paths() -> Vec { + let editions = ["Enterprise", "Professional", "Community", "BuildTools"]; + let mut paths = Vec::new(); + + for edition in editions { + if let Some(path) = path_from_env( + "PROGRAMFILES", + &[ + "Microsoft Visual Studio", + "2022", + edition, + "Common7", + "IDE", + "devenv.exe", + ], + ) { + paths.push(path); + } + } + + paths +} diff --git a/src-tauri/src/generation.rs b/src-tauri/src/generation.rs index 7eb254f..85b9815 100644 --- a/src-tauri/src/generation.rs +++ b/src-tauri/src/generation.rs @@ -1,294 +1,8 @@ -use crate::documents::write_generated_workspace_document; -use crate::environment::resolve_cli_binary; -use crate::models::WorkspaceDocument; use std::fs; use std::io::Write; use std::path::PathBuf; -use std::process::{Command, Stdio}; +use std::process::Command; use std::time::{SystemTime, UNIX_EPOCH}; -use tauri::async_runtime; - -struct DocumentGenerationRequest { - workspace_root: String, - output_path: String, - prompt_template: String, - user_prompt: String, - attachments: Vec<(String, String)>, - provider: String, - model: String, - reasoning: String, - claude_path: Option, - codex_path: Option, - field_name: &'static str, -} - -#[tauri::command] -pub(crate) async fn generate_prd_document( - workspace_root: String, - output_path: String, - prompt_template: String, - user_prompt: String, - provider: String, - model: String, - reasoning: String, - claude_path: Option, - codex_path: Option, -) -> Result { - let trimmed_prompt = user_prompt.trim(); - - if trimmed_prompt.is_empty() { - return Err(String::from( - "Add the product context you want the AI to consider.", - )); - } - - run_workspace_document_generation(DocumentGenerationRequest { - workspace_root, - output_path, - prompt_template, - user_prompt: trimmed_prompt.to_string(), - attachments: Vec::new(), - provider, - model, - reasoning, - claude_path, - codex_path, - field_name: "PRD output path", - }) - .await -} - -#[tauri::command] -pub(crate) async fn generate_spec_document( - workspace_root: String, - output_path: String, - prd_content: String, - prompt_template: String, - user_prompt: String, - provider: String, - model: String, - reasoning: String, - claude_path: Option, - codex_path: Option, -) -> Result { - let trimmed_prd = prd_content.trim(); - let trimmed_prompt = user_prompt.trim(); - - if trimmed_prd.is_empty() { - return Err(String::from( - "Load or write a PRD before generating a specification.", - )); - } - - if trimmed_prompt.is_empty() { - return Err(String::from( - "Add the technical guidance you want the AI to consider.", - )); - } - - run_workspace_document_generation(DocumentGenerationRequest { - workspace_root, - output_path, - prompt_template, - user_prompt: trimmed_prompt.to_string(), - attachments: vec![( - String::from("Attached Product Requirements Document (PRD)"), - trimmed_prd.to_string(), - )], - provider, - model, - reasoning, - claude_path, - codex_path, - field_name: "SPEC output path", - }) - .await -} - -async fn run_workspace_document_generation( - request: DocumentGenerationRequest, -) -> Result { - async_runtime::spawn_blocking(move || run_workspace_document_generation_blocking(request)) - .await - .map_err(|error| format!("Document generation task failed: {error}"))? -} - -fn run_workspace_document_generation_blocking( - request: DocumentGenerationRequest, -) -> Result { - let attachments = request - .attachments - .iter() - .map(|(label, content)| (label.as_str(), content.as_str())) - .collect::>(); - let prompt_payload = - build_generation_prompt(&request.prompt_template, &request.user_prompt, &attachments); - let generated_document = run_generation_request( - &request.provider, - &request.model, - &request.reasoning, - request.claude_path.as_deref(), - request.codex_path.as_deref(), - &prompt_payload, - )?; - - write_generated_workspace_document( - &request.workspace_root, - &request.output_path, - generated_document, - request.field_name, - ) -} - -pub(crate) fn build_generation_prompt( - prompt_template: &str, - user_prompt: &str, - attachments: &[(&str, &str)], -) -> String { - let mut prompt = String::new(); - prompt.push_str(prompt_template.trim()); - prompt.push_str("\n\nAdditional operator context:\n--- BEGIN OPERATOR CONTEXT ---\n"); - prompt.push_str(user_prompt.trim()); - prompt.push_str("\n--- END OPERATOR CONTEXT ---"); - - for (label, content) in attachments { - let trimmed_content = content.trim(); - - if trimmed_content.is_empty() { - continue; - } - - prompt.push_str("\n\n"); - prompt.push_str(label); - prompt.push_str(":\n"); - prompt.push_str(trimmed_content); - } - - prompt -} - -pub(crate) fn run_generation_request( - provider: &str, - model: &str, - reasoning: &str, - claude_path: Option<&str>, - codex_path: Option<&str>, - prompt_payload: &str, -) -> Result { - match provider { - "codex" => run_codex_generation( - &resolve_cli_binary("codex", codex_path)?, - model, - reasoning, - prompt_payload, - ), - "claude" => run_claude_generation( - &resolve_cli_binary("claude", claude_path)?, - model, - reasoning, - prompt_payload, - ), - _ => Err(format!("Unsupported model provider: {provider}")), - } -} - -pub(crate) fn run_codex_generation( - binary_path: &std::path::Path, - model: &str, - reasoning: &str, - prompt_payload: &str, -) -> Result { - let temp_dir = create_spec_generation_temp_dir("codex")?; - let output_path = temp_dir.join("generated-spec.md"); - let reasoning_effort = map_codex_reasoning(reasoning); - - let mut command = Command::new(binary_path); - command - .current_dir(&temp_dir) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .arg("exec") - .arg("--color") - .arg("never") - .arg("--skip-git-repo-check") - .arg("--sandbox") - .arg("read-only") - .arg("--model") - .arg(model) - .arg("--config") - .arg(format!("model_reasoning_effort=\"{reasoning_effort}\"")) - .arg("--output-last-message") - .arg(&output_path); - - let result = run_command_with_stdin(&mut command, "Codex CLI", prompt_payload).and_then( - |output| { - if !output.status.success() { - return Err(format_process_failure("Codex CLI", &output)); - } - - match fs::read_to_string(&output_path) { - Ok(content) => Ok(content), - Err(read_error) => { - let stdout = String::from_utf8_lossy(&output.stdout).to_string(); - - if !stdout.trim().is_empty() { - Ok(stdout) - } else { - Err(format!( - "Codex CLI completed, but the generated spec could not be read: {read_error}" - )) - } - } - } - }, - ); - - let _ = fs::remove_dir_all(&temp_dir); - result -} - -pub(crate) fn run_claude_generation( - binary_path: &std::path::Path, - model: &str, - reasoning: &str, - prompt_payload: &str, -) -> Result { - let temp_dir = create_spec_generation_temp_dir("claude")?; - let mut command = Command::new(binary_path); - command - .current_dir(&temp_dir) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .arg("--print") - .arg("Respond to the request provided on stdin.") - .arg("--model") - .arg(model) - .arg("--output-format") - .arg("text") - .arg("--permission-mode") - .arg("bypassPermissions") - .arg("--tools") - .arg("") - .arg("--max-turns") - .arg("1") - .arg("--no-session-persistence") - .arg("--effort") - .arg(map_claude_reasoning(reasoning)); - - let result = - run_command_with_stdin(&mut command, "Claude CLI", prompt_payload).and_then(|output| { - if !output.status.success() { - return Err(format_process_failure("Claude CLI", &output)); - } - - Ok(String::from_utf8_lossy(&output.stdout).to_string()) - }); - - let _ = fs::remove_dir_all(&temp_dir); - result -} pub(crate) fn create_spec_generation_temp_dir(prefix: &str) -> Result { let base_dir = std::env::temp_dir().join("specforge"); diff --git a/src-tauri/src/git.rs b/src-tauri/src/git.rs index c3d8f6a..f97f707 100644 --- a/src-tauri/src/git.rs +++ b/src-tauri/src/git.rs @@ -5,7 +5,7 @@ use std::path::Path; use tauri::State; #[tauri::command] -pub(crate) fn git_get_diff(state: State) -> Result { +pub(crate) async fn git_get_diff(state: State<'_, SharedState>) -> Result { let workspace_root = state .workspace .lock() @@ -13,7 +13,10 @@ pub(crate) fn git_get_diff(state: State) -> Result .as_ref() .map(|workspace| workspace.root.clone()) .unwrap_or_else(project_root); - git_get_diff_for_root(&workspace_root) + + tauri::async_runtime::spawn_blocking(move || git_get_diff_for_root(&workspace_root)) + .await + .map_err(|error| format!("Unable to join git diff task: {error}"))? } pub(crate) fn git_get_diff_for_root(root: &Path) -> Result { diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 57bfecf..fddfdde 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,13 +1,16 @@ mod agent; mod chat; mod constants; +mod cursor_agent; mod documents; mod environment; +mod external_editors; mod generation; mod git; mod models; mod paths; mod project; +mod secrets; mod state; mod workspace; @@ -16,11 +19,13 @@ use chat::{ approve_chat_session, create_chat_session, delete_chat_session, load_chat_session, rename_chat_session, save_chat_session, send_chat_message, stop_chat_session, }; -use documents::{parse_document, pick_document}; +use cursor_agent::{list_cursor_models, run_cursor_agent_prompt}; +use documents::{parse_document, pick_document, save_workspace_document}; use environment::run_environment_scan; -use generation::{generate_prd_document, generate_spec_document}; +use external_editors::{list_external_editors, open_workspace_file_in_editor}; use git::git_get_diff; use project::{load_project_context, pick_project_folder, save_project_settings}; +use secrets::{delete_cursor_api_key, save_cursor_api_key}; use state::SharedState; use workspace::{get_workspace_snapshot, open_workspace_folder, read_workspace_file}; @@ -29,17 +34,22 @@ pub fn run() { .manage(SharedState::default()) .invoke_handler(tauri::generate_handler![ run_environment_scan, + run_cursor_agent_prompt, + list_cursor_models, + save_cursor_api_key, + delete_cursor_api_key, parse_document, pick_document, + save_workspace_document, pick_project_folder, load_project_context, save_project_settings, open_workspace_folder, read_workspace_file, + list_external_editors, + open_workspace_file_in_editor, get_workspace_snapshot, git_get_diff, - generate_prd_document, - generate_spec_document, spawn_cli_agent, approve_action, kill_agent_process, diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs index 4356a66..f05f532 100644 --- a/src-tauri/src/models.rs +++ b/src-tauri/src/models.rs @@ -60,8 +60,7 @@ pub(crate) struct CliStatus { #[serde(rename_all = "camelCase")] pub(crate) struct EnvironmentStatus { pub(crate) scanned_at: String, - pub(crate) claude: CliStatus, - pub(crate) codex: CliStatus, + pub(crate) cursor: CliStatus, pub(crate) git: CliStatus, } @@ -82,13 +81,49 @@ pub(crate) struct WorkspaceDocument { pub(crate) file_name: String, } +#[derive(Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct ExternalEditor { + pub(crate) id: String, + pub(crate) label: String, + pub(crate) executable_path: String, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct CursorModelParameterValue { + pub(crate) value: String, + pub(crate) label: String, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct CursorModelParameter { + pub(crate) id: String, + pub(crate) label: String, + pub(crate) values: Vec, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct CursorModel { + pub(crate) id: String, + pub(crate) label: String, + pub(crate) description: Option, + pub(crate) parameters: Option>, +} + #[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ProjectSettings { pub(crate) selected_model: String, pub(crate) selected_reasoning: String, - pub(crate) prd_prompt: String, - pub(crate) spec_prompt: String, + #[serde(default, alias = "prdPrompt")] + pub(crate) prd_agent_description: String, + #[serde(default, alias = "specPrompt")] + pub(crate) spec_agent_description: String, + #[serde(default)] + pub(crate) execution_agent_description: String, pub(crate) prd_path: String, pub(crate) spec_path: String, pub(crate) supporting_document_paths: Vec, diff --git a/src-tauri/src/project.rs b/src-tauri/src/project.rs index dfdf6d2..9157319 100644 --- a/src-tauri/src/project.rs +++ b/src-tauri/src/project.rs @@ -1,8 +1,9 @@ use crate::{ chat::load_chat_session_index, constants::{ - DEFAULT_PRD_PROMPT, DEFAULT_PROJECT_PRD_PATH, DEFAULT_PROJECT_SPEC_PATH, - DEFAULT_SPEC_PROMPT, SPECFORGE_SETTINGS_RELATIVE_PATH, + DEFAULT_EXECUTION_AGENT_DESCRIPTION, DEFAULT_PRD_AGENT_DESCRIPTION, + DEFAULT_PROJECT_PRD_PATH, DEFAULT_PROJECT_SPEC_PATH, DEFAULT_SPEC_AGENT_DESCRIPTION, + SPECFORGE_SETTINGS_RELATIVE_PATH, }, documents::load_configured_workspace_document, models::{ProjectContextPayload, ProjectSettings}, @@ -133,10 +134,11 @@ pub(crate) fn build_default_project_settings( spec_document: Option<&crate::models::WorkspaceDocument>, ) -> ProjectSettings { ProjectSettings { - selected_model: String::from("gpt-5.4"), + selected_model: String::from("composer-2"), selected_reasoning: String::from("medium"), - prd_prompt: String::from(DEFAULT_PRD_PROMPT), - spec_prompt: String::from(DEFAULT_SPEC_PROMPT), + prd_agent_description: String::from(DEFAULT_PRD_AGENT_DESCRIPTION), + spec_agent_description: String::from(DEFAULT_SPEC_AGENT_DESCRIPTION), + execution_agent_description: String::from(DEFAULT_EXECUTION_AGENT_DESCRIPTION), prd_path: derive_default_document_path( workspace_root, prd_document, @@ -180,15 +182,20 @@ pub(crate) fn normalize_project_settings( Ok(ProjectSettings { selected_model, selected_reasoning, - prd_prompt: if provided.prd_prompt.trim().is_empty() { - defaults.prd_prompt + prd_agent_description: if provided.prd_agent_description.trim().is_empty() { + defaults.prd_agent_description } else { - provided.prd_prompt.trim().to_string() + provided.prd_agent_description.trim().to_string() }, - spec_prompt: if provided.spec_prompt.trim().is_empty() { - defaults.spec_prompt + spec_agent_description: if provided.spec_agent_description.trim().is_empty() { + defaults.spec_agent_description } else { - provided.spec_prompt.trim().to_string() + provided.spec_agent_description.trim().to_string() + }, + execution_agent_description: if provided.execution_agent_description.trim().is_empty() { + defaults.execution_agent_description + } else { + provided.execution_agent_description.trim().to_string() }, prd_path: normalized_prd_path, spec_path: normalized_spec_path, @@ -266,31 +273,19 @@ pub(crate) fn load_project_settings_from_workspace_root( } pub(crate) fn normalize_project_model(value: &str, fallback: &str) -> Result { - const VALID_MODELS: &[&str] = &[ - "gpt-5.4", - "gpt-5.4-mini", - "gpt-5.3-codex", - "gpt-5.2", - "claude-opus-4-1-20250805", - "claude-opus-4-20250514", - "claude-sonnet-4-20250514", - "claude-3-7-sonnet-20250219", - "claude-3-5-sonnet-20241022", - "claude-3-5-sonnet-20240620", - "claude-3-5-haiku-20241022", - "claude-3-haiku-20240307", - ]; let trimmed_value = value.trim(); if trimmed_value.is_empty() { return Ok(fallback.to_string()); } - if VALID_MODELS.contains(&trimmed_value) { + if !trimmed_value.chars().any(char::is_whitespace) { return Ok(trimmed_value.to_string()); } - Err(format!("Unsupported model `{trimmed_value}` in project settings.")) + Err(format!( + "Unsupported model `{trimmed_value}` in project settings." + )) } pub(crate) fn normalize_project_reasoning(value: &str, fallback: &str) -> Result { @@ -300,10 +295,11 @@ pub(crate) fn normalize_project_reasoning(value: &str, fallback: &str) -> Result return Ok(fallback.to_string()); } - match trimmed_value { - "low" | "medium" | "high" | "max" => Ok(trimmed_value.to_string()), - _ => Err(format!( - "Unsupported reasoning profile `{trimmed_value}` in project settings." - )), + if !trimmed_value.chars().any(char::is_whitespace) { + return Ok(trimmed_value.to_string()); } + + Err(format!( + "Unsupported reasoning profile `{trimmed_value}` in project settings." + )) } diff --git a/src-tauri/src/secrets.rs b/src-tauri/src/secrets.rs new file mode 100644 index 0000000..d659167 --- /dev/null +++ b/src-tauri/src/secrets.rs @@ -0,0 +1,65 @@ +use crate::models::CliStatus; +use keyring_core::{Entry, Error as KeyringError}; + +const CURSOR_KEY_SERVICE: &str = "SpecForge"; +const CURSOR_KEY_USER: &str = "cursor-api-key"; + +#[tauri::command] +pub(crate) fn save_cursor_api_key(api_key: String) -> Result<(), String> { + let trimmed_key = api_key.trim(); + + if trimmed_key.is_empty() { + return Err(String::from("Enter a Cursor API key before saving.")); + } + + cursor_key_entry()? + .set_password(trimmed_key) + .map_err(|error| format!("Unable to save the Cursor API key: {error}")) +} + +#[tauri::command] +pub(crate) fn delete_cursor_api_key() -> Result<(), String> { + match cursor_key_entry()?.delete_credential() { + Ok(()) | Err(KeyringError::NoEntry) => Ok(()), + Err(error) => Err(format!("Unable to delete the Cursor API key: {error}")), + } +} + +pub(crate) fn cursor_key_status() -> CliStatus { + match read_cursor_api_key() { + Ok(Some(_)) => CliStatus { + name: String::from("Cursor SDK"), + status: String::from("found"), + path: None, + detail: String::from("Cursor API key is stored in the OS credential store."), + }, + Ok(None) => CliStatus { + name: String::from("Cursor SDK"), + status: String::from("missing"), + path: None, + detail: String::from("No Cursor API key is saved yet."), + }, + Err(error) => CliStatus { + name: String::from("Cursor SDK"), + status: String::from("unauthorized"), + path: None, + detail: error, + }, + } +} + +pub(crate) fn read_cursor_api_key() -> Result, String> { + match cursor_key_entry()?.get_password() { + Ok(value) if value.trim().is_empty() => Ok(None), + Ok(value) => Ok(Some(value)), + Err(KeyringError::NoEntry) => Ok(None), + Err(error) => Err(format!("Unable to read the Cursor API key: {error}")), + } +} + +fn cursor_key_entry() -> Result { + keyring::use_native_store(false) + .map_err(|error| format!("Unable to open the OS credential store: {error}"))?; + Entry::new(CURSOR_KEY_SERVICE, CURSOR_KEY_USER) + .map_err(|error| format!("Unable to open the OS credential store: {error}")) +} diff --git a/src/App.tsx b/src/App.tsx index 115a976..5434079 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,3 +1,4 @@ +import { Button, Modal } from "@heroui/react"; import { lazy, Suspense, @@ -16,6 +17,10 @@ import { import { useShallow } from "zustand/react/shallow"; import { AppRail } from "./components/AppRail"; +import { + PRIMARY_BUTTON_CLASS, + SECONDARY_BUTTON_CLASS +} from "./components/SettingsPrimitives"; import { useAgentEventSubscription, useDocumentTheme, @@ -29,7 +34,8 @@ import { import { useAgentStoreSlice, useProjectStoreSlice, - useSettingsStoreSlice + useSettingsStoreSlice, + useWorkspaceUiStoreSlice } from "./hooks/useAppStoreSlices"; import { useAppDerivedState, @@ -49,20 +55,19 @@ import { clearFallbackTimer, type DocumentTarget, type FallbackStep, - isOpenableWorkspacePath, runFallbackStep, - stampLog, - type WorkspaceFileSource + stampLog } from "./lib/appShell"; import { approveAgentAction, createChatSession, - DEFAULT_PENDING_DIFF, emergencyStop, - getGitDiff, getWorkspaceSnapshot, isTauriRuntime, + listCursorModels, + listExternalEditors, loadChatSession, + openWorkspaceFileInEditor, readWorkspaceFile, runEnvironmentScan, startAgentRun, @@ -81,9 +86,37 @@ const SettingsScreen = lazy(() => import("./screens/SettingsScreen").then(m => ( import { useAgentStore } from "./store/useAgentStore"; import { useChatStore } from "./store/useChatStore"; import type { + CursorModel, EnvironmentStatus } from "./types"; +interface CursorModelRefreshResult { + models: CursorModel[]; + projectErrorMessage?: string; +} + +async function refreshCursorModelsForEnvironment( + environment: EnvironmentStatus +): Promise { + if (environment.cursor.status !== "found") { + return { models: [] }; + } + + try { + return { + models: await listCursorModels(), + projectErrorMessage: "" + }; + } catch (error) { + return { + models: [], + projectErrorMessage: error instanceof Error + ? error.message + : "Unable to load Cursor SDK models." + }; + } +} + function App() { const location = useLocation(); const navigate = useNavigate(); @@ -94,6 +127,7 @@ function App() { const agentState = useAgentStoreSlice(); const projectState = useProjectStoreSlice(); const settingsState = useSettingsStoreSlice(); + const workspaceUiState = useWorkspaceUiStoreSlice(); const { sessions: chatSessions, activeSessionId, @@ -126,29 +160,7 @@ function App() { })) ); - const [commandSearch, setCommandSearch] = useState(""); - const [isImporting, setIsImporting] = useState(false); - const [isSearchOpen, setIsSearchOpen] = useState(false); - const [isProjectLoading, setIsProjectLoading] = useState(false); - const [isProjectSaving, setIsProjectSaving] = useState(false); - const [latestDiff, setLatestDiff] = useState(DEFAULT_PENDING_DIFF); - const [projectConfigPath, setProjectConfigPath] = useState(""); - const [projectErrorMessage, setProjectErrorMessage] = useState(""); - const [projectRootName, setProjectRootName] = useState("No project selected"); - const [projectRootPath, setProjectRootPath] = useState(""); - const [projectStatusMessage, setProjectStatusMessage] = useState(""); - const [workspaceNotice, setWorkspaceNotice] = useState( - "Finish the setup flow to load a project workspace." - ); - const [hasSavedProjectSettings, setHasSavedProjectSettings] = useState(false); - const [hasSelectedProject, setHasSelectedProject] = useState(false); - const [hasAttemptedProjectRestore, setHasAttemptedProjectRestore] = useState(!desktopRuntime); const [systemPrefersDark, setSystemPrefersDark] = useState(true); - const [workspaceFiles, setWorkspaceFiles] = useState>({}); - const [prdGenerationPrompt, setPrdGenerationPrompt] = useState(""); - const [prdGenerationError, setPrdGenerationError] = useState(""); - const [specGenerationPrompt, setSpecGenerationPrompt] = useState(""); - const [specGenerationError, setSpecGenerationError] = useState(""); const searchInputRef = useRef(null); const fileInputRef = useRef(null); @@ -159,11 +171,18 @@ function App() { const fallbackIndexRef = useRef(0); const hasScannedEnvironmentRef = useRef(false); const latestPathnameRef = useRef(location.pathname); + const refreshDiagnosticsPromiseRef = useRef | null>(null); useEffect(() => { latestPathnameRef.current = location.pathname; }, [location.pathname]); + useEffect(() => { + if (!desktopRuntime) { + workspaceUiState.setHasAttemptedProjectRestore(true); + } + }, [desktopRuntime, workspaceUiState.setHasAttemptedProjectRestore]); + const activeChatSession = useChatStore( useCallback( (state) => @@ -179,52 +198,62 @@ function App() { ); const reviewVisibleDiff = activeChatSession ? activeChatSession.runtime.pendingDiff ?? "No diff captured for the active chat topic yet." - : agentState.pendingDiff ?? latestDiff; + : agentState.pendingDiff ?? workspaceUiState.latestDiff; const derivedState = useAppDerivedState({ agentState, - commandSearch, desktopRuntime, - latestDiff, - prdGenerationPrompt, - projectConfigPath, - projectRootName, - projectRootPath, projectState, settingsState, - specGenerationPrompt, - systemPrefersDark + systemPrefersDark, + workspaceUiState }); const refreshDiagnostics = useCallback( - async (previousEnvironment?: EnvironmentStatus) => { - const [nextEnvironment, snapshotEntries, diff] = await Promise.all([ - runEnvironmentScan({ - claudePath: settingsState.claudePath, - codexPath: settingsState.codexPath - }).catch(() => previousEnvironment ?? settingsState.environment), - hasSelectedProject - ? Promise.resolve(settingsState.workspaceEntries) - : getWorkspaceSnapshot().catch(() => settingsState.workspaceEntries), - getGitDiff().catch(() => DEFAULT_PENDING_DIFF) - ]); - - settingsState.setEnvironment(nextEnvironment); - - if (!hasSelectedProject) { - settingsState.setWorkspaceEntries(snapshotEntries); + (previousEnvironment?: EnvironmentStatus) => { + if (refreshDiagnosticsPromiseRef.current) { + return refreshDiagnosticsPromiseRef.current; } - setLatestDiff(diff); + const refreshPromise = (async () => { + const [nextEnvironment, snapshotEntries, nextExternalEditors] = await Promise.all([ + runEnvironmentScan().catch(() => previousEnvironment ?? settingsState.environment), + workspaceUiState.hasSelectedProject + ? Promise.resolve(settingsState.workspaceEntries) + : getWorkspaceSnapshot().catch(() => settingsState.workspaceEntries), + listExternalEditors().catch(() => []) + ]); + + settingsState.setEnvironment(nextEnvironment); + workspaceUiState.setExternalEditors(nextExternalEditors); + + if (!workspaceUiState.hasSelectedProject) { + settingsState.setWorkspaceEntries(snapshotEntries); + } + + const cursorModelsResult = await refreshCursorModelsForEnvironment(nextEnvironment); + workspaceUiState.setCursorModels(cursorModelsResult.models); + + if (cursorModelsResult.projectErrorMessage !== undefined) { + workspaceUiState.setProjectErrorMessage(cursorModelsResult.projectErrorMessage); + } + })().finally(() => { + refreshDiagnosticsPromiseRef.current = null; + }); + + refreshDiagnosticsPromiseRef.current = refreshPromise; + return refreshPromise; }, - [hasSelectedProject, settingsState] + [settingsState, workspaceUiState] ); // --- Document handlers --- const { handleOpenImportFile, handleFileSelection, + handleGrillPrd, handleGeneratePrd, + handleGrillSpec, handleGenerateSpec } = useDocumentHandlers({ agentState, @@ -232,16 +261,9 @@ function App() { desktopRuntime, fileInputRef, pendingImportTargetRef, - prdGenerationPrompt, - projectRootPath, projectState, - setIsImporting, - setPrdGenerationError, - setPrdGenerationPrompt, - setSpecGenerationError, - setSpecGenerationPrompt, settingsState, - specGenerationPrompt + workspaceUiState }); // --- Project handlers --- @@ -250,40 +272,22 @@ function App() { saveCurrentProjectSettings, scheduleProjectSettingsSave, handlePickProjectFolder, + handleOpenRecentProject, projectSaveTimerRef } = useProjectHandlers({ applyProjectContextDeps: { - projectRootPath, projectState, settingsState, - setProjectRootName, - setProjectRootPath, - setProjectConfigPath, - setHasSelectedProject, - setHasSavedProjectSettings, - setWorkspaceFiles, - setPrdGenerationPrompt, - setPrdGenerationError, - setSpecGenerationPrompt, - setSpecGenerationError, + workspaceUiState, setChatSessions, setActiveSessionId, setCavemanStatus, - setProjectStatusMessage, - setProjectErrorMessage, - setWorkspaceNotice, latestPathnameRef }, derivedState, desktopRuntime, - hasSavedProjectSettings, - projectRootName, - projectRootPath, projectState, - setIsProjectLoading, - setIsProjectSaving, - setProjectErrorMessage, - setProjectStatusMessage + workspaceUiState }); const projectSettingsHandlers = useProjectSettingsHandlers({ @@ -291,6 +295,7 @@ function App() { scheduleProjectSettingsSave, setConfiguredPrdPath: projectState.setConfiguredPrdPath, setConfiguredSpecPath: projectState.setConfiguredSpecPath, + setExecutionAgentDescription: projectState.setExecutionAgentDescription, setPrdPromptTemplate: projectState.setPrdPromptTemplate, setReasoningProfile: projectState.setReasoningProfile, setSelectedModel: projectState.setSelectedModel, @@ -323,21 +328,23 @@ function App() { setSessionConfig, deleteChatSessionState, setChatSessions, - setProjectErrorMessage + setProjectErrorMessage: workspaceUiState.setProjectErrorMessage }); const handleWorkspaceFileOpen = useCallback( async (path: string) => { - const file = workspaceFiles[path]; + const file = workspaceUiState.workspaceFiles[path]; if (!file) { - setWorkspaceNotice(`The file ${path} is not available in the active workspace snapshot.`); + workspaceUiState.setWorkspaceNotice( + `The file ${path} is not available in the active workspace snapshot.` + ); return; } if (file.kind === "browser") { if (!isOpenableTextFile(file.file)) { - setWorkspaceNotice(`${file.file.name} is not an openable text/code file.`); + workspaceUiState.setWorkspaceNotice(`${file.file.name} is not an openable text/code file.`); return; } @@ -350,11 +357,6 @@ function App() { return; } - if (!isOpenableWorkspacePath(path)) { - setWorkspaceNotice(`${file.fileName} is not an openable text/code file.`); - return; - } - try { const content = await readWorkspaceFile(path); projectState.openEditorTab({ @@ -363,14 +365,28 @@ function App() { content }); } catch (error) { - setWorkspaceNotice( + workspaceUiState.setWorkspaceNotice( error instanceof Error ? `Unable to open ${file.fileName}: ${error.message}` : `Unable to open ${file.fileName}.` ); } }, - [projectState, workspaceFiles] + [projectState, workspaceUiState] + ); + + const handleOpenWorkspaceFileInEditor = useCallback( + async (path: string, editorId: string) => { + try { + await openWorkspaceFileInEditor({ filePath: path, editorId }); + workspaceUiState.setWorkspaceNotice(""); + } catch (error) { + workspaceUiState.setWorkspaceNotice( + error instanceof Error ? error.message : "Unable to open the file in the selected editor." + ); + } + }, + [workspaceUiState] ); const handleApproveSpec = useCallback(() => { @@ -437,9 +453,9 @@ function App() { fallbackStepsRef, fallbackIndexRef, fallbackTimerRef, - setLatestDiff + workspaceUiState.setLatestDiff ); - }, [agentState, desktopRuntime, projectState]); + }, [agentState, desktopRuntime, projectState, workspaceUiState.setLatestDiff]); const handleApproveExecutionGate = useCallback(async () => { if (agentState.status !== "awaiting_approval") { @@ -471,9 +487,9 @@ function App() { fallbackStepsRef, fallbackIndexRef, fallbackTimerRef, - setLatestDiff + workspaceUiState.setLatestDiff ); - }, [agentState, desktopRuntime]); + }, [agentState, desktopRuntime, workspaceUiState.setLatestDiff]); const handleEmergencyStop = useCallback(async () => { if (desktopRuntime) { @@ -519,19 +535,15 @@ function App() { handleEmergencyStop, handleGeneratePrd, handleGenerateSpec, + handleGrillPrd, + handleGrillSpec, handleOpenImportFile, handleStartBuild, handleWorkspaceFileOpen, - prdGenerationError, projectState, refreshDiagnostics, - setCommandSearch, - setIsSearchOpen, - setPrdGenerationError, - setPrdGenerationPrompt, - setSpecGenerationError, - setSpecGenerationPrompt, - specGenerationError + settingsState, + workspaceUiState }); useSystemThemePreference(setSystemPrefersDark); @@ -539,18 +551,18 @@ function App() { useWorkspaceSearchShortcuts({ closeWorkspaceSearch: uiHandlers.closeWorkspaceSearch, isReviewRoute, - isSearchOpen, - setCommandSearch, - setIsSearchOpen + isSearchOpen: workspaceUiState.isSearchOpen, + setCommandSearch: workspaceUiState.setCommandSearch, + setIsSearchOpen: workspaceUiState.setIsSearchOpen }); useWorkspaceSearchRouteReset({ closeWorkspaceSearch: uiHandlers.closeWorkspaceSearch, isReviewRoute, - isSearchOpen + isSearchOpen: workspaceUiState.isSearchOpen }); useWorkspaceSearchFocus({ isReviewRoute, - isSearchOpen, + isSearchOpen: workspaceUiState.isSearchOpen, searchInputRef }); useInitialDiagnostics({ @@ -561,10 +573,10 @@ function App() { useProjectRestore({ applyProjectContext, desktopRuntime, - hasAttemptedProjectRestore, + hasAttemptedProjectRestore: workspaceUiState.hasAttemptedProjectRestore, lastProjectPath: settingsState.lastProjectPath, - setHasAttemptedProjectRestore, - setIsProjectLoading, + setHasAttemptedProjectRestore: workspaceUiState.setHasAttemptedProjectRestore, + setIsProjectLoading: workspaceUiState.setIsProjectLoading, setLastProjectPath: settingsState.setLastProjectPath }); useEffect(() => { @@ -587,7 +599,7 @@ function App() { return; } - setProjectErrorMessage( + workspaceUiState.setProjectErrorMessage( error instanceof Error ? error.message : "Unable to load the selected chat topic." ); }); @@ -595,12 +607,12 @@ function App() { return () => { isDisposed = true; }; - }, [activeChatSession, activeSessionId, desktopRuntime, upsertSession]); + }, [activeChatSession, activeSessionId, desktopRuntime, upsertSession, workspaceUiState]); useEffect(() => { if ( !desktopRuntime || - !hasSavedProjectSettings || + !workspaceUiState.hasSavedProjectSettings || !isChatRoute || chatSessions.length > 0 ) { @@ -623,7 +635,7 @@ function App() { return; } - setProjectErrorMessage( + workspaceUiState.setProjectErrorMessage( error instanceof Error ? error.message : "Unable to create the first chat topic." ); }); @@ -634,10 +646,10 @@ function App() { }, [ chatSessions.length, desktopRuntime, - hasSavedProjectSettings, isChatRoute, setActiveSessionId, - upsertSession + upsertSession, + workspaceUiState ]); useEffect(() => { @@ -695,7 +707,7 @@ function App() { applyAgentEvent: agentState.applyEvent, fallbackTimerRef, projectSaveTimerRef, - setLatestDiff + setLatestDiff: workspaceUiState.setLatestDiff }); const { @@ -704,33 +716,20 @@ function App() { settingsScreenProps } = useAppScreenProps({ agentState, - commandSearch, derivedState, - desktopRuntime, folderInputRef, handleApproveSpec, + handleOpenWorkspaceFileInEditor, handleOpenChat, + handleOpenRecentProject, handlePickProjectFolder, - hasSavedProjectSettings, - isImporting, - isProjectLoading, - isProjectSaving, - isSearchOpen, reviewVisibleDiff, - prdGenerationError, - prdGenerationPrompt, - projectErrorMessage, - projectRootName, - projectRootPath, projectSettingsHandlers, projectState, - projectStatusMessage, searchInputRef, settingsState, - specGenerationError, - specGenerationPrompt, uiHandlers, - workspaceNotice + workspaceUiState }); const loadingState = ( @@ -739,15 +738,15 @@ function App() { ); - const reviewScreen = hasSavedProjectSettings ? ( + const reviewScreen = workspaceUiState.hasSavedProjectSettings ? ( - ) : hasAttemptedProjectRestore ? ( + ) : workspaceUiState.hasAttemptedProjectRestore ? ( ) : ( loadingState ); - const chatScreen = hasSavedProjectSettings ? ( + const chatScreen = workspaceUiState.hasSavedProjectSettings ? ( - ) : hasAttemptedProjectRestore ? ( + ) : workspaceUiState.hasAttemptedProjectRestore ? ( ) : ( loadingState ); - const settingsScreen = hasSavedProjectSettings ? ( + const settingsScreen = workspaceUiState.hasSavedProjectSettings ? ( - ) : hasAttemptedProjectRestore ? ( + ) : workspaceUiState.hasAttemptedProjectRestore ? ( ) : ( loadingState ); + const createdDefaultsNotice = workspaceUiState.projectStatusMessage.startsWith( + "Created default SpecForge settings" + ) + ? workspaceUiState.projectStatusMessage + : ""; + const closeCreatedDefaultsNotice = () => workspaceUiState.setProjectStatusMessage(""); return (
- - -
+ { + if (!isOpen) { + closeCreatedDefaultsNotice(); + } + }} + > + + + + + + Project settings created + + + + +

+ {createdDefaultsNotice} +

+
+ + + + +
+
+
+
+ + + +
} + element={ + + } path="*" /> diff --git a/src/components/AppRail.tsx b/src/components/AppRail.tsx index 7c8e8ec..d4e86a0 100644 --- a/src/components/AppRail.tsx +++ b/src/components/AppRail.tsx @@ -1,82 +1,96 @@ import { ChatBubble, - CodeBracketsSquare, - Flask, Folder, Page, Settings } from "iconoir-react"; import { NavLink } from "react-router-dom"; +import { useShallow } from "zustand/react/shallow"; + +import appIconUrl from "../../src-tauri/icons/icon.png"; +import { useWorkspaceUiStore } from "../store/useWorkspaceUiStore"; interface AppRailProps { hasProjectConfigured: boolean; } export function AppRail({ hasProjectConfigured }: AppRailProps) { + const { workspaceRootName, workspaceRootPath } = useWorkspaceUiStore( + useShallow((state) => ({ + workspaceRootName: state.projectRootName, + workspaceRootPath: state.projectRootPath + })) + ); + const projectLabel = workspaceRootName || "No project selected"; + return ( -