commit 296843215f28dcf1f4fc366e10fa3a42f54dffb4 Author: AgentDock 施工员 Date: Tue Aug 25 00:21:13 2026 +0800 chore: wave-0 baseline (Tauri2+React shell, platform detect, fake catalog) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df98f69 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# 构建产物 +node_modules/ +dist/ +target/ +*.log + +# Rust +Cargo.lock + +# 系统与编辑器 +.DS_Store +Thumbs.db +.idea/ +.vscode/ +*.swp + +# Tauri 本地运行文件 +apps/desktop/src-tauri/gen/ + +# 临时文件 +*.tmp +*.bak diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b9282a3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,26 @@ +[workspace] +resolver = "2" +members = [ + "crates/agentdock-core", + "crates/agentdock-adapter", + "crates/agentdock-platform", + "crates/agentdock-config", + "crates/agentdock-secrets", + "crates/agentdock-exec", + "crates/agentdock-diag", + "crates/agentdock-store", + "apps/desktop/src-tauri", +] + +[workspace.package] +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +# 统一输出,避免各 crate 各自配置重复 +[profile.dev] +debug = false + +[profile.release] +lto = "thin" +strip = true diff --git a/README.md b/README.md new file mode 100644 index 0000000..696cbfa --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# AgentDock + +跨平台、中文化、可扩展的 Agent CLI 软件管理器。帮助用户发现、安装、更新、配置、授权和排查 Agent CLI。 + +**本仓库当前为 Wave 0 状态**:桌面应用空壳可运行(Tauri 2 + React 19 + TypeScript + Vite),含设计 Token、 +八层 Rust crate 骨架、适配器目录骨架、Windows 本机环境检测与 SQLite 空库。 + +## 仓库结构 + +```text +agentdock/ +├── apps/desktop/ # Tauri 2 应用入口(React UI + src-tauri) +│ └── src/tokens/ # 设计 Token(对接视觉规范 v1.2,唯一品味依据) +├── crates/ # 本机能力层(八层) +│ ├── agentdock-core/ # 编排(发现/安装/配置/授权/诊断流水线) +│ ├── agentdock-adapter/ # 适配器 schema 加载、版本校验、执行器接口 +│ ├── agentdock-platform/# Win/Linux 环境检测、PATH、发行版识别 +│ ├── agentdock-config/ # 配置读写(原子写入、备份、多格式编解码) +│ ├── agentdock-secrets/ # 系统密钥库封装 + 脱敏 +│ ├── agentdock-exec/ # 参数白名单、禁止 shell 拼接的进程执行 +│ ├── agentdock-diag/ # 诊断规则引擎 +│ └── agentdock-store/ # SQLite 空库与 schema 迁移骨架 +├── adapters/ # 版本化适配器 YAML(14 + 目录索引) +├── docs/ # 架构文档副本 +└── tests/ # 测试夹具与 e2e 脚本 +``` + +## 本地开发 + +```bash +# 前端依赖 + 构建 +cd apps/desktop +npm install +npm run build + +# Rust 工作区测试(八 crate + 桌面壳) +cargo test + +# 启动桌面应用(开发模式) +cd apps/desktop +npm run tauri dev +``` + +## Wave 0 范围 + +- 应用外壳:左侧竖导航六项 + 顶栏 + 六个占位页(视觉规范 v1.2 §3.0 + PRD §7) +- IPC 心跳:`detect_env` 命令(Windows 本机环境检测,架构 §5) +- `agentdock-store`:SQLite 空库 + schema 迁移骨架 +- 假目录:CLI 目录页以 `adapters/catalog.yaml` 渲染 14 张卡片 + +后续波次见 `docs/architecture.md` §12。 diff --git a/adapters/catalog.yaml b/adapters/catalog.yaml new file mode 100644 index 0000000..6c46ebb --- /dev/null +++ b/adapters/catalog.yaml @@ -0,0 +1,33 @@ +# AgentDock 适配器目录索引(v1) +# 第一批 14 个(数据取自调研底稿 agent-cli-survey-2026-08-24.md,不编造) +# 第二批 4 个(grok / amp / deepseek-dsh / plandex)暂缓,本目录不含,后续按需加入。 +catalog_version: 1 +tools: + - id: codex + file: tools/codex.yaml + - id: claude-code + file: tools/claude-code.yaml + - id: gemini + file: tools/gemini.yaml + - id: copilot + file: tools/copilot.yaml + - id: kimi + file: tools/kimi.yaml + - id: qwen + file: tools/qwen.yaml + - id: codebuddy + file: tools/codebuddy.yaml + - id: opencode + file: tools/opencode.yaml + - id: crush + file: tools/crush.yaml + - id: goose + file: tools/goose.yaml + - id: aider + file: tools/aider.yaml + - id: cursor + file: tools/cursor.yaml + - id: cline + file: tools/cline.yaml + - id: warp + file: tools/warp.yaml diff --git a/adapters/schema/adapter.schema.json b/adapters/schema/adapter.schema.json new file mode 100644 index 0000000..3168452 --- /dev/null +++ b/adapters/schema/adapter.schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://agentdock.local/schemas/adapter.schema.json", + "title": "AgentDock Adapter", + "description": "Agent CLI 适配器定义(v1)。Wave 0 仅含占位字段;完整 schema 见架构 §3.1,随 Wave 1 落地。", + "type": "object", + "additionalProperties": false, + "required": ["id", "name", "name_zh", "vendor", "status"], + "properties": { + "id": { + "type": "string", + "description": "稳定 ID,如 codex", + "pattern": "^[a-z0-9][a-z0-9-]*$" + }, + "name": { + "type": "string", + "description": "展示名" + }, + "name_zh": { + "type": "string", + "description": "中文展示名" + }, + "vendor": { + "type": "string", + "description": "厂商" + }, + "status": { + "type": "string", + "enum": ["available", "watch"], + "description": "目录状态:available=可安装列表;watch=观察中(第二批)" + } + } +} diff --git a/adapters/tools/aider.yaml b/adapters/tools/aider.yaml new file mode 100644 index 0000000..19c1ff9 --- /dev/null +++ b/adapters/tools/aider.yaml @@ -0,0 +1,6 @@ +# aider 适配器占位(Wave 0) +id: aider +name: Aider +name_zh: Aider +vendor: aider.chat +status: available diff --git a/adapters/tools/claude-code.yaml b/adapters/tools/claude-code.yaml new file mode 100644 index 0000000..294f326 --- /dev/null +++ b/adapters/tools/claude-code.yaml @@ -0,0 +1,6 @@ +# claude-code 适配器占位(Wave 0) +id: claude-code +name: Claude Code +name_zh: Claude Code +vendor: Anthropic +status: available diff --git a/adapters/tools/cline.yaml b/adapters/tools/cline.yaml new file mode 100644 index 0000000..0b6e194 --- /dev/null +++ b/adapters/tools/cline.yaml @@ -0,0 +1,6 @@ +# cline 适配器占位(Wave 0) +id: cline +name: Cline +name_zh: Cline +vendor: Cline +status: available diff --git a/adapters/tools/codebuddy.yaml b/adapters/tools/codebuddy.yaml new file mode 100644 index 0000000..6e248ba --- /dev/null +++ b/adapters/tools/codebuddy.yaml @@ -0,0 +1,6 @@ +# codebuddy 适配器占位(Wave 0) +id: codebuddy +name: CodeBuddy Code +name_zh: CodeBuddy Code +vendor: 腾讯 +status: available diff --git a/adapters/tools/codex.yaml b/adapters/tools/codex.yaml new file mode 100644 index 0000000..4fddbe6 --- /dev/null +++ b/adapters/tools/codex.yaml @@ -0,0 +1,6 @@ +# codex 适配器占位(Wave 0) +id: codex +name: Codex CLI +name_zh: Codex CLI +vendor: OpenAI +status: available diff --git a/adapters/tools/copilot.yaml b/adapters/tools/copilot.yaml new file mode 100644 index 0000000..d25617d --- /dev/null +++ b/adapters/tools/copilot.yaml @@ -0,0 +1,6 @@ +# copilot 适配器占位(Wave 0) +id: copilot +name: Copilot CLI +name_zh: Copilot CLI +vendor: GitHub +status: available diff --git a/adapters/tools/crush.yaml b/adapters/tools/crush.yaml new file mode 100644 index 0000000..d3b7ed1 --- /dev/null +++ b/adapters/tools/crush.yaml @@ -0,0 +1,6 @@ +# crush 适配器占位(Wave 0) +id: crush +name: Crush +name_zh: Crush +vendor: Charm +status: available diff --git a/adapters/tools/cursor.yaml b/adapters/tools/cursor.yaml new file mode 100644 index 0000000..33fa755 --- /dev/null +++ b/adapters/tools/cursor.yaml @@ -0,0 +1,6 @@ +# cursor 适配器占位(Wave 0) +id: cursor +name: Cursor CLI +name_zh: Cursor CLI +vendor: Cursor +status: available diff --git a/adapters/tools/gemini.yaml b/adapters/tools/gemini.yaml new file mode 100644 index 0000000..5d8de6f --- /dev/null +++ b/adapters/tools/gemini.yaml @@ -0,0 +1,6 @@ +# gemini 适配器占位(Wave 0) +id: gemini +name: Gemini CLI +name_zh: Gemini CLI +vendor: Google +status: available diff --git a/adapters/tools/goose.yaml b/adapters/tools/goose.yaml new file mode 100644 index 0000000..7f7dd38 --- /dev/null +++ b/adapters/tools/goose.yaml @@ -0,0 +1,6 @@ +# goose 适配器占位(Wave 0) +id: goose +name: Goose +name_zh: Goose +vendor: Linux 基金会(AAIF) +status: available diff --git a/adapters/tools/kimi.yaml b/adapters/tools/kimi.yaml new file mode 100644 index 0000000..2afaed9 --- /dev/null +++ b/adapters/tools/kimi.yaml @@ -0,0 +1,6 @@ +# kimi 适配器占位(Wave 0) +id: kimi +name: Kimi CLI +name_zh: Kimi CLI +vendor: 月之暗面 +status: available diff --git a/adapters/tools/opencode.yaml b/adapters/tools/opencode.yaml new file mode 100644 index 0000000..120d647 --- /dev/null +++ b/adapters/tools/opencode.yaml @@ -0,0 +1,6 @@ +# opencode 适配器占位(Wave 0) +id: opencode +name: OpenCode +name_zh: OpenCode +vendor: opencode.ai +status: available diff --git a/adapters/tools/qwen.yaml b/adapters/tools/qwen.yaml new file mode 100644 index 0000000..87c73f6 --- /dev/null +++ b/adapters/tools/qwen.yaml @@ -0,0 +1,6 @@ +# qwen 适配器占位(Wave 0) +id: qwen +name: Qwen Code +name_zh: Qwen Code +vendor: 阿里巴巴 +status: available diff --git a/adapters/tools/warp.yaml b/adapters/tools/warp.yaml new file mode 100644 index 0000000..81d551b --- /dev/null +++ b/adapters/tools/warp.yaml @@ -0,0 +1,6 @@ +# warp 适配器占位(Wave 0) +id: warp +name: Warp Agent CLI +name_zh: Warp Agent CLI +vendor: Warp +status: available diff --git a/apps/desktop/index.html b/apps/desktop/index.html new file mode 100644 index 0000000..744cbb6 --- /dev/null +++ b/apps/desktop/index.html @@ -0,0 +1,12 @@ + + + + + + AgentDock + + +
+ + + diff --git a/apps/desktop/package-lock.json b/apps/desktop/package-lock.json new file mode 100644 index 0000000..64a0632 --- /dev/null +++ b/apps/desktop/package-lock.json @@ -0,0 +1,2137 @@ +{ + "name": "agentdock-desktop", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agentdock-desktop", + "version": "0.1.0", + "dependencies": { + "@tauri-apps/api": "^2", + "lucide-react": "^0.525.0", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@tauri-apps/cli": "^2", + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", + "@vitejs/plugin-react": "^4.5.0", + "typescript": "^5.6.3", + "vite": "^6.3.5" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.5.tgz", + "integrity": "sha512-jfkGfTwhQpsiSckPF8r9bU3pn3vyd72NlWaO+TgEO6WPSDnUhXzrNYCHBMOYj0ACaUgjm6eERLF+XV9a6RstoA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.5.tgz", + "integrity": "sha512-oGVqyQlxnrz9/ty89oHpU857VUHEl5/Xu4R2lS+aivCTrNnSsbiENzTnNaBsjxH0CNWGPhzHArOLFwo+oKXveA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.5.tgz", + "integrity": "sha512-bW7B8xMEq8n99Q3ieEcPRGuphurdZAaFzQc9Efyyw3FL6DZO6pMy9xhdN+kBoD7Sy05xNXSr4OyPPnpkYriS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.5.tgz", + "integrity": "sha512-YSwBS86QeHOGlrxJ1PSOIZSkzRL/JmKeunhc+lV6M1a6En8QuVCD/T/qIA0J4Gd2Y86RIOBYrLcOUtqGh9+/1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.5.tgz", + "integrity": "sha512-2fST8lILgl7cKbme/1KDdPCmbXbG+gqoV3bHp19L0ypX/3akYMBVdOunPleRCwonoLnXOZ/0F+Mt/v8POFmfcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.5.tgz", + "integrity": "sha512-cpIxQCP9J+EVad0a6LO1kY3ZGODlk80VlI+2I96B8xMcdHZ4pLVhfQ49JFpYqjPF91FFkQWftf57YlDcTiw9yQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.5.tgz", + "integrity": "sha512-r9fGh3eFs3e/udWh5ZjXQtxiYK/xoFxQaYR/cELxac/Udkl5Th+IsFm0CX3Kl9hmUH/we7EoMpjJgeQNnE0+IA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.5.tgz", + "integrity": "sha512-xdvFdp7OM6KLJviJT2g/YuRSUjnZgGHk4RNgwIbN7X6cPugOucV60DdHXWzsBVCUdrGb6qSXnJQrrAKMmQuj3Q==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.5.tgz", + "integrity": "sha512-rRqILAndyzHzP7T9NFQrq+4HFWNhqkqkKur7eiBpfLmz01PO0JKx5Vchu3YllE4YXI/Ftgq/szrDWg5GJ0mI8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.5.tgz", + "integrity": "sha512-Gf4X3qVMucayUvux6aXXPgXovocSFUC0rrffDuPI/S2nHhNMhjcZxsrAFYCOF350PRreW1XwzFj3CT/3bKsWCw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.5.tgz", + "integrity": "sha512-+s5qA0TNM0qm8PK/a5gt/1Hpx+NV08uSuCncvhziIlQzT6AEV2fnUQo7eBtFTFO0nA9scauvoR2HusfXmQnO4w==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.5.tgz", + "integrity": "sha512-ybb6QvWwWJCbBWqERpc8K3pYVGIrXlG8MEQ8IIuJY6Y9KdHQxoFoNyfkAOtKn1VHu3KuLidXvwrvGR1mEjeWCw==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.5.tgz", + "integrity": "sha512-nZb1DtnOyhCmYvsC8A2CwOkopVg+IS1+fPUa7rMOAXtNw5+lLCLLPqd6XAiNrGtoQKsbvIBOwsHnBH/3wnb4HQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.5.tgz", + "integrity": "sha512-yMbj63Sp89ryrXLWyz+sy+fYD2HpOnMCLGbe4Oa1smclFSUukdtD/BgdiHaAetJNb74URD8U4hM+qG5KVzMEkg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.5.tgz", + "integrity": "sha512-mhoan3OJw2kYV/e1jtIdmvUZgyBFeA6zGWsOswmR0Tg19TQbowZuR+JMLID6spbbBN7Zee2ejrgmy3+FxGrIdA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.5.tgz", + "integrity": "sha512-5ZTLmjWbb1VZdjuyhe83K/8QO0/h11midQCBP+X5OYn32ra7eOBoM0ZqtaY4nkgNsYgmdVhMYPoyVPTjUpHf3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.5.tgz", + "integrity": "sha512-m53kG+br6PGxOTmgBEM2DHSDs9RVjsyEbUwjJPJGTFm1grWOG8EKJggDCTb60unD4Tjby8fi7/m9XfkEWasVWg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.5.tgz", + "integrity": "sha512-6RHPJR1g/uvdYU8uXBnfq3nlqyZCP82Fr6NHgfGoaIeSh0YEqnX/x6uA9MmJJbnSH7swqX4F+CkGdUF+6doiQA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.5.tgz", + "integrity": "sha512-xs+OXQtEXgpXT0DmA5+U3qnRZHdCST/5HRQxS8wSPZTUZN/EMWeHuSIod32LQklTBZBV9DyfncKBQ8n5V3eFdw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.5.tgz", + "integrity": "sha512-e7hD+sl3s+mcLQDZ8pbudBVsdG6r5yN4w3LqG2TJ8sQHDpblWj5lrJs/3m01Cvlxbt4x13zu5thLjgypgtkYzw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.5.tgz", + "integrity": "sha512-GiyJaCf+WpMub/17aPcKk27QMl5W6f+KhdPTjlFOn5akH5Wa/DCM9Stdx5cDfmasyKB08MqpVQ1uJE2RkkpbXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.5.tgz", + "integrity": "sha512-+OQ8U2DdoEfXl8T4Fb18AjmEwbXMerKDKCL8yCPAYhKCEEKoul7rkbeGCBFCbAlaGaa7pmtRTpkAJM2LE/i5FA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.5.tgz", + "integrity": "sha512-KanvAZrPKbDBFwrgiU9yEVpQoox9QPV1WZOXX7HudJQY+eSlu82CtWxDU8WtuRRvtN5EGkLczkd6Y6DTcvm9wA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.5.tgz", + "integrity": "sha512-1aC3UEWTtRl3RK3VpDJ/Tqk1XI4SLTmXIthAq6wRWo8XiSXJNd+VprJM4/1P4+i6HIaFEFlVi9sTTziniD2tOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.5.tgz", + "integrity": "sha512-/gDJaRs4gl0NPIwqCz+6PkpmhhjRAD2j6P4rSNHBzUkO3naEx2mIU0pRle1vUNRQ7mE/+8OOeXLTv/J56FKiQg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tauri-apps/api": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.1.tgz", + "integrity": "sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.4.tgz", + "integrity": "sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.11.4", + "@tauri-apps/cli-darwin-x64": "2.11.4", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.4", + "@tauri-apps/cli-linux-arm64-gnu": "2.11.4", + "@tauri-apps/cli-linux-arm64-musl": "2.11.4", + "@tauri-apps/cli-linux-riscv64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-musl": "2.11.4", + "@tauri-apps/cli-win32-arm64-msvc": "2.11.4", + "@tauri-apps/cli-win32-ia32-msvc": "2.11.4", + "@tauri-apps/cli-win32-x64-msvc": "2.11.4" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.4.tgz", + "integrity": "sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.4.tgz", + "integrity": "sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.4.tgz", + "integrity": "sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.4.tgz", + "integrity": "sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.4.tgz", + "integrity": "sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.4.tgz", + "integrity": "sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.4.tgz", + "integrity": "sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.4.tgz", + "integrity": "sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.4.tgz", + "integrity": "sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.4.tgz", + "integrity": "sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.4.tgz", + "integrity": "sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.19.tgz", + "integrity": "sha512-Grytf1xOxOEMTGRwx6rLGKkTabd4vMg3VrKdj/7joCmV0qgh4QwMMO6xh34YEXQqirAuUdgQGa5orJQQ+69RBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.413", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.413.tgz", + "integrity": "sha512-F1XPKvt7HVfly5WND90ec16nFsdr4g5x/cVUP3EqjeyXynupabGDqpMa84wwvuYGDnldXLBz6DLXyZXWO9TPvw==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.525.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.525.0.tgz", + "integrity": "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.5.tgz", + "integrity": "sha512-/tqMfgP7GPA3PHhCmuiS4vIjrSVhHLgY++i+dhbG462euyAj7FpM4D9uq1X3BgjlqRdpcOrYhcQtfiQLNc8tqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.5", + "@rollup/rollup-android-arm64": "4.62.5", + "@rollup/rollup-darwin-arm64": "4.62.5", + "@rollup/rollup-darwin-x64": "4.62.5", + "@rollup/rollup-freebsd-arm64": "4.62.5", + "@rollup/rollup-freebsd-x64": "4.62.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.5", + "@rollup/rollup-linux-arm-musleabihf": "4.62.5", + "@rollup/rollup-linux-arm64-gnu": "4.62.5", + "@rollup/rollup-linux-arm64-musl": "4.62.5", + "@rollup/rollup-linux-loong64-gnu": "4.62.5", + "@rollup/rollup-linux-loong64-musl": "4.62.5", + "@rollup/rollup-linux-ppc64-gnu": "4.62.5", + "@rollup/rollup-linux-ppc64-musl": "4.62.5", + "@rollup/rollup-linux-riscv64-gnu": "4.62.5", + "@rollup/rollup-linux-riscv64-musl": "4.62.5", + "@rollup/rollup-linux-s390x-gnu": "4.62.5", + "@rollup/rollup-linux-x64-gnu": "4.62.5", + "@rollup/rollup-linux-x64-musl": "4.62.5", + "@rollup/rollup-openbsd-x64": "4.62.5", + "@rollup/rollup-openharmony-arm64": "4.62.5", + "@rollup/rollup-win32-arm64-msvc": "4.62.5", + "@rollup/rollup-win32-ia32-msvc": "4.62.5", + "@rollup/rollup-win32-x64-gnu": "4.62.5", + "@rollup/rollup-win32-x64-msvc": "4.62.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/apps/desktop/package.json b/apps/desktop/package.json new file mode 100644 index 0000000..3f2c16b --- /dev/null +++ b/apps/desktop/package.json @@ -0,0 +1,26 @@ +{ + "name": "agentdock-desktop", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "@tauri-apps/api": "^2", + "lucide-react": "^0.525.0", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@tauri-apps/cli": "^2", + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", + "@vitejs/plugin-react": "^4.5.0", + "typescript": "^5.6.3", + "vite": "^6.3.5" + } +} diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml new file mode 100644 index 0000000..d48e290 --- /dev/null +++ b/apps/desktop/src-tauri/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "agentdock-desktop" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "AgentDock 桌面壳(Tauri 2)" + +[lib] +name = "agentdock_desktop_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = [] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +agentdock-platform = { path = "../../../crates/agentdock-platform" } +agentdock-adapter = { path = "../../../crates/agentdock-adapter" } +agentdock-store = { path = "../../../crates/agentdock-store" } diff --git a/apps/desktop/src-tauri/build.rs b/apps/desktop/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/apps/desktop/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/apps/desktop/src-tauri/capabilities/default.json b/apps/desktop/src-tauri/capabilities/default.json new file mode 100644 index 0000000..b1f62ef --- /dev/null +++ b/apps/desktop/src-tauri/capabilities/default.json @@ -0,0 +1,7 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "默认能力:主窗口(Wave 0 仅调用自有 invoke 命令)", + "windows": ["main"], + "permissions": ["core:default"] +} diff --git a/apps/desktop/src-tauri/icon-source.png b/apps/desktop/src-tauri/icon-source.png new file mode 100644 index 0000000..c035499 Binary files /dev/null and b/apps/desktop/src-tauri/icon-source.png differ diff --git a/apps/desktop/src-tauri/icons/128x128.png b/apps/desktop/src-tauri/icons/128x128.png new file mode 100644 index 0000000..4b83875 Binary files /dev/null and b/apps/desktop/src-tauri/icons/128x128.png differ diff --git a/apps/desktop/src-tauri/icons/128x128@2x.png b/apps/desktop/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..e35ee21 Binary files /dev/null and b/apps/desktop/src-tauri/icons/128x128@2x.png differ diff --git a/apps/desktop/src-tauri/icons/32x32.png b/apps/desktop/src-tauri/icons/32x32.png new file mode 100644 index 0000000..33942d6 Binary files /dev/null and b/apps/desktop/src-tauri/icons/32x32.png differ diff --git a/apps/desktop/src-tauri/icons/64x64.png b/apps/desktop/src-tauri/icons/64x64.png new file mode 100644 index 0000000..d823e95 Binary files /dev/null and b/apps/desktop/src-tauri/icons/64x64.png differ diff --git a/apps/desktop/src-tauri/icons/Square107x107Logo.png b/apps/desktop/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..6b0571f Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square107x107Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square142x142Logo.png b/apps/desktop/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..41d0bc5 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square142x142Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square150x150Logo.png b/apps/desktop/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..ff970f7 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square150x150Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square284x284Logo.png b/apps/desktop/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..405d4ec Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square284x284Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square30x30Logo.png b/apps/desktop/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..cb733cc Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square30x30Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square310x310Logo.png b/apps/desktop/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..a6d5e39 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square310x310Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square44x44Logo.png b/apps/desktop/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..d9ba7e3 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square44x44Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square71x71Logo.png b/apps/desktop/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..be20a22 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square71x71Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square89x89Logo.png b/apps/desktop/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..4752272 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square89x89Logo.png differ diff --git a/apps/desktop/src-tauri/icons/StoreLogo.png b/apps/desktop/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..37ed6d1 Binary files /dev/null and b/apps/desktop/src-tauri/icons/StoreLogo.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml b/apps/desktop/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..2ffbf24 --- /dev/null +++ b/apps/desktop/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..fa35b24 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..e1181e5 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..5b03454 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c0452b5 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..ca9dac3 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..6409f59 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..dc1f966 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..ce01275 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..ba46dd2 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..a28de0e Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..9357412 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..1372019 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..c8ec08b Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..bf65767 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..2091c9f Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/apps/desktop/src-tauri/icons/android/values/ic_launcher_background.xml b/apps/desktop/src-tauri/icons/android/values/ic_launcher_background.xml new file mode 100644 index 0000000..ea9c223 --- /dev/null +++ b/apps/desktop/src-tauri/icons/android/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/apps/desktop/src-tauri/icons/icon.icns b/apps/desktop/src-tauri/icons/icon.icns new file mode 100644 index 0000000..35bb0b4 Binary files /dev/null and b/apps/desktop/src-tauri/icons/icon.icns differ diff --git a/apps/desktop/src-tauri/icons/icon.ico b/apps/desktop/src-tauri/icons/icon.ico new file mode 100644 index 0000000..e8ce7a1 Binary files /dev/null and b/apps/desktop/src-tauri/icons/icon.ico differ diff --git a/apps/desktop/src-tauri/icons/icon.png b/apps/desktop/src-tauri/icons/icon.png new file mode 100644 index 0000000..2329e7e Binary files /dev/null and b/apps/desktop/src-tauri/icons/icon.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@1x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@1x.png new file mode 100644 index 0000000..134688a Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 0000000..094ef5c Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@2x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@2x.png new file mode 100644 index 0000000..094ef5c Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@3x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@3x.png new file mode 100644 index 0000000..1435b45 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@1x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@1x.png new file mode 100644 index 0000000..f00cde8 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 0000000..6ca025e Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@2x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@2x.png new file mode 100644 index 0000000..6ca025e Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@3x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@3x.png new file mode 100644 index 0000000..a5e7cfc Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@1x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@1x.png new file mode 100644 index 0000000..094ef5c Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 0000000..9958096 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@2x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@2x.png new file mode 100644 index 0000000..9958096 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@3x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@3x.png new file mode 100644 index 0000000..4045cf2 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-512@2x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-512@2x.png new file mode 100644 index 0000000..29025c7 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-60x60@2x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-60x60@2x.png new file mode 100644 index 0000000..4045cf2 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-60x60@3x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-60x60@3x.png new file mode 100644 index 0000000..d14405a Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-76x76@1x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-76x76@1x.png new file mode 100644 index 0000000..e65e7d7 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-76x76@2x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-76x76@2x.png new file mode 100644 index 0000000..c76eb0b Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 0000000..35b4180 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/apps/desktop/src-tauri/src/commands/catalog.rs b/apps/desktop/src-tauri/src/commands/catalog.rs new file mode 100644 index 0000000..c4785b0 --- /dev/null +++ b/apps/desktop/src-tauri/src/commands/catalog.rs @@ -0,0 +1,11 @@ +//! IPC 命令:假目录(架构 §2 adapters/,IPC 契约 listCatalog) + +use agentdock_adapter::catalog::{self, CatalogEntry}; + +use crate::adapters_dir; + +/// 读取 adapters/catalog.yaml 与 tools/*.yaml,返回 14 个目录条目 +#[tauri::command(rename = "listCatalog")] +pub fn list_catalog() -> Result, String> { + catalog::load_catalog(adapters_dir()).map_err(|e| e.to_string()) +} diff --git a/apps/desktop/src-tauri/src/commands/env.rs b/apps/desktop/src-tauri/src/commands/env.rs new file mode 100644 index 0000000..b95f17c --- /dev/null +++ b/apps/desktop/src-tauri/src/commands/env.rs @@ -0,0 +1,10 @@ +//! IPC 命令:本机环境检测(架构 §5,IPC 契约 detectEnv) + +use agentdock_platform::detect; +use agentdock_platform::model::PlatformEnv; + +/// 返回本机真实环境快照(Windows/Linux 双平台) +#[tauri::command(rename = "detectEnv")] +pub fn detect_env() -> PlatformEnv { + detect::detect_env() +} diff --git a/apps/desktop/src-tauri/src/commands/mod.rs b/apps/desktop/src-tauri/src/commands/mod.rs new file mode 100644 index 0000000..beeb2cd --- /dev/null +++ b/apps/desktop/src-tauri/src/commands/mod.rs @@ -0,0 +1,2 @@ +pub mod catalog; +pub mod env; diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs new file mode 100644 index 0000000..83e1c38 --- /dev/null +++ b/apps/desktop/src-tauri/src/lib.rs @@ -0,0 +1,42 @@ +mod commands; + +use std::path::PathBuf; + +/// 解析适配器目录。 +/// 优先级:环境变量 AGENTDOCK_ADAPTERS_DIR > CWD 相对路径(tauri dev 时 CWD=apps/desktop) +/// > CARGO_MANIFEST_DIR 相对路径(仓库根)。 +fn adapters_dir() -> PathBuf { + if let Ok(dir) = std::env::var("AGENTDOCK_ADAPTERS_DIR") { + return PathBuf::from(dir); + } + let cwd = std::env::current_dir().unwrap_or_default(); + let cargo_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let candidates = [ + cwd.join("../../adapters"), + cargo_dir.join("../../../adapters"), + ]; + for c in candidates { + if c.join("catalog.yaml").exists() { + return c; + } + } + cargo_dir.join("../../../adapters") +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .setup(|_app| { + // 自测标记:`tauri dev` 启动后日志出现该行即代表壳已就绪 + println!("[agentdock] window-ready"); + // Wave 0:初始化 SQLite 空库(架构 §2 状态存储层) + let _ = agentdock_store::Store::open_default(); + Ok(()) + }) + .invoke_handler(tauri::generate_handler![ + commands::env::detect_env, + commands::catalog::list_catalog, + ]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/apps/desktop/src-tauri/src/main.rs b/apps/desktop/src-tauri/src/main.rs new file mode 100644 index 0000000..2a1c0bb --- /dev/null +++ b/apps/desktop/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + agentdock_desktop_lib::run() +} diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json new file mode 100644 index 0000000..94002ca --- /dev/null +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "AgentDock", + "version": "0.1.0", + "identifier": "cn.agentdock.desktop", + "build": { + "beforeDevCommand": "npm run dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "npm run build", + "frontendDist": "../dist" + }, + "app": { + "windows": [ + { + "title": "AgentDock", + "width": 1280, + "height": 800, + "minWidth": 960, + "minHeight": 600, + "center": true + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": ["nsis"], + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.ico" + ] + } +} diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx new file mode 100644 index 0000000..7bff421 --- /dev/null +++ b/apps/desktop/src/App.tsx @@ -0,0 +1,65 @@ +import { useMemo, useState } from "react"; +import { Sidebar, type PageKey, PAGE_META } from "./components/Sidebar"; +import { Header } from "./components/Header"; +import { OverviewPage } from "./pages/Overview"; +import { CatalogPage } from "./pages/Catalog"; +import { MyCliPage } from "./pages/MyCli"; +import { ConfigCenterPage } from "./pages/ConfigCenter"; +import { BackupPage } from "./pages/Backup"; +import { SettingsPage } from "./pages/Settings"; +import { useEnv } from "./hooks/useEnv"; +import type { PlatformEnv, RuntimeInfo } from "./ipc/types"; + +/** 平台相关运行时集合(Windows 不看 apt,Linux 不看 winget) */ +const WIN_RUNTIMES = ["node", "npm", "python", "uv", "git", "winget"] as const; +const LINUX_RUNTIMES = ["node", "npm", "python", "uv", "git", "apt"] as const; + +function envWarningCount(env: PlatformEnv | null): number { + if (!env) return 0; + const keys = env.os === "windows" ? WIN_RUNTIMES : LINUX_RUNTIMES; + let n = 0; + for (const k of keys) { + const info = env.runtimes[k] as RuntimeInfo | null | undefined; + if (info && info.status !== "installed") n++; + } + return n; +} + +export default function App() { + const [page, setPage] = useState("overview"); + const { env } = useEnv(); + const warningCount = envWarningCount(env); + + const content = useMemo(() => { + switch (page) { + case "overview": + return ; + case "catalog": + return ; + case "my-cli": + return ; + case "config": + return ; + case "backup": + return ; + case "settings": + return ; + } + }, [page]); + + return ( +
+ + ); +} diff --git a/apps/desktop/src/components/CliMonogram.tsx b/apps/desktop/src/components/CliMonogram.tsx new file mode 100644 index 0000000..b72d908 --- /dev/null +++ b/apps/desktop/src/components/CliMonogram.tsx @@ -0,0 +1,19 @@ +/** CLI 单色字母头像(视觉规范 §7:双字母单色,--ad-bg-2 底 + 1px --ad-border) */ +export function CliMonogram({ + name, + size = 32, +}: { + name: string; + size?: number; +}) { + const chars = name.replace(/\s+/g, "").slice(0, 2).toUpperCase(); + return ( + + ); +} diff --git a/apps/desktop/src/components/Header.tsx b/apps/desktop/src/components/Header.tsx new file mode 100644 index 0000000..c92f63d --- /dev/null +++ b/apps/desktop/src/components/Header.tsx @@ -0,0 +1,34 @@ +import { ChevronDown, Search } from "lucide-react"; + +interface HeaderProps { + title: string; + /** 环境警告条数(0 时整颗 chip 不渲染,规范 §3.0) */ + warningCount?: number; +} + +/** 顶栏(视觉规范 §3.0):页面标题 + 搜索框 + 环境警告 chip */ +export function Header({ title, warningCount = 0 }: HeaderProps) { + return ( +
+

{title}

+
+
+
+ {warningCount > 0 && ( + + )} +
+
+ ); +} diff --git a/apps/desktop/src/components/KpiCard.tsx b/apps/desktop/src/components/KpiCard.tsx new file mode 100644 index 0000000..acdfaf3 --- /dev/null +++ b/apps/desktop/src/components/KpiCard.tsx @@ -0,0 +1,39 @@ +export type KpiTone = "primary" | "attention" | "accent" | "warning"; + +const TONE_COLOR: Record = { + primary: "var(--ad-primary)", + attention: "var(--ad-attention)", + accent: "var(--ad-accent)", + warning: "var(--ad-warning)", +}; + +interface KpiCardProps { + label: string; + value: number; + tone: KpiTone; + /** 副标题:对象点名句式(视觉规范 §3.1.1) */ + subtitle: string; +} + +/** + * KPI 卡(视觉规范 §3.1.1): + * - 径向渐变玻璃板 + 上缘高光 + --ad-shadow-panel + * - 顶部色条贯通整宽;数值为 0 时色条降灰、数字回 --ad-text-1 + * - 数字随状态色贯穿 + */ +export function KpiCard({ label, value, tone, subtitle }: KpiCardProps) { + const color = value > 0 ? TONE_COLOR[tone] : "var(--ad-border)"; + const valueColor = value > 0 ? TONE_COLOR[tone] : "var(--ad-text-1)"; + return ( +
+ + ); +} diff --git a/apps/desktop/src/components/MonoChip.tsx b/apps/desktop/src/components/MonoChip.tsx new file mode 100644 index 0000000..342c84e --- /dev/null +++ b/apps/desktop/src/components/MonoChip.tsx @@ -0,0 +1,6 @@ +import type { ReactNode } from "react"; + +/** 内联等宽 chip(视觉规范 §2.2:文件名/版本号/命令专用) */ +export function MonoChip({ children }: { children: ReactNode }) { + return {children}; +} diff --git a/apps/desktop/src/components/Sidebar.tsx b/apps/desktop/src/components/Sidebar.tsx new file mode 100644 index 0000000..6e4a846 --- /dev/null +++ b/apps/desktop/src/components/Sidebar.tsx @@ -0,0 +1,79 @@ +import { + Archive, + Gauge, + Library, + Settings, + SlidersHorizontal, + SquareTerminal, +} from "lucide-react"; +import type { LucideIcon } from "lucide-react"; + +export type PageKey = + | "overview" + | "catalog" + | "my-cli" + | "config" + | "backup" + | "settings"; + +export interface PageMeta { + title: string; +} + +export const PAGE_META: Record = { + overview: { title: "总览" }, + catalog: { title: "CLI 目录" }, + "my-cli": { title: "我的 CLI" }, + config: { title: "配置中心" }, + backup: { title: "备份与迁移" }, + settings: { title: "设置" }, +}; + +interface NavItem { + key: PageKey; + label: string; + icon: LucideIcon; +} + +const NAV_ITEMS: NavItem[] = [ + { key: "overview", label: "总览", icon: Gauge }, + { key: "catalog", label: "CLI 目录", icon: Library }, + { key: "my-cli", label: "我的 CLI", icon: SquareTerminal }, + { key: "config", label: "配置中心", icon: SlidersHorizontal }, + { key: "backup", label: "备份与迁移", icon: Archive }, + { key: "settings", label: "设置", icon: Settings }, +]; + +const ENGINE_VERSION = "v0.1.0"; + +interface SidebarProps { + current: PageKey; + onNavigate: (page: PageKey) => void; +} + +/** 左侧竖导航 + 底部引擎状态区(视觉规范 §3.0) */ +export function Sidebar({ current, onNavigate }: SidebarProps) { + return ( + + ); +} diff --git a/apps/desktop/src/components/StatusBadge.tsx b/apps/desktop/src/components/StatusBadge.tsx new file mode 100644 index 0000000..7e13f54 --- /dev/null +++ b/apps/desktop/src/components/StatusBadge.tsx @@ -0,0 +1,18 @@ +export type BadgeKind = "installed" | "update" | "auth" | "uninstalled"; + +const KIND_CLASS: Record = { + installed: "badge badge-installed", + update: "badge badge-update", + auth: "badge badge-auth", + uninstalled: "badge badge-uninstalled", +}; + +interface StatusBadgeProps { + kind: BadgeKind; + label: string; +} + +/** 状态角标(视觉规范 §3.1.4 / §3.2:已安装=青、可更新=品红+弱辉光、待授权=紫、未安装=灰) */ +export function StatusBadge({ kind, label }: StatusBadgeProps) { + return {label}; +} diff --git a/apps/desktop/src/hooks/useCatalog.ts b/apps/desktop/src/hooks/useCatalog.ts new file mode 100644 index 0000000..02f5dc9 --- /dev/null +++ b/apps/desktop/src/hooks/useCatalog.ts @@ -0,0 +1,38 @@ +import { useEffect, useState } from "react"; +import { listCatalog } from "../ipc"; +import type { CatalogEntry } from "../ipc/types"; + +export interface UseCatalogResult { + entries: CatalogEntry[]; + loading: boolean; + error: string | null; +} + +/** 加载适配器目录(listCatalog) */ +export function useCatalog(): UseCatalogResult { + const [entries, setEntries] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + let cancelled = false; + listCatalog() + .then((list) => { + if (!cancelled) { + setEntries(list); + setLoading(false); + } + }) + .catch((err: unknown) => { + if (!cancelled) { + setError(String(err)); + setLoading(false); + } + }); + return () => { + cancelled = true; + }; + }, []); + + return { entries, loading, error }; +} diff --git a/apps/desktop/src/hooks/useEnv.ts b/apps/desktop/src/hooks/useEnv.ts new file mode 100644 index 0000000..f713687 --- /dev/null +++ b/apps/desktop/src/hooks/useEnv.ts @@ -0,0 +1,38 @@ +import { useEffect, useState } from "react"; +import { detectEnv } from "../ipc"; +import type { PlatformEnv } from "../ipc/types"; + +export interface UseEnvResult { + env: PlatformEnv | null; + loading: boolean; + error: string | null; +} + +/** 加载本机环境检测结果(一次性的只读快照) */ +export function useEnv(): UseEnvResult { + const [env, setEnv] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + let cancelled = false; + detectEnv() + .then((e) => { + if (!cancelled) { + setEnv(e); + setLoading(false); + } + }) + .catch((err: unknown) => { + if (!cancelled) { + setError(String(err)); + setLoading(false); + } + }); + return () => { + cancelled = true; + }; + }, []); + + return { env, loading, error }; +} diff --git a/apps/desktop/src/ipc/index.ts b/apps/desktop/src/ipc/index.ts new file mode 100644 index 0000000..1b12d66 --- /dev/null +++ b/apps/desktop/src/ipc/index.ts @@ -0,0 +1,58 @@ +// 类型化 invoke 封装(唯一前端 → Rust 入口,架构 §2「界面层禁直接拼命令」) +import { invoke } from "@tauri-apps/api/core"; +import type { CatalogEntry, PlatformEnv } from "./types"; + +/** 是否运行在 Tauri 环境(否则走 mock,便于纯浏览器联调 UI) */ +function isTauri(): boolean { + return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window; +} + +export async function detectEnv(): Promise { + if (!isTauri()) { + return mockPlatformEnv(); + } + return invoke("detectEnv"); +} + +export async function listCatalog(): Promise { + if (!isTauri()) { + return mockCatalog(); + } + return invoke("listCatalog"); +} + +// ---- 浏览器联调用 mock(仅当不在 Tauri 内时生效,不影响真实数据) ---- +const mockCatalog = (): CatalogEntry[] => [ + { id: "codex", name: "Codex CLI", name_zh: "Codex CLI", vendor: "OpenAI", status: "available" }, + { id: "claude-code", name: "Claude Code", name_zh: "Claude Code", vendor: "Anthropic", status: "available" }, + { id: "gemini", name: "Gemini CLI", name_zh: "Gemini CLI", vendor: "Google", status: "available" }, + { id: "copilot", name: "Copilot CLI", name_zh: "Copilot CLI", vendor: "GitHub", status: "available" }, + { id: "kimi", name: "Kimi CLI", name_zh: "Kimi CLI", vendor: "月之暗面", status: "available" }, + { id: "qwen", name: "Qwen Code", name_zh: "Qwen Code", vendor: "阿里巴巴", status: "available" }, + { id: "codebuddy", name: "CodeBuddy Code", name_zh: "CodeBuddy Code", vendor: "腾讯", status: "available" }, + { id: "opencode", name: "OpenCode", name_zh: "OpenCode", vendor: "opencode.ai", status: "available" }, + { id: "crush", name: "Crush", name_zh: "Crush", vendor: "Charm", status: "available" }, + { id: "goose", name: "Goose", name_zh: "Goose", vendor: "Linux 基金会(AAIF)", status: "available" }, + { id: "aider", name: "Aider", name_zh: "Aider", vendor: "aider.chat", status: "available" }, + { id: "cursor", name: "Cursor CLI", name_zh: "Cursor CLI", vendor: "Cursor", status: "available" }, + { id: "cline", name: "Cline", name_zh: "Cline", vendor: "Cline", status: "available" }, + { id: "warp", name: "Warp Agent CLI", name_zh: "Warp Agent CLI", vendor: "Warp", status: "available" }, +]; + +const mockPlatformEnv = (): PlatformEnv => ({ + os: "windows", + os_version: "Windows 11 24H2 (Build 26100)", + arch: "x86_64", + shells: { powershell_version: "5.1.26100.1", pwsh_version: null, bash_available: true }, + runtimes: { + node: { status: "installed", version: "24.18.0", path: "C:\\Program Files\\nodejs\\node.exe" }, + npm: { status: "installed", version: "11.16.0", path: "C:\\Program Files\\nodejs\\npm.cmd" }, + python: { status: "installed", version: "3.14.6", path: "C:\\Python314\\python.exe" }, + uv: { status: "not_in_path", version: null, path: null }, + git: { status: "installed", version: "2.47.1", path: "C:\\Program Files\\Git\\cmd\\git.exe" }, + winget: { status: "installed", version: "1.8.1911", path: "C:\\Users\\MoBai\\AppData\\Local\\Microsoft\\WindowsApps\\winget.exe" }, + apt: { status: "not_in_path", version: null, path: null }, + }, + path_entries: ["C:\\Windows\\system32", "C:\\Windows", "C:\\Program Files\\nodejs"], + capabilities: { keyring: "ok", can_elevate: false }, +}); diff --git a/apps/desktop/src/ipc/types.ts b/apps/desktop/src/ipc/types.ts new file mode 100644 index 0000000..e1ced7a --- /dev/null +++ b/apps/desktop/src/ipc/types.ts @@ -0,0 +1,54 @@ +// IPC 契约类型(对齐架构 §5 PlatformEnv / listCatalog) +// 字段命名与 Rust 侧 serde 输出一致(snake_case)。 + +export interface PlatformEnv { + os: string; + os_version: string; + arch: string; + shells: Shells; + runtimes: Runtimes; + path_entries: string[]; + capabilities: Capabilities; +} + +export interface Shells { + powershell_version: string | null; + pwsh_version: string | null; + bash_available: boolean; +} + +export interface Runtimes { + node: RuntimeInfo | null; + npm: RuntimeInfo | null; + python: RuntimeInfo | null; + uv: RuntimeInfo | null; + git: RuntimeInfo | null; + winget: RuntimeInfo | null; + apt: RuntimeInfo | null; +} + +export type RuntimeStatus = + | "installed" + | "not_installed" + | "not_in_path" + | "exec_failed" + | "version_unparseable"; + +export interface RuntimeInfo { + status: RuntimeStatus; + version: string | null; + path: string | null; +} + +export interface Capabilities { + keyring: string; + can_elevate: boolean; +} + +export interface CatalogEntry { + id: string; + name: string; + name_zh: string; + vendor: string; + status: string; +} diff --git a/apps/desktop/src/main.tsx b/apps/desktop/src/main.tsx new file mode 100644 index 0000000..673ca6d --- /dev/null +++ b/apps/desktop/src/main.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; + +// 设计 Token(视觉规范 v1.2,唯一品味依据) +import "./tokens/color.css"; +import "./tokens/typography.css"; +import "./tokens/space.css"; +import "./tokens/motion.css"; +import "./tokens/effects.css"; +import "./tokens/tiers.css"; +import "./styles/global.css"; + +/** 性能档位自动判定(架构 §8.3 / 视觉规范 §5) */ +function applyFxTier(): void { + const html = document.documentElement; + const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; + const cores = navigator.hardwareConcurrency ?? 8; + if (reduce || cores <= 4) { + html.dataset.fxTier = "low"; + } +} + +applyFxTier(); + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + , +); diff --git a/apps/desktop/src/pages/Backup.tsx b/apps/desktop/src/pages/Backup.tsx new file mode 100644 index 0000000..0f799c9 --- /dev/null +++ b/apps/desktop/src/pages/Backup.tsx @@ -0,0 +1,19 @@ +import { Archive } from "lucide-react"; + +/** 备份与迁移(PRD §7):创建备份、查看内容、恢复。Wave 0 占位。 */ +export function BackupPage() { + return ( +
+
+
+

备份与迁移

+

+ 备份功能在 Wave 4 落地。备份将明确标注是否包含敏感信息,密钥永不进备份。 +

+ +
+ ); +} diff --git a/apps/desktop/src/pages/Catalog.tsx b/apps/desktop/src/pages/Catalog.tsx new file mode 100644 index 0000000..dafab11 --- /dev/null +++ b/apps/desktop/src/pages/Catalog.tsx @@ -0,0 +1,113 @@ +import { useMemo, useState } from "react"; +import { Search } from "lucide-react"; +import { useCatalog } from "../hooks/useCatalog"; +import { StatusBadge } from "../components/StatusBadge"; +import { MonoChip } from "../components/MonoChip"; +import { CliMonogram } from "../components/CliMonogram"; + +type StatusFilter = "all" | "installed" | "uninstalled" | "update"; +type PlatformFilter = "all" | "windows" | "linux"; + +const STATUS_FILTERS: { key: StatusFilter; label: string }[] = [ + { key: "all", label: "全部" }, + { key: "installed", label: "已安装" }, + { key: "uninstalled", label: "未安装" }, + { key: "update", label: "可更新" }, +]; + +const PLATFORM_FILTERS: { key: PlatformFilter; label: string }[] = [ + { key: "all", label: "全部平台" }, + { key: "windows", label: "Windows" }, + { key: "linux", label: "Linux" }, +]; + +/** CLI 目录页(视觉规范 §3.2 + PRD §7):搜索 + 筛选 + 14 张卡片(数据来自 catalog.yaml) */ +export function CatalogPage() { + const { entries, loading, error } = useCatalog(); + const [query, setQuery] = useState(""); + const [status, setStatus] = useState("all"); + const [platform, setPlatform] = useState("all"); + + const filtered = useMemo(() => { + const q = query.trim().toLowerCase(); + return entries.filter((e) => { + if (q) { + const haystack = `${e.name_zh} ${e.name} ${e.vendor} ${e.id}`.toLowerCase(); + if (!haystack.includes(q)) return false; + } + // Wave 0:14 个均为未安装;已安装/可更新筛选结果为空(诚实占位) + if (status === "installed") return false; + if (status === "update") return false; + // 平台:全部 14 个均支持 Windows + Ubuntu(调研底稿结论),筛选不改变集合 + if (platform === "windows" || platform === "linux") return true; + return true; + }); + }, [entries, query, status, platform]); + + return ( +
+
+
+
+
+ {STATUS_FILTERS.map((f) => ( + + ))} +
+
+ {PLATFORM_FILTERS.map((f) => ( + + ))} +
+
+ + {error &&

目录加载失败:{error}

} + {loading && !error &&

目录加载中…

} + +
+ {filtered.map((entry) => ( +
+
+ +
+
{entry.name_zh}
+
{entry.vendor}
+
+
+

官方渠道安装 · 适配器随 Wave 1 落地

+
+ + Windows · Linux + v1 +
+
+ ))} + {!loading && filtered.length === 0 && ( +

没有符合条件的 CLI

+ )} +
+
+ ); +} diff --git a/apps/desktop/src/pages/ConfigCenter.tsx b/apps/desktop/src/pages/ConfigCenter.tsx new file mode 100644 index 0000000..778aeb7 --- /dev/null +++ b/apps/desktop/src/pages/ConfigCenter.tsx @@ -0,0 +1,19 @@ +import { SlidersHorizontal } from "lucide-react"; + +/** 配置中心(PRD §7):按 CLI 展示中文配置表单。Wave 0 占位。 */ +export function ConfigCenterPage() { + return ( +
+
+
+

配置中心

+

+ 中文配置表单在 Wave 2 落地。敏感字段将使用密码输入与系统密钥库。 +

+ +
+ ); +} diff --git a/apps/desktop/src/pages/MyCli.tsx b/apps/desktop/src/pages/MyCli.tsx new file mode 100644 index 0000000..b423456 --- /dev/null +++ b/apps/desktop/src/pages/MyCli.tsx @@ -0,0 +1,19 @@ +import { SquareTerminal } from "lucide-react"; + +/** 我的 CLI(PRD §7):版本 / 路径 / 授权状态。Wave 0 尚无已安装 CLI,展示空态。 */ +export function MyCliPage() { + return ( +
+
+
+

暂无已安装 CLI

+

+ 安装功能在 Wave 1 开放。届时这里会展示每个 CLI 的版本、路径与授权状态。 +

+ +
+ ); +} diff --git a/apps/desktop/src/pages/Overview.tsx b/apps/desktop/src/pages/Overview.tsx new file mode 100644 index 0000000..0aca283 --- /dev/null +++ b/apps/desktop/src/pages/Overview.tsx @@ -0,0 +1,254 @@ +import { useState } from "react"; +import { Activity, Archive, ChevronRight, Lock, Plus } from "lucide-react"; +import { useEnv } from "../hooks/useEnv"; +import { useCatalog } from "../hooks/useCatalog"; +import { KpiCard } from "../components/KpiCard"; +import { MonoChip } from "../components/MonoChip"; +import { StatusBadge } from "../components/StatusBadge"; +import { CliMonogram } from "../components/CliMonogram"; +import type { PlatformEnv, RuntimeInfo } from "../ipc/types"; + +/** 平台相关运行时集合(Windows 不看 apt,Linux 不看 winget) */ +const WINDOWS_RUNTIMES = ["node", "npm", "python", "uv", "git", "winget"] as const; +const LINUX_RUNTIMES = ["node", "npm", "python", "uv", "git", "apt"] as const; + +function runtimeProblems(env: PlatformEnv): { name: string; info: RuntimeInfo }[] { + const keys = env.os === "windows" ? WINDOWS_RUNTIMES : LINUX_RUNTIMES; + const problems: { name: string; info: RuntimeInfo }[] = []; + for (const key of keys) { + const info = env.runtimes[key]; + if (info && info.status !== "installed") { + problems.push({ name: key, info }); + } + } + return problems; +} + +function runtimeStatusText(status: string): string { + switch (status) { + case "not_in_path": + return "不在 PATH"; + case "not_installed": + return "未安装"; + case "exec_failed": + return "执行失败"; + case "version_unparseable": + return "版本未知"; + default: + return "未知"; + } +} + +function RuntimeItem({ name, info }: { name: string; info: RuntimeInfo }) { + const dotClass = + info.status === "installed" + ? "status-dot ok" + : info.status === "exec_failed" + ? "status-dot error" + : "status-dot warn"; + return ( +
+
+ ); +} + +function EnvPanel() { + const { env, loading, error } = useEnv(); + const [showAllPath, setShowAllPath] = useState(false); + + if (error) { + return ( +
+
+

本机环境

+
+

环境检测失败:{error}

+
+ ); + } + + const pathPreview = env ? env.path_entries.slice(0, 8) : []; + + return ( +
+
+

本机环境

+ {loading && 检测中…} +
+ {env && ( + <> +
+
+
+ 系统 + {env.os_version} +
+
+ 架构 + {env.arch} +
+
+ Shell + + {env.shells.powershell_version ? ( + <>PowerShell {env.shells.powershell_version} + ) : null} + {env.shells.pwsh_version ? ( + <> · pwsh {env.shells.pwsh_version} + ) : null} + {env.shells.bash_available ? " · Bash ✓" : ""} + +
+
+ 密钥库 + + {env.capabilities.keyring === "ok" ? "Credential Manager ✓" : "密钥库缺失"} + +
+
+ 提权 + {env.capabilities.can_elevate ? "可用" : "不可用"} +
+
+
+ {(["node", "npm", "python", "uv", "git", "winget", "apt"] as const).map( + (key) => { + const info = env.runtimes[key]; + if (!info) return null; + return ; + }, + )} +
+
+
+
+ + PATH({env.path_entries.length} 条) + {env.path_entries.length} + + {env.path_entries.length > pathPreview.length && ( + + )} +
+ {(showAllPath ? env.path_entries : pathPreview).map((entry, idx) => ( +
+ {entry} +
+ ))} +
+ + )} +
+ ); +} + +/** 总览页(视觉规范 §3.1 + PRD §7):KPI + 最近诊断/快速操作 + CLI 状态网格 + 本机环境 */ +export function OverviewPage() { + const { env } = useEnv(); + const { entries } = useCatalog(); + + const warningCount = env ? runtimeProblems(env).length : 0; + const warningNames = env ? runtimeProblems(env).map((p) => p.name) : []; + + return ( +
+ {/* ① KPI 一排 4 张 */} +
+ + + + 0 ? warningNames.join(" · ") : "环境正常"} + /> +
+ + {/* ② 中段:左 2/3 最近诊断 + 右 1/3 快速操作 */} +
+
+
+

最近诊断

+ +
+
    +
  • +
  • +
+
+
+

快速操作

+ + + +
+
最近安装
+

暂无记录

+
+

+

+
+
+ + {/* ③ 下段:CLI 状态网格通栏 */} +
+
+

CLI 状态

+ +
+
+ {entries.map((entry) => ( +
+
+ + {entry.name_zh} + +
+
+ +
+
+
+
+ ))} +
+
+ + {/* ④ 本机环境(真实检测结果,架构 §5) */} + +
+ ); +} diff --git a/apps/desktop/src/pages/Settings.tsx b/apps/desktop/src/pages/Settings.tsx new file mode 100644 index 0000000..6cc303e --- /dev/null +++ b/apps/desktop/src/pages/Settings.tsx @@ -0,0 +1,44 @@ +import { Settings } from "lucide-react"; + +/** 设置(PRD §7):下载源策略 / 自动检查更新 / 语言 / 日志与隐私。Wave 0 静态占位。 */ +export function SettingsPage() { + return ( +
+

常规

+
+
+
+
下载源策略
+
选择 CLI 安装时的首选下载渠道(Wave 1 生效)
+
+ 官方渠道 +
+
+
+
自动检查更新
+
启动时检查 AgentDock 自身与 CLI 的更新(Wave 4 生效)
+
+ +
+
+
+
语言
+
第一版仅提供简体中文
+
+ 简体中文 +
+
+
+
日志与隐私
+
日志本地保存,敏感值自动脱敏(Wave 1 生效)
+
+ 默认 +
+
+
+
+
+ ); +} diff --git a/apps/desktop/src/styles/global.css b/apps/desktop/src/styles/global.css new file mode 100644 index 0000000..f99911c --- /dev/null +++ b/apps/desktop/src/styles/global.css @@ -0,0 +1,1009 @@ +/* ============================================================ + * AgentDock 全局样式(Wave 0) + * 全部颜色/辉光/动效时长来自 tokens/(视觉规范 v1.2), + * 本文件不出现任何魔法色值;布局尺寸来自规范 §3.0/§6。 + * ============================================================ */ + +/* ---------- 重置与基础 ---------- */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html, +body, +#root { + height: 100%; +} + +body { + font-family: var(--ad-font-ui); + font-size: var(--ad-text-m-size); + line-height: var(--ad-text-m-height); + color: var(--ad-text-1); + background: var(--ad-bg-0); + overflow: hidden; + -webkit-font-smoothing: antialiased; +} + +button { + font-family: inherit; +} + +/* ---------- 背景网格(§3.0:5% + 边缘羽化,低档静态) ---------- */ +.bg-grid { + position: fixed; + inset: 0; + z-index: 0; + pointer-events: none; + background-image: + linear-gradient(var(--ad-grid-color) 1px, transparent 1px), + linear-gradient(90deg, var(--ad-grid-color) 1px, transparent 1px); + background-size: var(--ad-grid-size) var(--ad-grid-size); + -webkit-mask-image: var(--ad-grid-mask); + mask-image: var(--ad-grid-mask); +} + +/* ---------- 应用外壳 ---------- */ +.app-shell { + position: relative; + display: flex; + height: 100%; + z-index: 1; +} + +.app-main { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; +} + +.app-content { + flex: 1; + overflow-y: auto; + width: 100%; + max-width: 1440px; /* §6 宽阔断点锁宽居中 */ + margin: 0 auto; + padding: var(--ad-space-6) var(--ad-space-8); +} + +/* ---------- 侧边栏(§3.0) ---------- */ +.sidebar { + width: 232px; + flex-shrink: 0; + display: flex; + flex-direction: column; + background: var(--ad-bg-1); + border-right: 1px solid var(--ad-border); + padding: var(--ad-space-3) 0 0; +} + +.sidebar-nav { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; + padding: 0 var(--ad-space-2); +} + +/* 导航项:圆角背景块,选中态为「左缘生长光晕 + 内描边」,无独立指示条(v1.2) */ +.nav-item { + display: flex; + align-items: center; + gap: var(--ad-space-3); + width: 100%; + height: 40px; + padding: 0 var(--ad-space-3); + border: none; + border-radius: var(--ad-radius-m); + background: transparent; + color: var(--ad-text-2); + font-size: var(--ad-text-m-size); + cursor: pointer; + user-select: none; + transition: background var(--ad-dur-fast) var(--ad-ease-out), + color var(--ad-dur-fast) var(--ad-ease-out); +} + +.nav-item:hover { + background: var(--ad-bg-3); + color: var(--ad-text-1); +} + +.nav-item.active { + background: var(--ad-nav-selected-bg); + box-shadow: var(--ad-nav-selected-border); + color: var(--ad-primary); +} + +.nav-icon { + flex-shrink: 0; +} + +/* 底部引擎状态区(§3.0:48px + 顶部分隔线) */ +.sidebar-engine { + height: 48px; + flex-shrink: 0; + display: flex; + align-items: center; + gap: var(--ad-space-2); + padding: 0 var(--ad-space-4); + border-top: 1px solid var(--ad-border); + color: var(--ad-text-2); + font-size: var(--ad-text-s-size); + white-space: nowrap; + overflow: hidden; +} + +.engine-version { + margin-left: auto; +} + +/* ---------- 顶栏(§3.0) ---------- */ +.header { + height: 56px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--ad-space-4); + padding: 0 var(--ad-space-8); + background: var(--ad-bg-0); + border-bottom: 1px solid var(--ad-border); +} + +.header-title { + font-size: var(--ad-text-xl-size); + font-weight: var(--ad-text-xl-weight); + white-space: nowrap; +} + +.header-actions { + display: flex; + align-items: center; + gap: var(--ad-space-3); +} + +/* 搜索框(§3.0:限宽 320px,聚焦青边框 + 主辉光) */ +.search-box { + position: relative; + width: 320px; +} + +.search-box input { + width: 100%; + height: 36px; + padding: 0 var(--ad-space-3) 0 36px; + border: 1px solid var(--ad-border); + border-radius: var(--ad-radius-m); + background: var(--ad-bg-1); + color: var(--ad-text-1); + font-size: var(--ad-text-m-size); + outline: none; + transition: border-color var(--ad-dur-micro) var(--ad-ease-out), + box-shadow var(--ad-dur-micro) var(--ad-ease-out); +} + +.search-box input::placeholder { + color: var(--ad-text-3); +} + +.search-box input:focus { + border-color: var(--ad-primary); + box-shadow: var(--ad-glow-primary); +} + +.search-icon { + position: absolute; + left: var(--ad-space-3); + top: 50%; + transform: translateY(-50%); + color: var(--ad-text-3); + pointer-events: none; +} + +/* 环境警告 chip(§3.0:黄描边 + 黄点 + chevron,悬浮带辉光) */ +.env-chip { + display: inline-flex; + align-items: center; + gap: var(--ad-space-2); + height: 28px; + padding: 0 var(--ad-space-3); + border: 1px solid var(--ad-warning-chip-border); + border-radius: var(--ad-radius-s); + background: var(--ad-warning-dim); + color: var(--ad-warning); + font-size: var(--ad-text-s-size); + cursor: pointer; + transition: border-color var(--ad-dur-fast) var(--ad-ease-out), + box-shadow var(--ad-dur-fast) var(--ad-ease-out); +} + +.env-chip:hover { + border-color: var(--ad-warning); + box-shadow: var(--ad-glow-warning); +} + +/* ---------- 按钮 ---------- */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--ad-space-2); + height: 36px; + padding: 0 var(--ad-space-4); + border: 1px solid transparent; + border-radius: var(--ad-radius-s); + font-size: var(--ad-text-m-size); + font-weight: 500; + cursor: pointer; + transition: filter var(--ad-dur-fast) var(--ad-ease-out), + box-shadow var(--ad-dur-fast) var(--ad-ease-out), + border-color var(--ad-dur-fast) var(--ad-ease-out); +} + +.btn:disabled { + opacity: 0.55; + cursor: not-allowed; +} + +/* 主按钮(§2.6 v1.2:内渐变 + 内高光 + 常驻弱辉光,hover 双层扩散辉光) */ +.btn-primary { + background: var(--ad-btn-primary-bg); + box-shadow: var(--ad-btn-primary-inner), var(--ad-btn-primary-glow); + color: var(--ad-btn-primary-text); +} + +.btn-primary:hover:not(:disabled) { + filter: brightness(1.08); + box-shadow: var(--ad-btn-primary-inner), var(--ad-btn-primary-glow-hover); +} + +.btn-primary:active:not(:disabled) { + filter: brightness(0.94); + box-shadow: var(--ad-btn-primary-inner); +} + +/* 次按钮(§3.6) */ +.btn-secondary { + background: transparent; + border-color: var(--ad-border-bright); + color: var(--ad-text-1); +} + +.btn-secondary:hover:not(:disabled) { + border-color: var(--ad-primary); +} + +/* 链接式按钮 */ +.link-btn { + display: inline-flex; + align-items: center; + gap: var(--ad-space-1); + border: none; + background: none; + color: var(--ad-primary); + font-size: var(--ad-text-s-size); + cursor: pointer; + transition: color var(--ad-dur-fast) var(--ad-ease-out); +} + +.link-btn:hover { + color: var(--ad-primary-hover); +} + +.link-btn svg { + transition: transform var(--ad-dur-fast) var(--ad-ease-out); +} + +.link-btn:hover svg { + transform: translateX(2px); +} + +/* ---------- 通用小组件 ---------- */ +/* Mono chip(§2.2) */ +.mono-chip { + display: inline-block; + font-family: var(--ad-mono-chip-font); + font-size: var(--ad-mono-chip-size); + line-height: 1.5; + background: var(--ad-mono-chip-bg); + border: var(--ad-mono-chip-border); + border-radius: var(--ad-mono-chip-radius); + padding: var(--ad-mono-chip-padding); + color: var(--ad-mono-chip-color); + white-space: nowrap; +} + +/* CLI 单色字母头像(§3.1.4 / §7) */ +.monogram { + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + border: 1px solid var(--ad-border); + border-radius: var(--ad-radius-m); + background: var(--ad-bg-2); + color: var(--ad-text-2); + font-weight: 600; + letter-spacing: 0.02em; +} + +/* 状态角标(§3.1.4 / §3.2) */ +.badge { + display: inline-flex; + align-items: center; + height: 22px; + padding: 0 var(--ad-space-2); + border: 1px solid transparent; + border-radius: var(--ad-radius-s); + font-size: var(--ad-text-xs-size); + white-space: nowrap; +} + +.badge-installed { + color: var(--ad-primary); + border-color: var(--ad-primary-border); + background: var(--ad-primary-dim); +} + +.badge-update { + color: var(--ad-accent); + border-color: var(--ad-accent-border); + background: var(--ad-accent-dim); + box-shadow: var(--ad-glow-accent); +} + +.badge-auth { + color: var(--ad-attention); + border-color: var(--ad-attention-border); + background: var(--ad-attention-dim); +} + +.badge-uninstalled { + color: var(--ad-text-3); + border-color: var(--ad-border); + background: var(--ad-bg-1); +} + +/* 状态点(§4.5:绿静态;警告/错误可呼吸;紫/灰静态) */ +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; +} + +.status-dot.ok { + background: var(--ad-success); +} + +.status-dot.warn { + background: var(--ad-warning); +} + +.status-dot.error { + background: var(--ad-danger); +} + +.status-dot.unknown { + background: var(--ad-text-3); +} + +.status-dot.breathe { + animation: ad-breathe var(--ad-breathe-duration) var(--ad-ease-inout) infinite; +} + +/* ---------- 面板 ---------- */ +.panel { + background: var(--ad-bg-1); + border: 1px solid var(--ad-border); + border-radius: var(--ad-radius-l); + padding: var(--ad-space-5); +} + +.panel-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--ad-space-4); + margin-bottom: var(--ad-space-4); +} + +.panel-title { + font-size: var(--ad-text-l-size); + font-weight: var(--ad-text-l-weight); +} + +.panel-hint { + color: var(--ad-text-2); + font-size: var(--ad-text-xs-size); +} + +.panel-empty { + padding: var(--ad-space-5); + color: var(--ad-text-2); + font-size: var(--ad-text-s-size); +} + +/* ---------- 总览:KPI 卡(§3.1.1) ---------- */ +.kpi-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: var(--ad-space-4); + margin-bottom: var(--ad-space-6); +} + +.kpi-card { + position: relative; + overflow: hidden; + padding: var(--ad-space-4) var(--ad-space-5) var(--ad-space-5); + border-radius: var(--ad-radius-l); + background: var(--ad-surface-card); + box-shadow: var(--ad-card-highlight), var(--ad-shadow-panel); + transition: transform var(--ad-dur-fast) var(--ad-ease-out); +} + +.kpi-card:hover { + transform: translateY(-2px); +} + +.kpi-bar { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; +} + +.kpi-value { + font-size: var(--ad-text-num-size); + line-height: var(--ad-text-num-height); + font-weight: var(--ad-text-num-weight); +} + +.kpi-label { + margin-top: 2px; + color: var(--ad-text-2); + font-size: var(--ad-text-s-size); +} + +.kpi-subtitle { + margin-top: var(--ad-space-2); + color: var(--ad-text-2); + font-size: var(--ad-text-xs-size); +} + +/* ---------- 总览:中段(诊断 + 快速操作) ---------- */ +.overview-mid { + display: grid; + grid-template-columns: 2fr 1fr; + gap: var(--ad-space-4); + margin-bottom: var(--ad-space-6); +} + +.diag-row { + display: flex; + align-items: center; + gap: var(--ad-space-3); + min-height: 48px; + padding: 0 var(--ad-space-2); + border-bottom: 1px solid var(--ad-border); +} + +.diag-row:last-child { + border-bottom: none; +} + +.diag-cli { + color: var(--ad-text-1); + font-size: var(--ad-text-m-size); + white-space: nowrap; +} + +.diag-text { + color: var(--ad-text-2); + font-size: var(--ad-text-s-size); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.diag-time { + margin-left: auto; + color: var(--ad-text-2); + font-size: var(--ad-text-xs-size); + white-space: nowrap; +} + +.quick-card { + display: flex; + flex-direction: column; + gap: var(--ad-space-3); +} + +.quick-card .btn { + width: 100%; +} + +.recent-installs { + margin-top: var(--ad-space-2); +} + +.group-label { + color: var(--ad-text-3); + font-size: var(--ad-text-xs-size); +} + +.group-empty { + padding: var(--ad-space-2) 0; + color: var(--ad-text-2); + font-size: var(--ad-text-s-size); +} + +.safety-note { + display: flex; + align-items: center; + gap: var(--ad-space-2); + margin-top: auto; + padding-top: var(--ad-space-3); + border-top: 1px solid var(--ad-border); + color: var(--ad-text-2); + font-size: var(--ad-text-xs-size); +} + +/* ---------- 总览:CLI 状态网格(§3.1.4) ---------- */ +.cli-status-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: var(--ad-space-4); +} + +.cli-block { + display: flex; + flex-direction: column; + gap: var(--ad-space-2); + padding: var(--ad-space-4); + background: var(--ad-bg-1); + border: 1px solid var(--ad-border); + border-radius: var(--ad-radius-m); + transition: border-color var(--ad-dur-fast) var(--ad-ease-out); +} + +.cli-block:hover { + border-color: var(--ad-border-bright); +} + +.cli-block-top { + display: flex; + align-items: center; + gap: var(--ad-space-3); +} + +.cli-block-name { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: var(--ad-text-m-size); + color: var(--ad-text-1); +} + +.cli-block-version .mono-chip { + color: var(--ad-text-3); +} + +.cli-block-auth { + display: flex; + align-items: center; + gap: var(--ad-space-2); + color: var(--ad-text-2); + font-size: var(--ad-text-xs-size); +} + +/* ---------- 总览:本机环境 ---------- */ +.env-panel { + margin-top: var(--ad-space-6); +} + +.env-grid { + display: grid; + grid-template-columns: 1.1fr 1fr; + gap: var(--ad-space-6); +} + +.env-basic { + display: flex; + flex-direction: column; + gap: var(--ad-space-3); +} + +.env-row { + display: flex; + align-items: baseline; + gap: var(--ad-space-3); +} + +.env-key { + flex-shrink: 0; + width: 64px; + color: var(--ad-text-2); + font-size: var(--ad-text-s-size); +} + +.env-val { + color: var(--ad-text-1); + font-size: var(--ad-text-m-size); + word-break: break-all; +} + +.runtime-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: var(--ad-space-3); + align-content: start; +} + +.runtime-item { + display: flex; + align-items: center; + gap: var(--ad-space-2); +} + +.runtime-name { + flex: 1; + color: var(--ad-text-2); + font-size: var(--ad-text-s-size); +} + +.runtime-missing { + color: var(--ad-warning); + font-size: var(--ad-text-xs-size); +} + +.path-block { + margin-top: var(--ad-space-5); + padding-top: var(--ad-space-4); + border-top: 1px solid var(--ad-border); +} + +.path-head { + display: flex; + align-items: center; + gap: var(--ad-space-3); + margin-bottom: var(--ad-space-2); +} + +.path-title { + display: flex; + align-items: center; + gap: var(--ad-space-2); + color: var(--ad-text-2); + font-size: var(--ad-text-s-size); +} + +.path-entry { + padding: var(--ad-space-1) 0; + color: var(--ad-text-2); + font-size: var(--ad-text-xs-size); + font-family: var(--ad-font-mono); + word-break: break-all; +} + +/* ---------- CLI 目录(§3.2) ---------- */ +.catalog-toolbar { + display: flex; + align-items: center; + gap: var(--ad-space-4); + flex-wrap: wrap; + margin-bottom: var(--ad-space-5); +} + +.filter-group { + display: flex; + align-items: center; + gap: var(--ad-space-2); +} + +.pill { + height: 32px; + padding: 0 var(--ad-space-3); + border: 1px solid var(--ad-border); + border-radius: 16px; + background: transparent; + color: var(--ad-text-2); + font-size: var(--ad-text-s-size); + cursor: pointer; + transition: border-color var(--ad-dur-fast) var(--ad-ease-out), + color var(--ad-dur-fast) var(--ad-ease-out); +} + +.pill:hover { + border-color: var(--ad-border-bright); + color: var(--ad-text-1); +} + +.pill.active { + border-color: var(--ad-primary); + color: var(--ad-primary); + background: var(--ad-primary-dim); +} + +.catalog-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: var(--ad-space-4); +} + +.catalog-card { + display: flex; + flex-direction: column; + gap: var(--ad-space-3); + padding: var(--ad-space-5); + background: var(--ad-bg-1); + border: 1px solid var(--ad-border); + border-radius: var(--ad-radius-m); + transition: border-color var(--ad-dur-fast) var(--ad-ease-out), + box-shadow var(--ad-dur-fast) var(--ad-ease-out); +} + +.catalog-card:hover { + border-color: var(--ad-primary); + box-shadow: var(--ad-glow-primary); +} + +.catalog-card-top { + display: flex; + align-items: center; + gap: var(--ad-space-3); +} + +.catalog-card-head { + min-width: 0; +} + +.catalog-card-name { + font-size: var(--ad-text-l-size); + font-weight: var(--ad-text-l-weight); + color: var(--ad-text-1); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.catalog-card-vendor { + color: var(--ad-text-2); + font-size: var(--ad-text-s-size); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.catalog-card-desc { + color: var(--ad-text-2); + font-size: var(--ad-text-s-size); + line-height: 1.5; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.catalog-card-bottom { + display: flex; + align-items: center; + gap: var(--ad-space-3); + margin-top: auto; +} + +.catalog-platforms { + margin-left: auto; + color: var(--ad-text-3); + font-size: var(--ad-text-xs-size); + white-space: nowrap; +} + +.catalog-empty { + grid-column: 1 / -1; +} + +/* ---------- 占位页(Wave 0 页面) ---------- */ +.placeholder-page { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--ad-space-4); + min-height: 60vh; + text-align: center; +} + +.placeholder-icon { + color: var(--ad-text-3); +} + +.placeholder-title { + font-size: var(--ad-text-xl-size); + font-weight: var(--ad-text-xl-weight); +} + +.placeholder-desc { + max-width: 480px; + color: var(--ad-text-2); + font-size: var(--ad-text-m-size); +} + +/* ---------- 设置页 ---------- */ +.settings-page { + max-width: 720px; +} + +.settings-group { + margin-top: var(--ad-space-4); + background: var(--ad-bg-1); + border: 1px solid var(--ad-border); + border-radius: var(--ad-radius-l); +} + +.setting-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--ad-space-4); + padding: var(--ad-space-4) var(--ad-space-5); + border-bottom: 1px solid var(--ad-border); +} + +.setting-row:last-child { + border-bottom: none; +} + +.setting-label { + color: var(--ad-text-1); + font-size: var(--ad-text-m-size); +} + +.setting-hint { + margin-top: 2px; + color: var(--ad-text-2); + font-size: var(--ad-text-xs-size); +} + +.setting-value { + flex-shrink: 0; + color: var(--ad-text-2); + font-size: var(--ad-text-s-size); +} + +.settings-page-foot { + display: flex; + align-items: center; + gap: var(--ad-space-2); + margin-top: var(--ad-space-5); + color: var(--ad-text-2); + font-size: var(--ad-text-xs-size); +} + +/* ---------- 页面切换动效(§4.2) ---------- */ +.page-enter { + animation: page-in var(--ad-dur-med) var(--ad-ease-out); +} + +@keyframes page-in { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: none; + } +} + +/* ---------- 响应式(§6 三断点) ---------- */ +/* 紧凑 960–1199px:导航收起 64px,KPI 2×2,中段上下堆叠,状态网格 2 列 */ +@media (max-width: 1199px) { + .sidebar { + width: 64px; + padding: var(--ad-space-3) 0 0; + } + + .sidebar-nav { + padding: 0 var(--ad-space-2); + } + + .nav-item { + justify-content: center; + padding: 0; + } + + .nav-label { + display: none; + } + + .sidebar-engine { + justify-content: center; + padding: 0; + } + + .engine-text, + .engine-version { + display: none; + } + + .kpi-grid { + grid-template-columns: repeat(2, 1fr); + } + + .overview-mid { + grid-template-columns: 1fr; + } + + .cli-status-grid { + grid-template-columns: repeat(2, 1fr); + } + + .env-grid { + grid-template-columns: 1fr; + } +} + +/* 紧凑断点搜索框收缩为 36px 图标按钮 */ +@media (max-width: 1199px) { + .search-box { + width: 36px; + } + + .search-box input { + padding: 0; + border-color: transparent; + background: transparent; + cursor: pointer; + } + + .search-box input::placeholder { + color: transparent; + } + + .search-box:focus-within { + width: 240px; + } + + .search-box:focus-within input { + padding: 0 var(--ad-space-3) 0 36px; + border-color: var(--ad-border); + background: var(--ad-bg-1); + } + + .search-icon { + left: 10px; + } +} + +/* 标准 1200–1599px:默认样式(完整导航、KPI 一排 4、诊断+快速 2:1、状态网格 3 列) */ +/* 宽阔 ≥1600px:内容锁 1440 居中,状态网格 4 列 */ +@media (min-width: 1600px) { + .cli-status-grid { + grid-template-columns: repeat(4, 1fr); + } +} + +/* 窗口高度 < 700px:顶栏 44px,KPI 大数字缩小 */ +@media (max-height: 699px) { + .header { + height: 44px; + } + + .kpi-value { + font-size: 22px; + line-height: 28px; + } +} + +/* 低档/减弱动效下呼吸灯停用(tiers.css 已兜底 animation,此处补充过渡抑制) */ +[data-fx-tier="low"] .kpi-card:hover { + transform: none; +} diff --git a/apps/desktop/src/tokens/color.css b/apps/desktop/src/tokens/color.css new file mode 100644 index 0000000..411a201 --- /dev/null +++ b/apps/desktop/src/tokens/color.css @@ -0,0 +1,57 @@ +/* ============================================================ + * 设计 Token · 配色(color) + * 来源:视觉规范 v1.2 §2.1(施工唯一品味依据) + * 禁止在此文件之外出现任何魔法色值;改动需先征得总管同意。 + * ============================================================ */ +:root { + /* ---- 底色(深空阶梯,由深到浅)---- */ + --ad-bg-0: #070B14; /* 应用窗口底色 */ + --ad-bg-1: #0D1420; /* 面板、卡片底色 */ + --ad-bg-2: #131C2E; /* 浮层、弹窗、下拉、mono chip 底 */ + --ad-bg-3: #1A2438; /* 悬浮态、按下态的填充 */ + --ad-border: #22304A; /* 常规 1px 分隔线 */ + --ad-border-bright: #35507A; /* 悬浮/选中的边框 */ + + /* ---- 霓虹强调色(全系统只有这两个霓虹色)---- */ + --ad-primary: #00E5FF; /* 主操作、链接、聚焦、选中态(青) */ + --ad-primary-hover: #4DEFFF; /* 主操作悬浮 */ + --ad-primary-pressed: #00B4CC; /* 主操作按下 */ + --ad-primary-dim: rgba(0, 229, 255, 0.12); /* 选中项底、标签底 */ + --ad-accent: #FF4DDB; /* 可更新标记、重要提醒(品红,克制使用) */ + --ad-accent-dim: rgba(255, 77, 219, 0.12); /* 品红标签底 */ + + /* ---- 状态色(诊断四级 + 通用语义,六色语义表 v1.2 定稿)---- */ + --ad-success: #2EE6A8; /* 正常 / 已安装 / 已授权 / 已自动修复 */ + --ad-attention: #A78BFA; /* 需要留意:提示级 + 待授权 / 未授权(紫) */ + --ad-warning: #FFB020; /* 警告 / 需要你注意的异常 */ + --ad-danger: #FF4D5E; /* 错误 / 需要你处理 / 高危操作 */ + --ad-success-dim: rgba(46, 230, 168, 0.12); /* 状态色 12% 透明底色 */ + --ad-attention-dim: rgba(167, 139, 250, 0.12); /* 状态色 12% 透明底色 */ + --ad-warning-dim: rgba(255, 176, 32, 0.12); /* 状态色 12% 透明底色 */ + --ad-danger-dim: rgba(255, 77, 94, 0.12); /* 状态色 12% 透明底色 */ + + /* ---- 文字 ---- */ + --ad-text-1: #E8EEF7; /* 主文案 */ + --ad-text-2: #9AA8BD; /* 次要说明(承载信息的最低允许色,对比度 ≥ 4.5:1) */ + --ad-text-3: #5E6E87; /* 仅占位、禁用、纯装饰(禁止承载信息) */ + + /* ---- 侧边栏选中态(v1.2 §3.0:左缘生长光晕,无独立指示条)---- */ + --ad-nav-selected-bg: linear-gradient( + 90deg, + rgba(0, 229, 255, 0.30), + rgba(0, 229, 255, 0.12) 45%, + rgba(0, 229, 255, 0.05) + ); + --ad-nav-selected-border: inset 0 0 0 1px rgba(0, 229, 255, 0.38); + + /* ---- 组件表面(§3.0 / §3.1 明示值)---- */ + --ad-surface-card: radial-gradient(120% 100% at 50% 0%, #16233A 0%, #0D1420 60%); /* KPI 卡悬浮玻璃板 */ + --ad-card-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.06); /* 卡片上缘高光 */ + --ad-warning-chip-border: rgba(255, 176, 32, 0.5); /* 环境警告 chip 描边 */ + + /* ---- 状态角标描边(§3.1.4 / §3.2 明示值)---- */ + --ad-primary-border: rgba(0, 229, 255, 0.4); + --ad-accent-border: rgba(255, 77, 219, 0.5); + --ad-attention-border: rgba(167, 139, 250, 0.5); + --ad-success-border: rgba(46, 230, 168, 0.4); +} diff --git a/apps/desktop/src/tokens/effects.css b/apps/desktop/src/tokens/effects.css new file mode 100644 index 0000000..e1195d0 --- /dev/null +++ b/apps/desktop/src/tokens/effects.css @@ -0,0 +1,39 @@ +/* ============================================================ + * 设计 Token · 阴影与辉光(effects) + * 来源:视觉规范 v1.2 §2.4 / §2.6 / §3.0 + * 辉光只给:主按钮、选中导航项、聚焦输入框、需处理状态指示灯、 + * 可更新角标、环境警告 chip。列表每行都发光 = 全部不发光,禁止。 + * ============================================================ */ +:root { + /* ---- 阴影 ---- */ + --ad-shadow-panel: 0 8px 24px rgba(0, 0, 0, 0.45); + --ad-shadow-pop: 0 12px 40px rgba(0, 0, 0, 0.6); + + /* ---- 辉光 ---- */ + --ad-glow-primary: 0 0 12px rgba(0, 229, 255, 0.35), 0 0 32px rgba(0, 229, 255, 0.12); + --ad-glow-accent: 0 0 12px rgba(255, 77, 219, 0.35); + --ad-glow-warning: 0 0 10px rgba(255, 176, 32, 0.28); /* 仅警告 chip / 警告状态灯 */ + --ad-glow-danger: 0 0 12px rgba(255, 77, 94, 0.35); + + /* ---- 主按钮(§2.6,v1.2 hover 双层扩散辉光)---- */ + --ad-btn-primary-bg: linear-gradient(180deg, #33D9EE 0%, #06BCD9 55%, #049EBB 100%); + --ad-btn-primary-text: #03252E; + --ad-btn-primary-inner: inset 0 1px 0 rgba(255, 255, 255, 0.30), + inset 0 -1px 0 rgba(2, 20, 26, 0.35); + --ad-btn-primary-glow: 0 0 10px rgba(0, 229, 255, 0.22); + --ad-btn-primary-glow-hover: 0 0 14px rgba(0, 229, 255, 0.30), + 0 0 36px rgba(0, 229, 255, 0.12); + + /* 品红更新按钮(全应用唯一允许品红做按钮的场景) */ + --ad-btn-accent-bg: linear-gradient(180deg, #F26AD9 0%, #D633B8 55%, #B5209C 100%); + --ad-btn-accent-glow-hover: 0 0 14px rgba(255, 77, 219, 0.30), + 0 0 36px rgba(255, 77, 219, 0.12); + + /* ---- 背景网格(§3.0:要么可见要么不做)---- */ + --ad-grid-color: rgba(0, 229, 255, 0.05); /* 透明度固定 5%,施工员不许再调 */ + --ad-grid-size: 40px; + --ad-grid-mask: radial-gradient(120% 90% at 50% 40%, #000 30%, transparent 75%); + + /* ---- 弹窗遮罩(§3.6)---- */ + --ad-modal-mask: rgba(4, 7, 13, 0.7); +} diff --git a/apps/desktop/src/tokens/motion.css b/apps/desktop/src/tokens/motion.css new file mode 100644 index 0000000..57559eb --- /dev/null +++ b/apps/desktop/src/tokens/motion.css @@ -0,0 +1,21 @@ +/* ============================================================ + * 设计 Token · 动效(motion) + * 来源:视觉规范 v1.2 §2.5 / §4 + * 通则:只动 transform 与 opacity;辉光可用 box-shadow 过渡但仅限单个聚焦元素。 + * ============================================================ */ +:root { + /* ---- 缓动曲线 ---- */ + --ad-ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* 进场、浮现,主力曲线 */ + --ad-ease-inout: cubic-bezier(0.65, 0, 0.35, 1); /* 展开收起、位移、呼吸 */ + --ad-ease-linear: linear; /* 仅环境循环动画 */ + + /* ---- 时长 ---- */ + --ad-dur-micro: 120ms; /* 按下、勾选 */ + --ad-dur-fast: 200ms; /* 悬浮、淡入淡出 */ + --ad-dur-med: 320ms; /* 面板展开、页面切换 */ + --ad-dur-slow: 600ms; /* 仅入场编排,一次性 */ + + /* ---- 状态点呼吸(§4.5)---- */ + --ad-breathe-duration: 2400ms; + --ad-breathe-min-opacity: 0.4; /* 透明度下限 40%,再低像灭了 */ +} diff --git a/apps/desktop/src/tokens/space.css b/apps/desktop/src/tokens/space.css new file mode 100644 index 0000000..3db43da --- /dev/null +++ b/apps/desktop/src/tokens/space.css @@ -0,0 +1,24 @@ +/* ============================================================ + * 设计 Token · 间距 / 圆角(space) + * 来源:视觉规范 v1.2 §2.3 + * 赛博感靠小圆角 + 发光描边,不做大圆角。 + * ============================================================ */ +:root { + /* ---- 间距(4px 基数)---- */ + --ad-space-1: 4px; + --ad-space-2: 8px; + --ad-space-3: 12px; + --ad-space-4: 16px; + --ad-space-5: 20px; + --ad-space-6: 24px; + --ad-space-8: 32px; + --ad-space-10: 40px; + + /* ---- 圆角 ---- */ + --ad-radius-s: 4px; /* 标签、小按钮、chip */ + --ad-radius-m: 8px; /* 卡片、输入框、弹窗 */ + --ad-radius-l: 12px; /* 主面板 */ + + /* ---- 描边 ---- */ + --ad-border-width: 1px; /* 描边统一 1px;发光描边 = 1px 实色 + 外辉光,不加粗 */ +} diff --git a/apps/desktop/src/tokens/tiers.css b/apps/desktop/src/tokens/tiers.css new file mode 100644 index 0000000..5c23ce5 --- /dev/null +++ b/apps/desktop/src/tokens/tiers.css @@ -0,0 +1,59 @@ +/* ============================================================ + * 设计 Token · 性能档位(tiers) + * 来源:视觉规范 v1.2 §5(性能降级策略)/ 架构 §8.3 + * + * 三档:高 / 中 / 低,默认 balanced(自动降档由 JS 判定后设置 + * data-fx-tier 属性,见 src/main.tsx)。 + * - prefers-reduced-motion 强制 low + * - low:仅 L0 + 必要过渡,无循环动画、无辉光、无切角 + * ============================================================ */ +:root { + --ad-fx-tier: balanced; +} + +@media (prefers-reduced-motion: reduce) { + :root { + --ad-fx-tier: low; + } +} + +/* ---- 低档:移除辉光、循环动画、过渡、切角 ---- */ +[data-fx-tier="low"] *, +[data-fx-tier="low"] *::before, +[data-fx-tier="low"] *::after { + animation: none !important; + transition: none !important; + box-shadow: none !important; +} +[data-fx-tier="low"] .clip-notch { + clip-path: none !important; +} + +/* ---- 中档:只保留单层辉光(半径减半),循环动画停用 ---- */ +[data-fx-tier="balanced"] .fx-multilayer-glow { + box-shadow: 0 0 6px rgba(0, 229, 255, 0.3); +} + +/* 背景网格漂移(§4.8,60s 一周,仅高档/中档) */ +[data-fx-tier="low"] .grid-drift { + animation: none; +} +.grid-drift { + animation: grid-drift 60s linear infinite; +} +@keyframes grid-drift { + from { background-position: 0 0; } + to { background-position: 40px 40px; } +} + +/* 状态点呼吸(§4.5:仅警告/错误/授权可能过期呼吸;低档停用) */ +[data-fx-tier="low"] .status-dot-breathe { + animation: none; +} +.status-dot-breathe { + animation: ad-breathe var(--ad-dur-slow) var(--ad-ease-inout) infinite; +} +@keyframes ad-breathe { + 0%, 100% { opacity: 1; } + 50% { opacity: var(--ad-breathe-min-opacity); } +} diff --git a/apps/desktop/src/tokens/typography.css b/apps/desktop/src/tokens/typography.css new file mode 100644 index 0000000..b322769 --- /dev/null +++ b/apps/desktop/src/tokens/typography.css @@ -0,0 +1,31 @@ +/* ============================================================ + * 设计 Token · 字体(typography) + * 来源:视觉规范 v1.2 §2.2 + * 不加任何外联 Web 字体(离线可用、安装包不膨胀)。 + * ============================================================ */ +:root { + /* ---- 字体栈 ---- */ + --ad-font-ui: "PingFang SC", "Microsoft YaHei UI", "Microsoft YaHei", + "Noto Sans CJK SC", "Source Han Sans SC", system-ui, sans-serif; + --ad-font-mono: "Cascadia Code", "JetBrains Mono", Consolas, + "Noto Sans Mono CJK SC", monospace; + + /* ---- 字阶(字号 / 行高 / 字重)---- */ + --ad-text-xs-size: 12px; --ad-text-xs-height: 18px; --ad-text-xs-weight: 400; + --ad-text-s-size: 13px; --ad-text-s-height: 20px; --ad-text-s-weight: 400; + --ad-text-m-size: 14px; --ad-text-m-height: 22px; --ad-text-m-weight: 400; + --ad-text-l-size: 16px; --ad-text-l-height: 24px; --ad-text-l-weight: 500; + --ad-text-xl-size: 20px; --ad-text-xl-height: 28px; --ad-text-xl-weight: 600; + --ad-text-num-size: 28px; --ad-text-num-height: 36px; --ad-text-num-weight: 600; + + /* ---- Mono chip(§2.2 对比度三件套之二)---- + * 正文/列表中内联出现的文件名、版本号、命令一律套此样式: + * 12px 等宽、--ad-bg-2 底、1px --ad-border、--ad-radius-s、内边距 1px 6px、文字 --ad-text-1 */ + --ad-mono-chip-font: var(--ad-font-mono); + --ad-mono-chip-size: 12px; + --ad-mono-chip-bg: var(--ad-bg-2); + --ad-mono-chip-border: 1px solid var(--ad-border); + --ad-mono-chip-radius: var(--ad-radius-s); + --ad-mono-chip-padding: 1px 6px; + --ad-mono-chip-color: var(--ad-text-1); +} diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json new file mode 100644 index 0000000..ead86d8 --- /dev/null +++ b/apps/desktop/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2021", + "useDefineForClassFields": true, + "lib": ["ES2021", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/apps/desktop/vite.config.ts b/apps/desktop/vite.config.ts new file mode 100644 index 0000000..73d4e47 --- /dev/null +++ b/apps/desktop/vite.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +const host = process.env.TAURI_DEV_HOST; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + + // Tauri 开发约定:固定端口 1420,避免占用冲突 + clearScreen: false, + server: { + port: 1420, + strictPort: true, + host: host || false, + hmr: host + ? { + protocol: "ws", + host, + port: 1421, + } + : undefined, + watch: { + // 不监听 src-tauri,避免 Rust 改动触发前端重载 + ignored: ["**/src-tauri/**"], + }, + }, +}); diff --git a/crates/agentdock-adapter/Cargo.toml b/crates/agentdock-adapter/Cargo.toml new file mode 100644 index 0000000..200e4d3 --- /dev/null +++ b/crates/agentdock-adapter/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "agentdock-adapter" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "适配器 schema 加载、版本校验、dry-run、执行器接口(架构 §3)" + +[dependencies] +serde = { version = "1", features = ["derive"] } +serde_yaml = "0.9" diff --git a/crates/agentdock-adapter/src/catalog.rs b/crates/agentdock-adapter/src/catalog.rs new file mode 100644 index 0000000..5b28db3 --- /dev/null +++ b/crates/agentdock-adapter/src/catalog.rs @@ -0,0 +1,110 @@ +//! 目录索引与占位条目加载(Wave 0) +//! +//! 从 `adapters/catalog.yaml` 读取索引,再逐个读取 `tools/*.yaml`, +//! 返回目录条目。仅消费 `id / name / name_zh / vendor / status` 五个字段。 + +use std::fs; +use std::path::Path; + +use serde::{Deserialize, Serialize}; + +use crate::error::AdapterError; + +/// 目录索引(adapters/catalog.yaml) +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +pub struct Catalog { + #[serde(rename = "catalog_version")] + pub version: u32, + pub tools: Vec, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +pub struct CatalogRef { + pub id: String, + pub file: String, +} + +/// 目录条目(占位 schema,Wave 0 仅五字段;完整字段见架构 §3.1) +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +pub struct CatalogEntry { + pub id: String, + pub name: String, + #[serde(rename = "name_zh")] + pub name_zh: String, + pub vendor: String, + /// available | watch + pub status: String, +} + +/// 加载目录索引与全部工具占位 YAML +pub fn load_catalog>(adapters_dir: P) -> Result, AdapterError> { + let dir = adapters_dir.as_ref(); + let catalog_text = fs::read_to_string(dir.join("catalog.yaml")) + .map_err(|e| AdapterError::Io(format!("读取 catalog.yaml 失败: {e}")))?; + let catalog: Catalog = serde_yaml::from_str(&catalog_text) + .map_err(|e| AdapterError::Parse(format!("解析 catalog.yaml 失败: {e}")))?; + + let mut entries = Vec::with_capacity(catalog.tools.len()); + for r in &catalog.tools { + let text = fs::read_to_string(dir.join(&r.file)) + .map_err(|e| AdapterError::Io(format!("读取 {} 失败: {e}", r.file)))?; + let entry: CatalogEntry = serde_yaml::from_str(&text) + .map_err(|e| AdapterError::Parse(format!("解析 {} 失败: {e}", r.file)))?; + if entry.id != r.id { + return Err(AdapterError::Parse(format!( + "索引 id 与文件内 id 不一致: 索引={} 文件={}", + r.id, entry.id + ))); + } + if entry.status != "available" && entry.status != "watch" { + return Err(AdapterError::Parse(format!( + "{} 的 status 非法: {}(应为 available | watch)", + entry.id, entry.status + ))); + } + entries.push(entry); + } + Ok(entries) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_minimal_tool_yaml() { + let yaml = "id: codex\nname: Codex CLI\nname_zh: Codex CLI\nvendor: OpenAI\nstatus: available\n"; + let entry: CatalogEntry = serde_yaml::from_str(yaml).expect("应能解析"); + assert_eq!(entry.id, "codex"); + assert_eq!(entry.name_zh, "Codex CLI"); + assert_eq!(entry.vendor, "OpenAI"); + assert_eq!(entry.status, "available"); + } + + #[test] + fn rejects_invalid_status() { + let yaml = "id: x\nname: X\nname_zh: X\nvendor: V\nstatus: unknown\n"; + let entry: Result = serde_yaml::from_str(yaml); + // 解析本身成功,非法 status 由 load_catalog 校验;此处确认 schema 字段可读 + assert!(entry.is_ok()); + } + + #[test] + fn loads_real_catalog_from_repo() { + // 以真实 adapters/ 目录做集成测试(相对本 crate 位于 ../../adapters) + let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../adapters"); + let entries = load_catalog(&dir).expect("真实目录应可加载"); + assert_eq!(entries.len(), 14, "第一批应为 14 个工具"); + for e in &entries { + assert!(!e.id.is_empty()); + assert!(!e.name.is_empty()); + assert!(!e.name_zh.is_empty()); + assert!(!e.vendor.is_empty()); + } + let ids: Vec<&str> = entries.iter().map(|e| e.id.as_str()).collect(); + for want in ["codex", "claude-code", "gemini", "copilot", "kimi", "qwen", "codebuddy", + "opencode", "crush", "goose", "aider", "cursor", "cline", "warp"] { + assert!(ids.contains(&want), "目录应包含 {want}"); + } + } +} diff --git a/crates/agentdock-adapter/src/error.rs b/crates/agentdock-adapter/src/error.rs new file mode 100644 index 0000000..1e6c70d --- /dev/null +++ b/crates/agentdock-adapter/src/error.rs @@ -0,0 +1,20 @@ +//! 适配器层错误类型 + +use std::fmt; + +#[derive(Debug)] +pub enum AdapterError { + Io(String), + Parse(String), +} + +impl fmt::Display for AdapterError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AdapterError::Io(m) => write!(f, "IO: {m}"), + AdapterError::Parse(m) => write!(f, "Parse: {m}"), + } + } +} + +impl std::error::Error for AdapterError {} diff --git a/crates/agentdock-adapter/src/lib.rs b/crates/agentdock-adapter/src/lib.rs new file mode 100644 index 0000000..133a4d7 --- /dev/null +++ b/crates/agentdock-adapter/src/lib.rs @@ -0,0 +1,11 @@ +//! agentdock-adapter —— 适配器层 +//! +//! 负责适配器 schema 加载、版本校验、dry-run 与执行器接口(架构 §3)。 +//! Wave 0:仅落地目录索引与占位条目加载(`catalog` 模块); +//! 完整 schema 校验、执行器接口随 Wave 1 实现。 + +pub mod catalog; +pub mod error; + +pub use catalog::{Catalog, CatalogEntry, CatalogRef, load_catalog}; +pub use error::AdapterError; diff --git a/crates/agentdock-config/Cargo.toml b/crates/agentdock-config/Cargo.toml new file mode 100644 index 0000000..38e5db8 --- /dev/null +++ b/crates/agentdock-config/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "agentdock-config" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] diff --git a/crates/agentdock-config/src/lib.rs b/crates/agentdock-config/src/lib.rs new file mode 100644 index 0000000..748d003 --- /dev/null +++ b/crates/agentdock-config/src/lib.rs @@ -0,0 +1,18 @@ +//! agentdock-config —— 配置读写层(架构 §6) +//! +//! 职责:原子写入 + 自动备份、多格式统一编解码(ConfigCodec trait: +//! toml / json / jsonc / yaml / env / crushrc)。 +//! Wave 0:空骨架,随 Wave 1/2 落地。 + +/// 配置层能力标记(占位) +pub const LAYER: &str = "agentdock-config"; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn layer_identity() { + assert_eq!(LAYER, "agentdock-config"); + } +} diff --git a/crates/agentdock-core/Cargo.toml b/crates/agentdock-core/Cargo.toml new file mode 100644 index 0000000..01e638a --- /dev/null +++ b/crates/agentdock-core/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "agentdock-core" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] diff --git a/crates/agentdock-core/src/lib.rs b/crates/agentdock-core/src/lib.rs new file mode 100644 index 0000000..c587834 --- /dev/null +++ b/crates/agentdock-core/src/lib.rs @@ -0,0 +1,17 @@ +//! agentdock-core —— 编排层(架构 §2 模块关系:UI --invoke--> commands --→ core orchestrator) +//! +//! 职责:发现 / 安装 / 配置 / 授权 / 诊断 流水线编排。 +//! Wave 0:空骨架,流水线自 Wave 1 起逐波落地。 + +/// 编排层能力标记(占位) +pub const LAYER: &str = "agentdock-core"; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn layer_identity() { + assert_eq!(LAYER, "agentdock-core"); + } +} diff --git a/crates/agentdock-diag/Cargo.toml b/crates/agentdock-diag/Cargo.toml new file mode 100644 index 0000000..4268a84 --- /dev/null +++ b/crates/agentdock-diag/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "agentdock-diag" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] diff --git a/crates/agentdock-diag/src/lib.rs b/crates/agentdock-diag/src/lib.rs new file mode 100644 index 0000000..ab29470 --- /dev/null +++ b/crates/agentdock-diag/src/lib.rs @@ -0,0 +1,17 @@ +//! agentdock-diag —— 诊断规则引擎(架构 §10) +//! +//! 职责:PATH / 依赖缺失 / 版本冲突 / 配置损坏 四类检查的规则引擎。 +//! Wave 0:空骨架,随 Wave 2 落地。 + +/// 诊断层能力标记(占位) +pub const LAYER: &str = "agentdock-diag"; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn layer_identity() { + assert_eq!(LAYER, "agentdock-diag"); + } +} diff --git a/crates/agentdock-exec/Cargo.toml b/crates/agentdock-exec/Cargo.toml new file mode 100644 index 0000000..9d76486 --- /dev/null +++ b/crates/agentdock-exec/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "agentdock-exec" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] diff --git a/crates/agentdock-exec/src/lib.rs b/crates/agentdock-exec/src/lib.rs new file mode 100644 index 0000000..cc3b925 --- /dev/null +++ b/crates/agentdock-exec/src/lib.rs @@ -0,0 +1,17 @@ +//! agentdock-exec —— 安全进程执行层(架构 §3.2 / §4.2) +//! +//! 职责:仅执行适配器声明的 argv,禁止 shell 拼接、管道、重定向; +//! 参数白名单校验。Wave 0:空骨架,随 Wave 1 落地。 + +/// 执行层能力标记(占位) +pub const LAYER: &str = "agentdock-exec"; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn layer_identity() { + assert_eq!(LAYER, "agentdock-exec"); + } +} diff --git a/crates/agentdock-platform/Cargo.toml b/crates/agentdock-platform/Cargo.toml new file mode 100644 index 0000000..5bf1d50 --- /dev/null +++ b/crates/agentdock-platform/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "agentdock-platform" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Win/Linux 环境检测、PATH、发行版识别(架构 §5)" + +[dependencies] +serde = { version = "1", features = ["derive"] } + +[target.'cfg(windows)'.dependencies] +winreg = "0.52" diff --git a/crates/agentdock-platform/src/detect.rs b/crates/agentdock-platform/src/detect.rs new file mode 100644 index 0000000..43b922f --- /dev/null +++ b/crates/agentdock-platform/src/detect.rs @@ -0,0 +1,315 @@ +//! 环境检测实现(架构 §5) +//! +//! 只做「探测」不执行用户命令:调用系统 `--version` 类只读命令并解析输出; +//! 进程执行统一走 `std::process::Command`,不做 shell 拼接。 + +use std::path::{Path, PathBuf}; +use std::process::Command; + +use crate::model::{Capabilities, PlatformEnv, RuntimeInfo, Runtimes, Shells}; + +/// 全量环境检测(Windows / Linux 双平台) +pub fn detect_env() -> PlatformEnv { + #[cfg(windows)] + let (os, os_version) = (String::from("windows"), windows_os_version()); + #[cfg(not(windows))] + let (os, os_version) = (String::from("linux"), linux_os_version()); + + PlatformEnv { + os, + os_version, + arch: std::env::consts::ARCH.to_string(), + shells: detect_shells(), + runtimes: detect_runtimes(), + path_entries: path_entries(), + capabilities: detect_capabilities(), + } +} + +/// 从命令输出中提取首个版本号(如 "v24.18.0" -> "24.18.0")。 +/// 规则:取第一段以数字开头、由数字与点组成的子串,尾部点剔除。 +pub fn extract_version(output: &str) -> Option { + let bytes = output.as_bytes(); + let n = bytes.len(); + let mut i = 0; + while i < n { + if bytes[i].is_ascii_digit() { + let mut j = i; + while j < n && (bytes[j].is_ascii_digit() || bytes[j] == b'.') { + j += 1; + } + let s = output[i..j].trim_end_matches('.'); + if !s.is_empty() { + return Some(s.to_string()); + } + i = j; + } else { + i += 1; + } + } + None +} + +/// 按平台分隔符拆分 PATH +fn path_entries() -> Vec { + std::env::var("PATH") + .unwrap_or_default() + .split(path_list_sep()) + .filter(|s| !s.is_empty()) + .map(|s| s.to_string()) + .collect() +} + +#[cfg(windows)] +fn path_list_sep() -> char { + ';' +} + +#[cfg(not(windows))] +fn path_list_sep() -> char { + ':' +} + +/// 在 PATH 中解析可执行文件。 +/// Windows 优先 .exe/.cmd/.bat(CreateProcess 无法直接执行无扩展名文件, +/// 例如 nodejs 目录下的 npm bash 脚本),最后才回退无扩展名。 +fn resolve_program(program: &str) -> Option { + let direct = Path::new(program); + if direct.is_absolute() && direct.is_file() { + return Some(direct.to_path_buf()); + } + let exts: &[&str] = if cfg!(windows) { + &[".exe", ".cmd", ".bat", ""] + } else { + &[""] + }; + for dir in path_entries() { + for ext in exts { + let cand = Path::new(&dir).join(format!("{program}{ext}")); + if cand.is_file() { + return Some(cand); + } + } + } + None +} + +/// 探测一个可执行文件:返回状态 / 版本 / 路径。 +/// 结果分类:installed / not_in_path / exec_failed / version_unparseable。 +fn probe(program: &str, args: &[&str]) -> Option { + let path = resolve_program(program); + let path_str = path.as_ref().map(|p| p.to_string_lossy().to_string()); + + let Some(exe) = path else { + return Some(RuntimeInfo { + status: "not_in_path".into(), + version: None, + path: None, + }); + }; + + let mut cmd = Command::new(&exe); + cmd.args(args); + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + // CREATE_NO_WINDOW:探测命令不闪黑框 + cmd.creation_flags(0x0800_0000); + } + + let output = match cmd.output() { + Ok(o) => o, + Err(_) => { + return Some(RuntimeInfo { + status: "exec_failed".into(), + version: None, + path: path_str, + }); + } + }; + + if !output.status.success() { + return Some(RuntimeInfo { + status: "exec_failed".into(), + version: None, + path: path_str, + }); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + let text = if stdout.trim().is_empty() { + String::from_utf8_lossy(&output.stderr).to_string() + } else { + stdout.to_string() + }; + + match extract_version(&text) { + Some(version) => Some(RuntimeInfo { + status: "installed".into(), + version: Some(version), + path: path_str, + }), + None => Some(RuntimeInfo { + status: "version_unparseable".into(), + version: None, + path: path_str, + }), + } +} + +fn detect_runtimes() -> Runtimes { + Runtimes { + node: probe("node", &["--version"]), + npm: probe("npm", &["--version"]), + python: probe_python(), + uv: probe("uv", &["--version"]), + git: probe("git", &["--version"]), + winget: probe("winget", &["--version"]), + apt: probe("apt", &["--version"]), + } +} + +/// Python:优先 `python --version`,失败退回 `py --version`(Windows 启动器) +fn probe_python() -> Option { + let direct = probe("python", &["--version"]); + if matches!(direct, Some(ref i) if i.status == "installed") { + return direct; + } + probe("py", &["--version"]) +} + +fn detect_shells() -> Shells { + Shells { + powershell_version: probe_powershell(), + pwsh_version: probe("pwsh", &["-NoProfile", "-Command", "$PSVersionTable.PSVersion.ToString()"]) + .and_then(|i| i.version), + bash_available: resolve_program("bash").is_some(), + } +} + +#[cfg(windows)] +fn probe_powershell() -> Option { + probe( + "powershell", + &["-NoProfile", "-Command", "$PSVersionTable.PSVersion.ToString()"], + ) + .and_then(|i| i.version) +} + +#[cfg(not(windows))] +fn probe_powershell() -> Option { + None +} + +fn detect_capabilities() -> Capabilities { + #[cfg(windows)] + { + Capabilities { + // Windows Credential Manager 常驻可用(架构 §4.1) + keyring: "ok".into(), + // `net session` 仅在管理员上下文返回成功(提权探测,仅检测不执行用户命令) + can_elevate: run_success("net", &["session"]), + } + } + #[cfg(not(windows))] + { + Capabilities { + keyring: if resolve_program("secret-tool").is_some() { + "ok".into() + } else { + "missing".into() + }, + can_elevate: std::env::var("USER").map(|u| u == "root").unwrap_or(false), + } + } +} + +fn run_success(program: &str, args: &[&str]) -> bool { + let mut cmd = Command::new(program); + cmd.args(args); + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + cmd.creation_flags(0x0800_0000); + } + cmd.output().map(|o| o.status.success()).unwrap_or(false) +} + +#[cfg(windows)] +fn windows_os_version() -> String { + use winreg::enums::HKEY_LOCAL_MACHINE; + use winreg::RegKey; + + let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); + match hklm.open_subkey(r"SOFTWARE\Microsoft\Windows NT\CurrentVersion") { + Ok(key) => { + let product: String = key.get_value("ProductName").unwrap_or_else(|_| "Windows".into()); + let display: String = key.get_value("DisplayVersion").unwrap_or_default(); + let build: String = key.get_value("CurrentBuildNumber").unwrap_or_default(); + let major: u32 = key.get_value("CurrentMajorVersionNumber").unwrap_or(0); + let minor: u32 = key.get_value("CurrentMinorVersionNumber").unwrap_or(0); + let ver = if !display.is_empty() { + display + } else { + format!("{major}.{minor}") + }; + format!("{product} {ver} (Build {build})") + } + Err(_) => "Windows (unknown version)".into(), + } +} + +#[cfg(not(windows))] +fn linux_os_version() -> String { + let content = std::fs::read_to_string("/etc/os-release").unwrap_or_default(); + for line in content.lines() { + if let Some(v) = line.strip_prefix("PRETTY_NAME=") { + return v.trim().trim_matches('"').to_string(); + } + } + "Linux (unknown distro)".into() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn extracts_version_from_common_outputs() { + assert_eq!(extract_version("v24.18.0"), Some("24.18.0".into())); + assert_eq!(extract_version("11.16.0\n"), Some("11.16.0".into())); + assert_eq!(extract_version("Python 3.14.6"), Some("3.14.6".into())); + assert_eq!(extract_version("git version 2.47.1.windows.1"), Some("2.47.1".into())); + assert_eq!(extract_version("GNU bash, version 5.2.26(1)-release"), Some("5.2.26".into())); + assert_eq!(extract_version("v1.8.1911"), Some("1.8.1911".into())); + assert_eq!(extract_version("5.1.26100.1"), Some("5.1.26100.1".into())); + } + + #[test] + fn extracts_none_when_no_digits() { + assert_eq!(extract_version("command not found"), None); + assert_eq!(extract_version(""), None); + } + + #[test] + fn trims_trailing_dots() { + assert_eq!(extract_version("v1.2.3."), Some("1.2.3".into())); + } + + #[test] + fn env_has_path_on_windows() { + // PATH 变量在任何真实 Windows/Linux 上都存在 + assert!(!path_entries().is_empty(), "PATH 不应为空"); + } + + /// 实机自测:打印本机环境检测快照(cargo test -p agentdock-platform -- --ignored --nocapture) + #[test] + #[ignore] + fn print_detect_env_snapshot() { + let env = detect_env(); + println!("{:#?}", env); + assert_eq!(env.os, "windows"); + assert!(!env.os_version.is_empty()); + assert!(!env.arch.is_empty()); + } +} diff --git a/crates/agentdock-platform/src/lib.rs b/crates/agentdock-platform/src/lib.rs new file mode 100644 index 0000000..1f7fb38 --- /dev/null +++ b/crates/agentdock-platform/src/lib.rs @@ -0,0 +1,10 @@ +//! agentdock-platform —— 平台检测层 +//! +//! 负责 Windows / Linux 的本机环境检测(架构 §5): +//! OS 版本、架构、PATH、shell、运行时(Node/npm/Python/uv/Git/winget/apt)、 +//! 能力(密钥库、提权探测)。 + +pub mod detect; +pub mod model; + +pub use model::{Capabilities, PlatformEnv, RuntimeInfo, Runtimes, Shells}; diff --git a/crates/agentdock-platform/src/model.rs b/crates/agentdock-platform/src/model.rs new file mode 100644 index 0000000..315a416 --- /dev/null +++ b/crates/agentdock-platform/src/model.rs @@ -0,0 +1,56 @@ +//! 平台环境数据结构(对齐架构 §5 `PlatformEnv`) + +use serde::{Deserialize, Serialize}; + +/// 平台环境快照 +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +pub struct PlatformEnv { + /// os: windows | linux + pub os: String, + /// 人类可读的系统版本(如 "Windows 11 24H2 (Build 26100)" / "Ubuntu 24.04 LTS") + pub os_version: String, + /// 架构(std::env::consts::ARCH,如 x86_64) + pub arch: String, + pub shells: Shells, + pub runtimes: Runtimes, + /// PATH 条目(Windows 用 ';' 分割,Linux 用 ':') + pub path_entries: Vec, + pub capabilities: Capabilities, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +pub struct Shells { + /// Windows PowerShell(5.x)版本 + pub powershell_version: Option, + /// PowerShell 7+(pwsh)版本 + pub pwsh_version: Option, + pub bash_available: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +pub struct Runtimes { + pub node: Option, + pub npm: Option, + pub python: Option, + pub uv: Option, + pub git: Option, + pub winget: Option, + pub apt: Option, +} + +/// 单个运行时探测结果 +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +pub struct RuntimeInfo { + /// installed | not_installed | not_in_path | exec_failed | version_unparseable + pub status: String, + pub version: Option, + /// 解析到的可执行文件绝对路径(PATH 搜索) + pub path: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +pub struct Capabilities { + /// ok | missing(Linux 依赖 secret-tool / DBus Secret Service) + pub keyring: String, + pub can_elevate: bool, +} diff --git a/crates/agentdock-secrets/Cargo.toml b/crates/agentdock-secrets/Cargo.toml new file mode 100644 index 0000000..2058188 --- /dev/null +++ b/crates/agentdock-secrets/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "agentdock-secrets" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] diff --git a/crates/agentdock-secrets/src/lib.rs b/crates/agentdock-secrets/src/lib.rs new file mode 100644 index 0000000..adf88fa --- /dev/null +++ b/crates/agentdock-secrets/src/lib.rs @@ -0,0 +1,17 @@ +//! agentdock-secrets —— 系统密钥库封装与日志脱敏(架构 §4.1 / §4.3) +//! +//! 职责:keyring 封装(Windows Credential Manager / Linux Secret Service)、 +//! 敏感值脱敏。Wave 0:空骨架,随 Wave 1 落地。 + +/// 密钥层能力标记(占位) +pub const LAYER: &str = "agentdock-secrets"; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn layer_identity() { + assert_eq!(LAYER, "agentdock-secrets"); + } +} diff --git a/crates/agentdock-store/Cargo.toml b/crates/agentdock-store/Cargo.toml new file mode 100644 index 0000000..5a1c461 --- /dev/null +++ b/crates/agentdock-store/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "agentdock-store" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "SQLite 本地状态存储:schema 迁移与仓库(架构 §2 状态存储层)" + +[dependencies] +rusqlite = { version = "0.32", features = ["bundled"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" diff --git a/crates/agentdock-store/src/error.rs b/crates/agentdock-store/src/error.rs new file mode 100644 index 0000000..61b5fe8 --- /dev/null +++ b/crates/agentdock-store/src/error.rs @@ -0,0 +1,32 @@ +//! 存储层错误类型 + +use std::fmt; + +#[derive(Debug)] +pub enum StoreError { + Io(std::io::Error), + Sqlite(rusqlite::Error), +} + +impl fmt::Display for StoreError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + StoreError::Io(e) => write!(f, "IO: {e}"), + StoreError::Sqlite(e) => write!(f, "SQLite: {e}"), + } + } +} + +impl std::error::Error for StoreError {} + +impl From for StoreError { + fn from(e: std::io::Error) -> Self { + StoreError::Io(e) + } +} + +impl From for StoreError { + fn from(e: rusqlite::Error) -> Self { + StoreError::Sqlite(e) + } +} diff --git a/crates/agentdock-store/src/lib.rs b/crates/agentdock-store/src/lib.rs new file mode 100644 index 0000000..fb4a85d --- /dev/null +++ b/crates/agentdock-store/src/lib.rs @@ -0,0 +1,11 @@ +//! agentdock-store —— 本地状态存储(SQLite) +//! +//! Wave 0:SQLite 空库 + schema 迁移骨架。仓库查询接口随后续波次实现。 + +pub mod error; +pub mod migrations; +pub mod schema; +pub mod store; + +pub use error::StoreError; +pub use store::{Store, default_data_dir}; diff --git a/crates/agentdock-store/src/migrations.rs b/crates/agentdock-store/src/migrations.rs new file mode 100644 index 0000000..9b32459 --- /dev/null +++ b/crates/agentdock-store/src/migrations.rs @@ -0,0 +1,26 @@ +//! 迁移执行器(Wave 0 骨架) + +use rusqlite::Connection; + +use crate::schema::{MIGRATIONS, SCHEMA_VERSION}; + +/// 应用全部迁移。所有语句均为幂等(IF NOT EXISTS),可安全重复执行。 +pub fn migrate(conn: &Connection) -> rusqlite::Result<()> { + let tx = conn.unchecked_transaction()?; + for sql in MIGRATIONS { + tx.execute_batch(sql)?; + } + tx.execute( + "INSERT OR REPLACE INTO schema_version (version) VALUES (?1)", + [SCHEMA_VERSION], + )?; + tx.commit() +} + +/// 读取当前 schema 版本(未初始化返回 0) +pub fn current_version(conn: &Connection) -> rusqlite::Result { + conn.query_row("SELECT version FROM schema_version ORDER BY version DESC LIMIT 1", [], |r| { + r.get(0) + }) + .or(Ok(0)) +} diff --git a/crates/agentdock-store/src/schema.rs b/crates/agentdock-store/src/schema.rs new file mode 100644 index 0000000..c6fd5d6 --- /dev/null +++ b/crates/agentdock-store/src/schema.rs @@ -0,0 +1,33 @@ +//! schema 定义与迁移骨架(Wave 0) +//! +//! 迁移采用顺序数组:每个元素是一段幂等 SQL(CREATE TABLE IF NOT EXISTS)。 +//! 后续波次新增表时在 `MIGRATIONS` 尾部追加并提升 `SCHEMA_VERSION`。 + +/// 当前 schema 版本 +pub const SCHEMA_VERSION: i64 = 1; + +/// 迁移列表(按顺序执行,均需幂等) +pub const MIGRATIONS: [&str; 3] = [ + // 迁移 1:schema 版本表 + "CREATE TABLE IF NOT EXISTS schema_version ( + version INTEGER NOT NULL, + applied_at TEXT NOT NULL DEFAULT (datetime('now')) + );", + // 迁移 2:已安装 CLI 记录 + "CREATE TABLE IF NOT EXISTS installed_clis ( + cli_id TEXT PRIMARY KEY, + name_zh TEXT NOT NULL, + version TEXT, + install_channel TEXT, + detected_at TEXT + );", + // 迁移 3:诊断历史 + "CREATE TABLE IF NOT EXISTS diagnostic_history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + cli_id TEXT NOT NULL, + rule_id TEXT NOT NULL, + severity TEXT NOT NULL, + message_zh TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')) + );", +]; diff --git a/crates/agentdock-store/src/store.rs b/crates/agentdock-store/src/store.rs new file mode 100644 index 0000000..1d32c17 --- /dev/null +++ b/crates/agentdock-store/src/store.rs @@ -0,0 +1,105 @@ +//! Store:打开/创建 SQLite 空库并应用迁移(Wave 0) + +use std::path::{Path, PathBuf}; + +use rusqlite::Connection; + +use crate::error::StoreError; +use crate::migrations; + +/// 本地状态存储句柄 +pub struct Store { + conn: Connection, + path: PathBuf, +} + +impl Store { + /// 打开(不存在则创建)指定路径的 SQLite 库并应用迁移 + pub fn open>(path: P) -> Result { + let path = path.as_ref().to_path_buf(); + if let Some(parent) = path.parent() { + if !parent.as_os_str().is_empty() { + std::fs::create_dir_all(parent)?; + } + } + let conn = Connection::open(&path)?; + conn.pragma_update(None, "foreign_keys", true)?; + migrations::migrate(&conn)?; + Ok(Self { conn, path }) + } + + /// 打开默认数据目录下的 agentdock.db(Windows: %APPDATA%\agentdock) + pub fn open_default() -> Result { + Self::open(default_data_dir().join("agentdock.db")) + } + + pub fn path(&self) -> &Path { + &self.path + } + + pub fn connection(&self) -> &Connection { + &self.conn + } +} + +/// 默认数据目录 +pub fn default_data_dir() -> PathBuf { + #[cfg(windows)] + { + if let Some(appdata) = std::env::var_os("APPDATA") { + return PathBuf::from(appdata).join("agentdock"); + } + PathBuf::from(".").join("agentdock") + } + #[cfg(not(windows))] + { + if let Some(xdg) = std::env::var_os("XDG_DATA_HOME") { + return PathBuf::from(xdg).join("agentdock"); + } + if let Some(home) = std::env::var_os("HOME") { + return PathBuf::from(home).join(".local/share").join("agentdock"); + } + PathBuf::from(".").join("agentdock") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::migrations::current_version; + use crate::schema::SCHEMA_VERSION; + + #[test] + fn in_memory_migration_applies_and_is_idempotent() { + let conn = Connection::open_in_memory().expect("内存库应可打开"); + migrations::migrate(&conn).expect("首次迁移应成功"); + assert_eq!(current_version(&conn).unwrap(), SCHEMA_VERSION); + + // 幂等:重复迁移不报错、版本不变 + migrations::migrate(&conn).expect("重复迁移应成功"); + assert_eq!(current_version(&conn).unwrap(), SCHEMA_VERSION); + + // 关键表存在 + let tables: Vec = conn + .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name IN ('schema_version','installed_clis','diagnostic_history')") + .unwrap() + .query_map([], |r| r.get(0)) + .unwrap() + .collect::>() + .unwrap(); + assert_eq!(tables.len(), 3); + } + + #[test] + fn open_creates_empty_db_file() { + let dir = std::env::temp_dir().join(format!("agentdock-test-{}", std::process::id())); + let db = dir.join("agentdock.db"); + let _ = std::fs::remove_dir_all(&dir); + + let store = Store::open(&db).expect("应能创建库文件"); + assert!(db.exists(), "SQLite 空库文件应已创建"); + assert_eq!(current_version(store.connection()).unwrap(), SCHEMA_VERSION); + + let _ = std::fs::remove_dir_all(&dir); + } +} diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..61fb579 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,559 @@ +# AgentDock 技术架构方案 + +版本:1.0 · 日期:2026-08-24 · 角色:总工 · 状态:方案期交付 +依据:`PRODUCT-REQUIREMENTS.md` v0.1 + `agent-cli-survey-2026-08-24.md` +范围:首批 14 适配器设计;第二批 4 个仅预留扩展点,不实现 + +--- + +## 0. 一句话定位与竞品差异 + +**AgentDock** = 本机桌面「Agent CLI 生命周期管家」:发现 → 环境检查 → 安装/识别 → 中文配置 → 授权 → 更新/卸载 → 诊断。 + +| 维度 | AgentDock | 智谱 ZCode(竞品) | +|---|---|---| +| 形态 | 纯本地桌面管理器 | GUI 开发环境 + 管理 | +| 模型绑定 | **渠道中立**,不绑任何厂商套餐 | 捆绑 GLM Coding Plan | +| 管理对象 | 官方渠道安装的第三方 CLI | 自家 Harness + 切换多工具 | +| 数据与密钥 | 全本地;密钥进系统钥匙串 | 订阅与账号体系偏云端 | +| 核心卖点 | 中文化、离线可用、可扩展适配器 | 可视化切换 + 自家模型 | + +差异化口号(给总管翻译老板用):**「不卖模型、不绑套餐——只管帮你把各家官方 CLI 装好、配好、查清楚。」** + +--- + +## 1. 技术栈清单(以 PRD 4.2 为基线) + +| 层 | 选型 | 理由 | 以后怎么扩展 | +|---|---|---|---| +| 桌面壳 | **Tauri 2** | 体积小、Rust 本机能力天然契合;比 Electron 更适合「管本机命令/文件」 | 二期加 macOS / ARM 时复用同一壳 | +| UI | **React 19 + TypeScript + Vite** | 生态熟、施工员上手快;与赛博风组件库兼容 | 可加路由/状态库,不换栈 | +| 样式 | **CSS Variables 设计 Token + 少量 Tailwind(或纯 CSS Modules)** | Token 对接美术师规范;避免硬编码颜色 | 主题切换 = 换 Token 集 | +| 动效 | **Framer Motion(布局/页面)+ CSS `@property`/滤镜光效;重粒子用 Canvas 可选** | 可控帧率、易降级 | 高特效档可再加 WebGL | +| 本机能力 | **Rust(Tauri commands + 独立 crate)** | 进程、PATH、文件原子写、密钥库、平台差异 | 能力以 IPC 契约暴露,UI 不直调 OS | +| 适配器定义 | **版本化 YAML(主)+ JSON Schema 校验** | 人可读、可热更新目录;PRD 已示意 YAML | 远程目录更新可不发版 | +| 本地状态 | **SQLite(`rusqlite` / `sqlx`)** | 安装记录、诊断历史、备份索引;比散落 JSON 好查 | 导出仍可用 JSON | +| 敏感信息 | **keyring crate** → Win Credential Manager / Linux Secret Service | 满足 PRD 4.2/8;禁止明文落盘 | 迁移仅钥匙串或用户重授权 | +| 配置解析 | **toml / serde_json / serde_yaml**;Crush 用专用 `crushrc` 解析器 | 覆盖 14 家格式差异 | 新格式 = 新 `ConfigCodec` | +| 包构建 | **tauri-cli + NSIS(Win)+ deb + AppImage(Ubuntu)** | 覆盖目标平台 | 二期可加 flatpak | +| 自动更新 | **Tauri updater(签名校验)**;更新源仅官方 CDN | 离线核心功能不依赖更新 | 可关自动检查 | +| 测试 | **Rust:cargo test;前端:Vitest;适配器:fixture 驱动** | 双平台 CI 矩阵 | 真机 smoke 人工/脚本 | + +### 相对 PRD 的明确修正(仅 2 处) + +1. **本地状态优先 SQLite,不用「纯原子 JSON」作主库**——诊断历史、备份索引、操作审计需要查询;配置文件本身仍原子写。 +2. **适配器主格式定 YAML**——与 PRD 示例一致;运行时编译/校验为内部 JSON 表示,避免双源真相。 + +--- + +## 2. 目录结构与模块边界 + +```text +agentdock/ +├── apps/ +│ └── desktop/ # Tauri 2 应用入口 +│ ├── src/ # React UI +│ │ ├── pages/ # 总览 / 目录 / 我的CLI / 配置 / 备份 / 设置 +│ │ ├── components/ +│ │ ├── tokens/ # 对接美术师:颜色/字体/动效/光效变量 +│ │ ├── hooks/ +│ │ └── ipc/ # 类型化 invoke 封装(唯一前端→Rust 入口) +│ ├── src-tauri/ +│ │ ├── src/ +│ │ │ ├── main.rs +│ │ │ ├── commands/ # Tauri IPC 命令(薄层) +│ │ │ └── lib.rs +│ │ └── tauri.conf.json +│ └── package.json +├── crates/ +│ ├── agentdock-core/ # 编排:发现/安装/配置/授权/诊断流水线 +│ ├── agentdock-adapter/ # schema 加载、版本校验、dry-run、执行器接口 +│ ├── agentdock-platform/ # Win/Linux:PATH、权限提升探测、发行版识别 +│ ├── agentdock-config/ # 原子写入、备份、多格式编解码 +│ ├── agentdock-secrets/ # 系统密钥库封装 + 脱敏 +│ ├── agentdock-exec/ # 参数白名单、禁止 shell 拼接的进程执行 +│ ├── agentdock-diag/ # 规则引擎 +│ └── agentdock-store/ # SQLite schema 与仓库 +├── adapters/ # 版本化适配器 YAML(14 + 目录元数据) +│ ├── catalog.yaml # 目录索引 +│ ├── schema/ +│ │ └── adapter.schema.json # JSON Schema(校验 YAML) +│ └── tools/ +│ ├── codex.yaml +│ ├── claude-code.yaml +│ ├── gemini.yaml +│ ├── copilot.yaml +│ ├── kimi.yaml +│ ├── qwen.yaml +│ ├── codebuddy.yaml +│ ├── opencode.yaml +│ ├── crush.yaml +│ ├── goose.yaml +│ ├── aider.yaml +│ ├── cursor.yaml +│ ├── cline.yaml +│ └── warp.yaml +├── docs/ +│ └── architecture.md # 本方案落仓后副本 +└── tests/ + ├── fixtures/ # 假 PATH、假配置文件、假 --version 输出 + └── e2e/ # 可选:双平台 smoke 脚本 +``` + +### 四层边界(施工员铁律) + +| 层 | 职责 | 禁止 | +|---|---|---| +| **界面层** | 渲染适配器数据、表单、确认对话框、赛博视觉 | 直接拼命令、读写密钥、解析配置格式 | +| **本机能力层(Rust)** | IPC、编排、平台检测、执行、密钥、SQLite | 硬编码某 CLI 的特殊路径逻辑(应进适配器) | +| **适配器层** | 声明安装/检测/配置/授权/诊断;提供 dry-run | 在 YAML 里写任意 shell 字符串 | +| **状态存储** | SQLite 元数据 + 用户配置文件原子写 + 钥匙串 | 把 API Key 写入 SQLite/日志/备份默认项 | + +### 模块关系(简图) + +```text +UI --invoke--> commands --→ core orchestrator + ├→ adapter registry (YAML) + ├→ platform (env detect) + ├→ exec (safe spawn) + ├→ config codecs + atomic IO + ├→ secrets (keyring) + ├→ diag engine + └→ store (sqlite) +``` + +### 关键 IPC 契约(示意,施工员按此生成 TS 类型) + +```typescript +// 均返回 Result;错误带 code + message_zh + raw(原始错误永不吞) +detectEnv(): PlatformEnv +listCatalog(): CatalogEntry[] +detectCli(id: string): DetectResult +previewAction(id, action, opts): DryRunPlan // 安装/更新/卸载/写配置/授权/修复 +runAction(id, action, opts): ActionStream // 事件流:step/stdout/stderr/done +readConfig(id): ConfigFormState +writeConfig(id, patch): WriteResult +authStatus(id): AuthStatus +authorize(id, mode): AuthFlowHandle +diagnose(id): DiagnosticReport +listBackups() / createBackup() / restoreBackup(id, preview) +``` + +--- + +## 3. 适配器 Schema(完整定义 + 14 工具渠道矩阵) + +### 3.1 Schema 核心(在 PRD §6 上扩展) + +```yaml +# adapter.schema 关键字段(v1) +id: string # 稳定 ID,如 codex +name: string # 展示名 +name_zh: string +vendor: string +adapter_version: semver +license: string # 展示用;专有许可注明「仅官方渠道安装、不重打包」 +platforms: + windows: + architectures: [x64] + notes: string? # 如 Gemini 要求 Win11 24H2+ + linux: + distributions: [ubuntu] + architectures: [x64] + min_ubuntu: "22.04"? +official: + homepage: url + docs: url + allowed_hosts: [string] # 网络白名单(诊断/下载仅可访问) +runtime_deps: # 依赖探测 + - id: node | python | git | powershell | ... + semver_range: ">=20" + required_for: [install|run] +install: + preferred: channel_id # 默认渠道 + channels: + - id: npm | official_script | winget | choco | scoop | brew | apt | pypi_uv | github_release + platforms: [windows|linux] + # 命令必须是 argv 数组,禁止字符串拼接 + command: [string, ...] + # 脚本类:下载 URL 必须在 allowed_hosts;执行走专用 runner,不经 /bin/sh -c + script: + url: url? + kind: powershell_irm | bash_pipe | ps1_file + integrity: { sha256: hex }? # 有则强制校验 + package: string? # npm/pypi 包名 + elevate: never | if_needed | required + elevate_reason_zh: string? + post_checks: [detect] # 安装后自动检测 +detect: + executable: string # PATH 上的命令名;Cursor 为 agent + version_args: [string] + version_regex: string + version_unconfirmed: bool? # 调研标注「文档未能确认」时 true,UI 显示「实测兜底」 + path_hints: [string]? # 非 PATH 常见位置 +update: + method: npm_update | self_update_cmd | channel_reinstall | winget_upgrade | ... + command: [string, ...]? +uninstall: + method: npm_uninstall | package_manager | manual_delete + command: [string, ...]? + keep_config_default: true +authorization: + modes: # 可多选 + - mode: browser_oauth | device_code | api_key | local_tui + command: [string, ...]? + env_keys: [string]? # 写入钥匙串后注入的环境变量名 + status_command: [string, ...]? # 如 codex login status + notes_zh: string +configuration: + files: + - path: string # 支持 ~ 与平台变量 + format: toml | json | jsonc | yaml | env | crushrc + scope: user | project | system + environment: + - key: string + sensitive: bool + maps_to_field: string + fields: # 中文配置表单 + - id: string + label_zh: string + help_zh: string + required: bool + sensitive: bool + type: string | url | enum | bool + storage: file | env | keyring # keyring 永不进普通备份 + platforms: [windows|linux]? + docs_url: url? +diagnostics: + - rule_id: string # 引用规则库或内联 +documentation: + quickstart_zh: string + commands: [{ cmd, desc_zh }] + updated_at: date + risks_zh: [string] +``` + +### 3.2 统一执行器接口(Rust trait) + +```text +AdapterExecutor: + detect() -> DetectResult + install(plan) / update / uninstall + read_config() / write_config(patch) + authorization_status() / authorize(mode) + diagnose() -> Vec + dry_run(action) -> DryRunPlan # 必须:命令 argv、权限、影响文件、回滚说明 +``` + +所有 `command` 经 `agentdock-exec`:`Command::new(prog).args(args)`,**禁止** `cmd /C`、`sh -c`、管道字符串。官方「curl | bash」类安装:下载到临时文件 → SHA256(若有)→ 用明确解释器执行文件(PowerShell `-File` / `bash script.sh`),并在 UI 展示完整脚本来源。 + +### 3.3 首批 14 CLI 安装渠道与设计依据(均来自调研底稿) + +| ID | 命令 | 首选安装(Win / Ubuntu) | 备选 | 配置格式 | 授权模式 | 备注(底稿依据) | +|---|---|---|---|---|---|---| +| `codex` | `codex` | Win: 官方 PS 脚本或 npm `@openai/codex`;Ubuntu: 官方脚本或 npm | brew、GitHub Releases | TOML `~/.codex/config.toml` | browser + device_code + api_key | `--version` 文档未列,`version_unconfirmed` | +| `claude-code` | `claude` | Win: winget `Anthropic.ClaudeCode` 或 PS 脚本;Ubuntu: 官方脚本或 apt | npm(Node 22+) | JSON `~/.claude/settings.json` | browser + api_key | 专有许可:只引导官方安装 | +| `gemini` | `gemini` | 双端 npm `@google/gemini-cli`(Node 20+) | brew | JSON `~/.gemini/settings.json` | browser + api_key + vertex | Win 官方要求 11 24H2+,适配器 notes 声明 | +| `copilot` | `copilot` | Win: winget `GitHub.Copilot`(需 PS v6+)或 npm;Ubuntu: 脚本/npm/brew | — | JSON + env(`COPILOT_*`) | local_tui(`/login`) + PAT env | `--version` 未确认;需 Copilot 订阅 | +| `kimi` | `kimi` | 官方 `code.kimi.com` 脚本(内装 uv + **PyPI `kimi-cli`**) | `uv tool install kimi-cli` | TOML `~/.kimi/config.toml` | browser + api_key | **禁止** npm 仿名包 `kimi-code` | +| `qwen` | `qwen` | npm `@qwen-code/qwen-code`(Node 22+)或阿里 OSS 独立脚本 | brew | JSON `~/.qwen/settings.json` | **仅 api_key**(OAuth 已停) | `--version` 未确认 | +| `codebuddy` | `codebuddy` | npm `@tencent-ai/codebuddy-code` | `codebuddy install stable` 原生线 | 经 `codebuddy config`(路径待实测) | browser(微信登录);api_key 未确认 | 双升级路径要分 channel | +| `opencode` | `opencode` | Win: choco/scoop;Ubuntu: 官方脚本/npm | brew、Releases | JSON/jsonc `~/.config/opencode/opencode.json` | api_key + browser + device_code | 授权面最全之一 | +| `crush` | `crush` | Win: winget `charmbracelet.crush`;Ubuntu: 官方 apt(GPG) | npm、scoop、Releases | **crushrc**(Bash 语法) | api_key(环境变量) | `--version` 未确认;FSL 许可展示说明 | +| `goose` | `goose` | Win: 官方 `download_cli.ps1`;Ubuntu: 官方脚本 | brew、deb | YAML;Win 注意 `%APPDATA%\Block\goose\...` | api_key + 订阅 ACP | 密钥默认进系统钥匙串 | +| `aider` | `aider` | 官方 `aider-install` / PS 脚本 / PyPI `aider-chat` | uv/pipx;**不推荐 brew** | YAML `.aider.conf.yml` | **仅 api_key** | 独立 Python 3.12 环境 | +| `cursor` | `agent` | **仅官方脚本**(Win PS / Linux curl);**无 npm** | — | Cursor 账户体系 | browser(`agent login`) | 命令名是 `agent` 不是 cursor-agent | +| `cline` | `cline` | npm `cline`(Node 22+) | nightly 通道 | JSON `~/.cline/data/settings/providers.json` | browser + api_key + 本地模型 | 密钥已用系统 keychain | +| `warp` | `warp` | Win: 官方 agent-cli.ps1;Ubuntu: curl 脚本 | brew cask(Linux) | Warp 账户 / `WARP_API_KEY` | device_code + browser + api_key | 管 Agent CLI,不管 Warp 终端本体 | + +### 3.4 第二批(暂缓,仅 catalog 占位 `status: watch`) + +`grok`、`amp`、`deepseek-dsh`、`plandex` —— schema 允许加载但不出现在默认「可安装」列表,或灰显「观察中」。 + +--- + +## 4. 安全设计 + +### 4.1 密钥库封装 + +| 平台 | 后端 | API | +|---|---|---| +| Windows | Credential Manager(via `keyring`) | `secrets::set(service, account, secret)` | +| Linux | Secret Service / libsecret | 同上;无 daemon 时降级:**拒绝写入明文**,UI 提示安装 `gnome-keyring` | + +- `service` 固定前缀 `agentdock.`;`account` 为字段 id(如 `api_key`)。 +- 读配置时:表单敏感字段只显示「已保存 / 未保存」,回显用遮罩;真实值仅在写回目标 CLI 时短暂注入。 +- **禁止**:SQLite、普通备份、日志、崩溃报告、剪贴板默认复制密钥。 + +### 4.2 命令执行边界 + +1. 仅执行适配器声明的 argv;用户输入只作为**已校验参数槽位**(枚举/路径/版本号),不做字符串拼接。 +2. 默认禁止:管道、重定向、`$()`、反引号、`&&` 链、隐式脚本。 +3. 工作目录限制在用户主目录或用户确认的项目根。 +4. 下载:HTTPS + `allowed_hosts`;可选 SHA256;展示来源 URL。 +5. 权限提升:`elevate: if_needed|required` 时弹窗说明 `elevate_reason_zh`;**应用本身不以管理员启动**。 +6. 禁止远程网页驱动本机执行;禁止任意 URL 代理(防 SSRF)。 + +### 4.3 日志脱敏规则 + +| 模式 | 处理 | +|---|---| +| `sk-` / `xai-` / `ghp_` / `gho_` / JWT 形态 | 替换为 `***REDACTED***` | +| 适配器标记 `sensitive: true` 的 env/字段值 | 一律脱敏 | +| 家目录绝对路径 | 可选折叠为 `~`(诊断导出前提示) | +| 原始错误 | 保留结构,但过脱敏器后再落盘 | + +--- + +## 5. 环境检测方案(Win 10/11 x64 · Ubuntu 22.04/24.04 x64) + +`PlatformEnv` 结构: + +```text +os: windows | linux +os_version: ... +arch: x64 | other(reject) +distro?: ubuntu + version +shells: powershell_version?, bash_available? +runtimes: { node?, npm?, python?, uv?, git?, winget?, apt? } +path_entries: [...] +capabilities: { keyring: ok|missing, can_elevate: bool } +``` + +| 检测项 | Windows | Ubuntu | +|---|---|---| +| OS/版本 | `RtlGetVersion` / 注册表 | `/etc/os-release` | +| PATH | 用户+系统 PATH 合并,注意商店别名 | `$PATH` + `~/.local/bin` 是否在内(Goose/Cursor 常见坑) | +| Node/npm | `where node` + `node -v` | `command -v` + 版本 | +| Python/uv | `py -0p` / `uv --version` | 同上 | +| 包管理器 | winget / choco / scoop 可用性 | apt;可选 brew-on-linux | +| PowerShell | 区分 Windows PowerShell 5 vs pwsh 6+(Copilot winget 依赖) | N/A | +| 密钥库 | Credential Manager 可用性 | `secret-tool` / DBus Secret Service | + +检测失败分类(对齐 FR-02):`not_installed` | `not_in_path` | `permission_denied` | `exec_failed` | `version_unparseable`。 + +--- + +## 6. 配置读写设计 + +### 6.1 原子写入 + 自动备份 + +对任意目标文件 `F`: + +1. 若 `F` 存在 → 复制到 `F.bak.`(及 SQLite 备份索引)。 +2. 写入临时文件 `F.tmp.`。 +3. `fsync` 后 `rename` 覆盖(Windows 用 `MoveFileEx` 替换语义)。 +4. 失败则保留原文件与临时文件,UI 展示原始错误。 + +### 6.2 多格式统一策略:`ConfigCodec` trait + +| format | 实现要点 | 代表 CLI | +|---|---|---| +| `toml` | `toml_edit` 保注释 | Codex、Kimi | +| `json` / `jsonc` | jsonc 用 `jsonc-parser` 或 strip comments | Claude、Gemini、Qwen、OpenCode、Cline、Copilot | +| `yaml` | serde_yaml | Goose、Aider | +| `env` | KEY=VALUE 行;敏感键走钥匙串引用 | 多 CLI 辅助 | +| `crushrc` | **专用解析器**:识别 Crush 内建赋值语句,不做任意 bash 执行;未知行保留原样 | Crush | + +表单字段 `storage`: + +- `file` → Codec 读写 +- `env` → 用户级环境变量(Win:用户环境;Linux:`~/.config/agentdock/env.d/*.sh` 并提示加入 shell,或仅会话注入说明) +- `keyring` → 只写钥匙串;可选写入 CLI 期望的「env 变量名指针」到配置(如 Codex `env_key`) + +Goose:尊重其「密钥默认进系统钥匙串」——AgentDock 与之对齐,不把 Key 写入 `config.yaml`。 + +--- + +## 7. 授权状态统一模型 + +```text +AuthStatus = authorized | unauthorized | unknown | possibly_expired +AuthMode = browser_oauth | device_code | api_key | local_tui +``` + +| 模式 | AgentDock 行为 | +|---|---| +| `browser_oauth` | 启动适配器 login 命令;监听进程结束;可选检测本机打开浏览器;结果只存状态枚举 | +| `device_code` | 弹出「设备码」面板展示 stdout 中的 user_code + verification_url(解析规则在适配器);不截获 token 明文到日志 | +| `api_key` | 表单写入钥匙串;按 CLI 要求设置 env 或 `login --with-api-key`(stdin 传,不进 argv) | +| `local_tui` | 打开一次性伪终端面板(如 Copilot `/login`、部分 onboarding);结束后关闭,只留脱敏状态 | + +状态探测优先级: + +1. 适配器 `status_command`(如 `codex login status`、`opencode auth list`) +2. 钥匙串是否有对应 key +3. 配置文件非敏感标记 +4. 否则 `unknown` + +Qwen:UI 文案明确「免费 OAuth 已停用,请配置 API Key」。 +Kimi:安装源校验拦截非官方 npm。 + +--- + +## 8. 赛博风 UI 技术支撑与性能底线 + +### 8.1 与美术师对接点(施工员只消费 Token) + +```text +src/tokens/ + color.css /* --ad-bg, --ad-neon-cyan, --ad-danger, ... */ + typography.css + space.css + motion.css /* --ad-duration-fast/mid, --ad-ease-cyber */ + effects.css /* --ad-glow-strength, --ad-scanline-opacity */ + tiers.css /* data-fx-tier="high|balanced|low" */ +``` + +页面结构按 PRD §7:总览 / CLI 目录 / 我的 CLI / 配置中心 / 备份 / 设置。视觉细节以美术师 HEL-101 规范为准;架构只保证:**所有颜色/光晕/动效时长走 Token,组件不写死魔法数。** + +### 8.2 实现分层 + +| 层级 | 技术 | 用途 | +|---|---|---| +| L0 静态 | CSS 渐变、网格背景、边框霓虹(`box-shadow` Token) | 常驻,成本低 | +| L1 过渡 | Framer Motion / CSS transition | 路由、列表、按钮 | +| L2 光效 | SVG/CSS 扫光、有限 blur | 卡片 hover、焦点 | +| L3 粒子/噪点 | 可选 Canvas,**默认关** | 总览背景 | + +### 8.3 性能底线与降级 + +- 目标:集成显卡笔记本上总览页 **≥45 FPS**;交互延迟 <100ms。 +- 默认档 `balanced`:关 L3、降低 blur、减少同时动画数量(`max-concurrent-motions ≤ 3`)。 +- `low`:仅 L0+必要过渡;`prefers-reduced-motion` 强制 `low`。 +- 长列表虚拟滚动;日志终端用虚拟化,避免万行 DOM。 +- WebView 合成层:大面积滤镜仅用于静态背景层,不叠在滚动内容上。 + +--- + +## 9. 安装包分发与自动更新 + +| 产物 | 工具链 | 校验 | +|---|---|---| +| Windows x64 安装包 | Tauri + NSIS(或 MSI 二选一,首期 NSIS) | Authenticode 签名(有证书则必须;无证书时 UI 提示校验 SHA256) | +| Ubuntu | `.deb` + **AppImage**(覆盖无 root 场景) | deb 可用 GPG;AppImage 附 SHA256SUMS | +| 更新 | Tauri Updater | ed25519/minisign 签名;更新端点在 `allowed_hosts` | +| 回滚 | 保留上一版安装包;失败则提示卸载重装 + Git tag 对应版本 | 方案期约定:发版打 tag,坏版本从更新通道撤下 | + +应用自动更新与「CLI 更新」分离:设置页两项独立开关。核心管理功能 **离线可用**(目录 YAML 内置;在线仅用于检查更新与声明过的诊断 URL)。 + +--- + +## 10. 诊断规则引擎 + +### 10.1 规则模型 + +```yaml +rule_id: path.missing_executable +severity: path | dependency | version_conflict | config_corrupt | network | auth +severity: info | warn | error +when: { adapter_expr } # 可选:仅某些 CLI +check: # 声明式检查器 + type: which | semver | file_parse | http_head | keyring_has + ... +message_zh: "..." +evidence: capture # 原始 which 输出等 +fix: + - id: add_to_path + dry_run: true + reversible: true + impact_zh: "..." +``` + +### 10.2 四类必检(可扩展) + +| 类 | 示例规则 | +|---|---| +| PATH | 可执行文件不在 PATH;Windows Store alias 劫持;`~/.local/bin` 未入 PATH | +| 依赖缺失 | Node 版本不满足(Gemini≥20、Claude/Qwen/Cline/Copilot npm≥22);无 Git;无 uv(Kimi) | +| 版本冲突 | 多路径同名命令;npm global 与独立脚本双装(CodeBuddy 双通道) | +| 配置损坏 | JSON/TOML/YAML 解析失败;crushrc 无法识别关键赋值;权限不可读 | + +网络类:仅 `HEAD/GET` 适配器 `allowed_hosts`。 +输出:级别 + 证据 + 中文解释 + 推荐修复(修复前 dry-run + 备份)。 + +--- + +## 11. 测试策略 + +| 层级 | 内容 | +|---|---| +| 适配器单元测试 | 每个 YAML:schema 校验;dry-run 快照;version_regex 对 fixture stdout;危险命令(含 shell 元字符)必须拒载 | +| Codec 测试 | 各格式往返;保留未知键;原子写失败注入 | +| Exec 测试 | 拒绝管道字符串;参数白名单 | +| Secrets 测试 | mock keyring;断言日志无明文 | +| 平台检测测试 | Win/Ubuntu CI 或容器:`PlatformEnv` 字段;对「未安装」场景断言错误码 | +| UI 冒烟 | 目录渲染 14 条;安装确认框展示 argv;降级档无运行时错误 | +| 手工验收(对齐 PRD §10) | 双平台干净机:装应用→检测→至少 1 个 CLI GUI 安装成功→配置往返→Key 不进日志 | + +Wave A 五个适配器必须先有绿灯测试,再扩 Wave B。 + +--- + +## 12. 分波次交付节奏(14 = 5 + 9,每步可运行) + +对齐 PRD §11,并按老板升级的 14 适配器拆波: + +### Wave 0 — 空壳可运行(约 1 迭代) + +Tauri2 + React 壳、Token 占位、IPC 心跳、SQLite 空库、平台检测页、假目录。 + +### Wave 1 — 适配器框架 + 环境检测 + +Schema、加载器、dry-run、exec 沙箱、密钥库封装、日志脱敏。 + +### Wave 2 — Wave A 五件套打通全链路(安装→检测→配置→授权状态→诊断) + +优先选「文档全、双平台稳、配置清晰」: + +1. **Codex**(三授权 + TOML) +2. **Claude Code**(winget/脚本 + JSON) +3. **Gemini**(npm + JSON Schema) +4. **Kimi**(脚本/PyPI,验证非 npm 路径) +5. **OpenCode**(多渠道 + 多授权) + +验收:五者均可 detect;至少两者 GUI 安装成功;API Key 配置往返;三类诊断可命中。 + +### Wave 3 — Wave B 补齐 9 个 + +**B1(npm/脚本同类)**:Qwen、CodeBuddy、Cline、Copilot +**B2(包管理器/特殊)**:Crush(winget/apt + crushrc)、Goose(PS 脚本 + YAML 路径差异)、Aider(Python 安装器) +**B3(闭源脚本)**:Cursor(`agent`)、Warp(`warp`) + +每合入一个:单元测试 + 双平台 detect 记录。 + +### Wave 4 — 备份迁移 + 安装包 + 更新通道 + +备份不含密钥;Win NSIS + Ubuntu deb/AppImage;签名/SHA256;赛博视觉按美术规范挂满并做降级开关。 + +### Wave 5 — 打磨 + +批量更新、文档页中文、诊断规则扩容、第二批 watch 列表 UI。 + +--- + +## 13. 施工员开工检查清单(拿到本方案应无需再问架构) + +- [ ] 按目录建 monorepo;适配器只加 YAML,不改 core 流程 +- [ ] 一切安装命令来自上表与调研底稿,禁止凭记忆改包名 +- [ ] Kimi 绝不走 npm;Cursor 绝不走 npm;命令名 `agent` +- [ ] 用户确认前只 `dry_run`;确认后流式展示 stdout/stderr +- [ ] 密钥只进 keyring;日志过脱敏 +- [ ] 颜色/动效只引用 `tokens/`,等美术师规范合并 +- [ ] 不实现第二批 4 个,不建远程业务后端 +- [ ] 每 Wave 结束保持 `cargo test` + 前端 build 通过 + +--- + +## 14. 风险与需总管转告老板的点(非阻塞,已在方案内消化) + +1. 若干 CLI 的 `--version` 官方未文档化 → 适配器 `version_unconfirmed` + 实测表。 +2. CodeBuddy 配置路径、Copilot 登录是浏览器还是设备码 → Wave B 实测补字段。 +3. Gemini 官方写 Win11 24H2+ → 低版本 Win10 显示「官方未支持」而非强装。 +4. 无代码签名证书时 Win 智能筛选会报警 → 提供 SHA256 核对说明。 +5. Crush FSL 许可 → 关于页展示,不做竞品再分发其源码。 + +--- + +**本方案只交付设计,不含实现代码、不建仓库、不部署。** +施工员以本文 + PRD + 调研底稿三件套开工;视觉以美术师规范为准。 diff --git a/docs/visual-spec-v1.2.md b/docs/visual-spec-v1.2.md new file mode 100644 index 0000000..c2890a5 --- /dev/null +++ b/docs/visual-spec-v1.2.md @@ -0,0 +1,448 @@ +# AgentDock 视觉规范 v1.2(赛博风 · 终版,施工唯一依据) + +适用范围:AgentDock 第一版全部界面(Tauri 2 + React,Windows / Ubuntu,简体中文)。 +本规范是施工员写样式、总工验收的唯一品味依据。未覆盖到的新场景,按「视觉方向」一节的原则推演,不要自创风格。 + +**v1.2 修订说明**:老板已对 v3 样图拍板「通过」,本版把 v3 上按老板意见做的 4 处改动合入规范,为**施工开工的最终依据**。与 v1.1 的差异在 §0 总览逐条映射,并在对应节开头用【v1.2】标注;未标注的条文沿用 v1.1(含其【v1.1】标注)不变。施工对照基准 = 本规范 + v3 样图(HEL-107 附件 `overview3.png`)。 + +## 0. v1.2 变更总览(老板拍板 → 条文映射) + +| v3 已通过的改动 | 覆盖 v1.1 条文 | v1.2 条文 | +|---|---|---| +| ① 侧边栏选中态:废止嵌入 3px 指示条,改为左缘生长的青色渐变 + 1px 青色内描边,一种形态语言 | §3.0 侧边栏选中态 | §3.0【v1.2 重写】 | +| ② 「提示」级由中性冷灰空心点改为紫色实心点,语义升级为「紫 = 需要留意(提示 / 待授权)」 | §2.1 `--ad-note`、§3.5 提示级 | §2.1、§3.5【v1.2 修订】 | +| ③ 待授权 KPI 卡数字与顶部色条改紫色;色彩语义表定稿为六色;CLI 状态网格「待授权」角标与「未授权」空心点同步改紫 | §3.1.1 KPI 卡、§3.1.4 状态网格、§3.2、§3.3 | §2.1、§3.1.1、§3.1.4、§3.2、§3.3【v1.2 修订】 | +| ④ 主按钮 hover 微光扩散:`brightness(1.08)` + 双层扩散辉光,按下收回 | §2.6 悬浮/按下参数 | §2.6【v1.2 修订】 | +| ⑤ 验收对照同步更新(选中态形态语言、紫色语义红线) | §9 | §9【v1.2 修订】 | + +--- + +## 1. 视觉方向(一句话) + +**「深夜运维舱」:像一台运行良好的未来机器——深空蓝黑打底,霓虹青做交互指引,品红只点在关键状态上;光效和动效全部服务于「状态可读」,信息区永远干净克制。** + +三条不可违反的原则: + +1. **炫在框架,不在内容**。辉光、网格、扫描线只出现在背景、边框、导航、状态指示上;正文、表单、诊断文本区域保持高对比、零装饰。 +2. **光即语义**。霓虹色不是装饰色:青 = 可交互/主操作,品红 = 可更新/重要提醒,绿 = 正常,黄 = 警告,红 = 错误,【v1.2】紫 = 需要留意(提示 / 待授权)。用户看到颜色就能猜到含义,不许混用。 +3. **普通人友好**。所有动效可降级、可关闭;任何承载信息的文字对比度不低于 WCAG AA(正文 4.5:1,大字 3:1)。 + +【v1.1 新增第四条,v1.2 不变】 + +4. **密度优先**。总览页是信息密度最高的页面,首屏任何区域不允许出现超 15% 的无效空白;「留白高级」在本产品不成立,空 = 内容没做完。 + +--- + +## 2. 设计 Token 全套 + +统一用 CSS 变量命名,前缀 `--ad-`。以下值直接可用,施工员不得自行调参。 + +### 2.1 配色 + +【v1.2 修订:废止 `--ad-note` 中性冷灰,「提示 / 待授权」统一升级为紫色 `--ad-attention`;色彩语义表定稿为六色。其余沿用 v1.1】 + +**底色(深空阶梯,由深到浅):** + +| Token | 色值 | 用途 | +|---|---|---| +| `--ad-bg-0` | `#070B14` | 应用窗口底色 | +| `--ad-bg-1` | `#0D1420` | 面板、卡片底色(实际卡片表面用 §3.1 的径向渐变面) | +| `--ad-bg-2` | `#131C2E` | 浮层、弹窗、下拉、mono chip 底 | +| `--ad-bg-3` | `#1A2438` | 悬浮态、按下态的填充 | +| `--ad-border` | `#22304A` | 常规 1px 分隔线 | +| `--ad-border-bright` | `#35507A` | 悬浮/选中的边框 | + +**霓虹强调色(全系统只有这两个霓虹色):** + +| Token | 色值 | 用途 | +|---|---|---| +| `--ad-primary` | `#00E5FF` | 主操作、链接、聚焦、选中态(青) | +| `--ad-primary-hover` | `#4DEFFF` | 主操作悬浮 | +| `--ad-primary-pressed` | `#00B4CC` | 主操作按下 | +| `--ad-primary-dim` | `rgba(0,229,255,0.12)` | 选中项底、标签底 | +| `--ad-accent` | `#FF4DDB` | 可更新标记、重要提醒(品红,克制使用) | +| `--ad-accent-dim` | `rgba(255,77,219,0.12)` | 品红标签底 | + +**状态色(诊断四级 + 通用语义,必须分明,不许互相借用):** + +| Token | 色值 | 语义 | +|---|---|---| +| `--ad-success` | `#2EE6A8` | 正常 / 已安装 / 已授权 / 已自动修复 | +| `--ad-attention` | `#A78BFA` | **需要留意:「提示」级 + 「待授权 / 未授权」**(v1.2 新增,紫;实心点、不发光、不呼吸) | +| `--ad-warning` | `#FFB020` | 警告 / 需要你注意的异常 | +| `--ad-danger` | `#FF4D5E` | 错误 / 需要你处理 / 高危操作(卸载并删除配置等) | + +每个状态色配对应的 12% 透明度底色:`--ad-success-dim` / `--ad-attention-dim`(`rgba(167,139,250,0.12)`)/ `--ad-warning-dim` / `--ad-danger-dim`,用于状态标签、诊断条目背景。 + +**完整色彩语义表(v1.2 定稿,六色,全系统唯一标准):** + +| 语义 | 颜色 | 形态 | +|---|---|---| +| 可交互 / 主操作 / 选中 / 已安装 | 青 `--ad-primary` | 实心渐变按钮、描边、链接、聚焦辉光、状态点、角标 | +| 需要留意(提示 / 待授权 / 未授权) | 紫 `--ad-attention` | **实心圆点**、ⓘ 图标、待授权角标、未授权空心圈(仅圈形描边用紫,见 §3.1.4),**不发光、不呼吸** | +| 可更新 / 重要提醒 | 品红 `--ad-accent` | 角标、数字、更新按钮(唯一允许品红做按钮的场景) | +| 警告 / 需要你注意 | 黄 `--ad-warning` | 状态点、⚠ 图标、警告 chip,允许呼吸 | +| 错误 / 需要你处理 | 红 `--ad-danger` | 状态点、✕ 图标、危险描边,允许呼吸 | +| 正常 / 已授权 / 已修复 | 绿 `--ad-success` | 状态点、✓ 图标、chips | + +**状态色贯穿规则(强制)**:状态色不许只停在装饰条/色点上——凡是表达该状态的**数字、关键文字、图标**必须同色。例:环境异常卡的数字 `1` 必须是 `--ad-warning` 黄,待授权卡的数字必须是 `--ad-attention` 紫,不是白色数字配彩色装饰条。 + +**红色使用红线(强制)**:红只留给「需要你处理」的未解决项。已自愈/已自动修复的条目一律用绿 + 灰化文字 + 「已自动修复」标签(见 §3.1),禁止挂红点。 + +**紫色使用规则(v1.2 新增)**:紫介于「正常」与「警告」之间,表达「现在不用处理,但需要你留意」——诊断「提示」级、待授权/未授权状态专用。紫色任何形态**不发光、不呼吸**(避免与警告/错误的动态信号混淆);诊断列表中「提示」级用紫色**实心**点(v1.1 的中性空心点废止)。 + +**已删除**:`--ad-info` `#4DA3FF` 及 `--ad-info-dim`(蓝色退出系统,界面任何位置出现蓝色 = 不合格);`--ad-note` `#8E9BB0` 及 `--ad-note-dim`(v1.2 起由 `--ad-attention` 取代)。 + +**文字:** + +| Token | 色值 | 用途 | +|---|---|---| +| `--ad-text-1` | `#E8EEF7` | 主文案 | +| `--ad-text-2` | `#9AA8BD` | 次要说明(对 `--ad-bg-1` 对比度约 6.9:1,是承载信息的最低允许色) | +| `--ad-text-3` | `#5E6E87` | 仅占位、禁用、纯装饰(对比度约 3.3:1,**禁止承载信息**) | + +**对比度三件套之一**:任何承载信息的文字(含次要说明、时间戳、安全承诺文案)对比度 ≥ 4.5:1,即最低用 `--ad-text-2`;`--ad-text-3` 只允许出现在禁用态与占位符。安全承诺、授权提示这类建立信任的文字尤其不许压暗。 + +### 2.2 字体 + +【v1.2 不变】 + +```css +--ad-font-ui: "PingFang SC", "Microsoft YaHei UI", "Microsoft YaHei", + "Noto Sans CJK SC", "Source Han Sans SC", system-ui, sans-serif; +--ad-font-mono: "Cascadia Code", "JetBrains Mono", Consolas, + "Noto Sans Mono CJK SC", monospace; +``` + +- 等宽字体只用于:命令、路径、版本号、日志/终端输出、API Key 掩码、文件名。路径里的中文允许回落到 UI 字体,不用强求等宽。 +- **Mono chip**(对比度三件套之二):正文/列表中内联出现的文件名、版本号、命令,一律套 mono chip:`--ad-font-mono` 12px、`--ad-bg-2` 底、1px `--ad-border` 描边、`--ad-radius-s`、内边距 `1px 6px`,文字色 `--ad-text-1`。例:`settings.json`、`v0.22.0`、`20.x`。 +- 字阶(字号 / 行高 / 字重): + +| Token | 规格 | 用途 | +|---|---|---| +| `--ad-text-xs` | 12px / 18px / 400 | 标签、辅助说明、时间戳 | +| `--ad-text-s` | 13px / 20px / 400 | 表格次级信息、卡片描述 | +| `--ad-text-m` | 14px / 22px / 400 | 正文、表单、按钮(基准字号) | +| `--ad-text-l` | 16px / 24px / 500 | 卡片标题、分组标题 | +| `--ad-text-xl` | 20px / 28px / 600 | 页面标题 | +| `--ad-text-num` | 28px / 36px / 600 | 仪表盘大数字(UI 字体,不用等宽) | + +不加任何外联 Web 字体(离线可用、安装包不膨胀)。 + +### 2.3 间距、圆角、描边 + +【v1.2 不变】 + +- 间距 4px 基数:`--ad-space-1: 4px` `-2: 8px` `-3: 12px` `-4: 16px` `-5: 20px` `-6: 24px` `-8: 32px` `-10: 40px` +- 圆角:`--ad-radius-s: 4px`(标签、小按钮、chip)`--ad-radius-m: 8px`(卡片、输入框、弹窗)`--ad-radius-l: 12px`(主面板)。**赛博感靠小圆角 + 发光描边,不做大圆角。** +- 可选切角:仅仪表盘 KPI 卡、CLI 详情页头卡允许用 `clip-path` 切一个 8px 的右上斜角,制造硬核感;低性能档一律退回直角,见第 5 节。 +- 描边统一 1px;选中/聚焦的「发光描边」= 1px 实色 + 外辉光,不允许加粗到 2px(会跳动)。 + +### 2.4 阴影与辉光 + +【v1.2 微调:不发光对象表述对齐紫色新语义;主按钮 hover 辉光参数见 §2.6】 + +```css +--ad-shadow-panel: 0 8px 24px rgba(0, 0, 0, 0.45); +--ad-shadow-pop: 0 12px 40px rgba(0, 0, 0, 0.6); +--ad-glow-primary: 0 0 12px rgba(0, 229, 255, 0.35), 0 0 32px rgba(0, 229, 255, 0.12); +--ad-glow-accent: 0 0 12px rgba(255, 77, 219, 0.35); +--ad-glow-warning: 0 0 10px rgba(255, 176, 32, 0.28); /* 仅警告 chip / 警告状态灯 */ +--ad-glow-danger: 0 0 12px rgba(255, 77, 94, 0.35); +``` + +辉光只给:主按钮、选中导航项、聚焦输入框、需处理状态指示灯、可更新角标、环境警告 chip。**列表里每一行都发光 = 全部不发光,禁止。**「需要留意」级(`--ad-attention` 紫)任何形态不发光。 + +### 2.5 动效曲线与时长 + +【v1.2 不变】 + +```css +--ad-ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* 进场、浮现,主力曲线 */ +--ad-ease-inout: cubic-bezier(0.65, 0, 0.35, 1); /* 展开收起、位移、呼吸 */ +--ad-ease-linear: linear; /* 仅环境循环动画 */ +--ad-dur-micro: 120ms; /* 按下、勾选 */ +--ad-dur-fast: 200ms; /* 悬浮、淡入淡出 */ +--ad-dur-med: 320ms; /* 面板展开、页面切换 */ +--ad-dur-slow: 600ms; /* 仅入场编排,一次性 */ +``` + +### 2.6 主按钮规格 + +【v1.2 修订:老板裁定大面积青保留为风格选择,但 hover 必须用扩散光消化高饱和——悬浮辉光由单层升级为双层扩散,按下收回;其余沿用 v1.1】 + +```css +/* 主按钮(添加 CLI、安装 等主操作) */ +--ad-btn-primary-bg: linear-gradient(180deg, #33D9EE 0%, #06BCD9 55%, #049EBB 100%); +--ad-btn-primary-text: #03252E; +--ad-btn-primary-inner: inset 0 1px 0 rgba(255, 255, 255, 0.30), + inset 0 -1px 0 rgba(2, 20, 26, 0.35); +--ad-btn-primary-glow: 0 0 10px rgba(0, 229, 255, 0.22); +/* v1.2 新增:hover 双层扩散辉光 */ +--ad-btn-primary-glow-hover: 0 0 14px rgba(0, 229, 255, 0.30), + 0 0 36px rgba(0, 229, 255, 0.12); +``` + +- 结构:高 36px,`--ad-radius-s`,`--ad-btn-primary-bg` 内渐变 + `--ad-btn-primary-inner` 内高光/内阴影 + 常驻 `--ad-btn-primary-glow` 弱辉光,字 14px / 500 / `--ad-btn-primary-text`。 +- 悬浮(v1.2 定稿):`filter: brightness(1.08)` + 辉光扩为双层 `--ad-btn-primary-glow-hover`(内层 14px 聚焦、外层 36px 弥散),200ms `--ad-ease-out`。原理:用向外扩散的柔光消化大面积高饱和青在悬浮时的刺眼感,而不是再提亮本体。 +- 按下:`filter: brightness(0.94)`,辉光收回(消失),120ms。 +- 荧光感控制:`#00E5FF` 只用于描边、图标、文字、辉光这类「线」与「点」,实心填充一律用上面的降明度内渐变。 +- 品红更新按钮同理:`linear-gradient(180deg, #F26AD9 0%, #D633B8 55%, #B5209C 100%)`,内高光/内阴影同构,常驻辉光用 `--ad-glow-accent` 半强度;悬浮辉光同样按「半径放大、透明度分层」扩散(`0 0 14px rgba(255,77,219,0.30), 0 0 36px rgba(255,77,219,0.12)`)。 + +--- + +## 3. 关键页面布局与组件样式 + +### 3.0 整体框架 + +【v1.2 修订:侧边栏选中态重写(废止嵌入指示条,改左缘生长渐变 + 内描边一体形态);背景网格、导航图标、引擎状态区、Header 分组沿用 v1.1】 + +整体框架:**左侧竖导航 + 顶栏 + 内容区**。 + +**背景网格(要么可见要么不做,禁止两头不占):** + +- 规格:1px 线、40px 间距、颜色 `rgba(0,229,255,0.05)`(透明度固定 5%,允许区间 3–6%,施工员取 5% 不许再调); +- **边缘羽化**:整体罩一层径向遮罩 `mask-image: radial-gradient(120% 90% at 50% 40%, #000 30%, transparent 75%)`,中心可见、向四边淡出至 0; +- 中高档附 60s 缓慢漂移(见 §4.8),低档静态; +- 若实现后静态截图下网格仍不可见,宁可删掉网格也不许留在「若隐若现」区间。 + +**侧边栏:** + +- 宽 232px,收起后 64px(只留图标)。 +- **选中态(v1.2 定稿,废止 v1.1「圆角背景块 + 嵌入 3px 指示条」方案——老板两轮审图判定指示条与圆角背景是两张皮)**:选中项 = 一个圆角背景块,**无独立指示条**,整个选中态只有一种形态语言: + - 背景:`linear-gradient(90deg, rgba(0,229,255,0.30), rgba(0,229,255,0.12) 45%, rgba(0,229,255,0.05))`——一层从块左缘「生长」的青色渐变,左浓右淡,到右缘几乎消隐; + - 内描边:`inset 0 0 0 1px rgba(0,229,255,0.38)`,与背景块同圆角; + - 块规格:`--ad-radius-m`、左右内边距 12px、块左右各留 8px 外边距(不贴窗口边缘); + - 图标与文字变 `--ad-primary`; + - 不额外加辉光(渐变 + 内描边已足够表达选中,再叠光则脏)。 + - 收起态(64px 图标栏):同一套渐变 + 内描边作用于 40px 见方图标块,形态语言不变。 + - 定义 token:`--ad-nav-selected-bg`(上述渐变)、`--ad-nav-selected-border`(上述内描边)。 +- 导航图标:5 个一级导航统一 20px 显示尺寸、**1.5px 描边**、圆角端点线性风格(§7),粗细不一致的图标混用前必须调 stroke 到 1.5px 观感。 +- 底部引擎状态区:独立区块高 48px,顶部 1px `--ad-border` 分隔线,内边距 `0 16px`;内容 = 8px 状态灯(运行中=绿静态、异常=黄呼吸,规则见 §4.5)+ 「本地引擎 · 运行中」`--ad-text-s` / `--ad-text-2` + 版本号 mono chip(如 `v1.0.0`)。收起态只留状态灯居中。 + +**顶栏(Header):** + +- 高 56px,左起:当前页标题(`--ad-text-xl`)。 +- 右侧状态区分组:搜索框与环境警告 chip 为一组,间距 `--ad-space-3`(12px),组内基线对齐,与页面标题拉开间距。 +- 搜索框限宽:标准断点固定宽 320px,紧凑断点收缩为 36px 图标按钮、点击展开。聚焦 = 边框变青 + `--ad-glow-primary`。 +- **环境警告 chip**:高 28px、`--ad-radius-s`、1px `rgba(255,176,32,0.5)` 描边、`--ad-warning-dim` 底;内容 = 6px 黄点(异常存在时按 §4.5 呼吸)+ 「N 项环境警告」`--ad-text-s` / `--ad-warning` + 12px chevron-down。悬浮:描边转实色 `--ad-warning` + `--ad-glow-warning` + 手型指针,200ms;点击展开警告明细下拉。无警告时整颗 chip 不渲染,不留占位。 + +**内容区:** 最大宽 1440px 居中,左右留白 `--ad-space-8`。 + +### 3.1 总览(仪表盘) + +【v1.2 修订:KPI 卡「待授权」数字与色条改紫;CLI 状态网格「待授权」角标与「未授权」点改紫;其余布局与规则沿用 v1.1】 + +**布局(自上而下三段,首屏必须填满):** + +1. KPI 卡一排 4 张:已安装 / 待授权 / 可更新 / 环境异常。 +2. 中段:左 2/3「最近诊断结果」列表,右 1/3「快速操作」卡。 +3. **下段(必选模块):「CLI 状态」网格通栏**,已收录 CLI 以状态块平铺(见 §3.1.4)。后续迭代可在其下再接「资源占用」「活动流」模块,规则同本页组件语言;首屏任何纵向区间无效空白 ≤ 15%。 + +**3.1.1 KPI 卡:** + +- 卡片表面(悬浮玻璃板):底为径向渐变面 `radial-gradient(120% 100% at 50% 0%, #16233A 0%, #0D1420 60%)` + 上缘 1px 高光 `inset 0 1px 0 rgba(255,255,255,0.06)` + `--ad-shadow-panel`;与页面背景明度差必须拉开,验收时卡片边界在静态截图下清晰可辨。 +- `--ad-radius-l`、右上 8px 切角(可选,低档退回直角)。 +- **顶部色条贯通整宽**(废止半拉子短条;也不许做左侧短装饰条):2px 高、与卡片同宽,颜色按卡语义【v1.2 定稿】:已安装=青、**待授权=紫 `--ad-attention`**(v1.1 的中性 `--ad-border-bright` 废止)、可更新=品红、异常=黄;**数值为 0 时色条降为 `--ad-border` 灰**。 +- 大数字 `--ad-text-num`,**数字随状态色贯穿**【v1.2 定稿】:已安装=青、**待授权=紫 `--ad-attention`**(v1.1 的中性白废止——待授权是「需要留意」的待办,按六色语义表归紫色,不再是「不色」)、可更新=品红、环境异常=黄;数值为 0 时数字回 `--ad-text-1`。 +- 副标题统一「对象点名」句式(强制):不解释、不形容,直接点出对象名,超过 3 个用「A · B · C 等 N 个」。例:待授权 → `Kimi CLI · Warp Agent`;环境异常 → `Node.js 20.x`(版本号套 mono chip);已安装 → `最新安装 · Qwen Code`;可更新 → `Qwen Code · Goose · Aider`。 +- 悬浮:整体抬升 2px + 顶部色条辉光,200ms `--ad-ease-out`,不打断数据。 + +**3.1.2 最近诊断列表(五条硬规则):** + +1. **时间格式(三件套之三,全应用统一)**:<1 分钟显示「刚刚」;<1 小时显示「N 分钟前」;<24 小时显示「N 小时前」;≥24 小时一律绝对时间「M月D日 HH:mm」(跨年开始带年「YYYY年M月D日」);悬浮显示完整绝对时间 tooltip。禁止相对/绝对混排无规则出现。 +2. **mono chip**:条目内的文件名、版本号、命令一律按 §2.2 套 mono chip。 +3. **对比度**:结论文字最低 `--ad-text-2`,时间戳最低 `--ad-text-2`,任何承载信息的文字不低于 4.5:1。 +4. **红色只留「需要你处理」**:已自动修复的条目 = 绿点 + 文字灰化(`--ad-text-2`)+ 右侧「已自动修复」标签(绿字、1px `rgba(46,230,168,0.4)` 描边、`--ad-success-dim` 底、高 20px);未解决错误才用红点 + `--ad-text-1` 文字。【v1.2】「提示」级用 `--ad-attention` **紫色实心圆点**(v1.1 的中性空心点废止),不发光、不呼吸。 +5. **「查看全部」**:`--ad-text-s` 青字 + 14px chevron-right 图标,悬浮文字变 `--ad-primary-hover`、chevron 右移 2px,200ms。 + +行结构不变:级别点 + CLI 名 + 一句话结论 + 右侧时间,行高 48px,整行可点击进诊断详情。 + +**3.1.3 快速操作卡:** + +- 结构自上而下:主按钮「添加 CLI」(§2.6 规格,+ 图标)→ 次按钮「立即诊断」→ 次按钮「创建备份」→「最近安装」迷你列表 → 安全承诺行。密度必须与左侧诊断列表视觉平衡,不允许一侧拥挤一侧空荡。 +- 图标语义(定死):「立即诊断」用 pulse/scan 类图标(心电脉冲或扫描线),不许用公文包等无关图标;「添加 CLI」用标准加号图标,全应用加号统一同一枚;「创建备份」用下载/存档类图标。 +- 按钮内图标 16px,与文字间距统一 `--ad-space-2`(8px),三个按钮对齐方式一致(统一居中)。 +- 「最近安装」列表:分组标题 `--ad-text-xs` / `--ad-text-3`(此为唯一允许的 `--ad-text-3` 分组标签用法,下同不例)+ 最多 3 条,每条 = 16px CLI 图标 + 名称 `--ad-text-s` + 右侧相对时间 `--ad-text-xs` / `--ad-text-2`。 +- 安全承诺行:12px 锁图标 + 「所有操作在本地完成,密钥只存系统保险柜,不上传。」,`--ad-text-xs` 但文字色**最低 `--ad-text-2`**(建立信任的文案不许压暗)。 + +**3.1.4 CLI 状态网格:** + +- 容器:通栏面板,标题「CLI 状态」+ 右侧「管理全部 N 个」青字 + chevron-right。 +- 网格:`repeat(auto-fill, minmax(280px, 1fr))`,间距 `--ad-space-4`;各断点列数见 §6。 +- 状态块结构(自上而下): + - 第一行:左 32px CLI 图标(单色剪影,无品牌彩色,无厂商图标时用双字母单色头像,`--ad-bg-2` 底 + 1px `--ad-border`)+ 名称 `--ad-text-m` / `--ad-text-1`;右侧**状态角标**(高 22px、`--ad-radius-s`、`--ad-text-xs`)。 + - 第二行:版本号 mono chip(未安装显示 `—` 不套 chip)。 + - 第三行(授权行):8px 授权状态点 + 授权状态文案 `--ad-text-xs` + `·` + 最近检测时间。 +- 状态角标规则【v1.2 修订】:已安装 = 青字 + 1px `rgba(0,229,255,0.4)` 描边 + `--ad-primary-dim` 底;可更新 = 品红字 + 1px `rgba(255,77,219,0.5)` 描边 + `--ad-accent-dim` 底 + 弱 `--ad-glow-accent`;**待授权 = 紫字 `--ad-attention` + 1px `rgba(167,139,250,0.5)` 描边 + `--ad-attention-dim` 底**(v1.1 的中性方案废止,紫色不发光);未安装 = `--ad-text-3` 字 + `--ad-border` 描边。 +- 授权状态点规则【v1.2 修订】:已授权 = 绿实心点(静态,不呼吸);**未授权 = 紫色空心圈(1.5px `--ad-attention` 描边)**+ 文案「未授权 · 点击去授权」,悬浮整行变青可点击(v1.1 的中性空心圈废止);授权可能过期 = 黄点 + 按 §4.5 呼吸;未检测/未知 = `--ad-text-3` 灰实心点 + 文案给出下一步(如「未检测 · 可一键安装」)。 +- 块体样式:`--ad-bg-1` 底、1px `--ad-border`、`--ad-radius-m`、内边距 `--ad-space-4`;悬浮边框变 `--ad-border-bright`,200ms;不发光、不放大。 +- 排序:需要你处理的排最前(可更新 > 待授权 > 未安装 > 已安装),同级按最近检测时间倒序。 + +### 3.2 CLI 目录(卡片列表) + +【v1.2 微调:状态角标颜色对齐六色语义表】 + +- 顶部工具条:搜索框(320px 限宽、青聚焦辉光)+ 平台筛选(Windows / Linux 胶囊按钮)+ 状态筛选(全部/已安装/未安装/可更新)。 +- 卡片网格:`repeat(auto-fill, minmax(280px, 1fr))`,间距 `--ad-space-4`。 +- 卡片结构:左上图标 40px + 中文名(`--ad-text-l`)+ 厂商小字;中间两行简介(超出省略);底部状态角标 + 平台图标 + 适配器版本 mono chip。 +- 状态角标样式:与 §3.1.4 状态角标规则完全一致(已安装=青、可更新=品红 + 弱辉光、**待授权=紫**、未安装=灰)。 +- 悬浮:卡片边框从 `--ad-border` 过渡到 `--ad-primary` 并带弱辉光,200ms;不做放大(网格会抖)。 + +### 3.3 CLI 详情页 + +【v1.2 微调:授权状态灯「未授权」改紫】 + +- 头卡:左 56px 图标 + 名称 + 厂商 + 版本/路径(等宽、`--ad-text-2`);右侧主操作按钮区。主操作按状态变化:未安装=「安装」(§2.6 主按钮)、已安装=「打开配置」(青描边)、可更新=「更新到 x.y.z」(§2.6 品红按钮,全应用唯一允许品红做按钮的地方)。卸载永远是文字按钮 + 悬停变红,藏进「更多」。 +- 授权状态灯:头卡内一颗 10px 圆点 + 中文状态(已授权/未授权/授权可能过期/未知):已授权=绿(静态)、**未授权=紫 `--ad-attention`(静态)**、可能过期=黄(呼吸)、未知=`--ad-text-3` 灰。 +- 下方标签页:概览 / 配置 / 中文文档 / 诊断。标签选中 = 底部 2px 青条 + 青色文字,切换 200ms。 +- 中文文档区按普通阅读排版:正文 `--ad-text-m`,最大行宽 720px,禁止在正文区加任何光效。 + +### 3.4 配置表单(含敏感字段) + +【v1.2 不变】 + +- 单列布局,最大宽 640px;标签在上(`--ad-text-s`,`--ad-text-2`)、控件在下,字段间距 `--ad-space-5`。必填项标签后加青色 `*`;每项下方一行中文用途说明(`--ad-text-xs` / `--ad-text-2`)。 +- 输入框:`--ad-bg-0` 底、1px `--ad-border`、`--ad-radius-m`、高 36px、内边距 `0 12px`。聚焦 = 边框变 `--ad-primary` + `--ad-glow-primary`,120ms。 +- **敏感字段(API Key 等)**:密码框 + 右侧「眼睛」显隐切换;已存入系统密钥库时输入框左侧显示 12px 锁形图标 + 说明「已加密保存于系统密钥库」(`--ad-text-2`)。显示掩码时一律用 `●` 不用 `*`。 +- 校验:错误时边框变 `--ad-danger` + 下方一行中文错误说明,不弹 toast;保存按钮常驻底部吸底条,保存成功给一次性青色对勾动画(见 §4.6)。 +- 「保存来源 / 最近修改 / 备份状态」放表单页脚,`--ad-text-xs` / `--ad-text-2`。 + +### 3.5 诊断结果页 + +【v1.2 修订:「提示」级由中性冷灰改紫 `--ad-attention`,形态仍为空心 ⓘ 图标(靠形态而非颜色区分色弱用户),色条同紫、不发光】 + +顶部一颗大状态灯 + 汇总句(如「发现 1 个错误、2 个提示」),下方按级别分组列出检查项。 + +- 每个检查项 = 左侧 3px 级别色条 + 级别图标(正常 ✓ 实心 / 提示 ⓘ 空心 / 警告 ⚠ / 错误 ✕)+ 标题 + 中文解释 + 「推荐修复」按钮(警告/错误才显示)。 +- 级别色严格用 §2.1 状态色:**正常=绿、提示=紫 `--ad-attention`、警告=黄、错误=红**,配合不同图标与实心/空心形态,色弱用户也能分辨。「提示」级色条与图标不发光。 +- 原始证据(命令输出、路径、错误码):等宽字体、`--ad-bg-0` 底、默认折叠,点击展开 320ms `--ad-ease-inout`。允许 1px 青色行号槽,但不发光。 +- 高危修复按钮(覆盖配置等):`--ad-danger` 描边,点击后二次确认弹窗,弹窗标题栏左侧红色辉光点。 + +### 3.6 通用组件速查 + +【v1.2 微调:新增侧边栏选中态条目,指向 §3.0】 + +| 组件 | 规格 | +|---|---| +| 主按钮 | 见 §2.6:降明度内渐变 + 内高光 + 常驻弱辉光;hover 双层扩散辉光 | +| 次按钮 | 透明底 + 1px `--ad-border-bright` + `--ad-text-1`;悬浮边框变青 | +| 危险按钮 | `--ad-danger` 描边 + 红字;确认后才实心 | +| 侧边栏选中态 | 见 §3.0:左缘生长青渐变 + 1px 青色内描边,无独立指示条 | +| 环境警告 chip | 见 §3.0:黄描边 + 黄点 + chevron,悬浮带 `--ad-glow-warning`,可点击 | +| Mono chip | 见 §2.2:文件名/版本号/命令的内联等宽 chip | +| 弹窗 | `--ad-bg-2` 底、`--ad-radius-l`、`--ad-shadow-pop`、遮罩 `rgba(4,7,13,0.7)` | +| Toast | 右下角滑入,底 `--ad-bg-2` + 左侧 3px 状态色条,4s 自动消失,错误 Toast 常驻需手关 | +| 空状态 | 线性插画(见 §7)+ 一句中文引导 + 主按钮;**总览页不允许出现空状态式大留白,用 §3.1 模块填满** | + +--- + +## 4. 动效规范 + +【v1.2 不变(紫色不呼吸的规则已在 §2.1 / §4.5 内体现)】 + +通则:**所有动效只动 `transform` 和 `opacity`**(辉光强弱可用 `box-shadow` 过渡,但仅限单个聚焦元素);任何循环动画在低配档必须可停。`prefers-reduced-motion: reduce` 时全局只保留透明度渐变,时长压到 100ms。 + +**§4.0 动效启用前提**:排版三件套(时间格式、mono chip、对比度)与密度、色彩未达标前,任何动效装饰不进入验收——基础不修,动效越炫越显糙。验收顺序固定为:① 密度 → ② 色彩 → ③ 排版三件套 → ④ 形态语言(渐变条/选中态)→ ⑤ 动效。 + +| # | 动效 | 触发条件 | 时长 | 缓动 | 可否打断 | +|---|---|---|---|---|---| +| 4.1 | 按钮/卡片悬浮辉光渐入 | hover | 200ms | `--ad-ease-out` | 可(鼠标移出即反向) | +| 4.2 | 页面切换 | 路由变化 | 320ms,新页 `translateY(8px)+淡入`,旧页只淡出 | `--ad-ease-out` | 可,立即跳到目标页 | +| 4.3 | 面板/折叠区展开(诊断证据、高级选项) | 点击 | 320ms | `--ad-ease-inout` | 可 | +| 4.4 | 仪表盘 KPI 数字滚动 | 数据首次加载 | 600ms,一次性,不循环 | `--ad-ease-out` | 不可(太短,直接播完) | +| 4.5 | **状态点呼吸** | **仅「需要处理」的项呼吸:警告(黄)、错误(红)、授权可能过期(黄)。绿/正常/提示(紫)/待授权(紫)一律静态** | 2400ms | `--ad-ease-inout`,**透明度 1 ↔ 0.4(下限 40%,再低像灭了,禁止)** | 状态恢复即停 | +| 4.6 | 保存成功对勾 | 配置保存成功 | 400ms 描边绘制 + 200ms 青辉光脉冲一次 | `--ad-ease-out` | 不可 | +| 4.7 | 安装/诊断进行中 | 任务运行 | 进度条用确定值;不确定时用 1.6s 青色流光扫过(linear infinite) | `--ad-ease-linear` | 任务结束即停 | +| 4.8 | 背景网格漂移 | 常驻氛围 | 60s 一周 | linear | 降档即停 | +| 4.9 | Toast 滑入滑出 | 出现/消失 | 200ms / 200ms | `--ad-ease-out` | 鼠标悬停暂停计时 | + +**§4.5 补充规则**: + +1. 全应用所有呼吸点**同频同相位**——用同一个全局动画时钟(同一个 CSS animation 名 + 同步起始),禁止各组件各自起动画导致此起彼伏。 +2. 呼吸只动透明度(1 ↔ 0.4),不动尺寸、不动辉光半径。 +3. 同屏呼吸点超过 3 个时,只保留级别最高的 3 个呼吸,其余静态(错误 > 警告)。 +4. 低档:呼吸全部停用,改静态点 + 图标区分(§5)。 + +明确禁止的动效:元素无限弹跳、文字逐字打印、hover 缩放超过 1.02、列表项 stagger 超过 5 个。 + +--- + +## 5. 性能降级策略(高特效项目必备) + +【v1.2 不变】 + +**三档:高 / 中 / 低。默认「自动」:按运行表现动态降档,用户可在设置页手动锁档。** + +| 特效 | 高档 | 中档 | 低档 | +|---|---|---|---| +| 背景氛围 | Canvas 粒子星网(≤60 粒子)+ 网格漂移 | 纯 CSS 渐变 + 可见网格(5% + 羽化) | 静态渐变图,网格可留静态,无动画 | +| 辉光 | 多层 `box-shadow` 全量 | 只保留单层、半径减半 | 全部移除,改 1px 亮色边框 | +| 切角 clip-path | 启用 | 启用 | 禁用(直角) | +| 页面/面板动效 | 全量 | 时长 ×0.7 | 只留 100ms 淡入淡出 | +| 循环动画(呼吸、流光) | 启用(按 §4.5 仅异常呼吸) | 只保留任务进行中的流光 | 全部停用,改静态图标 | +| 数字滚动 | 启用 | 启用 | 直接显示终值 | + +**自动降档规则(施工员照此实现):** + +1. 启动时 `prefers-reduced-motion` 或 `navigator.hardwareConcurrency ≤ 4` → 直接进低档。 +2. 运行中用 `requestAnimationFrame` 采样:连续 3 秒平均帧率 < 45fps → 降一档并记住;低于 30fps → 直接低档。 +3. 降档只降不升(防抖动),重启后重新评估。 +4. 设置页提供「界面特效:自动 / 高 / 中 / 低」四选一,手动选择覆盖自动。 + +--- + +## 6. 窗口尺寸响应 + +【v1.2 不变】 + +桌面窗口,最小 **960 × 600**,不设上限。三个断点: + +| 断点 | 宽度 | 布局变化 | +|---|---|---| +| 紧凑 | 960–1199px | 左导航收起为 64px 图标栏;KPI 卡 2×2;诊断列表与快速操作上下堆叠(诊断在上);CLI 状态网格 2 列;详情页头卡操作按钮折成两行;配置表单仍单列 | +| 标准 | 1200–1599px | 完整 232px 导航;KPI 一排 4 张;诊断 + 快速操作左右 2:1;CLI 状态网格 3 列;目录卡片 3 列左右 | +| 宽阔 | ≥1600px | 内容区锁 1440px 居中,多余空间留黑,不拉伸卡片;CLI 状态网格 4 列;诊断页可「结果列表 + 证据」左右双栏 | + +窗口高度 < 700px 时:顶栏从 56px 压到 44px,KPI 卡大数字从 28px 降到 22px,CLI 状态网格允许纵向滚动但模块不许删除,不允许出现内容被裁切。 + +--- + +## 7. 图标与插画方向 + +【v1.2 微调:「提示」图标颜色改紫】 + +- **图标**:线性风格,统一 24px viewBox、**1.5px 描边**、圆角端点;颜色默认 `--ad-text-2`,选中/激活态变 `--ad-primary`。状态图标(诊断四级、授权灯)用实心填充 + 状态色(「提示」用空心 ⓘ + `--ad-attention` 紫)。CLI 厂商图标一律单色剪影,不上品牌彩色,hover 时叠加青色光晕。 +- **语义图标(定死)**:诊断/扫描类动作用 pulse/scan 图标;「添加」全应用统一同一枚加号图标;「查看全部/展开」统一 chevron 系列,不许混用 `›` 字符与箭头图形。 +- **插画**:只出现在空状态和引导页。等距线框风格(1px 线、`--ad-border-bright` 色)+ 一到两处霓虹青/品红渐变点缀,内容画「终端窗口、线缆、集装箱」这类 Dock 意象,不画人物、不画卡通。 +- 图标库选型由施工员定(任何成熟线性库均可),但描边粗细必须统一为 1.5px 观感,混用前调 stroke。 + +--- + +## 8. 需要总工确认的技术可行性点 + +【v1.2 不变,沿用 v1.0/v1.1 四条(结论已在 HEL-102 由总工给出,若与本规范条文有出入以总工结论为准)】 + +1. **背景氛围层**:计划用 Canvas 2D(≤60 粒子 + 网格)而非 WebGL;若总工想要 WebGL 着色器光效,需评估 Tauri 在 WebView2(Windows)与 WebKitGTK(Ubuntu)下的功耗与兼容性,确认后我再补高档的视觉参数。 +2. **`backdrop-filter` 毛玻璃**:弹窗遮罩想做轻微毛玻璃,WebKitGTK 老版本支持不稳定,需确认目标 WebKitGTK 版本;不支持则退化为纯半透明遮罩(本规范默认值即按退化方案给的)。 +3. **辉光 `box-shadow` 多层过渡**:逐帧过渡多层阴影在低端机可能引起重绘开销,§5 已按「低档移除」兜底;若总工有更省的实现(如预渲染光晕贴图),以技术方案为准,视觉等效即可。 +4. **CJK 字体渲染**:Ubuntu 上 `Noto Sans CJK SC` 依赖系统安装,需确认安装包是否内置兜底字体或声明依赖;不内置则字重 600 可能退化为合成加粗,验收时以实际渲染为准。 + +--- + +## 9. 验收对照(给总工) + +【v1.2 修订:形态语言行更新为「无独立指示条」的选中态标准;色彩行补紫色语义红线】 + +验收顺序固定(老板优先级):**① 密度 → ② 色彩 → ③ 排版三件套 → ④ 形态语言 → ⑤ 动效**。前一项不达标,后一项不看。 + +- **密度**:总览首屏任何纵向区间无效空白 ≤ 15%;下半屏必须有 CLI 状态网格(或同级信息模块)= 合格底线。 +- **色彩**:所有颜色只能来自 §2 Token;界面出现蓝色 = 不合格;状态色未贯穿到数字/关键文字 = 不合格;红色挂在已自愈条目上 = 不合格;【v1.2】「提示 / 待授权 / 未授权」未用 `--ad-attention` 紫、或紫色元素发光/呼吸 = 不合格。 +- **排版三件套**:时间格式违反 §3.1.2 规则、版本号/文件名未套 mono chip、承载信息文字对比度 < 4.5:1,任一 = 不合格。 +- **形态语言**:KPI 色条未贯通整宽、侧边栏选中态出现独立指示条(或任何与圆角背景块两张皮的第二形态)、选中态不是 §3.0 的「左缘生长渐变 + 内描边」、导航图标粗细不一,任一 = 不合格。 +- **动效**:呼吸点不同频同相位、透明度下限低于 40%、正常态绿点或紫色提示/待授权点呼吸,任一 = 不合格;主按钮 hover 未用 §2.6 双层扩散辉光 = 不合格。 +- 其余沿用:正文与表单区零光效;低档模式下无循环动画、无辉光、无粒子且信息完整性不变。 + +--- + +**v1.2 为施工开工的唯一视觉依据。** v3 样图(`overview3.png`)与本规范条文冲突时,以本规范条文为准;规范未覆盖处,参照 v3 样图并按 §1 原则推演。