From 6e5d26cd0dd85aa502e06849826f5e2308268915 Mon Sep 17 00:00:00 2001 From: uknes <120968852+uknes@users.noreply.github.com> Date: Tue, 24 Mar 2026 19:18:23 +0100 Subject: [PATCH] feat: add Windows port and production installer setup --- README.md | 95 +- package-lock.json | 1739 ++++++++++++++--- package.json | 28 +- src/main/claude/control-plane.ts | 21 +- src/main/claude/pty-run-manager.ts | 115 +- src/main/claude/run-manager.ts | 61 +- src/main/cli-env.ts | 60 +- src/main/index.ts | 269 ++- src/main/process-manager.ts | 81 +- src/main/skills/installer.ts | 52 +- src/preload/index.ts | 19 +- src/renderer/App.tsx | 51 +- src/renderer/components/ConversationView.tsx | 2 +- src/renderer/components/SetupWizard.tsx | 206 ++ src/renderer/components/StatusBar.tsx | 19 +- src/renderer/hooks/useHealthReconciliation.ts | 18 +- src/renderer/index.css | 14 + src/renderer/index.html | 23 +- src/renderer/stores/sessionStore.ts | 102 +- 19 files changed, 2328 insertions(+), 647 deletions(-) create mode 100644 src/renderer/components/SetupWizard.tsx diff --git a/README.md b/README.md index 596808a3..d7fe44af 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Clui CC — Command Line User Interface for Claude Code -A lightweight, transparent desktop overlay for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) on macOS. Clui CC wraps the Claude Code CLI in a floating pill interface with multi-tab sessions, a permission approval UI, voice input, and a skills marketplace. +A lightweight, transparent desktop overlay for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) on macOS and Windows. Clui CC wraps the Claude Code CLI in a floating pill interface with multi-tab sessions, a permission approval UI, voice input, and a skills marketplace. ## Demo @@ -10,8 +10,9 @@ A lightweight, transparent desktop overlay for [Claude Code](https://docs.anthro ## Features -- **Floating overlay** — transparent, click-through window that stays on top. Toggle with `⌥ + Space` (fallback: `Cmd+Shift+K`). +- **Floating overlay** — transparent, click-through window that stays on top. Toggle with `⌥ + Space` on Mac or `Alt + Space` on Windows. - **Multi-tab sessions** — each tab spawns its own `claude -p` process with independent session state. +- **Ollama Support (Experimental)** — Use cloud models (Qwen, GLM, etc.) via Ollama and skip the standard Claude login. - **Permission approval UI** — intercepts tool calls via PreToolUse HTTP hooks so you can review and approve/deny from the UI. - **Conversation history** — browse and resume past Claude Code sessions. - **Skills marketplace** — install plugins from Anthropic's GitHub repos without leaving Clui CC. @@ -25,18 +26,21 @@ A lightweight, transparent desktop overlay for [Claude Code](https://docs.anthro - **Human-in-the-loop safety** — tool calls are reviewed and approved in-app before execution. - **Session-native workflow** — each tab runs an independent Claude session you can resume later. - **Local-first** — everything runs through your local Claude CLI. No telemetry, no cloud dependency. +- **Ollama-friendly** — point to cloud ollama models to test without consuming Anthropic credits. ## How It Works ``` UI prompt → Main process spawns claude -p → NDJSON stream → live render - → tool call? → permission UI → approve/deny + → tool call? → permission UI → approve/deny ``` See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the full deep-dive. ## Install App (Recommended) +### macOS + The fastest way to get Clui CC running as a regular Mac app. This installs dependencies, voice support (Whisper), builds the app, copies it to `/Applications`, and launches it. **1) Clone the repo** @@ -52,9 +56,47 @@ Open the `clui-cc` folder in Finder and double-click `install-app.command`. > **First launch:** macOS may block the app because it's unsigned. Go to **System Settings → Privacy & Security → Open Anyway**. You only need to do this once. > **Folder cleanup:** the installer removes temporary `dist/` and `release/` folders after a successful install to keep the repo tidy. -

Press Option + Space to show or hide Clui CC

+### Windows + +Currently, Windows users must build the application from source. A pre-compiled installer will be available in the future. + +**1) Clone the repo** + +```powershell +git clone https://github.com/lcoutodemos/clui-cc.git +cd clui-cc +``` + +**2) Install Dependencies & Build** + +```powershell +npm install +npm run build +``` + +**3) Run the App** + +```powershell +npm run dev +``` + +To create a standalone installer for yourself: +```powershell +npm run dist:win +``` +The installer will be located in the `release/` folder. + +

Press Alt + Space (Windows) or Option + Space (Mac) to show/hide the overlay

-After the initial install, just open **Clui CC** from your Applications folder or Spotlight. +## Ollama Support (Skip Login) + +Clui CC includes experimental support for [Ollama](https://ollama.com/), allowing you to use cloud LLM models and skip the standard Claude Code login/API key check. + +**How to use:** +1. Ensure Ollama is running locally (`http://localhost:11434`). +2. In Clui CC, when prompted for setup, you can select **"Skip Login (Use Local LLM or API Key)"**. +3. Point your Claude CLI to Ollama by using specific model names (e.g., `qwen`, `glm`, `kimi`). +4. Clui CC will automatically set the `ANTHROPIC_BASE_URL` to your local Ollama instance and use dummy authentication.
Terminal / Developer Commands @@ -71,6 +113,7 @@ git clone https://github.com/lcoutodemos/clui-cc.git cd clui-cc ``` +**Mac:** ```bash ./commands/setup.command ``` @@ -79,13 +122,18 @@ cd clui-cc ./commands/start.command ``` -> Press **⌥ + Space** to show/hide the overlay. If your macOS input source claims that combo, use **Cmd+Shift+K**. - To stop: ```bash ./commands/stop.command ``` +**Windows:** +```powershell +npm install +npm run dev +``` + +> Press **Alt + Space** (Windows) or **⌥ + Space** (Mac) to show/hide the overlay. ### Developer Workflow @@ -106,8 +154,10 @@ Renderer changes update instantly. Main-process changes require restarting `npm | `./commands/setup.command` | Environment check + install dependencies | | `./commands/start.command` | Build and launch from source | | `./commands/stop.command` | Stop all Clui CC processes | +| `npm run dev` | Launch development server | | `npm run build` | Production build (no packaging) | -| `npm run dist` | Package as macOS `.app` into `release/` | +| `npm run dist:mac` | Package as macOS `.app` into `release/` | +| `npm run dist:win` | Package as Windows `.exe` installer into `release/` | | `npm run doctor` | Run environment diagnostic |
@@ -115,6 +165,19 @@ Renderer changes update instantly. Main-process changes require restarting `npm
Setup Prerequisites (Detailed) +### For Windows + +1. **Install Claude Code CLI**: + ```powershell + npm install -g @anthropic-ai/claude-code + ``` +2. **Authentication**: (Optional if using Ollama) + ```powershell + claude auth login + ``` + +### For macOS + You need **macOS 13+**. Then install these one at a time — copy each command and paste it into Terminal. **Step 1.** Install Xcode Command Line Tools (needed to compile native modules): @@ -147,7 +210,7 @@ python3 -m pip install --upgrade pip setuptools npm install -g @anthropic-ai/claude-code ``` -**Step 5.** Authenticate Claude Code (follow the prompts that appear): +**Step 5.** Authenticate Claude Code (follow the prompts that appear - Optional if using Ollama): ```bash claude @@ -220,7 +283,7 @@ Quick self-check: npm run doctor ``` -## Tested On +## Tested On (Mac) | Component | Version | |-----------|---------| @@ -230,9 +293,19 @@ npm run doctor | Electron | 33.x | | Claude Code CLI | 2.1.71 | +## Tested On (Windows) + +| Component | Version | +|-----------|---------| +| Windows | 11 | +| Node.js | 20.x LTS, 22.x | +| Python | 3.12 (with setuptools installed) | +| Electron | 33.x | +| Claude Code CLI | 2.1.71 | + ## Known Limitations -- **macOS only** — transparent overlay, tray icon, and node-pty are macOS-specific. Windows/Linux support is not currently implemented. +- **macOS & Windows support** — transparent overlay, tray icon, and multi-tab sessions are fully supported on both platforms. Linux support is not currently implemented. - **Requires Claude Code CLI** — Clui CC is a UI layer, not a standalone AI client. You need an authenticated `claude` CLI. - **Permission mode** — uses `--permission-mode default`. The PTY interactive transport is legacy and disabled by default. diff --git a/package-lock.json b/package-lock.json index 454afac8..cd76217d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "license": "MIT", "dependencies": { "@phosphor-icons/react": "^2.1.10", + "electron-log": "^5.4.3", "framer-motion": "^12.35.1", "node-pty": "^1.1.0", "react-markdown": "^9.0.0", @@ -18,6 +19,7 @@ "zustand": "^5.0.0" }, "devDependencies": { + "@electron/rebuild": "^3.7.1", "@tailwindcss/vite": "^4.2.1", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", @@ -475,6 +477,204 @@ "global-agent": "^3.0.0" } }, + "node_modules/@electron/node-gyp": { + "version": "10.2.0-electron.1", + "resolved": "git+ssh://git@github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2", + "integrity": "sha512-4MSBTT8y07YUDqf69/vSh80Hh791epYqGtWHO3zSKhYFwQg+gx9wi1PqbqP6YqC4WMsNxZ5l9oDmnWdK5pfCKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^8.1.0", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.2.1", + "nopt": "^6.0.0", + "proc-log": "^2.0.1", + "semver": "^7.3.5", + "tar": "^6.2.1", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/@electron/node-gyp/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/node-gyp/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/node-gyp/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/node-gyp/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/node-gyp/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/@electron/node-gyp/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/node-gyp/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/@electron/node-gyp/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@electron/node-gyp/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@electron/node-gyp/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/node-gyp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/node-gyp/node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/node-gyp/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@electron/node-gyp/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/@electron/notarize": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", @@ -603,86 +803,48 @@ } }, "node_modules/@electron/rebuild": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.3.tgz", - "integrity": "sha512-u9vpTHRMkOYCs/1FLiSVAFZ7FbjsXK+bQuzviJZa+lG7BHZl1nz52/IcGvwa3sk80/fc3llutBkbCq10Vh8WQA==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-3.7.2.tgz", + "integrity": "sha512-19/KbIR/DAxbsCkiaGMXIdPnMCJLkcf8AvGnduJtWBs/CBwiAjY1apCqOLVxrXg+rtXFCngbXhBanWjxLUt1Mg==", "dev": true, "license": "MIT", "dependencies": { + "@electron/node-gyp": "git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2", "@malept/cross-spawn-promise": "^2.0.0", + "chalk": "^4.0.0", "debug": "^4.1.1", "detect-libc": "^2.0.1", + "fs-extra": "^10.0.0", "got": "^11.7.0", - "graceful-fs": "^4.2.11", - "node-abi": "^4.2.0", - "node-api-version": "^0.2.1", - "node-gyp": "^11.2.0", + "node-abi": "^3.45.0", + "node-api-version": "^0.2.0", "ora": "^5.1.0", "read-binary-file-arch": "^1.0.6", "semver": "^7.3.5", - "tar": "^7.5.6", + "tar": "^6.0.5", "yargs": "^17.0.1" }, "bin": { "electron-rebuild": "lib/cli.js" }, "engines": { - "node": ">=22.12.0" + "node": ">=12.13.0" } }, - "node_modules/@electron/rebuild/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "node_modules/@electron/rebuild/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true, "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, "engines": { "node": ">=10" } }, - "node_modules/@electron/universal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", - "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@electron/asar": "^3.3.1", - "@malept/cross-spawn-promise": "^2.0.0", - "debug": "^4.3.1", - "dir-compare": "^4.2.0", - "fs-extra": "^11.1.1", - "minimatch": "^9.0.3", - "plist": "^3.1.0" - }, - "engines": { - "node": ">=16.4" - } - }, - "node_modules/@electron/universal/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@electron/universal/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@electron/universal/node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "node_modules/@electron/rebuild/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", "dependencies": { @@ -691,10 +853,10 @@ "universalify": "^2.0.0" }, "engines": { - "node": ">=14.14" + "node": ">=12" } }, - "node_modules/@electron/universal/node_modules/jsonfile": { + "node_modules/@electron/rebuild/node_modules/jsonfile": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", @@ -707,48 +869,211 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/@electron/universal/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "node_modules/@electron/rebuild/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=8" } }, - "node_modules/@electron/universal/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "node_modules/@electron/rebuild/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, "engines": { - "node": ">= 10.0.0" + "node": ">= 8" } }, - "node_modules/@electron/windows-sign": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", - "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "node_modules/@electron/rebuild/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, + "license": "ISC", "dependencies": { - "cross-dirname": "^0.1.0", - "debug": "^4.3.4", - "fs-extra": "^11.1.1", - "minimist": "^1.2.8", - "postject": "^1.0.0-alpha.6" + "yallist": "^4.0.0" }, - "bin": { + "engines": { + "node": ">=8" + } + }, + "node_modules/@electron/rebuild/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/rebuild/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/rebuild/node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/rebuild/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/rebuild/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/@electron/universal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", + "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.3.1", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/universal/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/windows-sign": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "cross-dirname": "^0.1.0", + "debug": "^4.3.4", + "fs-extra": "^11.1.1", + "minimist": "^1.2.8", + "postject": "^1.0.0-alpha.6" + }, + "bin": { "electron-windows-sign": "bin/electron-windows-sign.js" }, "engines": { @@ -1241,6 +1566,13 @@ "node": ">=18" } }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1509,17 +1841,33 @@ "dev": true, "license": "ISC" }, + "node_modules/@npmcli/agent/node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/@npmcli/fs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", - "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", "dev": true, "license": "ISC", "dependencies": { + "@gar/promisify": "^1.1.3", "semver": "^7.3.5" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/@npmcli/fs/node_modules/semver": { @@ -1535,6 +1883,51 @@ "node": ">=10" } }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/move-file/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/move-file/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@phosphor-icons/react": { "version": "2.1.10", "resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.10.tgz", @@ -2214,6 +2607,16 @@ "vite": "^5.2.0 || ^6 || ^7" } }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "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", @@ -2468,14 +2871,11 @@ "license": "MIT" }, "node_modules/abbrev": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", - "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } + "license": "ISC" }, "node_modules/agent-base": { "version": "7.1.4", @@ -2487,6 +2887,33 @@ "node": ">= 14" } }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ajv": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", @@ -2646,15 +3073,43 @@ "semver": "bin/semver.js" } }, - "node_modules/app-builder-lib/node_modules/ci-info": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", - "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "node_modules/app-builder-lib/node_modules/@electron/rebuild": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.3.tgz", + "integrity": "sha512-u9vpTHRMkOYCs/1FLiSVAFZ7FbjsXK+bQuzviJZa+lG7BHZl1nz52/IcGvwa3sk80/fc3llutBkbCq10Vh8WQA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.1.1", + "detect-libc": "^2.0.1", + "got": "^11.7.0", + "graceful-fs": "^4.2.11", + "node-abi": "^4.2.0", + "node-api-version": "^0.2.1", + "node-gyp": "^11.2.0", + "ora": "^5.1.0", + "read-binary-file-arch": "^1.0.6", + "semver": "^7.3.5", + "tar": "^7.5.6", + "yargs": "^17.0.1" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/app-builder-lib/node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" } ], "license": "MIT", @@ -2700,6 +3155,19 @@ "node": ">= 10.0.0" } }, + "node_modules/app-builder-lib/node_modules/node-abi": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.28.0.tgz", + "integrity": "sha512-Qfp5XZL1cJDOabOT8H5gnqMTmM4NjvYzHp4I/Kt/Sl76OVkOBBHRFlPspGV0hYvMoqQsypFjT/Yp7Km0beXW9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, "node_modules/app-builder-lib/node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -3065,27 +3533,33 @@ } }, "node_modules/cacache": { - "version": "19.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", - "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/fs": "^4.0.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "p-map": "^7.0.2", - "ssri": "^12.0.0", - "tar": "^7.4.3", - "unique-filename": "^4.0.0" + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/cacache/node_modules/balanced-match": { @@ -3105,51 +3579,199 @@ "balanced-match": "^1.0.0" } }, + "node_modules/cacache/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/cacache/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/cacache/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": ">=12" + } }, "node_modules/cacache/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.2" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cacache/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/cacache/node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/cacache/node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/cacache/node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -3314,6 +3936,16 @@ "node": ">=8" } }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -4002,6 +4634,15 @@ "node": ">= 10.0.0" } }, + "node_modules/electron-log": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.4.3.tgz", + "integrity": "sha512-sOUsM3LjZdugatazSQ/XTyNcw8dfvH1SYhXWiJyfYodAAKOZdHs0txPiLDXFzOZbhXgAgshQkshH2ccq0feyLQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/electron-publish": { "version": "26.8.1", "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.8.1.tgz", @@ -4537,18 +5178,38 @@ } }, "node_modules/fs-minipass": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, "license": "ISC", "dependencies": { - "minipass": "^7.0.3" + "minipass": "^3.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5004,6 +5665,16 @@ "node": ">= 14" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/iconv-corefoundation": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", @@ -5066,10 +5737,27 @@ "node": ">=0.8.19" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true, + "license": "ISC" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "license": "ISC", @@ -5165,6 +5853,13 @@ "node": ">=8" } }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -5678,28 +6373,105 @@ } }, "node_modules/make-fetch-happen": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", - "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/agent": "^3.0.0", - "cacache": "^19.0.1", - "http-cache-semantics": "^4.1.1", - "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "negotiator": "^1.0.0", - "proc-log": "^5.0.0", + "negotiator": "^0.6.3", "promise-retry": "^2.0.1", - "ssri": "^12.0.0" + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" } }, + "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/make-fetch-happen/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", @@ -6672,36 +7444,90 @@ } }, "node_modules/minipass-collect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", - "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", "dev": true, "license": "ISC", "dependencies": { - "minipass": "^7.0.3" + "minipass": "^3.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">= 8" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, + "node_modules/minipass-collect/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/minipass-fetch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", - "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", "dev": true, "license": "MIT", "dependencies": { - "minipass": "^7.0.3", + "minipass": "^3.1.6", "minipass-sized": "^1.0.3", - "minizlib": "^3.0.1" + "minizlib": "^2.1.2" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" }, "optionalDependencies": { "encoding": "^0.1.13" } }, + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-fetch/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/minipass-flush": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", @@ -6811,64 +7637,332 @@ "minipass": "^7.1.2" }, "engines": { - "node": ">= 18" + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/motion-dom": { + "version": "12.35.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.35.1.tgz", + "integrity": "sha512-7n6r7TtNOsH2UFSAXzTkfzOeO5616v9B178qBIjmu/WgEyJK0uqwytCEhwKBTuM/HJA40ptAw7hLFpxtPAMRZQ==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.29.2" + } + }, + "node_modules/motion-utils": { + "version": "12.29.2", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz", + "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.89.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", + "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-api-version/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", + "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/@npmcli/fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-gyp/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/node-gyp/node_modules/cacache": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/node-gyp/node_modules/make-fetch-happen": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/node-gyp/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, - "license": "MIT", - "peer": true, + "license": "ISC", "dependencies": { - "minimist": "^1.2.6" + "brace-expansion": "^2.0.2" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/motion-dom": { - "version": "12.35.1", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.35.1.tgz", - "integrity": "sha512-7n6r7TtNOsH2UFSAXzTkfzOeO5616v9B178qBIjmu/WgEyJK0uqwytCEhwKBTuM/HJA40ptAw7hLFpxtPAMRZQ==", - "license": "MIT", + "node_modules/node-gyp/node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", "dependencies": { - "motion-utils": "^12.29.2" + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" } }, - "node_modules/motion-utils": { - "version": "12.29.2", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz", - "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "node_modules/node-gyp/node_modules/minipass-fetch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" } }, - "node_modules/negotiator": { + "node_modules/node-gyp/node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", @@ -6878,51 +7972,46 @@ "node": ">= 0.6" } }, - "node_modules/node-abi": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.28.0.tgz", - "integrity": "sha512-Qfp5XZL1cJDOabOT8H5gnqMTmM4NjvYzHp4I/Kt/Sl76OVkOBBHRFlPspGV0hYvMoqQsypFjT/Yp7Km0beXW9g==", + "node_modules/node-gyp/node_modules/nopt": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "semver": "^7.6.3" + "abbrev": "^3.0.0" }, - "engines": { - "node": ">=22.12.0" - } - }, - "node_modules/node-abi/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", "bin": { - "semver": "bin/semver.js" + "nopt": "bin/nopt.js" }, "engines": { - "node": ">=10" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/node-addon-api": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", - "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "node_modules/node-gyp/node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", "dev": true, "license": "MIT", - "optional": true + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/node-api-version": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", - "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "node_modules/node-gyp/node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/node-api-version/node_modules/semver": { + "node_modules/node-gyp/node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", @@ -6935,42 +8024,43 @@ "node": ">=10" } }, - "node_modules/node-gyp": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", - "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", + "node_modules/node-gyp/node_modules/ssri": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^14.0.3", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "tar": "^7.4.3", - "tinyglobby": "^0.2.12", - "which": "^5.0.0" + "minipass": "^7.0.3" }, - "bin": { - "node-gyp": "bin/node-gyp.js" + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/unique-filename": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/node-gyp/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "node_modules/node-gyp/node_modules/unique-slug": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "imurmurhash": "^0.1.4" }, "engines": { - "node": ">=10" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/node-pty": { @@ -6997,19 +8087,19 @@ "license": "MIT" }, "node_modules/nopt": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", - "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", "dev": true, "license": "ISC", "dependencies": { - "abbrev": "^3.0.0" + "abbrev": "^1.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/normalize-url": { @@ -7113,13 +8203,16 @@ } }, "node_modules/p-map": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", - "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -7312,13 +8405,13 @@ } }, "node_modules/proc-log": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", - "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", + "integrity": "sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==", "dev": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/progress": { @@ -7331,6 +8424,13 @@ "node": ">=0.4.0" } }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true, + "license": "ISC" + }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", @@ -7892,18 +8992,31 @@ } }, "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" }, "engines": { - "node": ">= 14" + "node": ">= 10" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" } }, "node_modules/source-map": { @@ -7956,18 +9069,38 @@ "optional": true }, "node_modules/ssri": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", - "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", "dev": true, "license": "ISC", "dependencies": { - "minipass": "^7.0.3" + "minipass": "^3.1.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ssri/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, + "node_modules/ssri/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/stat-mode": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", @@ -8395,29 +9528,29 @@ } }, "node_modules/unique-filename": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", - "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", "dev": true, "license": "ISC", "dependencies": { - "unique-slug": "^5.0.0" + "unique-slug": "^3.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/unique-slug": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", - "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/unist-util-is": { diff --git a/package.json b/package.json index 77a26ebc..00f5aa5f 100644 --- a/package.json +++ b/package.json @@ -16,12 +16,14 @@ "dev": "electron-vite dev", "build": "electron-vite build", "preview": "electron-vite preview", - "dist": "electron-vite build --mode production && electron-builder --mac --dir", - "doctor": "bash scripts/doctor.sh", - "postinstall": "electron-builder install-app-deps && bash scripts/patch-dev-icon.sh" + "dist:win": "electron-vite build --mode production && electron-builder --win", + "dist:mac": "electron-vite build --mode production && electron-builder --mac --dir", + "doctor": "node scripts/doctor.js", + "postinstall": "electron-builder install-app-deps" }, "dependencies": { "@phosphor-icons/react": "^2.1.10", + "electron-log": "^5.4.3", "framer-motion": "^12.35.1", "node-pty": "^1.1.0", "react-markdown": "^9.0.0", @@ -39,8 +41,21 @@ "dist/preload/**/*", "dist/renderer/**/*", "resources/**/*", + "skills/**/*", "package.json" ], + "win": { + "target": "nsis", + "icon": "resources/icon.png" + }, + "nsis": { + "oneClick": false, + "allowToChangeInstallationDirectory": true, + "createDesktopShortcut": true, + "createStartMenuShortcut": true, + "shortcutName": "Clui CC", + "artifactName": "${productName}-Setup-${version}.${ext}" + }, "mac": { "icon": "resources/icon.icns", "entitlements": "resources/entitlements.mac.plist", @@ -50,6 +65,11 @@ } } }, + "electronRebuildConfig": { + "additionalArgs": [ + "/p:vcc_SpectreMitigation=false" + ] + }, "devDependencies": { "@tailwindcss/vite": "^4.2.1", "@types/react": "^19.0.0", @@ -66,4 +86,4 @@ "typescript": "^5.7.0", "vite": "^6.0.0" } -} +} \ No newline at end of file diff --git a/src/main/claude/control-plane.ts b/src/main/claude/control-plane.ts index 2ae90bc9..e028c5d6 100644 --- a/src/main/claude/control-plane.ts +++ b/src/main/claude/control-plane.ts @@ -429,10 +429,10 @@ export class ControlPlane extends EventEmitter { // ─── Tab Lifecycle ─── - createTab(): string { - const tabId = crypto.randomUUID() + createTab(tabId?: string): string { + const id = tabId || (typeof crypto !== 'undefined' && (crypto as any).randomUUID ? (crypto as any).randomUUID() : require('crypto').randomUUID()) const entry: TabRegistryEntry = { - tabId, + tabId: id, claudeSessionId: null, status: 'idle', activeRequestId: null, @@ -441,16 +441,16 @@ export class ControlPlane extends EventEmitter { lastActivityAt: Date.now(), promptCount: 0, } - this.tabs.set(tabId, entry) - log(`Tab created: ${tabId}`) - return tabId + this.tabs.set(id, entry) + log(`Tab created: ${id}`) + return id } /** * Eagerly initialize a session for a tab by running a minimal prompt. * Populates session metadata (model, MCP servers, tools) without visible messages. */ - initSession(tabId: string): void { + initSession(tabId: string, options?: RunOptions): void { const tab = this.tabs.get(tabId) if (!tab) return @@ -459,8 +459,9 @@ export class ControlPlane extends EventEmitter { this.submitPrompt(tabId, requestId, { prompt: 'hi', - projectPath: process.cwd(), + projectPath: options?.projectPath || process.cwd(), maxTurns: 1, + model: options?.model, }).catch((err) => { this.initRequestIds.delete(requestId) log(`Init session failed for tab ${tabId}: ${(err as Error).message}`) @@ -758,6 +759,10 @@ export class ControlPlane extends EventEmitter { return this.tabs.get(tabId) } + getClaudeBinaryPath(): string { + return this.runManager.getBinaryPath() + } + getEnrichedError(requestId: string, exitCode: number | null): EnrichedError { if (this.ptyRuns.has(requestId)) { return this.ptyRunManager.getEnrichedError(requestId, exitCode) diff --git a/src/main/claude/pty-run-manager.ts b/src/main/claude/pty-run-manager.ts index be217232..717e08a2 100644 --- a/src/main/claude/pty-run-manager.ts +++ b/src/main/claude/pty-run-manager.ts @@ -21,7 +21,7 @@ import { join } from 'path' import { execSync } from 'child_process' import { appendFileSync, chmodSync, existsSync, statSync } from 'fs' import type { NormalizedEvent, RunOptions, EnrichedError } from '../../shared/types' -import { getCliEnv } from '../cli-env' +import { getCliEnv, findClaudeBinary } from '../cli-env' // node-pty is a native module — require at runtime to avoid Vite bundling issues // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -32,22 +32,22 @@ try { // Will be set when first needed — fail at startRun() time, not import time } -const LOG_FILE = join(homedir(), '.clui-debug.log') +import electronLog from 'electron-log' +const log = Object.assign( + (...args: any[]) => electronLog.info(...args), + { + info: (...args: any[]) => electronLog.info(...args), + error: (...args: any[]) => electronLog.error(...args), + warn: (...args: any[]) => electronLog.warn(...args), + debug: (...args: any[]) => electronLog.debug(...args), + } +) + const MAX_RING_LINES = 100 -const PTY_BUFFER_SIZE = 50 // rolling window of cleaned lines for parser context -const PERMISSION_TIMEOUT_MS = 5 * 60 * 1000 // 5 minutes +const PTY_BUFFER_SIZE = 50 +const PERMISSION_TIMEOUT_MS = 5 * 60 * 1000 const QUIESCENCE_MS = 2000 -function log(msg: string): void { - const line = `[${new Date().toISOString()}] [PtyRunManager] ${msg}\n` - try { appendFileSync(LOG_FILE, line) } catch {} -} - -// ─── ANSI Stripping ─── - -/** - * Strip ANSI escape sequences (colors, cursor movement, clear line, etc.) - */ function stripAnsi(str: string): string { // Covers CSI sequences including private modes like ?2004h return str.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, '') @@ -281,7 +281,7 @@ export class PtyRunManager extends EventEmitter { super() this.claudeBinary = this._findClaudeBinary() this._ensureSpawnHelperExecutable() - log(`Claude binary: ${this.claudeBinary}`) + log.info(`Claude binary: ${this.claudeBinary}`) } /** @@ -296,50 +296,47 @@ export class PtyRunManager extends EventEmitter { path.dirname(pkgPath), 'prebuilds', `${process.platform}-${process.arch}`, - 'spawn-helper', + process.platform === 'win32' ? 'spawn-helper.exe' : 'spawn-helper', ) if (!existsSync(helperPath)) return const st = statSync(helperPath) const isExecutable = (st.mode & 0o111) !== 0 if (!isExecutable) { chmodSync(helperPath, 0o755) - log(`Fixed spawn-helper permissions: ${helperPath}`) + log.info(`Fixed spawn-helper permissions: ${helperPath}`) } } catch (err) { - log(`spawn-helper permission check failed: ${(err as Error).message}`) + log.info(`spawn-helper permission check failed: ${(err as Error).message}`) } } private _findClaudeBinary(): string { - const candidates = [ - '/usr/local/bin/claude', - '/opt/homebrew/bin/claude', - join(homedir(), '.npm-global/bin/claude'), - ] - - for (const c of candidates) { - try { - execSync(`test -x "${c}"`, { stdio: 'ignore' }) - return c - } catch {} - } + return findClaudeBinary() + } - try { - return execSync('/bin/zsh -ilc "whence -p claude"', { encoding: 'utf-8', env: getCliEnv() }).trim() - } catch {} + private _getEnv(model?: string): NodeJS.ProcessEnv { + const env = getCliEnv() - try { - return execSync('/bin/bash -lc "which claude"', { encoding: 'utf-8', env: getCliEnv() }).trim() - } catch {} + // Ollama support: point Claude Code to the local Ollama provider if an Ollama model is selected. + const isOllama = model && ( + model.includes(':cloud') || + model.includes('qwen') || + model.includes('glm') || + model.includes('kimi') || + model.includes('minimax') + ) - return 'claude' - } + if (isOllama) { + env.ANTHROPIC_AUTH_TOKEN = 'ollama' + env.ANTHROPIC_API_KEY = 'ollama-unused-key' + env.ANTHROPIC_BASE_URL = 'http://localhost:11434' + } - private _getEnv(): NodeJS.ProcessEnv { - const env = getCliEnv() - const binDir = this.claudeBinary.substring(0, this.claudeBinary.lastIndexOf('/')) + const sep = process.platform === 'win32' ? '\\' : '/' + const binDir = this.claudeBinary.substring(0, this.claudeBinary.lastIndexOf(sep)) + const pathSep = process.platform === 'win32' ? ';' : ':' if (env.PATH && !env.PATH.includes(binDir)) { - env.PATH = `${binDir}:${env.PATH}` + env.PATH = `${binDir}${pathSep}${env.PATH}` } return env @@ -373,18 +370,18 @@ export class PtyRunManager extends EventEmitter { // Pass prompt as positional argument args.push(options.prompt) - log(`Starting PTY run ${requestId}: ${this.claudeBinary} ${args.join(' ')}`) - log(`Prompt: ${options.prompt.substring(0, 200)}`) + log.info(`Starting PTY run ${requestId}: ${this.claudeBinary} ${args.join(' ')}`) + log.info(`Prompt: ${options.prompt.substring(0, 200)}`) const ptyProcess = pty.spawn(this.claudeBinary, args, { name: 'xterm-256color', cols: 120, rows: 40, cwd, - env: this._getEnv(), + env: this._getEnv(options.model), }) - log(`Spawned PTY PID: ${ptyProcess.pid}`) + log.info(`Spawned PTY PID: ${ptyProcess.pid}`) const handle: PtyRunHandle = { runId: requestId, @@ -467,7 +464,7 @@ export class PtyRunManager extends EventEmitter { }) ptyProcess.onExit(({ exitCode, signal }) => { - log(`PTY exited [${requestId}]: code=${exitCode} signal=${signal}`) + log.info(`PTY exited [${requestId}]: code=${exitCode} signal=${signal}`) // Clear permission timeout if (handle.permissionTimeout) { @@ -524,7 +521,7 @@ export class PtyRunManager extends EventEmitter { // Push to rolling buffer this._ringPushBuffer(handle.ptyBuffer, cleaned) - log(`PTY line [${requestId}]: ${cleaned.substring(0, 200)}`) + log.info(`PTY line [${requestId}]: ${cleaned.substring(0, 200)}`) // ─── Try to extract session ID ─── if (!handle.emittedSessionInit) { @@ -563,7 +560,7 @@ export class PtyRunManager extends EventEmitter { // ─── Permission phase: collecting detection context ─── if (handle.permissionPhase === 'detecting' || handle.permissionPhase === 'idle') { this._checkPermissionInBuffer(requestId, handle, cleaned) - if (handle.permissionPhase === 'waiting_user') { + if ((handle.permissionPhase as any) === 'waiting_user') { return // Permission prompt detected and emitted } } @@ -682,7 +679,7 @@ export class PtyRunManager extends EventEmitter { } // Permission prompt detected! - log(`Permission prompt detected [${requestId}]: tool=${permission.toolName}, options=${permission.options.length}`) + log.info(`Permission prompt detected [${requestId}]: tool=${permission.toolName}, options=${permission.options.length}`) handle.pendingPermission = permission handle.permissionPhase = 'waiting_user' @@ -709,7 +706,7 @@ export class PtyRunManager extends EventEmitter { // Set timeout for user response handle.permissionTimeout = setTimeout(() => { if (handle.permissionPhase === 'waiting_user') { - log(`Permission timeout [${requestId}] — auto-denying`) + log.info(`Permission timeout [${requestId}] — auto-denying`) this.emit('normalized', requestId, { type: 'text_chunk', text: '\n[Permission timed out — automatically denied after 5 minutes]\n', @@ -730,12 +727,12 @@ export class PtyRunManager extends EventEmitter { respondToPermission(requestId: string, _questionId: string, optionId: string): boolean { const handle = this.activeRuns.get(requestId) if (!handle) { - log(`respondToPermission: no active run for ${requestId}`) + log.info(`respondToPermission: no active run for ${requestId}`) return false } if (handle.permissionPhase !== 'waiting_user' || !handle.pendingPermission) { - log(`respondToPermission: not waiting for permission (phase=${handle.permissionPhase})`) + log.info(`respondToPermission: not waiting for permission (phase=${handle.permissionPhase})`) return false } @@ -747,11 +744,11 @@ export class PtyRunManager extends EventEmitter { const option = handle.pendingPermission.options.find((o) => o.optionId === optionId) if (!option) { - log(`respondToPermission: option ${optionId} not found`) + log.info(`respondToPermission: option ${optionId} not found`) return false } - log(`respondToPermission [${requestId}]: optionId=${optionId}, label=${option.label}`) + log.info(`respondToPermission [${requestId}]: optionId=${optionId}, label=${option.label}`) // ─── Send keystrokes to PTY ─── // The Claude interactive CLI uses Ink's Select component. @@ -782,7 +779,7 @@ export class PtyRunManager extends EventEmitter { }, 50) } } catch (err) { - log(`respondToPermission: write error: ${(err as Error).message}`) + log.info(`respondToPermission: write error: ${(err as Error).message}`) return false } @@ -806,7 +803,7 @@ export class PtyRunManager extends EventEmitter { const handle = this.activeRuns.get(requestId) if (!handle) return false - log(`Cancelling PTY run ${requestId}`) + log.info(`Cancelling PTY run ${requestId}`) // Clear permission timeout if (handle.permissionTimeout) { @@ -822,7 +819,7 @@ export class PtyRunManager extends EventEmitter { // Fallback: kill after 5s setTimeout(() => { if (this.activeRuns.has(requestId)) { - log(`Force killing PTY run ${requestId}`) + log.info(`Force killing PTY run ${requestId}`) try { handle.pty.kill() } catch {} @@ -839,7 +836,7 @@ export class PtyRunManager extends EventEmitter { const handle = this.activeRuns.get(requestId) if (!handle) return false - log(`Writing to PTY stdin [${requestId}]: ${message.substring(0, 200)}`) + log.info(`Writing to PTY stdin [${requestId}]: ${message.substring(0, 200)}`) try { handle.pty.write(message) return true diff --git a/src/main/claude/run-manager.ts b/src/main/claude/run-manager.ts index 0068b69e..74e2155b 100644 --- a/src/main/claude/run-manager.ts +++ b/src/main/claude/run-manager.ts @@ -5,7 +5,7 @@ import { join } from 'path' import { StreamParser } from '../stream-parser' import { normalize } from './event-normalizer' import { log as _log } from '../logger' -import { getCliEnv } from '../cli-env' +import { getCliEnv, findClaudeBinary } from '../cli-env' import type { ClaudeEvent, NormalizedEvent, RunOptions, EnrichedError } from '../../shared/types' const MAX_RING_LINES = 100 @@ -101,35 +101,34 @@ export class RunManager extends EventEmitter { } private _findClaudeBinary(): string { - const candidates = [ - '/usr/local/bin/claude', - '/opt/homebrew/bin/claude', - join(homedir(), '.npm-global/bin/claude'), - ] - - for (const c of candidates) { - try { - execSync(`test -x "${c}"`, { stdio: 'ignore' }) - return c - } catch {} - } - - try { - return execSync('/bin/zsh -ilc "whence -p claude"', { encoding: 'utf-8', env: getCliEnv() }).trim() - } catch {} - - try { - return execSync('/bin/bash -lc "which claude"', { encoding: 'utf-8', env: getCliEnv() }).trim() - } catch {} - - return 'claude' + return findClaudeBinary() } - private _getEnv(): NodeJS.ProcessEnv { + private _getEnv(model?: string): NodeJS.ProcessEnv { const env = getCliEnv() - const binDir = this.claudeBinary.substring(0, this.claudeBinary.lastIndexOf('/')) - if (env.PATH && !env.PATH.includes(binDir)) { - env.PATH = `${binDir}:${env.PATH}` + + // Ollama support: point Claude Code to the local Ollama provider if an Ollama model is selected. + const isOllama = model && ( + model.includes(':cloud') || + model.includes('qwen') || + model.includes('glm') || + model.includes('kimi') || + model.includes('minimax') + ) + + if (isOllama) { + env.ANTHROPIC_AUTH_TOKEN = 'ollama' + env.ANTHROPIC_API_KEY = 'ollama-unused-key' + env.ANTHROPIC_BASE_URL = 'http://localhost:11434' + } + + const lastSep = this.claudeBinary.lastIndexOf(process.platform === 'win32' ? '\\' : '/') + if (lastSep !== -1) { + const binDir = this.claudeBinary.substring(0, lastSep) + const pathSep = process.platform === 'win32' ? ';' : ':' + if (env.PATH && !env.PATH.includes(binDir)) { + env.PATH = `${binDir}${pathSep}${env.PATH}` + } } return env @@ -200,7 +199,7 @@ export class RunManager extends EventEmitter { const child = spawn(this.claudeBinary, args, { stdio: ['pipe', 'pipe', 'pipe'], cwd, - env: this._getEnv(), + env: this._getEnv(options.model), }) log(`Spawned PID: ${child.pid}`) @@ -262,7 +261,7 @@ export class RunManager extends EventEmitter { // stays alive waiting for more input; closing stdin triggers clean exit. if (raw.type === 'result') { log(`Run complete [${requestId}]: sawPermissionRequest=${handle.sawPermissionRequest}, denials=${handle.permissionDenials.length}`) - try { child.stdin?.end() } catch {} + try { child.stdin?.end() } catch { } } }) @@ -383,6 +382,10 @@ export class RunManager extends EventEmitter { return Array.from(this.activeRuns.keys()) } + getBinaryPath(): string { + return this.claudeBinary + } + private _ringPush(buffer: string[], line: string): void { buffer.push(line) if (buffer.length > MAX_RING_LINES) { diff --git a/src/main/cli-env.ts b/src/main/cli-env.ts index 60efe067..32aeb2db 100644 --- a/src/main/cli-env.ts +++ b/src/main/cli-env.ts @@ -2,9 +2,14 @@ import { execSync } from 'child_process' let cachedPath: string | null = null -function appendPathEntries(target: string[], seen: Set, rawPath: string | undefined): void { +export function clearCliPathCache(): void { + cachedPath = null +} + +function appendPathEntries(target: string[], seen: Set, rawPath: string | undefined, forceSep?: string): void { if (!rawPath) return - for (const entry of rawPath.split(':')) { + const sep = forceSep || (process.platform === 'win32' ? ';' : ':') + for (const entry of rawPath.split(sep)) { const p = entry.trim() if (!p || seen.has(p)) continue seen.add(p) @@ -22,14 +27,18 @@ export function getCliPath(): string { appendPathEntries(ordered, seen, process.env.PATH) // Add common binary locations used on macOS (Homebrew + system). - appendPathEntries(ordered, seen, '/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin') + if (process.platform === 'darwin') { + appendPathEntries(ordered, seen, '/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin', ':') + } - // Try interactive login shell first so nvm/asdf/etc. PATH hooks are loaded. - const pathCommands = [ - '/bin/zsh -ilc "echo $PATH"', - '/bin/zsh -lc "echo $PATH"', - '/bin/bash -lc "echo $PATH"', - ] + // Context-aware shell probing + const pathCommands = process.platform === 'win32' + ? [] // Windows handles PATH differently, process.env.PATH is usually sufficient + : [ + '/bin/zsh -ilc "echo $PATH"', + '/bin/zsh -lc "echo $PATH"', + '/bin/bash -lc "echo $PATH"', + ] for (const cmd of pathCommands) { try { @@ -40,10 +49,41 @@ export function getCliPath(): string { } } - cachedPath = ordered.join(':') + const sep = process.platform === 'win32' ? ';' : ':' + cachedPath = ordered.join(sep) return cachedPath } +export function findClaudeBinary(): string { + const { existsSync } = require('fs') + const { join } = require('path') + const { homedir } = require('os') + + const candidates = process.platform === 'win32' + ? [ + join(homedir(), 'AppData/Roaming/npm/claude.cmd'), + join(process.env.PROGRAMFILES || 'C:/Program Files', 'nodejs/claude.cmd'), + ] + : [ + '/usr/local/bin/claude', + '/opt/homebrew/bin/claude', + join(homedir(), '.npm-global/bin/claude'), + ] + + for (const c of candidates) { + if (existsSync(c)) return c + } + + try { + const { execSync } = require('child_process') + const cmd = process.platform === 'win32' ? 'where claude' : 'which claude' + const result = execSync(cmd, { encoding: 'utf-8', env: getCliEnv() }).trim() + if (result) return result.split(/\r?\n/)[0].trim() + } catch {} + + return 'claude' +} + export function getCliEnv(extraEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv { const env: NodeJS.ProcessEnv = { ...process.env, diff --git a/src/main/index.ts b/src/main/index.ts index 2036c450..721283e5 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -6,18 +6,42 @@ import { homedir } from 'os' import { ControlPlane } from './claude/control-plane' import { ensureSkills, type SkillStatus } from './skills/installer' import { fetchCatalog, listInstalled, installPlugin, uninstallPlugin } from './marketplace/catalog' -import { log as _log, LOG_FILE, flushLogs } from './logger' import { getCliEnv } from './cli-env' import { IPC } from '../shared/types' import type { RunOptions, NormalizedEvent, EnrichedError } from '../shared/types' +import electronLog from 'electron-log' +const log = Object.assign( + (...args: any[]) => electronLog.info(...args), + { + info: (...args: any[]) => electronLog.info(...args), + error: (...args: any[]) => electronLog.error(...args), + warn: (...args: any[]) => electronLog.warn(...args), + debug: (...args: any[]) => electronLog.debug(...args), + } +) -const DEBUG_MODE = process.env.CLUI_DEBUG === '1' -const SPACES_DEBUG = DEBUG_MODE || process.env.CLUI_SPACES_DEBUG === '1' +// Configure electron-log +electronLog.transports.file.resolvePathFn = () => join(app.getPath('userData'), 'logs/main.log') +const LOG_FILE = join(app.getPath('userData'), 'logs/main.log') + +function flushLogs(): void { + try { + // electron-log flushes automatically, but ensure any pending writes complete + electronLog.info('Clui CC shutting down...') + } catch {} +} -function log(msg: string): void { - _log('main', msg) +// Force transparent visuals on Windows to prevent white border artifacts +if (process.platform === 'win32') { + app.commandLine.appendSwitch('enable-transparent-visuals') + app.commandLine.appendSwitch('disable-gpu-compositing') } +electronLog.info('Clui CC starting...') + +const DEBUG_MODE = process.env.CLUI_DEBUG === '1' +const SPACES_DEBUG = DEBUG_MODE || process.env.CLUI_SPACES_DEBUG === '1' + let mainWindow: BrowserWindow | null = null let tray: Tray | null = null let screenshotCounter = 0 @@ -28,6 +52,10 @@ const INTERACTIVE_PTY = process.env.CLUI_INTERACTIVE_PERMISSIONS_PTY === '1' const controlPlane = new ControlPlane(INTERACTIVE_PTY) +if (process.platform === 'win32') { + app.setName(' ') // Overrides productName fallback for window title +} + // Keep native width fixed to avoid renderer animation vs setBounds race. // The UI itself still launches in compact mode; extra width is transparent/click-through. const BAR_WIDTH = 1040 @@ -45,7 +73,7 @@ function broadcast(channel: string, ...args: unknown[]): void { function snapshotWindowState(reason: string): void { if (!SPACES_DEBUG) return if (!mainWindow || mainWindow.isDestroyed()) { - log(`[spaces] ${reason} window=none`) + log.info(`[spaces] ${reason} window=none`) return } @@ -55,7 +83,7 @@ function snapshotWindowState(reason: string): void { const visibleOnAll = mainWindow.isVisibleOnAllWorkspaces() const wcFocused = mainWindow.webContents.isFocused() - log( + log.info( `[spaces] ${reason} ` + `vis=${mainWindow.isVisible()} focused=${mainWindow.isFocused()} wcFocused=${wcFocused} ` + `alwaysOnTop=${mainWindow.isAlwaysOnTop()} allWs=${visibleOnAll} ` + @@ -114,17 +142,34 @@ function createWindow(): void { alwaysOnTop: true, skipTaskbar: true, hasShadow: false, - roundedCorners: true, + roundedCorners: false, backgroundColor: '#00000000', show: false, - icon: join(__dirname, '../../resources/icon.icns'), + icon: join(__dirname, `../../resources/icon.${process.platform === 'win32' ? 'ico' : 'icns'}`), + ...(process.platform === 'win32' ? ({ + autoHideMenuBar: true, + thickFrame: false, + } as any) : {}), + title: ' ', webPreferences: { preload: join(__dirname, '../preload/index.js'), contextIsolation: true, nodeIntegration: false, + backgroundThrottling: false, }, }) + if (process.platform === 'win32') { + mainWindow.removeMenu() + mainWindow.setMenu(null) + mainWindow.setMenuBarVisibility(false) + } + + // Trap and kill any attempt to set a window title (prevents white bar on Windows focus/blur) + mainWindow.on('page-title-updated', (e) => { + e.preventDefault() + }) + // Belt-and-suspenders: panel already joins all spaces and floats, // but explicit flags ensure correct behavior on older Electron builds. mainWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }) @@ -136,6 +181,15 @@ function createWindow(): void { // { forward: true } ensures mousemove events still reach the renderer // so it can toggle click-through off when cursor enters interactive UI. mainWindow?.setIgnoreMouseEvents(true, { forward: true }) + + // Deep-trap Alt+Space even when focused (Windows system menu bypass) + mainWindow?.webContents.on('before-input-event', (event, input) => { + if (input.type === 'keyDown' && input.alt && input.key === ' ' && process.platform === 'win32') { + event.preventDefault() + toggleWindow('before-input-event') + } + }) + if (process.env.ELECTRON_RENDERER_URL) { mainWindow?.webContents.openDevTools({ mode: 'detach' }) } @@ -150,6 +204,16 @@ function createWindow(): void { } }) + // Prevent Windows from re-showing the title bar/menu on blur + if (process.platform === 'win32') { + mainWindow.on('blur', () => { + mainWindow?.setMenuBarVisibility(false) + }) + mainWindow.on('focus', () => { + mainWindow?.setMenuBarVisibility(false) + }) + } + if (process.env.ELECTRON_RENDERER_URL) { mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL) } else { @@ -179,7 +243,7 @@ function showWindow(source = 'unknown'): void { mainWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }) if (SPACES_DEBUG) { - log(`[spaces] showWindow#${toggleId} source=${source} move-to-display id=${display.id}`) + log.info(`[spaces] showWindow#${toggleId} source=${source} move-to-display id=${display.id}`) snapshotWindowState(`showWindow#${toggleId} pre-show`) } // As an accessory app (app.dock.hide), show() + focus gives keyboard @@ -194,7 +258,7 @@ function toggleWindow(source = 'unknown'): void { if (!mainWindow) return const toggleId = ++toggleSequence if (SPACES_DEBUG) { - log(`[spaces] toggle#${toggleId} source=${source} start`) + log.info(`[spaces] toggle#${toggleId} source=${source} start`) snapshotWindowState(`toggle#${toggleId} pre`) } @@ -245,35 +309,61 @@ ipcMain.handle(IPC.START, async () => { log('IPC START — fetching static CLI info') const { execSync } = require('child_process') + const isNodeInstalled = true // If we're running Electron, Node is effectively here + let isClaudeInstalled = false + let isAuthValid = false + + const claudePath = controlPlane.getClaudeBinaryPath() let version = 'unknown' try { - version = execSync('claude -v', { encoding: 'utf-8', timeout: 5000, env: getCliEnv() }).trim() + version = execSync(`"${claudePath}" -v`, { encoding: 'utf-8', timeout: 5000, env: getCliEnv() }).trim() + isClaudeInstalled = true } catch {} let auth: { email?: string; subscriptionType?: string; authMethod?: string } = {} try { - const raw = execSync('claude auth status', { encoding: 'utf-8', timeout: 5000, env: getCliEnv() }).trim() - auth = JSON.parse(raw) + // Note: claude auth status might not return JSON directly; + // we'll try to parse it but fall back to checking if it ran successfully. + const raw = execSync(`"${claudePath}" auth status`, { encoding: 'utf-8', timeout: 5000, env: getCliEnv() }).trim() + try { + auth = JSON.parse(raw) + } catch { + // If not JSON, check for common patterns like "Logged in as" + if (raw.toLowerCase().includes('logged in as')) { + const emailMatch = raw.match(/as\s+([^\s]+)/i) + if (emailMatch) auth.email = emailMatch[1] + } + } + isAuthValid = !!auth.email || raw.toLowerCase().includes('logged in as') } catch {} let mcpServers: string[] = [] try { - const raw = execSync('claude mcp list', { encoding: 'utf-8', timeout: 5000, env: getCliEnv() }).trim() + const raw = execSync(`"${claudePath}" mcp list`, { encoding: 'utf-8', timeout: 5000, env: getCliEnv() }).trim() if (raw) mcpServers = raw.split('\n').filter(Boolean) } catch {} - return { version, auth, mcpServers, projectPath: process.cwd(), homePath: require('os').homedir() } + return { + version, + auth, + mcpServers, + projectPath: process.cwd(), + homePath: require('os').homedir(), + isNodeInstalled, + isClaudeInstalled, + isAuthValid, + } }) -ipcMain.handle(IPC.CREATE_TAB, () => { - const tabId = controlPlane.createTab() - log(`IPC CREATE_TAB → ${tabId}`) - return { tabId } +ipcMain.handle(IPC.CREATE_TAB, (_event, tabId?: string) => { + const id = controlPlane.createTab(tabId) + log(`IPC CREATE_TAB → ${id}`) + return { tabId: id } }) -ipcMain.on(IPC.INIT_SESSION, (_event, tabId: string) => { +ipcMain.on(IPC.INIT_SESSION, (_event, tabId: string, options?: RunOptions) => { log(`IPC INIT_SESSION: ${tabId}`) - controlPlane.initSession(tabId) + controlPlane.initSession(tabId, options) }) ipcMain.on(IPC.RESET_TAB_SESSION, (_event, tabId: string) => { @@ -283,9 +373,9 @@ ipcMain.on(IPC.RESET_TAB_SESSION, (_event, tabId: string) => { ipcMain.handle(IPC.PROMPT, async (_event, { tabId, requestId, options }: { tabId: string; requestId: string; options: RunOptions }) => { if (DEBUG_MODE) { - log(`IPC PROMPT: tab=${tabId} req=${requestId} prompt="${options.prompt.substring(0, 100)}"`) + log.info(`IPC PROMPT: tab=${tabId} req=${requestId} prompt="${options.prompt.substring(0, 100)}"`) } else { - log(`IPC PROMPT: tab=${tabId} req=${requestId}`) + log.info(`IPC PROMPT: tab=${tabId} req=${requestId}`) } if (!tabId) { @@ -299,7 +389,7 @@ ipcMain.handle(IPC.PROMPT, async (_event, { tabId, requestId, options }: { tabId await controlPlane.submitPrompt(tabId, requestId, options) } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err) - log(`PROMPT error: ${msg}`) + log.info(`PROMPT error: ${msg}`) throw err } }) @@ -334,7 +424,7 @@ ipcMain.handle(IPC.CLOSE_TAB, (_event, tabId: string) => { ipcMain.on(IPC.SET_PERMISSION_MODE, (_event, mode: string) => { if (mode !== 'ask' && mode !== 'auto') { - log(`IPC SET_PERMISSION_MODE: invalid mode "${mode}" — ignoring`) + log.info(`IPC SET_PERMISSION_MODE: invalid mode "${mode}" — ignoring`) return } log(`IPC SET_PERMISSION_MODE: ${mode}`) @@ -352,7 +442,7 @@ ipcMain.handle(IPC.LIST_SESSIONS, async (_e, projectPath?: string) => { const cwd = projectPath || process.cwd() // Validate projectPath — reject null bytes, newlines, non-absolute paths if (/[\0\r\n]/.test(cwd) || !cwd.startsWith('/')) { - log(`LIST_SESSIONS: rejected invalid projectPath: ${cwd}`) + log.info(`LIST_SESSIONS: rejected invalid projectPath: ${cwd}`) return [] } // Claude stores project sessions at ~/.claude/projects// @@ -360,7 +450,7 @@ ipcMain.handle(IPC.LIST_SESSIONS, async (_e, projectPath?: string) => { const encodedPath = cwd.replace(/\//g, '-') const sessionsDir = join(homedir(), '.claude', 'projects', encodedPath) if (!existsSync(sessionsDir)) { - log(`LIST_SESSIONS: directory not found: ${sessionsDir}`) + log.info(`LIST_SESSIONS: directory not found: ${sessionsDir}`) return [] } const files = readdirSync(sessionsDir).filter((f: string) => f.endsWith('.jsonl')) @@ -425,7 +515,7 @@ ipcMain.handle(IPC.LIST_SESSIONS, async (_e, projectPath?: string) => { sessions.sort((a, b) => new Date(b.lastTimestamp).getTime() - new Date(a.lastTimestamp).getTime()) return sessions.slice(0, 20) // Return top 20 } catch (err) { - log(`LIST_SESSIONS error: ${err}`) + log.info(`LIST_SESSIONS error: ${err}`) return [] } }) @@ -439,7 +529,7 @@ ipcMain.handle(IPC.LOAD_SESSION, async (_e, arg: { sessionId: string; projectPat // Validate sessionId — must be strict UUID to prevent path traversal via crafted filenames const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i if (!UUID_RE.test(sessionId)) { - log(`LOAD_SESSION: rejected invalid sessionId: ${sessionId}`) + log.info(`LOAD_SESSION: rejected invalid sessionId: ${sessionId}`) return [] } @@ -447,7 +537,7 @@ ipcMain.handle(IPC.LOAD_SESSION, async (_e, arg: { sessionId: string; projectPat const cwd = projectPath || process.cwd() // Validate projectPath — reject null bytes, newlines, non-absolute paths if (/[\0\r\n]/.test(cwd) || !cwd.startsWith('/')) { - log(`LOAD_SESSION: rejected invalid projectPath: ${cwd}`) + log.info(`LOAD_SESSION: rejected invalid projectPath: ${cwd}`) return [] } const encodedPath = cwd.replace(/\//g, '-') @@ -497,7 +587,7 @@ ipcMain.handle(IPC.LOAD_SESSION, async (_e, arg: { sessionId: string; projectPat }) return messages } catch (err) { - log(`LOAD_SESSION error: ${err}`) + log.info(`LOAD_SESSION error: ${err}`) return [] } }) @@ -508,7 +598,7 @@ ipcMain.handle(IPC.SELECT_DIRECTORY, async () => { // Unparented avoids modal dimming on the transparent overlay. // Activation is fine here — user is actively interacting with CLUI. if (process.platform === 'darwin') app.focus() - const options = { properties: ['openDirectory'] as const } + const options = { properties: ['openDirectory'] as any } const result = process.platform === 'darwin' ? await dialog.showOpenDialog(options) : await dialog.showOpenDialog(mainWindow, options) @@ -532,7 +622,7 @@ ipcMain.handle(IPC.ATTACH_FILES, async () => { if (!mainWindow) return null // macOS: activate app so unparented dialog appears on top if (process.platform === 'darwin') app.focus() - const options = { + const options: any = { properties: ['openFile', 'multiSelections'], filters: [ { name: 'All Files', extensions: ['*'] }, @@ -598,10 +688,25 @@ ipcMain.handle(IPC.TAKE_SCREENSHOT, async () => { const timestamp = Date.now() const screenshotPath = join(tmpdir(), `clui-screenshot-${timestamp}.png`) - execSync(`/usr/sbin/screencapture -i "${screenshotPath}"`, { - timeout: 30000, - stdio: 'ignore', - }) + if (process.platform === 'darwin') { + execSync(`/usr/sbin/screencapture -i "${screenshotPath}"`, { + timeout: 30000, + stdio: 'ignore', + }) + } else if (process.platform === 'win32') { + // Basic PowerShell screenshot (requires .NET) + const script = ` + Add-Type -AssemblyName System.Windows.Forms + $screen = [System.Windows.Forms.Screen]::PrimaryScreen + $bitmap = New-Object System.Drawing.Bitmap($screen.Bounds.Width, $screen.Bounds.Height) + $graphics = [System.Drawing.Graphics]::FromImage($bitmap) + $graphics.CopyFromScreen($screen.Bounds.X, $screen.Bounds.Y, 0, 0, $bitmap.Size) + $bitmap.Save("${screenshotPath.replace(/\\/g, '\\\\')}", [System.Drawing.Imaging.ImageFormat]::Png) + $graphics.Dispose() + $bitmap.Dispose() + ` + execSync(`powershell -Command "${script.replace(/\n/g, ' ')}"`, { timeout: 30000 }) + } if (!existsSync(screenshotPath)) { return null @@ -627,7 +732,7 @@ ipcMain.handle(IPC.TAKE_SCREENSHOT, async () => { } broadcast(IPC.WINDOW_SHOWN) if (SPACES_DEBUG) { - log('[spaces] screenshot restore show+focus') + log.info('[spaces] screenshot restore show+focus') snapshotWindowState('screenshot restore immediate') setTimeout(() => snapshotWindowState('screenshot restore +200ms'), 200) } @@ -704,6 +809,10 @@ ipcMain.handle(IPC.TRANSCRIBE_AUDIO, async (_event, audioBase64: string) => { '/opt/homebrew/bin/whisper', '/usr/local/bin/whisper', join(homedir(), '.local/bin/whisper'), + // Windows candidates + join(process.env.PROGRAMFILES || 'C:/Program Files', 'whisper-cpp/whisper-cli.exe'), + join(homedir(), 'AppData/Local/bin/whisper-cli.exe'), + join(homedir(), 'AppData/Local/bin/whisper.exe'), ] let whisperBin = '' @@ -714,19 +823,26 @@ ipcMain.handle(IPC.TRANSCRIBE_AUDIO, async (_event, audioBase64: string) => { if (!whisperBin) { t0 = Date.now() + const searchCmd = process.platform === 'win32' ? 'where' : 'whence -p' + const shell = process.platform === 'win32' ? 'cmd.exe' : '/bin/zsh' + const shellArgs = process.platform === 'win32' ? ['/c'] : ['-lc'] + for (const name of ['whisperkit-cli', 'whisper-cli', 'whisper']) { try { - whisperBin = await runExecFile('/bin/zsh', ['-lc', `whence -p ${name}`], 5000).then((s) => s.trim()) + const exeName = process.platform === 'win32' ? `${name}.exe` : name + whisperBin = await runExecFile(shell, [...shellArgs, `${searchCmd} ${exeName}`], 5000).then((s) => s.trim().split(/\r?\n/)[0]) if (whisperBin) break } catch {} } - mark('probe_binary_whence', t0) + mark('probe_binary_discovery', t0) } if (!whisperBin) { - const hint = process.arch === 'arm64' - ? 'brew install whisperkit-cli (or: brew install whisper-cpp)' - : 'brew install whisper-cpp' + const hint = process.platform === 'win32' + ? 'Download whisper-cli.exe from whisper.cpp releases' + : (process.arch === 'arm64' + ? 'brew install whisperkit-cli (or: brew install whisper-cpp)' + : 'brew install whisper-cpp') return { error: `Whisper not found. Install with:\n ${hint}`, transcript: null, @@ -736,7 +852,7 @@ ipcMain.handle(IPC.TRANSCRIBE_AUDIO, async (_event, audioBase64: string) => { const isWhisperKit = whisperBin.includes('whisperkit-cli') const isWhisperCpp = !isWhisperKit && whisperBin.includes('whisper-cli') - log(`Transcribing with: ${whisperBin} (backend: ${isWhisperKit ? 'WhisperKit' : isWhisperCpp ? 'whisper-cpp' : 'Python whisper'})`) + log.info(`Transcribing with: ${whisperBin} (backend: ${isWhisperKit ? 'WhisperKit' : isWhisperCpp ? 'whisper-cpp' : 'Python whisper'})`) let output: string if (isWhisperKit) { @@ -764,10 +880,10 @@ ipcMain.handle(IPC.TRANSCRIBE_AUDIO, async (_event, audioBase64: string) => { // Also clean up .srt that --report creates const srtPath = join(reportDir, `${wavBasename}.srt`) try { unlinkSync(srtPath) } catch {} - log(`Transcription timing(ms): ${JSON.stringify({ ...phaseMs, total: Date.now() - startedAt })}`) + log.info(`Transcription timing(ms): ${JSON.stringify({ ...phaseMs, total: Date.now() - startedAt })}`) return { error: null, transcript } } catch (parseErr: any) { - log(`WhisperKit JSON parse failed: ${parseErr.message}, falling back to stdout`) + log.info(`WhisperKit JSON parse failed: ${parseErr.message}, falling back to stdout`) try { unlinkSync(reportPath) } catch {} } } @@ -834,7 +950,7 @@ ipcMain.handle(IPC.TRANSCRIBE_AUDIO, async (_event, audioBase64: string) => { const transcript = readFileSync(txtPath, 'utf-8').trim() mark('python_whisper_read_txt', t0) try { unlinkSync(txtPath) } catch {} - log(`Transcription timing(ms): ${JSON.stringify({ ...phaseMs, total: Date.now() - startedAt })}`) + log.info(`Transcription timing(ms): ${JSON.stringify({ ...phaseMs, total: Date.now() - startedAt })}`) return { error: null, transcript } } // File not created — Python whisper failed silently @@ -852,15 +968,15 @@ ipcMain.handle(IPC.TRANSCRIBE_AUDIO, async (_event, audioBase64: string) => { .trim() if (HALLUCINATIONS.test(transcript)) { - log(`Transcription timing(ms): ${JSON.stringify({ ...phaseMs, total: Date.now() - startedAt })}`) + log.info(`Transcription timing(ms): ${JSON.stringify({ ...phaseMs, total: Date.now() - startedAt })}`) return { error: null, transcript: '' } } - log(`Transcription timing(ms): ${JSON.stringify({ ...phaseMs, total: Date.now() - startedAt })}`) + log.info(`Transcription timing(ms): ${JSON.stringify({ ...phaseMs, total: Date.now() - startedAt })}`) return { error: null, transcript: transcript || '' } } catch (err: any) { - log(`Transcription error: ${err.message}`) - log(`Transcription timing(ms): ${JSON.stringify({ ...phaseMs, total: Date.now() - startedAt, failed: true })}`) + log.info(`Transcription error: ${err.message}`) + log.info(`Transcription timing(ms): ${JSON.stringify({ ...phaseMs, total: Date.now() - startedAt, failed: true })}`) return { error: `Transcription failed: ${err.message}`, transcript: null, @@ -898,7 +1014,7 @@ ipcMain.handle(IPC.GET_DIAGNOSTICS, () => { ipcMain.handle(IPC.OPEN_IN_TERMINAL, (_event, arg: string | null | { sessionId?: string | null; projectPath?: string }) => { const { execFile } = require('child_process') - const claudeBin = 'claude' + const claudeBin = controlPlane.getClaudeBinaryPath() const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i @@ -914,13 +1030,16 @@ ipcMain.handle(IPC.OPEN_IN_TERMINAL, (_event, arg: string | null | { sessionId?: // Validate sessionId — must be a strict UUID to prevent injection into the shell command if (sessionId && !UUID_RE.test(sessionId)) { - log(`OPEN_IN_TERMINAL: rejected invalid sessionId: ${sessionId}`) + log.info(`OPEN_IN_TERMINAL: rejected invalid sessionId: ${sessionId}`) return false } // Sanitize projectPath — reject null bytes, newlines, and non-absolute paths - if (/[\0\r\n]/.test(projectPath) || !projectPath.startsWith('/')) { - log(`OPEN_IN_TERMINAL: rejected invalid projectPath: ${projectPath}`) + const isAbsolute = process.platform === 'win32' + ? /^[A-Za-z]:[\\/]/.test(projectPath) // e.g. C:\ or D:/ + : projectPath.startsWith('/') + if (/[\0\r\n]/.test(projectPath) || !isAbsolute) { + log.info(`OPEN_IN_TERMINAL: rejected invalid projectPath: ${projectPath}`) return false } @@ -946,13 +1065,21 @@ ipcMain.handle(IPC.OPEN_IN_TERMINAL, (_event, arg: string | null | { sessionId?: end tell` try { - execFile('/usr/bin/osascript', ['-e', script], (err: Error | null) => { - if (err) log(`Failed to open terminal: ${err.message}`) - else log(`Opened terminal with: ${cmd}`) - }) + if (process.platform === 'darwin') { + execFile('/usr/bin/osascript', ['-e', script], (err: Error | null) => { + if (err) log.error(`Failed to open terminal: ${err.message}`) + else log.info(`Opened terminal with: ${cmd}`) + }) + } else if (process.platform === 'win32') { + // Open cmd.exe in the project directory and run 'claude' + const { spawn } = require('child_process') + const terminalCmd = sessionId ? `claude --resume ${sessionId}` : 'claude' + spawn('cmd.exe', ['/c', `start cmd.exe /k "cd /d ${projectPath} && ${terminalCmd}"`], { shell: true }) + log.info(`Opened Windows Terminal (cmd.exe) in ${projectPath}`) + } return true } catch (err: unknown) { - log(`Failed to open terminal: ${err}`) + log.error(`Failed to open terminal: ${err}`) return false } }) @@ -1003,7 +1130,7 @@ async function requestPermissions(): Promise { await systemPreferences.askForMediaAccess('microphone') } } catch (err: any) { - log(`Permission preflight: microphone check failed — ${err.message}`) + log.info(`Permission preflight: microphone check failed — ${err.message}`) } // ── Accessibility (for global ⌥+Space shortcut) ── @@ -1028,7 +1155,7 @@ app.whenReady().then(async () => { // Skill provisioning — non-blocking, streams status to renderer ensureSkills((status: SkillStatus) => { - log(`Skill ${status.name}: ${status.state}${status.error ? ` — ${status.error}` : ''}`) + log.info(`Skill ${status.name}: ${status.state}${status.error ? ` — ${status.error}` : ''}`) broadcast(IPC.SKILL_STATUS, status) }).catch((err: Error) => log(`Skill provisioning error: ${err.message}`)) @@ -1047,15 +1174,15 @@ app.whenReady().then(async () => { app.on('browser-window-blur', () => snapshotWindowState('event app browser-window-blur')) screen.on('display-added', (_e, display) => { - log(`[spaces] event display-added id=${display.id}`) + log.info(`[spaces] event display-added id=${display.id}`) snapshotWindowState('event display-added') }) screen.on('display-removed', (_e, display) => { - log(`[spaces] event display-removed id=${display.id}`) + log.info(`[spaces] event display-removed id=${display.id}`) snapshotWindowState('event display-removed') }) screen.on('display-metrics-changed', (_e, display, changedMetrics) => { - log(`[spaces] event display-metrics-changed id=${display.id} changed=${changedMetrics.join(',')}`) + log.info(`[spaces] event display-metrics-changed id=${display.id} changed=${changedMetrics.join(',')}`) snapshotWindowState('event display-metrics-changed') }) } @@ -1063,9 +1190,11 @@ app.whenReady().then(async () => { // Primary: Option+Space (2 keys, doesn't conflict with shell) // Fallback: Cmd+Shift+K kept as secondary shortcut - const registered = globalShortcut.register('Alt+Space', () => toggleWindow('shortcut Alt+Space')) + // Global Summer Shortcut + const mainShortcut = process.platform === 'win32' ? 'Alt+Space' : 'Alt+Space' // Prompt said Alt+Space for Windows + const registered = globalShortcut.register(mainShortcut, () => toggleWindow('shortcut ' + mainShortcut)) if (!registered) { - log('Alt+Space shortcut registration failed — macOS input sources may claim it') + log.error(`${mainShortcut} shortcut registration failed`) } globalShortcut.register('CommandOrControl+Shift+K', () => toggleWindow('shortcut Cmd/Ctrl+Shift+K')) @@ -1091,7 +1220,9 @@ app.whenReady().then(async () => { app.on('will-quit', () => { globalShortcut.unregisterAll() - controlPlane.shutdown() + if (typeof (controlPlane as any).shutdown === 'function') { + (controlPlane as any).shutdown() + } flushLogs() }) diff --git a/src/main/process-manager.ts b/src/main/process-manager.ts index add7af0f..38dbf6df 100644 --- a/src/main/process-manager.ts +++ b/src/main/process-manager.ts @@ -1,18 +1,22 @@ import { spawn, execSync, ChildProcess } from 'child_process' import { EventEmitter } from 'events' import { homedir } from 'os' -import { appendFileSync } from 'fs' +import { appendFileSync, existsSync } from 'fs' import { join } from 'path' import { StreamParser } from './stream-parser' -import { getCliEnv } from './cli-env' +import { getCliEnv, findClaudeBinary } from './cli-env' import type { ClaudeEvent, RunOptions } from '../shared/types' -const LOG_FILE = join(homedir(), '.clui-debug.log') - -function log(msg: string): void { - const line = `[${new Date().toISOString()}] ${msg}\n` - try { appendFileSync(LOG_FILE, line) } catch {} -} +import electronLog from 'electron-log' +const log = Object.assign( + (...args: any[]) => electronLog.info(...args), + { + info: (...args: any[]) => electronLog.info(...args), + error: (...args: any[]) => electronLog.error(...args), + warn: (...args: any[]) => electronLog.warn(...args), + debug: (...args: any[]) => electronLog.debug(...args), + } +) export interface RunHandle { runId: string @@ -31,40 +35,11 @@ export class ProcessManager extends EventEmitter { constructor() { super() // Find the real claude binary — Electron doesn't inherit shell aliases or full PATH - this.claudeBinary = this.findClaudeBinary() - log(`Claude binary: ${this.claudeBinary}`) + this.claudeBinary = findClaudeBinary() + log.info(`Claude binary: ${this.claudeBinary}`) } - private findClaudeBinary(): string { - // Try common locations - const candidates = [ - '/usr/local/bin/claude', - '/opt/homebrew/bin/claude', - join(homedir(), '.npm-global/bin/claude'), - join(homedir(), '.nvm/versions/node', '**', 'bin/claude'), - ] - - for (const c of candidates) { - try { - execSync(`test -x "${c}"`, { stdio: 'ignore' }) - return c - } catch {} - } - - // Fallback: ask a login shell - try { - const result = execSync('/bin/zsh -ilc "whence -p claude"', { encoding: 'utf-8', env: getCliEnv() }).trim() - if (result) return result - } catch {} - try { - const result = execSync('/bin/bash -lc "which claude"', { encoding: 'utf-8', env: getCliEnv() }).trim() - if (result) return result - } catch {} - - // Last resort - return 'claude' - } startRun(options: RunOptions): RunHandle { const runId = crypto.randomUUID() @@ -99,17 +74,21 @@ export class ProcessManager extends EventEmitter { args.push('--system-prompt', options.systemPrompt) } - log(`Starting run ${runId}: ${this.claudeBinary} ${args.join(' ')}`) - log(`Prompt: ${options.prompt.substring(0, 200)}`) + log.info(`Starting run ${runId}: ${this.claudeBinary} ${args.join(' ')}`) + log.info(`Prompt: ${options.prompt.substring(0, 200)}`) // Build environment: merge login shell PATH with Electron's env // Electron doesn't source ~/.zshrc so PATH is often incomplete const env = getCliEnv() // Ensure our claude binary's directory is in PATH - const binDir = this.claudeBinary.substring(0, this.claudeBinary.lastIndexOf('/')) - if (env.PATH && !env.PATH.includes(binDir)) { - env.PATH = `${binDir}:${env.PATH}` + const lastSep = this.claudeBinary.lastIndexOf(process.platform === 'win32' ? '\\' : '/') + if (lastSep !== -1) { + const binDir = this.claudeBinary.substring(0, lastSep) + const pathSep = process.platform === 'win32' ? ';' : ':' + if (env.PATH && !env.PATH.includes(binDir)) { + env.PATH = `${binDir}${pathSep}${env.PATH}` + } } const child = spawn(this.claudeBinary, args, { @@ -118,7 +97,7 @@ export class ProcessManager extends EventEmitter { env, }) - log(`Spawned PID: ${child.pid}`) + log.info(`Spawned PID: ${child.pid}`) const parser = StreamParser.fromStream(child.stdout!) @@ -130,7 +109,7 @@ export class ProcessManager extends EventEmitter { } parser.on('event', (event: ClaudeEvent) => { - log(`Event [${runId}]: ${event.type}`) + log.info(`Event [${runId}]: ${event.type}`) if (event.type === 'system' && 'subtype' in event && event.subtype === 'init') { handle.sessionId = (event as any).session_id } @@ -138,25 +117,25 @@ export class ProcessManager extends EventEmitter { }) parser.on('parse-error', (line: string) => { - log(`Parse error [${runId}]: ${line.substring(0, 200)}`) + log.info(`Parse error [${runId}]: ${line.substring(0, 200)}`) this.emit('parse-error', runId, line) }) child.on('close', (code) => { - log(`Process closed [${runId}]: code=${code}`) + log.info(`Process closed [${runId}]: code=${code}`) this.activeRuns.delete(runId) this.emit('exit', runId, code, handle.sessionId) }) child.on('error', (err) => { - log(`Process error [${runId}]: ${err.message}`) + log.info(`Process error [${runId}]: ${err.message}`) this.activeRuns.delete(runId) this.emit('error', runId, err) }) child.stderr?.setEncoding('utf-8') child.stderr?.on('data', (data: string) => { - log(`Stderr [${runId}]: ${data.trim().substring(0, 500)}`) + log.info(`Stderr [${runId}]: ${data.trim().substring(0, 500)}`) this.emit('stderr', runId, data) }) @@ -171,7 +150,7 @@ export class ProcessManager extends EventEmitter { const handle = this.activeRuns.get(runId) if (!handle) return false - log(`Cancelling run ${runId}`) + log.info(`Cancelling run ${runId}`) handle.process.kill('SIGINT') setTimeout(() => { diff --git a/src/main/skills/installer.ts b/src/main/skills/installer.ts index 0f8c13c1..48f69e49 100644 --- a/src/main/skills/installer.ts +++ b/src/main/skills/installer.ts @@ -15,8 +15,13 @@ import { execSync } from 'child_process' import { randomUUID } from 'crypto' import { SKILLS, type SkillEntry } from './manifest' -/** Directory containing bundled skill sources (relative to main process __dirname) */ -const BUNDLED_SKILLS_DIR = join(__dirname, '../../skills') +/** Directory containing bundled skill sources */ +const BUNDLED_SKILLS_DIR = process.env.NODE_ENV === 'production' + ? join(process.resourcesPath, 'app.asar.unpacked', 'skills') // if unpacked + : join(__dirname, '../../skills') + +// Fallback for non-unpacked asar +const BUNDLED_SKILLS_DIR_ALT = join(__dirname, '../../skills') const SKILLS_DIR = join(homedir(), '.claude', 'skills') const VERSION_FILE = '.clui-version' @@ -37,11 +42,16 @@ interface VersionMeta { installedAt: string } -function log(msg: string): void { - const { appendFileSync } = require('fs') - const line = `[${new Date().toISOString()}] [skills] ${msg}\n` - try { appendFileSync(join(homedir(), '.clui-debug.log'), line) } catch {} -} +import electronLog from 'electron-log' +const log = Object.assign( + (...args: any[]) => electronLog.info(...args), + { + info: (...args: any[]) => electronLog.info(...args), + error: (...args: any[]) => electronLog.error(...args), + warn: (...args: any[]) => electronLog.warn(...args), + debug: (...args: any[]) => electronLog.debug(...args), + } +) function readVersionFile(skillDir: string): VersionMeta | null { const fp = join(skillDir, VERSION_FILE) @@ -94,12 +104,10 @@ async function installGithubSkill( const pathDepth = path.split('/').length + 1 // +1 for the github top-level dir const tarballUrl = `https://api.github.com/repos/${repo}/tarball/${commitSha}` - // Use curl + tar — both always available on macOS - const cmd = [ - `curl -sL "${tarballUrl}"`, - '|', - `tar -xz --strip-components=${pathDepth} -C "${tmpDir}" "*/${path}"`, - ].join(' ') + // Use curl + tar — both available on modern Windows and macOS + const cmd = process.platform === 'win32' + ? `curl -sL "${tarballUrl}" | tar -xz --strip-components=${pathDepth} -C "${tmpDir}" "*/${path}"` + : `curl -sL "${tarballUrl}" | tar -xz --strip-components=${pathDepth} -C "${tmpDir}" "*/${path}"` execSync(cmd, { timeout: 60000, stdio: 'pipe' }) @@ -126,11 +134,11 @@ async function installGithubSkill( renameSync(tmpDir, targetDir) writeVersionFile(targetDir, entry) - log(`Installed ${entry.name} v${entry.version}`) + log.info(`Installed ${entry.name} v${entry.version}`) onStatus({ name: entry.name, state: 'installed' }) } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err) - log(`Failed to install ${entry.name}: ${msg}`) + log.info(`Failed to install ${entry.name}: ${msg}`) // Clean up tmp dir on failure try { rmSync(tmpDir, { recursive: true, force: true }) } catch {} @@ -180,11 +188,11 @@ async function installBundledSkill( renameSync(tmpDir, targetDir) writeVersionFile(targetDir, entry) - log(`Installed bundled skill ${entry.name} v${entry.version}`) + log.info(`Installed bundled skill ${entry.name} v${entry.version}`) onStatus({ name: entry.name, state: 'installed' }) } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err) - log(`Failed to install bundled skill ${entry.name}: ${msg}`) + log.info(`Failed to install bundled skill ${entry.name}: ${msg}`) try { rmSync(tmpDir, { recursive: true, force: true }) } catch {} onStatus({ name: entry.name, state: 'failed', error: msg }) } @@ -202,7 +210,7 @@ async function installSkill( if (!meta) { // Dir exists but no .clui-version — user-managed, don't touch - log(`Skipping ${entry.name}: user-managed (no ${VERSION_FILE})`) + log.info(`Skipping ${entry.name}: user-managed (no ${VERSION_FILE})`) onStatus({ name: entry.name, state: 'skipped', reason: 'user-managed' }) return } @@ -211,15 +219,15 @@ async function installSkill( // Re-validate required files to detect corrupt/partial installs const validationErr = validateSkill(targetDir, entry.requiredFiles) if (!validationErr) { - log(`Skipping ${entry.name}: already at v${entry.version}`) + log.info(`Skipping ${entry.name}: already at v${entry.version}`) onStatus({ name: entry.name, state: 'skipped', reason: 'up-to-date' }) return } - log(`Repairing ${entry.name}: version matches but ${validationErr}`) + log.info(`Repairing ${entry.name}: version matches but ${validationErr}`) } // Version mismatch — needs update - log(`Updating ${entry.name}: v${meta.version} → v${entry.version}`) + log.info(`Updating ${entry.name}: v${meta.version} → v${entry.version}`) } // Ensure parent dir exists @@ -250,7 +258,7 @@ export async function ensureSkills( await installSkill(entry, onStatus) } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err) - log(`Unexpected error installing ${entry.name}: ${msg}`) + log.info(`Unexpected error installing ${entry.name}: ${msg}`) onStatus({ name: entry.name, state: 'failed', error: msg }) } } diff --git a/src/preload/index.ts b/src/preload/index.ts index 81344d61..898498d3 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -4,8 +4,17 @@ import type { RunOptions, NormalizedEvent, HealthReport, EnrichedError, Attachme export interface CluiAPI { // ─── Request-response (renderer → main) ─── - start(): Promise<{ version: string; auth: { email?: string; subscriptionType?: string; authMethod?: string }; mcpServers: string[]; projectPath: string; homePath: string }> - createTab(): Promise<{ tabId: string }> + start(): Promise<{ + version: string; + auth: { email?: string; subscriptionType?: string; authMethod?: string }; + mcpServers: string[]; + projectPath: string; + homePath: string; + isNodeInstalled: boolean; + isClaudeInstalled: boolean; + isAuthValid: boolean; + }> + createTab(tabId?: string): Promise<{ tabId: string }> prompt(tabId: string, requestId: string, options: RunOptions): Promise cancel(requestId: string): Promise stopTab(tabId: string): Promise @@ -22,7 +31,7 @@ export interface CluiAPI { transcribeAudio(audioBase64: string): Promise<{ error: string | null; transcript: string | null }> getDiagnostics(): Promise respondPermission(tabId: string, questionId: string, optionId: string): Promise - initSession(tabId: string): void + initSession(tabId: string, options?: RunOptions): void resetTabSession(tabId: string): void listSessions(projectPath?: string): Promise loadSession(sessionId: string, projectPath?: string): Promise @@ -54,7 +63,7 @@ export interface CluiAPI { const api: CluiAPI = { // ─── Request-response ─── start: () => ipcRenderer.invoke(IPC.START), - createTab: () => ipcRenderer.invoke(IPC.CREATE_TAB), + createTab: (tabId?: string) => ipcRenderer.invoke(IPC.CREATE_TAB, tabId), prompt: (tabId, requestId, options) => ipcRenderer.invoke(IPC.PROMPT, { tabId, requestId, options }), cancel: (requestId) => ipcRenderer.invoke(IPC.CANCEL, requestId), stopTab: (tabId) => ipcRenderer.invoke(IPC.STOP_TAB, tabId), @@ -72,7 +81,7 @@ const api: CluiAPI = { getDiagnostics: () => ipcRenderer.invoke(IPC.GET_DIAGNOSTICS), respondPermission: (tabId, questionId, optionId) => ipcRenderer.invoke(IPC.RESPOND_PERMISSION, { tabId, questionId, optionId }), - initSession: (tabId) => ipcRenderer.send(IPC.INIT_SESSION, tabId), + initSession: (tabId, options) => ipcRenderer.send(IPC.INIT_SESSION, tabId, options), resetTabSession: (tabId) => ipcRenderer.send(IPC.RESET_TAB_SESSION, tabId), listSessions: (projectPath?: string) => ipcRenderer.invoke(IPC.LIST_SESSIONS, projectPath), loadSession: (sessionId: string, projectPath?: string) => ipcRenderer.invoke(IPC.LOAD_SESSION, { sessionId, projectPath }), diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 6cf443b3..4121d94d 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -6,6 +6,7 @@ import { ConversationView } from './components/ConversationView' import { InputBar } from './components/InputBar' import { StatusBar } from './components/StatusBar' import { MarketplacePanel } from './components/MarketplacePanel' +import { SetupWizard } from './components/SetupWizard' import { PopoverLayerProvider } from './components/PopoverLayer' import { useClaudeEvents } from './hooks/useClaudeEvents' import { useHealthReconciliation } from './hooks/useHealthReconciliation' @@ -38,15 +39,19 @@ export default function App() { return unsub }, [setSystemTheme]) + const [isInitializing, setIsInitializing] = React.useState(true) + const staticInfo = useSessionStore((s) => s.staticInfo) + useEffect(() => { useSessionStore.getState().initStaticInfo().then(() => { const homeDir = useSessionStore.getState().staticInfo?.homePath || '~' const tab = useSessionStore.getState().tabs[0] if (tab) { - // Set working directory to home by default (user hasn't chosen yet) + // Set working directory to home by default useSessionStore.setState((s) => ({ tabs: s.tabs.map((t, i) => (i === 0 ? { ...t, workingDirectory: homeDir, hasChosenDirectory: false } : t)), })) + // Ensure we handle the tab ID properly window.clui.createTab().then(({ tabId }) => { useSessionStore.setState((s) => ({ tabs: s.tabs.map((t, i) => (i === 0 ? { ...t, id: tabId } : t)), @@ -54,7 +59,22 @@ export default function App() { })) }).catch(() => {}) } - }) + }).finally(() => setIsInitializing(false)) + }, []) + + // Local keyboard shortcut for toggling (Alt+Space fallback) + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + // Check for Alt+Space + const isAltSpace = e.altKey && (e.code === 'Space' || e.key === ' ') + if (isAltSpace) { + e.preventDefault() + window.clui.hideWindow() + } + } + // Use capture phase to ensure we catch it before other elements + window.addEventListener('keydown', onKeyDown, true) + return () => window.removeEventListener('keydown', onKeyDown, true) }, []) // OS-level click-through (RAF-throttled to avoid per-pixel IPC) @@ -93,14 +113,10 @@ export default function App() { const isExpanded = useSessionStore((s) => s.isExpanded) const marketplaceOpen = useSessionStore((s) => s.marketplaceOpen) + const authBypassed = useSessionStore((s) => s.authBypassed) const isRunning = activeTabStatus === 'running' || activeTabStatus === 'connecting' - // Layout dimensions — expandedUI widens and heightens the panel - const contentWidth = expandedUI ? 700 : spacing.contentWidth - const cardExpandedWidth = expandedUI ? 700 : 460 - const cardCollapsedWidth = expandedUI ? 670 : 430 - const cardCollapsedMargin = expandedUI ? 15 : 15 - const bodyMaxHeight = expandedUI ? 520 : 400 + const needsSetup = staticInfo && (!staticInfo.isNodeInstalled || !staticInfo.isClaudeInstalled || (!staticInfo.isAuthValid && !authBypassed)) const handleScreenshot = useCallback(async () => { const result = await window.clui.takeScreenshot() @@ -114,6 +130,25 @@ export default function App() { addAttachments(files) }, [addAttachments]) + if (isInitializing) { + return ( +
+ {/* Simple loading state covers the flash */} +
+ ) + } + + if (needsSetup) { + return + } + + // Layout dimensions — expandedUI widens and heightens the panel + const contentWidth = expandedUI ? 700 : spacing.contentWidth + const cardExpandedWidth = expandedUI ? 700 : 460 + const cardCollapsedWidth = expandedUI ? 670 : 430 + const cardCollapsedMargin = expandedUI ? 15 : 15 + const bodyMaxHeight = expandedUI ? 520 : 400 + return (
diff --git a/src/renderer/components/ConversationView.tsx b/src/renderer/components/ConversationView.tsx index ea8ecb26..d5c32418 100644 --- a/src/renderer/components/ConversationView.tsx +++ b/src/renderer/components/ConversationView.tsx @@ -313,7 +313,7 @@ function EmptyState() { Choose folder - Press ⌥ + Space to show/hide this overlay + Press {navigator.userAgent.includes('Win') ? 'Alt' : '⌥'} + Space to show/hide this overlay
) diff --git a/src/renderer/components/SetupWizard.tsx b/src/renderer/components/SetupWizard.tsx new file mode 100644 index 00000000..44faa860 --- /dev/null +++ b/src/renderer/components/SetupWizard.tsx @@ -0,0 +1,206 @@ +import React, { useState } from 'react' +import { motion, AnimatePresence } from 'framer-motion' +import { CheckCircle, XCircle, Warning, ArrowClockwise, Globe, Copy, Check } from '@phosphor-icons/react' +import { useSessionStore } from '../stores/sessionStore' +import { useColors } from '../theme' + +const TRANSITION = { duration: 0.26, ease: [0.4, 0, 0.1, 1] as const } + +export function SetupWizard() { + const staticInfo = useSessionStore((s) => s.staticInfo) + const initStaticInfo = useSessionStore((s) => s.initStaticInfo) + const setAuthBypassed = useSessionStore((s) => s.setAuthBypassed) + const colors = useColors() + + if (!staticInfo) return null + + const { isNodeInstalled, isClaudeInstalled, isAuthValid } = staticInfo + + const steps = [ + { + title: 'Node.js Runtime', + installed: isNodeInstalled, + description: 'Required to run Electron and Claude Code dependencies.', + link: 'https://nodejs.org/', + }, + { + title: 'Claude CLI', + installed: isClaudeInstalled, + description: 'The core engine that powers Clui CC. Install via npm.', + command: 'npm install -g @anthropic-ai/claude-code', + link: 'https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview', + }, + { + title: 'Authentication', + installed: isAuthValid, + description: 'You must be logged into Claude Code to use Clui CC.', + command: 'claude auth login', + }, + ] + + return ( +
+
+ + {/* Header */} +
+

Setup Required

+

+ Please complete these steps to get your environment ready. +

+
+ + {/* Steps Body */} +
+ {steps.map((step, i) => ( +
+
+ {step.installed ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+
+

{step.title}

+

{step.description}

+ {!step.installed && ( +
+ {step.command && ( + + )} + {step.link && ( + { e.preventDefault(); window.clui.openExternal(step.link!) }} + className="text-[10px] flex items-center gap-1 hover:underline w-fit transition-all hover:opacity-80" + style={{ color: colors.accent }} + > + + View Instructions + + )} + {step.title === 'Authentication' && ( + + )} +
+ )} +
+
+ ))} +
+ + {/* Footer / Action */} +
+ +

+ RESTART CLUI CC AFTER INSTALLING DEPENDENCIES +

+
+
+
+
+ ) +} + +function CommandBlock({ command, colors }: { command: string, colors: any }) { + const [copied, setCopied] = useState(false) + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(command) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } catch (err) { + console.error('Failed to copy text: ', err) + } + } + + return ( +
+ + {command} + + +
+ ) +} diff --git a/src/renderer/components/StatusBar.tsx b/src/renderer/components/StatusBar.tsx index 0ebe2f25..9d2b1c81 100644 --- a/src/renderer/components/StatusBar.tsx +++ b/src/renderer/components/StatusBar.tsx @@ -11,10 +11,7 @@ import { useColors } from '../theme' function ModelPicker() { const preferredModel = useSessionStore((s) => s.preferredModel) const setPreferredModel = useSessionStore((s) => s.setPreferredModel) - const tab = useSessionStore( - (s) => s.tabs.find((t) => t.id === s.activeTabId), - (a, b) => a === b || (!!a && !!b && a.status === b.status && a.sessionModel === b.sessionModel), - ) + const tab = useSessionStore((s) => s.tabs.find((t) => t.id === s.activeTabId)) const popoverLayer = usePopoverLayer() const colors = useColors() @@ -252,21 +249,13 @@ function PermissionModePicker() { /** Get a compact display path: basename for deep paths, ~ for home */ function compactPath(fullPath: string): string { if (fullPath === '~') return '~' - const parts = fullPath.replace(/\/$/, '').split('/') + const normalized = fullPath.replace(/[/\\]+$/, '') + const parts = normalized.split(/[/\\]/) return parts[parts.length - 1] || fullPath } export function StatusBar() { - const tab = useSessionStore( - (s) => s.tabs.find((t) => t.id === s.activeTabId), - (a, b) => a === b || (!!a && !!b - && a.status === b.status - && a.additionalDirs === b.additionalDirs - && a.hasChosenDirectory === b.hasChosenDirectory - && a.workingDirectory === b.workingDirectory - && a.claudeSessionId === b.claudeSessionId - ), - ) + const tab = useSessionStore((s) => s.tabs.find((t) => t.id === s.activeTabId)) const addDirectory = useSessionStore((s) => s.addDirectory) const removeDirectory = useSessionStore((s) => s.removeDirectory) const popoverLayer = usePopoverLayer() diff --git a/src/renderer/hooks/useHealthReconciliation.ts b/src/renderer/hooks/useHealthReconciliation.ts index ce539c56..282af809 100644 --- a/src/renderer/hooks/useHealthReconciliation.ts +++ b/src/renderer/hooks/useHealthReconciliation.ts @@ -27,8 +27,24 @@ export function useHealthReconciliation() { health.tabs.map((h) => [h.tabId, h]) ) + // If the backend has no tabs (likely a restart), re-register all current tabs from the store. + const { tabs: currentTabs, activeTabId, preferredModel } = useSessionStore.getState() + if (activeTabId && !stateByTab.has(activeTabId)) { + // Re-register every tab we have in the store so the backend knows about them. + for (const t of currentTabs) { + await window.clui.createTab(t.id) + if (t.workingDirectory) { + window.clui.initSession(t.id, { + prompt: 'init', + model: preferredModel || undefined, + projectPath: t.workingDirectory + }) + } + } + return // Let the next poll tick handle status reconciliation + } + // Build updated tabs, tracking whether anything actually changed - const { tabs: currentTabs } = useSessionStore.getState() let changed = false const newTabs = currentTabs.map((t) => { if (t.status !== 'running' && t.status !== 'connecting') return t diff --git a/src/renderer/index.css b/src/renderer/index.css index 5a80ef83..ba185ba2 100644 --- a/src/renderer/index.css +++ b/src/renderer/index.css @@ -16,6 +16,20 @@ html, body, #root { -webkit-font-smoothing: antialiased; } +/* Cover any residual system-drawn top border on Windows frameless windows */ +html::before { + content: ''; + display: block; + position: fixed; + top: -1px; + left: 0; + right: 0; + height: 2px; + background: transparent; + z-index: 999999; + pointer-events: none; +} + /* ─── Scrollbar — thin & subtle ─── */ ::-webkit-scrollbar { width: 4px; diff --git a/src/renderer/index.html b/src/renderer/index.html index 5bd3f28d..cc67f466 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -1,12 +1,15 @@ - - - - Clui CC - - -
- - - + + + + + Clui CC + + + +
+ + + + \ No newline at end of file diff --git a/src/renderer/stores/sessionStore.ts b/src/renderer/stores/sessionStore.ts index aafcc43a..867f424c 100644 --- a/src/renderer/stores/sessionStore.ts +++ b/src/renderer/stores/sessionStore.ts @@ -9,6 +9,12 @@ export const AVAILABLE_MODELS = [ { id: 'claude-opus-4-6', label: 'Opus 4.6' }, { id: 'claude-sonnet-4-6', label: 'Sonnet 4.6' }, { id: 'claude-haiku-4-5-20251001', label: 'Haiku 4.5' }, + { id: 'kimi-k2.5:cloud', label: 'Ollama: Kimi K2.5' }, + { id: 'glm-5:cloud', label: 'Ollama: GLM-5' }, + { id: 'minimax-m2.7:cloud', label: 'Ollama: Minimax M2.7' }, + { id: 'qwen3.5:cloud', label: 'Ollama: Qwen 3.5 Cloud' }, + { id: 'glm-4.7-flash', label: 'Ollama: GLM-4.7 Flash' }, + { id: 'qwen3.5', label: 'Ollama: Qwen 3.5' }, ] as const function normalizeModelId(modelId: string): string { @@ -47,6 +53,9 @@ interface StaticInfo { subscriptionType: string | null projectPath: string homePath: string + isNodeInstalled: boolean + isClaudeInstalled: boolean + isAuthValid: boolean } interface State { @@ -60,6 +69,8 @@ interface State { preferredModel: string | null /** Global permission mode: 'ask' shows cards, 'auto' auto-approves all tool calls */ permissionMode: 'ask' | 'auto' + /** Flag to allow users to bypass the Claude Code login requirement (e.g., for local LLMs) */ + authBypassed: boolean // Marketplace state marketplaceOpen: boolean @@ -75,6 +86,7 @@ interface State { initStaticInfo: () => Promise setPreferredModel: (model: string | null) => void setPermissionMode: (mode: 'ask' | 'auto') => void + setAuthBypassed: (bypassed: boolean) => void createTab: () => Promise selectTab: (tabId: string) => void closeTab: (tabId: string) => void @@ -116,9 +128,9 @@ async function playNotificationIfHidden(): Promise { const visible = await window.clui.isVisible() if (!visible) { notificationAudio.currentTime = 0 - notificationAudio.play().catch(() => {}) + notificationAudio.play().catch(() => { }) } - } catch {} + } catch { } } function makeLocalTab(): TabState { @@ -156,6 +168,7 @@ export const useSessionStore = create((set, get) => ({ staticInfo: null, preferredModel: null, permissionMode: 'ask', + authBypassed: false, // Marketplace marketplaceOpen: false, @@ -177,9 +190,12 @@ export const useSessionStore = create((set, get) => ({ subscriptionType: result.auth?.subscriptionType || null, projectPath: result.projectPath || '~', homePath: result.homePath || '~', + isNodeInstalled: result.isNodeInstalled, + isClaudeInstalled: result.isClaudeInstalled, + isAuthValid: result.isAuthValid, }, }) - } catch {} + } catch { } }, setPreferredModel: (model) => { @@ -191,6 +207,10 @@ export const useSessionStore = create((set, get) => ({ window.clui.setPermissionMode(mode) }, + setAuthBypassed: (bypassed) => { + set({ authBypassed: bypassed }) + }, + createTab: async () => { const homeDir = get().staticInfo?.homePath || '~' try { @@ -348,7 +368,7 @@ export const useSessionStore = create((set, get) => ({ }, closeTab: (tabId) => { - window.clui.closeTab(tabId).catch(() => {}) + window.clui.closeTab(tabId).catch(() => { }) const s = get() const remaining = s.tabs.filter((t) => t.id !== tabId) @@ -431,12 +451,12 @@ export const useSessionStore = create((set, get) => ({ tabs: s.tabs.map((t) => t.id === activeTabId ? { - ...t, - messages: [ - ...t.messages, - { id: nextMsgId(), role: 'system' as const, content, timestamp: Date.now() }, - ], - } + ...t, + messages: [ + ...t.messages, + { id: nextMsgId(), role: 'system' as const, content, timestamp: Date.now() }, + ], + } : t ), })) @@ -446,7 +466,7 @@ export const useSessionStore = create((set, get) => ({ respondPermission: (tabId, questionId, optionId) => { // Send to backend - window.clui.respondPermission(tabId, questionId, optionId).catch(() => {}) + window.clui.respondPermission(tabId, questionId, optionId).catch(() => { }) // Remove answered item from queue; show next tool's activity or clear set((s) => ({ @@ -472,11 +492,11 @@ export const useSessionStore = create((set, get) => ({ tabs: s.tabs.map((t) => t.id === activeTabId ? { - ...t, - additionalDirs: t.additionalDirs.includes(dir) - ? t.additionalDirs - : [...t.additionalDirs, dir], - } + ...t, + additionalDirs: t.additionalDirs.includes(dir) + ? t.additionalDirs + : [...t.additionalDirs, dir], + } : t ), })) @@ -500,12 +520,12 @@ export const useSessionStore = create((set, get) => ({ tabs: s.tabs.map((t) => t.id === activeTabId ? { - ...t, - workingDirectory: dir, - hasChosenDirectory: true, - claudeSessionId: null, - additionalDirs: [], - } + ...t, + workingDirectory: dir, + hasChosenDirectory: true, + claudeSessionId: null, + additionalDirs: [], + } : t ), })) @@ -580,12 +600,12 @@ export const useSessionStore = create((set, get) => ({ const withEffectiveBase = t.hasChosenDirectory ? t : { - ...t, - // Once the user sends the first message, lock in the effective - // base directory (home by default) so the footer no longer shows "—". - hasChosenDirectory: true, - workingDirectory: resolvedPath, - } + ...t, + // Once the user sends the first message, lock in the effective + // base directory (home by default) so the footer no longer shows "—". + hasChosenDirectory: true, + workingDirectory: resolvedPath, + } if (isBusy) { return { ...withEffectiveBase, @@ -891,11 +911,11 @@ export const useSessionStore = create((set, get) => ({ tabs: s.tabs.map((t) => t.id === tabId ? { - ...t, - status: newStatus as TabStatus, - // Clear activity when transitioning to idle (e.g., after warmup init) - ...(newStatus === 'idle' ? { currentActivity: '', permissionQueue: [] as import('../../shared/types').PermissionRequest[], permissionDenied: null } : {}), - } + ...t, + status: newStatus as TabStatus, + // Clear activity when transitioning to idle (e.g., after warmup init) + ...(newStatus === 'idle' ? { currentActivity: '', permissionQueue: [] as import('../../shared/types').PermissionRequest[], permissionDenied: null } : {}), + } : t ), })) @@ -919,14 +939,14 @@ export const useSessionStore = create((set, get) => ({ messages: alreadyHasError ? t.messages : [ - ...t.messages, - { - id: nextMsgId(), - role: 'system' as const, - content: `Error: ${error.message}${error.stderrTail.length > 0 ? '\n\n' + error.stderrTail.slice(-5).join('\n') : ''}`, - timestamp: Date.now(), - }, - ], + ...t.messages, + { + id: nextMsgId(), + role: 'system' as const, + content: `Error: ${error.message}${error.stderrTail.length > 0 ? '\n\n' + error.stderrTail.slice(-5).join('\n') : ''}`, + timestamp: Date.now(), + }, + ], } }), }))